[PATCH 12/13] D: The Phobos runtime library and license.

This patch add the Phobos runtime library and license (Boost) files.  Phobos is the standard runtime library that comes with the D language compiler.  The bulk of which is comprised mostly of generic algorithms and high level primitives for D applications.

---

diff --git a/libphobos/src/LICENSE_1_0.txt b/libphobos/src/LICENSE_1_0.txt
new file mode 100644
index 00000000000..36b7cd93cdf
--- /dev/null
+++ b/libphobos/src/LICENSE_1_0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/libphobos/src/etc/c/curl.d b/libphobos/src/etc/c/curl.d
new file mode 100644
index 00000000000..3f87c73d3e1
--- /dev/null
+++ b/libphobos/src/etc/c/curl.d
@@ -0,0 +1,2320 @@
+/**
+   This is an interface to the libcurl library.
+
+   Converted to D from curl headers by $(LINK2 http://www.digitalmars.com/d/2.0/htod.html, htod) and
+   cleaned up by Jonas Drewsen (jdrewsen)
+
+   Windows x86 note:
+   A DMD compatible libcurl static library can be downloaded from the dlang.org
+   $(LINK2 http://dlang.org/download.html, download page).
+*/
+
+/* **************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ */
+
+/**
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, &lt;daniel@haxx.se&gt;, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at $(LINK http://curl.haxx.se/docs/copyright.html).
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+module etc.c.curl;
+
+import core.stdc.time;
+import core.stdc.config;
+import std.socket;
+
+// linux
+import core.sys.posix.sys.socket;
+
+//
+// LICENSE FROM CURL HEADERS
+//
+
+/** This is the global package copyright */
+enum LIBCURL_COPYRIGHT = "1996 - 2010 Daniel Stenberg, <daniel@haxx.se>.";
+
+/** This is the version number of the libcurl package from which this header
+   file origins: */
+enum LIBCURL_VERSION = "7.21.4";
+
+/** The numeric version number is also available "in parts" by using these
+   constants */
+enum LIBCURL_VERSION_MAJOR = 7;
+/// ditto
+enum LIBCURL_VERSION_MINOR = 21;
+/// ditto
+enum LIBCURL_VERSION_PATCH = 4;
+
+/** This is the numeric version of the libcurl version number, meant for easier
+   parsing and comparions by programs. The LIBCURL_VERSION_NUM define will
+   always follow this syntax:
+
+         0xXXYYZZ
+
+   Where XX, YY and ZZ are the main version, release and patch numbers in
+   hexadecimal (using 8 bits each). All three numbers are always represented
+   using two digits.  1.2 would appear as "0x010200" while version 9.11.7
+   appears as "0x090b07".
+
+   This 6-digit (24 bits) hexadecimal number does not show pre-release number,
+   and it is always a greater number in a more recent release. It makes
+   comparisons with greater than and less than work.
+*/
+
+enum LIBCURL_VERSION_NUM = 0x071504;
+
+/**
+ * This is the date and time when the full source package was created. The
+ * timestamp is not stored in git, as the timestamp is properly set in the
+ * tarballs by the maketgz script.
+ *
+ * The format of the date should follow this template:
+ *
+ * "Mon Feb 12 11:35:33 UTC 2007"
+ */
+enum LIBCURL_TIMESTAMP = "Thu Feb 17 12:19:40 UTC 2011";
+
+/** Data type definition of curl_off_t.
+ *
+ * jdrewsen - Always 64bit signed and that is what long is in D.
+ *
+ * Comment below is from curlbuild.h:
+ *
+ * NOTE 2:
+ *
+ * For any given platform/compiler curl_off_t must be typedef'ed to a
+ * 64-bit wide signed integral data type. The width of this data type
+ * must remain constant and independent of any possible large file
+ * support settings.
+ *
+ * As an exception to the above, curl_off_t shall be typedef'ed to a
+ * 32-bit wide signed integral data type if there is no 64-bit type.
+ */
+alias long curl_off_t;
+
+///
+alias void CURL;
+
+/// jdrewsen - Get socket alias from std.socket
+alias socket_t curl_socket_t;
+
+/// jdrewsen - Would like to get socket error constant from std.socket by it is private atm.
+version(Windows) {
+  private import core.sys.windows.windows, core.sys.windows.winsock2;
+  enum CURL_SOCKET_BAD = SOCKET_ERROR;
+}
+version(Posix) enum CURL_SOCKET_BAD = -1;
+
+///
+extern (C) struct curl_httppost
+{
+    curl_httppost *next;        /** next entry in the list */
+    char *name;                 /** pointer to allocated name */
+    c_long namelength;          /** length of name length */
+    char *contents;             /** pointer to allocated data contents */
+    c_long contentslength;      /** length of contents field */
+    char *buffer;               /** pointer to allocated buffer contents */
+    c_long bufferlength;        /** length of buffer field */
+    char *contenttype;          /** Content-Type */
+    curl_slist *contentheader;  /** list of extra headers for this form */
+    curl_httppost *more;        /** if one field name has more than one
+                                    file, this link should link to following
+                                    files */
+    c_long flags;               /** as defined below */
+    char *showfilename;         /** The file name to show. If not set, the
+                                    actual file name will be used (if this
+                                    is a file part) */
+    void *userp;                /** custom pointer used for
+                                    HTTPPOST_CALLBACK posts */
+}
+
+enum HTTPPOST_FILENAME    = 1;  /** specified content is a file name */
+enum HTTPPOST_READFILE    = 2;  /** specified content is a file name */
+enum HTTPPOST_PTRNAME     = 4;  /** name is only stored pointer
+                                    do not free in formfree */
+enum HTTPPOST_PTRCONTENTS = 8;  /** contents is only stored pointer
+                                    do not free in formfree */
+enum HTTPPOST_BUFFER      = 16; /** upload file from buffer */
+enum HTTPPOST_PTRBUFFER   = 32; /** upload file from pointer contents */
+enum HTTPPOST_CALLBACK    = 64; /** upload file contents by using the
+                                    regular read callback to get the data
+                                    and pass the given pointer as custom
+                                    pointer */
+
+///
+alias int function(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) curl_progress_callback;
+
+/** Tests have proven that 20K is a very bad buffer size for uploads on
+   Windows, while 16K for some odd reason performed a lot better.
+   We do the ifndef check to allow this value to easier be changed at build
+   time for those who feel adventurous. The practical minimum is about
+   400 bytes since libcurl uses a buffer of this size as a scratch area
+   (unrelated to network send operations). */
+enum CURL_MAX_WRITE_SIZE = 16384;
+
+/** The only reason to have a max limit for this is to avoid the risk of a bad
+   server feeding libcurl with a never-ending header that will cause reallocs
+   infinitely */
+enum CURL_MAX_HTTP_HEADER = (100*1024);
+
+
+/** This is a magic return code for the write callback that, when returned,
+   will signal libcurl to pause receiving on the current transfer. */
+enum CURL_WRITEFUNC_PAUSE = 0x10000001;
+
+///
+alias size_t  function(char *buffer, size_t size, size_t nitems, void *outstream)curl_write_callback;
+
+/** enumeration of file types */
+enum CurlFileType {
+    file,       ///
+    directory,  ///
+    symlink,    ///
+    device_block, ///
+    device_char, ///
+    namedpipe,  ///
+    socket,     ///
+    door,       ///
+    unknown /** is possible only on Sun Solaris now */
+}
+
+///
+alias int curlfiletype;
+
+///
+enum CurlFInfoFlagKnown {
+  filename    = 1,      ///
+  filetype    = 2,      ///
+  time        = 4,      ///
+  perm        = 8,      ///
+  uid         = 16,     ///
+  gid         = 32,     ///
+  size        = 64,     ///
+  hlinkcount  = 128     ///
+}
+
+/** Content of this structure depends on information which is known and is
+   achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man
+   page for callbacks returning this structure -- some fields are mandatory,
+   some others are optional. The FLAG field has special meaning. */
+
+
+/** If some of these fields is not NULL, it is a pointer to b_data. */
+extern (C) struct _N2
+{
+    char *time;   ///
+    char *perm;   ///
+    char *user;   ///
+    char *group;  ///
+    char *target; /** pointer to the target filename of a symlink */
+}
+
+/** Content of this structure depends on information which is known and is
+   achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man
+   page for callbacks returning this structure -- some fields are mandatory,
+   some others are optional. The FLAG field has special meaning. */
+extern (C) struct curl_fileinfo
+{
+    char *filename;             ///
+    curlfiletype filetype;      ///
+    time_t time;                ///
+    uint perm;                  ///
+    int uid;                    ///
+    int gid;                    ///
+    curl_off_t size;            ///
+    c_long hardlinks;           ///
+    _N2 strings;                ///
+    uint flags;                 ///
+    char *b_data;               ///
+    size_t b_size;              ///
+    size_t b_used;              ///
+}
+
+/** return codes for CURLOPT_CHUNK_BGN_FUNCTION */
+enum CurlChunkBgnFunc {
+  ok = 0,   ///
+  fail = 1, /** tell the lib to end the task */
+  skip = 2 /** skip this chunk over */
+}
+
+/** if splitting of data transfer is enabled, this callback is called before
+   download of an individual chunk started. Note that parameter "remains" works
+   only for FTP wildcard downloading (for now), otherwise is not used */
+alias c_long function(void *transfer_info, void *ptr, int remains)curl_chunk_bgn_callback;
+
+/** return codes for CURLOPT_CHUNK_END_FUNCTION */
+enum CurlChunkEndFunc {
+  ok = 0,       ///
+  fail = 1,     ///
+}
+/** If splitting of data transfer is enabled this callback is called after
+   download of an individual chunk finished.
+   Note! After this callback was set then it have to be called FOR ALL chunks.
+   Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC.
+   This is the reason why we don't need "transfer_info" parameter in this
+   callback and we are not interested in "remains" parameter too. */
+alias c_long function(void *ptr)curl_chunk_end_callback;
+
+/** return codes for FNMATCHFUNCTION */
+enum CurlFnMAtchFunc {
+  match = 0,    ///
+  nomatch = 1,  ///
+  fail = 2      ///
+}
+
+/** callback type for wildcard downloading pattern matching. If the
+   string matches the pattern, return CURL_FNMATCHFUNC_MATCH value, etc. */
+alias int  function(void *ptr, in char *pattern, in char *string)curl_fnmatch_callback;
+
+/// seek whence...
+enum CurlSeekPos {
+  set,          ///
+  current,      ///
+  end           ///
+}
+
+/** These are the return codes for the seek callbacks */
+enum CurlSeek {
+  ok,       ///
+  fail,     /** fail the entire transfer */
+  cantseek  /** tell libcurl seeking can't be done, so
+               libcurl might try other means instead */
+}
+
+///
+alias int  function(void *instream, curl_off_t offset, int origin)curl_seek_callback;
+
+///
+enum CurlReadFunc {
+  /** This is a return code for the read callback that, when returned, will
+     signal libcurl to immediately abort the current transfer. */
+  abort = 0x10000000,
+
+  /** This is a return code for the read callback that, when returned,
+     will const signal libcurl to pause sending data on the current
+     transfer. */
+  pause = 0x10000001
+}
+
+///
+alias size_t  function(char *buffer, size_t size, size_t nitems, void *instream)curl_read_callback;
+
+///
+enum CurlSockType {
+    ipcxn, /** socket created for a specific IP connection */
+    last   /** never use */
+}
+///
+alias int curlsocktype;
+
+///
+alias int  function(void *clientp, curl_socket_t curlfd, curlsocktype purpose)curl_sockopt_callback;
+
+/** addrlen was a socklen_t type before 7.18.0 but it turned really
+   ugly and painful on the systems that lack this type */
+extern (C) struct curl_sockaddr
+{
+    int family;   ///
+    int socktype; ///
+    int protocol; ///
+    uint addrlen; /** addrlen was a socklen_t type before 7.18.0 but it
+                     turned really ugly and painful on the systems that
+                     lack this type */
+    sockaddr addr; ///
+}
+
+///
+alias curl_socket_t  function(void *clientp, curlsocktype purpose, curl_sockaddr *address)curl_opensocket_callback;
+
+///
+enum CurlIoError
+{
+    ok,            /** I/O operation successful */
+    unknowncmd,    /** command was unknown to callback */
+    failrestart,   /** failed to restart the read */
+    last           /** never use */
+}
+///
+alias int curlioerr;
+
+///
+enum CurlIoCmd {
+    nop,         /** command was unknown to callback */
+    restartread, /** failed to restart the read */
+    last,        /** never use */
+}
+///
+alias int curliocmd;
+
+///
+alias curlioerr  function(CURL *handle, int cmd, void *clientp)curl_ioctl_callback;
+
+/**
+ * The following typedef's are signatures of malloc, free, realloc, strdup and
+ * calloc respectively.  Function pointers of these types can be passed to the
+ * curl_global_init_mem() function to set user defined memory management
+ * callback routines.
+ */
+alias void * function(size_t size)curl_malloc_callback;
+/// ditto
+alias void  function(void *ptr)curl_free_callback;
+/// ditto
+alias void * function(void *ptr, size_t size)curl_realloc_callback;
+/// ditto
+alias char * function(in char *str)curl_strdup_callback;
+/// ditto
+alias void * function(size_t nmemb, size_t size)curl_calloc_callback;
+
+/** the kind of data that is passed to information_callback*/
+enum CurlCallbackInfo {
+    text,       ///
+    header_in,  ///
+    header_out, ///
+    data_in,    ///
+    data_out,   ///
+    ssl_data_in, ///
+    ssl_data_out, ///
+    end         ///
+}
+///
+alias int curl_infotype;
+
+///
+alias int  function(CURL *handle,        /** the handle/transfer this concerns */
+                    curl_infotype type,  /** what kind of data */
+                    char *data,          /** points to the data */
+                    size_t size,         /** size of the data pointed to */
+                    void *userptr        /** whatever the user please */
+                    )curl_debug_callback;
+
+/** All possible error codes from all sorts of curl functions. Future versions
+   may return other values, stay prepared.
+
+   Always add new return codes last. Never *EVER* remove any. The return
+   codes must remain the same!
+ */
+enum CurlError
+{
+    ok,                          ///
+    unsupported_protocol,        /** 1 */
+    failed_init,                 /** 2 */
+    url_malformat,               /** 3 */
+    not_built_in,                /** 4 - [was obsoleted in August 2007 for
+                                    7.17.0, reused in April 2011 for 7.21.5] */
+    couldnt_resolve_proxy,       /** 5 */
+    couldnt_resolve_host,        /** 6 */
+    couldnt_connect,             /** 7 */
+    ftp_weird_server_reply,      /** 8 */
+    remote_access_denied,        /** 9 a service was denied by the server
+                                    due to lack of access - when login fails
+                                    this is not returned. */
+    obsolete10,                  /** 10 - NOT USED */
+    ftp_weird_pass_reply,        /** 11 */
+    obsolete12,                  /** 12 - NOT USED */
+    ftp_weird_pasv_reply,        /** 13 */
+    ftp_weird_227_format,        /** 14 */
+    ftp_cant_get_host,           /** 15 */
+    obsolete16,                  /** 16 - NOT USED */
+    ftp_couldnt_set_type,        /** 17 */
+    partial_file,                /** 18 */
+    ftp_couldnt_retr_file,       /** 19 */
+    obsolete20,                  /** 20 - NOT USED */
+    quote_error,                 /** 21 - quote command failure */
+    http_returned_error,         /** 22 */
+    write_error,                 /** 23 */
+    obsolete24,                  /** 24 - NOT USED */
+    upload_failed,               /** 25 - failed upload "command" */
+    read_error,                  /** 26 - couldn't open/read from file */
+    out_of_memory,               /** 27 */
+    /** Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error
+             instead of a memory allocation error if CURL_DOES_CONVERSIONS
+             is defined
+    */
+    operation_timedout,          /** 28 - the timeout time was reached */
+    obsolete29,                  /** 29 - NOT USED */
+    ftp_port_failed,             /** 30 - FTP PORT operation failed */
+    ftp_couldnt_use_rest,        /** 31 - the REST command failed */
+    obsolete32,                  /** 32 - NOT USED */
+    range_error,                 /** 33 - RANGE "command" didn't work */
+    http_post_error,             /** 34 */
+    ssl_connect_error,           /** 35 - wrong when connecting with SSL */
+    bad_download_resume,         /** 36 - couldn't resume download */
+    file_couldnt_read_file,      /** 37 */
+    ldap_cannot_bind,            /** 38 */
+    ldap_search_failed,          /** 39 */
+    obsolete40,                  /** 40 - NOT USED */
+    function_not_found,          /** 41 */
+    aborted_by_callback,         /** 42 */
+    bad_function_argument,       /** 43 */
+    obsolete44,                  /** 44 - NOT USED */
+    interface_failed,            /** 45 - CURLOPT_INTERFACE failed */
+    obsolete46,                  /** 46 - NOT USED */
+    too_many_redirects,          /** 47 - catch endless re-direct loops */
+    unknown_option,              /** 48 - User specified an unknown option */
+    telnet_option_syntax,        /** 49 - Malformed telnet option */
+    obsolete50,                  /** 50 - NOT USED */
+    peer_failed_verification,    /** 51 - peer's certificate or fingerprint
+                                         wasn't verified fine */
+    got_nothing,                 /** 52 - when this is a specific error */
+    ssl_engine_notfound,         /** 53 - SSL crypto engine not found */
+    ssl_engine_setfailed,        /** 54 - can not set SSL crypto engine as default */
+    send_error,                  /** 55 - failed sending network data */
+    recv_error,                  /** 56 - failure in receiving network data */
+    obsolete57,                  /** 57 - NOT IN USE */
+    ssl_certproblem,             /** 58 - problem with the local certificate */
+    ssl_cipher,                  /** 59 - couldn't use specified cipher */
+    ssl_cacert,                  /** 60 - problem with the CA cert (path?) */
+    bad_content_encoding,        /** 61 - Unrecognized transfer encoding */
+    ldap_invalid_url,            /** 62 - Invalid LDAP URL */
+    filesize_exceeded,           /** 63 - Maximum file size exceeded */
+    use_ssl_failed,              /** 64 - Requested FTP SSL level failed */
+    send_fail_rewind,            /** 65 - Sending the data requires a rewind that failed */
+    ssl_engine_initfailed,       /** 66 - failed to initialise ENGINE */
+    login_denied,                /** 67 - user, password or similar was not accepted and we failed to login */
+    tftp_notfound,               /** 68 - file not found on server */
+    tftp_perm,                   /** 69 - permission problem on server */
+    remote_disk_full,            /** 70 - out of disk space on server */
+    tftp_illegal,                /** 71 - Illegal TFTP operation */
+    tftp_unknownid,              /** 72 - Unknown transfer ID */
+    remote_file_exists,          /** 73 - File already exists */
+    tftp_nosuchuser,             /** 74 - No such user */
+    conv_failed,                 /** 75 - conversion failed */
+    conv_reqd,                   /** 76 - caller must register conversion
+                                    callbacks using curl_easy_setopt options
+                                    CURLOPT_CONV_FROM_NETWORK_FUNCTION,
+                                    CURLOPT_CONV_TO_NETWORK_FUNCTION, and
+                                    CURLOPT_CONV_FROM_UTF8_FUNCTION */
+    ssl_cacert_badfile,          /** 77 - could not load CACERT file, missing  or wrong format */
+    remote_file_not_found,       /** 78 - remote file not found */
+    ssh,                         /** 79 - error from the SSH layer, somewhat
+                                    generic so the error message will be of
+                                    interest when this has happened */
+    ssl_shutdown_failed,         /** 80 - Failed to shut down the SSL connection */
+    again,                       /** 81 - socket is not ready for send/recv,
+                                    wait till it's ready and try again (Added
+                                    in 7.18.2) */
+    ssl_crl_badfile,             /** 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */
+    ssl_issuer_error,            /** 83 - Issuer check failed.  (Added in 7.19.0) */
+    ftp_pret_failed,             /** 84 - a PRET command failed */
+    rtsp_cseq_error,             /** 85 - mismatch of RTSP CSeq numbers */
+    rtsp_session_error,          /** 86 - mismatch of RTSP Session Identifiers */
+    ftp_bad_file_list,           /** 87 - unable to parse FTP file list */
+    chunk_failed,                /** 88 - chunk callback reported error */
+    curl_last                    /** never use! */
+}
+///
+alias int CURLcode;
+
+/** This prototype applies to all conversion callbacks */
+alias CURLcode  function(char *buffer, size_t length)curl_conv_callback;
+
+/** actually an OpenSSL SSL_CTX */
+alias CURLcode  function(CURL *curl,    /** easy handle */
+                         void *ssl_ctx, /** actually an
+                                           OpenSSL SSL_CTX */
+                         void *userptr
+                         )curl_ssl_ctx_callback;
+
+///
+enum CurlProxy {
+    http,         /** added in 7.10, new in 7.19.4 default is to use CONNECT HTTP/1.1 */
+    http_1_0,     /** added in 7.19.4, force to use CONNECT HTTP/1.0  */
+    socks4 = 4,   /** support added in 7.15.2, enum existed already in 7.10 */
+    socks5 = 5,   /** added in 7.10 */
+    socks4a = 6,  /** added in 7.18.0 */
+    socks5_hostname =7   /** Use the SOCKS5 protocol but pass along the
+                         host name rather than the IP address. added
+                         in 7.18.0 */
+}
+///
+alias int curl_proxytype;
+
+///
+enum CurlAuth : long {
+  none =         0,
+  basic =        1,  /** Basic (default) */
+  digest =       2,  /** Digest */
+  gssnegotiate = 4,  /** GSS-Negotiate */
+  ntlm =         8,  /** NTLM */
+  digest_ie =    16, /** Digest with IE flavour */
+  only =         2147483648, /** used together with a single other
+                                type to force no auth or just that
+                                single type */
+  any = -17,     /* (~CURLAUTH_DIGEST_IE) */  /** all fine types set */
+  anysafe = -18  /* (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE)) */ ///
+}
+
+///
+enum CurlSshAuth {
+  any       = -1,     /** all types supported by the server */
+  none      = 0,      /** none allowed, silly but complete */
+  publickey = 1, /** public/private key files */
+  password  = 2, /** password */
+  host      = 4, /** host key files */
+  keyboard  = 8, /** keyboard interactive */
+  default_  = -1 // CURLSSH_AUTH_ANY;
+}
+///
+enum CURL_ERROR_SIZE = 256;
+/** points to a zero-terminated string encoded with base64
+   if len is zero, otherwise to the "raw" data */
+enum CurlKHType
+{
+    unknown,    ///
+    rsa1,       ///
+    rsa,        ///
+    dss         ///
+}
+///
+extern (C) struct curl_khkey
+{
+    const(char) *key; /** points to a zero-terminated string encoded with base64
+                         if len is zero, otherwise to the "raw" data */
+    size_t len; ///
+    CurlKHType keytype; ///
+}
+
+/** this is the set of return values expected from the curl_sshkeycallback
+   callback */
+enum CurlKHStat {
+    fine_add_to_file, ///
+    fine,       ///
+    reject,  /** reject the connection, return an error */
+    defer,   /** do not accept it, but we can't answer right now so
+                this causes a CURLE_DEFER error but otherwise the
+                connection will be left intact etc */
+    last     /** not for use, only a marker for last-in-list */
+}
+
+/** this is the set of status codes pass in to the callback */
+enum CurlKHMatch {
+    ok,       /** match */
+    mismatch, /** host found, key mismatch! */
+    missing,  /** no matching host/key found */
+    last      /** not for use, only a marker for last-in-list */
+}
+
+///
+alias int  function(CURL *easy,            /** easy handle */
+                    curl_khkey *knownkey,  /** known */
+                    curl_khkey *foundkey,  /** found */
+                    CurlKHMatch m,         /** libcurl's view on the keys */
+                    void *clientp          /** custom pointer passed from app */
+                    )curl_sshkeycallback;
+
+/** parameter for the CURLOPT_USE_SSL option */
+enum CurlUseSSL {
+    none,     /** do not attempt to use SSL */
+    tryssl,   /** try using SSL, proceed anyway otherwise */
+    control,  /** SSL for the control connection or fail */
+    all,      /** SSL for all communication or fail */
+    last      /** not an option, never use */
+}
+///
+alias int curl_usessl;
+
+/** parameter for the CURLOPT_FTP_SSL_CCC option */
+enum CurlFtpSSL {
+    ccc_none,     /** do not send CCC */
+    ccc_passive,  /** Let the server initiate the shutdown */
+    ccc_active,   /** Initiate the shutdown */
+    ccc_last      /** not an option, never use */
+}
+///
+alias int curl_ftpccc;
+
+/** parameter for the CURLOPT_FTPSSLAUTH option */
+enum CurlFtpAuth {
+    defaultauth, /** let libcurl decide */
+    ssl,         /** use "AUTH SSL" */
+    tls,         /** use "AUTH TLS" */
+    last         /** not an option, never use */
+}
+///
+alias int curl_ftpauth;
+
+/** parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */
+enum CurlFtp {
+    create_dir_none,   /** do NOT create missing dirs! */
+    create_dir,        /** (FTP/SFTP) if CWD fails, try MKD and then CWD again if MKD
+                          succeeded, for SFTP this does similar magic */
+    create_dir_retry,  /** (FTP only) if CWD fails, try MKD and then CWD again even if MKD
+                          failed! */
+    create_dir_last    /** not an option, never use */
+}
+///
+alias int curl_ftpcreatedir;
+
+/** parameter for the CURLOPT_FTP_FILEMETHOD option */
+enum CurlFtpMethod {
+    defaultmethod,    /** let libcurl pick */
+    multicwd,         /** single CWD operation for each path part */
+    nocwd,            /** no CWD at all */
+    singlecwd,        /** one CWD to full dir, then work on file */
+    last              /** not an option, never use */
+}
+///
+alias int curl_ftpmethod;
+
+/** CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */
+enum CurlProto {
+  http   = 1,   ///
+  https  = 2,   ///
+  ftp    = 4,   ///
+  ftps   = 8,   ///
+  scp    = 16,  ///
+  sftp   = 32,  ///
+  telnet = 64,  ///
+  ldap   = 128, ///
+  ldaps  = 256, ///
+  dict   = 512, ///
+  file   = 1024,        ///
+  tftp   = 2048,        ///
+  imap   = 4096,        ///
+  imaps  = 8192,        ///
+  pop3   = 16384,       ///
+  pop3s  = 32768,       ///
+  smtp   = 65536,       ///
+  smtps  = 131072,      ///
+  rtsp   = 262144,      ///
+  rtmp   = 524288,      ///
+  rtmpt  = 1048576,     ///
+  rtmpe  = 2097152,     ///
+  rtmpte = 4194304,     ///
+  rtmps  = 8388608,     ///
+  rtmpts = 16777216,    ///
+  gopher = 33554432,    ///
+  all    = -1 /** enable everything */
+}
+
+/** long may be 32 or 64 bits, but we should never depend on anything else
+   but 32 */
+enum CURLOPTTYPE_LONG = 0;
+/// ditto
+enum CURLOPTTYPE_OBJECTPOINT = 10000;
+/// ditto
+enum CURLOPTTYPE_FUNCTIONPOINT = 20000;
+
+/// ditto
+enum CURLOPTTYPE_OFF_T = 30000;
+/** name is uppercase CURLOPT_$(LT)name$(GT),
+   type is one of the defined CURLOPTTYPE_$(LT)type$(GT)
+   number is unique identifier */
+
+/** The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */
+alias CURLOPTTYPE_LONG LONG;
+/// ditto
+alias CURLOPTTYPE_OBJECTPOINT OBJECTPOINT;
+/// ditto
+alias CURLOPTTYPE_FUNCTIONPOINT FUNCTIONPOINT;
+
+/// ditto
+alias CURLOPTTYPE_OFF_T OFF_T;
+
+///
+enum CurlOption {
+  /** This is the FILE * or void * the regular output should be written to. */
+  file = 10001,
+  /** The full URL to get/put */
+  url,
+  /** Port number to connect to, if other than default. */
+  port = 3,
+  /** Name of proxy to use. */
+  proxy = 10004,
+  /** "name:password" to use when fetching. */
+  userpwd,
+  /** "name:password" to use with proxy. */
+  proxyuserpwd,
+  /** Range to get, specified as an ASCII string. */
+  range,
+  /** not used */
+
+  /** Specified file stream to upload from (use as input): */
+  infile = 10009,
+  /** Buffer to receive error messages in, must be at least CURL_ERROR_SIZE
+   * bytes big. If this is not used, error messages go to stderr instead: */
+  errorbuffer,
+  /** Function that will be called to store the output (instead of fwrite). The
+   * parameters will use fwrite() syntax, make sure to follow them. */
+  writefunction = 20011,
+  /** Function that will be called to read the input (instead of fread). The
+   * parameters will use fread() syntax, make sure to follow them. */
+  readfunction,
+  /** Time-out the read operation after this amount of seconds */
+  timeout = 13,
+  /** If the CURLOPT_INFILE is used, this can be used to inform libcurl about
+   * how large the file being sent really is. That allows better error
+   * checking and better verifies that the upload was successful. -1 means
+   * unknown size.
+   *
+   * For large file support, there is also a _LARGE version of the key
+   * which takes an off_t type, allowing platforms with larger off_t
+   * sizes to handle larger files.  See below for INFILESIZE_LARGE.
+   */
+  infilesize,
+  /** POST static input fields. */
+  postfields = 10015,
+  /** Set the referrer page (needed by some CGIs) */
+  referer,
+  /** Set the FTP PORT string (interface name, named or numerical IP address)
+     Use i.e '-' to use default address. */
+  ftpport,
+  /** Set the User-Agent string (examined by some CGIs) */
+  useragent,
+  /** If the download receives less than "low speed limit" bytes/second
+   * during "low speed time" seconds, the operations is aborted.
+   * You could i.e if you have a pretty high speed connection, abort if
+   * it is less than 2000 bytes/sec during 20 seconds.
+   */
+
+  /** Set the "low speed limit" */
+  low_speed_limit = 19,
+  /** Set the "low speed time" */
+  low_speed_time,
+  /** Set the continuation offset.
+   *
+   * Note there is also a _LARGE version of this key which uses
+   * off_t types, allowing for large file offsets on platforms which
+   * use larger-than-32-bit off_t's.  Look below for RESUME_FROM_LARGE.
+   */
+  resume_from,
+  /** Set cookie in request: */
+  cookie = 10022,
+  /** This points to a linked list of headers, struct curl_slist kind */
+  httpheader,
+  /** This points to a linked list of post entries, struct curl_httppost */
+  httppost,
+  /** name of the file keeping your private SSL-certificate */
+  sslcert,
+  /** password for the SSL or SSH private key */
+  keypasswd,
+  /** send TYPE parameter? */
+  crlf = 27,
+  /** send linked-list of QUOTE commands */
+  quote = 10028,
+  /** send FILE * or void * to store headers to, if you use a callback it
+     is simply passed to the callback unmodified */
+  writeheader,
+  /** point to a file to read the initial cookies from, also enables
+     "cookie awareness" */
+  cookiefile = 10031,
+  /** What version to specifically try to use.
+     See CURL_SSLVERSION defines below. */
+  sslversion = 32,
+  /** What kind of HTTP time condition to use, see defines */
+  timecondition,
+  /** Time to use with the above condition. Specified in number of seconds
+     since 1 Jan 1970 */
+  timevalue,
+  /* 35 = OBSOLETE */
+
+  /** Custom request, for customizing the get command like
+     HTTP: DELETE, TRACE and others
+     FTP: to use a different list command
+     */
+  customrequest = 10036,
+  /** HTTP request, for odd commands like DELETE, TRACE and others */
+  stderr,
+  /* 38 is not used */
+
+  /** send linked-list of post-transfer QUOTE commands */
+  postquote = 10039,
+  /** Pass a pointer to string of the output using full variable-replacement
+     as described elsewhere. */
+  writeinfo,
+  verbose = 41,       /** talk a lot */
+  header,             /** throw the header out too */
+  noprogress,         /** shut off the progress meter */
+  nobody,             /** use HEAD to get http document */
+  failonerror,        /** no output on http error codes >= 300 */
+  upload,             /** this is an upload */
+  post,               /** HTTP POST method */
+  dirlistonly,        /** return bare names when listing directories */
+  append = 50,        /** Append instead of overwrite on upload! */
+  /** Specify whether to read the user+password from the .netrc or the URL.
+   * This must be one of the CURL_NETRC_* enums below. */
+  netrc,
+  followlocation, /** use Location: Luke! */
+  transfertext,  /** transfer data in text/ASCII format */
+  put,           /** HTTP PUT */
+  /* 55 = OBSOLETE */
+
+  /** Function that will be called instead of the internal progress display
+   * function. This function should be defined as the curl_progress_callback
+   * prototype defines. */
+  progressfunction = 20056,
+  /** Data passed to the progress callback */
+  progressdata = 10057,
+  /** We want the referrer field set automatically when following locations */
+  autoreferer = 58,
+  /** Port of the proxy, can be set in the proxy string as well with:
+     "[host]:[port]" */
+  proxyport,
+  /** size of the POST input data, if strlen() is not good to use */
+  postfieldsize,
+  /** tunnel non-http operations through a HTTP proxy */
+  httpproxytunnel,
+  /** Set the interface string to use as outgoing network interface */
+  intrface = 10062,
+  /** Set the krb4/5 security level, this also enables krb4/5 awareness.  This
+   * is a string, 'clear', 'safe', 'confidential' or 'private'.  If the string
+   * is set but doesn't match one of these, 'private' will be used.  */
+  krblevel,
+  /** Set if we should verify the peer in ssl handshake, set 1 to verify. */
+  ssl_verifypeer = 64,
+  /** The CApath or CAfile used to validate the peer certificate
+     this option is used only if SSL_VERIFYPEER is true */
+  cainfo = 10065,
+  /* 66 = OBSOLETE */
+  /* 67 = OBSOLETE */
+
+  /** Maximum number of http redirects to follow */
+  maxredirs = 68,
+  /** Pass a long set to 1 to get the date of the requested document (if
+     possible)! Pass a zero to shut it off. */
+  filetime,
+  /** This points to a linked list of telnet options */
+  telnetoptions = 10070,
+  /** Max amount of cached alive connections */
+  maxconnects = 71,
+  /** What policy to use when closing connections when the cache is filled
+     up */
+  closepolicy,
+  /* 73 = OBSOLETE */
+
+  /** Set to explicitly use a new connection for the upcoming transfer.
+     Do not use this unless you're absolutely sure of this, as it makes the
+     operation slower and is less friendly for the network. */
+  fresh_connect = 74,
+  /** Set to explicitly forbid the upcoming transfer's connection to be re-used
+     when done. Do not use this unless you're absolutely sure of this, as it
+     makes the operation slower and is less friendly for the network. */
+  forbid_reuse,
+  /** Set to a file name that contains random data for libcurl to use to
+     seed the random engine when doing SSL connects. */
+  random_file = 10076,
+  /** Set to the Entropy Gathering Daemon socket pathname */
+  egdsocket,
+  /** Time-out connect operations after this amount of seconds, if connects
+     are OK within this time, then fine... This only aborts the connect
+     phase. [Only works on unix-style/SIGALRM operating systems] */
+  connecttimeout = 78,
+  /** Function that will be called to store headers (instead of fwrite). The
+   * parameters will use fwrite() syntax, make sure to follow them. */
+  headerfunction = 20079,
+  /** Set this to force the HTTP request to get back to GET. Only really usable
+     if POST, PUT or a custom request have been used first.
+   */
+  httpget = 80,
+  /** Set if we should verify the Common name from the peer certificate in ssl
+   * handshake, set 1 to check existence, 2 to ensure that it matches the
+   * provided hostname. */
+  ssl_verifyhost,
+  /** Specify which file name to write all known cookies in after completed
+     operation. Set file name to "-" (dash) to make it go to stdout. */
+  cookiejar = 10082,
+  /** Specify which SSL ciphers to use */
+  ssl_cipher_list,
+  /** Specify which HTTP version to use! This must be set to one of the
+     CURL_HTTP_VERSION* enums set below. */
+  http_version = 84,
+  /** Specifically switch on or off the FTP engine's use of the EPSV command. By
+     default, that one will always be attempted before the more traditional
+     PASV command. */
+  ftp_use_epsv,
+  /** type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */
+  sslcerttype = 10086,
+  /** name of the file keeping your private SSL-key */
+  sslkey,
+  /** type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */
+  sslkeytype,
+  /** crypto engine for the SSL-sub system */
+  sslengine,
+  /** set the crypto engine for the SSL-sub system as default
+     the param has no meaning...
+   */
+  sslengine_default = 90,
+  /** Non-zero value means to use the global dns cache */
+  dns_use_global_cache,
+  /** DNS cache timeout */
+  dns_cache_timeout,
+  /** send linked-list of pre-transfer QUOTE commands */
+  prequote = 10093,
+  /** set the debug function */
+  debugfunction = 20094,
+  /** set the data for the debug function */
+  debugdata = 10095,
+  /** mark this as start of a cookie session */
+  cookiesession = 96,
+  /** The CApath directory used to validate the peer certificate
+     this option is used only if SSL_VERIFYPEER is true */
+  capath = 10097,
+  /** Instruct libcurl to use a smaller receive buffer */
+  buffersize = 98,
+  /** Instruct libcurl to not use any signal/alarm handlers, even when using
+     timeouts. This option is useful for multi-threaded applications.
+     See libcurl-the-guide for more background information. */
+  nosignal,
+  /** Provide a CURLShare for mutexing non-ts data */
+  share = 10100,
+  /** indicates type of proxy. accepted values are CURLPROXY_HTTP (default),
+     CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */
+  proxytype = 101,
+  /** Set the Accept-Encoding string. Use this to tell a server you would like
+     the response to be compressed. */
+  encoding = 10102,
+  /** Set pointer to private data */
+  private_opt,
+  /** Set aliases for HTTP 200 in the HTTP Response header */
+  http200aliases,
+  /** Continue to send authentication (user+password) when following locations,
+     even when hostname changed. This can potentially send off the name
+     and password to whatever host the server decides. */
+  unrestricted_auth = 105,
+  /** Specifically switch on or off the FTP engine's use of the EPRT command ( it
+     also disables the LPRT attempt). By default, those ones will always be
+     attempted before the good old traditional PORT command. */
+  ftp_use_eprt,
+  /** Set this to a bitmask value to enable the particular authentications
+     methods you like. Use this in combination with CURLOPT_USERPWD.
+     Note that setting multiple bits may cause extra network round-trips. */
+  httpauth,
+  /** Set the ssl context callback function, currently only for OpenSSL ssl_ctx
+     in second argument. The function must be matching the
+     curl_ssl_ctx_callback proto. */
+  ssl_ctx_function = 20108,
+  /** Set the userdata for the ssl context callback function's third
+     argument */
+  ssl_ctx_data = 10109,
+  /** FTP Option that causes missing dirs to be created on the remote server.
+     In 7.19.4 we introduced the convenience enums for this option using the
+     CURLFTP_CREATE_DIR prefix.
+  */
+  ftp_create_missing_dirs = 110,
+  /** Set this to a bitmask value to enable the particular authentications
+     methods you like. Use this in combination with CURLOPT_PROXYUSERPWD.
+     Note that setting multiple bits may cause extra network round-trips. */
+  proxyauth,
+  /** FTP option that changes the timeout, in seconds, associated with
+     getting a response.  This is different from transfer timeout time and
+     essentially places a demand on the FTP server to acknowledge commands
+     in a timely manner. */
+  ftp_response_timeout,
+  /** Set this option to one of the CURL_IPRESOLVE_* defines (see below) to
+     tell libcurl to resolve names to those IP versions only. This only has
+     affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */
+  ipresolve,
+  /** Set this option to limit the size of a file that will be downloaded from
+     an HTTP or FTP server.
+
+     Note there is also _LARGE version which adds large file support for
+     platforms which have larger off_t sizes.  See MAXFILESIZE_LARGE below. */
+  maxfilesize,
+  /** See the comment for INFILESIZE above, but in short, specifies
+   * the size of the file being uploaded.  -1 means unknown.
+   */
+  infilesize_large = 30115,
+  /** Sets the continuation offset.  There is also a LONG version of this;
+   * look above for RESUME_FROM.
+   */
+  resume_from_large,
+  /** Sets the maximum size of data that will be downloaded from
+   * an HTTP or FTP server.  See MAXFILESIZE above for the LONG version.
+   */
+  maxfilesize_large,
+  /** Set this option to the file name of your .netrc file you want libcurl
+     to parse (using the CURLOPT_NETRC option). If not set, libcurl will do
+     a poor attempt to find the user's home directory and check for a .netrc
+     file in there. */
+  netrc_file = 10118,
+  /** Enable SSL/TLS for FTP, pick one of:
+     CURLFTPSSL_TRY     - try using SSL, proceed anyway otherwise
+     CURLFTPSSL_CONTROL - SSL for the control connection or fail
+     CURLFTPSSL_ALL     - SSL for all communication or fail
+  */
+  use_ssl = 119,
+  /** The _LARGE version of the standard POSTFIELDSIZE option */
+  postfieldsize_large = 30120,
+  /** Enable/disable the TCP Nagle algorithm */
+  tcp_nodelay = 121,
+  /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */
+  /* 123 OBSOLETE. Gone in 7.16.0 */
+  /* 124 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */
+  /* 125 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */
+  /* 126 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */
+  /* 127 OBSOLETE. Gone in 7.16.0 */
+  /* 128 OBSOLETE. Gone in 7.16.0 */
+
+  /** When FTP over SSL/TLS is selected (with CURLOPT_USE_SSL), this option
+     can be used to change libcurl's default action which is to first try
+     "AUTH SSL" and then "AUTH TLS" in this order, and proceed when a OK
+     response has been received.
+
+     Available parameters are:
+     CURLFTPAUTH_DEFAULT - let libcurl decide
+     CURLFTPAUTH_SSL     - try "AUTH SSL" first, then TLS
+     CURLFTPAUTH_TLS     - try "AUTH TLS" first, then SSL
+  */
+  ftpsslauth = 129,
+  ioctlfunction = 20130,        ///
+  ioctldata = 10131,            ///
+  /* 132 OBSOLETE. Gone in 7.16.0 */
+  /* 133 OBSOLETE. Gone in 7.16.0 */
+
+  /** zero terminated string for pass on to the FTP server when asked for
+     "account" info */
+  ftp_account = 10134,
+  /** feed cookies into cookie engine */
+  cookielist,
+  /** ignore Content-Length */
+  ignore_content_length = 136,
+  /** Set to non-zero to skip the IP address received in a 227 PASV FTP server
+     response. Typically used for FTP-SSL purposes but is not restricted to
+     that. libcurl will then instead use the same IP address it used for the
+     control connection. */
+  ftp_skip_pasv_ip,
+  /** Select "file method" to use when doing FTP, see the curl_ftpmethod
+     above. */
+  ftp_filemethod,
+  /** Local port number to bind the socket to */
+  localport,
+  /** Number of ports to try, including the first one set with LOCALPORT.
+     Thus, setting it to 1 will make no additional attempts but the first.
+  */
+  localportrange,
+  /** no transfer, set up connection and let application use the socket by
+     extracting it with CURLINFO_LASTSOCKET */
+  connect_only,
+  /** Function that will be called to convert from the
+     network encoding (instead of using the iconv calls in libcurl) */
+  conv_from_network_function = 20142,
+  /** Function that will be called to convert to the
+     network encoding (instead of using the iconv calls in libcurl) */
+  conv_to_network_function,
+  /** Function that will be called to convert from UTF8
+     (instead of using the iconv calls in libcurl)
+     Note that this is used only for SSL certificate processing */
+  conv_from_utf8_function,
+  /** If the connection proceeds too quickly then need to slow it down */
+  /** */
+  /** limit-rate: maximum number of bytes per second to send or receive */
+  max_send_speed_large = 30145,
+  max_recv_speed_large, /// ditto
+  /** Pointer to command string to send if USER/PASS fails. */
+  ftp_alternative_to_user = 10147,
+  /** callback function for setting socket options */
+  sockoptfunction = 20148,
+  sockoptdata = 10149,
+  /** set to 0 to disable session ID re-use for this transfer, default is
+     enabled (== 1) */
+  ssl_sessionid_cache = 150,
+  /** allowed SSH authentication methods */
+  ssh_auth_types,
+  /** Used by scp/sftp to do public/private key authentication */
+  ssh_public_keyfile = 10152,
+  ssh_private_keyfile,
+  /** Send CCC (Clear Command Channel) after authentication */
+  ftp_ssl_ccc = 154,
+  /** Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */
+  timeout_ms,
+  connecttimeout_ms,
+  /** set to zero to disable the libcurl's decoding and thus pass the raw body
+     data to the application even when it is encoded/compressed */
+  http_transfer_decoding,
+  http_content_decoding,        /// ditto
+  /** Permission used when creating new files and directories on the remote
+     server for protocols that support it, SFTP/SCP/FILE */
+  new_file_perms,
+  new_directory_perms,          /// ditto
+  /** Set the behaviour of POST when redirecting. Values must be set to one
+     of CURL_REDIR* defines below. This used to be called CURLOPT_POST301 */
+  postredir,
+  /** used by scp/sftp to verify the host's public key */
+  ssh_host_public_key_md5 = 10162,
+  /** Callback function for opening socket (instead of socket(2)). Optionally,
+     callback is able change the address or refuse to connect returning
+     CURL_SOCKET_BAD.  The callback should have type
+     curl_opensocket_callback */
+  opensocketfunction = 20163,
+  opensocketdata = 10164,       /// ditto
+  /** POST volatile input fields. */
+  copypostfields,
+  /** set transfer mode (;type=$(LT)a|i$(GT)) when doing FTP via an HTTP proxy */
+  proxy_transfer_mode = 166,
+  /** Callback function for seeking in the input stream */
+  seekfunction = 20167,
+  seekdata = 10168,     /// ditto
+  /** CRL file */
+  crlfile,
+  /** Issuer certificate */
+  issuercert,
+  /** (IPv6) Address scope */
+  address_scope = 171,
+  /** Collect certificate chain info and allow it to get retrievable with
+     CURLINFO_CERTINFO after the transfer is complete. (Unfortunately) only
+     working with OpenSSL-powered builds. */
+  certinfo,
+  /** "name" and "pwd" to use when fetching. */
+  username = 10173,
+  password,     /// ditto
+  /** "name" and "pwd" to use with Proxy when fetching. */
+  proxyusername,
+  proxypassword,        /// ditto
+  /** Comma separated list of hostnames defining no-proxy zones. These should
+     match both hostnames directly, and hostnames within a domain. For
+     example, local.com will match local.com and www.local.com, but NOT
+     notlocal.com or www.notlocal.com. For compatibility with other
+     implementations of this, .local.com will be considered to be the same as
+     local.com. A single * is the only valid wildcard, and effectively
+     disables the use of proxy. */
+  noproxy,
+  /** block size for TFTP transfers */
+  tftp_blksize = 178,
+  /** Socks Service */
+  socks5_gssapi_service = 10179,
+  /** Socks Service */
+  socks5_gssapi_nec = 180,
+  /** set the bitmask for the protocols that are allowed to be used for the
+     transfer, which thus helps the app which takes URLs from users or other
+     external inputs and want to restrict what protocol(s) to deal
+     with. Defaults to CURLPROTO_ALL. */
+  protocols,
+  /** set the bitmask for the protocols that libcurl is allowed to follow to,
+     as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs
+     to be set in both bitmasks to be allowed to get redirected to. Defaults
+     to all protocols except FILE and SCP. */
+  redir_protocols,
+  /** set the SSH knownhost file name to use */
+  ssh_knownhosts = 10183,
+  /** set the SSH host key callback, must point to a curl_sshkeycallback
+     function */
+  ssh_keyfunction = 20184,
+  /** set the SSH host key callback custom pointer */
+  ssh_keydata = 10185,
+  /** set the SMTP mail originator */
+  mail_from,
+  /** set the SMTP mail receiver(s) */
+  mail_rcpt,
+  /** FTP: send PRET before PASV */
+  ftp_use_pret = 188,
+  /** RTSP request method (OPTIONS, SETUP, PLAY, etc...) */
+  rtsp_request,
+  /** The RTSP session identifier */
+  rtsp_session_id = 10190,
+  /** The RTSP stream URI */
+  rtsp_stream_uri,
+  /** The Transport: header to use in RTSP requests */
+  rtsp_transport,
+  /** Manually initialize the client RTSP CSeq for this handle */
+  rtsp_client_cseq = 193,
+  /** Manually initialize the server RTSP CSeq for this handle */
+  rtsp_server_cseq,
+  /** The stream to pass to INTERLEAVEFUNCTION. */
+  interleavedata = 10195,
+  /** Let the application define a custom write method for RTP data */
+  interleavefunction = 20196,
+  /** Turn on wildcard matching */
+  wildcardmatch = 197,
+  /** Directory matching callback called before downloading of an
+     individual file (chunk) started */
+  chunk_bgn_function = 20198,
+  /** Directory matching callback called after the file (chunk)
+     was downloaded, or skipped */
+  chunk_end_function,
+  /** Change match (fnmatch-like) callback for wildcard matching */
+  fnmatch_function,
+  /** Let the application define custom chunk data pointer */
+  chunk_data = 10201,
+  /** FNMATCH_FUNCTION user pointer */
+  fnmatch_data,
+  /** send linked-list of name:port:address sets */
+  resolve,
+  /** Set a username for authenticated TLS */
+  tlsauth_username,
+  /** Set a password for authenticated TLS */
+  tlsauth_password,
+  /** Set authentication type for authenticated TLS */
+  tlsauth_type,
+  /** the last unused */
+  lastentry,
+
+  writedata = file, /// convenient alias
+  readdata = infile, /// ditto
+  headerdata = writeheader, /// ditto
+  rtspheader = httpheader, /// ditto
+}
+///
+alias int CURLoption;
+///
+enum CURLOPT_SERVER_RESPONSE_TIMEOUT = CurlOption.ftp_response_timeout;
+
+/** Below here follows defines for the CURLOPT_IPRESOLVE option. If a host
+   name resolves addresses using more than one IP protocol version, this
+   option might be handy to force libcurl to use a specific IP version. */
+enum CurlIpResolve {
+  whatever = 0, /** default, resolves addresses to all IP versions that your system allows */
+  v4 = 1,       /** resolve to ipv4 addresses */
+  v6 = 2        /** resolve to ipv6 addresses */
+}
+
+/** three convenient "aliases" that follow the name scheme better */
+enum CURLOPT_WRITEDATA = CurlOption.file;
+/// ditto
+enum CURLOPT_READDATA = CurlOption.infile;
+/// ditto
+enum CURLOPT_HEADERDATA = CurlOption.writeheader;
+/// ditto
+enum CURLOPT_RTSPHEADER = CurlOption.httpheader;
+
+/** These enums are for use with the CURLOPT_HTTP_VERSION option. */
+enum CurlHttpVersion {
+    none, /** setting this means we don't care, and that we'd
+             like the library to choose the best possible
+             for us! */
+    v1_0, /** please use HTTP 1.0 in the request */
+    v1_1, /** please use HTTP 1.1 in the request */
+    last  /** *ILLEGAL* http version */
+}
+
+/**
+ * Public API enums for RTSP requests
+ */
+enum CurlRtspReq {
+    none,       ///
+    options,    ///
+    describe,   ///
+    announce,   ///
+    setup,      ///
+    play,       ///
+    pause,      ///
+    teardown,   ///
+    get_parameter,      ///
+    set_parameter,      ///
+    record,     ///
+    receive,    ///
+    last        ///
+}
+
+ /** These enums are for use with the CURLOPT_NETRC option. */
+enum CurlNetRcOption {
+    ignored,  /** The .netrc will never be read. This is the default. */
+    optional  /** A user:password in the URL will be preferred to one in the .netrc. */,
+    required, /** A user:password in the URL will be ignored.
+               * Unless one is set programmatically, the .netrc
+               * will be queried. */
+    last        ///
+}
+
+///
+enum CurlSslVersion {
+    default_version,    ///
+    tlsv1,      ///
+    sslv2,      ///
+    sslv3,      ///
+    last /** never use */
+}
+
+///
+enum CurlTlsAuth {
+    none,       ///
+    srp,        ///
+    last /** never use */
+}
+
+/** symbols to use with CURLOPT_POSTREDIR.
+   CURL_REDIR_POST_301 and CURL_REDIR_POST_302 can be bitwise ORed so that
+   CURL_REDIR_POST_301 | CURL_REDIR_POST_302 == CURL_REDIR_POST_ALL */
+enum CurlRedir {
+  get_all = 0,  ///
+  post_301 = 1, ///
+  post_302 = 2, ///
+  ///
+  post_all = (1 | 2) // (CURL_REDIR_POST_301|CURL_REDIR_POST_302);
+}
+///
+enum CurlTimeCond {
+    none,       ///
+    ifmodsince, ///
+    ifunmodsince,       ///
+    lastmod,    ///
+    last        ///
+}
+///
+alias int curl_TimeCond;
+
+
+/** curl_strequal() and curl_strnequal() are subject for removal in a future
+   libcurl, see lib/README.curlx for details */
+extern (C) {
+int  curl_strequal(in char *s1, in char *s2);
+/// ditto
+int  curl_strnequal(in char *s1, in char *s2, size_t n);
+}
+enum CurlForm {
+    nothing, /********** the first one is unused ************/
+    copyname,
+    ptrname,
+    namelength,
+    copycontents,
+    ptrcontents,
+    contentslength,
+    filecontent,
+    array,
+    obsolete,
+    file,
+    buffer,
+    bufferptr,
+    bufferlength,
+    contenttype,
+    contentheader,
+    filename,
+    end,
+    obsolete2,
+    stream,
+    lastentry /** the last unused */
+}
+alias int CURLformoption;
+
+
+/** structure to be used as parameter for CURLFORM_ARRAY */
+extern (C) struct curl_forms
+{
+    CURLformoption option;      ///
+    const(char) *value;        ///
+}
+
+/** Use this for multipart formpost building
+ *
+ * Returns code for curl_formadd()
+ *
+ * Returns:
+ *
+ * $(UL
+ * $(LI CURL_FORMADD_OK             on success )
+ * $(LI CURL_FORMADD_MEMORY         if the FormInfo allocation fails )
+ * $(LI CURL_FORMADD_OPTION_TWICE   if one option is given twice for one Form )
+ * $(LI CURL_FORMADD_NULL           if a null pointer was given for a char )
+ * $(LI CURL_FORMADD_MEMORY         if the allocation of a FormInfo struct failed )
+ * $(LI CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used )
+ * $(LI CURL_FORMADD_INCOMPLETE     if the some FormInfo is not complete (or error) )
+ * $(LI CURL_FORMADD_MEMORY         if a curl_httppost struct cannot be allocated )
+ * $(LI CURL_FORMADD_MEMORY         if some allocation for string copying failed. )
+ * $(LI CURL_FORMADD_ILLEGAL_ARRAY  if an illegal option is used in an array )
+ * )
+ *
+ ***************************************************************************/
+enum CurlFormAdd {
+    ok, /** first, no error */
+    memory,     ///
+    option_twice,       ///
+    null_ptr,   ///
+    unknown_option,     ///
+    incomplete, ///
+    illegal_array,      ///
+    disabled,  /** libcurl was built with this disabled */
+    last        ///
+}
+///
+alias int CURLFORMcode;
+
+extern (C) {
+
+/**
+ * Name: curl_formadd()
+ *
+ * Description:
+ *
+ * Pretty advanced function for building multi-part formposts. Each invoke
+ * adds one part that together construct a full post. Then use
+ * CURLOPT_HTTPPOST to send it off to libcurl.
+ */
+CURLFORMcode  curl_formadd(curl_httppost **httppost, curl_httppost **last_post,...);
+
+/**
+ * callback function for curl_formget()
+ * The void *arg pointer will be the one passed as second argument to
+ *   curl_formget().
+ * The character buffer passed to it must not be freed.
+ * Should return the buffer length passed to it as the argument "len" on
+ *   success.
+ */
+alias size_t  function(void *arg, in char *buf, size_t len)curl_formget_callback;
+
+/**
+ * Name: curl_formget()
+ *
+ * Description:
+ *
+ * Serialize a curl_httppost struct built with curl_formadd().
+ * Accepts a void pointer as second argument which will be passed to
+ * the curl_formget_callback function.
+ * Returns 0 on success.
+ */
+int  curl_formget(curl_httppost *form, void *arg, curl_formget_callback append);
+/**
+ * Name: curl_formfree()
+ *
+ * Description:
+ *
+ * Free a multipart formpost previously built with curl_formadd().
+ */
+void  curl_formfree(curl_httppost *form);
+
+/**
+ * Name: curl_getenv()
+ *
+ * Description:
+ *
+ * Returns a malloc()'ed string that MUST be curl_free()ed after usage is
+ * complete. DEPRECATED - see lib/README.curlx
+ */
+char * curl_getenv(in char *variable);
+
+/**
+ * Name: curl_version()
+ *
+ * Description:
+ *
+ * Returns a static ascii string of the libcurl version.
+ */
+char * curl_version();
+
+/**
+ * Name: curl_easy_escape()
+ *
+ * Description:
+ *
+ * Escapes URL strings (converts all letters consider illegal in URLs to their
+ * %XX versions). This function returns a new allocated string or NULL if an
+ * error occurred.
+ */
+char * curl_easy_escape(CURL *handle, in char *string, int length) @trusted;
+
+/** the previous version: */
+char * curl_escape(in char *string, int length) @trusted;
+
+
+/**
+ * Name: curl_easy_unescape()
+ *
+ * Description:
+ *
+ * Unescapes URL encoding in strings (converts all %XX codes to their 8bit
+ * versions). This function returns a new allocated string or NULL if an error
+ * occurred.
+ * Conversion Note: On non-ASCII platforms the ASCII %XX codes are
+ * converted into the host encoding.
+ */
+char * curl_easy_unescape(CURL *handle, in char *string, int length, int *outlength) @trusted;
+
+/** the previous version */
+char * curl_unescape(in char *string, int length) @trusted;
+
+/**
+ * Name: curl_free()
+ *
+ * Description:
+ *
+ * Provided for de-allocation in the same translation unit that did the
+ * allocation. Added in libcurl 7.10
+ */
+void  curl_free(void *p);
+
+/**
+ * Name: curl_global_init()
+ *
+ * Description:
+ *
+ * curl_global_init() should be invoked exactly once for each application that
+ * uses libcurl and before any call of other libcurl functions.
+ *
+ * This function is not thread-safe!
+ */
+CURLcode  curl_global_init(c_long flags);
+
+/**
+ * Name: curl_global_init_mem()
+ *
+ * Description:
+ *
+ * curl_global_init() or curl_global_init_mem() should be invoked exactly once
+ * for each application that uses libcurl.  This function can be used to
+ * initialize libcurl and set user defined memory management callback
+ * functions.  Users can implement memory management routines to check for
+ * memory leaks, check for mis-use of the curl library etc.  User registered
+ * callback routines with be invoked by this library instead of the system
+ * memory management routines like malloc, free etc.
+ */
+CURLcode  curl_global_init_mem(c_long flags, curl_malloc_callback m, curl_free_callback f, curl_realloc_callback r, curl_strdup_callback s, curl_calloc_callback c);
+
+/**
+ * Name: curl_global_cleanup()
+ *
+ * Description:
+ *
+ * curl_global_cleanup() should be invoked exactly once for each application
+ * that uses libcurl
+ */
+void  curl_global_cleanup();
+}
+
+/** linked-list structure for the CURLOPT_QUOTE option (and other) */
+extern (C) {
+
+struct curl_slist
+{
+    char *data;
+    curl_slist *next;
+}
+
+/**
+ * Name: curl_slist_append()
+ *
+ * Description:
+ *
+ * Appends a string to a linked list. If no list exists, it will be created
+ * first. Returns the new list, after appending.
+ */
+curl_slist * curl_slist_append(curl_slist *, in char *);
+
+/**
+ * Name: curl_slist_free_all()
+ *
+ * Description:
+ *
+ * free a previously built curl_slist.
+ */
+void  curl_slist_free_all(curl_slist *);
+
+/**
+ * Name: curl_getdate()
+ *
+ * Description:
+ *
+ * Returns the time, in seconds since 1 Jan 1970 of the time string given in
+ * the first argument. The time argument in the second parameter is unused
+ * and should be set to NULL.
+ */
+time_t  curl_getdate(char *p, time_t *unused);
+
+/** info about the certificate chain, only for OpenSSL builds. Asked
+   for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */
+struct curl_certinfo
+{
+    int num_of_certs;      /** number of certificates with information */
+    curl_slist **certinfo; /** for each index in this array, there's a
+                              linked list with textual information in the
+                              format "name: value" */
+}
+
+} // extern (C) end
+
+///
+enum CURLINFO_STRING = 0x100000;
+///
+enum CURLINFO_LONG = 0x200000;
+///
+enum CURLINFO_DOUBLE = 0x300000;
+///
+enum CURLINFO_SLIST = 0x400000;
+///
+enum CURLINFO_MASK = 0x0fffff;
+
+///
+enum CURLINFO_TYPEMASK = 0xf00000;
+
+///
+enum CurlInfo {
+    none,       ///
+    effective_url = 1048577,    ///
+    response_code = 2097154,    ///
+    total_time = 3145731,       ///
+    namelookup_time,    ///
+    connect_time,       ///
+    pretransfer_time,   ///
+    size_upload,        ///
+    size_download,      ///
+    speed_download,     ///
+    speed_upload,       ///
+    header_size = 2097163,      ///
+    request_size,       ///
+    ssl_verifyresult,   ///
+    filetime,   ///
+    content_length_download = 3145743,  ///
+    content_length_upload,      ///
+    starttransfer_time, ///
+    content_type = 1048594,     ///
+    redirect_time = 3145747,    ///
+    redirect_count = 2097172,   ///
+    private_info = 1048597,     ///
+    http_connectcode = 2097174, ///
+    httpauth_avail,     ///
+    proxyauth_avail,    ///
+    os_errno,   ///
+    num_connects,       ///
+    ssl_engines = 4194331,      ///
+    cookielist, ///
+    lastsocket = 2097181,       ///
+    ftp_entry_path = 1048606,   ///
+    redirect_url,       ///
+    primary_ip, ///
+    appconnect_time = 3145761,  ///
+    certinfo = 4194338, ///
+    condition_unmet = 2097187,  ///
+    rtsp_session_id = 1048612,  ///
+    rtsp_client_cseq = 2097189, ///
+    rtsp_server_cseq,   ///
+    rtsp_cseq_recv,     ///
+    primary_port,       ///
+    local_ip = 1048617, ///
+    local_port = 2097194,       ///
+    /** Fill in new entries below here! */
+    lastone = 42
+}
+///
+alias int CURLINFO;
+
+/** CURLINFO_RESPONSE_CODE is the new name for the option previously known as
+   CURLINFO_HTTP_CODE */
+enum CURLINFO_HTTP_CODE = CurlInfo.response_code;
+
+///
+enum CurlClosePolicy {
+    none,       ///
+    oldest,     ///
+    least_recently_used,        ///
+    least_traffic,      ///
+    slowest,    ///
+    callback,   ///
+    last        ///
+}
+///
+alias int curl_closepolicy;
+
+///
+enum CurlGlobal {
+  ssl = 1,      ///
+  win32 = 2,    ///
+  ///
+  all = (1 | 2), // (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32);
+  nothing = 0,  ///
+  default_ = (1 | 2) /// all
+}
+
+/******************************************************************************
+ * Setup defines, protos etc for the sharing stuff.
+ */
+
+/** Different data locks for a single share */
+enum CurlLockData {
+    none,       ///
+    /**  CURL_LOCK_DATA_SHARE is used internally to say that
+     *  the locking is just made to change the internal state of the share
+     *  itself.
+     */
+    share,
+    cookie,     ///
+    dns,        ///
+    ssl_session,        ///
+    connect,    ///
+    last        ///
+}
+///
+alias int curl_lock_data;
+
+/** Different lock access types */
+enum CurlLockAccess {
+    none,            /** unspecified action */
+    shared_access,   /** for read perhaps */
+    single,          /** for write perhaps */
+    last             /** never use */
+}
+///
+alias int curl_lock_access;
+
+///
+alias void  function(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr)curl_lock_function;
+///
+alias void  function(CURL *handle, curl_lock_data data, void *userptr)curl_unlock_function;
+
+///
+alias void CURLSH;
+
+///
+enum CurlShError {
+    ok,          /** all is fine */
+    bad_option,  /** 1 */
+    in_use,      /** 2 */
+    invalid,     /** 3 */
+    nomem,       /** out of memory */
+    last         /** never use */
+}
+///
+alias int CURLSHcode;
+
+/** pass in a user data pointer used in the lock/unlock callback
+   functions */
+enum CurlShOption {
+    none,         /** don't use */
+    share,        /** specify a data type to share */
+    unshare,      /** specify which data type to stop sharing */
+    lockfunc,     /** pass in a 'curl_lock_function' pointer */
+    unlockfunc,   /** pass in a 'curl_unlock_function' pointer */
+    userdata,     /** pass in a user data pointer used in the lock/unlock
+                     callback functions */
+    last          /** never use */
+}
+///
+alias int CURLSHoption;
+
+extern (C) {
+///
+CURLSH * curl_share_init();
+///
+CURLSHcode  curl_share_setopt(CURLSH *, CURLSHoption option,...);
+///
+CURLSHcode  curl_share_cleanup(CURLSH *);
+}
+
+/*****************************************************************************
+ * Structures for querying information about the curl library at runtime.
+ */
+
+// CURLVERSION_*
+enum CurlVer {
+    first,      ///
+    second,     ///
+    third,      ///
+    fourth,     ///
+    last        ///
+}
+///
+alias int CURLversion;
+
+/** The 'CURLVERSION_NOW' is the symbolic name meant to be used by
+   basically all programs ever that want to get version information. It is
+   meant to be a built-in version number for what kind of struct the caller
+   expects. If the struct ever changes, we redefine the NOW to another enum
+   from above. */
+enum CURLVERSION_NOW = CurlVer.fourth;
+
+///
+extern (C) struct _N28
+{
+  CURLversion age;     /** age of the returned struct */
+  const(char) *version_;      /** LIBCURL_VERSION */
+  uint version_num;    /** LIBCURL_VERSION_NUM */
+  const(char) *host;          /** OS/host/cpu/machine when configured */
+  int features;        /** bitmask, see defines below */
+  const(char) *ssl_version;   /** human readable string */
+  c_long ssl_version_num; /** not used anymore, always 0 */
+  const(char) *libz_version;     /** human readable string */
+  /** protocols is terminated by an entry with a NULL protoname */
+  const(char) **protocols;
+  /** The fields below this were added in CURLVERSION_SECOND */
+  const(char) *ares;
+  int ares_num;
+  /** This field was added in CURLVERSION_THIRD */
+  const(char) *libidn;
+  /** These field were added in CURLVERSION_FOURTH. */
+  /** Same as '_libiconv_version' if built with HAVE_ICONV */
+  int iconv_ver_num;
+  const(char) *libssh_version;  /** human readable string */
+}
+///
+alias _N28 curl_version_info_data;
+
+///
+// CURL_VERSION_*
+enum CurlVersion {
+  ipv6         = 1,     /** IPv6-enabled */
+  kerberos4    = 2,     /** kerberos auth is supported */
+  ssl          = 4,     /** SSL options are present */
+  libz         = 8,     /** libz features are present */
+  ntlm         = 16,    /** NTLM auth is supported */
+  gssnegotiate = 32,    /** Negotiate auth support */
+  dbg          = 64,    /** built with debug capabilities */
+  asynchdns    = 128,   /** asynchronous dns resolves */
+  spnego       = 256,   /** SPNEGO auth */
+  largefile    = 512,   /** supports files bigger than 2GB */
+  idn          = 1024,  /** International Domain Names support */
+  sspi         = 2048,  /** SSPI is supported */
+  conv         = 4096,  /** character conversions supported */
+  curldebug    = 8192,  /** debug memory tracking supported */
+  tlsauth_srp  = 16384  /** TLS-SRP auth is supported */
+}
+
+extern (C) {
+/**
+ * Name: curl_version_info()
+ *
+ * Description:
+ *
+ * This function returns a pointer to a static copy of the version info
+ * struct. See above.
+ */
+curl_version_info_data * curl_version_info(CURLversion );
+
+/**
+ * Name: curl_easy_strerror()
+ *
+ * Description:
+ *
+ * The curl_easy_strerror function may be used to turn a CURLcode value
+ * into the equivalent human readable error string.  This is useful
+ * for printing meaningful error messages.
+ */
+const(char)* curl_easy_strerror(CURLcode );
+
+/**
+ * Name: curl_share_strerror()
+ *
+ * Description:
+ *
+ * The curl_share_strerror function may be used to turn a CURLSHcode value
+ * into the equivalent human readable error string.  This is useful
+ * for printing meaningful error messages.
+ */
+const(char)* curl_share_strerror(CURLSHcode );
+
+/**
+ * Name: curl_easy_pause()
+ *
+ * Description:
+ *
+ * The curl_easy_pause function pauses or unpauses transfers. Select the new
+ * state by setting the bitmask, use the convenience defines below.
+ *
+ */
+CURLcode  curl_easy_pause(CURL *handle, int bitmask);
+}
+
+
+///
+enum CurlPause {
+  recv      = 1,        ///
+  recv_cont = 0,        ///
+  send      = 4,        ///
+  send_cont = 0,        ///
+  ///
+  all       = (1 | 4), // CURLPAUSE_RECV | CURLPAUSE_SEND
+  ///
+  cont      = (0 | 0), // CURLPAUSE_RECV_CONT | CURLPAUSE_SEND_CONT
+}
+
+/* unfortunately, the easy.h and multi.h include files need options and info
+  stuff before they can be included! */
+/* ***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+extern (C) {
+  ///
+  CURL * curl_easy_init();
+  ///
+  CURLcode  curl_easy_setopt(CURL *curl, CURLoption option,...);
+  ///
+  CURLcode  curl_easy_perform(CURL *curl);
+  ///
+  void  curl_easy_cleanup(CURL *curl);
+}
+
+/**
+ * Name: curl_easy_getinfo()
+ *
+ * Description:
+ *
+ * Request internal information from the curl session with this function.  The
+ * third argument MUST be a pointer to a long, a pointer to a char * or a
+ * pointer to a double (as the documentation describes elsewhere).  The data
+ * pointed to will be filled in accordingly and can be relied upon only if the
+ * function returns CURLE_OK.  This function is intended to get used *AFTER* a
+ * performed transfer, all results from this function are undefined until the
+ * transfer is completed.
+ */
+extern (C) CURLcode  curl_easy_getinfo(CURL *curl, CURLINFO info,...);
+
+
+/**
+ * Name: curl_easy_duphandle()
+ *
+ * Description:
+ *
+ * Creates a new curl session handle with the same options set for the handle
+ * passed in. Duplicating a handle could only be a matter of cloning data and
+ * options, internal state info and things like persistant connections cannot
+ * be transfered. It is useful in multithreaded applications when you can run
+ * curl_easy_duphandle() for each new thread to avoid a series of identical
+ * curl_easy_setopt() invokes in every thread.
+ */
+extern (C) CURL * curl_easy_duphandle(CURL *curl);
+
+/**
+ * Name: curl_easy_reset()
+ *
+ * Description:
+ *
+ * Re-initializes a CURL handle to the default values. This puts back the
+ * handle to the same state as it was in when it was just created.
+ *
+ * It does keep: live connections, the Session ID cache, the DNS cache and the
+ * cookies.
+ */
+extern (C) void  curl_easy_reset(CURL *curl);
+
+/**
+ * Name: curl_easy_recv()
+ *
+ * Description:
+ *
+ * Receives data from the connected socket. Use after successful
+ * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ */
+extern (C) CURLcode  curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n);
+
+/**
+ * Name: curl_easy_send()
+ *
+ * Description:
+ *
+ * Sends data over the connected socket. Use after successful
+ * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ */
+extern (C) CURLcode  curl_easy_send(CURL *curl, void *buffer, size_t buflen, size_t *n);
+
+
+/*
+ * This header file should not really need to include "curl.h" since curl.h
+ * itself includes this file and we expect user applications to do #include
+ * <curl/curl.h> without the need for especially including multi.h.
+ *
+ * For some reason we added this include here at one point, and rather than to
+ * break existing (wrongly written) libcurl applications, we leave it as-is
+ * but with this warning attached.
+ */
+/* ***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+///
+alias void CURLM;
+
+///
+enum CurlM {
+    call_multi_perform = -1, /** please call curl_multi_perform() or curl_multi_socket*() soon */
+    ok, ///
+    bad_handle,              /** the passed-in handle is not a valid CURLM handle */
+    bad_easy_handle,       /** an easy handle was not good/valid */
+    out_of_memory,         /** if you ever get this, you're in deep sh*t */
+    internal_error,        /** this is a libcurl bug */
+    bad_socket,            /** the passed in socket argument did not match */
+    unknown_option,        /** curl_multi_setopt() with unsupported option */
+    last,       ///
+}
+///
+alias int CURLMcode;
+
+/** just to make code nicer when using curl_multi_socket() you can now check
+   for CURLM_CALL_MULTI_SOCKET too in the same style it works for
+   curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */
+enum CURLM_CALL_MULTI_SOCKET = CurlM.call_multi_perform;
+
+///
+enum CurlMsg
+{
+    none,       ///
+    done, /** This easy handle has completed. 'result' contains
+             the CURLcode of the transfer */
+    last, /** no used */
+}
+///
+alias int CURLMSG;
+
+///
+extern (C) union _N31
+{
+    void *whatever;  /** message-specific data */
+    CURLcode result; /** return code for transfer */
+}
+
+///
+extern (C) struct CURLMsg
+{
+    CURLMSG msg;        /** what this message means */
+    CURL *easy_handle;  /** the handle it concerns */
+    _N31 data;  ///
+}
+
+/**
+ * Name:    curl_multi_init()
+ *
+ * Desc:    inititalize multi-style curl usage
+ *
+ * Returns: a new CURLM handle to use in all 'curl_multi' functions.
+ */
+extern (C) CURLM * curl_multi_init();
+
+/**
+ * Name:    curl_multi_add_handle()
+ *
+ * Desc:    add a standard curl handle to the multi stack
+ *
+ * Returns: CURLMcode type, general multi error code.
+ */
+extern (C) CURLMcode  curl_multi_add_handle(CURLM *multi_handle, CURL *curl_handle);
+
+ /**
+  * Name:    curl_multi_remove_handle()
+  *
+  * Desc:    removes a curl handle from the multi stack again
+  *
+  * Returns: CURLMcode type, general multi error code.
+  */
+extern (C) CURLMcode  curl_multi_remove_handle(CURLM *multi_handle, CURL *curl_handle);
+
+ /**
+  * Name:    curl_multi_fdset()
+  *
+  * Desc:    Ask curl for its fd_set sets. The app can use these to select() or
+  *          poll() on. We want curl_multi_perform() called as soon as one of
+  *          them are ready.
+  *
+  * Returns: CURLMcode type, general multi error code.
+  */
+
+/** tmp decl */
+alias int fd_set;
+///
+extern (C) CURLMcode  curl_multi_fdset(CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd);
+
+ /**
+  * Name:    curl_multi_perform()
+  *
+  * Desc:    When the app thinks there's data available for curl it calls this
+  *          function to read/write whatever there is right now. This returns
+  *          as soon as the reads and writes are done. This function does not
+  *          require that there actually is data available for reading or that
+  *          data can be written, it can be called just in case. It returns
+  *          the number of handles that still transfer data in the second
+  *          argument's integer-pointer.
+  *
+  * Returns: CURLMcode type, general multi error code. *NOTE* that this only
+  *          returns errors etc regarding the whole multi stack. There might
+  *          still have occurred problems on invidual transfers even when this
+  *          returns OK.
+  */
+extern (C) CURLMcode  curl_multi_perform(CURLM *multi_handle, int *running_handles);
+
+ /**
+  * Name:    curl_multi_cleanup()
+  *
+  * Desc:    Cleans up and removes a whole multi stack. It does not free or
+  *          touch any individual easy handles in any way. We need to define
+  *          in what state those handles will be if this function is called
+  *          in the middle of a transfer.
+  *
+  * Returns: CURLMcode type, general multi error code.
+  */
+extern (C) CURLMcode  curl_multi_cleanup(CURLM *multi_handle);
+
+/**
+ * Name:    curl_multi_info_read()
+ *
+ * Desc:    Ask the multi handle if there's any messages/informationals from
+ *          the individual transfers. Messages include informationals such as
+ *          error code from the transfer or just the fact that a transfer is
+ *          completed. More details on these should be written down as well.
+ *
+ *          Repeated calls to this function will return a new struct each
+ *          time, until a special "end of msgs" struct is returned as a signal
+ *          that there is no more to get at this point.
+ *
+ *          The data the returned pointer points to will not survive calling
+ *          curl_multi_cleanup().
+ *
+ *          The 'CURLMsg' struct is meant to be very simple and only contain
+ *          very basic informations. If more involved information is wanted,
+ *          we will provide the particular "transfer handle" in that struct
+ *          and that should/could/would be used in subsequent
+ *          curl_easy_getinfo() calls (or similar). The point being that we
+ *          must never expose complex structs to applications, as then we'll
+ *          undoubtably get backwards compatibility problems in the future.
+ *
+ * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out
+ *          of structs. It also writes the number of messages left in the
+ *          queue (after this read) in the integer the second argument points
+ *          to.
+ */
+extern (C) CURLMsg * curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue);
+
+/**
+ * Name:    curl_multi_strerror()
+ *
+ * Desc:    The curl_multi_strerror function may be used to turn a CURLMcode
+ *          value into the equivalent human readable error string.  This is
+ *          useful for printing meaningful error messages.
+ *
+ * Returns: A pointer to a zero-terminated error message.
+ */
+extern (C) const(char)* curl_multi_strerror(CURLMcode );
+
+/**
+ * Name:    curl_multi_socket() and
+ *          curl_multi_socket_all()
+ *
+ * Desc:    An alternative version of curl_multi_perform() that allows the
+ *          application to pass in one of the file descriptors that have been
+ *          detected to have "action" on them and let libcurl perform.
+ *          See man page for details.
+ */
+enum CurlPoll {
+  none_ = 0,   /** jdrewsen - underscored in order not to clash with reserved D symbols */
+  in_ = 1,      ///
+  out_ = 2,     ///
+  inout_ = 3,   ///
+  remove_ = 4   ///
+}
+
+///
+alias CURL_SOCKET_BAD CURL_SOCKET_TIMEOUT;
+
+///
+enum CurlCSelect {
+  in_ = 0x01,  /** jdrewsen - underscored in order not to clash with reserved D symbols */
+  out_ = 0x02,  ///
+  err_ = 0x04   ///
+}
+
+extern (C) {
+  ///
+  alias int function(CURL *easy,                            /** easy handle */
+                     curl_socket_t s,                     /** socket */
+                     int what,                            /** see above */
+                     void *userp,                         /** private callback pointer */
+                     void *socketp)curl_socket_callback;          /** private socket pointer */
+}
+
+/**
+ * Name:    curl_multi_timer_callback
+ *
+ * Desc:    Called by libcurl whenever the library detects a change in the
+ *          maximum number of milliseconds the app is allowed to wait before
+ *          curl_multi_socket() or curl_multi_perform() must be called
+ *          (to allow libcurl's timed events to take place).
+ *
+ * Returns: The callback should return zero.
+ */
+
+extern (C) {
+  alias int function(CURLM *multi,    /** multi handle */
+                     c_long timeout_ms,  /** see above */
+                     void *userp) curl_multi_timer_callback;  /** private callback pointer */
+  /// ditto
+  CURLMcode  curl_multi_socket(CURLM *multi_handle, curl_socket_t s, int *running_handles);
+  /// ditto
+  CURLMcode  curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, int ev_bitmask, int *running_handles);
+  /// ditto
+  CURLMcode  curl_multi_socket_all(CURLM *multi_handle, int *running_handles);
+}
+
+/** This macro below was added in 7.16.3 to push users who recompile to use
+   the new curl_multi_socket_action() instead of the old curl_multi_socket()
+*/
+
+/**
+ * Name:    curl_multi_timeout()
+ *
+ * Desc:    Returns the maximum number of milliseconds the app is allowed to
+ *          wait before curl_multi_socket() or curl_multi_perform() must be
+ *          called (to allow libcurl's timed events to take place).
+ *
+ * Returns: CURLM error code.
+ */
+extern (C) CURLMcode  curl_multi_timeout(CURLM *multi_handle, c_long *milliseconds);
+
+///
+enum CurlMOption {
+    socketfunction = 20001,    /** This is the socket callback function pointer */
+    socketdata = 10002,        /** This is the argument passed to the socket callback */
+    pipelining = 3,             /** set to 1 to enable pipelining for this multi handle */
+    timerfunction = 20004,     /** This is the timer callback function pointer */
+    timerdata = 10005,          /** This is the argument passed to the timer callback */
+    maxconnects = 6,            /** maximum number of entries in the connection cache */
+    lastentry   ///
+}
+///
+alias int CURLMoption;
+
+
+/**
+ * Name:    curl_multi_setopt()
+ *
+ * Desc:    Sets options for the multi handle.
+ *
+ * Returns: CURLM error code.
+ */
+extern (C) CURLMcode  curl_multi_setopt(CURLM *multi_handle, CURLMoption option,...);
+
+
+/**
+ * Name:    curl_multi_assign()
+ *
+ * Desc:    This function sets an association in the multi handle between the
+ *          given socket and a private pointer of the application. This is
+ *          (only) useful for curl_multi_socket uses.
+ *
+ * Returns: CURLM error code.
+ */
+extern (C) CURLMcode  curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd, void *sockp);
diff --git a/libphobos/src/etc/c/sqlite3.d b/libphobos/src/etc/c/sqlite3.d
new file mode 100644
index 00000000000..2176c1dded5
--- /dev/null
+++ b/libphobos/src/etc/c/sqlite3.d
@@ -0,0 +1,2126 @@
+module etc.c.sqlite3;
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the SQLite library
+** presents to client programs.  If a C-function, structure, datatype,
+** or constant definition does not appear in this file, then it is
+** not a published API of SQLite, is subject to change without
+** notice, and should not be referenced by programs that use SQLite.
+**
+** Some of the definitions that are in this file are marked as
+** "experimental".  Experimental interfaces are normally new
+** features recently added to SQLite.  We do not anticipate changes
+** to experimental interfaces but reserve the right to make minor changes
+** if experience from use "in the wild" suggest such changes are prudent.
+**
+** The official C-language API documentation for SQLite is derived
+** from comments in this file.  This file is the authoritative source
+** on how SQLite interfaces are suppose to operate.
+**
+** The name of this file under configuration management is "sqlite.h.in".
+** The makefile makes some minor changes to this file (such as inserting
+** the version number) and changes its name to "sqlite3.h" as
+** part of the build process.
+*/
+
+import core.stdc.stdarg : va_list;
+
+extern (C) __gshared nothrow:
+
+/**
+** CAPI3REF: Compile-Time Library Version Numbers
+*/
+enum SQLITE_VERSION = "3.10.2";
+/// Ditto
+enum SQLITE_VERSION_NUMBER = 3010002;
+/// Ditto
+enum SQLITE_SOURCE_ID = "2016-01-20 15:27:19 17efb4209f97fb4971656086b138599a91a75ff9";
+
+/**
+** CAPI3REF: Run-Time Library Version Numbers
+*/
+extern immutable(char)* sqlite3_version;
+/// Ditto
+immutable(char)* sqlite3_libversion();
+/// Ditto
+immutable(char)* sqlite3_sourceid();
+/// Ditto
+int sqlite3_libversion_number();
+
+/**
+** CAPI3REF: Run-Time Library Compilation Options Diagnostics
+*/
+int sqlite3_compileoption_used(const char *zOptName);
+/// Ditto
+immutable(char)* sqlite3_compileoption_get(int N);
+
+/**
+** CAPI3REF: Test To See If The Library Is Threadsafe
+*/
+int sqlite3_threadsafe();
+
+/**
+** CAPI3REF: Database Connection Handle
+*/
+struct sqlite3;
+
+///
+alias long sqlite3_int64;
+///
+alias ulong sqlite3_uint64;
+
+/**
+** CAPI3REF: Closing A Database Connection
+**
+*/
+int sqlite3_close(sqlite3 *);
+int sqlite3_close_v2(sqlite3*);
+
+/**
+** The type for a callback function.
+** This is legacy and deprecated.  It is included for historical
+** compatibility and is not documented.
+*/
+alias sqlite3_callback = int function (void*,int,char**, char**);
+
+/**
+** CAPI3REF: One-Step Query Execution Interface
+*/
+int sqlite3_exec(
+    sqlite3*,                                         /** An open database */
+    const(char)*sql,                                  /** SQL to be evaluated */
+    int function (void*,int,char**,char**) callback,  /** Callback function */
+    void *,                                           /** 1st argument to callback */
+    char **errmsg                                     /** Error msg written here */
+);
+
+/**
+** CAPI3REF: Result Codes
+*/
+enum
+{
+    SQLITE_OK          = 0,    /** Successful result */
+/* beginning-of-error-codes */
+/// Ditto
+    SQLITE_ERROR       = 1,    /** SQL error or missing database */
+    SQLITE_INTERNAL    = 2,    /** Internal logic error in SQLite */
+    SQLITE_PERM        = 3,    /** Access permission denied */
+    SQLITE_ABORT       = 4,    /** Callback routine requested an abort */
+    SQLITE_BUSY        = 5,    /** The database file is locked */
+    SQLITE_LOCKED      = 6,    /** A table in the database is locked */
+    SQLITE_NOMEM       = 7,    /** A malloc() failed */
+    SQLITE_READONLY    = 8,    /** Attempt to write a readonly database */
+    SQLITE_INTERRUPT   = 9,    /** Operation terminated by sqlite3_interrupt()*/
+    SQLITE_IOERR       = 10,   /** Some kind of disk I/O error occurred */
+    SQLITE_CORRUPT     = 11,   /** The database disk image is malformed */
+    SQLITE_NOTFOUND    = 12,   /** Unknown opcode in sqlite3_file_control() */
+    SQLITE_FULL        = 13,   /** Insertion failed because database is full */
+    SQLITE_CANTOPEN    = 14,   /** Unable to open the database file */
+    SQLITE_PROTOCOL    = 15,   /** Database lock protocol error */
+    SQLITE_EMPTY       = 16,   /** Database is empty */
+    SQLITE_SCHEMA      = 17,   /** The database schema changed */
+    SQLITE_TOOBIG      = 18,   /** String or BLOB exceeds size limit */
+    SQLITE_CONSTRAINT  = 19,   /** Abort due to constraint violation */
+    SQLITE_MISMATCH    = 20,   /** Data type mismatch */
+    SQLITE_MISUSE      = 21,   /** Library used incorrectly */
+    SQLITE_NOLFS       = 22,   /** Uses OS features not supported on host */
+    SQLITE_AUTH        = 23,   /** Authorization denied */
+    SQLITE_FORMAT      = 24,   /** Auxiliary database format error */
+    SQLITE_RANGE       = 25,   /** 2nd parameter to sqlite3_bind out of range */
+    SQLITE_NOTADB      = 26,   /** File opened that is not a database file */
+    SQLITE_NOTICE      = 27,
+    SQLITE_WARNING     = 28,
+    SQLITE_ROW         = 100,  /** sqlite3_step() has another row ready */
+    SQLITE_DONE        = 101  /** sqlite3_step() has finished executing */
+}
+/* end-of-error-codes */
+
+/**
+** CAPI3REF: Extended Result Codes
+*/
+enum
+{
+    SQLITE_IOERR_READ              = (SQLITE_IOERR | (1<<8)),
+    SQLITE_IOERR_SHORT_READ        = (SQLITE_IOERR | (2<<8)),
+    SQLITE_IOERR_WRITE             = (SQLITE_IOERR | (3<<8)),
+    SQLITE_IOERR_FSYNC             = (SQLITE_IOERR | (4<<8)),
+    SQLITE_IOERR_DIR_FSYNC         = (SQLITE_IOERR | (5<<8)),
+    SQLITE_IOERR_TRUNCATE          = (SQLITE_IOERR | (6<<8)),
+    SQLITE_IOERR_FSTAT             = (SQLITE_IOERR | (7<<8)),
+    SQLITE_IOERR_UNLOCK            = (SQLITE_IOERR | (8<<8)),
+    SQLITE_IOERR_RDLOCK            = (SQLITE_IOERR | (9<<8)),
+    SQLITE_IOERR_DELETE            = (SQLITE_IOERR | (10<<8)),
+    SQLITE_IOERR_BLOCKED           = (SQLITE_IOERR | (11<<8)),
+    SQLITE_IOERR_NOMEM             = (SQLITE_IOERR | (12<<8)),
+    SQLITE_IOERR_ACCESS            = (SQLITE_IOERR | (13<<8)),
+    SQLITE_IOERR_CHECKRESERVEDLOCK = (SQLITE_IOERR | (14<<8)),
+    SQLITE_IOERR_LOCK              = (SQLITE_IOERR | (15<<8)),
+    SQLITE_IOERR_CLOSE             = (SQLITE_IOERR | (16<<8)),
+    SQLITE_IOERR_DIR_CLOSE         = (SQLITE_IOERR | (17<<8)),
+    SQLITE_IOERR_SHMOPEN           = (SQLITE_IOERR | (18<<8)),
+    SQLITE_IOERR_SHMSIZE           = (SQLITE_IOERR | (19<<8)),
+    SQLITE_IOERR_SHMLOCK           = (SQLITE_IOERR | (20<<8)),
+    SQLITE_IOERR_SHMMAP            = (SQLITE_IOERR | (21<<8)),
+    SQLITE_IOERR_SEEK              = (SQLITE_IOERR | (22<<8)),
+    SQLITE_IOERR_DELETE_NOENT      = (SQLITE_IOERR | (23<<8)),
+    SQLITE_IOERR_MMAP              = (SQLITE_IOERR | (24<<8)),
+    SQLITE_LOCKED_SHAREDCACHE      = (SQLITE_LOCKED |  (1<<8)),
+    SQLITE_BUSY_RECOVERY           = (SQLITE_BUSY   |  (1<<8)),
+    SQLITE_CANTOPEN_NOTEMPDIR      = (SQLITE_CANTOPEN | (1<<8)),
+    SQLITE_IOERR_GETTEMPPATH       = (SQLITE_IOERR | (25<<8)),
+    SQLITE_IOERR_CONVPATH          = (SQLITE_IOERR | (26<<8)),
+    SQLITE_BUSY_SNAPSHOT           = (SQLITE_BUSY   |  (2<<8)),
+    SQLITE_CANTOPEN_ISDIR          = (SQLITE_CANTOPEN | (2<<8)),
+    SQLITE_CANTOPEN_FULLPATH       = (SQLITE_CANTOPEN | (3<<8)),
+    SQLITE_CANTOPEN_CONVPATH       = (SQLITE_CANTOPEN | (4<<8)),
+    SQLITE_CORRUPT_VTAB            = (SQLITE_CORRUPT | (1<<8)),
+    SQLITE_READONLY_RECOVERY       = (SQLITE_READONLY | (1<<8)),
+    SQLITE_READONLY_CANTLOCK       = (SQLITE_READONLY | (2<<8)),
+    SQLITE_READONLY_ROLLBACK       = (SQLITE_READONLY | (3<<8)),
+    SQLITE_READONLY_DBMOVED        = (SQLITE_READONLY | (4<<8)),
+    SQLITE_ABORT_ROLLBACK          = (SQLITE_ABORT | (2<<8)),
+    SQLITE_CONSTRAINT_CHECK        = (SQLITE_CONSTRAINT | (1<<8)),
+    SQLITE_CONSTRAINT_COMMITHOOK   = (SQLITE_CONSTRAINT | (2<<8)),
+    SQLITE_CONSTRAINT_FOREIGNKEY   = (SQLITE_CONSTRAINT | (3<<8)),
+    SQLITE_CONSTRAINT_FUNCTION     = (SQLITE_CONSTRAINT | (4<<8)),
+    SQLITE_CONSTRAINT_NOTNULL      = (SQLITE_CONSTRAINT | (5<<8)),
+    SQLITE_CONSTRAINT_PRIMARYKEY   = (SQLITE_CONSTRAINT | (6<<8)),
+    SQLITE_CONSTRAINT_TRIGGER      = (SQLITE_CONSTRAINT | (7<<8)),
+    SQLITE_CONSTRAINT_UNIQUE       = (SQLITE_CONSTRAINT | (8<<8)),
+    SQLITE_CONSTRAINT_VTAB         = (SQLITE_CONSTRAINT | (9<<8)),
+    SQLITE_CONSTRAINT_ROWID        = (SQLITE_CONSTRAINT |(10<<8)),
+    SQLITE_NOTICE_RECOVER_WAL      = (SQLITE_NOTICE | (1<<8)),
+    SQLITE_NOTICE_RECOVER_ROLLBACK = (SQLITE_NOTICE | (2<<8)),
+    SQLITE_WARNING_AUTOINDEX       = (SQLITE_WARNING | (1<<8)),
+    SQLITE_AUTH_USER               = (SQLITE_AUTH | (1<<8))
+}
+
+/**
+** CAPI3REF: Flags For File Open Operations
+*/
+enum
+{
+    SQLITE_OPEN_READONLY         = 0x00000001,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_READWRITE        = 0x00000002,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_CREATE           = 0x00000004,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_DELETEONCLOSE    = 0x00000008,  /** VFS only */
+    SQLITE_OPEN_EXCLUSIVE        = 0x00000010,  /** VFS only */
+    SQLITE_OPEN_AUTOPROXY        = 0x00000020,  /** VFS only */
+    SQLITE_OPEN_URI              = 0x00000040,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_MEMORY           = 0x00000080,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_MAIN_DB          = 0x00000100,  /** VFS only */
+    SQLITE_OPEN_TEMP_DB          = 0x00000200,  /** VFS only */
+    SQLITE_OPEN_TRANSIENT_DB     = 0x00000400,  /** VFS only */
+    SQLITE_OPEN_MAIN_JOURNAL     = 0x00000800,  /** VFS only */
+    SQLITE_OPEN_TEMP_JOURNAL     = 0x00001000,  /** VFS only */
+    SQLITE_OPEN_SUBJOURNAL       = 0x00002000,  /** VFS only */
+    SQLITE_OPEN_MASTER_JOURNAL   = 0x00004000,  /** VFS only */
+    SQLITE_OPEN_NOMUTEX          = 0x00008000,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_FULLMUTEX        = 0x00010000,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_SHAREDCACHE      = 0x00020000,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_PRIVATECACHE     = 0x00040000,  /** Ok for sqlite3_open_v2() */
+    SQLITE_OPEN_WAL              = 0x00080000  /** VFS only */
+}
+
+/**
+** CAPI3REF: Device Characteristics
+*/
+enum
+{
+    SQLITE_IOCAP_ATOMIC                 = 0x00000001,
+    SQLITE_IOCAP_ATOMIC512              = 0x00000002,
+    SQLITE_IOCAP_ATOMIC1K               = 0x00000004,
+    SQLITE_IOCAP_ATOMIC2K               = 0x00000008,
+    SQLITE_IOCAP_ATOMIC4K               = 0x00000010,
+    SQLITE_IOCAP_ATOMIC8K               = 0x00000020,
+    SQLITE_IOCAP_ATOMIC16K              = 0x00000040,
+    SQLITE_IOCAP_ATOMIC32K              = 0x00000080,
+    SQLITE_IOCAP_ATOMIC64K              = 0x00000100,
+    SQLITE_IOCAP_SAFE_APPEND            = 0x00000200,
+    SQLITE_IOCAP_SEQUENTIAL             = 0x00000400,
+    SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN  = 0x00000800,
+    SQLITE_IOCAP_POWERSAFE_OVERWRITE    = 0x00001000,
+    SQLITE_IOCAP_IMMUTABLE              = 0x00002000
+}
+
+/**
+** CAPI3REF: File Locking Levels
+*/
+enum
+{
+    SQLITE_LOCK_NONE          = 0,
+    SQLITE_LOCK_SHARED        = 1,
+    SQLITE_LOCK_RESERVED      = 2,
+    SQLITE_LOCK_PENDING       = 3,
+    SQLITE_LOCK_EXCLUSIVE     = 4
+}
+
+/**
+** CAPI3REF: Synchronization Type Flags
+*/
+enum
+{
+    SQLITE_SYNC_NORMAL        = 0x00002,
+    SQLITE_SYNC_FULL          = 0x00003,
+    SQLITE_SYNC_DATAONLY      = 0x00010
+}
+
+/**
+** CAPI3REF: OS Interface Open File Handle
+*/
+struct sqlite3_file
+{
+    const(sqlite3_io_methods)*pMethods;  /* Methods for an open file */
+}
+
+/**
+** CAPI3REF: OS Interface File Virtual Methods Object
+*/
+
+struct sqlite3_io_methods
+{
+    int iVersion;
+    int  function (sqlite3_file*) xClose;
+    int  function (sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) xRead;
+    int  function (sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) xWrite;
+    int  function (sqlite3_file*, sqlite3_int64 size) xTruncate;
+    int  function (sqlite3_file*, int flags) xSync;
+    int  function (sqlite3_file*, sqlite3_int64 *pSize) xFileSize;
+    int  function (sqlite3_file*, int) xLock;
+    int  function (sqlite3_file*, int) xUnlock;
+    int  function (sqlite3_file*, int *pResOut) xCheckReservedLock;
+    int  function (sqlite3_file*, int op, void *pArg) xFileControl;
+    int  function (sqlite3_file*) xSectorSize;
+    int  function (sqlite3_file*) xDeviceCharacteristics;
+    /* Methods above are valid for version 1 */
+    int  function (sqlite3_file*, int iPg, int pgsz, int, void **) xShmMap;
+    int  function (sqlite3_file*, int offset, int n, int flags) xShmLock;
+    void  function (sqlite3_file*) xShmBarrier;
+    int  function (sqlite3_file*, int deleteFlag) xShmUnmap;
+    /* Methods above are valid for version 2 */
+    /* Additional methods may be added in future releases */
+    int function (sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp) xFetch;
+    int function (sqlite3_file*, sqlite3_int64 iOfst, void *p) xUnfetch;
+}
+
+/**
+** CAPI3REF: Standard File Control Opcodes
+*/
+enum
+{
+    SQLITE_FCNTL_LOCKSTATE           = 1,
+    SQLITE_GET_LOCKPROXYFILE         = 2,
+    SQLITE_SET_LOCKPROXYFILE         = 3,
+    SQLITE_LAST_ERRNO                = 4,
+    SQLITE_FCNTL_SIZE_HINT           = 5,
+    SQLITE_FCNTL_CHUNK_SIZE          = 6,
+    SQLITE_FCNTL_FILE_POINTER        = 7,
+    SQLITE_FCNTL_SYNC_OMITTED        = 8,
+    SQLITE_FCNTL_WIN32_AV_RETRY      = 9,
+    SQLITE_FCNTL_PERSIST_WAL         = 10,
+    SQLITE_FCNTL_OVERWRITE           = 11,
+    SQLITE_FCNTL_VFSNAME             = 12,
+    SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13,
+    SQLITE_FCNTL_PRAGMA              = 14,
+    SQLITE_FCNTL_BUSYHANDLER         = 15,
+    SQLITE_FCNTL_TEMPFILENAME        = 16,
+    SQLITE_FCNTL_MMAP_SIZE           = 18,
+    SQLITE_FCNTL_TRACE               = 19,
+    SQLITE_FCNTL_HAS_MOVED           = 20,
+    SQLITE_FCNTL_SYNC                = 21,
+    SQLITE_FCNTL_COMMIT_PHASETWO     = 22,
+    SQLITE_FCNTL_WIN32_SET_HANDLE    = 23,
+    SQLITE_FCNTL_WAL_BLOCK           = 24,
+    SQLITE_FCNTL_ZIPVFS              = 25,
+    SQLITE_FCNTL_RBU                 = 26,
+    SQLITE_FCNTL_VFS_POINTER         = 27,
+}
+
+/**
+** CAPI3REF: Mutex Handle
+*/
+struct sqlite3_mutex;
+
+/**
+** CAPI3REF: OS Interface Object
+*/
+
+alias void * function() xDlSymReturn;
+/// Ditto
+alias void function() sqlite3_syscall_ptr;
+
+struct sqlite3_vfs
+{
+    int iVersion;            /** Structure version number (currently 2) */
+    int szOsFile;            /** Size of subclassed sqlite3_file */
+    int mxPathname;          /** Maximum file pathname length */
+    sqlite3_vfs *pNext;      /** Next registered VFS */
+    const(char)*zName;       /** Name of this virtual file system */
+    void *pAppData;          /** Pointer to application-specific data */
+    int function (sqlite3_vfs*, const char *zName, sqlite3_file*,
+               int flags, int *pOutFlags) xOpen;
+    int  function (sqlite3_vfs*, const char *zName, int syncDir) xDelete;
+    int  function (sqlite3_vfs*, const char *zName, int flags, int *pResOut) xAccess;
+    int  function (sqlite3_vfs*, const char *zName, int nOut, char *zOut) xFullPathname;
+    void* function (sqlite3_vfs*, const char *zFilename) xDlOpen;
+    void  function (sqlite3_vfs*, int nByte, char *zErrMsg) xDlError;
+    xDlSymReturn function (sqlite3_vfs*,void*, const char *zSymbol) *xDlSym;
+    void  function (sqlite3_vfs*, void*) xDlClose;
+    int  function (sqlite3_vfs*, int nByte, char *zOut) xRandomness;
+    int  function (sqlite3_vfs*, int microseconds) xSleep;
+    int  function (sqlite3_vfs*, double*) xCurrentTime;
+    int  function (sqlite3_vfs*, int, char *) xGetLastError;
+    /*
+    ** The methods above are in version 1 of the sqlite_vfs object
+    ** definition.  Those that follow are added in version 2 or later
+    */
+    int  function (sqlite3_vfs*, sqlite3_int64*) xCurrentTimeInt64;
+    /*
+    ** The methods above are in versions 1 and 2 of the sqlite_vfs object.
+    ** Those below are for version 3 and greater.
+    */
+    int function(sqlite3_vfs*, const char * zName, sqlite3_syscall_ptr) xSetSystemCall;
+    sqlite3_syscall_ptr function(sqlite3_vfs*, const char * zName) xGetSystemCall;
+    const(char)* function(sqlite3_vfs*, const char * zName) xNextSystemCall;
+    /*
+    ** The methods above are in versions 1 through 3 of the sqlite_vfs object.
+    ** New fields may be appended in figure versions.  The iVersion
+    ** value will increment whenever this happens.
+    */
+}
+
+/**
+** CAPI3REF: Flags for the xAccess VFS method
+*/
+enum
+{
+    SQLITE_ACCESS_EXISTS    = 0,
+
+    SQLITE_ACCESS_READWRITE = 1,   /** Used by PRAGMA temp_store_directory */
+    SQLITE_ACCESS_READ      = 2   /** Unused */
+}
+
+/**
+** CAPI3REF: Flags for the xShmLock VFS method
+*/
+enum
+{
+    SQLITE_SHM_UNLOCK       = 1,
+    SQLITE_SHM_LOCK         = 2,
+    SQLITE_SHM_SHARED       = 4,
+    SQLITE_SHM_EXCLUSIVE    = 8
+}
+
+/**
+** CAPI3REF: Maximum xShmLock index
+*/
+enum SQLITE_SHM_NLOCK        = 8;
+
+
+/**
+** CAPI3REF: Initialize The SQLite Library
+*/
+int sqlite3_initialize();
+/// Ditto
+int sqlite3_shutdown();
+/// Ditto
+int sqlite3_os_init();
+/// Ditto
+int sqlite3_os_end();
+
+/**
+** CAPI3REF: Configuring The SQLite Library
+*/
+int sqlite3_config(int, ...);
+
+/**
+** CAPI3REF: Configure database connections
+*/
+int sqlite3_db_config(sqlite3*, int op, ...);
+
+/**
+** CAPI3REF: Memory Allocation Routines
+*/
+struct sqlite3_mem_methods
+{
+    void* function (int) xMalloc;         /** Memory allocation function */
+    void function (void*) xFree;          /** Free a prior allocation */
+    void* function (void*,int) xRealloc;  /** Resize an allocation */
+    int function (void*) xSize;           /** Return the size of an allocation */
+    int function (int) xRoundup;          /** Round up request size to allocation size */
+    int function (void*) xInit;           /** Initialize the memory allocator */
+    void function (void*) xShutdown;      /** Deinitialize the memory allocator */
+    void *pAppData;                       /** Argument to xInit() and xShutdown() */
+}
+
+/**
+** CAPI3REF: Configuration Options
+*/
+enum
+{
+    SQLITE_CONFIG_SINGLETHREAD         = 1,  /** nil */
+    SQLITE_CONFIG_MULTITHREAD          = 2,  /** nil */
+    SQLITE_CONFIG_SERIALIZED           = 3,  /** nil */
+    SQLITE_CONFIG_MALLOC               = 4,  /** sqlite3_mem_methods* */
+    SQLITE_CONFIG_GETMALLOC            = 5,  /** sqlite3_mem_methods* */
+    SQLITE_CONFIG_SCRATCH              = 6,  /** void*, int sz, int N */
+    SQLITE_CONFIG_PAGECACHE            = 7,  /** void*, int sz, int N */
+    SQLITE_CONFIG_HEAP                 = 8,  /** void*, int nByte, int min */
+    SQLITE_CONFIG_MEMSTATUS            = 9,  /** boolean */
+    SQLITE_CONFIG_MUTEX                = 10,  /** sqlite3_mutex_methods* */
+    SQLITE_CONFIG_GETMUTEX             = 11,  /** sqlite3_mutex_methods* */
+/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
+    SQLITE_CONFIG_LOOKASIDE            = 13,  /** int int */
+    SQLITE_CONFIG_PCACHE               = 14,  /** sqlite3_pcache_methods* */
+    SQLITE_CONFIG_GETPCACHE            = 15,  /** sqlite3_pcache_methods* */
+    SQLITE_CONFIG_LOG                  = 16,  /** xFunc, void* */
+    SQLITE_CONFIG_URI                  = 17,
+    SQLITE_CONFIG_PCACHE2              = 18,
+    SQLITE_CONFIG_GETPCACHE2           = 19,
+    SQLITE_CONFIG_COVERING_INDEX_SCAN  = 20,
+    SQLITE_CONFIG_SQLLOG               = 21,
+    SQLITE_CONFIG_MMAP_SIZE            = 22,
+    SQLITE_CONFIG_WIN32_HEAPSIZE       = 23,
+    SQLITE_CONFIG_PCACHE_HDRSZ         = 24,
+    SQLITE_CONFIG_PMASZ                = 25,
+}
+
+/**
+** CAPI3REF: Database Connection Configuration Options
+*/
+enum
+{
+    SQLITE_DBCONFIG_LOOKASIDE      = 1001,  /** void* int int */
+    SQLITE_DBCONFIG_ENABLE_FKEY    = 1002,  /** int int* */
+    SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003  /** int int* */
+}
+
+
+/**
+** CAPI3REF: Enable Or Disable Extended Result Codes
+*/
+int sqlite3_extended_result_codes(sqlite3*, int onoff);
+
+/**
+** CAPI3REF: Last Insert Rowid
+*/
+sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
+
+/**
+** CAPI3REF: Count The Number Of Rows Modified
+*/
+int sqlite3_changes(sqlite3*);
+
+/**
+** CAPI3REF: Total Number Of Rows Modified
+*/
+int sqlite3_total_changes(sqlite3*);
+
+/**
+** CAPI3REF: Interrupt A Long-Running Query
+*/
+void sqlite3_interrupt(sqlite3*);
+
+/**
+** CAPI3REF: Determine If An SQL Statement Is Complete
+*/
+int sqlite3_complete(const char *sql);
+/// Ditto
+int sqlite3_complete16(const void *sql);
+
+/**
+** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
+*/
+int sqlite3_busy_handler(sqlite3*, int function (void*,int), void*);
+
+/**
+** CAPI3REF: Set A Busy Timeout
+*/
+int sqlite3_busy_timeout(sqlite3*, int ms);
+
+/**
+** CAPI3REF: Convenience Routines For Running Queries
+*/
+int sqlite3_get_table(
+    sqlite3 *db,          /** An open database */
+    const(char)*zSql,     /** SQL to be evaluated */
+    char ***pazResult,    /** Results of the query */
+    int *pnRow,           /** Number of result rows written here */
+    int *pnColumn,        /** Number of result columns written here */
+    char **pzErrmsg       /** Error msg written here */
+);
+///
+void sqlite3_free_table(char **result);
+
+/**
+** CAPI3REF: Formatted String Printing Functions
+*/
+char *sqlite3_mprintf(const char*,...);
+char *sqlite3_vmprintf(const char*, va_list);
+char *sqlite3_snprintf(int,char*,const char*, ...);
+char *sqlite3_vsnprintf(int,char*,const char*, va_list);
+
+/**
+** CAPI3REF: Memory Allocation Subsystem
+*/
+void *sqlite3_malloc(int);
+/// Ditto
+void *sqlite3_malloc64(sqlite3_uint64);
+/// Ditto
+void *sqlite3_realloc(void*, int);
+/// Ditto
+void *sqlite3_realloc64(void*, sqlite3_uint64);
+/// Ditto
+void sqlite3_free(void*);
+/// Ditto
+sqlite3_uint64 sqlite3_msize(void*);
+
+/**
+** CAPI3REF: Memory Allocator Statistics
+*/
+sqlite3_int64 sqlite3_memory_used();
+sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
+
+/**
+** CAPI3REF: Pseudo-Random Number Generator
+*/
+void sqlite3_randomness(int N, void *P);
+
+/**
+** CAPI3REF: Compile-Time Authorization Callbacks
+*/
+int sqlite3_set_authorizer(
+    sqlite3*,
+    int function (void*,int,const char*,const char*,const char*,const char*) xAuth,
+    void *pUserData
+);
+
+/**
+** CAPI3REF: Authorizer Return Codes
+*/
+enum
+{
+    SQLITE_DENY   = 1,   /** Abort the SQL statement with an error */
+    SQLITE_IGNORE = 2   /** Don't allow access, but don't generate an error */
+}
+
+/**
+** CAPI3REF: Authorizer Action Codes
+*/
+/******************************************* 3rd ************ 4th ***********/
+enum
+{
+    SQLITE_CREATE_INDEX         =  1,   /** Index Name      Table Name      */
+    SQLITE_CREATE_TABLE         =  2,   /** Table Name      NULL            */
+    SQLITE_CREATE_TEMP_INDEX    =  3,   /** Index Name      Table Name      */
+    SQLITE_CREATE_TEMP_TABLE    =  4,   /** Table Name      NULL            */
+    SQLITE_CREATE_TEMP_TRIGGER  =  5,   /** Trigger Name    Table Name      */
+    SQLITE_CREATE_TEMP_VIEW     =  6,   /** View Name       NULL            */
+    SQLITE_CREATE_TRIGGER       =  7,   /** Trigger Name    Table Name      */
+    SQLITE_CREATE_VIEW          =  8,   /** View Name       NULL            */
+    SQLITE_DELETE               =  9,   /** Table Name      NULL            */
+    SQLITE_DROP_INDEX           = 10,   /** Index Name      Table Name      */
+    SQLITE_DROP_TABLE           = 11,   /** Table Name      NULL            */
+    SQLITE_DROP_TEMP_INDEX      = 12,   /** Index Name      Table Name      */
+    SQLITE_DROP_TEMP_TABLE      = 13,   /** Table Name      NULL            */
+    SQLITE_DROP_TEMP_TRIGGER    = 14,   /** Trigger Name    Table Name      */
+    SQLITE_DROP_TEMP_VIEW       = 15,   /** View Name       NULL            */
+    SQLITE_DROP_TRIGGER         = 16,   /** Trigger Name    Table Name      */
+    SQLITE_DROP_VIEW            = 17,   /** View Name       NULL            */
+    SQLITE_INSERT               = 18,   /** Table Name      NULL            */
+    SQLITE_PRAGMA               = 19,   /** Pragma Name     1st arg or NULL */
+    SQLITE_READ                 = 20,   /** Table Name      Column Name     */
+    SQLITE_SELECT               = 21,   /** NULL            NULL            */
+    SQLITE_TRANSACTION          = 22,   /** Operation       NULL            */
+    SQLITE_UPDATE               = 23,   /** Table Name      Column Name     */
+    SQLITE_ATTACH               = 24,   /** Filename        NULL            */
+    SQLITE_DETACH               = 25,   /** Database Name   NULL            */
+    SQLITE_ALTER_TABLE          = 26,   /** Database Name   Table Name      */
+    SQLITE_REINDEX              = 27,   /** Index Name      NULL            */
+    SQLITE_ANALYZE              = 28,   /** Table Name      NULL            */
+    SQLITE_CREATE_VTABLE        = 29,   /** Table Name      Module Name     */
+    SQLITE_DROP_VTABLE          = 30,   /** Table Name      Module Name     */
+    SQLITE_FUNCTION             = 31,   /** NULL            Function Name   */
+    SQLITE_SAVEPOINT            = 32,   /** Operation       Savepoint Name  */
+    SQLITE_COPY                 =  0,   /** No longer used */
+    SQLITE_RECURSIVE            = 33
+}
+
+/**
+** CAPI3REF: Tracing And Profiling Functions
+*/
+void *sqlite3_trace(sqlite3*, void function (void*,const char*) xTrace, void*);
+/// Ditto
+void *sqlite3_profile(sqlite3*, void function (void*,const char*,sqlite3_uint64) xProfile, void*);
+
+/**
+** CAPI3REF: Query Progress Callbacks
+*/
+void sqlite3_progress_handler(sqlite3*, int, int function (void*), void*);
+
+/**
+** CAPI3REF: Opening A New Database Connection
+*/
+int sqlite3_open(
+    const(char)*filename,   /** Database filename (UTF-8) */
+    sqlite3 **ppDb          /** OUT: SQLite db handle */
+);
+/// Ditto
+int sqlite3_open16(
+    const(void)*filename,   /** Database filename (UTF-16) */
+    sqlite3 **ppDb          /** OUT: SQLite db handle */
+);
+/// Ditto
+int sqlite3_open_v2(
+    const(char)*filename,   /** Database filename (UTF-8) */
+    sqlite3 **ppDb,         /** OUT: SQLite db handle */
+    int flags,              /** Flags */
+    const(char)*zVfs        /** Name of VFS module to use */
+);
+
+/*
+** CAPI3REF: Obtain Values For URI Parameters
+*/
+const(char)* sqlite3_uri_parameter(const(char)* zFilename, const(char)* zParam);
+/// Ditto
+int sqlite3_uri_boolean(const(char)* zFile, const(char)* zParam, int bDefault);
+/// Ditto
+sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
+
+/**
+** CAPI3REF: Error Codes And Messages
+*/
+int sqlite3_errcode(sqlite3 *db);
+/// Ditto
+int sqlite3_extended_errcode(sqlite3 *db);
+/// Ditto
+const(char)* sqlite3_errmsg(sqlite3*);
+/// Ditto
+const(void)* sqlite3_errmsg16(sqlite3*);
+/// Ditto
+const(char)* sqlite3_errstr(int);
+
+/**
+** CAPI3REF: SQL Statement Object
+*/
+struct sqlite3_stmt;
+
+/**
+** CAPI3REF: Run-time Limits
+*/
+int sqlite3_limit(sqlite3*, int id, int newVal);
+
+/**
+** CAPI3REF: Run-Time Limit Categories
+*/
+enum
+{
+    SQLITE_LIMIT_LENGTH                    = 0,
+    SQLITE_LIMIT_SQL_LENGTH                = 1,
+    SQLITE_LIMIT_COLUMN                    = 2,
+    SQLITE_LIMIT_EXPR_DEPTH                = 3,
+    SQLITE_LIMIT_COMPOUND_SELECT           = 4,
+    SQLITE_LIMIT_VDBE_OP                   = 5,
+    SQLITE_LIMIT_FUNCTION_ARG              = 6,
+    SQLITE_LIMIT_ATTACHED                  = 7,
+    SQLITE_LIMIT_LIKE_PATTERN_LENGTH       = 8,
+    SQLITE_LIMIT_VARIABLE_NUMBER           = 9,
+    SQLITE_LIMIT_TRIGGER_DEPTH             = 10,
+    SQLITE_LIMIT_WORKER_THREADS            = 11,
+}
+
+/**
+** CAPI3REF: Compiling An SQL Statement
+*/
+int sqlite3_prepare(
+    sqlite3 *db,            /** Database handle */
+    const(char)*zSql,       /** SQL statement, UTF-8 encoded */
+    int nByte,              /** Maximum length of zSql in bytes. */
+    sqlite3_stmt **ppStmt,  /** OUT: Statement handle */
+    const(char*)*pzTail     /** OUT: Pointer to unused portion of zSql */
+);
+/// Ditto
+int sqlite3_prepare_v2(
+    sqlite3 *db,            /** Database handle */
+    const(char)*zSql,       /** SQL statement, UTF-8 encoded */
+    int nByte,              /** Maximum length of zSql in bytes. */
+    sqlite3_stmt **ppStmt,  /** OUT: Statement handle */
+    const(char*)*pzTail     /** OUT: Pointer to unused portion of zSql */
+);
+/// Ditto
+int sqlite3_prepare16(
+    sqlite3 *db,            /** Database handle */
+    const(void)*zSql,       /** SQL statement, UTF-16 encoded */
+    int nByte,              /** Maximum length of zSql in bytes. */
+    sqlite3_stmt **ppStmt,  /** OUT: Statement handle */
+    const(void*)*pzTail     /** OUT: Pointer to unused portion of zSql */
+);
+/// Ditto
+int sqlite3_prepare16_v2(
+    sqlite3 *db,            /** Database handle */
+    const(void)*zSql,       /** SQL statement, UTF-16 encoded */
+    int nByte,              /** Maximum length of zSql in bytes. */
+    sqlite3_stmt **ppStmt,  /** OUT: Statement handle */
+    const(void*)*pzTail     /** OUT: Pointer to unused portion of zSql */
+);
+
+/**
+** CAPI3REF: Retrieving Statement SQL
+*/
+const(char)* sqlite3_sql(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Determine If An SQL Statement Writes The Database
+*/
+int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Determine If A Prepared Statement Has Been Reset
+*/
+int sqlite3_stmt_busy(sqlite3_stmt*);
+
+
+/**
+** CAPI3REF: Dynamically Typed Value Object
+*/
+struct sqlite3_value;
+
+/**
+** CAPI3REF: SQL Function Context Object
+*/
+struct sqlite3_context;
+
+/**
+** CAPI3REF: Binding Values To Prepared Statements
+*/
+int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void function (void*));
+/// Ditto
+int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,void function (void*));
+/// Ditto
+int sqlite3_bind_double(sqlite3_stmt*, int, double);
+/// Ditto
+int sqlite3_bind_int(sqlite3_stmt*, int, int);
+/// Ditto
+int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
+/// Ditto
+int sqlite3_bind_null(sqlite3_stmt*, int);
+/// Ditto
+int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void function (void*));
+/// Ditto
+int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void function (void*));
+/// Ditto
+int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,void function (void*), ubyte encoding);
+/// Ditto
+int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
+/// Ditto
+int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
+/// Ditto
+int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64 n);
+
+/**
+** CAPI3REF: Number Of SQL Parameters
+*/
+int sqlite3_bind_parameter_count(sqlite3_stmt*);
+
+/**
+** CAPI3REF: Name Of A Host Parameter
+*/
+const(char)* sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+
+/**
+** CAPI3REF: Index Of A Parameter With A Given Name
+*/
+int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+
+/**
+** CAPI3REF: Reset All Bindings On A Prepared Statement
+*/
+int sqlite3_clear_bindings(sqlite3_stmt*);
+
+/**
+** CAPI3REF: Number Of Columns In A Result Set
+*/
+int sqlite3_column_count(sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Column Names In A Result Set
+*/
+const(char)* sqlite3_column_name(sqlite3_stmt*, int N);
+/// Ditto
+const(void)* sqlite3_column_name16(sqlite3_stmt*, int N);
+
+/**
+** CAPI3REF: Source Of Data In A Query Result
+*/
+const(char)* sqlite3_column_database_name(sqlite3_stmt*,int);
+/// Ditto
+const(void)* sqlite3_column_database_name16(sqlite3_stmt*,int);
+/// Ditto
+const(char)* sqlite3_column_table_name(sqlite3_stmt*,int);
+/// Ditto
+const (void)* sqlite3_column_table_name16(sqlite3_stmt*,int);
+/// Ditto
+const (char)* sqlite3_column_origin_name(sqlite3_stmt*,int);
+/// Ditto
+const (void)* sqlite3_column_origin_name16(sqlite3_stmt*,int);
+
+/**
+** CAPI3REF: Declared Datatype Of A Query Result
+*/
+const (char)* sqlite3_column_decltype(sqlite3_stmt*,int);
+/// Ditto
+const (void)* sqlite3_column_decltype16(sqlite3_stmt*,int);
+
+/**
+** CAPI3REF: Evaluate An SQL Statement
+*/
+int sqlite3_step(sqlite3_stmt*);
+
+/**
+** CAPI3REF: Number of columns in a result set
+*/
+int sqlite3_data_count(sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Fundamental Datatypes
+*/
+enum
+{
+    SQLITE_INTEGER  = 1,
+    SQLITE_FLOAT    = 2,
+    SQLITE_BLOB     = 4,
+    SQLITE_NULL     = 5,
+    SQLITE3_TEXT    = 3
+}
+
+/**
+** CAPI3REF: Result Values From A Query
+*/
+const (void)* sqlite3_column_blob(sqlite3_stmt*, int iCol);
+/// Ditto
+int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
+/// Ditto
+int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
+/// Ditto
+double sqlite3_column_double(sqlite3_stmt*, int iCol);
+/// Ditto
+int sqlite3_column_int(sqlite3_stmt*, int iCol);
+/// Ditto
+sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
+/// Ditto
+const (char)* sqlite3_column_text(sqlite3_stmt*, int iCol);
+/// Ditto
+const (void)* sqlite3_column_text16(sqlite3_stmt*, int iCol);
+/// Ditto
+int sqlite3_column_type(sqlite3_stmt*, int iCol);
+/// Ditto
+sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
+
+/**
+** CAPI3REF: Destroy A Prepared Statement Object
+*/
+int sqlite3_finalize(sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Reset A Prepared Statement Object
+*/
+int sqlite3_reset(sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Create Or Redefine SQL Functions
+*/
+int sqlite3_create_function(
+    sqlite3 *db,
+    const(char)*zFunctionName,
+    int nArg,
+    int eTextRep,
+    void *pApp,
+    void function (sqlite3_context*,int,sqlite3_value**) xFunc,
+    void function (sqlite3_context*,int,sqlite3_value**) xStep,
+    void function (sqlite3_context*) xFinal
+);
+/// Ditto
+int sqlite3_create_function16(
+    sqlite3 *db,
+    const(void)*zFunctionName,
+    int nArg,
+    int eTextRep,
+    void *pApp,
+    void function (sqlite3_context*,int,sqlite3_value**) xFunc,
+    void function (sqlite3_context*,int,sqlite3_value**) xStep,
+    void function (sqlite3_context*) xFinal
+);
+/// Ditto
+int sqlite3_create_function_v2(
+    sqlite3 *db,
+    const(char)*zFunctionName,
+    int nArg,
+    int eTextRep,
+    void *pApp,
+    void function (sqlite3_context*,int,sqlite3_value**) xFunc,
+    void function (sqlite3_context*,int,sqlite3_value**) xStep,
+    void function (sqlite3_context*) xFinal,
+    void function (void*) xDestroy
+);
+
+/**
+** CAPI3REF: Text Encodings
+**
+** These constant define integer codes that represent the various
+** text encodings supported by SQLite.
+*/
+enum
+{
+    SQLITE_UTF8           = 1,
+    SQLITE_UTF16LE        = 2,
+    SQLITE_UTF16BE        = 3
+}
+/// Ditto
+enum
+{
+    SQLITE_UTF16          = 4,    /** Use native byte order */
+    SQLITE_ANY            = 5,    /** sqlite3_create_function only */
+    SQLITE_UTF16_ALIGNED  = 8    /** sqlite3_create_collation only */
+}
+
+/**
+** CAPI3REF: Function Flags
+*/
+enum SQLITE_DETERMINISTIC = 0x800;
+
+/**
+** CAPI3REF: Deprecated Functions
+*/
+deprecated int sqlite3_aggregate_count(sqlite3_context*);
+deprecated int sqlite3_expired(sqlite3_stmt*);
+deprecated int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
+deprecated int sqlite3_global_recover();
+deprecated void sqlite3_thread_cleanup();
+deprecated int sqlite3_memory_alarm(void function(void*,sqlite3_int64,int),void*,sqlite3_int64);
+
+/**
+** CAPI3REF: Obtaining SQL Function Parameter Values
+*/
+const (void)* sqlite3_value_blob(sqlite3_value*);
+/// Ditto
+int sqlite3_value_bytes(sqlite3_value*);
+/// Ditto
+int sqlite3_value_bytes16(sqlite3_value*);
+/// Ditto
+double sqlite3_value_double(sqlite3_value*);
+/// Ditto
+int sqlite3_value_int(sqlite3_value*);
+/// Ditto
+sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
+/// Ditto
+const (char)* sqlite3_value_text(sqlite3_value*);
+/// Ditto
+const (void)* sqlite3_value_text16(sqlite3_value*);
+/// Ditto
+const (void)* sqlite3_value_text16le(sqlite3_value*);
+/// Ditto
+const (void)* sqlite3_value_text16be(sqlite3_value*);
+/// Ditto
+int sqlite3_value_type(sqlite3_value*);
+/// Ditto
+int sqlite3_value_numeric_type(sqlite3_value*);
+
+/*
+** CAPI3REF: Finding The Subtype Of SQL Values
+*/
+uint sqlite3_value_subtype(sqlite3_value*);
+
+/*
+** CAPI3REF: Copy And Free SQL Values
+*/
+sqlite3_value* sqlite3_value_dup(const sqlite3_value*);
+void sqlite3_value_free(sqlite3_value*);
+
+/**
+** CAPI3REF: Obtain Aggregate Function Context
+*/
+void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+
+/**
+** CAPI3REF: User Data For Functions
+*/
+void *sqlite3_user_data(sqlite3_context*);
+
+/**
+** CAPI3REF: Database Connection For Functions
+*/
+sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
+
+/**
+** CAPI3REF: Function Auxiliary Data
+*/
+void *sqlite3_get_auxdata(sqlite3_context*, int N);
+/// Ditto
+void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void function (void*));
+
+
+/**
+** CAPI3REF: Constants Defining Special Destructor Behavior
+*/
+alias void function (void*) sqlite3_destructor_type;
+/// Ditto
+enum
+{
+    SQLITE_STATIC      = (cast(sqlite3_destructor_type) 0),
+    SQLITE_TRANSIENT   = (cast (sqlite3_destructor_type) -1)
+}
+
+/**
+** CAPI3REF: Setting The Result Of An SQL Function
+*/
+void sqlite3_result_blob(sqlite3_context*, const void*, int, void function(void*));
+/// Ditto
+void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void function(void*));
+/// Ditto
+void sqlite3_result_double(sqlite3_context*, double);
+/// Ditto
+void sqlite3_result_error(sqlite3_context*, const char*, int);
+/// Ditto
+void sqlite3_result_error16(sqlite3_context*, const void*, int);
+/// Ditto
+void sqlite3_result_error_toobig(sqlite3_context*);
+/// Ditto
+void sqlite3_result_error_nomem(sqlite3_context*);
+/// Ditto
+void sqlite3_result_error_code(sqlite3_context*, int);
+/// Ditto
+void sqlite3_result_int(sqlite3_context*, int);
+/// Ditto
+void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
+/// Ditto
+void sqlite3_result_null(sqlite3_context*);
+/// Ditto
+void sqlite3_result_text(sqlite3_context*, const char*, int, void function(void*));
+/// Ditto
+void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64,void function(void*), ubyte encoding);
+/// Ditto
+void sqlite3_result_text16(sqlite3_context*, const void*, int, void function(void*));
+/// Ditto
+void sqlite3_result_text16le(sqlite3_context*, const void*, int, void function(void*));
+/// Ditto
+void sqlite3_result_text16be(sqlite3_context*, const void*, int, void function(void*));
+/// Ditto
+void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
+/// Ditto
+void sqlite3_result_zeroblob(sqlite3_context*, int n);
+/// Ditto
+int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
+
+/*
+** CAPI3REF: Setting The Subtype Of An SQL Function
+*/
+void sqlite3_result_subtype(sqlite3_context*,uint);
+
+/**
+** CAPI3REF: Define New Collating Sequences
+*/
+int sqlite3_create_collation(
+    sqlite3*,
+    const(char)*zName,
+    int eTextRep,
+    void *pArg,
+    int function (void*,int,const void*,int,const void*) xCompare
+);
+/// Ditto
+int sqlite3_create_collation_v2(
+    sqlite3*,
+    const(char)*zName,
+    int eTextRep,
+    void *pArg,
+    int function (void*,int,const void*,int,const void*) xCompare,
+    void function (void*) xDestroy
+);
+/// Ditto
+int sqlite3_create_collation16(
+    sqlite3*,
+    const(void)*zName,
+    int eTextRep,
+    void *pArg,
+    int function (void*,int,const void*,int,const void*) xCompare
+);
+
+/**
+** CAPI3REF: Collation Needed Callbacks
+*/
+int sqlite3_collation_needed(
+    sqlite3*,
+    void*,
+    void function (void*,sqlite3*,int eTextRep,const char*)
+);
+/// Ditto
+int sqlite3_collation_needed16(
+    sqlite3*,
+    void*,
+    void function (void*,sqlite3*,int eTextRep,const void*)
+);
+
+///
+int sqlite3_key(
+    sqlite3 *db,                   /** Database to be rekeyed */
+    const(void)*pKey, int nKey     /** The key */
+);
+/// Ditto
+int sqlite3_key_v2(
+    sqlite3 *db,                   /* Database to be rekeyed */
+    const(char)* zDbName,           /* Name of the database */
+    const(void)* pKey, int nKey     /* The key */
+);
+
+/**
+** Change the key on an open database.  If the current database is not
+** encrypted, this routine will encrypt it.  If pNew==0 or nNew==0, the
+** database is decrypted.
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+int sqlite3_rekey(
+    sqlite3 *db,                   /** Database to be rekeyed */
+    const(void)*pKey, int nKey     /** The new key */
+);
+int sqlite3_rekey_v2(
+    sqlite3 *db,                   /* Database to be rekeyed */
+    const(char)* zDbName,           /* Name of the database */
+    const(void)* pKey, int nKey     /* The new key */
+);
+
+/**
+** Specify the activation key for a SEE database.  Unless
+** activated, none of the SEE routines will work.
+*/
+void sqlite3_activate_see(
+    const(char)*zPassPhrase        /** Activation phrase */
+);
+
+/**
+** Specify the activation key for a CEROD database.  Unless
+** activated, none of the CEROD routines will work.
+*/
+void sqlite3_activate_cerod(
+    const(char)*zPassPhrase        /** Activation phrase */
+);
+
+/**
+** CAPI3REF: Suspend Execution For A Short Time
+*/
+int sqlite3_sleep(int);
+
+/**
+** CAPI3REF: Name Of The Folder Holding Temporary Files
+*/
+extern char *sqlite3_temp_directory;
+
+/**
+** CAPI3REF: Name Of The Folder Holding Database Files
+*/
+extern char *sqlite3_data_directory;
+
+/**
+** CAPI3REF: Test For Auto-Commit Mode
+*/
+int sqlite3_get_autocommit(sqlite3*);
+
+/**
+** CAPI3REF: Find The Database Handle Of A Prepared Statement
+*/
+sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+
+/**
+** CAPI3REF: Return The Filename For A Database Connection
+*/
+const(char)* sqlite3_db_filename(sqlite3 *db, const char* zDbName);
+
+/**
+** CAPI3REF: Determine if a database is read-only
+*/
+int sqlite3_db_readonly(sqlite3 *db, const char * zDbName);
+
+/*
+** CAPI3REF: Find the next prepared statement
+*/
+sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
+
+/**
+** CAPI3REF: Commit And Rollback Notification Callbacks
+*/
+void *sqlite3_commit_hook(sqlite3*, int function (void*), void*);
+/// Ditto
+void *sqlite3_rollback_hook(sqlite3*, void function (void *), void*);
+
+/**
+** CAPI3REF: Data Change Notification Callbacks
+*/
+void *sqlite3_update_hook(
+    sqlite3*,
+    void function (void *,int ,char *, char *, sqlite3_int64),
+    void*
+);
+
+/**
+** CAPI3REF: Enable Or Disable Shared Pager Cache
+*/
+int sqlite3_enable_shared_cache(int);
+
+/**
+** CAPI3REF: Attempt To Free Heap Memory
+*/
+int sqlite3_release_memory(int);
+
+/**
+** CAPI3REF: Free Memory Used By A Database Connection
+*/
+int sqlite3_db_release_memory(sqlite3*);
+
+/*
+** CAPI3REF: Impose A Limit On Heap Size
+*/
+sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
+
+/**
+** CAPI3REF: Deprecated Soft Heap Limit Interface
+*/
+deprecated void sqlite3_soft_heap_limit(int N);
+
+/**
+** CAPI3REF: Extract Metadata About A Column Of A Table
+*/
+int sqlite3_table_column_metadata(
+    sqlite3 *db,                /** Connection handle */
+    const(char)*zDbName,        /** Database name or NULL */
+    const(char)*zTableName,     /** Table name */
+    const(char)*zColumnName,    /** Column name */
+    char **pzDataType,    /** OUTPUT: Declared data type */
+    char **pzCollSeq,     /** OUTPUT: Collation sequence name */
+    int *pNotNull,              /** OUTPUT: True if NOT NULL constraint exists */
+    int *pPrimaryKey,           /** OUTPUT: True if column part of PK */
+    int *pAutoinc               /** OUTPUT: True if column is auto-increment */
+);
+
+/**
+** CAPI3REF: Load An Extension
+*/
+int sqlite3_load_extension(
+    sqlite3 *db,          /** Load the extension into this database connection */
+    const(char)*zFile,    /** Name of the shared library containing extension */
+    const(char)*zProc,    /** Entry point.  Derived from zFile if 0 */
+    char **pzErrMsg       /** Put error message here if not 0 */
+);
+
+/**
+** CAPI3REF: Enable Or Disable Extension Loading
+*/
+int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
+
+/**
+** CAPI3REF: Automatically Load Statically Linked Extensions
+*/
+int sqlite3_auto_extension(void function () xEntryPoint);
+
+/**
+** CAPI3REF: Cancel Automatic Extension Loading
+*/
+int sqlite3_cancel_auto_extension(void function() xEntryPoint);
+
+/**
+** CAPI3REF: Reset Automatic Extension Loading
+*/
+void sqlite3_reset_auto_extension();
+
+/**
+** The interface to the virtual-table mechanism is currently considered
+** to be experimental.  The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stabilizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+*/
+
+/**
+** CAPI3REF: Virtual Table Object
+*/
+
+alias void function (sqlite3_context*,int,sqlite3_value**) mapFunction;
+
+/// Ditto
+struct sqlite3_module
+{
+    int iVersion;
+    int function (sqlite3*, void *pAux,
+               int argc, const char **argv,
+               sqlite3_vtab **ppVTab, char**) xCreate;
+    int function (sqlite3*, void *pAux,
+               int argc, const char **argv,
+               sqlite3_vtab **ppVTab, char**) xConnect;
+    int function (sqlite3_vtab *pVTab, sqlite3_index_info*) xBestIndex;
+    int function (sqlite3_vtab *pVTab) xDisconnect;
+    int function (sqlite3_vtab *pVTab) xDestroy;
+    int function (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) xOpen;
+    int function (sqlite3_vtab_cursor*) xClose;
+    int function (sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
+                int argc, sqlite3_value **argv) xFilter;
+    int function (sqlite3_vtab_cursor*) xNext;
+    int function (sqlite3_vtab_cursor*) xEof;
+    int function (sqlite3_vtab_cursor*, sqlite3_context*, int) xColumn;
+    int function (sqlite3_vtab_cursor*, sqlite3_int64 *pRowid) xRowid;
+    int function (sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *) xUpdate;
+    int function (sqlite3_vtab *pVTab) xBegin;
+    int function (sqlite3_vtab *pVTab) xSync;
+    int function (sqlite3_vtab *pVTab) xCommit;
+    int function (sqlite3_vtab *pVTab) xRollback;
+    int function (sqlite3_vtab *pVtab, int nArg, const char *zName,
+                       mapFunction*,
+                       void **ppArg) xFindFunction;
+    int function (sqlite3_vtab *pVtab, const char *zNew) xRename;
+    int function (sqlite3_vtab *pVTab, int) xSavepoint;
+    int function (sqlite3_vtab *pVTab, int) xRelease;
+    int function (sqlite3_vtab *pVTab, int) xRollbackTo;
+}
+
+/**
+** CAPI3REF: Virtual Table Indexing Information
+*/
+struct sqlite3_index_info
+{
+    struct sqlite3_index_constraint
+    {
+        int iColumn;            /** Column on left-hand side of constraint */
+        char op;                /** Constraint operator */
+        char usable;            /** True if this constraint is usable */
+        int iTermOffset;        /** Used internally - xBestIndex should ignore */
+    }
+    struct sqlite3_index_orderby
+    {
+        int iColumn;            /** Column number */
+        char desc;              /** True for DESC.  False for ASC. */
+    }
+    struct sqlite3_index_constraint_usage
+    {
+        int argvIndex;           /** if >0, constraint is part of argv to xFilter */
+        char omit;               /** Do not code a test for this constraint */
+    }
+    /* Inputs */
+    int nConstraint;           /** Number of entries in aConstraint */
+    sqlite3_index_constraint* aConstraint;  /** Table of WHERE clause constraints */
+    int nOrderBy;              /** Number of terms in the ORDER BY clause */
+    sqlite3_index_orderby *aOrderBy; /** The ORDER BY clause */
+    /* Outputs */
+    sqlite3_index_constraint_usage *aConstraintUsage;
+    int idxNum;                /** Number used to identify the index */
+    char *idxStr;              /** String, possibly obtained from sqlite3_malloc */
+    int needToFreeIdxStr;      /** Free idxStr using sqlite3_free() if true */
+    int orderByConsumed;       /** True if output is already ordered */
+    double estimatedCost;      /** Estimated cost of using this index */
+    sqlite3_int64 estimatedRows;
+    int idxFlags;
+    sqlite3_uint64 colUsed;
+}
+
+/**
+** CAPI3REF: Virtual Table Constraint Operator Codes
+*/
+enum
+{
+    SQLITE_INDEX_SCAN_UNIQUE       = 1,
+    SQLITE_INDEX_CONSTRAINT_EQ     = 2,
+    SQLITE_INDEX_CONSTRAINT_GT     = 4,
+    SQLITE_INDEX_CONSTRAINT_LE     = 8,
+    SQLITE_INDEX_CONSTRAINT_LT     = 16,
+    SQLITE_INDEX_CONSTRAINT_GE     = 32,
+    SQLITE_INDEX_CONSTRAINT_MATCH  = 64,
+    SQLITE_INDEX_CONSTRAINT_LIKE   = 65,
+    SQLITE_INDEX_CONSTRAINT_GLOB   = 66,
+    SQLITE_INDEX_CONSTRAINT_REGEXP = 67,
+}
+
+/**
+** CAPI3REF: Register A Virtual Table Implementation
+*/
+int sqlite3_create_module(
+    sqlite3 *db,                    /* SQLite connection to register module with */
+    const(char)*zName,              /* Name of the module */
+    const(sqlite3_module)*p,        /* Methods for the module */
+    void *pClientData               /* Client data for xCreate/xConnect */
+);
+/// Ditto
+int sqlite3_create_module_v2(
+    sqlite3 *db,                    /* SQLite connection to register module with */
+    const(char)*zName,              /* Name of the module */
+    const(sqlite3_module)*p,        /* Methods for the module */
+    void *pClientData,              /* Client data for xCreate/xConnect */
+    void function (void*) xDestroy  /* Module destructor function */
+);
+
+/**
+** CAPI3REF: Virtual Table Instance Object
+*/
+struct sqlite3_vtab
+{
+    const(sqlite3_module)*pModule;  /** The module for this virtual table */
+    int nRef;                       /** NO LONGER USED */
+    char *zErrMsg;                  /** Error message from sqlite3_mprintf() */
+    /* Virtual table implementations will typically add additional fields */
+}
+
+/**
+** CAPI3REF: Virtual Table Cursor Object
+*/
+struct sqlite3_vtab_cursor
+{
+    sqlite3_vtab *pVtab;      /** Virtual table of this cursor */
+    /* Virtual table implementations will typically add additional fields */
+}
+
+/**
+** CAPI3REF: Declare The Schema Of A Virtual Table
+*/
+int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
+
+/**
+** CAPI3REF: Overload A Function For A Virtual Table
+*/
+int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
+
+/**
+** The interface to the virtual-table mechanism defined above (back up
+** to a comment remarkably similar to this one) is currently considered
+** to be experimental.  The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stabilizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+*/
+
+/*
+** CAPI3REF: A Handle To An Open BLOB
+*/
+struct sqlite3_blob;
+
+/**
+** CAPI3REF: Open A BLOB For Incremental I/O
+*/
+int sqlite3_blob_open(
+    sqlite3*,
+    const(char)* zDb,
+    const(char)* zTable,
+    const(char)* zColumn,
+    sqlite3_int64 iRow,
+    int flags,
+    sqlite3_blob **ppBlob
+);
+
+/**
+** CAPI3REF: Move a BLOB Handle to a New Row
+*/
+int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
+
+/**
+** CAPI3REF: Close A BLOB Handle
+*/
+int sqlite3_blob_close(sqlite3_blob *);
+
+/**
+** CAPI3REF: Return The Size Of An Open BLOB
+*/
+int sqlite3_blob_bytes(sqlite3_blob *);
+
+/**
+** CAPI3REF: Read Data From A BLOB Incrementally
+*/
+int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
+
+/**
+** CAPI3REF: Write Data Into A BLOB Incrementally
+*/
+int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+
+/**
+** CAPI3REF: Virtual File System Objects
+*/
+sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
+/// Ditto
+int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
+/// Ditto
+int sqlite3_vfs_unregister(sqlite3_vfs*);
+
+/**
+** CAPI3REF: Mutexes
+*/
+sqlite3_mutex *sqlite3_mutex_alloc(int);
+/// Ditto
+void sqlite3_mutex_free(sqlite3_mutex*);
+/// Ditto
+void sqlite3_mutex_enter(sqlite3_mutex*);
+/// Ditto
+int sqlite3_mutex_try(sqlite3_mutex*);
+/// Ditto
+void sqlite3_mutex_leave(sqlite3_mutex*);
+
+/**
+** CAPI3REF: Mutex Methods Object
+*/
+struct sqlite3_mutex_methods
+{
+    int  function () xMutexInit;
+    int  function () xMutexEnd;
+    sqlite3_mutex* function (int) xMutexAlloc;
+    void  function (sqlite3_mutex *) xMutexFree;
+    void  function (sqlite3_mutex *) xMutexEnter;
+    int  function (sqlite3_mutex *) xMutexTry;
+    void  function (sqlite3_mutex *) xMutexLeave;
+    int  function (sqlite3_mutex *) xMutexHeld;
+    int  function (sqlite3_mutex *) xMutexNotheld;
+}
+
+/**
+** CAPI3REF: Mutex Verification Routines
+*/
+
+//#ifndef NDEBUG
+int sqlite3_mutex_held(sqlite3_mutex*);
+/// Ditto
+int sqlite3_mutex_notheld(sqlite3_mutex*);
+//#endif
+
+/**
+** CAPI3REF: Mutex Types
+*/
+enum
+{
+    SQLITE_MUTEX_FAST             = 0,
+    SQLITE_MUTEX_RECURSIVE        = 1,
+    SQLITE_MUTEX_STATIC_MASTER    = 2,
+    SQLITE_MUTEX_STATIC_MEM       = 3,  /** sqlite3_malloc() */
+    SQLITE_MUTEX_STATIC_MEM2      = 4,  /** NOT USED */
+    SQLITE_MUTEX_STATIC_OPEN      = 4,  /** sqlite3BtreeOpen() */
+    SQLITE_MUTEX_STATIC_PRNG      = 5,  /** sqlite3_random() */
+    SQLITE_MUTEX_STATIC_LRU       = 6,  /** lru page list */
+    SQLITE_MUTEX_STATIC_LRU2      = 7,  /** NOT USED */
+    SQLITE_MUTEX_STATIC_PMEM      = 7,  /** sqlite3PageMalloc() */
+    SQLITE_MUTEX_STATIC_APP1      = 8,  /** For use by application */
+    SQLITE_MUTEX_STATIC_APP2      = 9,  /** For use by application */
+    SQLITE_MUTEX_STATIC_APP3      = 10, /** For use by application */
+    SQLITE_MUTEX_STATIC_VFS1      = 11, /** For use by built-in VFS */
+    SQLITE_MUTEX_STATIC_VFS2      = 12, /** For use by extension VFS */
+    SQLITE_MUTEX_STATIC_VFS3      = 13, /** For use by application VFS */
+}
+
+/**
+** CAPI3REF: Retrieve the mutex for a database connection
+*/
+sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
+
+/**
+** CAPI3REF: Low-Level Control Of Database Files
+*/
+int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
+
+/**
+** CAPI3REF: Testing Interface
+*/
+int sqlite3_test_control(int op, ...);
+
+/**
+** CAPI3REF: Testing Interface Operation Codes
+*/
+enum
+{
+    SQLITE_TESTCTRL_FIRST                   = 5,
+    SQLITE_TESTCTRL_PRNG_SAVE               = 5,
+    SQLITE_TESTCTRL_PRNG_RESTORE            = 6,
+    SQLITE_TESTCTRL_PRNG_RESET              = 7,
+    SQLITE_TESTCTRL_BITVEC_TEST             = 8,
+    SQLITE_TESTCTRL_FAULT_INSTALL           = 9,
+    SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS     = 10,
+    SQLITE_TESTCTRL_PENDING_BYTE            = 11,
+    SQLITE_TESTCTRL_ASSERT                  = 12,
+    SQLITE_TESTCTRL_ALWAYS                  = 13,
+    SQLITE_TESTCTRL_RESERVE                 = 14,
+    SQLITE_TESTCTRL_OPTIMIZATIONS           = 15,
+    SQLITE_TESTCTRL_ISKEYWORD               = 16,
+    SQLITE_TESTCTRL_SCRATCHMALLOC           = 17,
+    SQLITE_TESTCTRL_LOCALTIME_FAULT         = 18,
+    SQLITE_TESTCTRL_EXPLAIN_STMT            = 19,
+    SQLITE_TESTCTRL_NEVER_CORRUPT           = 20,
+    SQLITE_TESTCTRL_VDBE_COVERAGE           = 21,
+    SQLITE_TESTCTRL_BYTEORDER               = 22,
+    SQLITE_TESTCTRL_ISINIT                  = 23,
+    SQLITE_TESTCTRL_SORTER_MMAP             = 24,
+    SQLITE_TESTCTRL_IMPOSTER                = 25,
+    SQLITE_TESTCTRL_LAST                    = 25,
+}
+
+/**
+** CAPI3REF: SQLite Runtime Status
+*/
+int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag);
+/// Ditto
+int  sqlite3_status64(int op, long *pCurrent, long *pHighwater, int resetFlag);
+
+/**
+** CAPI3REF: Status Parameters
+*/
+enum
+{
+    SQLITE_STATUS_MEMORY_USED          = 0,
+    SQLITE_STATUS_PAGECACHE_USED       = 1,
+    SQLITE_STATUS_PAGECACHE_OVERFLOW   = 2,
+    SQLITE_STATUS_SCRATCH_USED         = 3,
+    SQLITE_STATUS_SCRATCH_OVERFLOW     = 4,
+    SQLITE_STATUS_MALLOC_SIZE          = 5,
+    SQLITE_STATUS_PARSER_STACK         = 6,
+    SQLITE_STATUS_PAGECACHE_SIZE       = 7,
+    SQLITE_STATUS_SCRATCH_SIZE         = 8,
+    SQLITE_STATUS_MALLOC_COUNT         = 9
+}
+
+/**
+** CAPI3REF: Database Connection Status
+*/
+int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
+
+/**
+** CAPI3REF: Status Parameters for database connections
+*/
+enum
+{
+    SQLITE_DBSTATUS_LOOKASIDE_USED      = 0,
+    SQLITE_DBSTATUS_CACHE_USED          = 1,
+    SQLITE_DBSTATUS_SCHEMA_USED         = 2,
+    SQLITE_DBSTATUS_STMT_USED           = 3,
+    SQLITE_DBSTATUS_LOOKASIDE_HIT       = 4,
+    SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5,
+    SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6,
+    SQLITE_DBSTATUS_CACHE_HIT           = 7,
+    SQLITE_DBSTATUS_CACHE_MISS          = 8,
+    SQLITE_DBSTATUS_CACHE_WRITE         = 9,
+    SQLITE_DBSTATUS_DEFERRED_FKS        = 10,
+    SQLITE_DBSTATUS_MAX                 = 10   /** Largest defined DBSTATUS */
+}
+
+/**
+** CAPI3REF: Prepared Statement Status
+*/
+int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
+
+/**
+** CAPI3REF: Status Parameters for prepared statements
+*/
+enum
+{
+    SQLITE_STMTSTATUS_FULLSCAN_STEP     = 1,
+    SQLITE_STMTSTATUS_SORT              = 2,
+    SQLITE_STMTSTATUS_AUTOINDEX         = 3,
+    SQLITE_STMTSTATUS_VM_STEP           = 4
+}
+
+/**
+** CAPI3REF: Custom Page Cache Object
+*/
+struct sqlite3_pcache;
+
+/**
+** CAPI3REF: Custom Page Cache Object
+*/
+struct sqlite3_pcache_page
+{
+    void *pBuf;        /* The content of the page */
+    void *pExtra;      /* Extra information associated with the page */
+}
+
+/**
+** CAPI3REF: Application Defined Page Cache.
+*/
+struct sqlite3_pcache_methods2
+{
+    int iVersion;
+    void *pArg;
+    int function(void*) xInit;
+    void function(void*) xShutdown;
+    sqlite3_pcache * function(int szPage, int szExtra, int bPurgeable) xCreate;
+    void function(sqlite3_pcache*, int nCachesize) xCachesize;
+    int function(sqlite3_pcache*) xPagecount;
+    sqlite3_pcache_page * function(sqlite3_pcache*, uint key, int createFlag) xFetch;
+    void function(sqlite3_pcache*, sqlite3_pcache_page*, int discard) xUnpin;
+    void function(sqlite3_pcache*, sqlite3_pcache_page*,
+     uint oldKey, uint newKey) xRekey;
+    void function(sqlite3_pcache*, uint iLimit) xTruncate;
+    void function(sqlite3_pcache*) xDestroy;
+    void function(sqlite3_pcache*) xShrink;
+}
+
+struct sqlite3_pcache_methods
+{
+    void *pArg;
+    int  function (void*) xInit;
+    void  function (void*) xShutdown;
+    sqlite3_pcache* function (int szPage, int bPurgeable) xCreate;
+    void  function (sqlite3_pcache*, int nCachesize) xCachesize;
+    int  function (sqlite3_pcache*) xPagecount;
+    void* function (sqlite3_pcache*, uint key, int createFlag) xFetch;
+    void  function (sqlite3_pcache*, void*, int discard) xUnpin;
+    void  function (sqlite3_pcache*, void*, uint oldKey, uint newKey) xRekey;
+    void  function (sqlite3_pcache*, uint iLimit) xTruncate;
+    void  function (sqlite3_pcache*) xDestroy;
+}
+
+/**
+** CAPI3REF: Online Backup Object
+*/
+struct sqlite3_backup;
+
+/**
+** CAPI3REF: Online Backup API.
+*/
+sqlite3_backup *sqlite3_backup_init(
+    sqlite3 *pDest,                        /** Destination database handle */
+    const(char)*zDestName,                 /** Destination database name */
+    sqlite3 *pSource,                      /** Source database handle */
+    const(char)*zSourceName                /** Source database name */
+);
+/// Ditto
+int sqlite3_backup_step(sqlite3_backup *p, int nPage);
+/// Ditto
+int sqlite3_backup_finish(sqlite3_backup *p);
+/// Ditto
+int sqlite3_backup_remaining(sqlite3_backup *p);
+/// Ditto
+int sqlite3_backup_pagecount(sqlite3_backup *p);
+
+/**
+** CAPI3REF: Unlock Notification
+*/
+int sqlite3_unlock_notify(
+    sqlite3 *pBlocked,                               /** Waiting connection */
+    void function (void **apArg, int nArg) xNotify,  /** Callback function to invoke */
+    void *pNotifyArg                                 /** Argument to pass to xNotify */
+);
+
+/**
+** CAPI3REF: String Comparison
+*/
+int sqlite3_stricmp(const char * , const char * );
+int sqlite3_strnicmp(const char * , const char * , int);
+
+/*
+** CAPI3REF: String Globbing
+*
+*/
+int sqlite3_strglob(const(char)* zGlob, const(char)* zStr);
+
+/*
+** CAPI3REF: String LIKE Matching
+*/
+int sqlite3_strlike(const(char)* zGlob, const(char)* zStr, uint cEsc);
+
+/**
+** CAPI3REF: Error Logging Interface
+*/
+void sqlite3_log(int iErrCode, const char *zFormat, ...);
+
+/**
+** CAPI3REF: Write-Ahead Log Commit Hook
+*/
+void *sqlite3_wal_hook(
+    sqlite3*,
+    int function (void *,sqlite3*,const char*,int),
+    void*
+);
+
+/**
+** CAPI3REF: Configure an auto-checkpoint
+*/
+int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
+
+/**
+** CAPI3REF: Checkpoint a database
+*/
+int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
+
+/**
+** CAPI3REF: Checkpoint a database
+*/
+int sqlite3_wal_checkpoint_v2(
+    sqlite3 *db,                    /** Database handle */
+    const(char)*zDb,                /** Name of attached database (or NULL) */
+    int eMode,                      /** SQLITE_CHECKPOINT_* value */
+    int *pnLog,                     /** OUT: Size of WAL log in frames */
+    int *pnCkpt                     /** OUT: Total number of frames checkpointed */
+);
+
+/**
+** CAPI3REF: Checkpoint operation parameters
+*/
+enum
+{
+    SQLITE_CHECKPOINT_PASSIVE  = 0,
+    SQLITE_CHECKPOINT_FULL     = 1,
+    SQLITE_CHECKPOINT_RESTART  = 2,
+    SQLITE_CHECKPOINT_TRUNCATE = 3,
+}
+
+/*
+** CAPI3REF: Virtual Table Interface Configuration
+*/
+int sqlite3_vtab_config(sqlite3*, int op, ...);
+
+/**
+** CAPI3REF: Virtual Table Configuration Options
+*/
+enum SQLITE_VTAB_CONSTRAINT_SUPPORT = 1;
+
+/*
+** 2010 August 30
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+*/
+
+//#ifndef _SQLITE3RTREE_H_
+//#define _SQLITE3RTREE_H_
+
+
+/*
+** CAPI3REF: Determine The Virtual Table Conflict Policy
+*/
+int sqlite3_vtab_on_conflict(sqlite3 *);
+
+/*
+** CAPI3REF: Conflict resolution modes
+*/
+enum
+{
+    SQLITE_ROLLBACK = 1,
+    SQLITE_FAIL     = 3,
+    SQLITE_REPLACE  = 5
+}
+
+/*
+** CAPI3REF: Prepared Statement Scan Status Opcodes
+*/
+enum
+{
+    SQLITE_SCANSTAT_NLOOP    = 0,
+    SQLITE_SCANSTAT_NVISIT   = 1,
+    SQLITE_SCANSTAT_EST      = 2,
+    SQLITE_SCANSTAT_NAME     = 3,
+    SQLITE_SCANSTAT_EXPLAIN  = 4,
+    SQLITE_SCANSTAT_SELECTID = 5,
+}
+
+/*
+** CAPI3REF: Prepared Statement Scan Status
+*/
+int sqlite3_stmt_scanstatus(sqlite3_stmt *pStmt, int idx, int iScanStatusOp, void *pOut);
+
+/*
+** CAPI3REF: Zero Scan-Status Counters
+*/
+void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *);
+
+/*
+** CAPI3REF: Flush caches to disk mid-transaction
+*/
+int sqlite3_db_cacheflush(sqlite3 *);
+
+struct sqlite3_snapshot;
+
+/*
+** CAPI3REF: Record A Database Snapshot
+*/
+int sqlite3_snapshot_get(sqlite3 *db, char *zSchema, sqlite3_snapshot **ppSnapshot);
+
+/*
+** CAPI3REF: Start a read transaction on an historical snapshot
+*/
+int sqlite3_snapshot_open(sqlite3 *db, char *zSchema, sqlite3_snapshot *pSnapshot);
+
+/*
+** CAPI3REF: Destroy a snapshot
+*/
+void sqlite3_snapshot_free(sqlite3_snapshot *);
+
+/**
+** Register a geometry callback named zGeom that can be used as part of an
+** R-Tree geometry query as follows:
+**
+**   SELECT ... FROM $(LT)rtree$(GT) WHERE $(LT)rtree col$(GT) MATCH $zGeom(... params ...)
+*/
+int sqlite3_rtree_geometry_callback(
+    sqlite3 *db,
+    const(char)*zGeom,
+    int function (sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes) xGeom,
+    void *pContext
+);
+
+/**
+** A pointer to a structure of the following type is passed as the first
+** argument to callbacks registered using rtree_geometry_callback().
+*/
+struct sqlite3_rtree_geometry
+{
+    void *pContext;                   /** Copy of pContext passed to s_r_g_c() */
+    int nParam;                       /** Size of array aParam[] */
+    double *aParam;                   /** Parameters passed to SQL geom function */
+    void *pUser;                      /** Callback implementation user data */
+    void function (void *) xDelUser;  /** Called by SQLite to clean up pUser */
+}
+
+int sqlite3_rtree_query_callback(
+    sqlite3 *db,
+    const(char)* zQueryFunc,
+    int function(sqlite3_rtree_query_info*) xQueryFunc,
+    void *pContext,
+    void function(void*) xDestructor
+);
+
+struct sqlite3_rtree_query_info
+{
+    void *pContext;                   /* pContext from when function registered */
+    int nParam;                       /* Number of function parameters */
+    double*aParam;        /* value of function parameters */
+    void *pUser;                      /* callback can use this, if desired */
+    void function(void*) xDelUser;          /* function to free pUser */
+    double*aCoord;        /* Coordinates of node or entry to check */
+    uint *anQueue;            /* Number of pending entries in the queue */
+    int nCoord;                       /* Number of coordinates */
+    int iLevel;                       /* Level of current node or entry */
+    int mxLevel;                      /* The largest iLevel value in the tree */
+    sqlite3_int64 iRowid;             /* Rowid for current entry */
+    double rParentScore;   /* Score of parent node */
+    int eParentWithin;                /* Visibility of parent node */
+    int eWithin;                      /* OUT: Visiblity */
+    double rScore;         /* OUT: Write the score here */
+    sqlite3_value **apSqlParam;       /* Original SQL values of parameters */
+}
+
+enum
+{
+    NOT_WITHIN    = 0,
+    PARTLY_WITHIN = 1,
+    FULLY_WITHIN  = 2
+}
+
+/******************************************************************************
+** Interfaces to extend FTS5.
+*/
+struct Fts5Context;
+/// Ditto
+alias fts5_extension_function = void function(
+    const Fts5ExtensionApi *pApi,
+    Fts5Context *pFts,
+    sqlite3_context *pCtx,
+    int nVal,
+    sqlite3_value **apVal
+);
+/// Ditto
+struct Fts5PhraseIter
+{
+    const(ubyte) *a;
+    const(ubyte) *b;
+}
+/// Ditto
+struct Fts5ExtensionApi
+{
+    int iVersion;
+    void* function(Fts5Context*) xUserData;
+    int function(Fts5Context*) xColumnCount;
+    int function(Fts5Context*, sqlite3_int64 *pnRow) xRowCount;
+    int function(Fts5Context*, int iCol, sqlite3_int64 *pnToken) xColumnTotalSize;
+    int function(Fts5Context*,
+        const char *pText, int nText,
+        void *pCtx,
+        int function(void*, int, const char*, int, int, int) xToken
+    ) xTokenize;
+    int function(Fts5Context*) xPhraseCount;
+    int function(Fts5Context*, int iPhrase) xPhraseSize;
+    int function(Fts5Context*, int *pnInst) xInstCount;
+    int function(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff) xInst;
+    sqlite3_int64 function(Fts5Context*) xRowid;
+    int function(Fts5Context*, int iCol, const char **pz, int *pn) xColumnText;
+    int function(Fts5Context*, int iCol, int *pnToken) xColumnSize;
+    int function(Fts5Context*, int iPhrase, void *pUserData,
+        int function(const Fts5ExtensionApi*,Fts5Context*,void*)
+    ) xQueryPhrase;
+    int function(Fts5Context*, void *pAux, void function(void*) xDelete) xSetAuxdata;
+    void* function(Fts5Context*, int bClear) xGetAuxdata;
+    void function(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*) xPhraseFirst;
+    void function(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff) xPhraseNext;
+}
+/// Ditto
+struct Fts5Tokenizer;
+struct fts5_tokenizer
+{
+    int function(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut) xCreate;
+    void function(Fts5Tokenizer*) xDelete;
+    int function(Fts5Tokenizer*,
+        void *pCtx,
+        int flags,
+        const char *pText, int nText,
+        int function(
+            void *pCtx,
+            int tflags,
+            const char *pToken,
+            int nToken,
+            int iStart,
+            int iEnd
+        ) xToken
+    ) xTokenize;
+}
+/// Ditto
+enum FTS5_TOKENIZE_QUERY     = 0x0001;
+/// Ditto
+enum FTS5_TOKENIZE_PREFIX    = 0x0002;
+/// Ditto
+enum FTS5_TOKENIZE_DOCUMENT  = 0x0004;
+/// Ditto
+enum FTS5_TOKENIZE_AUX       = 0x0008;
+/// Ditto
+enum FTS5_TOKEN_COLOCATED    = 0x0001;
+/// Ditto
+struct fts5_api
+{
+    int iVersion;
+
+    int function(
+        fts5_api *pApi,
+        const char *zName,
+        void *pContext,
+        fts5_tokenizer *pTokenizer,
+        void function(void*) xDestroy
+    ) xCreateTokenizer;
+
+    int function(
+        fts5_api *pApi,
+        const char *zName,
+        void **ppContext,
+        fts5_tokenizer *pTokenizer
+    ) xFindTokenizer;
+
+    int function(
+        fts5_api *pApi,
+        const char *zName,
+        void *pContext,
+        fts5_extension_function xFunction,
+        void function(void*) xDestroy
+    ) xCreateFunction;
+}
diff --git a/libphobos/src/etc/c/zlib.d b/libphobos/src/etc/c/zlib.d
new file mode 100644
index 00000000000..cd9be791ef4
--- /dev/null
+++ b/libphobos/src/etc/c/zlib.d
@@ -0,0 +1,1364 @@
+/* zlib.d: modified from zlib.h by Walter Bright */
+/* updated from 1.2.1 to 1.2.3 by Thomas Kuehne */
+/* updated from 1.2.3 to 1.2.8 by Dmitry Atamanov */
+
+module etc.c.zlib;
+
+import core.stdc.config;
+
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+   version 1.2.3, July 18th, 2005
+
+  Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Jean-loup Gailly        Mark Adler
+  jloup@gzip.org          madler@alumni.caltech.edu
+
+
+  The data format used by the zlib library is described by RFCs (Request for
+  Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt
+  (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format).
+*/
+
+extern (C):
+
+const char[] ZLIB_VERSION = "1.2.8";
+const ZLIB_VERNUM = 0x1280;
+
+/*
+     The 'zlib' compression library provides in-memory compression and
+  decompression functions, including integrity checks of the uncompressed
+  data.  This version of the library supports only one compression method
+  (deflation) but other algorithms will be added later and will have the same
+  stream interface.
+
+     Compression can be done in a single step if the buffers are large
+  enough (for example if an input file is mmap'ed), or can be done by
+  repeated calls of the compression function.  In the latter case, the
+  application must provide more input and/or consume the output
+  (providing more output space) before each call.
+
+     The compressed data format used by default by the in-memory functions is
+  the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
+  around a deflate stream, which is itself documented in RFC 1951.
+
+     The library also supports reading and writing files in gzip (.gz) format
+  with an interface similar to that of stdio using the functions that start
+  with "gz".  The gzip format is different from the zlib format.  gzip is a
+  gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
+
+     This library can optionally read and write gzip streams in memory as well.
+
+     The zlib format was designed to be compact and fast for use in memory
+  and on communications channels.  The gzip format was designed for single-
+  file compression on file systems, has a larger header than zlib to maintain
+  directory information, and uses a different, slower check method than zlib.
+
+     The library does not install any signal handler. The decoder checks
+  the consistency of the compressed data, so the library should never
+  crash even in case of corrupted input.
+*/
+
+alias void* function (void* opaque, uint items, uint size) alloc_func;
+alias void  function (void* opaque, void* address) free_func;
+
+struct z_stream
+{
+    const(ubyte)*   next_in;  /* next input byte */
+    uint     avail_in;  /* number of bytes available at next_in */
+    c_ulong  total_in;  /* total nb of input bytes read so far */
+
+    ubyte*   next_out; /* next output byte should be put there */
+    uint     avail_out; /* remaining free space at next_out */
+    c_ulong  total_out; /* total nb of bytes output so far */
+
+    const(char)*    msg;      /* last error message, NULL if no error */
+    void*    state;     /* not visible by applications */
+
+    alloc_func zalloc;  /* used to allocate the internal state */
+    free_func  zfree;   /* used to free the internal state */
+    void*      opaque;  /* private data object passed to zalloc and zfree */
+
+    int     data_type;  /* best guess about the data type: binary or text */
+    c_ulong adler;      /* adler32 value of the uncompressed data */
+    c_ulong reserved;   /* reserved for future use */
+}
+
+alias z_stream* z_streamp;
+
+/*
+     gzip header information passed to and from zlib routines.  See RFC 1952
+  for more details on the meanings of these fields.
+*/
+struct gz_header
+{
+    int     text;       /* true if compressed data believed to be text */
+    c_ulong time;       /* modification time */
+    int     xflags;     /* extra flags (not used when writing a gzip file) */
+    int     os;         /* operating system */
+    byte    *extra;     /* pointer to extra field or Z_NULL if none */
+    uint    extra_len;  /* extra field length (valid if extra != Z_NULL) */
+    uint    extra_max;  /* space at extra (only when reading header) */
+    byte*   name;      /* pointer to zero-terminated file name or Z_NULL */
+    uint    name_max;   /* space at name (only when reading header) */
+    byte*   comment;   /* pointer to zero-terminated comment or Z_NULL */
+    uint    comm_max;   /* space at comment (only when reading header) */
+    int     hcrc;       /* true if there was or will be a header crc */
+    int     done;       /* true when done reading gzip header (not used
+                           when writing a gzip file) */
+}
+
+alias gz_header* gz_headerp;
+
+/*
+  The application must update next_in and avail_in when avail_in has
+   dropped to zero. It must update next_out and avail_out when avail_out
+   has dropped to zero. The application must initialize zalloc, zfree and
+   opaque before calling the init function. All other fields are set by the
+   compression library and must not be updated by the application.
+
+   The opaque value provided by the application will be passed as the first
+   parameter for calls of zalloc and zfree. This can be useful for custom
+   memory management. The compression library attaches no meaning to the
+   opaque value.
+
+   zalloc must return Z_NULL if there is not enough memory for the object.
+   If zlib is used in a multi-threaded application, zalloc and zfree must be
+   thread safe.
+
+   On 16-bit systems, the functions zalloc and zfree must be able to allocate
+   exactly 65536 bytes, but will not be required to allocate more than this
+   if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS,
+   pointers returned by zalloc for objects of exactly 65536 bytes *must*
+   have their offset normalized to zero. The default allocation function
+   provided by this library ensures this (see zutil.c). To reduce memory
+   requirements and avoid any allocation of 64K objects, at the expense of
+   compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h).
+
+   The fields total_in and total_out can be used for statistics or
+   progress reports. After compression, total_in holds the total size of
+   the uncompressed data and may be saved for use in the decompressor
+   (particularly if the decompressor wants to decompress everything in
+   a single step).
+*/
+
+                        /* constants */
+
+enum
+{
+        Z_NO_FLUSH      = 0,
+        Z_PARTIAL_FLUSH = 1, /* will be removed, use Z_SYNC_FLUSH instead */
+        Z_SYNC_FLUSH    = 2,
+        Z_FULL_FLUSH    = 3,
+        Z_FINISH        = 4,
+        Z_BLOCK         = 5,
+        Z_TREES         = 6,
+}
+/* Allowed flush values; see deflate() and inflate() below for details */
+
+enum
+{
+        Z_OK            = 0,
+        Z_STREAM_END    = 1,
+        Z_NEED_DICT     = 2,
+        Z_ERRNO         = -1,
+        Z_STREAM_ERROR  = -2,
+        Z_DATA_ERROR    = -3,
+        Z_MEM_ERROR     = -4,
+        Z_BUF_ERROR     = -5,
+        Z_VERSION_ERROR = -6,
+}
+/* Return codes for the compression/decompression functions. Negative
+ * values are errors, positive values are used for special but normal events.
+ */
+
+enum
+{
+        Z_NO_COMPRESSION         = 0,
+        Z_BEST_SPEED             = 1,
+        Z_BEST_COMPRESSION       = 9,
+        Z_DEFAULT_COMPRESSION    = -1,
+}
+/* compression levels */
+
+enum
+{
+        Z_FILTERED            = 1,
+        Z_HUFFMAN_ONLY        = 2,
+        Z_RLE                 = 3,
+        Z_FIXED               = 4,
+        Z_DEFAULT_STRATEGY    = 0,
+}
+/* compression strategy; see deflateInit2() below for details */
+
+enum
+{
+        Z_BINARY   = 0,
+        Z_TEXT     = 1,
+        Z_UNKNOWN  = 2,
+
+        Z_ASCII    = Z_TEXT
+}
+/* Possible values of the data_type field (though see inflate()) */
+
+enum
+{
+        Z_DEFLATED   = 8,
+}
+/* The deflate compression method (the only one supported in this version) */
+
+const int Z_NULL = 0;  /* for initializing zalloc, zfree, opaque */
+
+                        /* basic functions */
+
+const(char)* zlibVersion();
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+   If the first character differs, the library code actually used is
+   not compatible with the zlib.h header file used by the application.
+   This check is automatically made by deflateInit and inflateInit.
+ */
+
+int deflateInit(z_streamp strm, int level)
+{
+    return deflateInit_(strm, level, ZLIB_VERSION.ptr, z_stream.sizeof);
+}
+/*
+     Initializes the internal stream state for compression. The fields
+   zalloc, zfree and opaque must be initialized before by the caller.
+   If zalloc and zfree are set to Z_NULL, deflateInit updates them to
+   use default allocation functions.
+
+     The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+   1 gives best speed, 9 gives best compression, 0 gives no compression at
+   all (the input data is simply copied a block at a time).
+   Z_DEFAULT_COMPRESSION requests a default compromise between speed and
+   compression (currently equivalent to level 6).
+
+     deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if level is not a valid compression level,
+   Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+   with the version assumed by the caller (ZLIB_VERSION).
+   msg is set to null if there is no error message.  deflateInit does not
+   perform any compression: this will be done by deflate().
+*/
+
+
+int deflate(z_streamp strm, int flush);
+/*
+    deflate compresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full. It may introduce some
+  output latency (reading input without producing any output) except when
+  forced to flush.
+
+    The detailed semantics are as follows. deflate performs one or both of the
+  following actions:
+
+  - Compress more input starting at next_in and update next_in and avail_in
+    accordingly. If not all input can be processed (because there is not
+    enough room in the output buffer), next_in and avail_in are updated and
+    processing will resume at this point for the next call of deflate().
+
+  - Provide more output starting at next_out and update next_out and avail_out
+    accordingly. This action is forced if the parameter flush is non zero.
+    Forcing flush frequently degrades the compression ratio, so this parameter
+    should be set only when necessary (in interactive applications).
+    Some output may be provided even if flush is not set.
+
+  Before the call of deflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming
+  more output, and updating avail_in or avail_out accordingly; avail_out
+  should never be zero before the call. The application can consume the
+  compressed output when it wants, for example when the output buffer is full
+  (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK
+  and with zero avail_out, it must be called again after making room in the
+  output buffer because there might be more output pending.
+
+    Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
+  decide how much data to accumualte before producing output, in order to
+  maximize compression.
+
+    If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+  flushed to the output buffer and the output is aligned on a byte boundary, so
+  that the decompressor can get all input data available so far. (In particular
+  avail_in is zero after the call if enough output space has been provided
+  before the call.)  Flushing may degrade compression for some compression
+  algorithms and so it should be used only when necessary.
+
+    If flush is set to Z_FULL_FLUSH, all output is flushed as with
+  Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+  restart from this point if previous compressed data has been damaged or if
+  random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
+  compression.
+
+    If deflate returns with avail_out == 0, this function must be called again
+  with the same value of the flush parameter and more output space (updated
+  avail_out), until the flush is complete (deflate returns with non-zero
+  avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
+  avail_out is greater than six to avoid repeated flush markers due to
+  avail_out == 0 on return.
+
+    If the parameter flush is set to Z_FINISH, pending input is processed,
+  pending output is flushed and deflate returns with Z_STREAM_END if there
+  was enough output space; if deflate returns with Z_OK, this function must be
+  called again with Z_FINISH and more output space (updated avail_out) but no
+  more input data, until it returns with Z_STREAM_END or an error. After
+  deflate has returned Z_STREAM_END, the only possible operations on the
+  stream are deflateReset or deflateEnd.
+
+    Z_FINISH can be used immediately after deflateInit if all the compression
+  is to be done in a single step. In this case, avail_out must be at least
+  the value returned by deflateBound (see below). If deflate does not return
+  Z_STREAM_END, then it must be called again as described above.
+
+    deflate() sets strm->adler to the adler32 checksum of all input read
+  so far (that is, total_in bytes).
+
+    deflate() may update strm->data_type if it can make a good guess about
+  the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
+  binary. This field is only for information purposes and does not affect
+  the compression algorithm in any manner.
+
+    deflate() returns Z_OK if some progress has been made (more input
+  processed or more output produced), Z_STREAM_END if all input has been
+  consumed and all output has been produced (only when flush is set to
+  Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+  if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible
+  (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
+  fatal, and deflate() can be called again with more input and more output
+  space to continue compressing.
+*/
+
+
+int deflateEnd(z_streamp strm);
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any
+   pending output.
+
+     deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+   stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+   prematurely (some input or output was discarded). In the error case,
+   msg may be set but then points to a static string (which must not be
+   deallocated).
+*/
+
+
+int inflateInit(z_streamp strm)
+{
+    return inflateInit_(strm, ZLIB_VERSION.ptr, z_stream.sizeof);
+}
+/*
+     Initializes the internal stream state for decompression. The fields
+   next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+   the caller. If next_in is not Z_NULL and avail_in is large enough (the exact
+   value depends on the compression method), inflateInit determines the
+   compression method from the zlib header and allocates all data structures
+   accordingly; otherwise the allocation will be deferred to the first call of
+   inflate.  If zalloc and zfree are set to Z_NULL, inflateInit updates them to
+   use default allocation functions.
+
+     inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+   version assumed by the caller.  msg is set to null if there is no error
+   message. inflateInit does not perform any decompression apart from reading
+   the zlib header if present: this will be done by inflate().  (So next_in and
+   avail_in may be modified, but next_out and avail_out are unchanged.)
+*/
+
+
+int inflate(z_streamp strm, int flush);
+/*
+    inflate decompresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full. It may introduce
+  some output latency (reading input without producing any output) except when
+  forced to flush.
+
+  The detailed semantics are as follows. inflate performs one or both of the
+  following actions:
+
+  - Decompress more input starting at next_in and update next_in and avail_in
+    accordingly. If not all input can be processed (because there is not
+    enough room in the output buffer), next_in is updated and processing
+    will resume at this point for the next call of inflate().
+
+  - Provide more output starting at next_out and update next_out and avail_out
+    accordingly.  inflate() provides as much output as possible, until there
+    is no more input data or no more space in the output buffer (see below
+    about the flush parameter).
+
+  Before the call of inflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming
+  more output, and updating the next_* and avail_* values accordingly.
+  The application can consume the uncompressed output when it wants, for
+  example when the output buffer is full (avail_out == 0), or after each
+  call of inflate(). If inflate returns Z_OK and with zero avail_out, it
+  must be called again after making room in the output buffer because there
+  might be more output pending.
+
+    The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH,
+  Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much
+  output as possible to the output buffer. Z_BLOCK requests that inflate() stop
+  if and when it gets to the next deflate block boundary. When decoding the
+  zlib or gzip format, this will cause inflate() to return immediately after
+  the header and before the first block. When doing a raw inflate, inflate()
+  will go ahead and process the first block, and will return when it gets to
+  the end of that block, or when it runs out of data.
+
+    The Z_BLOCK option assists in appending to or combining deflate streams.
+  Also to assist in this, on return inflate() will set strm->data_type to the
+  number of unused bits in the last byte taken from strm->next_in, plus 64
+  if inflate() is currently decoding the last block in the deflate stream,
+  plus 128 if inflate() returned immediately after decoding an end-of-block
+  code or decoding the complete header up to just before the first byte of the
+  deflate stream. The end-of-block will not be indicated until all of the
+  uncompressed data from that block has been written to strm->next_out.  The
+  number of unused bits may in general be greater than seven, except when
+  bit 7 of data_type is set, in which case the number of unused bits will be
+  less than eight.
+
+    inflate() should normally be called until it returns Z_STREAM_END or an
+  error. However if all decompression is to be performed in a single step
+  (a single call of inflate), the parameter flush should be set to
+  Z_FINISH. In this case all pending input is processed and all pending
+  output is flushed; avail_out must be large enough to hold all the
+  uncompressed data. (The size of the uncompressed data may have been saved
+  by the compressor for this purpose.) The next operation on this stream must
+  be inflateEnd to deallocate the decompression state. The use of Z_FINISH
+  is never required, but can be used to inform inflate that a faster approach
+  may be used for the single inflate() call.
+
+     In this implementation, inflate() always flushes as much output as
+  possible to the output buffer, and always uses the faster approach on the
+  first call. So the only effect of the flush parameter in this implementation
+  is on the return value of inflate(), as noted below, or when it returns early
+  because Z_BLOCK is used.
+
+     If a preset dictionary is needed after this call (see inflateSetDictionary
+  below), inflate sets strm->adler to the adler32 checksum of the dictionary
+  chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
+  strm->adler to the adler32 checksum of all output produced so far (that is,
+  total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
+  below. At the end of the stream, inflate() checks that its computed adler32
+  checksum is equal to that saved by the compressor and returns Z_STREAM_END
+  only if the checksum is correct.
+
+    inflate() will decompress and check either zlib-wrapped or gzip-wrapped
+  deflate data.  The header type is detected automatically.  Any information
+  contained in the gzip header is not retained, so applications that need that
+  information should instead use raw inflate, see inflateInit2() below, or
+  inflateBack() and perform their own processing of the gzip header and
+  trailer.
+
+    inflate() returns Z_OK if some progress has been made (more input processed
+  or more output produced), Z_STREAM_END if the end of the compressed data has
+  been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+  preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+  corrupted (input stream not conforming to the zlib format or incorrect check
+  value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+  if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory,
+  Z_BUF_ERROR if no progress is possible or if there was not enough room in the
+  output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
+  inflate() can be called again with more input and more output space to
+  continue decompressing. If Z_DATA_ERROR is returned, the application may then
+  call inflateSync() to look for a good compression block if a partial recovery
+  of the data is desired.
+*/
+
+
+int inflateEnd(z_streamp strm);
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any
+   pending output.
+
+     inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
+   was inconsistent. In the error case, msg may be set but then points to a
+   static string (which must not be deallocated).
+*/
+
+                        /* Advanced functions */
+
+/*
+    The following functions are needed only in some special applications.
+*/
+
+int deflateInit2(z_streamp strm,
+                 int  level,
+                 int  method,
+                 int  windowBits,
+                 int  memLevel,
+                 int  strategy)
+{
+    return deflateInit2_(strm, level, method, windowBits, memLevel,
+                         strategy, ZLIB_VERSION.ptr, z_stream.sizeof);
+}
+/*
+     This is another version of deflateInit with more compression options. The
+   fields next_in, zalloc, zfree and opaque must be initialized before by
+   the caller.
+
+     The method parameter is the compression method. It must be Z_DEFLATED in
+   this version of the library.
+
+     The windowBits parameter is the base two logarithm of the window size
+   (the size of the history buffer). It should be in the range 8..15 for this
+   version of the library. Larger values of this parameter result in better
+   compression at the expense of memory usage. The default value is 15 if
+   deflateInit is used instead.
+
+     windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
+   determines the window size. deflate() will then generate raw deflate data
+   with no zlib header or trailer, and will not compute an adler32 check value.
+
+     windowBits can also be greater than 15 for optional gzip encoding. Add
+   16 to windowBits to write a simple gzip header and trailer around the
+   compressed data instead of a zlib wrapper. The gzip header will have no
+   file name, no extra data, no comment, no modification time (set to zero),
+   no header crc, and the operating system will be set to 255 (unknown).  If a
+   gzip stream is being written, strm->adler is a crc32 instead of an adler32.
+
+     The memLevel parameter specifies how much memory should be allocated
+   for the internal compression state. memLevel=1 uses minimum memory but
+   is slow and reduces compression ratio; memLevel=9 uses maximum memory
+   for optimal speed. The default value is 8. See zconf.h for total memory
+   usage as a function of windowBits and memLevel.
+
+     The strategy parameter is used to tune the compression algorithm. Use the
+   value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+   filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
+   string match), or Z_RLE to limit match distances to one (run-length
+   encoding). Filtered data consists mostly of small values with a somewhat
+   random distribution. In this case, the compression algorithm is tuned to
+   compress them better. The effect of Z_FILTERED is to force more Huffman
+   coding and less string matching; it is somewhat intermediate between
+   Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as
+   Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy
+   parameter only affects the compression ratio but not the correctness of the
+   compressed output even if it is not set appropriately.  Z_FIXED prevents the
+   use of dynamic Huffman codes, allowing for a simpler decoder for special
+   applications.
+
+      deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid
+   method). msg is set to null if there is no error message.  deflateInit2 does
+   not perform any compression: this will be done by deflate().
+*/
+
+int deflateSetDictionary(z_streamp strm, const(ubyte)* dictionary, uint  dictLength);
+/*
+     Initializes the compression dictionary from the given byte sequence
+   without producing any compressed output. This function must be called
+   immediately after deflateInit, deflateInit2 or deflateReset, before any
+   call of deflate. The compressor and decompressor must use exactly the same
+   dictionary (see inflateSetDictionary).
+
+     The dictionary should consist of strings (byte sequences) that are likely
+   to be encountered later in the data to be compressed, with the most commonly
+   used strings preferably put towards the end of the dictionary. Using a
+   dictionary is most useful when the data to be compressed is short and can be
+   predicted with good accuracy; the data can then be compressed better than
+   with the default empty dictionary.
+
+     Depending on the size of the compression data structures selected by
+   deflateInit or deflateInit2, a part of the dictionary may in effect be
+   discarded, for example if the dictionary is larger than the window size in
+   deflate or deflate2. Thus the strings most likely to be useful should be
+   put at the end of the dictionary, not at the front. In addition, the
+   current implementation of deflate will use at most the window size minus
+   262 bytes of the provided dictionary.
+
+     Upon return of this function, strm->adler is set to the adler32 value
+   of the dictionary; the decompressor may later use this value to determine
+   which dictionary has been used by the compressor. (The adler32 value
+   applies to the whole dictionary even if only a subset of the dictionary is
+   actually used by the compressor.) If a raw deflate was requested, then the
+   adler32 value is not computed and strm->adler is not set.
+
+     deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+   parameter is invalid (such as NULL dictionary) or the stream state is
+   inconsistent (for example if deflate has already been called for this stream
+   or if the compression method is bsort). deflateSetDictionary does not
+   perform any compression: this will be done by deflate().
+*/
+
+int deflateCopy(z_streamp dest, z_streamp source);
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when several compression strategies will be
+   tried, for example when there are several ways of pre-processing the input
+   data with a filter. The streams that will be discarded should then be freed
+   by calling deflateEnd.  Note that deflateCopy duplicates the internal
+   compression state which can be quite large, so this strategy is slow and
+   can consume lots of memory.
+
+     deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being NULL). msg is left unchanged in both source and
+   destination.
+*/
+
+int deflateReset(z_streamp strm);
+/*
+     This function is equivalent to deflateEnd followed by deflateInit,
+   but does not free and reallocate all the internal compression state.
+   The stream will keep the same compression level and any other attributes
+   that may have been set by deflateInit2.
+
+      deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being NULL).
+*/
+
+int inflatePrime(z_streamp strm, int bits, int value);
+/*
+     This function inserts bits in the inflate input stream.  The intent is
+  that this function is used to start inflating at a bit position in the
+  middle of a byte.  The provided bits will be used before any bytes are used
+  from next_in.  This function should only be used with raw inflate, and
+  should be used before the first inflate() call after inflateInit2() or
+  inflateReset().  bits must be less than or equal to 16, and that many of the
+  least significant bits of value will be inserted in the input.
+
+      inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+int inflateGetHeader(z_streamp strm, gz_headerp head);
+/*
+      inflateGetHeader() requests that gzip header information be stored in the
+   provided gz_header structure.  inflateGetHeader() may be called after
+   inflateInit2() or inflateReset(), and before the first call of inflate().
+   As inflate() processes the gzip stream, head->done is zero until the header
+   is completed, at which time head->done is set to one.  If a zlib stream is
+   being decoded, then head->done is set to -1 to indicate that there will be
+   no gzip header information forthcoming.  Note that Z_BLOCK can be used to
+   force inflate() to return immediately after header processing is complete
+   and before any actual data is decompressed.
+
+      The text, time, xflags, and os fields are filled in with the gzip header
+   contents.  hcrc is set to true if there is a header CRC.  (The header CRC
+   was valid if done is set to one.)  If extra is not Z_NULL, then extra_max
+   contains the maximum number of bytes to write to extra.  Once done is true,
+   extra_len contains the actual extra field length, and extra contains the
+   extra field, or that field truncated if extra_max is less than extra_len.
+   If name is not Z_NULL, then up to name_max characters are written there,
+   terminated with a zero unless the length is greater than name_max.  If
+   comment is not Z_NULL, then up to comm_max characters are written there,
+   terminated with a zero unless the length is greater than comm_max.  When
+   any of extra, name, or comment are not Z_NULL and the respective field is
+   not present in the header, then that field is set to Z_NULL to signal its
+   absence.  This allows the use of deflateSetHeader() with the returned
+   structure to duplicate the header.  However if those fields are set to
+   allocated memory, then the application will need to save those pointers
+   elsewhere so that they can be eventually freed.
+
+      If inflateGetHeader is not used, then the header information is simply
+   discarded.  The header is always checked for validity, including the header
+   CRC if present.  inflateReset() will reset the process to discard the header
+   information.  The application would need to call inflateGetHeader() again to
+   retrieve the header from the next gzip stream.
+
+      inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+int deflateParams(z_streamp strm, int level, int strategy);
+/*
+     Dynamically update the compression level and compression strategy.  The
+   interpretation of level and strategy is as in deflateInit2.  This can be
+   used to switch between compression and straight copy of the input data, or
+   to switch to a different kind of input data requiring a different
+   strategy. If the compression level is changed, the input available so far
+   is compressed with the old level (and may be flushed); the new level will
+   take effect only at the next call of deflate().
+
+     Before the call of deflateParams, the stream state must be set as for
+   a call of deflate(), since the currently available input may have to
+   be compressed and flushed. In particular, strm->avail_out must be non-zero.
+
+     deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
+   stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR
+   if strm->avail_out was zero.
+*/
+
+int deflateTune(z_streamp strm, int good_length, int max_lazy, int nice_length,
+        int max_chain);
+/*
+     Fine tune deflate's internal compression parameters.  This should only be
+   used by someone who understands the algorithm used by zlib's deflate for
+   searching for the best matching string, and even then only by the most
+   fanatic optimizer trying to squeeze out the last compressed bit for their
+   specific input data.  Read the deflate.c source code for the meaning of the
+   max_lazy, good_length, nice_length, and max_chain parameters.
+
+     deflateTune() can be called after deflateInit() or deflateInit2(), and
+   returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
+ */
+
+int deflateBound(z_streamp strm, size_t sourceLen);
+/*
+     deflateBound() returns an upper bound on the compressed size after
+   deflation of sourceLen bytes.  It must be called after deflateInit()
+   or deflateInit2().  This would be used to allocate an output buffer
+   for deflation in a single pass, and so would be called before deflate().
+*/
+
+int deflatePrime(z_streamp strm, int bits, int value);
+/*
+     deflatePrime() inserts bits in the deflate output stream.  The intent
+  is that this function is used to start off the deflate output with the
+  bits leftover from a previous deflate stream when appending to it.  As such,
+  this function can only be used for raw deflate, and must be used before the
+  first deflate() call after a deflateInit2() or deflateReset().  bits must be
+  less than or equal to 16, and that many of the least significant bits of
+  value will be inserted in the output.
+
+      deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+int deflateSetHeader(z_streamp strm, gz_headerp head);
+/*
+      deflateSetHeader() provides gzip header information for when a gzip
+   stream is requested by deflateInit2().  deflateSetHeader() may be called
+   after deflateInit2() or deflateReset() and before the first call of
+   deflate().  The text, time, os, extra field, name, and comment information
+   in the provided gz_header structure are written to the gzip header (xflag is
+   ignored -- the extra flags are set according to the compression level).  The
+   caller must assure that, if not Z_NULL, name and comment are terminated with
+   a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
+   available there.  If hcrc is true, a gzip header crc is included.  Note that
+   the current versions of the command-line version of gzip (up through version
+   1.3.x) do not support header crc's, and will report that it is a "multi-part
+   gzip file" and give up.
+
+      If deflateSetHeader is not used, the default gzip header has text false,
+   the time set to zero, and os set to 255, with no extra, name, or comment
+   fields.  The gzip header is returned to the default state by deflateReset().
+
+      deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+int inflateInit2(z_streamp strm, int windowBits)
+{
+    return inflateInit2_(strm, windowBits, ZLIB_VERSION.ptr, z_stream.sizeof);
+}
+/*
+     This is another version of inflateInit with an extra parameter. The
+   fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+   before by the caller.
+
+     The windowBits parameter is the base two logarithm of the maximum window
+   size (the size of the history buffer).  It should be in the range 8..15 for
+   this version of the library. The default value is 15 if inflateInit is used
+   instead. windowBits must be greater than or equal to the windowBits value
+   provided to deflateInit2() while compressing, or it must be equal to 15 if
+   deflateInit2() was not used. If a compressed stream with a larger window
+   size is given as input, inflate() will return with the error code
+   Z_DATA_ERROR instead of trying to allocate a larger window.
+
+     windowBits can also be -8..-15 for raw inflate. In this case, -windowBits
+   determines the window size. inflate() will then process raw deflate data,
+   not looking for a zlib or gzip header, not generating a check value, and not
+   looking for any check values for comparison at the end of the stream. This
+   is for use with other formats that use the deflate compressed data format
+   such as zip.  Those formats provide their own check values. If a custom
+   format is developed using the raw deflate format for compressed data, it is
+   recommended that a check value such as an adler32 or a crc32 be applied to
+   the uncompressed data as is done in the zlib, gzip, and zip formats.  For
+   most applications, the zlib format should be used as is. Note that comments
+   above on the use in deflateInit2() applies to the magnitude of windowBits.
+
+     windowBits can also be greater than 15 for optional gzip decoding. Add
+   32 to windowBits to enable zlib and gzip decoding with automatic header
+   detection, or add 16 to decode only the gzip format (the zlib format will
+   return a Z_DATA_ERROR).  If a gzip stream is being decoded, strm->adler is
+   a crc32 instead of an adler32.
+
+     inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg
+   is set to null if there is no error message.  inflateInit2 does not perform
+   any decompression apart from reading the zlib header if present: this will
+   be done by inflate(). (So next_in and avail_in may be modified, but next_out
+   and avail_out are unchanged.)
+/
+
+int inflateSetDictionary(z_streamp strm, const(ubyte)* dictionary, uint  dictLength);
+/*
+     Initializes the decompression dictionary from the given uncompressed byte
+   sequence. This function must be called immediately after a call of inflate,
+   if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
+   can be determined from the adler32 value returned by that call of inflate.
+   The compressor and decompressor must use exactly the same dictionary (see
+   deflateSetDictionary).  For raw inflate, this function can be called
+   immediately after inflateInit2() or inflateReset() and before any call of
+   inflate() to set the dictionary.  The application must insure that the
+   dictionary that was used for compression is provided.
+
+     inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+   parameter is invalid (such as NULL dictionary) or the stream state is
+   inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+   expected one (incorrect adler32 value). inflateSetDictionary does not
+   perform any decompression: this will be done by subsequent calls of
+   inflate().
+*/
+
+int inflateSync(z_streamp strm);
+/*
+    Skips invalid compressed data until a full flush point (see above the
+  description of deflate with Z_FULL_FLUSH) can be found, or until all
+  available input is skipped. No output is provided.
+
+    inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR
+  if no more input was provided, Z_DATA_ERROR if no flush point has been found,
+  or Z_STREAM_ERROR if the stream structure was inconsistent. In the success
+  case, the application may save the current current value of total_in which
+  indicates where valid compressed data was found. In the error case, the
+  application may repeatedly call inflateSync, providing more input each time,
+  until success or end of the input data.
+*/
+
+int inflateCopy (z_streamp dest, z_streamp source);
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when randomly accessing a large stream.  The
+   first pass through the stream can periodically record the inflate state,
+   allowing restarting inflate at those points when randomly accessing the
+   stream.
+
+     inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being NULL). msg is left unchanged in both source and
+   destination.
+*/
+
+int inflateReset(z_streamp strm);
+/*
+     This function is equivalent to inflateEnd followed by inflateInit,
+   but does not free and reallocate all the internal decompression state.
+   The stream will keep attributes that may have been set by inflateInit2.
+
+      inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being NULL).
+*/
+
+
+int inflateBackInit(z_stream* strm, int windowBits, ubyte* window)
+{
+    return inflateBackInit_(strm, windowBits, window, ZLIB_VERSION.ptr, z_stream.sizeof);
+}
+/*
+     Initialize the internal stream state for decompression using inflateBack()
+   calls.  The fields zalloc, zfree and opaque in strm must be initialized
+   before the call.  If zalloc and zfree are Z_NULL, then the default library-
+   derived memory allocation routines are used.  windowBits is the base two
+   logarithm of the window size, in the range 8..15.  window is a caller
+   supplied buffer of that size.  Except for special applications where it is
+   assured that deflate was used with small window sizes, windowBits must be 15
+   and a 32K byte window must be supplied to be able to decompress general
+   deflate streams.
+
+     See inflateBack() for the usage of these routines.
+
+     inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
+   the paramaters are invalid, Z_MEM_ERROR if the internal state could not
+   be allocated, or Z_VERSION_ERROR if the version of the library does not
+   match the version of the header file.
+*/
+
+alias uint function(void*, ubyte**) in_func;
+alias int function(void*, ubyte*, uint) out_func;
+
+int inflateBack(z_stream* strm,
+                in_func f_in,
+                void* in_desc,
+                out_func f_out,
+                void* out_desc);
+/*
+     inflateBack() does a raw inflate with a single call using a call-back
+   interface for input and output.  This is more efficient than inflate() for
+   file i/o applications in that it avoids copying between the output and the
+   sliding window by simply making the window itself the output buffer.  This
+   function trusts the application to not change the output buffer passed by
+   the output function, at least until inflateBack() returns.
+
+     inflateBackInit() must be called first to allocate the internal state
+   and to initialize the state with the user-provided window buffer.
+   inflateBack() may then be used multiple times to inflate a complete, raw
+   deflate stream with each call.  inflateBackEnd() is then called to free
+   the allocated state.
+
+     A raw deflate stream is one with no zlib or gzip header or trailer.
+   This routine would normally be used in a utility that reads zip or gzip
+   files and writes out uncompressed files.  The utility would decode the
+   header and process the trailer on its own, hence this routine expects
+   only the raw deflate stream to decompress.  This is different from the
+   normal behavior of inflate(), which expects either a zlib or gzip header and
+   trailer around the deflate stream.
+
+     inflateBack() uses two subroutines supplied by the caller that are then
+   called by inflateBack() for input and output.  inflateBack() calls those
+   routines until it reads a complete deflate stream and writes out all of the
+   uncompressed data, or until it encounters an error.  The function's
+   parameters and return types are defined above in the in_func and out_func
+   typedefs.  inflateBack() will call in(in_desc, &buf) which should return the
+   number of bytes of provided input, and a pointer to that input in buf.  If
+   there is no input available, in() must return zero--buf is ignored in that
+   case--and inflateBack() will return a buffer error.  inflateBack() will call
+   out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].  out()
+   should return zero on success, or non-zero on failure.  If out() returns
+   non-zero, inflateBack() will return with an error.  Neither in() nor out()
+   are permitted to change the contents of the window provided to
+   inflateBackInit(), which is also the buffer that out() uses to write from.
+   The length written by out() will be at most the window size.  Any non-zero
+   amount of input may be provided by in().
+
+     For convenience, inflateBack() can be provided input on the first call by
+   setting strm->next_in and strm->avail_in.  If that input is exhausted, then
+   in() will be called.  Therefore strm->next_in must be initialized before
+   calling inflateBack().  If strm->next_in is Z_NULL, then in() will be called
+   immediately for input.  If strm->next_in is not Z_NULL, then strm->avail_in
+   must also be initialized, and then if strm->avail_in is not zero, input will
+   initially be taken from strm->next_in[0 .. strm->avail_in - 1].
+
+     The in_desc and out_desc parameters of inflateBack() is passed as the
+   first parameter of in() and out() respectively when they are called.  These
+   descriptors can be optionally used to pass any information that the caller-
+   supplied in() and out() functions need to do their job.
+
+     On return, inflateBack() will set strm->next_in and strm->avail_in to
+   pass back any unused input that was provided by the last in() call.  The
+   return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
+   if in() or out() returned an error, Z_DATA_ERROR if there was a format
+   error in the deflate stream (in which case strm->msg is set to indicate the
+   nature of the error), or Z_STREAM_ERROR if the stream was not properly
+   initialized.  In the case of Z_BUF_ERROR, an input or output error can be
+   distinguished using strm->next_in which will be Z_NULL only if in() returned
+   an error.  If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to
+   out() returning non-zero.  (in() will always be called before out(), so
+   strm->next_in is assured to be defined if out() returns non-zero.)  Note
+   that inflateBack() cannot return Z_OK.
+*/
+
+int inflateBackEnd(z_stream* strm);
+/*
+     All memory allocated by inflateBackInit() is freed.
+
+     inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
+   state was inconsistent.
+*/
+
+uint zlibCompileFlags();
+/* Return flags indicating compile-time options.
+
+    Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
+     1.0: size of uInt
+     3.2: size of uLong
+     5.4: size of voidpf (pointer)
+     7.6: size of z_off_t
+
+    Compiler, assembler, and debug options:
+     8: DEBUG
+     9: ASMV or ASMINF -- use ASM code
+     10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
+     11: 0 (reserved)
+
+    One-time table building (smaller code, but not thread-safe if true):
+     12: BUILDFIXED -- build static block decoding tables when needed
+     13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
+     14,15: 0 (reserved)
+
+    Library content (indicates missing functionality):
+     16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
+                          deflate code when not needed)
+     17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
+                    and decode gzip streams (to avoid linking crc code)
+     18-19: 0 (reserved)
+
+    Operation variations (changes in library functionality):
+     20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
+     21: FASTEST -- deflate algorithm with only one, lowest compression level
+     22,23: 0 (reserved)
+
+    The sprintf variant used by gzprintf (zero is best):
+     24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
+     25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
+     26: 0 = returns value, 1 = void -- 1 means inferred string length returned
+
+    Remainder:
+     27-31: 0 (reserved)
+ */
+
+                        /* utility functions */
+
+/*
+     The following utility functions are implemented on top of the
+   basic stream-oriented functions. To simplify the interface, some
+   default options are assumed (compression level and memory usage,
+   standard memory allocation functions). The source code of these
+   utility functions can easily be modified if you need special options.
+*/
+
+int compress(ubyte* dest,
+             size_t* destLen,
+             const(ubyte)* source,
+             size_t sourceLen);
+/*
+     Compresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer. Upon entry, destLen is the total
+   size of the destination buffer, which must be at least the value returned
+   by compressBound(sourceLen). Upon exit, destLen is the actual size of the
+   compressed buffer.
+     This function can be used to compress a whole file at once if the
+   input file is mmap'ed.
+     compress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer.
+*/
+
+int compress2(ubyte* dest,
+              size_t* destLen,
+              const(ubyte)* source,
+              size_t sourceLen,
+              int level);
+/*
+     Compresses the source buffer into the destination buffer. The level
+   parameter has the same meaning as in deflateInit.  sourceLen is the byte
+   length of the source buffer. Upon entry, destLen is the total size of the
+   destination buffer, which must be at least the value returned by
+   compressBound(sourceLen). Upon exit, destLen is the actual size of the
+   compressed buffer.
+
+     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+   Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+size_t compressBound(size_t sourceLen);
+/*
+     compressBound() returns an upper bound on the compressed size after
+   compress() or compress2() on sourceLen bytes.  It would be used before
+   a compress() or compress2() call to allocate the destination buffer.
+*/
+
+int uncompress(ubyte* dest,
+               size_t* destLen,
+               const(ubyte)* source,
+               size_t sourceLen);
+/*
+     Decompresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer. Upon entry, destLen is the total
+   size of the destination buffer, which must be large enough to hold the
+   entire uncompressed data. (The size of the uncompressed data must have
+   been saved previously by the compressor and transmitted to the decompressor
+   by some mechanism outside the scope of this compression library.)
+   Upon exit, destLen is the actual size of the compressed buffer.
+     This function can be used to decompress a whole file at once if the
+   input file is mmap'ed.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.
+*/
+
+
+alias void* gzFile;
+alias int z_off_t;              // file offset
+
+gzFile gzopen(const(char)* path, const(char)* mode);
+/*
+     Opens a gzip (.gz) file for reading or writing. The mode parameter
+   is as in fopen ("rb" or "wb") but can also include a compression level
+   ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for
+   Huffman only compression as in "wb1h", or 'R' for run-length encoding
+   as in "wb1R". (See the description of deflateInit2 for more information
+   about the strategy parameter.)
+
+     gzopen can be used to read a file which is not in gzip format; in this
+   case gzread will directly read from the file without decompression.
+
+     gzopen returns NULL if the file could not be opened or if there was
+   insufficient memory to allocate the (de)compression state; errno
+   can be checked to distinguish the two cases (if errno is zero, the
+   zlib error is Z_MEM_ERROR).  */
+
+gzFile gzdopen(int fd, const(char)* mode);
+/*
+     gzdopen() associates a gzFile with the file descriptor fd.  File
+   descriptors are obtained from calls like open, dup, creat, pipe or
+   fileno (in the file has been previously opened with fopen).
+   The mode parameter is as in gzopen.
+     The next call of gzclose on the returned gzFile will also close the
+   file descriptor fd, just like fclose(fdopen(fd), mode) closes the file
+   descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode).
+     gzdopen returns NULL if there was insufficient memory to allocate
+   the (de)compression state.
+*/
+
+int gzsetparams(gzFile file, int level, int strategy);
+/*
+     Dynamically update the compression level or strategy. See the description
+   of deflateInit2 for the meaning of these parameters.
+     gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
+   opened for writing.
+*/
+
+int gzread(gzFile file, void* buf, uint len);
+/*
+     Reads the given number of uncompressed bytes from the compressed file.
+   If the input file was not in gzip format, gzread copies the given number
+   of bytes into the buffer.
+     gzread returns the number of uncompressed bytes actually read (0 for
+   end of file, -1 for error). */
+
+int gzwrite(gzFile file, void* buf, uint len);
+/*
+     Writes the given number of uncompressed bytes into the compressed file.
+   gzwrite returns the number of uncompressed bytes actually written
+   (0 in case of error).
+*/
+
+int gzprintf(gzFile file, const(char)* format, ...);
+/*
+     Converts, formats, and writes the args to the compressed file under
+   control of the format string, as in fprintf. gzprintf returns the number of
+   uncompressed bytes actually written (0 in case of error).  The number of
+   uncompressed bytes written is limited to 4095. The caller should assure that
+   this limit is not exceeded. If it is exceeded, then gzprintf() will return
+   return an error (0) with nothing written. In this case, there may also be a
+   buffer overflow with unpredictable consequences, which is possible only if
+   zlib was compiled with the insecure functions sprintf() or vsprintf()
+   because the secure snprintf() or vsnprintf() functions were not available.
+*/
+
+int gzputs(gzFile file, const(char)* s);
+/*
+      Writes the given null-terminated string to the compressed file, excluding
+   the terminating null character.
+      gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+const(char)* gzgets(gzFile file, const(char)* buf, int len);
+/*
+      Reads bytes from the compressed file until len-1 characters are read, or
+   a newline character is read and transferred to buf, or an end-of-file
+   condition is encountered.  The string is then terminated with a null
+   character.
+      gzgets returns buf, or Z_NULL in case of error.
+*/
+
+int gzputc(gzFile file, int c);
+/*
+      Writes c, converted to an unsigned char, into the compressed file.
+   gzputc returns the value that was written, or -1 in case of error.
+*/
+
+int    gzgetc(gzFile file);
+/*
+      Reads one byte from the compressed file. gzgetc returns this byte
+   or -1 in case of end of file or error.
+*/
+
+int gzungetc(int c, gzFile file);
+/*
+      Push one character back onto the stream to be read again later.
+   Only one character of push-back is allowed.  gzungetc() returns the
+   character pushed, or -1 on failure.  gzungetc() will fail if a
+   character has been pushed but not read yet, or if c is -1. The pushed
+   character will be discarded if the stream is repositioned with gzseek()
+   or gzrewind().
+*/
+
+int gzflush(gzFile file, int flush);
+/*
+     Flushes all pending output into the compressed file. The parameter
+   flush is as in the deflate() function. The return value is the zlib
+   error number (see function gzerror below). gzflush returns Z_OK if
+   the flush parameter is Z_FINISH and all output could be flushed.
+     gzflush should be called only when strictly necessary because it can
+   degrade compression.
+*/
+
+z_off_t gzseek(gzFile file, z_off_t offset, int whence);
+/*
+      Sets the starting position for the next gzread or gzwrite on the
+   given compressed file. The offset represents a number of bytes in the
+   uncompressed data stream. The whence parameter is defined as in lseek(2);
+   the value SEEK_END is not supported.
+     If the file is opened for reading, this function is emulated but can be
+   extremely slow. If the file is opened for writing, only forward seeks are
+   supported; gzseek then compresses a sequence of zeroes up to the new
+   starting position.
+
+      gzseek returns the resulting offset location as measured in bytes from
+   the beginning of the uncompressed stream, or -1 in case of error, in
+   particular if the file is opened for writing and the new starting position
+   would be before the current position.
+*/
+
+int gzrewind(gzFile file);
+/*
+     Rewinds the given file. This function is supported only for reading.
+
+   gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+z_off_t  gztell(gzFile file);
+/*
+     Returns the starting position for the next gzread or gzwrite on the
+   given compressed file. This position represents a number of bytes in the
+   uncompressed data stream.
+
+   gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+int gzeof(gzFile file);
+/*
+     Returns 1 when EOF has previously been detected reading the given
+   input stream, otherwise zero.
+*/
+
+int gzdirect(gzFile file);
+/*
+     Returns 1 if file is being read directly without decompression, otherwise
+   zero.
+*/
+
+int gzclose(gzFile file);
+/*
+     Flushes all pending output if necessary, closes the compressed file
+   and deallocates all the (de)compression state. The return value is the zlib
+   error number (see function gzerror below).
+*/
+
+const(char)* gzerror(gzFile file, int* errnum);
+/*
+     Returns the error message for the last error which occurred on the
+   given compressed file. errnum is set to zlib error number. If an
+   error occurred in the file system and not in the compression library,
+   errnum is set to Z_ERRNO and the application may consult errno
+   to get the exact error code.
+*/
+
+void gzclearerr (gzFile file);
+/*
+     Clears the error and end-of-file flags for file. This is analogous to the
+   clearerr() function in stdio. This is useful for continuing to read a gzip
+   file that is being written concurrently.
+*/
+
+                        /* checksum functions */
+
+/*
+     These functions are not related to compression but are exported
+   anyway because they might be useful in applications using the
+   compression library.
+*/
+
+ uint adler32  (uint adler, ubyte* buf, uint len);
+
+/*
+     Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+   return the updated checksum. If buf is NULL, this function returns
+   the required initial value for the checksum.
+   An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
+   much faster. Usage example:
+
+     uint adler = adler32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       adler = adler32(adler, buffer, length);
+     }
+     if (adler != original_adler) error();
+*/
+
+uint adler32_combine(uint adler1, uint adler2, z_off_t len2);
+/*
+     Combine two Adler-32 checksums into one.  For two sequences of bytes, seq1
+   and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
+   each, adler1 and adler2.  adler32_combine() returns the Adler-32 checksum of
+   seq1 and seq2 concatenated, requiring only adler1, adler2, and len2.
+*/
+
+uint crc32(uint crc, ubyte* buf, uint len);
+/*
+     Update a running CRC-32 with the bytes buf[0..len-1] and return the
+   updated CRC-32. If buf is NULL, this function returns the required initial
+   value for the for the crc. Pre- and post-conditioning (one's complement) is
+   performed within this function so it shouldn't be done by the application.
+   Usage example:
+
+     uint crc = crc32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       crc = crc32(crc, buffer, length);
+     }
+     if (crc != original_crc) error();
+*/
+
+uint crc32_combine (uint crc1, uint crc2, z_off_t len2);
+
+/*
+     Combine two CRC-32 check values into one.  For two sequences of bytes,
+   seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
+   calculated for each, crc1 and crc2.  crc32_combine() returns the CRC-32
+   check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
+   len2.
+*/
+
+
+                        /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+int deflateInit_(z_streamp strm,
+                 int level,
+                 const(char)* versionx,
+                 int stream_size);
+
+int inflateInit_(z_streamp strm,
+                 const(char)* versionx,
+                 int stream_size);
+
+int deflateInit2_(z_streamp strm,
+                  int level,
+                  int method,
+                  int windowBits,
+                  int memLevel,
+                  int strategy,
+                  const(char)* versionx,
+                  int stream_size);
+
+int inflateBackInit_(z_stream* strm,
+                     int windowBits,
+                     ubyte* window,
+                     const(char)* z_version,
+                     int stream_size);
+
+int inflateInit2_(z_streamp strm,
+                  int windowBits,
+                  const(char)* versionx,
+                  int stream_size);
+
+const(char)* zError(int err);
+int inflateSyncPoint(z_streamp z);
+const(uint)* get_crc_table();
diff --git a/libphobos/src/index.d b/libphobos/src/index.d
new file mode 100644
index 00000000000..bb4aa04d6cf
--- /dev/null
+++ b/libphobos/src/index.d
@@ -0,0 +1,522 @@
+Ddoc
+
+$(P Phobos is the standard runtime library that comes with the D language
+compiler.)
+
+$(P Generally, the $(D std) namespace is used for the main modules in the
+Phobos standard library. The $(D etc) namespace is used for external C/C++
+library bindings. The $(D core) namespace is used for low-level D runtime
+functions.)
+
+$(P The following table is a quick reference guide for which Phobos modules to
+use for a given category of functionality. Note that some modules may appear in
+more than one category, as some Phobos modules are quite generic and can be
+applied in a variety of situations.)
+
+$(BOOKTABLE ,
+    $(TR
+        $(TH Modules)
+        $(TH Description)
+    )
+    $(LEADINGROW Algorithms &amp; ranges)
+    $(TR
+        $(TDNW
+            $(LINK2 std_algorithm.html, std.algorithm)$(BR)
+            $(LINK2 std_range.html, std.range)$(BR)
+            $(LINK2 std_range_primitives.html, std.range.primitives)$(BR)
+            $(LINK2 std_range_interfaces.html, std.range.interfaces)$(BR)
+        )
+        $(TD Generic algorithms that work with $(LINK2 std_range.html, ranges)
+            of any type, including strings, arrays, and other kinds of
+            sequentially-accessed data. Algorithms include searching,
+            comparison, iteration, sorting, set operations, and mutation.
+        )
+    )
+    $(LEADINGROW Array manipulation)
+    $(TR
+        $(TDNW
+            $(LINK2 std_array.html, std.array)$(BR)
+            $(LINK2 std_algorithm.html, std.algorithm)$(BR)
+        )
+        $(TD Convenient operations commonly used with built-in arrays.
+            Note that many common array operations are subsets of more generic
+            algorithms that work with arbitrary ranges, so they are found in
+            $(D std.algorithm).
+        )
+    )
+    $(LEADINGROW Containers)
+    $(TR
+        $(TDNW
+            $(LINK2 std_container_array.html, std.container.array)$(BR)
+            $(LINK2 std_container_binaryheap.html, std.container.binaryheap)$(BR)
+            $(LINK2 std_container_dlist.html, std.container.dlist)$(BR)
+            $(LINK2 std_container_rbtree.html, std.container.rbtree)$(BR)
+            $(LINK2 std_container_slist.html, std.container.slist)$(BR)
+        )
+        $(TD See $(LINK2 std_container.html, std.container.*) for an
+            overview.
+        )
+    )
+    $(LEADINGROW Data formats)
+    $(TR
+        $(TDNW $(LINK2 std_base64.html, std.base64))
+        $(TD Encoding / decoding Base64 format.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_csv.html, std.csv))
+        $(TD Read Comma Separated Values and its variants from an input range of $(CODE dchar).)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_json.html, std.json))
+        $(TD Read/write data in JSON format.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_xml.html, std.xml))
+        $(TD Read/write data in XML format.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_zip.html, std.zip))
+        $(TD Read/write data in the ZIP archive format.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_zlib.html, std.zlib))
+        $(TD Compress/decompress data using the zlib library.)
+    )
+    $(LEADINGROW Data integrity)
+    $(TR
+        $(TDNW $(LINK2 std_digest_crc.html, std.digest.crc))
+        $(TD Cyclic Redundancy Check (32-bit) implementation.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_digest_digest.html, std.digest.digest))
+        $(TD Compute digests such as md5, sha1 and crc32.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_digest_hmac.html, std.digest.hmac))
+        $(TD Compute HMAC digests of arbitrary data.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_digest_md.html, std.digest.md))
+        $(TD Compute MD5 hash of arbitrary data.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_digest_ripemd.html, std.digest.ripemd))
+        $(TD Compute RIPEMD-160 hash of arbitrary data.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_digest_sha.html, std.digest.sha))
+        $(TD Compute SHA1 and SHA2 hashes of arbitrary data.)
+    )
+    $(LEADINGROW Date &amp; time)
+    $(TR
+        $(TDNW $(LINK2 std_datetime.html, std.datetime))
+        $(TD Provides convenient access to date and time representations.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_time.html, core.time))
+        $(TD Implements low-level time primitives.)
+    )
+    $(LEADINGROW Exception handling)
+    $(TR
+        $(TDNW $(LINK2 std_exception.html, std.exception))
+        $(TD Implements routines related to exceptions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_exception.html, core.exception))
+        $(TD Defines built-in exception types and low-level
+            language hooks required by the compiler.)
+    )
+    $(LEADINGROW External library bindings)
+    $(TR
+        $(TDNW $(LINK2 etc_c_curl.html, etc.c.curl))
+        $(TD Interface to libcurl C library.)
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_odbc_sql.html, etc.c.odbc.sql))
+        $(TD Interface to ODBC C library.)
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_odbc_sqlext.html, etc.c.odbc.sqlext))
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_odbc_sqltypes.html, etc.c.odbc.sqltypes))
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_odbc_sqlucode.html, etc.c.odbc.sqlucode))
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_sqlite3.html, etc.c.sqlite3))
+        $(TD Interface to SQLite C library.)
+    )
+    $(TR
+        $(TDNW $(LINK2 etc_c_zlib.html, etc.c.zlib))
+        $(TD Interface to zlib C library.)
+    )
+    $(LEADINGROW I/O &amp; File system)
+    $(TR
+        $(TDNW $(LINK2 std_file.html, std.file))
+        $(TD Manipulate files and directories.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_path.html, std.path))
+        $(TD Manipulate strings that represent filesystem paths.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_stdio.html, std.stdio))
+        $(TD Perform buffered I/O.)
+    )
+    $(LEADINGROW Interoperability)
+    $(TR
+        $(TDNW
+            $(LINK2 core_stdc_complex.html, core.stdc.complex)$(BR)
+            $(LINK2 core_stdc_ctype.html, core.stdc.ctype)$(BR)
+            $(LINK2 core_stdc_errno.html, core.stdc.errno)$(BR)
+            $(LINK2 core_stdc_fenv.html, core.stdc.fenv)$(BR)
+            $(LINK2 core_stdc_float_.html, core.stdc.float_)$(BR)
+            $(LINK2 core_stdc_inttypes.html, core.stdc.inttypes)$(BR)
+            $(LINK2 core_stdc_limits.html, core.stdc.limits)$(BR)
+            $(LINK2 core_stdc_locale.html, core.stdc.locale)$(BR)
+            $(LINK2 core_stdc_math.html, core.stdc.math)$(BR)
+            $(LINK2 core_stdc_signal.html, core.stdc.signal)$(BR)
+            $(LINK2 core_stdc_stdarg.html, core.stdc.stdarg)$(BR)
+            $(LINK2 core_stdc_stddef.html, core.stdc.stddef)$(BR)
+            $(LINK2 core_stdc_stdint.html, core.stdc.stdint)$(BR)
+            $(LINK2 core_stdc_stdio.html, core.stdc.stdio)$(BR)
+            $(LINK2 core_stdc_stdlib.html, core.stdc.stdlib)$(BR)
+            $(LINK2 core_stdc_string.html, core.stdc.string)$(BR)
+            $(LINK2 core_stdc_tgmath.html, core.stdc.tgmath)$(BR)
+            $(LINK2 core_stdc_time.html, core.stdc.time)$(BR)
+            $(LINK2 core_stdc_wchar_.html, core.stdc.wchar_)$(BR)
+            $(LINK2 core_stdc_wctype.html, core.stdc.wctype)$(BR)
+        )
+        $(TD
+            D bindings for standard C headers.$(BR)$(BR)
+            These are mostly undocumented, as documentation
+            for the functions these declarations provide
+            bindings to can be found on external resources.
+        )
+    )
+    $(LEADINGROW Memory management)
+    $(TR
+        $(TDNW $(LINK2 core_memory.html, core.memory))
+        $(TD Control the built-in garbage collector.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_typecons.html, std.typecons))
+        $(TD Build scoped variables and reference-counted types.)
+    )
+    $(LEADINGROW Metaprogramming)
+    $(TR
+        $(TDNW $(LINK2 core_attribute.html, core.attribute))
+        $(TD Definitions of special attributes recognized by the compiler.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_demangle.html, core.demangle))
+        $(TD Convert $(I mangled) D symbol identifiers to source representation.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_demangle.html, std.demangle))
+        $(TD A simple wrapper around core.demangle.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_meta.html, std.meta))
+        $(TD Construct and manipulate template argument lists (aka type lists).)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_traits.html, std.traits))
+        $(TD Extract information about types and symbols at compile time.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_typecons.html, std.typecons))
+        $(TD Construct new, useful general purpose types.)
+    )
+    $(LEADINGROW Multitasking)
+    $(TR
+        $(TDNW $(LINK2 std_concurrency.html, std.concurrency))
+        $(TD Low level messaging API for threads.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_parallelism.html, std.parallelism))
+        $(TD High level primitives for SMP parallelism.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_process.html, std.process))
+        $(TD Starting and manipulating processes.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_atomic.html, core.atomic))
+        $(TD Basic support for lock-free concurrent programming.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_barrier.html, core.sync.barrier))
+        $(TD Synchronize the progress of a group of threads.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_condition.html, core.sync.condition))
+        $(TD Synchronized condition checking.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_exception.html, core.sync.exception))
+        $(TD Base class for synchronization exceptions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_mutex.html, core.sync.mutex))
+        $(TD Mutex for mutually exclusive access.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_rwmutex.html, core.sync.rwmutex))
+        $(TD Shared read access and mutually exclusive write access.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_sync_semaphore.html, core.sync.semaphore))
+        $(TD General use synchronization semaphore.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_thread.html, core.thread))
+        $(TD Thread creation and management.)
+    )
+    $(LEADINGROW Networking)
+    $(TR
+        $(TDNW $(LINK2 std_socket.html, std.socket))
+        $(TD Socket primitives.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_net_curl.html, std.net.curl))
+        $(TD Networking client functionality as provided by libcurl.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_net_isemail.html, std.net.isemail))
+        $(TD Validates an email address according to RFCs 5321, 5322 and others.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_uri.html, std.uri))
+        $(TD Encode and decode Uniform Resource Identifiers (URIs).)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_uuid.html, std.uuid))
+        $(TD Universally-unique identifiers for resources in distributed
+        systems.)
+    )
+    $(LEADINGROW Numeric)
+    $(TR
+        $(TDNW $(LINK2 std_bigint.html, std.bigint))
+        $(TD An arbitrary-precision integer type.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_complex.html, std.complex))
+        $(TD A complex number type.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_math.html, std.math))
+        $(TD Elementary mathematical functions (powers, roots, trigonometry).)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_mathspecial.html, std.mathspecial))
+        $(TD Families of transcendental functions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_numeric.html, std.numeric))
+        $(TD Floating point numerics functions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_random.html, std.random))
+        $(TD Pseudo-random number generators.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_checkedint.html, core.checkedint))
+        $(TD Range-checking integral arithmetic primitives.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_math.html, core.math))
+        $(TD Built-in mathematical intrinsics.)
+    )
+    $(LEADINGROW Paradigms)
+    $(TR
+        $(TDNW $(LINK2 std_functional.html, std.functional))
+        $(TD Functions that manipulate other functions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_algorithm.html, std.algorithm))
+        $(TD Generic algorithms for processing sequences.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_signals.html, std.signals))
+        $(TD Signal-and-slots framework for event-driven programming.)
+    )
+    $(LEADINGROW Runtime utilities)
+    $(TR
+        $(TDNW $(LINK2 object.html, object))
+        $(TD Core language definitions. Automatically imported.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_getopt.html, std.getopt))
+        $(TD Parsing of command-line arguments.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_compiler.html, std.compiler))
+        $(TD Host compiler vendor string and language version.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_system.html, std.system))
+        $(TD Runtime environment, such as OS type and endianness.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_cpuid.html, core.cpuid))
+        $(TD Capabilities of the CPU the program is running on.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_memory.html, core.memory))
+        $(TD Control the built-in garbage collector.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_runtime.html, core.runtime))
+        $(TD Control and configure the D runtime.)
+    )
+    $(LEADINGROW String manipulation)
+    $(TR
+        $(TDNW $(LINK2 std_string.html, std.string))
+        $(TD Algorithms that work specifically with strings.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_array.html, std.array))
+        $(TD Manipulate builtin arrays.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_algorithm.html, std.algorithm))
+        $(TD Generic algorithms for processing sequences.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_uni.html, std.uni))
+        $(TD Fundamental Unicode algorithms and data structures.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_utf.html, std.utf))
+        $(TD Encode and decode UTF-8, UTF-16 and UTF-32 strings.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_format.html, std.format))
+        $(TD Format data into strings.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_path.html, std.path))
+        $(TD Manipulate strings that represent filesystem paths.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_regex.html, std.regex))
+        $(TD Regular expressions.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_ascii.html, std.ascii))
+        $(TD Routines specific to the ASCII subset of Unicode.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_encoding.html, std.encoding))
+        $(TD Handle and transcode between various text encodings.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_windows_charset.html, std.windows.charset))
+        $(TD Windows specific character set support.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_outbuffer.html, std.outbuffer))
+        $(TD Serialize data to $(CODE ubyte) arrays.)
+    )
+    $(LEADINGROW Type manipulations)
+    $(TR
+        $(TDNW $(LINK2 std_conv.html, std.conv))
+        $(TD Convert types from one type to another.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_typecons.html, std.typecons))
+        $(TD Type constructors for scoped variables, ref counted types, etc.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_bitmanip.html, std.bitmanip))
+        $(TD High level bit level manipulation, bit arrays, bit fields.)
+    )
+    $(TR
+        $(TDNW $(LINK2 std_variant.html, std.variant))
+        $(TD Discriminated unions and algebraic types.)
+    )
+    $(TR
+        $(TDNW $(LINK2 core_bitop.html, core.bitop))
+        $(TD Low level bit manipulation.)
+    )
+    $(LEADINGROW Vector programming)
+    $(TR
+        $(TDNW $(LINK2 core_simd.html, core.simd))
+        $(TD SIMD intrinsics)
+    )
+
+$(COMMENT
+    $(LEADINGROW Undocumented modules (intentionally omitted).)
+    $(TR
+        $(TDNW
+            $(LINK2 core_sync_config.html, core.sync.config)$(BR)
+            $(LINK2 std_concurrencybase.html, std.concurrencybase)$(BR)
+            $(LINK2 std_container_util.html, std.container.util)$(BR)
+            $(LINK2 std_regex_internal_backtracking.html, std.regex.internal.backtracking)$(BR)
+            $(LINK2 std_regex_internal_generator.html, std.regex.internal.generator)$(BR)
+            $(LINK2 std_regex_internal_ir.html, std.regex.internal.ir)$(BR)
+            $(LINK2 std_regex_internal_kickstart.html, std.regex.internal.kickstart)$(BR)
+            $(LINK2 std_regex_internal_parser.html, std.regex.internal.parser)$(BR)
+            $(LINK2 std_regex_internal_tests.html, std.regex.internal.tests)$(BR)
+            $(LINK2 std_regex_internal_thompson.html, std.regex.internal.thompson)$(BR)
+            $(LINK2 std_stdiobase.html, std.stdiobase)$(BR)
+        )
+        $(TD
+             Internal modules.
+        )
+    )
+    $(TR
+        $(TDNW
+            $(LINK2 core_vararg.html, core.vararg)$(BR)
+            $(LINK2 std_c_fenv.html, std.c.fenv)$(BR)
+            $(LINK2 std_c_linux_linux.html, std.c.linux_linux)$(BR)
+            $(LINK2 std_c_linux_socket.html, std.c.linux_socket)$(BR)
+            $(LINK2 std_c_locale.html, std.c.locale)$(BR)
+            $(LINK2 std_c_math.html, std.c.math)$(BR)
+            $(LINK2 std_c_process.html, std.c.process)$(BR)
+            $(LINK2 std_c_stdarg.html, std.c.stdarg)$(BR)
+            $(LINK2 std_c_stddef.html, std.c.stddef)$(BR)
+            $(LINK2 std_c_stdio.html, std.c.stdio)$(BR)
+            $(LINK2 std_c_stdlib.html, std.c.stdlib)$(BR)
+            $(LINK2 std_c_string.html, std.c.string)$(BR)
+            $(LINK2 std_c_time.html, std.c.time)$(BR)
+            $(LINK2 std_c_wcharh.html, std.c.wcharh)$(BR)
+            $(LINK2 std_stdint.html, std.stdint)$(BR)
+        )
+        $(TDN
+             Redirect modules.
+        )
+    )
+    $(TR
+        $(TDNW
+            $(LINK2 std_cstream.html, std.cstream)$(BR)
+            $(LINK2 std_mmfile.html, std.mmfile)$(BR)
+            $(LINK2 std_socketstream.html, std.socketstream)$(BR)
+            $(LINK2 std_stream.html, std.stream)$(BR)
+            $(LINK2 std_typetuple.html, std.typetuple)$(BR)
+        )
+        $(TD
+             Deprecated modules.
+        )
+    )
+    $(TR
+        $(TDNW
+            $(LINK2 std_experimental_logger.html, std.experimental.logger)$(BR)
+            $(LINK2 std_experimental_logger_core.html, std.experimental.logger.core)$(BR)
+            $(LINK2 std_experimental_logger_filelogger.html, std.experimental.logger.filelogger)$(BR)
+            $(LINK2 std_experimental_logger_multilogger.html, std.experimental.logger.multilogger)$(BR)
+            $(LINK2 std_experimental_logger_nulllogger.html, std.experimental.logger.nulllogger)$(BR)
+        )
+        $(TD
+             Experimental modules.
+        )
+    )
+)
+)
+
+Macros:
+        TITLE=Phobos Runtime Library
+        WIKI=Phobos
diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d
new file mode 100644
index 00000000000..371f4ec6921
--- /dev/null
+++ b/libphobos/src/std/algorithm/comparison.d
@@ -0,0 +1,2045 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic _comparison algorithms.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 among,
+        Checks if a value is among a set of values, e.g.
+        $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3))
+$(T2 castSwitch,
+        $(D (new A()).castSwitch((A a)=>1,(B b)=>2)) returns $(D 1).)
+$(T2 clamp,
+        $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).)
+$(T2 cmp,
+        $(D cmp("abc", "abcd")) is $(D -1), $(D cmp("abc", "aba")) is $(D 1),
+        and $(D cmp("abc", "abc")) is $(D 0).)
+$(T2 either,
+        Return first parameter $(D p) that passes an $(D if (p)) test, e.g.
+        $(D either(0, 42, 43)) returns $(D 42).)
+$(T2 equal,
+        Compares ranges for element-by-element equality, e.g.
+        $(D equal([1, 2, 3], [1.0, 2.0, 3.0])) returns $(D true).)
+$(T2 isPermutation,
+        $(D isPermutation([1, 2], [2, 1])) returns $(D true).)
+$(T2 isSameLength,
+        $(D isSameLength([1, 2, 3], [4, 5, 6])) returns $(D true).)
+$(T2 levenshteinDistance,
+        $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using
+        the $(LUCKY Levenshtein distance _algorithm).)
+$(T2 levenshteinDistanceAndPath,
+        $(D levenshteinDistanceAndPath("kitten", "sitting")) returns
+        $(D tuple(3, "snnnsni")) by using the $(LUCKY Levenshtein distance
+        _algorithm).)
+$(T2 max,
+        $(D max(3, 4, 2)) returns $(D 4).)
+$(T2 min,
+        $(D min(3, 4, 2)) returns $(D 2).)
+$(T2 mismatch,
+        $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).)
+$(T2 predSwitch,
+        $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_comparison.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.comparison;
+
+// FIXME
+import std.functional; // : unaryFun, binaryFun;
+import std.range;
+import std.traits;
+// FIXME
+import std.typecons; // : tuple, Tuple, Flag;
+import std.meta : allSatisfy;
+
+/**
+Find $(D value) _among $(D values), returning the 1-based index
+of the first matching value in $(D values), or $(D 0) if $(D value)
+is not _among $(D values). The predicate $(D pred) is used to
+compare values, and uses equality by default.
+
+Params:
+    pred = The predicate used to compare the values.
+    value = The value to search for.
+    values = The values to compare the value to.
+
+Returns:
+    0 if value was not found among the values, otherwise the index of the
+    found value plus one is returned.
+
+See_Also:
+$(XREF_PACK_NAMED algorithm,searching,find,find) and $(XREF_PACK_NAMED algorithm,searching,canFind, canFind) for finding a value in a
+range.
+*/
+uint among(alias pred = (a, b) => a == b, Value, Values...)
+    (Value value, Values values)
+    if (Values.length != 0)
+{
+    foreach (uint i, ref v; values)
+    {
+        import std.functional : binaryFun;
+        if (binaryFun!pred(value, v)) return i + 1;
+    }
+    return 0;
+}
+
+/// Ditto
+template among(values...)
+    if (isExpressionTuple!values)
+{
+    uint among(Value)(Value value)
+        if (!is(CommonType!(Value, values) == void))
+    {
+        switch (value)
+        {
+            foreach (uint i, v; values)
+                case v:
+                    return i + 1;
+            default:
+                return 0;
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    assert(3.among(1, 42, 24, 3, 2));
+
+    if (auto pos = "bar".among("foo", "bar", "baz"))
+        assert(pos == 2);
+    else
+        assert(false);
+
+    // 42 is larger than 24
+    assert(42.among!((lhs, rhs) => lhs > rhs)(43, 24, 100) == 2);
+}
+
+/**
+Alternatively, $(D values) can be passed at compile-time, allowing for a more
+efficient search, but one that only supports matching on equality:
+*/
+@safe unittest
+{
+    assert(3.among!(2, 3, 4));
+    assert("bar".among!("foo", "bar", "baz") == 2);
+}
+
+@safe unittest
+{
+    import std.meta : AliasSeq;
+
+    if (auto pos = 3.among(1, 2, 3))
+        assert(pos == 3);
+    else
+        assert(false);
+    assert(!4.among(1, 2, 3));
+
+    auto position = "hello".among("hello", "world");
+    assert(position);
+    assert(position == 1);
+
+    alias values = AliasSeq!("foo", "bar", "baz");
+    auto arr = [values];
+    assert(arr[0 .. "foo".among(values)] == ["foo"]);
+    assert(arr[0 .. "bar".among(values)] == ["foo", "bar"]);
+    assert(arr[0 .. "baz".among(values)] == arr);
+    assert("foobar".among(values) == 0);
+
+    if (auto pos = 3.among!(1, 2, 3))
+        assert(pos == 3);
+    else
+        assert(false);
+    assert(!4.among!(1, 2, 3));
+
+    position = "hello".among!("hello", "world");
+    assert(position);
+    assert(position == 1);
+
+    static assert(!__traits(compiles, "a".among!("a", 42)));
+    static assert(!__traits(compiles, (Object.init).among!(42, "a")));
+}
+
+// Used in castSwitch to find the first choice that overshadows the last choice
+// in a tuple.
+private template indexOfFirstOvershadowingChoiceOnLast(choices...)
+{
+    alias firstParameterTypes = Parameters!(choices[0]);
+    alias lastParameterTypes = Parameters!(choices[$ - 1]);
+
+    static if (lastParameterTypes.length == 0)
+    {
+        // If the last is null-typed choice, check if the first is null-typed.
+        enum isOvershadowing = firstParameterTypes.length == 0;
+    }
+    else static if (firstParameterTypes.length == 1)
+    {
+        // If the both first and last are not null-typed, check for overshadowing.
+        enum isOvershadowing =
+            is(firstParameterTypes[0] == Object) // Object overshadows all other classes!(this is needed for interfaces)
+            || is(lastParameterTypes[0] : firstParameterTypes[0]);
+    }
+    else
+    {
+        // If the first is null typed and the last is not - the is no overshadowing.
+        enum isOvershadowing = false;
+    }
+
+    static if (isOvershadowing)
+    {
+        enum indexOfFirstOvershadowingChoiceOnLast = 0;
+    }
+    else
+    {
+        enum indexOfFirstOvershadowingChoiceOnLast =
+            1 + indexOfFirstOvershadowingChoiceOnLast!(choices[1..$]);
+    }
+}
+
+/**
+Executes and returns one of a collection of handlers based on the type of the
+switch object.
+
+The first choice that $(D switchObject) can be casted to the type
+of argument it accepts will be called with $(D switchObject) casted to that
+type, and the value it'll return will be returned by $(D castSwitch).
+
+If a choice's return type is void, the choice must throw an exception, unless
+all the choices are void. In that case, castSwitch itself will return void.
+
+Throws: If none of the choice matches, a $(D SwitchError) will be thrown.  $(D
+SwitchError) will also be thrown if not all the choices are void and a void
+choice was executed without throwing anything.
+
+Params:
+    choices = The $(D choices) needs to be composed of function or delegate
+        handlers that accept one argument. There can also be a choice that
+        accepts zero arguments. That choice will be invoked if the $(D
+        switchObject) is null.
+    switchObject = the object against which the tests are being made.
+
+Returns:
+    The value of the selected choice.
+
+Note: $(D castSwitch) can only be used with object types.
+*/
+auto castSwitch(choices...)(Object switchObject)
+{
+    import core.exception : SwitchError;
+
+    // Check to see if all handlers return void.
+    enum areAllHandlersVoidResult = {
+        bool result = true;
+        foreach (index, choice; choices)
+        {
+            result &= is(ReturnType!choice == void);
+        }
+        return result;
+    }();
+
+    if (switchObject !is null)
+    {
+
+        // Checking for exact matches:
+        ClassInfo classInfo = typeid(switchObject);
+        foreach (index, choice; choices)
+        {
+            static assert(isCallable!choice,
+                    "A choice handler must be callable");
+
+            alias choiceParameterTypes = Parameters!choice;
+            static assert(choiceParameterTypes.length <= 1,
+                    "A choice handler can not have more than one argument.");
+
+            static if (choiceParameterTypes.length == 1)
+            {
+                alias CastClass = choiceParameterTypes[0];
+                static assert(is(CastClass == class) || is(CastClass == interface),
+                        "A choice handler can have only class or interface typed argument.");
+
+                // Check for overshadowing:
+                immutable indexOfOvershadowingChoice =
+                    indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
+                static assert(indexOfOvershadowingChoice == index,
+                        "choice number %d(type %s) is overshadowed by choice number %d(type %s)".format(
+                            index + 1, CastClass.stringof, indexOfOvershadowingChoice + 1,
+                            Parameters!(choices[indexOfOvershadowingChoice])[0].stringof));
+
+                if (classInfo == typeid(CastClass))
+                {
+                    static if(is(ReturnType!(choice) == void))
+                    {
+                        choice(cast(CastClass) switchObject);
+                        static if (areAllHandlersVoidResult)
+                        {
+                            return;
+                        }
+                        else
+                        {
+                            throw new SwitchError("Handlers that return void should throw");
+                        }
+                    }
+                    else
+                    {
+                        return choice(cast(CastClass) switchObject);
+                    }
+                }
+            }
+        }
+
+        // Checking for derived matches:
+        foreach (choice; choices)
+        {
+            alias choiceParameterTypes = Parameters!choice;
+            static if (choiceParameterTypes.length == 1)
+            {
+                if (auto castedObject = cast(choiceParameterTypes[0]) switchObject)
+                {
+                    static if(is(ReturnType!(choice) == void))
+                    {
+                        choice(castedObject);
+                        static if (areAllHandlersVoidResult)
+                        {
+                            return;
+                        }
+                        else
+                        {
+                            throw new SwitchError("Handlers that return void should throw");
+                        }
+                    }
+                    else
+                    {
+                        return choice(castedObject);
+                    }
+                }
+            }
+        }
+    }
+    else // If switchObject is null:
+    {
+        // Checking for null matches:
+        foreach (index, choice; choices)
+        {
+            static if (Parameters!(choice).length == 0)
+            {
+                immutable indexOfOvershadowingChoice =
+                    indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
+
+                // Check for overshadowing:
+                static assert(indexOfOvershadowingChoice == index,
+                        "choice number %d(null reference) is overshadowed by choice number %d(null reference)".format(
+                            index + 1, indexOfOvershadowingChoice + 1));
+
+                if (switchObject is null)
+                {
+                    static if(is(ReturnType!(choice) == void))
+                    {
+                        choice();
+                        static if (areAllHandlersVoidResult)
+                        {
+                            return;
+                        }
+                        else
+                        {
+                            throw new SwitchError("Handlers that return void should throw");
+                        }
+                    }
+                    else
+                    {
+                        return choice();
+                    }
+                }
+            }
+        }
+    }
+
+    // In case nothing matched:
+    throw new SwitchError("Input not matched by any choice");
+}
+
+///
+unittest
+{
+    import std.algorithm.iteration : map;
+    import std.format : format;
+
+    class A
+    {
+        int a;
+        this(int a) {this.a = a;}
+        @property int i() { return a; }
+    }
+    interface I { }
+    class B : I { }
+
+    Object[] arr = [new A(1), new B(), null];
+
+    auto results = arr.map!(castSwitch!(
+                                (A a) => "A with a value of %d".format(a.a),
+                                (I i) => "derived from I",
+                                ()    => "null reference",
+                            ))();
+
+    // A is handled directly:
+    assert(results[0] == "A with a value of 1");
+    // B has no handler - it is handled by the handler of I:
+    assert(results[1] == "derived from I");
+    // null is handled by the null handler:
+    assert(results[2] == "null reference");
+}
+
+/// Using with void handlers:
+unittest
+{
+    import std.exception : assertThrown;
+
+    class A { }
+    class B { }
+    // Void handlers are allowed if they throw:
+    assertThrown!Exception(
+        new B().castSwitch!(
+            (A a) => 1,
+            (B d)    { throw new Exception("B is not allowed!"); }
+        )()
+    );
+
+    // Void handlers are also allowed if all the handlers are void:
+    new A().castSwitch!(
+        (A a) { assert(true); },
+        (B b) { assert(false); },
+    )();
+}
+
+unittest
+{
+    import core.exception : SwitchError;
+    import std.exception : assertThrown;
+
+    interface I { }
+    class A : I { }
+    class B { }
+
+    // Nothing matches:
+    assertThrown!SwitchError((new A()).castSwitch!(
+                                 (B b) => 1,
+                                 () => 2,
+                             )());
+
+    // Choices with multiple arguments are not allowed:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                (A a, B b) => 0,
+                            )()));
+
+    // Only callable handlers allowed:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                1234,
+                            )()));
+
+    // Only object arguments allowed:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                (int x) => 0,
+                            )()));
+
+    // Object overshadows regular classes:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                (Object o) => 0,
+                                (A a) => 1,
+                            )()));
+
+    // Object overshadows interfaces:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                (Object o) => 0,
+                                (I i) => 1,
+                            )()));
+
+    // No multiple null handlers allowed:
+    static assert(!__traits(compiles,
+                            (new A()).castSwitch!(
+                                () => 0,
+                                () => 1,
+                            )()));
+
+    // No non-throwing void handlers allowed(when there are non-void handlers):
+    assertThrown!SwitchError((new A()).castSwitch!(
+                                 (A a)    {},
+                                 (B b) => 2,
+                             )());
+
+    // All-void handlers work for the null case:
+    null.castSwitch!(
+        (Object o) { assert(false); },
+        ()         { assert(true); },
+    )();
+
+    // Throwing void handlers work for the null case:
+    assertThrown!Exception(null.castSwitch!(
+                               (Object o) => 1,
+                               ()            { throw new Exception("null"); },
+                           )());
+}
+
+/** Clamps a value into the given bounds.
+
+This functions is equivalent to $(D max(lower, min(upper,val))).
+
+Params:
+    val = The value to _clamp.
+    lower = The _lower bound of the _clamp.
+    upper = The _upper bound of the _clamp.
+
+Returns:
+    Returns $(D val), if it is between $(D lower) and $(D upper).
+    Otherwise returns the nearest of the two.
+
+*/
+auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper)
+in
+{
+    import std.functional : greaterThan;
+    assert(!lower.greaterThan(upper));
+}
+body
+{
+    return max(lower, min(upper, val));
+}
+
+///
+@safe unittest
+{
+    assert(clamp(2, 1, 3) == 2);
+    assert(clamp(0, 1, 3) == 1);
+    assert(clamp(4, 1, 3) == 3);
+
+    assert(clamp(1, 1, 1) == 1);
+
+    assert(clamp(5, -1, 2u) == 2);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int a = 1;
+    short b = 6;
+    double c = 2;
+    static assert(is(typeof(clamp(c,a,b)) == double));
+    assert(clamp(c,   a, b) == c);
+    assert(clamp(a-c, a, b) == a);
+    assert(clamp(b+c, a, b) == b);
+    // mixed sign
+    a = -5;
+    uint f = 5;
+    static assert(is(typeof(clamp(f, a, b)) == int));
+    assert(clamp(f, a, b) == f);
+    // similar type deduction for (u)long
+    static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long));
+
+    // user-defined types
+    import std.datetime : Date;
+    assert(clamp(Date(1982, 1, 4), Date(1012, 12, 21), Date(2012, 12, 21)) == Date(1982, 1, 4));
+    assert(clamp(Date(1982, 1, 4), Date.min, Date.max) == Date(1982, 1, 4));
+    // UFCS style
+    assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4));
+
+}
+
+// cmp
+/**********************************
+Performs three-way lexicographical comparison on two input ranges
+according to predicate $(D pred). Iterating $(D r1) and $(D r2) in
+lockstep, $(D cmp) compares each element $(D e1) of $(D r1) with the
+corresponding element $(D e2) in $(D r2). If one of the ranges has been
+finished, $(D cmp) returns a negative value if $(D r1) has fewer
+elements than $(D r2), a positive value if $(D r1) has more elements
+than $(D r2), and $(D 0) if the ranges have the same number of
+elements.
+
+If the ranges are strings, $(D cmp) performs UTF decoding
+appropriately and compares the ranges one code point at a time.
+
+Params:
+    pred = The predicate used for comparison.
+    r1 = The first range.
+    r2 = The second range.
+
+Returns:
+    0 if both ranges compare equal. -1 if the first differing element of $(D
+    r1) is less than the corresponding element of $(D r2) according to $(D
+    pred). 1 if the first differing element of $(D r2) is less than the
+    corresponding element of $(D r1) according to $(D pred).
+
+*/
+int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2)
+if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2))
+{
+    for (;; r1.popFront(), r2.popFront())
+    {
+        if (r1.empty) return -cast(int)!r2.empty;
+        if (r2.empty) return !r1.empty;
+        auto a = r1.front, b = r2.front;
+        if (binaryFun!pred(a, b)) return -1;
+        if (binaryFun!pred(b, a)) return 1;
+    }
+}
+
+/// ditto
+int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) if (isSomeString!R1 && isSomeString!R2)
+{
+    import core.stdc.string : memcmp;
+    import std.utf : decode;
+
+    static if (is(typeof(pred) : string))
+        enum isLessThan = pred == "a < b";
+    else
+        enum isLessThan = false;
+
+    // For speed only
+    static int threeWay(size_t a, size_t b)
+    {
+        static if (size_t.sizeof == int.sizeof && isLessThan)
+            return a - b;
+        else
+            return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0;
+    }
+    // For speed only
+    // @@@BUG@@@ overloading should be allowed for nested functions
+    static int threeWayInt(int a, int b)
+    {
+        static if (isLessThan)
+            return a - b;
+        else
+            return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0;
+    }
+
+    static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan)
+    {
+        static if (typeof(r1[0]).sizeof == 1)
+        {
+            immutable len = min(r1.length, r2.length);
+            immutable result = __ctfe ?
+                {
+                    foreach (i; 0 .. len)
+                    {
+                        if (r1[i] != r2[i])
+                            return threeWayInt(r1[i], r2[i]);
+                    }
+                    return 0;
+                }()
+                : () @trusted { return memcmp(r1.ptr, r2.ptr, len); }();
+            if (result) return result;
+        }
+        else
+        {
+            auto p1 = r1.ptr, p2 = r2.ptr,
+                pEnd = p1 + min(r1.length, r2.length);
+            for (; p1 != pEnd; ++p1, ++p2)
+            {
+                if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2);
+            }
+        }
+        return threeWay(r1.length, r2.length);
+    }
+    else
+    {
+        for (size_t i1, i2;;)
+        {
+            if (i1 == r1.length) return threeWay(i2, r2.length);
+            if (i2 == r2.length) return threeWay(r1.length, i1);
+            immutable c1 = decode(r1, i1),
+                c2 = decode(r2, i2);
+            if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2);
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    int result;
+
+    result = cmp("abc", "abc");
+    assert(result == 0);
+    result = cmp("", "");
+    assert(result == 0);
+    result = cmp("abc", "abcd");
+    assert(result < 0);
+    result = cmp("abcd", "abc");
+    assert(result > 0);
+    result = cmp("abc"d, "abd");
+    assert(result < 0);
+    result = cmp("bbc", "abc"w);
+    assert(result > 0);
+    result = cmp("aaa", "aaaa"d);
+    assert(result < 0);
+    result = cmp("aaaa", "aaa"d);
+    assert(result > 0);
+    result = cmp("aaa", "aaa"d);
+    assert(result == 0);
+    result = cmp(cast(int[])[], cast(int[])[]);
+    assert(result == 0);
+    result = cmp([1, 2, 3], [1, 2, 3]);
+    assert(result == 0);
+    result = cmp([1, 3, 2], [1, 2, 3]);
+    assert(result > 0);
+    result = cmp([1, 2, 3], [1L, 2, 3, 4]);
+    assert(result < 0);
+    result = cmp([1L, 2, 3], [1, 2]);
+    assert(result > 0);
+}
+
+// equal
+/**
+Compares two ranges for equality, as defined by predicate $(D pred)
+(which is $(D ==) by default).
+*/
+template equal(alias pred = "a == b")
+{
+    /++
+    This function compares to ranges for equality. The ranges may have
+    different element types, as long as $(D pred(a, b)) evaluates to $(D bool)
+    for $(D a) in $(D r1) and $(D b) in $(D r2).
+    Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred).
+
+    Params:
+        r1 = The first range to be compared.
+        r2 = The second range to be compared.
+
+    Returns:
+        $(D true) if and only if the two ranges compare equal element
+        for element, according to binary predicate $(D pred).
+
+    See_Also:
+        $(WEB sgi.com/tech/stl/_equal.html, STL's _equal)
+    +/
+    bool equal(Range1, Range2)(Range1 r1, Range2 r2)
+    if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front))))
+    {
+        //Start by detecting default pred and compatible dynamicarray.
+        static if (is(typeof(pred) == string) && pred == "a == b" &&
+            isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
+        {
+            return r1 == r2;
+        }
+        //Try a fast implementation when the ranges have comparable lengths
+        else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
+        {
+            auto len1 = r1.length;
+            auto len2 = r2.length;
+            if (len1 != len2) return false; //Short circuit return
+
+            //Lengths are the same, so we need to do an actual comparison
+            //Good news is we can sqeeze out a bit of performance by not checking if r2 is empty
+            for (; !r1.empty; r1.popFront(), r2.popFront())
+            {
+                if (!binaryFun!(pred)(r1.front, r2.front)) return false;
+            }
+            return true;
+        }
+        else
+        {
+            //Generic case, we have to walk both ranges making sure neither is empty
+            for (; !r1.empty; r1.popFront(), r2.popFront())
+            {
+                if (r2.empty) return false;
+                if (!binaryFun!(pred)(r1.front, r2.front)) return false;
+            }
+            return r2.empty;
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    import std.math : approxEqual;
+    import std.algorithm : equal;
+
+    int[] a = [ 1, 2, 4, 3 ];
+    assert(!equal(a, a[1..$]));
+    assert(equal(a, a));
+
+    // different types
+    double[] b = [ 1.0, 2, 4, 3];
+    assert(!equal(a, b[1..$]));
+    assert(equal(a, b));
+
+    // predicated: ensure that two vectors are approximately equal
+    double[] c = [ 1.005, 2, 4, 3];
+    assert(equal!approxEqual(b, c));
+}
+
+/++
+Tip: $(D equal) can itself be used as a predicate to other functions.
+This can be very useful when the element type of a range is itself a
+range. In particular, $(D equal) can be its own predicate, allowing
+range of range (of range...) comparisons.
+ +/
+@safe unittest
+{
+    import std.range : iota, chunks;
+    import std.algorithm : equal;
+    assert(equal!(equal!equal)(
+        [[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
+        iota(0, 8).chunks(2).chunks(2)
+    ));
+}
+
+@safe unittest
+{
+    import std.algorithm.iteration : map;
+    import std.math : approxEqual;
+    import std.internal.test.dummyrange : ReferenceForwardRange,
+        ReferenceInputRange, ReferenceInfiniteForwardRange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    // various strings
+    assert(equal("æøå", "æøå")); //UTF8 vs UTF8
+    assert(!equal("???", "æøå")); //UTF8 vs UTF8
+    assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32
+    assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32
+    assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32
+    assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32
+    assert(!equal("hello", "world"));
+
+    // same strings, but "explicit non default" comparison (to test the non optimized array comparison)
+    assert( equal!("a == b")("æøå", "æøå")); //UTF8 vs UTF8
+    assert(!equal!("a == b")("???", "æøå")); //UTF8 vs UTF8
+    assert( equal!("a == b")("æøå"w, "æøå"d)); //UTF16 vs UTF32
+    assert(!equal!("a == b")("???"w, "æøå"d));//UTF16 vs UTF32
+    assert( equal!("a == b")("æøå"d, "æøå"d)); //UTF32 vs UTF32
+    assert(!equal!("a == b")("???"d, "æøå"d));//UTF32 vs UTF32
+    assert(!equal!("a == b")("hello", "world"));
+
+    //Array of string
+    assert(equal(["hello", "world"], ["hello", "world"]));
+    assert(!equal(["hello", "world"], ["hello"]));
+    assert(!equal(["hello", "world"], ["hello", "Bob!"]));
+
+    //Should not compile, because "string == dstring" is illegal
+    static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d]))));
+    //However, arrays of non-matching string can be compared using equal!equal. Neat-o!
+    equal!equal(["hello", "world"], ["hello"d, "world"d]);
+
+    //Tests, with more fancy map ranges
+    int[] a = [ 1, 2, 4, 3 ];
+    assert(equal([2, 4, 8, 6], map!"a*2"(a)));
+    double[] b = [ 1.0, 2, 4, 3];
+    double[] c = [ 1.005, 2, 4, 3];
+    assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c)));
+    assert(!equal([2, 4, 1, 3], map!"a*2"(a)));
+    assert(!equal([2, 4, 1], map!"a*2"(a)));
+    assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c)));
+
+    //Tests with some fancy reference ranges.
+    ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]);
+    ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]);
+    assert(equal(cir, a));
+    cir = new ReferenceInputRange!int([1, 2, 4, 3]);
+    assert(equal(cir, cfr.save));
+    assert(equal(cfr.save, cfr.save));
+    cir = new ReferenceInputRange!int([1, 2, 8, 1]);
+    assert(!equal(cir, cfr));
+
+    //Test with an infinte range
+    ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int;
+    assert(!equal(a, ifr));
+}
+
+// MaxType
+private template MaxType(T...)
+    if (T.length >= 1)
+{
+    static if (T.length == 1)
+    {
+        alias MaxType = T[0];
+    }
+    else static if (T.length == 2)
+    {
+        static if (!is(typeof(T[0].min)))
+            alias MaxType = CommonType!T;
+        else static if (T[1].max > T[0].max)
+            alias MaxType = T[1];
+        else
+            alias MaxType = T[0];
+    }
+    else
+    {
+        alias MaxType = MaxType!(MaxType!(T[0 .. ($+1)/2]), MaxType!(T[($+1)/2 .. $]));
+    }
+}
+
+// levenshteinDistance
+/**
+Encodes $(WEB realityinteractive.com/rgrzywinski/archives/000249.html,
+edit operations) necessary to transform one sequence into
+another. Given sequences $(D s) (source) and $(D t) (target), a
+sequence of $(D EditOp) encodes the steps that need to be taken to
+convert $(D s) into $(D t). For example, if $(D s = "cat") and $(D
+"cars"), the minimal sequence that transforms $(D s) into $(D t) is:
+skip two characters, replace 't' with 'r', and insert an 's'. Working
+with edit operations is useful in applications such as spell-checkers
+(to find the closest word to a given misspelled word), approximate
+searches, diff-style programs that compute the difference between
+files, efficient encoding of patches, DNA sequence analysis, and
+plagiarism detection.
+*/
+
+enum EditOp : char
+{
+    /** Current items are equal; no editing is necessary. */
+    none = 'n',
+    /** Substitute current item in target with current item in source. */
+    substitute = 's',
+    /** Insert current item from the source into the target. */
+    insert = 'i',
+    /** Remove current item from the target. */
+    remove = 'r'
+}
+
+private struct Levenshtein(Range, alias equals, CostType = size_t)
+{
+    EditOp[] path()
+    {
+        import std.algorithm.mutation : reverse;
+
+        EditOp[] result;
+        size_t i = rows - 1, j = cols - 1;
+        // restore the path
+        while (i || j) {
+            auto cIns = j == 0 ? CostType.max : matrix(i,j - 1);
+            auto cDel = i == 0 ? CostType.max : matrix(i - 1,j);
+            auto cSub = i == 0 || j == 0
+                ? CostType.max
+                : matrix(i - 1,j - 1);
+            switch (min_index(cSub, cIns, cDel)) {
+            case 0:
+                result ~= matrix(i - 1,j - 1) == matrix(i,j)
+                    ? EditOp.none
+                    : EditOp.substitute;
+                --i;
+                --j;
+                break;
+            case 1:
+                result ~= EditOp.insert;
+                --j;
+                break;
+            default:
+                result ~= EditOp.remove;
+                --i;
+                break;
+            }
+        }
+        reverse(result);
+        return result;
+    }
+
+    ~this() {
+        FreeMatrix();
+    }
+
+private:
+    CostType _deletionIncrement = 1,
+        _insertionIncrement = 1,
+        _substitutionIncrement = 1;
+    CostType[] _matrix;
+    size_t rows, cols;
+
+    // Treat _matrix as a rectangular array
+    ref CostType matrix(size_t row, size_t col) { return _matrix[row * cols + col]; }
+
+    void AllocMatrix(size_t r, size_t c) @trusted {
+        rows = r;
+        cols = c;
+        if (_matrix.length < r * c) {
+            import core.stdc.stdlib : realloc;
+            import core.exception : onOutOfMemoryError;
+            auto m = cast(CostType *)realloc(_matrix.ptr, r * c * _matrix[0].sizeof);
+            if (!m)
+                onOutOfMemoryError();
+            _matrix = m[0 .. r * c];
+            InitMatrix();
+        }
+    }
+
+    void FreeMatrix() @trusted {
+        import core.stdc.stdlib : free;
+
+        free(_matrix.ptr);
+        _matrix = null;
+    }
+
+    void InitMatrix() {
+        foreach (r; 0 .. rows)
+            matrix(r,0) = r * _deletionIncrement;
+        foreach (c; 0 .. cols)
+            matrix(0,c) = c * _insertionIncrement;
+    }
+
+    static uint min_index(CostType i0, CostType i1, CostType i2)
+    {
+        if (i0 <= i1)
+        {
+            return i0 <= i2 ? 0 : 2;
+        }
+        else
+        {
+            return i1 <= i2 ? 1 : 2;
+        }
+    }
+
+    CostType distanceWithPath(Range s, Range t)
+    {
+        auto slen = walkLength(s.save), tlen = walkLength(t.save);
+        AllocMatrix(slen + 1, tlen + 1);
+        foreach (i; 1 .. rows)
+        {
+            auto sfront = s.front;
+            auto tt = t.save;
+            foreach (j; 1 .. cols)
+            {
+                auto cSub = matrix(i - 1,j - 1)
+                    + (equals(sfront, tt.front) ? 0 : _substitutionIncrement);
+                tt.popFront();
+                auto cIns = matrix(i,j - 1) + _insertionIncrement;
+                auto cDel = matrix(i - 1,j) + _deletionIncrement;
+                switch (min_index(cSub, cIns, cDel))
+                {
+                case 0:
+                    matrix(i,j) = cSub;
+                    break;
+                case 1:
+                    matrix(i,j) = cIns;
+                    break;
+                default:
+                    matrix(i,j) = cDel;
+                    break;
+                }
+            }
+            s.popFront();
+        }
+        return matrix(slen,tlen);
+    }
+
+    CostType distanceLowMem(Range s, Range t, CostType slen, CostType tlen)
+    {
+        CostType lastdiag, olddiag;
+        AllocMatrix(slen + 1, 1);
+        foreach (y; 1 .. slen + 1)
+        {
+            _matrix[y] = y;
+        }
+        foreach (x; 1 .. tlen + 1)
+        {
+            auto tfront = t.front;
+            auto ss = s.save;
+            _matrix[0] = x;
+            lastdiag = x - 1;
+            foreach (y; 1 .. rows)
+            {
+                olddiag = _matrix[y];
+                auto cSub = lastdiag + (equals(ss.front, tfront) ? 0 : _substitutionIncrement);
+                ss.popFront();
+                auto cIns = _matrix[y - 1] + _insertionIncrement;
+                auto cDel = _matrix[y] + _deletionIncrement;
+                switch (min_index(cSub, cIns, cDel))
+                {
+                case 0:
+                    _matrix[y] = cSub;
+                    break;
+                case 1:
+                    _matrix[y] = cIns;
+                    break;
+                default:
+                    _matrix[y] = cDel;
+                    break;
+                }
+                lastdiag = olddiag;
+            }
+            t.popFront();
+        }
+        return _matrix[slen];
+    }
+}
+
+/**
+Returns the $(WEB wikipedia.org/wiki/Levenshtein_distance, Levenshtein
+distance) between $(D s) and $(D t). The Levenshtein distance computes
+the minimal amount of edit operations necessary to transform $(D s)
+into $(D t).  Performs $(BIGOH s.length * t.length) evaluations of $(D
+equals) and occupies $(BIGOH s.length * t.length) storage.
+
+Params:
+    equals = The binary predicate to compare the elements of the two ranges.
+    s = The original range.
+    t = The transformation target
+
+Returns:
+    The minimal number of edits to transform s into t.
+
+Does not allocate GC memory.
+*/
+size_t levenshteinDistance(alias equals = (a,b) => a == b, Range1, Range2)
+    (Range1 s, Range2 t)
+    if (isForwardRange!(Range1) && isForwardRange!(Range2))
+{
+    alias eq = binaryFun!(equals);
+
+    for (;;)
+    {
+        if (s.empty) return t.walkLength;
+        if (t.empty) return s.walkLength;
+        if (eq(s.front, t.front))
+        {
+            s.popFront();
+            t.popFront();
+            continue;
+        }
+        static if (isBidirectionalRange!(Range1) && isBidirectionalRange!(Range2))
+        {
+            if (eq(s.back, t.back))
+            {
+                s.popBack();
+                t.popBack();
+                continue;
+            }
+        }
+        break;
+    }
+
+    auto slen = walkLength(s.save);
+    auto tlen = walkLength(t.save);
+
+    if (slen == 1 && tlen == 1)
+    {
+        return eq(s.front, t.front) ? 0 : 1;
+    }
+
+    if (slen > tlen)
+    {
+        Levenshtein!(Range1, eq, size_t) lev;
+        return lev.distanceLowMem(s, t, slen, tlen);
+    }
+    else
+    {
+        Levenshtein!(Range2, eq, size_t) lev;
+        return lev.distanceLowMem(t, s, tlen, slen);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+    import std.uni : toUpper;
+
+    assert(levenshteinDistance("cat", "rat") == 1);
+    assert(levenshteinDistance("parks", "spark") == 2);
+    assert(levenshteinDistance("abcde", "abcde") == 0);
+    assert(levenshteinDistance("abcde", "abCde") == 1);
+    assert(levenshteinDistance("kitten", "sitting") == 3);
+    assert(levenshteinDistance!((a, b) => toUpper(a) == toUpper(b))
+        ("parks", "SPARK") == 2);
+    assert(levenshteinDistance("parks".filter!"true", "spark".filter!"true") == 2);
+    assert(levenshteinDistance("ID", "I♥D") == 1);
+}
+
+@safe @nogc nothrow unittest
+{
+    assert(levenshteinDistance("cat"d, "rat"d) == 1);
+}
+
+// compat overload for alias this strings
+size_t levenshteinDistance(alias equals = (a,b) => a == b, Range1, Range2)
+    (auto ref Range1 s, auto ref Range2 t)
+    if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, Range1, Range2);
+    return levenshteinDistance!(equals, Types)(s, t);
+}
+
+@safe unittest
+{
+    static struct S { string s; alias s this; }
+    assert(levenshteinDistance(S("cat"), S("rat")) == 1);
+    assert(levenshteinDistance("cat", S("rat")) == 1);
+    assert(levenshteinDistance(S("cat"), "rat") == 1);
+}
+
+@safe @nogc nothrow unittest
+{
+    static struct S { dstring s; alias s this; }
+    assert(levenshteinDistance(S("cat"d), S("rat"d)) == 1);
+    assert(levenshteinDistance("cat"d, S("rat"d)) == 1);
+    assert(levenshteinDistance(S("cat"d), "rat"d) == 1);
+}
+
+/**
+Returns the Levenshtein distance and the edit path between $(D s) and
+$(D t).
+
+Params:
+    equals = The binary predicate to compare the elements of the two ranges.
+    s = The original range.
+    t = The transformation target
+
+Returns:
+    Tuple with the first element being the minimal amount of edits to transform s into t and
+    the second element being the sequence of edits to effect this transformation.
+
+Allocates GC memory for the returned EditOp[] array.
+*/
+Tuple!(size_t, EditOp[])
+levenshteinDistanceAndPath(alias equals = (a,b) => a == b, Range1, Range2)
+    (Range1 s, Range2 t)
+    if (isForwardRange!(Range1) && isForwardRange!(Range2))
+{
+    Levenshtein!(Range1, binaryFun!(equals)) lev;
+    auto d = lev.distanceWithPath(s, t);
+    return tuple(d, lev.path());
+}
+
+///
+@safe unittest
+{
+    string a = "Saturday", b = "Sundays";
+    auto p = levenshteinDistanceAndPath(a, b);
+    assert(p[0] == 4);
+    assert(equal(p[1], "nrrnsnnni"));
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    assert(levenshteinDistance("a", "a") == 0);
+    assert(levenshteinDistance("a", "b") == 1);
+    assert(levenshteinDistance("aa", "ab") == 1);
+    assert(levenshteinDistance("aa", "abc") == 2);
+    assert(levenshteinDistance("Saturday", "Sunday") == 3);
+    assert(levenshteinDistance("kitten", "sitting") == 3);
+}
+
+// compat overload for alias this strings
+Tuple!(size_t, EditOp[])
+levenshteinDistanceAndPath(alias equals = (a,b) => a == b, Range1, Range2)
+    (auto ref Range1 s, auto ref Range2 t)
+    if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, Range1, Range2);
+    return levenshteinDistanceAndPath!(equals, Types)(s, t);
+}
+
+@safe unittest
+{
+    static struct S { string s; alias s this; }
+    assert(levenshteinDistanceAndPath(S("cat"), S("rat"))[0] == 1);
+    assert(levenshteinDistanceAndPath("cat", S("rat"))[0] == 1);
+    assert(levenshteinDistanceAndPath(S("cat"), "rat")[0] == 1);
+}
+
+// max
+/**
+Iterates the passed arguments and return the maximum value.
+
+Params:
+    args = The values to select the maximum from. At least two arguments must
+    be passed.
+
+Returns:
+    The maximum of the passed-in args. The type of the returned value is
+    the type among the passed arguments that is able to store the largest value.
+*/
+MaxType!T max(T...)(T args)
+    if (T.length >= 2)
+{
+    //Get "a"
+    static if (T.length <= 2)
+        alias a = args[0];
+    else
+        auto a = max(args[0 .. ($+1)/2]);
+    alias T0 = typeof(a);
+
+    //Get "b"
+    static if (T.length <= 3)
+        alias b = args[$-1];
+    else
+        auto b = max(args[($+1)/2 .. $]);
+    alias T1 = typeof(b);
+
+    import std.algorithm.internal : algoFormat;
+    static assert (is(typeof(a < b)),
+        algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof));
+
+    //Do the "max" proper with a and b
+    import std.functional : lessThan;
+    immutable chooseB = lessThan!(T0, T1)(a, b);
+    return cast(typeof(return)) (chooseB ? b : a);
+}
+
+///
+@safe unittest
+{
+    int a = 5;
+    short b = 6;
+    double c = 2;
+    auto d = max(a, b);
+    assert(is(typeof(d) == int));
+    assert(d == 6);
+    auto e = min(a, b, c);
+    assert(is(typeof(e) == double));
+    assert(e == 2);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int a = 5;
+    short b = 6;
+    double c = 2;
+    auto d = max(a, b);
+    static assert(is(typeof(d) == int));
+    assert(d == 6);
+    auto e = max(a, b, c);
+    static assert(is(typeof(e) == double));
+    assert(e == 6);
+    // mixed sign
+    a = -5;
+    uint f = 5;
+    static assert(is(typeof(max(a, f)) == uint));
+    assert(max(a, f) == 5);
+
+    //Test user-defined types
+    import std.datetime : Date;
+    assert(max(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(2012, 12, 21));
+    assert(max(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(2012, 12, 21));
+    assert(max(Date(1982, 1, 4), Date.min) == Date(1982, 1, 4));
+    assert(max(Date.min, Date(1982, 1, 4)) == Date(1982, 1, 4));
+    assert(max(Date(1982, 1, 4), Date.max) == Date.max);
+    assert(max(Date.max, Date(1982, 1, 4)) == Date.max);
+    assert(max(Date.min, Date.max) == Date.max);
+    assert(max(Date.max, Date.min) == Date.max);
+}
+
+// MinType
+private template MinType(T...)
+    if (T.length >= 1)
+{
+    static if (T.length == 1)
+    {
+        alias MinType = T[0];
+    }
+    else static if (T.length == 2)
+    {
+        static if (!is(typeof(T[0].min)))
+            alias MinType = CommonType!T;
+        else
+        {
+            enum hasMostNegative = is(typeof(mostNegative!(T[0]))) &&
+                                   is(typeof(mostNegative!(T[1])));
+            static if (hasMostNegative && mostNegative!(T[1]) < mostNegative!(T[0]))
+                alias MinType = T[1];
+            else static if (hasMostNegative && mostNegative!(T[1]) > mostNegative!(T[0]))
+                alias MinType = T[0];
+            else static if (T[1].max < T[0].max)
+                alias MinType = T[1];
+            else
+                alias MinType = T[0];
+        }
+    }
+    else
+    {
+        alias MinType = MinType!(MinType!(T[0 .. ($+1)/2]), MinType!(T[($+1)/2 .. $]));
+    }
+}
+
+// min
+/**
+Iterates the passed arguments and returns the minimum value.
+
+Params: args = The values to select the minimum from. At least two arguments
+    must be passed, and they must be comparable with `<`.
+Returns: The minimum of the passed-in values.
+*/
+MinType!T min(T...)(T args)
+    if (T.length >= 2)
+{
+    //Get "a"
+    static if (T.length <= 2)
+        alias a = args[0];
+    else
+        auto a = min(args[0 .. ($+1)/2]);
+    alias T0 = typeof(a);
+
+    //Get "b"
+    static if (T.length <= 3)
+        alias b = args[$-1];
+    else
+        auto b = min(args[($+1)/2 .. $]);
+    alias T1 = typeof(b);
+
+    import std.algorithm.internal : algoFormat;
+    static assert (is(typeof(a < b)),
+        algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof));
+
+    //Do the "min" proper with a and b
+    import std.functional : lessThan;
+    immutable chooseA = lessThan!(T0, T1)(a, b);
+    return cast(typeof(return)) (chooseA ? a : b);
+}
+
+///
+@safe unittest
+{
+    int a = 5;
+    short b = 6;
+    double c = 2;
+    auto d = min(a, b);
+    static assert(is(typeof(d) == int));
+    assert(d == 5);
+    auto e = min(a, b, c);
+    static assert(is(typeof(e) == double));
+    assert(e == 2);
+
+    // With arguments of mixed signedness, the return type is the one that can
+    // store the lowest values.
+    a = -10;
+    uint f = 10;
+    static assert(is(typeof(min(a, f)) == int));
+    assert(min(a, f) == -10);
+
+    // User-defined types that support comparison with < are supported.
+    import std.datetime;
+    assert(min(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(1982, 1, 4));
+    assert(min(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(1982, 1, 4));
+    assert(min(Date(1982, 1, 4), Date.min) == Date.min);
+    assert(min(Date.min, Date(1982, 1, 4)) == Date.min);
+    assert(min(Date(1982, 1, 4), Date.max) == Date(1982, 1, 4));
+    assert(min(Date.max, Date(1982, 1, 4)) == Date(1982, 1, 4));
+    assert(min(Date.min, Date.max) == Date.min);
+    assert(min(Date.max, Date.min) == Date.min);
+}
+
+// mismatch
+/**
+Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and
+stops at the first mismatch (according to $(D pred), by default
+equality). Returns a tuple with the reduced ranges that start with the
+two mismatched values. Performs $(BIGOH min(r1.length, r2.length))
+evaluations of $(D pred).
+
+See_Also:
+    $(WEB sgi.com/tech/stl/_mismatch.html, STL's _mismatch)
+*/
+Tuple!(Range1, Range2)
+mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
+    if (isInputRange!(Range1) && isInputRange!(Range2))
+{
+    for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
+    {
+        if (!binaryFun!(pred)(r1.front, r2.front)) break;
+    }
+    return tuple(r1, r2);
+}
+
+///
+@safe unittest
+{
+    int[]    x = [ 1,  5, 2, 7,   4, 3 ];
+    double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ];
+    auto m = mismatch(x, y);
+    assert(m[0] == x[3 .. $]);
+    assert(m[1] == y[3 .. $]);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 3 ];
+    int[] b = [ 1, 2, 4, 5 ];
+    auto mm = mismatch(a, b);
+    assert(mm[0] == [3]);
+    assert(mm[1] == [4, 5]);
+}
+
+/**
+Returns one of a collection of expressions based on the value of the switch
+expression.
+
+$(D choices) needs to be composed of pairs of test expressions and return
+expressions. Each test-expression is compared with $(D switchExpression) using
+$(D pred)($(D switchExpression) is the first argument) and if that yields true
+- the return expression is returned.
+
+Both the test and the return expressions are lazily evaluated.
+
+Params:
+
+switchExpression = The first argument for the predicate.
+
+choices = Pairs of test expressions and return expressions. The test
+expressions will be the second argument for the predicate, and the return
+expression will be returned if the predicate yields true with $(D
+switchExpression) and the test expression as arguments.  May also have a
+default return expression, that needs to be the last expression without a test
+expression before it. A return expression may be of void type only if it
+always throws.
+
+Returns: The return expression associated with the first test expression that
+made the predicate yield true, or the default return expression if no test
+expression matched.
+
+Throws: If there is no default return expression and the predicate does not
+yield true with any test expression - $(D SwitchError) is thrown. $(D
+SwitchError) is also thrown if a void return expression was executed without
+throwing anything.
+*/
+auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choices)
+{
+    import core.exception : SwitchError;
+    alias predicate = binaryFun!(pred);
+
+    foreach (index, ChoiceType; R)
+    {
+        //The even places in `choices` are for the predicate.
+        static if (index % 2 == 1)
+        {
+            if (predicate(switchExpression, choices[index - 1]()))
+            {
+                static if (is(typeof(choices[index]()) == void))
+                {
+                    choices[index]();
+                    throw new SwitchError("Choices that return void should throw");
+                }
+                else
+                {
+                    return choices[index]();
+                }
+            }
+        }
+    }
+
+    //In case nothing matched:
+    static if (R.length % 2 == 1) //If there is a default return expression:
+    {
+        static if (is(typeof(choices[$ - 1]()) == void))
+        {
+            choices[$ - 1]();
+            throw new SwitchError("Choices that return void should throw");
+        }
+        else
+        {
+            return choices[$ - 1]();
+        }
+    }
+    else //If there is no default return expression:
+    {
+        throw new SwitchError("Input not matched by any pattern");
+    }
+}
+
+///
+@safe unittest
+{
+    string res = 2.predSwitch!"a < b"(
+        1, "less than 1",
+        5, "less than 5",
+        10, "less than 10",
+        "greater or equal to 10");
+
+    assert(res == "less than 5");
+
+    //The arguments are lazy, which allows us to use predSwitch to create
+    //recursive functions:
+    int factorial(int n)
+    {
+        return n.predSwitch!"a <= b"(
+            -1, {throw new Exception("Can not calculate n! for n < 0");}(),
+            0, 1, // 0! = 1
+            n * factorial(n - 1) // n! = n * (n - 1)! for n >= 0
+            );
+    }
+    assert(factorial(3) == 6);
+
+    //Void return expressions are allowed if they always throw:
+    import std.exception : assertThrown;
+    assertThrown!Exception(factorial(-9));
+}
+
+unittest
+{
+    import core.exception : SwitchError;
+    import std.exception : assertThrown;
+
+    //Nothing matches - with default return expression:
+    assert(20.predSwitch!"a < b"(
+        1, "less than 1",
+        5, "less than 5",
+        10, "less than 10",
+        "greater or equal to 10") == "greater or equal to 10");
+
+    //Nothing matches - without default return expression:
+    assertThrown!SwitchError(20.predSwitch!"a < b"(
+        1, "less than 1",
+        5, "less than 5",
+        10, "less than 10",
+        ));
+
+    //Using the default predicate:
+    assert(2.predSwitch(
+                1, "one",
+                2, "two",
+                3, "three",
+                ) == "two");
+
+    //Void return expressions must always throw:
+    assertThrown!SwitchError(1.predSwitch(
+                0, "zero",
+                1, {}(), //A void return expression that doesn't throw
+                2, "two",
+                ));
+}
+
+/**
+Checks if the two ranges have the same number of elements. This function is
+optimized to always take advantage of the $(D length) member of either range
+if it exists.
+
+If both ranges have a length member, this function is $(BIGOH 1). Otherwise,
+this function is $(BIGOH min(r1.length, r2.length)).
+
+Params:
+    r1 = a finite input range
+    r2 = a finite input range
+
+Returns:
+    $(D true) if both ranges have the same length, $(D false) otherwise.
+*/
+bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2)
+    if (isInputRange!Range1 &&
+        isInputRange!Range2 &&
+        !isInfinite!Range1 &&
+        !isInfinite!Range2)
+{
+    static if (hasLength!(Range1) && hasLength!(Range2))
+    {
+        return r1.length == r2.length;
+    }
+    else static if (hasLength!(Range1) && !hasLength!(Range2))
+    {
+        size_t length;
+
+        while (!r2.empty)
+        {
+            r2.popFront;
+
+            if (++length > r1.length)
+            {
+                return false;
+            }
+        }
+
+        return !(length < r1.length);
+    }
+    else static if (!hasLength!(Range1) && hasLength!(Range2))
+    {
+        size_t length;
+
+        while (!r1.empty)
+        {
+            r1.popFront;
+
+            if (++length > r2.length)
+            {
+                return false;
+            }
+        }
+
+        return !(length < r2.length);
+    }
+    else
+    {
+        while (!r1.empty)
+        {
+           if (r2.empty)
+           {
+              return false;
+           }
+
+           r1.popFront;
+           r2.popFront;
+        }
+
+        return r2.empty;
+    }
+}
+
+///
+@safe nothrow pure unittest
+{
+    assert(isSameLength([1, 2, 3], [4, 5, 6]));
+    assert(isSameLength([0.3, 90.4, 23.7, 119.2], [42.6, 23.6, 95.5, 6.3]));
+    assert(isSameLength("abc", "xyz"));
+
+    int[] a;
+    int[] b;
+    assert(isSameLength(a, b));
+
+    assert(!isSameLength([1, 2, 3], [4, 5]));
+    assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]));
+    assert(!isSameLength("abcd", "xyz"));
+}
+
+// Test CTFE
+@safe pure unittest
+{
+    enum result1 = isSameLength([1, 2, 3], [4, 5, 6]);
+    static assert(result1);
+
+    enum result2 = isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]);
+    static assert(!result2);
+}
+
+@safe nothrow pure unittest
+{
+    import std.internal.test.dummyrange;
+
+    auto r1 = new ReferenceInputRange!int([1, 2, 3]);
+    auto r2 = new ReferenceInputRange!int([4, 5, 6]);
+    assert(isSameLength(r1, r2));
+
+    auto r3 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r4;
+    assert(isSameLength(r3, r4));
+
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r5;
+    auto r6 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+    assert(isSameLength(r5, r6));
+
+    auto r7 = new ReferenceInputRange!int([1, 2]);
+    auto r8 = new ReferenceInputRange!int([4, 5, 6]);
+    assert(!isSameLength(r7, r8));
+
+    auto r9 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]);
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r10;
+    assert(!isSameLength(r9, r10));
+
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r11;
+    auto r12 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]);
+    assert(!isSameLength(r11, r12));
+}
+
+/// For convenience
+alias AllocateGC = Flag!"allocateGC";
+
+/**
+Checks if both ranges are permutations of each other.
+
+This function can allocate if the $(D AllocateGC.yes) flag is passed. This has
+the benefit of have better complexity than the $(D AllocateGC.no) option. However,
+this option is only available for ranges whose equality can be determined via each
+element's $(D toHash) method. If customized equality is needed, then the $(D pred)
+template parameter can be passed, and the function will automatically switch to
+the non-allocating algorithm. See $(XREF functional,binaryFun) for more details on
+how to define $(D pred).
+
+Non-allocating forward range option: $(BIGOH n^2)
+Non-allocating forward range option with custom $(D pred): $(BIGOH n^2)
+Allocating forward range option: amortized $(BIGOH r1.length) + $(BIGOH r2.length)
+
+Params:
+    pred = an optional parameter to change how equality is defined
+    allocate_gc = AllocateGC.yes/no
+    r1 = A finite forward range
+    r2 = A finite forward range
+
+Returns:
+    $(D true) if all of the elements in $(D r1) appear the same number of times in $(D r2).
+    Otherwise, returns $(D false).
+*/
+
+bool isPermutation(AllocateGC allocate_gc, Range1, Range2)
+(Range1 r1, Range2 r2)
+    if (allocate_gc == AllocateGC.yes &&
+        isForwardRange!Range1 &&
+        isForwardRange!Range2 &&
+        !isInfinite!Range1 &&
+        !isInfinite!Range2)
+{
+    alias E1 = Unqual!(ElementType!Range1);
+    alias E2 = Unqual!(ElementType!Range2);
+
+    if (!isSameLength(r1.save, r2.save))
+    {
+        return false;
+    }
+
+    // Skip the elements at the beginning where r1.front == r2.front,
+    // they are in the same order and don't need to be counted.
+    while (!r1.empty && !r2.empty && r1.front == r2.front)
+    {
+        r1.popFront();
+        r2.popFront();
+    }
+
+    if (r1.empty && r2.empty)
+    {
+        return true;
+    }
+
+    int[CommonType!(E1, E2)] counts;
+
+    foreach (item; r1)
+    {
+        ++counts[item];
+    }
+
+    foreach (item; r2)
+    {
+        if (--counts[item] < 0)
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/// ditto
+bool isPermutation(alias pred = "a == b", Range1, Range2)
+(Range1 r1, Range2 r2)
+    if (is(typeof(binaryFun!(pred))) &&
+        isForwardRange!Range1 &&
+        isForwardRange!Range2 &&
+        !isInfinite!Range1 &&
+        !isInfinite!Range2)
+{
+    import std.algorithm.searching : count;
+
+    alias predEquals = binaryFun!(pred);
+    alias E1 = Unqual!(ElementType!Range1);
+    alias E2 = Unqual!(ElementType!Range2);
+
+    if (!isSameLength(r1.save, r2.save))
+    {
+        return false;
+    }
+
+    // Skip the elements at the beginning where r1.front == r2.front,
+    // they are in the same order and don't need to be counted.
+    while (!r1.empty && !r2.empty && predEquals(r1.front, r2.front))
+    {
+        r1.popFront();
+        r2.popFront();
+    }
+
+    if (r1.empty && r2.empty)
+    {
+        return true;
+    }
+
+    size_t r1_count;
+    size_t r2_count;
+
+    // At each element item, when computing the count of item, scan it while
+    // also keeping track of the scanning index. If the first occurrence
+    // of item in the scanning loop has an index smaller than the current index,
+    // then you know that the element has been seen before
+    outloop: foreach (index, item; r1.save.enumerate)
+    {
+        r1_count = 0;
+        r2_count = 0;
+
+        foreach (i, e; r1.save.enumerate)
+        {
+            if (predEquals(e, item) && i < index)
+            {
+                 continue outloop;
+            }
+            else if (predEquals(e, item))
+            {
+                ++r1_count;
+            }
+        }
+
+        r2_count = r2.save.count!pred(item);
+
+        if (r1_count != r2_count)
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+///
+@safe pure unittest
+{
+    assert(isPermutation([1, 2, 3], [3, 2, 1]));
+    assert(isPermutation([1.1, 2.3, 3.5], [2.3, 3.5, 1.1]));
+    assert(isPermutation("abc", "bca"));
+
+    assert(!isPermutation([1, 2], [3, 4]));
+    assert(!isPermutation([1, 1, 2, 3], [1, 2, 2, 3]));
+    assert(!isPermutation([1, 1], [1, 1, 1]));
+
+    // Faster, but allocates GC handled memory
+    assert(isPermutation!(AllocateGC.yes)([1.1, 2.3, 3.5], [2.3, 3.5, 1.1]));
+    assert(!isPermutation!(AllocateGC.yes)([1, 2], [3, 4]));
+}
+
+// Test @nogc inference
+@safe @nogc pure unittest
+{
+    static immutable arr1 = [1, 2, 3];
+    static immutable arr2 = [3, 2, 1];
+    assert(isPermutation(arr1, arr2));
+
+    static immutable arr3 = [1, 1, 2, 3];
+    static immutable arr4 = [1, 2, 2, 3];
+    assert(!isPermutation(arr3, arr4));
+}
+
+@safe pure unittest
+{
+    import std.internal.test.dummyrange;
+
+    auto r1 = new ReferenceForwardRange!int([1, 2, 3, 4]);
+    auto r2 = new ReferenceForwardRange!int([1, 2, 4, 3]);
+    assert(isPermutation(r1, r2));
+
+    auto r3 = new ReferenceForwardRange!int([1, 2, 3, 4]);
+    auto r4 = new ReferenceForwardRange!int([4, 2, 1, 3]);
+    assert(isPermutation!(AllocateGC.yes)(r3, r4));
+
+    auto r5 = new ReferenceForwardRange!int([1, 2, 3]);
+    auto r6 = new ReferenceForwardRange!int([4, 2, 1, 3]);
+    assert(!isPermutation(r5, r6));
+
+    auto r7 = new ReferenceForwardRange!int([4, 2, 1, 3]);
+    auto r8 = new ReferenceForwardRange!int([1, 2, 3]);
+    assert(!isPermutation!(AllocateGC.yes)(r7, r8));
+
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r9;
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r10;
+    assert(isPermutation(r9, r10));
+
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r11;
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r12;
+    assert(isPermutation!(AllocateGC.yes)(r11, r12));
+
+    alias mytuple = Tuple!(int, int);
+
+    assert(isPermutation!"a[0] == b[0]"(
+        [mytuple(1, 4), mytuple(2, 5)],
+        [mytuple(2, 3), mytuple(1, 2)]
+    ));
+}
+
+/**
+Get the _first argument `a` that passes an `if (unaryFun!pred(a))` test.  If
+no argument passes the test, return the last argument.
+
+Similar to behaviour of the `or` operator in dynamic languages such as Lisp's
+`(or ...)` and Python's `a or b or ...` except that the last argument is
+returned upon no match.
+
+Simplifies logic, for instance, in parsing rules where a set of alternative
+matchers are tried. The _first one that matches returns it match result,
+typically as an abstract syntax tree (AST).
+
+Bugs:
+Lazy parameters are currently, too restrictively, inferred by DMD to
+always throw even though they don't need to be. This makes it impossible to
+currently mark `either` as `nothrow`. See issue at $(BUGZILLA 12647).
+
+Returns:
+    The _first argument that passes the test `pred`.
+*/
+CommonType!(T, Ts) either(alias pred = a => a, T, Ts...)(T first, lazy Ts alternatives)
+    if (alternatives.length >= 1 &&
+        !is(CommonType!(T, Ts) == void) &&
+        allSatisfy!(ifTestable, T, Ts))
+{
+    alias predFun = unaryFun!pred;
+
+    if (predFun(first)) return first;
+
+    foreach (e; alternatives[0 .. $ - 1])
+        if (predFun(e)) return e;
+
+    return alternatives[$ - 1];
+}
+
+///
+@safe pure unittest
+{
+    const a = 1;
+    const b = 2;
+    auto ab = either(a, b);
+    static assert(is(typeof(ab) == const(int)));
+    assert(ab == a);
+
+    auto c = 2;
+    const d = 3;
+    auto cd = either!(a => a == 3)(c, d); // use predicate
+    static assert(is(typeof(cd) == int));
+    assert(cd == d);
+
+    auto e = 0;
+    const f = 2;
+    auto ef = either(e, f);
+    static assert(is(typeof(ef) == int));
+    assert(ef == f);
+
+    immutable p = 1;
+    immutable q = 2;
+    auto pq = either(p, q);
+    static assert(is(typeof(pq) == immutable(int)));
+    assert(pq == p);
+
+    assert(either(3, 4) == 3);
+    assert(either(0, 4) == 4);
+    assert(either(0, 0) == 0);
+    assert(either("", "a") == "");
+
+    string r = null;
+    assert(either(r, "a") == "a");
+    assert(either("a", "") == "a");
+
+    immutable s = [1, 2];
+    assert(either(s, s) == s);
+
+    assert(either([0, 1], [1, 2]) == [0, 1]);
+    assert(either([0, 1], [1]) == [0, 1]);
+    assert(either("a", "b") == "a");
+
+    static assert(!__traits(compiles, either(1, "a")));
+    static assert(!__traits(compiles, either(1.0, "a")));
+    static assert(!__traits(compiles, either('a', "a")));
+}
diff --git a/libphobos/src/std/algorithm/internal.d b/libphobos/src/std/algorithm/internal.d
new file mode 100644
index 00000000000..35ba503e3cc
--- /dev/null
+++ b/libphobos/src/std/algorithm/internal.d
@@ -0,0 +1,77 @@
+// Written in the D programming language.
+
+/// Helper functions for std.algorithm package.
+module std.algorithm.internal;
+
+
+// Same as std.string.format, but "self-importing".
+// Helps reduce code and imports, particularly in static asserts.
+// Also helps with missing imports errors.
+package template algoFormat()
+{
+    import std.format : format;
+    alias algoFormat = format;
+}
+
+// Internal random array generators
+version(unittest)
+{
+    package enum size_t maxArraySize = 50;
+    package enum size_t minArraySize = maxArraySize - 1;
+
+    package string[] rndstuff(T : string)()
+    {
+        import std.random : Random, unpredictableSeed, uniform;
+
+        static Random rnd;
+        static bool first = true;
+        if (first)
+        {
+            rnd = Random(unpredictableSeed);
+            first = false;
+        }
+        string[] result =
+            new string[uniform(minArraySize, maxArraySize, rnd)];
+        string alpha = "abcdefghijABCDEFGHIJ";
+        foreach (ref s; result)
+        {
+            foreach (i; 0 .. uniform(0u, 20u, rnd))
+            {
+                auto j = uniform(0, alpha.length - 1, rnd);
+                s ~= alpha[j];
+            }
+        }
+        return result;
+    }
+
+    package int[] rndstuff(T : int)()
+    {
+        import std.random : Random, unpredictableSeed, uniform;
+
+        static Random rnd;
+        static bool first = true;
+        if (first)
+        {
+            rnd = Random(unpredictableSeed);
+            first = false;
+        }
+        int[] result = new int[uniform(minArraySize, maxArraySize, rnd)];
+        foreach (ref i; result)
+        {
+            i = uniform(-100, 100, rnd);
+        }
+        return result;
+    }
+
+    package double[] rndstuff(T : double)()
+    {
+        double[] result;
+        foreach (i; rndstuff!(int)())
+        {
+            result ~= i / 50.0;
+        }
+        return result;
+    }
+}
+
+package(std) T* addressOf(T)(ref T val) { return &val; }
diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d
new file mode 100644
index 00000000000..50856915507
--- /dev/null
+++ b/libphobos/src/std/algorithm/iteration.d
@@ -0,0 +1,4397 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic _iteration algorithms.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 cache,
+        Eagerly evaluates and caches another range's $(D front).)
+$(T2 cacheBidirectional,
+        As above, but also provides $(D back) and $(D popBack).)
+$(T2 chunkBy,
+        $(D chunkBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]]))
+        returns a range containing 3 subranges: the first with just
+        $(D [1, 1]); the second with the elements $(D [1, 2]) and $(D [2, 2]);
+        and the third with just $(D [2, 1]).)
+$(T2 each,
+        $(D each!writeln([1, 2, 3])) eagerly prints the numbers $(D 1), $(D 2)
+        and $(D 3) on their own lines.)
+$(T2 filter,
+        $(D filter!(a => a > 0)([1, -1, 2, 0, -3])) iterates over elements $(D 1)
+        and $(D 2).)
+$(T2 filterBidirectional,
+        Similar to $(D filter), but also provides $(D back) and $(D popBack) at
+        a small increase in cost.)
+$(T2 fold,
+        $(D fold!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10).)
+$(T2 group,
+        $(D group([5, 2, 2, 3, 3])) returns a range containing the tuples
+        $(D tuple(5, 1)), $(D tuple(2, 2)), and $(D tuple(3, 2)).)
+$(T2 joiner,
+        $(D joiner(["hello", "world!"], "; ")) returns a range that iterates
+        over the characters $(D "hello; world!"). No new string is created -
+        the existing inputs are iterated.)
+$(T2 map,
+        $(D map!(a => a * 2)([1, 2, 3])) lazily returns a range with the numbers
+        $(D 2), $(D 4), $(D 6).)
+$(T2 permutations,
+        Lazily computes all permutations using Heap's algorithm.)
+$(T2 reduce,
+        $(D reduce!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10).
+        This is the old implementation of `fold`.)
+$(T2 splitter,
+        Lazily splits a range by a separator.)
+$(T2 sum,
+        Same as $(D fold), but specialized for accurate summation.)
+$(T2 uniq,
+        Iterates over the unique elements in a range, which is assumed sorted.)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_iteration.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.iteration;
+
+// FIXME
+import std.functional; // : unaryFun, binaryFun;
+import std.range.primitives;
+import std.traits;
+
+template aggregate(fun...) if (fun.length >= 1)
+{
+    /* --Intentionally not ddoc--
+     * Aggregates elements in each subrange of the given range of ranges using
+     * the given aggregating function(s).
+     * Params:
+     *  fun = One or more aggregating functions (binary functions that return a
+     *      single _aggregate value of their arguments).
+     *  ror = A range of ranges to be aggregated.
+     *
+     * Returns:
+     * A range representing the aggregated value(s) of each subrange
+     * of the original range. If only one aggregating function is specified,
+     * each element will be the aggregated value itself; if multiple functions
+     * are specified, each element will be a tuple of the aggregated values of
+     * each respective function.
+     */
+    auto aggregate(RoR)(RoR ror)
+        if (isInputRange!RoR && isIterable!(ElementType!RoR))
+    {
+        return ror.map!(reduce!fun);
+    }
+
+    unittest
+    {
+        import std.algorithm.comparison : equal, max, min;
+
+        auto data = [[4, 2, 1, 3], [4, 9, -1, 3, 2], [3]];
+
+        // Single aggregating function
+        auto agg1 = data.aggregate!max;
+        assert(agg1.equal([4, 9, 3]));
+
+        // Multiple aggregating functions
+        import std.typecons : tuple;
+        auto agg2 = data.aggregate!(max, min);
+        assert(agg2.equal([
+            tuple(4, 1),
+            tuple(9, -1),
+            tuple(3, 3)
+        ]));
+    }
+}
+
+/++
+$(D cache) eagerly evaluates $(D front) of $(D range)
+on each construction or call to $(D popFront),
+to store the result in a cache.
+The result is then directly returned when $(D front) is called,
+rather than re-evaluated.
+
+This can be a useful function to place in a chain, after functions
+that have expensive evaluation, as a lazy alternative to $(XREF array,array).
+In particular, it can be placed after a call to $(D map), or before a call
+to $(D filter).
+
+$(D cache) may provide bidirectional iteration if needed, but since
+this comes at an increased cost, it must be explicitly requested via the
+call to $(D cacheBidirectional). Furthermore, a bidirectional cache will
+evaluate the "center" element twice, when there is only one element left in
+the range.
+
+$(D cache) does not provide random access primitives,
+as $(D cache) would be unable to cache the random accesses.
+If $(D Range) provides slicing primitives,
+then $(D cache) will provide the same slicing primitives,
+but $(D hasSlicing!Cache) will not yield true (as the $(XREF_PACK _range,primitives,hasSlicing)
+trait also checks for random access).
+
+Params:
+    range = an input range
+
+Returns:
+    an input range with the cached values of range
++/
+auto cache(Range)(Range range)
+if (isInputRange!Range)
+{
+    return _Cache!(Range, false)(range);
+}
+
+/// ditto
+auto cacheBidirectional(Range)(Range range)
+if (isBidirectionalRange!Range)
+{
+    return _Cache!(Range, true)(range);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.stdio, std.range;
+    import std.typecons : tuple;
+
+    ulong counter = 0;
+    double fun(int x)
+    {
+        ++counter;
+        // http://en.wikipedia.org/wiki/Quartic_function
+        return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5;
+    }
+    // Without cache, with array (greedy)
+    auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
+                             .filter!(a => a[1] < 0)()
+                             .map!(a => a[0])()
+                             .array();
+
+    // the values of x that have a negative y are:
+    assert(equal(result1, [-3, -2, 2]));
+
+    // Check how many times fun was evaluated.
+    // As many times as the number of items in both source and result.
+    assert(counter == iota(-4, 5).length + result1.length);
+
+    counter = 0;
+    // Without array, with cache (lazy)
+    auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
+                             .cache()
+                             .filter!(a => a[1] < 0)()
+                             .map!(a => a[0])();
+
+    // the values of x that have a negative y are:
+    assert(equal(result2, [-3, -2, 2]));
+
+    // Check how many times fun was evaluated.
+    // Only as many times as the number of items in source.
+    assert(counter == iota(-4, 5).length);
+}
+
+/++
+Tip: $(D cache) is eager when evaluating elements. If calling front on the
+underlying _range has a side effect, it will be observeable before calling
+front on the actual cached _range.
+
+Furthermore, care should be taken composing $(D cache) with $(XREF _range,take).
+By placing $(D take) before $(D cache), then $(D cache) will be "aware"
+of when the _range ends, and correctly stop caching elements when needed.
+If calling front has no side effect though, placing $(D take) after $(D cache)
+may yield a faster _range.
+
+Either way, the resulting ranges will be equivalent, but maybe not at the
+same cost or side effects.
++/
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+    int i = 0;
+
+    auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop);
+    auto r1 = r.take(3).cache();
+    auto r2 = r.cache().take(3);
+
+    assert(equal(r1, [0, 1, 2]));
+    assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared.
+
+    assert(equal(r2, [0, 1, 2]));
+    assert(i == 3); //cache has accessed 3. It is still stored internally by cache.
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+    auto a = [1, 2, 3, 4];
+    assert(equal(a.map!(a => (a - 1) * a)().cache(),                      [ 0, 2, 6, 12]));
+    assert(equal(a.map!(a => (a - 1) * a)().cacheBidirectional().retro(), [12, 6, 2,  0]));
+    auto r1 = [1, 2, 3, 4].cache()             [1 .. $];
+    auto r2 = [1, 2, 3, 4].cacheBidirectional()[1 .. $];
+    assert(equal(r1, [2, 3, 4]));
+    assert(equal(r2, [2, 3, 4]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    //immutable test
+    static struct S
+    {
+        int i;
+        this(int i)
+        {
+            //this.i = i;
+        }
+    }
+    immutable(S)[] s = [S(1), S(2), S(3)];
+    assert(equal(s.cache(),              s));
+    assert(equal(s.cacheBidirectional(), s));
+}
+
+@safe pure nothrow unittest
+{
+    import std.algorithm.comparison : equal;
+
+    //safety etc
+    auto a = [1, 2, 3, 4];
+    assert(equal(a.cache(),              a));
+    assert(equal(a.cacheBidirectional(), a));
+}
+
+@safe unittest
+{
+    char[][] stringbufs = ["hello".dup, "world".dup];
+    auto strings = stringbufs.map!((a)=>a.idup)().cache();
+    assert(strings.front is strings.front);
+}
+
+@safe unittest
+{
+    import std.range;
+    auto c = [1, 2, 3].cycle().cache();
+    c = c[1 .. $];
+    auto d = c[0 .. 1];
+}
+
+@safe unittest
+{
+    static struct Range
+    {
+        bool initialized = false;
+        bool front() @property {return initialized = true;}
+        void popFront() {initialized = false;}
+        enum empty = false;
+    }
+    auto r = Range().cache();
+    assert(r.source.initialized == true);
+}
+
+private struct _Cache(R, bool bidir)
+{
+    import core.exception : RangeError;
+
+    private
+    {
+        import std.algorithm.internal : algoFormat;
+        import std.meta : AliasSeq;
+
+        alias E  = ElementType!R;
+        alias UE = Unqual!E;
+
+        R source;
+
+        static if (bidir) alias CacheTypes = AliasSeq!(UE, UE);
+        else              alias CacheTypes = AliasSeq!UE;
+        CacheTypes caches;
+
+        static assert(isAssignable!(UE, E) && is(UE : E),
+            algoFormat("Cannot instantiate range with %s because %s elements are not assignable to %s.", R.stringof, E.stringof, UE.stringof));
+    }
+
+    this(R range)
+    {
+        source = range;
+        if (!range.empty)
+        {
+             caches[0] = source.front;
+             static if (bidir)
+                 caches[1] = source.back;
+        }
+    }
+
+    static if (isInfinite!R)
+        enum empty = false;
+    else
+        bool empty() @property
+        {
+            return source.empty;
+        }
+
+    static if (hasLength!R) auto length() @property
+    {
+        return source.length;
+    }
+
+    E front() @property
+    {
+        version(assert) if (empty) throw new RangeError();
+        return caches[0];
+    }
+    static if (bidir) E back() @property
+    {
+        version(assert) if (empty) throw new RangeError();
+        return caches[1];
+    }
+
+    void popFront()
+    {
+        version(assert) if (empty) throw new RangeError();
+        source.popFront();
+        if (!source.empty)
+            caches[0] = source.front;
+        else
+            caches = CacheTypes.init;
+    }
+    static if (bidir) void popBack()
+    {
+        version(assert) if (empty) throw new RangeError();
+        source.popBack();
+        if (!source.empty)
+            caches[1] = source.back;
+        else
+            caches = CacheTypes.init;
+    }
+
+    static if (isForwardRange!R)
+    {
+        private this(R source, ref CacheTypes caches)
+        {
+            this.source = source;
+            this.caches = caches;
+        }
+        typeof(this) save() @property
+        {
+            return typeof(this)(source.save, caches);
+        }
+    }
+
+    static if (hasSlicing!R)
+    {
+        enum hasEndSlicing = is(typeof(source[size_t.max .. $]));
+
+        static if (hasEndSlicing)
+        {
+            private static struct DollarToken{}
+            enum opDollar = DollarToken.init;
+
+            auto opSlice(size_t low, DollarToken)
+            {
+                return typeof(this)(source[low .. $]);
+            }
+        }
+
+        static if (!isInfinite!R)
+        {
+            typeof(this) opSlice(size_t low, size_t high)
+            {
+                return typeof(this)(source[low .. high]);
+            }
+        }
+        else static if (hasEndSlicing)
+        {
+            auto opSlice(size_t low, size_t high)
+            in
+            {
+                assert(low <= high);
+            }
+            body
+            {
+                import std.range : take;
+                return this[low .. $].take(high - low);
+            }
+        }
+    }
+}
+
+/**
+$(D auto map(Range)(Range r) if (isInputRange!(Unqual!Range));)
+
+Implements the homonym function (also known as $(D transform)) present
+in many languages of functional flavor. The call $(D map!(fun)(range))
+returns a range of which elements are obtained by applying $(D fun(a))
+left to right for all elements $(D a) in $(D range). The original ranges are
+not changed. Evaluation is done lazily.
+
+Params:
+    fun = one or more functions
+    r = an input range
+
+Returns:
+    a range with each fun applied to all the elements. If there is more than one
+    fun, the element type will be $(D Tuple) containing one element for each fun.
+
+See_Also:
+    $(WEB en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
+*/
+template map(fun...) if (fun.length >= 1)
+{
+    auto map(Range)(Range r) if (isInputRange!(Unqual!Range))
+    {
+        import std.meta : AliasSeq, staticMap;
+
+        alias RE = ElementType!(Range);
+        static if (fun.length > 1)
+        {
+            import std.functional : adjoin;
+            import std.meta : staticIndexOf;
+
+            alias _funs = staticMap!(unaryFun, fun);
+            alias _fun = adjoin!_funs;
+
+            // Once DMD issue #5710 is fixed, this validation loop can be moved into a template.
+            foreach(f; _funs)
+            {
+                static assert(!is(typeof(f(RE.init)) == void),
+                    "Mapping function(s) must not return void: " ~ _funs.stringof);
+            }
+        }
+        else
+        {
+            alias _fun = unaryFun!fun;
+            alias _funs = AliasSeq!(_fun);
+
+            // Do the validation separately for single parameters due to DMD issue #15777.
+            static assert(!is(typeof(_fun(RE.init)) == void),
+                "Mapping function(s) must not return void: " ~ _funs.stringof);
+        }
+
+        return MapResult!(_fun, Range)(r);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range : chain;
+    int[] arr1 = [ 1, 2, 3, 4 ];
+    int[] arr2 = [ 5, 6 ];
+    auto squares = map!(a => a * a)(chain(arr1, arr2));
+    assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ]));
+}
+
+/**
+Multiple functions can be passed to $(D map). In that case, the
+element type of $(D map) is a tuple containing one element for each
+function.
+*/
+@safe unittest
+{
+    auto sums = [2, 4, 6, 8];
+    auto products = [1, 4, 9, 16];
+
+    size_t i = 0;
+    foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a"))
+    {
+        assert(result[0] == sums[i]);
+        assert(result[1] == products[i]);
+        ++i;
+    }
+}
+
+/**
+You may alias $(D map) with some function(s) to a symbol and use
+it separately:
+*/
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : to;
+
+    alias stringize = map!(to!string);
+    assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
+}
+
+@safe unittest
+{
+    // Verify workaround for DMD #15777
+
+    import std.algorithm.mutation, std.string;
+    auto foo(string[] args)
+    {
+        return args.map!strip;
+    }
+}
+
+private struct MapResult(alias fun, Range)
+{
+    alias R = Unqual!Range;
+    R _input;
+
+    static if (isBidirectionalRange!R)
+    {
+        @property auto ref back()()
+        {
+            return fun(_input.back);
+        }
+
+        void popBack()()
+        {
+            _input.popBack();
+        }
+    }
+
+    this(R input)
+    {
+        _input = input;
+    }
+
+    static if (isInfinite!R)
+    {
+        // Propagate infinite-ness.
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return _input.empty;
+        }
+    }
+
+    void popFront()
+    {
+        _input.popFront();
+    }
+
+    @property auto ref front()
+    {
+        return fun(_input.front);
+    }
+
+    static if (isRandomAccessRange!R)
+    {
+        static if (is(typeof(_input[ulong.max])))
+            private alias opIndex_t = ulong;
+        else
+            private alias opIndex_t = uint;
+
+        auto ref opIndex(opIndex_t index)
+        {
+            return fun(_input[index]);
+        }
+    }
+
+    static if (hasLength!R)
+    {
+        @property auto length()
+        {
+            return _input.length;
+        }
+
+        alias opDollar = length;
+    }
+
+    static if (hasSlicing!R)
+    {
+        static if (is(typeof(_input[ulong.max .. ulong.max])))
+            private alias opSlice_t = ulong;
+        else
+            private alias opSlice_t = uint;
+
+        static if (hasLength!R)
+        {
+            auto opSlice(opSlice_t low, opSlice_t high)
+            {
+                return typeof(this)(_input[low .. high]);
+            }
+        }
+        else static if (is(typeof(_input[opSlice_t.max .. $])))
+        {
+            struct DollarToken{}
+            enum opDollar = DollarToken.init;
+            auto opSlice(opSlice_t low, DollarToken)
+            {
+                return typeof(this)(_input[low .. $]);
+            }
+
+            auto opSlice(opSlice_t low, opSlice_t high)
+            {
+                import std.range : take;
+                return this[low .. $].take(high - low);
+            }
+        }
+    }
+
+    static if (isForwardRange!R)
+    {
+        @property auto save()
+        {
+            return typeof(this)(_input.save);
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : to;
+    import std.functional : adjoin;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    alias stringize = map!(to!string);
+    assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
+
+    uint counter;
+    alias count = map!((a) { return counter++; });
+    assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ]));
+
+    counter = 0;
+    adjoin!((a) { return counter++; }, (a) { return counter++; })(1);
+    alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; });
+    //assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ]));
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+    import std.ascii : toUpper;
+    import std.range;
+    import std.typecons : tuple;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] arr1 = [ 1, 2, 3, 4 ];
+    const int[] arr1Const = arr1;
+    int[] arr2 = [ 5, 6 ];
+    auto squares = map!("a * a")(arr1Const);
+    assert(squares[$ - 1] == 16);
+    assert(equal(squares, [ 1, 4, 9, 16 ][]));
+    assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
+
+    // Test the caching stuff.
+    assert(squares.back == 16);
+    auto squares2 = squares.save;
+    assert(squares2.back == 16);
+
+    assert(squares2.front == 1);
+    squares2.popFront();
+    assert(squares2.front == 4);
+    squares2.popBack();
+    assert(squares2.front == 4);
+    assert(squares2.back == 9);
+
+    assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
+
+    uint i;
+    foreach (e; map!("a", "a * a")(arr1))
+    {
+        assert(e[0] == ++i);
+        assert(e[1] == i * i);
+    }
+
+    // Test length.
+    assert(squares.length == 4);
+    assert(map!"a * a"(chain(arr1, arr2)).length == 6);
+
+    // Test indexing.
+    assert(squares[0] == 1);
+    assert(squares[1] == 4);
+    assert(squares[2] == 9);
+    assert(squares[3] == 16);
+
+    // Test slicing.
+    auto squareSlice = squares[1..squares.length - 1];
+    assert(equal(squareSlice, [4, 9][]));
+    assert(squareSlice.back == 9);
+    assert(squareSlice[1] == 9);
+
+    // Test on a forward range to make sure it compiles when all the fancy
+    // stuff is disabled.
+    auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1));
+    assert(fibsSquares.front == 1);
+    fibsSquares.popFront();
+    fibsSquares.popFront();
+    assert(fibsSquares.front == 4);
+    fibsSquares.popFront();
+    assert(fibsSquares.front == 9);
+
+    auto repeatMap = map!"a"(repeat(1));
+    static assert(isInfinite!(typeof(repeatMap)));
+
+    auto intRange = map!"a"([1,2,3]);
+    static assert(isRandomAccessRange!(typeof(intRange)));
+
+    foreach (DummyType; AllDummyRanges)
+    {
+        DummyType d;
+        auto m = map!"a * a"(d);
+
+        static assert(propagatesRangeType!(typeof(m), DummyType));
+        assert(equal(m, [1,4,9,16,25,36,49,64,81,100]));
+    }
+
+    //Test string access
+    string  s1 = "hello world!";
+    dstring s2 = "日本語";
+    dstring s3 = "hello world!"d;
+    auto ms1 = map!(std.ascii.toUpper)(s1);
+    auto ms2 = map!(std.ascii.toUpper)(s2);
+    auto ms3 = map!(std.ascii.toUpper)(s3);
+    static assert(!is(ms1[0])); //narrow strings can't be indexed
+    assert(ms2[0] == '日');
+    assert(ms3[0] == 'H');
+    static assert(!is(ms1[0..1])); //narrow strings can't be sliced
+    assert(equal(ms2[0..2], "日本"w));
+    assert(equal(ms3[0..2], "HE"));
+
+    // Issue 5753
+    static void voidFun(int) {}
+    static int nonvoidFun(int) { return 0; }
+    static assert(!__traits(compiles, map!voidFun([1])));
+    static assert(!__traits(compiles, map!(voidFun, voidFun)([1])));
+    static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1])));
+    static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1])));
+    static assert(!__traits(compiles, map!(a => voidFun(a))([1])));
+
+    // Phobos issue #15480
+    auto dd = map!(z => z * z, c => c * c * c)([ 1, 2, 3, 4 ]);
+    assert(dd[0] == tuple(1, 1));
+    assert(dd[1] == tuple(4, 8));
+    assert(dd[2] == tuple(9, 27));
+    assert(dd[3] == tuple(16, 64));
+    assert(dd.length == 4);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+    auto LL = iota(1L, 4L);
+    auto m = map!"a*a"(LL);
+    assert(equal(m, [1L, 4L, 9L]));
+}
+
+@safe unittest
+{
+    import std.range : iota;
+
+    // Issue #10130 - map of iota with const step.
+    const step = 2;
+    assert(map!(i => i)(iota(0, 10, step)).walkLength == 5);
+
+    // Need these to all by const to repro the float case, due to the
+    // CommonType template used in the float specialization of iota.
+    const floatBegin = 0.0;
+    const floatEnd = 1.0;
+    const floatStep = 0.02;
+    assert(map!(i => i)(iota(floatBegin, floatEnd, floatStep)).walkLength == 50);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+    //slicing infinites
+    auto rr = iota(0, 5).cycle().map!"a * a"();
+    alias RR = typeof(rr);
+    static assert(hasSlicing!RR);
+    rr = rr[6 .. $]; //Advances 1 cycle and 1 unit
+    assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0]));
+}
+
+@safe unittest
+{
+    import std.range;
+    struct S {int* p;}
+    auto m = immutable(S).init.repeat().map!"a".save;
+}
+
+// each
+/**
+Eagerly iterates over $(D r) and calls $(D pred) over _each element.
+
+If no predicate is specified, $(D each) will default to doing nothing
+but consuming the entire range. $(D .front) will be evaluated, but this
+can be avoided by explicitly specifying a predicate lambda with a
+$(D lazy) parameter.
+
+$(D each) also supports $(D opApply)-based iterators, so it will work
+with e.g. $(XREF parallelism, parallel).
+
+Params:
+    pred = predicate to apply to each element of the range
+    r = range or iterable over which each iterates
+
+See_Also: $(XREF range,tee)
+
+ */
+template each(alias pred = "a")
+{
+    import std.meta : AliasSeq;
+    alias BinaryArgs = AliasSeq!(pred, "i", "a");
+
+    enum isRangeUnaryIterable(R) =
+        is(typeof(unaryFun!pred(R.init.front)));
+
+    enum isRangeBinaryIterable(R) =
+        is(typeof(binaryFun!BinaryArgs(0, R.init.front)));
+
+    enum isRangeIterable(R) =
+        isInputRange!R &&
+        (isRangeUnaryIterable!R || isRangeBinaryIterable!R);
+
+    enum isForeachUnaryIterable(R) =
+        is(typeof((R r) {
+            foreach (ref a; r)
+                cast(void)unaryFun!pred(a);
+        }));
+
+    enum isForeachBinaryIterable(R) =
+        is(typeof((R r) {
+            foreach (i, ref a; r)
+                cast(void)binaryFun!BinaryArgs(i, a);
+        }));
+
+    enum isForeachIterable(R) =
+        (!isForwardRange!R || isDynamicArray!R) &&
+        (isForeachUnaryIterable!R || isForeachBinaryIterable!R);
+
+    void each(Range)(Range r)
+    if (isRangeIterable!Range && !isForeachIterable!Range)
+    {
+        debug(each) pragma(msg, "Using while for ", Range.stringof);
+        static if (isRangeUnaryIterable!Range)
+        {
+            while (!r.empty)
+            {
+                cast(void)unaryFun!pred(r.front);
+                r.popFront();
+            }
+        }
+        else // if (isRangeBinaryIterable!Range)
+        {
+            size_t i = 0;
+            while (!r.empty)
+            {
+                cast(void)binaryFun!BinaryArgs(i, r.front);
+                r.popFront();
+                i++;
+            }
+        }
+    }
+
+    void each(Iterable)(Iterable r)
+        if (isForeachIterable!Iterable)
+    {
+        debug(each) pragma(msg, "Using foreach for ", Iterable.stringof);
+        static if (isForeachUnaryIterable!Iterable)
+        {
+            foreach (ref e; r)
+                cast(void)unaryFun!pred(e);
+        }
+        else // if (isForeachBinaryIterable!Iterable)
+        {
+            foreach (i, ref e; r)
+                cast(void)binaryFun!BinaryArgs(i, e);
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.range : iota;
+
+    long[] arr;
+    iota(5).each!(n => arr ~= n);
+    assert(arr == [0, 1, 2, 3, 4]);
+
+    // If the range supports it, the value can be mutated in place
+    arr.each!((ref n) => n++);
+    assert(arr == [1, 2, 3, 4, 5]);
+
+    arr.each!"a++";
+    assert(arr == [2, 3, 4, 5, 6]);
+
+    // by-ref lambdas are not allowed for non-ref ranges
+    static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++))));
+
+    // The default predicate consumes the range
+    auto m = arr.map!(n => n);
+    (&m).each();
+    assert(m.empty);
+
+    // Indexes are also available for in-place mutations
+    arr[] = 0;
+    arr.each!"a=i"();
+    assert(arr == [0, 1, 2, 3, 4]);
+
+    // opApply iterators work as well
+    static assert(is(typeof({
+        import std.parallelism;
+        arr.parallel.each!"a++";
+    })));
+}
+
+/**
+$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));)
+
+Implements the higher order _filter function. The predicate is passed to
+$(XREF functional,unaryFun), and can either accept a string, or any callable
+that can be executed via $(D pred(element)).
+
+Params:
+    predicate = Function to apply to each element of range
+    range = Input range of elements
+
+Returns:
+    $(D filter!(predicate)(range)) returns a new range containing only elements $(D x) in $(D range) for
+    which $(D predicate(x)) returns $(D true).
+
+See_Also:
+    $(WEB en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function))
+ */
+template filter(alias predicate) if (is(typeof(unaryFun!predicate)))
+{
+    auto filter(Range)(Range range) if (isInputRange!(Unqual!Range))
+    {
+        return FilterResult!(unaryFun!predicate, Range)(range);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.math : approxEqual;
+    import std.range;
+
+    int[] arr = [ 1, 2, 3, 4, 5 ];
+
+    // Sum all elements
+    auto small = filter!(a => a < 3)(arr);
+    assert(equal(small, [ 1, 2 ]));
+
+    // Sum again, but with Uniform Function Call Syntax (UFCS)
+    auto sum = arr.filter!(a => a < 3);
+    assert(equal(sum, [ 1, 2 ]));
+
+    // In combination with chain() to span multiple ranges
+    int[] a = [ 3, -2, 400 ];
+    int[] b = [ 100, -101, 102 ];
+    auto r = chain(a, b).filter!(a => a > 0);
+    assert(equal(r, [ 3, 400, 100, 102 ]));
+
+    // Mixing convertible types is fair game, too
+    double[] c = [ 2.5, 3.0 ];
+    auto r1 = chain(c, a, b).filter!(a => cast(int) a != a);
+    assert(approxEqual(r1, [ 2.5 ]));
+}
+
+private struct FilterResult(alias pred, Range)
+{
+    alias R = Unqual!Range;
+    R _input;
+
+    this(R r)
+    {
+        _input = r;
+        while (!_input.empty && !pred(_input.front))
+        {
+            _input.popFront();
+        }
+    }
+
+    auto opSlice() { return this; }
+
+    static if (isInfinite!Range)
+    {
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty() { return _input.empty; }
+    }
+
+    void popFront()
+    {
+        do
+        {
+            _input.popFront();
+        } while (!_input.empty && !pred(_input.front));
+    }
+
+    @property auto ref front()
+    {
+        return _input.front;
+    }
+
+    static if (isForwardRange!R)
+    {
+        @property auto save()
+        {
+            return typeof(this)(_input.save);
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+    import std.range;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 3, 4, 2 ];
+    auto r = filter!("a > 3")(a);
+    static assert(isForwardRange!(typeof(r)));
+    assert(equal(r, [ 4 ][]));
+
+    a = [ 1, 22, 3, 42, 5 ];
+    auto under10 = filter!("a < 10")(a);
+    assert(equal(under10, [1, 3, 5][]));
+    static assert(isForwardRange!(typeof(under10)));
+    under10.front = 4;
+    assert(equal(under10, [4, 3, 5][]));
+    under10.front = 40;
+    assert(equal(under10, [40, 3, 5][]));
+    under10.front = 1;
+
+    auto infinite = filter!"a > 2"(repeat(3));
+    static assert(isInfinite!(typeof(infinite)));
+    static assert(isForwardRange!(typeof(infinite)));
+
+    foreach (DummyType; AllDummyRanges) {
+        DummyType d;
+        auto f = filter!"a & 1"(d);
+        assert(equal(f, [1,3,5,7,9]));
+
+        static if (isForwardRange!DummyType) {
+            static assert(isForwardRange!(typeof(f)));
+        }
+    }
+
+    // With delegates
+    int x = 10;
+    int overX(int a) { return a > x; }
+    typeof(filter!overX(a)) getFilter()
+    {
+        return filter!overX(a);
+    }
+    auto r1 = getFilter();
+    assert(equal(r1, [22, 42]));
+
+    // With chain
+    auto nums = [0,1,2,3,4];
+    assert(equal(filter!overX(chain(a, nums)), [22, 42]));
+
+    // With copying of inner struct Filter to Map
+    auto arr = [1,2,3,4,5];
+    auto m = map!"a + 1"(filter!"a < 4"(arr));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [ 3, 4 ];
+    const aConst = a;
+    auto r = filter!("a > 3")(aConst);
+    assert(equal(r, [ 4 ][]));
+
+    a = [ 1, 22, 3, 42, 5 ];
+    auto under10 = filter!("a < 10")(a);
+    assert(equal(under10, [1, 3, 5][]));
+    assert(equal(under10.save, [1, 3, 5][]));
+    assert(equal(under10.save, under10));
+
+    // With copying of inner struct Filter to Map
+    auto arr = [1,2,3,4,5];
+    auto m = map!"a + 1"(filter!"a < 4"(arr));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.functional : compose, pipe;
+
+    assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]),
+                    [2,6,10]));
+    assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]),
+            [2,6,10]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int x = 10;
+    int underX(int a) { return a < x; }
+    const(int)[] list = [ 1, 2, 10, 11, 3, 4 ];
+    assert(equal(filter!underX(list), [ 1, 2, 3, 4 ]));
+}
+
+/**
+ * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));)
+ *
+ * Similar to $(D filter), except it defines a bidirectional
+ * range. There is a speed disadvantage - the constructor spends time
+ * finding the last element in the range that satisfies the filtering
+ * condition (in addition to finding the first one). The advantage is
+ * that the filtered range can be spanned from both directions. Also,
+ * $(XREF range, retro) can be applied against the filtered range.
+ *
+ * The predicate is passed to $(XREF functional,unaryFun), and can either
+ * accept a string, or any callable that can be executed via $(D pred(element)).
+ *
+ * Params:
+ *     pred = Function to apply to each element of range
+ *     r = Bidirectional range of elements
+ *
+ * Returns:
+ *     a new range containing only the elements in r for which pred returns $(D true).
+ */
+template filterBidirectional(alias pred)
+{
+    auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range))
+    {
+        return FilterBidiResult!(unaryFun!pred, Range)(r);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+
+    int[] arr = [ 1, 2, 3, 4, 5 ];
+    auto small = filterBidirectional!("a < 3")(arr);
+    static assert(isBidirectionalRange!(typeof(small)));
+    assert(small.back == 2);
+    assert(equal(small, [ 1, 2 ]));
+    assert(equal(retro(small), [ 2, 1 ]));
+    // In combination with chain() to span multiple ranges
+    int[] a = [ 3, -2, 400 ];
+    int[] b = [ 100, -101, 102 ];
+    auto r = filterBidirectional!("a > 0")(chain(a, b));
+    assert(r.back == 102);
+}
+
+private struct FilterBidiResult(alias pred, Range)
+{
+    alias R = Unqual!Range;
+    R _input;
+
+    this(R r)
+    {
+        _input = r;
+        while (!_input.empty && !pred(_input.front)) _input.popFront();
+        while (!_input.empty && !pred(_input.back)) _input.popBack();
+    }
+
+    @property bool empty() { return _input.empty; }
+
+    void popFront()
+    {
+        do
+        {
+            _input.popFront();
+        } while (!_input.empty && !pred(_input.front));
+    }
+
+    @property auto ref front()
+    {
+        return _input.front;
+    }
+
+    void popBack()
+    {
+        do
+        {
+            _input.popBack();
+        } while (!_input.empty && !pred(_input.back));
+    }
+
+    @property auto ref back()
+    {
+        return _input.back;
+    }
+
+    @property auto save()
+    {
+        return typeof(this)(_input.save);
+    }
+}
+
+// group
+struct Group(alias pred, R) if (isInputRange!R)
+{
+    import std.typecons : Rebindable, tuple, Tuple;
+
+    private alias comp = binaryFun!pred;
+
+    private alias E = ElementType!R;
+    static if ((is(E == class) || is(E == interface)) &&
+               (is(E == const) || is(E == immutable)))
+    {
+        private alias MutableE = Rebindable!E;
+    }
+    else static if (is(E : Unqual!E))
+    {
+        private alias MutableE = Unqual!E;
+    }
+    else
+    {
+        private alias MutableE = E;
+    }
+
+    private R _input;
+    private Tuple!(MutableE, uint) _current;
+
+    this(R input)
+    {
+        _input = input;
+        if (!_input.empty) popFront();
+    }
+
+    void popFront()
+    {
+        if (_input.empty)
+        {
+            _current[1] = 0;
+        }
+        else
+        {
+            _current = tuple(_input.front, 1u);
+            _input.popFront();
+            while (!_input.empty && comp(_current[0], _input.front))
+            {
+                ++_current[1];
+                _input.popFront();
+            }
+        }
+    }
+
+    static if (isInfinite!R)
+    {
+        enum bool empty = false;  // Propagate infiniteness.
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return _current[1] == 0;
+        }
+    }
+
+    @property auto ref front()
+    {
+        assert(!empty);
+        return _current;
+    }
+
+    static if (isForwardRange!R) {
+        @property typeof(this) save() {
+            typeof(this) ret = this;
+            ret._input = this._input.save;
+            ret._current = this._current;
+            return ret;
+        }
+    }
+}
+
+/**
+Groups consecutively equivalent elements into a single tuple of the element and
+the number of its repetitions.
+
+Similarly to $(D uniq), $(D group) produces a range that iterates over unique
+consecutive elements of the given range. Each element of this range is a tuple
+of the element and the number of times it is repeated in the original range.
+Equivalence of elements is assessed by using the predicate $(D pred), which
+defaults to $(D "a == b").  The predicate is passed to $(XREF functional,binaryFun),
+and can either accept a string, or any callable that can be executed via
+$(D pred(element, element)).
+
+Params:
+    pred = Binary predicate for determining equivalence of two elements.
+    r = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to
+        iterate over.
+
+Returns: A range of elements of type $(D Tuple!(ElementType!R, uint)),
+representing each consecutively unique element and its respective number of
+occurrences in that run.  This will be an input range if $(D R) is an input
+range, and a forward range in all other cases.
+*/
+Group!(pred, Range) group(alias pred = "a == b", Range)(Range r)
+{
+    return typeof(return)(r);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.typecons : tuple, Tuple;
+
+    int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
+    assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
+        tuple(4, 3u), tuple(5, 1u) ][]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+    import std.typecons : tuple, Tuple;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
+    assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
+                            tuple(4, 3u), tuple(5, 1u) ][]));
+    static assert(isForwardRange!(typeof(group(arr))));
+
+    foreach (DummyType; AllDummyRanges) {
+        DummyType d;
+        auto g = group(d);
+
+        static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g)));
+
+        assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u),
+            tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u),
+            tuple(9, 1u), tuple(10, 1u)]));
+    }
+}
+
+unittest
+{
+    // Issue 13857
+    immutable(int)[] a1 = [1,1,2,2,2,3,4,4,5,6,6,7,8,9,9,9];
+    auto g1 = group(a1);
+
+    // Issue 13162
+    immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0];
+    auto g2 = a2.group;
+
+    // Issue 10104
+    const a3 = [1, 1, 2, 2];
+    auto g3 = a3.group;
+
+    interface I {}
+    class C : I {}
+    const C[] a4 = [new const C()];
+    auto g4 = a4.group!"a is b";
+
+    immutable I[] a5 = [new immutable C()];
+    auto g5 = a5.group!"a is b";
+
+    const(int[][]) a6 = [[1], [1]];
+    auto g6 = a6.group;
+}
+
+// Used by implementation of chunkBy for non-forward input ranges.
+private struct ChunkByChunkImpl(alias pred, Range)
+    if (isInputRange!Range && !isForwardRange!Range)
+{
+    alias fun = binaryFun!pred;
+
+    private Range r;
+    private ElementType!Range prev;
+
+    this(Range range, ElementType!Range _prev)
+    {
+        r = range;
+        prev = _prev;
+    }
+
+    @property bool empty()
+    {
+        return r.empty || !fun(prev, r.front);
+    }
+
+    @property ElementType!Range front() { return r.front; }
+    void popFront() { r.popFront(); }
+}
+
+private template ChunkByImplIsUnary(alias pred, Range)
+{
+    static if (is(typeof(binaryFun!pred(ElementType!Range.init,
+                                        ElementType!Range.init)) : bool))
+        enum ChunkByImplIsUnary = false;
+    else static if (is(typeof(
+            unaryFun!pred(ElementType!Range.init) ==
+            unaryFun!pred(ElementType!Range.init))))
+        enum ChunkByImplIsUnary = true;
+    else
+        static assert(0, "chunkBy expects either a binary predicate or "~
+                         "a unary predicate on range elements of type: "~
+                         ElementType!Range.stringof);
+}
+
+// Implementation of chunkBy for non-forward input ranges.
+private struct ChunkByImpl(alias pred, Range)
+    if (isInputRange!Range && !isForwardRange!Range)
+{
+    enum bool isUnary = ChunkByImplIsUnary!(pred, Range);
+
+    static if (isUnary)
+        alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
+    else
+        alias eq = binaryFun!pred;
+
+    private Range r;
+    private ElementType!Range _prev;
+
+    this(Range _r)
+    {
+        r = _r;
+        if (!empty)
+        {
+            // Check reflexivity if predicate is claimed to be an equivalence
+            // relation.
+            assert(eq(r.front, r.front),
+                   "predicate is not reflexive");
+
+            // _prev's type may be a nested struct, so must be initialized
+            // directly in the constructor (cannot call savePred()).
+            _prev = r.front;
+        }
+        else
+        {
+            // We won't use _prev, but must be initialized.
+            _prev = typeof(_prev).init;
+        }
+    }
+    @property bool empty() { return r.empty; }
+
+    @property auto front()
+    {
+        static if (isUnary)
+        {
+            import std.typecons : tuple;
+            return tuple(unaryFun!pred(_prev),
+                         ChunkByChunkImpl!(eq, Range)(r, _prev));
+        }
+        else
+        {
+            return ChunkByChunkImpl!(eq, Range)(r, _prev);
+        }
+    }
+
+    void popFront()
+    {
+        while (!r.empty)
+        {
+            if (!eq(_prev, r.front))
+            {
+                _prev = r.front;
+                break;
+            }
+            r.popFront();
+        }
+    }
+}
+
+// Single-pass implementation of chunkBy for forward ranges.
+private struct ChunkByImpl(alias pred, Range)
+    if (isForwardRange!Range)
+{
+    import std.typecons : RefCounted;
+
+    enum bool isUnary = ChunkByImplIsUnary!(pred, Range);
+
+    static if (isUnary)
+        alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
+    else
+        alias eq = binaryFun!pred;
+
+    // Outer range
+    static struct Impl
+    {
+        size_t groupNum;
+        Range  current;
+        Range  next;
+    }
+
+    // Inner range
+    static struct Group
+    {
+        private size_t groupNum;
+        private Range  start;
+        private Range  current;
+
+        private RefCounted!Impl mothership;
+
+        this(RefCounted!Impl origin)
+        {
+            groupNum = origin.groupNum;
+
+            start = origin.current.save;
+            current = origin.current.save;
+            assert(!start.empty);
+
+            mothership = origin;
+
+            // Note: this requires reflexivity.
+            assert(eq(start.front, current.front),
+                   "predicate is not reflexive");
+        }
+
+        @property bool empty() { return groupNum == size_t.max; }
+        @property auto ref front() { return current.front; }
+
+        void popFront()
+        {
+            current.popFront();
+
+            // Note: this requires transitivity.
+            if (current.empty || !eq(start.front, current.front))
+            {
+                if (groupNum == mothership.groupNum)
+                {
+                    // If parent range hasn't moved on yet, help it along by
+                    // saving location of start of next Group.
+                    mothership.next = current.save;
+                }
+
+                groupNum = size_t.max;
+            }
+        }
+
+        @property auto save()
+        {
+            auto copy = this;
+            copy.current = current.save;
+            return copy;
+        }
+    }
+    static assert(isForwardRange!Group);
+
+    private RefCounted!Impl impl;
+
+    this(Range r)
+    {
+        impl = RefCounted!Impl(0, r, r.save);
+    }
+
+    @property bool empty() { return impl.current.empty; }
+
+    @property auto front()
+    {
+        static if (isUnary)
+        {
+            import std.typecons : tuple;
+            return tuple(unaryFun!pred(impl.current.front), Group(impl));
+        }
+        else
+        {
+            return Group(impl);
+        }
+    }
+
+    void popFront()
+    {
+        // Scan for next group. If we're lucky, one of our Groups would have
+        // already set .next to the start of the next group, in which case the
+        // loop is skipped.
+        while (!impl.next.empty && eq(impl.current.front, impl.next.front))
+        {
+            impl.next.popFront();
+        }
+
+        impl.current = impl.next.save;
+
+        // Indicate to any remaining Groups that we have moved on.
+        impl.groupNum++;
+    }
+
+    @property auto save()
+    {
+        // Note: the new copy of the range will be detached from any existing
+        // satellite Groups, and will not benefit from the .next acceleration.
+        return typeof(this)(impl.current.save);
+    }
+
+    static assert(isForwardRange!(typeof(this)));
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+
+    size_t popCount = 0;
+    class RefFwdRange
+    {
+        int[]  impl;
+
+        this(int[] data) { impl = data; }
+        @property bool empty() { return impl.empty; }
+        @property auto ref front() { return impl.front; }
+        void popFront()
+        {
+            impl.popFront();
+            popCount++;
+        }
+        @property auto save() { return new RefFwdRange(impl); }
+    }
+    static assert(isForwardRange!RefFwdRange);
+
+    auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]);
+    auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2));
+    auto outerSave1 = groups.save;
+
+    // Sanity test
+    assert(groups.equal!equal([[1, 3, 5], [2, 4], [7], [6, 8], [9]]));
+    assert(groups.empty);
+
+    // Performance test for single-traversal use case: popFront should not have
+    // been called more times than there are elements if we traversed the
+    // segmented range exactly once.
+    assert(popCount == 9);
+
+    // Outer range .save test
+    groups = outerSave1.save;
+    assert(!groups.empty);
+
+    // Inner range .save test
+    auto grp1 = groups.front.save;
+    auto grp1b = grp1.save;
+    assert(grp1b.equal([1, 3, 5]));
+    assert(grp1.save.equal([1, 3, 5]));
+
+    // Inner range should remain consistent after outer range has moved on.
+    groups.popFront();
+    assert(grp1.save.equal([1, 3, 5]));
+
+    // Inner range should not be affected by subsequent inner ranges.
+    assert(groups.front.equal([2, 4]));
+    assert(grp1.save.equal([1, 3, 5]));
+}
+
+/**
+ * Chunks an input range into subranges of equivalent adjacent elements.
+ *
+ * Equivalence is defined by the predicate $(D pred), which can be either
+ * binary, which is passed to $(XREF functional,binaryFun), or unary, which is
+ * passed to $(XREF functional,unaryFun). In the binary form, two _range elements
+ * $(D a) and $(D b) are considered equivalent if $(D pred(a,b)) is true. In
+ * unary form, two elements are considered equivalent if $(D pred(a) == pred(b))
+ * is true.
+ *
+ * This predicate must be an equivalence relation, that is, it must be
+ * reflexive ($(D pred(x,x)) is always true), symmetric
+ * ($(D pred(x,y) == pred(y,x))), and transitive ($(D pred(x,y) && pred(y,z))
+ * implies $(D pred(x,z))). If this is not the case, the range returned by
+ * chunkBy may assert at runtime or behave erratically.
+ *
+ * Params:
+ *  pred = Predicate for determining equivalence.
+ *  r = The range to be chunked.
+ *
+ * Returns: With a binary predicate, a range of ranges is returned in which
+ * all elements in a given subrange are equivalent under the given predicate.
+ * With a unary predicate, a range of tuples is returned, with the tuple
+ * consisting of the result of the unary predicate for each subrange, and the
+ * subrange itself.
+ *
+ * Notes:
+ *
+ * Equivalent elements separated by an intervening non-equivalent element will
+ * appear in separate subranges; this function only considers adjacent
+ * equivalence. Elements in the subranges will always appear in the same order
+ * they appear in the original range.
+ *
+ * See_also:
+ * $(LREF group), which collapses adjacent equivalent elements into a single
+ * element.
+ */
+auto chunkBy(alias pred, Range)(Range r)
+    if (isInputRange!Range)
+{
+    return ChunkByImpl!(pred, Range)(r);
+}
+
+/// Showing usage with binary predicate:
+/*FIXME: @safe*/ unittest
+{
+    import std.algorithm.comparison : equal;
+
+    // Grouping by particular attribute of each element:
+    auto data = [
+        [1, 1],
+        [1, 2],
+        [2, 2],
+        [2, 3]
+    ];
+
+    auto r1 = data.chunkBy!((a,b) => a[0] == b[0]);
+    assert(r1.equal!equal([
+        [[1, 1], [1, 2]],
+        [[2, 2], [2, 3]]
+    ]));
+
+    auto r2 = data.chunkBy!((a,b) => a[1] == b[1]);
+    assert(r2.equal!equal([
+        [[1, 1]],
+        [[1, 2], [2, 2]],
+        [[2, 3]]
+    ]));
+}
+
+version(none) // this example requires support for non-equivalence relations
+unittest
+{
+    auto data = [
+        [1, 1],
+        [1, 2],
+        [2, 2],
+        [2, 3]
+    ];
+
+    version(none)
+    {
+        // Grouping by maximum adjacent difference:
+        import std.math : abs;
+        auto r3 = [1, 3, 2, 5, 4, 9, 10].chunkBy!((a, b) => abs(a-b) < 3);
+        assert(r3.equal!equal([
+            [1, 3, 2],
+            [5, 4],
+            [9, 10]
+        ]));
+    }
+}
+
+/// Showing usage with unary predicate:
+/* FIXME: pure @safe nothrow*/ unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.typecons : tuple;
+
+    // Grouping by particular attribute of each element:
+    auto range =
+    [
+        [1, 1],
+        [1, 1],
+        [1, 2],
+        [2, 2],
+        [2, 3],
+        [2, 3],
+        [3, 3]
+    ];
+
+    auto byX = chunkBy!(a => a[0])(range);
+    auto expected1 =
+    [
+        tuple(1, [[1, 1], [1, 1], [1, 2]]),
+        tuple(2, [[2, 2], [2, 3], [2, 3]]),
+        tuple(3, [[3, 3]])
+    ];
+    foreach (e; byX)
+    {
+        assert(!expected1.empty);
+        assert(e[0] == expected1.front[0]);
+        assert(e[1].equal(expected1.front[1]));
+        expected1.popFront();
+    }
+
+    auto byY = chunkBy!(a => a[1])(range);
+    auto expected2 =
+    [
+        tuple(1, [[1, 1], [1, 1]]),
+        tuple(2, [[1, 2], [2, 2]]),
+        tuple(3, [[2, 3], [2, 3], [3, 3]])
+    ];
+    foreach (e; byY)
+    {
+        assert(!expected2.empty);
+        assert(e[0] == expected2.front[0]);
+        assert(e[1].equal(expected2.front[1]));
+        expected2.popFront();
+    }
+}
+
+/*FIXME: pure @safe nothrow*/ unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.typecons : tuple;
+
+    struct Item { int x, y; }
+
+    // Force R to have only an input range API with reference semantics, so
+    // that we're not unknowingly making use of array semantics outside of the
+    // range API.
+    class RefInputRange(R)
+    {
+        R data;
+        this(R _data) pure @safe nothrow { data = _data; }
+        @property bool empty() pure @safe nothrow { return data.empty; }
+        @property auto front() pure @safe nothrow { return data.front; }
+        void popFront() pure @safe nothrow { data.popFront(); }
+    }
+    auto refInputRange(R)(R range) { return new RefInputRange!R(range); }
+
+    {
+        auto arr = [ Item(1,2), Item(1,3), Item(2,3) ];
+        static assert(isForwardRange!(typeof(arr)));
+
+        auto byX = chunkBy!(a => a.x)(arr);
+        static assert(isForwardRange!(typeof(byX)));
+
+        auto byX_subrange1 = byX.front[1].save;
+        auto byX_subrange2 = byX.front[1].save;
+        static assert(isForwardRange!(typeof(byX_subrange1)));
+        static assert(isForwardRange!(typeof(byX_subrange2)));
+
+        byX.popFront();
+        assert(byX_subrange1.equal([ Item(1,2), Item(1,3) ]));
+        byX_subrange1.popFront();
+        assert(byX_subrange1.equal([ Item(1,3) ]));
+        assert(byX_subrange2.equal([ Item(1,2), Item(1,3) ]));
+
+        auto byY = chunkBy!(a => a.y)(arr);
+        static assert(isForwardRange!(typeof(byY)));
+
+        auto byY2 = byY.save;
+        static assert(is(typeof(byY) == typeof(byY2)));
+        byY.popFront();
+        assert(byY.front[0] == 3);
+        assert(byY.front[1].equal([ Item(1,3), Item(2,3) ]));
+        assert(byY2.front[0] == 2);
+        assert(byY2.front[1].equal([ Item(1,2) ]));
+    }
+
+    // Test non-forward input ranges.
+    {
+        auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
+        auto byX = chunkBy!(a => a.x)(range);
+        assert(byX.front[0] == 1);
+        assert(byX.front[1].equal([ Item(1,1), Item(1,2) ]));
+        byX.popFront();
+        assert(byX.front[0] == 2);
+        assert(byX.front[1].equal([ Item(2,2) ]));
+        byX.popFront();
+        assert(byX.empty);
+        assert(range.empty);
+
+        range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
+        auto byY = chunkBy!(a => a.y)(range);
+        assert(byY.front[0] == 1);
+        assert(byY.front[1].equal([ Item(1,1) ]));
+        byY.popFront();
+        assert(byY.front[0] == 2);
+        assert(byY.front[1].equal([ Item(1,2), Item(2,2) ]));
+        byY.popFront();
+        assert(byY.empty);
+        assert(range.empty);
+    }
+}
+
+// Issue 13595
+version(none) // This requires support for non-equivalence relations
+unittest
+{
+    import std.algorithm.comparison : equal;
+    auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].chunkBy!((x, y) => ((x*y) % 3) == 0);
+    assert(r.equal!equal([
+        [1],
+        [2, 3, 4],
+        [5, 6, 7],
+        [8, 9]
+    ]));
+}
+
+// Issue 13805
+unittest
+{
+    [""].map!((s) => s).chunkBy!((x, y) => true);
+}
+
+// joiner
+/**
+Lazily joins a range of ranges with a separator. The separator itself
+is a range. If a separator is not provided, then the ranges are
+joined directly without anything in between them (often called $(D flatten)
+in other languages).
+
+Params:
+    r = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of input
+        ranges to be joined.
+    sep = A $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) of
+        element(s) to serve as separators in the joined range.
+
+Returns:
+A range of elements in the joined range. This will be a forward range if
+both outer and inner ranges of $(D RoR) are forward ranges; otherwise it will
+be only an input range.
+
+See_also:
+$(XREF range,chain), which chains a sequence of ranges with compatible elements
+into a single range.
+ */
+auto joiner(RoR, Separator)(RoR r, Separator sep)
+if (isInputRange!RoR && isInputRange!(ElementType!RoR)
+        && isForwardRange!Separator
+        && is(ElementType!Separator : ElementType!(ElementType!RoR)))
+{
+    static struct Result
+    {
+        private RoR _items;
+        private ElementType!RoR _current;
+        private Separator _sep, _currentSep;
+
+        // This is a mixin instead of a function for the following reason (as
+        // explained by Kenji Hara): "This is necessary from 2.061.  If a
+        // struct has a nested struct member, it must be directly initialized
+        // in its constructor to avoid leaving undefined state.  If you change
+        // setItem to a function, the initialization of _current field is
+        // wrapped into private member function, then compiler could not detect
+        // that is correctly initialized while constructing.  To avoid the
+        // compiler error check, string mixin is used."
+        private enum setItem =
+        q{
+            if (!_items.empty)
+            {
+                // If we're exporting .save, we must not consume any of the
+                // subranges, since RoR.save does not guarantee that the states
+                // of the subranges are also saved.
+                static if (isForwardRange!RoR &&
+                           isForwardRange!(ElementType!RoR))
+                    _current = _items.front.save;
+                else
+                    _current = _items.front;
+            }
+        };
+
+        private void useSeparator()
+        {
+            // Separator must always come after an item.
+            assert(_currentSep.empty && !_items.empty,
+                    "joiner: internal error");
+            _items.popFront();
+
+            // If there are no more items, we're done, since separators are not
+            // terminators.
+            if (_items.empty) return;
+
+            if (_sep.empty)
+            {
+                // Advance to the next range in the
+                // input
+                while (_items.front.empty)
+                {
+                    _items.popFront();
+                    if (_items.empty) return;
+                }
+                mixin(setItem);
+            }
+            else
+            {
+                _currentSep = _sep.save;
+                assert(!_currentSep.empty);
+            }
+        }
+
+        private enum useItem =
+        q{
+            // FIXME: this will crash if either _currentSep or _current are
+            // class objects, because .init is null when the ctor invokes this
+            // mixin.
+            //assert(_currentSep.empty && _current.empty,
+            //        "joiner: internal error");
+
+            // Use the input
+            if (_items.empty) return;
+            mixin(setItem);
+            if (_current.empty)
+            {
+                // No data in the current item - toggle to use the separator
+                useSeparator();
+            }
+        };
+
+        this(RoR items, Separator sep)
+        {
+            _items = items;
+            _sep = sep;
+
+            //mixin(useItem); // _current should be initialized in place
+            if (_items.empty)
+                _current = _current.init;   // set invalid state
+            else
+            {
+                // If we're exporting .save, we must not consume any of the
+                // subranges, since RoR.save does not guarantee that the states
+                // of the subranges are also saved.
+                static if (isForwardRange!RoR &&
+                           isForwardRange!(ElementType!RoR))
+                    _current = _items.front.save;
+                else
+                    _current = _items.front;
+
+                if (_current.empty)
+                {
+                    // No data in the current item - toggle to use the separator
+                    useSeparator();
+                }
+            }
+        }
+
+        @property auto empty()
+        {
+            return _items.empty;
+        }
+
+        @property ElementType!(ElementType!RoR) front()
+        {
+            if (!_currentSep.empty) return _currentSep.front;
+            assert(!_current.empty);
+            return _current.front;
+        }
+
+        void popFront()
+        {
+            assert(!_items.empty);
+            // Using separator?
+            if (!_currentSep.empty)
+            {
+                _currentSep.popFront();
+                if (!_currentSep.empty) return;
+                mixin(useItem);
+            }
+            else
+            {
+                // we're using the range
+                _current.popFront();
+                if (!_current.empty) return;
+                useSeparator();
+            }
+        }
+
+        static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
+        {
+            @property auto save()
+            {
+                Result copy = this;
+                copy._items = _items.save;
+                copy._current = _current.save;
+                copy._sep = _sep.save;
+                copy._currentSep = _currentSep.save;
+                return copy;
+            }
+        }
+    }
+    return Result(r, sep);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : text;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    static assert(isInputRange!(typeof(joiner([""], ""))));
+    static assert(isForwardRange!(typeof(joiner([""], ""))));
+    assert(equal(joiner([""], "xyz"), ""), text(joiner([""], "xyz")));
+    assert(equal(joiner(["", ""], "xyz"), "xyz"), text(joiner(["", ""], "xyz")));
+    assert(equal(joiner(["", "abc"], "xyz"), "xyzabc"));
+    assert(equal(joiner(["abc", ""], "xyz"), "abcxyz"));
+    assert(equal(joiner(["abc", "def"], "xyz"), "abcxyzdef"));
+    assert(equal(joiner(["Mary", "has", "a", "little", "lamb"], "..."),
+                    "Mary...has...a...little...lamb"));
+    assert(equal(joiner(["abc", "def"]), "abcdef"));
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range.primitives;
+    import std.range.interfaces;
+    // joiner() should work for non-forward ranges too.
+    auto r = inputRangeObject(["abc", "def"]);
+    assert (equal(joiner(r, "xyz"), "abcxyzdef"));
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+
+    // Related to issue 8061
+    auto r = joiner([
+        inputRangeObject("abc"),
+        inputRangeObject("def"),
+    ], "-*-");
+
+    assert(equal(r, "abc-*-def"));
+
+    // Test case where separator is specified but is empty.
+    auto s = joiner([
+        inputRangeObject("abc"),
+        inputRangeObject("def"),
+    ], "");
+
+    assert(equal(s, "abcdef"));
+
+    // Test empty separator with some empty elements
+    auto t = joiner([
+        inputRangeObject("abc"),
+        inputRangeObject(""),
+        inputRangeObject("def"),
+        inputRangeObject(""),
+    ], "");
+
+    assert(equal(t, "abcdef"));
+
+    // Test empty elements with non-empty separator
+    auto u = joiner([
+        inputRangeObject(""),
+        inputRangeObject("abc"),
+        inputRangeObject(""),
+        inputRangeObject("def"),
+        inputRangeObject(""),
+    ], "+-");
+
+    assert(equal(u, "+-abc+-+-def+-"));
+
+    // Issue 13441: only(x) as separator
+    string[][] lines = [null];
+    lines
+        .joiner(only("b"))
+        .array();
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    // Transience correctness test
+    struct TransientRange
+    {
+    @safe:
+        int[][] src;
+        int[] buf;
+
+        this(int[][] _src)
+        {
+            src = _src;
+            buf.length = 100;
+        }
+        @property bool empty() { return src.empty; }
+        @property int[] front()
+        {
+            assert(src.front.length <= buf.length);
+            buf[0 .. src.front.length] = src.front[0..$];
+            return buf[0 .. src.front.length];
+        }
+        void popFront() { src.popFront(); }
+    }
+
+    // Test embedded empty elements
+    auto tr1 = TransientRange([[], [1,2,3], [], [4]]);
+    assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4]));
+
+    // Test trailing empty elements
+    auto tr2 = TransientRange([[], [1,2,3], []]);
+    assert(equal(joiner(tr2, [0]), [0,1,2,3,0]));
+
+    // Test no empty elements
+    auto tr3 = TransientRange([[1,2], [3,4]]);
+    assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4]));
+
+    // Test consecutive empty elements
+    auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]);
+    assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4]));
+
+    // Test consecutive trailing empty elements
+    auto tr5 = TransientRange([[1,2], [3,4], [], []]);
+    assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1]));
+}
+
+/// Ditto
+auto joiner(RoR)(RoR r)
+if (isInputRange!RoR && isInputRange!(ElementType!RoR))
+{
+    static struct Result
+    {
+    private:
+        RoR _items;
+        ElementType!RoR _current;
+        enum prepare =
+        q{
+            // Skip over empty subranges.
+            if (_items.empty) return;
+            while (_items.front.empty)
+            {
+                _items.popFront();
+                if (_items.empty) return;
+            }
+            // We cannot export .save method unless we ensure subranges are not
+            // consumed when a .save'd copy of ourselves is iterated over. So
+            // we need to .save each subrange we traverse.
+            static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
+                _current = _items.front.save;
+            else
+                _current = _items.front;
+        };
+    public:
+        this(RoR r)
+        {
+            _items = r;
+            //mixin(prepare); // _current should be initialized in place
+
+            // Skip over empty subranges.
+            while (!_items.empty && _items.front.empty)
+                _items.popFront();
+
+            if (_items.empty)
+                _current = _current.init;   // set invalid state
+            else
+            {
+                // We cannot export .save method unless we ensure subranges are not
+                // consumed when a .save'd copy of ourselves is iterated over. So
+                // we need to .save each subrange we traverse.
+                static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
+                    _current = _items.front.save;
+                else
+                    _current = _items.front;
+            }
+        }
+        static if (isInfinite!RoR)
+        {
+            enum bool empty = false;
+        }
+        else
+        {
+            @property auto empty()
+            {
+                return _items.empty;
+            }
+        }
+        @property auto ref front()
+        {
+            assert(!empty);
+            return _current.front;
+        }
+        void popFront()
+        {
+            assert(!_current.empty);
+            _current.popFront();
+            if (_current.empty)
+            {
+                assert(!_items.empty);
+                _items.popFront();
+                mixin(prepare);
+            }
+        }
+        static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
+        {
+            @property auto save()
+            {
+                Result copy = this;
+                copy._items = _items.save;
+                copy._current = _current.save;
+                return copy;
+            }
+        }
+    }
+    return Result(r);
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range.interfaces;
+    import std.range : repeat;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    static assert(isInputRange!(typeof(joiner([""]))));
+    static assert(isForwardRange!(typeof(joiner([""]))));
+    assert(equal(joiner([""]), ""));
+    assert(equal(joiner(["", ""]), ""));
+    assert(equal(joiner(["", "abc"]), "abc"));
+    assert(equal(joiner(["abc", ""]), "abc"));
+    assert(equal(joiner(["abc", "def"]), "abcdef"));
+    assert(equal(joiner(["Mary", "has", "a", "little", "lamb"]),
+                    "Maryhasalittlelamb"));
+    assert(equal(joiner(std.range.repeat("abc", 3)), "abcabcabc"));
+
+    // joiner allows in-place mutation!
+    auto a = [ [1, 2, 3], [42, 43] ];
+    auto j = joiner(a);
+    j.front = 44;
+    assert(a == [ [44, 2, 3], [42, 43] ]);
+
+    // bugzilla 8240
+    assert(equal(joiner([inputRangeObject("")]), ""));
+
+    // issue 8792
+    auto b = [[1], [2], [3]];
+    auto jb = joiner(b);
+    auto js = jb.save;
+    assert(equal(jb, js));
+
+    auto js2 = jb.save;
+    jb.popFront();
+    assert(!equal(jb, js));
+    assert(equal(js2, js));
+    js.popFront();
+    assert(equal(jb, js));
+    assert(!equal(js2, js));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    struct TransientRange
+    {
+    @safe:
+        int[] _buf;
+        int[][] _values;
+        this(int[][] values)
+        {
+            _values = values;
+            _buf = new int[128];
+        }
+        @property bool empty()
+        {
+            return _values.length == 0;
+        }
+        @property auto front()
+        {
+            foreach (i; 0 .. _values.front.length)
+            {
+                _buf[i] = _values[0][i];
+            }
+            return _buf[0 .. _values.front.length];
+        }
+        void popFront()
+        {
+            _values = _values[1 .. $];
+        }
+    }
+
+    auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]);
+
+    // Can't use array() or equal() directly because they fail with transient
+    // .front.
+    int[] result;
+    foreach (c; rr.joiner()) {
+        result ~= c;
+    }
+
+    assert(equal(result, [1,2,3,4,5,6,7]));
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : algoFormat;
+    import std.algorithm.comparison : equal;
+
+    struct TransientRange
+    {
+    @safe:
+        dchar[] _buf;
+        dstring[] _values;
+        this(dstring[] values)
+        {
+            _buf.length = 128;
+            _values = values;
+        }
+        @property bool empty()
+        {
+            return _values.length == 0;
+        }
+        @property auto front()
+        {
+            foreach (i; 0 .. _values.front.length)
+            {
+                _buf[i] = _values[0][i];
+            }
+            return _buf[0 .. _values.front.length];
+        }
+        void popFront()
+        {
+            _values = _values[1 .. $];
+        }
+    }
+
+    auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]);
+
+    // Can't use array() or equal() directly because they fail with transient
+    // .front.
+    dchar[] result;
+    foreach (c; rr.joiner()) {
+        result ~= c;
+    }
+
+    import std.conv : to;
+    assert(equal(result, "abc12def34"d),
+        //Convert to string for assert's message
+        to!string("Unexpected result: '%s'"d.algoFormat(result)));
+}
+
+// Issue 8061
+unittest
+{
+    import std.range.interfaces;
+    import std.conv : to;
+
+    auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]);
+    assert(isForwardRange!(typeof(r)));
+
+    auto str = to!string(r);
+    assert(str == "abcd");
+}
+
+/++
+Implements the homonym function (also known as $(D accumulate), $(D
+compress), $(D inject), or $(D foldl)) present in various programming
+languages of functional flavor. There is also $(LREF fold) which does
+the same thing but with the opposite parameter order.
+The call $(D reduce!(fun)(seed, range)) first assigns $(D seed) to
+an internal variable $(D result), also called the accumulator.
+Then, for each element $(D x) in $(D range), $(D result = fun(result, x))
+gets evaluated. Finally, $(D result) is returned.
+The one-argument version $(D reduce!(fun)(range))
+works similarly, but it uses the first element of the range as the
+seed (the range must be non-empty).
+
+Returns:
+    the accumulated $(D result)
+
+See_Also:
+    $(WEB en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function))
+
+    $(LREF fold) is functionally equivalent to $(LREF reduce) with the argument order reversed,
+    and without the need to use $(LREF tuple) for multiple seeds. This makes it easier
+    to use in UFCS chains.
+
+    $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers
+    precise summing of floating point numbers.
++/
+template reduce(fun...) if (fun.length >= 1)
+{
+    import std.meta : staticMap;
+
+    alias binfuns = staticMap!(binaryFun, fun);
+    static if (fun.length > 1)
+        import std.typecons : tuple, isTuple;
+
+    /++
+    No-seed version. The first element of $(D r) is used as the seed's value.
+
+    For each function $(D f) in $(D fun), the corresponding
+    seed type $(D S) is $(D Unqual!(typeof(f(e, e)))), where $(D e) is an
+    element of $(D r): $(D ElementType!R) for ranges,
+    and $(D ForeachType!R) otherwise.
+
+    Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);)
+    must both be legal.
+
+    If $(D r) is empty, an $(D Exception) is thrown.
+
+    Params:
+        fun = one or more functions
+        r = an iterable value as defined by $(D isIterable)
+
+    Returns:
+        the final result of the accumulator applied to the iterable
+    +/
+    auto reduce(R)(R r)
+    if (isIterable!R)
+    {
+        import std.exception : enforce;
+        alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
+        alias Args = staticMap!(ReduceSeedType!E, binfuns);
+
+        static if (isInputRange!R)
+        {
+            enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value.");
+            Args result = r.front;
+            r.popFront();
+            return reduceImpl!false(r, result);
+        }
+        else
+        {
+            auto result = Args.init;
+            return reduceImpl!true(r, result);
+        }
+    }
+
+    /++
+    Seed version. The seed should be a single value if $(D fun) is a
+    single function. If $(D fun) is multiple functions, then $(D seed)
+    should be a $(XREF typecons,Tuple), with one field per function in $(D f).
+
+    For convenience, if the seed is const, or has qualified fields, then
+    $(D reduce) will operate on an unqualified copy. If this happens
+    then the returned type will not perfectly match $(D S).
+
+    Use $(D fold) instead of $(D reduce) to use the seed version in a UFCS chain.
+
+    Params:
+        fun = one or more functions
+        seed = the initial value of the accumulator
+        r = an iterable value as defined by $(D isIterable)
+
+    Returns:
+        the final result of the accumulator applied to the iterable
+    +/
+    auto reduce(S, R)(S seed, R r)
+    if (isIterable!R)
+    {
+        static if (fun.length == 1)
+            return reducePreImpl(r, seed);
+        else
+        {
+            import std.algorithm.internal : algoFormat;
+            static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof));
+            return reducePreImpl(r, seed.expand);
+        }
+    }
+
+    private auto reducePreImpl(R, Args...)(R r, ref Args args)
+    {
+        alias Result = staticMap!(Unqual, Args);
+        static if (is(Result == Args))
+            alias result = args;
+        else
+            Result result = args;
+        return reduceImpl!false(r, result);
+    }
+
+    private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args)
+    if (isIterable!R)
+    {
+        import std.algorithm.internal : algoFormat;
+        static assert(Args.length == fun.length,
+            algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length));
+        alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
+
+        static if (mustInitialize) bool initialized = false;
+        foreach (/+auto ref+/ E e; r) // @@@4707@@@
+        {
+            foreach (i, f; binfuns)
+            {
+                static assert(!is(typeof(f(args[i], e))) || is(typeof(args[i] = f(args[i], e))),
+                    algoFormat("Incompatible function/seed/element: %s/%s/%s", fullyQualifiedName!f, Args[i].stringof, E.stringof));
+            }
+
+            static if (mustInitialize) if (initialized == false)
+            {
+                import std.conv : emplaceRef;
+                foreach (i, f; binfuns)
+                    emplaceRef!(Args[i])(args[i], e);
+                initialized = true;
+                continue;
+            }
+
+            foreach (i, f; binfuns)
+                args[i] = f(args[i], e);
+        }
+        static if (mustInitialize) if (!initialized) throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value.");
+
+        static if (Args.length == 1)
+            return args[0];
+        else
+            return tuple(args);
+    }
+}
+
+/**
+Many aggregate range operations turn out to be solved with $(D reduce)
+quickly and easily. The example below illustrates $(D reduce)'s
+remarkable power and flexibility.
+*/
+@safe unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.math : approxEqual;
+    import std.range;
+
+    int[] arr = [ 1, 2, 3, 4, 5 ];
+    // Sum all elements
+    auto sum = reduce!((a,b) => a + b)(0, arr);
+    assert(sum == 15);
+
+    // Sum again, using a string predicate with "a" and "b"
+    sum = reduce!"a + b"(0, arr);
+    assert(sum == 15);
+
+    // Compute the maximum of all elements
+    auto largest = reduce!(max)(arr);
+    assert(largest == 5);
+
+    // Max again, but with Uniform Function Call Syntax (UFCS)
+    largest = arr.reduce!(max);
+    assert(largest == 5);
+
+    // Compute the number of odd elements
+    auto odds = reduce!((a,b) => a + (b & 1))(0, arr);
+    assert(odds == 3);
+
+    // Compute the sum of squares
+    auto ssquares = reduce!((a,b) => a + b * b)(0, arr);
+    assert(ssquares == 55);
+
+    // Chain multiple ranges into seed
+    int[] a = [ 3, 4 ];
+    int[] b = [ 100 ];
+    auto r = reduce!("a + b")(chain(a, b));
+    assert(r == 107);
+
+    // Mixing convertible types is fair game, too
+    double[] c = [ 2.5, 3.0 ];
+    auto r1 = reduce!("a + b")(chain(a, b, c));
+    assert(approxEqual(r1, 112.5));
+
+    // To minimize nesting of parentheses, Uniform Function Call Syntax can be used
+    auto r2 = chain(a, b, c).reduce!("a + b");
+    assert(approxEqual(r2, 112.5));
+}
+
+/**
+Sometimes it is very useful to compute multiple aggregates in one pass.
+One advantage is that the computation is faster because the looping overhead
+is shared. That's why $(D reduce) accepts multiple functions.
+If two or more functions are passed, $(D reduce) returns a
+$(XREF typecons, Tuple) object with one member per passed-in function.
+The number of seeds must be correspondingly increased.
+*/
+@safe unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.math : approxEqual, sqrt;
+    import std.typecons : tuple, Tuple;
+
+    double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ];
+    // Compute minimum and maximum in one pass
+    auto r = reduce!(min, max)(a);
+    // The type of r is Tuple!(int, int)
+    assert(approxEqual(r[0], 2));  // minimum
+    assert(approxEqual(r[1], 11)); // maximum
+
+    // Compute sum and sum of squares in one pass
+    r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a);
+    assert(approxEqual(r[0], 35));  // sum
+    assert(approxEqual(r[1], 233)); // sum of squares
+    // Compute average and standard deviation from the above
+    auto avg = r[0] / a.length;
+    auto stdev = sqrt(r[1] / a.length - avg * avg);
+}
+
+unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.exception : assertThrown;
+    import std.range;
+    import std.typecons : tuple, Tuple;
+
+    double[] a = [ 3, 4 ];
+    auto r = reduce!("a + b")(0.0, a);
+    assert(r == 7);
+    r = reduce!("a + b")(a);
+    assert(r == 7);
+    r = reduce!(min)(a);
+    assert(r == 3);
+    double[] b = [ 100 ];
+    auto r1 = reduce!("a + b")(chain(a, b));
+    assert(r1 == 107);
+
+    // two funs
+    auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a);
+    assert(r2[0] == 7 && r2[1] == -7);
+    auto r3 = reduce!("a + b", "a - b")(a);
+    assert(r3[0] == 7 && r3[1] == -1);
+
+    a = [ 1, 2, 3, 4, 5 ];
+    // Stringize with commas
+    string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a);
+    assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]");
+
+    // Test the opApply case.
+    static struct OpApply
+    {
+        bool actEmpty;
+
+        int opApply(int delegate(ref int) dg)
+        {
+            int res;
+            if (actEmpty) return res;
+
+            foreach (i; 0..100)
+            {
+                res = dg(i);
+                if (res) break;
+            }
+            return res;
+        }
+    }
+
+    OpApply oa;
+    auto hundredSum = reduce!"a + b"(iota(100));
+    assert(reduce!"a + b"(5, oa) == hundredSum + 5);
+    assert(reduce!"a + b"(oa) == hundredSum);
+    assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99));
+    assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99));
+
+    // Test for throwing on empty range plus no seed.
+    assertThrown(reduce!"a + b"([1, 2][0..0]));
+
+    oa.actEmpty = true;
+    assertThrown(reduce!"a + b"(oa));
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    const float a = 0.0;
+    const float[] b = [ 1.2, 3, 3.3 ];
+    float[] c = [ 1.2, 3, 3.3 ];
+    auto r = reduce!"a + b"(a, b);
+    r = reduce!"a + b"(a, c);
+}
+
+@safe unittest
+{
+    // Issue #10408 - Two-function reduce of a const array.
+    import std.algorithm.comparison : max, min;
+    import std.typecons : tuple, Tuple;
+
+    const numbers = [10, 30, 20];
+    immutable m = reduce!(min)(numbers);
+    assert(m == 10);
+    immutable minmax = reduce!(min, max)(numbers);
+    assert(minmax == tuple(10, 30));
+}
+
+@safe unittest
+{
+    //10709
+    import std.typecons : tuple, Tuple;
+
+    enum foo = "a + 0.5 * b";
+    auto r = [0, 1, 2, 3];
+    auto r1 = reduce!foo(r);
+    auto r2 = reduce!(foo, foo)(r);
+    assert(r1 == 3);
+    assert(r2 == tuple(3, 3));
+}
+
+unittest
+{
+    int i = 0;
+    static struct OpApply
+    {
+        int opApply(int delegate(ref int) dg)
+        {
+            int[] a = [1, 2, 3];
+
+            int res = 0;
+            foreach (ref e; a)
+            {
+                res = dg(e);
+                if (res) break;
+            }
+            return res;
+        }
+    }
+    //test CTFE and functions with context
+    int fun(int a, int b){return a + b + 1;}
+    auto foo()
+    {
+        import std.algorithm.comparison : max;
+        import std.typecons : tuple, Tuple;
+
+        auto a = reduce!(fun)([1, 2, 3]);
+        auto b = reduce!(fun, fun)([1, 2, 3]);
+        auto c = reduce!(fun)(0, [1, 2, 3]);
+        auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]);
+        auto e = reduce!(fun)(0, OpApply());
+        auto f = reduce!(fun, fun)(tuple(0, 0), OpApply());
+
+        return max(a, b.expand, c, d.expand);
+    }
+    auto a = foo();
+    enum b = foo();
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.typecons : tuple, Tuple;
+
+    //http://forum.dlang.org/post/oghtttkopzjshsuflelk@forum.dlang.org
+    //Seed is tuple of const.
+    static auto minmaxElement(alias F = min, alias G = max, R)(in R range)
+        @safe pure nothrow if (isInputRange!R)
+    {
+        return reduce!(F, G)(tuple(ElementType!R.max,
+                                   ElementType!R.min), range);
+    }
+    assert(minmaxElement([1, 2, 3])== tuple(1, 3));
+}
+
+@safe unittest //12569
+{
+    import std.algorithm.comparison : max, min;
+    import std.typecons: tuple;
+    dchar c = 'a';
+    reduce!(min, max)(tuple(c, c), "hello"); // OK
+    static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
+    static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
+
+
+    //"Seed dchar should be a Tuple"
+    static assert(!is(typeof(reduce!(min, max)(c, "hello"))));
+    //"Seed (dchar) does not have the correct amount of fields (should be 2)"
+    static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
+    //"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)"
+    static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
+    //"Incompatable function/seed/element: all(alias pred = "a")/int/dchar"
+    static assert(!is(typeof(reduce!all(1, "hello"))));
+    static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello"))));
+}
+
+@safe unittest //13304
+{
+    int[] data;
+    static assert(is(typeof(reduce!((a, b)=>a+b)(data))));
+}
+
+//Helper for Reduce
+private template ReduceSeedType(E)
+{
+    static template ReduceSeedType(alias fun)
+    {
+        import std.algorithm.internal : algoFormat;
+
+        alias ReduceSeedType = Unqual!(typeof(fun(lvalueOf!E, lvalueOf!E)));
+
+        //Check the Seed type is useable.
+        ReduceSeedType s = ReduceSeedType.init;
+        static assert(is(typeof({ReduceSeedType s = lvalueOf!E;})) &&
+            is(typeof(lvalueOf!ReduceSeedType = fun(lvalueOf!ReduceSeedType, lvalueOf!E))),
+            algoFormat("Unable to deduce an acceptable seed type for %s with element type %s.", fullyQualifiedName!fun, E.stringof));
+    }
+}
+
+
+/++
+Implements the homonym function (also known as $(D accumulate), $(D
+compress), $(D inject), or $(D foldl)) present in various programming
+languages of functional flavor. The call $(D fold!(fun)(range, seed))
+first assigns $(D seed) to an internal variable $(D result),
+also called the accumulator. Then, for each element $(D x) in $(D
+range), $(D result = fun(result, x)) gets evaluated. Finally, $(D
+result) is returned. The one-argument version $(D fold!(fun)(range))
+works similarly, but it uses the first element of the range as the
+seed (the range must be non-empty).
+
+Returns:
+    the accumulated $(D result)
+
+See_Also:
+    $(WEB en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function))
+
+    $(LREF sum) is similar to $(D fold!((a, b) => a + b)) that offers
+    precise summing of floating point numbers.
+
+    This is functionally equivalent to $(LREF reduce) with the argument order reversed,
+    and without the need to use $(LREF tuple) for multiple seeds.
++/
+template fold(fun...) if (fun.length >= 1)
+{
+    auto fold(R, S...)(R r, S seed)
+    {
+        static if (S.length < 2)
+        {
+            return reduce!fun(seed, r);
+        }
+        else
+        {
+            import std.typecons: tuple;
+            return reduce!fun(tuple(seed), r);
+        }
+    }
+}
+
+///
+@safe pure unittest
+{
+    immutable arr = [1, 2, 3, 4, 5];
+
+    // Sum all elements
+    assert(arr.fold!((a, b) => a + b) == 15);
+
+    // Sum all elements with explicit seed
+    assert(arr.fold!((a, b) => a + b)(6) == 21);
+
+    import std.algorithm.comparison: min, max;
+    import std.typecons: tuple;
+
+    // Compute minimum and maximum at the same time
+    assert(arr.fold!(min, max) == tuple(1, 5));
+
+    // Compute minimum and maximum at the same time with seeds
+    assert(arr.fold!(min, max)(0, 7) == tuple(0, 7));
+
+    // Can be used in a UFCS chain
+    assert(arr.map!(a => a + 1).fold!((a, b) => a + b) == 20);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    int[1] arr;
+    static assert(!is(typeof(arr.fold!())));
+    static assert(!is(typeof(arr.fold!(a => a))));
+    static assert(is(typeof(arr.fold!((a, b) => a))));
+    static assert(is(typeof(arr.fold!((a, b) => a)(1))));
+}
+
+
+// splitter
+/**
+Lazily splits a range using an element as a separator. This can be used with
+any narrow string type or sliceable range type, but is most popular with string
+types.
+
+Two adjacent separators are considered to surround an empty element in
+the split range. Use $(D filter!(a => !a.empty)) on the result to compress
+empty elements.
+
+The predicate is passed to $(XREF functional,binaryFun), and can either accept
+a string, or any callable that can be executed via $(D pred(element, s)).
+
+If the empty range is given, the result is a range with one empty
+element. If a range with one separator is given, the result is a range
+with two empty elements.
+
+If splitting a string on whitespace and token compression is desired,
+consider using $(D splitter) without specifying a separator (see fourth overload
+below).
+
+Params:
+    pred = The predicate for comparing each element with the separator,
+        defaulting to $(D "a == b").
+    r = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to be
+        split. Must support slicing and $(D .length).
+    s = The element to be treated as the separator between range segments to be
+        split.
+
+Constraints:
+    The predicate $(D pred) needs to accept an element of $(D r) and the
+    separator $(D s).
+
+Returns:
+    An input range of the subranges of elements between separators. If $(D r)
+    is a forward range or bidirectional range, the returned range will be
+    likewise.
+
+See_Also:
+ $(XREF regex, _splitter) for a version that splits using a regular
+expression defined separator.
+*/
+auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s)
+if (is(typeof(binaryFun!pred(r.front, s)) : bool)
+        && ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range))
+{
+    import std.algorithm.searching : find;
+    import std.conv : unsigned;
+
+    static struct Result
+    {
+    private:
+        Range _input;
+        Separator _separator;
+        // Do we need hasLength!Range? popFront uses _input.length...
+        alias IndexType = typeof(unsigned(_input.length));
+        enum IndexType _unComputed = IndexType.max - 1, _atEnd = IndexType.max;
+        IndexType _frontLength = _unComputed;
+        IndexType _backLength = _unComputed;
+
+        static if (isNarrowString!Range)
+        {
+            size_t _separatorLength;
+        }
+        else
+        {
+            enum _separatorLength = 1;
+        }
+
+        static if (isBidirectionalRange!Range)
+        {
+            static IndexType lastIndexOf(Range haystack, Separator needle)
+            {
+                import std.range : retro;
+                auto r = haystack.retro().find!pred(needle);
+                return r.retro().length - 1;
+            }
+        }
+
+    public:
+        this(Range input, Separator separator)
+        {
+            _input = input;
+            _separator = separator;
+
+            static if (isNarrowString!Range)
+            {
+                import std.utf : codeLength;
+
+                _separatorLength = codeLength!(ElementEncodingType!Range)(separator);
+            }
+            if (_input.empty)
+                _frontLength = _atEnd;
+        }
+
+        static if (isInfinite!Range)
+        {
+            enum bool empty = false;
+        }
+        else
+        {
+            @property bool empty()
+            {
+                return _frontLength == _atEnd;
+            }
+        }
+
+        @property Range front()
+        {
+            assert(!empty);
+            if (_frontLength == _unComputed)
+            {
+                auto r = _input.find!pred(_separator);
+                _frontLength = _input.length - r.length;
+            }
+            return _input[0 .. _frontLength];
+        }
+
+        void popFront()
+        {
+            assert(!empty);
+            if (_frontLength == _unComputed)
+            {
+                front;
+            }
+            assert(_frontLength <= _input.length);
+            if (_frontLength == _input.length)
+            {
+                // no more input and need to fetch => done
+                _frontLength = _atEnd;
+
+                // Probably don't need this, but just for consistency:
+                _backLength = _atEnd;
+            }
+            else
+            {
+                _input = _input[_frontLength + _separatorLength .. _input.length];
+                _frontLength = _unComputed;
+            }
+        }
+
+        static if (isForwardRange!Range)
+        {
+            @property typeof(this) save()
+            {
+                auto ret = this;
+                ret._input = _input.save;
+                return ret;
+            }
+        }
+
+        static if (isBidirectionalRange!Range)
+        {
+            @property Range back()
+            {
+                assert(!empty);
+                if (_backLength == _unComputed)
+                {
+                    immutable lastIndex = lastIndexOf(_input, _separator);
+                    if (lastIndex == -1)
+                    {
+                        _backLength = _input.length;
+                    }
+                    else
+                    {
+                        _backLength = _input.length - lastIndex - 1;
+                    }
+                }
+                return _input[_input.length - _backLength .. _input.length];
+            }
+
+            void popBack()
+            {
+                assert(!empty);
+                if (_backLength == _unComputed)
+                {
+                    // evaluate back to make sure it's computed
+                    back;
+                }
+                assert(_backLength <= _input.length);
+                if (_backLength == _input.length)
+                {
+                    // no more input and need to fetch => done
+                    _frontLength = _atEnd;
+                    _backLength = _atEnd;
+                }
+                else
+                {
+                    _input = _input[0 .. _input.length - _backLength - _separatorLength];
+                    _backLength = _unComputed;
+                }
+            }
+        }
+    }
+
+    return Result(r, s);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    assert(equal(splitter("hello  world", ' '), [ "hello", "", "world" ]));
+    int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
+    int[][] w = [ [1, 2], [], [3], [4, 5], [] ];
+    assert(equal(splitter(a, 0), w));
+    a = [ 0 ];
+    assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ]));
+    a = [ 0, 1 ];
+    assert(equal(splitter(a, 0), [ [], [1] ]));
+    w = [ [0], [1], [2] ];
+    assert(equal(splitter!"a.front == b"(w, 1), [ [[0]], [[2]] ]));
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm;
+    import std.array : array;
+    import std.range : retro;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    assert(equal(splitter("hello  world", ' '), [ "hello", "", "world" ]));
+    assert(equal(splitter("žlutoučkýřkůň", 'ř'), [ "žlutoučký", "kůň" ]));
+    int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
+    int[][] w = [ [1, 2], [], [3], [4, 5], [] ];
+    static assert(isForwardRange!(typeof(splitter(a, 0))));
+
+    // foreach (x; splitter(a, 0)) {
+    //     writeln("[", x, "]");
+    // }
+    assert(equal(splitter(a, 0), w));
+    a = null;
+    assert(equal(splitter(a, 0),  (int[][]).init));
+    a = [ 0 ];
+    assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][]));
+    a = [ 0, 1 ];
+    assert(equal(splitter(a, 0), [ [], [1] ][]));
+
+    // Thoroughly exercise the bidirectional stuff.
+    auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada";
+    assert(equal(
+        retro(splitter(str, 'a')),
+        retro(array(splitter(str, 'a')))
+    ));
+
+    // Test interleaving front and back.
+    auto split = splitter(str, 'a');
+    assert(split.front == "");
+    assert(split.back == "");
+    split.popBack();
+    assert(split.back == "d");
+    split.popFront();
+    assert(split.front == "bc ");
+    assert(split.back == "d");
+    split.popFront();
+    split.popBack();
+    assert(split.back == "t ");
+    split.popBack();
+    split.popBack();
+    split.popFront();
+    split.popFront();
+    assert(split.front == "b ");
+    assert(split.back == "r ");
+
+    foreach (DummyType; AllDummyRanges) {  // Bug 4408
+        static if (isRandomAccessRange!DummyType) {
+            static assert(isBidirectionalRange!DummyType);
+            DummyType d;
+            auto s = splitter(d, 5);
+            assert(equal(s.front, [1,2,3,4]));
+            assert(equal(s.back, [6,7,8,9,10]));
+
+            auto s2 = splitter(d, [4, 5]);
+            assert(equal(s2.front, [1,2,3]));
+        }
+    }
+}
+@safe unittest
+{
+    import std.algorithm;
+    import std.range;
+    auto L = retro(iota(1L, 10L));
+    auto s = splitter(L, 5L);
+    assert(equal(s.front, [9L, 8L, 7L, 6L]));
+    s.popFront();
+    assert(equal(s.front, [4L, 3L, 2L, 1L]));
+    s.popFront();
+    assert(s.empty);
+}
+
+/**
+Similar to the previous overload of $(D splitter), except this one uses another
+range as a separator. This can be used with any narrow string type or sliceable
+range type, but is most popular with string types. The predicate is passed to
+$(XREF functional,binaryFun), and can either accept a string, or any callable
+that can be executed via $(D pred(r.front, s.front)).
+
+Two adjacent separators are considered to surround an empty element in
+the split range. Use $(D filter!(a => !a.empty)) on the result to compress
+empty elements.
+
+Params:
+    pred = The predicate for comparing each element with the separator,
+        defaulting to $(D "a == b").
+    r = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to be
+        split.
+    s = The $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) to
+        be treated as the separator between segments of $(D r) to be split.
+
+Constraints:
+    The predicate $(D pred) needs to accept an element of $(D r) and an
+    element of $(D s).
+
+Returns:
+    An input range of the subranges of elements between separators. If $(D r)
+    is a forward range or bidirectional range, the returned range will be
+    likewise.
+
+See_Also: $(XREF regex, _splitter) for a version that splits using a regular
+expression defined separator.
+ */
+auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s)
+if (is(typeof(binaryFun!pred(r.front, s.front)) : bool)
+        && (hasSlicing!Range || isNarrowString!Range)
+        && isForwardRange!Separator
+        && (hasLength!Separator || isNarrowString!Separator))
+{
+    import std.algorithm.searching : find;
+    import std.conv : unsigned;
+
+    static struct Result
+    {
+    private:
+        Range _input;
+        Separator _separator;
+        alias RIndexType = typeof(unsigned(_input.length));
+        // _frontLength == size_t.max means empty
+        RIndexType _frontLength = RIndexType.max;
+        static if (isBidirectionalRange!Range)
+            RIndexType _backLength = RIndexType.max;
+
+        @property auto separatorLength() { return _separator.length; }
+
+        void ensureFrontLength()
+        {
+            if (_frontLength != _frontLength.max) return;
+            assert(!_input.empty);
+            // compute front length
+            _frontLength = (_separator.empty) ? 1 :
+                           _input.length - find!pred(_input, _separator).length;
+            static if (isBidirectionalRange!Range)
+                if (_frontLength == _input.length) _backLength = _frontLength;
+        }
+
+        void ensureBackLength()
+        {
+            static if (isBidirectionalRange!Range)
+                if (_backLength != _backLength.max) return;
+            assert(!_input.empty);
+            // compute back length
+            static if (isBidirectionalRange!Range && isBidirectionalRange!Separator)
+            {
+                import std.range : retro;
+                _backLength = _input.length -
+                    find!pred(retro(_input), retro(_separator)).source.length;
+            }
+        }
+
+    public:
+        this(Range input, Separator separator)
+        {
+            _input = input;
+            _separator = separator;
+        }
+
+        @property Range front()
+        {
+            assert(!empty);
+            ensureFrontLength();
+            return _input[0 .. _frontLength];
+        }
+
+        static if (isInfinite!Range)
+        {
+            enum bool empty = false;  // Propagate infiniteness
+        }
+        else
+        {
+            @property bool empty()
+            {
+                return _frontLength == RIndexType.max && _input.empty;
+            }
+        }
+
+        void popFront()
+        {
+            assert(!empty);
+            ensureFrontLength();
+            if (_frontLength == _input.length)
+            {
+                // done, there's no separator in sight
+                _input = _input[_frontLength .. _frontLength];
+                _frontLength = _frontLength.max;
+                static if (isBidirectionalRange!Range)
+                    _backLength = _backLength.max;
+                return;
+            }
+            if (_frontLength + separatorLength == _input.length)
+            {
+                // Special case: popping the first-to-last item; there is
+                // an empty item right after this.
+                _input = _input[_input.length .. _input.length];
+                _frontLength = 0;
+                static if (isBidirectionalRange!Range)
+                    _backLength = 0;
+                return;
+            }
+            // Normal case, pop one item and the separator, get ready for
+            // reading the next item
+            _input = _input[_frontLength + separatorLength .. _input.length];
+            // mark _frontLength as uninitialized
+            _frontLength = _frontLength.max;
+        }
+
+        static if (isForwardRange!Range)
+        {
+            @property typeof(this) save()
+            {
+                auto ret = this;
+                ret._input = _input.save;
+                return ret;
+            }
+        }
+    }
+
+    return Result(r, s);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    assert(equal(splitter("hello  world", "  "), [ "hello", "world" ]));
+    int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
+    int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ];
+    assert(equal(splitter(a, [0, 0]), w));
+    a = [ 0, 0 ];
+    assert(equal(splitter(a, [0, 0]), [ (int[]).init, (int[]).init ]));
+    a = [ 0, 0, 1 ];
+    assert(equal(splitter(a, [0, 0]), [ [], [1] ]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.typecons : Tuple;
+
+    alias C = Tuple!(int, "x", int, "y");
+    auto a = [C(1,0), C(2,0), C(3,1), C(4,0)];
+    assert(equal(splitter!"a.x == b"(a, [2, 3]), [ [C(1,0)], [C(4,0)] ]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : text;
+    import std.array : split;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto s = ",abc, de, fg,hi,";
+    auto sp0 = splitter(s, ',');
+    // //foreach (e; sp0) writeln("[", e, "]");
+    assert(equal(sp0, ["", "abc", " de", " fg", "hi", ""][]));
+
+    auto s1 = ", abc, de,  fg, hi, ";
+    auto sp1 = splitter(s1, ", ");
+    //foreach (e; sp1) writeln("[", e, "]");
+    assert(equal(sp1, ["", "abc", "de", " fg", "hi", ""][]));
+    static assert(isForwardRange!(typeof(sp1)));
+
+    int[] a = [ 1, 2, 0, 3, 0, 4, 5, 0 ];
+    int[][] w = [ [1, 2], [3], [4, 5], [] ];
+    uint i;
+    foreach (e; splitter(a, 0))
+    {
+        assert(i < w.length);
+        assert(e == w[i++]);
+    }
+    assert(i == w.length);
+    // // Now go back
+    // auto s2 = splitter(a, 0);
+
+    // foreach (e; retro(s2))
+    // {
+    //     assert(i > 0);
+    //     assert(equal(e, w[--i]), text(e));
+    // }
+    // assert(i == 0);
+
+    wstring names = ",peter,paul,jerry,";
+    auto words = split(names, ",");
+    assert(walkLength(words) == 5, text(walkLength(words)));
+}
+
+@safe unittest
+{
+    int[][] a = [ [1], [2], [0], [3], [0], [4], [5], [0] ];
+    int[][][] w = [ [[1], [2]], [[3]], [[4], [5]], [] ];
+    uint i;
+    foreach (e; splitter!"a.front == 0"(a, 0))
+    {
+        assert(i < w.length);
+        assert(e == w[i++]);
+    }
+    assert(i == w.length);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto s6 = ",";
+    auto sp6 = splitter(s6, ',');
+    foreach (e; sp6)
+    {
+        //writeln("{", e, "}");
+    }
+    assert(equal(sp6, ["", ""][]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    // Issue 10773
+    auto s = splitter("abc", "");
+    assert(s.equal(["a", "b", "c"]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    // Test by-reference separator
+    class RefSep {
+    @safe:
+        string _impl;
+        this(string s) { _impl = s; }
+        @property empty() { return _impl.empty; }
+        @property auto front() { return _impl.front; }
+        void popFront() { _impl = _impl[1..$]; }
+        @property RefSep save() { return new RefSep(_impl); }
+        @property auto length() { return _impl.length; }
+    }
+    auto sep = new RefSep("->");
+    auto data = "i->am->pointing";
+    auto words = splitter(data, sep);
+    assert(words.equal([ "i", "am", "pointing" ]));
+}
+
+/**
+
+Similar to the previous overload of $(D splitter), except this one does not use a separator.
+Instead, the predicate is an unary function on the input range's element type.
+The $(D isTerminator) predicate is passed to $(XREF functional,unaryFun) and can
+either accept a string, or any callable that can be executed via $(D pred(element, s)).
+
+Two adjacent separators are considered to surround an empty element in
+the split range. Use $(D filter!(a => !a.empty)) on the result to compress
+empty elements.
+
+Params:
+    isTerminator = The predicate for deciding where to split the range.
+    input = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to
+        be split.
+
+Constraints:
+    The predicate $(D isTerminator) needs to accept an element of $(D input).
+
+Returns:
+    An input range of the subranges of elements between separators. If $(D input)
+    is a forward range or bidirectional range, the returned range will be
+    likewise.
+
+See_Also: $(XREF regex, _splitter) for a version that splits using a regular
+expression defined separator.
+ */
+auto splitter(alias isTerminator, Range)(Range input)
+if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front))))
+{
+    return SplitterResult!(unaryFun!isTerminator, Range)(input);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    assert(equal(splitter!(a => a == ' ')("hello  world"), [ "hello", "", "world" ]));
+    int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
+    int[][] w = [ [1, 2], [], [3], [4, 5], [] ];
+    assert(equal(splitter!(a => a == 0)(a), w));
+    a = [ 0 ];
+    assert(equal(splitter!(a => a == 0)(a), [ (int[]).init, (int[]).init ]));
+    a = [ 0, 1 ];
+    assert(equal(splitter!(a => a == 0)(a), [ [], [1] ]));
+    w = [ [0], [1], [2] ];
+    assert(equal(splitter!(a => a.front == 1)(w), [ [[0]], [[2]] ]));
+}
+
+private struct SplitterResult(alias isTerminator, Range)
+{
+    import std.algorithm.searching : find;
+    enum fullSlicing = (hasLength!Range && hasSlicing!Range) || isSomeString!Range;
+
+    private Range _input;
+    private size_t _end = 0;
+    static if(!fullSlicing)
+        private Range _next;
+
+    private void findTerminator()
+    {
+        static if (fullSlicing)
+        {
+            auto r = find!isTerminator(_input.save);
+            _end = _input.length - r.length;
+        }
+        else
+            for ( _end = 0; !_next.empty ; _next.popFront)
+            {
+                if (isTerminator(_next.front))
+                    break;
+                ++_end;
+            }
+    }
+
+    this(Range input)
+    {
+        _input = input;
+        static if(!fullSlicing)
+            _next = _input.save;
+
+        if (!_input.empty)
+            findTerminator();
+        else
+            _end = size_t.max;
+    }
+
+    static if (isInfinite!Range)
+    {
+        enum bool empty = false;  // Propagate infiniteness.
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return _end == size_t.max;
+        }
+    }
+
+    @property auto front()
+    {
+        version(assert)
+        {
+            import core.exception : RangeError;
+            if (empty)
+                throw new RangeError();
+        }
+        static if (fullSlicing)
+            return _input[0 .. _end];
+        else
+        {
+            import std.range : takeExactly;
+            return _input.takeExactly(_end);
+        }
+    }
+
+    void popFront()
+    {
+        version(assert)
+        {
+            import core.exception : RangeError;
+            if (empty)
+                throw new RangeError();
+        }
+
+        static if (fullSlicing)
+        {
+            _input = _input[_end .. _input.length];
+            if (_input.empty)
+            {
+                _end = size_t.max;
+                return;
+            }
+            _input.popFront();
+        }
+        else
+        {
+            if (_next.empty)
+            {
+                _input = _next;
+                _end = size_t.max;
+                return;
+            }
+            _next.popFront();
+            _input = _next.save;
+        }
+        findTerminator();
+    }
+
+    @property typeof(this) save()
+    {
+        auto ret = this;
+        ret._input = _input.save;
+        static if (!fullSlicing)
+            ret._next = _next.save;
+        return ret;
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range : iota;
+
+    auto L = iota(1L, 10L);
+    auto s = splitter(L, [5L, 6L]);
+    assert(equal(s.front, [1L, 2L, 3L, 4L]));
+    s.popFront();
+    assert(equal(s.front, [7L, 8L, 9L]));
+    s.popFront();
+    assert(s.empty);
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : algoFormat;
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    void compare(string sentence, string[] witness)
+    {
+        auto r = splitter!"a == ' '"(sentence);
+        assert(equal(r.save, witness), algoFormat("got: %(%s, %) expected: %(%s, %)", r, witness));
+    }
+
+    compare(" Mary  has a little lamb.   ",
+            ["", "Mary", "", "has", "a", "little", "lamb.", "", "", ""]);
+    compare("Mary  has a little lamb.   ",
+            ["Mary", "", "has", "a", "little", "lamb.", "", "", ""]);
+    compare("Mary  has a little lamb.",
+            ["Mary", "", "has", "a", "little", "lamb."]);
+    compare("", (string[]).init);
+    compare(" ", ["", ""]);
+
+    static assert(isForwardRange!(typeof(splitter!"a == ' '"("ABC"))));
+
+    foreach (DummyType; AllDummyRanges)
+    {
+        static if (isRandomAccessRange!DummyType)
+        {
+            auto rangeSplit = splitter!"a == 5"(DummyType.init);
+            assert(equal(rangeSplit.front, [1,2,3,4]));
+            rangeSplit.popFront();
+            assert(equal(rangeSplit.front, [6,7,8,9,10]));
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : algoFormat;
+    import std.algorithm.comparison : equal;
+    import std.range;
+
+    struct Entry
+    {
+        int low;
+        int high;
+        int[][] result;
+    }
+    Entry[] entries = [
+        Entry(0, 0, []),
+        Entry(0, 1, [[0]]),
+        Entry(1, 2, [[], []]),
+        Entry(2, 7, [[2], [4], [6]]),
+        Entry(1, 8, [[], [2], [4], [6], []]),
+    ];
+    foreach ( entry ; entries )
+    {
+        auto a = iota(entry.low, entry.high).filter!"true"();
+        auto b = splitter!"a%2"(a);
+        assert(equal!equal(b.save, entry.result), algoFormat("got: %(%s, %) expected: %(%s, %)", b, entry.result));
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.uni : isWhite;
+
+    //@@@6791@@@
+    assert(equal(splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"]));
+    assert(equal(splitter!(std.uni.isWhite)("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"]));
+    assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"]));
+}
+
+/++
+Lazily splits the string $(D s) into words, using whitespace as the delimiter.
+
+This function is string specific and, contrary to
+$(D splitter!(std.uni.isWhite)), runs of whitespace will be merged together
+(no empty tokens will be produced).
+
+Params:
+    s = The string to be split.
+
+Returns:
+    An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of slices of
+    the original string split by whitespace.
+ +/
+auto splitter(C)(C[] s)
+if (isSomeChar!C)
+{
+    import std.algorithm.searching : find;
+    static struct Result
+    {
+    private:
+        import core.exception;
+        C[] _s;
+        size_t _frontLength;
+
+        void getFirst() pure @safe
+        {
+            import std.uni : isWhite;
+
+            auto r = find!(isWhite)(_s);
+            _frontLength = _s.length - r.length;
+        }
+
+    public:
+        this(C[] s) pure @safe
+        {
+            import std.string : strip;
+            _s = s.strip();
+            getFirst();
+        }
+
+        @property C[] front() pure @safe
+        {
+            version(assert) if (empty) throw new RangeError();
+            return _s[0 .. _frontLength];
+        }
+
+        void popFront() pure @safe
+        {
+            import std.string : stripLeft;
+            version(assert) if (empty) throw new RangeError();
+            _s = _s[_frontLength .. $].stripLeft();
+            getFirst();
+        }
+
+        @property bool empty() const @safe pure nothrow
+        {
+            return _s.empty;
+        }
+
+        @property inout(Result) save() inout @safe pure nothrow
+        {
+            return this;
+        }
+    }
+    return Result(s);
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm.comparison : equal;
+    auto a = " a     bcd   ef gh ";
+    assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][]));
+}
+
+@safe pure unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.meta : AliasSeq;
+    foreach(S; AliasSeq!(string, wstring, dstring))
+    {
+        import std.conv : to;
+        S a = " a     bcd   ef gh ";
+        assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")]));
+        a = "";
+        assert(splitter(a).empty);
+    }
+
+    immutable string s = " a     bcd   ef gh ";
+    assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][]));
+}
+
+@safe unittest
+{
+    import std.conv : to;
+    import std.string : strip;
+
+    // TDPL example, page 8
+    uint[string] dictionary;
+    char[][3] lines;
+    lines[0] = "line one".dup;
+    lines[1] = "line \ttwo".dup;
+    lines[2] = "yah            last   line\ryah".dup;
+    foreach (line; lines) {
+       foreach (word; splitter(strip(line))) {
+            if (word in dictionary) continue; // Nothing to do
+            auto newID = dictionary.length;
+            dictionary[to!string(word)] = cast(uint)newID;
+        }
+    }
+    assert(dictionary.length == 5);
+    assert(dictionary["line"]== 0);
+    assert(dictionary["one"]== 1);
+    assert(dictionary["two"]== 2);
+    assert(dictionary["yah"]== 3);
+    assert(dictionary["last"]== 4);
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : algoFormat;
+    import std.algorithm.comparison : equal;
+    import std.conv : text;
+    import std.array : split;
+
+    // Check consistency:
+    // All flavors of split should produce the same results
+    foreach (input; [(int[]).init,
+                     [0],
+                     [0, 1, 0],
+                     [1, 1, 0, 0, 1, 1],
+                    ])
+    {
+        foreach (s; [0, 1])
+        {
+            auto result = split(input, s);
+
+            assert(equal(result, split(input, [s])), algoFormat(`"[%(%s,%)]"`, split(input, [s])));
+            //assert(equal(result, split(input, [s].filter!"true"())));                          //Not yet implemented
+            assert(equal(result, split!((a) => a == s)(input)), text(split!((a) => a == s)(input)));
+
+            //assert(equal!equal(result, split(input.filter!"true"(), s)));                      //Not yet implemented
+            //assert(equal!equal(result, split(input.filter!"true"(), [s])));                    //Not yet implemented
+            //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"())));    //Not yet implemented
+            assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"())));
+
+            assert(equal(result, splitter(input, s)));
+            assert(equal(result, splitter(input, [s])));
+            //assert(equal(result, splitter(input, [s].filter!"true"())));                       //Not yet implemented
+            assert(equal(result, splitter!((a) => a == s)(input)));
+
+            //assert(equal!equal(result, splitter(input.filter!"true"(), s)));                   //Not yet implemented
+            //assert(equal!equal(result, splitter(input.filter!"true"(), [s])));                 //Not yet implemented
+            //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
+            assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"())));
+        }
+    }
+    foreach (input; [string.init,
+                     " ",
+                     "  hello ",
+                     "hello   hello",
+                     " hello   what heck   this ?  "
+                    ])
+    {
+        foreach (s; [' ', 'h'])
+        {
+            auto result = split(input, s);
+
+            assert(equal(result, split(input, [s])));
+            //assert(equal(result, split(input, [s].filter!"true"())));                          //Not yet implemented
+            assert(equal(result, split!((a) => a == s)(input)));
+
+            //assert(equal!equal(result, split(input.filter!"true"(), s)));                      //Not yet implemented
+            //assert(equal!equal(result, split(input.filter!"true"(), [s])));                    //Not yet implemented
+            //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"())));    //Not yet implemented
+            assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"())));
+
+            assert(equal(result, splitter(input, s)));
+            assert(equal(result, splitter(input, [s])));
+            //assert(equal(result, splitter(input, [s].filter!"true"())));                       //Not yet implemented
+            assert(equal(result, splitter!((a) => a == s)(input)));
+
+            //assert(equal!equal(result, splitter(input.filter!"true"(), s)));                   //Not yet implemented
+            //assert(equal!equal(result, splitter(input.filter!"true"(), [s])));                 //Not yet implemented
+            //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
+            assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"())));
+        }
+    }
+}
+
+// sum
+/**
+Sums elements of $(D r), which must be a finite
+$(XREF_PACK_NAMED range,primitives,isInputRange,input range). Although
+conceptually $(D sum(r)) is equivalent to $(LREF fold)!((a, b) => a +
+b)(r, 0), $(D sum) uses specialized algorithms to maximize accuracy,
+as follows.
+
+$(UL
+$(LI If $(D $(XREF_PACK range,primitives,ElementType)!R) is a floating-point
+type and $(D R) is a
+$(XREF_PACK_NAMED range,primitives,isRandomAccessRange,random-access range) with
+length and slicing, then $(D sum) uses the
+$(WEB en.wikipedia.org/wiki/Pairwise_summation, pairwise summation)
+algorithm.)
+$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a
+finite input range (but not a random-access range with slicing), then
+$(D sum) uses the $(WEB en.wikipedia.org/wiki/Kahan_summation,
+Kahan summation) algorithm.)
+$(LI In all other cases, a simple element by element addition is done.)
+)
+
+For floating point inputs, calculations are made in
+$(DDLINK spec/type, Types, $(D real))
+precision for $(D real) inputs and in $(D double) precision otherwise
+(Note this is a special case that deviates from $(D fold)'s behavior,
+which would have kept $(D float) precision for a $(D float) range).
+For all other types, the calculations are done in the same type obtained
+from from adding two elements of the range, which may be a different
+type from the elements themselves (for example, in case of
+$(DDSUBLINK spec/type,integer-promotions, integral promotion)).
+
+A seed may be passed to $(D sum). Not only will this seed be used as an initial
+value, but its type will override all the above, and determine the algorithm
+and precision used for summation.
+
+Note that these specialized summing algorithms execute more primitive operations
+than vanilla summation. Therefore, if in certain cases maximum speed is required
+at expense of precision, one can use $(D fold!((a, b) => a + b)(r, 0)), which
+is not specialized for summation.
+
+Params:
+    seed = the initial value of the summation
+    r = a finite input range
+
+Returns:
+    The sum of all the elements in the range r.
+ */
+auto sum(R)(R r)
+if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front)))
+{
+    alias E = Unqual!(ElementType!R);
+    static if (isFloatingPoint!E)
+        alias Seed = typeof(E.init  + 0.0); //biggest of double/real
+    else
+        alias Seed = typeof(r.front + r.front);
+    return sum(r, Unqual!Seed(0));
+}
+/// ditto
+auto sum(R, E)(R r, E seed)
+if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front)))
+{
+    static if (isFloatingPoint!E)
+    {
+        static if (hasLength!R && hasSlicing!R)
+            return seed + sumPairwise!E(r);
+        else
+            return sumKahan!E(seed, r);
+    }
+    else
+    {
+        return reduce!"a + b"(seed, r);
+    }
+}
+
+// Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation
+private auto sumPairwise(Result, R)(R r)
+{
+    static assert (isFloatingPoint!Result);
+    switch (r.length)
+    {
+    case 0: return cast(Result) 0;
+    case 1: return cast(Result) r.front;
+    case 2: return cast(Result) r[0] + cast(Result) r[1];
+    default: return sumPairwise!Result(r[0 .. $ / 2]) + sumPairwise!Result(r[$ / 2 .. $]);
+    }
+}
+
+// Kahan algo http://en.wikipedia.org/wiki/Kahan_summation_algorithm
+private auto sumKahan(Result, R)(Result result, R r)
+{
+    static assert (isFloatingPoint!Result && isMutable!Result);
+    Result c = 0;
+    for (; !r.empty; r.popFront())
+    {
+        auto y = r.front - c;
+        auto t = result + y;
+        c = (t - result) - y;
+        result = t;
+    }
+    return result;
+}
+
+/// Ditto
+@safe pure nothrow unittest
+{
+    import std.range;
+
+    //simple integral sumation
+    assert(sum([ 1, 2, 3, 4]) == 10);
+
+    //with integral promotion
+    assert(sum([false, true, true, false, true]) == 3);
+    assert(sum(ubyte.max.repeat(100)) == 25500);
+
+    //The result may overflow
+    assert(uint.max.repeat(3).sum()           ==  4294967293U );
+    //But a seed can be used to change the sumation primitive
+    assert(uint.max.repeat(3).sum(ulong.init) == 12884901885UL);
+
+    //Floating point sumation
+    assert(sum([1.0, 2.0, 3.0, 4.0]) == 10);
+
+    //Floating point operations have double precision minimum
+    static assert(is(typeof(sum([1F, 2F, 3F, 4F])) == double));
+    assert(sum([1F, 2, 3, 4]) == 10);
+
+    //Force pair-wise floating point sumation on large integers
+    import std.math : approxEqual;
+    assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0)
+               .approxEqual((ulong.max / 2) * 4096.0 + 4096^^2 / 2));
+}
+
+@safe pure nothrow unittest
+{
+    static assert(is(typeof(sum([cast( byte)1])) ==  int));
+    static assert(is(typeof(sum([cast(ubyte)1])) ==  int));
+    static assert(is(typeof(sum([  1,   2,   3,   4])) ==  int));
+    static assert(is(typeof(sum([ 1U,  2U,  3U,  4U])) == uint));
+    static assert(is(typeof(sum([ 1L,  2L,  3L,  4L])) ==  long));
+    static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong));
+
+    int[] empty;
+    assert(sum(empty) == 0);
+    assert(sum([42]) == 42);
+    assert(sum([42, 43]) == 42 + 43);
+    assert(sum([42, 43, 44]) == 42 + 43 + 44);
+    assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45);
+}
+
+@safe pure nothrow unittest
+{
+    static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double));
+    static assert(is(typeof(sum([ 1F,  2F,  3F,  4F])) == double));
+    const(float[]) a = [1F, 2F, 3F, 4F];
+    static assert(is(typeof(sum(a)) == double));
+    const(float)[] b = [1F, 2F, 3F, 4F];
+    static assert(is(typeof(sum(a)) == double));
+
+    double[] empty;
+    assert(sum(empty) == 0);
+    assert(sum([42.]) == 42);
+    assert(sum([42., 43.]) == 42 + 43);
+    assert(sum([42., 43., 44.]) == 42 + 43 + 44);
+    assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5);
+}
+
+@safe pure nothrow unittest
+{
+    import std.container;
+    static assert(is(typeof(sum(SList!float()[])) == double));
+    static assert(is(typeof(sum(SList!double()[])) == double));
+    static assert(is(typeof(sum(SList!real()[])) == real));
+
+    assert(sum(SList!double()[]) == 0);
+    assert(sum(SList!double(1)[]) == 1);
+    assert(sum(SList!double(1, 2)[]) == 1 + 2);
+    assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3);
+    assert(sum(SList!double(1, 2, 3, 4)[]) == 10);
+}
+
+@safe pure nothrow unittest // 12434
+{
+    immutable a = [10, 20];
+    auto s1 = sum(a);             // Error
+    auto s2 = a.map!(x => x).sum; // Error
+}
+
+unittest
+{
+    import std.bigint;
+    import std.range;
+
+    immutable BigInt[] a = BigInt("1_000_000_000_000_000_000").repeat(10).array();
+    immutable ulong[]  b = (ulong.max/2).repeat(10).array();
+    auto sa = a.sum();
+    auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint
+    assert(sa == BigInt("10_000_000_000_000_000_000"));
+    assert(sb == (BigInt(ulong.max/2) * 10));
+}
+
+// uniq
+/**
+Lazily iterates unique consecutive elements of the given range (functionality
+akin to the $(WEB wikipedia.org/wiki/_Uniq, _uniq) system
+utility). Equivalence of elements is assessed by using the predicate
+$(D pred), by default $(D "a == b"). The predicate is passed to
+$(XREF functional,binaryFun), and can either accept a string, or any callable
+that can be executed via $(D pred(element, element)). If the given range is
+bidirectional, $(D uniq) also yields a bidirectional range.
+
+Params:
+    pred = Predicate for determining equivalence between range elements.
+    r = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of
+        elements to filter.
+
+Returns:
+    An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of
+    consecutively unique elements in the original range. If $(D r) is also a
+    forward range or bidirectional range, the returned range will be likewise.
+*/
+auto uniq(alias pred = "a == b", Range)(Range r)
+if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool))
+{
+    return UniqResult!(binaryFun!pred, Range)(r);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.mutation : copy;
+    import std.algorithm.comparison : equal;
+
+    int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
+    assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][]));
+
+    // Filter duplicates in-place using copy
+    arr.length -= arr.uniq().copy(arr).length;
+    assert(arr == [ 1, 2, 3, 4, 5 ]);
+
+    // Note that uniqueness is only determined consecutively; duplicated
+    // elements separated by an intervening different element will not be
+    // eliminated:
+    assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1]));
+}
+
+private struct UniqResult(alias pred, Range)
+{
+    Range _input;
+
+    this(Range input)
+    {
+        _input = input;
+    }
+
+    auto opSlice()
+    {
+        return this;
+    }
+
+    void popFront()
+    {
+        auto last = _input.front;
+        do
+        {
+            _input.popFront();
+        }
+        while (!_input.empty && pred(last, _input.front));
+    }
+
+    @property ElementType!Range front() { return _input.front; }
+
+    static if (isBidirectionalRange!Range)
+    {
+        void popBack()
+        {
+            auto last = _input.back;
+            do
+            {
+                _input.popBack();
+            }
+            while (!_input.empty && pred(last, _input.back));
+        }
+
+        @property ElementType!Range back() { return _input.back; }
+    }
+
+    static if (isInfinite!Range)
+    {
+        enum bool empty = false;  // Propagate infiniteness.
+    }
+    else
+    {
+        @property bool empty() { return _input.empty; }
+    }
+
+    static if (isForwardRange!Range) {
+        @property typeof(this) save() {
+            return typeof(this)(_input.save);
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+    import std.range;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
+    auto r = uniq(arr);
+    static assert(isForwardRange!(typeof(r)));
+
+    assert(equal(r, [ 1, 2, 3, 4, 5 ][]));
+    assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][])));
+
+    foreach (DummyType; AllDummyRanges) {
+        DummyType d;
+        auto u = uniq(d);
+        assert(equal(u, [1,2,3,4,5,6,7,8,9,10]));
+
+        static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u)));
+
+        static if (d.rt >= RangeType.Bidirectional) {
+            assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1]));
+        }
+    }
+}
+
+// permutations
+struct Permutations(Range)
+    if (isRandomAccessRange!Range && hasLength!Range)
+{
+    size_t[] indices, state;
+    Range r;
+
+    this(Range r)
+    {
+        import std.range : iota;
+        import std.array : array;
+
+        this.r = r;
+        state = r.length ? new size_t[r.length-1] : null;
+        indices = iota(size_t(r.length)).array;
+        empty = r.length == 0;
+    }
+
+    bool empty;
+
+    @property auto front()
+    {
+        import std.range : indexed;
+        return r.indexed(indices);
+    }
+
+    void popFront()
+    {
+        void next(int n)
+        {
+            import std.algorithm.mutation : swap;
+
+            if (n > indices.length)
+            {
+                empty = true;
+                return;
+            }
+
+            if (n % 2 == 1)
+                swap(indices[0], indices[n-1]);
+            else
+                swap(indices[state[n-2]], indices[n-1]);
+
+            if (++state[n-2] == n)
+            {
+                state[n-2] = 0;
+                next(n+1);
+            }
+        }
+
+        next(2);
+    }
+}
+
+/**
+Lazily computes all _permutations of $(D r) using $(WEB
+en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm).
+
+Returns:
+A forward range the elements of which are an $(XREF range,
+indexed) view into $(D r).
+
+See_Also:
+$(XREF_PACK algorithm,sorting,nextPermutation).
+*/
+Permutations!Range permutations(Range)(Range r)
+    if (isRandomAccessRange!Range && hasLength!Range)
+{
+    return typeof(return)(r);
+}
+
+///
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range : iota;
+    assert(equal!equal(iota(3).permutations,
+        [[0, 1, 2],
+         [1, 0, 2],
+         [2, 0, 1],
+         [0, 2, 1],
+         [1, 2, 0],
+         [2, 1, 0]]));
+}
diff --git a/libphobos/src/std/algorithm/mutation.d b/libphobos/src/std/algorithm/mutation.d
new file mode 100644
index 00000000000..5333792bc47
--- /dev/null
+++ b/libphobos/src/std/algorithm/mutation.d
@@ -0,0 +1,2495 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic _mutation algorithms.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 bringToFront,
+        If $(D a = [1, 2, 3]) and $(D b = [4, 5, 6, 7]),
+        $(D bringToFront(a, b)) leaves $(D a = [4, 5, 6]) and
+        $(D b = [7, 1, 2, 3]).)
+$(T2 copy,
+        Copies a range to another. If
+        $(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b))
+        leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).)
+$(T2 fill,
+        Fills a range with a pattern,
+        e.g., if $(D a = new int[3]), then $(D fill(a, 4))
+        leaves $(D a = [4, 4, 4]) and $(D fill(a, [3, 4])) leaves
+        $(D a = [3, 4, 3]).)
+$(T2 initializeAll,
+        If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves
+        $(D a = [double.init, double.init]).)
+$(T2 move,
+        $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a)
+        destructively.)
+$(T2 moveAll,
+        Moves all elements from one range to another.)
+$(T2 moveSome,
+        Moves as many elements as possible from one range to another.)
+$(T2 remove,
+        Removes elements from a range in-place, and returns the shortened
+        range.)
+$(T2 reverse,
+        If $(D a = [1, 2, 3]), $(D reverse(a)) changes it to $(D [3, 2, 1]).)
+$(T2 strip,
+        Strips all leading and trailing elements equal to a value, or that
+        satisfy a predicate.
+        If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and
+        $(D strip!(e => e == 1)(a)) returns $(D [0]).)
+$(T2 stripLeft,
+        Strips all leading elements equal to a value, or that satisfy a
+        predicate.  If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and
+        $(D stripLeft!(e => e == 1)(a)) returns $(D [0, 1, 1]).)
+$(T2 stripRight,
+        Strips all trailing elements equal to a value, or that satisfy a
+        predicate.
+        If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and
+        $(D stripRight!(e => e == 1)(a)) returns $(D [1, 1, 0]).)
+$(T2 swap,
+        Swaps two values.)
+$(T2 swapRanges,
+        Swaps all elements of two ranges.)
+$(T2 uninitializedFill,
+        Fills a range (assumed uninitialized) with a value.)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_mutation.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.mutation;
+
+import std.range.primitives;
+import std.traits : isArray, isBlitAssignable, isNarrowString, Unqual;
+// FIXME
+import std.typecons; // : tuple, Tuple;
+
+// FIXME: somehow deleting this breaks the bringToFront() unittests.
+import std.range;
+
+// bringToFront
+/**
+The $(D bringToFront) function has considerable flexibility and
+usefulness. It can rotate elements in one buffer left or right, swap
+buffers of equal length, and even move elements across disjoint
+buffers of different types and different lengths.
+
+$(D bringToFront) takes two ranges $(D front) and $(D back), which may
+be of different types. Considering the concatenation of $(D front) and
+$(D back) one unified range, $(D bringToFront) rotates that unified
+range such that all elements in $(D back) are brought to the beginning
+of the unified range. The relative ordering of elements in $(D front)
+and $(D back), respectively, remains unchanged.
+
+Performs $(BIGOH max(front.length, back.length)) evaluations of $(D
+swap).
+
+Preconditions:
+
+Either $(D front) and $(D back) are disjoint, or $(D back) is
+reachable from $(D front) and $(D front) is not reachable from $(D
+back).
+
+Params:
+    front = an input range
+    back = a forward range
+
+Returns:
+    The number of elements brought to the front, i.e., the length of $(D back).
+
+See_Also:
+    $(WEB sgi.com/tech/stl/_rotate.html, STL's rotate)
+*/
+size_t bringToFront(Range1, Range2)(Range1 front, Range2 back)
+    if (isInputRange!Range1 && isForwardRange!Range2)
+{
+    import std.range: Take, take;
+    enum bool sameHeadExists = is(typeof(front.sameHead(back)));
+    size_t result;
+    for (bool semidone; !front.empty && !back.empty; )
+    {
+        static if (sameHeadExists)
+        {
+            if (front.sameHead(back)) break; // shortcut
+        }
+        // Swap elements until front and/or back ends.
+        auto back0 = back.save;
+        size_t nswaps;
+        do
+        {
+            static if (sameHeadExists)
+            {
+                // Detect the stepping-over condition.
+                if (front.sameHead(back0)) back0 = back.save;
+            }
+            swapFront(front, back);
+            ++nswaps;
+            front.popFront();
+            back.popFront();
+        }
+        while (!front.empty && !back.empty);
+
+        if (!semidone) result += nswaps;
+
+        // Now deal with the remaining elements.
+        if (back.empty)
+        {
+            if (front.empty) break;
+            // Right side was shorter, which means that we've brought
+            // all the back elements to the front.
+            semidone = true;
+            // Next pass: bringToFront(front, back0) to adjust the rest.
+            back = back0;
+        }
+        else
+        {
+            assert(front.empty);
+            // Left side was shorter. Let's step into the back.
+            static if (is(Range1 == Take!Range2))
+            {
+                front = take(back0, nswaps);
+            }
+            else
+            {
+                immutable subresult = bringToFront(take(back0, nswaps),
+                                                   back);
+                if (!semidone) result += subresult;
+                break; // done
+            }
+        }
+    }
+    return result;
+}
+
+/**
+The simplest use of $(D bringToFront) is for rotating elements in a
+buffer. For example:
+*/
+@safe unittest
+{
+    auto arr = [4, 5, 6, 7, 1, 2, 3];
+    auto p = bringToFront(arr[0 .. 4], arr[4 .. $]);
+    assert(p == arr.length - 4);
+    assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]);
+}
+
+/**
+The $(D front) range may actually "step over" the $(D back)
+range. This is very useful with forward ranges that cannot compute
+comfortably right-bounded subranges like $(D arr[0 .. 4]) above. In
+the example below, $(D r2) is a right subrange of $(D r1).
+*/
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.container : SList;
+
+    auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3);
+    auto r1 = list[];
+    auto r2 = list[]; popFrontN(r2, 4);
+    assert(equal(r2, [ 1, 2, 3 ]));
+    bringToFront(r1, r2);
+    assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ]));
+}
+
+
+/**
+Elements can be swapped across ranges of different types:
+*/
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.container : SList;
+
+    auto list = SList!(int)(4, 5, 6, 7);
+    auto vec = [ 1, 2, 3 ];
+    bringToFront(list[], vec);
+    assert(equal(list[], [ 1, 2, 3, 4 ]));
+    assert(equal(vec, [ 5, 6, 7 ]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : text;
+    import std.random : Random, unpredictableSeed, uniform;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    // a more elaborate test
+    {
+        auto rnd = Random(unpredictableSeed);
+        int[] a = new int[uniform(100, 200, rnd)];
+        int[] b = new int[uniform(100, 200, rnd)];
+        foreach (ref e; a) e = uniform(-100, 100, rnd);
+        foreach (ref e; b) e = uniform(-100, 100, rnd);
+        int[] c = a ~ b;
+        // writeln("a= ", a);
+        // writeln("b= ", b);
+        auto n = bringToFront(c[0 .. a.length], c[a.length .. $]);
+        //writeln("c= ", c);
+        assert(n == b.length);
+        assert(c == b ~ a, text(c, "\n", a, "\n", b));
+    }
+    // different types, moveFront, no sameHead
+    {
+        static struct R(T)
+        {
+            T[] data;
+            size_t i;
+            @property
+            {
+                R save() { return this; }
+                bool empty() { return i >= data.length; }
+                T front() { return data[i]; }
+                T front(real e) { return data[i] = cast(T) e; }
+            }
+            void popFront() { ++i; }
+        }
+        auto a = R!int([1, 2, 3, 4, 5]);
+        auto b = R!real([6, 7, 8, 9]);
+        auto n = bringToFront(a, b);
+        assert(n == 4);
+        assert(a.data == [6, 7, 8, 9, 1]);
+        assert(b.data == [2, 3, 4, 5]);
+    }
+    // front steps over back
+    {
+        int[] arr, r1, r2;
+
+        // back is shorter
+        arr = [4, 5, 6, 7, 1, 2, 3];
+        r1 = arr;
+        r2 = arr[4 .. $];
+        bringToFront(r1, r2) == 3 || assert(0);
+        assert(equal(arr, [1, 2, 3, 4, 5, 6, 7]));
+
+        // front is shorter
+        arr = [5, 6, 7, 1, 2, 3, 4];
+        r1 = arr;
+        r2 = arr[3 .. $];
+        bringToFront(r1, r2) == 4 || assert(0);
+        assert(equal(arr, [1, 2, 3, 4, 5, 6, 7]));
+    }
+}
+
+// Tests if types are arrays and support slice assign.
+private enum bool areCopyCompatibleArrays(T1, T2) =
+    isArray!T1 && isArray!T2 && is(typeof(T2.init[] = T1.init[]));
+
+// copy
+/**
+Copies the content of $(D source) into $(D target) and returns the
+remaining (unfilled) part of $(D target).
+
+Preconditions: $(D target) shall have enough room to accommodate
+the entirety of $(D source).
+
+Params:
+    source = an input range
+    target = an output range
+
+Returns:
+    The unfilled part of target
+
+See_Also:
+    $(WEB sgi.com/tech/stl/_copy.html, STL's _copy)
+ */
+TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target)
+    if (areCopyCompatibleArrays!(SourceRange, TargetRange))
+{
+    const tlen = target.length;
+    const slen = source.length;
+    assert(tlen >= slen,
+            "Cannot copy a source range into a smaller target range.");
+
+    immutable overlaps = () @trusted {
+        return source.ptr < target.ptr + tlen &&
+               target.ptr < source.ptr + slen; }();
+
+    if (overlaps)
+    {
+        foreach (idx; 0 .. slen)
+            target[idx] = source[idx];
+        return target[slen .. tlen];
+    }
+    else
+    {
+        // Array specialization.  This uses optimized memory copying
+        // routines under the hood and is about 10-20x faster than the
+        // generic implementation.
+        target[0 .. slen] = source[];
+        return target[slen .. $];
+    }
+}
+
+/// ditto
+TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target)
+    if (!areCopyCompatibleArrays!(SourceRange, TargetRange) &&
+        isInputRange!SourceRange &&
+        isOutputRange!(TargetRange, ElementType!SourceRange))
+{
+    // Specialize for 2 random access ranges.
+    // Typically 2 random access ranges are faster iterated by common
+    // index than by x.popFront(), y.popFront() pair
+    static if (isRandomAccessRange!SourceRange &&
+               hasLength!SourceRange &&
+               hasSlicing!TargetRange &&
+               isRandomAccessRange!TargetRange &&
+               hasLength!TargetRange)
+    {
+        auto len = source.length;
+        foreach (idx; 0 .. len)
+            target[idx] = source[idx];
+        return target[len .. $];
+    }
+    else
+    {
+        put(target, source);
+        return target;
+    }
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 5 ];
+    int[] b = [ 9, 8 ];
+    int[] buf = new int[](a.length + b.length + 10);
+    auto rem = a.copy(buf);    // copy a into buf
+    rem = b.copy(rem);         // copy b into remainder of buf
+    assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]);
+    assert(rem.length == 10);   // unused slots in buf
+}
+
+/**
+As long as the target range elements support assignment from source
+range elements, different types of ranges are accepted:
+*/
+@safe unittest
+{
+    float[] src = [ 1.0f, 5 ];
+    double[] dest = new double[src.length];
+    src.copy(dest);
+}
+
+/**
+To _copy at most $(D n) elements from a range, you may want to use
+$(XREF range, take):
+*/
+@safe unittest
+{
+    import std.range;
+    int[] src = [ 1, 5, 8, 9, 10 ];
+    auto dest = new int[](3);
+    src.take(dest.length).copy(dest);
+    assert(dest == [ 1, 5, 8 ]);
+}
+
+/**
+To _copy just those elements from a range that satisfy a predicate,
+use $(LREF filter):
+*/
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+    int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ];
+    auto dest = new int[src.length];
+    auto rem = src
+        .filter!(a => (a & 1) == 1)
+        .copy(dest);
+    assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]);
+}
+
+/**
+$(XREF range, retro) can be used to achieve behavior similar to
+$(WEB sgi.com/tech/stl/copy_backward.html, STL's copy_backward'):
+*/
+@safe unittest
+{
+    import std.algorithm, std.range;
+    int[] src = [1, 2, 4];
+    int[] dest = [0, 0, 0, 0, 0];
+    src.retro.copy(dest.retro);
+    assert(dest == [0, 0, 1, 2, 4]);
+}
+
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    {
+        int[] a = [ 1, 5 ];
+        int[] b = [ 9, 8 ];
+        auto e = copy(filter!("a > 1")(a), b);
+        assert(b[0] == 5 && e.length == 1);
+    }
+
+    {
+        int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+        copy(a[5..10], a[4..9]);
+        assert(a[4..9] == [6, 7, 8, 9, 10]);
+    }
+
+    {   // Test for bug 7898
+        enum v =
+        {
+            import std.algorithm;
+            int[] arr1 = [10, 20, 30, 40, 50];
+            int[] arr2 = arr1.dup;
+            copy(arr1, arr2);
+            return 35;
+        }();
+    }
+}
+
+@safe unittest
+{
+    // Issue 13650
+    import std.meta : AliasSeq;
+    foreach (Char; AliasSeq!(char, wchar, dchar))
+    {
+        Char[3] a1 = "123";
+        Char[6] a2 = "456789";
+        assert(copy(a1[], a2[]) is a2[3..$]);
+        assert(a1[] == "123");
+        assert(a2[] == "123789");
+    }
+}
+
+/**
+Assigns $(D value) to each element of input _range $(D range).
+
+Params:
+        range = An
+                $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+                that exposes references to its elements and has assignable
+                elements
+        value = Assigned to each element of range
+
+See_Also:
+        $(LREF uninitializedFill)
+        $(LREF initializeAll)
+ */
+void fill(Range, Value)(Range range, Value value)
+    if (isInputRange!Range && is(typeof(range.front = value)))
+{
+    alias T = ElementType!Range;
+
+    static if (is(typeof(range[] = value)))
+    {
+        range[] = value;
+    }
+    else static if (is(typeof(range[] = T(value))))
+    {
+        range[] = T(value);
+    }
+    else
+    {
+        for ( ; !range.empty; range.popFront() )
+        {
+            range.front = value;
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 2, 3, 4 ];
+    fill(a, 5);
+    assert(a == [ 5, 5, 5, 5 ]);
+}
+
+@safe unittest
+{
+    import std.conv : text;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 3 ];
+    fill(a, 6);
+    assert(a == [ 6, 6, 6 ], text(a));
+
+    void fun0()
+    {
+        foreach (i; 0 .. 1000)
+        {
+            foreach (ref e; a) e = 6;
+        }
+    }
+    void fun1() { foreach (i; 0 .. 1000) fill(a, 6); }
+    //void fun2() { foreach (i; 0 .. 1000) fill2(a, 6); }
+    //writeln(benchmark!(fun0, fun1, fun2)(10000));
+
+    // fill should accept InputRange
+    alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input);
+    enum filler = uint.max;
+    InputRange range;
+    fill(range, filler);
+    foreach (value; range.arr)
+        assert(value == filler);
+}
+
+@safe unittest
+{
+    //ER8638_1 IS_NOT self assignable
+    static struct ER8638_1
+    {
+        void opAssign(int){}
+    }
+
+    //ER8638_1 IS self assignable
+    static struct ER8638_2
+    {
+        void opAssign(ER8638_2){}
+        void opAssign(int){}
+    }
+
+    auto er8638_1 = new ER8638_1[](10);
+    auto er8638_2 = new ER8638_2[](10);
+    er8638_1.fill(5); //generic case
+    er8638_2.fill(5); //opSlice(T.init) case
+}
+
+@safe unittest
+{
+    {
+        int[] a = [1, 2, 3];
+        immutable(int) b = 0;
+        a.fill(b);
+        assert(a == [0, 0, 0]);
+    }
+    {
+        double[] a = [1, 2, 3];
+        immutable(int) b = 0;
+        a.fill(b);
+        assert(a == [0, 0, 0]);
+    }
+}
+
+/**
+Fills $(D range) with a pattern copied from $(D filler). The length of
+$(D range) does not have to be a multiple of the length of $(D
+filler). If $(D filler) is empty, an exception is thrown.
+
+Params:
+    range = An $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+            that exposes references to its elements and has assignable elements.
+    filler = The
+             $(XREF_PACK_NAMED _range,primitives,isForwardRange,forward _range)
+             representing the _fill pattern.
+ */
+void fill(Range1, Range2)(Range1 range, Range2 filler)
+    if (isInputRange!Range1
+        && (isForwardRange!Range2
+            || (isInputRange!Range2 && isInfinite!Range2))
+        && is(typeof(Range1.init.front = Range2.init.front)))
+{
+    static if (isInfinite!Range2)
+    {
+        //Range2 is infinite, no need for bounds checking or saving
+        static if (hasSlicing!Range2 && hasLength!Range1
+            && is(typeof(filler[0 .. range.length])))
+        {
+            copy(filler[0 .. range.length], range);
+        }
+        else
+        {
+            //manual feed
+            for ( ; !range.empty; range.popFront(), filler.popFront())
+            {
+                range.front = filler.front;
+            }
+        }
+    }
+    else
+    {
+        import std.exception : enforce;
+
+        enforce(!filler.empty, "Cannot fill range with an empty filler");
+
+        static if (hasLength!Range1 && hasLength!Range2
+            && is(typeof(range.length > filler.length)))
+        {
+            //Case we have access to length
+            auto len = filler.length;
+            //Start by bulk copies
+            while (range.length > len)
+            {
+                range = copy(filler.save, range);
+            }
+
+            //and finally fill the partial range. No need to save here.
+            static if (hasSlicing!Range2 && is(typeof(filler[0 .. range.length])))
+            {
+                //use a quick copy
+                auto len2 = range.length;
+                range = copy(filler[0 .. len2], range);
+            }
+            else
+            {
+                //iterate. No need to check filler, it's length is longer than range's
+                for (; !range.empty; range.popFront(), filler.popFront())
+                {
+                    range.front = filler.front;
+                }
+            }
+        }
+        else
+        {
+            //Most basic case.
+            auto bck = filler.save;
+            for (; !range.empty; range.popFront(), filler.popFront())
+            {
+                if (filler.empty) filler = bck.save;
+                range.front = filler.front;
+            }
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    int[] b = [ 8, 9 ];
+    fill(a, b);
+    assert(a == [ 8, 9, 8, 9, 8 ]);
+}
+
+@safe unittest
+{
+    import std.exception : assertThrown;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    int[] b = [1, 2];
+    fill(a, b);
+    assert(a == [ 1, 2, 1, 2, 1 ]);
+
+    // fill should accept InputRange
+    alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input);
+    InputRange range;
+    fill(range,[1,2]);
+    foreach (i,value;range.arr)
+    assert(value == (i%2==0?1:2));
+
+    //test with a input being a "reference forward" range
+    fill(a, new ReferenceForwardRange!int([8, 9]));
+    assert(a == [8, 9, 8, 9, 8]);
+
+    //test with a input being an "infinite input" range
+    fill(a, new ReferenceInfiniteInputRange!int());
+    assert(a == [0, 1, 2, 3, 4]);
+
+    //empty filler test
+    assertThrown(fill(a, a[$..$]));
+}
+
+/**
+Initializes all elements of $(D range) with their $(D .init) value.
+Assumes that the elements of the range are uninitialized.
+
+Params:
+        range = An
+                $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+                that exposes references to its elements and has assignable
+                elements
+
+See_Also:
+        $(LREF fill)
+        $(LREF uninitializeFill)
+ */
+void initializeAll(Range)(Range range)
+    if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range)
+{
+    import core.stdc.string : memset, memcpy;
+    import std.traits : hasElaborateAssign, isDynamicArray;
+
+    alias T = ElementType!Range;
+    static if (hasElaborateAssign!T)
+    {
+        import std.algorithm.internal : addressOf;
+        //Elaborate opAssign. Must go the memcpy road.
+        //We avoid calling emplace here, because our goal is to initialize to
+        //the static state of T.init,
+        //So we want to avoid any un-necassarilly CC'ing of T.init
+        auto p = typeid(T).initializer().ptr;
+        if (p)
+            for ( ; !range.empty ; range.popFront() )
+                memcpy(addressOf(range.front), p, T.sizeof);
+        else
+            static if (isDynamicArray!Range)
+                memset(range.ptr, 0, range.length * T.sizeof);
+            else
+                for ( ; !range.empty ; range.popFront() )
+                    memset(addressOf(range.front), 0, T.sizeof);
+    }
+    else
+        fill(range, T.init);
+}
+
+/// ditto
+void initializeAll(Range)(Range range)
+    if (is(Range == char[]) || is(Range == wchar[]))
+{
+    alias T = ElementEncodingType!Range;
+    range[] = T.init;
+}
+
+///
+unittest
+{
+    import core.stdc.stdlib: malloc, free;
+
+    struct S
+    {
+        int a = 10;
+    }
+
+    auto s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5];
+    initializeAll(s);
+    assert(s == [S(10), S(10), S(10), S(10), S(10)]);
+
+    scope(exit) free(s.ptr);
+}
+
+unittest
+{
+    import std.algorithm.iteration : filter;
+    import std.meta : AliasSeq;
+    import std.traits : hasElaborateAssign;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    //Test strings:
+    //Must work on narrow strings.
+    //Must reject const
+    char[3] a = void;
+    a[].initializeAll();
+    assert(a[] == [char.init, char.init, char.init]);
+    string s;
+    assert(!__traits(compiles, s.initializeAll()));
+
+    //Note: Cannot call uninitializedFill on narrow strings
+
+    enum e {e1, e2}
+    e[3] b1 = void;
+    b1[].initializeAll();
+    assert(b1[] == [e.e1, e.e1, e.e1]);
+    e[3] b2 = void;
+    b2[].uninitializedFill(e.e2);
+    assert(b2[] == [e.e2, e.e2, e.e2]);
+
+    static struct S1
+    {
+        int i;
+    }
+    static struct S2
+    {
+        int i = 1;
+    }
+    static struct S3
+    {
+        int i;
+        this(this){}
+    }
+    static struct S4
+    {
+        int i = 1;
+        this(this){}
+    }
+    static assert (!hasElaborateAssign!S1);
+    static assert (!hasElaborateAssign!S2);
+    static assert ( hasElaborateAssign!S3);
+    static assert ( hasElaborateAssign!S4);
+    assert (!typeid(S1).initializer().ptr);
+    assert ( typeid(S2).initializer().ptr);
+    assert (!typeid(S3).initializer().ptr);
+    assert ( typeid(S4).initializer().ptr);
+
+    foreach(S; AliasSeq!(S1, S2, S3, S4))
+    {
+        //initializeAll
+        {
+            //Array
+            S[3] ss1 = void;
+            ss1[].initializeAll();
+            assert(ss1[] == [S.init, S.init, S.init]);
+
+            //Not array
+            S[3] ss2 = void;
+            auto sf = ss2[].filter!"true"();
+
+            sf.initializeAll();
+            assert(ss2[] == [S.init, S.init, S.init]);
+        }
+        //uninitializedFill
+        {
+            //Array
+            S[3] ss1 = void;
+            ss1[].uninitializedFill(S(2));
+            assert(ss1[] == [S(2), S(2), S(2)]);
+
+            //Not array
+            S[3] ss2 = void;
+            auto sf = ss2[].filter!"true"();
+            sf.uninitializedFill(S(2));
+            assert(ss2[] == [S(2), S(2), S(2)]);
+        }
+    }
+}
+
+// move
+/**
+Moves $(D source) into $(D target) via a destructive copy.
+
+Params:
+    source = Data to copy. If a destructor or postblit is defined, it is reset
+        to its $(D .init) value after it is moved into target.  Note that data
+        with internal pointers that point to itself cannot be moved, and will
+        trigger an assertion failure.
+    target = Where to copy into. The destructor, if any, is invoked before the
+        copy is performed.
+*/
+void move(T)(ref T source, ref T target)
+{
+    // test @safe destructible
+    static if (__traits(compiles, (T t) @safe {}))
+        trustedMoveImpl(source, target);
+    else
+        moveImpl(source, target);
+}
+
+///
+unittest
+{
+    Object obj1 = new Object;
+    Object obj2 = obj1;
+    Object obj3;
+
+    move(obj2, obj3);
+    assert(obj3 is obj1);
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+    // Structs without destructors are simply copied
+    struct S1
+    {
+        int a = 1;
+        int b = 2;
+    }
+    S1 s11 = { 10, 11 };
+    S1 s12;
+
+    move(s11, s12);
+
+    assert(s11.a == 10 && s11.b == 11 &&
+           s12.a == 10 && s12.b == 11);
+
+    // But structs with destructors or postblits are reset to their .init value
+    // after copying to the target.
+    struct S2
+    {
+        int a = 1;
+        int b = 2;
+
+        ~this() pure nothrow @safe @nogc { }
+    }
+    S2 s21 = { 3, 4 };
+    S2 s22;
+
+    move(s21, s22);
+
+    assert(s21.a == 1 && s21.b == 2 &&
+           s22.a == 3 && s22.b == 4);
+}
+
+unittest
+{
+    import std.traits;
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    import std.exception : assertCTFEable;
+    assertCTFEable!((){
+        Object obj1 = new Object;
+        Object obj2 = obj1;
+        Object obj3;
+        move(obj2, obj3);
+        assert(obj3 is obj1);
+
+        static struct S1 { int a = 1, b = 2; }
+        S1 s11 = { 10, 11 };
+        S1 s12;
+        move(s11, s12);
+        assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11);
+
+        static struct S2 { int a = 1; int * b; }
+        S2 s21 = { 10, null };
+        s21.b = new int;
+        S2 s22;
+        move(s21, s22);
+        assert(s21 == s22);
+    });
+    // Issue 5661 test(1)
+    static struct S3
+    {
+        static struct X { int n = 0; ~this(){n = 0;} }
+        X x;
+    }
+    static assert(hasElaborateDestructor!S3);
+    S3 s31, s32;
+    s31.x.n = 1;
+    move(s31, s32);
+    assert(s31.x.n == 0);
+    assert(s32.x.n == 1);
+
+    // Issue 5661 test(2)
+    static struct S4
+    {
+        static struct X { int n = 0; this(this){n = 0;} }
+        X x;
+    }
+    static assert(hasElaborateCopyConstructor!S4);
+    S4 s41, s42;
+    s41.x.n = 1;
+    move(s41, s42);
+    assert(s41.x.n == 0);
+    assert(s42.x.n == 1);
+
+    // Issue 13990 test
+    class S5;
+
+    S5 s51;
+    S5 s52 = s51;
+    S5 s53;
+    move(s52, s53);
+    assert(s53 is s51);
+}
+
+/// Ditto
+T move(T)(ref T source)
+{
+    // test @safe destructible
+    static if (__traits(compiles, (T t) @safe {}))
+        return trustedMoveImpl(source);
+    else
+        return moveImpl(source);
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+    struct S
+    {
+        @disable this(this);
+        ~this() pure nothrow @safe @nogc {}
+    }
+    S s1;
+    S s2 = move(s1);
+}
+
+private void trustedMoveImpl(T)(ref T source, ref T target) @trusted
+{
+    moveImpl(source, target);
+}
+
+private void moveImpl(T)(ref T source, ref T target)
+{
+    import std.traits : hasElaborateDestructor;
+
+    static if (is(T == struct))
+    {
+        if (&source == &target) return;
+        // Destroy target before overwriting it
+        static if (hasElaborateDestructor!T) target.__xdtor();
+    }
+    // move and emplace source into target
+    moveEmplace(source, target);
+}
+
+private T trustedMoveImpl(T)(ref T source) @trusted
+{
+    return moveImpl(source);
+}
+
+private T moveImpl(T)(ref T source)
+{
+    T result = void;
+    moveEmplace(source, result);
+    return result;
+}
+
+unittest
+{
+    import std.traits;
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    import std.exception : assertCTFEable;
+    assertCTFEable!((){
+        Object obj1 = new Object;
+        Object obj2 = obj1;
+        Object obj3 = move(obj2);
+        assert(obj3 is obj1);
+
+        static struct S1 { int a = 1, b = 2; }
+        S1 s11 = { 10, 11 };
+        S1 s12 = move(s11);
+        assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11);
+
+        static struct S2 { int a = 1; int * b; }
+        S2 s21 = { 10, null };
+        s21.b = new int;
+        S2 s22 = move(s21);
+        assert(s21 == s22);
+    });
+
+    // Issue 5661 test(1)
+    static struct S3
+    {
+        static struct X { int n = 0; ~this(){n = 0;} }
+        X x;
+    }
+    static assert(hasElaborateDestructor!S3);
+    S3 s31;
+    s31.x.n = 1;
+    S3 s32 = move(s31);
+    assert(s31.x.n == 0);
+    assert(s32.x.n == 1);
+
+    // Issue 5661 test(2)
+    static struct S4
+    {
+        static struct X { int n = 0; this(this){n = 0;} }
+        X x;
+    }
+    static assert(hasElaborateCopyConstructor!S4);
+    S4 s41;
+    s41.x.n = 1;
+    S4 s42 = move(s41);
+    assert(s41.x.n == 0);
+    assert(s42.x.n == 1);
+
+    // Issue 13990 test
+    class S5;
+
+    S5 s51;
+    S5 s52 = s51;
+    S5 s53;
+    s53 = move(s52);
+    assert(s53 is s51);
+}
+
+unittest
+{
+    static struct S { int n = 0; ~this() @system { n = 0; } }
+    S a, b;
+    static assert(!__traits(compiles, () @safe { move(a, b); }));
+    static assert(!__traits(compiles, () @safe { move(a); }));
+    a.n = 1;
+    () @trusted { move(a, b); }();
+    assert(a.n == 0);
+    a.n = 1;
+    () @trusted { move(a); }();
+    assert(a.n == 0);
+}
+
+unittest//Issue 6217
+{
+    import std.algorithm.iteration : map;
+    auto x = map!"a"([1,2,3]);
+    x = move(x);
+}
+
+unittest// Issue 8055
+{
+    static struct S
+    {
+        int x;
+        ~this()
+        {
+            assert(x == 0);
+        }
+    }
+    S foo(S s)
+    {
+        return move(s);
+    }
+    S a;
+    a.x = 0;
+    auto b = foo(a);
+    assert(b.x == 0);
+}
+
+unittest// Issue 8057
+{
+    int n = 10;
+    struct S
+    {
+        int x;
+        ~this()
+        {
+            // Access to enclosing scope
+            assert(n == 10);
+        }
+    }
+    S foo(S s)
+    {
+        // Move nested struct
+        return move(s);
+    }
+    S a;
+    a.x = 1;
+    auto b = foo(a);
+    assert(b.x == 1);
+
+    // Regression 8171
+    static struct Array(T)
+    {
+        // nested struct has no member
+        struct Payload
+        {
+            ~this() {}
+        }
+    }
+    Array!int.Payload x = void;
+    move(x);
+    move(x, x);
+}
+
+/**
+ * Similar to $(LREF move) but assumes `target` is uninitialized. This
+ * is more efficient because `source` can be blitted over `target`
+ * without destroying or initializing it first.
+ *
+ * Params:
+ *   source = value to be moved into target
+ *   target = uninitialized value to be filled by source
+ */
+void moveEmplace(T)(ref T source, ref T target) @system
+{
+    import core.stdc.string : memcpy, memset;
+    import std.traits : hasAliasing, hasElaborateAssign,
+                        hasElaborateCopyConstructor, hasElaborateDestructor,
+                        isAssignable;
+
+    static if (!is(T == class) && hasAliasing!T) if (!__ctfe)
+    {
+        import std.exception : doesPointTo;
+        assert(!doesPointTo(source, source), "Cannot move object with internal pointer.");
+    }
+
+    static if (is(T == struct))
+    {
+        assert(&source !is &target, "source and target must not be identical");
+
+        static if (hasElaborateAssign!T || !isAssignable!T)
+            memcpy(&target, &source, T.sizeof);
+        else
+            target = source;
+
+        // If the source defines a destructor or a postblit hook, we must obliterate the
+        // object in order to avoid double freeing and undue aliasing
+        static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T)
+        {
+            // If T is nested struct, keep original context pointer
+            static if (__traits(isNested, T))
+                enum sz = T.sizeof - (void*).sizeof;
+            else
+                enum sz = T.sizeof;
+
+            auto init = typeid(T).initializer();
+            if (init.ptr is null) // null ptr means initialize to 0s
+                memset(&source, 0, sz);
+            else
+                memcpy(&source, init.ptr, sz);
+        }
+    }
+    else
+    {
+        // Primitive data (including pointers and arrays) or class -
+        // assignment works great
+        target = source;
+    }
+}
+
+///
+pure nothrow @nogc unittest
+{
+    static struct Foo
+    {
+    pure nothrow @nogc:
+        this(int* ptr) { _ptr = ptr; }
+        ~this() { if (_ptr) ++*_ptr; }
+        int* _ptr;
+    }
+
+    int val;
+    Foo foo1 = void; // uninitialized
+    auto foo2 = Foo(&val); // initialized
+
+    // Using `move(foo2, foo1)` has an undefined effect because it destroys the uninitialized foo1.
+    // MoveEmplace directly overwrites foo1 without destroying or initializing it first.
+    assert(foo2._ptr is &val);
+    moveEmplace(foo2, foo1);
+    assert(foo1._ptr is &val && foo2._ptr is null);
+}
+
+// moveAll
+/**
+For each element $(D a) in $(D src) and each element $(D b) in $(D
+tgt) in lockstep in increasing order, calls $(D move(a, b)).
+
+Preconditions:
+$(D walkLength(src) <= walkLength(tgt)).
+This precondition will be asserted. If you cannot ensure there is enough room in
+`tgt` to accommodate all of `src` use $(LREF moveSome) instead.
+
+Params:
+    src = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) with
+        movable elements.
+    tgt = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) with
+        elements that elements from $(D src) can be moved into.
+
+Returns: The leftover portion of $(D tgt) after all elements from $(D src) have
+been moved.
+ */
+Range2 moveAll(Range1, Range2)(Range1 src, Range2 tgt)
+if (isInputRange!Range1 && isInputRange!Range2
+        && is(typeof(move(src.front, tgt.front))))
+{
+    return moveAllImpl!move(src, tgt);
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+    int[3] a = [ 1, 2, 3 ];
+    int[5] b;
+    assert(moveAll(a[], b[]) is b[3 .. $]);
+    assert(a[] == b[0 .. 3]);
+    int[3] cmp = [ 1, 2, 3 ];
+    assert(a[] == cmp[]);
+}
+
+/**
+ * Similar to $(LREF moveAll) but assumes all elements in `target` are
+ * uninitialized. Uses $(LREF moveEmplace) to move elements from
+ * `source` over elements from `target`.
+ */
+Range2 moveEmplaceAll(Range1, Range2)(Range1 src, Range2 tgt) @system
+if (isInputRange!Range1 && isInputRange!Range2
+        && is(typeof(moveEmplace(src.front, tgt.front))))
+{
+    return moveAllImpl!moveEmplace(src, tgt);
+}
+
+///
+pure nothrow @nogc unittest
+{
+    static struct Foo
+    {
+        ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; }
+        int* _ptr;
+    }
+    int[3] refs = [0, 1, 2];
+    Foo[3] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2])];
+    Foo[5] dst = void;
+
+    auto tail = moveEmplaceAll(src[], dst[]); // move 3 value from src over dst
+    assert(tail.length == 2); // returns remaining uninitialized values
+    initializeAll(tail);
+
+    import std.algorithm.searching : all;
+    assert(src[].all!(e => e._ptr is null));
+    assert(dst[0 .. 3].all!(e => e._ptr !is null));
+}
+
+unittest
+{
+    struct InputRange
+    {
+        ref int front() { return data[0]; }
+        void popFront() { data.popFront; }
+        bool empty() { return data.empty; }
+        int[] data;
+    }
+    auto a = InputRange([ 1, 2, 3 ]);
+    auto b = InputRange(new int[5]);
+    moveAll(a, b);
+    assert(a.data == b.data[0 .. 3]);
+    assert(a.data == [ 1, 2, 3 ]);
+}
+
+private Range2 moveAllImpl(alias moveOp, Range1, Range2)(
+    ref Range1 src, ref Range2 tgt)
+{
+    import std.exception : enforce;
+
+    static if (isRandomAccessRange!Range1 && hasLength!Range1 && hasLength!Range2
+         && hasSlicing!Range2 && isRandomAccessRange!Range2)
+    {
+        auto toMove = src.length;
+        assert(toMove <= tgt.length);
+        foreach (idx; 0 .. toMove)
+            moveOp(src[idx], tgt[idx]);
+        return tgt[toMove .. tgt.length];
+    }
+    else
+    {
+        for (; !src.empty; src.popFront(), tgt.popFront())
+        {
+            assert(!tgt.empty);
+            moveOp(src.front, tgt.front);
+        }
+        return tgt;
+    }
+}
+
+// moveSome
+/**
+For each element $(D a) in $(D src) and each element $(D b) in $(D
+tgt) in lockstep in increasing order, calls $(D move(a, b)). Stops
+when either $(D src) or $(D tgt) have been exhausted.
+
+Params:
+    src = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) with
+        movable elements.
+    tgt = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) with
+        elements that elements from $(D src) can be moved into.
+
+Returns: The leftover portions of the two ranges after one or the other of the
+ranges have been exhausted.
+ */
+Tuple!(Range1, Range2) moveSome(Range1, Range2)(Range1 src, Range2 tgt)
+if (isInputRange!Range1 && isInputRange!Range2
+        && is(typeof(move(src.front, tgt.front))))
+{
+    return moveSomeImpl!move(src, tgt);
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+    int[5] a = [ 1, 2, 3, 4, 5 ];
+    int[3] b;
+    assert(moveSome(a[], b[])[0] is a[3 .. $]);
+    assert(a[0 .. 3] == b);
+    assert(a == [ 1, 2, 3, 4, 5 ]);
+}
+
+/**
+ * Same as $(LREF moveSome) but assumes all elements in `target` are
+ * uninitialized. Uses $(LREF moveEmplace) to move elements from
+ * `source` over elements from `target`.
+ */
+Tuple!(Range1, Range2) moveEmplaceSome(Range1, Range2)(Range1 src, Range2 tgt) @system
+if (isInputRange!Range1 && isInputRange!Range2
+        && is(typeof(move(src.front, tgt.front))))
+{
+    return moveSomeImpl!moveEmplace(src, tgt);
+}
+
+///
+pure nothrow @nogc unittest
+{
+    static struct Foo
+    {
+        ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; }
+        int* _ptr;
+    }
+    int[4] refs = [0, 1, 2, 3];
+    Foo[4] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2]), Foo(&refs[3])];
+    Foo[3] dst = void;
+
+    auto res = moveEmplaceSome(src[], dst[]);
+
+    import std.algorithm.searching : all;
+    assert(src[0 .. 3].all!(e => e._ptr is null));
+    assert(src[3]._ptr !is null);
+    assert(dst[].all!(e => e._ptr !is null));
+}
+
+private Tuple!(Range1, Range2) moveSomeImpl(alias moveOp, Range1, Range2)(
+    ref Range1 src, ref Range2 tgt)
+{
+    for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront())
+        moveOp(src.front, tgt.front);
+    return tuple(src, tgt);
+ }
+
+
+// SwapStrategy
+/**
+Defines the swapping strategy for algorithms that need to swap
+elements in a range (such as partition and sort). The strategy
+concerns the swapping of elements that are not the core concern of the
+algorithm. For example, consider an algorithm that sorts $(D [ "abc",
+"b", "aBc" ]) according to $(D toUpper(a) < toUpper(b)). That
+algorithm might choose to swap the two equivalent strings $(D "abc")
+and $(D "aBc"). That does not affect the sorting since both $(D [
+"abc", "aBc", "b" ]) and $(D [ "aBc", "abc", "b" ]) are valid
+outcomes.
+
+Some situations require that the algorithm must NOT ever change the
+relative ordering of equivalent elements (in the example above, only
+$(D [ "abc", "aBc", "b" ]) would be the correct result). Such
+algorithms are called $(B stable). If the ordering algorithm may swap
+equivalent elements discretionarily, the ordering is called $(B
+unstable).
+
+Yet another class of algorithms may choose an intermediate tradeoff by
+being stable only on a well-defined subrange of the range. There is no
+established terminology for such behavior; this library calls it $(B
+semistable).
+
+Generally, the $(D stable) ordering strategy may be more costly in
+time and/or space than the other two because it imposes additional
+constraints. Similarly, $(D semistable) may be costlier than $(D
+unstable). As (semi-)stability is not needed very often, the ordering
+algorithms in this module parameterized by $(D SwapStrategy) all
+choose $(D SwapStrategy.unstable) as the default.
+*/
+
+enum SwapStrategy
+{
+    /**
+       Allows freely swapping of elements as long as the output
+       satisfies the algorithm's requirements.
+    */
+    unstable,
+    /**
+       In algorithms partitioning ranges in two, preserve relative
+       ordering of elements only to the left of the partition point.
+    */
+    semistable,
+    /**
+       Preserve the relative ordering of elements to the largest
+       extent allowed by the algorithm's requirements.
+    */
+    stable,
+}
+
+/**
+Eliminates elements at given offsets from $(D range) and returns the
+shortened range. In the simplest call, one element is removed.
+
+----
+int[] a = [ 3, 5, 7, 8 ];
+assert(remove(a, 1) == [ 3, 7, 8 ]);
+assert(a == [ 3, 7, 8, 8 ]);
+----
+
+In the case above the element at offset $(D 1) is removed and $(D
+remove) returns the range smaller by one element. The original array
+has remained of the same length because all functions in $(D
+std.algorithm) only change $(I content), not $(I topology). The value
+$(D 8) is repeated because $(LREF move) was invoked to
+move elements around and on integers $(D move) simply copies the source to
+the destination. To replace $(D a) with the effect of the removal,
+simply assign $(D a = remove(a, 1)). The slice will be rebound to the
+shorter array and the operation completes with maximal efficiency.
+
+Multiple indices can be passed into $(D remove). In that case,
+elements at the respective indices are all removed. The indices must
+be passed in increasing order, otherwise an exception occurs.
+
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+assert(remove(a, 1, 3, 5) ==
+    [ 0, 2, 4, 6, 7, 8, 9, 10 ]);
+----
+
+(Note how all indices refer to slots in the $(I original) array, not
+in the array as it is being progressively shortened.) Finally, any
+combination of integral offsets and tuples composed of two integral
+offsets can be passed in.
+
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 5, 6, 7, 8, 10 ]);
+----
+
+In this case, the slots at positions 1, 3, 4, and 9 are removed from
+the array. The tuple passes in a range closed to the left and open to
+the right (consistent with built-in slices), e.g. $(D tuple(3, 5))
+means indices $(D 3) and $(D 4) but not $(D 5).
+
+If the need is to remove some elements in the range but the order of
+the remaining elements does not have to be preserved, you may want to
+pass $(D SwapStrategy.unstable) to $(D remove).
+
+----
+int[] a = [ 0, 1, 2, 3 ];
+assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]);
+----
+
+In the case above, the element at slot $(D 1) is removed, but replaced
+with the last element of the range. Taking advantage of the relaxation
+of the stability requirement, $(D remove) moved elements from the end
+of the array over the slots to be removed. This way there is less data
+movement to be done which improves the execution time of the function.
+
+The function $(D remove) works on any forward range. The moving
+strategy is (listed from fastest to slowest): $(UL $(LI If $(D s ==
+SwapStrategy.unstable && isRandomAccessRange!Range && hasLength!Range
+&& hasLvalueElements!Range), then elements are moved from the end
+of the range into the slots to be filled. In this case, the absolute
+minimum of moves is performed.)  $(LI Otherwise, if $(D s ==
+SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range
+&& hasLvalueElements!Range), then elements are still moved from the
+end of the range, but time is spent on advancing between slots by repeated
+calls to $(D range.popFront).)  $(LI Otherwise, elements are moved
+incrementally towards the front of $(D range); a given element is never
+moved several times, but more elements are moved than in the previous
+cases.))
+
+Params:
+    s = a SwapStrategy to determine if the original order needs to be preserved
+    range = a bidirectional range with a length member
+    offset = which element(s) to remove
+
+Returns:
+    a range containing all of the elements of range with offset removed
+ */
+Range remove
+(SwapStrategy s = SwapStrategy.stable, Range, Offset...)
+(Range range, Offset offset)
+if (s != SwapStrategy.stable
+    && isBidirectionalRange!Range
+    && hasLvalueElements!Range
+    && hasLength!Range
+    && Offset.length >= 1)
+{
+    import std.algorithm.comparison : min;
+
+    Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts;
+    foreach (i, v; offset)
+    {
+        static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t))
+        {
+            blackouts[i].pos = v[0];
+            blackouts[i].len = v[1] - v[0];
+        }
+        else
+        {
+            static assert(is(typeof(v) : size_t), typeof(v).stringof);
+            blackouts[i].pos = v;
+            blackouts[i].len = 1;
+        }
+        static if (i > 0)
+        {
+            import std.exception : enforce;
+
+            enforce(blackouts[i - 1].pos + blackouts[i - 1].len
+                    <= blackouts[i].pos,
+                "remove(): incorrect ordering of elements to remove");
+        }
+    }
+
+    size_t left = 0, right = offset.length - 1;
+    auto tgt = range.save;
+    size_t steps = 0;
+
+    while (left <= right)
+    {
+        // Look for a blackout on the right
+        if (blackouts[right].pos + blackouts[right].len >= range.length)
+        {
+            range.popBackExactly(blackouts[right].len);
+
+            // Since right is unsigned, we must check for this case, otherwise
+            // we might turn it into size_t.max and the loop condition will not
+            // fail when it should.
+            if (right > 0)
+            {
+                --right;
+                continue;
+            }
+            else
+                break;
+        }
+        // Advance to next blackout on the left
+        assert(blackouts[left].pos >= steps);
+        tgt.popFrontExactly(blackouts[left].pos - steps);
+        steps = blackouts[left].pos;
+        auto toMove = min(
+            blackouts[left].len,
+            range.length - (blackouts[right].pos + blackouts[right].len));
+        foreach (i; 0 .. toMove)
+        {
+            move(range.back, tgt.front);
+            range.popBack();
+            tgt.popFront();
+        }
+        steps += toMove;
+        if (toMove == blackouts[left].len)
+        {
+            // Filled the entire left hole
+            ++left;
+            continue;
+        }
+    }
+
+    return range;
+}
+
+// Ditto
+Range remove
+(SwapStrategy s = SwapStrategy.stable, Range, Offset...)
+(Range range, Offset offset)
+if (s == SwapStrategy.stable
+    && isBidirectionalRange!Range
+    && hasLvalueElements!Range
+    && Offset.length >= 1)
+{
+    auto result = range;
+    auto src = range, tgt = range;
+    size_t pos;
+    foreach (pass, i; offset)
+    {
+        static if (is(typeof(i[0])) && is(typeof(i[1])))
+        {
+            auto from = i[0], delta = i[1] - i[0];
+        }
+        else
+        {
+            auto from = i;
+            enum delta = 1;
+        }
+
+        static if (pass > 0)
+        {
+            import std.exception : enforce;
+            enforce(pos <= from,
+                    "remove(): incorrect ordering of elements to remove");
+
+            for (; pos < from; ++pos, src.popFront(), tgt.popFront())
+            {
+                move(src.front, tgt.front);
+            }
+        }
+        else
+        {
+            src.popFrontExactly(from);
+            tgt.popFrontExactly(from);
+            pos = from;
+        }
+        // now skip source to the "to" position
+        src.popFrontExactly(delta);
+        result.popBackExactly(delta);
+        pos += delta;
+    }
+    // leftover move
+    moveAll(src, tgt);
+    return result;
+}
+
+@safe unittest
+{
+    import std.exception : assertThrown;
+    import std.range;
+
+    // http://d.puremagic.com/issues/show_bug.cgi?id=10173
+    int[] test = iota(0, 10).array();
+    assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3)));
+    assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3)));
+    assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3));
+    assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3));
+}
+
+@safe unittest
+{
+    import std.range;
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    //writeln(remove!(SwapStrategy.stable)(a, 1));
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.stable)(a, 1) ==
+        [ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]);
+
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.unstable)(a, 0, 10) ==
+           [ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]);
+
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) ==
+            [ 8, 1, 2, 3, 4, 5, 6, 7 ]);
+    // http://d.puremagic.com/issues/show_bug.cgi?id=5224
+    a = [ 1, 2, 3, 4 ];
+    assert(remove!(SwapStrategy.unstable)(a, 2) ==
+           [ 1, 2, 4 ]);
+
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    //writeln(remove!(SwapStrategy.stable)(a, 1, 5));
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.stable)(a, 1, 5) ==
+        [ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]);
+
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    //writeln(remove!(SwapStrategy.stable)(a, 1, 3, 5));
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.stable)(a, 1, 3, 5)
+            == [ 0, 2, 4, 6, 7, 8, 9, 10]);
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    //writeln(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)));
+    a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5))
+            == [ 0, 2, 5, 6, 7, 8, 9, 10]);
+
+    a = iota(0, 10).array();
+    assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7))
+            == [0, 9, 8, 7, 4, 5]);
+}
+
+@safe unittest
+{
+    // Issue 11576
+    auto arr = [1,2,3];
+    arr = arr.remove!(SwapStrategy.unstable)(2);
+    assert(arr == [1,2]);
+
+}
+
+@safe unittest
+{
+    import std.range;
+    // Bug# 12889
+    int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]];
+    auto orig = arr.dup;
+    foreach (i; iota(arr.length))
+    {
+        assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i)));
+        assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i)));
+    }
+}
+
+/**
+Reduces the length of the bidirectional range $(D range) by removing
+elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable),
+elements are moved from the right end of the range over the elements
+to eliminate. If $(D s = SwapStrategy.stable) (the default),
+elements are moved progressively to front such that their relative
+order is preserved. Returns the filtered range.
+
+Params:
+    range = a bidirectional ranges with lvalue elements
+
+Returns:
+    the range with all of the elements where $(D pred) is $(D true)
+    removed
+*/
+Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range)
+(Range range)
+if (isBidirectionalRange!Range
+    && hasLvalueElements!Range)
+{
+    import std.functional : unaryFun;
+    auto result = range;
+    static if (s != SwapStrategy.stable)
+    {
+        for (;!range.empty;)
+        {
+            if (!unaryFun!pred(range.front))
+            {
+                range.popFront();
+                continue;
+            }
+            move(range.back, range.front);
+            range.popBack();
+            result.popBack();
+        }
+    }
+    else
+    {
+        auto tgt = range;
+        for (; !range.empty; range.popFront())
+        {
+            if (unaryFun!(pred)(range.front))
+            {
+                // yank this guy
+                result.popBack();
+                continue;
+            }
+            // keep this guy
+            move(range.front, tgt.front);
+            tgt.popFront();
+        }
+    }
+    return result;
+}
+
+///
+@safe unittest
+{
+    static immutable base = [1, 2, 3, 2, 4, 2, 5, 2];
+
+    int[] arr = base[].dup;
+
+    // using a string-based predicate
+    assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]);
+
+    // The original array contents have been modified,
+    // so we need to reset it to its original state.
+    // The length is unmodified however.
+    arr[] = base[];
+
+    // using a lambda predicate
+    assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ];
+    assert(remove!("a == 2", SwapStrategy.unstable)(a) ==
+            [ 1, 6, 3, 5, 3, 4, 5 ]);
+    a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ];
+    //writeln(remove!("a != 2", SwapStrategy.stable)(a));
+    assert(remove!("a == 2", SwapStrategy.stable)(a) ==
+            [ 1, 3, 3, 4, 5, 5, 6 ]);
+}
+
+// reverse
+/**
+Reverses $(D r) in-place.  Performs $(D r.length / 2) evaluations of $(D
+swap).
+
+Params:
+    r = a bidirectional range with swappable elements or a random access range with a length member
+
+See_Also:
+    $(WEB sgi.com/tech/stl/_reverse.html, STL's _reverse)
+*/
+void reverse(Range)(Range r)
+if (isBidirectionalRange!Range && !isRandomAccessRange!Range
+    && hasSwappableElements!Range)
+{
+    while (!r.empty)
+    {
+        swap(r.front, r.back);
+        r.popFront();
+        if (r.empty) break;
+        r.popBack();
+    }
+}
+
+///
+@safe unittest
+{
+    int[] arr = [ 1, 2, 3 ];
+    reverse(arr);
+    assert(arr == [ 3, 2, 1 ]);
+}
+
+///ditto
+void reverse(Range)(Range r)
+if (isRandomAccessRange!Range && hasLength!Range)
+{
+    //swapAt is in fact the only way to swap non lvalue ranges
+    immutable last = r.length-1;
+    immutable steps = r.length/2;
+    for (size_t i = 0; i < steps; i++)
+    {
+        swapAt(r, i, last-i);
+    }
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] range = null;
+    reverse(range);
+    range = [ 1 ];
+    reverse(range);
+    assert(range == [1]);
+    range = [1, 2];
+    reverse(range);
+    assert(range == [2, 1]);
+    range = [1, 2, 3];
+    reverse(range);
+    assert(range == [3, 2, 1]);
+}
+
+/**
+Reverses $(D r) in-place, where $(D r) is a narrow string (having
+elements of type $(D char) or $(D wchar)). UTF sequences consisting of
+multiple code units are preserved properly.
+
+Params:
+    s = a narrow string
+
+Bugs:
+    When passing a sting with unicode modifiers on characters, such as $(D \u0301),
+    this function will not properly keep the position of the modifier. For example,
+    reversing $(D ba\u0301d) ("bád") will result in d\u0301ab ("d́ab") instead of
+    $(D da\u0301b) ("dáb").
+*/
+void reverse(Char)(Char[] s)
+if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable))
+{
+    import std.string : representation;
+    import std.utf : stride;
+
+    auto r = representation(s);
+    for (size_t i = 0; i < s.length; )
+    {
+        immutable step = stride(s, i);
+        if (step > 1)
+        {
+            .reverse(r[i .. i + step]);
+            i += step;
+        }
+        else
+        {
+            ++i;
+        }
+    }
+    reverse(r);
+}
+
+///
+@safe unittest
+{
+    char[] arr = "hello\U00010143\u0100\U00010143".dup;
+    reverse(arr);
+    assert(arr == "\U00010143\u0100\U00010143olleh");
+}
+
+@safe unittest
+{
+    void test(string a, string b)
+    {
+        auto c = a.dup;
+        reverse(c);
+        assert(c == b, c ~ " != " ~ b);
+    }
+
+    test("a", "a");
+    test(" ", " ");
+    test("\u2029", "\u2029");
+    test("\u0100", "\u0100");
+    test("\u0430", "\u0430");
+    test("\U00010143", "\U00010143");
+    test("abcdefcdef", "fedcfedcba");
+    test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh");
+}
+
+//private
+void swapAt(R)(R r, size_t i1, size_t i2)
+{
+    static if (is(typeof(&r[i1])))
+    {
+        swap(r[i1], r[i2]);
+    }
+    else
+    {
+        if (i1 == i2) return;
+        auto t1 = moveAt(r, i1);
+        auto t2 = moveAt(r, i2);
+        r[i2] = t1;
+        r[i1] = t2;
+    }
+}
+
+/**
+    The strip group of functions allow stripping of either leading, trailing,
+    or both leading and trailing elements.
+
+    The $(D stripLeft) function will strip the $(D front) of the range,
+    the $(D stripRight) function will strip the $(D back) of the range,
+    while the $(D strip) function will strip both the $(D front) and $(D back)
+    of the range.
+
+    Note that the $(D strip) and $(D stripRight) functions require the range to
+    be a $(LREF BidirectionalRange) range.
+
+    All of these functions come in two varieties: one takes a target element,
+    where the range will be stripped as long as this element can be found.
+    The other takes a lambda predicate, where the range will be stripped as
+    long as the predicate returns true.
+
+    Params:
+        range = a bidirectional or input range
+        element = the elements to remove
+
+    Returns:
+        a Range with all of range except element at the start and end
+*/
+Range strip(Range, E)(Range range, E element)
+    if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool))
+{
+    return range.stripLeft(element).stripRight(element);
+}
+
+/// ditto
+Range strip(alias pred, Range)(Range range)
+    if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool))
+{
+    return range.stripLeft!pred().stripRight!pred();
+}
+
+/// ditto
+Range stripLeft(Range, E)(Range range, E element)
+    if (isInputRange!Range && is(typeof(range.front == element) : bool))
+{
+    import std.algorithm.searching : find;
+    return find!((auto ref a) => a != element)(range);
+}
+
+/// ditto
+Range stripLeft(alias pred, Range)(Range range)
+    if (isInputRange!Range && is(typeof(pred(range.front)) : bool))
+{
+    import std.algorithm.searching : find;
+    import std.functional : not;
+
+    return find!(not!pred)(range);
+}
+
+/// ditto
+Range stripRight(Range, E)(Range range, E element)
+    if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool))
+{
+    for (; !range.empty; range.popBack())
+    {
+        if (range.back != element)
+            break;
+    }
+    return range;
+}
+
+/// ditto
+Range stripRight(alias pred, Range)(Range range)
+    if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool))
+{
+    for (; !range.empty; range.popBack())
+    {
+        if (!pred(range.back))
+            break;
+    }
+    return range;
+}
+
+/// Strip leading and trailing elements equal to the target element.
+@safe pure unittest
+{
+    assert("  foobar  ".strip(' ') == "foobar");
+    assert("00223.444500".strip('0') == "223.4445");
+    assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê");
+    assert([1, 1, 0, 1, 1].strip(1) == [0]);
+    assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2);
+}
+
+/// Strip leading and trailing elements while the predicate returns true.
+@safe pure unittest
+{
+    assert("  foobar  ".strip!(a => a == ' ')() == "foobar");
+    assert("00223.444500".strip!(a => a == '0')() == "223.4445");
+    assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê");
+    assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]);
+    assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2);
+}
+
+/// Strip leading elements equal to the target element.
+@safe pure unittest
+{
+    assert("  foobar  ".stripLeft(' ') == "foobar  ");
+    assert("00223.444500".stripLeft('0') == "223.444500");
+    assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé");
+    assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]);
+    assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3);
+}
+
+/// Strip leading elements while the predicate returns true.
+@safe pure unittest
+{
+    assert("  foobar  ".stripLeft!(a => a == ' ')() == "foobar  ");
+    assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500");
+    assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé");
+    assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]);
+    assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2);
+}
+
+/// Strip trailing elements equal to the target element.
+@safe pure unittest
+{
+    assert("  foobar  ".stripRight(' ') == "  foobar");
+    assert("00223.444500".stripRight('0') == "00223.4445");
+    assert("ùniçodêéé".stripRight('é') == "ùniçodê");
+    assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]);
+    assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3);
+}
+
+/// Strip trailing elements while the predicate returns true.
+@safe pure unittest
+{
+    assert("  foobar  ".stripRight!(a => a == ' ')() == "  foobar");
+    assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445");
+    assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê");
+    assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]);
+    assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3);
+}
+
+// swap
+/**
+Swaps $(D lhs) and $(D rhs). The instances $(D lhs) and $(D rhs) are moved in
+memory, without ever calling $(D opAssign), nor any other function. $(D T)
+need not be assignable at all to be swapped.
+
+If $(D lhs) and $(D rhs) reference the same instance, then nothing is done.
+
+$(D lhs) and $(D rhs) must be mutable. If $(D T) is a struct or union, then
+its fields must also all be (recursively) mutable.
+
+Params:
+    lhs = Data to be swapped with $(D rhs).
+    rhs = Data to be swapped with $(D lhs).
+*/
+void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc
+if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs))))
+{
+    import std.traits : hasAliasing, hasElaborateAssign, isAssignable,
+                        isStaticArray;
+    static if (hasAliasing!T) if (!__ctfe)
+    {
+        import std.exception : doesPointTo;
+        assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer.");
+        assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer.");
+        assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs.");
+        assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs.");
+    }
+
+    static if (hasElaborateAssign!T || !isAssignable!T)
+    {
+        if (&lhs != &rhs)
+        {
+            // For structs with non-trivial assignment, move memory directly
+            ubyte[T.sizeof] t = void;
+            auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof];
+            auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof];
+            t[] = a[];
+            a[] = b[];
+            b[] = t[];
+        }
+    }
+    else
+    {
+        //Avoid assigning overlapping arrays. Dynamic arrays are fine, because
+        //it's their ptr and length properties which get assigned rather
+        //than their elements when assigning them, but static arrays are value
+        //types and therefore all of their elements get copied as part of
+        //assigning them, which would be assigning overlapping arrays if lhs
+        //and rhs were the same array.
+        static if (isStaticArray!T)
+        {
+            if (lhs.ptr == rhs.ptr)
+                return;
+        }
+
+        // For non-struct types, suffice to do the classic swap
+        auto tmp = lhs;
+        lhs = rhs;
+        rhs = tmp;
+    }
+}
+
+///
+@safe unittest
+{
+    // Swapping POD (plain old data) types:
+    int a = 42, b = 34;
+    swap(a, b);
+    assert(a == 34 && b == 42);
+
+    // Swapping structs with indirection:
+    static struct S { int x; char c; int[] y; }
+    S s1 = { 0, 'z', [ 1, 2 ] };
+    S s2 = { 42, 'a', [ 4, 6 ] };
+    swap(s1, s2);
+    assert(s1.x == 42);
+    assert(s1.c == 'a');
+    assert(s1.y == [ 4, 6 ]);
+
+    assert(s2.x == 0);
+    assert(s2.c == 'z');
+    assert(s2.y == [ 1, 2 ]);
+
+    // Immutables cannot be swapped:
+    immutable int imm1, imm2;
+    static assert(!__traits(compiles, swap(imm1, imm2)));
+}
+
+///
+@safe unittest
+{
+    // Non-copyable types can still be swapped.
+    static struct NoCopy
+    {
+        this(this) { assert(0); }
+        int n;
+        string s;
+    }
+    NoCopy nc1, nc2;
+    nc1.n = 127; nc1.s = "abc";
+    nc2.n = 513; nc2.s = "uvwxyz";
+
+    swap(nc1, nc2);
+    assert(nc1.n == 513 && nc1.s == "uvwxyz");
+    assert(nc2.n == 127 && nc2.s == "abc");
+
+    swap(nc1, nc1);
+    swap(nc2, nc2);
+    assert(nc1.n == 513 && nc1.s == "uvwxyz");
+    assert(nc2.n == 127 && nc2.s == "abc");
+
+    // Types containing non-copyable fields can also be swapped.
+    static struct NoCopyHolder
+    {
+        NoCopy noCopy;
+    }
+    NoCopyHolder h1, h2;
+    h1.noCopy.n = 31; h1.noCopy.s = "abc";
+    h2.noCopy.n = 65; h2.noCopy.s = null;
+
+    swap(h1, h2);
+    assert(h1.noCopy.n == 65 && h1.noCopy.s == null);
+    assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc");
+
+    swap(h1, h1);
+    swap(h2, h2);
+    assert(h1.noCopy.n == 65 && h1.noCopy.s == null);
+    assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc");
+
+    // Const types cannot be swapped.
+    const NoCopy const1, const2;
+    static assert(!__traits(compiles, swap(const1, const2)));
+}
+
+@safe unittest
+{
+    //Bug# 4789
+    int[1] s = [1];
+    swap(s, s);
+}
+
+@safe unittest
+{
+    static struct NoAssign
+    {
+        int i;
+        void opAssign(NoAssign) @disable;
+    }
+    auto s1 = NoAssign(1);
+    auto s2 = NoAssign(2);
+    swap(s1, s2);
+    assert(s1.i == 2);
+    assert(s2.i == 1);
+}
+
+@safe unittest
+{
+    struct S
+    {
+        const int i;
+    }
+    S s;
+    static assert(!__traits(compiles, swap(s, s)));
+}
+
+@safe unittest
+{
+    //11853
+    import std.traits : isAssignable;
+    alias T = Tuple!(int, double);
+    static assert(isAssignable!T);
+}
+
+@safe unittest
+{
+    // 12024
+    import std.datetime;
+    SysTime a, b;
+}
+
+unittest // 9975
+{
+    import std.exception : doesPointTo, mayPointTo;
+    static struct S2
+    {
+        union
+        {
+            size_t sz;
+            string s;
+        }
+    }
+    S2 a , b;
+    a.sz = -1;
+    assert(!doesPointTo(a, b));
+    assert( mayPointTo(a, b));
+    swap(a, b);
+
+    //Note: we can catch an error here, because there is no RAII in this test
+    import std.exception : assertThrown;
+    void* p, pp;
+    p = &p;
+    assertThrown!Error(move(p));
+    assertThrown!Error(move(p, pp));
+    assertThrown!Error(swap(p, pp));
+}
+
+unittest
+{
+    static struct A
+    {
+        int* x;
+        this(this) { x = new int; }
+    }
+    A a1, a2;
+    swap(a1, a2);
+
+    static struct B
+    {
+        int* x;
+        void opAssign(B) { x = new int; }
+    }
+    B b1, b2;
+    swap(b1, b2);
+}
+
+// Not yet documented
+void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs))))
+{
+    lhs.proxySwap(rhs);
+}
+
+void swapFront(R1, R2)(R1 r1, R2 r2)
+    if (isInputRange!R1 && isInputRange!R2)
+{
+    static if (is(typeof(swap(r1.front, r2.front))))
+    {
+        swap(r1.front, r2.front);
+    }
+    else
+    {
+        auto t1 = moveFront(r1), t2 = moveFront(r2);
+        r1.front = move(t2);
+        r2.front = move(t1);
+    }
+}
+
+// swapRanges
+/**
+Swaps all elements of $(D r1) with successive elements in $(D r2).
+Returns a tuple containing the remainder portions of $(D r1) and $(D
+r2) that were not swapped (one of them will be empty). The ranges may
+be of different types but must have the same element type and support
+swapping.
+
+Params:
+    r1 = an $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+         with swappable elements
+    r2 = an $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+         with swappable elements
+
+Returns:
+    Tuple containing the remainder portions of r1 and r2 that were not swapped
+*/
+Tuple!(Range1, Range2)
+swapRanges(Range1, Range2)(Range1 r1, Range2 r2)
+    if (isInputRange!(Range1) && isInputRange!(Range2)
+            && hasSwappableElements!(Range1) && hasSwappableElements!(Range2)
+            && is(ElementType!(Range1) == ElementType!(Range2)))
+{
+    for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
+    {
+        swap(r1.front, r2.front);
+    }
+    return tuple(r1, r2);
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 100, 101, 102, 103 ];
+    int[] b = [ 0, 1, 2, 3 ];
+    auto c = swapRanges(a[1 .. 3], b[2 .. 4]);
+    assert(c[0].empty && c[1].empty);
+    assert(a == [ 100, 2, 3, 103 ]);
+    assert(b == [ 0, 1, 101, 102 ]);
+}
+
+/**
+Initializes each element of $(D range) with $(D value).
+Assumes that the elements of the range are uninitialized.
+This is of interest for structs that
+define copy constructors (for all other types, $(LREF fill) and
+uninitializedFill are equivalent).
+
+Params:
+        range = An
+                $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+                that exposes references to its elements and has assignable
+                elements
+        value = Assigned to each element of range
+
+See_Also:
+        $(LREF fill)
+        $(LREF initializeAll)
+ */
+void uninitializedFill(Range, Value)(Range range, Value value)
+    if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = value)))
+{
+    import std.traits : hasElaborateAssign;
+
+    alias T = ElementType!Range;
+    static if (hasElaborateAssign!T)
+    {
+        import std.conv : emplaceRef;
+
+        // Must construct stuff by the book
+        for (; !range.empty; range.popFront())
+            emplaceRef!T(range.front, value);
+    }
+    else
+        // Doesn't matter whether fill is initialized or not
+        return fill(range, value);
+}
+
+///
+nothrow unittest
+{
+    import core.stdc.stdlib : malloc, free;
+
+    auto s = (cast(int*) malloc(5 * int.sizeof))[0 .. 5];
+    uninitializedFill(s, 42);
+    assert(s == [ 42, 42, 42, 42, 42 ]);
+
+    scope(exit) free(s.ptr);
+}
diff --git a/libphobos/src/std/algorithm/package.d b/libphobos/src/std/algorithm/package.d
new file mode 100644
index 00000000000..c368374731f
--- /dev/null
+++ b/libphobos/src/std/algorithm/package.d
@@ -0,0 +1,191 @@
+// Written in the D programming language.
+
+/**
+This package implements generic algorithms oriented towards the processing of
+sequences. Sequences processed by these functions define range-based
+interfaces.  See also $(LINK2 std_range.html, Reference on ranges) and
+$(WEB ddili.org/ders/d.en/ranges.html, tutorial on ranges).
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+Algorithms are categorized into the following submodules:
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Submodule) $(TH Functions)
+)
+$(TR $(TDNW Searching)
+     $(TDNW $(SUBMODULE searching))
+     $(TD
+        $(SUBREF searching, all)
+        $(SUBREF searching, any)
+        $(SUBREF searching, balancedParens)
+        $(SUBREF searching, boyerMooreFinder)
+        $(SUBREF searching, canFind)
+        $(SUBREF searching, commonPrefix)
+        $(SUBREF searching, count)
+        $(SUBREF searching, countUntil)
+        $(SUBREF searching, endsWith)
+        $(SUBREF searching, find)
+        $(SUBREF searching, findAdjacent)
+        $(SUBREF searching, findAmong)
+        $(SUBREF searching, findSkip)
+        $(SUBREF searching, findSplit)
+        $(SUBREF searching, findSplitAfter)
+        $(SUBREF searching, findSplitBefore)
+        $(SUBREF searching, minCount)
+        $(SUBREF searching, maxCount)
+        $(SUBREF searching, minPos)
+        $(SUBREF searching, maxPos)
+        $(SUBREF searching, skipOver)
+        $(SUBREF searching, startsWith)
+        $(SUBREF searching, until)
+    )
+)
+$(TR $(TDNW Comparison)
+    $(TDNW $(SUBMODULE comparison))
+    $(TD
+        $(SUBREF comparison, among)
+        $(SUBREF comparison, castSwitch)
+        $(SUBREF comparison, clamp)
+        $(SUBREF comparison, cmp)
+        $(SUBREF comparison, either)
+        $(SUBREF comparison, equal)
+        $(SUBREF comparison, isPermutation)
+        $(SUBREF comparison, isSameLength)
+        $(SUBREF comparison, levenshteinDistance)
+        $(SUBREF comparison, levenshteinDistanceAndPath)
+        $(SUBREF comparison, max)
+        $(SUBREF comparison, min)
+        $(SUBREF comparison, mismatch)
+        $(SUBREF comparison, predSwitch)
+    )
+)
+$(TR $(TDNW Iteration)
+    $(TDNW $(SUBMODULE iteration))
+    $(TD
+        $(SUBREF iteration, cache)
+        $(SUBREF iteration, cacheBidirectional)
+        $(SUBREF iteration, chunkBy)
+        $(SUBREF iteration, each)
+        $(SUBREF iteration, filter)
+        $(SUBREF iteration, filterBidirectional)
+        $(SUBREF iteration, fold)
+        $(SUBREF iteration, group)
+        $(SUBREF iteration, joiner)
+        $(SUBREF iteration, map)
+        $(SUBREF iteration, permutations)
+        $(SUBREF iteration, reduce)
+        $(SUBREF iteration, splitter)
+        $(SUBREF iteration, sum)
+        $(SUBREF iteration, uniq)
+    )
+)
+$(TR $(TDNW Sorting)
+    $(TDNW $(SUBMODULE sorting))
+    $(TD
+        $(SUBREF sorting, completeSort)
+        $(SUBREF sorting, isPartitioned)
+        $(SUBREF sorting, isSorted)
+        $(SUBREF sorting, makeIndex)
+        $(SUBREF sorting, multiSort)
+        $(SUBREF sorting, nextEvenPermutation)
+        $(SUBREF sorting, nextPermutation)
+        $(SUBREF sorting, partialSort)
+        $(SUBREF sorting, partition)
+        $(SUBREF sorting, partition3)
+        $(SUBREF sorting, schwartzSort)
+        $(SUBREF sorting, sort)
+        $(SUBREF sorting, topN)
+        $(SUBREF sorting, topNCopy)
+        $(SUBREF sorting, topNIndex)
+    )
+)
+$(TR $(TDNW Set&nbsp;operations)
+    $(TDNW $(SUBMODULE setops))
+    $(TD
+        $(SUBREF setops, cartesianProduct)
+        $(SUBREF setops, largestPartialIntersection)
+        $(SUBREF setops, largestPartialIntersectionWeighted)
+        $(SUBREF setops, nWayUnion)
+        $(SUBREF setops, setDifference)
+        $(SUBREF setops, setIntersection)
+        $(SUBREF setops, setSymmetricDifference)
+        $(SUBREF setops, setUnion)
+    )
+)
+$(TR $(TDNW Mutation)
+    $(TDNW $(SUBMODULE mutation))
+    $(TD
+        $(SUBREF mutation, bringToFront)
+        $(SUBREF mutation, copy)
+        $(SUBREF mutation, fill)
+        $(SUBREF mutation, initializeAll)
+        $(SUBREF mutation, move)
+        $(SUBREF mutation, moveAll)
+        $(SUBREF mutation, moveSome)
+        $(SUBREF mutation, remove)
+        $(SUBREF mutation, reverse)
+        $(SUBREF mutation, strip)
+        $(SUBREF mutation, stripLeft)
+        $(SUBREF mutation, stripRight)
+        $(SUBREF mutation, swap)
+        $(SUBREF mutation, swapRanges)
+        $(SUBREF mutation, uninitializedFill)
+    )
+)
+))
+
+Many functions in this package are parameterized with a $(GLOSSARY predicate).
+The predicate may be any suitable callable type
+(a function, a delegate, a $(GLOSSARY functor), or a lambda), or a
+compile-time string. The string may consist of $(B any) legal D
+expression that uses the symbol $(D a) (for unary functions) or the
+symbols $(D a) and $(D b) (for binary functions). These names will NOT
+interfere with other homonym symbols in user code because they are
+evaluated in a different context. The default for all binary
+comparison predicates is $(D "a == b") for unordered operations and
+$(D "a < b") for ordered operations.
+
+Example:
+
+----
+int[] a = ...;
+static bool greater(int a, int b)
+{
+    return a > b;
+}
+sort!(greater)(a);         // predicate as alias
+sort!((a, b) => a > b)(a); // predicate as a lambda.
+sort!("a > b")(a);         // predicate as string
+                           // (no ambiguity with array name)
+sort(a);                   // no predicate, "a < b" is implicit
+----
+
+Macros:
+WIKI = Phobos/StdAlgorithm
+SUBMODULE = $(LINK2 std_algorithm_$1.html, std.algorithm.$1)
+SUBREF = $(LINK2 std_algorithm_$1.html#.$2, $(TT $2))$(NBSP)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/_algorithm/package.d)
+ */
+module std.algorithm;
+//debug = std_algorithm;
+
+public import std.algorithm.comparison;
+public import std.algorithm.iteration;
+public import std.algorithm.mutation;
+public import std.algorithm.setops;
+public import std.algorithm.searching;
+public import std.algorithm.sorting;
+
+static import std.functional;
+// Explicitly undocumented. It will be removed in March 2017. @@@DEPRECATED_2017-03@@@
+deprecated("Please use std.functional.forward instead.")
+alias forward = std.functional.forward;
diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d
new file mode 100644
index 00000000000..41f716bf43b
--- /dev/null
+++ b/libphobos/src/std/algorithm/searching.d
@@ -0,0 +1,3752 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic _searching algorithms.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 all,
+        $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements
+        are positive)
+$(T2 any,
+        $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one
+        element is positive)
+$(T2 balancedParens,
+        $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the
+        string has balanced parentheses.)
+$(T2 boyerMooreFinder,
+        $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld")
+        using the $(LUCKY Boyer-Moore _algorithm).)
+$(T2 canFind,
+        $(D canFind("hello world", "or")) returns $(D true).)
+$(T2 count,
+        Counts elements that are equal to a specified value or satisfy a
+        predicate.  $(D count([1, 2, 1], 1)) returns $(D 2) and
+        $(D count!"a < 0"([1, -3, 0])) returns $(D 1).)
+$(T2 countUntil,
+        $(D countUntil(a, b)) returns the number of steps taken in $(D a) to
+        reach $(D b); for example, $(D countUntil("hello!", "o")) returns
+        $(D 4).)
+$(T2 commonPrefix,
+        $(D commonPrefix("parakeet", "parachute")) returns $(D "para").)
+$(T2 endsWith,
+        $(D endsWith("rocks", "ks")) returns $(D true).)
+$(T2 find,
+        $(D find("hello world", "or")) returns $(D "orld") using linear search.
+        (For binary search refer to $(XREF range,sortedRange).))
+$(T2 findAdjacent,
+        $(D findAdjacent([1, 2, 3, 3, 4])) returns the subrange starting with
+        two equal adjacent elements, i.e. $(D [3, 3, 4]).)
+$(T2 findAmong,
+        $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is
+        among $(D "qcx").)
+$(T2 findSkip,
+        If $(D a = "abcde"), then $(D findSkip(a, "x")) returns $(D false) and
+        leaves $(D a) unchanged, whereas $(D findSkip(a, "c")) advances $(D a)
+        to $(D "de") and returns $(D true).)
+$(T2 findSplit,
+        $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"),
+        $(D "de"), and $(D "fg").)
+$(T2 findSplitAfter,
+        $(D findSplitAfter("abcdefg", "de")) returns the two ranges
+        $(D "abcde") and $(D "fg").)
+$(T2 findSplitBefore,
+        $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc")
+        and $(D "defg").)
+$(T2 minCount,
+        $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).)
+$(T2 maxCount,
+        $(D maxCount([2, 4, 1, 4, 1])) returns $(D tuple(4, 2)).)
+$(T2 minPos,
+        $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]),
+        i.e., positions the range at the first occurrence of its minimal
+        element.)
+$(T2 maxPos,
+        $(D maxPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [4, 1]),
+        i.e., positions the range at the first occurrence of its maximal
+        element.)
+$(T2 mismatch,
+        $(D mismatch("parakeet", "parachute")) returns the two ranges
+        $(D "keet") and $(D "chute").)
+$(T2 skipOver,
+        Assume $(D a = "blah"). Then $(D skipOver(a, "bi")) leaves $(D a)
+        unchanged and returns $(D false), whereas $(D skipOver(a, "bl"))
+        advances $(D a) to refer to $(D "ah") and returns $(D true).)
+$(T2 startsWith,
+        $(D startsWith("hello, world", "hello")) returns $(D true).)
+$(T2 until,
+        Lazily iterates a range until a specific value is found.)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_searching.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.searching;
+
+// FIXME
+import std.functional; // : unaryFun, binaryFun;
+import std.range.primitives;
+import std.traits;
+// FIXME
+import std.typecons; // : Tuple;
+
+/++
+Checks if $(I _all) of the elements verify $(D pred).
+ +/
+template all(alias pred = "a")
+{
+    /++
+    Returns $(D true) if and only if $(I _all) values $(D v) found in the
+    input _range $(D range) satisfy the predicate $(D pred).
+    Performs (at most) $(BIGOH range.length) evaluations of $(D pred).
+     +/
+    bool all(Range)(Range range)
+    if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))))
+    {
+        import std.functional : not;
+
+        return find!(not!(unaryFun!pred))(range).empty;
+    }
+}
+
+///
+@safe unittest
+{
+    assert( all!"a & 1"([1, 3, 5, 7, 9]));
+    assert(!all!"a & 1"([1, 2, 3, 5, 7, 9]));
+}
+
+/++
+$(D all) can also be used without a predicate, if its items can be
+evaluated to true or false in a conditional statement. This can be a
+convenient way to quickly evaluate that $(I _all) of the elements of a range
+are true.
+ +/
+@safe unittest
+{
+    int[3] vals = [5, 3, 18];
+    assert( all(vals[]));
+}
+
+@safe unittest
+{
+    int x = 1;
+    assert(all!(a => a > x)([2, 3]));
+}
+
+/++
+Checks if $(I _any) of the elements verifies $(D pred).
+$(D !any) can be used to verify that $(I none) of the elements verify
+$(D pred).
+ +/
+template any(alias pred = "a")
+{
+    /++
+    Returns $(D true) if and only if $(I _any) value $(D v) found in the
+    input _range $(D range) satisfies the predicate $(D pred).
+    Performs (at most) $(BIGOH range.length) evaluations of $(D pred).
+     +/
+    bool any(Range)(Range range)
+    if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))))
+    {
+        return !find!pred(range).empty;
+    }
+}
+
+///
+@safe unittest
+{
+    import std.ascii : isWhite;
+    assert( all!(any!isWhite)(["a a", "b b"]));
+    assert(!any!(all!isWhite)(["a a", "b b"]));
+}
+
+/++
+$(D any) can also be used without a predicate, if its items can be
+evaluated to true or false in a conditional statement. $(D !any) can be a
+convenient way to quickly test that $(I none) of the elements of a range
+evaluate to true.
+ +/
+@safe unittest
+{
+    int[3] vals1 = [0, 0, 0];
+    assert(!any(vals1[])); //none of vals1 evaluate to true
+
+    int[3] vals2 = [2, 0, 2];
+    assert( any(vals2[]));
+    assert(!all(vals2[]));
+
+    int[3] vals3 = [3, 3, 3];
+    assert( any(vals3[]));
+    assert( all(vals3[]));
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto a = [ 1, 2, 0, 4 ];
+    assert(any!"a == 2"(a));
+}
+
+// balancedParens
+/**
+Checks whether $(D r) has "balanced parentheses", i.e. all instances
+of $(D lPar) are closed by corresponding instances of $(D rPar). The
+parameter $(D maxNestingLevel) controls the nesting level allowed. The
+most common uses are the default or $(D 0). In the latter case, no
+nesting is allowed.
+
+Params:
+    r = The range to check.
+    lPar = The element corresponding with a left (opening) parenthesis.
+    rPar = The element corresponding with a right (closing) parenthesis.
+    maxNestingLevel = The maximum allowed nesting level.
+
+Returns:
+    true if the given range has balanced parenthesis within the given maximum
+    nesting level; false otherwise.
+*/
+bool balancedParens(Range, E)(Range r, E lPar, E rPar,
+        size_t maxNestingLevel = size_t.max)
+if (isInputRange!(Range) && is(typeof(r.front == lPar)))
+{
+    size_t count;
+    for (; !r.empty; r.popFront())
+    {
+        if (r.front == lPar)
+        {
+            if (count > maxNestingLevel) return false;
+            ++count;
+        }
+        else if (r.front == rPar)
+        {
+            if (!count) return false;
+            --count;
+        }
+    }
+    return count == 0;
+}
+
+///
+@safe unittest
+{
+    auto s = "1 + (2 * (3 + 1 / 2)";
+    assert(!balancedParens(s, '(', ')'));
+    s = "1 + (2 * (3 + 1) / 2)";
+    assert(balancedParens(s, '(', ')'));
+    s = "1 + (2 * (3 + 1) / 2)";
+    assert(!balancedParens(s, '(', ')', 0));
+    s = "1 + (2 * 3 + 1) / (2 - 5)";
+    assert(balancedParens(s, '(', ')', 0));
+}
+
+/**
+ * Sets up Boyer-Moore matching for use with $(D find) below.
+ * By default, elements are compared for equality.
+ *
+ * $(D BoyerMooreFinder) allocates GC memory.
+ *
+ * Params:
+ * pred = Predicate used to compare elements.
+ * needle = A random-access range with length and slicing.
+ *
+ * Returns:
+ * An instance of $(D BoyerMooreFinder) that can be used with $(D find()) to
+ * invoke the Boyer-Moore matching algorithm for finding of $(D needle) in a
+ * given haystack.
+ */
+BoyerMooreFinder!(binaryFun!(pred), Range) boyerMooreFinder
+(alias pred = "a == b", Range)
+(Range needle) if (isRandomAccessRange!(Range) || isSomeString!Range)
+{
+    return typeof(return)(needle);
+}
+
+/// Ditto
+struct BoyerMooreFinder(alias pred, Range)
+{
+private:
+    size_t[] skip;                              // GC allocated
+    ptrdiff_t[ElementType!(Range)] occ;         // GC allocated
+    Range needle;
+
+    ptrdiff_t occurrence(ElementType!(Range) c)
+    {
+        auto p = c in occ;
+        return p ? *p : -1;
+    }
+
+/*
+This helper function checks whether the last "portion" bytes of
+"needle" (which is "nlen" bytes long) exist within the "needle" at
+offset "offset" (counted from the end of the string), and whether the
+character preceding "offset" is not a match.  Notice that the range
+being checked may reach beyond the beginning of the string. Such range
+is ignored.
+ */
+    static bool needlematch(R)(R needle,
+                              size_t portion, size_t offset)
+    {
+        import std.algorithm.comparison : equal;
+        ptrdiff_t virtual_begin = needle.length - offset - portion;
+        ptrdiff_t ignore = 0;
+        if (virtual_begin < 0) {
+            ignore = -virtual_begin;
+            virtual_begin = 0;
+        }
+        if (virtual_begin > 0
+            && needle[virtual_begin - 1] == needle[$ - portion - 1])
+            return 0;
+
+        immutable delta = portion - ignore;
+        return equal(needle[needle.length - delta .. needle.length],
+                needle[virtual_begin .. virtual_begin + delta]);
+    }
+
+public:
+    this(Range needle)
+    {
+        if (!needle.length) return;
+        this.needle = needle;
+        /* Populate table with the analysis of the needle */
+        /* But ignoring the last letter */
+        foreach (i, n ; needle[0 .. $ - 1])
+        {
+            this.occ[n] = i;
+        }
+        /* Preprocess #2: init skip[] */
+        /* Note: This step could be made a lot faster.
+         * A simple implementation is shown here. */
+        this.skip = new size_t[needle.length];
+        foreach (a; 0 .. needle.length)
+        {
+            size_t value = 0;
+            while (value < needle.length
+                   && !needlematch(needle, a, value))
+            {
+                ++value;
+            }
+            this.skip[needle.length - a - 1] = value;
+        }
+    }
+
+    Range beFound(Range haystack)
+    {
+        import std.algorithm.comparison : max;
+
+        if (!needle.length) return haystack;
+        if (needle.length > haystack.length) return haystack[$ .. $];
+        /* Search: */
+        auto limit = haystack.length - needle.length;
+        for (size_t hpos = 0; hpos <= limit; )
+        {
+            size_t npos = needle.length - 1;
+            while (pred(needle[npos], haystack[npos+hpos]))
+            {
+                if (npos == 0) return haystack[hpos .. $];
+                --npos;
+            }
+            hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos]));
+        }
+        return haystack[$ .. $];
+    }
+
+    @property size_t length()
+    {
+        return needle.length;
+    }
+
+    alias opDollar = length;
+}
+
+/**
+Returns the common prefix of two ranges.
+
+Params:
+    pred = The predicate to use in comparing elements for commonality. Defaults
+        to equality $(D "a == b").
+
+    r1 = A $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) of
+        elements.
+
+    r2 = An $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of
+        elements.
+
+Returns:
+A slice of $(D r1) which contains the characters that both ranges start with,
+if the first argument is a string; otherwise, the same as the result of
+$(D takeExactly(r1, n)), where $(D n) is the number of elements in the common
+prefix of both ranges.
+
+See_Also:
+    $(XREF range, takeExactly)
+ */
+auto commonPrefix(alias pred = "a == b", R1, R2)(R1 r1, R2 r2)
+if (isForwardRange!R1 && isInputRange!R2 &&
+    !isNarrowString!R1 &&
+    is(typeof(binaryFun!pred(r1.front, r2.front))))
+{
+    import std.algorithm.comparison : min;
+    static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 &&
+               hasLength!R1 && hasLength!R2 &&
+               hasSlicing!R1)
+    {
+        immutable limit = min(r1.length, r2.length);
+        foreach (i; 0 .. limit)
+        {
+            if (!binaryFun!pred(r1[i], r2[i]))
+            {
+                return r1[0 .. i];
+            }
+        }
+        return r1[0 .. limit];
+    }
+    else
+    {
+        import std.range : takeExactly;
+        auto result = r1.save;
+        size_t i = 0;
+        for (;
+             !r1.empty && !r2.empty && binaryFun!pred(r1.front, r2.front);
+             ++i, r1.popFront(), r2.popFront())
+        {}
+        return takeExactly(result, i);
+    }
+}
+
+///
+@safe unittest
+{
+    assert(commonPrefix("hello, world", "hello, there") == "hello, ");
+}
+
+/// ditto
+auto commonPrefix(alias pred, R1, R2)(R1 r1, R2 r2)
+if (isNarrowString!R1 && isInputRange!R2 &&
+    is(typeof(binaryFun!pred(r1.front, r2.front))))
+{
+    import std.utf : decode;
+
+    auto result = r1.save;
+    immutable len = r1.length;
+    size_t i = 0;
+
+    for (size_t j = 0; i < len && !r2.empty; r2.popFront(), i = j)
+    {
+        immutable f = decode(r1, j);
+        if (!binaryFun!pred(f, r2.front))
+            break;
+    }
+
+    return result[0 .. i];
+}
+
+/// ditto
+auto commonPrefix(R1, R2)(R1 r1, R2 r2)
+if (isNarrowString!R1 && isInputRange!R2 && !isNarrowString!R2 &&
+    is(typeof(r1.front == r2.front)))
+{
+    return commonPrefix!"a == b"(r1, r2);
+}
+
+/// ditto
+auto commonPrefix(R1, R2)(R1 r1, R2 r2)
+if (isNarrowString!R1 && isNarrowString!R2)
+{
+    import std.algorithm.comparison : min;
+
+    static if (ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof)
+    {
+        import std.utf : stride, UTFException;
+
+        immutable limit = min(r1.length, r2.length);
+        for (size_t i = 0; i < limit;)
+        {
+            immutable codeLen = stride(r1, i);
+            size_t j = 0;
+
+            for (; j < codeLen && i < limit; ++i, ++j)
+            {
+                if (r1[i] != r2[i])
+                    return r1[0 .. i - j];
+            }
+
+            if (i == limit && j < codeLen)
+                throw new UTFException("Invalid UTF-8 sequence", i);
+        }
+        return r1[0 .. limit];
+    }
+    else
+        return commonPrefix!"a == b"(r1, r2);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.algorithm.iteration : filter;
+    import std.conv : to;
+    import std.exception : assertThrown;
+    import std.meta : AliasSeq;
+    import std.range;
+    import std.utf : UTFException;
+
+    assert(commonPrefix([1, 2, 3], [1, 2, 3, 4, 5]) == [1, 2, 3]);
+    assert(commonPrefix([1, 2, 3, 4, 5], [1, 2, 3]) == [1, 2, 3]);
+    assert(commonPrefix([1, 2, 3, 4], [1, 2, 3, 4]) == [1, 2, 3, 4]);
+    assert(commonPrefix([1, 2, 3], [7, 2, 3, 4, 5]).empty);
+    assert(commonPrefix([7, 2, 3, 4, 5], [1, 2, 3]).empty);
+    assert(commonPrefix([1, 2, 3], cast(int[])null).empty);
+    assert(commonPrefix(cast(int[])null, [1, 2, 3]).empty);
+    assert(commonPrefix(cast(int[])null, cast(int[])null).empty);
+
+    foreach (S; AliasSeq!(char[], const(char)[], string,
+                          wchar[], const(wchar)[], wstring,
+                          dchar[], const(dchar)[], dstring))
+    {
+        foreach(T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(commonPrefix(to!S(""), to!T("")).empty);
+            assert(commonPrefix(to!S(""), to!T("hello")).empty);
+            assert(commonPrefix(to!S("hello"), to!T("")).empty);
+            assert(commonPrefix(to!S("hello, world"), to!T("hello, there")) == to!S("hello, "));
+            assert(commonPrefix(to!S("hello, there"), to!T("hello, world")) == to!S("hello, "));
+            assert(commonPrefix(to!S("hello, "), to!T("hello, world")) == to!S("hello, "));
+            assert(commonPrefix(to!S("hello, world"), to!T("hello, ")) == to!S("hello, "));
+            assert(commonPrefix(to!S("hello, world"), to!T("hello, world")) == to!S("hello, world"));
+
+            //Bug# 8890
+            assert(commonPrefix(to!S("Пиво"), to!T("Пони"))== to!S("П"));
+            assert(commonPrefix(to!S("Пони"), to!T("Пиво"))== to!S("П"));
+            assert(commonPrefix(to!S("Пиво"), to!T("Пиво"))== to!S("Пиво"));
+            assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFE"),
+                                to!T("\U0010FFFF\U0010FFFB\U0010FFFC")) == to!S("\U0010FFFF\U0010FFFB"));
+            assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFC"),
+                                to!T("\U0010FFFF\U0010FFFB\U0010FFFE")) == to!S("\U0010FFFF\U0010FFFB"));
+            assert(commonPrefix!"a != b"(to!S("Пиво"), to!T("онво")) == to!S("Пи"));
+            assert(commonPrefix!"a != b"(to!S("онво"), to!T("Пиво")) == to!S("он"));
+        }();
+
+        static assert(is(typeof(commonPrefix(to!S("Пиво"), filter!"true"("Пони"))) == S));
+        assert(equal(commonPrefix(to!S("Пиво"), filter!"true"("Пони")), to!S("П")));
+
+        static assert(is(typeof(commonPrefix(filter!"true"("Пиво"), to!S("Пони"))) ==
+                      typeof(takeExactly(filter!"true"("П"), 1))));
+        assert(equal(commonPrefix(filter!"true"("Пиво"), to!S("Пони")), takeExactly(filter!"true"("П"), 1)));
+    }
+
+    assertThrown!UTFException(commonPrefix("\U0010FFFF\U0010FFFB", "\U0010FFFF\U0010FFFB"[0 .. $ - 1]));
+
+    assert(commonPrefix("12345"d, [49, 50, 51, 60, 60]) == "123"d);
+    assert(commonPrefix([49, 50, 51, 60, 60], "12345" ) == [49, 50, 51]);
+    assert(commonPrefix([49, 50, 51, 60, 60], "12345"d) == [49, 50, 51]);
+
+    assert(commonPrefix!"a == ('0' + b)"("12345" , [1, 2, 3, 9, 9]) == "123");
+    assert(commonPrefix!"a == ('0' + b)"("12345"d, [1, 2, 3, 9, 9]) == "123"d);
+    assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345" ) == [1, 2, 3]);
+    assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345"d) == [1, 2, 3]);
+}
+
+// count
+/**
+The first version counts the number of elements $(D x) in $(D r) for
+which $(D pred(x, value)) is $(D true). $(D pred) defaults to
+equality. Performs $(BIGOH haystack.length) evaluations of $(D pred).
+
+The second version returns the number of times $(D needle) occurs in
+$(D haystack). Throws an exception if $(D needle.empty), as the _count
+of the empty range in any range would be infinite. Overlapped counts
+are not considered, for example $(D count("aaa", "aa")) is $(D 1), not
+$(D 2).
+
+The third version counts the elements for which $(D pred(x)) is $(D
+true). Performs $(BIGOH haystack.length) evaluations of $(D pred).
+
+Note: Regardless of the overload, $(D count) will not accept
+infinite ranges for $(D haystack).
+
+Params:
+    pred = The predicate to evaluate.
+    haystack = The range to _count.
+    needle = The element or sub-range to _count in the `haystack`.
+
+Returns:
+    The number of positions in the `haystack` for which `pred` returned true.
+*/
+size_t count(alias pred = "a == b", Range, E)(Range haystack, E needle)
+    if (isInputRange!Range && !isInfinite!Range &&
+        is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
+{
+    bool pred2(ElementType!Range a) { return binaryFun!pred(a, needle); }
+    return count!pred2(haystack);
+}
+
+///
+@safe unittest
+{
+    import std.uni : toLower;
+
+    // count elements in range
+    int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
+    assert(count(a, 2) == 3);
+    assert(count!("a > b")(a, 2) == 5);
+    // count range in range
+    assert(count("abcadfabf", "ab") == 2);
+    assert(count("ababab", "abab") == 1);
+    assert(count("ababab", "abx") == 0);
+    // fuzzy count range in range
+    assert(count!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))("AbcAdFaBf", "ab") == 2);
+    // count predicate in range
+    assert(count!("a > 1")(a) == 8);
+}
+
+@safe unittest
+{
+    import std.conv : text;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
+    assert(count(a, 2) == 3, text(count(a, 2)));
+    assert(count!("a > b")(a, 2) == 5, text(count!("a > b")(a, 2)));
+
+    // check strings
+    assert(count("日本語")  == 3);
+    assert(count("日本語"w) == 3);
+    assert(count("日本語"d) == 3);
+
+    assert(count!("a == '日'")("日本語")  == 1);
+    assert(count!("a == '本'")("日本語"w) == 1);
+    assert(count!("a == '語'")("日本語"d) == 1);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) printf("algorithm.count.unittest\n");
+    string s = "This is a fofofof list";
+    string sub = "fof";
+    assert(count(s, sub) == 2);
+}
+
+/// Ditto
+size_t count(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+    if (isForwardRange!R1 && !isInfinite!R1 &&
+        isForwardRange!R2 &&
+        is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
+{
+    assert(!needle.empty, "Cannot count occurrences of an empty range");
+
+    static if (isInfinite!R2)
+    {
+        //Note: This is the special case of looking for an infinite inside a finite...
+        //"How many instances of the Fibonacci sequence can you count in [1, 2, 3]?" - "None."
+        return 0;
+    }
+    else
+    {
+        size_t result;
+        //Note: haystack is not saved, because findskip is designed to modify it
+        for ( ; findSkip!pred(haystack, needle.save) ; ++result)
+        {}
+        return result;
+    }
+}
+
+/// Ditto
+size_t count(alias pred = "true", R)(R haystack)
+    if (isInputRange!R && !isInfinite!R &&
+        is(typeof(unaryFun!pred(haystack.front)) : bool))
+{
+    size_t result;
+    alias T = ElementType!R; //For narrow strings forces dchar iteration
+    foreach (T elem; haystack)
+        if (unaryFun!pred(elem)) ++result;
+    return result;
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
+    assert(count!("a == 3")(a) == 2);
+    assert(count("日本語") == 3);
+}
+
+// Issue 11253
+@safe nothrow unittest
+{
+    assert([1, 2, 3].count([2, 3]) == 1);
+}
+
+/++
+    Counts elements in the given
+    $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range)
+    until the given predicate is true for one of the given $(D needles).
+
+    Params:
+        pred = The predicate for determining when to stop counting.
+        haystack = The
+            $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to be
+            counted.
+        needles = Either a single element, or a
+            $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range)
+            of elements, to be evaluated in turn against each
+            element in $(D haystack) under the given predicate.
+
+    Returns: The number of elements which must be popped from the front of
+    $(D haystack) before reaching an element for which
+    $(D startsWith!pred(haystack, needles)) is $(D true). If
+    $(D startsWith!pred(haystack, needles)) is not $(D true) for any element in
+    $(D haystack), then $(D -1) is returned.
+  +/
+ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
+    if (isForwardRange!R
+        && Rs.length > 0
+        && isForwardRange!(Rs[0]) == isInputRange!(Rs[0])
+        && is(typeof(startsWith!pred(haystack, needles[0])))
+        && (Rs.length == 1
+            || is(typeof(countUntil!pred(haystack, needles[1 .. $])))))
+{
+    typeof(return) result;
+
+    static if (needles.length == 1)
+    {
+        static if (hasLength!R) //Note: Narrow strings don't have length.
+        {
+            //We delegate to find because find is very efficient.
+            //We store the length of the haystack so we don't have to save it.
+            auto len = haystack.length;
+            auto r2 = find!pred(haystack, needles[0]);
+            if (!r2.empty)
+              return cast(typeof(return)) (len - r2.length);
+        }
+        else
+        {
+            import std.range : dropOne;
+
+            if (needles[0].empty)
+              return 0;
+
+            //Default case, slower route doing startsWith iteration
+            for ( ; !haystack.empty ; ++result )
+            {
+                //We compare the first elements of the ranges here before
+                //forwarding to startsWith. This avoids making useless saves to
+                //haystack/needle if they aren't even going to be mutated anyways.
+                //It also cuts down on the amount of pops on haystack.
+                if (binaryFun!pred(haystack.front, needles[0].front))
+                {
+                    //Here, we need to save the needle before popping it.
+                    //haystack we pop in all paths, so we do that, and then save.
+                    haystack.popFront();
+                    if (startsWith!pred(haystack.save, needles[0].save.dropOne()))
+                      return result;
+                }
+                else
+                  haystack.popFront();
+            }
+        }
+    }
+    else
+    {
+        foreach (i, Ri; Rs)
+        {
+            static if (isForwardRange!Ri)
+            {
+                if (needles[i].empty)
+                  return 0;
+            }
+        }
+        Tuple!Rs t;
+        foreach (i, Ri; Rs)
+        {
+            static if (!isForwardRange!Ri)
+            {
+                t[i] = needles[i];
+            }
+        }
+        for (; !haystack.empty ; ++result, haystack.popFront())
+        {
+            foreach (i, Ri; Rs)
+            {
+                static if (isForwardRange!Ri)
+                {
+                    t[i] = needles[i].save;
+                }
+            }
+            if (startsWith!pred(haystack.save, t.expand))
+            {
+                return result;
+            }
+        }
+    }
+
+    //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement"
+    static if (isInfinite!R) assert(0);
+    else return -1;
+}
+
+/// ditto
+ptrdiff_t countUntil(alias pred = "a == b", R, N)(R haystack, N needle)
+    if (isInputRange!R &&
+        is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
+{
+    bool pred2(ElementType!R a) { return binaryFun!pred(a, needle); }
+    return countUntil!pred2(haystack);
+}
+
+///
+@safe unittest
+{
+    assert(countUntil("hello world", "world") == 6);
+    assert(countUntil("hello world", 'r') == 8);
+    assert(countUntil("hello world", "programming") == -1);
+    assert(countUntil("日本語", "本語") == 1);
+    assert(countUntil("日本語", '語')   == 2);
+    assert(countUntil("日本語", "五") == -1);
+    assert(countUntil("日本語", '五') == -1);
+    assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2);
+    assert(countUntil([0, 7, 12, 22, 9], 9) == 4);
+    assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3);
+}
+
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+    import std.internal.test.dummyrange;
+
+    assert(countUntil("日本語", "") == 0);
+    assert(countUntil("日本語"d, "") == 0);
+
+    assert(countUntil("", "") == 0);
+    assert(countUntil("".filter!"true"(), "") == 0);
+
+    auto rf = [0, 20, 12, 22, 9].filter!"true"();
+    assert(rf.countUntil!"a > b"((int[]).init) == 0);
+    assert(rf.countUntil!"a > b"(20) == 3);
+    assert(rf.countUntil!"a > b"([20, 8]) == 3);
+    assert(rf.countUntil!"a > b"([20, 10]) == -1);
+    assert(rf.countUntil!"a > b"([20, 8, 0]) == -1);
+
+    auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]);
+    auto r2 = new ReferenceForwardRange!int([3, 4]);
+    auto r3 = new ReferenceForwardRange!int([3, 5]);
+    assert(r.save.countUntil(3)  == 3);
+    assert(r.save.countUntil(r2) == 3);
+    assert(r.save.countUntil(7)  == -1);
+    assert(r.save.countUntil(r3) == -1);
+}
+
+@safe unittest
+{
+    assert(countUntil("hello world", "world", "asd") == 6);
+    assert(countUntil("hello world", "world", "ello") == 1);
+    assert(countUntil("hello world", "world", "") == 0);
+    assert(countUntil("hello world", "world", 'l') == 2);
+}
+
+/++
+    Similar to the previous overload of $(D countUntil), except that this one
+    evaluates only the predicate $(D pred).
+
+    Params:
+        pred = Predicate to when to stop counting.
+        haystack = An
+          $(XREF_PACK_NAMED range,primitives,isInputRange,input range) of
+          elements to be counted.
+    Returns: The number of elements which must be popped from $(D haystack)
+    before $(D pred(haystack.front)) is $(D true).
+  +/
+ptrdiff_t countUntil(alias pred, R)(R haystack)
+    if (isInputRange!R &&
+        is(typeof(unaryFun!pred(haystack.front)) : bool))
+{
+    typeof(return) i;
+    static if (isRandomAccessRange!R)
+    {
+        //Optimized RA implementation. Since we want to count *and* iterate at
+        //the same time, it is more efficient this way.
+        static if (hasLength!R)
+        {
+            immutable len = cast(typeof(return)) haystack.length;
+            for ( ; i < len ; ++i )
+                if (unaryFun!pred(haystack[i])) return i;
+        }
+        else //if (isInfinite!R)
+        {
+            for ( ;  ; ++i )
+                if (unaryFun!pred(haystack[i])) return i;
+        }
+    }
+    else static if (hasLength!R)
+    {
+        //For those odd ranges that have a length, but aren't RA.
+        //It is faster to quick find, and then compare the lengths
+        auto r2 = find!pred(haystack.save);
+        if (!r2.empty) return cast(typeof(return)) (haystack.length - r2.length);
+    }
+    else //Everything else
+    {
+        alias T = ElementType!R; //For narrow strings forces dchar iteration
+        foreach (T elem; haystack)
+        {
+            if (unaryFun!pred(elem)) return i;
+            ++i;
+        }
+    }
+
+    //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement"
+    static if (isInfinite!R) assert(0);
+    else return -1;
+}
+
+///
+@safe unittest
+{
+    import std.ascii : isDigit;
+    import std.uni : isWhite;
+
+    assert(countUntil!(std.uni.isWhite)("hello world") == 5);
+    assert(countUntil!(std.ascii.isDigit)("hello world") == -1);
+    assert(countUntil!"a > 20"([0, 7, 12, 22, 9]) == 3);
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+
+    // References
+    {
+        // input
+        ReferenceInputRange!int r;
+        r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]);
+        assert(r.countUntil(3) == 3);
+        r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]);
+        assert(r.countUntil(7) == -1);
+    }
+    {
+        // forward
+        auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]);
+        assert(r.save.countUntil([3, 4]) == 3);
+        assert(r.save.countUntil(3) == 3);
+        assert(r.save.countUntil([3, 7]) == -1);
+        assert(r.save.countUntil(7) == -1);
+    }
+    {
+        // infinite forward
+        auto r = new ReferenceInfiniteForwardRange!int(0);
+        assert(r.save.countUntil([3, 4]) == 3);
+        assert(r.save.countUntil(3) == 3);
+    }
+}
+
+/**
+Checks if the given range ends with (one of) the given needle(s).
+The reciprocal of $(D startsWith).
+
+Params:
+    pred = The predicate to use for comparing elements between the range and
+        the needle(s).
+
+    doesThisEnd = The
+        $(XREF_PACK_NAMED range,primitives,isBidirectionalRange,bidirectional range)
+        to check.
+
+    withOneOfThese = The needles to check against, which may be single
+        elements, or bidirectional ranges of elements.
+
+    withThis = The single element to check.
+
+Returns:
+0 if the needle(s) do not occur at the end of the given range;
+otherwise the position of the matching needle, that is, 1 if the range ends
+with $(D withOneOfThese[0]), 2 if it ends with $(D withOneOfThese[1]), and so
+on.
+
+In the case when no needle parameters are given, return $(D true) iff back of
+$(D doesThisStart) fulfils predicate $(D pred).
+*/
+uint endsWith(alias pred = "a == b", Range, Needles...)(Range doesThisEnd, Needles withOneOfThese)
+if (isBidirectionalRange!Range && Needles.length > 1 &&
+    is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) &&
+    is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint))
+{
+    alias haystack = doesThisEnd;
+    alias needles  = withOneOfThese;
+
+    // Make one pass looking for empty ranges in needles
+    foreach (i, Unused; Needles)
+    {
+        // Empty range matches everything
+        static if (!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
+        {
+            if (needles[i].empty) return i + 1;
+        }
+    }
+
+    for (; !haystack.empty; haystack.popBack())
+    {
+        foreach (i, Unused; Needles)
+        {
+            static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
+            {
+                // Single-element
+                if (binaryFun!pred(haystack.back, needles[i]))
+                {
+                    // found, but continue to account for one-element
+                    // range matches (consider endsWith("ab", "b",
+                    // 'b') should return 1, not 2).
+                    continue;
+                }
+            }
+            else
+            {
+                if (binaryFun!pred(haystack.back, needles[i].back))
+                    continue;
+            }
+
+            // This code executed on failure to match
+            // Out with this guy, check for the others
+            uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
+            if (result > i) ++result;
+            return result;
+        }
+
+        // If execution reaches this point, then the back matches for all
+        // needles ranges. What we need to do now is to lop off the back of
+        // all ranges involved and recurse.
+        foreach (i, Unused; Needles)
+        {
+            static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
+            {
+                // Test has passed in the previous loop
+                return i + 1;
+            }
+            else
+            {
+                needles[i].popBack();
+                if (needles[i].empty) return i + 1;
+            }
+        }
+    }
+    return 0;
+}
+
+/// Ditto
+bool endsWith(alias pred = "a == b", R1, R2)(R1 doesThisEnd, R2 withThis)
+if (isBidirectionalRange!R1 &&
+    isBidirectionalRange!R2 &&
+    is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool))
+{
+    alias haystack = doesThisEnd;
+    alias needle   = withThis;
+
+    static if (is(typeof(pred) : string))
+        enum isDefaultPred = pred == "a == b";
+    else
+        enum isDefaultPred = false;
+
+    static if (isDefaultPred && isArray!R1 && isArray!R2 &&
+               is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2)))
+    {
+        if (haystack.length < needle.length) return false;
+
+        return haystack[$ - needle.length .. $] == needle;
+    }
+    else
+    {
+        import std.range : retro;
+        return startsWith!pred(retro(doesThisEnd), retro(withThis));
+    }
+}
+
+/// Ditto
+bool endsWith(alias pred = "a == b", R, E)(R doesThisEnd, E withThis)
+if (isBidirectionalRange!R &&
+    is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool))
+{
+    return doesThisEnd.empty
+        ? false
+        : binaryFun!pred(doesThisEnd.back, withThis);
+}
+
+/// Ditto
+bool endsWith(alias pred, R)(R doesThisEnd)
+    if (isInputRange!R &&
+        ifTestable!(typeof(doesThisEnd.front), unaryFun!pred))
+{
+    return !doesThisEnd.empty && unaryFun!pred(doesThisEnd.back);
+}
+
+///
+@safe unittest
+{
+    import std.ascii : isAlpha;
+    assert("abc".endsWith!(a => a.isAlpha));
+    assert("abc".endsWith!isAlpha);
+
+    assert(!"ab1".endsWith!(a => a.isAlpha));
+
+    assert(!"ab1".endsWith!isAlpha);
+    assert(!"".endsWith!(a => a.isAlpha));
+
+    import std.algorithm.comparison : among;
+    assert("abc".endsWith!(a => a.among('c', 'd') != 0));
+    assert(!"abc".endsWith!(a => a.among('a', 'b') != 0));
+
+    assert(endsWith("abc", ""));
+    assert(!endsWith("abc", "b"));
+    assert(endsWith("abc", "a", 'c') == 2);
+    assert(endsWith("abc", "c", "a") == 1);
+    assert(endsWith("abc", "c", "c") == 1);
+    assert(endsWith("abc", "bc", "c") == 2);
+    assert(endsWith("abc", "x", "c", "b") == 2);
+    assert(endsWith("abc", "x", "aa", "bc") == 3);
+    assert(endsWith("abc", "x", "aaa", "sab") == 0);
+    assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3);
+}
+
+@safe unittest
+{
+    import std.algorithm.iteration : filterBidirectional;
+    import std.meta : AliasSeq;
+    import std.conv : to;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        assert(!endsWith(to!S("abc"), 'a'));
+        assert(endsWith(to!S("abc"), 'a', 'c') == 2);
+        assert(!endsWith(to!S("abc"), 'x', 'n', 'b'));
+        assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3);
+        assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2);
+
+        foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            //Lots of strings
+            assert(endsWith(to!S("abc"), to!T("")));
+            assert(!endsWith(to!S("abc"), to!T("a")));
+            assert(!endsWith(to!S("abc"), to!T("b")));
+            assert(endsWith(to!S("abc"), to!T("bc"), 'c') == 2);
+            assert(endsWith(to!S("abc"), to!T("a"), "c") == 2);
+            assert(endsWith(to!S("abc"), to!T("c"), "a") == 1);
+            assert(endsWith(to!S("abc"), to!T("c"), "c") == 1);
+            assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2);
+            assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3);
+            assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
+            assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3);
+            assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")));
+            assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2);
+
+            //Unicode
+            assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")));
+            assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2);
+            assert(endsWith(to!S("日本語"), to!T("本語")));
+            assert(endsWith(to!S("日本語"), to!T("日本語")));
+            assert(!endsWith(to!S("本語"), to!T("日本語")));
+
+            //Empty
+            assert(endsWith(to!S(""),  T.init));
+            assert(!endsWith(to!S(""), 'a'));
+            assert(endsWith(to!S("a"), T.init));
+            assert(endsWith(to!S("a"), T.init, "") == 1);
+            assert(endsWith(to!S("a"), T.init, 'a') == 1);
+            assert(endsWith(to!S("a"), 'a', T.init) == 2);
+        }();
+    }
+
+    foreach (T; AliasSeq!(int, short))
+    {
+        immutable arr = cast(T[])[0, 1, 2, 3, 4, 5];
+
+        //RA range
+        assert(endsWith(arr, cast(int[])null));
+        assert(!endsWith(arr, 0));
+        assert(!endsWith(arr, 4));
+        assert(endsWith(arr, 5));
+        assert(endsWith(arr, 0, 4, 5) == 3);
+        assert(endsWith(arr, [5]));
+        assert(endsWith(arr, [4, 5]));
+        assert(endsWith(arr, [4, 5], 7) == 1);
+        assert(!endsWith(arr, [2, 4, 5]));
+        assert(endsWith(arr, [2, 4, 5], [3, 4, 5]) == 2);
+
+        //Normal input range
+        assert(!endsWith(filterBidirectional!"true"(arr), 4));
+        assert(endsWith(filterBidirectional!"true"(arr), 5));
+        assert(endsWith(filterBidirectional!"true"(arr), [5]));
+        assert(endsWith(filterBidirectional!"true"(arr), [4, 5]));
+        assert(endsWith(filterBidirectional!"true"(arr), [4, 5], 7) == 1);
+        assert(!endsWith(filterBidirectional!"true"(arr), [2, 4, 5]));
+        assert(endsWith(filterBidirectional!"true"(arr), [2, 4, 5], [3, 4, 5]) == 2);
+        assert(endsWith(arr, filterBidirectional!"true"([4, 5])));
+        assert(endsWith(arr, filterBidirectional!"true"([4, 5]), 7) == 1);
+        assert(!endsWith(arr, filterBidirectional!"true"([2, 4, 5])));
+        assert(endsWith(arr, [2, 4, 5], filterBidirectional!"true"([3, 4, 5])) == 2);
+
+        //Non-default pred
+        assert(endsWith!("a%10 == b%10")(arr, [14, 15]));
+        assert(!endsWith!("a%10 == b%10")(arr, [15, 14]));
+    }
+}
+
+// find
+/**
+Finds an individual element in an input range. Elements of $(D
+haystack) are compared with $(D needle) by using predicate $(D
+pred). Performs $(BIGOH walkLength(haystack)) evaluations of $(D
+pred).
+
+To _find the last occurrence of $(D needle) in $(D haystack), call $(D
+find(retro(haystack), needle)). See $(XREF range, retro).
+
+Params:
+
+pred = The predicate for comparing each element with the needle, defaulting to
+$(D "a == b").
+The negated predicate $(D "a != b") can be used to search instead for the first
+element $(I not) matching the needle.
+
+haystack = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range)
+searched in.
+
+needle = The element searched for.
+
+Constraints:
+
+$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle)
+: bool)))
+
+Returns:
+
+$(D haystack) advanced such that the front element is the one searched for;
+that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no
+such position exists, returns an empty $(D haystack).
+
+See_Also:
+     $(WEB sgi.com/tech/stl/_find.html, STL's _find)
+ */
+InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, Element needle)
+if (isInputRange!InputRange &&
+    is (typeof(binaryFun!pred(haystack.front, needle)) : bool))
+{
+    alias R = InputRange;
+    alias E = Element;
+    alias predFun = binaryFun!pred;
+    static if (is(typeof(pred == "a == b")))
+        enum isDefaultPred = pred == "a == b";
+    else
+        enum isDefaultPred = false;
+    enum  isIntegralNeedle = isSomeChar!E || isIntegral!E || isBoolean!E;
+
+    alias EType  = ElementType!R;
+
+    static if (isNarrowString!R)
+    {
+        alias EEType = ElementEncodingType!R;
+        alias UEEType = Unqual!EEType;
+
+        //These are two special cases which can search without decoding the UTF stream.
+        static if (isDefaultPred && isIntegralNeedle)
+        {
+            import std.utf : canSearchInCodeUnits;
+
+            //This special case deals with UTF8 search, when the needle
+            //is represented by a single code point.
+            //Note: "needle <= 0x7F" properly handles sign via unsigned promotion
+            static if (is(UEEType == char))
+            {
+                if (!__ctfe && canSearchInCodeUnits!char(needle))
+                {
+                    static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure
+                    {
+                        import core.stdc.string : memchr;
+                        auto ptr = memchr(haystack.ptr, needle, haystack.length);
+                        return ptr ?
+                             haystack[ptr - haystack.ptr .. $] :
+                             haystack[$ .. $];
+                    }
+                    return trustedMemchr(haystack, needle);
+                }
+            }
+
+            //Ditto, but for UTF16
+            static if (is(UEEType == wchar))
+            {
+                if (canSearchInCodeUnits!wchar(needle))
+                {
+                    foreach (i, ref EEType e; haystack)
+                    {
+                        if (e == needle)
+                            return haystack[i .. $];
+                    }
+                    return haystack[$ .. $];
+                }
+            }
+        }
+
+        //Previous conditonal optimizations did not succeed. Fallback to
+        //unconditional implementations
+        static if (isDefaultPred)
+        {
+            import std.utf : encode;
+
+            //In case of default pred, it is faster to do string/string search.
+            UEEType[is(UEEType == char) ? 4 : 2] buf;
+
+            size_t len = encode(buf, needle);
+            return find(haystack, buf[0 .. len]);
+        }
+        else
+        {
+            import std.utf : decode;
+
+            //Explicit pred: we must test each character by the book.
+            //We choose a manual decoding approach, because it is faster than
+            //the built-in foreach, or doing a front/popFront for-loop.
+            immutable len = haystack.length;
+            size_t i = 0, next = 0;
+            while (next < len)
+            {
+                if (predFun(decode(haystack, next), needle))
+                    return haystack[i .. $];
+                i = next;
+            }
+            return haystack[$ .. $];
+        }
+    }
+    else static if (isArray!R)
+    {
+        //10403 optimization
+        static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle)
+        {
+            import std.algorithm.comparison : max, min;
+
+            R findHelper(ref R haystack, ref E needle) @trusted nothrow pure
+            {
+                import core.stdc.string : memchr;
+
+                EType* ptr = null;
+                //Note: we use "min/max" to handle sign mismatch.
+                if (min(EType.min, needle) == EType.min &&
+                    max(EType.max, needle) == EType.max)
+                {
+                    ptr = cast(EType*) memchr(haystack.ptr, needle,
+                        haystack.length);
+                }
+
+                return ptr ?
+                    haystack[ptr - haystack.ptr .. $] :
+                    haystack[$ .. $];
+            }
+
+            if (!__ctfe)
+                return findHelper(haystack, needle);
+        }
+
+        //Default implementation.
+        foreach (i, ref e; haystack)
+            if (predFun(e, needle))
+                return haystack[i .. $];
+        return haystack[$ .. $];
+    }
+    else
+    {
+        //Everything else. Walk.
+        for ( ; !haystack.empty; haystack.popFront() )
+        {
+            if (predFun(haystack.front, needle))
+                break;
+        }
+        return haystack;
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.container : SList;
+
+    assert(find("hello, world", ',') == ", world");
+    assert(find([1, 2, 3, 5], 4) == []);
+    assert(equal(find(SList!int(1, 2, 3, 4, 5)[], 4), SList!int(4, 5)[]));
+    assert(find!"a > b"([1, 2, 3, 5], 2) == [3, 5]);
+
+    auto a = [ 1, 2, 3 ];
+    assert(find(a, 5).empty);       // not found
+    assert(!find(a, 2).empty);      // found
+
+    // Case-insensitive find of a string
+    string[] s = [ "Hello", "world", "!" ];
+    assert(!find!("toLower(a) == b")(s, "hello").empty);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.container : SList;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto lst = SList!int(1, 2, 5, 7, 3);
+    assert(lst.front == 1);
+    auto r = find(lst[], 5);
+    assert(equal(r, SList!int(5, 7, 3)[]));
+    assert(find([1, 2, 3, 5], 4).empty);
+    assert(equal(find!"a > b"("hello", 'k'), "llo"));
+}
+
+@safe pure nothrow unittest
+{
+    int[] a1 = [1, 2, 3];
+    assert(!find              ([1, 2, 3], 2).empty);
+    assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty);
+    ubyte[] a2 = [1, 2, 3];
+    ubyte   b2 = 2;
+    assert(!find              ([1, 2, 3], 2).empty);
+    assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty);
+}
+
+@safe pure unittest
+{
+    import std.meta : AliasSeq;
+    foreach(R; AliasSeq!(string, wstring, dstring))
+    {
+        foreach(E; AliasSeq!(char, wchar, dchar))
+        {
+            R r1 = "hello world";
+            E e1 = 'w';
+            assert(find              ("hello world", 'w') == "world");
+            assert(find!((a,b)=>a==b)("hello world", 'w') == "world");
+            R r2 = "日c語";
+            E e2 = 'c';
+            assert(find              ("日c語", 'c') == "c語");
+            assert(find!((a,b)=>a==b)("日c語", 'c') == "c語");
+            static if (E.sizeof >= 2)
+            {
+                R r3 = "hello world";
+                E e3 = 'w';
+                assert(find              ("日本語", '本') == "本語");
+                assert(find!((a,b)=>a==b)("日本語", '本') == "本語");
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    //CTFE
+    static assert (find("abc", 'b') == "bc");
+    static assert (find("日b語", 'b') == "b語");
+    static assert (find("日本語", '本') == "本語");
+    static assert (find([1, 2, 3], 2)  == [2, 3]);
+
+    int[] a1 = [1, 2, 3];
+    static assert(find              ([1, 2, 3], 2));
+    static assert(find!((a,b)=>a==b)([1, 2, 3], 2));
+    ubyte[] a2 = [1, 2, 3];
+    ubyte   b2 = 2;
+    static assert(find              ([1, 2, 3], 2));
+    static assert(find!((a,b)=>a==b)([1, 2, 3], 2));
+}
+
+@safe unittest
+{
+    import std.exception : assertCTFEable;
+    import std.meta : AliasSeq;
+
+    void dg() @safe pure nothrow
+    {
+        byte[]  sarr = [1, 2, 3, 4];
+        ubyte[] uarr = [1, 2, 3, 4];
+        foreach(arr; AliasSeq!(sarr, uarr))
+        {
+            foreach(T; AliasSeq!(byte, ubyte, int, uint))
+            {
+                assert(find(arr, cast(T) 3) == arr[2 .. $]);
+                assert(find(arr, cast(T) 9) == arr[$ .. $]);
+            }
+            assert(find(arr, 256) == arr[$ .. $]);
+        }
+    }
+    dg();
+    assertCTFEable!dg;
+}
+
+@safe unittest
+{
+    // Bugzilla 11603
+    enum Foo : ubyte { A }
+    assert([Foo.A].find(Foo.A).empty == false);
+
+    ubyte x = 0;
+    assert([x].find(x).empty == false);
+}
+
+/**
+Advances the input range $(D haystack) by calling $(D haystack.popFront)
+until either $(D pred(haystack.front)), or $(D
+haystack.empty). Performs $(BIGOH haystack.length) evaluations of $(D
+pred).
+
+To _find the last element of a bidirectional $(D haystack) satisfying
+$(D pred), call $(D find!(pred)(retro(haystack))). See $(XREF
+range, retro).
+
+Params:
+
+pred = The predicate for determining if a given element is the one being
+searched for.
+
+haystack = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to
+search in.
+
+Returns:
+
+$(D haystack) advanced such that the front element is the one searched for;
+that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no
+such position exists, returns an empty $(D haystack).
+
+See_Also:
+     $(WEB sgi.com/tech/stl/find_if.html, STL's find_if)
+*/
+InputRange find(alias pred, InputRange)(InputRange haystack)
+if (isInputRange!InputRange)
+{
+    alias R = InputRange;
+    alias predFun = unaryFun!pred;
+    static if (isNarrowString!R)
+    {
+        import std.utf : decode;
+
+        immutable len = haystack.length;
+        size_t i = 0, next = 0;
+        while (next < len)
+        {
+            if (predFun(decode(haystack, next)))
+                return haystack[i .. $];
+            i = next;
+        }
+        return haystack[$ .. $];
+    }
+    else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $])))
+    {
+        size_t i = 0;
+        for (auto h = haystack.save; !h.empty; h.popFront())
+        {
+            if (predFun(h.front))
+                return haystack[i .. $];
+            ++i;
+        }
+        return haystack[$ .. $];
+    }
+    else
+    {
+        //standard range
+        for ( ; !haystack.empty; haystack.popFront() )
+        {
+            if (predFun(haystack.front))
+                break;
+        }
+        return haystack;
+    }
+}
+
+///
+@safe unittest
+{
+    auto arr = [ 1, 2, 3, 4, 1 ];
+    assert(find!("a > 2")(arr) == [ 3, 4, 1 ]);
+
+    // with predicate alias
+    bool pred(int x) { return x + 1 > 1.5; }
+    assert(find!(pred)(arr) == arr);
+}
+
+@safe pure unittest
+{
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] r = [ 1, 2, 3 ];
+    assert(find!(a=>a > 2)(r) == [3]);
+    bool pred(int x) { return x + 1 > 1.5; }
+    assert(find!(pred)(r) == r);
+
+    assert(find!(a=>a > 'v')("hello world") == "world");
+    assert(find!(a=>a%4 == 0)("日本語") == "本語");
+}
+
+/**
+Finds the first occurrence of a forward range in another forward range.
+
+Performs $(BIGOH walkLength(haystack) * walkLength(needle)) comparisons in the
+worst case.  There are specializations that improve performance by taking
+advantage of bidirectional or random access in the given ranges (where
+possible), depending on the statistics of the two ranges' content.
+
+Params:
+
+pred = The predicate to use for comparing respective elements from the haystack
+and the needle. Defaults to simple equality $(D "a == b").
+
+haystack = The $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range)
+searched in.
+
+needle = The $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range)
+searched for.
+
+Returns:
+
+$(D haystack) advanced such that $(D needle) is a prefix of it (if no
+such position exists, returns $(D haystack) advanced to termination).
+ */
+R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isForwardRange!R1 && isForwardRange!R2
+        && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
+        && !isRandomAccessRange!R1)
+{
+    static if (is(typeof(pred == "a == b")) && pred == "a == b" && isSomeString!R1 && isSomeString!R2
+            && haystack[0].sizeof == needle[0].sizeof)
+    {
+        //return cast(R1) find(representation(haystack), representation(needle));
+        // Specialization for simple string search
+        alias Representation =
+            Select!(haystack[0].sizeof == 1, ubyte[],
+                Select!(haystack[0].sizeof == 2, ushort[], uint[]));
+        // Will use the array specialization
+        static TO force(TO, T)(T r) @trusted { return cast(TO)r; }
+        return force!R1(.find!(pred, Representation, Representation)
+            (force!Representation(haystack), force!Representation(needle)));
+    }
+    else
+    {
+        return simpleMindedFind!pred(haystack, needle);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.container : SList;
+
+    assert(find("hello, world", "World").empty);
+    assert(find("hello, world", "wo") == "world");
+    assert([1, 2, 3, 4].find(SList!int(2, 3)[]) == [2, 3, 4]);
+    alias C = Tuple!(int, "x", int, "y");
+    auto a = [C(1,0), C(2,0), C(3,1), C(4,0)];
+    assert(a.find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]);
+    assert(a[1 .. $].find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]);
+}
+
+@safe unittest
+{
+    import std.container : SList;
+    alias C = Tuple!(int, "x", int, "y");
+    assert([C(1,0), C(2,0), C(3,1), C(4,0)].find!"a.x == b"(SList!int(2, 3)[]) == [C(2,0), C(3,1), C(4,0)]);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.container : SList;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto lst = SList!int(1, 2, 5, 7, 3);
+    static assert(isForwardRange!(int[]));
+    static assert(isForwardRange!(typeof(lst[])));
+    auto r = find(lst[], [2, 5]);
+    assert(equal(r, SList!int(2, 5, 7, 3)[]));
+}
+
+// Specialization for searching a random-access range for a
+// bidirectional range
+R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isRandomAccessRange!R1 && isBidirectionalRange!R2
+        && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
+{
+    if (needle.empty) return haystack;
+    const needleLength = walkLength(needle.save);
+    if (needleLength > haystack.length)
+    {
+        // @@@BUG@@@
+        //return haystack[$ .. $];
+        return haystack[haystack.length .. haystack.length];
+    }
+    // @@@BUG@@@
+    // auto needleBack = moveBack(needle);
+    // Stage 1: find the step
+    size_t step = 1;
+    auto needleBack = needle.back;
+    needle.popBack();
+    for (auto i = needle.save; !i.empty && i.back != needleBack;
+         i.popBack(), ++step)
+    {
+    }
+    // Stage 2: linear find
+    size_t scout = needleLength - 1;
+    for (;;)
+    {
+        if (scout >= haystack.length)
+        {
+            return haystack[haystack.length .. haystack.length];
+        }
+        if (!binaryFun!pred(haystack[scout], needleBack))
+        {
+            ++scout;
+            continue;
+        }
+        // Found a match with the last element in the needle
+        auto cand = haystack[scout + 1 - needleLength .. haystack.length];
+        if (startsWith!pred(cand, needle))
+        {
+            // found
+            return cand;
+        }
+        // Continue with the stride
+        scout += step;
+    }
+}
+
+@safe unittest
+{
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    // @@@BUG@@@ removing static below makes unittest fail
+    static struct BiRange
+    {
+        int[] payload;
+        @property bool empty() { return payload.empty; }
+        @property BiRange save() { return this; }
+        @property ref int front() { return payload[0]; }
+        @property ref int back() { return payload[$ - 1]; }
+        void popFront() { return payload.popFront(); }
+        void popBack() { return payload.popBack(); }
+    }
+    //static assert(isBidirectionalRange!BiRange);
+    auto r = BiRange([1, 2, 3, 10, 11, 4]);
+    //assert(equal(find(r, [3, 10]), BiRange([3, 10, 11, 4])));
+    //assert(find("abc", "bc").length == 2);
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    //assert(find!"a == b"("abc", "bc").length == 2);
+}
+
+// Leftover specialization: searching a random-access range for a
+// non-bidirectional forward range
+R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 &&
+    is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
+{
+    static if (!is(ElementType!R1 == ElementType!R2))
+    {
+        return simpleMindedFind!pred(haystack, needle);
+    }
+    else
+    {
+        // Prepare the search with needle's first element
+        if (needle.empty)
+            return haystack;
+
+        haystack = .find!pred(haystack, needle.front);
+
+        static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == R1))
+        {
+            if (needle.length > haystack.length)
+                return takeNone(haystack);
+        }
+        else
+        {
+            if (haystack.empty)
+                return haystack;
+        }
+
+        needle.popFront();
+        size_t matchLen = 1;
+
+        // Loop invariant: haystack[0 .. matchLen] matches everything in
+        // the initial needle that was popped out of needle.
+        for (;;)
+        {
+            // Extend matchLength as much as possible
+            for (;;)
+            {
+                import std.range : takeNone;
+
+                if (needle.empty || haystack.empty)
+                    return haystack;
+
+                static if (hasLength!R1 && is(typeof(takeNone(haystack)) == R1))
+                {
+                    if (matchLen == haystack.length)
+                        return takeNone(haystack);
+                }
+
+                if (!binaryFun!pred(haystack[matchLen], needle.front))
+                    break;
+
+                ++matchLen;
+                needle.popFront();
+            }
+
+            auto bestMatch = haystack[0 .. matchLen];
+            haystack.popFront();
+            haystack = .find!pred(haystack, bestMatch);
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.container : SList;
+
+    assert(find([ 1, 2, 3 ], SList!int(2, 3)[]) == [ 2, 3 ]);
+    assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]);
+}
+
+//Bug# 8334
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+    import std.range;
+
+    auto haystack = [1, 2, 3, 4, 1, 9, 12, 42];
+    auto needle = [12, 42, 27];
+
+    //different overload of find, but it's the base case.
+    assert(find(haystack, needle).empty);
+
+    assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty);
+    assert(find(haystack, filter!"true"(needle)).empty);
+}
+
+// Internally used by some find() overloads above. Can't make it
+// private due to bugs in the compiler.
+/*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle)
+{
+    enum estimateNeedleLength = hasLength!R1 && !hasLength!R2;
+
+    static if (hasLength!R1)
+    {
+        static if (!hasLength!R2)
+            size_t estimatedNeedleLength = 0;
+        else
+            immutable size_t estimatedNeedleLength = needle.length;
+    }
+
+    bool haystackTooShort()
+    {
+        static if (estimateNeedleLength)
+        {
+            return haystack.length < estimatedNeedleLength;
+        }
+        else
+        {
+            return haystack.empty;
+        }
+    }
+
+  searching:
+    for (;; haystack.popFront())
+    {
+        if (haystackTooShort())
+        {
+            // Failed search
+            static if (hasLength!R1)
+            {
+                static if (is(typeof(haystack[haystack.length ..
+                                                haystack.length]) : R1))
+                    return haystack[haystack.length .. haystack.length];
+                else
+                    return R1.init;
+            }
+            else
+            {
+                assert(haystack.empty);
+                return haystack;
+            }
+        }
+        static if (estimateNeedleLength)
+            size_t matchLength = 0;
+        for (auto h = haystack.save, n = needle.save;
+             !n.empty;
+             h.popFront(), n.popFront())
+        {
+            if (h.empty || !binaryFun!pred(h.front, n.front))
+            {
+                // Failed searching n in h
+                static if (estimateNeedleLength)
+                {
+                    if (estimatedNeedleLength < matchLength)
+                        estimatedNeedleLength = matchLength;
+                }
+                continue searching;
+            }
+            static if (estimateNeedleLength)
+                ++matchLength;
+        }
+        break;
+    }
+    return haystack;
+}
+
+@safe unittest
+{
+    // Test simpleMindedFind for the case where both haystack and needle have
+    // length.
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    struct CustomString
+    {
+    @safe:
+        string _impl;
+
+        // This is what triggers issue 7992.
+        @property size_t length() const { return _impl.length; }
+        @property void length(size_t len) { _impl.length = len; }
+
+        // This is for conformance to the forward range API (we deliberately
+        // make it non-random access so that we will end up in
+        // simpleMindedFind).
+        @property bool empty() const { return _impl.empty; }
+        @property dchar front() const { return _impl.front; }
+        void popFront() { _impl.popFront(); }
+        @property CustomString save() { return this; }
+    }
+
+    // If issue 7992 occurs, this will throw an exception from calling
+    // popFront() on an empty range.
+    auto r = find(CustomString("a"), CustomString("b"));
+}
+
+/**
+Finds two or more $(D needles) into a $(D haystack). The predicate $(D
+pred) is used throughout to compare elements. By default, elements are
+compared for equality.
+
+Params:
+
+pred = The predicate to use for comparing elements.
+
+haystack = The target of the search. Must be an input range.
+If any of $(D needles) is a range with elements comparable to
+elements in $(D haystack), then $(D haystack) must be a forward range
+such that the search can backtrack.
+
+needles = One or more items to search for. Each of $(D needles) must
+be either comparable to one element in $(D haystack), or be itself a
+forward range with elements comparable with elements in
+$(D haystack).
+
+Returns:
+
+A tuple containing $(D haystack) positioned to match one of the
+needles and also the 1-based index of the matching element in $(D
+needles) (0 if none of $(D needles) matched, 1 if $(D needles[0])
+matched, 2 if $(D needles[1]) matched...). The first needle to be found
+will be the one that matches. If multiple needles are found at the
+same spot in the range, then the shortest one is the one which matches
+(if multiple needles of the same length are found at the same spot (e.g
+$(D "a") and $(D 'a')), then the left-most of them in the argument list
+matches).
+
+The relationship between $(D haystack) and $(D needles) simply means
+that one can e.g. search for individual $(D int)s or arrays of $(D
+int)s in an array of $(D int)s. In addition, if elements are
+individually comparable, searches of heterogeneous types are allowed
+as well: a $(D double[]) can be searched for an $(D int) or a $(D
+short[]), and conversely a $(D long) can be searched for a $(D float)
+or a $(D double[]). This makes for efficient searches without the need
+to coerce one side of the comparison into the other's side type.
+
+The complexity of the search is $(BIGOH haystack.length *
+max(needles.length)). (For needles that are individual items, length
+is considered to be 1.) The strategy used in searching several
+subranges at once maximizes cache usage by moving in $(D haystack) as
+few times as possible.
+ */
+Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...)
+(Range haystack, Ranges needles)
+if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles))))
+{
+    for (;; haystack.popFront())
+    {
+        size_t r = startsWith!pred(haystack, needles);
+        if (r || haystack.empty)
+        {
+            return tuple(haystack, r);
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 4, 2, 3 ];
+    assert(find(a, 4) == [ 4, 2, 3 ]);
+    assert(find(a, [ 1, 4 ]) == [ 1, 4, 2, 3 ]);
+    assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2));
+    // Mixed types allowed if comparable
+    assert(find(a, 5, [ 1.2, 3.5 ], 2.0) == tuple([ 2, 3 ], 3));
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto s1 = "Mary has a little lamb";
+    //writeln(find(s1, "has a", "has an"));
+    assert(find(s1, "has a", "has an") == tuple("has a little lamb", 1));
+    assert(find(s1, 't', "has a", "has an") == tuple("has a little lamb", 2));
+    assert(find(s1, 't', "has a", 'y', "has an") == tuple("y has a little lamb", 3));
+    assert(find("abc", "bc").length == 2);
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : rndstuff;
+    import std.meta : AliasSeq;
+    import std.uni : toUpper;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 3 ];
+    assert(find(a, 5).empty);
+    assert(find(a, 2) == [2, 3]);
+
+    foreach (T; AliasSeq!(int, double))
+    {
+        auto b = rndstuff!(T)();
+        if (!b.length) continue;
+        b[$ / 2] = 200;
+        b[$ / 4] = 200;
+        assert(find(b, 200).length == b.length - b.length / 4);
+    }
+
+    // Case-insensitive find of a string
+    string[] s = [ "Hello", "world", "!" ];
+    //writeln(find!("toUpper(a) == toUpper(b)")(s, "hello"));
+    assert(find!("toUpper(a) == toUpper(b)")(s, "hello").length == 3);
+
+    static bool f(string a, string b) { return toUpper(a) == toUpper(b); }
+    assert(find!(f)(s, "hello").length == 3);
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : rndstuff;
+    import std.algorithm.comparison : equal;
+    import std.meta : AliasSeq;
+    import std.range : retro;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 1, 2, 3, 2, 6 ];
+    assert(find(retro(a), 5).empty);
+    assert(equal(find(retro(a), 2), [ 2, 3, 2, 1 ][]));
+
+    foreach (T; AliasSeq!(int, double))
+    {
+        auto b = rndstuff!(T)();
+        if (!b.length) continue;
+        b[$ / 2] = 200;
+        b[$ / 4] = 200;
+        assert(find(retro(b), 200).length ==
+                b.length - (b.length - 1) / 2);
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
+    int[] b = [ 1, 2, 3 ];
+    assert(find(a, b) == [ 1, 2, 3, 4, 5 ]);
+    assert(find(b, a).empty);
+
+    foreach (DummyType; AllDummyRanges) {
+        DummyType d;
+        auto findRes = find(d, 5);
+        assert(equal(findRes, [5,6,7,8,9,10]));
+    }
+}
+
+/**
+ * Finds $(D needle) in $(D haystack) efficiently using the
+ * $(LUCKY Boyer-Moore) method.
+ *
+ * Params:
+ * haystack = A random-access range with length and slicing.
+ * needle = A $(LREF BoyerMooreFinder).
+ *
+ * Returns:
+ * $(D haystack) advanced such that $(D needle) is a prefix of it (if no
+ * such position exists, returns $(D haystack) advanced to termination).
+ */
+Range1 find(Range1, alias pred, Range2)(
+    Range1 haystack, BoyerMooreFinder!(pred, Range2) needle)
+{
+    return needle.beFound(haystack);
+}
+
+@safe unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    string h = "/homes/aalexand/d/dmd/bin/../lib/libphobos.a(dmain2.o)"~
+        "(.gnu.linkonce.tmain+0x74): In function `main' undefined reference"~
+        " to `_Dmain':";
+    string[] ns = ["libphobos", "function", " undefined", "`", ":"];
+    foreach (n ; ns) {
+        auto p = find(h, boyerMooreFinder(n));
+        assert(!p.empty);
+    }
+}
+
+///
+@safe unittest
+{
+    int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
+    int[] b = [ 1, 2, 3 ];
+
+    assert(find(a, boyerMooreFinder(b)) == [ 1, 2, 3, 4, 5 ]);
+    assert(find(b, boyerMooreFinder(a)).empty);
+}
+
+@safe unittest
+{
+    auto bm = boyerMooreFinder("for");
+    auto match = find("Moor", bm);
+    assert(match.empty);
+}
+
+// canFind
+/++
+Convenience function. Like find, but only returns whether or not the search
+was successful.
+
+See_Also:
+$(LREF among) for checking a value against multiple possibilities.
+ +/
+template canFind(alias pred="a == b")
+{
+    import std.meta : allSatisfy;
+
+    /++
+    Returns $(D true) if and only if any value $(D v) found in the
+    input range $(D range) satisfies the predicate $(D pred).
+    Performs (at most) $(BIGOH haystack.length) evaluations of $(D pred).
+     +/
+    bool canFind(Range)(Range haystack)
+    if (is(typeof(find!pred(haystack))))
+    {
+        return any!pred(haystack);
+    }
+
+    /++
+    Returns $(D true) if and only if $(D needle) can be found in $(D
+    range). Performs $(BIGOH haystack.length) evaluations of $(D pred).
+     +/
+    bool canFind(Range, Element)(Range haystack, Element needle)
+    if (is(typeof(find!pred(haystack, needle))))
+    {
+        return !find!pred(haystack, needle).empty;
+    }
+
+    /++
+    Returns the 1-based index of the first needle found in $(D haystack). If no
+    needle is found, then $(D 0) is returned.
+
+    So, if used directly in the condition of an if statement or loop, the result
+    will be $(D true) if one of the needles is found and $(D false) if none are
+    found, whereas if the result is used elsewhere, it can either be cast to
+    $(D bool) for the same effect or used to get which needle was found first
+    without having to deal with the tuple that $(D LREF find) returns for the
+    same operation.
+     +/
+    size_t canFind(Range, Ranges...)(Range haystack, Ranges needles)
+    if (Ranges.length > 1 &&
+        allSatisfy!(isForwardRange, Ranges) &&
+        is(typeof(find!pred(haystack, needles))))
+    {
+        return find!pred(haystack, needles)[1];
+    }
+}
+
+///
+@safe unittest
+{
+    assert(canFind([0, 1, 2, 3], 2) == true);
+    assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]));
+    assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]) == 1);
+    assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]));
+    assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]) == 2);
+
+    assert(canFind([0, 1, 2, 3], 4) == false);
+    assert(!canFind([0, 1, 2, 3], [1, 3], [2, 4]));
+    assert(canFind([0, 1, 2, 3], [1, 3], [2, 4]) == 0);
+}
+
+/**
+ * Example using a custom predicate.
+ * Note that the needle appears as the second argument of the predicate.
+ */
+@safe unittest
+{
+    auto words = [
+        "apple",
+        "beeswax",
+        "cardboard"
+    ];
+    assert(!canFind(words, "bees"));
+    assert( canFind!((string a, string b) => a.startsWith(b))(words, "bees"));
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : rndstuff;
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto a = rndstuff!(int)();
+    if (a.length)
+    {
+        auto b = a[a.length / 2];
+        assert(canFind(a, b));
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    assert(equal!(canFind!"a < b")([[1, 2, 3], [7, 8, 9]], [2, 8]));
+}
+
+// findAdjacent
+/**
+Advances $(D r) until it finds the first two adjacent elements $(D a),
+$(D b) that satisfy $(D pred(a, b)). Performs $(BIGOH r.length)
+evaluations of $(D pred).
+
+Params:
+    pred = The predicate to satisfy.
+    r = A $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) to
+        search in.
+
+Returns:
+$(D r) advanced to the first occurrence of two adjacent elements that satisfy
+the given predicate. If there are no such two elements, returns $(D r) advanced
+until empty.
+
+See_Also:
+     $(WEB sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find)
+*/
+Range findAdjacent(alias pred = "a == b", Range)(Range r)
+    if (isForwardRange!(Range))
+{
+    auto ahead = r.save;
+    if (!ahead.empty)
+    {
+        for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront())
+        {
+            if (binaryFun!(pred)(r.front, ahead.front)) return r;
+        }
+    }
+    static if (!isInfinite!Range)
+        return ahead;
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ];
+    auto r = findAdjacent(a);
+    assert(r == [ 10, 10, 9, 8, 8, 7, 8, 9 ]);
+    auto p = findAdjacent!("a < b")(a);
+    assert(p == [ 7, 8, 9 ]);
+
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+    import std.range;
+
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ];
+    auto p = findAdjacent(a);
+    assert(p == [10, 10, 9, 8, 8, 7, 8, 9 ]);
+    p = findAdjacent!("a < b")(a);
+    assert(p == [7, 8, 9]);
+    // empty
+    a = [];
+    p = findAdjacent(a);
+    assert(p.empty);
+    // not found
+    a = [ 1, 2, 3, 4, 5 ];
+    p = findAdjacent(a);
+    assert(p.empty);
+    p = findAdjacent!"a > b"(a);
+    assert(p.empty);
+    ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]);
+    assert(equal(findAdjacent(rfr), [2, 2, 3]));
+
+    // Issue 9350
+    assert(!repeat(1).findAdjacent().empty);
+}
+
+// findAmong
+/**
+Searches the given range for an element that matches one of the given choices.
+
+Advances $(D seq) by calling $(D seq.popFront) until either
+$(D find!(pred)(choices, seq.front)) is $(D true), or $(D seq) becomes empty.
+Performs $(BIGOH seq.length * choices.length) evaluations of $(D pred).
+
+Params:
+    pred = The predicate to use for determining a match.
+    seq = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to
+        search.
+    choices = A $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range)
+        of possible choices.
+
+Returns:
+$(D seq) advanced to the first matching element, or until empty if there are no
+matching elements.
+
+See_Also:
+    $(WEB sgi.com/tech/stl/find_first_of.html, STL's find_first_of)
+*/
+Range1 findAmong(alias pred = "a == b", Range1, Range2)(
+    Range1 seq, Range2 choices)
+    if (isInputRange!Range1 && isForwardRange!Range2)
+{
+    for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront())
+    {
+    }
+    return seq;
+}
+
+///
+@safe unittest
+{
+    int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
+    int[] b = [ 3, 1, 2 ];
+    assert(findAmong(a, b) == a[2 .. $]);
+}
+
+@safe unittest
+{
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ];
+    int[] b = [ 1, 2, 3 ];
+    assert(findAmong(a, b) == [2, 1, 2, 3, 4, 5 ]);
+    assert(findAmong(b, [ 4, 6, 7 ][]).empty);
+    assert(findAmong!("a == b")(a, b).length == a.length - 2);
+    assert(findAmong!("a == b")(b, [ 4, 6, 7 ][]).empty);
+}
+
+// findSkip
+/**
+ * Finds $(D needle) in $(D haystack) and positions $(D haystack)
+ * right after the first occurrence of $(D needle).
+ *
+ * Params:
+ *  haystack = The
+ *   $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) to search
+ *   in.
+ *  needle = The
+ *   $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) to search
+ *   for.
+ *
+ * Returns: $(D true) if the needle was found, in which case $(D haystack) is
+ * positioned after the end of the first occurrence of $(D needle); otherwise
+ * $(D false), leaving $(D haystack) untouched.
+ */
+bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle)
+if (isForwardRange!R1 && isForwardRange!R2
+        && is(typeof(binaryFun!pred(haystack.front, needle.front))))
+{
+    auto parts = findSplit!pred(haystack, needle);
+    if (parts[1].empty) return false;
+    // found
+    haystack = parts[2];
+    return true;
+}
+
+///
+@safe unittest
+{
+    // Needle is found; s is replaced by the substring following the first
+    // occurrence of the needle.
+    string s = "abcdef";
+    assert(findSkip(s, "cd") && s == "ef");
+
+    // Needle is not found; s is left untouched.
+    s = "abcdef";
+    assert(!findSkip(s, "cxd") && s == "abcdef");
+
+    // If the needle occurs at the end of the range, the range is left empty.
+    s = "abcdef";
+    assert(findSkip(s, "def") && s.empty);
+}
+
+/**
+These functions find the first occurrence of `needle` in `haystack` and then
+split `haystack` as follows.
+
+`findSplit` returns a tuple `result` containing $(I three) ranges. `result[0]`
+is the portion of `haystack` before `needle`, `result[1]` is the portion of
+`haystack` that matches `needle`, and `result[2]` is the portion of `haystack`
+after the match. If `needle` was not found, `result[0]` comprehends `haystack`
+entirely and `result[1]` and `result[2]` are empty.
+
+`findSplitBefore` returns a tuple `result` containing two ranges. `result[0]` is
+the portion of `haystack` before `needle`, and `result[1]` is the balance of
+`haystack` starting with the match. If `needle` was not found, `result[0]`
+comprehends `haystack` entirely and `result[1]` is empty.
+
+`findSplitAfter` returns a tuple `result` containing two ranges.
+`result[0]` is the portion of `haystack` up to and including the
+match, and `result[1]` is the balance of `haystack` starting
+after the match. If `needle` was not found, `result[0]` is empty
+and `result[1]` is `haystack`.
+
+In all cases, the concatenation of the returned ranges spans the
+entire `haystack`.
+
+If `haystack` is a random-access range, all three components of the tuple have
+the same type as `haystack`. Otherwise, `haystack` must be a forward range and
+the type of `result[0]` and `result[1]` is the same as $(XREF
+range,takeExactly).
+
+Params:
+    pred = Predicate to use for comparing needle against haystack.
+    haystack = The range to search.
+    needle = What to look for.
+
+Returns:
+
+A sub-type of `Tuple!()` of the split portions of `haystack` (see above for
+details).  This sub-type of `Tuple!()` has `opCast` defined for `bool`.  This
+`opCast` returns `true` when the separating `needle` was found
+(`!result[1].empty`) and `false` otherwise.  This enables the convenient idiom
+shown in the following example.
+
+Example:
+---
+if (const split = haystack.findSplit(needle))
+{
+     doSomethingWithSplit(split);
+}
+---
+ */
+auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isForwardRange!R1 && isForwardRange!R2)
+{
+    static struct Result(S1, S2) if (isForwardRange!S1 &&
+                                     isForwardRange!S2)
+    {
+        this(S1 pre, S1 separator, S2 post)
+        {
+            asTuple = typeof(asTuple)(pre, separator, post);
+        }
+        Tuple!(S1, S1, S2) asTuple;
+        bool opCast(T : bool)()
+        {
+            return !asTuple[1].empty;
+        }
+        alias asTuple this;
+    }
+
+    static if (isSomeString!R1 && isSomeString!R2
+            || isRandomAccessRange!R1 && hasLength!R2)
+    {
+        auto balance = find!pred(haystack, needle);
+        immutable pos1 = haystack.length - balance.length;
+        immutable pos2 = balance.empty ? pos1 : pos1 + needle.length;
+        return Result!(typeof(haystack[0 .. pos1]),
+                       typeof(haystack[pos2 .. haystack.length]))(haystack[0 .. pos1],
+                                                                  haystack[pos1 .. pos2],
+                                                                  haystack[pos2 .. haystack.length]);
+    }
+    else
+    {
+        import std.range : takeExactly;
+        auto original = haystack.save;
+        auto h = haystack.save;
+        auto n = needle.save;
+        size_t pos1, pos2;
+        while (!n.empty && !h.empty)
+        {
+            if (binaryFun!pred(h.front, n.front))
+            {
+                h.popFront();
+                n.popFront();
+                ++pos2;
+            }
+            else
+            {
+                haystack.popFront();
+                n = needle.save;
+                h = haystack.save;
+                pos2 = ++pos1;
+            }
+        }
+        return Result!(typeof(takeExactly(original, pos1)),
+                       typeof(h))(takeExactly(original, pos1),
+                                  takeExactly(haystack, pos2 - pos1),
+                                  h);
+    }
+}
+
+/// Ditto
+auto findSplitBefore(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isForwardRange!R1 && isForwardRange!R2)
+{
+    static struct Result(S1, S2) if (isForwardRange!S1 &&
+                                     isForwardRange!S2)
+    {
+        this(S1 pre, S2 post)
+        {
+            asTuple = typeof(asTuple)(pre, post);
+        }
+        Tuple!(S1, S2) asTuple;
+        bool opCast(T : bool)()
+        {
+            return !asTuple[0].empty;
+        }
+        alias asTuple this;
+    }
+
+    static if (isSomeString!R1 && isSomeString!R2
+            || isRandomAccessRange!R1 && hasLength!R2)
+    {
+        auto balance = find!pred(haystack, needle);
+        immutable pos = haystack.length - balance.length;
+        return Result!(typeof(haystack[0 .. pos]),
+                       typeof(haystack[pos .. haystack.length]))(haystack[0 .. pos],
+                                                                 haystack[pos .. haystack.length]);
+    }
+    else
+    {
+        import std.range : takeExactly;
+        auto original = haystack.save;
+        auto h = haystack.save;
+        auto n = needle.save;
+        size_t pos;
+        while (!n.empty && !h.empty)
+        {
+            if (binaryFun!pred(h.front, n.front))
+            {
+                h.popFront();
+                n.popFront();
+            }
+            else
+            {
+                haystack.popFront();
+                n = needle.save;
+                h = haystack.save;
+                ++pos;
+            }
+        }
+        return Result!(typeof(takeExactly(original, pos)),
+                       typeof(haystack))(takeExactly(original, pos),
+                                         haystack);
+    }
+}
+
+/// Ditto
+auto findSplitAfter(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
+if (isForwardRange!R1 && isForwardRange!R2)
+{
+    static struct Result(S1, S2) if (isForwardRange!S1 &&
+                                     isForwardRange!S2)
+    {
+        this(S1 pre, S2 post)
+        {
+            asTuple = typeof(asTuple)(pre, post);
+        }
+        Tuple!(S1, S2) asTuple;
+        bool opCast(T : bool)()
+        {
+            return !asTuple[1].empty;
+        }
+        alias asTuple this;
+    }
+
+    static if (isSomeString!R1 && isSomeString!R2
+            || isRandomAccessRange!R1 && hasLength!R2)
+    {
+        auto balance = find!pred(haystack, needle);
+        immutable pos = balance.empty ? 0 : haystack.length - balance.length + needle.length;
+        return Result!(typeof(haystack[0 .. pos]),
+                       typeof(haystack[pos .. haystack.length]))(haystack[0 .. pos],
+                                                                 haystack[pos .. haystack.length]);
+    }
+    else
+    {
+        import std.range : takeExactly;
+        auto original = haystack.save;
+        auto h = haystack.save;
+        auto n = needle.save;
+        size_t pos1, pos2;
+        while (!n.empty)
+        {
+            if (h.empty)
+            {
+                // Failed search
+                return Result!(typeof(takeExactly(original, 0)),
+                               typeof(original))(takeExactly(original, 0),
+                                                 original);
+            }
+            if (binaryFun!pred(h.front, n.front))
+            {
+                h.popFront();
+                n.popFront();
+                ++pos2;
+            }
+            else
+            {
+                haystack.popFront();
+                n = needle.save;
+                h = haystack.save;
+                pos2 = ++pos1;
+            }
+        }
+        return Result!(typeof(takeExactly(original, pos2)),
+                       typeof(h))(takeExactly(original, pos2),
+                                  h);
+    }
+}
+
+///
+@safe unittest
+{
+    auto a = "Carl Sagan Memorial Station";
+    auto r = findSplit(a, "Velikovsky");
+    import std.typecons : isTuple;
+    static assert(isTuple!(typeof(r.asTuple)));
+    static assert(isTuple!(typeof(r)));
+    assert(!r);
+    assert(r[0] == a);
+    assert(r[1].empty);
+    assert(r[2].empty);
+    r = findSplit(a, " ");
+    assert(r[0] == "Carl");
+    assert(r[1] == " ");
+    assert(r[2] == "Sagan Memorial Station");
+    auto r1 = findSplitBefore(a, "Sagan");
+    assert(r1);
+    assert(r1[0] == "Carl ", r1[0]);
+    assert(r1[1] == "Sagan Memorial Station");
+    auto r2 = findSplitAfter(a, "Sagan");
+    assert(r2);
+    assert(r2[0] == "Carl Sagan");
+    assert(r2[1] == " Memorial Station");
+}
+
+@safe unittest
+{
+    auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
+    auto r = findSplit(a, [9, 1]);
+    assert(!r);
+    assert(r[0] == a);
+    assert(r[1].empty);
+    assert(r[2].empty);
+    r = findSplit(a, [3]);
+    assert(r);
+    assert(r[0] == a[0 .. 2]);
+    assert(r[1] == a[2 .. 3]);
+    assert(r[2] == a[3 .. $]);
+
+    auto r1 = findSplitBefore(a, [9, 1]);
+    assert(r1);
+    assert(r1[0] == a);
+    assert(r1[1].empty);
+    r1 = findSplitBefore(a, [3, 4]);
+    assert(r1);
+    assert(r1[0] == a[0 .. 2]);
+    assert(r1[1] == a[2 .. $]);
+
+    auto r2 = findSplitAfter(a, [9, 1]);
+    assert(r2);
+    assert(r2[0].empty);
+    assert(r2[1] == a);
+    r2 = findSplitAfter(a, [3, 4]);
+    assert(r2);
+    assert(r2[0] == a[0 .. 4]);
+    assert(r2[1] == a[4 .. $]);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.algorithm.iteration : filter;
+
+    auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
+    auto fwd = filter!"a > 0"(a);
+    auto r = findSplit(fwd, [9, 1]);
+    assert(!r);
+    assert(equal(r[0], a));
+    assert(r[1].empty);
+    assert(r[2].empty);
+    r = findSplit(fwd, [3]);
+    assert(r);
+    assert(equal(r[0],  a[0 .. 2]));
+    assert(equal(r[1], a[2 .. 3]));
+    assert(equal(r[2], a[3 .. $]));
+
+    auto r1 = findSplitBefore(fwd, [9, 1]);
+    assert(r1);
+    assert(equal(r1[0], a));
+    assert(r1[1].empty);
+    r1 = findSplitBefore(fwd, [3, 4]);
+    assert(r1);
+    assert(equal(r1[0], a[0 .. 2]));
+    assert(equal(r1[1], a[2 .. $]));
+
+    auto r2 = findSplitAfter(fwd, [9, 1]);
+    assert(r2);
+    assert(r2[0].empty);
+    assert(equal(r2[1], a));
+    r2 = findSplitAfter(fwd, [3, 4]);
+    assert(r2);
+    assert(equal(r2[0], a[0 .. 4]));
+    assert(equal(r2[1], a[4 .. $]));
+}
+
+// minCount
+/**
+
+Computes the minimum (respectively maximum) of `range` along with its number of
+occurrences. Formally, the minimum is a value `x` in `range` such that $(D
+pred(a, x)) is `false` for all values `a` in `range`. Conversely, the maximum is
+a value `x` in `range` such that $(D pred(x, a)) is `false` for all values `a`
+in `range` (note the swapped arguments to `pred`).
+
+These functions may be used for computing arbitrary extrema by choosing `pred`
+appropriately. For corrrect functioning, `pred` must be a strict partial order,
+i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and
+irreflexive ($(D pred(a, a)) is `false`). The $(LUCKY trichotomy property of
+inequality) is not required: these algoritms consider elements `a` and `b` equal
+(for the purpose of counting) if `pred` puts them in the same equivalence class,
+i.e. $(D !pred(a, b) && !pred(b, a)).
+
+Params:
+    pred = The ordering predicate to use to determine the extremum (minimum
+        or maximum).
+    range = The input range to count.
+
+Returns: The minimum, respectively maximum element of a range together with the
+number it occurs in the range.
+
+Throws: `Exception` if `range.empty`.
+ */
+Tuple!(ElementType!Range, size_t)
+minCount(alias pred = "a < b", Range)(Range range)
+if (isInputRange!Range && !isInfinite!Range &&
+    is(typeof(binaryFun!pred(range.front, range.front))))
+{
+    import std.algorithm.internal : algoFormat;
+    import std.exception : enforce;
+
+    alias T  = ElementType!Range;
+    alias UT = Unqual!T;
+    alias RetType = Tuple!(T, size_t);
+
+    static assert (is(typeof(RetType(range.front, 1))),
+        algoFormat("Error: Cannot call minCount on a %s, because it is not possible "~
+               "to copy the result value (a %s) into a Tuple.", Range.stringof, T.stringof));
+
+    enforce(!range.empty, "Can't count elements from an empty range");
+    size_t occurrences = 1;
+
+    static if (isForwardRange!Range)
+    {
+        Range least = range.save;
+        for (range.popFront(); !range.empty; range.popFront())
+        {
+            if (binaryFun!pred(least.front, range.front))
+            {
+                assert(!binaryFun!pred(range.front, least.front),
+                    "min/maxPos: predicate must be a strict partial order.");
+                continue;
+            }
+            if (binaryFun!pred(range.front, least.front))
+            {
+                // change the min
+                least = range.save;
+                occurrences = 1;
+            }
+            else
+                ++occurrences;
+        }
+        return RetType(least.front, occurrences);
+    }
+    else static if (isAssignable!(UT, T) || (!hasElaborateAssign!UT && isAssignable!UT))
+    {
+        UT v = UT.init;
+        static if (isAssignable!(UT, T)) v = range.front;
+        else                             v = cast(UT)range.front;
+
+        for (range.popFront(); !range.empty; range.popFront())
+        {
+            if (binaryFun!pred(*cast(T*)&v, range.front)) continue;
+            if (binaryFun!pred(range.front, *cast(T*)&v))
+            {
+                // change the min
+                static if (isAssignable!(UT, T)) v = range.front;
+                else                             v = cast(UT)range.front; //Safe because !hasElaborateAssign!UT
+                occurrences = 1;
+            }
+            else
+                ++occurrences;
+        }
+        return RetType(*cast(T*)&v, occurrences);
+    }
+    else static if (hasLvalueElements!Range)
+    {
+        import std.algorithm.internal : addressOf;
+        T* p = addressOf(range.front);
+        for (range.popFront(); !range.empty; range.popFront())
+        {
+            if (binaryFun!pred(*p, range.front)) continue;
+            if (binaryFun!pred(range.front, *p))
+            {
+                // change the min
+                p = addressOf(range.front);
+                occurrences = 1;
+            }
+            else
+                ++occurrences;
+        }
+        return RetType(*p, occurrences);
+    }
+    else
+        static assert(false,
+            algoFormat("Sorry, can't find the minCount of a %s: Don't know how "~
+                   "to keep track of the smallest %s element.", Range.stringof, T.stringof));
+}
+
+/// Ditto
+Tuple!(ElementType!Range, size_t)
+maxCount(alias pred = "a < b", Range)(Range range)
+if (isInputRange!Range && !isInfinite!Range &&
+    is(typeof(binaryFun!pred(range.front, range.front))))
+{
+    return range.minCount!((a, b) => binaryFun!pred(b, a));
+}
+
+///
+unittest
+{
+    import std.conv : text;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
+    // Minimum is 1 and occurs 3 times
+    assert(a.minCount == tuple(1, 3));
+    // Maximum is 4 and occurs 2 times
+    assert(a.maxCount == tuple(4, 2));
+}
+
+unittest
+{
+    import std.conv : text;
+    import std.exception : assertThrown;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[][] b = [ [4], [2, 4], [4], [4] ];
+    auto c = minCount!("a[0] < b[0]")(b);
+    assert(c == tuple([2, 4], 1), text(c[0]));
+
+    //Test empty range
+    assertThrown(minCount(b[$..$]));
+
+    //test with reference ranges. Test both input and forward.
+    assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
+    assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
+}
+
+unittest
+{
+    import std.conv : text;
+    import std.meta : AliasSeq;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    static struct R(T) //input range
+    {
+        T[] arr;
+        alias arr this;
+    }
+
+    immutable         a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
+    R!(immutable int) b = R!(immutable int)(a);
+
+    assert(minCount(a) == tuple(1, 3));
+    assert(minCount(b) == tuple(1, 3));
+    assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(a) == tuple(4, 2));
+    assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(b) == tuple(4, 2));
+
+    immutable(int[])[] c = [ [4], [2, 4], [4], [4] ];
+    assert(minCount!("a[0] < b[0]")(c) == tuple([2, 4], 1), text(c[0]));
+
+    static struct S1
+    {
+        int i;
+    }
+    alias IS1 = immutable(S1);
+    static assert( isAssignable!S1);
+    static assert( isAssignable!(S1, IS1));
+
+    static struct S2
+    {
+        int* p;
+        this(ref immutable int i) immutable {p = &i;}
+        this(ref int i) {p = &i;}
+        @property ref inout(int) i() inout {return *p;}
+        bool opEquals(const S2 other) const {return i == other.i;}
+    }
+    alias IS2 = immutable(S2);
+    static assert( isAssignable!S2);
+    static assert(!isAssignable!(S2, IS2));
+    static assert(!hasElaborateAssign!S2);
+
+    static struct S3
+    {
+        int i;
+        void opAssign(ref S3 other) @disable;
+    }
+    static assert(!isAssignable!S3);
+
+    foreach (Type; AliasSeq!(S1, IS1, S2, IS2, S3))
+    {
+        static if (is(Type == immutable)) alias V = immutable int;
+        else                              alias V = int;
+        V one = 1, two = 2;
+        auto r1 = [Type(two), Type(one), Type(one)];
+        auto r2 = R!Type(r1);
+        assert(minCount!"a.i < b.i"(r1) == tuple(Type(one), 2));
+        assert(minCount!"a.i < b.i"(r2) == tuple(Type(one), 2));
+        assert(one == 1 && two == 2);
+    }
+}
+
+// minPos
+/**
+Computes a subrange of `range` starting at the first occurrence of `range`'s
+minimum (respectively maximum) and with the same ending as `range`, or the
+empty range if `range` itself is empty.
+
+Formally, the minimum is a value `x` in `range` such that $(D pred(a, x)) is
+`false` for all values `a` in `range`. Conversely, the maximum is a value `x` in
+`range` such that $(D pred(x, a)) is `false` for all values `a` in `range` (note
+the swapped arguments to `pred`).
+
+These functions may be used for computing arbitrary extrema by choosing `pred`
+appropriately. For corrrect functioning, `pred` must be a strict partial order,
+i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and
+irreflexive ($(D pred(a, a)) is `false`).
+
+Params:
+    pred = The ordering predicate to use to determine the extremum (minimum or
+        maximum) element.
+    range = The input range to search.
+
+Returns: The position of the minimum (respectively maximum) element of forward
+range `range`, i.e. a subrange of `range` starting at the position of  its
+smallest (respectively largest) element and with the same ending as `range`.
+
+*/
+Range minPos(alias pred = "a < b", Range)(Range range)
+    if (isForwardRange!Range && !isInfinite!Range &&
+        is(typeof(binaryFun!pred(range.front, range.front))))
+{
+    static if (hasSlicing!Range && isRandomAccessRange!Range && hasLength!Range)
+    {
+        // Prefer index-based access
+        size_t pos = 0;
+        foreach (i; 1 .. range.length)
+        {
+            if (binaryFun!pred(range[i], range[pos]))
+            {
+                pos = i;
+            }
+        }
+        return range[pos .. $];
+    }
+    else
+    {
+        auto result = range.save;
+        if (range.empty) return result;
+        for (range.popFront(); !range.empty; range.popFront())
+        {
+            // Note: Unlike minCount, we do not care to find equivalence, so a
+            // single pred call is enough.
+            if (binaryFun!pred(range.front, result.front))
+            {
+                // change the min
+                result = range.save;
+            }
+        }
+        return result;
+    }
+}
+
+/// Ditto
+Range maxPos(alias pred = "a < b", Range)(Range range)
+if (isForwardRange!Range && !isInfinite!Range &&
+    is(typeof(binaryFun!pred(range.front, range.front))))
+{
+    return range.minPos!((a, b) => binaryFun!pred(b, a));
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
+    // Minimum is 1 and first occurs in position 3
+    assert(a.minPos == [ 1, 2, 4, 1, 1, 2 ]);
+    // Maximum is 4 and first occurs in position 2
+    assert(a.maxPos == [ 4, 1, 2, 4, 1, 1, 2 ]);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
+    //Test that an empty range works
+    int[] b = a[$..$];
+    assert(equal(minPos(b), b));
+
+    //test with reference range.
+    assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) );
+}
+
+unittest
+{
+    //Rvalue range
+    import std.algorithm.comparison : equal;
+    import std.container : Array;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    assert(Array!int(2, 3, 4, 1, 2, 4, 1, 1, 2)
+               []
+               .minPos()
+               .equal([ 1, 2, 4, 1, 1, 2 ]));
+}
+
+@safe unittest
+{
+    //BUG 9299
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
+    // Minimum is 1 and first occurs in position 3
+    assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]);
+    // Maximum is 4 and first occurs in position 5
+    assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]);
+
+    immutable(int[])[] b = [ [4], [2, 4], [4], [4] ];
+    assert(minPos!("a[0] < b[0]")(b) == [ [2, 4], [4], [4] ]);
+}
+
+/**
+Skip over the initial portion of the first given range that matches the second
+range, or do nothing if there is no match.
+
+Params:
+    pred = The predicate that determines whether elements from each respective
+        range match. Defaults to equality $(D "a == b").
+    r1 = The $(XREF_PACK_NAMED range,primitives,isForwardRange,forward range) to
+        move forward.
+    r2 = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range)
+        representing the initial segment of $(D r1) to skip over.
+
+Returns:
+true if the initial segment of $(D r1) matches $(D r2), and $(D r1) has been
+advanced to the point past this segment; otherwise false, and $(D r1) is left
+in its original position.
+ */
+bool skipOver(R1, R2)(ref R1 r1, R2 r2)
+    if (isForwardRange!R1 && isInputRange!R2
+        && is(typeof(r1.front == r2.front)))
+{
+    static if (is(typeof(r1[0 .. $] == r2) : bool)
+        && is(typeof(r2.length > r1.length) : bool)
+        && is(typeof(r1 = r1[r2.length .. $])))
+    {
+        if (r2.length > r1.length || r1[0 .. r2.length] != r2)
+        {
+            return false;
+        }
+        r1 = r1[r2.length .. $];
+        return true;
+    }
+    else
+    {
+        return skipOver!((a, b) => a == b)(r1, r2);
+    }
+}
+
+/// Ditto
+bool skipOver(alias pred, R1, R2)(ref R1 r1, R2 r2)
+    if (is(typeof(binaryFun!pred(r1.front, r2.front))) &&
+        isForwardRange!R1 &&
+        isInputRange!R2)
+{
+    static if (hasLength!R1 && hasLength!R2)
+    {
+        // Shortcut opportunity!
+        if (r2.length > r1.length)
+            return false;
+    }
+    auto r = r1.save;
+    while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front))
+    {
+        r.popFront();
+        r2.popFront();
+    }
+    if (r2.empty)
+        r1 = r;
+    return r2.empty;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    auto s1 = "Hello world";
+    assert(!skipOver(s1, "Ha"));
+    assert(s1 == "Hello world");
+    assert(skipOver(s1, "Hell") && s1 == "o world");
+
+    string[]  r1 = ["abc", "def", "hij"];
+    dstring[] r2 = ["abc"d];
+    assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d]));
+    assert(r1 == ["abc", "def", "hij"]);
+    assert(skipOver!((a, b) => a.equal(b))(r1, r2));
+    assert(r1 == ["def", "hij"]);
+}
+
+/**
+Skip over the first element of the given range if it matches the given element,
+otherwise do nothing.
+
+Params:
+    pred = The predicate that determines whether an element from the range
+        matches the given element.
+
+    r = The $(XREF_PACK_NAMED range,primitives,isInputRange,input range) to skip
+        over.
+
+    e = The element to match.
+
+Returns:
+true if the first element matches the given element according to the given
+predicate, and the range has been advanced by one element; otherwise false, and
+the range is left untouched.
+ */
+bool skipOver(R, E)(ref R r, E e)
+    if (isInputRange!R && is(typeof(r.front == e) : bool))
+{
+    return skipOver!((a, b) => a == b)(r, e);
+}
+
+/// Ditto
+bool skipOver(alias pred, R, E)(ref R r, E e)
+    if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R)
+{
+    if (r.empty || !binaryFun!pred(r.front, e))
+        return false;
+    r.popFront();
+    return true;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    auto s1 = "Hello world";
+    assert(!skipOver(s1, 'a'));
+    assert(s1 == "Hello world");
+    assert(skipOver(s1, 'H') && s1 == "ello world");
+
+    string[] r = ["abc", "def", "hij"];
+    dstring e = "abc"d;
+    assert(!skipOver!((a, b) => a.equal(b))(r, "def"d));
+    assert(r == ["abc", "def", "hij"]);
+    assert(skipOver!((a, b) => a.equal(b))(r, e));
+    assert(r == ["def", "hij"]);
+
+    auto s2 = "";
+    assert(!s2.skipOver('a'));
+}
+
+/**
+Checks whether the given
+$(XREF_PACK_NAMED range,primitives,isInputRange,input range) starts with (one
+of) the given needle(s) or, if no needles are given,
+if its front element fulfils predicate $(D pred).
+
+Params:
+
+    pred = Predicate to use in comparing the elements of the haystack and the
+        needle(s). Mandatory if no needles are given.
+
+    doesThisStart = The input range to check.
+
+    withOneOfThese = The needles against which the range is to be checked,
+        which may be individual elements or input ranges of elements.
+
+    withThis = The single needle to check, which may be either a single element
+        or an input range of elements.
+
+Returns:
+
+0 if the needle(s) do not occur at the beginning of the given range;
+otherwise the position of the matching needle, that is, 1 if the range starts
+with $(D withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so
+on.
+
+In the case where $(D doesThisStart) starts with multiple of the ranges or
+elements in $(D withOneOfThese), then the shortest one matches (if there are
+two which match which are of the same length (e.g. $(D "a") and $(D 'a')), then
+the left-most of them in the argument
+list matches).
+
+In the case when no needle parameters are given, return $(D true) iff front of
+$(D doesThisStart) fulfils predicate $(D pred).
+ */
+uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese)
+if (isInputRange!Range && Needles.length > 1 &&
+    is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) &&
+    is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint))
+{
+    alias haystack = doesThisStart;
+    alias needles  = withOneOfThese;
+
+    // Make one pass looking for empty ranges in needles
+    foreach (i, Unused; Needles)
+    {
+        // Empty range matches everything
+        static if (!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
+        {
+            if (needles[i].empty) return i + 1;
+        }
+    }
+
+    for (; !haystack.empty; haystack.popFront())
+    {
+        foreach (i, Unused; Needles)
+        {
+            static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
+            {
+                // Single-element
+                if (binaryFun!pred(haystack.front, needles[i]))
+                {
+                    // found, but instead of returning, we just stop searching.
+                    // This is to account for one-element
+                    // range matches (consider startsWith("ab", "a",
+                    // 'a') should return 1, not 2).
+                    break;
+                }
+            }
+            else
+            {
+                if (binaryFun!pred(haystack.front, needles[i].front))
+                {
+                    continue;
+                }
+            }
+
+            // This code executed on failure to match
+            // Out with this guy, check for the others
+            uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
+            if (result > i) ++result;
+            return result;
+        }
+
+        // If execution reaches this point, then the front matches for all
+        // needle ranges, or a needle element has been matched.
+        // What we need to do now is iterate, lopping off the front of
+        // the range and checking if the result is empty, or finding an
+        // element needle and returning.
+        // If neither happens, we drop to the end and loop.
+        foreach (i, Unused; Needles)
+        {
+            static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
+            {
+                // Test has passed in the previous loop
+                return i + 1;
+            }
+            else
+            {
+                needles[i].popFront();
+                if (needles[i].empty) return i + 1;
+            }
+        }
+    }
+    return 0;
+}
+
+/// Ditto
+bool startsWith(alias pred = "a == b", R1, R2)(R1 doesThisStart, R2 withThis)
+if (isInputRange!R1 &&
+    isInputRange!R2 &&
+    is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool))
+{
+    alias haystack = doesThisStart;
+    alias needle   = withThis;
+
+    static if (is(typeof(pred) : string))
+        enum isDefaultPred = pred == "a == b";
+    else
+        enum isDefaultPred = false;
+
+    //Note: While narrow strings don't have a "true" length, for a narrow string to start with another
+    //narrow string *of the same type*, it must have *at least* as many code units.
+    static if ((hasLength!R1 && hasLength!R2) ||
+        (isNarrowString!R1 && isNarrowString!R2 && ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof))
+    {
+        if (haystack.length < needle.length)
+            return false;
+    }
+
+    static if (isDefaultPred && isArray!R1 && isArray!R2 &&
+               is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2)))
+    {
+        //Array slice comparison mode
+        return haystack[0 .. needle.length] == needle;
+    }
+    else static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && hasLength!R2)
+    {
+        //RA dual indexing mode
+        foreach (j; 0 .. needle.length)
+        {
+            if (!binaryFun!pred(haystack[j], needle[j]))
+                // not found
+                return false;
+        }
+        // found!
+        return true;
+    }
+    else
+    {
+        //Standard input range mode
+        if (needle.empty) return true;
+        static if (hasLength!R1 && hasLength!R2)
+        {
+            //We have previously checked that haystack.length > needle.length,
+            //So no need to check haystack.empty during iteration
+            for ( ; ; haystack.popFront() )
+            {
+                if (!binaryFun!pred(haystack.front, needle.front)) break;
+                needle.popFront();
+                if (needle.empty) return true;
+            }
+        }
+        else
+        {
+            for ( ; !haystack.empty ; haystack.popFront() )
+            {
+                if (!binaryFun!pred(haystack.front, needle.front)) break;
+                needle.popFront();
+                if (needle.empty) return true;
+            }
+        }
+        return false;
+    }
+}
+
+/// Ditto
+bool startsWith(alias pred = "a == b", R, E)(R doesThisStart, E withThis)
+    if (isInputRange!R &&
+        is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool))
+{
+    return doesThisStart.empty
+        ? false
+        : binaryFun!pred(doesThisStart.front, withThis);
+}
+
+/// Ditto
+bool startsWith(alias pred, R)(R doesThisStart)
+    if (isInputRange!R &&
+        ifTestable!(typeof(doesThisStart.front), unaryFun!pred))
+{
+    return !doesThisStart.empty && unaryFun!pred(doesThisStart.front);
+}
+
+///
+@safe unittest
+{
+    import std.ascii : isAlpha;
+
+    assert("abc".startsWith!(a => a.isAlpha));
+    assert("abc".startsWith!isAlpha);
+    assert(!"1ab".startsWith!(a => a.isAlpha));
+    assert(!"".startsWith!(a => a.isAlpha));
+
+    import std.algorithm.comparison : among;
+    assert("abc".startsWith!(a => a.among('a', 'b') != 0));
+    assert(!"abc".startsWith!(a => a.among('b', 'c') != 0));
+
+    assert(startsWith("abc", ""));
+    assert(startsWith("abc", "a"));
+    assert(!startsWith("abc", "b"));
+    assert(startsWith("abc", 'a', "b") == 1);
+    assert(startsWith("abc", "b", "a") == 2);
+    assert(startsWith("abc", "a", "a") == 1);
+    assert(startsWith("abc", "ab", "a") == 2);
+    assert(startsWith("abc", "x", "a", "b") == 2);
+    assert(startsWith("abc", "x", "aa", "ab") == 3);
+    assert(startsWith("abc", "x", "aaa", "sab") == 0);
+    assert(startsWith("abc", "x", "aaa", "a", "sab") == 3);
+    alias C = Tuple!(int, "x", int, "y");
+    assert(startsWith!"a.x == b"([ C(1,1), C(1,2), C(2,2) ], [1, 1]));
+    assert(startsWith!"a.x == b"([ C(1,1), C(2,1), C(2,2) ], [1, 1], [1, 2], [1, 3]) == 2);
+}
+
+@safe unittest
+{
+    import std.algorithm.iteration : filter;
+    import std.conv : to;
+    import std.meta : AliasSeq;
+    import std.range;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        assert(!startsWith(to!S("abc"), 'c'));
+        assert(startsWith(to!S("abc"), 'a', 'c') == 1);
+        assert(!startsWith(to!S("abc"), 'x', 'n', 'b'));
+        assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3);
+        assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2);
+
+        foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            //Lots of strings
+            assert(startsWith(to!S("abc"), to!T("")));
+            assert(startsWith(to!S("ab"), to!T("a")));
+            assert(startsWith(to!S("abc"), to!T("a")));
+            assert(!startsWith(to!S("abc"), to!T("b")));
+            assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz"));
+            assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 2);
+            assert(startsWith(to!S("abc"), to!T("a"), "b") == 1);
+            assert(startsWith(to!S("abc"), to!T("b"), "a") == 2);
+            assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1);
+            assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1);
+            assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2);
+            assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3);
+            assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
+            assert(startsWith(to!S("abc"), 'a'));
+            assert(!startsWith(to!S("abc"), to!T("sab")));
+            assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3);
+
+            //Unicode
+            assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")));
+            assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2);
+            assert(startsWith(to!S("日本語"), to!T("日本")));
+            assert(startsWith(to!S("日本語"), to!T("日本語")));
+            assert(!startsWith(to!S("日本"), to!T("日本語")));
+
+            //Empty
+            assert(startsWith(to!S(""),  T.init));
+            assert(!startsWith(to!S(""), 'a'));
+            assert(startsWith(to!S("a"), T.init));
+            assert(startsWith(to!S("a"), T.init, "") == 1);
+            assert(startsWith(to!S("a"), T.init, 'a') == 1);
+            assert(startsWith(to!S("a"), 'a', T.init) == 2);
+        }();
+    }
+
+    //Length but no RA
+    assert(!startsWith("abc".takeExactly(3), "abcd".takeExactly(4)));
+    assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(3)));
+    assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(1)));
+
+    foreach (T; AliasSeq!(int, short))
+    {
+        immutable arr = cast(T[])[0, 1, 2, 3, 4, 5];
+
+        //RA range
+        assert(startsWith(arr, cast(int[])null));
+        assert(!startsWith(arr, 5));
+        assert(!startsWith(arr, 1));
+        assert(startsWith(arr, 0));
+        assert(startsWith(arr, 5, 0, 1) == 2);
+        assert(startsWith(arr, [0]));
+        assert(startsWith(arr, [0, 1]));
+        assert(startsWith(arr, [0, 1], 7) == 1);
+        assert(!startsWith(arr, [0, 1, 7]));
+        assert(startsWith(arr, [0, 1, 7], [0, 1, 2]) == 2);
+
+        //Normal input range
+        assert(!startsWith(filter!"true"(arr), 1));
+        assert(startsWith(filter!"true"(arr), 0));
+        assert(startsWith(filter!"true"(arr), [0]));
+        assert(startsWith(filter!"true"(arr), [0, 1]));
+        assert(startsWith(filter!"true"(arr), [0, 1], 7) == 1);
+        assert(!startsWith(filter!"true"(arr), [0, 1, 7]));
+        assert(startsWith(filter!"true"(arr), [0, 1, 7], [0, 1, 2]) == 2);
+        assert(startsWith(arr, filter!"true"([0, 1])));
+        assert(startsWith(arr, filter!"true"([0, 1]), 7) == 1);
+        assert(!startsWith(arr, filter!"true"([0, 1, 7])));
+        assert(startsWith(arr, [0, 1, 7], filter!"true"([0, 1, 2])) == 2);
+
+        //Non-default pred
+        assert(startsWith!("a%10 == b%10")(arr, [10, 11]));
+        assert(!startsWith!("a%10 == b%10")(arr, [10, 12]));
+    }
+}
+
+/* (Not yet documented.)
+Consume all elements from $(D r) that are equal to one of the elements
+$(D es).
+ */
+void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es)
+//if (is(typeof(binaryFun!pred(r1.front, es[0]))))
+{
+  loop:
+    for (; !r.empty; r.popFront())
+    {
+        foreach (i, E; Es)
+        {
+            if (binaryFun!pred(r.front, es[i]))
+            {
+                continue loop;
+            }
+        }
+        break;
+    }
+}
+
+@safe unittest
+{
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    auto s1 = "Hello world";
+    skipAll(s1, 'H', 'e');
+    assert(s1 == "llo world");
+}
+
+/**
+Interval option specifier for $(D until) (below) and others.
+ */
+enum OpenRight
+{
+    no, /// Interval is closed to the right (last element included)
+    yes /// Interval is open to the right (last element is not included)
+}
+
+struct Until(alias pred, Range, Sentinel) if (isInputRange!Range)
+{
+    private Range _input;
+    static if (!is(Sentinel == void))
+        private Sentinel _sentinel;
+    // mixin(bitfields!(
+    //             OpenRight, "_openRight", 1,
+    //             bool,  "_done", 1,
+    //             uint, "", 6));
+    //             OpenRight, "_openRight", 1,
+    //             bool,  "_done", 1,
+    OpenRight _openRight;
+    bool _done;
+
+    static if (!is(Sentinel == void))
+        this(Range input, Sentinel sentinel,
+                OpenRight openRight = OpenRight.yes)
+        {
+            _input = input;
+            _sentinel = sentinel;
+            _openRight = openRight;
+            _done = _input.empty || openRight && predSatisfied();
+        }
+    else
+        this(Range input, OpenRight openRight = OpenRight.yes)
+        {
+            _input = input;
+            _openRight = openRight;
+            _done = _input.empty || openRight && predSatisfied();
+        }
+
+    @property bool empty()
+    {
+        return _done;
+    }
+
+    @property auto ref front()
+    {
+        assert(!empty);
+        return _input.front;
+    }
+
+    private bool predSatisfied()
+    {
+        static if (is(Sentinel == void))
+            return cast(bool) unaryFun!pred(_input.front);
+        else
+            return cast(bool) startsWith!pred(_input, _sentinel);
+    }
+
+    void popFront()
+    {
+        assert(!empty);
+        if (!_openRight)
+        {
+            _done = predSatisfied();
+            _input.popFront();
+            _done = _done || _input.empty;
+        }
+        else
+        {
+            _input.popFront();
+            _done = _input.empty || predSatisfied();
+        }
+    }
+
+    static if (isForwardRange!Range)
+    {
+        static if (!is(Sentinel == void))
+            @property Until save()
+            {
+                Until result = this;
+                result._input     = _input.save;
+                result._sentinel  = _sentinel;
+                result._openRight = _openRight;
+                result._done      = _done;
+                return result;
+            }
+        else
+            @property Until save()
+            {
+                Until result = this;
+                result._input     = _input.save;
+                result._openRight = _openRight;
+                result._done      = _done;
+                return result;
+            }
+    }
+}
+
+/**
+Lazily iterates $(D range) _until the element $(D e) for which
+$(D pred(e, sentinel)) is true.
+
+Params:
+    pred = Predicate to determine when to stop.
+    range = The $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range)
+    to iterate over.
+    sentinel = The element to stop at.
+    openRight = Determines whether the element for which the given predicate is
+        true should be included in the resulting range ($(D OpenRight.no)), or
+        not ($(D OpenRight.yes)).
+
+Returns:
+    An $(XREF_PACK_NAMED _range,primitives,isInputRange,input _range) that
+    iterates over the original range's elements, but ends when the specified
+    predicate becomes true. If the original range is a
+    $(XREF_PACK_NAMED _range,primitives,isForwardRange,forward _range) or
+    higher, this range will be a forward range.
+ */
+Until!(pred, Range, Sentinel)
+until(alias pred = "a == b", Range, Sentinel)
+(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes)
+if (!is(Sentinel == OpenRight))
+{
+    return typeof(return)(range, sentinel, openRight);
+}
+
+/// Ditto
+Until!(pred, Range, void)
+until(alias pred, Range)
+(Range range, OpenRight openRight = OpenRight.yes)
+{
+    return typeof(return)(range, openRight);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5];
+    assert(equal(a.until(7), [1, 2, 4][]));
+    assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5];
+
+    static assert(isForwardRange!(typeof(a.until(7))));
+    static assert(isForwardRange!(typeof(until!"a == 2"(a, OpenRight.no))));
+
+    assert(equal(a.until(7), [1, 2, 4][]));
+    assert(equal(a.until([7, 2]), [1, 2, 4, 7][]));
+    assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][]));
+    assert(equal(until!"a == 2"(a, OpenRight.no), [1, 2][]));
+}
+
+unittest // bugzilla 13171
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+    auto a = [1, 2, 3, 4];
+    assert(equal(refRange(&a).until(3, OpenRight.no), [1, 2, 3]));
+    assert(a == [4]);
+}
+
+@safe unittest // Issue 10460
+{
+    import std.algorithm.comparison : equal;
+    auto a = [1, 2, 3, 4];
+    foreach (ref e; a.until(3))
+        e = 0;
+    assert(equal(a, [0, 0, 3, 4]));
+}
+
+unittest // Issue 13124
+{
+    import std.algorithm.comparison : among;
+    auto s = "hello how\nare you";
+    s.until!(c => c.among!('\n', '\r'));
+}
diff --git a/libphobos/src/std/algorithm/setops.d b/libphobos/src/std/algorithm/setops.d
new file mode 100644
index 00000000000..7c40941743f
--- /dev/null
+++ b/libphobos/src/std/algorithm/setops.d
@@ -0,0 +1,1397 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic algorithms that implement set operations.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 cartesianProduct,
+        Computes Cartesian product of two ranges.)
+$(T2 largestPartialIntersection,
+        Copies out the values that occur most frequently in a range of ranges.)
+$(T2 largestPartialIntersectionWeighted,
+        Copies out the values that occur most frequently (multiplied by
+        per-value weights) in a range of ranges.)
+$(T2 nWayUnion,
+        Computes the union of a set of sets implemented as a range of sorted
+        ranges.)
+$(T2 setDifference,
+        Lazily computes the set difference of two or more sorted ranges.)
+$(T2 setIntersection,
+        Lazily computes the intersection of two or more sorted ranges.)
+$(T2 setSymmetricDifference,
+        Lazily computes the symmetric set difference of two or more sorted
+        ranges.)
+$(T2 setUnion,
+        Lazily computes the set union of two or more sorted ranges.)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_setops.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.setops;
+
+import std.range.primitives;
+
+// FIXME
+import std.functional; // : unaryFun, binaryFun;
+import std.traits;
+// FIXME
+import std.meta; // : AliasSeq, staticMap, allSatisfy, anySatisfy;
+
+// cartesianProduct
+/**
+Lazily computes the Cartesian product of two or more ranges. The product is a
+_range of tuples of elements from each respective range.
+
+The conditions for the two-range case are as follows:
+
+If both ranges are finite, then one must be (at least) a forward range and the
+other an input range.
+
+If one _range is infinite and the other finite, then the finite _range must
+be a forward _range, and the infinite range can be an input _range.
+
+If both ranges are infinite, then both must be forward ranges.
+
+When there are more than two ranges, the above conditions apply to each
+adjacent pair of ranges.
+
+Params:
+    range1 = The first range
+    range2 = The second range
+    ranges = Two or more non-infinite forward ranges
+    otherRanges = Zero or more non-infinite forward ranges
+
+Returns:
+    A forward range of $(XREF typecons,Tuple) representing elements of the
+    cartesian product of the given ranges.
+*/
+auto cartesianProduct(R1, R2)(R1 range1, R2 range2)
+    if (!allSatisfy!(isForwardRange, R1, R2) ||
+        anySatisfy!(isInfinite, R1, R2))
+{
+    import std.algorithm.iteration : map, joiner;
+
+    static if (isInfinite!R1 && isInfinite!R2)
+    {
+        static if (isForwardRange!R1 && isForwardRange!R2)
+        {
+            import std.range : zip, repeat, take, chain, sequence;
+
+            // This algorithm traverses the cartesian product by alternately
+            // covering the right and bottom edges of an increasing square area
+            // over the infinite table of combinations. This schedule allows us
+            // to require only forward ranges.
+            return zip(sequence!"n"(cast(size_t)0), range1.save, range2.save,
+                       repeat(range1), repeat(range2))
+                .map!(function(a) => chain(
+                    zip(repeat(a[1]), take(a[4].save, a[0])),
+                    zip(take(a[3].save, a[0]+1), repeat(a[2]))
+                ))()
+                .joiner();
+        }
+        else static assert(0, "cartesianProduct of infinite ranges requires "~
+                              "forward ranges");
+    }
+    else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2)
+    {
+        import std.range : zip, repeat;
+        return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save))
+                          (range1));
+    }
+    else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1)
+    {
+        import std.range : zip, repeat;
+        return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a)))
+                          (range2));
+    }
+    else static assert(0, "cartesianProduct involving finite ranges must "~
+                          "have at least one finite forward range");
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.range;
+    import std.typecons : tuple;
+
+    auto N = sequence!"n"(0);         // the range of natural numbers
+    auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers
+
+    // Various arbitrary number pairs can be found in the range in finite time.
+    assert(canFind(N2, tuple(0, 0)));
+    assert(canFind(N2, tuple(123, 321)));
+    assert(canFind(N2, tuple(11, 35)));
+    assert(canFind(N2, tuple(279, 172)));
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.typecons : tuple;
+
+    auto B = [ 1, 2, 3 ];
+    auto C = [ 4, 5, 6 ];
+    auto BC = cartesianProduct(B, C);
+
+    foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
+                 [2, 6], [3, 6]])
+    {
+        assert(canFind(BC, tuple(n[0], n[1])));
+    }
+}
+
+@safe unittest
+{
+    // Test cartesian product of two infinite ranges
+    import std.algorithm.searching : canFind;
+    import std.range;
+    import std.typecons : tuple;
+
+    auto Even = sequence!"2*n"(0);
+    auto Odd = sequence!"2*n+1"(0);
+    auto EvenOdd = cartesianProduct(Even, Odd);
+
+    foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5],
+                    [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]])
+    {
+        assert(canFind(EvenOdd, tuple(pair[0], pair[1])));
+    }
+
+    // This should terminate in finite time
+    assert(canFind(EvenOdd, tuple(124, 73)));
+    assert(canFind(EvenOdd, tuple(0, 97)));
+    assert(canFind(EvenOdd, tuple(42, 1)));
+}
+
+@safe unittest
+{
+    // Test cartesian product of an infinite input range and a finite forward
+    // range.
+    import std.algorithm.searching : canFind;
+    import std.range;
+    import std.typecons : tuple;
+
+    auto N = sequence!"n"(0);
+    auto M = [100, 200, 300];
+    auto NM = cartesianProduct(N,M);
+
+    foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300],
+                    [2, 100], [2, 200], [2, 300], [3, 100], [3, 200],
+                    [3, 300]])
+    {
+        assert(canFind(NM, tuple(pair[0], pair[1])));
+    }
+
+    // We can't solve the halting problem, so we can only check a finite
+    // initial segment here.
+    assert(!canFind(NM.take(100), tuple(100, 0)));
+    assert(!canFind(NM.take(100), tuple(1, 1)));
+    assert(!canFind(NM.take(100), tuple(100, 200)));
+
+    auto MN = cartesianProduct(M,N);
+    foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1],
+                    [100, 2], [200, 2], [300, 2], [100, 3], [200, 3],
+                    [300, 3]])
+    {
+        assert(canFind(MN, tuple(pair[0], pair[1])));
+    }
+
+    // We can't solve the halting problem, so we can only check a finite
+    // initial segment here.
+    assert(!canFind(MN.take(100), tuple(0, 100)));
+    assert(!canFind(MN.take(100), tuple(0, 1)));
+    assert(!canFind(MN.take(100), tuple(100, 200)));
+}
+
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.typecons : tuple;
+
+    // Test cartesian product of two finite ranges.
+    auto X = [1, 2, 3];
+    auto Y = [4, 5, 6];
+    auto XY = cartesianProduct(X, Y);
+    auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4],
+                     [3, 5], [3, 6]];
+
+    // Verify Expected ⊆ XY
+    foreach (pair; Expected)
+    {
+        assert(canFind(XY, tuple(pair[0], pair[1])));
+    }
+
+    // Verify XY ⊆ Expected
+    foreach (pair; XY)
+    {
+        assert(canFind(Expected, [pair[0], pair[1]]));
+    }
+
+    // And therefore, by set comprehension, XY == Expected
+}
+
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.algorithm.comparison : equal;
+    import std.algorithm.iteration : map;
+    import std.typecons : tuple;
+
+    import std.range;
+    auto N = sequence!"n"(0);
+
+    // To force the template to fall to the second case, we wrap N in a struct
+    // that doesn't allow bidirectional access.
+    struct FwdRangeWrapper(R)
+    {
+        R impl;
+
+        // Input range API
+        @property auto front() { return impl.front; }
+        void popFront() { impl.popFront(); }
+        static if (isInfinite!R)
+            enum empty = false;
+        else
+            @property bool empty() { return impl.empty; }
+
+        // Forward range API
+        @property auto save() { return typeof(this)(impl.save); }
+    }
+    auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); }
+
+    // General test: two infinite bidirectional ranges
+    auto N2 = cartesianProduct(N, N);
+
+    assert(canFind(N2, tuple(0, 0)));
+    assert(canFind(N2, tuple(123, 321)));
+    assert(canFind(N2, tuple(11, 35)));
+    assert(canFind(N2, tuple(279, 172)));
+
+    // Test first case: forward range with bidirectional range
+    auto fwdN = fwdWrap(N);
+    auto N2_a = cartesianProduct(fwdN, N);
+
+    assert(canFind(N2_a, tuple(0, 0)));
+    assert(canFind(N2_a, tuple(123, 321)));
+    assert(canFind(N2_a, tuple(11, 35)));
+    assert(canFind(N2_a, tuple(279, 172)));
+
+    // Test second case: bidirectional range with forward range
+    auto N2_b = cartesianProduct(N, fwdN);
+
+    assert(canFind(N2_b, tuple(0, 0)));
+    assert(canFind(N2_b, tuple(123, 321)));
+    assert(canFind(N2_b, tuple(11, 35)));
+    assert(canFind(N2_b, tuple(279, 172)));
+
+    // Test third case: finite forward range with (infinite) input range
+    static struct InpRangeWrapper(R)
+    {
+        R impl;
+
+        // Input range API
+        @property auto front() { return impl.front; }
+        void popFront() { impl.popFront(); }
+        static if (isInfinite!R)
+            enum empty = false;
+        else
+            @property bool empty() { return impl.empty; }
+    }
+    auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); }
+
+    auto inpN = inpWrap(N);
+    auto B = [ 1, 2, 3 ];
+    auto fwdB = fwdWrap(B);
+    auto BN = cartesianProduct(fwdB, inpN);
+
+    assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0],
+                 [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]]));
+
+    // Test fourth case: (infinite) input range with finite forward range
+    auto NB = cartesianProduct(inpN, fwdB);
+
+    assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3],
+                 [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]]));
+
+    // General finite range case
+    auto C = [ 4, 5, 6 ];
+    auto BC = cartesianProduct(B, C);
+
+    foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
+                 [2, 6], [3, 6]])
+    {
+        assert(canFind(BC, tuple(n[0], n[1])));
+    }
+}
+
+// Issue 13091
+pure nothrow @safe @nogc unittest
+{
+    import std.algorithm: cartesianProduct;
+    int[1] a = [1];
+    foreach (t; cartesianProduct(a[], a[])) {}
+}
+
+/// ditto
+auto cartesianProduct(RR...)(RR ranges)
+    if (ranges.length >= 2 &&
+        allSatisfy!(isForwardRange, RR) &&
+        !anySatisfy!(isInfinite, RR))
+{
+    // This overload uses a much less template-heavy implementation when
+    // all ranges are finite forward ranges, which is the most common use
+    // case, so that we don't run out of resources too quickly.
+    //
+    // For infinite ranges or non-forward ranges, we fall back to the old
+    // implementation which expands an exponential number of templates.
+    import std.typecons : tuple;
+
+    static struct Result
+    {
+        RR ranges;
+        RR current;
+        bool empty = true;
+
+        this(RR _ranges)
+        {
+            ranges = _ranges;
+            empty = false;
+            foreach (i, r; ranges)
+            {
+                current[i] = r.save;
+                if (current[i].empty)
+                    empty = true;
+            }
+        }
+        @property auto front()
+        {
+            import std.algorithm.internal : algoFormat;
+            import std.range : iota;
+            return mixin(algoFormat("tuple(%(current[%d].front%|,%))",
+                                    iota(0, current.length)));
+        }
+        void popFront()
+        {
+            foreach_reverse (i, ref r; current)
+            {
+                r.popFront();
+                if (!r.empty) break;
+
+                static if (i==0)
+                    empty = true;
+                else
+                    r = ranges[i].save; // rollover
+            }
+        }
+        @property Result save()
+        {
+            Result copy = this;
+            foreach (i, r; ranges)
+            {
+                copy.ranges[i] = r.save;
+                copy.current[i] = current[i].save;
+            }
+            return copy;
+        }
+    }
+    static assert(isForwardRange!Result);
+
+    return Result(ranges);
+}
+
+@safe unittest
+{
+    // Issue 10693: cartesian product of empty ranges should be empty.
+    int[] a, b, c, d, e;
+    auto cprod = cartesianProduct(a,b,c,d,e);
+    assert(cprod.empty);
+    foreach (_; cprod) {} // should not crash
+
+    // Test case where only one of the ranges is empty: the result should still
+    // be empty.
+    int[] p=[1], q=[];
+    auto cprod2 = cartesianProduct(p,p,p,q,p);
+    assert(cprod2.empty);
+    foreach (_; cprod2) {} // should not crash
+}
+
+@safe unittest
+{
+    // .init value of cartesianProduct should be empty
+    auto cprod = cartesianProduct([0,0], [1,1], [2,2]);
+    assert(!cprod.empty);
+    assert(cprod.init.empty);
+}
+
+@safe unittest
+{
+    // Issue 13393
+    assert(!cartesianProduct([0],[0],[0]).save.empty);
+}
+
+/// ditto
+auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges)
+    if (!allSatisfy!(isForwardRange, R1, R2, RR) ||
+        anySatisfy!(isInfinite, R1, R2, RR))
+{
+    /* We implement the n-ary cartesian product by recursively invoking the
+     * binary cartesian product. To make the resulting range nicer, we denest
+     * one level of tuples so that a ternary cartesian product, for example,
+     * returns 3-element tuples instead of nested 2-element tuples.
+     */
+    import std.algorithm.internal : algoFormat;
+    import std.algorithm.iteration : map;
+    import std.range : iota;
+
+    enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))",
+                                iota(0, otherRanges.length+1));
+    return map!denest(
+        cartesianProduct(range1, cartesianProduct(range2, otherRanges))
+    );
+}
+
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.range;
+    import std.typecons : tuple, Tuple;
+
+    auto N = sequence!"n"(0);
+    auto N3 = cartesianProduct(N, N, N);
+
+    // Check that tuples are properly denested
+    assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t)));
+
+    assert(canFind(N3, tuple(0, 27, 7)));
+    assert(canFind(N3, tuple(50, 23, 71)));
+    assert(canFind(N3, tuple(9, 3, 0)));
+}
+
+@safe unittest
+{
+    import std.algorithm.searching : canFind;
+    import std.range;
+    import std.typecons : tuple, Tuple;
+
+    auto N = sequence!"n"(0);
+    auto N4 = cartesianProduct(N, N, N, N);
+
+    // Check that tuples are properly denested
+    assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t)));
+
+    assert(canFind(N4, tuple(1, 2, 3, 4)));
+    assert(canFind(N4, tuple(4, 3, 2, 1)));
+    assert(canFind(N4, tuple(10, 31, 7, 12)));
+}
+
+// Issue 9878
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.typecons : tuple;
+
+    auto A = [ 1, 2, 3 ];
+    auto B = [ 'a', 'b', 'c' ];
+    auto C = [ "x", "y", "z" ];
+    auto ABC = cartesianProduct(A, B, C);
+
+    assert(ABC.equal([
+        tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"),
+        tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"),
+        tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"),
+        tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"),
+        tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"),
+        tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"),
+        tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"),
+        tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"),
+        tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z")
+    ]));
+}
+
+pure @safe nothrow @nogc unittest
+{
+    int[2] A = [1,2];
+    auto C = cartesianProduct(A[], A[], A[]);
+    assert(isForwardRange!(typeof(C)));
+
+    C.popFront();
+    auto front1 = C.front;
+    auto D = C.save;
+    C.popFront();
+    assert(D.front == front1);
+}
+
+// Issue 13935
+unittest
+{
+    import std.algorithm.iteration : map;
+    auto seq = [1, 2].map!(x => x);
+    foreach (pair; cartesianProduct(seq, seq)) {}
+}
+
+// largestPartialIntersection
+/**
+Given a range of sorted forward ranges $(D ror), copies to $(D tgt)
+the elements that are common to most ranges, along with their number
+of occurrences. All ranges in $(D ror) are assumed to be sorted by $(D
+less). Only the most frequent $(D tgt.length) elements are returned.
+
+Params:
+    less = The predicate the ranges are sorted by.
+    ror = A range of forward ranges sorted by `less`.
+    tgt = The target range to copy common elements to.
+    sorted = Whether the elements copied should be in sorted order.
+
+The function $(D largestPartialIntersection) is useful for
+e.g. searching an $(LUCKY inverted index) for the documents most
+likely to contain some terms of interest. The complexity of the search
+is $(BIGOH n * log(tgt.length)), where $(D n) is the sum of lengths of
+all input ranges. This approach is faster than keeping an associative
+array of the occurrences and then selecting its top items, and also
+requires less memory ($(D largestPartialIntersection) builds its
+result directly in $(D tgt) and requires no extra memory).
+
+Warning: Because $(D largestPartialIntersection) does not allocate
+extra memory, it will leave $(D ror) modified. Namely, $(D
+largestPartialIntersection) assumes ownership of $(D ror) and
+discretionarily swaps and advances elements of it. If you want $(D
+ror) to preserve its contents after the call, you may want to pass a
+duplicate to $(D largestPartialIntersection) (and perhaps cache the
+duplicate in between calls).
+ */
+void largestPartialIntersection
+(alias less = "a < b", RangeOfRanges, Range)
+(RangeOfRanges ror, Range tgt, SortOutput sorted = SortOutput.no)
+{
+    struct UnitWeights
+    {
+        static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; }
+    }
+    return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(),
+            sorted);
+}
+
+///
+unittest
+{
+    import std.typecons : tuple, Tuple;
+
+    // Figure which number can be found in most arrays of the set of
+    // arrays below.
+    double[][] a =
+    [
+        [ 1, 4, 7, 8 ],
+        [ 1, 7 ],
+        [ 1, 7, 8],
+        [ 4 ],
+        [ 7 ],
+    ];
+    auto b = new Tuple!(double, uint)[1];
+    // it will modify the input range, hence we need to create a duplicate
+    largestPartialIntersection(a.dup, b);
+    // First member is the item, second is the occurrence count
+    assert(b[0] == tuple(7.0, 4u));
+    // 7.0 occurs in 4 out of 5 inputs, more than any other number
+
+    // If more of the top-frequent numbers are needed, just create a larger
+    // tgt range
+    auto c = new Tuple!(double, uint)[2];
+    largestPartialIntersection(a, c);
+    assert(c[0] == tuple(1.0, 3u));
+    // 1.0 occurs in 3 inputs
+}
+
+import std.algorithm.sorting : SortOutput; // FIXME
+
+// largestPartialIntersectionWeighted
+/**
+Similar to $(D largestPartialIntersection), but associates a weight
+with each distinct element in the intersection.
+
+Params:
+    less = The predicate the ranges are sorted by.
+    ror = A range of forward ranges sorted by `less`.
+    tgt = The target range to copy common elements to.
+    weights = An associative array mapping elements to weights.
+    sorted = Whether the elements copied should be in sorted order.
+
+*/
+void largestPartialIntersectionWeighted
+(alias less = "a < b", RangeOfRanges, Range, WeightsAA)
+(RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = SortOutput.no)
+{
+    import std.algorithm.iteration : group;
+    import std.algorithm.sorting : topNCopy;
+
+    if (tgt.empty) return;
+    alias InfoType = ElementType!Range;
+    bool heapComp(InfoType a, InfoType b)
+    {
+        return weights[a[0]] * a[1] > weights[b[0]] * b[1];
+    }
+    topNCopy!heapComp(group(nWayUnion!less(ror)), tgt, sorted);
+}
+
+///
+unittest
+{
+    import std.typecons : tuple, Tuple;
+
+    // Figure which number can be found in most arrays of the set of
+    // arrays below, with specific per-element weights
+    double[][] a =
+    [
+        [ 1, 4, 7, 8 ],
+        [ 1, 7 ],
+        [ 1, 7, 8],
+        [ 4 ],
+        [ 7 ],
+    ];
+    auto b = new Tuple!(double, uint)[1];
+    double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
+    largestPartialIntersectionWeighted(a, b, weights);
+    // First member is the item, second is the occurrence count
+    assert(b[0] == tuple(4.0, 2u));
+    // 4.0 occurs 2 times -> 4.6 (2 * 2.3)
+    // 7.0 occurs 3 times -> 4.4 (3 * 1.1)
+}
+
+unittest
+{
+    import std.conv : text;
+    import std.typecons : tuple, Tuple;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    double[][] a =
+        [
+            [ 1, 4, 7, 8 ],
+            [ 1, 7 ],
+            [ 1, 7, 8],
+            [ 4 ],
+            [ 7 ],
+        ];
+    auto b = new Tuple!(double, uint)[2];
+    largestPartialIntersection(a, b, SortOutput.yes);
+    //sort(b);
+    //writeln(b);
+    assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b));
+    assert(a[0].empty);
+}
+
+unittest
+{
+    import std.conv : text;
+    import std.typecons : tuple, Tuple;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    string[][] a =
+        [
+            [ "1", "4", "7", "8" ],
+            [ "1", "7" ],
+            [ "1", "7", "8"],
+            [ "4" ],
+            [ "7" ],
+        ];
+    auto b = new Tuple!(string, uint)[2];
+    largestPartialIntersection(a, b, SortOutput.yes);
+    //writeln(b);
+    assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b));
+}
+
+unittest
+{
+    import std.typecons : tuple, Tuple;
+
+    //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+// Figure which number can be found in most arrays of the set of
+// arrays below, with specific per-element weights
+    double[][] a =
+        [
+            [ 1, 4, 7, 8 ],
+            [ 1, 7 ],
+            [ 1, 7, 8],
+            [ 4 ],
+            [ 7 ],
+            ];
+    auto b = new Tuple!(double, uint)[1];
+    double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
+    largestPartialIntersectionWeighted(a, b, weights);
+// First member is the item, second is the occurrence count
+    //writeln(b[0]);
+    assert(b[0] == tuple(4.0, 2u));
+}
+
+unittest
+{
+    import std.container : Array;
+    import std.typecons : Tuple;
+
+    alias T = Tuple!(uint, uint);
+    const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] );
+    const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] );
+
+    assert(arrayOne == arrayTwo);
+}
+
+// NWayUnion
+/**
+Computes the union of multiple sets. The input sets are passed as a
+range of ranges and each is assumed to be sorted by $(D
+less). Computation is done lazily, one union element at a time. The
+complexity of one $(D popFront) operation is $(BIGOH
+log(ror.length)). However, the length of $(D ror) decreases as ranges
+in it are exhausted, so the complexity of a full pass through $(D
+NWayUnion) is dependent on the distribution of the lengths of ranges
+contained within $(D ror). If all ranges have the same length $(D n)
+(worst case scenario), the complexity of a full pass through $(D
+NWayUnion) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D
+log(ror.length)) times worse than just spanning all ranges in
+turn. The output comes sorted (unstably) by $(D less).
+
+Params:
+    less = Predicate the given ranges are sorted by.
+    ror = A range of ranges sorted by `less` to compute the union for.
+
+Returns:
+    A range of the union of the ranges in `ror`.
+
+Warning: Because $(D NWayUnion) does not allocate extra memory, it
+will leave $(D ror) modified. Namely, $(D NWayUnion) assumes ownership
+of $(D ror) and discretionarily swaps and advances elements of it. If
+you want $(D ror) to preserve its contents after the call, you may
+want to pass a duplicate to $(D NWayUnion) (and perhaps cache the
+duplicate in between calls).
+ */
+struct NWayUnion(alias less, RangeOfRanges)
+{
+    import std.container : BinaryHeap;
+
+    private alias ElementType = .ElementType!(.ElementType!RangeOfRanges);
+    private alias comp = binaryFun!less;
+    private RangeOfRanges _ror;
+    static bool compFront(.ElementType!RangeOfRanges a,
+            .ElementType!RangeOfRanges b)
+    {
+        // revert comparison order so we get the smallest elements first
+        return comp(b.front, a.front);
+    }
+    BinaryHeap!(RangeOfRanges, compFront) _heap;
+
+    this(RangeOfRanges ror)
+    {
+        import std.algorithm.mutation : remove, SwapStrategy;
+
+        // Preemptively get rid of all empty ranges in the input
+        // No need for stability either
+        _ror = remove!("a.empty", SwapStrategy.unstable)(ror);
+        //Build the heap across the range
+        _heap.acquire(_ror);
+    }
+
+    @property bool empty() { return _ror.empty; }
+
+    @property auto ref front()
+    {
+        return _heap.front.front;
+    }
+
+    void popFront()
+    {
+        _heap.removeFront();
+        // let's look at the guy just popped
+        _ror.back.popFront();
+        if (_ror.back.empty)
+        {
+            _ror.popBack();
+            // nothing else to do: the empty range is not in the
+            // heap and not in _ror
+            return;
+        }
+        // Put the popped range back in the heap
+        _heap.conditionalInsert(_ror.back) || assert(false);
+    }
+}
+
+/// Ditto
+NWayUnion!(less, RangeOfRanges) nWayUnion
+(alias less = "a < b", RangeOfRanges)
+(RangeOfRanges ror)
+{
+    return typeof(return)(ror);
+}
+
+///
+unittest
+{
+    import std.algorithm.comparison : equal;
+
+    double[][] a =
+    [
+        [ 1, 4, 7, 8 ],
+        [ 1, 7 ],
+        [ 1, 7, 8],
+        [ 4 ],
+        [ 7 ],
+    ];
+    auto witness = [
+        1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8
+    ];
+    assert(equal(nWayUnion(a), witness));
+}
+
+/**
+Lazily computes the difference of $(D r1) and $(D r2). The two ranges
+are assumed to be sorted by $(D less). The element types of the two
+ranges must have a common type.
+
+Params:
+    less = Predicate the given ranges are sorted by.
+    r1 = The first range.
+    r2 = The range to subtract from `r1`.
+
+Returns:
+    A range of the difference of `r1` and `r2`.
+
+See_also: $(LREF setSymmetricDifference)
+ */
+struct SetDifference(alias less = "a < b", R1, R2)
+    if (isInputRange!(R1) && isInputRange!(R2))
+{
+private:
+    R1 r1;
+    R2 r2;
+    alias comp = binaryFun!(less);
+
+    void adjustPosition()
+    {
+        while (!r1.empty)
+        {
+            if (r2.empty || comp(r1.front, r2.front)) break;
+            if (comp(r2.front, r1.front))
+            {
+                r2.popFront();
+            }
+            else
+            {
+                // both are equal
+                r1.popFront();
+                r2.popFront();
+            }
+        }
+    }
+
+public:
+    this(R1 r1, R2 r2)
+    {
+        this.r1 = r1;
+        this.r2 = r2;
+        // position to the first element
+        adjustPosition();
+    }
+
+    void popFront()
+    {
+        r1.popFront();
+        adjustPosition();
+    }
+
+    @property auto ref front()
+    {
+        assert(!empty);
+        return r1.front;
+    }
+
+    static if (isForwardRange!R1 && isForwardRange!R2)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret.r1 = r1.save;
+            ret.r2 = r2.save;
+            return ret;
+        }
+    }
+
+    @property bool empty() { return r1.empty; }
+}
+
+/// Ditto
+SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2)
+(R1 r1, R2 r2)
+{
+    return typeof(return)(r1, r2);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [ 1, 2, 4, 5, 7, 9 ];
+    int[] b = [ 0, 1, 2, 4, 7, 8 ];
+    assert(equal(setDifference(a, b), [5, 9][]));
+    static assert(isForwardRange!(typeof(setDifference(a, b))));
+}
+
+@safe unittest // Issue 10460
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [1, 2, 3, 4, 5];
+    int[] b = [2, 4];
+    foreach (ref e; setDifference(a, b))
+        e = 0;
+    assert(equal(a, [0, 2, 0, 4, 0]));
+}
+
+/**
+Lazily computes the intersection of two or more input ranges $(D
+ranges). The ranges are assumed to be sorted by $(D less). The element
+types of the ranges must have a common type.
+
+Params:
+    less = Predicate the given ranges are sorted by.
+    ranges = The ranges to compute the intersection for.
+
+Returns:
+    A range containing the intersection of the given ranges.
+ */
+struct SetIntersection(alias less = "a < b", Rs...)
+    if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
+        !is(CommonType!(staticMap!(ElementType, Rs)) == void))
+{
+private:
+    Rs _input;
+    alias comp = binaryFun!less;
+    alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
+
+    // Positions to the first elements that are all equal
+    void adjustPosition()
+    {
+        if (empty) return;
+
+        size_t done = Rs.length;
+        static if (Rs.length > 1) while (true)
+        {
+            foreach (i, ref r; _input)
+            {
+                alias next = _input[(i + 1) % Rs.length];
+
+                if (comp(next.front, r.front))
+                {
+                    do {
+                        next.popFront();
+                        if (next.empty) return;
+                    } while(comp(next.front, r.front));
+                    done = Rs.length;
+                }
+                if (--done == 0) return;
+            }
+        }
+    }
+
+public:
+    this(Rs input)
+    {
+        this._input = input;
+        // position to the first element
+        adjustPosition();
+    }
+
+    @property bool empty()
+    {
+        foreach (ref r; _input)
+        {
+            if (r.empty) return true;
+        }
+        return false;
+    }
+
+    void popFront()
+    {
+        assert(!empty);
+        static if (Rs.length > 1) foreach (i, ref r; _input)
+        {
+            alias next = _input[(i + 1) % Rs.length];
+            assert(!comp(r.front, next.front));
+        }
+
+        foreach (ref r; _input)
+        {
+            r.popFront();
+        }
+        adjustPosition();
+    }
+
+    @property ElementType front()
+    {
+        assert(!empty);
+        return _input[0].front;
+    }
+
+    static if (allSatisfy!(isForwardRange, Rs))
+    {
+        @property SetIntersection save()
+        {
+            auto ret = this;
+            foreach (i, ref r; _input)
+            {
+                ret._input[i] = r.save;
+            }
+            return ret;
+        }
+    }
+}
+
+/// Ditto
+SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges)
+    if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
+        !is(CommonType!(staticMap!(ElementType, Rs)) == void))
+{
+    return typeof(return)(ranges);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [ 1, 2, 4, 5, 7, 9 ];
+    int[] b = [ 0, 1, 2, 4, 7, 8 ];
+    int[] c = [ 0, 1, 4, 5, 7, 8 ];
+    assert(equal(setIntersection(a, a), a));
+    assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
+    assert(equal(setIntersection(a, b, c), [1, 4, 7]));
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.algorithm.iteration : filter;
+
+    int[] a = [ 1, 2, 4, 5, 7, 9 ];
+    int[] b = [ 0, 1, 2, 4, 7, 8 ];
+    int[] c = [ 0, 1, 4, 5, 7, 8 ];
+    int[] d = [ 1, 3, 4 ];
+    int[] e = [ 4, 5 ];
+
+    assert(equal(setIntersection(a, a), a));
+    assert(equal(setIntersection(a, a, a), a));
+    assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
+    assert(equal(setIntersection(a, b, c), [1, 4, 7]));
+    assert(equal(setIntersection(a, b, c, d), [1, 4]));
+    assert(equal(setIntersection(a, b, c, d, e), [4]));
+
+    auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true);
+    auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true);
+    assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4]));
+
+    assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7]));
+    assert(equal(setIntersection(a, c, b), [1, 4, 7]));
+    assert(equal(setIntersection(b, a, c), [1, 4, 7]));
+    assert(equal(setIntersection(b, c, a), [1, 4, 7]));
+    assert(equal(setIntersection(c, a, b), [1, 4, 7]));
+    assert(equal(setIntersection(c, b, a), [1, 4, 7]));
+}
+
+/**
+Lazily computes the symmetric difference of $(D r1) and $(D r2),
+i.e. the elements that are present in exactly one of $(D r1) and $(D
+r2). The two ranges are assumed to be sorted by $(D less), and the
+output is also sorted by $(D less). The element types of the two
+ranges must have a common type.
+
+If both arguments are ranges of L-values of the same type then
+$(D SetSymmetricDifference) will also be a range of L-values of
+that type.
+
+Params:
+    less = Predicate the given ranges are sorted by.
+    r1 = The first range.
+    r2 = The second range.
+
+Returns:
+    A range of the symmetric difference between `r1` and `r2`.
+
+See_also: $(LREF setDifference)
+ */
+struct SetSymmetricDifference(alias less = "a < b", R1, R2)
+    if (isInputRange!(R1) && isInputRange!(R2))
+{
+private:
+    R1 r1;
+    R2 r2;
+    //bool usingR2;
+    alias comp = binaryFun!(less);
+
+    void adjustPosition()
+    {
+        while (!r1.empty && !r2.empty)
+        {
+            if (comp(r1.front, r2.front) || comp(r2.front, r1.front))
+            {
+                break;
+            }
+            // equal, pop both
+            r1.popFront();
+            r2.popFront();
+        }
+    }
+
+public:
+    this(R1 r1, R2 r2)
+    {
+        this.r1 = r1;
+        this.r2 = r2;
+        // position to the first element
+        adjustPosition();
+    }
+
+    void popFront()
+    {
+        assert(!empty);
+        if (r1.empty) r2.popFront();
+        else if (r2.empty) r1.popFront();
+        else
+        {
+            // neither is empty
+            if (comp(r1.front, r2.front))
+            {
+                r1.popFront();
+            }
+            else
+            {
+                assert(comp(r2.front, r1.front));
+                r2.popFront();
+            }
+        }
+        adjustPosition();
+    }
+
+    @property auto ref front()
+    {
+        assert(!empty);
+        bool chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front);
+        assert(chooseR1 || r1.empty || comp(r2.front, r1.front));
+        return chooseR1 ? r1.front : r2.front;
+    }
+
+    static if (isForwardRange!R1 && isForwardRange!R2)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret.r1 = r1.save;
+            ret.r2 = r2.save;
+            return ret;
+        }
+    }
+
+    ref auto opSlice() { return this; }
+
+    @property bool empty() { return r1.empty && r2.empty; }
+}
+
+/// Ditto
+SetSymmetricDifference!(less, R1, R2)
+setSymmetricDifference(alias less = "a < b", R1, R2)
+(R1 r1, R2 r2)
+{
+    return typeof(return)(r1, r2);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [ 1, 2, 4, 5, 7, 9 ];
+    int[] b = [ 0, 1, 2, 4, 7, 8 ];
+    assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][]));
+    static assert(isForwardRange!(typeof(setSymmetricDifference(a, b))));
+}
+
+@safe unittest // Issue 10460
+{
+    int[] a = [1, 2];
+    double[] b = [2.0, 3.0];
+    int[] c = [2, 3];
+
+    alias R1 = typeof(setSymmetricDifference(a, b));
+    static assert(is(ElementType!R1 == double));
+    static assert(!hasLvalueElements!R1);
+
+    alias R2 = typeof(setSymmetricDifference(a, c));
+    static assert(is(ElementType!R2 == int));
+    static assert(hasLvalueElements!R2);
+}
+
+/**
+Lazily computes the union of two or more ranges $(D rs). The ranges
+are assumed to be sorted by $(D less). Elements in the output are not
+unique; the length of the output is the sum of the lengths of the
+inputs. (The $(D length) member is offered if all ranges also have
+length.) The element types of all ranges must have a common type.
+
+Params:
+    less = Predicate the given ranges are sorted by.
+    rs = The ranges to compute the union for.
+
+Returns:
+    A range containing the union of the given ranges.
+ */
+struct SetUnion(alias less = "a < b", Rs...) if (allSatisfy!(isInputRange, Rs))
+{
+private:
+    Rs _r;
+    alias comp = binaryFun!(less);
+    uint _crt;
+
+    void adjustPosition(uint candidate = 0)()
+    {
+        static if (candidate == Rs.length)
+        {
+            _crt = _crt.max;
+        }
+        else
+        {
+            if (_r[candidate].empty)
+            {
+                adjustPosition!(candidate + 1)();
+                return;
+            }
+            foreach (i, U; Rs[candidate + 1 .. $])
+            {
+                enum j = candidate + i + 1;
+                if (_r[j].empty) continue;
+                if (comp(_r[j].front, _r[candidate].front))
+                {
+                    // a new candidate was found
+                    adjustPosition!(j)();
+                    return;
+                }
+            }
+            // Found a successful candidate
+            _crt = candidate;
+        }
+    }
+
+public:
+    alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
+    static assert(!is(CommonType!(staticMap!(.ElementType, Rs)) == void),
+        typeof(this).stringof ~ ": incompatible element types.");
+
+    this(Rs rs)
+    {
+        this._r = rs;
+        adjustPosition();
+    }
+
+    @property bool empty()
+    {
+        return _crt == _crt.max;
+    }
+
+    void popFront()
+    {
+        // Assumes _crt is correct
+        assert(!empty);
+        foreach (i, U; Rs)
+        {
+            if (i < _crt) continue;
+            // found _crt
+            assert(!_r[i].empty);
+            _r[i].popFront();
+            adjustPosition();
+            return;
+        }
+        assert(false);
+    }
+
+    @property auto ref ElementType front()
+    {
+        assert(!empty);
+        // Assume _crt is correct
+        foreach (i, U; Rs)
+        {
+            if (i < _crt) continue;
+            assert(!_r[i].empty);
+            return _r[i].front;
+        }
+        assert(false);
+    }
+
+    static if (allSatisfy!(isForwardRange, Rs))
+    {
+        @property auto save()
+        {
+            auto ret = this;
+            foreach (ti, elem; _r)
+            {
+                ret._r[ti] = elem.save;
+            }
+            return ret;
+        }
+    }
+
+    static if (allSatisfy!(hasLength, Rs))
+    {
+        @property size_t length()
+        {
+            size_t result;
+            foreach (i, U; Rs)
+            {
+                result += _r[i].length;
+            }
+            return result;
+        }
+
+        alias opDollar = length;
+    }
+}
+
+/// Ditto
+SetUnion!(less, Rs) setUnion(alias less = "a < b", Rs...)
+(Rs rs)
+{
+    return typeof(return)(rs);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    int[] a = [ 1, 2, 4, 5, 7, 9 ];
+    int[] b = [ 0, 1, 2, 4, 7, 8 ];
+    double[] c = [ 10.5 ];
+
+    static assert(isForwardRange!(typeof(setUnion(a, b))));
+    assert(setUnion(a, b).length == a.length + b.length);
+    assert(equal(setUnion(a, b), [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][]));
+    assert(equal(setUnion(a, c, b),
+                    [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9, 10.5][]));
+    auto u = setUnion(a, b);
+    u.front--;
+    assert(equal(u, [-1, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][]));
+}
+
diff --git a/libphobos/src/std/algorithm/sorting.d b/libphobos/src/std/algorithm/sorting.d
new file mode 100644
index 00000000000..67ffc6eac98
--- /dev/null
+++ b/libphobos/src/std/algorithm/sorting.d
@@ -0,0 +1,3077 @@
+// Written in the D programming language.
+/**
+This is a submodule of $(LINK2 std_algorithm.html, std.algorithm).
+It contains generic _sorting algorithms.
+
+$(BOOKTABLE Cheat Sheet,
+
+$(TR $(TH Function Name) $(TH Description))
+
+$(T2 completeSort,
+        If $(D a = [10, 20, 30]) and $(D b = [40, 6, 15]), then
+        $(D completeSort(a, b)) leaves $(D a = [6, 10, 15]) and $(D b = [20,
+        30, 40]).
+        The range $(D a) must be sorted prior to the call, and as a result the
+        combination $(D $(XREF range,chain)(a, b)) is sorted.)
+$(T2 isPartitioned,
+        $(D isPartitioned!"a < 0"([-1, -2, 1, 0, 2])) returns $(D true) because
+        the predicate is $(D true) for a portion of the range and $(D false)
+        afterwards.)
+$(T2 isSorted,
+        $(D isSorted([1, 1, 2, 3])) returns $(D true).)
+$(T2 ordered,
+        $(D ordered(1, 1, 2, 3)) returns $(D true).)
+$(T2 strictlyOrdered,
+        $(D strictlyOrdered(1, 1, 2, 3)) returns $(D false).)
+$(T2 makeIndex,
+        Creates a separate index for a range.)
+$(T2 multiSort,
+        Sorts by multiple keys.)
+$(T2 nextEvenPermutation,
+        Computes the next lexicographically greater even permutation of a range
+        in-place.)
+$(T2 nextPermutation,
+        Computes the next lexicographically greater permutation of a range
+        in-place.)
+$(T2 partialSort,
+        If $(D a = [5, 4, 3, 2, 1]), then $(D partialSort(a, 3)) leaves
+        $(D a[0 .. 3] = [1, 2, 3]).
+        The other elements of $(D a) are left in an unspecified order.)
+$(T2 partition,
+        Partitions a range according to a predicate.)
+$(T2 partition3,
+        Partitions a range in three parts (less than, equal, greater than the
+        given pivot).)
+$(T2 schwartzSort,
+        Sorts with the help of the $(LUCKY Schwartzian transform).)
+$(T2 sort,
+        Sorts.)
+$(T2 topN,
+        Separates the top elements in a range.)
+$(T2 topNCopy,
+        Copies out the top elements of a range.)
+$(T2 topNIndex,
+        Builds an index of the top elements of a range.)
+)
+
+Copyright: Andrei Alexandrescu 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/algorithm/_sorting.d)
+
+Macros:
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+ */
+module std.algorithm.sorting;
+
+import std.algorithm.mutation : SwapStrategy;
+import std.functional; // : unaryFun, binaryFun;
+import std.range.primitives;
+// FIXME
+import std.range; // : SortedRange;
+import std.traits;
+
+/**
+Specifies whether the output of certain algorithm is desired in sorted
+format.
+ */
+enum SortOutput
+{
+    no,  /// Don't sort output
+    yes, /// Sort output
+}
+
+// completeSort
+/**
+Sorts the random-access range $(D chain(lhs, rhs)) according to
+predicate $(D less). The left-hand side of the range $(D lhs) is
+assumed to be already sorted; $(D rhs) is assumed to be unsorted. The
+exact strategy chosen depends on the relative sizes of $(D lhs) and
+$(D rhs).  Performs $(BIGOH lhs.length + rhs.length * log(rhs.length))
+(best case) to $(BIGOH (lhs.length + rhs.length) * log(lhs.length +
+rhs.length)) (worst-case) evaluations of $(D swap).
+
+Params:
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    lhs = The sorted, left-hand side of the random access range to be sorted.
+    rhs = The unsorted, right-hand side of the random access range to be
+        sorted.
+*/
+void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
+        Range1, Range2)(SortedRange!(Range1, less) lhs, Range2 rhs)
+if (hasLength!(Range2) && hasSlicing!(Range2))
+{
+    import std.algorithm : bringToFront; // FIXME
+    import std.range : chain, assumeSorted;
+    // Probably this algorithm can be optimized by using in-place
+    // merge
+    auto lhsOriginal = lhs.release();
+    foreach (i; 0 .. rhs.length)
+    {
+        auto sortedSoFar = chain(lhsOriginal, rhs[0 .. i]);
+        auto ub = assumeSorted!less(sortedSoFar).upperBound(rhs[i]);
+        if (!ub.length) continue;
+        bringToFront(ub.release(), rhs[i .. i + 1]);
+    }
+}
+
+///
+unittest
+{
+    import std.range : assumeSorted;
+    int[] a = [ 1, 2, 3 ];
+    int[] b = [ 4, 0, 6, 5 ];
+    completeSort(assumeSorted(a), b);
+    assert(a == [ 0, 1, 2 ]);
+    assert(b == [ 3, 4, 5, 6 ]);
+}
+
+// isSorted
+/**
+Checks whether a forward range is sorted according to the comparison
+operation $(D less). Performs $(BIGOH r.length) evaluations of $(D
+less).
+
+Params:
+    less = Predicate the range should be sorted by.
+    r = Forward range to check for sortedness.
+
+Returns: true if the range is sorted, false otherwise.
+*/
+bool isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!(Range))
+{
+    if (r.empty) return true;
+
+    static if (isRandomAccessRange!Range && hasLength!Range)
+    {
+        immutable limit = r.length - 1;
+        foreach (i; 0 .. limit)
+        {
+            if (!binaryFun!less(r[i + 1], r[i])) continue;
+            assert(
+                !binaryFun!less(r[i], r[i + 1]),
+                "Predicate for isSorted is not antisymmetric. Both" ~
+                        " pred(a, b) and pred(b, a) are true for certain values.");
+            return false;
+        }
+    }
+    else
+    {
+        auto ahead = r;
+        ahead.popFront();
+        size_t i;
+
+        for (; !ahead.empty; ahead.popFront(), r.popFront(), ++i)
+        {
+            if (!binaryFun!less(ahead.front, r.front)) continue;
+            // Check for antisymmetric predicate
+            assert(
+                !binaryFun!less(r.front, ahead.front),
+                "Predicate for isSorted is not antisymmetric. Both" ~
+                        " pred(a, b) and pred(b, a) are true for certain values.");
+            return false;
+        }
+    }
+    return true;
+}
+
+///
+@safe unittest
+{
+    int[] arr = [4, 3, 2, 1];
+    assert(!isSorted(arr));
+    sort(arr);
+    assert(isSorted(arr));
+    sort!("a > b")(arr);
+    assert(isSorted!("a > b")(arr));
+}
+
+@safe unittest
+{
+    import std.conv : to;
+
+    // Issue 9457
+    auto x = "abcd";
+    assert(isSorted(x));
+    auto y = "acbd";
+    assert(!isSorted(y));
+
+    int[] a = [1, 2, 3];
+    assert(isSorted(a));
+    int[] b = [1, 3, 2];
+    assert(!isSorted(b));
+
+    dchar[] ds = "コーヒーが好きです"d.dup;
+    sort(ds);
+    string s = to!string(ds);
+    assert(isSorted(ds));  // random-access
+    assert(isSorted(s));   // bidirectional
+}
+
+/**
+Like $(D isSorted), returns $(D true) if the given $(D values) are ordered
+according to the comparison operation $(D less). Unlike $(D isSorted), takes values
+directly instead of structured in a range.
+
+$(D ordered) allows repeated values, e.g. $(D ordered(1, 1, 2)) is $(D true). To verify
+that the values are ordered strictly monotonically, use $(D strictlyOrdered);
+$(D strictlyOrdered(1, 1, 2)) is $(D false).
+
+With either function, the predicate must be a strict ordering just like with $(D isSorted). For
+example, using $(D "a <= b") instead of $(D "a < b") is incorrect and will cause failed
+assertions.
+
+Params:
+    values = The tested value
+    less = The comparison predicate
+
+Returns:
+    $(D true) if the values are ordered; $(D ordered) allows for duplicates,
+    $(D strictlyOrdered) does not.
+*/
+
+bool ordered(alias less = "a < b", T...)(T values)
+if ((T.length == 2 && is(typeof(binaryFun!less(values[1], values[0])) : bool))
+    ||
+    (T.length > 2 && is(typeof(ordered!less(values[0 .. 1 + $ / 2])))
+        && is(typeof(ordered!less(values[$ / 2 .. $]))))
+    )
+{
+    foreach (i, _; T[0 .. $ - 1])
+    {
+        if (binaryFun!less(values[i + 1], values[i]))
+        {
+            assert(!binaryFun!less(values[i], values[i + 1]),
+                __FUNCTION__ ~ ": incorrect non-strict predicate.");
+            return false;
+        }
+    }
+    return true;
+}
+
+/// ditto
+bool strictlyOrdered(alias less = "a < b", T...)(T values)
+if (is(typeof(ordered!less(values))))
+{
+    foreach (i, _; T[0 .. $ - 1])
+    {
+        if (!binaryFun!less(values[i], values[i + 1]))
+        {
+            return false;
+        }
+        assert(!binaryFun!less(values[i + 1], values[i]),
+            __FUNCTION__ ~ ": incorrect non-strict predicate.");
+    }
+    return true;
+}
+
+///
+unittest
+{
+    assert(ordered(42, 42, 43));
+    assert(!strictlyOrdered(43, 42, 45));
+    assert(ordered(42, 42, 43));
+    assert(!strictlyOrdered(42, 42, 43));
+    assert(!ordered(43, 42, 45));
+    // Ordered lexicographically
+    assert(ordered("Jane", "Jim", "Joe"));
+    assert(strictlyOrdered("Jane", "Jim", "Joe"));
+    // Incidentally also ordered by length decreasing
+    assert(ordered!((a, b) => a.length > b.length)("Jane", "Jim", "Joe"));
+    // ... but not strictly so: "Jim" and "Joe" have the same length
+    assert(!strictlyOrdered!((a, b) => a.length > b.length)("Jane", "Jim", "Joe"));
+}
+
+// partition
+/**
+Partitions a range in two using the given $(D predicate).
+Specifically, reorders the range $(D r = [left, right$(RPAREN)) using $(D swap)
+such that all elements $(D i) for which $(D predicate(i)) is $(D true) come
+before all elements $(D j) for which $(D predicate(j)) returns $(D false).
+
+Performs $(BIGOH r.length) (if unstable or semistable) or $(BIGOH
+r.length * log(r.length)) (if stable) evaluations of $(D less) and $(D
+swap). The unstable version computes the minimum possible evaluations
+of $(D swap) (roughly half of those performed by the semistable
+version).
+
+Params:
+    predicate = The predicate to partition by.
+    ss = The swapping strategy to employ.
+    r = The random-access range to partition.
+
+Returns:
+
+The right part of $(D r) after partitioning.
+
+If $(D ss == SwapStrategy.stable), $(D partition) preserves the relative
+ordering of all elements $(D a), $(D b) in $(D r) for which $(D predicate(a) ==
+predicate(b)). If $(D ss == SwapStrategy.semistable), $(D partition) preserves
+the relative ordering of all elements $(D a), $(D b) in the left part of $(D r)
+for which $(D predicate(a) == predicate(b)).
+
+See_Also:
+    STL's $(WEB sgi.com/tech/stl/_partition.html, _partition)$(BR)
+    STL's $(WEB sgi.com/tech/stl/stable_partition.html, stable_partition)
+*/
+Range partition(alias predicate,
+        SwapStrategy ss = SwapStrategy.unstable, Range)(Range r)
+    if ((ss == SwapStrategy.stable && isRandomAccessRange!(Range))
+            || (ss != SwapStrategy.stable && isForwardRange!(Range)))
+{
+    import std.algorithm : bringToFront, swap; // FIXME;
+    alias pred = unaryFun!(predicate);
+    if (r.empty) return r;
+    static if (ss == SwapStrategy.stable)
+    {
+        if (r.length == 1)
+        {
+            if (pred(r.front)) r.popFront();
+            return r;
+        }
+        const middle = r.length / 2;
+        alias recurse = .partition!(pred, ss, Range);
+        auto lower = recurse(r[0 .. middle]);
+        auto upper = recurse(r[middle .. $]);
+        bringToFront(lower, r[middle .. r.length - upper.length]);
+        return r[r.length - lower.length - upper.length .. r.length];
+    }
+    else static if (ss == SwapStrategy.semistable)
+    {
+        for (; !r.empty; r.popFront())
+        {
+            // skip the initial portion of "correct" elements
+            if (pred(r.front)) continue;
+            // hit the first "bad" element
+            auto result = r;
+            for (r.popFront(); !r.empty; r.popFront())
+            {
+                if (!pred(r.front)) continue;
+                swap(result.front, r.front);
+                result.popFront();
+            }
+            return result;
+        }
+        return r;
+    }
+    else // ss == SwapStrategy.unstable
+    {
+        // Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf,
+        // section "Bidirectional Partition Algorithm (Hoare)"
+        static if (isDynamicArray!Range)
+        {
+            // For dynamic arrays prefer index-based manipulation
+            if (!r.length) return r;
+            size_t lo = 0, hi = r.length - 1;
+            for (;;)
+            {
+                for (;;)
+                {
+                    if (lo > hi) return r[lo .. $];
+                    if (!pred(r[lo])) break;
+                    ++lo;
+                }
+                // found the left bound
+                assert(lo <= hi);
+                for (;;)
+                {
+                    if (lo == hi) return r[lo .. $];
+                    if (pred(r[hi])) break;
+                    --hi;
+                }
+                // found the right bound, swap & make progress
+                swap(r[lo++], r[hi--]);
+            }
+        }
+        else
+        {
+            auto result = r;
+            for (;;)
+            {
+                for (;;)
+                {
+                    if (r.empty) return result;
+                    if (!pred(r.front)) break;
+                    r.popFront();
+                    result.popFront();
+                }
+                // found the left bound
+                assert(!r.empty);
+                for (;;)
+                {
+                    if (pred(r.back)) break;
+                    r.popBack();
+                    if (r.empty) return result;
+                }
+                // found the right bound, swap & make progress
+                static if (is(typeof(swap(r.front, r.back))))
+                {
+                    swap(r.front, r.back);
+                }
+                else
+                {
+                    auto t1 = moveFront(r), t2 = moveBack(r);
+                    r.front = t2;
+                    r.back = t1;
+                }
+                r.popFront();
+                result.popFront();
+                r.popBack();
+            }
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : count, find; // FIXME
+    import std.conv : text;
+
+    auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    auto arr = Arr.dup;
+    static bool even(int a) { return (a & 1) == 0; }
+    // Partition arr such that even numbers come first
+    auto r = partition!(even)(arr);
+    // Now arr is separated in evens and odds.
+    // Numbers may have become shuffled due to instability
+    assert(r == arr[5 .. $]);
+    assert(count!(even)(arr[0 .. 5]) == 5);
+    assert(find!(even)(r).empty);
+
+    // Can also specify the predicate as a string.
+    // Use 'a' as the predicate argument name
+    arr[] = Arr[];
+    r = partition!(q{(a & 1) == 0})(arr);
+    assert(r == arr[5 .. $]);
+
+    // Now for a stable partition:
+    arr[] = Arr[];
+    r = partition!(q{(a & 1) == 0}, SwapStrategy.stable)(arr);
+    // Now arr is [2 4 6 8 10 1 3 5 7 9], and r points to 1
+    assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && r == arr[5 .. $]);
+
+    // In case the predicate needs to hold its own state, use a delegate:
+    arr[] = Arr[];
+    int x = 3;
+    // Put stuff greater than 3 on the left
+    bool fun(int a) { return a > x; }
+    r = partition!(fun, SwapStrategy.semistable)(arr);
+    // Now arr is [4 5 6 7 8 9 10 2 3 1] and r points to 2
+    assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && r == arr[7 .. $]);
+}
+
+@safe unittest
+{
+    import std.algorithm.internal : rndstuff;
+    static bool even(int a) { return (a & 1) == 0; }
+
+    // test with random data
+    auto a = rndstuff!int();
+    partition!even(a);
+    assert(isPartitioned!even(a));
+    auto b = rndstuff!string();
+    partition!`a.length < 5`(b);
+    assert(isPartitioned!`a.length < 5`(b));
+}
+
+/**
+Params:
+    pred = The predicate that the range should be partitioned by.
+    r = The range to check.
+Returns: $(D true) if $(D r) is partitioned according to predicate $(D pred).
+ */
+bool isPartitioned(alias pred, Range)(Range r)
+    if (isForwardRange!(Range))
+{
+    for (; !r.empty; r.popFront())
+    {
+        if (unaryFun!(pred)(r.front)) continue;
+        for (r.popFront(); !r.empty; r.popFront())
+        {
+            if (unaryFun!(pred)(r.front)) return false;
+        }
+        break;
+    }
+    return true;
+}
+
+///
+@safe unittest
+{
+    int[] r = [ 1, 3, 5, 7, 8, 2, 4, ];
+    assert(isPartitioned!"a & 1"(r));
+}
+
+// partition3
+/**
+Rearranges elements in $(D r) in three adjacent ranges and returns
+them. The first and leftmost range only contains elements in $(D r)
+less than $(D pivot). The second and middle range only contains
+elements in $(D r) that are equal to $(D pivot). Finally, the third
+and rightmost range only contains elements in $(D r) that are greater
+than $(D pivot). The less-than test is defined by the binary function
+$(D less).
+
+Params:
+    less = The predicate to use for the rearrangement.
+    ss = The swapping strategy to use.
+    r = The random-access range to rearrange.
+    pivot = The pivot element.
+
+Returns:
+    A $(XREF typecons,Tuple) of the three resulting ranges. These ranges are
+    slices of the original range.
+
+BUGS: stable $(D partition3) has not been implemented yet.
+ */
+auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E)
+(Range r, E pivot)
+if (ss == SwapStrategy.unstable && isRandomAccessRange!Range
+        && hasSwappableElements!Range && hasLength!Range
+        && is(typeof(binaryFun!less(r.front, pivot)) == bool)
+        && is(typeof(binaryFun!less(pivot, r.front)) == bool)
+        && is(typeof(binaryFun!less(r.front, r.front)) == bool))
+{
+    // The algorithm is described in "Engineering a sort function" by
+    // Jon Bentley et al, pp 1257.
+
+    import std.algorithm : swap, swapRanges; // FIXME
+    import std.algorithm.comparison : min;
+    import std.typecons : tuple;
+
+    alias lessFun = binaryFun!less;
+    size_t i, j, k = r.length, l = k;
+
+ bigloop:
+    for (;;)
+    {
+        for (;; ++j)
+        {
+            if (j == k) break bigloop;
+            assert(j < r.length);
+            if (lessFun(r[j], pivot)) continue;
+            if (lessFun(pivot, r[j])) break;
+            swap(r[i++], r[j]);
+        }
+        assert(j < k);
+        for (;;)
+        {
+            assert(k > 0);
+            if (!lessFun(pivot, r[--k]))
+            {
+                if (lessFun(r[k], pivot)) break;
+                swap(r[k], r[--l]);
+            }
+            if (j == k) break bigloop;
+        }
+        // Here we know r[j] > pivot && r[k] < pivot
+        swap(r[j++], r[k]);
+    }
+
+    // Swap the equal ranges from the extremes into the middle
+    auto strictlyLess = j - i, strictlyGreater = l - k;
+    auto swapLen = min(i, strictlyLess);
+    swapRanges(r[0 .. swapLen], r[j - swapLen .. j]);
+    swapLen = min(r.length - l, strictlyGreater);
+    swapRanges(r[k .. k + swapLen], r[r.length - swapLen .. r.length]);
+    return tuple(r[0 .. strictlyLess],
+            r[strictlyLess .. r.length - strictlyGreater],
+            r[r.length - strictlyGreater .. r.length]);
+}
+
+///
+@safe unittest
+{
+    auto a = [ 8, 3, 4, 1, 4, 7, 4 ];
+    auto pieces = partition3(a, 4);
+    assert(pieces[0] == [ 1, 3 ]);
+    assert(pieces[1] == [ 4, 4, 4 ]);
+    assert(pieces[2] == [ 8, 7 ]);
+}
+
+@safe unittest
+{
+    import std.random : uniform;
+
+    auto a = new int[](uniform(0, 100));
+    foreach (ref e; a)
+    {
+        e = uniform(0, 50);
+    }
+    auto pieces = partition3(a, 25);
+    assert(pieces[0].length + pieces[1].length + pieces[2].length == a.length);
+    foreach (e; pieces[0])
+    {
+        assert(e < 25);
+    }
+    foreach (e; pieces[1])
+    {
+        assert(e == 25);
+    }
+    foreach (e; pieces[2])
+    {
+        assert(e > 25);
+    }
+}
+
+// makeIndex
+/**
+Computes an index for $(D r) based on the comparison $(D less). The
+index is a sorted array of pointers or indices into the original
+range. This technique is similar to sorting, but it is more flexible
+because (1) it allows "sorting" of immutable collections, (2) allows
+binary search even if the original collection does not offer random
+access, (3) allows multiple indexes, each on a different predicate,
+and (4) may be faster when dealing with large objects. However, using
+an index may also be slower under certain circumstances due to the
+extra indirection, and is always larger than a sorting-based solution
+because it needs space for the index in addition to the original
+collection. The complexity is the same as $(D sort)'s.
+
+The first overload of $(D makeIndex) writes to a range containing
+pointers, and the second writes to a range containing offsets. The
+first overload requires $(D Range) to be a forward range, and the
+latter requires it to be a random-access range.
+
+$(D makeIndex) overwrites its second argument with the result, but
+never reallocates it.
+
+Params:
+    less = The comparison to use.
+    ss = The swapping strategy.
+    r = The range to index.
+    index = The resulting index.
+
+Returns: The pointer-based version returns a $(D SortedRange) wrapper
+over index, of type $(D SortedRange!(RangeIndex, (a, b) =>
+binaryFun!less(*a, *b))) thus reflecting the ordering of the
+index. The index-based version returns $(D void) because the ordering
+relation involves not only $(D index) but also $(D r).
+
+Throws: If the second argument's length is less than that of the range
+indexed, an exception is thrown.
+*/
+SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b))
+makeIndex(
+    alias less = "a < b",
+    SwapStrategy ss = SwapStrategy.unstable,
+    Range,
+    RangeIndex)
+(Range r, RangeIndex index)
+    if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex)
+            && is(ElementType!(RangeIndex) : ElementType!(Range)*))
+{
+    import std.algorithm.internal : addressOf;
+    import std.exception : enforce;
+
+    // assume collection already ordered
+    size_t i;
+    for (; !r.empty; r.popFront(), ++i)
+        index[i] = addressOf(r.front);
+    enforce(index.length == i);
+    // sort the index
+    sort!((a, b) => binaryFun!less(*a, *b), ss)(index);
+    return typeof(return)(index);
+}
+
+/// Ditto
+void makeIndex(
+    alias less = "a < b",
+    SwapStrategy ss = SwapStrategy.unstable,
+    Range,
+    RangeIndex)
+(Range r, RangeIndex index)
+if (isRandomAccessRange!Range && !isInfinite!Range &&
+    isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex &&
+    isIntegral!(ElementType!RangeIndex))
+{
+    import std.exception : enforce;
+    import std.conv : to;
+
+    alias IndexType = Unqual!(ElementType!RangeIndex);
+    enforce(r.length == index.length,
+        "r and index must be same length for makeIndex.");
+    static if (IndexType.sizeof < size_t.sizeof)
+    {
+        enforce(r.length <= IndexType.max, "Cannot create an index with " ~
+            "element type " ~ IndexType.stringof ~ " with length " ~
+            to!string(r.length) ~ ".");
+    }
+
+    for (IndexType i = 0; i < r.length; ++i)
+    {
+        index[cast(size_t) i] = i;
+    }
+
+    // sort the index
+    sort!((a, b) => binaryFun!less(r[cast(size_t) a], r[cast(size_t) b]), ss)
+      (index);
+}
+
+///
+unittest
+{
+    immutable(int[]) arr = [ 2, 3, 1, 5, 0 ];
+    // index using pointers
+    auto index1 = new immutable(int)*[arr.length];
+    makeIndex!("a < b")(arr, index1);
+    assert(isSorted!("*a < *b")(index1));
+    // index using offsets
+    auto index2 = new size_t[arr.length];
+    makeIndex!("a < b")(arr, index2);
+    assert(isSorted!
+        ((size_t a, size_t b){ return arr[a] < arr[b];})
+        (index2));
+}
+
+unittest
+{
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    immutable(int)[] arr = [ 2, 3, 1, 5, 0 ];
+    // index using pointers
+    auto index1 = new immutable(int)*[arr.length];
+    alias ImmRange = typeof(arr);
+    alias ImmIndex = typeof(index1);
+    static assert(isForwardRange!(ImmRange));
+    static assert(isRandomAccessRange!(ImmIndex));
+    static assert(!isIntegral!(ElementType!(ImmIndex)));
+    static assert(is(ElementType!(ImmIndex) : ElementType!(ImmRange)*));
+    makeIndex!("a < b")(arr, index1);
+    assert(isSorted!("*a < *b")(index1));
+
+    // index using offsets
+    auto index2 = new long[arr.length];
+    makeIndex(arr, index2);
+    assert(isSorted!
+            ((long a, long b){
+                return arr[cast(size_t) a] < arr[cast(size_t) b];
+            })(index2));
+
+    // index strings using offsets
+    string[] arr1 = ["I", "have", "no", "chocolate"];
+    auto index3 = new byte[arr1.length];
+    makeIndex(arr1, index3);
+    assert(isSorted!
+            ((byte a, byte b){ return arr1[a] < arr1[b];})
+            (index3));
+}
+
+private template validPredicates(E, less...)
+{
+    static if (less.length == 0)
+        enum validPredicates = true;
+    else static if (less.length == 1 && is(typeof(less[0]) == SwapStrategy))
+        enum validPredicates = true;
+    else
+        enum validPredicates =
+            is(typeof((E a, E b){ bool r = binaryFun!(less[0])(a, b); }))
+            && validPredicates!(E, less[1 .. $]);
+}
+
+/**
+$(D void multiSort(Range)(Range r)
+    if (validPredicates!(ElementType!Range, less));)
+
+Sorts a range by multiple keys. The call $(D multiSort!("a.id < b.id",
+"a.date > b.date")(r)) sorts the range $(D r) by $(D id) ascending,
+and sorts elements that have the same $(D id) by $(D date)
+descending. Such a call is equivalent to $(D sort!"a.id != b.id ? a.id
+< b.id : a.date > b.date"(r)), but $(D multiSort) is faster because it
+does fewer comparisons (in addition to being more convenient).
+ */
+template multiSort(less...) //if (less.length > 1)
+{
+    void multiSort(Range)(Range r)
+    if (validPredicates!(ElementType!Range, less))
+    {
+        static if (is(typeof(less[$ - 1]) == SwapStrategy))
+        {
+            enum ss = less[$ - 1];
+            alias funs = less[0 .. $ - 1];
+        }
+        else
+        {
+            alias ss = SwapStrategy.unstable;
+            alias funs = less;
+        }
+        alias lessFun = binaryFun!(funs[0]);
+
+        static if (funs.length > 1)
+        {
+            while (r.length > 1)
+            {
+                auto p = getPivot!lessFun(r);
+                auto t = partition3!(less[0], ss)(r, r[p]);
+                if (t[0].length <= t[2].length)
+                {
+                    .multiSort!less(t[0]);
+                    .multiSort!(less[1 .. $])(t[1]);
+                    r = t[2];
+                }
+                else
+                {
+                    .multiSort!(less[1 .. $])(t[1]);
+                    .multiSort!less(t[2]);
+                    r = t[0];
+                }
+            }
+        }
+        else
+        {
+            sort!(lessFun, ss)(r);
+        }
+    }
+}
+
+///
+@safe unittest
+{
+    static struct Point { int x, y; }
+    auto pts1 = [ Point(0, 0), Point(5, 5), Point(0, 1), Point(0, 2) ];
+    auto pts2 = [ Point(0, 0), Point(0, 1), Point(0, 2), Point(5, 5) ];
+    multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1);
+    assert(pts1 == pts2);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range;
+
+    static struct Point { int x, y; }
+    auto pts1 = [ Point(5, 6), Point(1, 0), Point(5, 7), Point(1, 1), Point(1, 2), Point(0, 1) ];
+    auto pts2 = [ Point(0, 1), Point(1, 0), Point(1, 1), Point(1, 2), Point(5, 6), Point(5, 7) ];
+    static assert(validPredicates!(Point, "a.x < b.x", "a.y < b.y"));
+    multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1);
+    assert(pts1 == pts2);
+
+    auto pts3 = indexed(pts1, iota(pts1.length));
+    multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts3);
+    assert(equal(pts3, pts2));
+}
+
+@safe unittest //issue 9160 (L-value only comparators)
+{
+    static struct A
+    {
+        int x;
+        int y;
+    }
+
+    static bool byX(const ref A lhs, const ref A rhs)
+    {
+        return lhs.x < rhs.x;
+    }
+
+    static bool byY(const ref A lhs, const ref A rhs)
+    {
+        return lhs.y < rhs.y;
+    }
+
+    auto points = [ A(4, 1), A(2, 4)];
+    multiSort!(byX, byY)(points);
+    assert(points[0] == A(2, 4));
+    assert(points[1] == A(4, 1));
+}
+
+private size_t getPivot(alias less, Range)(Range r)
+{
+    import std.algorithm.mutation : swapAt;
+
+    // This algorithm sorts the first, middle and last elements of r,
+    // then returns the index of the middle element.  In effect, it uses the
+    // median-of-three heuristic.
+
+    alias pred = binaryFun!(less);
+    immutable len = r.length;
+    immutable size_t mid = len / 2;
+    immutable uint result = ((cast(uint) (pred(r[0], r[mid]))) << 2) |
+                            ((cast(uint) (pred(r[0], r[len - 1]))) << 1) |
+                            (cast(uint) (pred(r[mid], r[len - 1])));
+
+    switch(result) {
+        case 0b001:
+            swapAt(r, 0, len - 1);
+            swapAt(r, 0, mid);
+            break;
+        case 0b110:
+            swapAt(r, mid, len - 1);
+            break;
+        case 0b011:
+            swapAt(r, 0, mid);
+            break;
+        case 0b100:
+            swapAt(r, mid, len - 1);
+            swapAt(r, 0, mid);
+            break;
+        case 0b000:
+            swapAt(r, 0, len - 1);
+            break;
+        case 0b111:
+            break;
+        default:
+            assert(0);
+    }
+
+    return mid;
+}
+
+private void optimisticInsertionSort(alias less, Range)(Range r)
+{
+    import std.algorithm.mutation : swapAt;
+
+    alias pred = binaryFun!(less);
+    if (r.length < 2)
+    {
+        return;
+    }
+
+    immutable maxJ = r.length - 1;
+    for (size_t i = r.length - 2; i != size_t.max; --i)
+    {
+        size_t j = i;
+
+        static if (hasAssignableElements!Range)
+        {
+            auto temp = r[i];
+
+            for (; j < maxJ && pred(r[j + 1], temp); ++j)
+            {
+                r[j] = r[j + 1];
+            }
+
+            r[j] = temp;
+        }
+        else
+        {
+            for (; j < maxJ && pred(r[j + 1], r[j]); ++j)
+            {
+                swapAt(r, j, j + 1);
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.random : Random, uniform;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    auto rnd = Random(1);
+    auto a = new int[uniform(100, 200, rnd)];
+    foreach (ref e; a) {
+        e = uniform(-100, 100, rnd);
+    }
+
+    optimisticInsertionSort!(binaryFun!("a < b"), int[])(a);
+    assert(isSorted(a));
+}
+
+// sort
+/**
+Sorts a random-access range according to the predicate $(D less). Performs
+$(BIGOH r.length * log(r.length)) evaluations of $(D less). Stable sorting
+requires $(D hasAssignableElements!Range) to be true.
+
+$(D sort) returns a $(XREF range, SortedRange) over the original range, which
+functions that can take advantage of sorted data can then use to know that the
+range is sorted and adjust accordingly. The $(XREF range, SortedRange) is a
+wrapper around the original range, so both it and the original range are sorted,
+but other functions won't know that the original range has been sorted, whereas
+they $(I can) know that $(XREF range, SortedRange) has been sorted.
+
+The predicate is expected to satisfy certain rules in order for $(D sort) to
+behave as expected - otherwise, the program may fail on certain inputs (but not
+others) when not compiled in release mode, due to the cursory $(D assumeSorted)
+check. Specifically, $(D sort) expects $(D less(a,b) && less(b,c)) to imply
+$(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to
+imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not
+always satisfy these conditions for floating point types, because the expression
+will always be $(D false) when either $(D a) or $(D b) is NaN.
+Use $(XREF math, cmp) instead.
+
+If `less` involves expensive computations on the _sort key, it may be
+worthwhile to use $(LREF schwartzSort) instead.
+
+Params:
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    r = The range to sort.
+
+Returns: The initial range wrapped as a $(D SortedRange) with the predicate
+$(D binaryFun!less).
+
+Algorithms: $(WEB en.wikipedia.org/wiki/Introsort) is used for unstable sorting and
+$(WEB en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting.
+Each algorithm has benefits beyond stability. Introsort is generally faster but
+Timsort may achieve greater speeds on data with low entropy or if predicate calls
+are expensive. Introsort performs no allocations whereas Timsort will perform one
+or more allocations per call. Both algorithms have $(BIGOH n log n) worst-case
+time complexity.
+
+See_Also:
+    $(XREF range, assumeSorted)$(BR)
+    $(XREF range, SortedRange)$(BR)
+    $(XREF_PACK algorithm,mutation,SwapStrategy)$(BR)
+    $(XREF functional, binaryFun)
+*/
+SortedRange!(Range, less)
+sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
+        Range)(Range r)
+    if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range ||
+                                          hasAssignableElements!Range)) ||
+         (ss != SwapStrategy.unstable && hasAssignableElements!Range)) &&
+        isRandomAccessRange!Range &&
+        hasSlicing!Range &&
+        hasLength!Range)
+    /+ Unstable sorting uses the quicksort algorithm, which uses swapAt,
+       which either uses swap(...), requiring swappable elements, or just
+       swaps using assignment.
+       Stable sorting uses TimSort, which needs to copy elements into a buffer,
+       requiring assignable elements. +/
+{
+    import std.range : assumeSorted;
+    alias lessFun = binaryFun!(less);
+    alias LessRet = typeof(lessFun(r.front, r.front));    // instantiate lessFun
+    static if (is(LessRet == bool))
+    {
+        static if (ss == SwapStrategy.unstable)
+            quickSortImpl!(lessFun)(r, r.length);
+        else //use Tim Sort for semistable & stable
+            TimSortImpl!(lessFun, Range).sort(r, null);
+
+        enum maxLen = 8;
+        assert(isSorted!lessFun(r), "Failed to sort range of type " ~ Range.stringof);
+    }
+    else
+    {
+        static assert(false, "Invalid predicate passed to sort: " ~ less.stringof);
+    }
+    return assumeSorted!less(r);
+}
+
+///
+@safe pure nothrow unittest
+{
+    int[] array = [ 1, 2, 3, 4 ];
+    // sort in descending order
+    sort!("a > b")(array);
+    assert(array == [ 4, 3, 2, 1 ]);
+    // sort in ascending order
+    sort(array);
+    assert(array == [ 1, 2, 3, 4 ]);
+    // sort with a delegate
+    bool myComp(int x, int y) @safe pure nothrow { return x > y; }
+    sort!(myComp)(array);
+    assert(array == [ 4, 3, 2, 1 ]);
+}
+///
+unittest
+{
+    // Showcase stable sorting
+    string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ];
+    sort!("toUpper(a) < toUpper(b)", SwapStrategy.stable)(words);
+    assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
+}
+
+///
+unittest
+{
+    // Sorting floating-point numbers in presence of NaN
+    double[] numbers = [-0.0, 3.0, -2.0, double.nan, 0.0, -double.nan];
+
+    import std.math : cmp, isIdentical;
+    import std.algorithm.comparison : equal;
+
+    sort!((a, b) => cmp(a, b) < 0)(numbers);
+
+    double[] sorted = [-double.nan, -2.0, -0.0, 0.0, 3.0, double.nan];
+    assert(numbers.equal!isIdentical(sorted));
+}
+
+unittest
+{
+    import std.algorithm.internal : rndstuff;
+    import std.algorithm : swapRanges; // FIXME
+    import std.random : Random, unpredictableSeed, uniform;
+    import std.uni : toUpper;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    // sort using delegate
+    auto a = new int[100];
+    auto rnd = Random(unpredictableSeed);
+    foreach (ref e; a) {
+        e = uniform(-100, 100, rnd);
+    }
+
+    int i = 0;
+    bool greater2(int a, int b) { return a + i > b + i; }
+    bool delegate(int, int) greater = &greater2;
+    sort!(greater)(a);
+    assert(isSorted!(greater)(a));
+
+    // sort using string
+    sort!("a < b")(a);
+    assert(isSorted!("a < b")(a));
+
+    // sort using function; all elements equal
+    foreach (ref e; a) {
+        e = 5;
+    }
+    static bool less(int a, int b) { return a < b; }
+    sort!(less)(a);
+    assert(isSorted!(less)(a));
+
+    string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ];
+    bool lessi(string a, string b) { return toUpper(a) < toUpper(b); }
+    sort!(lessi, SwapStrategy.stable)(words);
+    assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
+
+    // sort using ternary predicate
+    //sort!("b - a")(a);
+    //assert(isSorted!(less)(a));
+
+    a = rndstuff!(int)();
+    sort(a);
+    assert(isSorted(a));
+    auto b = rndstuff!(string)();
+    sort!("toLower(a) < toLower(b)")(b);
+    assert(isSorted!("toUpper(a) < toUpper(b)")(b));
+
+    {
+        // Issue 10317
+        enum E_10317 { a, b }
+        auto a_10317 = new E_10317[10];
+        sort(a_10317);
+    }
+
+    {
+        // Issue 7767
+        // Unstable sort should complete without an excessive number of predicate calls
+        // This would suggest it's running in quadratic time
+
+        // Compilation error if predicate is not static, i.e. a nested function
+        static uint comp;
+        static bool pred(size_t a, size_t b)
+        {
+            ++comp;
+            return a < b;
+        }
+
+        size_t[] arr;
+        arr.length = 1024;
+
+        foreach(k; 0..arr.length) arr[k] = k;
+        swapRanges(arr[0..$/2], arr[$/2..$]);
+
+        sort!(pred, SwapStrategy.unstable)(arr);
+        assert(comp < 25_000);
+    }
+
+    {
+        import std.algorithm : swap; // FIXME
+
+        bool proxySwapCalled;
+        struct S
+        {
+            int i;
+            alias i this;
+            void proxySwap(ref S other) { swap(i, other.i); proxySwapCalled = true; }
+            @disable void opAssign(S value);
+        }
+
+        alias R = S[];
+        R r = [S(3), S(2), S(1)];
+        static assert(hasSwappableElements!R);
+        static assert(!hasAssignableElements!R);
+        r.sort();
+        assert(proxySwapCalled);
+    }
+}
+
+private void quickSortImpl(alias less, Range)(Range r, size_t depth)
+{
+    import std.algorithm : swap; // FIXME
+    import std.algorithm.mutation : swapAt;
+    import std.algorithm.comparison : min;
+
+    alias Elem = ElementType!(Range);
+    enum size_t optimisticInsertionSortGetsBetter = 25;
+    static assert(optimisticInsertionSortGetsBetter >= 1);
+
+    // partition
+    while (r.length > optimisticInsertionSortGetsBetter)
+    {
+        if (depth == 0)
+        {
+            HeapOps!(less, Range).heapSort(r);
+            return;
+        }
+        depth = depth >= depth.max / 2 ? (depth / 3) * 2 : (depth * 2) / 3;
+
+        const pivotIdx = getPivot!(less)(r);
+        auto pivot = r[pivotIdx];
+
+        alias pred = binaryFun!(less);
+
+        // partition
+        swapAt(r, pivotIdx, r.length - 1);
+        size_t lessI = size_t.max, greaterI = r.length - 1;
+
+        while (true)
+        {
+            while (pred(r[++lessI], pivot)) {}
+            while (greaterI > 0 && pred(pivot, r[--greaterI])) {}
+
+            if (lessI >= greaterI)
+            {
+                break;
+            }
+            swapAt(r, lessI, greaterI);
+        }
+
+        swapAt(r, r.length - 1, lessI);
+        auto right = r[lessI + 1 .. r.length];
+
+        auto left = r[0 .. min(lessI, greaterI + 1)];
+        if (right.length > left.length)
+        {
+            swap(left, right);
+        }
+        .quickSortImpl!(less, Range)(right, depth);
+        r = left;
+    }
+    // residual sort
+    static if (optimisticInsertionSortGetsBetter > 1)
+    {
+        optimisticInsertionSort!(less, Range)(r);
+    }
+}
+
+// Heap operations for random-access ranges
+package(std) template HeapOps(alias less, Range)
+{
+    import std.algorithm.mutation : swapAt;
+
+    static assert(isRandomAccessRange!Range);
+    static assert(hasLength!Range);
+    static assert(hasSwappableElements!Range || hasAssignableElements!Range);
+
+    alias lessFun = binaryFun!less;
+
+    //template because of @@@12410@@@
+    void heapSort()(Range r)
+    {
+        // If true, there is nothing to do
+        if (r.length < 2) return;
+        // Build Heap
+        buildHeap(r);
+        // Sort
+        for (size_t i = r.length - 1; i > 0; --i)
+        {
+            r.swapAt(0, i);
+            percolate(r, 0, i);
+        }
+    }
+
+    //template because of @@@12410@@@
+    void buildHeap()(Range r)
+    {
+        immutable n = r.length;
+        for (size_t i = n / 2; i-- > 0; )
+        {
+            siftDown(r, i, n);
+        }
+        assert(isHeap(r));
+    }
+
+    bool isHeap()(Range r)
+    {
+        size_t parent = 0;
+        foreach (child; 1 .. r.length)
+        {
+            if (lessFun(r[parent], r[child])) return false;
+            // Increment parent every other pass
+            parent += !(child & 1);
+        }
+        return true;
+    }
+
+    // Sifts down r[parent] (which is initially assumed to be messed up) so the
+    // heap property is restored for r[parent .. end].
+    // template because of @@@12410@@@
+    void siftDown()(Range r, size_t parent, immutable size_t end)
+    {
+        for (;;)
+        {
+            auto child = (parent + 1) * 2;
+            if (child >= end)
+            {
+                // Leftover left child?
+                if (child == end && lessFun(r[parent], r[--child]))
+                    r.swapAt(parent, child);
+                break;
+            }
+            auto leftChild = child - 1;
+            if (lessFun(r[child], r[leftChild])) child = leftChild;
+            if (!lessFun(r[parent], r[child])) break;
+            r.swapAt(parent, child);
+            parent = child;
+        }
+    }
+
+    // Alternate version of siftDown that performs fewer comparisons, see
+    // https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort. The percolate
+    // process first sifts the parent all the way down (without comparing it
+    // against the leaves), and then a bit up until the heap property is
+    // restored. So there are more swaps but fewer comparisons. Gains are made
+    // when the final position is likely to end toward the bottom of the heap,
+    // so not a lot of sifts back are performed.
+    //template because of @@@12410@@@
+    void percolate()(Range r, size_t parent, immutable size_t end)
+    {
+        immutable root = parent;
+
+        // Sift down
+        for (;;)
+        {
+            auto child = (parent + 1) * 2;
+
+            if (child >= end)
+            {
+                if (child == end)
+                {
+                    // Leftover left node.
+                    --child;
+                    r.swapAt(parent, child);
+                    parent = child;
+                }
+                break;
+            }
+
+            auto leftChild = child - 1;
+            if (lessFun(r[child], r[leftChild])) child = leftChild;
+            r.swapAt(parent, child);
+            parent = child;
+        }
+
+        // Sift up
+        for (auto child = parent; child > root; child = parent)
+        {
+            parent = (child - 1) / 2;
+            if (!lessFun(r[parent], r[child])) break;
+            r.swapAt(parent, child);
+        }
+    }
+}
+
+// Tim Sort implementation
+private template TimSortImpl(alias pred, R)
+{
+    import core.bitop : bsr;
+    import std.array : uninitializedArray;
+
+    static assert(isRandomAccessRange!R);
+    static assert(hasLength!R);
+    static assert(hasSlicing!R);
+    static assert(hasAssignableElements!R);
+
+    alias T = ElementType!R;
+
+    alias less = binaryFun!pred;
+    bool greater(T a, T b){ return less(b, a); }
+    bool greaterEqual(T a, T b){ return !less(a, b); }
+    bool lessEqual(T a, T b){ return !less(b, a); }
+
+    enum minimalMerge = 128;
+    enum minimalGallop = 7;
+    enum minimalStorage = 256;
+    enum stackSize = 40;
+
+    struct Slice{ size_t base, length; }
+
+    // Entry point for tim sort
+    void sort(R range, T[] temp)
+    {
+        import std.algorithm.comparison : min;
+
+        // Do insertion sort on small range
+        if (range.length <= minimalMerge)
+        {
+            binaryInsertionSort(range);
+            return;
+        }
+
+        immutable minRun = minRunLength(range.length);
+        immutable minTemp = min(range.length / 2, minimalStorage);
+        size_t minGallop = minimalGallop;
+        Slice[stackSize] stack = void;
+        size_t stackLen = 0;
+
+        // Allocate temporary memory if not provided by user
+        if (temp.length < minTemp) temp = uninitializedArray!(T[])(minTemp);
+
+        for (size_t i = 0; i < range.length; )
+        {
+            // Find length of first run in list
+            size_t runLen = firstRun(range[i .. range.length]);
+
+            // If run has less than minRun elements, extend using insertion sort
+            if (runLen < minRun)
+            {
+                // Do not run farther than the length of the range
+                immutable force = range.length - i > minRun ? minRun : range.length - i;
+                binaryInsertionSort(range[i .. i + force], runLen);
+                runLen = force;
+            }
+
+            // Push run onto stack
+            stack[stackLen++] = Slice(i, runLen);
+            i += runLen;
+
+            // Collapse stack so that (e1 > e2 + e3 && e2 > e3)
+            // STACK is | ... e1 e2 e3 >
+            while (stackLen > 1)
+            {
+                immutable run4 = stackLen - 1;
+                immutable run3 = stackLen - 2;
+                immutable run2 = stackLen - 3;
+                immutable run1 = stackLen - 4;
+
+                if ( (stackLen > 2 && stack[run2].length <= stack[run3].length + stack[run4].length) ||
+                     (stackLen > 3 && stack[run1].length <= stack[run3].length + stack[run2].length) )
+                {
+                    immutable at = stack[run2].length < stack[run4].length ? run2 : run3;
+                    mergeAt(range, stack[0 .. stackLen], at, minGallop, temp);
+                }
+                else if (stack[run3].length > stack[run4].length) break;
+                else mergeAt(range, stack[0 .. stackLen], run3, minGallop, temp);
+
+                stackLen -= 1;
+            }
+
+            // Assert that the code above established the invariant correctly
+            version (assert)
+            {
+                if (stackLen == 2) assert(stack[0].length > stack[1].length);
+                else if (stackLen > 2)
+                {
+                    foreach(k; 2 .. stackLen)
+                    {
+                        assert(stack[k - 2].length > stack[k - 1].length + stack[k].length);
+                        assert(stack[k - 1].length > stack[k].length);
+                    }
+                }
+            }
+        }
+
+        // Force collapse stack until there is only one run left
+        while (stackLen > 1)
+        {
+            immutable run3 = stackLen - 1;
+            immutable run2 = stackLen - 2;
+            immutable run1 = stackLen - 3;
+            immutable at = stackLen >= 3 && stack[run1].length <= stack[run3].length
+                ? run1 : run2;
+            mergeAt(range, stack[0 .. stackLen], at, minGallop, temp);
+            --stackLen;
+        }
+    }
+
+    // Calculates optimal value for minRun:
+    // take first 6 bits of n and add 1 if any lower bits are set
+    pure size_t minRunLength(size_t n)
+    {
+        immutable shift = bsr(n)-5;
+        auto result = (n>>shift) + !!(n & ~((1<<shift)-1));
+        return result;
+    }
+
+    // Returns length of first run in range
+    size_t firstRun(R range)
+    out(ret)
+    {
+        assert(ret <= range.length);
+    }
+    body
+    {
+        import std.algorithm : reverse; // FIXME
+
+        if (range.length < 2) return range.length;
+
+        size_t i = 2;
+        if (lessEqual(range[0], range[1]))
+        {
+            while (i < range.length && lessEqual(range[i-1], range[i])) ++i;
+        }
+        else
+        {
+            while (i < range.length && greater(range[i-1], range[i])) ++i;
+            reverse(range[0 .. i]);
+        }
+        return i;
+    }
+
+    // A binary insertion sort for building runs up to minRun length
+    void binaryInsertionSort(R range, size_t sortedLen = 1)
+    out
+    {
+        if (!__ctfe) assert(isSorted!pred(range));
+    }
+    body
+    {
+    import std.algorithm : move; // FIXME
+
+        for (; sortedLen < range.length; ++sortedLen)
+        {
+            T item = moveAt(range, sortedLen);
+            size_t lower = 0;
+            size_t upper = sortedLen;
+            while (upper != lower)
+            {
+                size_t center = (lower + upper) / 2;
+                if (less(item, range[center])) upper = center;
+                else lower = center + 1;
+            }
+            //Currently (DMD 2.061) moveAll+retro is slightly less
+            //efficient then stright 'for' loop
+            //11 instructions vs 7 in the innermost loop [checked on Win32]
+            //moveAll(retro(range[lower .. sortedLen]),
+            //            retro(range[lower+1 .. sortedLen+1]));
+            for(upper=sortedLen; upper>lower; upper--)
+                range[upper] = moveAt(range, upper-1);
+            range[lower] = move(item);
+        }
+    }
+
+    // Merge two runs in stack (at, at + 1)
+    void mergeAt(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp)
+    in
+    {
+        assert(stack.length >= 2);
+        assert(stack.length - at == 2 || stack.length - at == 3);
+    }
+    body
+    {
+        immutable base = stack[at].base;
+        immutable mid  = stack[at].length;
+        immutable len  = stack[at + 1].length + mid;
+
+        // Pop run from stack
+        stack[at] = Slice(base, len);
+        if (stack.length - at == 3) stack[$ - 2] = stack[$ - 1];
+
+        // Merge runs (at, at + 1)
+        return merge(range[base .. base + len], mid, minGallop, temp);
+    }
+
+    // Merge two runs in a range. Mid is the starting index of the second run.
+    // minGallop and temp are references; The calling function must receive the updated values.
+    void merge(R range, size_t mid, ref size_t minGallop, ref T[] temp)
+    in
+    {
+        if (!__ctfe)
+        {
+            assert(isSorted!pred(range[0 .. mid]));
+            assert(isSorted!pred(range[mid .. range.length]));
+        }
+    }
+    body
+    {
+        assert(mid < range.length);
+
+        // Reduce range of elements
+        immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]);
+        immutable lastElement  = gallopReverseLower(range[mid .. range.length], range[mid - 1]) + mid;
+        range = range[firstElement .. lastElement];
+        mid -= firstElement;
+
+        if (mid == 0 || mid == range.length) return;
+
+        // Call function which will copy smaller run into temporary memory
+        if (mid <= range.length / 2)
+        {
+            temp = ensureCapacity(mid, temp);
+            minGallop = mergeLo(range, mid, minGallop, temp);
+        }
+        else
+        {
+            temp = ensureCapacity(range.length - mid, temp);
+            minGallop = mergeHi(range, mid, minGallop, temp);
+        }
+    }
+
+    // Enlarge size of temporary memory if needed
+    T[] ensureCapacity(size_t minCapacity, T[] temp)
+    out(ret)
+    {
+        assert(ret.length >= minCapacity);
+    }
+    body
+    {
+        if (temp.length < minCapacity)
+        {
+            size_t newSize = 1<<(bsr(minCapacity)+1);
+            //Test for overflow
+            if (newSize < minCapacity) newSize = minCapacity;
+
+            if (__ctfe) temp.length = newSize;
+            else temp = uninitializedArray!(T[])(newSize);
+        }
+        return temp;
+    }
+
+    // Merge front to back. Returns new value of minGallop.
+    // temp must be large enough to store range[0 .. mid]
+    size_t mergeLo(R range, immutable size_t mid, size_t minGallop, T[] temp)
+    out
+    {
+        if (!__ctfe) assert(isSorted!pred(range));
+    }
+    body
+    {
+    import std.algorithm : copy; // FIXME
+
+        assert(mid <= range.length);
+        assert(temp.length >= mid);
+
+        // Copy run into temporary memory
+        temp = temp[0 .. mid];
+        copy(range[0 .. mid], temp);
+
+        // Move first element into place
+        range[0] = range[mid];
+
+        size_t i = 1, lef = 0, rig = mid + 1;
+        size_t count_lef, count_rig;
+        immutable lef_end = temp.length - 1;
+
+        if (lef < lef_end && rig < range.length)
+        outer: while(true)
+        {
+            count_lef = 0;
+            count_rig = 0;
+
+            // Linear merge
+            while ((count_lef | count_rig) < minGallop)
+            {
+                if (lessEqual(temp[lef], range[rig]))
+                {
+                    range[i++] = temp[lef++];
+                    if(lef >= lef_end) break outer;
+                    ++count_lef;
+                    count_rig = 0;
+                }
+                else
+                {
+                    range[i++] = range[rig++];
+                    if(rig >= range.length) break outer;
+                    count_lef = 0;
+                    ++count_rig;
+                }
+            }
+
+            // Gallop merge
+            do
+            {
+                count_lef = gallopForwardUpper(temp[lef .. $], range[rig]);
+                foreach (j; 0 .. count_lef) range[i++] = temp[lef++];
+                if(lef >= temp.length) break outer;
+
+                count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]);
+                foreach (j; 0 .. count_rig) range[i++] = range[rig++];
+                if (rig >= range.length) while(true)
+                {
+                    range[i++] = temp[lef++];
+                    if(lef >= temp.length) break outer;
+                }
+
+                if (minGallop > 0) --minGallop;
+            }
+            while (count_lef >= minimalGallop || count_rig >= minimalGallop);
+
+            minGallop += 2;
+        }
+
+        // Move remaining elements from right
+        while (rig < range.length)
+            range[i++] = range[rig++];
+
+        // Move remaining elements from left
+        while (lef < temp.length)
+            range[i++] = temp[lef++];
+
+        return minGallop > 0 ? minGallop : 1;
+    }
+
+    // Merge back to front. Returns new value of minGallop.
+    // temp must be large enough to store range[mid .. range.length]
+    size_t mergeHi(R range, immutable size_t mid, size_t minGallop, T[] temp)
+    out
+    {
+        if (!__ctfe) assert(isSorted!pred(range));
+    }
+    body
+    {
+    import std.algorithm : copy; // FIXME
+
+        assert(mid <= range.length);
+        assert(temp.length >= range.length - mid);
+
+        // Copy run into temporary memory
+        temp = temp[0 .. range.length - mid];
+        copy(range[mid .. range.length], temp);
+
+        // Move first element into place
+        range[range.length - 1] = range[mid - 1];
+
+        size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1;
+        size_t count_lef, count_rig;
+
+        outer:
+        while(true)
+        {
+            count_lef = 0;
+            count_rig = 0;
+
+            // Linear merge
+            while((count_lef | count_rig) < minGallop)
+            {
+                if(greaterEqual(temp[rig], range[lef]))
+                {
+                    range[i--] = temp[rig];
+                    if(rig == 1)
+                    {
+                        // Move remaining elements from left
+                        while(true)
+                        {
+                            range[i--] = range[lef];
+                            if(lef == 0) break;
+                            --lef;
+                        }
+
+                        // Move last element into place
+                        range[i] = temp[0];
+
+                        break outer;
+                    }
+                    --rig;
+                    count_lef = 0;
+                    ++count_rig;
+                }
+                else
+                {
+                    range[i--] = range[lef];
+                    if(lef == 0) while(true)
+                    {
+                        range[i--] = temp[rig];
+                        if(rig == 0) break outer;
+                        --rig;
+                    }
+                    --lef;
+                    ++count_lef;
+                    count_rig = 0;
+                }
+            }
+
+            // Gallop merge
+            do
+            {
+                count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]);
+                foreach(j; 0 .. count_rig)
+                {
+                    range[i--] = temp[rig];
+                    if(rig == 0) break outer;
+                    --rig;
+                }
+
+                count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]);
+                foreach(j; 0 .. count_lef)
+                {
+                    range[i--] = range[lef];
+                    if(lef == 0) while(true)
+                    {
+                        range[i--] = temp[rig];
+                        if(rig == 0) break outer;
+                        --rig;
+                    }
+                    --lef;
+                }
+
+                if(minGallop > 0) --minGallop;
+            }
+            while(count_lef >= minimalGallop || count_rig >= minimalGallop);
+
+            minGallop += 2;
+        }
+
+        return minGallop > 0 ? minGallop : 1;
+    }
+
+    // false = forward / lower, true = reverse / upper
+    template gallopSearch(bool forwardReverse, bool lowerUpper)
+    {
+        // Gallop search on range according to attributes forwardReverse and lowerUpper
+        size_t gallopSearch(R)(R range, T value)
+        out(ret)
+        {
+            assert(ret <= range.length);
+        }
+        body
+        {
+            size_t lower = 0, center = 1, upper = range.length;
+            alias gap = center;
+
+            static if (forwardReverse)
+            {
+                static if (!lowerUpper) alias comp = lessEqual; // reverse lower
+                static if (lowerUpper)  alias comp = less;      // reverse upper
+
+                // Gallop Search Reverse
+                while (gap <= upper)
+                {
+                    if (comp(value, range[upper - gap]))
+                    {
+                        upper -= gap;
+                        gap *= 2;
+                    }
+                    else
+                    {
+                        lower = upper - gap;
+                        break;
+                    }
+                }
+
+                // Binary Search Reverse
+                while (upper != lower)
+                {
+                    center = lower + (upper - lower) / 2;
+                    if (comp(value, range[center])) upper = center;
+                    else lower = center + 1;
+                }
+            }
+            else
+            {
+                static if (!lowerUpper) alias comp = greater;      // forward lower
+                static if (lowerUpper)  alias comp = greaterEqual; // forward upper
+
+                // Gallop Search Forward
+                while (lower + gap < upper)
+                {
+                    if (comp(value, range[lower + gap]))
+                    {
+                        lower += gap;
+                        gap *= 2;
+                    }
+                    else
+                    {
+                        upper = lower + gap;
+                        break;
+                    }
+                }
+
+                // Binary Search Forward
+                while (lower != upper)
+                {
+                    center = lower + (upper - lower) / 2;
+                    if (comp(value, range[center])) lower = center + 1;
+                    else upper = center;
+                }
+            }
+
+            return lower;
+        }
+    }
+
+    alias gallopForwardLower = gallopSearch!(false, false);
+    alias gallopForwardUpper = gallopSearch!(false,  true);
+    alias gallopReverseLower = gallopSearch!( true, false);
+    alias gallopReverseUpper = gallopSearch!( true,  true);
+}
+
+unittest
+{
+    import std.random : Random, uniform, randomShuffle;
+
+    // Element type with two fields
+    static struct E
+    {
+        size_t value, index;
+    }
+
+    // Generates data especially for testing sorting with Timsort
+    static E[] genSampleData(uint seed)
+    {
+    import std.algorithm : swap, swapRanges; // FIXME
+
+        auto rnd = Random(seed);
+
+        E[] arr;
+        arr.length = 64 * 64;
+
+        // We want duplicate values for testing stability
+        foreach(i, ref v; arr) v.value = i / 64;
+
+        // Swap ranges at random middle point (test large merge operation)
+        immutable mid = uniform(arr.length / 4, arr.length / 4 * 3, rnd);
+        swapRanges(arr[0 .. mid], arr[mid .. $]);
+
+        // Shuffle last 1/8 of the array (test insertion sort and linear merge)
+        randomShuffle(arr[$ / 8 * 7 .. $], rnd);
+
+        // Swap few random elements (test galloping mode)
+        foreach(i; 0 .. arr.length / 64)
+        {
+            immutable a = uniform(0, arr.length, rnd), b = uniform(0, arr.length, rnd);
+            swap(arr[a], arr[b]);
+        }
+
+        // Now that our test array is prepped, store original index value
+        // This will allow us to confirm the array was sorted stably
+        foreach(i, ref v; arr) v.index = i;
+
+        return arr;
+    }
+
+    // Tests the Timsort function for correctness and stability
+    static bool testSort(uint seed)
+    {
+        auto arr = genSampleData(seed);
+
+        // Now sort the array!
+        static bool comp(E a, E b)
+        {
+            return a.value < b.value;
+        }
+
+        sort!(comp, SwapStrategy.stable)(arr);
+
+        // Test that the array was sorted correctly
+        assert(isSorted!comp(arr));
+
+        // Test that the array was sorted stably
+        foreach(i; 0 .. arr.length - 1)
+        {
+            if(arr[i].value == arr[i + 1].value) assert(arr[i].index < arr[i + 1].index);
+        }
+
+        return true;
+    }
+
+    enum seed = 310614065;
+    testSort(seed);
+
+    //@@BUG: Timsort fails with CTFE as of DMD 2.060
+    // enum result = testSort(seed);
+}
+
+unittest
+{//bugzilla 4584
+    assert(isSorted!"a < b"(sort!("a < b", SwapStrategy.stable)(
+       [83, 42, 85, 86, 87, 22, 89, 30, 91, 46, 93, 94, 95, 6,
+         97, 14, 33, 10, 101, 102, 103, 26, 105, 106, 107, 6]
+    )));
+
+}
+
+unittest
+{
+    //test stable sort + zip
+    import std.range;
+    auto x = [10, 50, 60, 60, 20];
+    dchar[] y = "abcde"d.dup;
+
+    sort!("a[0] < b[0]", SwapStrategy.stable)(zip(x, y));
+    assert(x == [10, 20, 50, 60, 60]);
+    assert(y == "aebcd"d);
+}
+
+unittest
+{
+    // Issue 14223
+    import std.range, std.array;
+    auto arr = chain(iota(0, 384), iota(0, 256), iota(0, 80), iota(0, 64), iota(0, 96)).array;
+    sort!("a < b", SwapStrategy.stable)(arr);
+}
+
+// schwartzSort
+/**
+Alternative sorting method that should be used when comparing keys involves an
+expensive computation. Instead of using `less(a, b)` for comparing elements,
+`schwartzSort` uses `less(transform(a), transform(b))`. The values of the
+`transform` function are precomputed in a temporary array, thus saving on
+repeatedly computing it. Conversely, if the cost of `transform` is small
+compared to the cost of allocating and filling the precomputed array, `sort`
+may be faster and therefore preferable.
+
+This approach to sorting is akin to the $(WEB
+wikipedia.org/wiki/Schwartzian_transform, Schwartzian transform), also known as
+the decorate-sort-undecorate pattern in Python and Lisp. The complexity is the
+same as that of the corresponding `sort`, but `schwartzSort` evaluates
+`transform` only `r.length` times (less than half when compared to regular
+sorting). The usage can be best illustrated with an example.
+
+Example:
+----
+uint hashFun(string) { ... expensive computation ... }
+string[] array = ...;
+// Sort strings by hash, slow
+sort!((a, b) => hashFun(a) < hashFun(b))(array);
+// Sort strings by hash, fast (only computes arr.length hashes):
+schwartzSort!(hashFun, "a < b")(array);
+----
+
+The $(D schwartzSort) function might require less temporary data and
+be faster than the Perl idiom or the decorate-sort-undecorate idiom
+present in Python and Lisp. This is because sorting is done in-place
+and only minimal extra data (one array of transformed elements) is
+created.
+
+To check whether an array was sorted and benefit of the speedup of
+Schwartz sorting, a function $(D schwartzIsSorted) is not provided
+because the effect can be achieved by calling $(D
+isSorted!less(map!transform(r))).
+
+Params:
+    transform = The transformation to apply.
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    r = The range to sort.
+
+Returns: The initial range wrapped as a $(D SortedRange) with the
+predicate $(D (a, b) => binaryFun!less(transform(a),
+transform(b))).
+ */
+SortedRange!(R, ((a, b) => binaryFun!less(unaryFun!transform(a),
+                                          unaryFun!transform(b))))
+schwartzSort(alias transform, alias less = "a < b",
+        SwapStrategy ss = SwapStrategy.unstable, R)(R r)
+    if (isRandomAccessRange!R && hasLength!R)
+{
+    import core.stdc.stdlib : malloc, free;
+    import std.conv : emplace;
+    import std.string : representation;
+    import std.range : zip, SortedRange;
+
+    alias T = typeof(unaryFun!transform(r.front));
+    auto xform1 = (cast(T*) malloc(r.length * T.sizeof))[0 .. r.length];
+    size_t length;
+    scope(exit)
+    {
+        static if (hasElaborateDestructor!T)
+        {
+            foreach (i; 0 .. length) collectException(destroy(xform1[i]));
+        }
+        free(xform1.ptr);
+    }
+    for (; length != r.length; ++length)
+    {
+        emplace(xform1.ptr + length, unaryFun!transform(r[length]));
+    }
+    // Make sure we use ubyte[] and ushort[], not char[] and wchar[]
+    // for the intermediate array, lest zip gets confused.
+    static if (isNarrowString!(typeof(xform1)))
+    {
+        auto xform = xform1.representation();
+    }
+    else
+    {
+        alias xform = xform1;
+    }
+    zip(xform, r).sort!((a, b) => binaryFun!less(a[0], b[0]), ss)();
+    return typeof(return)(r);
+}
+
+unittest
+{
+    // issue 4909
+    import std.typecons : Tuple;
+    Tuple!(char)[] chars;
+    schwartzSort!"a[0]"(chars);
+}
+
+unittest
+{
+    // issue 5924
+    import std.typecons : Tuple;
+    Tuple!(char)[] chars;
+    schwartzSort!((Tuple!(char) c){ return c[0]; })(chars);
+}
+
+unittest
+{
+    import std.algorithm.iteration : map;
+    import std.math : log2;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    static double entropy(double[] probs) {
+        double result = 0;
+        foreach (p; probs) {
+            if (!p) continue;
+            //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy");
+            result -= p * log2(p);
+        }
+        return result;
+    }
+
+    auto lowEnt = [ 1.0, 0, 0 ],
+         midEnt = [ 0.1, 0.1, 0.8 ],
+        highEnt = [ 0.31, 0.29, 0.4 ];
+    auto arr = new double[][3];
+    arr[0] = midEnt;
+    arr[1] = lowEnt;
+    arr[2] = highEnt;
+
+    schwartzSort!(entropy, q{a > b})(arr);
+    assert(arr[0] == highEnt);
+    assert(arr[1] == midEnt);
+    assert(arr[2] == lowEnt);
+    assert(isSorted!("a > b")(map!(entropy)(arr)));
+}
+
+unittest
+{
+    import std.algorithm.iteration : map;
+    import std.math : log2;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    static double entropy(double[] probs) {
+        double result = 0;
+        foreach (p; probs) {
+            if (!p) continue;
+            //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy");
+            result -= p * log2(p);
+        }
+        return result;
+    }
+
+    auto lowEnt = [ 1.0, 0, 0 ],
+        midEnt = [ 0.1, 0.1, 0.8 ],
+        highEnt = [ 0.31, 0.29, 0.4 ];
+    auto arr = new double[][3];
+    arr[0] = midEnt;
+    arr[1] = lowEnt;
+    arr[2] = highEnt;
+
+    schwartzSort!(entropy, q{a < b})(arr);
+    assert(arr[0] == lowEnt);
+    assert(arr[1] == midEnt);
+    assert(arr[2] == highEnt);
+    assert(isSorted!("a < b")(map!(entropy)(arr)));
+}
+
+// partialSort
+/**
+Reorders the random-access range $(D r) such that the range $(D r[0
+.. mid]) is the same as if the entire $(D r) were sorted, and leaves
+the range $(D r[mid .. r.length]) in no particular order. Performs
+$(BIGOH r.length * log(mid)) evaluations of $(D pred). The
+implementation simply calls $(D topN!(less, ss)(r, n)) and then $(D
+sort!(less, ss)(r[0 .. n])).
+
+Params:
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    r = The random-access range to reorder.
+    n = The length of the initial segment of `r` to sort.
+*/
+void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
+    Range)(Range r, size_t n)
+    if (isRandomAccessRange!(Range) && hasLength!(Range) && hasSlicing!(Range))
+{
+    topN!(less, ss)(r, n);
+    sort!(less, ss)(r[0 .. n]);
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ];
+    partialSort(a, 5);
+    assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]);
+}
+
+// topN
+/**
+Reorders the range $(D r) using $(D swap) such that $(D r[nth]) refers
+to the element that would fall there if the range were fully
+sorted. In addition, it also partitions $(D r) such that all elements
+$(D e1) from $(D r[0]) to $(D r[nth]) satisfy $(D !less(r[nth], e1)),
+and all elements $(D e2) from $(D r[nth]) to $(D r[r.length]) satisfy
+$(D !less(e2, r[nth])). Effectively, it finds the nth smallest
+(according to $(D less)) elements in $(D r). Performs an expected
+$(BIGOH r.length) (if unstable) or $(BIGOH r.length * log(r.length))
+(if stable) evaluations of $(D less) and $(D swap).
+
+If $(D n >= r.length), the algorithm has no effect and returns `r[0 .. $]`.
+
+Params:
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    r = The random-access range to reorder.
+    nth = The index of the element that should be in sorted position after the
+        function is done.
+
+See_Also:
+    $(LREF topNIndex),
+    $(WEB sgi.com/tech/stl/nth_element.html, STL's nth_element)
+
+BUGS:
+
+Stable topN has not been implemented yet.
+*/
+auto topN(alias less = "a < b",
+        SwapStrategy ss = SwapStrategy.unstable,
+        Range)(Range r, size_t nth)
+    if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range)
+{
+    static assert(ss == SwapStrategy.unstable,
+            "Stable topN not yet implemented");
+
+    if (nth >= r.length) return r[0 .. $];
+
+    auto ret = r[0 .. nth];
+    for (;;)
+    {
+        assert(nth < r.length);
+        import std.algorithm.mutation : swap;
+        import std.algorithm.searching : minPos;
+        if (nth == 0)
+        {
+            // Special-case "min"
+            swap(r.front, r.minPos!less.front);
+            break;
+        }
+        if (nth + 1 == r.length)
+        {
+            // Special-case "max"
+            swap(r.back, r.minPos!((a, b) => binaryFun!less(b, a)).front);
+            break;
+        }
+        auto pivot = r.getPivot!less;
+        assert(!binaryFun!less(r[pivot], r[pivot]));
+        swap(r[pivot], r.back);
+        auto right = r.partition!(a => binaryFun!less(a, r.back), ss);
+        assert(right.length >= 1);
+        pivot = r.length - right.length;
+        if (pivot > nth)
+        {
+            // We don't care to swap the pivot back, won't be visited anymore
+            assert(pivot < r.length);
+            r = r[0 .. pivot];
+            continue;
+        }
+        // Swap the pivot to where it should be
+        swap(right.front, r.back);
+        if (pivot == nth)
+        {
+            // Found Waldo!
+            break;
+        }
+        ++pivot; // skip the pivot
+        r = r[pivot .. $];
+        nth -= pivot;
+    }
+    return ret;
+}
+
+///
+@safe unittest
+{
+    int[] v = [ 25, 7, 9, 2, 0, 5, 21 ];
+    topN!"a < b"(v, 100);
+    assert(v == [ 25, 7, 9, 2, 0, 5, 21 ]);
+    auto n = 4;
+    topN!"a < b"(v, n);
+    assert(v[n] == 9);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.algorithm.iteration : reduce;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+    //scope(failure) writeln(stderr, "Failure testing algorithm");
+    //auto v = [ 25, 7, 9, 2, 0, 5, 21 ];
+    int[] v = [ 7, 6, 5, 4, 3, 2, 1, 0 ];
+    ptrdiff_t n = 3;
+    topN!("a < b")(v, n);
+    assert(reduce!max(v[0 .. n]) <= v[n]);
+    assert(reduce!min(v[n + 1 .. $]) >= v[n]);
+    //
+    v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5];
+    n = 3;
+    topN(v, n);
+    assert(reduce!max(v[0 .. n]) <= v[n]);
+    assert(reduce!min(v[n + 1 .. $]) >= v[n]);
+    //
+    v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5];
+    n = 1;
+    topN(v, n);
+    assert(reduce!max(v[0 .. n]) <= v[n]);
+    assert(reduce!min(v[n + 1 .. $]) >= v[n]);
+    //
+    v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5];
+    n = v.length - 1;
+    topN(v, n);
+    assert(v[n] == 7);
+    //
+    v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5];
+    n = 0;
+    topN(v, n);
+    assert(v[n] == 1);
+
+    double[][] v1 = [[-10, -5], [-10, -3], [-10, -5], [-10, -4],
+            [-10, -5], [-9, -5], [-9, -3], [-9, -5],];
+
+    // double[][] v1 = [ [-10, -5], [-10, -4], [-9, -5], [-9, -5],
+    //         [-10, -5], [-10, -3], [-10, -5], [-9, -3],];
+    double[]*[] idx = [ &v1[0], &v1[1], &v1[2], &v1[3], &v1[4], &v1[5], &v1[6],
+            &v1[7], ];
+
+    auto mid = v1.length / 2;
+    topN!((a, b){ return (*a)[1] < (*b)[1]; })(idx, mid);
+    foreach (e; idx[0 .. mid]) assert((*e)[1] <= (*idx[mid])[1]);
+    foreach (e; idx[mid .. $]) assert((*e)[1] >= (*idx[mid])[1]);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : max, min;
+    import std.algorithm.iteration : reduce;
+    import std.random : uniform;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    int[] a = new int[uniform(1, 10000)];
+        foreach (ref e; a) e = uniform(-1000, 1000);
+    auto k = uniform(0, a.length);
+    topN(a, k);
+    if (k > 0)
+    {
+        auto left = reduce!max(a[0 .. k]);
+        assert(left <= a[k]);
+    }
+    if (k + 1 < a.length)
+    {
+        auto right = reduce!min(a[k + 1 .. $]);
+        assert(right >= a[k]);
+    }
+}
+
+// bug 12987
+@safe unittest
+{
+    int[] a = [ 25, 7, 9, 2, 0, 5, 21 ];
+    auto n = 4;
+    auto t = topN(a, n);
+    sort(t);
+    assert(t == [0, 2, 5, 7]);
+}
+
+/**
+Stores the smallest elements of the two ranges in the left-hand range.
+
+Params:
+    less = The predicate to sort by.
+    ss = The swapping strategy to use.
+    r1 = The first range.
+    r2 = The second range.
+ */
+auto topN(alias less = "a < b",
+        SwapStrategy ss = SwapStrategy.unstable,
+        Range1, Range2)(Range1 r1, Range2 r2)
+    if (isRandomAccessRange!(Range1) && hasLength!Range1 &&
+            isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2))
+{
+    import std.container : BinaryHeap;
+
+    static assert(ss == SwapStrategy.unstable,
+            "Stable topN not yet implemented");
+    auto heap = BinaryHeap!(Range1, less)(r1);
+    for (; !r2.empty; r2.popFront())
+    {
+        heap.conditionalInsert(r2.front);
+    }
+    return r1;
+}
+
+///
+unittest
+{
+    int[] a = [ 5, 7, 2, 6, 7 ];
+    int[] b = [ 2, 1, 5, 6, 7, 3, 0 ];
+    topN(a, b);
+    sort(a);
+    assert(a == [0, 1, 2, 2, 3]);
+}
+
+// bug 12987
+unittest
+{
+    int[] a = [ 5, 7, 2, 6, 7 ];
+    int[] b = [ 2, 1, 5, 6, 7, 3, 0 ];
+    auto t = topN(a, b);
+    sort(t);
+    assert(t == [ 0, 1, 2, 2, 3 ]);
+}
+
+// bug 15420
+unittest
+{
+    int[] a = [ 5, 7, 2, 6, 7 ];
+    int[] b = [ 2, 1, 5, 6, 7, 3, 0 ];
+    topN!"a > b"(a, b);
+    sort!"a > b"(a);
+    assert(a == [ 7, 7, 7, 6, 6 ]);
+}
+
+/**
+Copies the top $(D n) elements of the input range $(D source) into the
+random-access range $(D target), where $(D n =
+target.length). Elements of $(D source) are not touched. If $(D
+sorted) is $(D true), the target is sorted. Otherwise, the target
+respects the $(WEB en.wikipedia.org/wiki/Binary_heap, heap property).
+
+Params:
+    less = The predicate to sort by.
+    source = The source range.
+    target = The target range.
+    sorted = Whether to sort the elements copied into `target`.
+
+Returns: The slice of `target` containing the copied elements.
+ */
+TRange topNCopy(alias less = "a < b", SRange, TRange)
+    (SRange source, TRange target, SortOutput sorted = SortOutput.no)
+    if (isInputRange!(SRange) && isRandomAccessRange!(TRange)
+            && hasLength!(TRange) && hasSlicing!(TRange))
+{
+    import std.container : BinaryHeap;
+
+    if (target.empty) return target;
+    auto heap = BinaryHeap!(TRange, less)(target, 0);
+    foreach (e; source) heap.conditionalInsert(e);
+    auto result = target[0 .. heap.length];
+    if (sorted == SortOutput.yes)
+    {
+        while (!heap.empty) heap.removeFront();
+    }
+    return result;
+}
+
+///
+unittest
+{
+    int[] a = [ 10, 16, 2, 3, 1, 5, 0 ];
+    int[] b = new int[3];
+    topNCopy(a, b, SortOutput.yes);
+    assert(b == [ 0, 1, 2 ]);
+}
+
+unittest
+{
+    import std.random : Random, unpredictableSeed, uniform, randomShuffle;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    auto r = Random(unpredictableSeed);
+    ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)];
+    foreach (i, ref e; a) e = i;
+    randomShuffle(a, r);
+    auto n = uniform(0, a.length, r);
+    ptrdiff_t[] b = new ptrdiff_t[n];
+    topNCopy!(binaryFun!("a < b"))(a, b, SortOutput.yes);
+    assert(isSorted!(binaryFun!("a < b"))(b));
+}
+
+/**
+Given a range of elements, constructs an index of its top $(I n) elements
+(i.e., the first $(I n) elements if the range were sorted).
+
+Similar to $(LREF topN), except that the range is not modified.
+
+Params:
+    less = A binary predicate that defines the ordering of range elements.
+        Defaults to $(D a < b).
+    ss = $(RED (Not implemented yet.)) Specify the swapping strategy.
+    r = A
+        $(XREF_PACK_NAMED range,primitives,isRandomAccessRange,random-access range)
+        of elements to make an index for.
+    index = A
+        $(XREF_PACK_NAMED range,primitives,isRandomAccessRange,random-access range)
+        with assignable elements to build the index in. The length of this range
+        determines how many top elements to index in $(D r).
+
+        This index range can either have integral elements, in which case the
+        constructed index will consist of zero-based numerical indices into
+        $(D r); or it can have pointers to the element type of $(D r), in which
+        case the constructed index will be pointers to the top elements in
+        $(D r).
+    sorted = Determines whether to sort the index by the elements they refer
+        to.
+
+See_also: $(LREF topN), $(LREF topNCopy).
+
+BUGS:
+The swapping strategy parameter is not implemented yet; currently it is
+ignored.
+*/
+void topNIndex(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
+               Range, RangeIndex)
+              (Range r, RangeIndex index, SortOutput sorted = SortOutput.no)
+    if (isRandomAccessRange!Range &&
+        isRandomAccessRange!RangeIndex &&
+        hasAssignableElements!RangeIndex &&
+        isIntegral!(ElementType!(RangeIndex)))
+{
+    static assert(ss == SwapStrategy.unstable,
+                  "Stable swap strategy not implemented yet.");
+
+    import std.container : BinaryHeap;
+    import std.exception : enforce;
+
+    if (index.empty) return;
+    enforce(ElementType!(RangeIndex).max >= index.length,
+            "Index type too small");
+    bool indirectLess(ElementType!(RangeIndex) a, ElementType!(RangeIndex) b)
+    {
+        return binaryFun!(less)(r[a], r[b]);
+    }
+    auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0);
+    foreach (i; 0 .. r.length)
+    {
+        heap.conditionalInsert(cast(ElementType!RangeIndex) i);
+    }
+    if (sorted == SortOutput.yes)
+    {
+        while (!heap.empty) heap.removeFront();
+    }
+}
+
+/// ditto
+void topNIndex(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
+               Range, RangeIndex)
+              (Range r, RangeIndex index, SortOutput sorted = SortOutput.no)
+    if (isRandomAccessRange!Range &&
+        isRandomAccessRange!RangeIndex &&
+        hasAssignableElements!RangeIndex &&
+        is(ElementType!(RangeIndex) == ElementType!(Range)*))
+{
+    static assert(ss == SwapStrategy.unstable,
+                  "Stable swap strategy not implemented yet.");
+
+    import std.container : BinaryHeap;
+
+    if (index.empty) return;
+    static bool indirectLess(const ElementType!(RangeIndex) a,
+                             const ElementType!(RangeIndex) b)
+    {
+        return binaryFun!less(*a, *b);
+    }
+    auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0);
+    foreach (i; 0 .. r.length)
+    {
+        heap.conditionalInsert(&r[i]);
+    }
+    if (sorted == SortOutput.yes)
+    {
+        while (!heap.empty) heap.removeFront();
+    }
+}
+
+///
+unittest
+{
+    // Construct index to top 3 elements using numerical indices:
+    int[] a = [ 10, 2, 7, 5, 8, 1 ];
+    int[] index = new int[3];
+    topNIndex(a, index, SortOutput.yes);
+    assert(index == [5, 1, 3]); // because a[5]==1, a[1]==2, a[3]==5
+
+    // Construct index to top 3 elements using pointer indices:
+    int*[] ptrIndex = new int*[3];
+    topNIndex(a, ptrIndex, SortOutput.yes);
+    assert(ptrIndex == [ &a[5], &a[1], &a[3] ]);
+}
+
+unittest
+{
+    import std.conv : text;
+
+    debug(std_algorithm) scope(success)
+        writeln("unittest @", __FILE__, ":", __LINE__, " done.");
+
+    {
+        int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ];
+        int*[] b = new int*[5];
+        topNIndex!("a > b")(a, b, SortOutput.yes);
+        //foreach (e; b) writeln(*e);
+        assert(b == [ &a[0], &a[2], &a[1], &a[6], &a[5]]);
+    }
+    {
+        int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ];
+        auto b = new ubyte[5];
+        topNIndex!("a > b")(a, b, SortOutput.yes);
+        //foreach (e; b) writeln(e, ":", a[e]);
+        assert(b == [ cast(ubyte) 0, cast(ubyte)2, cast(ubyte)1, cast(ubyte)6, cast(ubyte)5], text(b));
+    }
+}
+
+// nextPermutation
+/**
+ * Permutes $(D range) in-place to the next lexicographically greater
+ * permutation.
+ *
+ * The predicate $(D less) defines the lexicographical ordering to be used on
+ * the range.
+ *
+ * If the range is currently the lexicographically greatest permutation, it is
+ * permuted back to the least permutation and false is returned.  Otherwise,
+ * true is returned. One can thus generate all permutations of a range by
+ * sorting it according to $(D less), which produces the lexicographically
+ * least permutation, and then calling nextPermutation until it returns false.
+ * This is guaranteed to generate all distinct permutations of the range
+ * exactly once.  If there are $(I N) elements in the range and all of them are
+ * unique, then $(I N)! permutations will be generated. Otherwise, if there are
+ * some duplicated elements, fewer permutations will be produced.
+----
+// Enumerate all permutations
+int[] a = [1,2,3,4,5];
+do
+{
+    // use the current permutation and
+    // proceed to the next permutation of the array.
+} while (nextPermutation(a));
+----
+ * Params:
+ *  less = The ordering to be used to determine lexicographical ordering of the
+ *      permutations.
+ *  range = The range to permute.
+ *
+ * Returns: false if the range was lexicographically the greatest, in which
+ * case the range is reversed back to the lexicographically smallest
+ * permutation; otherwise returns true.
+ * See_Also:
+ * $(XREF_PACK algorithm,iteration,permutations).
+ */
+bool nextPermutation(alias less="a < b", BidirectionalRange)
+                    (BidirectionalRange range)
+    if (isBidirectionalRange!BidirectionalRange &&
+        hasSwappableElements!BidirectionalRange)
+{
+    import std.algorithm : find, reverse, swap; // FIXME
+    import std.range : retro, takeExactly;
+    // Ranges of 0 or 1 element have no distinct permutations.
+    if (range.empty) return false;
+
+    auto i = retro(range);
+    auto last = i.save;
+
+    // Find last occurring increasing pair of elements
+    size_t n = 1;
+    for (i.popFront(); !i.empty; i.popFront(), last.popFront(), n++)
+    {
+        if (binaryFun!less(i.front, last.front))
+            break;
+    }
+
+    if (i.empty) {
+        // Entire range is decreasing: it's lexicographically the greatest. So
+        // wrap it around.
+        range.reverse();
+        return false;
+    }
+
+    // Find last element greater than i.front.
+    auto j = find!((a) => binaryFun!less(i.front, a))(
+                   takeExactly(retro(range), n));
+
+    assert(!j.empty);   // shouldn't happen since i.front < last.front
+    swap(i.front, j.front);
+    reverse(takeExactly(retro(range), n));
+
+    return true;
+}
+
+///
+@safe unittest
+{
+    // Step through all permutations of a sorted array in lexicographic order
+    int[] a = [1,2,3];
+    assert(nextPermutation(a) == true);
+    assert(a == [1,3,2]);
+    assert(nextPermutation(a) == true);
+    assert(a == [2,1,3]);
+    assert(nextPermutation(a) == true);
+    assert(a == [2,3,1]);
+    assert(nextPermutation(a) == true);
+    assert(a == [3,1,2]);
+    assert(nextPermutation(a) == true);
+    assert(a == [3,2,1]);
+    assert(nextPermutation(a) == false);
+    assert(a == [1,2,3]);
+}
+
+///
+@safe unittest
+{
+    // Step through permutations of an array containing duplicate elements:
+    int[] a = [1,1,2];
+    assert(nextPermutation(a) == true);
+    assert(a == [1,2,1]);
+    assert(nextPermutation(a) == true);
+    assert(a == [2,1,1]);
+    assert(nextPermutation(a) == false);
+    assert(a == [1,1,2]);
+}
+
+@safe unittest
+{
+    // Boundary cases: arrays of 0 or 1 element.
+    int[] a1 = [];
+    assert(!nextPermutation(a1));
+    assert(a1 == []);
+
+    int[] a2 = [1];
+    assert(!nextPermutation(a2));
+    assert(a2 == [1]);
+}
+
+@safe unittest
+{
+    import std.algorithm.comparison : equal;
+
+    auto a1 = [1, 2, 3, 4];
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [1, 2, 4, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [1, 3, 2, 4]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [1, 3, 4, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [1, 4, 2, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [1, 4, 3, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 1, 3, 4]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 1, 4, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 3, 1, 4]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 3, 4, 1]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 4, 1, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [2, 4, 3, 1]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 1, 2, 4]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 1, 4, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 2, 1, 4]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 2, 4, 1]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 4, 1, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [3, 4, 2, 1]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 1, 2, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 1, 3, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 2, 1, 3]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 2, 3, 1]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 3, 1, 2]));
+
+    assert(nextPermutation(a1));
+    assert(equal(a1, [4, 3, 2, 1]));
+
+    assert(!nextPermutation(a1));
+    assert(equal(a1, [1, 2, 3, 4]));
+}
+
+@safe unittest
+{
+    // Test with non-default sorting order
+    int[] a = [3,2,1];
+    assert(nextPermutation!"a > b"(a) == true);
+    assert(a == [3,1,2]);
+    assert(nextPermutation!"a > b"(a) == true);
+    assert(a == [2,3,1]);
+    assert(nextPermutation!"a > b"(a) == true);
+    assert(a == [2,1,3]);
+    assert(nextPermutation!"a > b"(a) == true);
+    assert(a == [1,3,2]);
+    assert(nextPermutation!"a > b"(a) == true);
+    assert(a == [1,2,3]);
+    assert(nextPermutation!"a > b"(a) == false);
+    assert(a == [3,2,1]);
+}
+
+// Issue 13594
+@safe unittest
+{
+    int[3] a = [1,2,3];
+    assert(nextPermutation(a[]));
+    assert(a == [1,3,2]);
+}
+
+// nextEvenPermutation
+/**
+ * Permutes $(D range) in-place to the next lexicographically greater $(I even)
+ * permutation.
+ *
+ * The predicate $(D less) defines the lexicographical ordering to be used on
+ * the range.
+ *
+ * An even permutation is one which is produced by swapping an even number of
+ * pairs of elements in the original range. The set of $(I even) permutations
+ * is distinct from the set of $(I all) permutations only when there are no
+ * duplicate elements in the range. If the range has $(I N) unique elements,
+ * then there are exactly $(I N)!/2 even permutations.
+ *
+ * If the range is already the lexicographically greatest even permutation, it
+ * is permuted back to the least even permutation and false is returned.
+ * Otherwise, true is returned, and the range is modified in-place to be the
+ * lexicographically next even permutation.
+ *
+ * One can thus generate the even permutations of a range with unique elements
+ * by starting with the lexicographically smallest permutation, and repeatedly
+ * calling nextEvenPermutation until it returns false.
+----
+// Enumerate even permutations
+int[] a = [1,2,3,4,5];
+do
+{
+    // use the current permutation and
+    // proceed to the next even permutation of the array.
+} while (nextEvenPermutation(a));
+----
+ * One can also generate the $(I odd) permutations of a range by noting that
+ * permutations obey the rule that even + even = even, and odd + even = odd.
+ * Thus, by swapping the last two elements of a lexicographically least range,
+ * it is turned into the first odd permutation. Then calling
+ * nextEvenPermutation on this first odd permutation will generate the next
+ * even permutation relative to this odd permutation, which is actually the
+ * next odd permutation of the original range. Thus, by repeatedly calling
+ * nextEvenPermutation until it returns false, one enumerates the odd
+ * permutations of the original range.
+----
+// Enumerate odd permutations
+int[] a = [1,2,3,4,5];
+swap(a[$-2], a[$-1]);    // a is now the first odd permutation of [1,2,3,4,5]
+do
+{
+    // use the current permutation and
+    // proceed to the next odd permutation of the original array
+    // (which is an even permutation of the first odd permutation).
+} while (nextEvenPermutation(a));
+----
+ *
+ * Warning: Since even permutations are only distinct from all permutations
+ * when the range elements are unique, this function assumes that there are no
+ * duplicate elements under the specified ordering. If this is not _true, some
+ * permutations may fail to be generated. When the range has non-unique
+ * elements, you should use $(MYREF nextPermutation) instead.
+ *
+ * Params:
+ *  less = The ordering to be used to determine lexicographical ordering of the
+ *      permutations.
+ *  range = The range to permute.
+ *
+ * Returns: false if the range was lexicographically the greatest, in which
+ * case the range is reversed back to the lexicographically smallest
+ * permutation; otherwise returns true.
+ */
+bool nextEvenPermutation(alias less="a < b", BidirectionalRange)
+                        (BidirectionalRange range)
+    if (isBidirectionalRange!BidirectionalRange &&
+        hasSwappableElements!BidirectionalRange)
+{
+    import std.algorithm : find, reverse, swap; // FIXME
+    import std.range : retro, takeExactly;
+    // Ranges of 0 or 1 element have no distinct permutations.
+    if (range.empty) return false;
+
+    bool oddParity = false;
+    bool ret = true;
+    do
+    {
+        auto i = retro(range);
+        auto last = i.save;
+
+        // Find last occurring increasing pair of elements
+        size_t n = 1;
+        for (i.popFront(); !i.empty;
+            i.popFront(), last.popFront(), n++)
+        {
+            if (binaryFun!less(i.front, last.front))
+                break;
+        }
+
+        if (!i.empty)
+        {
+            // Find last element greater than i.front.
+            auto j = find!((a) => binaryFun!less(i.front, a))(
+                           takeExactly(retro(range), n));
+
+            // shouldn't happen since i.front < last.front
+            assert(!j.empty);
+
+            swap(i.front, j.front);
+            oddParity = !oddParity;
+        }
+        else
+        {
+            // Entire range is decreasing: it's lexicographically
+            // the greatest.
+            ret = false;
+        }
+
+        reverse(takeExactly(retro(range), n));
+        if ((n / 2) % 2 == 1)
+            oddParity = !oddParity;
+    } while(oddParity);
+
+    return ret;
+}
+
+///
+@safe unittest
+{
+    // Step through even permutations of a sorted array in lexicographic order
+    int[] a = [1,2,3];
+    assert(nextEvenPermutation(a) == true);
+    assert(a == [2,3,1]);
+    assert(nextEvenPermutation(a) == true);
+    assert(a == [3,1,2]);
+    assert(nextEvenPermutation(a) == false);
+    assert(a == [1,2,3]);
+}
+
+@safe unittest
+{
+    auto a3 = [ 1, 2, 3, 4 ];
+    int count = 1;
+    while (nextEvenPermutation(a3)) count++;
+    assert(count == 12);
+}
+
+@safe unittest
+{
+    // Test with non-default sorting order
+    auto a = [ 3, 2, 1 ];
+
+    assert(nextEvenPermutation!"a > b"(a) == true);
+    assert(a == [ 2, 1, 3 ]);
+    assert(nextEvenPermutation!"a > b"(a) == true);
+    assert(a == [ 1, 3, 2 ]);
+    assert(nextEvenPermutation!"a > b"(a) == false);
+    assert(a == [ 3, 2, 1 ]);
+}
+
+@safe unittest
+{
+    // Test various cases of rollover
+    auto a = [ 3, 1, 2 ];
+    assert(nextEvenPermutation(a) == false);
+    assert(a == [ 1, 2, 3 ]);
+
+    auto b = [ 3, 2, 1 ];
+    assert(nextEvenPermutation(b) == false);
+    assert(b == [ 1, 3, 2 ]);
+}
+
+@safe unittest
+{
+    // Issue 13594
+    int[3] a = [1,2,3];
+    assert(nextEvenPermutation(a[]));
+    assert(a == [2,3,1]);
+}
+
+/**
+Even permutations are useful for generating coordinates of certain geometric
+shapes. Here's a non-trivial example:
+*/
+@safe unittest
+{
+    import std.math : sqrt;
+
+    // Print the 60 vertices of a uniform truncated icosahedron (soccer ball)
+    enum real Phi = (1.0 + sqrt(5.0)) / 2.0;    // Golden ratio
+    real[][] seeds = [
+        [0.0, 1.0, 3.0*Phi],
+        [1.0, 2.0+Phi, 2.0*Phi],
+        [Phi, 2.0, Phi^^3]
+    ];
+    size_t n;
+    foreach (seed; seeds)
+    {
+        // Loop over even permutations of each seed
+        do
+        {
+            // Loop over all sign changes of each permutation
+            size_t i;
+            do
+            {
+                // Generate all possible sign changes
+                for (i=0; i < seed.length; i++)
+                {
+                    if (seed[i] != 0.0)
+                    {
+                        seed[i] = -seed[i];
+                        if (seed[i] < 0.0)
+                            break;
+                    }
+                }
+                n++;
+            } while (i < seed.length);
+        } while (nextEvenPermutation(seed));
+    }
+    assert(n == 60);
+}
diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d
new file mode 100644
index 00000000000..56e552fca8f
--- /dev/null
+++ b/libphobos/src/std/array.d
@@ -0,0 +1,3574 @@
+// Written in the D programming language.
+/**
+Functions and types that manipulate built-in arrays and associative arrays.
+
+This module provides all kinds of functions to create, manipulate or convert arrays:
+
+$(BOOKTABLE ,
+$(TR $(TH Function Name) $(TH Description)
+)
+    $(TR $(TD $(D $(LREF _array)))
+        $(TD Returns a copy of the input in a newly allocated dynamic _array.
+    ))
+    $(TR $(TD $(D $(LREF appender)))
+        $(TD Returns a new Appender initialized with a given _array.
+    ))
+    $(TR $(TD $(D $(LREF assocArray)))
+        $(TD Returns a newly allocated associative _array from a range of key/value tuples.
+    ))
+    $(TR $(TD $(D $(LREF byPair)))
+        $(TD Construct a range iterating over an associative _array by key/value tuples.
+    ))
+    $(TR $(TD $(D $(LREF insertInPlace)))
+        $(TD Inserts into an existing _array at a given position.
+    ))
+    $(TR $(TD $(D $(LREF join)))
+        $(TD Concatenates a range of ranges into one _array.
+    ))
+    $(TR $(TD $(D $(LREF minimallyInitializedArray)))
+        $(TD Returns a new _array of type $(D T).
+    ))
+    $(TR $(TD $(D $(LREF replace)))
+        $(TD Returns a new _array with all occurrences of a certain subrange replaced.
+    ))
+    $(TR $(TD $(D $(LREF replaceFirst)))
+        $(TD Returns a new _array with the first occurrence of a certain subrange replaced.
+    ))
+    $(TR $(TD $(D $(LREF replaceInPlace)))
+        $(TD Replaces all occurrences of a certain subrange and puts the result into a given _array.
+    ))
+    $(TR $(TD $(D $(LREF replaceInto)))
+        $(TD Replaces all occurrences of a certain subrange and puts the result into an output range.
+    ))
+    $(TR $(TD $(D $(LREF replaceLast)))
+        $(TD Returns a new _array with the last occurrence of a certain subrange replaced.
+    ))
+    $(TR $(TD $(D $(LREF replaceSlice)))
+        $(TD Returns a new _array with a given slice replaced.
+    ))
+    $(TR $(TD $(D $(LREF replicate)))
+        $(TD Creates a new _array out of several copies of an input _array or range.
+    ))
+    $(TR $(TD $(D $(LREF sameHead)))
+        $(TD Checks if the initial segments of two arrays refer to the same
+        place in memory.
+    ))
+    $(TR $(TD $(D $(LREF sameTail)))
+        $(TD Checks if the final segments of two arrays refer to the same place
+        in memory.
+    ))
+    $(TR $(TD $(D $(LREF split)))
+        $(TD Eagerly split a range or string into an _array.
+    ))
+    $(TR $(TD $(D $(LREF uninitializedArray)))
+        $(TD Returns a new _array of type $(D T) without initializing its elements.
+    ))
+)
+
+Copyright: Copyright Andrei Alexandrescu 2008- and Jonathan M Davis 2011-.
+
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   $(WEB erdani.org, Andrei Alexandrescu) and Jonathan M Davis
+
+Source: $(PHOBOSSRC std/_array.d)
+*/
+module std.array;
+
+import std.meta;
+import std.traits;
+import std.functional;
+static import std.algorithm; // FIXME, remove with alias of splitter
+
+import std.range.primitives;
+public import std.range.primitives : save, empty, popFront, popBack, front, back;
+
+/**
+ * Allocates an array and initializes it with copies of the elements
+ * of range $(D r).
+ *
+ * Narrow strings are handled as a special case in an overload.
+ *
+ * Params:
+ *      r = range (or aggregate with $(D opApply) function) whose elements are copied into the allocated array
+ * Returns:
+ *      allocated and initialized array
+ */
+ForeachType!Range[] array(Range)(Range r)
+if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range)
+{
+    if (__ctfe)
+    {
+        // Compile-time version to avoid memcpy calls.
+        // Also used to infer attributes of array().
+        typeof(return) result;
+        foreach (e; r)
+            result ~= e;
+        return result;
+    }
+
+    alias E = ForeachType!Range;
+    static if (hasLength!Range)
+    {
+        auto length = r.length;
+        if (length == 0)
+            return null;
+
+        import std.conv : emplaceRef;
+
+        auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))();
+
+        // Every element of the uninitialized array must be initialized
+        size_t i;
+        foreach (e; r)
+        {
+            emplaceRef!E(result[i], e);
+            ++i;
+        }
+        return (() @trusted => cast(E[])result)();
+    }
+    else
+    {
+        auto a = appender!(E[])();
+        foreach (e; r)
+        {
+            a.put(e);
+        }
+        return a.data;
+    }
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = array([1, 2, 3, 4, 5][]);
+    assert(a == [ 1, 2, 3, 4, 5 ]);
+}
+
+@safe pure nothrow unittest
+{
+    import std.algorithm : equal;
+    struct Foo
+    {
+        int a;
+    }
+    auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]);
+    assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)]));
+}
+
+@system unittest
+{
+    import std.algorithm : equal;
+    struct Foo
+    {
+        int a;
+        auto opAssign(Foo foo)
+        {
+            assert(0);
+        }
+        auto opEquals(Foo foo)
+        {
+            return a == foo.a;
+        }
+    }
+    auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]);
+    assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)]));
+}
+
+unittest
+{
+    // Issue 12315
+    static struct Bug12315 { immutable int i; }
+    enum bug12315 = [Bug12315(123456789)].array();
+    static assert(bug12315[0].i == 123456789);
+}
+
+unittest
+{
+    import std.range;
+    static struct S{int* p;}
+    auto a = array(immutable(S).init.repeat(5));
+}
+
+/**
+Convert a narrow string to an array type that fully supports random access.
+This is handled as a special case and always returns a $(D dchar[]),
+$(D const(dchar)[]), or $(D immutable(dchar)[]) depending on the constness of
+the input.
+*/
+ElementType!String[] array(String)(String str) if (isNarrowString!String)
+{
+    import std.utf : toUTF32;
+    return cast(typeof(return)) str.toUTF32;
+}
+
+unittest
+{
+    import std.conv : to;
+
+    static struct TestArray { int x; string toString() { return to!string(x); } }
+
+    static struct OpAssign
+    {
+        uint num;
+        this(uint num) { this.num = num; }
+
+        // Templating opAssign to make sure the bugs with opAssign being
+        // templated are fixed.
+        void opAssign(T)(T rhs) { this.num = rhs.num; }
+    }
+
+    static struct OpApply
+    {
+        int opApply(int delegate(ref int) dg)
+        {
+            int res;
+            foreach(i; 0..10)
+            {
+                res = dg(i);
+                if(res) break;
+            }
+
+            return res;
+        }
+    }
+
+    auto a = array([1, 2, 3, 4, 5][]);
+    //writeln(a);
+    assert(a == [ 1, 2, 3, 4, 5 ]);
+
+    auto b = array([TestArray(1), TestArray(2)][]);
+    //writeln(b);
+
+    class C
+    {
+        int x;
+        this(int y) { x = y; }
+        override string toString() const { return to!string(x); }
+    }
+    auto c = array([new C(1), new C(2)][]);
+    //writeln(c);
+
+    auto d = array([1.0, 2.2, 3][]);
+    assert(is(typeof(d) == double[]));
+    //writeln(d);
+
+    auto e = [OpAssign(1), OpAssign(2)];
+    auto f = array(e);
+    assert(e == f);
+
+    assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]);
+    assert(array("ABC") == "ABC"d);
+    assert(array("ABC".dup) == "ABC"d.dup);
+}
+
+//Bug# 8233
+unittest
+{
+    assert(array("hello world"d) == "hello world"d);
+    immutable a = [1, 2, 3, 4, 5];
+    assert(array(a) == a);
+    const b = a;
+    assert(array(b) == a);
+
+    //To verify that the opAssign branch doesn't get screwed up by using Unqual.
+    //EDIT: array no longer calls opAssign.
+    struct S
+    {
+        ref S opAssign(S)(const ref S rhs)
+        {
+            assert(0);
+        }
+
+        int i;
+    }
+
+    foreach(T; AliasSeq!(S, const S, immutable S))
+    {
+        auto arr = [T(1), T(2), T(3), T(4)];
+        assert(array(arr) == arr);
+    }
+}
+
+unittest
+{
+    //9824
+    static struct S
+    {
+        @disable void opAssign(S);
+        int i;
+    }
+    auto arr = [S(0), S(1), S(2)];
+    arr.array();
+}
+
+// Bugzilla 10220
+unittest
+{
+    import std.exception;
+    import std.algorithm : equal;
+    import std.range : repeat;
+
+    static struct S
+    {
+        int val;
+
+        @disable this();
+        this(int v) { val = v; }
+    }
+    assertCTFEable!(
+    {
+        auto r = S(1).repeat(2).array();
+        assert(equal(r, [S(1), S(1)]));
+    });
+}
+
+unittest
+{
+    //Turn down infinity:
+    static assert(!is(typeof(
+        repeat(1).array()
+    )));
+}
+
+/**
+Returns a newly allocated associative _array from a range of key/value tuples.
+Params: r = An input range of tuples of keys and values.
+Returns: A newly allocated associative array out of elements of the input
+range, which must be a range of tuples (Key, Value). Returns a null associative
+array reference when given an empty range.
+Duplicates: Associative arrays have unique keys. If r contains duplicate keys,
+then the result will contain the value of the last pair for that key in r.
+See_Also: $(XREF typecons, Tuple)
+ */
+
+auto assocArray(Range)(Range r)
+    if (isInputRange!Range)
+{
+    import std.typecons : isTuple;
+
+    alias E = ElementType!Range;
+    static assert(isTuple!E, "assocArray: argument must be a range of tuples");
+    static assert(E.length == 2, "assocArray: tuple dimension must be 2");
+    alias KeyType = E.Types[0];
+    alias ValueType = E.Types[1];
+    static assert(isMutable!ValueType, "assocArray: value type must be mutable");
+
+    ValueType[KeyType] aa;
+    foreach (t; r)
+        aa[t[0]] = t[1];
+    return aa;
+}
+
+///
+/*@safe*/ pure /*nothrow*/ unittest
+{
+    import std.range;
+    import std.typecons;
+    auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"]));
+    assert(is(typeof(a) == string[int]));
+    assert(a == [0:"a", 1:"b", 2:"c"]);
+
+    auto b = assocArray([ tuple("foo", "bar"), tuple("baz", "quux") ]);
+    assert(is(typeof(b) == string[string]));
+    assert(b == ["foo":"bar", "baz":"quux"]);
+}
+
+// @@@11053@@@ - Cannot be version(unittest) - recursive instantiation error
+unittest
+{
+    import std.typecons;
+    static assert(!__traits(compiles, [ tuple("foo", "bar", "baz") ].assocArray()));
+    static assert(!__traits(compiles, [ tuple("foo") ].assocArray()));
+    assert([ tuple("foo", "bar") ].assocArray() == ["foo": "bar"]);
+}
+
+// Issue 13909
+unittest
+{
+    import std.typecons;
+    auto a = [tuple!(const string, string)("foo", "bar")];
+    auto b = [tuple!(string, const string)("foo", "bar")];
+    assert(assocArray(a) == [cast(const(string)) "foo": "bar"]);
+    static assert(!__traits(compiles, assocArray(b)));
+}
+
+/**
+Construct a range iterating over an associative array by key/value tuples.
+
+Params: aa = The associative array to iterate over.
+
+Returns: A forward range of Tuple's of key and value pairs from the given
+associative array.
+*/
+auto byPair(Key, Value)(Value[Key] aa)
+{
+    import std.typecons : tuple;
+    import std.algorithm : map;
+
+    return aa.byKeyValue.map!(pair => tuple(pair.key, pair.value));
+}
+
+///
+unittest
+{
+    import std.typecons : tuple, Tuple;
+    import std.algorithm : sort;
+
+    auto aa = ["a": 1, "b": 2, "c": 3];
+    Tuple!(string, int)[] pairs;
+
+    // Iteration over key/value pairs.
+    foreach (pair; aa.byPair)
+    {
+        pairs ~= pair;
+    }
+
+    // Iteration order is implementation-dependent, so we should sort it to get
+    // a fixed order.
+    sort(pairs);
+    assert(pairs == [
+        tuple("a", 1),
+        tuple("b", 2),
+        tuple("c", 3)
+    ]);
+}
+
+unittest
+{
+    import std.typecons : tuple, Tuple;
+
+    auto aa = ["a":2];
+    auto pairs = aa.byPair();
+
+    static assert(is(typeof(pairs.front) == Tuple!(string,int)));
+    static assert(isForwardRange!(typeof(pairs)));
+
+    assert(!pairs.empty);
+    assert(pairs.front == tuple("a", 2));
+
+    auto savedPairs = pairs.save;
+
+    pairs.popFront();
+    assert(pairs.empty);
+    assert(!savedPairs.empty);
+    assert(savedPairs.front == tuple("a", 2));
+}
+
+private template blockAttribute(T)
+{
+    import core.memory;
+    static if (hasIndirections!(T) || is(T == void))
+    {
+        enum blockAttribute = 0;
+    }
+    else
+    {
+        enum blockAttribute = GC.BlkAttr.NO_SCAN;
+    }
+}
+version(unittest)
+{
+    import core.memory : UGC = GC;
+    static assert(!(blockAttribute!void & UGC.BlkAttr.NO_SCAN));
+}
+
+// Returns the number of dimensions in an array T.
+private template nDimensions(T)
+{
+    static if(isArray!T)
+    {
+        enum nDimensions = 1 + nDimensions!(typeof(T.init[0]));
+    }
+    else
+    {
+        enum nDimensions = 0;
+    }
+}
+
+version(unittest)
+{
+    static assert(nDimensions!(uint[]) == 1);
+    static assert(nDimensions!(float[][]) == 2);
+}
+
+/++
+Returns a new array of type $(D T) allocated on the garbage collected heap
+without initializing its elements.  This can be a useful optimization if every
+element will be immediately initialized.  $(D T) may be a multidimensional
+array.  In this case sizes may be specified for any number of dimensions from 0
+to the number in $(D T).
+
+uninitializedArray is nothrow and weakly pure.
+
+uninitializedArray is @system if the uninitialized element type has pointers.
++/
+auto uninitializedArray(T, I...)(I sizes) nothrow @system
+if (isDynamicArray!T && allSatisfy!(isIntegral, I) && hasIndirections!(ElementEncodingType!T))
+{
+    enum isSize_t(E) = is (E : size_t);
+    alias toSize_t(E) = size_t;
+
+    static assert(allSatisfy!(isSize_t, I),
+        "Argument types in "~I.stringof~" are not all convertible to size_t: "
+        ~Filter!(templateNot!(isSize_t), I).stringof);
+
+    //Eagerlly transform non-size_t into size_t to avoid template bloat
+    alias ST = staticMap!(toSize_t, I);
+
+    return arrayAllocImpl!(false, T, ST)(sizes);
+}
+
+///
+auto uninitializedArray(T, I...)(I sizes) nothrow @trusted
+if (isDynamicArray!T && allSatisfy!(isIntegral, I) && !hasIndirections!(ElementEncodingType!T))
+{
+    enum isSize_t(E) = is (E : size_t);
+    alias toSize_t(E) = size_t;
+
+    static assert(allSatisfy!(isSize_t, I),
+        "Argument types in "~I.stringof~" are not all convertible to size_t: "
+        ~Filter!(templateNot!(isSize_t), I).stringof);
+
+    //Eagerlly transform non-size_t into size_t to avoid template bloat
+    alias ST = staticMap!(toSize_t, I);
+
+    return arrayAllocImpl!(false, T, ST)(sizes);
+}
+///
+@system nothrow pure unittest
+{
+    double[] arr = uninitializedArray!(double[])(100);
+    assert(arr.length == 100);
+
+    double[][] matrix = uninitializedArray!(double[][])(42, 31);
+    assert(matrix.length == 42);
+    assert(matrix[0].length == 31);
+
+    char*[] ptrs = uninitializedArray!(char*[])(100);
+    assert(ptrs.length == 100);
+}
+
+/++
+Returns a new array of type $(D T) allocated on the garbage collected heap.
+
+Partial initialization is done for types with indirections, for preservation
+of memory safety. Note that elements will only be initialized to 0, but not
+necessarily the element type's $(D .init).
+
+minimallyInitializedArray is nothrow and weakly pure.
++/
+auto minimallyInitializedArray(T, I...)(I sizes) nothrow @trusted
+if (isDynamicArray!T && allSatisfy!(isIntegral, I))
+{
+    enum isSize_t(E) = is (E : size_t);
+    alias toSize_t(E) = size_t;
+
+    static assert(allSatisfy!(isSize_t, I),
+        "Argument types in "~I.stringof~" are not all convertible to size_t: "
+        ~Filter!(templateNot!(isSize_t), I).stringof);
+    //Eagerlly transform non-size_t into size_t to avoid template bloat
+    alias ST = staticMap!(toSize_t, I);
+
+    return arrayAllocImpl!(true, T, ST)(sizes);
+}
+
+@safe pure nothrow unittest
+{
+    cast(void)minimallyInitializedArray!(int[][][][][])();
+    double[] arr = minimallyInitializedArray!(double[])(100);
+    assert(arr.length == 100);
+
+    double[][] matrix = minimallyInitializedArray!(double[][])(42);
+    assert(matrix.length == 42);
+    foreach(elem; matrix)
+    {
+        assert(elem.ptr is null);
+    }
+}
+
+private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow
+{
+    static assert(I.length <= nDimensions!T,
+        I.length.stringof~"dimensions specified for a "~nDimensions!T.stringof~" dimensional array.");
+
+    alias E = ElementEncodingType!T;
+
+    E[] ret;
+
+    static if (I.length != 0)
+    {
+        static assert (is(I[0] == size_t));
+        alias size = sizes[0];
+    }
+
+    static if (I.length == 1)
+    {
+        if (__ctfe)
+        {
+            static if (__traits(compiles, new E[](size)))
+                ret = new E[](size);
+            else static if (__traits(compiles, ret ~= E.init))
+            {
+                try
+                {
+                    //Issue: if E has an impure postblit, then all of arrayAllocImpl
+                    //Will be impure, even during non CTFE.
+                    foreach (i; 0 .. size)
+                        ret ~= E.init;
+                }
+                catch (Exception e)
+                    throw new Error(e.msg);
+            }
+            else
+                assert(0, "No postblit nor default init on " ~ E.stringof ~
+                    ": At least one is required for CTFE.");
+        }
+        else
+        {
+            import core.stdc.string : memset;
+            import core.memory;
+            auto ptr = cast(E*) GC.malloc(sizes[0] * E.sizeof, blockAttribute!E);
+            static if (minimallyInitialized && hasIndirections!E)
+                memset(ptr, 0, size * E.sizeof);
+            ret = ptr[0 .. size];
+        }
+    }
+    else static if (I.length > 1)
+    {
+        ret = arrayAllocImpl!(false, E[])(size);
+        foreach(ref elem; ret)
+            elem = arrayAllocImpl!(minimallyInitialized, E)(sizes[1..$]);
+    }
+
+    return ret;
+}
+
+nothrow pure unittest
+{
+    auto s1 = uninitializedArray!(int[])();
+    auto s2 = minimallyInitializedArray!(int[])();
+    assert(s1.length == 0);
+    assert(s2.length == 0);
+}
+
+nothrow pure unittest //@@@9803@@@
+{
+    auto a = minimallyInitializedArray!(int*[])(1);
+    assert(a[0] == null);
+    auto b = minimallyInitializedArray!(int[][])(1);
+    assert(b[0].empty);
+    auto c = minimallyInitializedArray!(int*[][])(1, 1);
+    assert(c[0][0] == null);
+}
+
+unittest //@@@10637@@@
+{
+    static struct S
+    {
+        static struct I{int i; alias i this;}
+        int* p;
+        this() @disable;
+        this(int i)
+        {
+            p = &(new I(i)).i;
+        }
+        this(this)
+        {
+            p = &(new I(*p)).i;
+        }
+        ~this()
+        {
+            assert(p != null);
+        }
+    }
+    auto a = minimallyInitializedArray!(S[])(1);
+    assert(a[0].p == null);
+    enum b = minimallyInitializedArray!(S[])(1);
+}
+
+nothrow unittest
+{
+    static struct S1
+    {
+        this() @disable;
+        this(this) @disable;
+    }
+    auto a1 = minimallyInitializedArray!(S1[][])(2, 2);
+    //enum b1 = minimallyInitializedArray!(S1[][])(2, 2);
+    static struct S2
+    {
+        this() @disable;
+        //this(this) @disable;
+    }
+    auto a2 = minimallyInitializedArray!(S2[][])(2, 2);
+    enum b2 = minimallyInitializedArray!(S2[][])(2, 2);
+    static struct S3
+    {
+        //this() @disable;
+        this(this) @disable;
+    }
+    auto a3 = minimallyInitializedArray!(S3[][])(2, 2);
+    enum b3 = minimallyInitializedArray!(S3[][])(2, 2);
+}
+
+// overlap
+/*
+NOTE: Undocumented for now, overlap does not yet work with ctfe.
+Returns the overlapping portion, if any, of two arrays. Unlike $(D
+equal), $(D overlap) only compares the pointers in the ranges, not the
+values referred by them. If $(D r1) and $(D r2) have an overlapping
+slice, returns that slice. Otherwise, returns the null slice.
+*/
+inout(T)[] overlap(T)(inout(T)[] r1, inout(T)[] r2) @trusted pure nothrow
+{
+    alias U = inout(T);
+    static U* max(U* a, U* b) nothrow { return a > b ? a : b; }
+    static U* min(U* a, U* b) nothrow { return a < b ? a : b; }
+
+    auto b = max(r1.ptr, r2.ptr);
+    auto e = min(r1.ptr + r1.length, r2.ptr + r2.length);
+    return b < e ? b[0 .. e - b] : null;
+}
+
+///
+@safe pure /*nothrow*/ unittest
+{
+    int[] a = [ 10, 11, 12, 13, 14 ];
+    int[] b = a[1 .. 3];
+    assert(overlap(a, b) == [ 11, 12 ]);
+    b = b.dup;
+    // overlap disappears even though the content is the same
+    assert(overlap(a, b).empty);
+}
+
+/*@safe nothrow*/ unittest
+{
+    static void test(L, R)(L l, R r)
+    {
+        import std.stdio;
+        scope(failure) writeln("Types: L %s  R %s", L.stringof, R.stringof);
+
+        assert(overlap(l, r) == [ 100, 12 ]);
+
+        assert(overlap(l, l[0 .. 2]) is l[0 .. 2]);
+        assert(overlap(l, l[3 .. 5]) is l[3 .. 5]);
+        assert(overlap(l[0 .. 2], l) is l[0 .. 2]);
+        assert(overlap(l[3 .. 5], l) is l[3 .. 5]);
+    }
+
+    int[] a = [ 10, 11, 12, 13, 14 ];
+    int[] b = a[1 .. 3];
+    a[1] = 100;
+
+    immutable int[] c = a.idup;
+    immutable int[] d = c[1 .. 3];
+
+    test(a, b);
+    assert(overlap(a, b.dup).empty);
+    test(c, d);
+    assert(overlap(c, d.idup).empty);
+}
+
+@safe pure nothrow unittest // bugzilla 9836
+{
+    // range primitives for array should work with alias this types
+    struct Wrapper
+    {
+        int[] data;
+        alias data this;
+
+        @property Wrapper save() { return this; }
+    }
+    auto w = Wrapper([1,2,3,4]);
+    std.array.popFront(w); // should work
+
+    static assert(isInputRange!Wrapper);
+    static assert(isForwardRange!Wrapper);
+    static assert(isBidirectionalRange!Wrapper);
+    static assert(isRandomAccessRange!Wrapper);
+}
+
+private void copyBackwards(T)(T[] src, T[] dest)
+{
+    import core.stdc.string;
+
+    assert(src.length == dest.length);
+
+    if (!__ctfe || hasElaborateCopyConstructor!T)
+    {
+        /* insertInPlace relies on dest being uninitialized, so no postblits allowed,
+         * as this is a MOVE that overwrites the destination, not a COPY.
+         * BUG: insertInPlace will not work with ctfe and postblits
+         */
+        memmove(dest.ptr, src.ptr, src.length * T.sizeof);
+    }
+    else
+    {
+        immutable len = src.length;
+        for (size_t i = len; i-- > 0;)
+        {
+            dest[i] = src[i];
+        }
+    }
+}
+
+/++
+    Inserts $(D stuff) (which must be an input range or any number of
+    implicitly convertible items) in $(D array) at position $(D pos).
+
+ +/
+void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff)
+    if(!isSomeString!(T[])
+        && allSatisfy!(isInputRangeOrConvertible!T, U) && U.length > 0)
+{
+    static if(allSatisfy!(isInputRangeWithLengthOrConvertible!T, U))
+    {
+        import std.conv : emplaceRef;
+
+        immutable oldLen = array.length;
+
+        size_t to_insert = 0;
+        foreach (i, E; U)
+        {
+            static if (is(E : T)) //a single convertible value, not a range
+                to_insert += 1;
+            else
+                to_insert += stuff[i].length;
+        }
+        if (to_insert)
+        {
+            array.length += to_insert;
+
+            // Takes arguments array, pos, stuff
+            // Spread apart array[] at pos by moving elements
+            (() @trusted { copyBackwards(array[pos..oldLen], array[pos+to_insert..$]); })();
+
+            // Initialize array[pos .. pos+to_insert] with stuff[]
+            auto j = 0;
+            foreach (i, E; U)
+            {
+                static if (is(E : T))
+                {
+                    emplaceRef!T(array[pos + j++], stuff[i]);
+                }
+                else
+                {
+                    foreach (v; stuff[i])
+                    {
+                        emplaceRef!T(array[pos + j++], v);
+                    }
+                }
+            }
+        }
+    }
+    else
+    {
+        // stuff has some InputRanges in it that don't have length
+        // assume that stuff to be inserted is typically shorter
+        // then the array that can be arbitrary big
+        // TODO: needs a better implementation as there is no need to build an _array_
+        // a singly-linked list of memory blocks (rope, etc.) will do
+        auto app = appender!(T[])();
+        foreach (i, E; U)
+            app.put(stuff[i]);
+        insertInPlace(array, pos, app.data);
+    }
+}
+
+/// Ditto
+void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff)
+    if(isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U))
+{
+    static if(is(Unqual!T == T)
+        && allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U))
+    {
+        import std.utf : codeLength;
+        // mutable, can do in place
+        //helper function: re-encode dchar to Ts and store at *ptr
+        static T* putDChar(T* ptr, dchar ch)
+        {
+            static if(is(T == dchar))
+            {
+                *ptr++ = ch;
+                return ptr;
+            }
+            else
+            {
+                import std.utf : encode;
+                T[dchar.sizeof/T.sizeof] buf;
+                size_t len = encode(buf, ch);
+                final switch(len)
+                {
+                    static if(T.sizeof == char.sizeof)
+                    {
+                case 4:
+                        ptr[3] = buf[3];
+                        goto case;
+                case 3:
+                        ptr[2] = buf[2];
+                        goto case;
+                    }
+                case 2:
+                    ptr[1] = buf[1];
+                    goto case;
+                case 1:
+                    ptr[0] = buf[0];
+                }
+                ptr += len;
+                return ptr;
+            }
+        }
+        immutable oldLen = array.length;
+        size_t to_insert = 0;
+        //count up the number of *codeunits* to insert
+        foreach (i, E; U)
+            to_insert += codeLength!T(stuff[i]);
+        array.length += to_insert;
+
+        @trusted static void moveToRight(T[] arr, size_t gap)
+        {
+            static assert(!hasElaborateCopyConstructor!T);
+            import core.stdc.string;
+            if (__ctfe)
+            {
+                for (size_t i = arr.length - gap; i; --i)
+                    arr[gap + i - 1] = arr[i - 1];
+            }
+            else
+                memmove(arr.ptr + gap, arr.ptr, (arr.length - gap) * T.sizeof);
+        }
+        moveToRight(array[pos .. $], to_insert);
+        auto ptr = array.ptr + pos;
+        foreach (i, E; U)
+        {
+            static if(is(E : dchar))
+            {
+                ptr = putDChar(ptr, stuff[i]);
+            }
+            else
+            {
+                foreach (dchar ch; stuff[i])
+                    ptr = putDChar(ptr, ch);
+            }
+        }
+        assert(ptr == array.ptr + pos + to_insert, "(ptr == array.ptr + pos + to_insert) is false");
+    }
+    else
+    {
+        // immutable/const, just construct a new array
+        auto app = appender!(T[])();
+        app.put(array[0..pos]);
+        foreach (i, E; U)
+            app.put(stuff[i]);
+        app.put(array[pos..$]);
+        array = app.data;
+    }
+}
+
+///
+@safe pure unittest
+{
+    int[] a = [ 1, 2, 3, 4 ];
+    a.insertInPlace(2, [ 1, 2 ]);
+    assert(a == [ 1, 2, 1, 2, 3, 4 ]);
+    a.insertInPlace(3, 10u, 11);
+    assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]);
+}
+
+//constraint helpers
+private template isInputRangeWithLengthOrConvertible(E)
+{
+    template isInputRangeWithLengthOrConvertible(R)
+    {
+        //hasLength not defined for char[], wchar[] and dchar[]
+        enum isInputRangeWithLengthOrConvertible =
+            (isInputRange!R && is(typeof(R.init.length))
+                && is(ElementType!R : E))  || is(R : E);
+    }
+}
+
+//ditto
+private template isCharOrStringOrDcharRange(T)
+{
+    enum isCharOrStringOrDcharRange = isSomeString!T || isSomeChar!T ||
+        (isInputRange!T && is(ElementType!T : dchar));
+}
+
+//ditto
+private template isInputRangeOrConvertible(E)
+{
+    template isInputRangeOrConvertible(R)
+    {
+        enum isInputRangeOrConvertible =
+            (isInputRange!R && is(ElementType!R : E))  || is(R : E);
+    }
+}
+
+unittest
+{
+    import core.exception;
+    import std.conv : to;
+    import std.exception;
+    import std.algorithm;
+
+
+    bool test(T, U, V)(T orig, size_t pos, U toInsert, V result,
+               string file = __FILE__, size_t line = __LINE__)
+    {
+        {
+            static if(is(T == typeof(T.init.dup)))
+                auto a = orig.dup;
+            else
+                auto a = orig.idup;
+
+            a.insertInPlace(pos, toInsert);
+            if(!std.algorithm.equal(a, result))
+                return false;
+        }
+
+        static if(isInputRange!U)
+        {
+            orig.insertInPlace(pos, filter!"true"(toInsert));
+            return std.algorithm.equal(orig, result);
+        }
+        else
+            return true;
+    }
+
+
+    assert(test([1, 2, 3, 4], 0, [6, 7], [6, 7, 1, 2, 3, 4]));
+    assert(test([1, 2, 3, 4], 2, [8, 9], [1, 2, 8, 9, 3, 4]));
+    assert(test([1, 2, 3, 4], 4, [10, 11], [1, 2, 3, 4, 10, 11]));
+
+    assert(test([1, 2, 3, 4], 0, 22, [22, 1, 2, 3, 4]));
+    assert(test([1, 2, 3, 4], 2, 23, [1, 2, 23, 3, 4]));
+    assert(test([1, 2, 3, 4], 4, 24, [1, 2, 3, 4, 24]));
+
+    auto testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
+    {
+
+        auto l = to!T("hello");
+        auto r = to!U(" વિશ્વ");
+
+        enforce(test(l, 0, r, " વિશ્વhello"),
+                new AssertError("testStr failure 1", file, line));
+        enforce(test(l, 3, r, "hel વિશ્વlo"),
+                new AssertError("testStr failure 2", file, line));
+        enforce(test(l, l.length, r, "hello વિશ્વ"),
+                new AssertError("testStr failure 3", file, line));
+    }
+
+    foreach (T; AliasSeq!(char, wchar, dchar,
+        immutable(char), immutable(wchar), immutable(dchar)))
+    {
+        foreach (U; AliasSeq!(char, wchar, dchar,
+            immutable(char), immutable(wchar), immutable(dchar)))
+        {
+            testStr!(T[], U[])();
+        }
+
+    }
+
+    // variadic version
+    bool testVar(T, U...)(T orig, size_t pos, U args)
+    {
+        static if(is(T == typeof(T.init.dup)))
+            auto a = orig.dup;
+        else
+            auto a = orig.idup;
+        auto result = args[$-1];
+
+        a.insertInPlace(pos, args[0..$-1]);
+        if (!std.algorithm.equal(a, result))
+            return false;
+        return true;
+    }
+    assert(testVar([1, 2, 3, 4], 0, 6, 7u, [6, 7, 1, 2, 3, 4]));
+    assert(testVar([1L, 2, 3, 4], 2, 8, 9L, [1, 2, 8, 9, 3, 4]));
+    assert(testVar([1L, 2, 3, 4], 4, 10L, 11, [1, 2, 3, 4, 10, 11]));
+    assert(testVar([1L, 2, 3, 4], 4, [10, 11], 40L, 42L,
+                    [1, 2, 3, 4, 10, 11, 40, 42]));
+    assert(testVar([1L, 2, 3, 4], 4, 10, 11, [40L, 42],
+                    [1, 2, 3, 4, 10, 11, 40, 42]));
+    assert(testVar("t".idup, 1, 'e', 's', 't', "test"));
+    assert(testVar("!!"w.idup, 1, "\u00e9ll\u00f4", 'x', "TTT"w, 'y',
+                    "!\u00e9ll\u00f4xTTTy!"));
+    assert(testVar("flipflop"d.idup, 4, '_',
+                    "xyz"w, '\U00010143', '_', "abc"d, "__",
+                    "flip_xyz\U00010143_abc__flop"));
+}
+
+unittest
+{
+    import std.algorithm : equal;
+    // insertInPlace interop with postblit
+    static struct Int
+    {
+        int* payload;
+        this(int k)
+        {
+            payload = new int;
+            *payload = k;
+        }
+        this(this)
+        {
+            int* np = new int;
+            *np = *payload;
+            payload = np;
+        }
+        ~this()
+        {
+            if (payload)
+                *payload = 0; //'destroy' it
+        }
+        @property int getPayload(){ return *payload; }
+        alias getPayload this;
+    }
+
+    Int[] arr = [Int(1), Int(4), Int(5)];
+    assert(arr[0] == 1);
+    insertInPlace(arr, 1, Int(2), Int(3));
+    assert(equal(arr, [1, 2, 3, 4, 5]));  //check it works with postblit
+
+    version (none) // illustrates that insertInPlace() will not work with CTFE and postblit
+    {
+        static bool testctfe()
+        {
+            Int[] arr = [Int(1), Int(4), Int(5)];
+            assert(arr[0] == 1);
+            insertInPlace(arr, 1, Int(2), Int(3));
+            return equal(arr, [1, 2, 3, 4, 5]);  //check it works with postblit
+        }
+        enum E = testctfe();
+    }
+}
+
+@safe unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+        int[] a = [1, 2];
+        a.insertInPlace(2, 3);
+        a.insertInPlace(0, -1, 0);
+        return a == [-1, 0, 1, 2, 3];
+    });
+}
+
+unittest // bugzilla 6874
+{
+    import core.memory;
+    // allocate some space
+    byte[] a;
+    a.length = 1;
+
+    // fill it
+    a.length = a.capacity;
+
+    // write beyond
+    byte[] b = a[$ .. $];
+    b.insertInPlace(0, a);
+
+    // make sure that reallocation has happened
+    assert(GC.addrOf(&b[0]) == GC.addrOf(&b[$-1]));
+}
+
+
+/++
+    Returns whether the $(D front)s of $(D lhs) and $(D rhs) both refer to the
+    same place in memory, making one of the arrays a slice of the other which
+    starts at index $(D 0).
+  +/
+@safe
+pure nothrow bool sameHead(T)(in T[] lhs, in T[] rhs)
+{
+    return lhs.ptr == rhs.ptr;
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [1, 2, 3, 4, 5];
+    auto b = a[0..2];
+
+    assert(a.sameHead(b));
+}
+
+
+/++
+    Returns whether the $(D back)s of $(D lhs) and $(D rhs) both refer to the
+    same place in memory, making one of the arrays a slice of the other which
+    end at index $(D $).
+  +/
+@trusted
+pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs)
+{
+    return lhs.ptr + lhs.length == rhs.ptr + rhs.length;
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [1, 2, 3, 4, 5];
+    auto b = a[3..$];
+
+    assert(a.sameTail(b));
+}
+
+@safe pure nothrow unittest
+{
+    foreach(T; AliasSeq!(int[], const(int)[], immutable(int)[], const int[], immutable int[]))
+    {
+        T a = [1, 2, 3, 4, 5];
+        T b = a;
+        T c = a[1 .. $];
+        T d = a[0 .. 1];
+        T e = null;
+
+        assert(sameHead(a, a));
+        assert(sameHead(a, b));
+        assert(!sameHead(a, c));
+        assert(sameHead(a, d));
+        assert(!sameHead(a, e));
+
+        assert(sameTail(a, a));
+        assert(sameTail(a, b));
+        assert(sameTail(a, c));
+        assert(!sameTail(a, d));
+        assert(!sameTail(a, e));
+
+        //verifies R-value compatibilty
+        assert(a.sameHead(a[0 .. 0]));
+        assert(a.sameTail(a[$ .. $]));
+    }
+}
+
+/********************************************
+Returns an array that consists of $(D s) (which must be an input
+range) repeated $(D n) times. This function allocates, fills, and
+returns a new array. For a lazy version, refer to $(XREF range, repeat).
+ */
+ElementEncodingType!S[] replicate(S)(S s, size_t n) if (isDynamicArray!S)
+{
+    alias RetType = ElementEncodingType!S[];
+
+    // Optimization for return join(std.range.repeat(s, n));
+    if (n == 0)
+        return RetType.init;
+    if (n == 1)
+        return cast(RetType) s;
+    auto r = new Unqual!(typeof(s[0]))[n * s.length];
+    if (s.length == 1)
+        r[] = s[0];
+    else
+    {
+        immutable len = s.length, nlen = n * len;
+        for (size_t i = 0; i < nlen; i += len)
+        {
+            r[i .. i + len] = s[];
+        }
+    }
+    return r;
+}
+
+/// ditto
+ElementType!S[] replicate(S)(S s, size_t n)
+if (isInputRange!S && !isDynamicArray!S)
+{
+    import std.range : repeat;
+    return join(std.range.repeat(s, n));
+}
+
+
+///
+unittest
+{
+    auto a = "abc";
+    auto s = replicate(a, 3);
+
+    assert(s == "abcabcabc");
+
+    auto b = [1, 2, 3];
+    auto c = replicate(b, 3);
+
+    assert(c == [1, 2, 3, 1, 2, 3, 1, 2, 3]);
+
+    auto d = replicate(b, 0);
+
+    assert(d == []);
+}
+
+unittest
+{
+    import std.conv : to;
+
+    debug(std_array) printf("array.replicate.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+    {
+        S s;
+        immutable S t = "abc";
+
+        assert(replicate(to!S("1234"), 0) is null);
+        assert(replicate(to!S("1234"), 0) is null);
+        assert(replicate(to!S("1234"), 1) == "1234");
+        assert(replicate(to!S("1234"), 2) == "12341234");
+        assert(replicate(to!S("1"), 4) == "1111");
+        assert(replicate(t, 3) == "abcabcabc");
+        assert(replicate(cast(S) null, 4) is null);
+    }
+}
+
+/++
+Eagerly split the string $(D s) into an array of words, using whitespace as
+delimiter. Runs of whitespace are merged together (no empty words are produced).
+
+$(D @safe), $(D pure) and $(D CTFE)-able.
+
+See_Also:
+$(XREF_PACK algorithm,iteration,splitter) for a version that splits using any
+separator.
+
+$(XREF regex, splitter) for a version that splits using a regular
+expression defined separator.
++/
+S[] split(S)(S s) @safe pure
+if (isSomeString!S)
+{
+    size_t istart;
+    bool inword = false;
+    S[] result;
+
+    foreach (i, dchar c ; s)
+    {
+        import std.uni : isWhite;
+        if (isWhite(c))
+        {
+            if (inword)
+            {
+                result ~= s[istart .. i];
+                inword = false;
+            }
+        }
+        else
+        {
+            if (!inword)
+            {
+                istart = i;
+                inword = true;
+            }
+        }
+    }
+    if (inword)
+        result ~= s[istart .. $];
+    return result;
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.format;
+    import std.typecons;
+
+    static auto makeEntry(S)(string l, string[] r)
+    {return tuple(l.to!S(), r.to!(S[])());}
+
+    foreach (S; AliasSeq!(string, wstring, dstring,))
+    {
+        auto entries =
+        [
+            makeEntry!S("", []),
+            makeEntry!S(" ", []),
+            makeEntry!S("hello", ["hello"]),
+            makeEntry!S(" hello ", ["hello"]),
+            makeEntry!S("  h  e  l  l  o ", ["h", "e", "l", "l", "o"]),
+            makeEntry!S("peter\t\npaul\rjerry", ["peter", "paul", "jerry"]),
+            makeEntry!S(" \t\npeter paul\tjerry \n", ["peter", "paul", "jerry"]),
+            makeEntry!S("\u2000日\u202F本\u205F語\u3000", ["日", "本", "語"]),
+            makeEntry!S("　　哈・郎博尔德｝　　　　___一个", ["哈・郎博尔德｝", "___一个"])
+        ];
+        foreach (entry; entries)
+            assert(entry[0].split() == entry[1], format("got: %s, expected: %s.", entry[0].split(), entry[1]));
+    }
+
+    //Just to test that an immutable is split-able
+    immutable string s = " \t\npeter paul\tjerry \n";
+    assert(split(s) == ["peter", "paul", "jerry"]);
+}
+
+unittest //safety, purity, ctfe ...
+{
+    import std.exception;
+    void dg() @safe pure {
+        assert(split("hello world"c) == ["hello"c, "world"c]);
+        assert(split("hello world"w) == ["hello"w, "world"w]);
+        assert(split("hello world"d) == ["hello"d, "world"d]);
+    }
+    dg();
+    assertCTFEable!dg;
+}
+
+///
+unittest
+{
+    assert(split("hello world") == ["hello","world"]);
+    assert(split("192.168.0.1", ".") == ["192", "168", "0", "1"]);
+
+    auto a = split([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], [2, 3]);
+    assert(a == [[1], [4, 5, 1], [4, 5]]);
+}
+
+// @@@DEPRECATED_2017-01@@@
+/++
+    $(RED Deprecated. Use $(XREF_PACK algorithm,iteration,_splitter) instead.
+          This will be removed in January 2017.)
+
+    Alias for $(XREF_PACK algorithm,iteration,_splitter).
+ +/
+deprecated("Please use std.algorithm.iteration.splitter instead.")
+alias splitter = std.algorithm.iteration.splitter;
+
+/++
+    Eagerly splits $(D range) into an array, using $(D sep) as the delimiter.
+
+    The _range must be a
+    $(XREF_PACK_NAMED _range,primitives,isForwardRange,forward _range).
+    The separator can be a value of the same type as the elements in $(D range)
+    or it can be another forward _range.
+
+    Example:
+        If $(D range) is a $(D string), $(D sep) can be a $(D char) or another
+        $(D string). The return type will be an array of strings. If $(D range) is
+        an $(D int) array, $(D sep) can be an $(D int) or another $(D int) array.
+        The return type will be an array of $(D int) arrays.
+
+    Params:
+        range = a forward _range.
+        sep = a value of the same type as the elements of $(D range) or another
+        forward range.
+
+    Returns:
+        An array containing the divided parts of $(D range).
+
+    See_Also:
+        $(XREF_PACK algorithm,iteration,splitter) for the lazy version of this
+        function.
+ +/
+auto split(Range, Separator)(Range range, Separator sep)
+if (isForwardRange!Range && is(typeof(ElementType!Range.init == Separator.init)))
+{
+    import std.algorithm : splitter;
+    return range.splitter(sep).array;
+}
+///ditto
+auto split(Range, Separator)(Range range, Separator sep)
+if (isForwardRange!Range && isForwardRange!Separator && is(typeof(ElementType!Range.init == ElementType!Separator.init)))
+{
+    import std.algorithm : splitter;
+    return range.splitter(sep).array;
+}
+///ditto
+auto split(alias isTerminator, Range)(Range range)
+if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front))))
+{
+    import std.algorithm : splitter;
+    return range.splitter!isTerminator.array;
+}
+
+unittest
+{
+    import std.conv;
+    import std.algorithm : cmp;
+
+    debug(std_array) printf("array.split\n");
+    foreach (S; AliasSeq!(string, wstring, dstring,
+                    immutable(string), immutable(wstring), immutable(dstring),
+                    char[], wchar[], dchar[],
+                    const(char)[], const(wchar)[], const(dchar)[],
+                    const(char[]), immutable(char[])))
+    {
+        S s = to!S(",peter,paul,jerry,");
+
+        auto words = split(s, ",");
+        assert(words.length == 5, text(words.length));
+        assert(cmp(words[0], "") == 0);
+        assert(cmp(words[1], "peter") == 0);
+        assert(cmp(words[2], "paul") == 0);
+        assert(cmp(words[3], "jerry") == 0);
+        assert(cmp(words[4], "") == 0);
+
+        auto s1 = s[0 .. s.length - 1];   // lop off trailing ','
+        words = split(s1, ",");
+        assert(words.length == 4);
+        assert(cmp(words[3], "jerry") == 0);
+
+        auto s2 = s1[1 .. s1.length];   // lop off leading ','
+        words = split(s2, ",");
+        assert(words.length == 3);
+        assert(cmp(words[0], "peter") == 0);
+
+        auto s3 = to!S(",,peter,,paul,,jerry,,");
+
+        words = split(s3, ",,");
+        assert(words.length == 5);
+        assert(cmp(words[0], "") == 0);
+        assert(cmp(words[1], "peter") == 0);
+        assert(cmp(words[2], "paul") == 0);
+        assert(cmp(words[3], "jerry") == 0);
+        assert(cmp(words[4], "") == 0);
+
+        auto s4 = s3[0 .. s3.length - 2];    // lop off trailing ',,'
+        words = split(s4, ",,");
+        assert(words.length == 4);
+        assert(cmp(words[3], "jerry") == 0);
+
+        auto s5 = s4[2 .. s4.length];    // lop off leading ',,'
+        words = split(s5, ",,");
+        assert(words.length == 3);
+        assert(cmp(words[0], "peter") == 0);
+    }
+}
+
+/++
+   Conservative heuristic to determine if a range can be iterated cheaply.
+   Used by $(D join) in decision to do an extra iteration of the range to
+   compute the resultant length. If iteration is not cheap then precomputing
+   length could be more expensive than using $(D Appender).
+
+   For now, we only assume arrays are cheap to iterate.
+ +/
+private enum bool hasCheapIteration(R) = isArray!R;
+
+/++
+   Concatenates all of the ranges in $(D ror) together into one array using
+   $(D sep) as the separator if present.
+
+   Params:
+        ror = Range of Ranges of Elements
+        sep = Range of Elements
+
+   Returns:
+        an allocated array of Elements
+
+   See_Also:
+        $(XREF_PACK algorithm,iteration,joiner)
+  +/
+ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, R sep)
+    if(isInputRange!RoR &&
+       isInputRange!(Unqual!(ElementType!RoR)) &&
+       isInputRange!R &&
+       is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R)))
+{
+    alias RetType = typeof(return);
+    alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
+    alias RoRElem = ElementType!RoR;
+
+    if (ror.empty)
+        return RetType.init;
+
+    // Constraint only requires input range for sep.
+    // This converts sep to an array (forward range) if it isn't one,
+    // and makes sure it has the same string encoding for string types.
+    static if (isSomeString!RetType &&
+               !is(RetTypeElement == Unqual!(ElementEncodingType!R)))
+    {
+        import std.conv : to;
+        auto sepArr = to!RetType(sep);
+    }
+    else static if (!isArray!R)
+        auto sepArr = array(sep);
+    else
+        alias sepArr = sep;
+
+    static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
+    {
+        import std.conv : emplaceRef;
+        size_t length;          // length of result array
+        size_t rorLength;       // length of range ror
+        foreach(r; ror.save)
+        {
+            length += r.length;
+            ++rorLength;
+        }
+        if (!rorLength)
+            return null;
+        length += (rorLength - 1) * sepArr.length;
+
+        auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))();
+        size_t len;
+        foreach(e; ror.front)
+            emplaceRef(result[len++], e);
+        ror.popFront();
+        foreach(r; ror)
+        {
+            foreach(e; sepArr)
+                emplaceRef(result[len++], e);
+            foreach(e; r)
+                emplaceRef(result[len++], e);
+        }
+        assert(len == result.length);
+        return (() @trusted => cast(RetType) result)();
+    }
+    else
+    {
+        auto result = appender!RetType();
+        put(result, ror.front);
+        ror.popFront();
+        for (; !ror.empty; ror.popFront())
+        {
+            put(result, sep);
+            put(result, ror.front);
+        }
+        return result.data;
+    }
+}
+
+unittest // Issue 14230
+{
+   string[] ary = ["","aa","bb","cc"]; // leaded by _empty_ element
+   assert(ary.join(" @") == " @aa @bb @cc"); // OK in 2.067b1 and olders
+}
+
+/// Ditto
+ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, E sep)
+    if(isInputRange!RoR &&
+       isInputRange!(Unqual!(ElementType!RoR)) &&
+       is(E : ElementType!(ElementType!RoR)))
+{
+    alias RetType = typeof(return);
+    alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
+    alias RoRElem = ElementType!RoR;
+
+    if (ror.empty)
+        return RetType.init;
+
+    static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
+    {
+        static if (isSomeChar!E && isSomeChar!RetTypeElement && E.sizeof > RetTypeElement.sizeof)
+        {
+            import std.utf : encode;
+            RetTypeElement[4 / RetTypeElement.sizeof] encodeSpace;
+            immutable size_t sepArrLength = encode(encodeSpace, sep);
+            return join(ror, encodeSpace[0..sepArrLength]);
+        }
+        else
+        {
+            import std.conv : emplaceRef;
+            size_t length;
+            size_t rorLength;
+            foreach(r; ror.save)
+            {
+                length += r.length;
+                ++rorLength;
+            }
+            if (!rorLength)
+                return null;
+            length += rorLength - 1;
+            auto result = uninitializedArray!(RetTypeElement[])(length);
+
+
+            size_t len;
+            foreach(e; ror.front)
+                emplaceRef(result[len++], e);
+            ror.popFront();
+            foreach(r; ror)
+            {
+                emplaceRef(result[len++], sep);
+                foreach(e; r)
+                    emplaceRef(result[len++], e);
+            }
+            assert(len == result.length);
+            return (() @trusted => cast(RetType) result)();
+        }
+    }
+    else
+    {
+        auto result = appender!RetType();
+        put(result, ror.front);
+        ror.popFront();
+        for (; !ror.empty; ror.popFront())
+        {
+            put(result, sep);
+            put(result, ror.front);
+        }
+        return result.data;
+    }
+}
+
+unittest // Issue 10895
+{
+    class A
+    {
+        string name;
+        alias name this;
+        this(string name) { this.name = name; }
+    }
+    auto a = [new A(`foo`)];
+    assert(a[0].length == 3);
+    auto temp = join(a, " ");
+    assert(a[0].length == 3);
+}
+
+unittest // Issue 14230
+{
+   string[] ary = ["","aa","bb","cc"];
+   assert(ary.join('@') == "@aa@bb@cc");
+}
+
+/// Ditto
+ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror)
+    if(isInputRange!RoR &&
+       isInputRange!(Unqual!(ElementType!RoR)))
+{
+    alias RetType = typeof(return);
+    alias RetTypeElement = Unqual!(ElementEncodingType!RetType);
+    alias RoRElem = ElementType!RoR;
+
+    if (ror.empty)
+        return RetType.init;
+
+    static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem))
+    {
+        import std.conv : emplaceRef;
+        size_t length;
+        foreach(r; ror.save)
+            length += r.length;
+
+        auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))();
+        size_t len;
+        foreach(r; ror)
+            foreach(e; r)
+                emplaceRef(result[len++], e);
+        assert(len == result.length);
+        return (() @trusted => cast(RetType)result)();
+    }
+    else
+    {
+        auto result = appender!RetType();
+        for (; !ror.empty; ror.popFront())
+            put(result, ror.front);
+        return result.data;
+    }
+}
+
+///
+@safe pure nothrow unittest
+{
+    assert(join(["hello", "silly", "world"], " ") == "hello silly world");
+    assert(join(["hello", "silly", "world"]) == "hellosillyworld");
+
+    assert(join([[1, 2, 3], [4, 5]], [72, 73]) == [1, 2, 3, 72, 73, 4, 5]);
+    assert(join([[1, 2, 3], [4, 5]]) == [1, 2, 3, 4, 5]);
+
+    const string[] arr = ["apple", "banana"];
+    assert(arr.join(",") == "apple,banana");
+    assert(arr.join() == "applebanana");
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (T; AliasSeq!(string,wstring,dstring))
+    {
+        auto arr2 = "Здравствуй Мир Unicode".to!(T);
+        auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
+        assert(join(arr) == "ЗдравствуйМирUnicode");
+        foreach (S; AliasSeq!(char,wchar,dchar))
+        {
+            auto jarr = arr.join(to!S(' '));
+            static assert(is(typeof(jarr) == T));
+            assert(jarr == arr2);
+        }
+        foreach (S; AliasSeq!(string,wstring,dstring))
+        {
+            auto jarr = arr.join(to!S(" "));
+            static assert(is(typeof(jarr) == T));
+            assert(jarr == arr2);
+        }
+    }
+
+    foreach (T; AliasSeq!(string,wstring,dstring))
+    {
+        auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T);
+        auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]);
+        foreach (S; AliasSeq!(wchar,dchar))
+        {
+            auto jarr = arr.join(to!S('\u047C'));
+            static assert(is(typeof(jarr) == T));
+            assert(jarr == arr2);
+        }
+    }
+
+    const string[] arr = ["apple", "banana"];
+    assert(arr.join(',') == "apple,banana");
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm;
+    import std.range;
+
+    debug(std_array) printf("array.join.unittest\n");
+
+    foreach(R; AliasSeq!(string, wstring, dstring))
+    {
+        R word1 = "日本語";
+        R word2 = "paul";
+        R word3 = "jerry";
+        R[] words = [word1, word2, word3];
+
+        auto filteredWord1    = filter!"true"(word1);
+        auto filteredLenWord1 = takeExactly(filteredWord1, word1.walkLength());
+        auto filteredWord2    = filter!"true"(word2);
+        auto filteredLenWord2 = takeExactly(filteredWord2, word2.walkLength());
+        auto filteredWord3    = filter!"true"(word3);
+        auto filteredLenWord3 = takeExactly(filteredWord3, word3.walkLength());
+        auto filteredWordsArr = [filteredWord1, filteredWord2, filteredWord3];
+        auto filteredLenWordsArr = [filteredLenWord1, filteredLenWord2, filteredLenWord3];
+        auto filteredWords    = filter!"true"(filteredWordsArr);
+
+        foreach(S; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(join(filteredWords, to!S(", ")) == "日本語, paul, jerry");
+            assert(join(filteredWords, to!(ElementType!S)(',')) == "日本語,paul,jerry");
+            assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry");
+            assert(join(filteredWordsArr, to!S(", ")) == "日本語, paul, jerry");
+            assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry");
+            assert(join(filteredLenWordsArr, to!S(", ")) == "日本語, paul, jerry");
+            assert(join(filter!"true"(words), to!S(", ")) == "日本語, paul, jerry");
+            assert(join(words, to!S(", ")) == "日本語, paul, jerry");
+
+            assert(join(filteredWords, to!S("")) == "日本語pauljerry");
+            assert(join(filteredWordsArr, to!S("")) == "日本語pauljerry");
+            assert(join(filteredLenWordsArr, to!S("")) == "日本語pauljerry");
+            assert(join(filter!"true"(words), to!S("")) == "日本語pauljerry");
+            assert(join(words, to!S("")) == "日本語pauljerry");
+
+            assert(join(filter!"true"([word1]), to!S(", ")) == "日本語");
+            assert(join([filteredWord1], to!S(", ")) == "日本語");
+            assert(join([filteredLenWord1], to!S(", ")) == "日本語");
+            assert(join(filter!"true"([filteredWord1]), to!S(", ")) == "日本語");
+            assert(join([word1], to!S(", ")) == "日本語");
+
+            assert(join(filteredWords, to!S(word1)) == "日本語日本語paul日本語jerry");
+            assert(join(filteredWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry");
+            assert(join(filteredLenWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry");
+            assert(join(filter!"true"(words), to!S(word1)) == "日本語日本語paul日本語jerry");
+            assert(join(words, to!S(word1)) == "日本語日本語paul日本語jerry");
+
+            auto filterComma = filter!"true"(to!S(", "));
+            assert(join(filteredWords, filterComma) == "日本語, paul, jerry");
+            assert(join(filteredWordsArr, filterComma) == "日本語, paul, jerry");
+            assert(join(filteredLenWordsArr, filterComma) == "日本語, paul, jerry");
+            assert(join(filter!"true"(words), filterComma) == "日本語, paul, jerry");
+            assert(join(words, filterComma) == "日本語, paul, jerry");
+        }();
+
+        assert(join(filteredWords) == "日本語pauljerry");
+        assert(join(filteredWordsArr) == "日本語pauljerry");
+        assert(join(filteredLenWordsArr) == "日本語pauljerry");
+        assert(join(filter!"true"(words)) == "日本語pauljerry");
+        assert(join(words) == "日本語pauljerry");
+
+        assert(join(filteredWords, filter!"true"(", ")) == "日本語, paul, jerry");
+        assert(join(filteredWordsArr, filter!"true"(", ")) == "日本語, paul, jerry");
+        assert(join(filteredLenWordsArr, filter!"true"(", ")) == "日本語, paul, jerry");
+        assert(join(filter!"true"(words), filter!"true"(", ")) == "日本語, paul, jerry");
+        assert(join(words, filter!"true"(", ")) == "日本語, paul, jerry");
+
+        assert(join(filter!"true"(cast(typeof(filteredWordsArr))[]), ", ").empty);
+        assert(join(cast(typeof(filteredWordsArr))[], ", ").empty);
+        assert(join(cast(typeof(filteredLenWordsArr))[], ", ").empty);
+        assert(join(filter!"true"(cast(R[])[]), ", ").empty);
+        assert(join(cast(R[])[], ", ").empty);
+
+        assert(join(filter!"true"(cast(typeof(filteredWordsArr))[])).empty);
+        assert(join(cast(typeof(filteredWordsArr))[]).empty);
+        assert(join(cast(typeof(filteredLenWordsArr))[]).empty);
+
+        assert(join(filter!"true"(cast(R[])[])).empty);
+        assert(join(cast(R[])[]).empty);
+    }
+
+    assert(join([[1, 2], [41, 42]], [5, 6]) == [1, 2, 5, 6, 41, 42]);
+    assert(join([[1, 2], [41, 42]], cast(int[])[]) == [1, 2, 41, 42]);
+    assert(join([[1, 2]], [5, 6]) == [1, 2]);
+    assert(join(cast(int[][])[], [5, 6]).empty);
+
+    assert(join([[1, 2], [41, 42]]) == [1, 2, 41, 42]);
+    assert(join(cast(int[][])[]).empty);
+
+    alias f = filter!"true";
+    assert(join([[1, 2], [41, 42]],          [5, 6]) == [1, 2, 5, 6, 41, 42]);
+    assert(join(f([[1, 2], [41, 42]]),       [5, 6]) == [1, 2, 5, 6, 41, 42]);
+    assert(join([f([1, 2]), f([41, 42])],    [5, 6]) == [1, 2, 5, 6, 41, 42]);
+    assert(join(f([f([1, 2]), f([41, 42])]), [5, 6]) == [1, 2, 5, 6, 41, 42]);
+    assert(join([[1, 2], [41, 42]],          f([5, 6])) == [1, 2, 5, 6, 41, 42]);
+    assert(join(f([[1, 2], [41, 42]]),       f([5, 6])) == [1, 2, 5, 6, 41, 42]);
+    assert(join([f([1, 2]), f([41, 42])],    f([5, 6])) == [1, 2, 5, 6, 41, 42]);
+    assert(join(f([f([1, 2]), f([41, 42])]), f([5, 6])) == [1, 2, 5, 6, 41, 42]);
+}
+
+// Issue 10683
+unittest
+{
+    import std.range : join;
+    import std.typecons : tuple;
+    assert([[tuple(1)]].join == [tuple(1)]);
+    assert([[tuple("x")]].join == [tuple("x")]);
+}
+
+// Issue 13877
+unittest
+{
+    // Test that the range is iterated only once.
+    import std.algorithm : map;
+    int c = 0;
+    auto j1 = [1, 2, 3].map!(_ => [c++]).join;
+    assert(c == 3);
+    assert(j1 == [0, 1, 2]);
+
+    c = 0;
+    auto j2 = [1, 2, 3].map!(_ => [c++]).join(9);
+    assert(c == 3);
+    assert(j2 == [0, 9, 1, 9, 2]);
+
+    c = 0;
+    auto j3 = [1, 2, 3].map!(_ => [c++]).join([9]);
+    assert(c == 3);
+    assert(j3 == [0, 9, 1, 9, 2]);
+}
+
+
+/++
+    Replace occurrences of $(D from) with $(D to) in $(D subject). Returns a new
+    array without changing the contents of $(D subject), or the original array
+    if no match is found.
+ +/
+E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to)
+if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2
+        && (hasLength!R2 || isSomeString!R2))
+{
+    if (from.empty) return subject;
+
+    auto balance = std.algorithm.find(subject, from.save);
+    if (balance.empty)
+        return subject;
+
+    auto app = appender!(E[])();
+    app.put(subject[0 .. subject.length - balance.length]);
+    app.put(to.save);
+    replaceInto(app, balance[from.length .. $], from, to);
+
+    return app.data;
+}
+
+///
+unittest
+{
+    assert("Hello Wörld".replace("o Wö", "o Wo") == "Hello World");
+    assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd");
+}
+
+/++
+    Same as above, but outputs the result via OutputRange $(D sink).
+    If no match is found the original array is transferred to $(D sink) as is.
++/
+void replaceInto(E, Sink, R1, R2)(Sink sink, E[] subject, R1 from, R2 to)
+if (isOutputRange!(Sink, E) && isDynamicArray!(E[])
+    && isForwardRange!R1 && isForwardRange!R2
+    && (hasLength!R2 || isSomeString!R2))
+{
+    if (from.empty)
+    {
+        sink.put(subject);
+        return;
+    }
+    for (;;)
+    {
+        auto balance = std.algorithm.find(subject, from.save);
+        if (balance.empty)
+        {
+            sink.put(subject);
+            break;
+        }
+        sink.put(subject[0 .. subject.length - balance.length]);
+        sink.put(to.save);
+        subject = balance[from.length .. $];
+    }
+}
+
+///
+unittest
+{
+    auto arr = [1, 2, 3, 4, 5];
+    auto from = [2, 3];
+    auto into = [4, 6];
+    auto sink = appender!(int[])();
+
+    replaceInto(sink, arr, from, into);
+
+    assert(sink.data == [1, 4, 6, 4, 5]);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : cmp;
+
+    debug(std_array) printf("array.replace.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            auto s = to!S("This is a foo foo list");
+            auto from = to!T("foo");
+            auto into = to!S("silly");
+            S r;
+            int i;
+
+            r = replace(s, from, into);
+            i = cmp(r, "This is a silly silly list");
+            assert(i == 0);
+
+            r = replace(s, to!S(""), into);
+            i = cmp(r, "This is a foo foo list");
+            assert(i == 0);
+
+            assert(replace(r, to!S("won't find this"), to!S("whatever")) is r);
+        }();
+    }
+
+    immutable s = "This is a foo foo list";
+    assert(replace(s, "foo", "silly") == "This is a silly silly list");
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : skipOver;
+
+    struct CheckOutput(C)
+    {
+        C[] desired;
+        this(C[] arr){ desired = arr; }
+        void put(C[] part){ assert(skipOver(desired, part)); }
+    }
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+    {
+        alias Char = ElementEncodingType!S;
+        S s = to!S("yet another dummy text, yet another ...");
+        S from = to!S("yet another");
+        S into = to!S("some");
+        replaceInto(CheckOutput!(Char)(to!S("some dummy text, some ..."))
+                    , s, from, into);
+    }
+}
+
+/++
+    Replaces elements from $(D array) with indices ranging from $(D from)
+    (inclusive) to $(D to) (exclusive) with the range $(D stuff). Returns a new
+    array without changing the contents of $(D subject).
+ +/
+T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff)
+    if(isInputRange!Range &&
+       (is(ElementType!Range : T) ||
+        isSomeString!(T[]) && is(ElementType!Range : dchar)))
+{
+    static if(hasLength!Range && is(ElementEncodingType!Range : T))
+    {
+        import std.algorithm : copy;
+        assert(from <= to);
+        immutable sliceLen = to - from;
+        auto retval = new Unqual!(T)[](subject.length - sliceLen + stuff.length);
+        retval[0 .. from] = subject[0 .. from];
+
+        if(!stuff.empty)
+            copy(stuff, retval[from .. from + stuff.length]);
+
+        retval[from + stuff.length .. $] = subject[to .. $];
+        return cast(T[])retval;
+    }
+    else
+    {
+        auto app = appender!(T[])();
+        app.put(subject[0 .. from]);
+        app.put(stuff);
+        app.put(subject[to .. $]);
+        return app.data;
+    }
+}
+
+///
+unittest
+{
+    auto a = [ 1, 2, 3, 4 ];
+    auto b = a.replace(1, 3, [ 9, 9, 9 ]);
+    assert(a == [ 1, 2, 3, 4 ]);
+    assert(b == [ 1, 9, 9, 9, 4 ]);
+}
+
+unittest
+{
+    import core.exception;
+    import std.conv : to;
+    import std.exception;
+    import std.algorithm;
+
+
+    auto a = [ 1, 2, 3, 4 ];
+    assert(replace(a, 0, 0, [5, 6, 7]) == [5, 6, 7, 1, 2, 3, 4]);
+    assert(replace(a, 0, 2, cast(int[])[]) == [3, 4]);
+    assert(replace(a, 0, 4, [5, 6, 7]) == [5, 6, 7]);
+    assert(replace(a, 0, 2, [5, 6, 7]) == [5, 6, 7, 3, 4]);
+    assert(replace(a, 2, 4, [5, 6, 7]) == [1, 2, 5, 6, 7]);
+
+    assert(replace(a, 0, 0, filter!"true"([5, 6, 7])) == [5, 6, 7, 1, 2, 3, 4]);
+    assert(replace(a, 0, 2, filter!"true"(cast(int[])[])) == [3, 4]);
+    assert(replace(a, 0, 4, filter!"true"([5, 6, 7])) == [5, 6, 7]);
+    assert(replace(a, 0, 2, filter!"true"([5, 6, 7])) == [5, 6, 7, 3, 4]);
+    assert(replace(a, 2, 4, filter!"true"([5, 6, 7])) == [1, 2, 5, 6, 7]);
+    assert(a == [ 1, 2, 3, 4 ]);
+
+    auto testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
+    {
+
+        auto l = to!T("hello");
+        auto r = to!U(" world");
+
+        enforce(replace(l, 0, 0, r) == " worldhello",
+                new AssertError("testStr failure 1", file, line));
+        enforce(replace(l, 0, 3, r) == " worldlo",
+                new AssertError("testStr failure 2", file, line));
+        enforce(replace(l, 3, l.length, r) == "hel world",
+                new AssertError("testStr failure 3", file, line));
+        enforce(replace(l, 0, l.length, r) == " world",
+                new AssertError("testStr failure 4", file, line));
+        enforce(replace(l, l.length, l.length, r) == "hello world",
+                new AssertError("testStr failure 5", file, line));
+    }
+
+    testStr!(string, string)();
+    testStr!(string, wstring)();
+    testStr!(string, dstring)();
+    testStr!(wstring, string)();
+    testStr!(wstring, wstring)();
+    testStr!(wstring, dstring)();
+    testStr!(dstring, string)();
+    testStr!(dstring, wstring)();
+    testStr!(dstring, dstring)();
+
+    enum s = "0123456789";
+    enum w = "⁰¹²³⁴⁵⁶⁷⁸⁹"w;
+    enum d = "⁰¹²³⁴⁵⁶⁷⁸⁹"d;
+
+    assert(replace(s, 0, 0, "***") == "***0123456789");
+    assert(replace(s, 10, 10, "***") == "0123456789***");
+    assert(replace(s, 3, 8, "1012") == "012101289");
+    assert(replace(s, 0, 5, "43210") == "4321056789");
+    assert(replace(s, 5, 10, "43210") == "0123443210");
+
+    assert(replace(w, 0, 0, "***"w) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"w);
+    assert(replace(w, 10, 10, "***"w) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"w);
+    assert(replace(w, 3, 8, "¹⁰¹²"w) == "⁰¹²¹⁰¹²⁸⁹"w);
+    assert(replace(w, 0, 5, "⁴³²¹⁰"w) == "⁴³²¹⁰⁵⁶⁷⁸⁹"w);
+    assert(replace(w, 5, 10, "⁴³²¹⁰"w) == "⁰¹²³⁴⁴³²¹⁰"w);
+
+    assert(replace(d, 0, 0, "***"d) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"d);
+    assert(replace(d, 10, 10, "***"d) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"d);
+    assert(replace(d, 3, 8, "¹⁰¹²"d) == "⁰¹²¹⁰¹²⁸⁹"d);
+    assert(replace(d, 0, 5, "⁴³²¹⁰"d) == "⁴³²¹⁰⁵⁶⁷⁸⁹"d);
+    assert(replace(d, 5, 10, "⁴³²¹⁰"d) == "⁰¹²³⁴⁴³²¹⁰"d);
+}
+
+/++
+    Replaces elements from $(D array) with indices ranging from $(D from)
+    (inclusive) to $(D to) (exclusive) with the range $(D stuff). Expands or
+    shrinks the array as needed.
+ +/
+void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff)
+    if(is(typeof(replace(array, from, to, stuff))))
+{
+    static if(isDynamicArray!Range &&
+              is(Unqual!(ElementEncodingType!Range) == T) &&
+              !isNarrowString!(T[]))
+    {
+        // optimized for homogeneous arrays that can be overwritten.
+        import std.algorithm : remove;
+        import std.typecons : tuple;
+
+        if (overlap(array, stuff).length)
+        {
+            // use slower/conservative method
+            array = array[0 .. from] ~ stuff ~ array[to .. $];
+        }
+        else if (stuff.length <= to - from)
+        {
+            // replacement reduces length
+            immutable stuffEnd = from + stuff.length;
+            array[from .. stuffEnd] = stuff[];
+            if (stuffEnd < to)
+                array = remove(array, tuple(stuffEnd, to));
+        }
+        else
+        {
+            // replacement increases length
+            // @@@TODO@@@: optimize this
+            immutable replaceLen = to - from;
+            array[from .. to] = stuff[0 .. replaceLen];
+            insertInPlace(array, to, stuff[replaceLen .. $]);
+        }
+    }
+    else
+    {
+        // default implementation, just do what replace does.
+        array = replace(array, from, to, stuff);
+    }
+}
+
+///
+unittest
+{
+    int[] a = [1, 4, 5];
+    replaceInPlace(a, 1u, 2u, [2, 3, 4]);
+    assert(a == [1, 2, 3, 4, 5]);
+    replaceInPlace(a, 1u, 2u, cast(int[])[]);
+    assert(a == [1, 3, 4, 5]);
+    replaceInPlace(a, 1u, 3u, a[2 .. 4]);
+    assert(a == [1, 4, 5, 5]);
+}
+
+unittest
+{
+    // Bug# 12889
+    int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]];
+    int[1][] stuff = [[0], [1]];
+    replaceInPlace(arr, 4, 6, stuff);
+    assert(arr == [[0], [1], [2], [3], [0], [1], [6]]);
+}
+
+unittest
+{
+    // Bug# 14925
+    char[] a = "mon texte 1".dup;
+    char[] b = "abc".dup;
+    replaceInPlace(a, 4, 9, b);
+    assert(a == "mon abc 1");
+
+    // ensure we can replace in place with different encodings
+    string unicoded = "\U00010437";
+    string unicodedLong = "\U00010437aaaaa";
+    string base = "abcXXXxyz";
+    string result = "abc\U00010437xyz";
+    string resultLong = "abc\U00010437aaaaaxyz";
+    size_t repstart = 3;
+    size_t repend = 3 + 3;
+
+    void testStringReplaceInPlace(T, U)()
+    {
+        import std.conv;
+        import std.algorithm : equal;
+        auto a = unicoded.to!(U[]);
+        auto b = unicodedLong.to!(U[]);
+
+        auto test = base.to!(T[]);
+
+        test.replaceInPlace(repstart, repend, a);
+        assert(equal(test, result), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof);
+
+        test = base.to!(T[]);
+
+        test.replaceInPlace(repstart, repend, b);
+        assert(equal(test, resultLong), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof);
+    }
+
+    import std.meta : AliasSeq;
+    alias allChars = AliasSeq!(char, immutable(char), const(char),
+                         wchar, immutable(wchar), const(wchar),
+                         dchar, immutable(dchar), const(dchar));
+    foreach(T; allChars)
+        foreach(U; allChars)
+            testStringReplaceInPlace!(T, U)();
+
+    void testInout(inout(int)[] a)
+    {
+        // will be transferred to the 'replace' function
+        replaceInPlace(a, 1, 2, [1,2,3]);
+    }
+}
+
+unittest
+{
+    // the constraint for the first overload used to match this, which wouldn't compile.
+    import std.algorithm : equal;
+    long[] a = [1L, 2, 3];
+    int[] b = [4, 5, 6];
+    a.replaceInPlace(1, 2, b);
+    assert(equal(a, [1L, 4, 5, 6, 3]));
+}
+
+unittest
+{
+    import core.exception;
+    import std.conv : to;
+    import std.exception;
+    import std.algorithm;
+
+
+    bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result,
+               string file = __FILE__, size_t line = __LINE__)
+    {
+        {
+            static if(is(T == typeof(T.init.dup)))
+                auto a = orig.dup;
+            else
+                auto a = orig.idup;
+
+            a.replaceInPlace(from, to, toReplace);
+            if(!std.algorithm.equal(a, result))
+                return false;
+        }
+
+        static if(isInputRange!U)
+        {
+            orig.replaceInPlace(from, to, filter!"true"(toReplace));
+            return std.algorithm.equal(orig, result);
+        }
+        else
+            return true;
+    }
+
+    assert(test([1, 2, 3, 4], 0, 0, [5, 6, 7], [5, 6, 7, 1, 2, 3, 4]));
+    assert(test([1, 2, 3, 4], 0, 2, cast(int[])[], [3, 4]));
+    assert(test([1, 2, 3, 4], 0, 4, [5, 6, 7], [5, 6, 7]));
+    assert(test([1, 2, 3, 4], 0, 2, [5, 6, 7], [5, 6, 7, 3, 4]));
+    assert(test([1, 2, 3, 4], 2, 4, [5, 6, 7], [1, 2, 5, 6, 7]));
+
+    assert(test([1, 2, 3, 4], 0, 0, filter!"true"([5, 6, 7]), [5, 6, 7, 1, 2, 3, 4]));
+    assert(test([1, 2, 3, 4], 0, 2, filter!"true"(cast(int[])[]), [3, 4]));
+    assert(test([1, 2, 3, 4], 0, 4, filter!"true"([5, 6, 7]), [5, 6, 7]));
+    assert(test([1, 2, 3, 4], 0, 2, filter!"true"([5, 6, 7]), [5, 6, 7, 3, 4]));
+    assert(test([1, 2, 3, 4], 2, 4, filter!"true"([5, 6, 7]), [1, 2, 5, 6, 7]));
+
+    auto testStr(T, U)(string file = __FILE__, size_t line = __LINE__)
+    {
+
+        auto l = to!T("hello");
+        auto r = to!U(" world");
+
+        enforce(test(l, 0, 0, r, " worldhello"),
+                new AssertError("testStr failure 1", file, line));
+        enforce(test(l, 0, 3, r, " worldlo"),
+                new AssertError("testStr failure 2", file, line));
+        enforce(test(l, 3, l.length, r, "hel world"),
+                new AssertError("testStr failure 3", file, line));
+        enforce(test(l, 0, l.length, r, " world"),
+                new AssertError("testStr failure 4", file, line));
+        enforce(test(l, l.length, l.length, r, "hello world"),
+                new AssertError("testStr failure 5", file, line));
+    }
+
+    testStr!(string, string)();
+    testStr!(string, wstring)();
+    testStr!(string, dstring)();
+    testStr!(wstring, string)();
+    testStr!(wstring, wstring)();
+    testStr!(wstring, dstring)();
+    testStr!(dstring, string)();
+    testStr!(dstring, wstring)();
+    testStr!(dstring, dstring)();
+}
+
+/++
+    Replaces the first occurrence of $(D from) with $(D to) in $(D a). Returns a
+    new array without changing the contents of $(D subject), or the original
+    array if no match is found.
+ +/
+E[] replaceFirst(E, R1, R2)(E[] subject, R1 from, R2 to)
+if (isDynamicArray!(E[]) &&
+    isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) &&
+    isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1]))))
+{
+    import std.algorithm : countUntil;
+    if (from.empty) return subject;
+    static if (isSomeString!(E[]))
+    {
+        import std.string : indexOf;
+        immutable idx = subject.indexOf(from);
+    }
+    else
+    {
+        import std.algorithm : countUntil;
+        immutable idx = subject.countUntil(from);
+    }
+    if (idx == -1)
+        return subject;
+
+    auto app = appender!(E[])();
+    app.put(subject[0 .. idx]);
+    app.put(to);
+
+    static if (isSomeString!(E[]) && isSomeString!R1)
+    {
+        import std.utf : codeLength;
+        immutable fromLength = codeLength!(Unqual!E, R1)(from);
+    }
+    else
+        immutable fromLength = from.length;
+
+    app.put(subject[idx + fromLength .. $]);
+
+    return app.data;
+}
+
+///
+unittest
+{
+    auto a = [1, 2, 2, 3, 4, 5];
+    auto b = a.replaceFirst([2], [1337]);
+    assert(b == [1, 1337, 2, 3, 4, 5]);
+
+    auto s = "This is a foo foo list";
+    auto r = s.replaceFirst("foo", "silly");
+    assert(r == "This is a silly foo list");
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : cmp;
+
+    debug(std_array) printf("array.replaceFirst.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
+                          const(char[]), immutable(char[])))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
+                              const(char[]), immutable(char[])))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            auto s = to!S("This is a foo foo list");
+            auto s2 = to!S("Thüs is a ßöö foo list");
+            auto from = to!T("foo");
+            auto from2 = to!T("ßöö");
+            auto into = to!T("silly");
+            auto into2 = to!T("sälly");
+
+            S r1 = replaceFirst(s, from, into);
+            assert(cmp(r1, "This is a silly foo list") == 0);
+
+            S r11 = replaceFirst(s2, from2, into2);
+            assert(cmp(r11, "Thüs is a sälly foo list") == 0,
+                to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof);
+
+            S r2 = replaceFirst(r1, from, into);
+            assert(cmp(r2, "This is a silly silly list") == 0);
+
+            S r3 = replaceFirst(s, to!T(""), into);
+            assert(cmp(r3, "This is a foo foo list") == 0);
+
+            assert(replaceFirst(r3, to!T("won't find"), to!T("whatever")) is r3);
+        }();
+    }
+}
+
+//Bug# 8187
+unittest
+{
+    auto res = ["a", "a"];
+    assert(replace(res, "a", "b") == ["b", "b"]);
+    assert(replaceFirst(res, "a", "b") == ["b", "a"]);
+}
+
+/++
+    Replaces the last occurrence of $(D from) with $(D to) in $(D a). Returns a
+    new array without changing the contents of $(D subject), or the original
+    array if no match is found.
+ +/
+E[] replaceLast(E, R1, R2)(E[] subject, R1 from , R2 to)
+if (isDynamicArray!(E[]) &&
+    isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) &&
+    isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1]))))
+{
+    import std.range : retro;
+    if (from.empty) return subject;
+    static if (isSomeString!(E[]))
+    {
+        import std.string : lastIndexOf;
+        auto idx = subject.lastIndexOf(from);
+    }
+    else
+    {
+        import std.algorithm : countUntil;
+        auto idx = retro(subject).countUntil(retro(from));
+    }
+
+    if (idx == -1)
+        return subject;
+
+    static if (isSomeString!(E[]) && isSomeString!R1)
+    {
+        import std.utf : codeLength;
+        auto fromLength = codeLength!(Unqual!E, R1)(from);
+    }
+    else
+        auto fromLength = from.length;
+
+    auto app = appender!(E[])();
+    static if (isSomeString!(E[]))
+        app.put(subject[0 .. idx]);
+    else
+        app.put(subject[0 .. $ - idx - fromLength]);
+
+    app.put(to);
+
+    static if (isSomeString!(E[]))
+        app.put(subject[idx+fromLength .. $]);
+    else
+        app.put(subject[$ - idx .. $]);
+
+    return app.data;
+}
+
+///
+unittest
+{
+    auto a = [1, 2, 2, 3, 4, 5];
+    auto b = a.replaceLast([2], [1337]);
+    assert(b == [1, 2, 1337, 3, 4, 5]);
+
+    auto s = "This is a foo foo list";
+    auto r = s.replaceLast("foo", "silly");
+    assert(r == "This is a foo silly list", r);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : cmp;
+
+    debug(std_array) printf("array.replaceLast.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
+                          const(char[]), immutable(char[])))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[],
+                              const(char[]), immutable(char[])))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            auto s = to!S("This is a foo foo list");
+            auto s2 = to!S("Thüs is a ßöö ßöö list");
+            auto from = to!T("foo");
+            auto from2 = to!T("ßöö");
+            auto into = to!T("silly");
+            auto into2 = to!T("sälly");
+
+            S r1 = replaceLast(s, from, into);
+            assert(cmp(r1, "This is a foo silly list") == 0, to!string(r1));
+
+            S r11 = replaceLast(s2, from2, into2);
+            assert(cmp(r11, "Thüs is a ßöö sälly list") == 0,
+                to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof);
+
+            S r2 = replaceLast(r1, from, into);
+            assert(cmp(r2, "This is a silly silly list") == 0);
+
+            S r3 = replaceLast(s, to!T(""), into);
+            assert(cmp(r3, "This is a foo foo list") == 0);
+
+            assert(replaceLast(r3, to!T("won't find"), to!T("whatever")) is r3);
+        }();
+    }
+}
+
+/++
+    Returns a new array that is $(D s) with $(D slice) replaced by
+    $(D replacement[]).
+ +/
+inout(T)[] replaceSlice(T)(inout(T)[] s, in T[] slice, in T[] replacement)
+in
+{
+    // Verify that slice[] really is a slice of s[]
+    assert(overlap(s, slice) is slice);
+}
+body
+{
+    auto result = new T[s.length - slice.length + replacement.length];
+    immutable so = slice.ptr - s.ptr;
+    result[0 .. so] = s[0 .. so];
+    result[so .. so + replacement.length] = replacement[];
+    result[so + replacement.length .. result.length] =
+        s[so + slice.length .. s.length];
+
+    return cast(inout(T)[]) result;
+}
+
+///
+unittest
+{
+    auto a = [1, 2, 3, 4, 5];
+    auto b = replaceSlice(a, a[1..4], [0, 0, 0]);
+
+    assert(b == [1, 0, 0, 0, 5]);
+}
+
+unittest
+{
+    import std.algorithm : cmp;
+    debug(std_array) printf("array.replaceSlice.unittest\n");
+
+    string s = "hello";
+    string slice = s[2 .. 4];
+
+    auto r = replaceSlice(s, slice, "bar");
+    int i;
+    i = cmp(r, "hebaro");
+    assert(i == 0);
+}
+
+/**
+Implements an output range that appends data to an array. This is
+recommended over $(D a ~= data) when appending many elements because it is more
+efficient.
+ */
+struct Appender(A)
+if (isDynamicArray!A)
+{
+    import core.memory;
+
+    private alias T = ElementEncodingType!A;
+    private struct Data
+    {
+        size_t capacity;
+        Unqual!T[] arr;
+        bool canExtend = false;
+    }
+
+    private Data* _data;
+
+    /**
+     * Construct an appender with a given array.  Note that this does not copy the
+     * data.  If the array has a larger capacity as determined by arr.capacity,
+     * it will be used by the appender.  After initializing an appender on an array,
+     * appending to the original array will reallocate.
+     */
+    this(T[] arr) @trusted pure nothrow
+    {
+        // initialize to a given array.
+        _data = new Data;
+        _data.arr = cast(Unqual!T[])arr; //trusted
+
+        if (__ctfe)
+            return;
+
+        // We want to use up as much of the block the array is in as possible.
+        // if we consume all the block that we can, then array appending is
+        // safe WRT built-in append, and we can use the entire block.
+        // We only do this for mutable types that can be extended.
+        static if (isMutable!T && is(typeof(arr.length = size_t.max)))
+        {
+            auto cap = arr.capacity; //trusted
+            // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed)
+            if (cap > arr.length)
+                arr.length = cap;
+        }
+        _data.capacity = arr.length;
+    }
+
+    //Broken function. To be removed.
+    static if (is(T == immutable))
+    {
+        // Explicitly undocumented. It will be removed in March 2016. @@@DEPRECATED_2016-03@@@
+        deprecated ("Using this constructor will break the type system. Please fix your code to use `Appender!(T[]).this(T[] arr)' directly.")
+        this(Unqual!T[] arr) pure nothrow
+        {
+            this(cast(T[]) arr);
+        }
+
+        //temporary: For resolving ambiguity:
+        this(typeof(null))
+        {
+            this(cast(T[]) null);
+        }
+    }
+
+    /**
+     * Reserve at least newCapacity elements for appending.  Note that more elements
+     * may be reserved than requested.  If newCapacity <= capacity, then nothing is
+     * done.
+     */
+    void reserve(size_t newCapacity) @safe pure nothrow
+    {
+        if (_data)
+        {
+            if (newCapacity > _data.capacity)
+                ensureAddable(newCapacity - _data.arr.length);
+        }
+        else
+        {
+            ensureAddable(newCapacity);
+        }
+    }
+
+    /**
+     * Returns the capacity of the array (the maximum number of elements the
+     * managed array can accommodate before triggering a reallocation).  If any
+     * appending will reallocate, $(D capacity) returns $(D 0).
+     */
+    @property size_t capacity() const @safe pure nothrow
+    {
+        return _data ? _data.capacity : 0;
+    }
+
+    /**
+     * Returns the managed array.
+     */
+    @property inout(T)[] data() inout @trusted pure nothrow
+    {
+        /* @trusted operation:
+         * casting Unqual!T[] to inout(T)[]
+         */
+        return cast(typeof(return))(_data ? _data.arr : null);
+    }
+
+    // ensure we can add nelems elements, resizing as necessary
+    private void ensureAddable(size_t nelems) @trusted pure nothrow
+    {
+        if (!_data)
+            _data = new Data;
+        immutable len = _data.arr.length;
+        immutable reqlen = len + nelems;
+
+        if (_data.capacity >= reqlen)
+            return;
+
+        // need to increase capacity
+        if (__ctfe)
+        {
+            static if (__traits(compiles, new Unqual!T[1]))
+            {
+                _data.arr.length = reqlen;
+            }
+            else
+            {
+                // avoid restriction of @disable this()
+                _data.arr = _data.arr[0 .. _data.capacity];
+                foreach (i; _data.capacity .. reqlen)
+                    _data.arr ~= Unqual!T.init;
+            }
+            _data.arr = _data.arr[0 .. len];
+            _data.capacity = reqlen;
+        }
+        else
+        {
+            // Time to reallocate.
+            // We need to almost duplicate what's in druntime, except we
+            // have better access to the capacity field.
+            auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen);
+            // first, try extending the current block
+            if (_data.canExtend)
+            {
+                auto u = GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof);
+                if (u)
+                {
+                    // extend worked, update the capacity
+                    _data.capacity = u / T.sizeof;
+                    return;
+                }
+            }
+
+            // didn't work, must reallocate
+            auto bi = GC.qalloc(newlen * T.sizeof, blockAttribute!T);
+            _data.capacity = bi.size / T.sizeof;
+            import core.stdc.string : memcpy;
+            if (len)
+                memcpy(bi.base, _data.arr.ptr, len * T.sizeof);
+            _data.arr = (cast(Unqual!T*)bi.base)[0 .. len];
+            _data.canExtend = true;
+            // leave the old data, for safety reasons
+        }
+    }
+
+    private template canPutItem(U)
+    {
+        enum bool canPutItem =
+            isImplicitlyConvertible!(U, T) ||
+            isSomeChar!T && isSomeChar!U;
+    }
+    private template canPutConstRange(Range)
+    {
+        enum bool canPutConstRange =
+            isInputRange!(Unqual!Range) &&
+            !isInputRange!Range;
+    }
+    private template canPutRange(Range)
+    {
+        enum bool canPutRange =
+            isInputRange!Range &&
+            is(typeof(Appender.init.put(Range.init.front)));
+    }
+
+    /**
+     * Appends one item to the managed array.
+     */
+    void put(U)(U item) if (canPutItem!U)
+    {
+        static if (isSomeChar!T && isSomeChar!U && T.sizeof < U.sizeof)
+        {
+            /* may throwable operation:
+             * - std.utf.encode
+             */
+            // must do some transcoding around here
+            import std.utf : encode;
+            Unqual!T[T.sizeof == 1 ? 4 : 2] encoded;
+            auto len = encode(encoded, item);
+            put(encoded[0 .. len]);
+        }
+        else
+        {
+            import std.conv : emplaceRef;
+
+            ensureAddable(1);
+            immutable len = _data.arr.length;
+
+            auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])();
+            emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T)item);
+            //We do this at the end, in case of exceptions
+            _data.arr = bigData;
+        }
+    }
+
+    // Const fixing hack.
+    void put(Range)(Range items) if (canPutConstRange!Range)
+    {
+        alias p = put!(Unqual!Range);
+        p(items);
+    }
+
+    /**
+     * Appends an entire range to the managed array.
+     */
+    void put(Range)(Range items) if (canPutRange!Range)
+    {
+        // note, we disable this branch for appending one type of char to
+        // another because we can't trust the length portion.
+        static if (!(isSomeChar!T && isSomeChar!(ElementType!Range) &&
+                     !is(immutable Range == immutable T[])) &&
+                    is(typeof(items.length) == size_t))
+        {
+            // optimization -- if this type is something other than a string,
+            // and we are adding exactly one element, call the version for one
+            // element.
+            static if (!isSomeChar!T)
+            {
+                if (items.length == 1)
+                {
+                    put(items.front);
+                    return;
+                }
+            }
+
+            // make sure we have enough space, then add the items
+            @trusted auto bigDataFun(size_t extra)
+            {
+                ensureAddable(extra);
+                return _data.arr.ptr[0 .. _data.arr.length + extra];
+            }
+            auto bigData = bigDataFun(items.length);
+
+            immutable len = _data.arr.length;
+            immutable newlen = bigData.length;
+
+            alias UT = Unqual!T;
+
+            static if (is(typeof(_data.arr[] = items[])) &&
+                !hasElaborateAssign!(Unqual!T) && isAssignable!(UT, ElementEncodingType!Range))
+            {
+                bigData[len .. newlen] = items[];
+            }
+            else
+            {
+                import std.conv : emplaceRef;
+                foreach (ref it ; bigData[len .. newlen])
+                {
+                    emplaceRef!T(it, items.front);
+                    items.popFront();
+                }
+            }
+
+            //We do this at the end, in case of exceptions
+            _data.arr = bigData;
+        }
+        else
+        {
+            //pragma(msg, Range.stringof);
+            // Generic input range
+            for (; !items.empty; items.popFront())
+            {
+                put(items.front);
+            }
+        }
+    }
+
+    /**
+     * Appends one item to the managed array.
+     */
+    void opOpAssign(string op : "~", U)(U item) if (canPutItem!U)
+    {
+        put(item);
+    }
+
+    // Const fixing hack.
+    void opOpAssign(string op : "~", Range)(Range items) if (canPutConstRange!Range)
+    {
+        put(items);
+    }
+
+    /**
+     * Appends an entire range to the managed array.
+     */
+    void opOpAssign(string op : "~", Range)(Range items) if (canPutRange!Range)
+    {
+        put(items);
+    }
+
+    // only allow overwriting data on non-immutable and non-const data
+    static if (isMutable!T)
+    {
+        /**
+         * Clears the managed array.  This allows the elements of the array to be reused
+         * for appending.
+         *
+         * Note that clear is disabled for immutable or const element types, due to the
+         * possibility that $(D Appender) might overwrite immutable data.
+         */
+        void clear() @trusted pure nothrow
+        {
+            if (_data)
+            {
+                _data.arr = _data.arr.ptr[0 .. 0];
+            }
+        }
+
+        /**
+         * Shrinks the managed array to the given length.
+         *
+         * Throws: $(D Exception) if newlength is greater than the current array length.
+         */
+        void shrinkTo(size_t newlength) @trusted pure
+        {
+            import std.exception : enforce;
+            if (_data)
+            {
+                enforce(newlength <= _data.arr.length, "Attempting to shrink Appender with newlength > length");
+                _data.arr = _data.arr.ptr[0 .. newlength];
+            }
+            else
+                enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength");
+        }
+    }
+
+    void toString()(scope void delegate(const(char)[]) sink)
+    {
+        import std.format : formattedWrite;
+        sink.formattedWrite(typeof(this).stringof ~ "(%s)", data);
+    }
+}
+
+///
+unittest{
+    auto app = appender!string();
+    string b = "abcdefg";
+    foreach (char c; b)
+        app.put(c);
+    assert(app.data == "abcdefg");
+
+    int[] a = [ 1, 2 ];
+    auto app2 = appender(a);
+    app2.put(3);
+    app2.put([ 4, 5, 6 ]);
+    assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]);
+}
+
+unittest
+{
+    import std.format : format;
+    auto app = appender!(int[])();
+    app.put(1);
+    app.put(2);
+    app.put(3);
+    assert("%s".format(app) == "Appender!(int[])(%s)".format([1,2,3]));
+}
+
+//Calculates an efficient growth scheme based on the old capacity
+//of data, and the minimum requested capacity.
+//arg curLen: The current length
+//arg reqLen: The length as requested by the user
+//ret sugLen: A suggested growth.
+private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) @safe pure nothrow
+{
+    import core.bitop : bsr;
+    import std.algorithm : max;
+    if(curLen == 0)
+        return max(reqLen,8);
+    ulong mult = 100 + (1000UL) / (bsr(curLen * TSizeOf) + 1);
+    // limit to doubling the length, we don't want to grow too much
+    if(mult > 200)
+        mult = 200;
+    auto sugLen = cast(size_t)((curLen * mult + 99) / 100);
+    return max(reqLen, sugLen);
+}
+
+/**
+ * An appender that can update an array in-place.  It forwards all calls to an
+ * underlying appender implementation.  Any calls made to the appender also update
+ * the pointer to the original array passed in.
+ */
+struct RefAppender(A)
+if (isDynamicArray!A)
+{
+    private
+    {
+        alias T = ElementEncodingType!A;
+        Appender!A impl;
+        T[] *arr;
+    }
+
+    /**
+     * Construct a ref appender with a given array reference.  This does not copy the
+     * data.  If the array has a larger capacity as determined by arr.capacity, it
+     * will be used by the appender.  $(D RefAppender) assumes that arr is a non-null
+     * value.
+     *
+     * Note, do not use builtin appending (i.e. ~=) on the original array passed in
+     * until you are done with the appender, because calls to the appender override
+     * those appends.
+     */
+    this(T[] *arr)
+    {
+        impl = Appender!A(*arr);
+        this.arr = arr;
+    }
+
+    auto opDispatch(string fn, Args...)(Args args) if (is(typeof(mixin("impl." ~ fn ~ "(args)"))))
+    {
+        // we do it this way because we can't cache a void return
+        scope(exit) *this.arr = impl.data;
+        mixin("return impl." ~ fn ~ "(args);");
+    }
+
+    private alias AppenderType = Appender!A;
+
+    /**
+     * Appends one item to the managed array.
+     */
+    void opOpAssign(string op : "~", U)(U item) if (AppenderType.canPutItem!U)
+    {
+        scope(exit) *this.arr = impl.data;
+        impl.put(item);
+    }
+
+    // Const fixing hack.
+    void opOpAssign(string op : "~", Range)(Range items) if (AppenderType.canPutConstRange!Range)
+    {
+        scope(exit) *this.arr = impl.data;
+        impl.put(items);
+    }
+
+    /**
+     * Appends an entire range to the managed array.
+     */
+    void opOpAssign(string op : "~", Range)(Range items) if (AppenderType.canPutRange!Range)
+    {
+        scope(exit) *this.arr = impl.data;
+        impl.put(items);
+    }
+
+    /**
+     * Returns the capacity of the array (the maximum number of elements the
+     * managed array can accommodate before triggering a reallocation).  If any
+     * appending will reallocate, $(D capacity) returns $(D 0).
+     */
+    @property size_t capacity() const
+    {
+        return impl.capacity;
+    }
+
+    /**
+     * Returns the managed array.
+     */
+    @property inout(T)[] data() inout
+    {
+        return impl.data;
+    }
+}
+
+/++
+    Convenience function that returns an $(D Appender!A) object initialized
+    with $(D array).
+ +/
+Appender!A appender(A)()
+if (isDynamicArray!A)
+{
+    return Appender!A(null);
+}
+/// ditto
+Appender!(E[]) appender(A : E[], E)(auto ref A array)
+{
+    static assert (!isStaticArray!A || __traits(isRef, array),
+        "Cannot create Appender from an rvalue static array");
+
+    return Appender!(E[])(array);
+}
+
+@safe pure nothrow unittest
+{
+    import std.exception;
+    {
+        auto app = appender!(char[])();
+        string b = "abcdefg";
+        foreach (char c; b) app.put(c);
+        assert(app.data == "abcdefg");
+    }
+    {
+        auto app = appender!(char[])();
+        string b = "abcdefg";
+        foreach (char c; b) app ~= c;
+        assert(app.data == "abcdefg");
+    }
+    {
+        int[] a = [ 1, 2 ];
+        auto app2 = appender(a);
+        assert(app2.data == [ 1, 2 ]);
+        app2.put(3);
+        app2.put([ 4, 5, 6 ][]);
+        assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]);
+        app2.put([ 7 ]);
+        assert(app2.data == [ 1, 2, 3, 4, 5, 6, 7 ]);
+    }
+
+    int[] a = [ 1, 2 ];
+    auto app2 = appender(a);
+    assert(app2.data == [ 1, 2 ]);
+    app2 ~= 3;
+    app2 ~= [ 4, 5, 6 ][];
+    assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]);
+    app2 ~= [ 7 ];
+    assert(app2.data == [ 1, 2, 3, 4, 5, 6, 7 ]);
+
+    app2.reserve(5);
+    assert(app2.capacity >= 5);
+
+    try // shrinkTo may throw
+    {
+        app2.shrinkTo(3);
+    }
+    catch (Exception) assert(0);
+    assert(app2.data == [ 1, 2, 3 ]);
+    assertThrown(app2.shrinkTo(5));
+
+    const app3 = app2;
+    assert(app3.capacity >= 3);
+    assert(app3.data == [1, 2, 3]);
+
+    auto app4 = appender([]);
+    try // shrinkTo may throw
+    {
+        app4.shrinkTo(0);
+    }
+    catch (Exception) assert(0);
+
+    // Issue 5663 & 9725 tests
+    foreach (S; AliasSeq!(char[], const(char)[], string))
+    {
+        {
+            Appender!S app5663i;
+            assertNotThrown(app5663i.put("\xE3"));
+            assert(app5663i.data == "\xE3");
+
+            Appender!S app5663c;
+            assertNotThrown(app5663c.put(cast(const(char)[])"\xE3"));
+            assert(app5663c.data == "\xE3");
+
+            Appender!S app5663m;
+            assertNotThrown(app5663m.put("\xE3".dup));
+            assert(app5663m.data == "\xE3");
+        }
+        // ditto for ~=
+        {
+            Appender!S app5663i;
+            assertNotThrown(app5663i ~= "\xE3");
+            assert(app5663i.data == "\xE3");
+
+            Appender!S app5663c;
+            assertNotThrown(app5663c ~= cast(const(char)[])"\xE3");
+            assert(app5663c.data == "\xE3");
+
+            Appender!S app5663m;
+            assertNotThrown(app5663m ~= "\xE3".dup);
+            assert(app5663m.data == "\xE3");
+        }
+    }
+
+    static struct S10122
+    {
+        int val;
+
+        @disable this();
+        this(int v) @safe pure nothrow { val = v; }
+    }
+    assertCTFEable!(
+    {
+        auto w = appender!(S10122[])();
+        w.put(S10122(1));
+        assert(w.data.length == 1 && w.data[0].val == 1);
+    });
+}
+
+@safe pure nothrow unittest
+{
+    {
+        auto w = appender!string();
+        w.reserve(4);
+        cast(void)w.capacity;
+        cast(void)w.data;
+        try
+        {
+            wchar wc = 'a';
+            dchar dc = 'a';
+            w.put(wc);    // decoding may throw
+            w.put(dc);    // decoding may throw
+        }
+        catch (Exception) assert(0);
+    }
+    {
+        auto w = appender!(int[])();
+        w.reserve(4);
+        cast(void)w.capacity;
+        cast(void)w.data;
+        w.put(10);
+        w.put([10]);
+        w.clear();
+        try
+        {
+            w.shrinkTo(0);
+        }
+        catch (Exception) assert(0);
+
+        struct N
+        {
+            int payload;
+            alias payload this;
+        }
+        w.put(N(1));
+        w.put([N(2)]);
+
+        struct S(T)
+        {
+            @property bool empty() { return true; }
+            @property T front() { return T.init; }
+            void popFront() {}
+        }
+        S!int r;
+        w.put(r);
+    }
+}
+
+unittest
+{
+    import std.typecons;
+    import std.algorithm;
+    //10690
+    [tuple(1)].filter!(t => true).array; // No error
+    [tuple("A")].filter!(t => true).array; // error
+}
+
+unittest
+{
+    import std.range;
+    //Coverage for put(Range)
+    struct S1
+    {
+    }
+    struct S2
+    {
+        void opAssign(S2){}
+    }
+    auto a1 = Appender!(S1[])();
+    auto a2 = Appender!(S2[])();
+    auto au1 = Appender!(const(S1)[])();
+    auto au2 = Appender!(const(S2)[])();
+    a1.put(S1().repeat().take(10));
+    a2.put(S2().repeat().take(10));
+    auto sc1 = const(S1)();
+    auto sc2 = const(S2)();
+    au1.put(sc1.repeat().take(10));
+    au2.put(sc2.repeat().take(10));
+}
+
+unittest
+{
+    struct S
+    {
+        int* p;
+    }
+
+    auto a0 = Appender!(S[])();
+    auto a1 = Appender!(const(S)[])();
+    auto a2 = Appender!(immutable(S)[])();
+    auto s0 = S(null);
+    auto s1 = const(S)(null);
+    auto s2 = immutable(S)(null);
+    a1.put(s0);
+    a1.put(s1);
+    a1.put(s2);
+    a1.put([s0]);
+    a1.put([s1]);
+    a1.put([s2]);
+    a0.put(s0);
+    static assert(!is(typeof(a0.put(a1))));
+    static assert(!is(typeof(a0.put(a2))));
+    a0.put([s0]);
+    static assert(!is(typeof(a0.put([a1]))));
+    static assert(!is(typeof(a0.put([a2]))));
+    static assert(!is(typeof(a2.put(a0))));
+    static assert(!is(typeof(a2.put(a1))));
+    a2.put(s2);
+    static assert(!is(typeof(a2.put([a0]))));
+    static assert(!is(typeof(a2.put([a1]))));
+    a2.put([s2]);
+}
+
+unittest
+{
+    //9528
+    const(E)[] fastCopy(E)(E[] src) {
+            auto app = appender!(const(E)[])();
+            foreach (i, e; src)
+                    app.put(e);
+            return app.data;
+    }
+
+    class C {}
+    struct S { const(C) c; }
+    S[] s = [ S(new C) ];
+
+    auto t = fastCopy(s); // Does not compile
+}
+
+unittest
+{
+    import std.algorithm : map;
+    //10753
+    struct Foo {
+       immutable dchar d;
+    }
+    struct Bar {
+       immutable int x;
+    }
+    "12".map!Foo.array;
+    [1, 2].map!Bar.array;
+}
+
+unittest
+{
+    //New appender signature tests
+    alias mutARR = int[];
+    alias conARR = const(int)[];
+    alias immARR = immutable(int)[];
+
+    mutARR mut;
+    conARR con;
+    immARR imm;
+
+    {auto app = Appender!mutARR(mut);}                //Always worked. Should work. Should not create a warning.
+    static assert(!is(typeof(Appender!mutARR(con)))); //Never worked.  Should not work.
+    static assert(!is(typeof(Appender!mutARR(imm)))); //Never worked.  Should not work.
+
+    {auto app = Appender!conARR(mut);} //Always worked. Should work. Should not create a warning.
+    {auto app = Appender!conARR(con);} //Didn't work.   Now works.   Should not create a warning.
+    {auto app = Appender!conARR(imm);} //Didn't work.   Now works.   Should not create a warning.
+
+    //{auto app = Appender!immARR(mut);}                //Worked. Will cease to work. Creates warning.
+    //static assert(!is(typeof(Appender!immARR(mut)))); //Worked. Will cease to work. Uncomment me after full deprecation.
+    static assert(!is(typeof(Appender!immARR(con))));   //Never worked. Should not work.
+    {auto app = Appender!immARR(imm);}                  //Didn't work.  Now works. Should not create a warning.
+
+    //Deprecated. Please uncomment and make sure this doesn't work:
+    //char[] cc;
+    //static assert(!is(typeof(Appender!string(cc))));
+
+    //This should always work:
+    {auto app = appender!string(null);}
+    {auto app = appender!(const(char)[])(null);}
+    {auto app = appender!(char[])(null);}
+}
+
+unittest //Test large allocations (for GC.extend)
+{
+    import std.range;
+    import std.algorithm : equal;
+    Appender!(char[]) app;
+    app.reserve(1); //cover reserve on non-initialized
+    foreach(_; 0 .. 100_000)
+        app.put('a');
+    assert(equal(app.data, 'a'.repeat(100_000)));
+}
+
+unittest
+{
+    auto reference = new ubyte[](2048 + 1); //a number big enough to have a full page (EG: the GC extends)
+    auto arr = reference.dup;
+    auto app = appender(arr[0 .. 0]);
+    app.reserve(1); //This should not trigger a call to extend
+    app.put(ubyte(1)); //Don't clobber arr
+    assert(reference[] == arr[]);
+}
+
+unittest // clear method is supported only for mutable element types
+{
+    Appender!string app;
+    static assert(!__traits(compiles, app.clear()));
+}
+
+unittest
+{
+    static struct D//dynamic
+    {
+        int[] i;
+        alias i this;
+    }
+    static struct S//static
+    {
+        int[5] i;
+        alias i this;
+    }
+    static assert(!is(Appender!(char[5])));
+    static assert(!is(Appender!D));
+    static assert(!is(Appender!S));
+
+    enum int[5] a = [];
+    int[5] b;
+    D d;
+    S s;
+    int[5] foo(){return a;}
+
+    static assert(!is(typeof(appender(a))));
+    static assert( is(typeof(appender(b))));
+    static assert( is(typeof(appender(d))));
+    static assert( is(typeof(appender(s))));
+    static assert(!is(typeof(appender(foo()))));
+}
+
+unittest
+{
+    // Issue 13077
+    static class A {}
+
+    // reduced case
+    auto w = appender!(shared(A)[])();
+    w.put(new shared A());
+
+    // original case
+    import std.range;
+    InputRange!(shared A) foo()
+    {
+        return [new shared A].inputRangeObject;
+    }
+    auto res = foo.array;
+}
+
+/++
+    Convenience function that returns a $(D RefAppender!A) object initialized
+    with $(D array).  Don't use null for the $(D array) pointer, use the other
+    version of $(D appender) instead.
+ +/
+RefAppender!(E[]) appender(A : E[]*, E)(A array)
+{
+    return RefAppender!(E[])(array);
+}
+
+unittest
+{
+    import std.exception;
+    {
+        auto arr = new char[0];
+        auto app = appender(&arr);
+        string b = "abcdefg";
+        foreach (char c; b) app.put(c);
+        assert(app.data == "abcdefg");
+        assert(arr == "abcdefg");
+    }
+    {
+        auto arr = new char[0];
+        auto app = appender(&arr);
+        string b = "abcdefg";
+        foreach (char c; b) app ~= c;
+        assert(app.data == "abcdefg");
+        assert(arr == "abcdefg");
+    }
+    {
+        int[] a = [ 1, 2 ];
+        auto app2 = appender(&a);
+        assert(app2.data == [ 1, 2 ]);
+        assert(a == [ 1, 2 ]);
+        app2.put(3);
+        app2.put([ 4, 5, 6 ][]);
+        assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]);
+        assert(a == [ 1, 2, 3, 4, 5, 6 ]);
+    }
+
+    int[] a = [ 1, 2 ];
+    auto app2 = appender(&a);
+    assert(app2.data == [ 1, 2 ]);
+    assert(a == [ 1, 2 ]);
+    app2 ~= 3;
+    app2 ~= [ 4, 5, 6 ][];
+    assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]);
+    assert(a == [ 1, 2, 3, 4, 5, 6 ]);
+
+    app2.reserve(5);
+    assert(app2.capacity >= 5);
+
+    try // shrinkTo may throw
+    {
+        app2.shrinkTo(3);
+    }
+    catch (Exception) assert(0);
+    assert(app2.data == [ 1, 2, 3 ]);
+    assertThrown(app2.shrinkTo(5));
+
+    const app3 = app2;
+    assert(app3.capacity >= 3);
+    assert(app3.data == [1, 2, 3]);
+}
+
+unittest // issue 14605
+{
+    static assert(isOutputRange!(Appender!(int[]), int));
+    static assert(isOutputRange!(RefAppender!(int[]), int));
+}
+
+unittest
+{
+    Appender!(int[]) app;
+    short[] range = [1, 2, 3];
+    app.put(range);
+    assert(app.data == [1, 2, 3]);
+}
+
+unittest
+{
+    string s = "hello".idup;
+    char[] a = "hello".dup;
+    auto appS = appender(s);
+    auto appA = appender(a);
+    put(appS, 'w');
+    put(appA, 'w');
+    s ~= 'a'; //Clobbers here?
+    a ~= 'a'; //Clobbers here?
+    assert(appS.data == "hellow");
+    assert(appA.data == "hellow");
+}
diff --git a/libphobos/src/std/ascii.d b/libphobos/src/std/ascii.d
new file mode 100644
index 00000000000..3e669f944c8
--- /dev/null
+++ b/libphobos/src/std/ascii.d
@@ -0,0 +1,671 @@
+// Written in the D programming language.
+
+/++
+    Functions which operate on ASCII characters.
+
+    All of the functions in std._ascii accept Unicode characters but
+    effectively ignore them if they're not ASCII. All $(D isX) functions return
+    $(D false) for non-ASCII characters, and all $(D toX) functions do nothing
+    to non-ASCII characters.
+
+    For functions which operate on Unicode characters, see
+    $(LINK2 std_uni.html, std.uni).
+
+    References:
+        $(LINK2 http://www.digitalmars.com/d/ascii-table.html, ASCII Table),
+        $(WEB en.wikipedia.org/wiki/Ascii, Wikipedia)
+
+    Macros:
+        WIKI=Phobos/StdASCII
+
+    Copyright: Copyright 2000 - 2013
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+    Authors:   $(WEB digitalmars.com, Walter Bright) and Jonathan M Davis
+    Source:    $(PHOBOSSRC std/_ascii.d)
+  +/
+module std.ascii;
+
+version (unittest)
+{
+    // FIXME: When dmd bug #314 is fixed, make these selective.
+    import std.meta; // : AliasSeq;
+    import std.range; // : chain;
+    import std.traits; // : functionAttributes, FunctionAttribute, isSafe;
+}
+
+
+immutable fullHexDigits  = "0123456789ABCDEFabcdef";     /// 0..9A..Fa..f
+immutable hexDigits      = fullHexDigits[0..16];         /// 0..9A..F
+immutable lowerHexDigits = "0123456789abcdef";           /// 0..9a..f
+immutable digits         = hexDigits[0..10];             /// 0..9
+immutable octalDigits    = digits[0..8];                 /// 0..7
+immutable letters        = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /// A..Za..z
+immutable uppercase      = letters[0..26];               /// A..Z
+immutable lowercase      = letters[26..52];              /// a..z
+immutable whitespace     = " \t\v\r\n\f";                /// ASCII _whitespace
+
+/++
+    Letter case specifier.
+  +/
+enum LetterCase : bool
+{
+    upper, /// Upper case letters
+    lower  /// Lower case letters
+}
+
+/// Newline sequence for this system.
+version(Windows)
+    immutable newline = "\r\n";
+else version(Posix)
+    immutable newline = "\n";
+else
+    static assert(0, "Unsupported OS");
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a letter or a number (0..9, a..z, A..Z).
+  +/
+bool isAlphaNum(dchar c) @safe pure nothrow @nogc
+{
+    return c <= 'z' && c >= '0' && (c <= '9' || c >= 'a' || (c >= 'A' && c <= 'Z'));
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isAlphaNum('A'));
+    assert( isAlphaNum('1'));
+    assert(!isAlphaNum('#'));
+
+    // N.B.: does not return true for non-ASCII Unicode alphanumerics:
+    assert(!isAlphaNum('á'));
+}
+
+unittest
+{
+    foreach(c; chain(digits, octalDigits, fullHexDigits, letters, lowercase, uppercase))
+        assert(isAlphaNum(c));
+
+    foreach(c; whitespace)
+        assert(!isAlphaNum(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is an ASCII letter (A..Z, a..z).
+  +/
+bool isAlpha(dchar c) @safe pure nothrow @nogc
+{
+    // Optimizer can turn this into a bitmask operation on 64 bit code
+    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isAlpha('A'));
+    assert(!isAlpha('1'));
+    assert(!isAlpha('#'));
+
+    // N.B.: does not return true for non-ASCII Unicode alphabetic characters:
+    assert(!isAlpha('á'));
+}
+
+unittest
+{
+    foreach(c; chain(letters, lowercase, uppercase))
+        assert(isAlpha(c));
+
+    foreach(c; chain(digits, octalDigits, whitespace))
+        assert(!isAlpha(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a lowercase ASCII letter (a..z).
+  +/
+bool isLower(dchar c) @safe pure nothrow @nogc
+{
+    return c >= 'a' && c <= 'z';
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isLower('a'));
+    assert(!isLower('A'));
+    assert(!isLower('#'));
+
+    // N.B.: does not return true for non-ASCII Unicode lowercase letters
+    assert(!isLower('á'));
+    assert(!isLower('Á'));
+}
+
+unittest
+{
+    foreach(c; lowercase)
+        assert(isLower(c));
+
+    foreach(c; chain(digits, uppercase, whitespace))
+        assert(!isLower(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is an uppercase ASCII letter (A..Z).
+  +/
+bool isUpper(dchar c) @safe pure nothrow @nogc
+{
+    return c <= 'Z' && 'A' <= c;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isUpper('A'));
+    assert(!isUpper('a'));
+    assert(!isUpper('#'));
+
+    // N.B.: does not return true for non-ASCII Unicode uppercase letters
+    assert(!isUpper('á'));
+    assert(!isUpper('Á'));
+}
+
+unittest
+{
+    foreach(c; uppercase)
+        assert(isUpper(c));
+
+    foreach(c; chain(digits, lowercase, whitespace))
+        assert(!isUpper(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a digit (0..9).
+  +/
+bool isDigit(dchar c) @safe pure nothrow @nogc
+{
+    return '0' <= c && c <= '9';
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isDigit('3'));
+    assert( isDigit('8'));
+    assert(!isDigit('B'));
+    assert(!isDigit('#'));
+
+    // N.B.: does not return true for non-ASCII Unicode numbers
+    assert(!isDigit('０')); // full-width digit zero (U+FF10)
+    assert(!isDigit('４')); // full-width digit four (U+FF14)
+}
+
+unittest
+{
+    foreach(c; digits)
+        assert(isDigit(c));
+
+    foreach(c; chain(letters, whitespace))
+        assert(!isDigit(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a digit in base 8 (0..7).
+  +/
+bool isOctalDigit(dchar c) @safe pure nothrow @nogc
+{
+    return c >= '0' && c <= '7';
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isOctalDigit('0'));
+    assert( isOctalDigit('7'));
+    assert(!isOctalDigit('8'));
+    assert(!isOctalDigit('A'));
+    assert(!isOctalDigit('#'));
+}
+
+unittest
+{
+    foreach(c; octalDigits)
+        assert(isOctalDigit(c));
+
+    foreach(c; chain(letters, ['8', '9'], whitespace))
+        assert(!isOctalDigit(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a digit in base 16 (0..9, A..F, a..f).
+  +/
+bool isHexDigit(dchar c) @safe pure nothrow @nogc
+{
+    return c <= 'f' && c >= '0' && (c <= '9' || c >= 'a' || (c >= 'A' && c <= 'F'));
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isHexDigit('0'));
+    assert( isHexDigit('A'));
+    assert( isHexDigit('f')); // lowercase hex digits are accepted
+    assert(!isHexDigit('g'));
+    assert(!isHexDigit('G'));
+    assert(!isHexDigit('#'));
+}
+
+unittest
+{
+    foreach(c; fullHexDigits)
+        assert(isHexDigit(c));
+
+    foreach(c; chain(lowercase[6 .. $], uppercase[6 .. $], whitespace))
+        assert(!isHexDigit(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether or not $(D c) is a whitespace character. That includes the
+    space, tab, vertical tab, form feed, carriage return, and linefeed
+    characters.
+  +/
+bool isWhite(dchar c) @safe pure nothrow @nogc
+{
+    return c == ' ' || (c >= 0x09 && c <= 0x0D);
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isWhite(' '));
+    assert( isWhite('\t'));
+    assert( isWhite('\n'));
+    assert(!isWhite('1'));
+    assert(!isWhite('a'));
+    assert(!isWhite('#'));
+
+    // N.B.: Does not return true for non-ASCII Unicode whitespace characters.
+    static import std.uni;
+    assert(std.uni.isWhite('\u00A0'));
+    assert(!isWhite('\u00A0')); // std.ascii.isWhite
+}
+
+unittest
+{
+    foreach(c; whitespace)
+        assert(isWhite(c));
+
+    foreach(c; chain(digits, letters))
+        assert(!isWhite(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether $(D c) is a control character.
+  +/
+bool isControl(dchar c) @safe pure nothrow @nogc
+{
+    return c < 0x20 || c == 0x7F;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isControl('\0'));
+    assert( isControl('\022'));
+    assert( isControl('\n')); // newline is both whitespace and control
+    assert(!isControl(' '));
+    assert(!isControl('1'));
+    assert(!isControl('a'));
+    assert(!isControl('#'));
+
+    // N.B.: non-ASCII Unicode control characters are not recognized:
+    assert(!isControl('\u0080'));
+    assert(!isControl('\u2028'));
+    assert(!isControl('\u2029'));
+}
+
+unittest
+{
+    foreach(dchar c; 0 .. 32)
+        assert(isControl(c));
+    assert(isControl(127));
+
+    foreach(c; chain(digits, letters, [' ']))
+        assert(!isControl(c));
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether or not $(D c) is a punctuation character. That includes
+    all ASCII characters which are not control characters, letters, digits, or
+    whitespace.
+  +/
+bool isPunctuation(dchar c) @safe pure nothrow @nogc
+{
+    return c <= '~' && c >= '!' && !isAlphaNum(c);
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isPunctuation('.'));
+    assert( isPunctuation(','));
+    assert( isPunctuation(':'));
+    assert( isPunctuation('!'));
+    assert( isPunctuation('#'));
+    assert( isPunctuation('~'));
+    assert( isPunctuation('+'));
+    assert( isPunctuation('_'));
+
+    assert(!isPunctuation('1'));
+    assert(!isPunctuation('a'));
+    assert(!isPunctuation(' '));
+    assert(!isPunctuation('\n'));
+    assert(!isPunctuation('\0'));
+
+    // N.B.: Non-ASCII Unicode punctuation characters are not recognized.
+    assert(!isPunctuation('\u2012')); // (U+2012 = en-dash)
+}
+
+unittest
+{
+    foreach(dchar c; 0 .. 128)
+    {
+        if(isControl(c) || isAlphaNum(c) || c == ' ')
+            assert(!isPunctuation(c));
+        else
+            assert(isPunctuation(c));
+    }
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether or not $(D c) is a printable character other than the
+    space character.
+  +/
+bool isGraphical(dchar c) @safe pure nothrow @nogc
+{
+    return '!' <= c && c <= '~';
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isGraphical('1'));
+    assert( isGraphical('a'));
+    assert( isGraphical('#'));
+    assert(!isGraphical(' ')); // whitespace is not graphical
+    assert(!isGraphical('\n'));
+    assert(!isGraphical('\0'));
+
+    // N.B.: Unicode graphical characters are not regarded as such.
+    assert(!isGraphical('á'));
+}
+
+unittest
+{
+    foreach(dchar c; 0 .. 128)
+    {
+        if(isControl(c) || c == ' ')
+            assert(!isGraphical(c));
+        else
+            assert(isGraphical(c));
+    }
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether or not $(D c) is a printable character - including the
+    space character.
+  +/
+bool isPrintable(dchar c) @safe pure nothrow @nogc
+{
+    return c >= ' ' && c <= '~';
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isPrintable(' '));  // whitespace is printable
+    assert( isPrintable('1'));
+    assert( isPrintable('a'));
+    assert( isPrintable('#'));
+    assert(!isPrintable('\0')); // control characters are not printable
+
+    // N.B.: Printable non-ASCII Unicode characters are not recognized.
+    assert(!isPrintable('á'));
+}
+
+unittest
+{
+    foreach(dchar c; 0 .. 128)
+    {
+        if(isControl(c))
+            assert(!isPrintable(c));
+        else
+            assert(isPrintable(c));
+    }
+}
+
+
+/++
+    Params: c = The character to test.
+    Returns: Whether or not $(D c) is in the ASCII character set - i.e. in the
+    range 0..0x7F.
+  +/
+bool isASCII(dchar c) @safe pure nothrow @nogc
+{
+    return c <= 0x7F;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isASCII('a'));
+    assert(!isASCII('á'));
+}
+
+unittest
+{
+    foreach(dchar c; 0 .. 128)
+        assert(isASCII(c));
+
+    assert(!isASCII(128));
+}
+
+
+/++
+    Converts an ASCII letter to lowercase.
+
+    Params: c = A character of any type that implicitly converts to $(D dchar).
+    In the case where it's a built-in type, or an enum of a built-in type,
+    $(D Unqual!(OriginalType!C)) is returned, whereas if it's a user-defined
+    type, $(D dchar) is returned.
+
+    Returns: The corresponding lowercase letter, if $(D c) is an uppercase
+    ASCII character, otherwise $(D c) itself.
+  +/
+auto toLower(C)(C c)
+    if(is(C : dchar))
+{
+    import std.traits : isAggregateType, OriginalType, Unqual;
+
+    alias OC = OriginalType!C;
+    static if (isAggregateType!OC)
+        alias R = dchar;
+    else
+        alias R = Unqual!OC;
+
+    return isUpper(c) ? cast(R)(cast(R)c + 'a' - 'A') : cast(R)c;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(toLower('a') == 'a');
+    assert(toLower('A') == 'a');
+    assert(toLower('#') == '#');
+
+    // N.B.: Non-ASCII Unicode uppercase letters are not converted.
+    assert(toLower('Á') == 'Á');
+}
+
+@safe pure nothrow unittest
+{
+
+    foreach(C; AliasSeq!(char, wchar, dchar, immutable char, ubyte))
+    {
+        foreach(i, c; uppercase)
+            assert(toLower(cast(C)c) == lowercase[i]);
+
+        foreach(C c; 0 .. 128)
+        {
+            if(c < 'A' || c > 'Z')
+                assert(toLower(c) == c);
+            else
+                assert(toLower(c) != c);
+        }
+
+        foreach(C c; 128 .. C.max)
+            assert(toLower(c) == c);
+
+        //CTFE
+        static assert(toLower(cast(C)'a') == 'a');
+        static assert(toLower(cast(C)'A') == 'a');
+    }
+}
+
+
+/++
+    Converts an ASCII letter to uppercase.
+
+    Params: c = Any type which implicitly converts to $(D dchar). In the case
+    where it's a built-in type, or an enum of a built-in type,
+    $(D Unqual!(OriginalType!C)) is returned, whereas if it's a user-defined
+    type, $(D dchar) is returned.
+
+    Returns: The corresponding uppercase letter, if $(D c) is a lowercase ASCII
+    character, otherwise $(D c) itself.
+  +/
+auto toUpper(C)(C c)
+    if(is(C : dchar))
+{
+    import std.traits : isAggregateType, OriginalType, Unqual;
+
+    alias OC = OriginalType!C;
+    static if (isAggregateType!OC)
+        alias R = dchar;
+    else
+        alias R = Unqual!OC;
+
+    return isLower(c) ? cast(R)(cast(R)c - ('a' - 'A')) : cast(R)c;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(toUpper('a') == 'A');
+    assert(toUpper('A') == 'A');
+    assert(toUpper('#') == '#');
+
+    // N.B.: Non-ASCII Unicode lowercase letters are not converted.
+    assert(toUpper('á') == 'á');
+}
+
+@safe pure nothrow unittest
+{
+    foreach(C; AliasSeq!(char, wchar, dchar, immutable char, ubyte))
+    {
+        foreach(i, c; lowercase)
+            assert(toUpper(cast(C)c) == uppercase[i]);
+
+        foreach(C c; 0 .. 128)
+        {
+            if(c < 'a' || c > 'z')
+                assert(toUpper(c) == c);
+            else
+                assert(toUpper(c) != c);
+        }
+
+        foreach(C c; 128 .. C.max)
+            assert(toUpper(c) == c);
+
+        //CTFE
+        static assert(toUpper(cast(C)'a') == 'A');
+        static assert(toUpper(cast(C)'A') == 'A');
+    }
+}
+
+
+unittest //Test both toUpper and toLower with non-builtin
+{
+    //User Defined [Char|Wchar|Dchar]
+    static struct UDC {  char c; alias c this; }
+    static struct UDW { wchar c; alias c this; }
+    static struct UDD { dchar c; alias c this; }
+    //[Char|Wchar|Dchar] Enum
+    enum CE :  char {a = 'a', A = 'A'}
+    enum WE : wchar {a = 'a', A = 'A'}
+    enum DE : dchar {a = 'a', A = 'A'}
+    //User Defined [Char|Wchar|Dchar] Enum
+    enum UDCE : UDC {a = UDC('a'), A = UDC('A')}
+    enum UDWE : UDW {a = UDW('a'), A = UDW('A')}
+    enum UDDE : UDD {a = UDD('a'), A = UDD('A')}
+
+    //User defined types with implicit cast to dchar test.
+    foreach (Char; AliasSeq!(UDC, UDW, UDD))
+    {
+        assert(toLower(Char('a')) == 'a');
+        assert(toLower(Char('A')) == 'a');
+        static assert(toLower(Char('a')) == 'a');
+        static assert(toLower(Char('A')) == 'a');
+        static assert(toUpper(Char('a')) == 'A');
+        static assert(toUpper(Char('A')) == 'A');
+    }
+
+    //Various enum tests.
+    foreach (Enum; AliasSeq!(CE, WE, DE, UDCE, UDWE, UDDE))
+    {
+        assert(toLower(Enum.a) == 'a');
+        assert(toLower(Enum.A) == 'a');
+        assert(toUpper(Enum.a) == 'A');
+        assert(toUpper(Enum.A) == 'A');
+        static assert(toLower(Enum.a) == 'a');
+        static assert(toLower(Enum.A) == 'a');
+        static assert(toUpper(Enum.a) == 'A');
+        static assert(toUpper(Enum.A) == 'A');
+    }
+
+    //Return value type tests for enum of non-UDT. These should be the original type.
+    foreach (T; AliasSeq!(CE, WE, DE))
+    {
+        alias C = OriginalType!T;
+        static assert(is(typeof(toLower(T.init)) == C));
+        static assert(is(typeof(toUpper(T.init)) == C));
+    }
+
+    //Return value tests for UDT and enum of UDT. These should be dchar
+    foreach (T; AliasSeq!(UDC, UDW, UDD, UDCE, UDWE, UDDE))
+    {
+        static assert(is(typeof(toLower(T.init)) == dchar));
+        static assert(is(typeof(toUpper(T.init)) == dchar));
+    }
+}
diff --git a/libphobos/src/std/base64.d b/libphobos/src/std/base64.d
new file mode 100644
index 00000000000..1dc8fb6c86b
--- /dev/null
+++ b/libphobos/src/std/base64.d
@@ -0,0 +1,2043 @@
+// Written in the D programming language.
+
+/**
+ * Support for Base64 encoding and decoding.
+ *
+ * This module provides two default implementations of Base64 encoding,
+ * $(D $(LREF Base64)) with a standard encoding alphabet, and a variant
+ * $(D $(LREF Base64URL)) that has a modified encoding alphabet designed to be
+ * safe for embedding in URLs and filenames.
+ *
+ * Both variants are implemented as instantiations of the template
+ * $(D $(LREF Base64Impl)). Most users will not need to use this template
+ * directly; however, it can be used to create customized Base64 encodings,
+ * such as one that omits padding characters, or one that is safe to embed
+ * inside a regular expression.
+ *
+ * Example:
+ * -----
+ * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
+ *
+ * const(char)[] encoded = Base64.encode(data);
+ * assert(encoded == "FPucA9l+");
+ *
+ * ubyte[] decoded = Base64.decode("FPucA9l+");
+ * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
+ * -----
+ *
+ * The range API is supported for both encoding and decoding:
+ *
+ * Example:
+ * -----
+ * // Create MIME Base64 with CRLF, per line 76.
+ * File f = File("./text.txt", "r");
+ * scope(exit) f.close();
+ *
+ * Appender!string mime64 = appender!string;
+ *
+ * foreach (encoded; Base64.encoder(f.byChunk(57)))
+ * {
+ *     mime64.put(encoded);
+ *     mime64.put("\r\n");
+ * }
+ *
+ * writeln(mime64.data);
+ * -----
+ *
+ * References:
+ * $(WEB tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64
+ * Data Encodings)
+ *
+ * Copyright: Masahiro Nakagawa 2010-.
+ * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder)
+ * Source:    $(PHOBOSSRC std/_base64.d)
+ * Macros:
+ *      LREF2=<a href="#$1">$(D $2)</a>
+ */
+module std.base64;
+
+import std.exception;  // enforce
+import std.range.primitives;      // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength
+import std.traits;     // isArray
+
+// Make sure module header code examples work correctly.
+unittest
+{
+    ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e];
+
+    const(char)[] encoded = Base64.encode(data);
+    assert(encoded == "FPucA9l+");
+
+    ubyte[] decoded = Base64.decode("FPucA9l+");
+    assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
+}
+
+/**
+ * Implementation of standard _Base64 encoding.
+ *
+ * See $(D $(LREF Base64Impl)) for a description of available methods.
+ */
+alias Base64 = Base64Impl!('+', '/');
+
+///
+unittest
+{
+    ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
+    assert(Base64.encode(data) == "g9cwegE/");
+    assert(Base64.decode("g9cwegE/") == data);
+}
+
+
+/**
+ * Variation of Base64 encoding that is safe for use in URLs and filenames.
+ *
+ * See $(D $(LREF Base64Impl)) for a description of available methods.
+ */
+alias Base64URL = Base64Impl!('-', '_');
+
+///
+unittest
+{
+    ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
+    assert(Base64URL.encode(data) == "g9cwegE_");
+    assert(Base64URL.decode("g9cwegE_") == data);
+}
+
+
+/**
+ * Template for implementing Base64 encoding and decoding.
+ *
+ * For most purposes, direct usage of this template is not necessary; instead,
+ * this module provides two default implementations: $(D $(LREF Base64)) and
+ * $(D $(LREF Base64URL)), that implement basic Base64 encoding and a variant
+ * intended for use in URLs and filenames, respectively.
+ *
+ * Customized Base64 encoding schemes can be implemented by instantiating this
+ * template with the appropriate arguments. For example:
+ *
+ * -----
+ * // Non-standard Base64 format for embedding in regular expressions.
+ * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
+ * -----
+ *
+ * NOTE:
+ * Encoded strings will not have any padding if the $(D Padding) parameter is
+ * set to $(D NoPadding).
+ */
+template Base64Impl(char Map62th, char Map63th, char Padding = '=')
+{
+    enum NoPadding = '\0';  /// represents no-padding encoding
+
+
+    // Verify Base64 characters
+    static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice");
+    static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice");
+    static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice");
+    static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice");
+    static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice");
+    static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice");
+    static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice");
+    static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice");
+    static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice");
+    static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice");
+    static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
+    static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice");
+    static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character");
+    static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character");
+
+
+    /* Encode functions */
+
+
+    private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th;
+
+
+    /**
+     * Calculates the length needed to store the encoded string corresponding
+     * to an input of the given length.
+     *
+     * Params:
+     *  sourceLength = Length of the source array.
+     *
+     * Returns:
+     *  The length of a Base64 encoding of an array of the given length.
+     */
+    @safe
+    pure nothrow size_t encodeLength(in size_t sourceLength)
+    {
+        static if (Padding == NoPadding)
+            return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3);
+        else
+            return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4;
+    }
+
+    ///
+    unittest
+    {
+        ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
+
+        // Allocate a buffer large enough to hold the encoded string.
+        auto buf = new char[encodeLength(data.length)];
+
+        Base64.encode(data, buf);
+        assert(buf == "Gis8TV1u");
+    }
+
+
+    // ubyte[] to char[]
+
+
+    /**
+     * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64
+     * encoding.
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *           range) to _encode.
+     *  buffer = The $(D char[]) buffer to store the encoded result.
+     *
+     * Returns:
+     *  The slice of $(D_PARAM buffer) that contains the encoded string.
+     */
+    @trusted
+    pure char[] encode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) &&
+                                                            is(R2 == char[]))
+    in
+    {
+        assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
+    }
+    out(result)
+    {
+        assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return [];
+
+        immutable blocks = srcLen / 3;
+        immutable remain = srcLen % 3;
+        auto      bufptr = buffer.ptr;
+        auto      srcptr = source.ptr;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
+            *bufptr++ = EncodeMap[val >> 18       ];
+            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
+            *bufptr++ = EncodeMap[val >>  6 & 0x3f];
+            *bufptr++ = EncodeMap[val       & 0x3f];
+            srcptr += 3;
+        }
+
+        if (remain)
+        {
+            immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
+            *bufptr++ = EncodeMap[val >> 18       ];
+            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
+
+            final switch (remain)
+            {
+            case 2:
+                *bufptr++ = EncodeMap[val >> 6 & 0x3f];
+                static if (Padding != NoPadding)
+                    *bufptr++ = Padding;
+                break;
+            case 1:
+                static if (Padding != NoPadding)
+                {
+                    *bufptr++ = Padding;
+                    *bufptr++ = Padding;
+                }
+                break;
+            }
+        }
+
+        // encode method can't assume buffer length. So, slice needed.
+        return buffer[0..bufptr - buffer.ptr];
+    }
+
+    ///
+    unittest
+    {
+        ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f];
+        char[32] buffer;    // much bigger than necessary
+
+        // Just to be sure...
+        auto encodedLength = Base64.encodeLength(data.length);
+        assert(buffer.length >= encodedLength);
+
+        // encode() returns a slice to the provided buffer.
+        auto encoded = Base64.encode(data, buffer[]);
+        assert(encoded is buffer[0 .. encodedLength]);
+        assert(encoded == "g9cwegE/");
+    }
+
+
+    // InputRange to char[]
+
+
+    /**
+     * ditto
+     */
+    char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
+                                                    is(ElementType!R1 : ubyte) && hasLength!R1 &&
+                                                    is(R2 == char[]))
+    in
+    {
+        assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding");
+    }
+    out(result)
+    {
+        // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition.
+        //assert(result.length == encodeLength(source.length), "The length of result is different from Base64");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return [];
+
+        immutable blocks = srcLen / 3;
+        immutable remain = srcLen % 3;
+        auto      bufptr = buffer.ptr;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = source.front; source.popFront();
+            immutable v2 = source.front; source.popFront();
+            immutable v3 = source.front; source.popFront();
+            immutable val = v1 << 16 | v2 << 8 | v3;
+            *bufptr++ = EncodeMap[val >> 18       ];
+            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
+            *bufptr++ = EncodeMap[val >>  6 & 0x3f];
+            *bufptr++ = EncodeMap[val       & 0x3f];
+        }
+
+        if (remain)
+        {
+            size_t val = source.front << 16;
+            if (remain == 2)
+            {
+                source.popFront();
+                val |= source.front << 8;
+            }
+
+            *bufptr++ = EncodeMap[val >> 18       ];
+            *bufptr++ = EncodeMap[val >> 12 & 0x3f];
+
+            final switch (remain)
+            {
+            case 2:
+                *bufptr++ = EncodeMap[val >> 6 & 0x3f];
+                static if (Padding != NoPadding)
+                    *bufptr++ = Padding;
+                break;
+            case 1:
+                static if (Padding != NoPadding)
+                {
+                    *bufptr++ = Padding;
+                    *bufptr++ = Padding;
+                }
+                break;
+            }
+        }
+
+        // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'.
+        version (unittest) assert(bufptr - buffer.ptr == encodeLength(srcLen), "The length of result is different from Base64");
+
+        // encode method can't assume buffer length. So, slice needed.
+        return buffer[0..bufptr - buffer.ptr];
+    }
+
+
+    // ubyte[] to OutputRange
+
+
+    /**
+     * Encodes $(D_PARAM source) into an
+     * $(LINK2 std_range_primitives.html#isOutputRange, output range) using
+     * Base64 encoding.
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *           range) to _encode.
+     *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output
+     *           range) to store the encoded result.
+     *
+     * Returns:
+     *  The number of times the output range's $(D put) method was invoked.
+     */
+    size_t encode(R1, R2)(in R1 source, auto ref R2 range)
+        if (isArray!R1 && is(ElementType!R1 : ubyte) &&
+            !is(R2 == char[]) && isOutputRange!(R2, char))
+    out(result)
+    {
+        assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return 0;
+
+        immutable blocks = srcLen / 3;
+        immutable remain = srcLen % 3;
+        auto      srcptr = source.ptr;
+        size_t    pcount;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2];
+            put(range, EncodeMap[val >> 18       ]);
+            put(range, EncodeMap[val >> 12 & 0x3f]);
+            put(range, EncodeMap[val >>  6 & 0x3f]);
+            put(range, EncodeMap[val       & 0x3f]);
+            srcptr += 3;
+            pcount += 4;
+        }
+
+        if (remain)
+        {
+            immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0);
+            put(range, EncodeMap[val >> 18       ]);
+            put(range, EncodeMap[val >> 12 & 0x3f]);
+            pcount += 2;
+
+            final switch (remain)
+            {
+            case 2:
+                put(range, EncodeMap[val >> 6 & 0x3f]);
+                pcount++;
+
+                static if (Padding != NoPadding)
+                {
+                    put(range, Padding);
+                    pcount++;
+                }
+                break;
+            case 1:
+                static if (Padding != NoPadding)
+                {
+                    put(range, Padding);
+                    put(range, Padding);
+                    pcount += 2;
+                }
+                break;
+            }
+        }
+
+        return pcount;
+    }
+
+    ///
+    unittest
+    {
+        struct OutputRange
+        {
+            char[] result;
+            void put(const(char) ch) { result ~= ch; }
+        }
+
+        ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
+
+        // This overload of encode() returns the number of calls to the output
+        // range's put method.
+        OutputRange output;
+        assert(Base64.encode(data, output) == 8);
+        assert(output.result == "Gis8TV1u");
+    }
+
+
+    // InputRange to OutputRange
+
+
+    /**
+     * ditto
+     */
+    size_t encode(R1, R2)(R1 source, auto ref R2 range)
+        if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : ubyte) &&
+            hasLength!R1 && !is(R2 == char[]) && isOutputRange!(R2, char))
+    out(result)
+    {
+        // @@@BUG@@@ Workaround for DbC problem.
+        //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return 0;
+
+        immutable blocks = srcLen / 3;
+        immutable remain = srcLen % 3;
+        size_t    pcount;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = source.front; source.popFront();
+            immutable v2 = source.front; source.popFront();
+            immutable v3 = source.front; source.popFront();
+            immutable val = v1 << 16 | v2 << 8 | v3;
+            put(range, EncodeMap[val >> 18       ]);
+            put(range, EncodeMap[val >> 12 & 0x3f]);
+            put(range, EncodeMap[val >>  6 & 0x3f]);
+            put(range, EncodeMap[val       & 0x3f]);
+            pcount += 4;
+        }
+
+        if (remain)
+        {
+            size_t val = source.front << 16;
+            if (remain == 2)
+            {
+                source.popFront();
+                val |= source.front << 8;
+            }
+
+            put(range, EncodeMap[val >> 18       ]);
+            put(range, EncodeMap[val >> 12 & 0x3f]);
+            pcount += 2;
+
+            final switch (remain)
+            {
+            case 2:
+                put(range, EncodeMap[val >> 6 & 0x3f]);
+                pcount++;
+
+                static if (Padding != NoPadding)
+                {
+                    put(range, Padding);
+                    pcount++;
+                }
+                break;
+            case 1:
+                static if (Padding != NoPadding)
+                {
+                    put(range, Padding);
+                    put(range, Padding);
+                    pcount += 2;
+                }
+                break;
+            }
+        }
+
+        // @@@BUG@@@ Workaround for DbC problem.
+        version (unittest) assert(pcount == encodeLength(srcLen), "The number of put is different from the length of Base64");
+
+        return pcount;
+    }
+
+
+    /**
+     * Encodes $(D_PARAM source) to newly-allocated buffer.
+     *
+     * This convenience method alleviates the need to manually manage output
+     * buffers.
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *           range) to _encode.
+     *
+     * Returns:
+     *  A newly-allocated $(D char[]) buffer containing the encoded string.
+     */
+    @safe
+    pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte))
+    {
+        return encode(source, new char[encodeLength(source.length)]);
+    }
+
+    ///
+    unittest
+    {
+        ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
+        assert(Base64.encode(data) == "Gis8TV1u");
+    }
+
+
+    /**
+     * ditto
+     */
+    char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
+                                           is(ElementType!Range : ubyte) && hasLength!Range)
+    {
+        return encode(source, new char[encodeLength(source.length)]);
+    }
+
+
+    /**
+     * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
+     * iterates over the respective Base64 encodings of a range of data items.
+     *
+     * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
+     * forward range) if the underlying data source is at least a forward
+     * range.
+     *
+     * Note: This struct is not intended to be created in user code directly;
+     * use the $(LREF encoder) function instead.
+     */
+    struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) ||
+                                                     is(ElementType!Range : const(char)[])))
+    {
+      private:
+        Range  range_;
+        char[] buffer_, encoded_;
+
+
+      public:
+        this(Range range)
+        {
+            range_ = range;
+            doEncoding();
+        }
+
+
+        /**
+         * Returns:
+         *  true if there is no more encoded data left.
+         */
+        @property @trusted
+        bool empty()
+        {
+            return range_.empty;
+        }
+
+
+        /**
+         * Returns: The current chunk of encoded data.
+         */
+        @property @safe
+        nothrow char[] front()
+        {
+            return encoded_;
+        }
+
+
+        /**
+         * Advance the range to the next chunk of encoded data.
+         *
+         * Throws:
+         *  $(D Base64Exception) If invoked when
+         *  $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true).
+         */
+        void popFront()
+        {
+            enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
+
+            range_.popFront();
+
+            /*
+             * This check is very ugly. I think this is a Range's flaw.
+             * I very strongly want the Range guideline for unified implementation.
+             *
+             * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding.
+             */
+            if (!empty)
+                doEncoding();
+        }
+
+
+        static if (isForwardRange!Range)
+        {
+            /**
+             * Save the current iteration state of the range.
+             *
+             * This method is only available if the underlying range is a
+             * $(LINK2 std_range_primitives.html#isForwardRange, forward
+             * range).
+             *
+             * Returns:
+             *  A copy of $(D this).
+             */
+            @property
+            typeof(this) save()
+            {
+                typeof(return) encoder;
+
+                encoder.range_   = range_.save;
+                encoder.buffer_  = buffer_.dup;
+                encoder.encoded_ = encoder.buffer_[0..encoded_.length];
+
+                return encoder;
+            }
+        }
+
+
+      private:
+        void doEncoding()
+        {
+            auto data = cast(const(ubyte)[])range_.front;
+            auto size = encodeLength(data.length);
+            if (size > buffer_.length)
+                buffer_.length = size;
+
+            encoded_ = encode(data, buffer_);
+        }
+    }
+
+
+    /**
+     * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
+     * iterates over the encoded bytes of the given source data.
+     *
+     * It will be a $(LINK2 std_range_primitives.html#isForwardRange, forward
+     * range) if the underlying data source is at least a forward range.
+     *
+     * Note: This struct is not intended to be created in user code directly;
+     * use the $(LREF encoder) function instead.
+     */
+    struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte))
+    {
+      private:
+        Range range_;
+        ubyte first;
+        int   pos, padding;
+
+
+      public:
+        this(Range range)
+        {
+            range_ = range;
+            static if (isForwardRange!Range)
+                range_ = range_.save;
+
+            if (range_.empty)
+                pos = -1;
+            else
+                popFront();
+        }
+
+
+        /**
+         * Returns:
+         *  true if there are no more encoded characters to be iterated.
+         */
+        @property @safe
+        nothrow bool empty() const
+        {
+            static if (Padding == NoPadding)
+                return pos < 0;
+            else
+                return pos < 0 && !padding;
+        }
+
+
+        /**
+         * Returns: The current encoded character.
+         */
+        @property @safe
+        nothrow ubyte front()
+        {
+            return first;
+        }
+
+
+        /**
+         * Advance to the next encoded character.
+         *
+         * Throws:
+         *  $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2,
+         *  empty) returns $(D true).
+         */
+        void popFront()
+        {
+            enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining"));
+
+            static if (Padding != NoPadding)
+                if (padding)
+                {
+                    first = Padding;
+                    pos   = -1;
+                    padding--;
+                    return;
+                }
+
+            if (range_.empty)
+            {
+                pos = -1;
+                return;
+            }
+
+            final switch (pos)
+            {
+            case 0:
+                first = EncodeMap[range_.front >> 2];
+                break;
+            case 1:
+                immutable t = (range_.front & 0b11) << 4;
+                range_.popFront();
+
+                if (range_.empty)
+                {
+                    first   = EncodeMap[t];
+                    padding = 3;
+                }
+                else
+                {
+                    first = EncodeMap[t | (range_.front >> 4)];
+                }
+                break;
+            case 2:
+                immutable t = (range_.front & 0b1111) << 2;
+                range_.popFront();
+
+                if (range_.empty)
+                {
+                    first   = EncodeMap[t];
+                    padding = 2;
+                }
+                else
+                {
+                    first = EncodeMap[t | (range_.front >> 6)];
+                }
+                break;
+            case 3:
+                first = EncodeMap[range_.front & 0b111111];
+                range_.popFront();
+                break;
+            }
+
+            ++pos %= 4;
+        }
+
+
+        static if (isForwardRange!Range)
+        {
+            /**
+             * Save the current iteration state of the range.
+             *
+             * This method is only available if the underlying range is a
+             * $(LINK2 std_range_primitives.html#isForwardRange, forward
+             * range).
+             *
+             * Returns:
+             *  A copy of $(D this).
+             */
+            @property
+            typeof(this) save()
+            {
+                auto encoder = this;
+                encoder.range_ = encoder.range_.save;
+                return encoder;
+            }
+        }
+    }
+
+
+    /**
+     * Construct an $(D Encoder) that iterates over the Base64 encoding of the
+     * given $(LINK2 std_range_primitives.html#isInputRange, input range).
+     *
+     * Params:
+     *  range = An $(LINK2 std_range_primitives.html#isInputRange, input
+     *      range) over the data to be encoded.
+     *
+     * Returns:
+     *  If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates
+     *  over the bytes of the corresponding Base64 encoding.
+     *
+     *  If $(D_PARAM range) is a range of ranges of bytes, an $(D Encoder) that
+     *  iterates over the Base64 encoded strings of each element of the range.
+     *
+     *  In both cases, the returned $(D Encoder) will be a
+     *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
+     *  given $(D range) is at least a forward range, otherwise it will be only
+     *  an input range.
+     *
+     * Example:
+     * This example encodes the input one line at a time.
+     * -----
+     * File f = File("text.txt", "r");
+     * scope(exit) f.close();
+     *
+     * uint line = 0;
+     * foreach (encoded; Base64.encoder(f.byLine()))
+     * {
+     *     writeln(++line, ". ", encoded);
+     * }
+     * -----
+     *
+     * Example:
+     * This example encodes the input data one byte at a time.
+     * -----
+     * ubyte[] data = cast(ubyte[]) "0123456789";
+     *
+     * // The ElementType of data is not aggregation type
+     * foreach (encoded; Base64.encoder(data))
+     * {
+     *     writeln(encoded);
+     * }
+     * -----
+     */
+    Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range)
+    {
+        return typeof(return)(range);
+    }
+
+
+    /* Decode functions */
+
+
+    private immutable int[char.max + 1] DecodeMap = [
+        'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100,
+        'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001,
+        'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110,
+        'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011,
+        'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000,
+        'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101,
+        'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010,
+        'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111,
+        'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100,
+        't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001,
+        'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110,
+        '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011,
+        '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1
+    ];
+
+
+    /**
+     * Given a Base64 encoded string, calculates the length of the decoded
+     * string.
+     *
+     * Params:
+     *  sourceLength = The length of the Base64 encoding.
+     *
+     * Returns:
+     *  The length of the decoded string corresponding to a Base64 encoding of
+     *  length $(D_PARAM sourceLength).
+     */
+    @safe
+    pure nothrow size_t decodeLength(in size_t sourceLength)
+    {
+        static if (Padding == NoPadding)
+            return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2);
+        else
+            return (sourceLength / 4) * 3;
+    }
+
+    ///
+    unittest
+    {
+        auto encoded = "Gis8TV1u";
+
+        // Allocate a sufficiently large buffer to hold to decoded result.
+        auto buffer = new ubyte[Base64.decodeLength(encoded.length)];
+
+        Base64.decode(encoded, buffer);
+        assert(buffer == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+    }
+
+
+    // Used in decode contracts. Calculates the actual size the decoded
+    // result should have, taking into account trailing padding.
+    @safe
+    pure nothrow private size_t realDecodeLength(R)(R source)
+    {
+        auto expect = decodeLength(source.length);
+        static if (Padding != NoPadding)
+        {
+            if (source.length % 4 == 0)
+            {
+                expect -= source.length == 0       ? 0 :
+                          source[$ - 2] == Padding ? 2 :
+                          source[$ - 1] == Padding ? 1 : 0;
+            }
+        }
+        return expect;
+    }
+
+
+    // char[] to ubyte[]
+
+
+    /**
+     * Decodes $(D_PARAM source) into the given buffer.
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *      range) to _decode.
+     *  buffer = The buffer to store decoded result.
+     *
+     * Returns:
+     *  The slice of $(D_PARAM buffer) containing the decoded result.
+     *
+     * Throws:
+     *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the
+     *  base alphabet of the current Base64 encoding scheme.
+     */
+    @trusted
+    pure ubyte[] decode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) &&
+                                                             is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
+    in
+    {
+        assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding");
+    }
+    out(result)
+    {
+        immutable expect = realDecodeLength(source);
+        assert(result.length == expect, "The length of result is different from the expected length");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return [];
+        static if (Padding != NoPadding)
+            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
+
+        immutable blocks = srcLen / 4;
+        auto      srcptr = source.ptr;
+        auto      bufptr = buffer.ptr;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = decodeChar(*srcptr++);
+            immutable v2 = decodeChar(*srcptr++);
+
+            *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
+
+            immutable v3 = decodeChar(*srcptr++);
+            if (v3 == -1)
+                break;
+
+            *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
+
+            immutable v4 = decodeChar(*srcptr++);
+            if (v4 == -1)
+                break;
+
+            *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
+        }
+
+        static if (Padding == NoPadding)
+        {
+            immutable remain = srcLen % 4;
+
+            if (remain)
+            {
+                immutable v1 = decodeChar(*srcptr++);
+                immutable v2 = decodeChar(*srcptr++);
+
+                *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
+
+                if (remain == 3)
+                    *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff);
+            }
+        }
+
+        return buffer[0..bufptr - buffer.ptr];
+    }
+
+    ///
+    unittest
+    {
+        auto encoded = "Gis8TV1u";
+        ubyte[32] buffer;   // much bigger than necessary
+
+        // Just to be sure...
+        auto decodedLength = Base64.decodeLength(encoded.length);
+        assert(buffer.length >= decodedLength);
+
+        // decode() returns a slice of the given buffer.
+        auto decoded = Base64.decode(encoded, buffer[]);
+        assert(decoded is buffer[0 .. decodedLength]);
+        assert(decoded == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+    }
+
+    // InputRange to ubyte[]
+
+
+    /**
+     * ditto
+     */
+    ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 &&
+                                                     is(ElementType!R1 : dchar) && hasLength!R1 &&
+                                                     is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
+    in
+    {
+        assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding");
+    }
+    out(result)
+    {
+        // @@@BUG@@@ Workaround for DbC problem.
+        //immutable expect = decodeLength(source.length) - 2;
+        //assert(result.length >= expect, "The length of result is smaller than expected length");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return [];
+        static if (Padding != NoPadding)
+            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
+
+        immutable blocks = srcLen / 4;
+        auto      bufptr = buffer.ptr;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = decodeChar(source.front); source.popFront();
+            immutable v2 = decodeChar(source.front); source.popFront();
+
+            *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
+
+            immutable v3 = decodeChar(source.front);
+            if (v3 == -1)
+                break;
+
+            *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff);
+            source.popFront();
+
+            immutable v4 = decodeChar(source.front);
+            if (v4 == -1)
+                break;
+
+            *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff);
+            source.popFront();
+        }
+
+        static if (Padding == NoPadding)
+        {
+            immutable remain = srcLen % 4;
+
+            if (remain)
+            {
+                immutable v1 = decodeChar(source.front); source.popFront();
+                immutable v2 = decodeChar(source.front);
+
+                *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4);
+
+                if (remain == 3)
+                {
+                    source.popFront();
+                    *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff);
+                }
+            }
+        }
+
+        // @@@BUG@@@ Workaround for DbC problem.
+        version (unittest) assert((bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length");
+
+        return buffer[0..bufptr - buffer.ptr];
+    }
+
+
+    // char[] to OutputRange
+
+
+    /**
+     * Decodes $(D_PARAM source) into a given
+     * $(LINK2 std_range_primitives.html#isOutputRange, output range).
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *           range) to _decode.
+     *  range  = The $(LINK2 std_range_primitives.html#isOutputRange, output
+     *           range) to store the decoded result.
+     *
+     * Returns:
+     *  The number of times the output range's $(D put) method was invoked.
+     *
+     * Throws:
+     *  $(D Base64Exception) if $(D_PARAM source) contains characters outside the
+     *  base alphabet of the current Base64 encoding scheme.
+     */
+    size_t decode(R1, R2)(in R1 source, auto ref R2 range)
+        if (isArray!R1 && is(ElementType!R1 : dchar) &&
+            !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
+    out(result)
+    {
+        immutable expect = realDecodeLength(source);
+        assert(result == expect, "The result of decode is different from the expected");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return 0;
+        static if (Padding != NoPadding)
+            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
+
+        immutable blocks = srcLen / 4;
+        auto      srcptr = source.ptr;
+        size_t    pcount;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = decodeChar(*srcptr++);
+            immutable v2 = decodeChar(*srcptr++);
+
+            put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
+            pcount++;
+
+            immutable v3 = decodeChar(*srcptr++);
+            if (v3 == -1)
+                break;
+
+            put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
+            pcount++;
+
+            immutable v4 = decodeChar(*srcptr++);
+            if (v4 == -1)
+                break;
+
+            put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
+            pcount++;
+        }
+
+        static if (Padding == NoPadding)
+        {
+            immutable remain = srcLen % 4;
+
+            if (remain)
+            {
+                immutable v1 = decodeChar(*srcptr++);
+                immutable v2 = decodeChar(*srcptr++);
+
+                put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
+                pcount++;
+
+                if (remain == 3)
+                {
+                    put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff));
+                    pcount++;
+                }
+            }
+        }
+
+        return pcount;
+    }
+
+    ///
+    unittest
+    {
+        struct OutputRange
+        {
+            ubyte[] result;
+            void put(ubyte b) { result ~= b; }
+        }
+        OutputRange output;
+
+        // This overload of decode() returns the number of calls to put().
+        assert(Base64.decode("Gis8TV1u", output) == 6);
+        assert(output.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+    }
+
+
+    // InputRange to OutputRange
+
+
+    /**
+     * ditto
+     */
+    size_t decode(R1, R2)(R1 source, auto ref R2 range)
+        if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : dchar) &&
+            hasLength!R1 && !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte))
+    out(result)
+    {
+        // @@@BUG@@@ Workaround for DbC problem.
+        //immutable expect = decodeLength(source.length) - 2;
+        //assert(result >= expect, "The length of result is smaller than expected length");
+    }
+    body
+    {
+        immutable srcLen = source.length;
+        if (srcLen == 0)
+            return 0;
+        static if (Padding != NoPadding)
+            enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data"));
+
+        immutable blocks = srcLen / 4;
+        size_t    pcount;
+
+        foreach (Unused; 0..blocks)
+        {
+            immutable v1 = decodeChar(source.front); source.popFront();
+            immutable v2 = decodeChar(source.front); source.popFront();
+
+            put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
+            pcount++;
+
+            immutable v3 = decodeChar(source.front);
+            if (v3 == -1)
+                break;
+
+            put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff));
+            source.popFront();
+            pcount++;
+
+            immutable v4 = decodeChar(source.front);
+            if (v4 == -1)
+                break;
+
+            put(range, cast(ubyte)((v3 << 6 | v4) & 0xff));
+            source.popFront();
+            pcount++;
+        }
+
+        static if (Padding == NoPadding)
+        {
+            immutable remain = srcLen % 4;
+
+            if (remain)
+            {
+                immutable v1 = decodeChar(source.front); source.popFront();
+                immutable v2 = decodeChar(source.front);
+
+                put(range, cast(ubyte)(v1 << 2 | v2 >> 4));
+                pcount++;
+
+                if (remain == 3)
+                {
+                    source.popFront();
+                    put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff));
+                    pcount++;
+                }
+            }
+        }
+
+        // @@@BUG@@@ Workaround for DbC problem.
+        version (unittest) assert(pcount >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length");
+
+        return pcount;
+    }
+
+
+    /**
+     * Decodes $(D_PARAM source) into newly-allocated buffer.
+     *
+     * This convenience method alleviates the need to manually manage decoding
+     * buffers.
+     *
+     * Params:
+     *  source = The $(LINK2 std_range_primitives.html#isInputRange, input
+     *           range) to _decode.
+     *
+     * Returns:
+     *  A newly-allocated $(D ubyte[]) buffer containing the decoded string.
+     */
+    @safe
+    pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar))
+    {
+        return decode(source, new ubyte[decodeLength(source.length)]);
+    }
+
+    ///
+    unittest
+    {
+        auto data = "Gis8TV1u";
+        assert(Base64.decode(data) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+    }
+
+
+    /**
+     * ditto
+     */
+    ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range &&
+                                            is(ElementType!Range : dchar) && hasLength!Range)
+    {
+        return decode(source, new ubyte[decodeLength(source.length)]);
+    }
+
+
+    /**
+     * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
+     * iterates over the decoded data of a range of Base64 encodings.
+     *
+     * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
+     * forward range) if the underlying data source is at least a forward
+     * range.
+     *
+     * Note: This struct is not intended to be created in user code directly;
+     * use the $(LREF decoder) function instead.
+     */
+    struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) ||
+                                                     is(ElementType!Range : const(ubyte)[])))
+    {
+      private:
+        Range   range_;
+        ubyte[] buffer_, decoded_;
+
+
+      public:
+        this(Range range)
+        {
+            range_ = range;
+            doDecoding();
+        }
+
+
+        /**
+         * Returns:
+         *  true if there are no more elements to be iterated.
+         */
+        @property @trusted
+        bool empty()
+        {
+            return range_.empty;
+        }
+
+
+        /**
+         * Returns: The decoding of the current element in the input.
+         */
+        @property @safe
+        nothrow ubyte[] front()
+        {
+            return decoded_;
+        }
+
+
+        /**
+         * Advance to the next element in the input to be decoded.
+         *
+         * Throws:
+         *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
+         *  empty) returns $(D true).
+         */
+        void popFront()
+        {
+            enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining."));
+
+            range_.popFront();
+
+            /*
+             * I mentioned Encoder's popFront.
+             */
+            if (!empty)
+                doDecoding();
+        }
+
+
+        static if (isForwardRange!Range)
+        {
+            /**
+             * Saves the current iteration state.
+             *
+             * This method is only available if the underlying range is a
+             * $(LINK2 std_range_primitives.html#isForwardRange, forward
+             * range).
+             *
+             * Returns: A copy of $(D this).
+             */
+            @property
+            typeof(this) save()
+            {
+                typeof(return) decoder;
+
+                decoder.range_   = range_.save;
+                decoder.buffer_  = buffer_.dup;
+                decoder.decoded_ = decoder.buffer_[0..decoded_.length];
+
+                return decoder;
+            }
+        }
+
+
+      private:
+        void doDecoding()
+        {
+            auto data = cast(const(char)[])range_.front;
+
+            static if (Padding == NoPadding)
+            {
+                while (data.length % 4 == 1)
+                {
+                    range_.popFront();
+                    data ~= cast(const(char)[])range_.front;
+                }
+            }
+            else
+            {
+                while (data.length % 4 != 0)
+                {
+                    range_.popFront();
+                    data ~= cast(const(char)[])range_.front;
+                }
+            }
+
+            auto size = decodeLength(data.length);
+            if (size > buffer_.length)
+                buffer_.length = size;
+
+            decoded_ = decode(data, buffer_);
+        }
+    }
+
+
+    /**
+     * An $(LINK2 std_range_primitives.html#isInputRange, input range) that
+     * iterates over the bytes of data decoded from a Base64 encoded string.
+     *
+     * This range will be a $(LINK2 std_range_primitives.html#isForwardRange,
+     * forward range) if the underlying data source is at least a forward
+     * range.
+     *
+     * Note: This struct is not intended to be created in user code directly;
+     * use the $(LREF decoder) function instead.
+     */
+    struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char))
+    {
+      private:
+        Range range_;
+        ubyte first;
+        int   pos;
+
+
+      public:
+        this(Range range)
+        {
+            range_ = range;
+            static if (isForwardRange!Range)
+                range_ = range_.save;
+
+            static if (Padding != NoPadding && hasLength!Range)
+                enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data"));
+
+            if (range_.empty)
+                pos = -1;
+            else
+                popFront();
+        }
+
+
+        /**
+         * Returns:
+         *  true if there are no more elements to be iterated.
+         */
+        @property @safe
+        nothrow bool empty() const
+        {
+            return pos < 0;
+        }
+
+
+        /**
+         * Returns: The current decoded byte.
+         */
+        @property @safe
+        nothrow ubyte front()
+        {
+            return first;
+        }
+
+
+        /**
+         * Advance to the next decoded byte.
+         *
+         * Throws:
+         *  $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty,
+         *  empty) returns $(D true).
+         */
+        void popFront()
+        {
+            enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining"));
+
+            static if (Padding == NoPadding)
+            {
+                bool endCondition()
+                {
+                    return range_.empty;
+                }
+            }
+            else
+            {
+                bool endCondition()
+                {
+                    enforce(!range_.empty, new Base64Exception("Missing padding"));
+                    return range_.front == Padding;
+                }
+            }
+
+            if (range_.empty || range_.front == Padding)
+            {
+                pos = -1;
+                return;
+            }
+
+            final switch (pos)
+            {
+            case 0:
+                enforce(!endCondition(), new Base64Exception("Premature end of data found"));
+
+                immutable t = DecodeMap[range_.front] << 2;
+                range_.popFront();
+
+                enforce(!endCondition(), new Base64Exception("Premature end of data found"));
+                first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4));
+                break;
+            case 1:
+                immutable t = (DecodeMap[range_.front] & 0b1111) << 4;
+                range_.popFront();
+
+                if (endCondition())
+                {
+                    pos = -1;
+                    return;
+                }
+                else
+                {
+                    first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2));
+                }
+                break;
+            case 2:
+                immutable t = (DecodeMap[range_.front] & 0b11) << 6;
+                range_.popFront();
+
+                if (endCondition())
+                {
+                    pos = -1;
+                    return;
+                }
+                else
+                {
+                    first = cast(ubyte)(t | DecodeMap[range_.front]);
+                }
+
+                range_.popFront();
+                break;
+            }
+
+            ++pos %= 3;
+        }
+
+
+        static if (isForwardRange!Range)
+        {
+            /**
+             * Saves the current iteration state.
+             *
+             * This method is only available if the underlying range is a
+             * $(LINK2 std_range_primitives.html#isForwardRange, forward
+             * range).
+             *
+             * Returns: A copy of $(D this).
+             */
+            @property
+            typeof(this) save()
+            {
+                auto decoder = this;
+                decoder.range_ = decoder.range_.save;
+                return decoder;
+            }
+        }
+    }
+
+
+    /**
+     * Construct a $(D Decoder) that iterates over the decoding of the given
+     * Base64 encoded data.
+     *
+     * Params:
+     *  range = An $(LINK2 std_range_primitives.html#isInputRange, input
+     *      range) over the data to be decoded.
+     *
+     * Returns:
+     *  If $(D_PARAM range) is a range of characters, a $(D Decoder) that
+     *  iterates over the bytes of the corresponding Base64 decoding.
+     *
+     *  If $(D_PARAM range) is a range of ranges of characters, a $(D Decoder)
+     *  that iterates over the decoded strings corresponding to each element of
+     *  the range. In this case, the length of each subrange must be a multiple
+     *  of 4; the returned _decoder does not keep track of Base64 decoding
+     *  state across subrange boundaries.
+     *
+     *  In both cases, the returned $(D Decoder) will be a
+     *  $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the
+     *  given $(D range) is at least a forward range, otherwise it will be only
+     *  an input range.
+     *
+     * If the input data contains characters not found in the base alphabet of
+     * the current Base64 encoding scheme, the returned range may throw a
+     * $(D Base64Exception).
+     *
+     * Example:
+     * This example shows decoding over a range of input data lines.
+     * -----
+     * foreach (decoded; Base64.decoder(stdin.byLine()))
+     * {
+     *     writeln(decoded);
+     * }
+     * -----
+     *
+     * Example:
+     * This example shows decoding one byte at a time.
+     * -----
+     * auto encoded = Base64.encoder(cast(ubyte[])"0123456789");
+     * foreach (n; map!q{a - '0'}(Base64.decoder(encoded)))
+     * {
+     *     writeln(n);
+     * }
+     * -----
+     */
+    Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range)
+    {
+        return typeof(return)(range);
+    }
+
+
+  private:
+    @safe
+    pure int decodeChar()(char chr)
+    {
+        immutable val = DecodeMap[chr];
+
+        // enforce can't be a pure function, so I use trivial check.
+        if (val == 0 && chr != 'A')
+            throw new Base64Exception("Invalid character: " ~ chr);
+
+        return val;
+    }
+
+
+    @safe
+    pure int decodeChar()(dchar chr)
+    {
+        // See above comment.
+        if (chr > 0x7f)
+            throw new Base64Exception("Base64-encoded character must be a single byte");
+
+        return decodeChar(cast(char)chr);
+    }
+}
+
+
+/**
+ * Exception thrown upon encountering Base64 encoding or decoding errors.
+ */
+class Base64Exception : Exception
+{
+    @safe pure nothrow
+    this(string s, string fn = __FILE__, size_t ln = __LINE__)
+    {
+        super(s, fn, ln);
+    }
+}
+
+
+unittest
+{
+    import std.algorithm : sort, equal;
+    import std.conv;
+    import std.file;
+    import std.stdio;
+
+    alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding);
+
+    // Test vectors from RFC 4648
+    ubyte[][string] tv = [
+         ""      :cast(ubyte[])"",
+         "f"     :cast(ubyte[])"f",
+         "fo"    :cast(ubyte[])"fo",
+         "foo"   :cast(ubyte[])"foo",
+         "foob"  :cast(ubyte[])"foob",
+         "fooba" :cast(ubyte[])"fooba",
+         "foobar":cast(ubyte[])"foobar"
+    ];
+
+    { // Base64
+        // encode
+        assert(Base64.encodeLength(tv[""].length)       == 0);
+        assert(Base64.encodeLength(tv["f"].length)      == 4);
+        assert(Base64.encodeLength(tv["fo"].length)     == 4);
+        assert(Base64.encodeLength(tv["foo"].length)    == 4);
+        assert(Base64.encodeLength(tv["foob"].length)   == 8);
+        assert(Base64.encodeLength(tv["fooba"].length)  == 8);
+        assert(Base64.encodeLength(tv["foobar"].length) == 8);
+
+        assert(Base64.encode(tv[""])       == "");
+        assert(Base64.encode(tv["f"])      == "Zg==");
+        assert(Base64.encode(tv["fo"])     == "Zm8=");
+        assert(Base64.encode(tv["foo"])    == "Zm9v");
+        assert(Base64.encode(tv["foob"])   == "Zm9vYg==");
+        assert(Base64.encode(tv["fooba"])  == "Zm9vYmE=");
+        assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy");
+
+        // decode
+        assert(Base64.decodeLength(Base64.encode(tv[""]).length)       == 0);
+        assert(Base64.decodeLength(Base64.encode(tv["f"]).length)      == 3);
+        assert(Base64.decodeLength(Base64.encode(tv["fo"]).length)     == 3);
+        assert(Base64.decodeLength(Base64.encode(tv["foo"]).length)    == 3);
+        assert(Base64.decodeLength(Base64.encode(tv["foob"]).length)   == 6);
+        assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length)  == 6);
+        assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6);
+
+        assert(Base64.decode(Base64.encode(tv[""]))       == tv[""]);
+        assert(Base64.decode(Base64.encode(tv["f"]))      == tv["f"]);
+        assert(Base64.decode(Base64.encode(tv["fo"]))     == tv["fo"]);
+        assert(Base64.decode(Base64.encode(tv["foo"]))    == tv["foo"]);
+        assert(Base64.decode(Base64.encode(tv["foob"]))   == tv["foob"]);
+        assert(Base64.decode(Base64.encode(tv["fooba"]))  == tv["fooba"]);
+        assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]);
+
+        assertThrown!Base64Exception(Base64.decode("ab|c"));
+
+        // Test decoding incomplete strings. RFC does not specify the correct
+        // behavior, but the code should never throw Errors on invalid input.
+
+        // decodeLength is nothrow
+        assert(Base64.decodeLength(1) == 0);
+        assert(Base64.decodeLength(2) <= 1);
+        assert(Base64.decodeLength(3) <= 2);
+
+        // may throw Exceptions, may not throw Errors
+        assertThrown!Base64Exception(Base64.decode("Zg"));
+        assertThrown!Base64Exception(Base64.decode("Zg="));
+        assertThrown!Base64Exception(Base64.decode("Zm8"));
+        assertThrown!Base64Exception(Base64.decode("Zg==;"));
+    }
+
+    { // No padding
+        // encode
+        assert(Base64Re.encodeLength(tv[""].length)       == 0);
+        assert(Base64Re.encodeLength(tv["f"].length)      == 2);
+        assert(Base64Re.encodeLength(tv["fo"].length)     == 3);
+        assert(Base64Re.encodeLength(tv["foo"].length)    == 4);
+        assert(Base64Re.encodeLength(tv["foob"].length)   == 6);
+        assert(Base64Re.encodeLength(tv["fooba"].length)  == 7);
+        assert(Base64Re.encodeLength(tv["foobar"].length) == 8);
+
+        assert(Base64Re.encode(tv[""])       == "");
+        assert(Base64Re.encode(tv["f"])      == "Zg");
+        assert(Base64Re.encode(tv["fo"])     == "Zm8");
+        assert(Base64Re.encode(tv["foo"])    == "Zm9v");
+        assert(Base64Re.encode(tv["foob"])   == "Zm9vYg");
+        assert(Base64Re.encode(tv["fooba"])  == "Zm9vYmE");
+        assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy");
+
+        // decode
+        assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length)       == 0);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length)      == 1);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length)     == 2);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length)    == 3);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length)   == 4);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length)  == 5);
+        assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6);
+
+        assert(Base64Re.decode(Base64Re.encode(tv[""]))       == tv[""]);
+        assert(Base64Re.decode(Base64Re.encode(tv["f"]))      == tv["f"]);
+        assert(Base64Re.decode(Base64Re.encode(tv["fo"]))     == tv["fo"]);
+        assert(Base64Re.decode(Base64Re.encode(tv["foo"]))    == tv["foo"]);
+        assert(Base64Re.decode(Base64Re.encode(tv["foob"]))   == tv["foob"]);
+        assert(Base64Re.decode(Base64Re.encode(tv["fooba"]))  == tv["fooba"]);
+        assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]);
+
+        // decodeLength is nothrow
+        assert(Base64.decodeLength(1) == 0);
+    }
+
+    { // with OutputRange
+        import std.array;
+
+        auto a = Appender!(char[])([]);
+        auto b = Appender!(ubyte[])([]);
+
+        assert(Base64.encode(tv[""], a) == 0);
+        assert(Base64.decode(a.data, b) == 0);
+        assert(tv[""] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["f"], a) == 4);
+        assert(Base64.decode(a.data,  b) == 1);
+        assert(tv["f"] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["fo"], a) == 4);
+        assert(Base64.decode(a.data,   b) == 2);
+        assert(tv["fo"] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["foo"], a) == 4);
+        assert(Base64.decode(a.data,    b) == 3);
+        assert(tv["foo"] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["foob"], a) == 8);
+        assert(Base64.decode(a.data,     b) == 4);
+        assert(tv["foob"] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["fooba"], a) == 8);
+        assert(Base64.decode(a.data, b)      == 5);
+        assert(tv["fooba"] == b.data); a.clear(); b.clear();
+
+        assert(Base64.encode(tv["foobar"], a) == 8);
+        assert(Base64.decode(a.data, b)       == 6);
+        assert(tv["foobar"] == b.data); a.clear(); b.clear();
+    }
+
+    // @@@9543@@@ These tests were disabled because they actually relied on the input range having length.
+    // The implementation (currently) doesn't support encoding/decoding from a length-less source.
+    version(none)
+    { // with InputRange
+        // InputRange to ubyte[] or char[]
+        auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]));
+        assert(encoded == "FPucA9l+");
+        assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
+
+        // InputRange to OutputRange
+        auto a = Appender!(char[])([]);
+        auto b = Appender!(ubyte[])([]);
+        assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8);
+        assert(a.data == "FPucA9l+");
+        assert(Base64.decode(map!q{a}(a.data), b) == 6);
+        assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]);
+    }
+
+    { // Encoder and Decoder
+        {
+            string encode_file = std.file.deleteme ~ "-testingEncoder";
+            std.file.write(encode_file, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar");
+
+            auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
+            auto f = File(encode_file);
+            scope(exit)
+            {
+                f.close();
+                assert(!f.isOpen);
+                std.file.remove(encode_file);
+            }
+
+            size_t i;
+            foreach (encoded; Base64.encoder(f.byLine()))
+                assert(encoded == witness[i++]);
+
+            assert(i == witness.length);
+        }
+
+        {
+            string decode_file = std.file.deleteme ~ "-testingDecoder";
+            std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy");
+
+            auto witness = sort(tv.keys);
+            auto f = File(decode_file);
+            scope(exit)
+            {
+                f.close();
+                assert(!f.isOpen);
+                std.file.remove(decode_file);
+            }
+
+            size_t i;
+            foreach (decoded; Base64.decoder(f.byLine()))
+                assert(decoded == witness[i++]);
+
+            assert(i == witness.length);
+        }
+
+        { // ForwardRange
+            {
+                auto encoder = Base64.encoder(sort(tv.values));
+                auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"];
+                size_t i;
+
+                assert(encoder.front == witness[i++]); encoder.popFront();
+                assert(encoder.front == witness[i++]); encoder.popFront();
+                assert(encoder.front == witness[i++]); encoder.popFront();
+
+                foreach (encoded; encoder.save)
+                    assert(encoded == witness[i++]);
+            }
+
+            {
+                auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]);
+                auto witness = sort(tv.values);
+                size_t i;
+
+                assert(decoder.front == witness[i++]); decoder.popFront();
+                assert(decoder.front == witness[i++]); decoder.popFront();
+                assert(decoder.front == witness[i++]); decoder.popFront();
+
+                foreach (decoded; decoder.save)
+                    assert(decoded == witness[i++]);
+            }
+        }
+    }
+
+    { // Encoder and Decoder for single character encoding and decoding
+        alias Base64NoPadding = Base64Impl!('+', '/', Base64.NoPadding);
+
+        auto tests = [
+            ""       : ["", "", "", ""],
+            "f"      : ["Zg==", "Zg==", "Zg", "Zg"],
+            "fo"     : ["Zm8=", "Zm8=", "Zm8", "Zm8"],
+            "foo"    : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"],
+            "foob"   : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"],
+            "fooba"  : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"],
+            "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"],
+        ];
+
+        foreach (u, e; tests)
+        {
+            assert(equal(Base64.encoder(cast(ubyte[])u), e[0]));
+            assert(equal(Base64.decoder(Base64.encoder(cast(ubyte[])u)), u));
+
+            assert(equal(Base64URL.encoder(cast(ubyte[])u), e[1]));
+            assert(equal(Base64URL.decoder(Base64URL.encoder(cast(ubyte[])u)), u));
+
+            assert(equal(Base64NoPadding.encoder(cast(ubyte[])u), e[2]));
+            assert(equal(Base64NoPadding.decoder(Base64NoPadding.encoder(cast(ubyte[])u)), u));
+
+            assert(equal(Base64Re.encoder(cast(ubyte[])u), e[3]));
+            assert(equal(Base64Re.decoder(Base64Re.encoder(cast(ubyte[])u)), u));
+        }
+    }
+}
+
+// Regression control for the output range ref bug in encode.
+unittest
+{
+    struct InputRange
+    {
+        ubyte[] impl = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e];
+        @property bool empty() { return impl.length == 0; }
+        @property ubyte front() { return impl[0]; }
+        void popFront() { impl = impl[1 .. $]; }
+        @property size_t length() { return impl.length; }
+    }
+
+    struct OutputRange
+    {
+        char[] result;
+        void put(char b) { result ~= b; }
+    }
+
+    InputRange ir;
+    OutputRange or;
+    assert(Base64.encode(ir, or) == 8);
+    assert(or.result == "Gis8TV1u");
+
+    // Verify that any existing workaround that uses & still works.
+    InputRange ir2;
+    OutputRange or2;
+    assert(Base64.encode(ir2, &or2) == 8);
+    assert(or2.result == "Gis8TV1u");
+}
+
+// Regression control for the output range ref bug in decode.
+unittest
+{
+    struct InputRange
+    {
+        const(char)[] impl = "Gis8TV1u";
+        @property bool empty() { return impl.length == 0; }
+        @property dchar front() { return impl[0]; }
+        void popFront() { impl = impl[1 .. $]; }
+        @property size_t length() { return impl.length; }
+    }
+
+    struct OutputRange
+    {
+        ubyte[] result;
+        void put(ubyte b) { result ~= b; }
+    }
+
+    InputRange ir;
+    OutputRange or;
+    assert(Base64.decode(ir, or) == 6);
+    assert(or.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+
+    // Verify that any existing workaround that uses & still works.
+    InputRange ir2;
+    OutputRange or2;
+    assert(Base64.decode(ir2, &or2) == 6);
+    assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]);
+}
diff --git a/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d
new file mode 100644
index 00000000000..863b150b5fa
--- /dev/null
+++ b/libphobos/src/std/bigint.d
@@ -0,0 +1,1596 @@
+/** Arbitrary-precision ('bignum') arithmetic.
+ *
+ * Performance is optimized for numbers below ~1000 decimal digits.
+ * For X86 machines, highly optimised assembly routines are used.
+ *
+ * The following algorithms are currently implemented:
+ * $(UL
+ * $(LI Karatsuba multiplication)
+ * $(LI Squaring is optimized independently of multiplication)
+ * $(LI Divide-and-conquer division)
+ * $(LI Binary exponentiation)
+ * )
+ *
+ * For very large numbers, consider using the $(WEB gmplib.org, GMP library) instead.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Don Clugston
+ * Source: $(PHOBOSSRC std/_bigint.d)
+ */
+/*          Copyright Don Clugston 2008 - 2010.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+module std.bigint;
+
+import std.conv : ConvException;
+
+private import std.internal.math.biguintcore;
+private import std.format : FormatSpec, FormatException;
+private import std.traits;
+
+/** A struct representing an arbitrary precision integer.
+ *
+ * All arithmetic operations are supported, except unsigned shift right (>>>).
+ * Bitwise operations (|, &, ^, ~) are supported, and behave as if BigInt was
+ * an infinite length 2's complement number.
+ *
+ * BigInt implements value semantics using copy-on-write. This means that
+ * assignment is cheap, but operations such as x++ will cause heap
+ * allocation. (But note that for most bigint operations, heap allocation is
+ * inevitable anyway.)
+ */
+struct BigInt
+{
+private:
+    BigUint data;     // BigInt adds signed arithmetic to BigUint.
+    bool sign = false;
+public:
+    /**
+     * Construct a BigInt from a decimal or hexadecimal string.
+     *
+     * The number must be in the form of a D decimal or hex literal. It may
+     * have a leading + or - sign; followed by "0x" if hexadecimal.
+     * Underscores are permitted.  An empty string is treated as "0".
+     *
+     * Throws: ConvException if invalid character found, or string is empty.
+     */
+    this(T : const(char)[])(T s, string file = __FILE__, size_t line = __LINE__)
+        pure
+    {
+        if (s.length == 0)
+            throw new ConvException("Can't initialize BigInt with "~
+                                    "empty string", file, line);
+        bool neg = false;
+        if (s[0] == '-') {
+            neg = true;
+            s = s[1..$];
+        } else if (s[0] == '+') {
+            s = s[1..$];
+        }
+        data = 0UL;
+        bool ok;
+        assert(isZero());
+        if (s.length > 2 && (s[0..2] == "0x" || s[0..2] == "0X"))
+        {
+            ok = data.fromHexString(s[2..$]);
+        } else {
+            ok = data.fromDecimalString(s);
+        }
+        if (!ok)
+            throw new ConvException("Invalid digit string", file, line);
+
+        if (isZero())
+            neg = false;
+        sign = neg;
+    }
+
+    /// Construct a BigInt from a built-in integral type.
+    this(T)(T x) pure nothrow if (isIntegral!T)
+    {
+        data = data.init; // @@@: Workaround for compiler bug
+        opAssign(x);
+    }
+
+    ///
+    unittest
+    {
+        ulong data = 1_000_000_000_000;
+        auto bigData = BigInt(data);
+        assert(data == BigInt("1_000_000_000_000"));
+    }
+
+    /// Construct a BigInt from another BigInt.
+    this(T)(T x) pure nothrow if (is(Unqual!T == BigInt))
+    {
+        opAssign(x);
+    }
+
+    ///
+    unittest
+    {
+        const(BigInt) b1 = BigInt("1_234_567_890");
+        BigInt b2 = BigInt(b1);
+        assert(b2 == BigInt("1_234_567_890"));
+    }
+
+    /// Assignment from built-in integer types.
+    BigInt opAssign(T)(T x) pure nothrow if (isIntegral!T)
+    {
+        data = cast(ulong)absUnsign(x);
+        sign = (x < 0);
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto b = BigInt("123");
+        b = 456;
+        assert(b == BigInt("456"));
+    }
+
+    /// Assignment from another BigInt.
+    BigInt opAssign(T:BigInt)(T x) pure @nogc
+    {
+        data = x.data;
+        sign = x.sign;
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto b1 = BigInt("123");
+        auto b2 = BigInt("456");
+        b2 = b1;
+        assert(b2 == BigInt("123"));
+    }
+
+    /**
+     * Implements assignment operators from built-in integers of the form
+     * $(D BigInt op= integer).
+     */
+    BigInt opOpAssign(string op, T)(T y) pure nothrow
+        if ((op=="+" || op=="-" || op=="*" || op=="/" || op=="%"
+          || op==">>" || op=="<<" || op=="^^" || op=="|" || op=="&" || op=="^") && isIntegral!T)
+    {
+        ulong u = absUnsign(y);
+
+        static if (op=="+")
+        {
+            data = BigUint.addOrSubInt(data, u, sign != (y<0), sign);
+        }
+        else static if (op=="-")
+        {
+            data = BigUint.addOrSubInt(data, u, sign == (y<0), sign);
+        }
+        else static if (op=="*")
+        {
+            if (y == 0) {
+                sign = false;
+                data = 0UL;
+            } else {
+                sign = ( sign != (y<0) );
+                data = BigUint.mulInt(data, u);
+            }
+        }
+        else static if (op=="/")
+        {
+            assert(y!=0, "Division by zero");
+            static if (T.sizeof <= uint.sizeof)
+            {
+                data = BigUint.divInt(data, cast(uint)u);
+            }
+            else
+            {
+                data = BigUint.divInt(data, u);
+            }
+            sign = data.isZero() ? false : sign ^ (y < 0);
+        }
+        else static if (op=="%")
+        {
+            assert(y!=0, "Division by zero");
+            static if (is(immutable(T) == immutable(long)) || is( immutable(T) == immutable(ulong) ))
+            {
+                this %= BigInt(y);
+            }
+            else
+            {
+                data = cast(ulong)BigUint.modInt(data, cast(uint)u);
+                if (data.isZero())
+                    sign = false;
+            }
+            // x%y always has the same sign as x.
+            // This is not the same as mathematical mod.
+        }
+        else static if (op==">>" || op=="<<")
+        {
+            // Do a left shift if y>0 and <<, or
+            // if y<0 and >>; else do a right shift.
+            if (y == 0)
+                return this;
+            else if ((y > 0) == (op=="<<"))
+            {
+                // Sign never changes during left shift
+                data = data.opShl(u);
+            } else
+            {
+                data = data.opShr(u);
+                if (data.isZero())
+                    sign = false;
+            }
+        }
+        else static if (op=="^^")
+        {
+            sign = (y & 1) ? sign : false;
+            data = BigUint.pow(data, u);
+        }
+        else static if (op=="|" || op=="&" || op=="^")
+        {
+            BigInt b = y;
+            opOpAssign!op(b);
+        }
+        else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~ T.stringof ~ " is not supported");
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto b = BigInt("1_000_000_000");
+
+        b += 12345;
+        assert(b == BigInt("1_000_012_345"));
+
+        b /= 5;
+        assert(b == BigInt("200_002_469"));
+    }
+
+    /**
+     * Implements assignment operators of the form $(D BigInt op= BigInt).
+     */
+    BigInt opOpAssign(string op, T)(T y) pure nothrow
+        if ((op=="+" || op== "-" || op=="*" || op=="|" || op=="&" || op=="^" || op=="/" || op=="%")
+            && is (T: BigInt))
+    {
+        static if (op == "+")
+        {
+            data = BigUint.addOrSub(data, y.data, sign != y.sign, &sign);
+        }
+        else static if (op == "-")
+        {
+            data = BigUint.addOrSub(data, y.data, sign == y.sign, &sign);
+        }
+        else static if (op == "*")
+        {
+            data = BigUint.mul(data, y.data);
+            sign = isZero() ? false : sign ^ y.sign;
+        }
+        else static if (op == "/")
+        {
+            y.checkDivByZero();
+            if (!isZero())
+            {
+                data = BigUint.div(data, y.data);
+                sign = isZero() ? false : sign ^ y.sign;
+            }
+        }
+        else static if (op == "%")
+        {
+            y.checkDivByZero();
+            if (!isZero())
+            {
+                data = BigUint.mod(data, y.data);
+                // x%y always has the same sign as x.
+                if (isZero())
+                    sign = false;
+            }
+        }
+        else static if (op == "|" || op == "&" || op == "^")
+        {
+            data = BigUint.bitwiseOp!op(data, y.data, sign, y.sign, sign);
+        }
+        else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~
+            T.stringof ~ " is not supported");
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("123");
+        auto y = BigInt("321");
+        x += y;
+        assert(x == BigInt("444"));
+    }
+
+    /**
+     * Implements binary operators between BigInts.
+     */
+    BigInt opBinary(string op, T)(T y) pure nothrow const
+        if ((op=="+" || op == "*" || op=="-" || op=="|" || op=="&" || op=="^" ||
+            op=="/" || op=="%")
+            && is (T: BigInt))
+    {
+        BigInt r = this;
+        return r.opOpAssign!(op)(y);
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("123");
+        auto y = BigInt("456");
+        BigInt z = x * y;
+        assert(z == BigInt("56088"));
+    }
+
+    /**
+     * Implements binary operators between BigInt's and built-in integers.
+     */
+    BigInt opBinary(string op, T)(T y) pure nothrow const
+        if ((op=="+" || op == "*" || op=="-" || op=="/" || op=="|" || op=="&" ||
+            op=="^"|| op==">>" || op=="<<" || op=="^^")
+            && isIntegral!T)
+    {
+        BigInt r = this;
+        return r.opOpAssign!(op)(y);
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("123");
+        x *= 300;
+        assert(x == BigInt("36900"));
+    }
+
+    /**
+        Implements a narrowing remainder operation with built-in integer types.
+
+        This binary operator returns a narrower, built-in integer type
+        where applicable, according to the following table.
+
+        $(TABLE ,
+        $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD `long`) $(TD $(RARR)) $(TD `long`))
+        $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD `ulong`) $(TD $(RARR)) $(TD `BigInt`))
+        $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD other type) $(TD $(RARR)) $(TD `int`))
+        )
+     */
+    auto opBinary(string op, T)(T y) pure nothrow const
+        if (op == "%" && isIntegral!T)
+    {
+        assert(y!=0);
+
+        // BigInt % long => long
+        // BigInt % ulong => BigInt
+        // BigInt % other_type => int
+        static if (is(Unqual!T == long) || is(Unqual!T == ulong))
+        {
+            auto r = this % BigInt(y);
+
+            static if (is(Unqual!T == long))
+            {
+                return r.toLong();
+            }
+            else
+            {
+                // return as-is to avoid overflow
+                return r;
+            }
+        }
+        else
+        {
+            uint u = absUnsign(y);
+            int rem = BigUint.modInt(data, u);
+            // x%y always has the same sign as x.
+            // This is not the same as mathematical mod.
+            return sign ? -rem : rem;
+        }
+    }
+
+    ///
+    unittest
+    {
+        auto  x  = BigInt("1_000_000_500");
+        long  l  = 1_000_000L;
+        ulong ul = 2_000_000UL;
+        int   i  = 500_000;
+        short s  = 30_000;
+
+        assert(is(typeof(x % l)  == long)   && x % l  == 500L);
+        assert(is(typeof(x % ul) == BigInt) && x % ul == BigInt(500));
+        assert(is(typeof(x % i)  == int)    && x % i  == 500);
+        assert(is(typeof(x % s)  == int)    && x % s  == 10500);
+    }
+
+    /**
+        Implements operators with built-in integers on the left-hand side and
+        BigInt on the right-hand side.
+     */
+    BigInt opBinaryRight(string op, T)(T y) pure nothrow const
+        if ((op=="+" || op=="*" || op=="|" || op=="&" || op=="^") && isIntegral!T)
+    {
+        return opBinary!(op)(y);
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("100");
+        BigInt y = 123 + x;
+        assert(y == BigInt("223"));
+
+        BigInt z = 123 - x;
+        assert(z == BigInt("23"));
+
+        // Dividing a built-in integer type by BigInt always results in
+        // something that fits in a built-in type, so the built-in type is
+        // returned, not BigInt.
+        assert(is(typeof(1000 / x) == int));
+        assert(1000 / x == 10);
+    }
+
+    //  BigInt = integer op BigInt
+    /// ditto
+    BigInt opBinaryRight(string op, T)(T y) pure nothrow const
+        if (op == "-" && isIntegral!T)
+    {
+        ulong u = absUnsign(y);
+        BigInt r;
+        static if (op == "-")
+        {
+            r.sign = sign;
+            r.data = BigUint.addOrSubInt(data, u, sign == (y<0), r.sign);
+            r.negate();
+        }
+        return r;
+    }
+
+    //  integer = integer op BigInt
+    /// ditto
+    T opBinaryRight(string op, T)(T x) pure nothrow const
+        if ((op=="%" || op=="/") && isIntegral!T)
+    {
+        checkDivByZero();
+
+        static if (op == "%")
+        {
+            // x%y always has the same sign as x.
+            if (data.ulongLength > 1)
+                return x;
+            ulong u = absUnsign(x);
+            ulong rem = u % data.peekUlong(0);
+            // x%y always has the same sign as x.
+            return cast(T)((x<0) ? -rem : rem);
+        }
+        else static if (op == "/")
+        {
+            if (data.ulongLength > 1)
+                return 0;
+            return cast(T)(x / data.peekUlong(0));
+        }
+    }
+
+    // const unary operations
+    /**
+        Implements BigInt unary operators.
+     */
+    BigInt opUnary(string op)() pure nothrow const if (op=="+" || op=="-" || op=="~")
+    {
+       static if (op=="-")
+       {
+            BigInt r = this;
+            r.negate();
+            return r;
+        }
+        else static if (op=="~")
+        {
+            return -(this+1);
+        }
+        else static if (op=="+")
+           return this;
+    }
+
+    // non-const unary operations
+    /// ditto
+    BigInt opUnary(string op)() pure nothrow if (op=="++" || op=="--")
+    {
+        static if (op=="++")
+        {
+            data = BigUint.addOrSubInt(data, 1UL, sign, sign);
+            return this;
+        }
+        else static if (op=="--")
+        {
+            data = BigUint.addOrSubInt(data, 1UL, !sign, sign);
+            return this;
+        }
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("1234");
+        assert(-x == BigInt("-1234"));
+
+        ++x;
+        assert(x == BigInt("1235"));
+    }
+
+    /**
+        Implements BigInt equality test with other BigInt's and built-in
+        integer types.
+     */
+    bool opEquals()(auto ref const BigInt y) const pure @nogc
+    {
+       return sign == y.sign && y.data == data;
+    }
+
+    /// ditto
+    bool opEquals(T)(T y) const pure nothrow @nogc if (isIntegral!T)
+    {
+        if (sign != (y<0))
+            return 0;
+        return data.opEquals(cast(ulong)absUnsign(y));
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("12345");
+        auto y = BigInt("12340");
+        int z = 12345;
+        int w = 54321;
+
+        assert(x == x);
+        assert(x != y);
+        assert(x == y + 5);
+        assert(x == z);
+        assert(x != w);
+    }
+
+    /**
+        Implements casting to bool.
+     */
+    T opCast(T:bool)() pure nothrow @nogc const
+    {
+        return !isZero();
+    }
+
+    ///
+    unittest
+    {
+        // Non-zero values are regarded as true
+        auto x = BigInt("1");
+        auto y = BigInt("10");
+        assert(x);
+        assert(y);
+
+        // Zero value is regarded as false
+        auto z = BigInt("0");
+        assert(!z);
+    }
+
+    /**
+        Implements casting to integer types.
+
+        Throws: $(XREF conv,ConvOverflowException) if the number exceeds
+        the target type's range.
+     */
+    T opCast(T:ulong)() /*pure*/ const
+    {
+        if (isUnsigned!T && sign)
+            { /* throw */ }
+        else
+        if (data.ulongLength == 1)
+        {
+            ulong l = data.peekUlong(0);
+            if (isUnsigned!T || !sign)
+            {
+                if (l <= T.max)
+                    return cast(T)l;
+            }
+            else
+            {
+                if (l <= ulong(T.max)+1)
+                    return cast(T)-long(l); // -long.min==long.min
+            }
+        }
+
+        import std.conv : ConvOverflowException;
+        import std.string : format;
+        throw new ConvOverflowException(
+            "BigInt(%d) cannot be represented as a %s"
+            .format(this, T.stringof));
+    }
+
+    ///
+    unittest
+    {
+        import std.conv : to, ConvOverflowException;
+        import std.exception : assertThrown;
+
+        assert(BigInt("0").to!int == 0);
+
+        assert(BigInt("0").to!ubyte == 0);
+        assert(BigInt("255").to!ubyte == 255);
+        assertThrown!ConvOverflowException(BigInt("256").to!ubyte);
+        assertThrown!ConvOverflowException(BigInt("-1").to!ubyte);
+    }
+
+    unittest
+    {
+        import std.conv : to, ConvOverflowException;
+        import std.exception : assertThrown;
+
+        assert(BigInt("-1").to!byte == -1);
+        assert(BigInt("-128").to!byte == -128);
+        assert(BigInt("127").to!byte == 127);
+        assertThrown!ConvOverflowException(BigInt("-129").to!byte);
+        assertThrown!ConvOverflowException(BigInt("128").to!byte);
+
+        assert(BigInt("0").to!uint == 0);
+        assert(BigInt("4294967295").to!uint == uint.max);
+        assertThrown!ConvOverflowException(BigInt("4294967296").to!uint);
+        assertThrown!ConvOverflowException(BigInt("-1").to!uint);
+
+        assert(BigInt("-1").to!int == -1);
+        assert(BigInt("-2147483648").to!int == int.min);
+        assert(BigInt("2147483647").to!int == int.max);
+        assertThrown!ConvOverflowException(BigInt("-2147483649").to!int);
+        assertThrown!ConvOverflowException(BigInt("2147483648").to!int);
+
+        assert(BigInt("0").to!ulong == 0);
+        assert(BigInt("18446744073709551615").to!ulong == ulong.max);
+        assertThrown!ConvOverflowException(BigInt("18446744073709551616").to!ulong);
+        assertThrown!ConvOverflowException(BigInt("-1").to!ulong);
+
+        assert(BigInt("-1").to!long == -1);
+        assert(BigInt("-9223372036854775808").to!long == long.min);
+        assert(BigInt("9223372036854775807").to!long == long.max);
+        assertThrown!ConvOverflowException(BigInt("-9223372036854775809").to!long);
+        assertThrown!ConvOverflowException(BigInt("9223372036854775808").to!long);
+    }
+
+    /**
+        Implements casting to/from qualified BigInt's.
+
+        Warning: Casting to/from $(D const) or $(D immutable) may break type
+        system guarantees. Use with care.
+     */
+    T opCast(T)() pure nothrow @nogc const if (is(Unqual!T == BigInt)) {
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        const(BigInt) x = BigInt("123");
+        BigInt y = cast() x;    // cast away const
+        assert(y == x);
+    }
+
+    // Hack to make BigInt's typeinfo.compare work properly.
+    // Note that this must appear before the other opCmp overloads, otherwise
+    // DMD won't find it.
+    /**
+        Implements 3-way comparisons of BigInt with BigInt or BigInt with
+        built-in integers.
+     */
+    int opCmp(ref const BigInt y) pure nothrow @nogc const
+    {
+        // Simply redirect to the "real" opCmp implementation.
+        return this.opCmp!BigInt(y);
+    }
+
+    /// ditto
+    int opCmp(T)(T y) pure nothrow @nogc const if (isIntegral!T)
+    {
+        if (sign != (y<0) )
+            return sign ? -1 : 1;
+        int cmp = data.opCmp(cast(ulong)absUnsign(y));
+        return sign? -cmp: cmp;
+    }
+    /// ditto
+    int opCmp(T:BigInt)(const T y) pure nothrow @nogc const
+    {
+        if (sign!=y.sign)
+            return sign ? -1 : 1;
+        int cmp = data.opCmp(y.data);
+        return sign? -cmp: cmp;
+    }
+
+    ///
+    unittest
+    {
+        auto x = BigInt("100");
+        auto y = BigInt("10");
+        int z = 50;
+        const int w = 200;
+
+        assert(y < x);
+        assert(x > z);
+        assert(z > y);
+        assert(x < w);
+    }
+
+    /**
+        Returns: The value of this BigInt as a long, or +/- long.max if outside
+        the representable range.
+     */
+    long toLong() @safe pure nothrow const @nogc
+    {
+        return (sign ? -1 : 1) *
+          (data.ulongLength == 1  && (data.peekUlong(0) <= sign+cast(ulong)(long.max)) // 1+long.max = |long.min|
+          ? cast(long)(data.peekUlong(0))
+          : long.max);
+    }
+
+    ///
+    unittest
+    {
+        auto b = BigInt("12345");
+        long l = b.toLong();
+        assert(l == 12345);
+    }
+
+    /**
+        Returns: The value of this BigInt as an int, or +/- int.max if outside
+        the representable range.
+     */
+    int toInt() @safe pure nothrow @nogc const
+    {
+        return (sign ? -1 : 1) *
+          (data.uintLength == 1  && (data.peekUint(0) <= sign+cast(uint)(int.max)) // 1+int.max = |int.min|
+          ? cast(int)(data.peekUint(0))
+          : int.max);
+    }
+
+    ///
+    unittest
+    {
+        auto big = BigInt("5_000_000");
+        auto i = big.toInt();
+        assert(i == 5_000_000);
+
+        // Numbers that are too big to fit into an int will be clamped to int.max.
+        auto tooBig = BigInt("5_000_000_000");
+        i = tooBig.toInt();
+        assert(i == int.max);
+    }
+
+    /// Number of significant uints which are used in storing this number.
+    /// The absolute value of this BigInt is always &lt; 2$(SUPERSCRIPT 32*uintLength)
+    @property size_t uintLength() @safe pure nothrow @nogc const
+    {
+        return data.uintLength;
+    }
+
+    /// Number of significant ulongs which are used in storing this number.
+    /// The absolute value of this BigInt is always &lt; 2$(SUPERSCRIPT 64*ulongLength)
+    @property size_t ulongLength() @safe pure nothrow @nogc const
+    {
+        return data.ulongLength;
+    }
+
+    /** Convert the BigInt to string, passing it to the given sink.
+     *
+     * Params:
+     *  sink = A delegate for accepting possibly piecewise segments of the
+     *      formatted string.
+     *  formatString = A format string specifying the output format.
+     *
+     * $(TABLE  Available output formats:,
+     * $(TR $(TD "d") $(TD  Decimal))
+     * $(TR $(TD "x") $(TD  Hexadecimal, lower case))
+     * $(TR $(TD "X") $(TD  Hexadecimal, upper case))
+     * $(TR $(TD "s") $(TD  Default formatting (same as "d") ))
+     * $(TR $(TD null) $(TD Default formatting (same as "d") ))
+     * )
+     */
+    void toString(scope void delegate(const (char)[]) sink, string formatString) const
+    {
+        auto f = FormatSpec!char(formatString);
+        f.writeUpToNextSpec(sink);
+        toString(sink, f);
+    }
+
+    /// ditto
+    void toString(scope void delegate(const(char)[]) sink, ref FormatSpec!char f) const
+    {
+        auto hex = (f.spec == 'x' || f.spec == 'X');
+        if (!(f.spec == 's' || f.spec == 'd' || hex))
+            throw new FormatException("Format specifier not understood: %" ~ f.spec);
+
+        char[] buff;
+        if (f.spec == 'X')
+        {
+            buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.upper);
+        }
+        else if (f.spec == 'x')
+        {
+            buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.lower);
+        }
+        else
+        {
+            buff = data.toDecimalString(0);
+        }
+        assert(buff.length > 0);
+
+        char signChar = isNegative() ? '-' : 0;
+        auto minw = buff.length + (signChar ? 1 : 0);
+
+        if (!hex && !signChar && (f.width == 0 || minw < f.width))
+        {
+            if (f.flPlus)
+            {
+                signChar = '+';
+                ++minw;
+            }
+            else if (f.flSpace)
+            {
+                signChar = ' ';
+                ++minw;
+            }
+        }
+
+        auto maxw = minw < f.width ? f.width : minw;
+        auto difw = maxw - minw;
+
+        if (!f.flDash && !f.flZero)
+            foreach (i; 0 .. difw)
+                sink(" ");
+
+        if (signChar)
+            sink((&signChar)[0..1]);
+
+        if (!f.flDash && f.flZero)
+            foreach (i; 0 .. difw)
+                sink("0");
+
+        sink(buff);
+
+        if (f.flDash)
+            foreach (i; 0 .. difw)
+                sink(" ");
+    }
+
+    /**
+        $(D toString) is rarely directly invoked; the usual way of using it is via
+        $(LINK2 std_format.html#format, std.format.format):
+     */
+    unittest
+    {
+        import std.format : format;
+
+        auto x = BigInt("1_000_000");
+        x *= 12345;
+
+        assert(format("%d", x) == "12345000000");
+        assert(format("%x", x) == "2_dfd1c040");
+        assert(format("%X", x) == "2_DFD1C040");
+    }
+
+    // Implement toHash so that BigInt works properly as an AA key.
+    /**
+        Returns: A unique hash of the BigInt's value suitable for use in a hash
+        table.
+     */
+    size_t toHash() const @safe nothrow
+    {
+        return data.toHash() + sign;
+    }
+
+    /**
+        $(D toHash) is rarely directly invoked; it is implicitly used when
+        BigInt is used as the key of an associative array.
+     */
+    unittest
+    {
+        string[BigInt] aa;
+        aa[BigInt(123)] = "abc";
+        aa[BigInt(456)] = "def";
+
+        assert(aa[BigInt(123)] == "abc");
+        assert(aa[BigInt(456)] == "def");
+    }
+
+private:
+    void negate() @safe pure nothrow @nogc
+    {
+        if (!data.isZero())
+            sign = !sign;
+    }
+    bool isZero() pure const nothrow @nogc @safe
+    {
+        return data.isZero();
+    }
+    bool isNegative() pure const nothrow @nogc @safe
+    {
+        return sign;
+    }
+
+    // Generate a runtime error if division by zero occurs
+    void checkDivByZero() pure const nothrow @safe
+    {
+        if (isZero())
+            throw new Error("BigInt division by zero");
+    }
+}
+
+///
+unittest
+{
+    BigInt a = "9588669891916142";
+    BigInt b = "7452469135154800";
+    auto c = a * b;
+    assert(c == BigInt("71459266416693160362545788781600"));
+    auto d = b * a;
+    assert(d == BigInt("71459266416693160362545788781600"));
+    assert(d == c);
+    d = c * BigInt("794628672112");
+    assert(d == BigInt("56783581982794522489042432639320434378739200"));
+    auto e = c + d;
+    assert(e == BigInt("56783581982865981755459125799682980167520800"));
+    auto f = d + c;
+    assert(f == e);
+    auto g = f - c;
+    assert(g == d);
+    g = f - d;
+    assert(g == c);
+    e = 12345678;
+    g = c + e;
+    auto h = g / b;
+    auto i = g % b;
+    assert(h == a);
+    assert(i == e);
+    BigInt j = "-0x9A56_57f4_7B83_AB78";
+    j ^^= 11;
+}
+
+/**
+Params:
+    x = The $(D BigInt) to convert to a decimal $(D string).
+
+Returns:
+    A $(D string) that represents the $(D BigInt) as a decimal number.
+
+*/
+string toDecimalString(const(BigInt) x)
+{
+    string outbuff="";
+    void sink(const(char)[] s) { outbuff ~= s; }
+    x.toString(&sink, "%d");
+    return outbuff;
+}
+
+///
+unittest
+{
+    auto x = BigInt("123");
+    x *= 1000;
+    x += 456;
+
+    auto xstr = x.toDecimalString();
+    assert(xstr == "123456");
+}
+
+/**
+Params:
+    x = The $(D BigInt) to convert to a hexadecimal $(D string).
+
+Returns:
+    A $(D string) that represents the $(D BigInt) as a hexadecimal (base 16)
+    number in upper case.
+
+*/
+string toHex(const(BigInt) x)
+{
+    string outbuff="";
+    void sink(const(char)[] s) { outbuff ~= s; }
+    x.toString(&sink, "%X");
+    return outbuff;
+}
+
+///
+unittest
+{
+    auto x = BigInt("123");
+    x *= 1000;
+    x += 456;
+
+    auto xstr = x.toHex();
+    assert(xstr == "1E240");
+}
+
+/** Returns the absolute value of x converted to the corresponding unsigned
+type.
+
+Params:
+    x = The integral value to return the absolute value of.
+
+Returns:
+    The absolute value of x.
+
+*/
+Unsigned!T absUnsign(T)(T x) if (isIntegral!T)
+{
+    static if (isSigned!T)
+    {
+        import std.conv;
+        /* This returns the correct result even when x = T.min
+         * on two's complement machines because unsigned(T.min) = |T.min|
+         * even though -T.min = T.min.
+         */
+        return unsigned((x < 0) ? -x : x);
+    }
+    else
+    {
+        return x;
+    }
+}
+
+nothrow pure
+unittest {
+    BigInt a, b;
+    a = 1;
+    b = 2;
+    auto c = a + b;
+}
+
+nothrow pure
+unittest {
+    long a;
+    BigInt b;
+    auto c = a + b;
+    auto d = b + a;
+}
+
+nothrow pure
+unittest {
+    BigInt x = 1, y = 2;
+    assert(x <  y);
+    assert(x <= y);
+    assert(y >= x);
+    assert(y >  x);
+    assert(x != y);
+
+    long r1 = x.toLong;
+    assert(r1 == 1);
+
+    BigInt r2 = 10 % x;
+    assert(r2 == 0);
+
+    BigInt r3 = 10 / y;
+    assert(r3 == 5);
+
+    BigInt[] arr = [BigInt(1)];
+    auto incr = arr[0]++;
+    assert(arr == [BigInt(2)]);
+    assert(incr == BigInt(1));
+}
+
+unittest {
+    // Radix conversion
+    assert( toDecimalString(BigInt("-1_234_567_890_123_456_789"))
+        == "-1234567890123456789");
+    assert( toHex(BigInt("0x1234567890123456789")) == "123_45678901_23456789");
+    assert( toHex(BigInt("0x00000000000000000000000000000000000A234567890123456789"))
+        == "A23_45678901_23456789");
+    assert( toHex(BigInt("0x000_00_000000_000_000_000000000000_000000_")) == "0");
+
+    assert(BigInt(-0x12345678).toInt() == -0x12345678);
+    assert(BigInt(-0x12345678).toLong() == -0x12345678);
+    assert(BigInt(0x1234_5678_9ABC_5A5AL).ulongLength == 1);
+    assert(BigInt(0x1234_5678_9ABC_5A5AL).toLong() == 0x1234_5678_9ABC_5A5AL);
+    assert(BigInt(-0x1234_5678_9ABC_5A5AL).toLong() == -0x1234_5678_9ABC_5A5AL);
+    assert(BigInt(0xF234_5678_9ABC_5A5AL).toLong() == long.max);
+    assert(BigInt(-0x123456789ABCL).toInt() == -int.max);
+    char[] s1 = "123".dup; // bug 8164
+    assert(BigInt(s1) == 123);
+    char[] s2 = "0xABC".dup;
+    assert(BigInt(s2) == 2748);
+
+    assert((BigInt(-2) + BigInt(1)) == BigInt(-1));
+    BigInt a = ulong.max - 5;
+    auto b = -long.max % a;
+    assert( b == -long.max % (ulong.max - 5));
+    b = long.max / a;
+    assert( b == long.max /(ulong.max - 5));
+    assert(BigInt(1) - 1 == 0);
+    assert((-4) % BigInt(5) == -4); // bug 5928
+    assert(BigInt(-4) % BigInt(5) == -4);
+    assert(BigInt(2)/BigInt(-3) == BigInt(0)); // bug 8022
+    assert(BigInt("-1") > long.min); // bug 9548
+}
+
+unittest // Minimum signed value bug tests.
+{
+    assert(BigInt("-0x8000000000000000") == BigInt(long.min));
+    assert(BigInt("-0x8000000000000000")+1 > BigInt(long.min));
+    assert(BigInt("-0x80000000") == BigInt(int.min));
+    assert(BigInt("-0x80000000")+1 > BigInt(int.min));
+    assert(BigInt(long.min).toLong() == long.min); // lossy toLong bug for long.min
+    assert(BigInt(int.min).toInt() == int.min); // lossy toInt bug for int.min
+    assert(BigInt(long.min).ulongLength == 1);
+    assert(BigInt(int.min).uintLength == 1); // cast/sign extend bug in opAssign
+    BigInt a;
+    a += int.min;
+    assert(a == BigInt(int.min));
+    a = int.min - BigInt(int.min);
+    assert(a == 0);
+    a = int.min;
+    assert(a == BigInt(int.min));
+    assert(int.min % (BigInt(int.min)-1) == int.min);
+    assert((BigInt(int.min)-1)%int.min == -1);
+}
+
+unittest // Recursive division, bug 5568
+{
+    enum Z = 4843;
+    BigInt m = (BigInt(1) << (Z*8) ) - 1;
+    m -= (BigInt(1) << (Z*6)) - 1;
+    BigInt oldm = m;
+
+    BigInt a = (BigInt(1) << (Z*4) )-1;
+    BigInt b = m % a;
+    m /= a;
+    m *= a;
+    assert( m + b == oldm);
+
+    m = (BigInt(1) << (4846 + 4843) ) - 1;
+    a = (BigInt(1) << 4846 ) - 1;
+    b = (BigInt(1) << (4846*2 + 4843)) - 1;
+    BigInt c = (BigInt(1) << (4846*2 + 4843*2)) - 1;
+    BigInt w =  c - b + a;
+    assert(w % m == 0);
+
+    // Bug 6819. ^^
+    BigInt z1 = BigInt(10)^^64;
+    BigInt w1 = BigInt(10)^^128;
+    assert(z1^^2 == w1);
+    BigInt z2 = BigInt(1)<<64;
+    BigInt w2 = BigInt(1)<<128;
+    assert(z2^^2 == w2);
+    // Bug 7993
+    BigInt n7793 = 10;
+    assert( n7793 / 1 == 10);
+    // Bug 7973
+    auto a7973 = 10_000_000_000_000_000;
+    const c7973 = 10_000_000_000_000_000;
+    immutable i7973 = 10_000_000_000_000_000;
+    BigInt v7973 = 2551700137;
+    v7973 %= a7973;
+    assert(v7973 == 2551700137);
+    v7973 %= c7973;
+    assert(v7973 == 2551700137);
+    v7973 %= i7973;
+    assert(v7973 == 2551700137);
+    // 8165
+    BigInt[2] a8165;
+    a8165[0] = a8165[1] = 1;
+}
+
+unittest
+{
+    import std.array;
+    import std.format;
+
+    immutable string[][] table = [
+    /*  fmt,        +10     -10 */
+        ["%d",      "10",   "-10"],
+        ["%+d",     "+10",  "-10"],
+        ["%-d",     "10",   "-10"],
+        ["%+-d",    "+10",  "-10"],
+
+        ["%4d",     "  10", " -10"],
+        ["%+4d",    " +10", " -10"],
+        ["%-4d",    "10  ", "-10 "],
+        ["%+-4d",   "+10 ", "-10 "],
+
+        ["%04d",    "0010", "-010"],
+        ["%+04d",   "+010", "-010"],
+        ["%-04d",   "10  ", "-10 "],
+        ["%+-04d",  "+10 ", "-10 "],
+
+        ["% 04d",   " 010", "-010"],
+        ["%+ 04d",  "+010", "-010"],
+        ["%- 04d",  " 10 ", "-10 "],
+        ["%+- 04d", "+10 ", "-10 "],
+    ];
+
+    auto w1 = appender!(char[])();
+    auto w2 = appender!(char[])();
+
+    foreach (entry; table)
+    {
+        immutable fmt = entry[0];
+
+        formattedWrite(w1, fmt, BigInt(10));
+        formattedWrite(w2, fmt, 10);
+        assert(w1.data == w2.data);
+        assert(w1.data == entry[1]);
+        w1.clear();
+        w2.clear();
+
+        formattedWrite(w1, fmt, BigInt(-10));
+        formattedWrite(w2, fmt, -10);
+        assert(w1.data == w2.data);
+        assert(w1.data == entry[2]);
+        w1.clear();
+        w2.clear();
+    }
+}
+
+unittest
+{
+    import std.array;
+    import std.format;
+
+    immutable string[][] table = [
+    /*  fmt,        +10     -10 */
+        ["%x",      "a",    "-a"],
+        ["%+x",     "a",    "-a"],
+        ["%-x",     "a",    "-a"],
+        ["%+-x",    "a",    "-a"],
+
+        ["%4x",     "   a", "  -a"],
+        ["%+4x",    "   a", "  -a"],
+        ["%-4x",    "a   ", "-a  "],
+        ["%+-4x",   "a   ", "-a  "],
+
+        ["%04x",    "000a", "-00a"],
+        ["%+04x",   "000a", "-00a"],
+        ["%-04x",   "a   ", "-a  "],
+        ["%+-04x",  "a   ", "-a  "],
+
+        ["% 04x",   "000a", "-00a"],
+        ["%+ 04x",  "000a", "-00a"],
+        ["%- 04x",  "a   ", "-a  "],
+        ["%+- 04x", "a   ", "-a  "],
+    ];
+
+    auto w1 = appender!(char[])();
+    auto w2 = appender!(char[])();
+
+    foreach (entry; table)
+    {
+        immutable fmt = entry[0];
+
+        formattedWrite(w1, fmt, BigInt(10));
+        formattedWrite(w2, fmt, 10);
+        assert(w1.data == w2.data);     // Equal only positive BigInt
+        assert(w1.data == entry[1]);
+        w1.clear();
+        w2.clear();
+
+        formattedWrite(w1, fmt, BigInt(-10));
+        //formattedWrite(w2, fmt, -10);
+        //assert(w1.data == w2.data);
+        assert(w1.data == entry[2]);
+        w1.clear();
+        //w2.clear();
+    }
+}
+
+unittest
+{
+    import std.array;
+    import std.format;
+
+    immutable string[][] table = [
+    /*  fmt,        +10     -10 */
+        ["%X",      "A",    "-A"],
+        ["%+X",     "A",    "-A"],
+        ["%-X",     "A",    "-A"],
+        ["%+-X",    "A",    "-A"],
+
+        ["%4X",     "   A", "  -A"],
+        ["%+4X",    "   A", "  -A"],
+        ["%-4X",    "A   ", "-A  "],
+        ["%+-4X",   "A   ", "-A  "],
+
+        ["%04X",    "000A", "-00A"],
+        ["%+04X",   "000A", "-00A"],
+        ["%-04X",   "A   ", "-A  "],
+        ["%+-04X",  "A   ", "-A  "],
+
+        ["% 04X",   "000A", "-00A"],
+        ["%+ 04X",  "000A", "-00A"],
+        ["%- 04X",  "A   ", "-A  "],
+        ["%+- 04X", "A   ", "-A  "],
+    ];
+
+    auto w1 = appender!(char[])();
+    auto w2 = appender!(char[])();
+
+    foreach (entry; table)
+    {
+        immutable fmt = entry[0];
+
+        formattedWrite(w1, fmt, BigInt(10));
+        formattedWrite(w2, fmt, 10);
+        assert(w1.data == w2.data);     // Equal only positive BigInt
+        assert(w1.data == entry[1]);
+        w1.clear();
+        w2.clear();
+
+        formattedWrite(w1, fmt, BigInt(-10));
+        //formattedWrite(w2, fmt, -10);
+        //assert(w1.data == w2.data);
+        assert(w1.data == entry[2]);
+        w1.clear();
+        //w2.clear();
+    }
+}
+
+// 6448
+unittest
+{
+    import std.array;
+    import std.format;
+
+    auto w1 = appender!string();
+    auto w2 = appender!string();
+
+    int x = 100;
+    formattedWrite(w1, "%010d", x);
+    BigInt bx = x;
+    formattedWrite(w2, "%010d", bx);
+    assert(w1.data == w2.data);
+    //8011
+    BigInt y = -3;
+    ++y;
+    assert(y.toLong() == -2);
+    y = 1;
+    --y;
+    assert(y.toLong() == 0);
+    --y;
+    assert(y.toLong() == -1);
+    --y;
+    assert(y.toLong() == -2);
+}
+
+unittest
+{
+    import std.math:abs;
+    auto r = abs(BigInt(-1000)); // 6486
+    assert(r == 1000);
+
+    auto r2 = abs(const(BigInt)(-500)); // 11188
+    assert(r2 == 500);
+    auto r3 = abs(immutable(BigInt)(-733)); // 11188
+    assert(r3 == 733);
+
+    // opCast!bool
+    BigInt one = 1, zero;
+    assert(one && !zero);
+}
+
+unittest // 6850
+{
+    pure long pureTest() {
+        BigInt a = 1;
+        BigInt b = 1336;
+        a += b;
+        return a.toLong();
+    }
+
+    assert(pureTest() == 1337);
+}
+
+unittest // 8435 & 10118
+{
+    auto i = BigInt(100);
+    auto j = BigInt(100);
+
+    // Two separate BigInt instances representing same value should have same
+    // hash.
+    assert(typeid(i).getHash(&i) == typeid(j).getHash(&j));
+    assert(typeid(i).compare(&i, &j) == 0);
+
+    // BigInt AA keys should behave consistently.
+    int[BigInt] aa;
+    aa[BigInt(123)] = 123;
+    assert(BigInt(123) in aa);
+
+    aa[BigInt(123)] = 321;
+    assert(aa[BigInt(123)] == 321);
+
+    auto keys = aa.byKey;
+    assert(keys.front == BigInt(123));
+    keys.popFront();
+    assert(keys.empty);
+}
+
+unittest // 11148
+{
+    void foo(BigInt) {}
+    const BigInt cbi = 3;
+    immutable BigInt ibi = 3;
+
+    assert(__traits(compiles, foo(cbi)));
+    assert(__traits(compiles, foo(ibi)));
+
+    import std.meta : AliasSeq;
+    import std.conv : to;
+
+    foreach (T1; AliasSeq!(BigInt, const(BigInt), immutable(BigInt)))
+    {
+        foreach (T2; AliasSeq!(BigInt, const(BigInt), immutable(BigInt)))
+        {
+            T1 t1 = 2;
+            T2 t2 = t1;
+
+            T2 t2_1 = to!T2(t1);
+            T2 t2_2 = cast(T2)t1;
+
+            assert(t2 == t1);
+            assert(t2 == 2);
+
+            assert(t2_1 == t1);
+            assert(t2_1 == 2);
+
+            assert(t2_2 == t1);
+            assert(t2_2 == 2);
+        }
+    }
+
+    BigInt n = 2;
+    n *= 2;
+}
+
+unittest // 8167
+{
+    BigInt a = BigInt(3);
+    BigInt b = BigInt(a);
+}
+
+unittest // 9061
+{
+    long l1 = 0x12345678_90ABCDEF;
+    long l2 = 0xFEDCBA09_87654321;
+    long l3 = l1 | l2;
+    long l4 = l1 & l2;
+    long l5 = l1 ^ l2;
+
+    BigInt b1 = l1;
+    BigInt b2 = l2;
+    BigInt b3 = b1 | b2;
+    BigInt b4 = b1 & b2;
+    BigInt b5 = b1 ^ b2;
+
+    assert(l3 == b3);
+    assert(l4 == b4);
+    assert(l5 == b5);
+}
+
+unittest // 11600
+{
+    import std.conv;
+    import std.exception : assertThrown;
+
+    // Original bug report
+    assertThrown!ConvException(to!BigInt("avadakedavra"));
+
+    // Digit string lookalikes that are actually invalid
+    assertThrown!ConvException(to!BigInt("0123hellothere"));
+    assertThrown!ConvException(to!BigInt("-hihomarylowe"));
+    assertThrown!ConvException(to!BigInt("__reallynow__"));
+    assertThrown!ConvException(to!BigInt("-123four"));
+}
+
+unittest // 11583
+{
+    BigInt x = 0;
+    assert((x > 0) == false);
+}
+
+unittest // 13391
+{
+    BigInt x1 = "123456789";
+    BigInt x2 = "123456789123456789";
+    BigInt x3 = "123456789123456789123456789";
+
+    import std.meta : AliasSeq;
+    foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        assert((x1 * T.max) / T.max == x1);
+        assert((x2 * T.max) / T.max == x2);
+        assert((x3 * T.max) / T.max == x3);
+    }
+
+    assert(x1 / -123456789 == -1);
+    assert(x1 / 123456789U == 1);
+    assert(x1 / -123456789L == -1);
+    assert(x1 / 123456789UL == 1);
+    assert(x2 / -123456789123456789L == -1);
+    assert(x2 / 123456789123456789UL == 1);
+
+    assert(x1 / uint.max == 0);
+    assert(x1 / ulong.max == 0);
+    assert(x2 / ulong.max == 0);
+
+    x1 /= 123456789UL;
+    assert(x1 == 1);
+    x2 /= 123456789123456789UL;
+    assert(x2 == 1);
+}
+
+unittest // 13963
+{
+    BigInt x = 1;
+    import std.meta : AliasSeq;
+    foreach(Int; AliasSeq!(byte, ubyte, short, ushort, int, uint))
+    {
+        assert(is(typeof(x % Int(1)) == int));
+    }
+    assert(is(typeof(x % 1L) == long));
+    assert(is(typeof(x % 1UL) == BigInt));
+
+    auto x1 = BigInt(8);
+    auto x2 = -BigInt(long.min) + 1;
+
+    // long
+    assert(x1 % 2L == 0L);
+    assert(-x1 % 2L == 0L);
+
+    assert(x1 % 3L == 2L);
+    assert(x1 % -3L == 2L);
+    assert(-x1 % 3L == -2L);
+    assert(-x1 % -3L == -2L);
+
+    assert(x1 % 11L == 8L);
+    assert(x1 % -11L == 8L);
+    assert(-x1 % 11L == -8L);
+    assert(-x1 % -11L == -8L);
+
+    // ulong
+    assert(x1 % 2UL == BigInt(0));
+    assert(-x1 % 2UL == BigInt(0));
+
+    assert(x1 % 3UL == BigInt(2));
+    assert(-x1 % 3UL == -BigInt(2));
+
+    assert(x1 % 11UL == BigInt(8));
+    assert(-x1 % 11UL == -BigInt(8));
+
+    assert(x2 % ulong.max == x2);
+    assert(-x2 % ulong.max == -x2);
+}
+
+unittest // 14124
+{
+    auto x = BigInt(-3);
+    x %= 3;
+    assert(!x.isNegative());
+    assert(x.isZero());
+
+    x = BigInt(-3);
+    x %= cast(ushort)3;
+    assert(!x.isNegative());
+    assert(x.isZero());
+
+    x = BigInt(-3);
+    x %= 3L;
+    assert(!x.isNegative());
+    assert(x.isZero());
+
+    x = BigInt(3);
+    x %= -3;
+    assert(!x.isNegative());
+    assert(x.isZero());
+}
+
+// issue 15678
+unittest
+{
+    import std.exception : assertThrown;
+    assertThrown!ConvException(BigInt(""));
+    assertThrown!ConvException(BigInt("0x1234BARF"));
+    assertThrown!ConvException(BigInt("1234PUKE"));
+}
diff --git a/libphobos/src/std/bitmanip.d b/libphobos/src/std/bitmanip.d
new file mode 100644
index 00000000000..94ca41f6981
--- /dev/null
+++ b/libphobos/src/std/bitmanip.d
@@ -0,0 +1,3999 @@
+// Written in the D programming language.
+
+/**
+Bit-level manipulation facilities.
+
+Macros:
+
+WIKI = StdBitarray
+
+Copyright: Copyright Digital Mars 2007 - 2011.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB digitalmars.com, Walter Bright),
+           $(WEB erdani.org, Andrei Alexandrescu),
+           Jonathan M Davis,
+           Alex Rønne Petersen,
+           Damian Ziemba
+           Amaury SECHET
+Source: $(PHOBOSSRC std/_bitmanip.d)
+*/
+/*
+         Copyright Digital Mars 2007 - 2012.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.bitmanip;
+
+//debug = bitarray;                // uncomment to turn on debugging printf's
+
+import std.range.primitives;
+public import std.system : Endian;
+import std.traits;
+
+version(unittest)
+{
+    import std.stdio;
+}
+
+
+private string myToString(ulong n)
+{
+    import core.internal.string;
+    UnsignedStringBuf buf;
+    auto s = unsignedToTempString(n, buf);
+    return cast(string)s ~ (n > uint.max ? "UL" : "U");
+}
+
+private template createAccessors(
+    string store, T, string name, size_t len, size_t offset)
+{
+    static if (!name.length)
+    {
+        // No need to create any accessor
+        enum result = "";
+    }
+    else static if (len == 0)
+    {
+        // Fields of length 0 are always zero
+        enum result = "enum "~T.stringof~" "~name~" = 0;\n";
+    }
+    else
+    {
+        enum ulong
+            maskAllElse = ((~0uL) >> (64 - len)) << offset,
+            signBitCheck = 1uL << (len - 1);
+
+        static if (T.min < 0)
+        {
+            enum long minVal = -(1uL << (len - 1));
+            enum ulong maxVal = (1uL << (len - 1)) - 1;
+            alias UT = Unsigned!(T);
+            enum UT extendSign = cast(UT)~((~0uL) >> (64 - len));
+        }
+        else
+        {
+            enum ulong minVal = 0;
+            enum ulong maxVal = (~0uL) >> (64 - len);
+            enum extendSign = 0;
+        }
+
+        static if (is(T == bool))
+        {
+            static assert(len == 1);
+            enum result =
+            // getter
+                "@property bool " ~ name ~ "() @safe pure nothrow @nogc const { return "
+                ~"("~store~" & "~myToString(maskAllElse)~") != 0;}\n"
+            // setter
+                ~"@property void " ~ name ~ "(bool v) @safe pure nothrow @nogc { "
+                ~"if (v) "~store~" |= "~myToString(maskAllElse)~";"
+                ~"else "~store~" &= ~cast(typeof("~store~"))"~myToString(maskAllElse)~";}\n";
+        }
+        else
+        {
+            // getter
+            enum result = "@property "~T.stringof~" "~name~"() @safe pure nothrow @nogc const { auto result = "
+                ~"("~store~" & "
+                ~ myToString(maskAllElse) ~ ") >>"
+                ~ myToString(offset) ~ ";"
+                ~ (T.min < 0
+                   ? "if (result >= " ~ myToString(signBitCheck)
+                   ~ ") result |= " ~ myToString(extendSign) ~ ";"
+                   : "")
+                ~ " return cast("~T.stringof~") result;}\n"
+            // setter
+                ~"@property void "~name~"("~T.stringof~" v) @safe pure nothrow @nogc { "
+                ~"assert(v >= "~name~`_min, "Value is smaller than the minimum value of bitfield '`~name~`'"); `
+                ~"assert(v <= "~name~`_max, "Value is greater than the maximum value of bitfield '`~name~`'"); `
+                ~store~" = cast(typeof("~store~"))"
+                ~" (("~store~" & ~cast(typeof("~store~"))"~myToString(maskAllElse)~")"
+                ~" | ((cast(typeof("~store~")) v << "~myToString(offset)~")"
+                ~" & "~myToString(maskAllElse)~"));}\n"
+            // constants
+                ~"enum "~T.stringof~" "~name~"_min = cast("~T.stringof~")"
+                ~myToString(minVal)~"; "
+                ~" enum "~T.stringof~" "~name~"_max = cast("~T.stringof~")"
+                ~myToString(maxVal)~"; ";
+        }
+    }
+}
+
+private template createStoreName(Ts...)
+{
+    static if (Ts.length < 2)
+        enum createStoreName = "";
+    else
+        enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]);
+}
+
+private template createFields(string store, size_t offset, Ts...)
+{
+    static if (!Ts.length)
+    {
+        static if (offset == ubyte.sizeof * 8)
+            alias StoreType = ubyte;
+        else static if (offset == ushort.sizeof * 8)
+            alias StoreType = ushort;
+        else static if (offset == uint.sizeof * 8)
+            alias StoreType = uint;
+        else static if (offset == ulong.sizeof * 8)
+            alias StoreType = ulong;
+        else
+        {
+            static assert(false, "Field widths must sum to 8, 16, 32, or 64");
+            alias StoreType = ulong; // just to avoid another error msg
+        }
+        enum result = "private " ~ StoreType.stringof ~ " " ~ store ~ ";";
+    }
+    else
+    {
+        enum result
+            = createAccessors!(store, Ts[0], Ts[1], Ts[2], offset).result
+            ~ createFields!(store, offset + Ts[2], Ts[3 .. $]).result;
+    }
+}
+
+private ulong getBitsForAlign(ulong a)
+{
+    ulong bits = 0;
+    while ((a & 0x01) == 0)
+    {
+        bits++;
+        a >>= 1;
+    }
+
+    assert(a == 1, "alignment is not a power of 2");
+    return bits;
+}
+
+private template createReferenceAccessor(string store, T, ulong bits, string name)
+{
+    enum mask = (1UL << bits) - 1;
+    // getter
+    enum result = "@property "~T.stringof~" "~name~"() @trusted pure nothrow @nogc const { auto result = "
+        ~ "("~store~" & "~myToString(~mask)~");"
+        ~ " return cast("~T.stringof~") cast(void*) result;}\n"
+    // setter
+        ~"@property void "~name~"("~T.stringof~" v) @trusted pure nothrow @nogc { "
+        ~"assert(((cast(typeof("~store~")) cast(void*) v) & "~myToString(mask)~`) == 0, "Value not properly aligned for '`~name~`'"); `
+        ~store~" = cast(typeof("~store~"))"
+        ~" (("~store~" & (cast(typeof("~store~")) "~myToString(mask)~"))"
+        ~" | ((cast(typeof("~store~")) cast(void*) v) & (cast(typeof("~store~")) "~myToString(~mask)~")));}\n";
+}
+
+private template sizeOfBitField(T...)
+{
+    static if(T.length < 2)
+        enum sizeOfBitField = 0;
+    else
+        enum sizeOfBitField = T[2] + sizeOfBitField!(T[3 .. $]);
+}
+
+private template createTaggedReference(string store, T, ulong a, string name, Ts...)
+{
+    static assert(sizeOfBitField!Ts <= getBitsForAlign(a), "Fields must fit in the bits know to be zero because of alignment.");
+    enum result
+        = createReferenceAccessor!(store, T, sizeOfBitField!Ts, name).result
+        ~ createFields!(store, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts).result;
+}
+
+/**
+Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM
+class)es.
+
+Example:
+
+----
+struct A
+{
+    int a;
+    mixin(bitfields!(
+        uint, "x",    2,
+        int,  "y",    3,
+        uint, "z",    2,
+        bool, "flag", 1));
+}
+A obj;
+obj.x = 2;
+obj.z = obj.x;
+----
+
+The example above creates a bitfield pack of eight bits, which fit in
+one $(D_PARAM ubyte). The bitfields are allocated starting from the
+least significant bit, i.e. x occupies the two least significant bits
+of the bitfields storage.
+
+The sum of all bit lengths in one $(D_PARAM bitfield) instantiation
+must be exactly 8, 16, 32, or 64. If padding is needed, just allocate
+one bitfield with an empty name.
+
+Example:
+
+----
+struct A
+{
+    mixin(bitfields!(
+        bool, "flag1",    1,
+        bool, "flag2",    1,
+        uint, "",         6));
+}
+----
+
+The type of a bit field can be any integral type or enumerated
+type. The most efficient type to store in bitfields is $(D_PARAM
+bool), followed by unsigned types, followed by signed types.
+*/
+
+template bitfields(T...)
+{
+    enum { bitfields = createFields!(createStoreName!(T), 0, T).result }
+}
+
+/**
+This string mixin generator allows one to create tagged pointers inside $(D_PARAM struct)s and $(D_PARAM class)es.
+
+A tagged pointer uses the bits known to be zero in a normal pointer or class reference to store extra information.
+For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
+One can store a 2-bit integer there.
+
+The example above creates a tagged pointer in the struct A. The pointer is of type
+$(D uint*) as specified by the first argument, and is named x, as specified by the second
+argument.
+
+Following arguments works the same way as $(D bitfield)'s. The bitfield must fit into the
+bits known to be zero because of the pointer alignment.
+
+$(RED Warning: Don't use $(D taggedPointer) with pointers to garbage collected objects, as it will result in
+undefined behaviour. See
+$(DDLINK spec/garbage, Garbage Collection, Garbage Collection) for details.)
+*/
+
+template taggedPointer(T : T*, string name, Ts...) {
+    enum taggedPointer = createTaggedReference!(createStoreName!(T, name, 0, Ts), T*, T.alignof, name, Ts).result;
+}
+
+///
+unittest
+{
+    struct A
+    {
+        int a;
+        mixin(taggedPointer!(
+            uint*, "x",
+            bool, "b1", 1,
+            bool, "b2", 1));
+    }
+    A obj;
+    obj.x = new uint;
+    obj.b1 = true;
+    obj.b2 = false;
+}
+
+/**
+This string mixin generator allows one to create tagged class reference inside $(D_PARAM struct)s and $(D_PARAM class)es.
+
+A tagged class reference uses the bits known to be zero in a normal class reference to store extra information.
+For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
+One can store a 2-bit integer there.
+
+The example above creates a tagged reference to an Object in the struct A. This expects the same parameters
+as $(D taggedPointer), except the first argument which must be a class type instead of a pointer type.
+
+$(RED Warning: Don't use $(D taggedClassRef) with references to garbage collected objects, as it will result in
+undefined behaviour. See
+$(DDLINK spec/garbage, Garbage Collection, Garbage Collection) for details.)
+*/
+
+template taggedClassRef(T, string name, Ts...) if(is(T == class)) {
+    enum taggedClassRef = createTaggedReference!(createStoreName!(T, name, 0, Ts), T, 8, name, Ts).result;
+}
+
+///
+unittest
+{
+    struct A
+    {
+        int a;
+        mixin(taggedClassRef!(
+            Object, "o",
+            uint, "i", 2));
+    }
+    A obj;
+    obj.o = new Object();
+    obj.i = 3;
+}
+
+@safe pure nothrow @nogc
+unittest
+{
+    // Degenerate bitfields (#8474 / #11160) tests mixed with range tests
+    struct Test1
+    {
+        mixin(bitfields!(uint, "a", 32,
+                        uint, "b", 4,
+                        uint, "c", 4,
+                        uint, "d", 8,
+                        uint, "e", 16,));
+
+        static assert(Test1.b_min == 0);
+        static assert(Test1.b_max == 15);
+    }
+
+    struct Test2
+    {
+        mixin(bitfields!(bool, "a", 0,
+                        ulong, "b", 64));
+
+        static assert(Test2.b_min == ulong.min);
+        static assert(Test2.b_max == ulong.max);
+    }
+
+    struct Test1b
+    {
+        mixin(bitfields!(bool, "a", 0,
+                        int, "b", 8));
+    }
+
+    struct Test2b
+    {
+        mixin(bitfields!(int, "a", 32,
+                        int, "b", 4,
+                        int, "c", 4,
+                        int, "d", 8,
+                        int, "e", 16,));
+
+        static assert(Test2b.b_min == -8);
+        static assert(Test2b.b_max == 7);
+    }
+
+    struct Test3b
+    {
+        mixin(bitfields!(bool, "a", 0,
+                        long, "b", 64));
+
+        static assert(Test3b.b_min == long.min);
+        static assert(Test3b.b_max == long.max);
+    }
+
+    struct Test4b
+    {
+        mixin(bitfields!(long, "a", 32,
+                        int, "b", 32));
+    }
+
+    // Sign extension tests
+    Test2b t2b;
+    Test4b t4b;
+    t2b.b = -5; assert(t2b.b == -5);
+    t2b.d = -5; assert(t2b.d == -5);
+    t2b.e = -5; assert(t2b.e == -5);
+    t4b.a = -5; assert(t4b.a == -5L);
+}
+
+unittest
+{
+    struct Test5
+    {
+        mixin(taggedPointer!(
+            int*, "a",
+            uint, "b", 2));
+    }
+
+    Test5 t5;
+    t5.a = null;
+    t5.b = 3;
+    assert(t5.a is null);
+    assert(t5.b == 3);
+
+    int myint = 42;
+    t5.a = &myint;
+    assert(t5.a is &myint);
+    assert(t5.b == 3);
+
+    struct Test6
+    {
+        mixin(taggedClassRef!(
+            Object, "o",
+            bool, "b", 1));
+    }
+
+    Test6 t6;
+    t6.o = null;
+    t6.b = false;
+    assert(t6.o is null);
+    assert(t6.b == false);
+
+    auto o = new Object();
+    t6.o = o;
+    t6.b = true;
+    assert(t6.o is o);
+    assert(t6.b == true);
+}
+
+unittest
+{
+    static assert(!__traits(compiles,
+        taggedPointer!(
+            int*, "a",
+            uint, "b", 3)));
+
+    static assert(!__traits(compiles,
+        taggedClassRef!(
+            Object, "a",
+            uint, "b", 4)));
+}
+
+unittest
+{
+    // Bug #6686
+    union  S {
+        ulong bits = ulong.max;
+        mixin (bitfields!(
+            ulong, "back",  31,
+            ulong, "front", 33)
+        );
+    }
+    S num;
+
+    num.bits = ulong.max;
+    num.back = 1;
+    assert(num.bits == 0xFFFF_FFFF_8000_0001uL);
+}
+
+unittest
+{
+    // Bug #5942
+    struct S
+    {
+        mixin(bitfields!(
+            int, "a" , 32,
+            int, "b" , 32
+        ));
+    }
+
+    S data;
+    data.b = 42;
+    data.a = 1;
+    assert(data.b == 42);
+}
+
+unittest
+{
+    struct Test
+    {
+        mixin(bitfields!(bool, "a", 1,
+                         uint, "b", 3,
+                         short, "c", 4));
+    }
+
+    @safe void test() pure nothrow
+    {
+        Test t;
+
+        t.a = true;
+        t.b = 5;
+        t.c = 2;
+
+        assert(t.a);
+        assert(t.b == 5);
+        assert(t.c == 2);
+    }
+
+    test();
+}
+
+unittest
+{
+    {
+        static struct Integrals {
+            bool checkExpectations(bool eb, int ei, short es) { return b == eb && i == ei && s == es; }
+
+            mixin(bitfields!(
+                      bool, "b", 1,
+                      uint, "i", 3,
+                      short, "s", 4));
+        }
+        Integrals i;
+        assert(i.checkExpectations(false, 0, 0));
+        i.b = true;
+        assert(i.checkExpectations(true, 0, 0));
+        i.i = 7;
+        assert(i.checkExpectations(true, 7, 0));
+        i.s = -8;
+        assert(i.checkExpectations(true, 7, -8));
+        i.s = 7;
+        assert(i.checkExpectations(true, 7, 7));
+    }
+
+    //Bug# 8876
+    {
+        struct MoreIntegrals {
+            bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; }
+
+            mixin(bitfields!(
+                  uint, "u", 24,
+                  short, "s", 16,
+                  int, "i", 24));
+        }
+
+        MoreIntegrals i;
+        assert(i.checkExpectations(0, 0, 0));
+        i.s = 20;
+        assert(i.checkExpectations(0, 20, 0));
+        i.i = 72;
+        assert(i.checkExpectations(0, 20, 72));
+        i.u = 8;
+        assert(i.checkExpectations(8, 20, 72));
+        i.s = 7;
+        assert(i.checkExpectations(8, 7, 72));
+    }
+
+    enum A { True, False }
+    enum B { One, Two, Three, Four }
+    static struct Enums {
+        bool checkExpectations(A ea, B eb) { return a == ea && b == eb; }
+
+        mixin(bitfields!(
+                  A, "a", 1,
+                  B, "b", 2,
+                  uint, "", 5));
+    }
+    Enums e;
+    assert(e.checkExpectations(A.True, B.One));
+    e.a = A.False;
+    assert(e.checkExpectations(A.False, B.One));
+    e.b = B.Three;
+    assert(e.checkExpectations(A.False, B.Three));
+
+    static struct SingleMember {
+        bool checkExpectations(bool eb) { return b == eb; }
+
+        mixin(bitfields!(
+                  bool, "b", 1,
+                  uint, "", 7));
+    }
+    SingleMember f;
+    assert(f.checkExpectations(false));
+    f.b = true;
+    assert(f.checkExpectations(true));
+}
+
+// Issue 12477
+unittest
+{
+    import std.algorithm : canFind;
+    import std.bitmanip : bitfields;
+    import core.exception : AssertError;
+
+    static struct S
+    {
+        mixin(bitfields!(
+            uint, "a", 6,
+            int, "b", 2));
+    }
+
+    S s;
+
+    try { s.a = uint.max; assert(0); }
+    catch (AssertError ae)
+    { assert(ae.msg.canFind("Value is greater than the maximum value of bitfield 'a'"), ae.msg); }
+
+    try { s.b = int.min;  assert(0); }
+    catch (AssertError ae)
+    { assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); }
+}
+
+/**
+   Allows manipulating the fraction, exponent, and sign parts of a
+   $(D_PARAM float) separately. The definition is:
+
+----
+struct FloatRep
+{
+    union
+    {
+        float value;
+        mixin(bitfields!(
+                  uint,  "fraction", 23,
+                  ubyte, "exponent",  8,
+                  bool,  "sign",      1));
+    }
+    enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1;
+}
+----
+*/
+
+struct FloatRep
+{
+    union
+    {
+        float value;
+        mixin(bitfields!(
+                  uint,  "fraction", 23,
+                  ubyte, "exponent",  8,
+                  bool,  "sign",      1));
+    }
+    enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1;
+}
+
+/**
+   Allows manipulating the fraction, exponent, and sign parts of a
+   $(D_PARAM double) separately. The definition is:
+
+----
+struct DoubleRep
+{
+    union
+    {
+        double value;
+        mixin(bitfields!(
+                  ulong,   "fraction", 52,
+                  ushort,  "exponent", 11,
+                  bool,    "sign",      1));
+    }
+    enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11;
+}
+----
+*/
+
+struct DoubleRep
+{
+    union
+    {
+        double value;
+        mixin(bitfields!(
+                  ulong,  "fraction", 52,
+                  ushort, "exponent", 11,
+                  bool,   "sign",      1));
+    }
+    enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11;
+}
+
+unittest
+{
+    // test reading
+    DoubleRep x;
+    x.value = 1.0;
+    assert(x.fraction == 0 && x.exponent == 1023 && !x.sign);
+    x.value = -0.5;
+    assert(x.fraction == 0 && x.exponent == 1022 && x.sign);
+    x.value = 0.5;
+    assert(x.fraction == 0 && x.exponent == 1022 && !x.sign);
+
+    // test writing
+    x.fraction = 1125899906842624;
+    x.exponent = 1025;
+    x.sign = true;
+    assert(x.value == -5.0);
+
+    // test enums
+    enum ABC { A, B, C }
+    struct EnumTest
+    {
+        mixin(bitfields!(
+                  ABC, "x", 2,
+                  bool, "y", 1,
+                  ubyte, "z", 5));
+    }
+}
+
+unittest
+{
+    // Issue #15305
+    struct S {
+            mixin(bitfields!(
+                    bool, "alice", 1,
+                    ulong, "bob", 63,
+            ));
+    }
+
+    S s;
+    s.bob = long.max - 1;
+    s.alice = false;
+    assert(s.bob == long.max - 1);
+}
+
+/**
+ * An array of bits.
+ */
+
+struct BitArray
+{
+    // Explicitly undocumented. They will be removed in April 2016. @@@DEPRECATED_2016-04@@@
+    deprecated("Use the constructor instead.")
+    @property void ptr(size_t* p) pure nothrow @nogc { _ptr = p; }
+    deprecated("Use .opIndex instead.")
+    @property inout(size_t)* ptr() inout pure nothrow @nogc { return _ptr; }
+
+    deprecated("Use .length instead.")
+    @property void len(size_t v) pure nothrow @nogc { _len = v; }
+    deprecated("Use .length instead.")
+    @property size_t len() const pure nothrow @nogc { return _len; }
+
+private:
+
+    import std.format : FormatSpec;
+    import core.bitop: bts, btr, bsf, bt;
+
+    size_t _len;
+    size_t* _ptr;
+    enum bitsPerSizeT = size_t.sizeof * 8;
+
+    @property size_t fullWords() const @nogc pure nothrow
+    {
+        return _len / bitsPerSizeT;
+    }
+    // Number of bits after the last full word
+    @property size_t endBits() const @nogc pure nothrow
+    {
+        return _len % bitsPerSizeT;
+    }
+    // Bit mask to extract the bits after the last full word
+    @property size_t endMask() const @nogc pure nothrow
+    {
+        return (size_t(1) << endBits) - 1;
+    }
+    static size_t lenToDim(size_t len) @nogc pure nothrow
+    {
+        return (len + (bitsPerSizeT-1)) / bitsPerSizeT;
+    }
+
+public:
+    /**********************************************
+     * Gets the amount of native words backing this $(D BitArray).
+     */
+    @property size_t dim() const @nogc pure nothrow
+    {
+        return lenToDim(_len);
+    }
+
+    /**********************************************
+     * Gets the amount of bits in the $(D BitArray).
+     */
+    @property size_t length() const @nogc pure nothrow
+    {
+        return _len;
+    }
+
+    /**********************************************
+     * Sets the amount of bits in the $(D BitArray).
+     * $(RED Warning: increasing length may overwrite bits in
+     * final word up to the next word boundary. i.e. D dynamic
+     * array extension semantics are not followed.)
+     */
+    @property size_t length(size_t newlen) pure nothrow
+    {
+        if (newlen != _len)
+        {
+            size_t olddim = dim;
+            size_t newdim = lenToDim(newlen);
+
+            if (newdim != olddim)
+            {
+                // Create a fake array so we can use D's realloc machinery
+                auto b = _ptr[0 .. olddim];
+                b.length = newdim;                // realloc
+                _ptr = b.ptr;
+            }
+
+            _len = newlen;
+        }
+        return _len;
+    }
+
+    /**********************************************
+     * Gets the $(D i)'th bit in the $(D BitArray).
+     */
+    bool opIndex(size_t i) const @nogc pure nothrow
+    in
+    {
+        assert(i < _len);
+    }
+    body
+    {
+        return cast(bool) bt(_ptr, i);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opIndex.unittest\n");
+
+        void Fun(const BitArray arr)
+        {
+            auto x = arr[0];
+            assert(x == 1);
+        }
+        BitArray a;
+        a.length = 3;
+        a[0] = 1;
+        Fun(a);
+    }
+
+    /**********************************************
+     * Sets the $(D i)'th bit in the $(D BitArray).
+     */
+    bool opIndexAssign(bool b, size_t i) @nogc pure nothrow
+    in
+    {
+        assert(i < _len);
+    }
+    body
+    {
+        if (b)
+            bts(_ptr, i);
+        else
+            btr(_ptr, i);
+        return b;
+    }
+
+    /**********************************************
+     * Duplicates the $(D BitArray) and its contents.
+     */
+    @property BitArray dup() const pure nothrow
+    {
+        BitArray ba;
+
+        auto b = _ptr[0 .. dim].dup;
+        ba._len = _len;
+        ba._ptr = b.ptr;
+        return ba;
+    }
+
+    unittest
+    {
+        BitArray a;
+        BitArray b;
+        int i;
+
+        debug(bitarray) printf("BitArray.dup.unittest\n");
+
+        a.length = 3;
+        a[0] = 1; a[1] = 0; a[2] = 1;
+        b = a.dup;
+        assert(b.length == 3);
+        for (i = 0; i < 3; i++)
+        {   debug(bitarray) printf("b[%d] = %d\n", i, b[i]);
+            assert(b[i] == (((i ^ 1) & 1) ? true : false));
+        }
+    }
+
+    /**********************************************
+     * Support for $(D foreach) loops for $(D BitArray).
+     */
+    int opApply(scope int delegate(ref bool) dg)
+    {
+        int result;
+
+        foreach (i; 0 .. _len)
+        {
+            bool b = opIndex(i);
+            result = dg(b);
+            this[i] = b;
+            if (result)
+                break;
+        }
+        return result;
+    }
+
+    /** ditto */
+    int opApply(scope int delegate(bool) dg) const
+    {
+        int result;
+
+        foreach (i; 0 .. _len)
+        {
+            bool b = opIndex(i);
+            result = dg(b);
+            if (result)
+                break;
+        }
+        return result;
+    }
+
+    /** ditto */
+    int opApply(scope int delegate(size_t, ref bool) dg)
+    {
+        int result;
+
+        foreach (i; 0 .. _len)
+        {
+            bool b = opIndex(i);
+            result = dg(i, b);
+            this[i] = b;
+            if (result)
+                break;
+        }
+        return result;
+    }
+
+    /** ditto */
+    int opApply(scope int delegate(size_t, bool) dg) const
+    {
+        int result;
+
+        foreach (i; 0 .. _len)
+        {
+            bool b = opIndex(i);
+            result = dg(i, b);
+            if (result)
+                break;
+        }
+        return result;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opApply unittest\n");
+
+        static bool[] ba = [1,0,1];
+
+        auto a = BitArray(ba);
+
+        int i;
+        foreach (b;a)
+        {
+            switch (i)
+            {
+                case 0: assert(b == true); break;
+                case 1: assert(b == false); break;
+                case 2: assert(b == true); break;
+                default: assert(0);
+            }
+            i++;
+        }
+
+        foreach (j,b;a)
+        {
+            switch (j)
+            {
+                case 0: assert(b == true); break;
+                case 1: assert(b == false); break;
+                case 2: assert(b == true); break;
+                default: assert(0);
+            }
+        }
+    }
+
+
+    /**********************************************
+     * Reverses the bits of the $(D BitArray).
+     */
+    @property BitArray reverse() @nogc pure nothrow
+    out (result)
+    {
+        assert(result == this);
+    }
+    body
+    {
+        if (_len >= 2)
+        {
+            bool t;
+            size_t lo, hi;
+
+            lo = 0;
+            hi = _len - 1;
+            for (; lo < hi; lo++, hi--)
+            {
+                t = this[lo];
+                this[lo] = this[hi];
+                this[hi] = t;
+            }
+        }
+        return this;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.reverse.unittest\n");
+
+        BitArray b;
+        static bool[5] data = [1,0,1,1,0];
+        int i;
+
+        b = BitArray(data);
+        b.reverse;
+        for (i = 0; i < data.length; i++)
+        {
+            assert(b[i] == data[4 - i]);
+        }
+    }
+
+
+    /**********************************************
+     * Sorts the $(D BitArray)'s elements.
+     */
+    @property BitArray sort() @nogc pure nothrow
+    out (result)
+    {
+        assert(result == this);
+    }
+    body
+    {
+        if (_len >= 2)
+        {
+            size_t lo, hi;
+
+            lo = 0;
+            hi = _len - 1;
+            while (1)
+            {
+                while (1)
+                {
+                    if (lo >= hi)
+                        goto Ldone;
+                    if (this[lo] == true)
+                        break;
+                    lo++;
+                }
+
+                while (1)
+                {
+                    if (lo >= hi)
+                        goto Ldone;
+                    if (this[hi] == false)
+                        break;
+                    hi--;
+                }
+
+                this[lo] = false;
+                this[hi] = true;
+
+                lo++;
+                hi--;
+            }
+        }
+    Ldone:
+        return this;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.sort.unittest\n");
+
+        __gshared size_t x = 0b1100011000;
+        __gshared ba = BitArray(10, &x);
+        ba.sort;
+        for (size_t i = 0; i < 6; i++)
+            assert(ba[i] == false);
+        for (size_t i = 6; i < 10; i++)
+            assert(ba[i] == true);
+    }
+
+
+    /***************************************
+     * Support for operators == and != for $(D BitArray).
+     */
+    bool opEquals(const ref BitArray a2) const @nogc pure nothrow
+    {
+        if (this.length != a2.length)
+            return false;
+        auto p1 = this._ptr;
+        auto p2 = a2._ptr;
+
+        if (p1[0..fullWords] != p2[0..fullWords])
+            return false;
+
+        if (!endBits)
+            return true;
+
+        auto i = fullWords;
+        return (p1[i] & endMask) == (p2[i] & endMask);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opEquals unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1];
+        static bool[] bc = [1,0,1,0,1,0,1];
+        static bool[] bd = [1,0,1,1,1];
+        static bool[] be = [1,0,1,0,1];
+        static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
+        static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+        auto c = BitArray(bc);
+        auto d = BitArray(bd);
+        auto e = BitArray(be);
+        auto f = BitArray(bf);
+        auto g = BitArray(bg);
+
+        assert(a != b);
+        assert(a != c);
+        assert(a != d);
+        assert(a == e);
+        assert(f != g);
+    }
+
+    /***************************************
+     * Supports comparison operators for $(D BitArray).
+     */
+    int opCmp(BitArray a2) const @nogc pure nothrow
+    {
+        auto lesser = this.length < a2.length ? &this : &a2;
+        size_t fullWords = lesser.fullWords;
+        size_t endBits = lesser.endBits;
+        auto p1 = this._ptr;
+        auto p2 = a2._ptr;
+
+        foreach (i; 0 .. fullWords)
+        {
+            if (p1[i] != p2[i])
+            {
+                return p1[i] & (size_t(1) << bsf(p1[i] ^ p2[i])) ? 1 : -1;
+            }
+        }
+
+        if (endBits)
+        {
+            immutable i = fullWords;
+            immutable diff = p1[i] ^ p2[i];
+            if (diff)
+            {
+                immutable index = bsf(diff);
+                if (index < endBits)
+                {
+                    return p1[i] & (size_t(1) << index) ? 1 : -1;
+                }
+            }
+        }
+
+        // Standard:
+        // A bool value can be implicitly converted to any integral type,
+        // with false becoming 0 and true becoming 1
+        return (this.length > a2.length) - (this.length < a2.length);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCmp unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1];
+        static bool[] bc = [1,0,1,0,1,0,1];
+        static bool[] bd = [1,0,1,1,1];
+        static bool[] be = [1,0,1,0,1];
+        static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
+        static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+        auto c = BitArray(bc);
+        auto d = BitArray(bd);
+        auto e = BitArray(be);
+        auto f = BitArray(bf);
+        auto g = BitArray(bg);
+
+        assert(a >  b);
+        assert(a >= b);
+        assert(a <  c);
+        assert(a <= c);
+        assert(a <  d);
+        assert(a <= d);
+        assert(a == e);
+        assert(a <= e);
+        assert(a >= e);
+        assert(f <  g);
+        assert(g <= g);
+
+        bool[] v;
+        foreach  (i; 1 .. 256)
+        {
+            v.length = i;
+            v[] = false;
+            auto x = BitArray(v);
+            v[i-1] = true;
+            auto y = BitArray(v);
+            assert(x < y);
+            assert(x <= y);
+        }
+
+        BitArray a1, a2;
+
+        for (size_t len = 4; len <= 256; len <<= 1)
+        {
+            a1.length = a2.length = len;
+            a1[len-2] = a2[len-1] = true;
+            assert(a1 > a2);
+            a1[len-2] = a2[len-1] = false;
+        }
+
+        foreach (j; 1 .. a1.length)
+        {
+            a1[j-1] = a2[j] = true;
+            assert(a1 > a2);
+            a1[j-1] = a2[j] = false;
+        }
+    }
+
+    /***************************************
+     * Support for hashing for $(D BitArray).
+     */
+    size_t toHash() const @nogc pure nothrow
+    {
+        size_t hash = 3557;
+        auto fullBytes = _len / 8;
+        foreach (i; 0 .. fullBytes)
+        {
+            hash *= 3559;
+            hash += (cast(byte*)this._ptr)[i];
+        }
+        foreach (i; 8*fullBytes .. _len)
+        {
+            hash *= 3571;
+            hash += this[i];
+        }
+        return hash;
+    }
+
+    // Explicitly undocumented. It will be removed in January 2017. @@@DEPRECATED_2017-01@@@
+    deprecated("Use the constructor instead.")
+    void init(bool[] ba) pure nothrow
+    {
+        this = BitArray(ba);
+    }
+
+    // Explicitly undocumented. It will be removed in January 2017. @@@DEPRECATED_2017-01@@@
+    deprecated("Use the constructor instead.")
+    void init(void[] v, size_t numbits) pure nothrow
+    {
+        this = BitArray(v, numbits);
+    }
+
+    /***************************************
+     * Set this $(D BitArray) to the contents of $(D ba).
+     */
+    this(bool[] ba) pure nothrow
+    {
+        length = ba.length;
+        foreach (i, b; ba)
+        {
+            this[i] = b;
+        }
+    }
+
+    // Deliberately undocumented: raw initialization of bit array.
+    this(size_t len, size_t* ptr)
+    {
+        _len = len;
+        _ptr = ptr;
+    }
+
+    /***************************************
+     * Map the $(D BitArray) onto $(D v), with $(D numbits) being the number of bits
+     * in the array. Does not copy the data. $(D v.length) must be a multiple of
+     * $(D size_t.sizeof). If there are unmapped bits in the final mapped word then
+     * these will be set to 0.
+     *
+     * This is the inverse of $(D opCast).
+     */
+    this(void[] v, size_t numbits) pure nothrow
+    in
+    {
+        assert(numbits <= v.length * 8);
+        assert(v.length % size_t.sizeof == 0);
+    }
+    body
+    {
+        _ptr = cast(size_t*)v.ptr;
+        _len = numbits;
+        if (endBits)
+        {
+            // Need to mask away extraneous bits from v.
+            _ptr[dim - 1] &= endMask;
+        }
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.init unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+
+        auto a = BitArray(ba);
+        void[] v;
+
+        v = cast(void[])a;
+        auto b = BitArray(v, a.length);
+
+        assert(b[0] == 1);
+        assert(b[1] == 0);
+        assert(b[2] == 1);
+        assert(b[3] == 0);
+        assert(b[4] == 1);
+
+        a[0] = 0;
+        assert(b[0] == 0);
+
+        assert(a == b);
+    }
+
+    /***************************************
+     * Convert to $(D void[]).
+     */
+    void[] opCast(T : void[])() @nogc pure nothrow
+    {
+        return cast(void[])_ptr[0 .. dim];
+    }
+
+    /***************************************
+     * Convert to $(D size_t[]).
+     */
+    size_t[] opCast(T : size_t[])() @nogc pure nothrow
+    {
+        return _ptr[0 .. dim];
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCast unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+
+        auto a = BitArray(ba);
+        void[] v = cast(void[])a;
+
+        assert(v.length == a.dim * size_t.sizeof);
+    }
+
+    /***************************************
+     * Support for unary operator ~ for $(D BitArray).
+     */
+    BitArray opCom() const pure nothrow
+    {
+        auto dim = this.dim;
+
+        BitArray result;
+        result.length = _len;
+
+        result._ptr[0..dim] = ~this._ptr[0..dim];
+
+        // Avoid putting garbage in extra bits
+        // Remove once we zero on length extension
+        if (endBits)
+            result._ptr[dim - 1] &= endMask;
+
+        return result;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCom unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+
+        auto a = BitArray(ba);
+        BitArray b = ~a;
+
+        assert(b[0] == 0);
+        assert(b[1] == 1);
+        assert(b[2] == 0);
+        assert(b[3] == 1);
+        assert(b[4] == 0);
+    }
+
+
+    /***************************************
+     * Support for binary bitwise operators for $(D BitArray).
+     */
+    BitArray opBinary(string op)(const BitArray e2) const pure nothrow
+        if (op == "-" || op == "&" || op == "|" || op == "^")
+    in
+    {
+        assert(_len == e2.length);
+    }
+    body
+    {
+        auto dim = this.dim;
+
+        BitArray result;
+        result.length = _len;
+
+        static if (op == "-")
+            result._ptr[0..dim] = this._ptr[0..dim] & ~e2._ptr[0..dim];
+        else
+            mixin("result._ptr[0..dim] = this._ptr[0..dim]"~op~" e2._ptr[0..dim];");
+
+        // Avoid putting garbage in extra bits
+        // Remove once we zero on length extension
+        if (endBits)
+            result._ptr[dim - 1] &= endMask;
+
+        return result;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opAnd unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        BitArray c = a & b;
+
+        assert(c[0] == 1);
+        assert(c[1] == 0);
+        assert(c[2] == 1);
+        assert(c[3] == 0);
+        assert(c[4] == 0);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opOr unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        BitArray c = a | b;
+
+        assert(c[0] == 1);
+        assert(c[1] == 0);
+        assert(c[2] == 1);
+        assert(c[3] == 1);
+        assert(c[4] == 1);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opXor unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        BitArray c = a ^ b;
+
+        assert(c[0] == 0);
+        assert(c[1] == 0);
+        assert(c[2] == 0);
+        assert(c[3] == 1);
+        assert(c[4] == 1);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opSub unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        BitArray c = a - b;
+
+        assert(c[0] == 0);
+        assert(c[1] == 0);
+        assert(c[2] == 0);
+        assert(c[3] == 0);
+        assert(c[4] == 1);
+    }
+
+
+    /***************************************
+     * Support for operator op= for $(D BitArray).
+     */
+    BitArray opOpAssign(string op)(const BitArray e2) @nogc pure nothrow
+        if (op == "-" || op == "&" || op == "|" || op == "^")
+    in
+    {
+        assert(_len == e2.length);
+    }
+    body
+    {
+        foreach (i; 0 .. fullWords)
+        {
+            static if (op == "-")
+                _ptr[i] &= ~e2._ptr[i];
+            else
+                mixin("_ptr[i] "~op~"= e2._ptr[i];");
+        }
+        if (!endBits)
+            return this;
+
+        size_t i = fullWords;
+        size_t endWord = _ptr[i];
+        static if (op == "-")
+            endWord &= ~e2._ptr[i];
+        else
+            mixin("endWord "~op~"= e2._ptr[i];");
+        _ptr[i] = (_ptr[i] & ~endMask) | (endWord & endMask);
+
+        return this;
+    }
+
+    unittest
+    {
+        static bool[] ba = [1,0,1,0,1,1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+        BitArray c = a;
+        c.length = 5;
+        c &= b;
+        assert(a[5] == 1);
+        assert(a[6] == 0);
+        assert(a[7] == 1);
+        assert(a[8] == 0);
+        assert(a[9] == 1);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opAndAssign unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        a &= b;
+        assert(a[0] == 1);
+        assert(a[1] == 0);
+        assert(a[2] == 1);
+        assert(a[3] == 0);
+        assert(a[4] == 0);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opOrAssign unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        a |= b;
+        assert(a[0] == 1);
+        assert(a[1] == 0);
+        assert(a[2] == 1);
+        assert(a[3] == 1);
+        assert(a[4] == 1);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opXorAssign unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        a ^= b;
+        assert(a[0] == 0);
+        assert(a[1] == 0);
+        assert(a[2] == 0);
+        assert(a[3] == 1);
+        assert(a[4] == 1);
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opSubAssign unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+        static bool[] bb = [1,0,1,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+
+        a -= b;
+        assert(a[0] == 0);
+        assert(a[1] == 0);
+        assert(a[2] == 0);
+        assert(a[3] == 0);
+        assert(a[4] == 1);
+    }
+
+    /***************************************
+     * Support for operator ~= for $(D BitArray).
+     * $(RED Warning: This will overwrite a bit in the final word
+     * of the current underlying data regardless of whether it is
+     * shared between BitArray objects. i.e. D dynamic array
+     * concatenation semantics are not followed)
+     */
+
+    BitArray opCatAssign(bool b) pure nothrow
+    {
+        length = _len + 1;
+        this[_len - 1] = b;
+        return this;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCatAssign unittest\n");
+
+        static bool[] ba = [1,0,1,0,1];
+
+        auto a = BitArray(ba);
+        BitArray b;
+
+        b = (a ~= true);
+        assert(a[0] == 1);
+        assert(a[1] == 0);
+        assert(a[2] == 1);
+        assert(a[3] == 0);
+        assert(a[4] == 1);
+        assert(a[5] == 1);
+
+        assert(b == a);
+    }
+
+    /***************************************
+     * ditto
+     */
+
+    BitArray opCatAssign(BitArray b) pure nothrow
+    {
+        auto istart = _len;
+        length = _len + b.length;
+        for (auto i = istart; i < _len; i++)
+            this[i] = b[i - istart];
+        return this;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCatAssign unittest\n");
+
+        static bool[] ba = [1,0];
+        static bool[] bb = [0,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+        BitArray c;
+
+        c = (a ~= b);
+        assert(a.length == 5);
+        assert(a[0] == 1);
+        assert(a[1] == 0);
+        assert(a[2] == 0);
+        assert(a[3] == 1);
+        assert(a[4] == 0);
+
+        assert(c == a);
+    }
+
+    /***************************************
+     * Support for binary operator ~ for $(D BitArray).
+     */
+    BitArray opCat(bool b) const pure nothrow
+    {
+        BitArray r;
+
+        r = this.dup;
+        r.length = _len + 1;
+        r[_len] = b;
+        return r;
+    }
+
+    /** ditto */
+    BitArray opCat_r(bool b) const pure nothrow
+    {
+        BitArray r;
+
+        r.length = _len + 1;
+        r[0] = b;
+        foreach (i; 0 .. _len)
+            r[1 + i] = this[i];
+        return r;
+    }
+
+    /** ditto */
+    BitArray opCat(BitArray b) const pure nothrow
+    {
+        BitArray r;
+
+        r = this.dup;
+        r ~= b;
+        return r;
+    }
+
+    unittest
+    {
+        debug(bitarray) printf("BitArray.opCat unittest\n");
+
+        static bool[] ba = [1,0];
+        static bool[] bb = [0,1,0];
+
+        auto a = BitArray(ba);
+        auto b = BitArray(bb);
+        BitArray c;
+
+        c = (a ~ b);
+        assert(c.length == 5);
+        assert(c[0] == 1);
+        assert(c[1] == 0);
+        assert(c[2] == 0);
+        assert(c[3] == 1);
+        assert(c[4] == 0);
+
+        c = (a ~ true);
+        assert(c.length == 3);
+        assert(c[0] == 1);
+        assert(c[1] == 0);
+        assert(c[2] == 1);
+
+        c = (false ~ a);
+        assert(c.length == 3);
+        assert(c[0] == 0);
+        assert(c[1] == 1);
+        assert(c[2] == 0);
+    }
+
+    // Rolls double word (upper, lower) to the right by n bits and returns the
+    // lower word of the result.
+    private static size_t rollRight()(size_t upper, size_t lower, size_t nbits)
+        pure @safe nothrow @nogc
+    in
+    {
+        assert(nbits < bitsPerSizeT);
+    }
+    body
+    {
+        return (upper << (bitsPerSizeT - nbits)) | (lower >> nbits);
+    }
+
+    unittest
+    {
+        static if (size_t.sizeof == 8)
+        {
+            size_t x = 0x12345678_90ABCDEF;
+            size_t y = 0xFEDBCA09_87654321;
+
+            assert(rollRight(x, y, 32) == 0x90ABCDEF_FEDBCA09);
+            assert(rollRight(y, x, 4) == 0x11234567_890ABCDE);
+        }
+        else static if (size_t.sizeof == 4)
+        {
+            size_t x = 0x12345678;
+            size_t y = 0x90ABCDEF;
+
+            assert(rollRight(x, y, 16) == 0x567890AB);
+            assert(rollRight(y, x, 4) == 0xF1234567);
+        }
+        else
+            static assert(0, "Unsupported size_t width");
+    }
+
+    // Rolls double word (upper, lower) to the left by n bits and returns the
+    // upper word of the result.
+    private static size_t rollLeft()(size_t upper, size_t lower, size_t nbits)
+        pure @safe nothrow @nogc
+    in
+    {
+        assert(nbits < bitsPerSizeT);
+    }
+    body
+    {
+        return (upper << nbits) | (lower >> (bitsPerSizeT - nbits));
+    }
+
+    unittest
+    {
+        static if (size_t.sizeof == 8)
+        {
+            size_t x = 0x12345678_90ABCDEF;
+            size_t y = 0xFEDBCA09_87654321;
+
+            assert(rollLeft(x, y, 32) == 0x90ABCDEF_FEDBCA09);
+            assert(rollLeft(y, x, 4) == 0xEDBCA098_76543211);
+        }
+        else static if (size_t.sizeof == 4)
+        {
+            size_t x = 0x12345678;
+            size_t y = 0x90ABCDEF;
+
+            assert(rollLeft(x, y, 16) == 0x567890AB);
+            assert(rollLeft(y, x, 4) == 0x0ABCDEF1);
+        }
+    }
+
+    /**
+     * Operator $(D <<=) support.
+     *
+     * Shifts all the bits in the array to the left by the given number of
+     * bits.  The leftmost bits are dropped, and 0's are appended to the end
+     * to fill up the vacant bits.
+     *
+     * $(RED Warning: unused bits in the final word up to the next word
+     * boundary may be overwritten by this operation. It does not attempt to
+     * preserve bits past the end of the array.)
+     */
+    void opOpAssign(string op)(size_t nbits) @nogc pure nothrow
+        if (op == "<<")
+    {
+        size_t wordsToShift = nbits / bitsPerSizeT;
+        size_t bitsToShift = nbits % bitsPerSizeT;
+
+        if (wordsToShift < dim)
+        {
+            foreach_reverse (i; 1 .. dim - wordsToShift)
+            {
+                _ptr[i + wordsToShift] = rollLeft(_ptr[i], _ptr[i-1],
+                                                 bitsToShift);
+            }
+            _ptr[wordsToShift] = rollLeft(_ptr[0], 0, bitsToShift);
+        }
+
+        import std.algorithm : min;
+        foreach (i; 0 .. min(wordsToShift, dim))
+        {
+            _ptr[i] = 0;
+        }
+    }
+
+    /**
+     * Operator $(D >>=) support.
+     *
+     * Shifts all the bits in the array to the right by the given number of
+     * bits.  The rightmost bits are dropped, and 0's are inserted at the back
+     * to fill up the vacant bits.
+     *
+     * $(RED Warning: unused bits in the final word up to the next word
+     * boundary may be overwritten by this operation. It does not attempt to
+     * preserve bits past the end of the array.)
+     */
+    void opOpAssign(string op)(size_t nbits) @nogc pure nothrow
+        if (op == ">>")
+    {
+        size_t wordsToShift = nbits / bitsPerSizeT;
+        size_t bitsToShift = nbits % bitsPerSizeT;
+
+        if (wordsToShift + 1 < dim)
+        {
+            foreach (i; 0 .. dim - wordsToShift - 1)
+            {
+                _ptr[i] = rollRight(_ptr[i + wordsToShift + 1],
+                                   _ptr[i + wordsToShift], bitsToShift);
+            }
+        }
+
+        // The last word needs some care, as it must shift in 0's from past the
+        // end of the array.
+        if (wordsToShift < dim)
+        {
+            _ptr[dim - wordsToShift - 1] = rollRight(0, _ptr[dim - 1] & endMask,
+                                                    bitsToShift);
+        }
+
+        import std.algorithm : min;
+        foreach (i; 0 .. min(wordsToShift, dim))
+        {
+            _ptr[dim - i - 1] = 0;
+        }
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        auto b = BitArray([1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]);
+
+        b <<= 1;
+        assert(format("%b", b) == "01100_10101101");
+
+        b >>= 1;
+        assert(format("%b", b) == "11001_01011010");
+
+        b <<= 4;
+        assert(format("%b", b) == "00001_10010101");
+
+        b >>= 5;
+        assert(format("%b", b) == "10010_10100000");
+
+        b <<= 13;
+        assert(format("%b", b) == "00000_00000000");
+
+        b = BitArray([1, 0, 1, 1, 0, 1, 1, 1]);
+        b >>= 8;
+        assert(format("%b", b) == "00000000");
+
+    }
+
+    // Test multi-word case
+    unittest
+    {
+        import std.format : format;
+
+        // This has to be long enough to occupy more than one size_t. On 64-bit
+        // machines, this would be at least 64 bits.
+        auto b = BitArray([
+            1, 0, 0, 0, 0, 0, 0, 0,  1, 1, 0, 0, 0, 0, 0, 0,
+            1, 1, 1, 0, 0, 0, 0, 0,  1, 1, 1, 1, 0, 0, 0, 0,
+            1, 1, 1, 1, 1, 0, 0, 0,  1, 1, 1, 1, 1, 1, 0, 0,
+            1, 1, 1, 1, 1, 1, 1, 0,  1, 1, 1, 1, 1, 1, 1, 1,
+            1, 0, 1, 0, 1, 0, 1, 0,  0, 1, 0, 1, 0, 1, 0, 1,
+        ]);
+        b <<= 8;
+        assert(format("%b", b) ==
+               "00000000_10000000_"~
+               "11000000_11100000_"~
+               "11110000_11111000_"~
+               "11111100_11111110_"~
+               "11111111_10101010");
+
+        // Test right shift of more than one size_t's worth of bits
+        b <<= 68;
+        assert(format("%b", b) ==
+               "00000000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00001000");
+
+        b = BitArray([
+            1, 0, 0, 0, 0, 0, 0, 0,  1, 1, 0, 0, 0, 0, 0, 0,
+            1, 1, 1, 0, 0, 0, 0, 0,  1, 1, 1, 1, 0, 0, 0, 0,
+            1, 1, 1, 1, 1, 0, 0, 0,  1, 1, 1, 1, 1, 1, 0, 0,
+            1, 1, 1, 1, 1, 1, 1, 0,  1, 1, 1, 1, 1, 1, 1, 1,
+            1, 0, 1, 0, 1, 0, 1, 0,  0, 1, 0, 1, 0, 1, 0, 1,
+        ]);
+        b >>= 8;
+        assert(format("%b", b) ==
+               "11000000_11100000_"~
+               "11110000_11111000_"~
+               "11111100_11111110_"~
+               "11111111_10101010_"~
+               "01010101_00000000");
+
+        // Test left shift of more than 1 size_t's worth of bits
+        b >>= 68;
+        assert(format("%b", b) ==
+               "01010000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00000000_"~
+               "00000000_00000000");
+    }
+
+    /***************************************
+     * Return a string representation of this BitArray.
+     *
+     * Two format specifiers are supported:
+     * $(LI $(B %s) which prints the bits as an array, and)
+     * $(LI $(B %b) which prints the bits as 8-bit byte packets)
+     * separated with an underscore.
+     */
+    void toString(scope void delegate(const(char)[]) sink,
+                  FormatSpec!char fmt) const
+    {
+        switch(fmt.spec)
+        {
+            case 'b':
+                return formatBitString(sink);
+            case 's':
+                return formatBitArray(sink);
+            default:
+                throw new Exception("Unknown format specifier: %" ~ fmt.spec);
+        }
+    }
+
+    ///
+    unittest
+    {
+        import std.format : format;
+
+        debug(bitarray) printf("BitArray.toString unittest\n");
+        auto b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
+
+        auto s1 = format("%s", b);
+        assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]");
+
+        auto s2 = format("%b", b);
+        assert(s2 == "00001111_00001111");
+    }
+
+    /***************************************
+     * Return a lazy range of the indices of set bits.
+     */
+    @property auto bitsSet() const nothrow
+    {
+        import std.algorithm : filter, map, joiner;
+        import std.range : iota;
+
+        return iota(dim).
+               filter!(i => _ptr[i])().
+               map!(i => BitsSet!size_t(_ptr[i], i * bitsPerSizeT))().
+               joiner();
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm : equal;
+
+        auto b1 = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
+        assert(b1.bitsSet.equal([4, 5, 6, 7, 12, 13, 14, 15]));
+
+        BitArray b2;
+        b2.length = 1000;
+        b2[333] = true;
+        b2[666] = true;
+        b2[999] = true;
+        assert(b2.bitsSet.equal([333, 666, 999]));
+    }
+
+    unittest
+    {
+        import std.algorithm : equal;
+        import std.range : iota;
+
+        debug(bitarray) printf("BitArray.bitsSet unittest\n");
+        BitArray b;
+        enum wordBits = size_t.sizeof * 8;
+        b = BitArray([size_t.max], 0);
+        assert(b.bitsSet.empty);
+        b = BitArray([size_t.max], 1);
+        assert(b.bitsSet.equal([0]));
+        b = BitArray([size_t.max], wordBits);
+        assert(b.bitsSet.equal(iota(wordBits)));
+        b = BitArray([size_t.max, size_t.max], wordBits);
+        assert(b.bitsSet.equal(iota(wordBits)));
+        b = BitArray([size_t.max, size_t.max], wordBits + 1);
+        assert(b.bitsSet.equal(iota(wordBits + 1)));
+        b = BitArray([size_t.max, size_t.max], wordBits * 2);
+        assert(b.bitsSet.equal(iota(wordBits * 2)));
+    }
+
+    private void formatBitString(scope void delegate(const(char)[]) sink) const
+    {
+        if (!length)
+            return;
+
+        auto leftover = _len % 8;
+        foreach (idx; 0 .. leftover)
+        {
+            char[1] res = cast(char)(this[idx] + '0');
+            sink.put(res[]);
+        }
+
+        if (leftover && _len > 8)
+            sink.put("_");
+
+        size_t count;
+        foreach (idx; leftover .. _len)
+        {
+            char[1] res = cast(char)(this[idx] + '0');
+            sink.put(res[]);
+            if (++count == 8 && idx != _len - 1)
+            {
+                sink.put("_");
+                count = 0;
+            }
+        }
+    }
+
+    private void formatBitArray(scope void delegate(const(char)[]) sink) const
+    {
+        sink("[");
+        foreach (idx; 0 .. _len)
+        {
+            char[1] res = cast(char)(this[idx] + '0');
+            sink(res[]);
+            if (idx+1 < _len)
+                sink(", ");
+        }
+        sink("]");
+    }
+}
+
+unittest
+{
+    import std.format : format;
+
+    BitArray b;
+
+    b = BitArray([]);
+    assert(format("%s", b) == "[]");
+    assert(format("%b", b) is null);
+
+    b = BitArray([1]);
+    assert(format("%s", b) == "[1]");
+    assert(format("%b", b) == "1");
+
+    b = BitArray([0, 0, 0, 0]);
+    assert(format("%b", b) == "0000");
+
+    b = BitArray([0, 0, 0, 0, 1, 1, 1, 1]);
+    assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1]");
+    assert(format("%b", b) == "00001111");
+
+    b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
+    assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]");
+    assert(format("%b", b) == "00001111_00001111");
+
+    b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1]);
+    assert(format("%b", b) == "1_00001111");
+
+    b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
+    assert(format("%b", b) == "1_00001111_00001111");
+}
+
+/++
+    Swaps the endianness of the given integral value or character.
+  +/
+T swapEndian(T)(T val) @safe pure nothrow @nogc
+    if(isIntegral!T || isSomeChar!T || isBoolean!T)
+{
+    static if(val.sizeof == 1)
+        return val;
+    else static if(isUnsigned!T)
+        return swapEndianImpl(val);
+    else static if(isIntegral!T)
+        return cast(T)swapEndianImpl(cast(Unsigned!T) val);
+    else static if(is(Unqual!T == wchar))
+        return cast(T)swapEndian(cast(ushort)val);
+    else static if(is(Unqual!T == dchar))
+        return cast(T)swapEndian(cast(uint)val);
+    else
+        static assert(0, T.stringof ~ " unsupported by swapEndian.");
+}
+
+private ushort swapEndianImpl(ushort val) @safe pure nothrow @nogc
+{
+    return ((val & 0xff00U) >> 8) |
+           ((val & 0x00ffU) << 8);
+}
+
+private uint swapEndianImpl(uint val) @trusted pure nothrow @nogc
+{
+    import core.bitop: bswap;
+    return bswap(val);
+}
+
+private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc
+{
+    import core.bitop: bswap;
+    immutable ulong res = bswap(cast(uint)val);
+    return res << 32 | bswap(cast(uint)(val >> 32));
+}
+
+unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar))
+    {
+        scope(failure) writefln("Failed type: %s", T.stringof);
+        T val;
+        const T cval;
+        immutable T ival;
+
+        assert(swapEndian(swapEndian(val)) == val);
+        assert(swapEndian(swapEndian(cval)) == cval);
+        assert(swapEndian(swapEndian(ival)) == ival);
+        assert(swapEndian(swapEndian(T.min)) == T.min);
+        assert(swapEndian(swapEndian(T.max)) == T.max);
+
+        foreach(i; 2 .. 10)
+        {
+            immutable T maxI = cast(T)(T.max / i);
+            immutable T minI = cast(T)(T.min / i);
+
+            assert(swapEndian(swapEndian(maxI)) == maxI);
+
+            static if(isSigned!T)
+                assert(swapEndian(swapEndian(minI)) == minI);
+        }
+
+        static if(isSigned!T)
+            assert(swapEndian(swapEndian(cast(T)0)) == 0);
+
+        // used to trigger BUG6354
+        static if(T.sizeof > 1 && isUnsigned!T)
+        {
+            T left = 0xffU;
+            left <<= (T.sizeof - 1) * 8;
+            T right = 0xffU;
+
+            for(size_t i = 1; i < T.sizeof; ++i)
+            {
+                assert(swapEndian(left) == right);
+                assert(swapEndian(right) == left);
+                left >>= 8;
+                right <<= 8;
+            }
+        }
+    }
+}
+
+
+private union EndianSwapper(T)
+    if(canSwapEndianness!T)
+{
+    Unqual!T value;
+    ubyte[T.sizeof] array;
+
+    static if(is(FloatingPointTypeOf!T == float))
+        uint  intValue;
+    else static if(is(FloatingPointTypeOf!T == double))
+        ulong intValue;
+
+}
+
+
+/++
+    Converts the given value from the native endianness to big endian and
+    returns it as a $(D ubyte[n]) where $(D n) is the size of the given type.
+
+    Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value
+    as a regular one (and in the case of floating point values, it's necessary,
+    because the FPU will mess up any swapped floating point values. So, you
+    can't actually have swapped floating point values as floating point values).
+
+    $(D real) is not supported, because its size is implementation-dependent
+    and therefore could vary from machine to machine (which could make it
+    unusable if you tried to transfer it to another machine).
+  +/
+auto nativeToBigEndian(T)(T val) @safe pure nothrow @nogc
+    if(canSwapEndianness!T)
+{
+    return nativeToBigEndianImpl(val);
+}
+
+///
+unittest
+{
+    int i = 12345;
+    ubyte[4] swappedI = nativeToBigEndian(i);
+    assert(i == bigEndianToNative!int(swappedI));
+
+    double d = 123.45;
+    ubyte[8] swappedD = nativeToBigEndian(d);
+    assert(d == bigEndianToNative!double(swappedD));
+}
+
+private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc
+    if(isIntegral!T || isSomeChar!T || isBoolean!T)
+{
+    EndianSwapper!T es = void;
+
+    version(LittleEndian)
+        es.value = swapEndian(val);
+    else
+        es.value = val;
+
+    return es.array;
+}
+
+private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc
+    if(isFloatOrDouble!T)
+{
+    version(LittleEndian)
+        return floatEndianImpl!(T, true)(val);
+    else
+        return floatEndianImpl!(T, false)(val);
+}
+
+unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong,
+                         char, wchar, dchar
+        /* The trouble here is with floats and doubles being compared against nan
+         * using a bit compare. There are two kinds of nans, quiet and signaling.
+         * When a nan passes through the x87, it converts signaling to quiet.
+         * When a nan passes through the XMM, it does not convert signaling to quiet.
+         * float.init is a signaling nan.
+         * The binary API sometimes passes the data through the XMM, sometimes through
+         * the x87, meaning these will fail the 'is' bit compare under some circumstances.
+         * I cannot think of a fix for this that makes consistent sense.
+         */
+                          /*,float, double*/))
+    {
+        scope(failure) writefln("Failed type: %s", T.stringof);
+        T val;
+        const T cval;
+        immutable T ival;
+
+        //is instead of == because of NaN for floating point values.
+        assert(bigEndianToNative!T(nativeToBigEndian(val)) is val);
+        assert(bigEndianToNative!T(nativeToBigEndian(cval)) is cval);
+        assert(bigEndianToNative!T(nativeToBigEndian(ival)) is ival);
+        assert(bigEndianToNative!T(nativeToBigEndian(T.min)) == T.min);
+        assert(bigEndianToNative!T(nativeToBigEndian(T.max)) == T.max);
+
+        static if(isSigned!T)
+            assert(bigEndianToNative!T(nativeToBigEndian(cast(T)0)) == 0);
+
+        static if(!is(T == bool))
+        {
+            foreach(i; [2, 4, 6, 7, 9, 11])
+            {
+                immutable T maxI = cast(T)(T.max / i);
+                immutable T minI = cast(T)(T.min / i);
+
+                assert(bigEndianToNative!T(nativeToBigEndian(maxI)) == maxI);
+
+                static if(T.sizeof > 1)
+                    assert(nativeToBigEndian(maxI) != nativeToLittleEndian(maxI));
+                else
+                    assert(nativeToBigEndian(maxI) == nativeToLittleEndian(maxI));
+
+                static if(isSigned!T)
+                {
+                    assert(bigEndianToNative!T(nativeToBigEndian(minI)) == minI);
+
+                    static if(T.sizeof > 1)
+                        assert(nativeToBigEndian(minI) != nativeToLittleEndian(minI));
+                    else
+                        assert(nativeToBigEndian(minI) == nativeToLittleEndian(minI));
+                }
+            }
+        }
+
+        static if(isUnsigned!T || T.sizeof == 1 || is(T == wchar))
+            assert(nativeToBigEndian(T.max) == nativeToLittleEndian(T.max));
+        else
+            assert(nativeToBigEndian(T.max) != nativeToLittleEndian(T.max));
+
+        static if(isUnsigned!T || T.sizeof == 1 || isSomeChar!T)
+            assert(nativeToBigEndian(T.min) == nativeToLittleEndian(T.min));
+        else
+            assert(nativeToBigEndian(T.min) != nativeToLittleEndian(T.min));
+    }
+}
+
+
+/++
+    Converts the given value from big endian to the native endianness and
+    returns it. The value is given as a $(D ubyte[n]) where $(D n) is the size
+    of the target type. You must give the target type as a template argument,
+    because there are multiple types with the same size and so the type of the
+    argument is not enough to determine the return type.
+
+    Taking a $(D ubyte[n]) helps prevent accidentally using a swapped value
+    as a regular one (and in the case of floating point values, it's necessary,
+    because the FPU will mess up any swapped floating point values. So, you
+    can't actually have swapped floating point values as floating point values).
+  +/
+T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if(canSwapEndianness!T && n == T.sizeof)
+{
+    return bigEndianToNativeImpl!(T, n)(val);
+}
+
+///
+unittest
+{
+    ushort i = 12345;
+    ubyte[2] swappedI = nativeToBigEndian(i);
+    assert(i == bigEndianToNative!ushort(swappedI));
+
+    dchar c = 'D';
+    ubyte[4] swappedC = nativeToBigEndian(c);
+    assert(c == bigEndianToNative!dchar(swappedC));
+}
+
+private T bigEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if((isIntegral!T || isSomeChar!T || isBoolean!T) &&
+       n == T.sizeof)
+{
+    EndianSwapper!T es = void;
+    es.array = val;
+
+    version(LittleEndian)
+        immutable retval = swapEndian(es.value);
+    else
+        immutable retval = es.value;
+
+    return retval;
+}
+
+private T bigEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if(isFloatOrDouble!T && n == T.sizeof)
+{
+    version(LittleEndian)
+        return cast(T) floatEndianImpl!(n, true)(val);
+    else
+        return cast(T) floatEndianImpl!(n, false)(val);
+}
+
+
+/++
+    Converts the given value from the native endianness to little endian and
+    returns it as a $(D ubyte[n]) where $(D n) is the size of the given type.
+
+    Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value
+    as a regular one (and in the case of floating point values, it's necessary,
+    because the FPU will mess up any swapped floating point values. So, you
+    can't actually have swapped floating point values as floating point values).
+  +/
+auto nativeToLittleEndian(T)(T val) @safe pure nothrow @nogc
+    if(canSwapEndianness!T)
+{
+    return nativeToLittleEndianImpl(val);
+}
+
+///
+unittest
+{
+    int i = 12345;
+    ubyte[4] swappedI = nativeToLittleEndian(i);
+    assert(i == littleEndianToNative!int(swappedI));
+
+    double d = 123.45;
+    ubyte[8] swappedD = nativeToLittleEndian(d);
+    assert(d == littleEndianToNative!double(swappedD));
+}
+
+private auto nativeToLittleEndianImpl(T)(T val) @safe pure nothrow @nogc
+    if(isIntegral!T || isSomeChar!T || isBoolean!T)
+{
+    EndianSwapper!T es = void;
+
+    version(BigEndian)
+        es.value = swapEndian(val);
+    else
+        es.value = val;
+
+    return es.array;
+}
+
+private auto nativeToLittleEndianImpl(T)(T val) @safe pure nothrow @nogc
+    if(isFloatOrDouble!T)
+{
+    version(BigEndian)
+        return floatEndianImpl!(T, true)(val);
+    else
+        return floatEndianImpl!(T, false)(val);
+}
+
+unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong,
+                         char, wchar, dchar/*,
+                         float, double*/))
+    {
+        scope(failure) writefln("Failed type: %s", T.stringof);
+        T val;
+        const T cval;
+        immutable T ival;
+
+        //is instead of == because of NaN for floating point values.
+        assert(littleEndianToNative!T(nativeToLittleEndian(val)) is val);
+        assert(littleEndianToNative!T(nativeToLittleEndian(cval)) is cval);
+        assert(littleEndianToNative!T(nativeToLittleEndian(ival)) is ival);
+        assert(littleEndianToNative!T(nativeToLittleEndian(T.min)) == T.min);
+        assert(littleEndianToNative!T(nativeToLittleEndian(T.max)) == T.max);
+
+        static if(isSigned!T)
+            assert(littleEndianToNative!T(nativeToLittleEndian(cast(T)0)) == 0);
+
+        static if(!is(T == bool))
+        {
+            foreach(i; 2 .. 10)
+            {
+                immutable T maxI = cast(T)(T.max / i);
+                immutable T minI = cast(T)(T.min / i);
+
+                assert(littleEndianToNative!T(nativeToLittleEndian(maxI)) == maxI);
+
+                static if(isSigned!T)
+                    assert(littleEndianToNative!T(nativeToLittleEndian(minI)) == minI);
+            }
+        }
+    }
+}
+
+
+/++
+    Converts the given value from little endian to the native endianness and
+    returns it. The value is given as a $(D ubyte[n]) where $(D n) is the size
+    of the target type. You must give the target type as a template argument,
+    because there are multiple types with the same size and so the type of the
+    argument is not enough to determine the return type.
+
+    Taking a $(D ubyte[n]) helps prevent accidentally using a swapped value
+    as a regular one (and in the case of floating point values, it's necessary,
+    because the FPU will mess up any swapped floating point values. So, you
+    can't actually have swapped floating point values as floating point values).
+
+    $(D real) is not supported, because its size is implementation-dependent
+    and therefore could vary from machine to machine (which could make it
+    unusable if you tried to transfer it to another machine).
+  +/
+T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if(canSwapEndianness!T && n == T.sizeof)
+{
+    return littleEndianToNativeImpl!T(val);
+}
+
+///
+unittest
+{
+    ushort i = 12345;
+    ubyte[2] swappedI = nativeToLittleEndian(i);
+    assert(i == littleEndianToNative!ushort(swappedI));
+
+    dchar c = 'D';
+    ubyte[4] swappedC = nativeToLittleEndian(c);
+    assert(c == littleEndianToNative!dchar(swappedC));
+}
+
+private T littleEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if((isIntegral!T || isSomeChar!T || isBoolean!T) &&
+       n == T.sizeof)
+{
+    EndianSwapper!T es = void;
+    es.array = val;
+
+    version(BigEndian)
+        immutable retval = swapEndian(es.value);
+    else
+        immutable retval = es.value;
+
+    return retval;
+}
+
+private T littleEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
+    if(((isFloatOrDouble!T) &&
+       n == T.sizeof))
+{
+    version(BigEndian)
+        return floatEndianImpl!(n, true)(val);
+    else
+        return floatEndianImpl!(n, false)(val);
+}
+
+private auto floatEndianImpl(T, bool swap)(T val) @safe pure nothrow @nogc
+    if(isFloatOrDouble!T)
+{
+    EndianSwapper!T es = void;
+    es.value = val;
+
+    static if(swap)
+        es.intValue = swapEndian(es.intValue);
+
+    return es.array;
+}
+
+private auto floatEndianImpl(size_t n, bool swap)(ubyte[n] val) @safe pure nothrow @nogc
+    if(n == 4 || n == 8)
+{
+    static if(n == 4)       EndianSwapper!float es = void;
+    else static if(n == 8)  EndianSwapper!double es = void;
+
+    es.array = val;
+
+    static if(swap)
+        es.intValue = swapEndian(es.intValue);
+
+    return es.value;
+}
+
+private template isFloatOrDouble(T)
+{
+    enum isFloatOrDouble = isFloatingPoint!T &&
+                           !is(Unqual!(FloatingPointTypeOf!T) == real);
+}
+
+unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(float, double))
+    {
+        static assert(isFloatOrDouble!(T));
+        static assert(isFloatOrDouble!(const T));
+        static assert(isFloatOrDouble!(immutable T));
+        static assert(isFloatOrDouble!(shared T));
+        static assert(isFloatOrDouble!(shared(const T)));
+        static assert(isFloatOrDouble!(shared(immutable T)));
+    }
+
+    static assert(!isFloatOrDouble!(real));
+    static assert(!isFloatOrDouble!(const real));
+    static assert(!isFloatOrDouble!(immutable real));
+    static assert(!isFloatOrDouble!(shared real));
+    static assert(!isFloatOrDouble!(shared(const real)));
+    static assert(!isFloatOrDouble!(shared(immutable real)));
+}
+
+private template canSwapEndianness(T)
+{
+    enum canSwapEndianness = isIntegral!T ||
+                             isSomeChar!T ||
+                             isBoolean!T ||
+                             isFloatOrDouble!T;
+}
+
+unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(bool, ubyte, byte, ushort, short, uint, int, ulong,
+                         long, char, wchar, dchar, float, double))
+    {
+        static assert(canSwapEndianness!(T));
+        static assert(canSwapEndianness!(const T));
+        static assert(canSwapEndianness!(immutable T));
+        static assert(canSwapEndianness!(shared(T)));
+        static assert(canSwapEndianness!(shared(const T)));
+        static assert(canSwapEndianness!(shared(immutable T)));
+    }
+
+    //!
+    foreach(T; AliasSeq!(real, string, wstring, dstring))
+    {
+        static assert(!canSwapEndianness!(T));
+        static assert(!canSwapEndianness!(const T));
+        static assert(!canSwapEndianness!(immutable T));
+        static assert(!canSwapEndianness!(shared(T)));
+        static assert(!canSwapEndianness!(shared(const T)));
+        static assert(!canSwapEndianness!(shared(immutable T)));
+    }
+}
+
+/++
+    Takes a range of $(D ubyte)s and converts the first $(D T.sizeof) bytes to
+    $(D T). The value returned is converted from the given endianness to the
+    native endianness. The range is not consumed.
+
+    Params:
+        T     = The integral type to convert the first $(D T.sizeof) bytes to.
+        endianness = The endianness that the bytes are assumed to be in.
+        range = The range to read from.
+        index = The index to start reading from (instead of starting at the
+                front). If index is a pointer, then it is updated to the index
+                after the bytes read. The overloads with index are only
+                available if $(D hasSlicing!R) is $(D true).
+  +/
+
+T peek(T, Endian endianness = Endian.bigEndian, R)(R range)
+    if (canSwapEndianness!T &&
+        isForwardRange!R &&
+        is(ElementType!R : const ubyte))
+{
+    static if(hasSlicing!R)
+        const ubyte[T.sizeof] bytes = range[0 .. T.sizeof];
+    else
+    {
+        ubyte[T.sizeof] bytes;
+        //Make sure that range is not consumed, even if it's a class.
+        range = range.save;
+
+        foreach(ref e; bytes)
+        {
+            e = range.front;
+            range.popFront();
+        }
+    }
+
+    static if(endianness == Endian.bigEndian)
+        return bigEndianToNative!T(bytes);
+    else
+        return littleEndianToNative!T(bytes);
+}
+
+/++ Ditto +/
+T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t index)
+    if(canSwapEndianness!T &&
+       isForwardRange!R &&
+       hasSlicing!R &&
+       is(ElementType!R : const ubyte))
+{
+    return peek!(T, endianness)(range, &index);
+}
+
+/++ Ditto +/
+T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t* index)
+    if(canSwapEndianness!T &&
+       isForwardRange!R &&
+       hasSlicing!R &&
+       is(ElementType!R : const ubyte))
+{
+    assert(index);
+
+    immutable begin = *index;
+    immutable end = begin + T.sizeof;
+    const ubyte[T.sizeof] bytes = range[begin .. end];
+    *index = end;
+
+    static if(endianness == Endian.bigEndian)
+        return bigEndianToNative!T(bytes);
+    else
+        return littleEndianToNative!T(bytes);
+}
+
+///
+unittest
+{
+    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
+    assert(buffer.peek!uint() == 17110537);
+    assert(buffer.peek!ushort() == 261);
+    assert(buffer.peek!ubyte() == 1);
+
+    assert(buffer.peek!uint(2) == 369700095);
+    assert(buffer.peek!ushort(2) == 5641);
+    assert(buffer.peek!ubyte(2) == 22);
+
+    size_t index = 0;
+    assert(buffer.peek!ushort(&index) == 261);
+    assert(index == 2);
+
+    assert(buffer.peek!uint(&index) == 369700095);
+    assert(index == 6);
+
+    assert(buffer.peek!ubyte(&index) == 8);
+    assert(index == 7);
+}
+
+unittest
+{
+    {
+        //bool
+        ubyte[] buffer = [0, 1];
+        assert(buffer.peek!bool() == false);
+        assert(buffer.peek!bool(1) == true);
+
+        size_t index = 0;
+        assert(buffer.peek!bool(&index) == false);
+        assert(index == 1);
+
+        assert(buffer.peek!bool(&index) == true);
+        assert(index == 2);
+    }
+
+    {
+        //char (8bit)
+        ubyte[] buffer = [97, 98, 99, 100];
+        assert(buffer.peek!char() == 'a');
+        assert(buffer.peek!char(1) == 'b');
+
+        size_t index = 0;
+        assert(buffer.peek!char(&index) == 'a');
+        assert(index == 1);
+
+        assert(buffer.peek!char(&index) == 'b');
+        assert(index == 2);
+    }
+
+    {
+        //wchar (16bit - 2x ubyte)
+        ubyte[] buffer = [1, 5, 32, 29, 1, 7];
+        assert(buffer.peek!wchar() == 'ą');
+        assert(buffer.peek!wchar(2) == '”');
+        assert(buffer.peek!wchar(4) == 'ć');
+
+        size_t index = 0;
+        assert(buffer.peek!wchar(&index) == 'ą');
+        assert(index == 2);
+
+        assert(buffer.peek!wchar(&index) == '”');
+        assert(index == 4);
+
+        assert(buffer.peek!wchar(&index) == 'ć');
+        assert(index == 6);
+    }
+
+    {
+        //dchar (32bit - 4x ubyte)
+        ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7];
+        assert(buffer.peek!dchar() == 'ą');
+        assert(buffer.peek!dchar(4) == '”');
+        assert(buffer.peek!dchar(8) == 'ć');
+
+        size_t index = 0;
+        assert(buffer.peek!dchar(&index) == 'ą');
+        assert(index == 4);
+
+        assert(buffer.peek!dchar(&index) == '”');
+        assert(index == 8);
+
+        assert(buffer.peek!dchar(&index) == 'ć');
+        assert(index == 12);
+    }
+
+    {
+        //float (32bit - 4x ubyte)
+        ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
+        assert(buffer.peek!float()== 32.0);
+        assert(buffer.peek!float(4) == 25.0f);
+
+        size_t index = 0;
+        assert(buffer.peek!float(&index) == 32.0f);
+        assert(index == 4);
+
+        assert(buffer.peek!float(&index) == 25.0f);
+        assert(index == 8);
+    }
+
+    {
+        //double (64bit - 8x ubyte)
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+        assert(buffer.peek!double() == 32.0);
+        assert(buffer.peek!double(8) == 25.0);
+
+        size_t index = 0;
+        assert(buffer.peek!double(&index) == 32.0);
+        assert(index == 8);
+
+        assert(buffer.peek!double(&index) == 25.0);
+        assert(index == 16);
+    }
+
+    {
+        //enum
+        ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30];
+
+        enum Foo
+        {
+            one = 10,
+            two = 20,
+            three = 30
+        }
+
+        assert(buffer.peek!Foo() == Foo.one);
+        assert(buffer.peek!Foo(0) == Foo.one);
+        assert(buffer.peek!Foo(4) == Foo.two);
+        assert(buffer.peek!Foo(8) == Foo.three);
+
+        size_t index = 0;
+        assert(buffer.peek!Foo(&index) == Foo.one);
+        assert(index == 4);
+
+        assert(buffer.peek!Foo(&index) == Foo.two);
+        assert(index == 8);
+
+        assert(buffer.peek!Foo(&index) == Foo.three);
+        assert(index == 12);
+    }
+
+    {
+        //enum - bool
+        ubyte[] buffer = [0, 1];
+
+        enum Bool: bool
+        {
+            bfalse = false,
+            btrue = true,
+        }
+
+        assert(buffer.peek!Bool() == Bool.bfalse);
+        assert(buffer.peek!Bool(0) == Bool.bfalse);
+        assert(buffer.peek!Bool(1) == Bool.btrue);
+
+        size_t index = 0;
+        assert(buffer.peek!Bool(&index) == Bool.bfalse);
+        assert(index == 1);
+
+        assert(buffer.peek!Bool(&index) == Bool.btrue);
+        assert(index == 2);
+    }
+
+    {
+        //enum - float
+        ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
+
+        enum Float: float
+        {
+            one = 32.0f,
+            two = 25.0f
+        }
+
+        assert(buffer.peek!Float() == Float.one);
+        assert(buffer.peek!Float(0) == Float.one);
+        assert(buffer.peek!Float(4) == Float.two);
+
+        size_t index = 0;
+        assert(buffer.peek!Float(&index) == Float.one);
+        assert(index == 4);
+
+        assert(buffer.peek!Float(&index) == Float.two);
+        assert(index == 8);
+    }
+
+    {
+        //enum - double
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+
+        enum Double: double
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        assert(buffer.peek!Double() == Double.one);
+        assert(buffer.peek!Double(0) == Double.one);
+        assert(buffer.peek!Double(8) == Double.two);
+
+        size_t index = 0;
+        assert(buffer.peek!Double(&index) == Double.one);
+        assert(index == 8);
+
+        assert(buffer.peek!Double(&index) == Double.two);
+        assert(index == 16);
+    }
+
+    {
+        //enum - real
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+
+        enum Real: real
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        static assert(!__traits(compiles, buffer.peek!Real()));
+    }
+}
+
+unittest
+{
+    import std.algorithm;
+    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 7];
+    auto range = filter!"true"(buffer);
+    assert(range.peek!uint() == 17110537);
+    assert(range.peek!ushort() == 261);
+    assert(range.peek!ubyte() == 1);
+}
+
+
+/++
+    Takes a range of $(D ubyte)s and converts the first $(D T.sizeof) bytes to
+    $(D T). The value returned is converted from the given endianness to the
+    native endianness. The $(D T.sizeof) bytes which are read are consumed from
+    the range.
+
+    Params:
+        T     = The integral type to convert the first $(D T.sizeof) bytes to.
+        endianness = The endianness that the bytes are assumed to be in.
+        range = The range to read from.
+  +/
+T read(T, Endian endianness = Endian.bigEndian, R)(ref R range)
+    if(canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte))
+{
+    static if(hasSlicing!R)
+    {
+        const ubyte[T.sizeof] bytes = range[0 .. T.sizeof];
+        range.popFrontN(T.sizeof);
+    }
+    else
+    {
+        ubyte[T.sizeof] bytes;
+
+        foreach(ref e; bytes)
+        {
+            e = range.front;
+            range.popFront();
+        }
+    }
+
+    static if(endianness == Endian.bigEndian)
+        return bigEndianToNative!T(bytes);
+    else
+        return littleEndianToNative!T(bytes);
+}
+
+///
+unittest
+{
+    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
+    assert(buffer.length == 7);
+
+    assert(buffer.read!ushort() == 261);
+    assert(buffer.length == 5);
+
+    assert(buffer.read!uint() == 369700095);
+    assert(buffer.length == 1);
+
+    assert(buffer.read!ubyte() == 8);
+    assert(buffer.empty);
+}
+
+unittest
+{
+    {
+        //bool
+        ubyte[] buffer = [0, 1];
+        assert(buffer.length == 2);
+
+        assert(buffer.read!bool() == false);
+        assert(buffer.length == 1);
+
+        assert(buffer.read!bool() == true);
+        assert(buffer.empty);
+    }
+
+    {
+        //char (8bit)
+        ubyte[] buffer = [97, 98, 99];
+        assert(buffer.length == 3);
+
+        assert(buffer.read!char() == 'a');
+        assert(buffer.length == 2);
+
+        assert(buffer.read!char() == 'b');
+        assert(buffer.length == 1);
+
+        assert(buffer.read!char() == 'c');
+        assert(buffer.empty);
+    }
+
+    {
+        //wchar (16bit - 2x ubyte)
+        ubyte[] buffer = [1, 5, 32, 29, 1, 7];
+        assert(buffer.length == 6);
+
+        assert(buffer.read!wchar() == 'ą');
+        assert(buffer.length == 4);
+
+        assert(buffer.read!wchar() == '”');
+        assert(buffer.length == 2);
+
+        assert(buffer.read!wchar() == 'ć');
+        assert(buffer.empty);
+    }
+
+    {
+        //dchar (32bit - 4x ubyte)
+        ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7];
+        assert(buffer.length == 12);
+
+        assert(buffer.read!dchar() == 'ą');
+        assert(buffer.length == 8);
+
+        assert(buffer.read!dchar() == '”');
+        assert(buffer.length == 4);
+
+        assert(buffer.read!dchar() == 'ć');
+        assert(buffer.empty);
+    }
+
+    {
+        //float (32bit - 4x ubyte)
+        ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
+        assert(buffer.length == 8);
+
+        assert(buffer.read!float()== 32.0);
+        assert(buffer.length == 4);
+
+        assert(buffer.read!float() == 25.0f);
+        assert(buffer.empty);
+    }
+
+    {
+        //double (64bit - 8x ubyte)
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+        assert(buffer.length == 16);
+
+        assert(buffer.read!double() == 32.0);
+        assert(buffer.length == 8);
+
+        assert(buffer.read!double() == 25.0);
+        assert(buffer.empty);
+    }
+
+    {
+        //enum - uint
+        ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30];
+        assert(buffer.length == 12);
+
+        enum Foo
+        {
+            one = 10,
+            two = 20,
+            three = 30
+        }
+
+        assert(buffer.read!Foo() == Foo.one);
+        assert(buffer.length == 8);
+
+        assert(buffer.read!Foo() == Foo.two);
+        assert(buffer.length == 4);
+
+        assert(buffer.read!Foo() == Foo.three);
+        assert(buffer.empty);
+    }
+
+    {
+        //enum - bool
+        ubyte[] buffer = [0, 1];
+        assert(buffer.length == 2);
+
+        enum Bool: bool
+        {
+            bfalse = false,
+            btrue = true,
+        }
+
+        assert(buffer.read!Bool() == Bool.bfalse);
+        assert(buffer.length == 1);
+
+        assert(buffer.read!Bool() == Bool.btrue);
+        assert(buffer.empty);
+    }
+
+    {
+        //enum - float
+        ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
+        assert(buffer.length == 8);
+
+        enum Float: float
+        {
+            one = 32.0f,
+            two = 25.0f
+        }
+
+        assert(buffer.read!Float() == Float.one);
+        assert(buffer.length == 4);
+
+        assert(buffer.read!Float() == Float.two);
+        assert(buffer.empty);
+    }
+
+    {
+        //enum - double
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+        assert(buffer.length == 16);
+
+        enum Double: double
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        assert(buffer.read!Double() == Double.one);
+        assert(buffer.length == 8);
+
+        assert(buffer.read!Double() == Double.two);
+        assert(buffer.empty);
+    }
+
+    {
+        //enum - real
+        ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
+
+        enum Real: real
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        static assert(!__traits(compiles, buffer.read!Real()));
+    }
+}
+
+unittest
+{
+    import std.algorithm;
+    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
+    auto range = filter!"true"(buffer);
+    assert(walkLength(range) == 7);
+
+    assert(range.read!ushort() == 261);
+    assert(walkLength(range) == 5);
+
+    assert(range.read!uint() == 369700095);
+    assert(walkLength(range) == 1);
+
+    assert(range.read!ubyte() == 8);
+    assert(range.empty);
+}
+
+
+/++
+    Takes an integral value, converts it to the given endianness, and writes it
+    to the given range of $(D ubyte)s as a sequence of $(D T.sizeof) $(D ubyte)s
+    starting at index. $(D hasSlicing!R) must be $(D true).
+
+    Params:
+        T     = The integral type to convert the first $(D T.sizeof) bytes to.
+        endianness = The endianness to _write the bytes in.
+        range = The range to _write to.
+        value = The value to _write.
+        index = The index to start writing to. If index is a pointer, then it
+                is updated to the index after the bytes read.
+  +/
+void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t index)
+    if(canSwapEndianness!T &&
+       isForwardRange!R &&
+       hasSlicing!R &&
+       is(ElementType!R : ubyte))
+{
+    write!(T, endianness)(range, value, &index);
+}
+
+/++ Ditto +/
+void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t* index)
+    if(canSwapEndianness!T &&
+       isForwardRange!R &&
+       hasSlicing!R &&
+       is(ElementType!R : ubyte))
+{
+    assert(index);
+
+    static if(endianness == Endian.bigEndian)
+        immutable bytes = nativeToBigEndian!T(value);
+    else
+        immutable bytes = nativeToLittleEndian!T(value);
+
+    immutable begin = *index;
+    immutable end = begin + T.sizeof;
+    *index = end;
+    range[begin .. end] = bytes[0 .. T.sizeof];
+}
+
+///
+unittest
+{
+    {
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
+        buffer.write!uint(29110231u, 0);
+        assert(buffer == [1, 188, 47, 215, 0, 0, 0, 0]);
+
+        buffer.write!ushort(927, 0);
+        assert(buffer == [3, 159, 47, 215, 0, 0, 0, 0]);
+
+        buffer.write!ubyte(42, 0);
+        assert(buffer == [42, 159, 47, 215, 0, 0, 0, 0]);
+    }
+
+    {
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0];
+        buffer.write!uint(142700095u, 2);
+        assert(buffer == [0, 0, 8, 129, 110, 63, 0, 0, 0]);
+
+        buffer.write!ushort(19839, 2);
+        assert(buffer == [0, 0, 77, 127, 110, 63, 0, 0, 0]);
+
+        buffer.write!ubyte(132, 2);
+        assert(buffer == [0, 0, 132, 127, 110, 63, 0, 0, 0]);
+    }
+
+    {
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
+        size_t index = 0;
+        buffer.write!ushort(261, &index);
+        assert(buffer == [1, 5, 0, 0, 0, 0, 0, 0]);
+        assert(index == 2);
+
+        buffer.write!uint(369700095u, &index);
+        assert(buffer == [1, 5, 22, 9, 44, 255, 0, 0]);
+        assert(index == 6);
+
+        buffer.write!ubyte(8, &index);
+        assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]);
+        assert(index == 7);
+    }
+}
+
+unittest
+{
+    {
+        //bool
+        ubyte[] buffer = [0, 0];
+
+        buffer.write!bool(false, 0);
+        assert(buffer == [0, 0]);
+
+        buffer.write!bool(true, 0);
+        assert(buffer == [1, 0]);
+
+        buffer.write!bool(true, 1);
+        assert(buffer == [1, 1]);
+
+        buffer.write!bool(false, 1);
+        assert(buffer == [1, 0]);
+
+        size_t index = 0;
+        buffer.write!bool(false, &index);
+        assert(buffer == [0, 0]);
+        assert(index == 1);
+
+        buffer.write!bool(true, &index);
+        assert(buffer == [0, 1]);
+        assert(index == 2);
+    }
+
+    {
+        //char (8bit)
+        ubyte[] buffer = [0, 0, 0];
+
+        buffer.write!char('a', 0);
+        assert(buffer == [97, 0, 0]);
+
+        buffer.write!char('b', 1);
+        assert(buffer == [97, 98, 0]);
+
+        size_t index = 0;
+        buffer.write!char('a', &index);
+        assert(buffer == [97, 98, 0]);
+        assert(index == 1);
+
+        buffer.write!char('b', &index);
+        assert(buffer == [97, 98, 0]);
+        assert(index == 2);
+
+        buffer.write!char('c', &index);
+        assert(buffer == [97, 98, 99]);
+        assert(index == 3);
+    }
+
+    {
+        //wchar (16bit - 2x ubyte)
+        ubyte[] buffer = [0, 0, 0, 0];
+
+        buffer.write!wchar('ą', 0);
+        assert(buffer == [1, 5, 0, 0]);
+
+        buffer.write!wchar('”', 2);
+        assert(buffer == [1, 5, 32, 29]);
+
+        size_t index = 0;
+        buffer.write!wchar('ć', &index);
+        assert(buffer == [1, 7, 32, 29]);
+        assert(index == 2);
+
+        buffer.write!wchar('ą', &index);
+        assert(buffer == [1, 7, 1, 5]);
+        assert(index == 4);
+    }
+
+    {
+        //dchar (32bit - 4x ubyte)
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
+
+        buffer.write!dchar('ą', 0);
+        assert(buffer == [0, 0, 1, 5, 0, 0, 0, 0]);
+
+        buffer.write!dchar('”', 4);
+        assert(buffer == [0, 0, 1, 5, 0, 0, 32, 29]);
+
+        size_t index = 0;
+        buffer.write!dchar('ć', &index);
+        assert(buffer == [0, 0, 1, 7, 0, 0, 32, 29]);
+        assert(index == 4);
+
+        buffer.write!dchar('ą', &index);
+        assert(buffer == [0, 0, 1, 7, 0, 0, 1, 5]);
+        assert(index == 8);
+    }
+
+    {
+        //float (32bit - 4x ubyte)
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
+
+        buffer.write!float(32.0f, 0);
+        assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]);
+
+        buffer.write!float(25.0f, 4);
+        assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]);
+
+        size_t index = 0;
+        buffer.write!float(25.0f, &index);
+        assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]);
+        assert(index == 4);
+
+        buffer.write!float(32.0f, &index);
+        assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]);
+        assert(index == 8);
+    }
+
+    {
+        //double (64bit - 8x ubyte)
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+        buffer.write!double(32.0, 0);
+        assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+        buffer.write!double(25.0, 8);
+        assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
+
+        size_t index = 0;
+        buffer.write!double(25.0, &index);
+        assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
+        assert(index == 8);
+
+        buffer.write!double(32.0, &index);
+        assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
+        assert(index == 16);
+    }
+
+    {
+        //enum
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+        enum Foo
+        {
+            one = 10,
+            two = 20,
+            three = 30
+        }
+
+        buffer.write!Foo(Foo.one, 0);
+        assert(buffer == [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+        buffer.write!Foo(Foo.two, 4);
+        assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 0]);
+
+        buffer.write!Foo(Foo.three, 8);
+        assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]);
+
+        size_t index = 0;
+        buffer.write!Foo(Foo.three, &index);
+        assert(buffer == [0, 0, 0, 30, 0, 0, 0, 20, 0, 0, 0, 30]);
+        assert(index == 4);
+
+        buffer.write!Foo(Foo.one, &index);
+        assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 30]);
+        assert(index == 8);
+
+        buffer.write!Foo(Foo.two, &index);
+        assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 20]);
+        assert(index == 12);
+    }
+
+    {
+        //enum - bool
+        ubyte[] buffer = [0, 0];
+
+        enum Bool: bool
+        {
+            bfalse = false,
+            btrue = true,
+        }
+
+        buffer.write!Bool(Bool.btrue, 0);
+        assert(buffer == [1, 0]);
+
+        buffer.write!Bool(Bool.btrue, 1);
+        assert(buffer == [1, 1]);
+
+        size_t index = 0;
+        buffer.write!Bool(Bool.bfalse, &index);
+        assert(buffer == [0, 1]);
+        assert(index == 1);
+
+        buffer.write!Bool(Bool.bfalse, &index);
+        assert(buffer == [0, 0]);
+        assert(index == 2);
+    }
+
+    {
+        //enum - float
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
+
+        enum Float: float
+        {
+            one = 32.0f,
+            two = 25.0f
+        }
+
+        buffer.write!Float(Float.one, 0);
+        assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]);
+
+        buffer.write!Float(Float.two, 4);
+        assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]);
+
+        size_t index = 0;
+        buffer.write!Float(Float.two, &index);
+        assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]);
+        assert(index == 4);
+
+        buffer.write!Float(Float.one, &index);
+        assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]);
+        assert(index == 8);
+    }
+
+    {
+        //enum - double
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+        enum Double: double
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        buffer.write!Double(Double.one, 0);
+        assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+        buffer.write!Double(Double.two, 8);
+        assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
+
+        size_t index = 0;
+        buffer.write!Double(Double.two, &index);
+        assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
+        assert(index == 8);
+
+        buffer.write!Double(Double.one, &index);
+        assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
+        assert(index == 16);
+    }
+
+    {
+        //enum - real
+        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+        enum Real: real
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        static assert(!__traits(compiles, buffer.write!Real(Real.one)));
+    }
+}
+
+
+/++
+    Takes an integral value, converts it to the given endianness, and appends
+    it to the given range of $(D ubyte)s (using $(D put)) as a sequence of
+    $(D T.sizeof) $(D ubyte)s starting at index. $(D hasSlicing!R) must be
+    $(D true).
+
+    Params:
+        T     = The integral type to convert the first $(D T.sizeof) bytes to.
+        endianness = The endianness to write the bytes in.
+        range = The range to _append to.
+        value = The value to _append.
+  +/
+void append(T, Endian endianness = Endian.bigEndian, R)(R range, T value)
+    if(canSwapEndianness!T && isOutputRange!(R, ubyte))
+{
+    static if(endianness == Endian.bigEndian)
+        immutable bytes = nativeToBigEndian!T(value);
+    else
+        immutable bytes = nativeToLittleEndian!T(value);
+
+    put(range, bytes[]);
+}
+
+///
+unittest
+{
+    import std.array;
+    auto buffer = appender!(const ubyte[])();
+    buffer.append!ushort(261);
+    assert(buffer.data == [1, 5]);
+
+    buffer.append!uint(369700095u);
+    assert(buffer.data == [1, 5, 22, 9, 44, 255]);
+
+    buffer.append!ubyte(8);
+    assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]);
+}
+
+unittest
+{
+    import std.array;
+    {
+        //bool
+        auto buffer = appender!(const ubyte[])();
+
+        buffer.append!bool(true);
+        assert(buffer.data == [1]);
+
+        buffer.append!bool(false);
+        assert(buffer.data == [1, 0]);
+    }
+
+    {
+        //char wchar dchar
+        auto buffer = appender!(const ubyte[])();
+
+        buffer.append!char('a');
+        assert(buffer.data == [97]);
+
+        buffer.append!char('b');
+        assert(buffer.data == [97, 98]);
+
+        buffer.append!wchar('ą');
+        assert(buffer.data == [97, 98, 1, 5]);
+
+        buffer.append!dchar('ą');
+        assert(buffer.data == [97, 98, 1, 5, 0, 0, 1, 5]);
+    }
+
+    {
+        //float double
+        auto buffer = appender!(const ubyte[])();
+
+        buffer.append!float(32.0f);
+        assert(buffer.data == [66, 0, 0, 0]);
+
+        buffer.append!double(32.0);
+        assert(buffer.data == [66, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
+    }
+
+    {
+        //enum
+        auto buffer = appender!(const ubyte[])();
+
+        enum Foo
+        {
+            one = 10,
+            two = 20,
+            three = 30
+        }
+
+        buffer.append!Foo(Foo.one);
+        assert(buffer.data == [0, 0, 0, 10]);
+
+        buffer.append!Foo(Foo.two);
+        assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20]);
+
+        buffer.append!Foo(Foo.three);
+        assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]);
+    }
+
+    {
+        //enum - bool
+        auto buffer = appender!(const ubyte[])();
+
+        enum Bool: bool
+        {
+            bfalse = false,
+            btrue = true,
+        }
+
+        buffer.append!Bool(Bool.btrue);
+        assert(buffer.data == [1]);
+
+        buffer.append!Bool(Bool.bfalse);
+        assert(buffer.data == [1, 0]);
+
+        buffer.append!Bool(Bool.btrue);
+        assert(buffer.data == [1, 0, 1]);
+    }
+
+    {
+        //enum - float
+        auto buffer = appender!(const ubyte[])();
+
+        enum Float: float
+        {
+            one = 32.0f,
+            two = 25.0f
+        }
+
+        buffer.append!Float(Float.one);
+        assert(buffer.data == [66, 0, 0, 0]);
+
+        buffer.append!Float(Float.two);
+        assert(buffer.data == [66, 0, 0, 0, 65, 200, 0, 0]);
+    }
+
+    {
+        //enum - double
+        auto buffer = appender!(const ubyte[])();
+
+        enum Double: double
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        buffer.append!Double(Double.one);
+        assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0]);
+
+        buffer.append!Double(Double.two);
+        assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
+    }
+
+    {
+        //enum - real
+        auto buffer = appender!(const ubyte[])();
+
+        enum Real: real
+        {
+            one = 32.0,
+            two = 25.0
+        }
+
+        static assert(!__traits(compiles, buffer.append!Real(Real.one)));
+    }
+}
+
+unittest
+{
+    import std.format : format;
+    import std.array;
+    import std.meta;
+    foreach(endianness; AliasSeq!(Endian.bigEndian, Endian.littleEndian))
+    {
+        auto toWrite = appender!(ubyte[])();
+        alias Types = AliasSeq!(uint, int, long, ulong, short, ubyte, ushort, byte, uint);
+        ulong[] values = [42, -11, long.max, 1098911981329L, 16, 255, 19012, 2, 17];
+        assert(Types.length == values.length);
+
+        size_t index = 0;
+        size_t length = 0;
+        foreach(T; Types)
+        {
+            toWrite.append!(T, endianness)(cast(T)values[index++]);
+            length += T.sizeof;
+        }
+
+        auto toRead = toWrite.data;
+        assert(toRead.length == length);
+
+        index = 0;
+        foreach(T; Types)
+        {
+            assert(toRead.peek!(T, endianness)() == values[index], format("Failed Index: %s", index));
+            assert(toRead.peek!(T, endianness)(0) == values[index], format("Failed Index: %s", index));
+            assert(toRead.length == length,
+                   format("Failed Index [%s], Actual Length: %s", index, toRead.length));
+            assert(toRead.read!(T, endianness)() == values[index], format("Failed Index: %s", index));
+            length -= T.sizeof;
+            assert(toRead.length == length,
+                   format("Failed Index [%s], Actual Length: %s", index, toRead.length));
+            ++index;
+        }
+        assert(toRead.empty);
+    }
+}
+
+/**
+Counts the number of trailing zeros in the binary representation of $(D value).
+For signed integers, the sign bit is included in the count.
+*/
+private uint countTrailingZeros(T)(T value) @nogc pure nothrow
+    if (isIntegral!T)
+{
+    import core.bitop : bsf;
+    // bsf doesn't give the correct result for 0.
+    if (!value)
+        return 8 * T.sizeof;
+    else
+        return bsf(value);
+}
+
+///
+unittest
+{
+    assert(countTrailingZeros(1) == 0);
+    assert(countTrailingZeros(0) == 32);
+    assert(countTrailingZeros(int.min) == 31);
+    assert(countTrailingZeros(256) == 8);
+}
+
+unittest
+{
+    import std.meta;
+    foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        assert(countTrailingZeros(cast(T)0) == 8 * T.sizeof);
+        assert(countTrailingZeros(cast(T)1) == 0);
+        assert(countTrailingZeros(cast(T)2) == 1);
+        assert(countTrailingZeros(cast(T)3) == 0);
+        assert(countTrailingZeros(cast(T)4) == 2);
+        assert(countTrailingZeros(cast(T)5) == 0);
+        assert(countTrailingZeros(cast(T)64) == 6);
+        static if (isSigned!T)
+        {
+            assert(countTrailingZeros(cast(T)-1) == 0);
+            assert(countTrailingZeros(T.min) == 8 * T.sizeof - 1);
+        }
+        else
+        {
+            assert(countTrailingZeros(T.max) == 0);
+        }
+    }
+    assert(countTrailingZeros(1_000_000) == 6);
+    foreach (i; 0..63)
+        assert(countTrailingZeros(1UL << i) == i);
+}
+
+/**
+Counts the number of set bits in the binary representation of $(D value).
+For signed integers, the sign bit is included in the count.
+*/
+private uint countBitsSet(T)(T value) @nogc pure nothrow
+    if (isIntegral!T)
+{
+    // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
+    static if (T.sizeof == 8)
+    {
+        T c = value - ((value >> 1) & 0x55555555_55555555);
+        c = ((c >> 2) & 0x33333333_33333333) + (c & 0x33333333_33333333);
+        c = ((c >> 4) + c) & 0x0F0F0F0F_0F0F0F0F;
+        c = ((c >> 8) + c) & 0x00FF00FF_00FF00FF;
+        c = ((c >> 16) + c) & 0x0000FFFF_0000FFFF;
+        c = ((c >> 32) + c) & 0x00000000_FFFFFFFF;
+    }
+    else static if (T.sizeof == 4)
+    {
+        T c = value - ((value >> 1) & 0x55555555);
+        c = ((c >> 2) & 0x33333333) + (c & 0x33333333);
+        c = ((c >> 4) + c) & 0x0F0F0F0F;
+        c = ((c >> 8) + c) & 0x00FF00FF;
+        c = ((c >> 16) + c) & 0x0000FFFF;
+    }
+    else static if (T.sizeof == 2)
+    {
+        uint c = value - ((value >> 1) & 0x5555);
+        c = ((c >> 2) & 0x3333) + (c & 0X3333);
+        c = ((c >> 4) + c) & 0x0F0F;
+        c = ((c >> 8) + c) & 0x00FF;
+    }
+    else static if (T.sizeof == 1)
+    {
+        uint c = value - ((value >> 1) & 0x55);
+        c = ((c >> 2) & 0x33) + (c & 0X33);
+        c = ((c >> 4) + c) & 0x0F;
+    }
+    else
+    {
+        static assert("countBitsSet only supports 1, 2, 4, or 8 byte sized integers.");
+    }
+    return cast(uint)c;
+}
+
+///
+unittest
+{
+    assert(countBitsSet(1) == 1);
+    assert(countBitsSet(0) == 0);
+    assert(countBitsSet(int.min) == 1);
+    assert(countBitsSet(uint.max) == 32);
+}
+
+unittest
+{
+    import std.meta;
+    foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        assert(countBitsSet(cast(T)0) == 0);
+        assert(countBitsSet(cast(T)1) == 1);
+        assert(countBitsSet(cast(T)2) == 1);
+        assert(countBitsSet(cast(T)3) == 2);
+        assert(countBitsSet(cast(T)4) == 1);
+        assert(countBitsSet(cast(T)5) == 2);
+        assert(countBitsSet(cast(T)127) == 7);
+        static if (isSigned!T)
+        {
+            assert(countBitsSet(cast(T)-1) == 8 * T.sizeof);
+            assert(countBitsSet(T.min) == 1);
+        }
+        else
+        {
+            assert(countBitsSet(T.max) == 8 * T.sizeof);
+        }
+    }
+    assert(countBitsSet(1_000_000) == 7);
+    foreach (i; 0..63)
+        assert(countBitsSet(1UL << i) == 1);
+}
+
+private struct BitsSet(T)
+{
+    static assert(T.sizeof <= 8, "bitsSet assumes T is no more than 64-bit.");
+
+@nogc pure nothrow:
+
+    this(T value, size_t startIndex = 0)
+    {
+        _value = value;
+        uint n = countTrailingZeros(value);
+        _index = startIndex + n;
+        _value >>>= n;
+    }
+
+    @property size_t front()
+    {
+        return _index;
+    }
+
+    @property bool empty() const
+    {
+        return !_value;
+    }
+
+    void popFront()
+    {
+        assert(_value, "Cannot call popFront on empty range.");
+
+        _value >>>= 1;
+        uint n = countTrailingZeros(_value);
+        _value >>>= n;
+        _index += n + 1;
+    }
+
+    @property auto save()
+    {
+        return this;
+    }
+
+    @property size_t length()
+    {
+        return countBitsSet(_value);
+    }
+
+    private T _value;
+    private size_t _index;
+}
+
+/**
+Range that iterates the indices of the set bits in $(D value).
+Index 0 corresponds to the least significant bit.
+For signed integers, the highest index corresponds to the sign bit.
+*/
+auto bitsSet(T)(T value) @nogc pure nothrow
+    if (isIntegral!T)
+{
+    return BitsSet!T(value);
+}
+
+///
+unittest
+{
+    import std.algorithm : equal;
+    import std.range : iota;
+
+    assert(bitsSet(1).equal([0]));
+    assert(bitsSet(5).equal([0, 2]));
+    assert(bitsSet(-1).equal(iota(32)));
+    assert(bitsSet(int.min).equal([31]));
+}
+
+unittest
+{
+    import std.algorithm : equal;
+    import std.range: iota;
+
+    import std.meta;
+    foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        assert(bitsSet(cast(T)0).empty);
+        assert(bitsSet(cast(T)1).equal([0]));
+        assert(bitsSet(cast(T)2).equal([1]));
+        assert(bitsSet(cast(T)3).equal([0, 1]));
+        assert(bitsSet(cast(T)4).equal([2]));
+        assert(bitsSet(cast(T)5).equal([0, 2]));
+        assert(bitsSet(cast(T)127).equal(iota(7)));
+        static if (isSigned!T)
+        {
+            assert(bitsSet(cast(T)-1).equal(iota(8 * T.sizeof)));
+            assert(bitsSet(T.min).equal([8 * T.sizeof - 1]));
+        }
+        else
+        {
+            assert(bitsSet(T.max).equal(iota(8 * T.sizeof)));
+        }
+    }
+    assert(bitsSet(1_000_000).equal([6, 9, 14, 16, 17, 18, 19]));
+    foreach (i; 0..63)
+        assert(bitsSet(1UL << i).equal([i]));
+}
diff --git a/libphobos/src/std/c/fenv.d b/libphobos/src/std/c/fenv.d
new file mode 100644
index 00000000000..32fdea27be3
--- /dev/null
+++ b/libphobos/src/std/c/fenv.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.fenv) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;fenv.h&gt;
+ * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCFenv
+ */
+deprecated("Import core.stdc.fenv instead")
+module std.c.fenv;
+
+public import core.stdc.fenv;
diff --git a/libphobos/src/std/c/freebsd/socket.d b/libphobos/src/std/c/freebsd/socket.d
new file mode 100644
index 00000000000..5a73d47572c
--- /dev/null
+++ b/libphobos/src/std/c/freebsd/socket.d
@@ -0,0 +1,17 @@
+// Written in the D programming language.
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use the appropriate $(D core.sys.posix.*) modules instead.
+          This module will be removed in June 2017.)
+  +/
+deprecated("Import the appropriate core.sys.posix.* modules instead")
+module std.c.freebsd.socket;
+
+version (FreeBSD):
+public import core.sys.posix.netdb;
+public import core.sys.posix.sys.socket : AF_APPLETALK, AF_IPX, SOCK_RDM, MSG_NOSIGNAL;
+public import core.sys.posix.netinet.in_ : IPPROTO_IGMP, IPPROTO_GGP,
+                                          IPPROTO_PUP, IPPROTO_IDP, IPPROTO_ND,
+                                          IPPROTO_MAX, INADDR_LOOPBACK, INADDR_NONE;
diff --git a/libphobos/src/std/c/linux/linux.d b/libphobos/src/std/c/linux/linux.d
new file mode 100644
index 00000000000..165068a1034
--- /dev/null
+++ b/libphobos/src/std/c/linux/linux.d
@@ -0,0 +1,70 @@
+// @@@DEPRECATED_2017-06@@@
+
+/* Written by Walter Bright, Christopher E. Miller, and many others.
+ * http://www.digitalmars.com/d/
+ * Placed into public domain.
+ * Linux(R) is the registered trademark of Linus Torvalds in the U.S. and other
+ * countries.
+ */
+
+/++
+    $(RED Deprecated. Use the appropriate $(D core.sys.posix.*) modules instead.
+          This module will be removed in June 2017.)
+  +/
+deprecated("Import the appropriate core.sys.posix.* modules instead")
+module std.c.linux.linux;
+
+version (linux):
+public import core.sys.posix.pthread;
+
+extern (C)
+{
+    extern __gshared
+    {
+        void* __libc_stack_end;
+        int __data_start;
+        int _end;
+
+        void *_deh_beg;
+        void *_deh_end;
+    }
+}
+
+struct struct_stat64    // distinguish it from the stat() function
+{
+    ulong st_dev;       /// device
+    uint __pad1;
+    uint st_ino;        /// file serial number
+    uint st_mode;       /// file mode
+    uint st_nlink;      /// link count
+    uint st_uid;        /// user ID of file's owner
+    uint st_gid;        /// user ID of group's owner
+    ulong st_rdev;      /// if device then device number
+    uint __pad2;
+    align(4) ulong st_size;
+    int st_blksize;     /// optimal I/O block size
+    ulong st_blocks;    /// number of allocated 512 byte blocks
+    int st_atime;
+    uint st_atimensec;
+    int st_mtime;
+    uint st_mtimensec;
+    int st_ctime;
+    uint st_ctimensec;
+
+    ulong st_ino64;
+}
+int fstat64(int, struct_stat64*);
+int fstat64(in char*, struct_stat64*);
+
+public import core.sys.posix.dirent;
+public import core.sys.posix.dlfcn;
+public import core.sys.posix.fcntl;
+public import core.sys.posix.pwd;
+public import core.sys.posix.time;
+public import core.sys.posix.unistd;
+public import core.sys.posix.utime;
+public import core.sys.posix.sys.mman;
+public import core.sys.posix.sys.stat;
+public import core.sys.posix.sys.time;
+public import core.sys.posix.sys.types;
+public import core.sys.posix.sys.wait;
diff --git a/libphobos/src/std/c/linux/linuxextern.d b/libphobos/src/std/c/linux/linuxextern.d
new file mode 100644
index 00000000000..f2c15ec7c2a
--- /dev/null
+++ b/libphobos/src/std/c/linux/linuxextern.d
@@ -0,0 +1,17 @@
+// @@@DEPRECATED_2017-06@@@
+
+/* Written by Walter Bright.
+ * www.digitalmars.com
+ * Placed into public domain.
+ * Linux(R) is the registered trademark of Linus Torvalds in the U.S. and other
+ * countries.
+ */
+
+/++
+    $(RED Deprecated. Remove this import. This module no longer contains
+          anything.)
+  +/
+deprecated("This module no longer contains anything. Just remove the import.")
+module std.c.linux.linuxextern;
+
+// No longer needed since "extern" storage class
diff --git a/libphobos/src/std/c/linux/pthread.d b/libphobos/src/std/c/linux/pthread.d
new file mode 100644
index 00000000000..041bc570c26
--- /dev/null
+++ b/libphobos/src/std/c/linux/pthread.d
@@ -0,0 +1,19 @@
+// @@@DEPRECATED_2017-06@@@
+
+/* Written by Walter Bright, Christopher E. Miller, and many others.
+ * www.digitalmars.com
+ * Placed into public domain.
+ */
+
+/++
+    $(RED Deprecated. Use $(core.sys.posix.pthread) or the appropriate
+          $(D core.sys.posix.*) modules instead. This module will be removed in
+          June 2017.)
+  +/
+deprecated("Import core.sys.posix.pthread or the appropriate core.sys.posix.* modules instead")
+module std.c.linux.pthread;
+
+version (linux):
+import std.c.linux.linux;
+
+public import core.sys.posix.pthread;
diff --git a/libphobos/src/std/c/linux/socket.d b/libphobos/src/std/c/linux/socket.d
new file mode 100644
index 00000000000..59c85c1c470
--- /dev/null
+++ b/libphobos/src/std/c/linux/socket.d
@@ -0,0 +1,82 @@
+/*
+    Written by Christopher E. Miller
+    Placed into public domain.
+*/
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use the appropriate $(D core.sys.posix.*) modules instead.
+          This module will be removed in June 2017.)
+  +/
+deprecated("Import the appropriate core.sys.posix.* modules instead")
+module std.c.linux.socket;
+
+version (linux):
+private import core.stdc.stdint;
+public import core.sys.posix.arpa.inet;
+public import core.sys.posix.netdb;
+public import core.sys.posix.netinet.tcp;
+public import core.sys.posix.netinet.in_;
+public import core.sys.posix.sys.select;
+public import core.sys.posix.sys.socket;
+
+extern(C):
+
+int gethostbyname_r(in char* name, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop);
+int gethostbyname2_r(in char* name, int af, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop);
+
+enum: int
+{
+    SD_RECEIVE =  0,
+    SD_SEND =     1,
+    SD_BOTH =     2,
+}
+
+enum: int
+{
+    IP_MULTICAST_LOOP =  34,
+    IP_ADD_MEMBERSHIP =  35,
+    IP_DROP_MEMBERSHIP = 36,
+
+    // ...
+
+    IPV6_ADDRFORM =        1,
+    IPV6_PKTINFO =         2,
+    IPV6_HOPOPTS =         3,
+    IPV6_DSTOPTS =         4,
+    IPV6_RTHDR =           5,
+    IPV6_PKTOPTIONS =      6,
+    IPV6_CHECKSUM =        7,
+    IPV6_HOPLIMIT =        8,
+    IPV6_NEXTHOP =         9,
+    IPV6_AUTHHDR =         10,
+    IPV6_MULTICAST_HOPS =  18,
+    IPV6_ROUTER_ALERT =    22,
+    IPV6_MTU_DISCOVER =    23,
+    IPV6_MTU =             24,
+    IPV6_RECVERR =         25,
+    IPV6_V6ONLY =          26,
+    IPV6_JOIN_ANYCAST =    27,
+    IPV6_LEAVE_ANYCAST =   28,
+    IPV6_IPSEC_POLICY =    34,
+    IPV6_XFRM_POLICY =     35,
+}
+
+enum: int
+{
+    TCP_NODELAY =        1,     // Don't delay send to coalesce packets
+    TCP_MAXSEG =         2,     // Set maximum segment size
+    TCP_CORK =           3,     // Control sending of partial frames
+    TCP_KEEPIDLE =       4,     // Start keeplives after this period
+    TCP_KEEPINTVL =      5,     // Interval between keepalives
+    TCP_KEEPCNT =        6,     // Number of keepalives before death
+    TCP_SYNCNT =         7,     // Number of SYN retransmits
+    TCP_LINGER2 =        8,     // Life time of orphaned FIN-WAIT-2 state
+    TCP_DEFER_ACCEPT =   9,     // Wake up listener only when data arrive
+    TCP_WINDOW_CLAMP =  10,     // Bound advertised window
+    TCP_INFO =          11,     // Information about this connection.
+    TCP_QUICKACK =      12,     // Bock/reenable quick ACKs.
+    TCP_CONGESTION =    13,     // Congestion control algorithm.
+    TCP_MD5SIG =        14,     // TCP MD5 Signature (RFC2385)
+}
diff --git a/libphobos/src/std/c/linux/termios.d b/libphobos/src/std/c/linux/termios.d
new file mode 100644
index 00000000000..2a9699f39e5
--- /dev/null
+++ b/libphobos/src/std/c/linux/termios.d
@@ -0,0 +1,12 @@
+// @@@DEPRECATED_2017-06@@@
+
+
+/++
+    $(RED Deprecated. Use $(D core.sys.posix.termios) instead. This module will
+          be removed in June 2017.)
+  +/
+deprecated("Import core.sys.posix.termios instead")
+module std.c.linux.termios;
+
+version (linux):
+public import core.sys.posix.termios;
diff --git a/libphobos/src/std/c/linux/tipc.d b/libphobos/src/std/c/linux/tipc.d
new file mode 100644
index 00000000000..44eba9ffe3f
--- /dev/null
+++ b/libphobos/src/std/c/linux/tipc.d
@@ -0,0 +1,17 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.sys.linux.tipc) instead.  This module will be
+ *       removed in June 2017.)
+ *
+ * Interface for Linux TIPC sockets, /usr/include/linux/tipc.h
+ *
+ * Copyright: Public Domain
+ * License:   Public Domain
+ * Authors:   Leandro Lucarella
+ */
+
+deprecated("Import core.sys.linux.tipc instead")
+module std.c.linux.tipc;
+
+public import core.sys.linux.tipc;
diff --git a/libphobos/src/std/c/locale.d b/libphobos/src/std/c/locale.d
new file mode 100644
index 00000000000..7c18d0d98ae
--- /dev/null
+++ b/libphobos/src/std/c/locale.d
@@ -0,0 +1,17 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.locale) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;locale.h&gt;
+ * License: Public Domain
+ * Standards:
+ *      ISO/IEC 9899:1999 7.11
+ * Macros:
+ *      WIKI=Phobos/StdCLocale
+ */
+deprecated("Import core.stdc.locale instead")
+module std.c.locale;
+
+public import core.stdc.locale;
diff --git a/libphobos/src/std/c/math.d b/libphobos/src/std/c/math.d
new file mode 100644
index 00000000000..7d33d754ccc
--- /dev/null
+++ b/libphobos/src/std/c/math.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.math) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;math.h&gt;
+ * Authors: Walter Bright, Digital Mars, www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCMath
+ */
+deprecated("Import core.stdc.math instead")
+module std.c.math;
+
+public import core.stdc.math;
diff --git a/libphobos/src/std/c/osx/socket.d b/libphobos/src/std/c/osx/socket.d
new file mode 100644
index 00000000000..835ed276cd2
--- /dev/null
+++ b/libphobos/src/std/c/osx/socket.d
@@ -0,0 +1,62 @@
+/*
+    Written by Christopher E. Miller
+    Placed into public domain.
+*/
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use the appropriate $(D core.sys.posix.*) modules instead.
+          This module will be removed in June 2017.)
+  +/
+deprecated("Import the appropriate core.sys.posix.* instead")
+module std.c.osx.socket;
+
+version (OSX):
+private import core.stdc.stdint;
+public import core.sys.posix.arpa.inet;
+public import core.sys.posix.netdb;
+public import core.sys.posix.netinet.tcp;
+public import core.sys.posix.netinet.in_;
+public import core.sys.posix.sys.select;
+public import core.sys.posix.sys.socket;
+
+extern(C):
+
+// Not defined in OSX, so these will be removed at the end of deprecation
+enum: int
+{
+    SD_RECEIVE =  0,
+    SD_SEND =     1,
+    SD_BOTH =     2,
+}
+
+enum: int
+{
+    IP_MULTICAST_LOOP =  11,
+    IP_ADD_MEMBERSHIP =  12,
+    IP_DROP_MEMBERSHIP = 13,
+
+    // ...
+
+    //IPV6_ADDRFORM =        1,
+    IPV6_PKTINFO =         19,
+    IPV6_HOPOPTS =         22,
+    IPV6_DSTOPTS =         23,
+    IPV6_RTHDR =           24,
+    IPV6_PKTOPTIONS =      25,
+    IPV6_CHECKSUM =        26,
+    IPV6_HOPLIMIT =        20,
+    IPV6_NEXTHOP =         21,
+    //IPV6_AUTHHDR =         10,
+    IPV6_MULTICAST_HOPS =  10,
+    //IPV6_ROUTER_ALERT =    22,
+    //IPV6_MTU_DISCOVER =    23,
+    //IPV6_MTU =             24,
+    //IPV6_RECVERR =         25,
+    IPV6_V6ONLY =          27,
+    //IPV6_JOIN_ANYCAST =    27,
+    //IPV6_LEAVE_ANYCAST =   28,
+    IPV6_IPSEC_POLICY =    28,
+    //IPV6_XFRM_POLICY =     35,
+}
diff --git a/libphobos/src/std/c/process.d b/libphobos/src/std/c/process.d
new file mode 100644
index 00000000000..ac6a3471e99
--- /dev/null
+++ b/libphobos/src/std/c/process.d
@@ -0,0 +1,95 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.stdlib) or the appropriate
+ *       core.sys.posix.* modules instead. This module will be removed in June
+ *       2017.)
+ *
+ * C's &lt;process.h&gt;
+ * Authors: Walter Bright, Digital Mars, www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCProcess
+ */
+deprecated("Import core.stdc.stdlib or the appropriate core.sys.posix.* modules instead")
+module std.c.process;
+
+private import core.stdc.stddef;
+public import core.stdc.stdlib : exit, abort, system;
+
+extern (C):
+
+//These declarations are not defined or used elsewhere.
+void _c_exit();
+void _cexit();
+void _dodtors();
+int getpid();
+enum { WAIT_CHILD, WAIT_GRANDCHILD }
+int cwait(int *,int,int);
+int wait(int *);
+int execlpe(in char *, in char *,...);
+
+//These constants are undefined elsewhere and only used in the deprecated part
+//of std.process.
+enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY };
+
+//These declarations are defined for Posix in core.sys.posix.unistd but unused
+//from here.
+void _exit(int);
+int execl(in char *, in char *,...);
+int execle(in char *, in char *,...);
+int execlp(in char *, in char *,...);
+
+//All of these except for execvpe are defined for Posix in core.sys.posix.unistd
+//and only used in the old part of std.process.
+int execv(in char *, in char **);
+int execve(in char *, in char **, in char **);
+int execvp(in char *, in char **);
+int execvpe(in char *, in char **, in char **);
+
+//All these Windows declarations are not publicly defined elsewhere and only
+//spawnvp is used once in a deprecated function in std.process.
+version (Windows)
+{
+    uint _beginthread(void function(void *),uint,void *);
+
+    extern (Windows) alias stdfp = uint function (void *);
+
+    uint _beginthreadex(void* security, uint stack_size,
+            stdfp start_addr, void* arglist, uint initflag,
+            uint* thrdaddr);
+
+    void _endthread();
+    void _endthreadex(uint);
+
+    int spawnl(int, in char *, in char *,...);
+    int spawnle(int, in char *, in char *,...);
+    int spawnlp(int, in char *, in char *,...);
+    int spawnlpe(int, in char *, in char *,...);
+    int spawnv(int, in char *, in char **);
+    int spawnve(int, in char *, in char **, in char **);
+    int spawnvp(int, in char *, in char **);
+    int spawnvpe(int, in char *, in char **, in char **);
+
+
+    int _wsystem(in wchar_t *);
+    int _wspawnl(int, in wchar_t *, in wchar_t *, ...);
+    int _wspawnle(int, in wchar_t *, in wchar_t *, ...);
+    int _wspawnlp(int, in wchar_t *, in wchar_t *, ...);
+    int _wspawnlpe(int, in wchar_t *, in wchar_t *, ...);
+    int _wspawnv(int, in wchar_t *, in wchar_t **);
+    int _wspawnve(int, in wchar_t *, in wchar_t **, in wchar_t **);
+    int _wspawnvp(int, in wchar_t *, in wchar_t **);
+    int _wspawnvpe(int, in wchar_t *, in wchar_t **, in wchar_t **);
+
+    int _wexecl(in wchar_t *, in wchar_t *, ...);
+    int _wexecle(in wchar_t *, in wchar_t *, ...);
+    int _wexeclp(in wchar_t *, in wchar_t *, ...);
+    int _wexeclpe(in wchar_t *, in wchar_t *, ...);
+    int _wexecv(in wchar_t *, in wchar_t **);
+    int _wexecve(in wchar_t *, in wchar_t **, in wchar_t **);
+    int _wexecvp(in wchar_t *, in wchar_t **);
+    int _wexecvpe(in wchar_t *, in wchar_t **, in wchar_t **);
+}
+
+
diff --git a/libphobos/src/std/c/stdarg.d b/libphobos/src/std/c/stdarg.d
new file mode 100644
index 00000000000..8bfe558fd7e
--- /dev/null
+++ b/libphobos/src/std/c/stdarg.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.stdarg) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;stdarg.h&gt;
+ * Authors: Hauke Duden and Walter Bright, Digital Mars, www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCStdarg
+ */
+deprecated("Import core.stdc.stdarg instead")
+module std.c.stdarg;
+
+public import core.stdc.stdarg;
diff --git a/libphobos/src/std/c/stddef.d b/libphobos/src/std/c/stddef.d
new file mode 100644
index 00000000000..2771d2e2f27
--- /dev/null
+++ b/libphobos/src/std/c/stddef.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.stddef) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;stddef.h&gt;
+ * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCStddef
+ */
+deprecated("Import core.stdc.stddef instead")
+module std.c.stddef;
+
+public import core.stdc.stddef;
diff --git a/libphobos/src/std/c/stdio.d b/libphobos/src/std/c/stdio.d
new file mode 100644
index 00000000000..a0afe50a7af
--- /dev/null
+++ b/libphobos/src/std/c/stdio.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.stdio) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;stdio.h&gt; for the D programming language
+ * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCStdio
+ */
+deprecated("Import core.stdc.stdio instead")
+module std.c.stdio;
+
+public import core.stdc.stdio;
diff --git a/libphobos/src/std/c/stdlib.d b/libphobos/src/std/c/stdlib.d
new file mode 100644
index 00000000000..8c2970fc3c4
--- /dev/null
+++ b/libphobos/src/std/c/stdlib.d
@@ -0,0 +1,18 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.stdlib) or $(D core.sys.posix.stdlib)
+ *       instead. This module will be removed in June 2017.)
+ *
+ * C's &lt;stdlib.h&gt;
+ * D Programming Language runtime library
+ * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCStdlib
+ */
+deprecated("Import core.stdc.stdlib or core.sys.posix.stdlib instead")
+module std.c.stdlib;
+
+public import core.stdc.stdlib;
+version(Posix) public import core.sys.posix.stdlib: setenv, unsetenv;
diff --git a/libphobos/src/std/c/string.d b/libphobos/src/std/c/string.d
new file mode 100644
index 00000000000..d7fc4578854
--- /dev/null
+++ b/libphobos/src/std/c/string.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.string) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;string.h&gt;
+ * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCString
+ */
+deprecated("Import core.stdc.string instead")
+module std.c.string;
+
+public import core.stdc.string;
diff --git a/libphobos/src/std/c/time.d b/libphobos/src/std/c/time.d
new file mode 100644
index 00000000000..24619e290a0
--- /dev/null
+++ b/libphobos/src/std/c/time.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.time) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;time.h&gt;
+ * Authors: Walter Bright, Digital Mars, www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCTime
+ */
+deprecated("Import core.stdc.time instead")
+module std.c.time;
+
+public import core.stdc.time;
diff --git a/libphobos/src/std/c/wcharh.d b/libphobos/src/std/c/wcharh.d
new file mode 100644
index 00000000000..3b58cfb17b3
--- /dev/null
+++ b/libphobos/src/std/c/wcharh.d
@@ -0,0 +1,16 @@
+// @@@DEPRECATED_2017-06@@@
+
+/**
+ * $(RED Deprecated. Use $(D core.stdc.wchar_) instead. This module will be
+ *       removed in June 2017.)
+ *
+ * C's &lt;wchar.h&gt;
+ * Authors: Walter Bright, Digital Mars, www.digitalmars.com
+ * License: Public Domain
+ * Macros:
+ *      WIKI=Phobos/StdCWchar
+ */
+deprecated("Import core.stdc.wchar_ instead")
+module std.c.wcharh;
+
+public import core.stdc.wchar_;
diff --git a/libphobos/src/std/c/windows/com.d b/libphobos/src/std/c/windows/com.d
new file mode 100644
index 00000000000..e96aceb8503
--- /dev/null
+++ b/libphobos/src/std/c/windows/com.d
@@ -0,0 +1,11 @@
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use $(D core.sys.windows.com) instead. This module will be
+          removed in June 2017.)
+  +/
+deprecated("Import core.sys.windows.com instead")
+module std.c.windows.com;
+
+version (Windows):
+public import core.sys.windows.com;
diff --git a/libphobos/src/std/c/windows/stat.d b/libphobos/src/std/c/windows/stat.d
new file mode 100644
index 00000000000..8aa082efd91
--- /dev/null
+++ b/libphobos/src/std/c/windows/stat.d
@@ -0,0 +1,15 @@
+
+/// Placed into public domain
+/// Author: Walter Bright
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use $(D core.sys.windows.stat) instead. This module will be
+          removed in June 2017.)
+  +/
+deprecated("Import core.sys.windows.stat instead")
+module std.c.windows.stat;
+
+version (Windows):
+public import core.sys.windows.stat;
diff --git a/libphobos/src/std/c/windows/windows.d b/libphobos/src/std/c/windows/windows.d
new file mode 100644
index 00000000000..7fcdb8ef20d
--- /dev/null
+++ b/libphobos/src/std/c/windows/windows.d
@@ -0,0 +1,15 @@
+
+/* Windows is a registered trademark of Microsoft Corporation in the United
+States and other countries. */
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use $(D core.sys.windows.windows) instead. This module will
+          be removed in June 2017.)
+  +/
+deprecated("Import core.sys.windows.windows instead")
+module std.c.windows.windows;
+
+version (Windows):
+public import core.sys.windows.windows;
diff --git a/libphobos/src/std/c/windows/winsock.d b/libphobos/src/std/c/windows/winsock.d
new file mode 100644
index 00000000000..5304e32b527
--- /dev/null
+++ b/libphobos/src/std/c/windows/winsock.d
@@ -0,0 +1,16 @@
+/*
+    Written by Christopher E. Miller
+    Placed into public domain.
+*/
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use $(D core.sys.windows.winsock2) instead. This module
+          will be removed in June 2017.)
+  +/
+deprecated("Import core.sys.windows.winsock2 instead")
+module std.c.windows.winsock;
+
+version (Windows):
+public import core.sys.windows.winsock2;
diff --git a/libphobos/src/std/compiler.d b/libphobos/src/std/compiler.d
new file mode 100644
index 00000000000..25936c94aec
--- /dev/null
+++ b/libphobos/src/std/compiler.d
@@ -0,0 +1,61 @@
+// Written in the D programming language.
+
+/**
+ * Identify the compiler used and its various features.
+ *
+ * Macros:
+ *      WIKI = Phobos/StdCompiler
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2011.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright), Alex Rønne Petersen
+ * Source:    $(PHOBOSSRC std/_compiler.d)
+ */
+/*          Copyright Digital Mars 2000 - 2011.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.compiler;
+
+immutable
+{
+    /// Vendor specific string naming the compiler, for example: "Digital Mars D".
+    string name = __VENDOR__;
+
+    /// Master list of D compiler vendors.
+    enum Vendor
+    {
+        unknown = 0,     /// Compiler vendor could not be detected
+        digitalMars = 1, /// Digital Mars D (DMD)
+        gnu = 2,         /// GNU D Compiler (GDC)
+        llvm = 3,        /// LLVM D Compiler (LDC)
+        dotNET = 4,      /// D.NET
+        sdc = 5,         /// Stupid D Compiler (SDC)
+    }
+
+    /// Which vendor produced this compiler.
+    version(StdDdoc)          Vendor vendor;
+    else version(DigitalMars) Vendor vendor = Vendor.digitalMars;
+    else version(GNU)         Vendor vendor = Vendor.gnu;
+    else version(LDC)         Vendor vendor = Vendor.llvm;
+    else version(D_NET)       Vendor vendor = Vendor.dotNET;
+    else version(SDC)         Vendor vendor = Vendor.sdc;
+    else                      Vendor vendor = Vendor.unknown;
+
+
+    /**
+     * The vendor specific version number, as in
+     * version_major.version_minor
+     */
+    uint version_major = __VERSION__ / 1000;
+    uint version_minor = __VERSION__ % 1000;    /// ditto
+
+
+    /**
+     * The version of the D Programming Language Specification
+     * supported by the compiler.
+     */
+    uint D_major = 2;
+    uint D_minor = 0;
+}
diff --git a/libphobos/src/std/complex.d b/libphobos/src/std/complex.d
new file mode 100644
index 00000000000..1f64cfa78fa
--- /dev/null
+++ b/libphobos/src/std/complex.d
@@ -0,0 +1,937 @@
+// Written in the D programming language.
+
+/** This module contains the $(LREF Complex) type, which is used to represent
+    _complex numbers, along with related mathematical operations and functions.
+
+    $(LREF Complex) will eventually
+    $(DDLINK deprecate, Deprecated Features, replace)
+    the built-in types $(D cfloat), $(D cdouble), $(D creal), $(D ifloat),
+    $(D idouble), and $(D ireal).
+
+    Authors:    Lars Tandle Kyllingstad, Don Clugston
+    Copyright:  Copyright (c) 2010, Lars T. Kyllingstad.
+    License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
+    Source:     $(PHOBOSSRC std/_complex.d)
+*/
+module std.complex;
+
+import std.traits;
+
+/** Helper function that returns a _complex number with the specified
+    real and imaginary parts.
+
+    Params:
+        R = (template parameter) type of real part of complex number
+        I = (template parameter) type of imaginary part of complex number
+
+        re = real part of complex number to be constructed
+        im = (optional) imaginary part of complex number
+
+    Returns:
+        $(D Complex) instance with real and imaginary parts set
+        to the values provided as input.  If neither $(D re) nor
+        $(D im) are floating-point numbers, the return type will
+        be $(D Complex!double).  Otherwise, the return type is
+        deduced using $(D std.traits.CommonType!(R, I)).
+*/
+auto complex(R)(R re)  @safe pure nothrow @nogc  if (is(R : double))
+{
+    static if (isFloatingPoint!R)
+        return Complex!R(re, 0);
+    else
+        return Complex!double(re, 0);
+}
+
+/// ditto
+auto complex(R, I)(R re, I im)  @safe pure nothrow @nogc
+    if (is(R : double) && is(I : double))
+{
+    static if (isFloatingPoint!R || isFloatingPoint!I)
+        return Complex!(CommonType!(R, I))(re, im);
+    else
+        return Complex!double(re, im);
+}
+
+///
+unittest
+{
+    auto a = complex(1.0);
+    static assert (is(typeof(a) == Complex!double));
+    assert (a.re == 1.0);
+    assert (a.im == 0.0);
+
+    auto b = complex(2.0L);
+    static assert (is(typeof(b) == Complex!real));
+    assert (b.re == 2.0L);
+    assert (b.im == 0.0L);
+
+    auto c = complex(1.0, 2.0);
+    static assert (is(typeof(c) == Complex!double));
+    assert (c.re == 1.0);
+    assert (c.im == 2.0);
+
+    auto d = complex(3.0, 4.0L);
+    static assert (is(typeof(d) == Complex!real));
+    assert (d.re == 3.0);
+    assert (d.im == 4.0L);
+
+    auto e = complex(1);
+    static assert (is(typeof(e) == Complex!double));
+    assert (e.re == 1);
+    assert (e.im == 0);
+
+    auto f = complex(1L, 2);
+    static assert (is(typeof(f) == Complex!double));
+    assert (f.re == 1L);
+    assert (f.im == 2);
+
+    auto g = complex(3, 4.0L);
+    static assert (is(typeof(g) == Complex!real));
+    assert (g.re == 3);
+    assert (g.im == 4.0L);
+}
+
+
+/** A complex number parametrised by a type $(D T), which must be either
+    $(D float), $(D double) or $(D real).
+*/
+struct Complex(T)  if (isFloatingPoint!T)
+{
+    import std.format : FormatSpec;
+
+    /** The real part of the number. */
+    T re;
+
+    /** The imaginary part of the number. */
+    T im;
+
+    /** Converts the complex number to a string representation.
+
+    The second form of this function is usually not called directly;
+    instead, it is used via $(XREF string,format), as shown in the examples
+    below.  Supported format characters are 'e', 'f', 'g', 'a', and 's'.
+
+    See the $(LINK2 std_format.html, std.format) and $(XREF string, format)
+    documentation for more information.
+    */
+    string toString() const /* TODO: @safe pure nothrow */
+    {
+        import std.exception : assumeUnique;
+        char[] buf;
+        buf.reserve(100);
+        auto fmt = FormatSpec!char("%s");
+        toString((const(char)[] s) { buf ~= s; }, fmt);
+        return assumeUnique(buf);
+    }
+
+    static if (is(T == double))
+    ///
+    unittest
+    {
+        auto c = complex(1.2, 3.4);
+
+        // Vanilla toString formatting:
+        assert(c.toString() == "1.2+3.4i");
+
+        // Formatting with std.string.format specs: the precision and width
+        // specifiers apply to both the real and imaginary parts of the
+        // complex number.
+        import std.format : format;
+        assert(format("%.2f", c)  == "1.20+3.40i");
+        assert(format("%4.1f", c) == " 1.2+ 3.4i");
+    }
+
+    /// ditto
+    void toString(Char)(scope void delegate(const(Char)[]) sink,
+                        FormatSpec!Char formatSpec) const
+    {
+        import std.math : signbit;
+        import std.format : formatValue;
+        formatValue(sink, re, formatSpec);
+        if (signbit(im) == 0) sink("+");
+        formatValue(sink, im, formatSpec);
+        sink("i");
+    }
+
+@safe pure nothrow @nogc:
+
+    this(R : T)(Complex!R z)
+    {
+        re = z.re;
+        im = z.im;
+    }
+
+    this(Rx : T, Ry : T)(Rx x, Ry y)
+    {
+        re = x;
+        im = y;
+    }
+
+    this(R : T)(R r)
+    {
+        re = r;
+        im = 0;
+    }
+
+    // ASSIGNMENT OPERATORS
+
+    // this = complex
+    ref Complex opAssign(R : T)(Complex!R z)
+    {
+        re = z.re;
+        im = z.im;
+        return this;
+    }
+
+    // this = numeric
+    ref Complex opAssign(R : T)(R r)
+    {
+        re = r;
+        im = 0;
+        return this;
+    }
+
+    // COMPARISON OPERATORS
+
+    // this == complex
+    bool opEquals(R : T)(Complex!R z) const
+    {
+        return re == z.re && im == z.im;
+    }
+
+    // this == numeric
+    bool opEquals(R : T)(R r) const
+    {
+        return re == r && im == 0;
+    }
+
+    // UNARY OPERATORS
+
+    // +complex
+    Complex opUnary(string op)() const
+        if (op == "+")
+    {
+        return this;
+    }
+
+    // -complex
+    Complex opUnary(string op)() const
+        if (op == "-")
+    {
+        return Complex(-re, -im);
+    }
+
+    // BINARY OPERATORS
+
+    // complex op complex
+    Complex!(CommonType!(T,R)) opBinary(string op, R)(Complex!R z) const
+    {
+        alias C = typeof(return);
+        auto w = C(this.re, this.im);
+        return w.opOpAssign!(op)(z);
+    }
+
+    // complex op numeric
+    Complex!(CommonType!(T,R)) opBinary(string op, R)(R r) const
+        if (isNumeric!R)
+    {
+        alias C = typeof(return);
+        auto w = C(this.re, this.im);
+        return w.opOpAssign!(op)(r);
+    }
+
+    // numeric + complex,  numeric * complex
+    Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const
+        if ((op == "+" || op == "*") && (isNumeric!R))
+    {
+        return opBinary!(op)(r);
+    }
+
+    // numeric - complex
+    Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const
+        if (op == "-" && isNumeric!R)
+    {
+        return Complex(r - re, -im);
+    }
+
+    // numeric / complex
+    Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const
+        if (op == "/" && isNumeric!R)
+    {
+        import std.math : fabs;
+        typeof(return) w = void;
+        if (fabs(re) < fabs(im))
+        {
+            immutable ratio = re/im;
+            immutable rdivd = r/(re*ratio + im);
+
+            w.re = rdivd*ratio;
+            w.im = -rdivd;
+        }
+        else
+        {
+            immutable ratio = im/re;
+            immutable rdivd = r/(re + im*ratio);
+
+            w.re = rdivd;
+            w.im = -rdivd*ratio;
+        }
+
+        return w;
+    }
+
+    // numeric ^^ complex
+    Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R lhs) const
+        if (op == "^^" && isNumeric!R)
+    {
+        import std.math : cos, exp, log, sin, PI;
+        Unqual!(CommonType!(T, R)) ab = void, ar = void;
+
+        if (lhs >= 0)
+        {
+            // r = lhs
+            // theta = 0
+            ab = lhs ^^ this.re;
+            ar = log(lhs) * this.im;
+        }
+        else
+        {
+            // r = -lhs
+            // theta = PI
+            ab = (-lhs) ^^ this.re * exp(-PI * this.im);
+            ar = PI * this.re + log(-lhs) * this.im;
+        }
+
+        return typeof(return)(ab * cos(ar), ab * sin(ar));
+    }
+
+    // OP-ASSIGN OPERATORS
+
+    // complex += complex,  complex -= complex
+    ref Complex opOpAssign(string op, C)(C z)
+        if ((op == "+" || op == "-") && is(C R == Complex!R))
+    {
+        mixin ("re "~op~"= z.re;");
+        mixin ("im "~op~"= z.im;");
+        return this;
+    }
+
+    // complex *= complex
+    ref Complex opOpAssign(string op, C)(C z)
+        if (op == "*" && is(C R == Complex!R))
+    {
+        auto temp = re*z.re - im*z.im;
+        im = im*z.re + re*z.im;
+        re = temp;
+        return this;
+    }
+
+    // complex /= complex
+    ref Complex opOpAssign(string op, C)(C z)
+        if (op == "/" && is(C R == Complex!R))
+    {
+        import std.math : fabs;
+        if (fabs(z.re) < fabs(z.im))
+        {
+            immutable ratio = z.re/z.im;
+            immutable denom = z.re*ratio + z.im;
+
+            immutable temp = (re*ratio + im)/denom;
+            im = (im*ratio - re)/denom;
+            re = temp;
+        }
+        else
+        {
+            immutable ratio = z.im/z.re;
+            immutable denom = z.re + z.im*ratio;
+
+            immutable temp = (re + im*ratio)/denom;
+            im = (im - re*ratio)/denom;
+            re = temp;
+        }
+        return this;
+    }
+
+    // complex ^^= complex
+    ref Complex opOpAssign(string op, C)(C z)
+        if (op == "^^" && is(C R == Complex!R))
+    {
+        import std.math: exp, log, cos, sin;
+        immutable r = abs(this);
+        immutable t = arg(this);
+        immutable ab = r^^z.re * exp(-t*z.im);
+        immutable ar = t*z.re + log(r)*z.im;
+
+        re = ab*cos(ar);
+        im = ab*sin(ar);
+        return this;
+    }
+
+    // complex += numeric,  complex -= numeric
+    ref Complex opOpAssign(string op, U : T)(U a)
+        if (op == "+" || op == "-")
+    {
+        mixin ("re "~op~"= a;");
+        return this;
+    }
+
+    // complex *= numeric,  complex /= numeric
+    ref Complex opOpAssign(string op, U : T)(U a)
+        if (op == "*" || op == "/")
+    {
+        mixin ("re "~op~"= a;");
+        mixin ("im "~op~"= a;");
+        return this;
+    }
+
+    // complex ^^= real
+    ref Complex opOpAssign(string op, R)(R r)
+        if (op == "^^" && isFloatingPoint!R)
+    {
+        import std.math: cos, sin;
+        immutable ab = abs(this)^^r;
+        immutable ar = arg(this)*r;
+        re = ab*cos(ar);
+        im = ab*sin(ar);
+        return this;
+    }
+
+    // complex ^^= int
+    ref Complex opOpAssign(string op, U)(U i)
+        if (op == "^^" && isIntegral!U)
+    {
+        switch (i)
+        {
+        case 0:
+            re = 1.0;
+            im = 0.0;
+            break;
+        case 1:
+            // identity; do nothing
+            break;
+        case 2:
+            this *= this;
+            break;
+        case 3:
+            auto z = this;
+            this *= z;
+            this *= z;
+            break;
+        default:
+            this ^^= cast(real) i;
+        }
+        return this;
+    }
+}
+
+unittest
+{
+    import std.math;
+    import std.complex;
+
+    enum EPS = double.epsilon;
+    auto c1 = complex(1.0, 1.0);
+
+    // Check unary operations.
+    auto c2 = Complex!double(0.5, 2.0);
+
+    assert (c2 == +c2);
+
+    assert ((-c2).re == -(c2.re));
+    assert ((-c2).im == -(c2.im));
+    assert (c2 == -(-c2));
+
+    // Check complex-complex operations.
+    auto cpc = c1 + c2;
+    assert (cpc.re == c1.re + c2.re);
+    assert (cpc.im == c1.im + c2.im);
+
+    auto cmc = c1 - c2;
+    assert (cmc.re == c1.re - c2.re);
+    assert (cmc.im == c1.im - c2.im);
+
+    auto ctc = c1 * c2;
+    assert (approxEqual(abs(ctc), abs(c1)*abs(c2), EPS));
+    assert (approxEqual(arg(ctc), arg(c1)+arg(c2), EPS));
+
+    auto cdc = c1 / c2;
+    assert (approxEqual(abs(cdc), abs(c1)/abs(c2), EPS));
+    assert (approxEqual(arg(cdc), arg(c1)-arg(c2), EPS));
+
+    auto cec = c1^^c2;
+    assert (approxEqual(cec.re, 0.11524131979943839881, EPS));
+    assert (approxEqual(cec.im, 0.21870790452746026696, EPS));
+
+    // Check complex-real operations.
+    double a = 123.456;
+
+    auto cpr = c1 + a;
+    assert (cpr.re == c1.re + a);
+    assert (cpr.im == c1.im);
+
+    auto cmr = c1 - a;
+    assert (cmr.re == c1.re - a);
+    assert (cmr.im == c1.im);
+
+    auto ctr = c1 * a;
+    assert (ctr.re == c1.re*a);
+    assert (ctr.im == c1.im*a);
+
+    auto cdr = c1 / a;
+    assert (approxEqual(abs(cdr), abs(c1)/a, EPS));
+    assert (approxEqual(arg(cdr), arg(c1), EPS));
+
+    auto cer = c1^^3.0;
+    assert (approxEqual(abs(cer), abs(c1)^^3, EPS));
+    assert (approxEqual(arg(cer), arg(c1)*3, EPS));
+
+    auto rpc = a + c1;
+    assert (rpc == cpr);
+
+    auto rmc = a - c1;
+    assert (rmc.re == a-c1.re);
+    assert (rmc.im == -c1.im);
+
+    auto rtc = a * c1;
+    assert (rtc == ctr);
+
+    auto rdc = a / c1;
+    assert (approxEqual(abs(rdc), a/abs(c1), EPS));
+    assert (approxEqual(arg(rdc), -arg(c1), EPS));
+
+    auto rec1a = 1.0 ^^ c1;
+    assert(rec1a.re == 1.0);
+    assert(rec1a.im == 0.0);
+
+    auto rec2a = 1.0 ^^ c2;
+    assert(rec2a.re == 1.0);
+    assert(rec2a.im == 0.0);
+
+    auto rec1b = (-1.0) ^^ c1;
+    assert(approxEqual(abs(rec1b), std.math.exp(-PI * c1.im), EPS));
+    auto arg1b = arg(rec1b);
+    /* The argument _should_ be PI, but floating-point rounding error
+     * means that in fact the imaginary part is very slightly negative.
+     */
+    assert(approxEqual(arg1b, PI, EPS) || approxEqual(arg1b, -PI, EPS));
+
+    auto rec2b = (-1.0) ^^ c2;
+    assert(approxEqual(abs(rec2b), std.math.exp(-2 * PI), EPS));
+    assert(approxEqual(arg(rec2b), PI_2, EPS));
+
+    auto rec3a = 0.79 ^^ complex(6.8, 5.7);
+    auto rec3b = complex(0.79, 0.0) ^^ complex(6.8, 5.7);
+    assert(approxEqual(rec3a.re, rec3b.re, EPS));
+    assert(approxEqual(rec3a.im, rec3b.im, EPS));
+
+    auto rec4a = (-0.79) ^^ complex(6.8, 5.7);
+    auto rec4b = complex(-0.79, 0.0) ^^ complex(6.8, 5.7);
+    assert(approxEqual(rec4a.re, rec4b.re, EPS));
+    assert(approxEqual(rec4a.im, rec4b.im, EPS));
+
+    auto rer = a ^^ complex(2.0, 0.0);
+    auto rcheck = a ^^ 2.0;
+    static assert(is(typeof(rcheck) == double));
+    assert(feqrel(rer.re, rcheck) == double.mant_dig);
+    assert(isIdentical(rer.re, rcheck));
+    assert(rer.im == 0.0);
+
+    auto rer2 = (-a) ^^ complex(2.0, 0.0);
+    rcheck = (-a) ^^ 2.0;
+    assert(feqrel(rer2.re, rcheck) == double.mant_dig);
+    assert(isIdentical(rer2.re, rcheck));
+    assert(approxEqual(rer2.im, 0.0, EPS));
+
+    auto rer3 = (-a) ^^ complex(-2.0, 0.0);
+    rcheck = (-a) ^^ (-2.0);
+    assert(feqrel(rer3.re, rcheck) == double.mant_dig);
+    assert(isIdentical(rer3.re, rcheck));
+    assert(approxEqual(rer3.im, 0.0, EPS));
+
+    auto rer4 = a ^^ complex(-2.0, 0.0);
+    rcheck = a ^^ (-2.0);
+    assert(feqrel(rer4.re, rcheck) == double.mant_dig);
+    assert(isIdentical(rer4.re, rcheck));
+    assert(rer4.im == 0.0);
+
+    // Check Complex-int operations.
+    foreach (i; 0..6)
+    {
+        auto cei = c1^^i;
+        assert (approxEqual(abs(cei), abs(c1)^^i, EPS));
+        // Use cos() here to deal with arguments that go outside
+        // the (-pi,pi] interval (only an issue for i>3).
+        assert (approxEqual(std.math.cos(arg(cei)), std.math.cos(arg(c1)*i), EPS));
+    }
+
+    // Check operations between different complex types.
+    auto cf = Complex!float(1.0, 1.0);
+    auto cr = Complex!real(1.0, 1.0);
+    auto c1pcf = c1 + cf;
+    auto c1pcr = c1 + cr;
+    static assert (is(typeof(c1pcf) == Complex!double));
+    static assert (is(typeof(c1pcr) == Complex!real));
+    assert (c1pcf.re == c1pcr.re);
+    assert (c1pcf.im == c1pcr.im);
+}
+
+unittest
+{
+    // Initialization
+    Complex!double a = 1;
+    assert (a.re == 1 && a.im == 0);
+    Complex!double b = 1.0;
+    assert (b.re == 1.0 && b.im == 0);
+    Complex!double c = Complex!real(1.0, 2);
+    assert (c.re == 1.0 && c.im == 2);
+}
+
+unittest
+{
+    // Assignments and comparisons
+    Complex!double z;
+
+    z = 1;
+    assert (z == 1);
+    assert (z.re == 1.0  &&  z.im == 0.0);
+
+    z = 2.0;
+    assert (z == 2.0);
+    assert (z.re == 2.0  &&  z.im == 0.0);
+
+    z = 1.0L;
+    assert (z == 1.0L);
+    assert (z.re == 1.0  &&  z.im == 0.0);
+
+    auto w = Complex!real(1.0, 1.0);
+    z = w;
+    assert (z == w);
+    assert (z.re == 1.0  &&  z.im == 1.0);
+
+    auto c = Complex!float(2.0, 2.0);
+    z = c;
+    assert (z == c);
+    assert (z.re == 2.0  &&  z.im == 2.0);
+}
+
+
+/*  Makes Complex!(Complex!T) fold to Complex!T.
+
+    The rationale for this is that just like the real line is a
+    subspace of the complex plane, the complex plane is a subspace
+    of itself.  Example of usage:
+    ---
+    Complex!T addI(T)(T x)
+    {
+        return x + Complex!T(0.0, 1.0);
+    }
+    ---
+    The above will work if T is both real and complex.
+*/
+template Complex(T) if (is(T R == Complex!R))
+{
+    alias Complex = T;
+}
+
+unittest
+{
+    static assert (is(Complex!(Complex!real) == Complex!real));
+
+    Complex!T addI(T)(T x)
+    {
+        return x + Complex!T(0.0, 1.0);
+    }
+
+    auto z1 = addI(1.0);
+    assert (z1.re == 1.0 && z1.im == 1.0);
+
+    enum one = Complex!double(1.0, 0.0);
+    auto z2 = addI(one);
+    assert (z1 == z2);
+}
+
+
+/**
+   Params: z = A complex number.
+   Returns: The absolute value (or modulus) of `z`.
+*/
+T abs(T)(Complex!T z) @safe pure nothrow @nogc
+{
+    import std.math : hypot;
+    return hypot(z.re, z.im);
+}
+
+///
+unittest
+{
+    static import std.math;
+    assert (abs(complex(1.0)) == 1.0);
+    assert (abs(complex(0.0, 1.0)) == 1.0);
+    assert (abs(complex(1.0L, -2.0L)) == std.math.sqrt(5.0L));
+}
+
+
+/++
+   Params:
+    z = A complex number.
+    x = A real number.
+   Returns: The squared modulus of `z`.
+   For genericity, if called on a real number, returns its square.
++/
+T sqAbs(T)(Complex!T z) @safe pure nothrow @nogc
+{
+    return z.re*z.re + z.im*z.im;
+}
+
+///
+unittest
+{
+    import std.math;
+    assert (sqAbs(complex(0.0)) == 0.0);
+    assert (sqAbs(complex(1.0)) == 1.0);
+    assert (sqAbs(complex(0.0, 1.0)) == 1.0);
+    assert (approxEqual(sqAbs(complex(1.0L, -2.0L)), 5.0L));
+    assert (approxEqual(sqAbs(complex(-3.0L, 1.0L)), 10.0L));
+    assert (approxEqual(sqAbs(complex(1.0f,-1.0f)), 2.0f));
+}
+
+/// ditto
+T sqAbs(T)(T x) @safe pure nothrow @nogc
+    if (isFloatingPoint!T)
+{
+    return x*x;
+}
+
+unittest
+{
+    import std.math;
+    assert (sqAbs(0.0) == 0.0);
+    assert (sqAbs(-1.0) == 1.0);
+    assert (approxEqual(sqAbs(-3.0L), 9.0L));
+    assert (approxEqual(sqAbs(-5.0f), 25.0f));
+}
+
+
+/**
+ Params: z = A complex number.
+ Returns: The argument (or phase) of `z`.
+ */
+T arg(T)(Complex!T z) @safe pure nothrow @nogc
+{
+    import std.math : atan2;
+    return atan2(z.im, z.re);
+}
+
+///
+unittest
+{
+    import std.math;
+    assert (arg(complex(1.0)) == 0.0);
+    assert (arg(complex(0.0L, 1.0L)) == PI_2);
+    assert (arg(complex(1.0L, 1.0L)) == PI_4);
+}
+
+
+/**
+  Params: z = A complex number.
+  Returns: The complex conjugate of `z`.
+*/
+Complex!T conj(T)(Complex!T z) @safe pure nothrow @nogc
+{
+    return Complex!T(z.re, -z.im);
+}
+
+///
+unittest
+{
+    assert (conj(complex(1.0)) == complex(1.0));
+    assert (conj(complex(1.0, 2.0)) == complex(1.0, -2.0));
+}
+
+
+/**
+  Constructs a complex number given its absolute value and argument.
+  Params:
+    modulus = The modulus
+    argument = The argument
+  Returns: The complex number with the given modulus and argument.
+*/
+Complex!(CommonType!(T, U)) fromPolar(T, U)(T modulus, U argument)
+    @safe pure nothrow @nogc
+{
+    import std.math : sin, cos;
+    return Complex!(CommonType!(T,U))
+        (modulus*cos(argument), modulus*sin(argument));
+}
+
+///
+unittest
+{
+    import std.math;
+    auto z = fromPolar(std.math.sqrt(2.0), PI_4);
+    assert (approxEqual(z.re, 1.0L, real.epsilon));
+    assert (approxEqual(z.im, 1.0L, real.epsilon));
+}
+
+
+/**
+    Trigonometric functions on complex numbers.
+
+    Params: z = A complex number.
+    Returns: The sine and cosine of `z`, respectively.
+*/
+Complex!T sin(T)(Complex!T z)  @safe pure nothrow @nogc
+{
+    import std.math : expi, coshisinh;
+    auto cs = expi(z.re);
+    auto csh = coshisinh(z.im);
+    return typeof(return)(cs.im * csh.re, cs.re * csh.im);
+}
+
+///
+unittest
+{
+    static import std.math;
+    assert(sin(complex(0.0)) == 0.0);
+    assert(sin(complex(2.0L, 0)) == std.math.sin(2.0L));
+}
+
+
+/// ditto
+Complex!T cos(T)(Complex!T z)  @safe pure nothrow @nogc
+{
+    import std.math : expi, coshisinh;
+    auto cs = expi(z.re);
+    auto csh = coshisinh(z.im);
+    return typeof(return)(cs.re * csh.re, - cs.im * csh.im);
+}
+
+///
+unittest{
+    import std.math;
+    import std.complex;
+    assert(cos(complex(0.0)) == 1.0);
+    assert(cos(complex(1.3L)) == std.math.cos(1.3L));
+    assert(cos(complex(0, 5.2L)) == cosh(5.2L));
+}
+
+
+/**
+    Params: y = A real number.
+    Returns: The value of cos(y) + i sin(y).
+
+    Note:
+    $(D expi) is included here for convenience and for easy migration of code
+    that uses $(XREF math,_expi).  Unlike $(XREF math,_expi), which uses the
+    x87 $(I fsincos) instruction when possible, this function is no faster
+    than calculating cos(y) and sin(y) separately.
+*/
+Complex!real expi(real y)  @trusted pure nothrow @nogc
+{
+    import std.math : cos, sin;
+    return Complex!real(cos(y), sin(y));
+}
+
+///
+unittest
+{
+    static import std.math;
+
+    assert(expi(1.3e5L) == complex(std.math.cos(1.3e5L), std.math.sin(1.3e5L)));
+    assert(expi(0.0L) == 1.0L);
+    auto z1 = expi(1.234);
+    auto z2 = std.math.expi(1.234);
+    assert(z1.re == z2.re && z1.im == z2.im);
+}
+
+
+/**
+    Params: z = A complex number.
+    Returns: The square root of `z`.
+*/
+Complex!T sqrt(T)(Complex!T z)  @safe pure nothrow @nogc
+{
+    static import std.math;
+    typeof(return) c;
+    real x,y,w,r;
+
+    if (z == 0)
+    {
+        c = typeof(return)(0, 0);
+    }
+    else
+    {
+        real z_re = z.re;
+        real z_im = z.im;
+
+        x = std.math.fabs(z_re);
+        y = std.math.fabs(z_im);
+        if (x >= y)
+        {
+            r = y / x;
+            w = std.math.sqrt(x)
+                * std.math.sqrt(0.5 * (1 + std.math.sqrt(1 + r * r)));
+        }
+        else
+        {
+            r = x / y;
+            w = std.math.sqrt(y)
+                * std.math.sqrt(0.5 * (r + std.math.sqrt(1 + r * r)));
+        }
+
+        if (z_re >= 0)
+        {
+            c = typeof(return)(w, z_im / (w + w));
+        }
+        else
+        {
+            if (z_im < 0)
+                w = -w;
+            c = typeof(return)(z_im / (w + w), w);
+        }
+    }
+    return c;
+}
+
+///
+unittest
+{
+    static import std.math;
+    assert (sqrt(complex(0.0)) == 0.0);
+    assert (sqrt(complex(1.0L, 0)) == std.math.sqrt(1.0L));
+    assert (sqrt(complex(-1.0L, 0)) == complex(0, 1.0L));
+}
+
+// Issue 10881: support %f formatting of complex numbers
+unittest
+{
+    import std.format : format;
+
+    auto x = complex(1.2, 3.4);
+    assert(format("%.2f", x) == "1.20+3.40i");
+
+    auto y = complex(1.2, -3.4);
+    assert(format("%.2f", y) == "1.20-3.40i");
+}
+
+unittest
+{
+    // Test wide string formatting
+    import std.format;
+    wstring wformat(T)(string format, Complex!T c)
+    {
+        import std.array : appender;
+        auto w = appender!wstring();
+        auto n = formattedWrite(w, format, c);
+        return w.data;
+    }
+
+    auto x = complex(1.2, 3.4);
+    assert(wformat("%.2f", x) == "1.20+3.40i"w);
+}
+
+unittest
+{
+    // Test ease of use (vanilla toString() should be supported)
+    assert(complex(1.2, 3.4).toString() == "1.2+3.4i");
+}
diff --git a/libphobos/src/std/concurrency.d b/libphobos/src/std/concurrency.d
new file mode 100644
index 00000000000..c9629ecdd79
--- /dev/null
+++ b/libphobos/src/std/concurrency.d
@@ -0,0 +1,2662 @@
+/**
+ * This is a low-level messaging API upon which more structured or restrictive
+ * APIs may be built.  The general idea is that every messageable entity is
+ * represented by a common handle type called a Tid, which allows messages to
+ * be sent to logical threads that are executing in both the current process
+ * and in external processes using the same interface.  This is an important
+ * aspect of scalability because it allows the components of a program to be
+ * spread across available resources with few to no changes to the actual
+ * implementation.
+ *
+ * A logical thread is an execution context that has its own stack and which
+ * runs asynchronously to other logical threads.  These may be preemptively
+ * scheduled kernel threads, fibers (cooperative user-space threads), or some
+ * other concept with similar behavior.
+ *
+ * The type of concurrency used when logical threads are created is determined
+ * by the Scheduler selected at initialization time.  The default behavior is
+ * currently to create a new kernel thread per call to spawn, but other
+ * schedulers are available that multiplex fibers across the main thread or
+ * use some combination of the two approaches.
+ *
+ * Synposis:
+ * ---
+ * import std.stdio;
+ * import std.concurrency;
+ *
+ * void spawnedFunc(Tid ownerTid)
+ * {
+ *     // Receive a message from the owner thread.
+ *     receive(
+ *         (int i) { writeln("Received the number ", i);}
+ *     );
+ *
+ *     // Send a message back to the owner thread
+ *     // indicating success.
+ *     send(ownerTid, true);
+ * }
+ *
+ * void main()
+ * {
+ *     // Start spawnedFunc in a new thread.
+ *     auto childTid = spawn(&spawnedFunc, thisTid);
+ *
+ *     // Send the number 42 to this new thread.
+ *     send(childTid, 42);
+ *
+ *     // Receive the result code.
+ *     auto wasSuccessful = receiveOnly!(bool);
+ *     assert(wasSuccessful);
+ *     writeln("Successfully printed number.");
+ * }
+ * ---
+ *
+ * Copyright: Copyright Sean Kelly 2009 - 2014.
+ * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+ * Authors:   Sean Kelly, Alex Rønne Petersen, Martin Nowak
+ * Source:    $(PHOBOSSRC std/_concurrency.d)
+ */
+/*          Copyright Sean Kelly 2009 - 2014.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.concurrency;
+
+
+public
+{
+    import std.variant;
+}
+private
+{
+    import core.atomic;
+    import core.thread;
+    import core.sync.mutex;
+    import core.sync.condition;
+    import std.algorithm;
+    import std.exception;
+    import std.meta;
+    import std.range;
+    import std.string;
+    import std.traits;
+    import std.typecons;
+    import std.concurrencybase;
+
+    template hasLocalAliasing(T...)
+    {
+        static if( !T.length )
+            enum hasLocalAliasing = false;
+        else
+            enum hasLocalAliasing = (std.traits.hasUnsharedAliasing!(T[0]) && !is(T[0] == Tid)) ||
+                                     std.concurrency.hasLocalAliasing!(T[1 .. $]);
+    }
+
+    enum MsgType
+    {
+        standard,
+        priority,
+        linkDead,
+    }
+
+    struct Message
+    {
+        MsgType type;
+        Variant data;
+
+        this(T...)( MsgType t, T vals )
+            if( T.length < 1 )
+        {
+            static assert( false, "messages must contain at least one item" );
+        }
+
+        this(T...)( MsgType t, T vals )
+            if( T.length == 1 )
+        {
+            type = t;
+            data = vals[0];
+        }
+
+        this(T...)( MsgType t, T vals )
+            if( T.length > 1 )
+        {
+            type = t;
+            data = Tuple!(T)( vals );
+        }
+
+        @property auto convertsTo(T...)()
+        {
+            static if( T.length == 1 )
+                return is( T[0] == Variant ) ||
+                       data.convertsTo!(T);
+            else
+                return data.convertsTo!(Tuple!(T));
+        }
+
+        @property auto get(T...)()
+        {
+            static if( T.length == 1 )
+            {
+                static if( is( T[0] == Variant ) )
+                    return data;
+                else
+                    return data.get!(T);
+            }
+            else
+            {
+                return data.get!(Tuple!(T));
+            }
+        }
+
+        auto map(Op)( Op op )
+        {
+            alias Args = Parameters!(Op);
+
+            static if( Args.length == 1 )
+            {
+                static if( is( Args[0] == Variant ) )
+                    return op( data );
+                else
+                    return op( data.get!(Args) );
+            }
+            else
+            {
+                return op( data.get!(Tuple!(Args)).expand );
+            }
+        }
+    }
+
+    void checkops(T...)( T ops )
+    {
+        foreach( i, t1; T )
+        {
+            static assert( isFunctionPointer!t1 || isDelegate!t1 );
+            alias a1 = Parameters!(t1);
+            alias r1 = ReturnType!(t1);
+
+            static if( i < T.length - 1 && is( r1 == void ) )
+            {
+                static assert( a1.length != 1 || !is( a1[0] == Variant ),
+                               "function with arguments " ~ a1.stringof ~
+                               " occludes successive function" );
+
+                foreach( t2; T[i+1 .. $] )
+                {
+                    static assert( isFunctionPointer!t2 || isDelegate!t2 );
+                    alias a2 = Parameters!(t2);
+
+                    static assert( !is( a1 == a2 ),
+                                   "function with arguments " ~ a1.stringof ~
+                                   " occludes successive function" );
+                }
+            }
+        }
+    }
+
+    @property ref ThreadInfo thisInfo()
+    {
+        if( scheduler is null )
+            return ThreadInfo.thisInfo;
+        return scheduler.thisInfo;
+    }
+}
+
+
+static ~this()
+{
+    thisInfo.cleanup();
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Exceptions
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Thrown on calls to $(D receiveOnly) if a message other than the type
+ * the receiving thread expected is sent.
+ */
+class MessageMismatch : Exception
+{
+    this( string msg = "Unexpected message type" )
+    {
+        super( msg );
+    }
+}
+
+
+/**
+ * Thrown on calls to $(D receive) if the thread that spawned the receiving
+ * thread has terminated and no more messages exist.
+ */
+class OwnerTerminated : Exception
+{
+    this( Tid t, string msg = "Owner terminated" )
+    {
+        super( msg );
+        tid = t;
+    }
+
+    Tid tid;
+}
+
+
+/**
+ * Thrown if a linked thread has terminated.
+ */
+class LinkTerminated : Exception
+{
+    this( Tid t, string msg = "Link terminated" )
+    {
+        super( msg );
+        tid = t;
+    }
+
+    Tid tid;
+}
+
+
+/**
+ * Thrown if a message was sent to a thread via
+ * $(XREF concurrency, prioritySend) and the receiver does not have a handler
+ * for a message of this type.
+ */
+class PriorityMessageException : Exception
+{
+    this( Variant vals )
+    {
+        super( "Priority message" );
+        message = vals;
+    }
+
+    /**
+     * The message that was sent.
+     */
+    Variant message;
+}
+
+
+/**
+ * Thrown on mailbox crowding if the mailbox is configured with
+ * $(D OnCrowding.throwException).
+ */
+class MailboxFull : Exception
+{
+    this( Tid t, string msg = "Mailbox full" )
+    {
+        super( msg );
+        tid = t;
+    }
+
+    Tid tid;
+}
+
+
+/**
+ * Thrown when a Tid is missing, e.g. when $(D ownerTid) doesn't
+ * find an owner thread.
+ */
+class TidMissingException : Exception
+{
+    mixin basicExceptionCtors;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Thread ID
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * An opaque type used to represent a logical thread.
+ */
+struct Tid
+{
+private:
+    this( MessageBox m ) @safe
+    {
+        mbox = m;
+    }
+
+
+    MessageBox  mbox;
+
+public:
+
+    /**
+     * Generate a convenient string for identifying this Tid.  This is only
+     * useful to see if Tid's that are currently executing are the same or
+     * different, e.g. for logging and debugging.  It is potentially possible
+     * that a Tid executed in the future will have the same toString() output
+     * as another Tid that has already terminated.
+     */
+    void toString(scope void delegate(const(char)[]) sink)
+    {
+        import std.format;
+        formattedWrite(sink, "Tid(%x)", cast(void*)mbox);
+    }
+
+}
+
+unittest
+{
+    import std.conv : text;
+    Tid tid;
+    assert(text(tid) == "Tid(0)");
+    auto tid2 = thisTid;
+    assert(text(tid2) != "Tid(0)");
+    auto tid3 = tid2;
+    assert(text(tid2) == text(tid3));
+}
+
+
+/**
+ * Returns the caller's Tid.
+ */
+@property Tid thisTid() @safe
+{
+    // TODO: remove when concurrency is safe
+    auto trus = delegate() @trusted
+    {
+        if( thisInfo.ident != Tid.init )
+            return thisInfo.ident;
+        thisInfo.ident = Tid( new MessageBox );
+        return thisInfo.ident;
+    };
+
+    return trus();
+}
+
+/**
+ * Return the Tid of the thread which spawned the caller's thread.
+ *
+ * Throws: A $(D TidMissingException) exception if
+ * there is no owner thread.
+ */
+@property Tid ownerTid()
+{
+    enforce!TidMissingException(thisInfo.owner.mbox !is null,
+                                  "Error: Thread has no owner thread.");
+    return thisInfo.owner;
+}
+
+unittest
+{
+    static void fun()
+    {
+        string res = receiveOnly!string();
+        assert(res == "Main calling");
+        ownerTid.send("Child responding");
+    }
+
+    assertThrown!TidMissingException(ownerTid);
+    auto child = spawn(&fun);
+    child.send("Main calling");
+    string res = receiveOnly!string();
+    assert(res == "Child responding");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Thread Creation
+//////////////////////////////////////////////////////////////////////////////
+
+private template isSpawnable(F, T...)
+{
+    template isParamsImplicitlyConvertible(F1, F2, int i=0)
+    {
+        alias param1 = Parameters!F1;
+        alias param2 = Parameters!F2;
+        static if (param1.length != param2.length)
+            enum isParamsImplicitlyConvertible = false;
+        else static if (param1.length == i)
+            enum isParamsImplicitlyConvertible = true;
+        else static if (isImplicitlyConvertible!(param2[i], param1[i]))
+            enum isParamsImplicitlyConvertible = isParamsImplicitlyConvertible!(F1, F2, i+1);
+        else
+            enum isParamsImplicitlyConvertible = false;
+    }
+    enum isSpawnable = isCallable!F
+      && is(ReturnType!F == void)
+      && isParamsImplicitlyConvertible!(F, void function(T))
+      && ( isFunctionPointer!F
+        || !hasUnsharedAliasing!F);
+}
+
+/**
+ * Starts fn(args) in a new logical thread.
+ *
+ * Executes the supplied function in a new logical thread represented by
+ * $(D Tid).  The calling thread is designated as the owner of the new thread.
+ * When the owner thread terminates an $(D OwnerTerminated) message will be
+ * sent to the new thread, causing an $(D OwnerTerminated) exception to be
+ * thrown on $(D receive()).
+ *
+ * Params:
+ *  fn   = The function to execute.
+ *  args = Arguments to the function.
+ *
+ * Returns:
+ *  A Tid representing the new logical thread.
+ *
+ * Notes:
+ *  $(D args) must not have unshared aliasing.  In other words, all arguments
+ *  to $(D fn) must either be $(D shared) or $(D immutable) or have no
+ *  pointer indirection.  This is necessary for enforcing isolation among
+ *  threads.
+ *
+ * Example:
+ * ---
+ * import std.stdio, std.concurrency;
+ *
+ * void f1(string str)
+ * {
+ *     writeln(str);
+ * }
+ *
+ * void f2(char[] str)
+ * {
+ *     writeln(str);
+ * }
+ *
+ * void main()
+ * {
+ *     auto str = "Hello, world";
+ *
+ *     // Works:  string is immutable.
+ *     auto tid1 = spawn(&f1, str);
+ *
+ *     // Fails:  char[] has mutable aliasing.
+ *     auto tid2 = spawn(&f2, str.dup);
+ *
+ *     // New thread with anonymous function
+ *     spawn({ writeln("This is so great!"); });
+ * }
+ * ---
+ */
+Tid spawn(F, T...)( F fn, T args )
+    if ( isSpawnable!(F, T) )
+{
+    static assert( !hasLocalAliasing!(T),
+                   "Aliases to mutable thread-local data not allowed." );
+    return _spawn( false, fn, args );
+}
+
+
+/**
+ * Starts fn(args) in a logical thread and will receive a LinkTerminated
+ * message when the operation terminates.
+ *
+ * Executes the supplied function in a new logical thread represented by
+ * Tid.  This new thread is linked to the calling thread so that if either
+ * it or the calling thread terminates a LinkTerminated message will be sent
+ * to the other, causing a LinkTerminated exception to be thrown on receive().
+ * The owner relationship from spawn() is preserved as well, so if the link
+ * between threads is broken, owner termination will still result in an
+ * OwnerTerminated exception to be thrown on receive().
+ *
+ * Params:
+ *  fn   = The function to execute.
+ *  args = Arguments to the function.
+ *
+ * Returns:
+ *  A Tid representing the new thread.
+ */
+Tid spawnLinked(F, T...)( F fn, T args )
+    if ( isSpawnable!(F, T) )
+{
+    static assert( !hasLocalAliasing!(T),
+                   "Aliases to mutable thread-local data not allowed." );
+    return _spawn( true, fn, args );
+}
+
+
+/*
+ *
+ */
+private Tid _spawn(F, T...)( bool linked, F fn, T args )
+    if ( isSpawnable!(F, T) )
+{
+    // TODO: MessageList and &exec should be shared.
+    auto spawnTid = Tid( new MessageBox );
+    auto ownerTid = thisTid;
+
+    void exec()
+    {
+        thisInfo.ident = spawnTid;
+        thisInfo.owner = ownerTid;
+        fn( args );
+    }
+
+    // TODO: MessageList and &exec should be shared.
+    if( scheduler !is null )
+        scheduler.spawn( &exec );
+    else
+    {
+        auto t = new Thread( &exec );
+        t.start();
+    }
+    thisInfo.links[spawnTid] = linked;
+    return spawnTid;
+}
+
+unittest
+{
+    void function()                                fn1;
+    void function(int)                             fn2;
+    static assert( __traits(compiles, spawn(fn1)));
+    static assert( __traits(compiles, spawn(fn2, 2)));
+    static assert(!__traits(compiles, spawn(fn1, 1)));
+    static assert(!__traits(compiles, spawn(fn2)));
+
+    void delegate(int) shared                      dg1;
+    shared(void delegate(int))                     dg2;
+    shared(void delegate(long) shared)             dg3;
+    shared(void delegate(real, int , long) shared) dg4;
+    void delegate(int) immutable                   dg5;
+    void delegate(int)                             dg6;
+    static assert( __traits(compiles, spawn(dg1, 1)));
+    static assert( __traits(compiles, spawn(dg2, 2)));
+    static assert( __traits(compiles, spawn(dg3, 3)));
+    static assert( __traits(compiles, spawn(dg4, 4, 4, 4)));
+    static assert( __traits(compiles, spawn(dg5, 5)));
+    static assert(!__traits(compiles, spawn(dg6, 6)));
+
+    auto callable1  = new class{ void opCall(int) shared {} };
+    auto callable2  = cast(shared)new class{ void opCall(int) shared {} };
+    auto callable3  = new class{ void opCall(int) immutable {} };
+    auto callable4  = cast(immutable)new class{ void opCall(int) immutable {} };
+    auto callable5  = new class{ void opCall(int) {} };
+    auto callable6  = cast(shared)new class{ void opCall(int) immutable {} };
+    auto callable7  = cast(immutable)new class{ void opCall(int) shared {} };
+    auto callable8  = cast(shared)new class{ void opCall(int) const shared {} };
+    auto callable9  = cast(const shared)new class{ void opCall(int) shared {} };
+    auto callable10 = cast(const shared)new class{ void opCall(int) const shared {} };
+    auto callable11 = cast(immutable)new class{ void opCall(int) const shared {} };
+    static assert(!__traits(compiles, spawn(callable1,  1)));
+    static assert( __traits(compiles, spawn(callable2,  2)));
+    static assert(!__traits(compiles, spawn(callable3,  3)));
+    static assert( __traits(compiles, spawn(callable4,  4)));
+    static assert(!__traits(compiles, spawn(callable5,  5)));
+    static assert(!__traits(compiles, spawn(callable6,  6)));
+    static assert(!__traits(compiles, spawn(callable7,  7)));
+    static assert( __traits(compiles, spawn(callable8,  8)));
+    static assert(!__traits(compiles, spawn(callable9,  9)));
+    static assert( __traits(compiles, spawn(callable10, 10)));
+    static assert( __traits(compiles, spawn(callable11, 11)));
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Sending and Receiving Messages
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Places the values as a message at the back of tid's message queue.
+ *
+ * Sends the supplied value to the thread represented by tid.  As with
+ * $(XREF concurrency, spawn), $(D T) must not have unshared aliasing.
+ */
+void send(T...)( Tid tid, T vals )
+{
+    static assert( !hasLocalAliasing!(T),
+                   "Aliases to mutable thread-local data not allowed." );
+    _send( tid, vals );
+}
+
+
+/**
+ * Places the values as a message on the front of tid's message queue.
+ *
+ * Send a message to $(D tid) but place it at the front of $(D tid)'s message
+ * queue instead of at the back.  This function is typically used for
+ * out-of-band communication, to signal exceptional conditions, etc.
+ */
+void prioritySend(T...)( Tid tid, T vals )
+{
+    static assert( !hasLocalAliasing!(T),
+                   "Aliases to mutable thread-local data not allowed." );
+    _send( MsgType.priority, tid, vals );
+}
+
+
+/*
+ * ditto
+ */
+private void _send(T...)( Tid tid, T vals )
+{
+    _send( MsgType.standard, tid, vals );
+}
+
+
+/*
+ * Implementation of send.  This allows parameter checking to be different for
+ * both Tid.send() and .send().
+ */
+private void _send(T...)( MsgType type, Tid tid, T vals )
+{
+    auto msg = Message( type, vals );
+    tid.mbox.put( msg );
+}
+
+
+/**
+ * Receives a message from another thread.
+ *
+ * Receive a message from another thread, or block if no messages of the
+ * specified types are available.  This function works by pattern matching
+ * a message against a set of delegates and executing the first match found.
+ *
+ * If a delegate that accepts a $(XREF variant, Variant) is included as
+ * the last argument to $(D receive), it will match any message that was not
+ * matched by an earlier delegate.  If more than one argument is sent,
+ * the $(D Variant) will contain a $(XREF typecons, Tuple) of all values
+ * sent.
+ *
+ * Example:
+ * ---
+ * import std.stdio;
+ * import std.variant;
+ * import std.concurrency;
+ *
+ * void spawnedFunction()
+ * {
+ *     receive(
+ *         (int i) { writeln("Received an int."); },
+ *         (float f) { writeln("Received a float."); },
+ *         (Variant v) { writeln("Received some other type."); }
+ *     );
+ * }
+ *
+ * void main()
+ * {
+ *      auto tid = spawn(&spawnedFunction);
+ *      send(tid, 42);
+ * }
+ * ---
+ */
+void receive(T...)( T ops )
+in
+{
+    assert(thisInfo.ident.mbox !is null,
+           "Cannot receive a message until a thread was spawned "
+           ~ "or thisTid was passed to a running thread.");
+}
+body
+{
+    checkops( ops );
+
+    thisInfo.ident.mbox.get( ops );
+}
+
+
+unittest
+{
+    assert( __traits( compiles,
+                      {
+                          receive( (Variant x) {} );
+                          receive( (int x) {}, (Variant x) {} );
+                      } ) );
+
+    assert( !__traits( compiles,
+                       {
+                           receive( (Variant x) {}, (int x) {} );
+                       } ) );
+
+    assert( !__traits( compiles,
+                       {
+                           receive( (int x) {}, (int x) {} );
+                       } ) );
+}
+
+// Make sure receive() works with free functions as well.
+version (unittest)
+{
+    private void receiveFunction(int x) {}
+}
+unittest
+{
+    assert( __traits( compiles,
+                      {
+                          receive( &receiveFunction );
+                          receive( &receiveFunction, (Variant x) {} );
+                      } ) );
+}
+
+
+private template receiveOnlyRet(T...)
+{
+    static if( T.length == 1 )
+        alias receiveOnlyRet = T[0];
+    else
+        alias receiveOnlyRet = Tuple!(T);
+}
+
+/**
+ * Receives only messages with arguments of types $(D T).
+ *
+ * Throws:  $(D MessageMismatch) if a message of types other than $(D T)
+ *          is received.
+ *
+ * Returns: The received message.  If $(D T.length) is greater than one,
+ *          the message will be packed into a $(XREF typecons, Tuple).
+ *
+ * Example:
+ * ---
+ * import std.concurrency;
+ *
+ * void spawnedFunc()
+ * {
+ *     auto msg = receiveOnly!(int, string)();
+ *     assert(msg[0] == 42);
+ *     assert(msg[1] == "42");
+ * }
+ *
+ * void main()
+ * {
+ *     auto tid = spawn(&spawnedFunc);
+ *     send(tid, 42, "42");
+ * }
+ * ---
+ */
+receiveOnlyRet!(T) receiveOnly(T...)()
+in
+{
+    assert(thisInfo.ident.mbox !is null,
+           "Cannot receive a message until a thread was spawned "
+           ~ "or thisTid was passed to a running thread.");
+}
+body
+{
+    Tuple!(T) ret;
+
+    thisInfo.ident.mbox.get(
+        ( T val )
+        {
+            static if( T.length )
+                ret.field = val;
+        },
+        ( LinkTerminated e )
+        {
+            throw e;
+        },
+        ( OwnerTerminated e )
+        {
+            throw e;
+        },
+        ( Variant val )
+        {
+            static if (T.length > 1)
+                string exp = T.stringof;
+            else
+                string exp = T[0].stringof;
+
+            throw new MessageMismatch(
+                format("Unexpected message type: expected '%s', got '%s'",
+                       exp, val.type.toString()));
+        } );
+    static if( T.length == 1 )
+        return ret[0];
+    else
+        return ret;
+}
+
+unittest
+{
+    static void t1(Tid mainTid)
+    {
+        try
+        {
+            receiveOnly!string();
+            mainTid.send("");
+        }
+        catch (Throwable th)
+        {
+            mainTid.send(th.msg);
+        }
+    }
+
+    auto tid = spawn(&t1, thisTid);
+    tid.send(1);
+    string result = receiveOnly!string();
+    assert(result == "Unexpected message type: expected 'string', got 'int'");
+}
+
+/**
+ * Tries to receive but will give up if no matches arrive within duration.
+ * Won't wait at all if provided $(CXREF time, Duration) is negative.
+ *
+ * Same as $(D receive) except that rather than wait forever for a message,
+ * it waits until either it receives a message or the given
+ * $(CXREF time, Duration) has passed. It returns $(D true) if it received a
+ * message and $(D false) if it timed out waiting for one.
+ */
+bool receiveTimeout(T...)( Duration duration, T ops )
+in
+{
+    assert(thisInfo.ident.mbox !is null,
+           "Cannot receive a message until a thread was spawned "
+           ~ "or thisTid was passed to a running thread.");
+}
+body
+{
+    checkops( ops );
+
+    return thisInfo.ident.mbox.get( duration, ops );
+}
+
+unittest
+{
+    assert( __traits( compiles,
+                      {
+                          receiveTimeout( msecs(0), (Variant x) {} );
+                          receiveTimeout( msecs(0), (int x) {}, (Variant x) {} );
+                      } ) );
+
+    assert( !__traits( compiles,
+                       {
+                           receiveTimeout( msecs(0), (Variant x) {}, (int x) {} );
+                       } ) );
+
+    assert( !__traits( compiles,
+                       {
+                           receiveTimeout( msecs(0), (int x) {}, (int x) {} );
+                       } ) );
+
+    assert( __traits( compiles,
+                      {
+                          receiveTimeout( msecs(10), (int x) {}, (Variant x) {} );
+                      } ) );
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// MessageBox Limits
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * These behaviors may be specified when a mailbox is full.
+ */
+enum OnCrowding
+{
+    block,          /// Wait until room is available.
+    throwException, /// Throw a MailboxFull exception.
+    ignore          /// Abort the send and return.
+}
+
+
+private
+{
+    bool onCrowdingBlock( Tid tid )
+    {
+        return true;
+    }
+
+
+    bool onCrowdingThrow( Tid tid )
+    {
+        throw new MailboxFull( tid );
+    }
+
+
+    bool onCrowdingIgnore( Tid tid )
+    {
+        return false;
+    }
+}
+
+
+/**
+ * Sets a maximum mailbox size.
+ *
+ * Sets a limit on the maximum number of user messages allowed in the mailbox.
+ * If this limit is reached, the caller attempting to add a new message will
+ * execute the behavior specified by doThis.  If messages is zero, the mailbox
+ * is unbounded.
+ *
+ * Params:
+ *  tid      = The Tid of the thread for which this limit should be set.
+ *  messages = The maximum number of messages or zero if no limit.
+ *  doThis   = The behavior executed when a message is sent to a full
+ *             mailbox.
+ */
+void setMaxMailboxSize( Tid tid, size_t messages, OnCrowding doThis )
+{
+    final switch( doThis )
+    {
+    case OnCrowding.block:
+        return tid.mbox.setMaxMsgs( messages, &onCrowdingBlock );
+    case OnCrowding.throwException:
+        return tid.mbox.setMaxMsgs( messages, &onCrowdingThrow );
+    case OnCrowding.ignore:
+        return tid.mbox.setMaxMsgs( messages, &onCrowdingIgnore );
+    }
+}
+
+
+/**
+ * Sets a maximum mailbox size.
+ *
+ * Sets a limit on the maximum number of user messages allowed in the mailbox.
+ * If this limit is reached, the caller attempting to add a new message will
+ * execute onCrowdingDoThis.  If messages is zero, the mailbox is unbounded.
+ *
+ * Params:
+ *  tid      = The Tid of the thread for which this limit should be set.
+ *  messages = The maximum number of messages or zero if no limit.
+ *  onCrowdingDoThis = The routine called when a message is sent to a full
+ *                     mailbox.
+ */
+void setMaxMailboxSize( Tid tid, size_t messages, bool function(Tid) onCrowdingDoThis )
+{
+    tid.mbox.setMaxMsgs( messages, onCrowdingDoThis );
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Name Registration
+//////////////////////////////////////////////////////////////////////////////
+
+
+private
+{
+    __gshared Tid[string]   tidByName;
+    __gshared string[][Tid] namesByTid;
+    __gshared Mutex         registryLock;
+}
+
+
+extern (C) void std_concurrency_static_this()
+{
+    registryLock = new Mutex;
+}
+
+
+private void unregisterMe()
+{
+    auto me = thisTid;
+
+    synchronized( registryLock )
+    {
+        if( auto allNames = me in namesByTid )
+        {
+            foreach( name; *allNames )
+                tidByName.remove( name );
+            namesByTid.remove( me );
+        }
+    }
+}
+
+
+/**
+ * Associates name with tid.
+ *
+ * Associates name with tid in a process-local map.  When the thread
+ * represented by tid terminates, any names associated with it will be
+ * automatically unregistered.
+ *
+ * Params:
+ *  name = The name to associate with tid.
+ *  tid  = The tid register by name.
+ *
+ * Returns:
+ *  true if the name is available and tid is not known to represent a
+ *  defunct thread.
+ */
+bool register( string name, Tid tid )
+{
+    synchronized( registryLock )
+    {
+        if( name in tidByName )
+            return false;
+        if( tid.mbox.isClosed )
+            return false;
+        namesByTid[tid] ~= name;
+        tidByName[name] = tid;
+        return true;
+    }
+}
+
+
+/**
+ * Removes the registered name associated with a tid.
+ *
+ * Params:
+ *  name = The name to unregister.
+ *
+ * Returns:
+ *  true if the name is registered, false if not.
+ */
+bool unregister( string name )
+{
+    synchronized( registryLock )
+    {
+        if( auto tid = name in tidByName )
+        {
+            auto allNames = *tid in namesByTid;
+            auto pos      = countUntil( *allNames, name );
+            remove!(SwapStrategy.unstable)( *allNames, pos );
+            tidByName.remove( name );
+            return true;
+        }
+        return false;
+    }
+}
+
+
+/**
+ * Gets the Tid associated with name.
+ *
+ * Params:
+ *  name = The name to locate within the registry.
+ *
+ * Returns:
+ *  The associated Tid or Tid.init if name is not registered.
+ */
+Tid locate( string name )
+{
+    synchronized( registryLock )
+    {
+        if( auto tid = name in tidByName )
+            return *tid;
+        return Tid.init;
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Scheduler
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Encapsulates all implementation-level data needed for scheduling.
+ *
+ * When definining a Scheduler, an instance of this struct must be associated
+ * with each logical thread.  It contains all implementation-level information
+ * needed by the internal API.
+ */
+struct ThreadInfo
+{
+    Tid       ident;
+    bool[Tid] links;
+    Tid       owner;
+
+    /**
+     * Gets a thread-local instance of ThreadInfo.
+     *
+     * Gets a thread-local instance of ThreadInfo, which should be used as the
+     * default instance when info is requested for a thread not created by the
+     * Scheduler.
+     */
+    static @property ref thisInfo() nothrow
+    {
+        static ThreadInfo val;
+        return val;
+    }
+
+
+    /**
+     * Cleans up this ThreadInfo.
+     *
+     * This must be called when a scheduled thread terminates.  It tears down
+     * the messaging system for the thread and notifies interested parties of
+     * the thread's termination.
+     */
+    void cleanup()
+    {
+        if( ident.mbox !is null )
+            ident.mbox.close();
+        foreach( tid; links.keys )
+            _send( MsgType.linkDead, tid, ident );
+        if( owner != Tid.init )
+            _send( MsgType.linkDead, owner, ident );
+        unregisterMe(); // clean up registry entries
+    }
+}
+
+
+/**
+ * A Scheduler controls how threading is performed by spawn.
+ *
+ * Implementing a Scheduler allows the concurrency mechanism used by this
+ * module to be customized according to different needs.  By default, a call
+ * to spawn will create a new kernel thread that executes the supplied routine
+ * and terminates when finished.  But it is possible to create Schedulers that
+ * reuse threads, that multiplex Fibers (coroutines) across a single thread,
+ * or any number of other approaches.  By making the choice of Scheduler a
+ * user-level option, std.concurrency may be used for far more types of
+ * application than if this behavior were predefined.
+ *
+ * Example:
+ * ---
+ * import std.concurrency;
+ * import std.stdio;
+ *
+ * void main()
+ * {
+ *     scheduler = new FiberScheduler;
+ *     scheduler.start(
+ *     {
+ *         writeln("the rest of main goes here");
+ *     });
+ * }
+ * ---
+ *
+ * Some schedulers have a dispatching loop that must run if they are to work
+ * properly, so for the sake of consistency, when using a scheduler, start()
+ * must be called within main().  This yields control to the scheduler and
+ * will ensure that any spawned threads are executed in an expected manner.
+ */
+interface Scheduler
+{
+    /**
+     * Spawns the supplied op and starts the Scheduler.
+     *
+     * This is intended to be called at the start of the program to yield all
+     * scheduling to the active Scheduler instance.  This is necessary for
+     * schedulers that explicitly dispatch threads rather than simply relying
+     * on the operating system to do so, and so start should always be called
+     * within main() to begin normal program execution.
+     *
+     * Params:
+     *  op = A wrapper for whatever the main thread would have done in the
+     *       absence of a custom scheduler.  It will be automatically executed
+     *       via a call to spawn by the Scheduler.
+     */
+    void start( void delegate() op );
+
+    /**
+     * Assigns a logical thread to execute the supplied op.
+     *
+     * This routine is called by spawn.  It is expected to instantiate a new
+     * logical thread and run the supplied operation.  This thread must call
+     * thisInfo.cleanup() when the thread terminates if the scheduled thread
+     * is not a kernel thread--all kernel threads will have their ThreadInfo
+     * cleaned up automatically by a thread-local destructor.
+     *
+     * Params:
+     *  op = The function to execute.  This may be the actual function passed
+     *       by the user to spawn itself, or may be a wrapper function.
+     */
+    void spawn( void delegate() op );
+
+    /**
+     * Yields execution to another logical thread.
+     *
+     * This routine is called at various points within concurrency-aware APIs
+     * to provide a scheduler a chance to yield execution when using some sort
+     * of cooperative multithreading model.  If this is not appropriate, such
+     * as when each logical thread is backed by a dedicated kernel thread,
+     * this routine may be a no-op.
+     */
+    void yield() nothrow;
+
+    /**
+     * Returns an appropriate ThreadInfo instance.
+     *
+     * Returns an instance of ThreadInfo specific to the logical thread that
+     * is calling this routine or, if the calling thread was not create by
+     * this scheduler, returns ThreadInfo.thisInfo instead.
+     */
+    @property ref ThreadInfo thisInfo() nothrow;
+
+    /**
+     * Creates a Condition variable analog for signaling.
+     *
+     * Creates a new Condition variable analog which is used to check for and
+     * to signal the addition of messages to a thread's message queue.  Like
+     * yield, some schedulers may need to define custom behavior so that calls
+     * to Condition.wait() yield to another thread when no new messages are
+     * available instead of blocking.
+     *
+     * Params:
+     *  m = The Mutex that will be associated with this condition.  It will be
+     *      locked prior to any operation on the condition, and so in some
+     *      cases a Scheduler may need to hold this reference and unlock the
+     *      mutex before yielding execution to another logical thread.
+     */
+    Condition newCondition( Mutex m ) nothrow;
+}
+
+
+/**
+ * An example Scheduler using kernel threads.
+ *
+ * This is an example Scheduler that mirrors the default scheduling behavior
+ * of creating one kernel thread per call to spawn.  It is fully functional
+ * and may be instantiated and used, but is not a necessary part of the
+ * default functioning of this module.
+ */
+class ThreadScheduler :
+    Scheduler
+{
+    /**
+     * This simply runs op directly, since no real scheduling is needed by
+     * this approach.
+     */
+    void start( void delegate() op )
+    {
+        op();
+    }
+
+
+    /**
+     * Creates a new kernel thread and assigns it to run the supplied op.
+     */
+    void spawn( void delegate() op )
+    {
+        auto t = new Thread( op );
+        t.start();
+    }
+
+
+    /**
+     * This scheduler does no explicit multiplexing, so this is a no-op.
+     */
+    void yield() nothrow
+    {
+        // no explicit yield needed
+    }
+
+
+    /**
+     * Returns ThreadInfo.thisInfo, since it is a thread-local instance of
+     * ThreadInfo, which is the correct behavior for this scheduler.
+     */
+    @property ref ThreadInfo thisInfo() nothrow
+    {
+        return ThreadInfo.thisInfo;
+    }
+
+
+    /**
+     * Creates a new Condition variable.  No custom behavior is needed here.
+     */
+    Condition newCondition( Mutex m ) nothrow
+    {
+        return new Condition( m );
+    }
+}
+
+
+/**
+ * An example Scheduler using Fibers.
+ *
+ * This is an example scheduler that creates a new Fiber per call to spawn
+ * and multiplexes the execution of all fibers within the main thread.
+ */
+class FiberScheduler :
+    Scheduler
+{
+    /**
+     * This creates a new Fiber for the supplied op and then starts the
+     * dispatcher.
+     */
+    void start( void delegate() op )
+    {
+        create( op );
+        dispatch();
+    }
+
+
+    /**
+     * This created a new Fiber for the supplied op and adds it to the
+     * dispatch list.
+     */
+    void spawn( void delegate() op ) nothrow
+    {
+        create( op );
+        yield();
+    }
+
+
+    /**
+     * If the caller is a scheduled Fiber, this yields execution to another
+     * scheduled Fiber.
+     */
+    void yield() nothrow
+    {
+        // NOTE: It's possible that we should test whether the calling Fiber
+        //       is an InfoFiber before yielding, but I think it's reasonable
+        //       that any (non-Generator) fiber should yield here.
+        if(Fiber.getThis())
+            Fiber.yield();
+    }
+
+
+    /**
+     * Returns an appropriate ThreadInfo instance.
+     *
+     * Returns a ThreadInfo instance specific to the calling Fiber if the
+     * Fiber was created by this dispatcher, otherwise it returns
+     * ThreadInfo.thisInfo.
+     */
+    @property ref ThreadInfo thisInfo() nothrow
+    {
+        auto f = cast(InfoFiber) Fiber.getThis();
+
+        if( f !is null )
+            return f.info;
+        return ThreadInfo.thisInfo;
+    }
+
+
+    /**
+     * Returns a Condition analog that yields when wait or notify is called.
+     */
+    Condition newCondition( Mutex m ) nothrow
+    {
+        return new FiberCondition( m );
+    }
+
+
+private:
+    static class InfoFiber :
+        Fiber
+    {
+        ThreadInfo info;
+
+        this( void delegate() op ) nothrow
+        {
+            super( op );
+        }
+    }
+
+
+    class FiberCondition :
+        Condition
+    {
+        this( Mutex m ) nothrow
+        {
+            super(m);
+            notified = false;
+        }
+
+        override void wait() nothrow
+        {
+            scope(exit) notified = false;
+
+            while( !notified )
+                switchContext();
+        }
+
+        override bool wait( Duration period ) nothrow
+        {
+            import core.time;
+            scope(exit) notified = false;
+
+            for( auto limit = MonoTime.currTime + period;
+                 !notified && !period.isNegative;
+                 period = limit - MonoTime.currTime )
+            {
+                yield();
+            }
+            return notified;
+        }
+
+        override void notify() nothrow
+        {
+            notified = true;
+            switchContext();
+        }
+
+        override void notifyAll() nothrow
+        {
+            notified = true;
+            switchContext();
+        }
+
+    private:
+        final void switchContext() nothrow
+        {
+            mutex_nothrow.unlock_nothrow();
+            scope(exit) mutex_nothrow.lock_nothrow();
+            yield();
+        }
+
+        private bool notified;
+    }
+
+
+private:
+    final void dispatch()
+    {
+        import std.algorithm : remove;
+
+        while( m_fibers.length > 0 )
+        {
+            auto t = m_fibers[m_pos].call( Fiber.Rethrow.no );
+            if (t !is null && !(cast(OwnerTerminated) t))
+                throw t;
+            if( m_fibers[m_pos].state == Fiber.State.TERM )
+            {
+                if( m_pos >= (m_fibers = remove( m_fibers, m_pos )).length )
+                    m_pos = 0;
+            }
+            else if( m_pos++ >= m_fibers.length - 1 )
+            {
+                m_pos = 0;
+            }
+        }
+    }
+
+
+    final void create( void delegate() op ) nothrow
+    {
+        void wrap()
+        {
+            scope(exit)
+            {
+                thisInfo.cleanup();
+            }
+            op();
+        }
+        m_fibers ~= new InfoFiber( &wrap );
+    }
+
+
+private:
+    Fiber[] m_fibers;
+    size_t  m_pos;
+}
+
+
+unittest
+{
+    static void receive(Condition cond, ref size_t received)
+    {
+        while (true)
+        {
+            synchronized (cond.mutex)
+            {
+                cond.wait();
+                ++received;
+            }
+        }
+    }
+
+    static void send(Condition cond, ref size_t sent)
+    {
+        while (true)
+        {
+            synchronized (cond.mutex)
+            {
+                ++sent;
+                cond.notify();
+            }
+        }
+    }
+
+    auto fs = new FiberScheduler;
+    auto mtx = new Mutex;
+    auto cond = fs.newCondition(mtx);
+
+    size_t received, sent;
+    auto waiter = new Fiber({receive(cond, received);}), notifier = new Fiber({send(cond, sent);});
+    waiter.call();
+    assert(received == 0);
+    notifier.call();
+    assert(sent == 1);
+    assert(received == 0);
+    waiter.call();
+    assert(received == 1);
+    waiter.call();
+    assert(received == 1);
+}
+
+
+/**
+ * Sets the Scheduler behavior within the program.
+ *
+ * This variable sets the Scheduler behavior within this program.  Typically,
+ * when setting a Scheduler, scheduler.start() should be called in main.  This
+ * routine will not return until program execution is complete.
+ */
+__gshared Scheduler scheduler;
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Generator
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * If the caller is a Fiber and is not a Generator, this function will call
+ * scheduler.yield() or Fiber.yield(), as appropriate.
+ */
+void yield() nothrow
+{
+    auto fiber = Fiber.getThis();
+    if (!(cast(IsGenerator) fiber))
+    {
+        if (scheduler is null)
+        {
+            if (fiber)
+                return Fiber.yield();
+        }
+        else scheduler.yield();
+    }
+}
+
+
+/// Used to determine whether a Generator is running.
+private interface IsGenerator {}
+
+
+/**
+ * A Generator is a Fiber that periodically returns values of type T to the
+ * caller via yield.  This is represented as an InputRange.
+ *
+ * Example:
+ * ---
+ * import std.concurrency;
+ * import std.stdio;
+ *
+ *
+ * void main()
+ * {
+ *     auto tid = spawn(
+ *     {
+ *         while (true)
+ *         {
+ *             writeln(receiveOnly!int());
+ *         }
+ *     });
+ *
+ *     auto r = new Generator!int(
+ *     {
+ *         foreach (i; 1 .. 10)
+ *             yield(i);
+ *     });
+ *
+ *     foreach (e; r)
+ *     {
+ *         tid.send(e);
+ *     }
+ * }
+ * ---
+ */
+class Generator(T) :
+    Fiber, IsGenerator
+{
+    /**
+     * Initializes a generator object which is associated with a static
+     * D function.  The function will be called once to prepare the range
+     * for iteration.
+     *
+     * Params:
+     *  fn = The fiber function.
+     *
+     * In:
+     *  fn must not be null.
+     */
+    this(void function() fn)
+    {
+        super(fn);
+        call();
+    }
+
+
+    /**
+     * Initializes a generator object which is associated with a static
+     * D function.  The function will be called once to prepare the range
+     * for iteration.
+     *
+     * Params:
+     *  fn = The fiber function.
+     *  sz = The stack size for this fiber.
+     *
+     * In:
+     *  fn must not be null.
+     */
+    this(void function() fn, size_t sz)
+    {
+        super(fn, sz);
+        call();
+    }
+
+
+    /**
+     * Initializes a generator object which is associated with a dynamic
+     * D function.  The function will be called once to prepare the range
+     * for iteration.
+     *
+     * Params:
+     *  dg = The fiber function.
+     *
+     * In:
+     *  dg must not be null.
+     */
+    this(void delegate() dg)
+    {
+        super(dg);
+        call();
+    }
+
+
+    /**
+     * Initializes a generator object which is associated with a dynamic
+     * D function.  The function will be called once to prepare the range
+     * for iteration.
+     *
+     * Params:
+     *  dg = The fiber function.
+     *  sz = The stack size for this fiber.
+     *
+     * In:
+     *  dg must not be null.
+     */
+    this(void delegate() dg, size_t sz)
+    {
+        super(dg, sz);
+        call();
+    }
+
+
+    /**
+     * Returns true if the generator is empty.
+     */
+    final bool empty() @property
+    {
+        return m_value is null || state == State.TERM;
+    }
+
+
+    /**
+     * Obtains the next value from the underlying function.
+     */
+    final void popFront()
+    {
+        call();
+    }
+
+
+    /**
+     * Returns the most recently generated value.
+     */
+    final T front() @property
+    {
+        return *m_value;
+    }
+
+
+private:
+    T*  m_value;
+}
+
+
+/**
+ * Yields a value of type T to the caller of the currently executing
+ * generator.
+ *
+ * Params:
+ *  value = The value to yield.
+ */
+void yield(T)(ref T value)
+{
+    Generator!T cur = cast(Generator!T) Fiber.getThis();
+    if (cur !is null && cur.state == Fiber.State.EXEC)
+    {
+        cur.m_value = &value;
+        return Fiber.yield();
+    }
+    throw new Exception("yield(T) called with no active generator for the supplied type");
+}
+
+
+/// ditto
+void yield(T)(T value)
+{
+    yield(value);
+}
+
+
+version (Win64) {
+    // fibers are broken on Win64
+} else version (Win32) {
+    // fibers are broken in Win32 under server 2012: bug 13821
+} else unittest {
+    import core.exception;
+    import std.exception;
+
+    static void testScheduler(Scheduler s)
+    {
+        scheduler = s;
+        scheduler.start(
+        {
+            auto tid = spawn(
+            {
+                int i;
+
+                try
+                {
+                    for (i = 1; i < 10; i++)
+                    {
+                        assertNotThrown!AssertError(
+                            assert(receiveOnly!int() == i));
+                    }
+                }
+                catch (OwnerTerminated e)
+                {
+
+                }
+
+                // i will advance 1 past the last value expected
+                assert(i == 4);
+            });
+
+            auto r = new Generator!int(
+            {
+                assertThrown!Exception(yield(2.0));
+                yield(); // ensure this is a no-op
+                yield(1);
+                yield(); // also once something has been yielded
+                yield(2);
+                yield(3);
+            });
+
+            foreach (e; r)
+            {
+                tid.send(e);
+            }
+        });
+        scheduler = null;
+    }
+
+    testScheduler(new ThreadScheduler);
+    testScheduler(new FiberScheduler);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// MessageBox Implementation
+//////////////////////////////////////////////////////////////////////////////
+
+
+private
+{
+    /*
+     * A MessageBox is a message queue for one thread.  Other threads may send
+     * messages to this owner by calling put(), and the owner receives them by
+     * calling get().  The put() call is therefore effectively shared and the
+     * get() call is effectively local.  setMaxMsgs may be used by any thread
+     * to limit the size of the message queue.
+     */
+    class MessageBox
+    {
+        this() @trusted /* TODO: make @safe after relevant druntime PR gets merged */
+        {
+            m_lock      = new Mutex;
+            m_closed    = false;
+
+            if( scheduler is null )
+            {
+                m_putMsg  = new Condition( m_lock );
+                m_notFull = new Condition( m_lock );
+            }
+            else
+            {
+                m_putMsg  = scheduler.newCondition( m_lock );
+                m_notFull = scheduler.newCondition( m_lock );
+            }
+        }
+
+    // @@@DEPRECATED_2016-03@@@
+    /++
+        $(RED Deprecated. isClosed can't be used with a const MessageBox.
+              It will be removed in March 2016).
+      +/
+        deprecated("isClosed can't be used with a const MessageBox")
+        final @property bool isClosed() const
+        {
+            synchronized( m_lock )
+            {
+                return m_closed;
+            }
+        }
+
+        ///
+        final @property bool isClosed()
+        {
+            synchronized( m_lock )
+            {
+                return m_closed;
+            }
+        }
+
+        /*
+         * Sets a limit on the maximum number of user messages allowed in the
+         * mailbox.  If this limit is reached, the caller attempting to add
+         * a new message will execute call.  If num is zero, there is no limit
+         * on the message queue.
+         *
+         * Params:
+         *  num  = The maximum size of the queue or zero if the queue is
+         *         unbounded.
+         *  call = The routine to call when the queue is full.
+         */
+        final void setMaxMsgs( size_t num, bool function(Tid) call )
+        {
+            synchronized( m_lock )
+            {
+                m_maxMsgs   = num;
+                m_onMaxMsgs = call;
+            }
+        }
+
+
+        /*
+         * If maxMsgs is not set, the message is added to the queue and the
+         * owner is notified.  If the queue is full, the message will still be
+         * accepted if it is a control message, otherwise onCrowdingDoThis is
+         * called.  If the routine returns true, this call will block until
+         * the owner has made space available in the queue.  If it returns
+         * false, this call will abort.
+         *
+         * Params:
+         *  msg = The message to put in the queue.
+         *
+         * Throws:
+         *  An exception if the queue is full and onCrowdingDoThis throws.
+         */
+        final void put( ref Message msg )
+        {
+            synchronized( m_lock )
+            {
+                // TODO: Generate an error here if m_closed is true, or maybe
+                //       put a message in the caller's queue?
+                if( !m_closed )
+                {
+                    while( true )
+                    {
+                        if( isPriorityMsg( msg ) )
+                        {
+                            m_sharedPty.put( msg );
+                            m_putMsg.notify();
+                            return;
+                        }
+                        if( !mboxFull() || isControlMsg( msg ) )
+                        {
+                            m_sharedBox.put( msg );
+                            m_putMsg.notify();
+                            return;
+                        }
+                        if( m_onMaxMsgs !is null && !m_onMaxMsgs( thisTid ) )
+                        {
+                            return;
+                        }
+                        m_putQueue++;
+                        m_notFull.wait();
+                        m_putQueue--;
+                    }
+                }
+            }
+        }
+
+
+        /*
+         * Matches ops against each message in turn until a match is found.
+         *
+         * Params:
+         *  ops = The operations to match.  Each may return a bool to indicate
+         *        whether a message with a matching type is truly a match.
+         *
+         * Returns:
+         *  true if a message was retrieved and false if not (such as if a
+         *  timeout occurred).
+         *
+         * Throws:
+         *  LinkTerminated if a linked thread terminated, or OwnerTerminated
+         * if the owner thread terminates and no existing messages match the
+         * supplied ops.
+         */
+        final bool get(T...)( scope T vals )
+        {
+            static assert( T.length );
+
+            static if( isImplicitlyConvertible!(T[0], Duration) )
+            {
+                alias Ops = AliasSeq!(T[1 .. $]);
+                alias ops = vals[1 .. $];
+                assert( vals[0] >= msecs(0) );
+                enum timedWait = true;
+                Duration period = vals[0];
+            }
+            else
+            {
+                alias Ops = AliasSeq!(T);
+                alias ops = vals[0 .. $];
+                enum timedWait = false;
+            }
+
+            bool onStandardMsg( ref Message msg )
+            {
+                foreach( i, t; Ops )
+                {
+                    alias Args = Parameters!(t);
+                    auto  op   = ops[i];
+
+                    if( msg.convertsTo!(Args) )
+                    {
+                        static if( is( ReturnType!(t) == bool ) )
+                        {
+                            return msg.map( op );
+                        }
+                        else
+                        {
+                            msg.map( op );
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            }
+
+            bool onLinkDeadMsg( ref Message msg )
+            {
+                assert( msg.convertsTo!(Tid) );
+                auto tid = msg.get!(Tid);
+
+                if( bool* pDepends = (tid in thisInfo.links) )
+                {
+                    auto depends = *pDepends;
+                    thisInfo.links.remove( tid );
+                    // Give the owner relationship precedence.
+                    if( depends && tid != thisInfo.owner )
+                    {
+                        auto e = new LinkTerminated( tid );
+                        auto m = Message( MsgType.standard, e );
+                        if( onStandardMsg( m ) )
+                            return true;
+                        throw e;
+                    }
+                }
+                if( tid == thisInfo.owner )
+                {
+                    thisInfo.owner = Tid.init;
+                    auto e = new OwnerTerminated( tid );
+                    auto m = Message( MsgType.standard, e );
+                    if( onStandardMsg( m ) )
+                        return true;
+                    throw e;
+                }
+                return false;
+            }
+
+            bool onControlMsg( ref Message msg )
+            {
+                switch( msg.type )
+                {
+                case MsgType.linkDead:
+                    return onLinkDeadMsg( msg );
+                default:
+                    return false;
+                }
+            }
+
+            bool scan( ref ListT list )
+            {
+                for( auto range = list[]; !range.empty; )
+                {
+                    // Only the message handler will throw, so if this occurs
+                    // we can be certain that the message was handled.
+                    scope(failure) list.removeAt( range );
+
+                    if( isControlMsg( range.front ) )
+                    {
+                        if( onControlMsg( range.front ) )
+                        {
+                            // Although the linkDead message is a control message,
+                            // it can be handled by the user.  Since the linkDead
+                            // message throws if not handled, if we get here then
+                            // it has been handled and we can return from receive.
+                            // This is a weird special case that will have to be
+                            // handled in a more general way if more are added.
+                            if( !isLinkDeadMsg( range.front ) )
+                            {
+                                list.removeAt( range );
+                                continue;
+                            }
+                            list.removeAt( range );
+                            return true;
+                        }
+                        range.popFront();
+                        continue;
+                    }
+                    else
+                    {
+                        if( onStandardMsg( range.front ) )
+                        {
+                            list.removeAt( range );
+                            return true;
+                        }
+                        range.popFront();
+                        continue;
+                    }
+                }
+                return false;
+            }
+
+
+            bool pty( ref ListT list )
+            {
+                if( !list.empty )
+                {
+                    auto range = list[];
+
+                    if( onStandardMsg( range.front ) )
+                    {
+                        list.removeAt( range );
+                        return true;
+                    }
+                    if( range.front.convertsTo!(Throwable) )
+                        throw range.front.get!(Throwable);
+                    else if( range.front.convertsTo!(shared(Throwable)) )
+                        throw range.front.get!(shared(Throwable));
+                    else throw new PriorityMessageException( range.front.data );
+                }
+                return false;
+            }
+
+            static if( timedWait )
+            {
+                import core.time;
+                auto limit = MonoTime.currTime + period;
+            }
+
+            while( true )
+            {
+                ListT arrived;
+
+                if( pty( m_localPty ) ||
+                    scan( m_localBox ) )
+                {
+                    return true;
+                }
+                yield();
+                synchronized( m_lock )
+                {
+                    updateMsgCount();
+                    while( m_sharedPty.empty && m_sharedBox.empty )
+                    {
+                        // NOTE: We're notifying all waiters here instead of just
+                        //       a few because the onCrowding behavior may have
+                        //       changed and we don't want to block sender threads
+                        //       unnecessarily if the new behavior is not to block.
+                        //       This will admittedly result in spurious wakeups
+                        //       in other situations, but what can you do?
+                        if( m_putQueue && !mboxFull() )
+                            m_notFull.notifyAll();
+                        static if( timedWait )
+                        {
+                            if( period.isNegative || !m_putMsg.wait( period ) )
+                                return false;
+                        }
+                        else
+                        {
+                            m_putMsg.wait();
+                        }
+                    }
+                    m_localPty.put( m_sharedPty );
+                    arrived.put( m_sharedBox );
+                }
+                if( m_localPty.empty )
+                {
+                    scope(exit) m_localBox.put( arrived );
+                    if( scan( arrived ) )
+                        return true;
+                    else
+                    {
+                        static if( timedWait )
+                        {
+                            period = limit - MonoTime.currTime;
+                        }
+                        continue;
+                    }
+                }
+                m_localBox.put( arrived );
+                pty( m_localPty );
+                return true;
+            }
+        }
+
+
+        /*
+         * Called on thread termination.  This routine processes any remaining
+         * control messages, clears out message queues, and sets a flag to
+         * reject any future messages.
+         */
+        final void close()
+        {
+            void onLinkDeadMsg( ref Message msg )
+            {
+                assert( msg.convertsTo!(Tid) );
+                auto tid = msg.get!(Tid);
+
+                thisInfo.links.remove( tid );
+                if( tid == thisInfo.owner )
+                    thisInfo.owner = Tid.init;
+            }
+
+            void sweep( ref ListT list )
+            {
+                for( auto range = list[]; !range.empty; range.popFront() )
+                {
+                    if( range.front.type == MsgType.linkDead )
+                        onLinkDeadMsg( range.front );
+                }
+            }
+
+            ListT arrived;
+
+            sweep( m_localBox );
+            synchronized( m_lock )
+            {
+                arrived.put( m_sharedBox );
+                m_closed = true;
+            }
+            m_localBox.clear();
+            sweep( arrived );
+        }
+
+
+    private:
+        //////////////////////////////////////////////////////////////////////
+        // Routines involving shared data, m_lock must be held.
+        //////////////////////////////////////////////////////////////////////
+
+
+        bool mboxFull()
+        {
+            return m_maxMsgs &&
+                   m_maxMsgs <= m_localMsgs + m_sharedBox.length;
+        }
+
+
+        void updateMsgCount()
+        {
+            m_localMsgs = m_localBox.length;
+        }
+
+
+    private:
+        //////////////////////////////////////////////////////////////////////
+        // Routines involving local data only, no lock needed.
+        //////////////////////////////////////////////////////////////////////
+
+
+        pure final bool isControlMsg( ref Message msg )
+        {
+            return msg.type != MsgType.standard &&
+                   msg.type != MsgType.priority;
+        }
+
+
+        pure final bool isPriorityMsg( ref Message msg )
+        {
+            return msg.type == MsgType.priority;
+        }
+
+
+        pure final bool isLinkDeadMsg( ref Message msg )
+        {
+            return msg.type == MsgType.linkDead;
+        }
+
+
+    private:
+        //////////////////////////////////////////////////////////////////////
+        // Type declarations.
+        //////////////////////////////////////////////////////////////////////
+
+
+        alias OnMaxFn = bool function(Tid);
+        alias ListT   = List!(Message);
+
+    private:
+        //////////////////////////////////////////////////////////////////////
+        // Local data, no lock needed.
+        //////////////////////////////////////////////////////////////////////
+
+
+        ListT       m_localBox;
+        ListT       m_localPty;
+
+
+    private:
+        //////////////////////////////////////////////////////////////////////
+        // Shared data, m_lock must be held on access.
+        //////////////////////////////////////////////////////////////////////
+
+
+        Mutex       m_lock;
+        Condition   m_putMsg;
+        Condition   m_notFull;
+        size_t      m_putQueue;
+        ListT       m_sharedBox;
+        ListT       m_sharedPty;
+        OnMaxFn     m_onMaxMsgs;
+        size_t      m_localMsgs;
+        size_t      m_maxMsgs;
+        bool        m_closed;
+
+    }
+
+
+    /*
+     *
+     */
+    struct List(T)
+    {
+        struct Range
+        {
+            @property bool empty() const
+            {
+                return !m_prev.next;
+            }
+
+            @property ref T front()
+            {
+                enforce( m_prev.next, "invalid list node" );
+                return m_prev.next.val;
+            }
+
+            @property void front( T val )
+            {
+                enforce( m_prev.next, "invalid list node" );
+                m_prev.next.val = val;
+            }
+
+            void popFront()
+            {
+                enforce( m_prev.next, "invalid list node" );
+                m_prev = m_prev.next;
+            }
+
+            //T moveFront()
+            //{
+            //    enforce( m_prev.next );
+            //    return move( m_prev.next.val );
+            //}
+
+            private this( Node* p )
+            {
+                m_prev = p;
+            }
+
+            private Node* m_prev;
+        }
+
+
+        /*
+         *
+         */
+        void put( T val )
+        {
+            put( newNode( val ) );
+        }
+
+
+        /*
+         *
+         */
+        void put( ref List!(T) rhs )
+        {
+            if( !rhs.empty )
+            {
+                put( rhs.m_first );
+                while( m_last.next !is null )
+                {
+                    m_last = m_last.next;
+                    m_count++;
+                }
+                rhs.m_first = null;
+                rhs.m_last  = null;
+                rhs.m_count = 0;
+            }
+        }
+
+
+        /*
+         *
+         */
+        Range opSlice()
+        {
+            return Range( cast(Node*) &m_first );
+        }
+
+
+        /*
+         *
+         */
+        void removeAt( Range r )
+        {
+            assert( m_count );
+            Node* n = r.m_prev;
+            enforce( n && n.next, "attempting to remove invalid list node" );
+
+            if( m_last is m_first )
+                m_last = null;
+            else if( m_last is n.next )
+                m_last = n;
+            Node* to_free = n.next;
+            n.next = n.next.next;
+            freeNode( to_free );
+            m_count--;
+        }
+
+
+        /*
+         *
+         */
+        @property size_t length()
+        {
+            return m_count;
+        }
+
+
+        /*
+         *
+         */
+        void clear()
+        {
+            m_first = m_last = null;
+            m_count = 0;
+        }
+
+
+        /*
+         *
+         */
+        @property bool empty()
+        {
+            return m_first is null;
+        }
+
+
+    private:
+        struct Node
+        {
+            Node*   next;
+            T       val;
+
+            this( T v )
+            {
+                val = v;
+            }
+        }
+
+        static shared struct SpinLock
+        {
+            void lock() { while (!cas(&locked, false, true)) { Thread.yield(); } }
+            void unlock() { atomicStore!(MemoryOrder.rel)(locked, false); }
+            bool locked;
+        }
+        static shared SpinLock sm_lock;
+        static shared Node* sm_head;
+
+        Node* newNode(T v)
+        {
+            Node *n;
+            {
+                sm_lock.lock();
+                scope (exit) sm_lock.unlock();
+
+                if (sm_head)
+                {
+                    n = cast(Node*)sm_head;
+                    sm_head = sm_head.next;
+                }
+            }
+            if (n)
+                *n = Node(v);
+            else
+                n = new Node(v);
+            return n;
+        }
+
+        void freeNode(Node* n)
+        {
+            // destroy val to free any owned GC memory
+            destroy(n.val);
+
+            sm_lock.lock();
+            scope (exit) sm_lock.unlock();
+
+            auto sn = cast(shared(Node)*)n;
+            sn.next = sm_head;
+            sm_head = sn;
+        }
+
+
+        /*
+         *
+         */
+        void put( Node* n )
+        {
+            m_count++;
+            if( !empty )
+            {
+                m_last.next = n;
+                m_last = n;
+                return;
+            }
+            m_first = n;
+            m_last = n;
+        }
+
+
+        Node*   m_first;
+        Node*   m_last;
+        size_t  m_count;
+    }
+}
+
+
+version( unittest )
+{
+    import std.stdio;
+
+    void testfn( Tid tid )
+    {
+        receive( (float val) { assert(0); },
+                 (int val, int val2)
+                 {
+                     assert( val == 42 && val2 == 86 );
+                 } );
+        receive( (Tuple!(int, int) val)
+                 {
+                     assert( val[0] == 42 &&
+                             val[1] == 86 );
+                 } );
+        receive( (Variant val) {} );
+        receive( (string val)
+                 {
+                     if( "the quick brown fox" != val )
+                         return false;
+                     return true;
+                 },
+                 (string val)
+                 {
+                     assert( false );
+                 } );
+        prioritySend( tid, "done" );
+    }
+
+    void runTest( Tid tid )
+    {
+        send( tid, 42, 86 );
+        send( tid, tuple(42, 86) );
+        send( tid, "hello", "there" );
+        send( tid, "the quick brown fox" );
+        receive( (string val) { assert(val == "done"); } );
+    }
+
+
+    void simpleTest()
+    {
+        auto tid = spawn( &testfn, thisTid );
+        runTest( tid );
+
+        // Run the test again with a limited mailbox size.
+        tid = spawn( &testfn, thisTid );
+        setMaxMailboxSize( tid, 2, OnCrowding.block );
+        runTest( tid );
+    }
+
+
+    unittest
+    {
+        simpleTest();
+    }
+
+
+    unittest
+    {
+        scheduler = new ThreadScheduler;
+        simpleTest();
+        scheduler = null;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// initOnce
+//////////////////////////////////////////////////////////////////////////////
+
+private @property Mutex initOnceLock()
+{
+    __gshared Mutex lock;
+    if (auto mtx = atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock))
+        return mtx;
+    auto mtx = new Mutex;
+    if (cas(cast(shared)&lock, cast(shared)null, cast(shared)mtx))
+        return mtx;
+    return atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock);
+}
+
+/**
+ * Initializes $(D_PARAM var) with the lazy $(D_PARAM init) value in a
+ * thread-safe manner.
+ *
+ * The implementation guarantees that all threads simultaneously calling
+ * initOnce with the same $(D_PARAM var) argument block until $(D_PARAM var) is
+ * fully initialized. All side-effects of $(D_PARAM init) are globally visible
+ * afterwards.
+ *
+ * Params:
+ *   var = The variable to initialize
+ *   init = The lazy initializer value
+ *
+ * Returns:
+ *   A reference to the initialized variable
+ */
+auto ref initOnce(alias var)(lazy typeof(var) init)
+{
+    return initOnce!var(init, initOnceLock);
+}
+
+/// A typical use-case is to perform lazy but thread-safe initialization.
+unittest
+{
+    static class MySingleton
+    {
+        static MySingleton instance()
+        {
+            static __gshared MySingleton inst;
+            return initOnce!inst(new MySingleton);
+        }
+    }
+    assert(MySingleton.instance !is null);
+}
+
+unittest
+{
+    static class MySingleton
+    {
+        static MySingleton instance()
+        {
+            static __gshared MySingleton inst;
+            return initOnce!inst(new MySingleton);
+        }
+    private:
+        this() { val = ++cnt; }
+        size_t val;
+        static __gshared size_t cnt;
+    }
+
+    foreach (_; 0 .. 10)
+        spawn({ownerTid.send(MySingleton.instance.val);});
+    foreach (_; 0 .. 10)
+        assert(receiveOnly!size_t == MySingleton.instance.val);
+    assert(MySingleton.cnt == 1);
+}
+
+/**
+ * Same as above, but takes a separate mutex instead of sharing one among
+ * all initOnce instances.
+ *
+ * This should be used to avoid dead-locks when the $(D_PARAM init)
+ * expression waits for the result of another thread that might also
+ * call initOnce. Use with care.
+ *
+ * Params:
+ *   var = The variable to initialize
+ *   init = The lazy initializer value
+ *   mutex = A mutex to prevent race conditions
+ *
+ * Returns:
+ *   A reference to the initialized variable
+ */
+auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex)
+{
+    // check that var is global, can't take address of a TLS variable
+    static assert(is(typeof({__gshared p = &var;})), "var must be 'static shared' or '__gshared'.");
+    import core.atomic;
+
+    static shared bool flag;
+    if (!atomicLoad!(MemoryOrder.acq)(flag))
+    {
+        synchronized (mutex)
+        {
+            if (!atomicLoad!(MemoryOrder.acq)(flag))
+            {
+                var = init;
+                atomicStore!(MemoryOrder.rel)(flag, true);
+            }
+        }
+    }
+    return var;
+}
+
+/// Use a separate mutex when init blocks on another thread that might also call initOnce.
+unittest
+{
+    static shared bool varA, varB;
+    __gshared Mutex m;
+    m = new Mutex;
+
+    spawn({
+        // use a different mutex for varB to avoid a dead-lock
+        initOnce!varB(true, m);
+        ownerTid.send(true);
+    });
+    // init depends on the result of the spawned thread
+    initOnce!varA(receiveOnly!bool);
+    assert(varA == true);
+    assert(varB == true);
+}
+
+unittest
+{
+     static shared bool a;
+     __gshared bool b;
+    static bool c;
+    bool d;
+    initOnce!a(true);
+    initOnce!b(true);
+    static assert(!__traits(compiles, initOnce!c(true))); // TLS
+    static assert(!__traits(compiles, initOnce!d(true))); // local variable
+}
diff --git a/libphobos/src/std/concurrencybase.d b/libphobos/src/std/concurrencybase.d
new file mode 100644
index 00000000000..a9ecbc196ad
--- /dev/null
+++ b/libphobos/src/std/concurrencybase.d
@@ -0,0 +1,20 @@
+// Written in the D programming language.
+
+/**
+ * The only purpose of this module is to do the static construction for
+ * std.concurrency, to eliminate cyclic construction errors.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Source:    $(PHOBOSSRC std/_concurrencybase.d)
+ */
+module std.concurrencybase;
+
+import core.sync.mutex;
+
+extern(C) void std_concurrency_static_this();
+
+shared static this()
+{
+    std_concurrency_static_this();
+}
+
diff --git a/libphobos/src/std/container/array.d b/libphobos/src/std/container/array.d
new file mode 100644
index 00000000000..35bf70d0e49
--- /dev/null
+++ b/libphobos/src/std/container/array.d
@@ -0,0 +1,2181 @@
+/**
+This module provides an $(D Array) type with deterministic memory usage not
+reliant on the GC, as an alternative to the built-in arrays.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_array.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.array;
+
+import std.range.primitives;
+import std.traits;
+import core.exception : RangeError;
+import std.algorithm : move;
+
+public import std.container.util;
+
+private struct RangeT(A)
+{
+    /* Workaround for Issue 13629 at https://issues.dlang.org/show_bug.cgi?id=13629
+       See also: http://forum.dlang.org/post/vbmwhzvawhnkoxrhbnyb@forum.dlang.org
+    */
+    private A[1] _outer_;
+    private @property ref inout(A) _outer() inout { return _outer_[0]; }
+
+    private size_t _a, _b;
+
+    /* E is different from T when A is more restrictively qualified than T:
+       immutable(Array!int) => T == int, E = immutable(int) */
+    alias E = typeof(_outer_[0]._data._payload[0]);
+
+    private this(ref A data, size_t a, size_t b)
+    {
+        _outer_ = data;
+        _a = a;
+        _b = b;
+    }
+
+    @property RangeT save()
+    {
+        return this;
+    }
+
+    @property bool empty() @safe pure nothrow const
+    {
+        return _a >= _b;
+    }
+
+    @property size_t length() @safe pure nothrow const
+    {
+        return _b - _a;
+    }
+    alias opDollar = length;
+
+    @property ref inout(E) front() inout
+    {
+        version (assert) if (empty) throw new RangeError();
+        return _outer[_a];
+    }
+    @property ref inout(E) back() inout
+    {
+        version (assert) if (empty) throw new RangeError();
+        return _outer[_b - 1];
+    }
+
+    void popFront() @safe pure nothrow
+    {
+        version (assert) if (empty) throw new RangeError();
+        ++_a;
+    }
+
+    void popBack() @safe pure nothrow
+    {
+        version (assert) if (empty) throw new RangeError();
+        --_b;
+    }
+
+    static if (isMutable!A)
+    {
+        E moveFront()
+        {
+            version (assert) if (empty || _a >= _outer.length) throw new RangeError();
+            return move(_outer._data._payload[_a]);
+        }
+
+        E moveBack()
+        {
+            version (assert) if (empty || _b  > _outer.length) throw new RangeError();
+            return move(_outer._data._payload[_b - 1]);
+        }
+
+        E moveAt(size_t i)
+        {
+            version (assert) if (_a + i >= _b || _a + i >= _outer.length) throw new RangeError();
+            return move(_outer._data._payload[_a + i]);
+        }
+    }
+
+    ref inout(E) opIndex(size_t i) inout
+    {
+        version (assert) if (_a + i >= _b) throw new RangeError();
+        return _outer[_a + i];
+    }
+
+    RangeT opSlice()
+    {
+        return typeof(return)(_outer, _a, _b);
+    }
+
+    RangeT opSlice(size_t i, size_t j)
+    {
+        version (assert) if (i > j || _a + j > _b) throw new RangeError();
+        return typeof(return)(_outer, _a + i, _a + j);
+    }
+
+    RangeT!(const(A)) opSlice() const
+    {
+        return typeof(return)(_outer, _a, _b);
+    }
+
+    RangeT!(const(A)) opSlice(size_t i, size_t j) const
+    {
+        version (assert) if (i > j || _a + j > _b) throw new RangeError();
+        return typeof(return)(_outer, _a + i, _a + j);
+    }
+
+    static if (isMutable!A)
+    {
+        void opSliceAssign(E value)
+        {
+            version (assert) if (_b > _outer.length) throw new RangeError();
+            _outer[_a .. _b] = value;
+        }
+
+        void opSliceAssign(E value, size_t i, size_t j)
+        {
+            version (assert) if (_a + j > _b) throw new RangeError();
+            _outer[_a + i .. _a + j] = value;
+        }
+
+        void opSliceUnary(string op)()
+        if (op == "++" || op == "--")
+        {
+            version (assert) if (_b > _outer.length) throw new RangeError();
+            mixin(op~"_outer[_a .. _b];");
+        }
+
+        void opSliceUnary(string op)(size_t i, size_t j)
+        if (op == "++" || op == "--")
+        {
+            version (assert) if (_a + j > _b) throw new RangeError();
+            mixin(op~"_outer[_a + i .. _a + j];");
+        }
+
+        void opSliceOpAssign(string op)(E value)
+        {
+            version (assert) if (_b > _outer.length) throw new RangeError();
+            mixin("_outer[_a .. _b] "~op~"= value;");
+        }
+
+        void opSliceOpAssign(string op)(E value, size_t i, size_t j)
+        {
+            version (assert) if (_a + j > _b) throw new RangeError();
+            mixin("_outer[_a + i .. _a + j] "~op~"= value;");
+        }
+    }
+}
+
+/**
+Array type with deterministic control of memory. The memory allocated
+for the array is reclaimed as soon as possible; there is no reliance
+on the garbage collector. $(D Array) uses $(D malloc) and $(D free)
+for managing its own memory.
+
+This means that pointers to elements of an $(D Array) will become
+dangling as soon as the element is removed from the $(D Array). On the other hand
+the memory allocated by an $(D Array) will be scanned by the GC and
+GC managed objects referenced from an $(D Array) will be kept alive.
+
+Note:
+
+When using $(D Array) with range-based functions like those in $(D std.algorithm),
+$(D Array) must be sliced to get a range (for example, use $(D array[].map!)
+instead of $(D array.map!)). The container itself is not a range.
+ */
+struct Array(T)
+if (!is(Unqual!T == bool))
+{
+    import core.stdc.stdlib;
+    import core.stdc.string;
+
+    import core.memory;
+
+    import std.algorithm : initializeAll, copy;
+    import std.exception : enforce;
+    import std.typecons : RefCounted, RefCountedAutoInitialize;
+
+    // This structure is not copyable.
+    private struct Payload
+    {
+        size_t _capacity;
+        T[] _payload;
+
+        // Convenience constructor
+        this(T[] p) { _capacity = p.length; _payload = p; }
+
+        // Destructor releases array memory
+        ~this()
+        {
+            //Warning: destroy will also destroy class instances.
+            //The hasElaborateDestructor protects us here.
+            static if (hasElaborateDestructor!T)
+                foreach (ref e; _payload)
+                    .destroy(e);
+
+            static if (hasIndirections!T)
+                GC.removeRange(_payload.ptr);
+
+            free(_payload.ptr);
+        }
+
+        this(this)
+        {
+            assert(0);
+        }
+
+        void opAssign(Payload rhs)
+        {
+            assert(false);
+        }
+
+        // Duplicate data
+        // @property Payload dup()
+        // {
+        //     Payload result;
+        //     result._payload = _payload.dup;
+        //     // Conservatively assume initial capacity == length
+        //     result._capacity = result._payload.length;
+        //     return result;
+        // }
+
+        // length
+        @property size_t length() const
+        {
+            return _payload.length;
+        }
+
+        // length
+        @property void length(size_t newLength)
+        {
+            if (length >= newLength)
+            {
+                // shorten
+                static if (hasElaborateDestructor!T)
+                    foreach (ref e; _payload.ptr[newLength .. _payload.length])
+                        .destroy(e);
+
+                _payload = _payload.ptr[0 .. newLength];
+                return;
+            }
+            // enlarge
+            auto startEmplace = length;
+            _payload = (cast(T*) realloc(_payload.ptr,
+                            T.sizeof * newLength))[0 .. newLength];
+            initializeAll(_payload.ptr[startEmplace .. length]);
+        }
+
+        // capacity
+        @property size_t capacity() const
+        {
+            return _capacity;
+        }
+
+        // reserve
+        void reserve(size_t elements)
+        {
+            if (elements <= capacity) return;
+            immutable sz = elements * T.sizeof;
+            static if (hasIndirections!T)       // should use hasPointers instead
+            {
+                /* Because of the transactional nature of this
+                 * relative to the garbage collector, ensure no
+                 * threading bugs by using malloc/copy/free rather
+                 * than realloc.
+                 */
+                immutable oldLength = length;
+                auto newPayload =
+                    enforce(cast(T*) malloc(sz))[0 .. oldLength];
+                // copy old data over to new array
+                memcpy(newPayload.ptr, _payload.ptr, T.sizeof * oldLength);
+                // Zero out unused capacity to prevent gc from seeing
+                // false pointers
+                memset(newPayload.ptr + oldLength,
+                        0,
+                        (elements - oldLength) * T.sizeof);
+                GC.addRange(newPayload.ptr, sz);
+                GC.removeRange(_payload.ptr);
+                free(_payload.ptr);
+                _payload = newPayload;
+            }
+            else
+            {
+                /* These can't have pointers, so no need to zero
+                 * unused region
+                 */
+                auto newPayload =
+                    enforce(cast(T*) realloc(_payload.ptr, sz))[0 .. length];
+                _payload = newPayload;
+            }
+            _capacity = elements;
+        }
+
+        // Insert one item
+        size_t insertBack(Stuff)(Stuff stuff)
+        if (isImplicitlyConvertible!(Stuff, T))
+        {
+            import std.conv : emplace;
+            if (_capacity == length)
+            {
+                reserve(1 + capacity * 3 / 2);
+            }
+            assert(capacity > length && _payload.ptr);
+            emplace(_payload.ptr + _payload.length, stuff);
+            _payload = _payload.ptr[0 .. _payload.length + 1];
+            return 1;
+        }
+
+        /// Insert a range of items
+        size_t insertBack(Stuff)(Stuff stuff)
+        if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+        {
+            static if (hasLength!Stuff)
+            {
+                immutable oldLength = length;
+                reserve(oldLength + stuff.length);
+            }
+            size_t result;
+            foreach (item; stuff)
+            {
+                insertBack(item);
+                ++result;
+            }
+            static if (hasLength!Stuff)
+            {
+                assert(length == oldLength + stuff.length);
+            }
+            return result;
+        }
+    }
+    private alias Data = RefCounted!(Payload, RefCountedAutoInitialize.no);
+    private Data _data;
+
+/**
+Constructor taking a number of items
+     */
+    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T))
+    {
+        import std.conv : emplace;
+        auto p = cast(T*) malloc(T.sizeof * values.length);
+        static if (hasIndirections!T)
+        {
+            if (p)
+                GC.addRange(p, T.sizeof * values.length);
+        }
+
+        foreach (i, e; values)
+        {
+            emplace(p + i, e);
+            assert(p[i] == e);
+        }
+        _data = Data(p[0 .. values.length]);
+    }
+
+/**
+Constructor taking an input range
+     */
+    this(Stuff)(Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T) && !is(Stuff == T[]))
+    {
+        insertBack(stuff);
+    }
+
+
+/**
+Comparison for equality.
+     */
+    bool opEquals(const Array rhs) const
+    {
+        return opEquals(rhs);
+    }
+
+    /// ditto
+    bool opEquals(ref const Array rhs) const
+    {
+        if (empty) return rhs.empty;
+        if (rhs.empty) return false;
+        return _data._payload == rhs._data._payload;
+    }
+
+/**
+   Defines the container's primary range, which is a random-access range.
+
+   ConstRange is a variant with const elements.
+   ImmutableRange is a variant with immutable elements.
+*/
+    alias Range = RangeT!Array;
+    alias ConstRange = RangeT!(const Array); /// ditto
+    alias ImmutableRange = RangeT!(immutable Array); /// ditto
+
+/**
+Duplicates the container. The elements themselves are not transitively
+duplicated.
+
+Complexity: $(BIGOH n).
+     */
+    @property Array dup()
+    {
+        if (!_data.refCountedStore.isInitialized) return this;
+        return Array(_data._payload);
+    }
+
+/**
+Property returning $(D true) if and only if the container has no
+elements.
+
+Complexity: $(BIGOH 1)
+     */
+    @property bool empty() const
+    {
+        return !_data.refCountedStore.isInitialized || _data._payload.empty;
+    }
+
+/**
+Returns the number of elements in the container.
+
+Complexity: $(BIGOH 1).
+     */
+    @property size_t length() const
+    {
+        return _data.refCountedStore.isInitialized ? _data._payload.length : 0;
+    }
+
+    /// ditto
+    size_t opDollar() const
+    {
+        return length;
+    }
+
+/**
+Returns the maximum number of elements the container can store without
+   (a) allocating memory, (b) invalidating iterators upon insertion.
+
+Complexity: $(BIGOH 1)
+     */
+    @property size_t capacity()
+    {
+        return _data.refCountedStore.isInitialized ? _data._capacity : 0;
+    }
+
+/**
+Ensures sufficient capacity to accommodate $(D e) elements.
+
+Postcondition: $(D capacity >= e)
+
+Complexity: $(BIGOH 1)
+     */
+    void reserve(size_t elements)
+    {
+        if (!_data.refCountedStore.isInitialized)
+        {
+            if (!elements) return;
+            immutable sz = elements * T.sizeof;
+            auto p = enforce(malloc(sz));
+            static if (hasIndirections!T)
+            {
+                GC.addRange(p, sz);
+            }
+            _data = Data(cast(T[]) p[0 .. 0]);
+            _data._capacity = elements;
+        }
+        else
+        {
+            _data.reserve(elements);
+        }
+    }
+
+/**
+Returns a range that iterates over elements of the container, in
+forward order.
+
+Complexity: $(BIGOH 1)
+     */
+    Range opSlice()
+    {
+        return typeof(return)(this, 0, length);
+    }
+    ConstRange opSlice() const
+    {
+        return typeof(return)(this, 0, length);
+    }
+    ImmutableRange opSlice() immutable
+    {
+        return typeof(return)(this, 0, length);
+    }
+
+/**
+Returns a range that iterates over elements of the container from
+index $(D a) up to (excluding) index $(D b).
+
+Precondition: $(D a <= b && b <= length)
+
+Complexity: $(BIGOH 1)
+*/
+    Range opSlice(size_t i, size_t j)
+    {
+        version (assert) if (i > j || j > length) throw new RangeError();
+        return typeof(return)(this, i, j);
+    }
+    ConstRange opSlice(size_t i, size_t j) const
+    {
+        version (assert) if (i > j || j > length) throw new RangeError();
+        return typeof(return)(this, i, j);
+    }
+    ImmutableRange opSlice(size_t i, size_t j) immutable
+    {
+        version (assert) if (i > j || j > length) throw new RangeError();
+        return typeof(return)(this, i, j);
+    }
+
+/**
+Forward to $(D opSlice().front) and $(D opSlice().back), respectively.
+
+Precondition: $(D !empty)
+
+Complexity: $(BIGOH 1)
+     */
+    @property ref inout(T) front() inout
+    {
+        version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError();
+        return _data._payload[0];
+    }
+
+    /// ditto
+    @property ref inout(T) back() inout
+    {
+        version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError();
+        return _data._payload[$ - 1];
+    }
+
+/**
+Indexing operators yield or modify the value at a specified index.
+
+Precondition: $(D i < length)
+
+Complexity: $(BIGOH 1)
+     */
+    ref inout(T) opIndex(size_t i) inout
+    {
+        version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError();
+        return _data._payload[i];
+    }
+
+/**
+Slicing operations execute an operation on an entire slice.
+
+Precondition: $(D i < j && j < length)
+
+Complexity: $(BIGOH slice.length)
+     */
+    void opSliceAssign(T value)
+    {
+        if (!_data.refCountedStore.isInitialized) return;
+        _data._payload[] = value;
+    }
+
+    /// ditto
+    void opSliceAssign(T value, size_t i, size_t j)
+    {
+        auto slice = _data.refCountedStore.isInitialized ?
+            _data._payload :
+            T[].init;
+        slice[i .. j] = value;
+    }
+
+    /// ditto
+    void opSliceUnary(string op)()
+        if (op == "++" || op == "--")
+    {
+        if (!_data.refCountedStore.isInitialized) return;
+        mixin(op~"_data._payload[];");
+    }
+
+    /// ditto
+    void opSliceUnary(string op)(size_t i, size_t j)
+        if (op == "++" || op == "--")
+    {
+        auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init;
+        mixin(op~"slice[i .. j];");
+    }
+
+    /// ditto
+    void opSliceOpAssign(string op)(T value)
+    {
+        if (!_data.refCountedStore.isInitialized) return;
+        mixin("_data._payload[] "~op~"= value;");
+    }
+
+    /// ditto
+    void opSliceOpAssign(string op)(T value, size_t i, size_t j)
+    {
+        auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init;
+        mixin("slice[i .. j] "~op~"= value;");
+    }
+
+/**
+Returns a new container that's the concatenation of $(D this) and its
+argument. $(D opBinaryRight) is only defined if $(D Stuff) does not
+define $(D opBinary).
+
+Complexity: $(BIGOH n + m), where m is the number of elements in $(D
+stuff)
+     */
+    Array opBinary(string op, Stuff)(Stuff stuff)
+        if (op == "~")
+    {
+        // TODO: optimize
+        Array result;
+        result ~= this[];
+        assert(result.length == length);
+        result ~= stuff[];
+        return result;
+    }
+
+/**
+Forwards to $(D insertBack(stuff)).
+     */
+    void opOpAssign(string op, Stuff)(Stuff stuff)
+        if (op == "~")
+    {
+        static if (is(typeof(stuff[])))
+        {
+            insertBack(stuff[]);
+        }
+        else
+        {
+            insertBack(stuff);
+        }
+    }
+
+/**
+Removes all contents from the container. The container decides how $(D
+capacity) is affected.
+
+Postcondition: $(D empty)
+
+Complexity: $(BIGOH n)
+     */
+    void clear()
+    {
+        _data = Data.init;
+    }
+
+/**
+Sets the number of elements in the container to $(D newSize). If $(D
+newSize) is greater than $(D length), the added elements are added to
+unspecified positions in the container and initialized with $(D
+T.init).
+
+Complexity: $(BIGOH abs(n - newLength))
+
+Postcondition: $(D length == newLength)
+     */
+    @property void length(size_t newLength)
+    {
+        _data.refCountedStore.ensureInitialized();
+        _data.length = newLength;
+    }
+
+/**
+Picks one value in an unspecified position in the container, removes
+it from the container, and returns it. The stable version behaves the same,
+but guarantees that ranges iterating over the container are never invalidated.
+
+Precondition: $(D !empty)
+
+Returns: The element removed.
+
+Complexity: $(BIGOH log(n)).
+     */
+    T removeAny()
+    {
+        auto result = back;
+        removeBack();
+        return result;
+    }
+    /// ditto
+    alias stableRemoveAny = removeAny;
+
+/**
+Inserts $(D value) to the front or back of the container. $(D stuff)
+can be a value convertible to $(D T) or a range of objects convertible
+to $(D T). The stable version behaves the same, but guarantees that
+ranges iterating over the container are never invalidated.
+
+Returns: The number of elements inserted
+
+Complexity: $(BIGOH m * log(n)), where $(D m) is the number of
+elements in $(D stuff)
+     */
+    size_t insertBack(Stuff)(Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T) ||
+            isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        _data.refCountedStore.ensureInitialized();
+        return _data.insertBack(stuff);
+    }
+    /// ditto
+    alias insert = insertBack;
+
+/**
+Removes the value at the back of the container. The stable version
+behaves the same, but guarantees that ranges iterating over the
+container are never invalidated.
+
+Precondition: $(D !empty)
+
+Complexity: $(BIGOH log(n)).
+     */
+    void removeBack()
+    {
+        enforce(!empty);
+        static if (hasElaborateDestructor!T)
+            .destroy(_data._payload[$ - 1]);
+
+        _data._payload = _data._payload[0 .. $ - 1];
+    }
+    /// ditto
+    alias stableRemoveBack = removeBack;
+
+/**
+Removes $(D howMany) values at the front or back of the
+container. Unlike the unparameterized versions above, these functions
+do not throw if they could not remove $(D howMany) elements. Instead,
+if $(D howMany > n), all elements are removed. The returned value is
+the effective number of elements removed. The stable version behaves
+the same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: The number of elements removed
+
+Complexity: $(BIGOH howMany).
+     */
+    size_t removeBack(size_t howMany)
+    {
+        if (howMany > length) howMany = length;
+        static if (hasElaborateDestructor!T)
+            foreach (ref e; _data._payload[$ - howMany .. $])
+                .destroy(e);
+
+        _data._payload = _data._payload[0 .. $ - howMany];
+        return howMany;
+    }
+    /// ditto
+    alias stableRemoveBack = removeBack;
+
+/**
+Inserts $(D stuff) before, after, or instead range $(D r), which must
+be a valid range previously extracted from this container. $(D stuff)
+can be a value convertible to $(D T) or a range of objects convertible
+to $(D T). The stable version behaves the same, but guarantees that
+ranges iterating over the container are never invalidated.
+
+Returns: The number of values inserted.
+
+Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff)
+     */
+    size_t insertBefore(Stuff)(Range r, Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T))
+    {
+        import std.conv : emplace;
+        enforce(r._outer._data is _data && r._a <= length);
+        reserve(length + 1);
+        assert(_data.refCountedStore.isInitialized);
+        // Move elements over by one slot
+        memmove(_data._payload.ptr + r._a + 1,
+                _data._payload.ptr + r._a,
+                T.sizeof * (length - r._a));
+        emplace(_data._payload.ptr + r._a, stuff);
+        _data._payload = _data._payload.ptr[0 .. _data._payload.length + 1];
+        return 1;
+    }
+
+    /// ditto
+    size_t insertBefore(Stuff)(Range r, Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        import std.conv : emplace;
+        enforce(r._outer._data is _data && r._a <= length);
+        static if (isForwardRange!Stuff)
+        {
+            // Can find the length in advance
+            auto extra = walkLength(stuff);
+            if (!extra) return 0;
+            reserve(length + extra);
+            assert(_data.refCountedStore.isInitialized);
+            // Move elements over by extra slots
+            memmove(_data._payload.ptr + r._a + extra,
+                    _data._payload.ptr + r._a,
+                    T.sizeof * (length - r._a));
+            foreach (p; _data._payload.ptr + r._a ..
+                    _data._payload.ptr + r._a + extra)
+            {
+                emplace(p, stuff.front);
+                stuff.popFront();
+            }
+            _data._payload =
+                _data._payload.ptr[0 .. _data._payload.length + extra];
+            return extra;
+        }
+        else
+        {
+            import std.algorithm : bringToFront;
+            enforce(_data);
+            immutable offset = r._a;
+            enforce(offset <= length);
+            auto result = insertBack(stuff);
+            bringToFront(this[offset .. length - result],
+                    this[length - result .. length]);
+            return result;
+        }
+    }
+
+    /// ditto
+    size_t insertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        import std.algorithm : bringToFront;
+        enforce(r._outer._data is _data);
+        // TODO: optimize
+        immutable offset = r._b;
+        enforce(offset <= length);
+        auto result = insertBack(stuff);
+        bringToFront(this[offset .. length - result],
+                this[length - result .. length]);
+        return result;
+    }
+
+    /// ditto
+    size_t replace(Stuff)(Range r, Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        enforce(r._outer._data is _data);
+        size_t result;
+        for (; !stuff.empty; stuff.popFront())
+        {
+            if (r.empty)
+            {
+                // insert the rest
+                return result + insertBefore(r, stuff);
+            }
+            r.front = stuff.front;
+            r.popFront();
+            ++result;
+        }
+        // Remove remaining stuff in r
+        linearRemove(r);
+        return result;
+    }
+
+    /// ditto
+    size_t replace(Stuff)(Range r, Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T))
+    {
+        enforce(r._outer._data is _data);
+        if (r.empty)
+        {
+            insertBefore(r, stuff);
+        }
+        else
+        {
+            r.front = stuff;
+            r.popFront();
+            linearRemove(r);
+        }
+        return 1;
+    }
+
+/**
+Removes all elements belonging to $(D r), which must be a range
+obtained originally from this container. The stable version behaves
+the same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: A range spanning the remaining elements in the container that
+initially were right after $(D r).
+
+Complexity: $(BIGOH n - m), where $(D m) is the number of elements in
+$(D r)
+     */
+    Range linearRemove(Range r)
+    {
+        enforce(r._outer._data is _data);
+        enforce(_data.refCountedStore.isInitialized);
+        enforce(r._a <= r._b && r._b <= length);
+        immutable offset1 = r._a;
+        immutable offset2 = r._b;
+        immutable tailLength = length - offset2;
+        // Use copy here, not a[] = b[] because the ranges may overlap
+        copy(this[offset2 .. length], this[offset1 .. offset1 + tailLength]);
+        length = offset1 + tailLength;
+        return this[length - tailLength .. length];
+    }
+}
+
+unittest
+{
+    Array!int a;
+    assert(a.empty);
+}
+
+unittest
+{
+    Array!int a = Array!int(1, 2, 3);
+    //a._data._refCountedDebug = true;
+    auto b = a.dup;
+    assert(b == Array!int(1, 2, 3));
+    b.front = 42;
+    assert(b == Array!int(42, 2, 3));
+    assert(a == Array!int(1, 2, 3));
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3);
+    assert(a.length == 3);
+}
+
+unittest
+{
+    const Array!int a = [1, 2];
+
+    assert(a[0] == 1);
+    assert(a.front == 1);
+    assert(a.back == 2);
+
+    static assert(!__traits(compiles, { a[0] = 1; }));
+    static assert(!__traits(compiles, { a.front = 1; }));
+    static assert(!__traits(compiles, { a.back = 1; }));
+
+    auto r = a[];
+    size_t i;
+    foreach (e; r)
+    {
+        assert(e == i + 1);
+        i++;
+    }
+}
+
+unittest
+{
+    // REG https://issues.dlang.org/show_bug.cgi?id=13621
+    import std.container : Array, BinaryHeap;
+    alias Heap = BinaryHeap!(Array!int);
+}
+
+unittest
+{
+    Array!int a;
+    a.reserve(1000);
+    assert(a.length == 0);
+    assert(a.empty);
+    assert(a.capacity >= 1000);
+    auto p = a._data._payload.ptr;
+    foreach (i; 0 .. 1000)
+    {
+        a.insertBack(i);
+    }
+    assert(p == a._data._payload.ptr);
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3);
+    a[1] *= 42;
+    assert(a[1] == 84);
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3);
+    auto b = Array!int(11, 12, 13);
+    auto c = a ~ b;
+    assert(c == Array!int(1, 2, 3, 11, 12, 13));
+    assert(a ~ b[] == Array!int(1, 2, 3, 11, 12, 13));
+    assert(a ~ [4,5] == Array!int(1,2,3,4,5));
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3);
+    auto b = Array!int(11, 12, 13);
+    a ~= b;
+    assert(a == Array!int(1, 2, 3, 11, 12, 13));
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3, 4);
+    assert(a.removeAny() == 4);
+    assert(a == Array!int(1, 2, 3));
+}
+
+unittest
+{
+    auto a = Array!int(1, 2, 3, 4, 5);
+    auto r = a[2 .. a.length];
+    assert(a.insertBefore(r, 42) == 1);
+    assert(a == Array!int(1, 2, 42, 3, 4, 5));
+    r = a[2 .. 2];
+    assert(a.insertBefore(r, [8, 9]) == 2);
+    assert(a == Array!int(1, 2, 8, 9, 42, 3, 4, 5));
+}
+
+unittest
+{
+    auto a = Array!int(0, 1, 2, 3, 4, 5, 6, 7, 8);
+    a.linearRemove(a[4 .. 6]);
+    assert(a == Array!int(0, 1, 2, 3, 6, 7, 8));
+}
+
+// Give the Range object some testing.
+unittest
+{
+    import std.algorithm : equal;
+    import std.range : retro;
+    auto a = Array!int(0, 1, 2, 3, 4, 5, 6)[];
+    auto b = Array!int(6, 5, 4, 3, 2, 1, 0)[];
+    alias A = typeof(a);
+
+    static assert(isRandomAccessRange!A);
+    static assert(hasSlicing!A);
+    static assert(hasAssignableElements!A);
+    static assert(hasMobileElements!A);
+
+    assert(equal(retro(b), a));
+    assert(a.length == 7);
+    assert(equal(a[1..4], [1, 2, 3]));
+}
+// Test issue 5920
+unittest
+{
+    struct structBug5920
+    {
+        int order;
+        uint* pDestructionMask;
+        ~this()
+        {
+            if (pDestructionMask)
+                *pDestructionMask += 1 << order;
+        }
+    }
+
+    alias S = structBug5920;
+    uint dMask;
+
+    auto arr = Array!S(cast(S[])[]);
+    foreach (i; 0..8)
+        arr.insertBack(S(i, &dMask));
+    // don't check dMask now as S may be copied multiple times (it's ok?)
+    {
+        assert(arr.length == 8);
+        dMask = 0;
+        arr.length = 6;
+        assert(arr.length == 6);    // make sure shrinking calls the d'tor
+        assert(dMask == 0b1100_0000);
+        arr.removeBack();
+        assert(arr.length == 5);    // make sure removeBack() calls the d'tor
+        assert(dMask == 0b1110_0000);
+        arr.removeBack(3);
+        assert(arr.length == 2);    // ditto
+        assert(dMask == 0b1111_1100);
+        arr.clear();
+        assert(arr.length == 0);    // make sure clear() calls the d'tor
+        assert(dMask == 0b1111_1111);
+    }
+    assert(dMask == 0b1111_1111);   // make sure the d'tor is called once only.
+}
+// Test issue 5792 (mainly just to check if this piece of code is compilable)
+unittest
+{
+    auto a = Array!(int[])([[1,2],[3,4]]);
+    a.reserve(4);
+    assert(a.capacity >= 4);
+    assert(a.length == 2);
+    assert(a[0] == [1,2]);
+    assert(a[1] == [3,4]);
+    a.reserve(16);
+    assert(a.capacity >= 16);
+    assert(a.length == 2);
+    assert(a[0] == [1,2]);
+    assert(a[1] == [3,4]);
+}
+
+// test replace!Stuff with range Stuff
+unittest
+{
+    import std.algorithm : equal;
+    auto a = Array!int([1, 42, 5]);
+    a.replace(a[1 .. 2], [2, 3, 4]);
+    assert(equal(a[], [1, 2, 3, 4, 5]));
+}
+
+// test insertBefore and replace with empty Arrays
+unittest
+{
+    import std.algorithm : equal;
+    auto a = Array!int();
+    a.insertBefore(a[], 1);
+    assert(equal(a[], [1]));
+}
+unittest
+{
+    import std.algorithm : equal;
+    auto a = Array!int();
+    a.insertBefore(a[], [1, 2]);
+    assert(equal(a[], [1, 2]));
+}
+unittest
+{
+    import std.algorithm : equal;
+    auto a = Array!int();
+    a.replace(a[], [1, 2]);
+    assert(equal(a[], [1, 2]));
+}
+unittest
+{
+    import std.algorithm : equal;
+    auto a = Array!int();
+    a.replace(a[], 1);
+    assert(equal(a[], [1]));
+}
+// make sure that Array instances refuse ranges that don't belong to them
+unittest
+{
+    import std.exception;
+    Array!int a = [1, 2, 3];
+    auto r = a.dup[];
+    assertThrown(a.insertBefore(r, 42));
+    assertThrown(a.insertBefore(r, [42]));
+    assertThrown(a.insertAfter(r, 42));
+    assertThrown(a.replace(r, 42));
+    assertThrown(a.replace(r, [42]));
+    assertThrown(a.linearRemove(r));
+}
+unittest
+{
+    auto a = Array!int([1, 1]);
+    a[1]  = 0; //Check Array.opIndexAssign
+    assert(a[1] == 0);
+    a[1] += 1; //Check Array.opIndexOpAssign
+    assert(a[1] == 1);
+
+    //Check Array.opIndexUnary
+    ++a[0];
+    //a[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed
+    assert(a[0] == 2);
+    assert(+a[0] == +2);
+    assert(-a[0] == -2);
+    assert(~a[0] == ~2);
+
+    auto r = a[];
+    r[1]  = 0; //Check Array.Range.opIndexAssign
+    assert(r[1] == 0);
+    r[1] += 1; //Check Array.Range.opIndexOpAssign
+    assert(r[1] == 1);
+
+    //Check Array.Range.opIndexUnary
+    ++r[0];
+    //r[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed
+    assert(r[0] == 3);
+    assert(+r[0] == +3);
+    assert(-r[0] == -3);
+    assert(~r[0] == ~3);
+}
+
+unittest
+{
+    import std.algorithm : equal;
+
+    //Test "array-wide" operations
+    auto a = Array!int([0, 1, 2]); //Array
+    a[] += 5;
+    assert(a[].equal([5, 6, 7]));
+    ++a[];
+    assert(a[].equal([6, 7, 8]));
+    a[1 .. 3] *= 5;
+    assert(a[].equal([6, 35, 40]));
+    a[0 .. 2] = 0;
+    assert(a[].equal([0, 0, 40]));
+
+    //Test empty array
+    auto a2 = Array!int.init;
+    ++a2[];
+    ++a2[0 .. 0];
+    a2[] = 0;
+    a2[0 .. 0] = 0;
+    a2[] += 0;
+    a2[0 .. 0] += 0;
+
+    //Test "range-wide" operations
+    auto r = Array!int([0, 1, 2])[]; //Array.Range
+    r[] += 5;
+    assert(r.equal([5, 6, 7]));
+    ++r[];
+    assert(r.equal([6, 7, 8]));
+    r[1 .. 3] *= 5;
+    assert(r.equal([6, 35, 40]));
+    r[0 .. 2] = 0;
+    assert(r.equal([0, 0, 40]));
+
+    //Test empty Range
+    auto r2 = Array!int.init[];
+    ++r2[];
+    ++r2[0 .. 0];
+    r2[] = 0;
+    r2[0 .. 0] = 0;
+    r2[] += 0;
+    r2[0 .. 0] += 0;
+}
+
+// Test issue 11194
+unittest {
+    static struct S {
+        int i = 1337;
+        void* p;
+        this(this) { assert(i == 1337); }
+        ~this() { assert(i == 1337); }
+    }
+    Array!S arr;
+    S s;
+    arr ~= s;
+    arr ~= s;
+}
+
+unittest //11459
+{
+    static struct S
+    {
+        bool b;
+        alias b this;
+    }
+    alias A = Array!S;
+    alias B = Array!(shared bool);
+}
+
+unittest //11884
+{
+    import std.algorithm : filter;
+    auto a = Array!int([1, 2, 2].filter!"true"());
+}
+
+unittest //8282
+{
+    auto arr = new Array!int;
+}
+
+unittest //6998
+{
+    static int i = 0;
+    class C
+    {
+        int dummy = 1;
+        this(){++i;}
+        ~this(){--i;}
+    }
+
+    assert(i == 0);
+    auto c = new C();
+    assert(i == 1);
+
+    //scope
+    {
+        auto arr = Array!C(c);
+        assert(i == 1);
+    }
+    //Array should not have destroyed the class instance
+    assert(i == 1);
+
+    //Just to make sure the GC doesn't collect before the above test.
+    assert(c.dummy ==1);
+}
+unittest //6998-2
+{
+    static class C {int i;}
+    auto c = new C;
+    c.i = 42;
+    Array!C a;
+    a ~= c;
+    a.clear;
+    assert(c.i == 42); //fails
+}
+
+unittest
+{
+    static assert(is(Array!int.Range));
+    static assert(is(Array!int.ConstRange));
+}
+
+unittest // const/immutable Array and Ranges
+{
+    static void test(A, R, E, S)()
+    {
+        A a;
+        R r = a[];
+        assert(r.empty);
+        assert(r.length == 0);
+        static assert(is(typeof(r.front) == E));
+        static assert(is(typeof(r.back) == E));
+        static assert(is(typeof(r[0]) == E));
+        static assert(is(typeof(r[]) == S));
+        static assert(is(typeof(r[0 .. 0]) == S));
+    }
+
+    alias A = Array!int;
+
+    test!(A, A.Range, int, A.Range);
+    test!(A, const A.Range, const int, A.ConstRange);
+
+    test!(const A, A.ConstRange, const int, A.ConstRange);
+    test!(const A, const A.ConstRange, const int, A.ConstRange);
+
+    test!(immutable A, A.ImmutableRange, immutable int, A.ImmutableRange);
+    test!(immutable A, const A.ImmutableRange, immutable int, A.ImmutableRange);
+    test!(immutable A, immutable A.ImmutableRange, immutable int,
+        A.ImmutableRange);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Array!bool
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+_Array specialized for $(D bool). Packs together values efficiently by
+allocating one bit per element.
+ */
+struct Array(T)
+if (is(Unqual!T == bool))
+{
+    import std.exception : enforce;
+    import std.typecons : RefCounted, RefCountedAutoInitialize;
+
+    static immutable uint bitsPerWord = size_t.sizeof * 8;
+    private static struct Data
+    {
+        Array!size_t.Payload _backend;
+        size_t _length;
+    }
+    private RefCounted!(Data, RefCountedAutoInitialize.no) _store;
+
+    private @property ref size_t[] data()
+    {
+        assert(_store.refCountedStore.isInitialized);
+        return _store._backend._payload;
+    }
+
+    /**
+       Defines the container's primary range.
+     */
+    struct Range
+    {
+        private Array _outer;
+        private size_t _a, _b;
+        /// Range primitives
+        @property Range save()
+        {
+            version (bug4437)
+            {
+                return this;
+            }
+            else
+            {
+                auto copy = this;
+                return copy;
+            }
+        }
+        /// Ditto
+        @property bool empty()
+        {
+            return _a >= _b || _outer.length < _b;
+        }
+        /// Ditto
+        @property T front()
+        {
+            enforce(!empty);
+            return _outer[_a];
+        }
+        /// Ditto
+        @property void front(bool value)
+        {
+            enforce(!empty);
+            _outer[_a] = value;
+        }
+        /// Ditto
+        T moveFront()
+        {
+            enforce(!empty);
+            return _outer.moveAt(_a);
+        }
+        /// Ditto
+        void popFront()
+        {
+            enforce(!empty);
+            ++_a;
+        }
+        /// Ditto
+        @property T back()
+        {
+            enforce(!empty);
+            return _outer[_b - 1];
+        }
+        /// Ditto
+        @property void back(bool value)
+        {
+            enforce(!empty);
+            _outer[_b - 1] = value;
+        }
+        /// Ditto
+        T moveBack()
+        {
+            enforce(!empty);
+            return _outer.moveAt(_b - 1);
+        }
+        /// Ditto
+        void popBack()
+        {
+            enforce(!empty);
+            --_b;
+        }
+        /// Ditto
+        T opIndex(size_t i)
+        {
+            return _outer[_a + i];
+        }
+        /// Ditto
+        void opIndexAssign(T value, size_t i)
+        {
+            _outer[_a + i] = value;
+        }
+        /// Ditto
+        T moveAt(size_t i)
+        {
+            return _outer.moveAt(_a + i);
+        }
+        /// Ditto
+        @property size_t length() const
+        {
+            assert(_a <= _b);
+            return _b - _a;
+        }
+        alias opDollar = length;
+        /// ditto
+        Range opSlice(size_t low, size_t high)
+        {
+            assert(_a <= low && low <= high && high <= _b);
+            return Range(_outer, _a + low, _a + high);
+        }
+    }
+
+    /**
+       Property returning $(D true) if and only if the container has
+       no elements.
+
+       Complexity: $(BIGOH 1)
+     */
+    @property bool empty()
+    {
+        return !length;
+    }
+
+    unittest
+    {
+        Array!bool a;
+        //a._store._refCountedDebug = true;
+        assert(a.empty);
+        a.insertBack(false);
+        assert(!a.empty);
+    }
+
+    /**
+       Returns a duplicate of the container. The elements themselves
+       are not transitively duplicated.
+
+       Complexity: $(BIGOH n).
+     */
+    @property Array dup()
+    {
+        Array result;
+        result.insertBack(this[]);
+        return result;
+    }
+
+    unittest
+    {
+        Array!bool a;
+        assert(a.empty);
+        auto b = a.dup;
+        assert(b.empty);
+        a.insertBack(true);
+        assert(b.empty);
+    }
+
+    /**
+       Returns the number of elements in the container.
+
+       Complexity: $(BIGOH log(n)).
+    */
+    @property size_t length() const
+    {
+        return _store.refCountedStore.isInitialized ? _store._length : 0;
+    }
+    size_t opDollar() const
+    {
+        return length;
+    }
+
+    unittest
+    {
+        import std.conv : to;
+        Array!bool a;
+        assert(a.length == 0);
+        a.insert(true);
+        assert(a.length == 1, to!string(a.length));
+    }
+
+    /**
+       Returns the maximum number of elements the container can store
+       without (a) allocating memory, (b) invalidating iterators upon
+       insertion.
+
+       Complexity: $(BIGOH log(n)).
+     */
+    @property size_t capacity()
+    {
+        return _store.refCountedStore.isInitialized
+            ? cast(size_t) bitsPerWord * _store._backend.capacity
+            : 0;
+    }
+
+    unittest
+    {
+        import std.conv : to;
+        Array!bool a;
+        assert(a.capacity == 0);
+        foreach (i; 0 .. 100)
+        {
+            a.insert(true);
+            assert(a.capacity >= a.length, to!string(a.capacity));
+        }
+    }
+
+    /**
+       Ensures sufficient capacity to accommodate $(D n) elements.
+
+       Postcondition: $(D capacity >= n)
+
+       Complexity: $(BIGOH log(e - capacity)) if $(D e > capacity),
+       otherwise $(BIGOH 1).
+     */
+    void reserve(size_t e)
+    {
+        import std.conv : to;
+        _store.refCountedStore.ensureInitialized();
+        _store._backend.reserve(to!size_t((e + bitsPerWord - 1) / bitsPerWord));
+    }
+
+    unittest
+    {
+        Array!bool a;
+        assert(a.capacity == 0);
+        a.reserve(15657);
+        assert(a.capacity >= 15657);
+    }
+
+    /**
+       Returns a range that iterates over all elements of the
+       container, in a container-defined order. The container should
+       choose the most convenient and fast method of iteration for $(D
+       opSlice()).
+
+       Complexity: $(BIGOH log(n))
+     */
+    Range opSlice()
+    {
+        return Range(this, 0, length);
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        assert(a[].length == 4);
+    }
+
+    /**
+       Returns a range that iterates the container between two
+       specified positions.
+
+       Complexity: $(BIGOH log(n))
+     */
+    Range opSlice(size_t a, size_t b)
+    {
+        enforce(a <= b && b <= length);
+        return Range(this, a, b);
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        assert(a[0 .. 2].length == 2);
+    }
+
+    /**
+       Equivalent to $(D opSlice().front) and $(D opSlice().back),
+       respectively.
+
+       Complexity: $(BIGOH log(n))
+     */
+    @property bool front()
+    {
+        enforce(!empty);
+        return data.ptr[0] & 1;
+    }
+
+    /// Ditto
+    @property void front(bool value)
+    {
+        enforce(!empty);
+        if (value) data.ptr[0] |= 1;
+        else data.ptr[0] &= ~cast(size_t) 1;
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        assert(a.front);
+        a.front = false;
+        assert(!a.front);
+    }
+
+    /// Ditto
+    @property bool back()
+    {
+        enforce(!empty);
+        return cast(bool)(data.back & (cast(size_t)1 << ((_store._length - 1) % bitsPerWord)));
+    }
+
+    /// Ditto
+    @property void back(bool value)
+    {
+        enforce(!empty);
+        if (value)
+        {
+            data.back |= (cast(size_t)1 << ((_store._length - 1) % bitsPerWord));
+        }
+        else
+        {
+            data.back &=
+                ~(cast(size_t)1 << ((_store._length - 1) % bitsPerWord));
+        }
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        assert(a.back);
+        a.back = false;
+        assert(!a.back);
+    }
+
+    /**
+       Indexing operators yield or modify the value at a specified index.
+     */
+    bool opIndex(size_t i)
+    {
+        auto div = cast(size_t) (i / bitsPerWord);
+        auto rem = i % bitsPerWord;
+        enforce(div < data.length);
+        return cast(bool)(data.ptr[div] & (cast(size_t)1 << rem));
+    }
+    /// ditto
+    void opIndexAssign(bool value, size_t i)
+    {
+        auto div = cast(size_t) (i / bitsPerWord);
+        auto rem = i % bitsPerWord;
+        enforce(div < data.length);
+        if (value) data.ptr[div] |= (cast(size_t)1 << rem);
+        else data.ptr[div] &= ~(cast(size_t)1 << rem);
+    }
+    /// ditto
+    void opIndexOpAssign(string op)(bool value, size_t i)
+    {
+        auto div = cast(size_t) (i / bitsPerWord);
+        auto rem = i % bitsPerWord;
+        enforce(div < data.length);
+        auto oldValue = cast(bool) (data.ptr[div] & (cast(size_t)1 << rem));
+        // Do the deed
+        auto newValue = mixin("oldValue "~op~" value");
+        // Write back the value
+        if (newValue != oldValue)
+        {
+            if (newValue) data.ptr[div] |= (cast(size_t)1 << rem);
+            else data.ptr[div] &= ~(cast(size_t)1 << rem);
+        }
+    }
+    /// Ditto
+    T moveAt(size_t i)
+    {
+        return this[i];
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        assert(a[0] && !a[1]);
+        a[0] &= a[1];
+        assert(!a[0]);
+    }
+
+    /**
+       Returns a new container that's the concatenation of $(D this)
+       and its argument.
+
+       Complexity: $(BIGOH n + m), where m is the number of elements
+       in $(D stuff)
+     */
+    Array!bool opBinary(string op, Stuff)(Stuff rhs) if (op == "~")
+    {
+        auto result = this;
+        return result ~= rhs;
+    }
+
+    unittest
+    {
+        import std.algorithm : equal;
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        Array!bool b;
+        b.insertBack([true, true, false, true]);
+        assert(equal((a ~ b)[],
+                        [true, false, true, true, true, true, false, true]));
+    }
+
+    // /// ditto
+    // TotalContainer opBinaryRight(Stuff, string op)(Stuff lhs) if (op == "~")
+    // {
+    //     assert(0);
+    // }
+
+    /**
+       Forwards to $(D insertAfter(this[], stuff)).
+     */
+    // @@@BUG@@@
+    //ref Array!bool opOpAssign(string op, Stuff)(Stuff stuff) if (op == "~")
+    Array!bool opOpAssign(string op, Stuff)(Stuff stuff) if (op == "~")
+    {
+        static if (is(typeof(stuff[]))) insertBack(stuff[]);
+        else insertBack(stuff);
+        return this;
+    }
+
+    unittest
+    {
+        import std.algorithm : equal;
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        Array!bool b;
+        a.insertBack([false, true, false, true, true]);
+        a ~= b;
+        assert(equal(
+                    a[],
+                    [true, false, true, true, false, true, false, true, true]));
+    }
+
+    /**
+       Removes all contents from the container. The container decides
+       how $(D capacity) is affected.
+
+       Postcondition: $(D empty)
+
+       Complexity: $(BIGOH n)
+     */
+    void clear()
+    {
+        this = Array();
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.insertBack([true, false, true, true]);
+        a.clear();
+        assert(a.capacity == 0);
+    }
+
+    /**
+       Sets the number of elements in the container to $(D
+       newSize). If $(D newSize) is greater than $(D length), the
+       added elements are added to the container and initialized with
+       $(D ElementType.init).
+
+       Complexity: $(BIGOH abs(n - newLength))
+
+       Postcondition: $(D _length == newLength)
+     */
+    @property void length(size_t newLength)
+    {
+        import std.conv : to;
+        _store.refCountedStore.ensureInitialized();
+        auto newDataLength =
+            to!size_t((newLength + bitsPerWord - 1) / bitsPerWord);
+        _store._backend.length = newDataLength;
+        _store._length = newLength;
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.length = 1057;
+        assert(a.length == 1057);
+        foreach (e; a)
+        {
+            assert(!e);
+        }
+    }
+
+    /**
+       Inserts $(D stuff) in the container. $(D stuff) can be a value
+       convertible to $(D ElementType) or a range of objects
+       convertible to $(D ElementType).
+
+       The $(D stable) version guarantees that ranges iterating over
+       the container are never invalidated. Client code that counts on
+       non-invalidating insertion should use $(D stableInsert).
+
+       Returns: The number of elements added.
+
+       Complexity: $(BIGOH m * log(n)), where $(D m) is the number of
+       elements in $(D stuff)
+     */
+    alias insert = insertBack;
+    ///ditto
+    alias stableInsert = insertBack;
+
+    /**
+       Same as $(D insert(stuff)) and $(D stableInsert(stuff))
+       respectively, but relax the complexity constraint to linear.
+     */
+    alias linearInsert = insertBack;
+    ///ditto
+    alias stableLinearInsert = insertBack;
+
+    /**
+       Picks one value in the container, removes it from the
+       container, and returns it. The stable version behaves the same,
+       but guarantees that ranges iterating over the container are
+       never invalidated.
+
+       Precondition: $(D !empty)
+
+       Returns: The element removed.
+
+       Complexity: $(BIGOH log(n))
+     */
+    T removeAny()
+    {
+        auto result = back;
+        removeBack();
+        return result;
+    }
+    /// ditto
+    alias stableRemoveAny = removeAny;
+
+    unittest
+    {
+        Array!bool a;
+        a.length = 1057;
+        assert(!a.removeAny());
+        assert(a.length == 1056);
+        foreach (e; a)
+        {
+            assert(!e);
+        }
+    }
+
+    /**
+       Inserts $(D value) to the back of the container. $(D stuff) can
+       be a value convertible to $(D ElementType) or a range of
+       objects convertible to $(D ElementType). The stable version
+       behaves the same, but guarantees that ranges iterating over the
+       container are never invalidated.
+
+       Returns: The number of elements inserted
+
+       Complexity: $(BIGOH log(n))
+     */
+    size_t insertBack(Stuff)(Stuff stuff) if (is(Stuff : bool))
+    {
+        _store.refCountedStore.ensureInitialized();
+        auto rem = _store._length % bitsPerWord;
+        if (rem)
+        {
+            // Fits within the current array
+            if (stuff)
+            {
+                data[$ - 1] |= (cast(size_t)1 << rem);
+            }
+            else
+            {
+                data[$ - 1] &= ~(cast(size_t)1 << rem);
+            }
+        }
+        else
+        {
+            // Need to add more data
+            _store._backend.insertBack(stuff);
+        }
+        ++_store._length;
+        return 1;
+    }
+    /// Ditto
+    size_t insertBack(Stuff)(Stuff stuff)
+    if (isInputRange!Stuff && is(ElementType!Stuff : bool))
+    {
+        static if (!hasLength!Stuff) size_t result;
+        for (; !stuff.empty; stuff.popFront())
+        {
+            insertBack(stuff.front);
+            static if (!hasLength!Stuff) ++result;
+        }
+        static if (!hasLength!Stuff) return result;
+        else return stuff.length;
+    }
+    /// ditto
+    alias stableInsertBack = insertBack;
+
+    unittest
+    {
+        Array!bool a;
+        for (int i = 0; i < 100; ++i)
+            a.insertBack(true);
+        foreach (e; a)
+            assert(e);
+    }
+
+    /**
+       Removes the value at the front or back of the container. The
+       stable version behaves the same, but guarantees that ranges
+       iterating over the container are never invalidated. The
+       optional parameter $(D howMany) instructs removal of that many
+       elements. If $(D howMany > n), all elements are removed and no
+       exception is thrown.
+
+       Precondition: $(D !empty)
+
+       Complexity: $(BIGOH log(n)).
+     */
+    void removeBack()
+    {
+        enforce(_store._length);
+        if (_store._length % bitsPerWord)
+        {
+            // Cool, just decrease the length
+            --_store._length;
+        }
+        else
+        {
+            // Reduce the allocated space
+            --_store._length;
+            _store._backend.length = _store._backend.length - 1;
+        }
+    }
+    /// ditto
+    alias stableRemoveBack = removeBack;
+
+    /**
+       Removes $(D howMany) values at the front or back of the
+       container. Unlike the unparameterized versions above, these
+       functions do not throw if they could not remove $(D howMany)
+       elements. Instead, if $(D howMany > n), all elements are
+       removed. The returned value is the effective number of elements
+       removed. The stable version behaves the same, but guarantees
+       that ranges iterating over the container are never invalidated.
+
+       Returns: The number of elements removed
+
+       Complexity: $(BIGOH howMany * log(n)).
+     */
+    /// ditto
+    size_t removeBack(size_t howMany)
+    {
+        if (howMany >= length)
+        {
+            howMany = length;
+            clear();
+        }
+        else
+        {
+            length = length - howMany;
+        }
+        return howMany;
+    }
+
+    unittest
+    {
+        Array!bool a;
+        a.length = 1057;
+        assert(a.removeBack(1000) == 1000);
+        assert(a.length == 57);
+        foreach (e; a)
+        {
+            assert(!e);
+        }
+    }
+
+    /**
+       Inserts $(D stuff) before, after, or instead range $(D r),
+       which must be a valid range previously extracted from this
+       container. $(D stuff) can be a value convertible to $(D
+       ElementType) or a range of objects convertible to $(D
+       ElementType). The stable version behaves the same, but
+       guarantees that ranges iterating over the container are never
+       invalidated.
+
+       Returns: The number of values inserted.
+
+       Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff)
+     */
+    size_t insertBefore(Stuff)(Range r, Stuff stuff)
+    {
+        import std.algorithm : bringToFront;
+        // TODO: make this faster, it moves one bit at a time
+        immutable inserted = stableInsertBack(stuff);
+        immutable tailLength = length - inserted;
+        bringToFront(
+            this[r._a .. tailLength],
+            this[tailLength .. length]);
+        return inserted;
+    }
+    /// ditto
+    alias stableInsertBefore = insertBefore;
+
+    unittest
+    {
+        import std.conv : to;
+        Array!bool a;
+        version (bugxxxx)
+        {
+            a._store.refCountedDebug = true;
+        }
+        a.insertBefore(a[], true);
+        assert(a.length == 1, to!string(a.length));
+        a.insertBefore(a[], false);
+        assert(a.length == 2, to!string(a.length));
+    }
+
+    /// ditto
+    size_t insertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        import std.algorithm : bringToFront;
+        // TODO: make this faster, it moves one bit at a time
+        immutable inserted = stableInsertBack(stuff);
+        immutable tailLength = length - inserted;
+        bringToFront(
+            this[r._b .. tailLength],
+            this[tailLength .. length]);
+        return inserted;
+    }
+    /// ditto
+    alias stableInsertAfter = insertAfter;
+
+    unittest
+    {
+        import std.conv : to;
+        Array!bool a;
+        a.length = 10;
+        a.insertAfter(a[0 .. 5], true);
+        assert(a.length == 11, to!string(a.length));
+        assert(a[5]);
+    }
+    /// ditto
+    size_t replace(Stuff)(Range r, Stuff stuff) if (is(Stuff : bool))
+    {
+        if (!r.empty)
+        {
+            // There is room
+            r.front = stuff;
+            r.popFront();
+            linearRemove(r);
+        }
+        else
+        {
+            // No room, must insert
+            insertBefore(r, stuff);
+        }
+        return 1;
+    }
+    /// ditto
+    alias stableReplace = replace;
+
+    unittest
+    {
+        import std.conv : to;
+        Array!bool a;
+        a.length = 10;
+        a.replace(a[3 .. 5], true);
+        assert(a.length == 9, to!string(a.length));
+        assert(a[3]);
+    }
+
+    /**
+       Removes all elements belonging to $(D r), which must be a range
+       obtained originally from this container. The stable version
+       behaves the same, but guarantees that ranges iterating over the
+       container are never invalidated.
+
+       Returns: A range spanning the remaining elements in the container that
+       initially were right after $(D r).
+
+       Complexity: $(BIGOH n)
+     */
+    Range linearRemove(Range r)
+    {
+        import std.algorithm : copy;
+        copy(this[r._b .. length], this[r._a .. length]);
+        length = length - r.length;
+        return this[r._a .. length];
+    }
+}
+
+unittest
+{
+    Array!bool a;
+    assert(a.empty);
+}
+
+unittest
+{
+    Array!bool arr;
+    arr.insert([false, false, false, false]);
+    assert(arr.front == false);
+    assert(arr.back == false);
+    assert(arr[1] == false);
+    auto slice = arr[];
+    slice = arr[0 .. $];
+    slice = slice[1 .. $];
+    slice.front = true;
+    slice.back = true;
+    slice[1] = true;
+    assert(slice.front == true);
+    assert(slice.back == true);
+    assert(slice[1] == true);
+    assert(slice.moveFront == true);
+    assert(slice.moveBack == true);
+    assert(slice.moveAt(1) == true);
+}
diff --git a/libphobos/src/std/container/binaryheap.d b/libphobos/src/std/container/binaryheap.d
new file mode 100644
index 00000000000..40fe6c3d3c3
--- /dev/null
+++ b/libphobos/src/std/container/binaryheap.d
@@ -0,0 +1,438 @@
+/**
+This module provides a $(D BinaryHeap) (aka priority queue)
+adaptor that makes a binary heap out of any user-provided random-access range.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_binaryheap.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.binaryheap;
+
+import std.range.primitives;
+import std.traits;
+
+public import std.container.util;
+
+// BinaryHeap
+/**
+Implements a $(WEB en.wikipedia.org/wiki/Binary_heap, binary heap)
+container on top of a given random-access range type (usually $(D
+T[])) or a random-access container type (usually $(D Array!T)). The
+documentation of $(D BinaryHeap) will refer to the underlying range or
+container as the $(I store) of the heap.
+
+The binary heap induces structure over the underlying store such that
+accessing the largest element (by using the $(D front) property) is a
+$(BIGOH 1) operation and extracting it (by using the $(D
+removeFront()) method) is done fast in $(BIGOH log n) time.
+
+If $(D less) is the less-than operator, which is the default option,
+then $(D BinaryHeap) defines a so-called max-heap that optimizes
+extraction of the $(I largest) elements. To define a min-heap,
+instantiate BinaryHeap with $(D "a > b") as its predicate.
+
+Simply extracting elements from a $(D BinaryHeap) container is
+tantamount to lazily fetching elements of $(D Store) in descending
+order. Extracting elements from the $(D BinaryHeap) to completion
+leaves the underlying store sorted in ascending order but, again,
+yields elements in descending order.
+
+If $(D Store) is a range, the $(D BinaryHeap) cannot grow beyond the
+size of that range. If $(D Store) is a container that supports $(D
+insertBack), the $(D BinaryHeap) may grow by adding elements to the
+container.
+     */
+struct BinaryHeap(Store, alias less = "a < b")
+if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[])))
+{
+    import std.functional : binaryFun;
+    import std.exception : enforce;
+    import std.algorithm : move, min, HeapOps, swapAt;
+    import std.typecons : RefCounted, RefCountedAutoInitialize;
+
+    static if(isRandomAccessRange!Store)
+        alias Range = Store;
+    else
+        alias Range = typeof(Store.init[]);
+    alias percolate = HeapOps!(less, Range).percolate;
+    alias buildHeap = HeapOps!(less, Range).buildHeap;
+
+// Really weird @@BUG@@: if you comment out the "private:" label below,
+// std.algorithm can't unittest anymore
+//private:
+
+    // The payload includes the support store and the effective length
+    private static struct Data
+    {
+        Store _store;
+        size_t _length;
+    }
+    private RefCounted!(Data, RefCountedAutoInitialize.no) _payload;
+    // Comparison predicate
+    private alias comp = binaryFun!(less);
+    // Convenience accessors
+    private @property ref Store _store()
+    {
+        assert(_payload.refCountedStore.isInitialized);
+        return _payload._store;
+    }
+    private @property ref size_t _length()
+    {
+        assert(_payload.refCountedStore.isInitialized);
+        return _payload._length;
+    }
+
+    // Asserts that the heap property is respected.
+    private void assertValid()
+    {
+        debug
+        {
+            import std.conv : to;
+            if (!_payload.refCountedStore.isInitialized) return;
+            if (_length < 2) return;
+            for (size_t n = _length - 1; n >= 1; --n)
+            {
+                auto parentIdx = (n - 1) / 2;
+                assert(!comp(_store[parentIdx], _store[n]), to!string(n));
+            }
+        }
+    }
+
+    // @@@BUG@@@: add private here, std.algorithm doesn't unittest anymore
+    /*private*/ void pop(Store store)
+    {
+        assert(!store.empty, "Cannot pop an empty store.");
+        if (store.length == 1) return;
+        auto t1 = moveFront(store[]);
+        auto t2 = moveBack(store[]);
+        store.front = move(t2);
+        store.back = move(t1);
+        percolate(store[], 0, store.length - 1);
+    }
+
+public:
+
+    /**
+       Converts the store $(D s) into a heap. If $(D initialSize) is
+       specified, only the first $(D initialSize) elements in $(D s)
+       are transformed into a heap, after which the heap can grow up
+       to $(D r.length) (if $(D Store) is a range) or indefinitely (if
+       $(D Store) is a container with $(D insertBack)). Performs
+       $(BIGOH min(r.length, initialSize)) evaluations of $(D less).
+     */
+    this(Store s, size_t initialSize = size_t.max)
+    {
+        acquire(s, initialSize);
+    }
+
+/**
+Takes ownership of a store. After this, manipulating $(D s) may make
+the heap work incorrectly.
+     */
+    void acquire(Store s, size_t initialSize = size_t.max)
+    {
+        _payload.refCountedStore.ensureInitialized();
+        _store = move(s);
+        _length = min(_store.length, initialSize);
+        if (_length < 2) return;
+        buildHeap(_store[]);
+        assertValid();
+    }
+
+/**
+Takes ownership of a store assuming it already was organized as a
+heap.
+     */
+    void assume(Store s, size_t initialSize = size_t.max)
+    {
+        _payload.refCountedStore.ensureInitialized();
+        _store = s;
+        _length = min(_store.length, initialSize);
+        assertValid();
+    }
+
+/**
+Clears the heap. Returns the portion of the store from $(D 0) up to
+$(D length), which satisfies the $(LUCKY heap property).
+     */
+    auto release()
+    {
+        if (!_payload.refCountedStore.isInitialized)
+        {
+            return typeof(_store[0 .. _length]).init;
+        }
+        assertValid();
+        auto result = _store[0 .. _length];
+        _payload = _payload.init;
+        return result;
+    }
+
+/**
+Returns $(D true) if the heap is _empty, $(D false) otherwise.
+     */
+    @property bool empty()
+    {
+        return !length;
+    }
+
+/**
+Returns a duplicate of the heap. The underlying store must also
+support a $(D dup) method.
+     */
+    @property BinaryHeap dup()
+    {
+        BinaryHeap result;
+        if (!_payload.refCountedStore.isInitialized) return result;
+        result.assume(_store.dup, length);
+        return result;
+    }
+
+/**
+Returns the _length of the heap.
+     */
+    @property size_t length()
+    {
+        return _payload.refCountedStore.isInitialized ? _length : 0;
+    }
+
+/**
+Returns the _capacity of the heap, which is the length of the
+underlying store (if the store is a range) or the _capacity of the
+underlying store (if the store is a container).
+     */
+    @property size_t capacity()
+    {
+        if (!_payload.refCountedStore.isInitialized) return 0;
+        static if (is(typeof(_store.capacity) : size_t))
+        {
+            return _store.capacity;
+        }
+        else
+        {
+            return _store.length;
+        }
+    }
+
+/**
+Returns a copy of the _front of the heap, which is the largest element
+according to $(D less).
+     */
+    @property ElementType!Store front()
+    {
+        enforce(!empty, "Cannot call front on an empty heap.");
+        return _store.front;
+    }
+
+/**
+Clears the heap by detaching it from the underlying store.
+     */
+    void clear()
+    {
+        _payload = _payload.init;
+    }
+
+/**
+Inserts $(D value) into the store. If the underlying store is a range
+and $(D length == capacity), throws an exception.
+     */
+    size_t insert(ElementType!Store value)
+    {
+        static if (is(typeof(_store.insertBack(value))))
+        {
+            _payload.refCountedStore.ensureInitialized();
+            if (length == _store.length)
+            {
+                // reallocate
+                _store.insertBack(value);
+            }
+            else
+            {
+                // no reallocation
+                _store[_length] = value;
+            }
+        }
+        else
+        {
+            // can't grow
+            enforce(length < _store.length,
+                    "Cannot grow a heap created over a range");
+            _store[_length] = value;
+        }
+
+        // sink down the element
+        for (size_t n = _length; n; )
+        {
+            auto parentIdx = (n - 1) / 2;
+            if (!comp(_store[parentIdx], _store[n])) break; // done!
+            // must swap and continue
+            swapAt(_store, parentIdx, n);
+            n = parentIdx;
+        }
+        ++_length;
+        debug(BinaryHeap) assertValid();
+        return 1;
+    }
+
+/**
+Removes the largest element from the heap.
+     */
+    void removeFront()
+    {
+        enforce(!empty, "Cannot call removeFront on an empty heap.");
+        if (_length > 1)
+        {
+            auto t1 = moveFront(_store[]);
+            auto t2 = moveAt(_store[], _length - 1);
+            _store.front = move(t2);
+            _store[_length - 1] = move(t1);
+        }
+        --_length;
+        percolate(_store[], 0, _length);
+    }
+
+    /// ditto
+    alias popFront = removeFront;
+
+/**
+Removes the largest element from the heap and returns a copy of
+it. The element still resides in the heap's store. For performance
+reasons you may want to use $(D removeFront) with heaps of objects
+that are expensive to copy.
+     */
+    ElementType!Store removeAny()
+    {
+        removeFront();
+        return _store[_length];
+    }
+
+/**
+Replaces the largest element in the store with $(D value).
+     */
+    void replaceFront(ElementType!Store value)
+    {
+        // must replace the top
+        assert(!empty, "Cannot call replaceFront on an empty heap.");
+        _store.front = value;
+        percolate(_store[], 0, _length);
+        debug(BinaryHeap) assertValid();
+    }
+
+/**
+If the heap has room to grow, inserts $(D value) into the store and
+returns $(D true). Otherwise, if $(D less(value, front)), calls $(D
+replaceFront(value)) and returns again $(D true). Otherwise, leaves
+the heap unaffected and returns $(D false). This method is useful in
+scenarios where the smallest $(D k) elements of a set of candidates
+must be collected.
+     */
+    bool conditionalInsert(ElementType!Store value)
+    {
+        _payload.refCountedStore.ensureInitialized();
+        if (_length < _store.length)
+        {
+            insert(value);
+            return true;
+        }
+        // must replace the top
+        assert(!_store.empty, "Cannot replace front of an empty heap.");
+        if (!comp(value, _store.front)) return false; // value >= largest
+        _store.front = value;
+        percolate(_store[], 0, _length);
+        debug(BinaryHeap) assertValid();
+        return true;
+    }
+}
+
+/// Example from "Introduction to Algorithms" Cormen et al, p 146
+unittest
+{
+    import std.algorithm : equal;
+    int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
+    auto h = heapify(a);
+    // largest element
+    assert(h.front == 16);
+    // a has the heap property
+    assert(equal(a, [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]));
+}
+
+/// $(D BinaryHeap) implements the standard input range interface, allowing
+/// lazy iteration of the underlying range in descending order.
+unittest
+{
+    import std.algorithm : equal;
+    import std.range : take;
+    int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7];
+    auto top5 = heapify(a).take(5);
+    assert(top5.equal([16, 14, 10, 9, 8]));
+}
+
+/**
+Convenience function that returns a $(D BinaryHeap!Store) object
+initialized with $(D s) and $(D initialSize).
+ */
+BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s,
+        size_t initialSize = size_t.max)
+{
+    return BinaryHeap!(Store, less)(s, initialSize);
+}
+
+unittest
+{
+    import std.conv : to;
+    {
+        // example from "Introduction to Algorithms" Cormen et al., p 146
+        int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
+        auto h = heapify(a);
+        h = heapify!"a < b"(a);
+        assert(h.front == 16);
+        assert(a == [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]);
+        auto witness = [ 16, 14, 10, 9, 8, 7, 4, 3, 2, 1 ];
+        for (; !h.empty; h.removeFront(), witness.popFront())
+        {
+            assert(!witness.empty);
+            assert(witness.front == h.front);
+        }
+        assert(witness.empty);
+    }
+    {
+        int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
+        int[] b = new int[a.length];
+        BinaryHeap!(int[]) h = BinaryHeap!(int[])(b, 0);
+        foreach (e; a)
+        {
+            h.insert(e);
+        }
+        assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ], to!string(b));
+    }
+}
+
+unittest
+{
+    // Test range interface.
+    import std.algorithm : equal;
+    int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7];
+    auto h = heapify(a);
+    static assert(isInputRange!(typeof(h)));
+    assert(h.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1]));
+}
+
+unittest // 15675
+{
+    import std.container.array : Array;
+
+    Array!int elements = [1, 2, 10, 12];
+    auto heap = heapify(elements);
+    assert(heap.front == 12);
+}
diff --git a/libphobos/src/std/container/dlist.d b/libphobos/src/std/container/dlist.d
new file mode 100644
index 00000000000..2d5940563df
--- /dev/null
+++ b/libphobos/src/std/container/dlist.d
@@ -0,0 +1,980 @@
+/**
+This module implements a generic doubly-linked list container.
+It can be used as a queue, dequeue or stack.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_dlist.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.dlist;
+
+///
+unittest
+{
+    import std.container: DList;
+    import std.algorithm: equal;
+
+    auto s = DList!int(1, 2, 3);
+    assert(equal(s[], [1, 2, 3]));
+
+    s.removeFront();
+    assert(equal(s[], [2, 3]));
+    s.removeBack();
+    assert(equal(s[], [2]));
+
+    s.insertFront([4, 5]);
+    assert(equal(s[], [4, 5, 2]));
+    s.insertBack([6, 7]);
+    assert(equal(s[], [4, 5, 2, 6, 7]));
+
+    // If you want to apply range operations, simply slice it.
+    import std.algorithm: countUntil;
+    import std.range: popFrontN, popBackN, walkLength;
+
+    auto sl = DList!int([1, 2, 3, 4, 5]);
+    assert(countUntil(sl[], 2) == 1);
+
+    auto r = sl[];
+    popFrontN(r, 2);
+    popBackN(r, 2);
+    assert(r.equal([3]));
+    assert(walkLength(r) == 1);
+}
+
+import std.range.primitives;
+import std.traits;
+
+public import std.container.util;
+
+/+
+A DList Node without payload. Used to handle the sentinel node (henceforth "sentinode").
+
+Also used for parts of the code that don't depend on the payload type.
+ +/
+private struct BaseNode
+{
+    private BaseNode* _prev = null;
+    private BaseNode* _next = null;
+
+    /+
+    Gets the payload associated with this node.
+    This is trusted because all nodes are associated with a payload, even
+    the sentinel node.
+    It is also not possible to mix Nodes in DLists of different types.
+    This function is implemented as a member function here, as UFCS does not
+    work with pointers.
+    +/
+    ref inout(T) getPayload(T)() inout @trusted
+    {
+        return (cast(inout(DList!T.PayNode)*)&this)._payload;
+    }
+
+    // Helper: Given nodes p and n, connects them.
+    static void connect(BaseNode* p, BaseNode* n) @safe nothrow pure
+    {
+        p._next = n;
+        n._prev = p;
+    }
+}
+
+/+
+The base DList Range. Contains Range primitives that don't depend on payload type.
+ +/
+private struct DRange
+{
+    unittest
+    {
+        static assert(isBidirectionalRange!DRange);
+        static assert(is(ElementType!DRange == BaseNode*));
+    }
+
+nothrow @safe pure:
+    private BaseNode* _first;
+    private BaseNode* _last;
+
+    private this(BaseNode* first, BaseNode* last)
+    {
+        assert((first is null) == (last is null), "Dlist.Range.this: Invalid arguments");
+        _first = first;
+        _last = last;
+    }
+    private this(BaseNode* n)
+    {
+        this(n, n);
+    }
+
+    @property
+    bool empty() const
+    {
+        assert((_first is null) == (_last is null), "DList.Range: Invalidated state");
+        return !_first;
+    }
+
+    @property BaseNode* front()
+    {
+        assert(!empty, "DList.Range.front: Range is empty");
+        return _first;
+    }
+
+    void popFront()
+    {
+        assert(!empty, "DList.Range.popFront: Range is empty");
+        if (_first is _last)
+        {
+            _first = _last = null;
+        }
+        else
+        {
+            assert(_first._next && _first is _first._next._prev, "DList.Range: Invalidated state");
+            _first = _first._next;
+        }
+    }
+
+    @property BaseNode* back()
+    {
+        assert(!empty, "DList.Range.front: Range is empty");
+        return _last;
+    }
+
+    void popBack()
+    {
+        assert(!empty, "DList.Range.popBack: Range is empty");
+        if (_first is _last)
+        {
+            _first = _last = null;
+        }
+        else
+        {
+            assert(_last._prev && _last is _last._prev._next, "DList.Range: Invalidated state");
+            _last = _last._prev;
+        }
+    }
+
+    /// Forward range primitive.
+    @property DRange save() { return this; }
+}
+
+/**
+Implements a doubly-linked list.
+
+$(D DList) uses reference semantics.
+ */
+struct DList(T)
+{
+    import std.range : Take;
+
+    /*
+    A Node with a Payload. A PayNode.
+     */
+    struct PayNode
+    {
+        BaseNode _base;
+        alias _base this;
+
+        T _payload = T.init;
+
+        inout(BaseNode)* asBaseNode() inout @trusted
+        {
+            return &_base;
+        }
+    }
+
+    //The sentinel node
+    private BaseNode* _root;
+
+  private
+  {
+    //Construct as new PayNode, and returns it as a BaseNode.
+    static BaseNode* createNode(Stuff)(auto ref Stuff arg, BaseNode* prev = null, BaseNode* next = null)
+    {
+        return (new PayNode(BaseNode(prev, next), arg)).asBaseNode();
+    }
+
+    void initialize() nothrow @safe pure
+    {
+        if (_root) return;
+        //Note: We allocate a PayNode for safety reasons.
+        _root = (new PayNode()).asBaseNode();
+        _root._next = _root._prev = _root;
+    }
+    ref inout(BaseNode*) _first() @property @safe nothrow pure inout
+    {
+        assert(_root);
+        return _root._next;
+    }
+    ref inout(BaseNode*) _last() @property @safe nothrow pure inout
+    {
+        assert(_root);
+        return _root._prev;
+    }
+  } //end private
+
+/**
+Constructor taking a number of nodes
+     */
+    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T))
+    {
+        insertBack(values);
+    }
+
+/**
+Constructor taking an input range
+     */
+    this(Stuff)(Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        insertBack(stuff);
+    }
+
+/**
+Comparison for equality.
+
+Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of
+elements in $(D rhs).
+     */
+    bool opEquals()(ref const DList rhs) const
+    if (is(typeof(front == front)))
+    {
+        const lhs = this;
+        const lroot = lhs._root;
+        const rroot = rhs._root;
+
+        if (lroot is rroot) return true;
+        if (lroot is null) return rroot is rroot._next;
+        if (rroot is null) return lroot is lroot._next;
+
+        const(BaseNode)* pl = lhs._first;
+        const(BaseNode)* pr = rhs._first;
+        while (true)
+        {
+            if (pl is lroot) return pr is rroot;
+            if (pr is rroot) return false;
+
+            // !== because of NaN
+            if (!(pl.getPayload!T() == pr.getPayload!T())) return false;
+
+            pl = pl._next;
+            pr = pr._next;
+        }
+    }
+
+    /**
+    Defines the container's primary range, which embodies a bidirectional range.
+     */
+    struct Range
+    {
+        static assert(isBidirectionalRange!Range);
+
+        DRange _base;
+        alias _base this;
+
+        private this(BaseNode* first, BaseNode* last)
+        {
+            _base = DRange(first, last);
+        }
+        private this(BaseNode* n)
+        {
+            this(n, n);
+        }
+
+        @property ref T front()
+        {
+            return _base.front.getPayload!T();
+        }
+
+        @property ref T back()
+        {
+            return _base.back.getPayload!T();
+        }
+
+        //Note: shadows base DRange.save.
+        //Necessary for static covariance.
+        @property Range save() { return this; }
+    }
+
+/**
+Property returning $(D true) if and only if the container has no
+elements.
+
+Complexity: $(BIGOH 1)
+     */
+    bool empty() @property const nothrow
+    {
+        return _root is null || _root is _first;
+    }
+
+/**
+Removes all contents from the $(D DList).
+
+Postcondition: $(D empty)
+
+Complexity: $(BIGOH 1)
+     */
+    void clear()
+    {
+        //remove actual elements.
+        remove(this[]);
+    }
+
+/**
+Duplicates the container. The elements themselves are not transitively
+duplicated.
+
+Complexity: $(BIGOH n).
+     */
+    @property DList dup()
+    {
+        return DList(this[]);
+    }
+
+/**
+Returns a range that iterates over all elements of the container, in
+forward order.
+
+Complexity: $(BIGOH 1)
+     */
+    Range opSlice()
+    {
+        if (empty)
+            return Range(null, null);
+        else
+            return Range(_first, _last);
+    }
+
+/**
+Forward to $(D opSlice().front).
+
+Complexity: $(BIGOH 1)
+     */
+    @property ref inout(T) front() inout
+    {
+        assert(!empty, "DList.front: List is empty");
+        return _first.getPayload!T();
+    }
+
+/**
+Forward to $(D opSlice().back).
+
+Complexity: $(BIGOH 1)
+     */
+    @property ref inout(T) back() inout
+    {
+        assert(!empty, "DList.back: List is empty");
+        return _last.getPayload!T();
+    }
+
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+/+                        BEGIN CONCAT FUNCTIONS HERE                         +/
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+
+/**
+Returns a new $(D DList) that's the concatenation of $(D this) and its
+argument $(D rhs).
+     */
+    DList opBinary(string op, Stuff)(Stuff rhs)
+    if (op == "~" && is(typeof(insertBack(rhs))))
+    {
+        auto ret = this.dup;
+        ret.insertBack(rhs);
+        return ret;
+    }
+
+/**
+Returns a new $(D DList) that's the concatenation of the argument $(D lhs)
+and $(D this).
+     */
+    DList opBinaryRight(string op, Stuff)(Stuff lhs)
+    if (op == "~" && is(typeof(insertFront(lhs))))
+    {
+        auto ret = this.dup;
+        ret.insertFront(lhs);
+        return ret;
+    }
+
+/**
+Appends the contents of the argument $(D rhs) into $(D this).
+     */
+    DList opOpAssign(string op, Stuff)(Stuff rhs)
+    if (op == "~" && is(typeof(insertBack(rhs))))
+    {
+        insertBack(rhs);
+        return this;
+    }
+
+    // Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+    deprecated("Please, use `dlist ~= dlist[];` instead.")
+    DList opOpAssign(string op)(DList rhs)
+    if (op == "~")
+    {
+        return this ~= rhs[];
+    }
+
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+/+                        BEGIN INSERT FUNCTIONS HERE                         +/
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+
+/**
+Inserts $(D stuff) to the front/back of the container. $(D stuff) can be a
+value convertible to $(D T) or a range of objects convertible to $(D
+T). The stable version behaves the same, but guarantees that ranges
+iterating over the container are never invalidated.
+
+Returns: The number of elements inserted
+
+Complexity: $(BIGOH log(n))
+     */
+    size_t insertFront(Stuff)(Stuff stuff)
+    {
+        initialize();
+        return insertAfterNode(_root, stuff);
+    }
+
+    /// ditto
+    size_t insertBack(Stuff)(Stuff stuff)
+    {
+        initialize();
+        return insertBeforeNode(_root, stuff);
+    }
+
+    /// ditto
+    alias insert = insertBack;
+
+    /// ditto
+    alias stableInsert = insert;
+
+    /// ditto
+    alias stableInsertFront = insertFront;
+
+    /// ditto
+    alias stableInsertBack = insertBack;
+
+/**
+Inserts $(D stuff) after range $(D r), which must be a non-empty range
+previously extracted from this container.
+
+$(D stuff) can be a value convertible to $(D T) or a range of objects
+convertible to $(D T). The stable version behaves the same, but
+guarantees that ranges iterating over the container are never
+invalidated.
+
+Returns: The number of values inserted.
+
+Complexity: $(BIGOH k + m), where $(D k) is the number of elements in
+$(D r) and $(D m) is the length of $(D stuff).
+     */
+    size_t insertBefore(Stuff)(Range r, Stuff stuff)
+    {
+        if (r._first)
+            return insertBeforeNode(r._first, stuff);
+        else
+        {
+            initialize();
+            return insertAfterNode(_root, stuff);
+        }
+    }
+
+    /// ditto
+    alias stableInsertBefore = insertBefore;
+
+    /// ditto
+    size_t insertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        if (r._last)
+            return insertAfterNode(r._last, stuff);
+        else
+        {
+            initialize();
+            return insertBeforeNode(_root, stuff);
+        }
+    }
+
+    /// ditto
+    alias stableInsertAfter = insertAfter;
+
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+/+                        BEGIN REMOVE FUNCTIONS HERE                         +/
+/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
+
+/**
+Picks one value in an unspecified position in the container, removes
+it from the container, and returns it. The stable version behaves the same,
+but guarantees that ranges iterating over the container are never invalidated.
+
+Precondition: $(D !empty)
+
+Returns: The element removed.
+
+Complexity: $(BIGOH 1).
+     */
+    T removeAny()
+    {
+        import std.algorithm : move;
+
+        assert(!empty, "DList.removeAny: List is empty");
+        auto result = move(back);
+        removeBack();
+        return result;
+    }
+    /// ditto
+    alias stableRemoveAny = removeAny;
+
+/**
+Removes the value at the front/back of the container. The stable version
+behaves the same, but guarantees that ranges iterating over the
+container are never invalidated.
+
+Precondition: $(D !empty)
+
+Complexity: $(BIGOH 1).
+     */
+    void removeFront()
+    {
+        assert(!empty, "DList.removeFront: List is empty");
+        assert(_root is _first._prev, "DList: Inconsistent state");
+        BaseNode.connect(_root, _first._next);
+    }
+
+    /// ditto
+    alias stableRemoveFront = removeFront;
+
+    /// ditto
+    void removeBack()
+    {
+        assert(!empty, "DList.removeBack: List is empty");
+        assert(_last._next is _root, "DList: Inconsistent state");
+        BaseNode.connect(_last._prev, _root);
+    }
+
+    /// ditto
+    alias stableRemoveBack = removeBack;
+
+/**
+Removes $(D howMany) values at the front or back of the
+container. Unlike the unparameterized versions above, these functions
+do not throw if they could not remove $(D howMany) elements. Instead,
+if $(D howMany > n), all elements are removed. The returned value is
+the effective number of elements removed. The stable version behaves
+the same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: The number of elements removed
+
+Complexity: $(BIGOH howMany).
+     */
+    size_t removeFront(size_t howMany)
+    {
+        if (!_root) return 0;
+        size_t result;
+        auto p = _first;
+        while (p !is _root && result < howMany)
+        {
+            p = p._next;
+            ++result;
+        }
+        BaseNode.connect(_root, p);
+        return result;
+    }
+
+    /// ditto
+    alias stableRemoveFront = removeFront;
+
+    /// ditto
+    size_t removeBack(size_t howMany)
+    {
+        if (!_root) return 0;
+        size_t result;
+        auto p = _last;
+        while (p !is _root && result < howMany)
+        {
+            p = p._prev;
+            ++result;
+        }
+        BaseNode.connect(p, _root);
+        return result;
+    }
+
+    /// ditto
+    alias stableRemoveBack = removeBack;
+
+/**
+Removes all elements belonging to $(D r), which must be a range
+obtained originally from this container.
+
+Returns: A range spanning the remaining elements in the container that
+initially were right after $(D r).
+
+Complexity: $(BIGOH 1)
+     */
+    Range remove(Range r)
+    {
+        if (r.empty)
+            return r;
+
+        assert(_root !is null, "Cannot remove from an un-initialized List");
+        assert(r._first, "Remove: Range is empty");
+
+        BaseNode.connect(r._first._prev, r._last._next);
+        auto after = r._last._next;
+        if (after is _root)
+            return Range(null, null);
+        else
+            return Range(after, _last);
+    }
+
+    /// ditto
+    Range linearRemove(Range r)
+    {
+         return remove(r);
+    }
+
+/**
+$(D linearRemove) functions as $(D remove), but also accepts ranges that are
+result the of a $(D take) operation. This is a convenient way to remove a
+fixed amount of elements from the range.
+
+Complexity: $(BIGOH r.walkLength)
+     */
+    Range linearRemove(Take!Range r)
+    {
+        assert(_root !is null, "Cannot remove from an un-initialized List");
+        assert(r.source._first, "Remove: Range is empty");
+
+        BaseNode* first = r.source._first;
+        BaseNode* last = null;
+        do
+        {
+            last = r.source._first;
+            r.popFront();
+        } while ( !r.empty );
+
+        return remove(Range(first, last));
+    }
+
+    /// ditto
+    alias stableRemove = remove;
+    /// ditto
+    alias stableLinearRemove = linearRemove;
+
+private:
+
+    // Helper: Inserts stuff before the node n.
+    size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T))
+    {
+        auto p = createNode(stuff, n._prev, n);
+        n._prev._next = p;
+        n._prev = p;
+        return 1;
+    }
+    // ditto
+    size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        if (stuff.empty) return 0;
+        size_t result;
+        Range r = createRange(stuff, result);
+        BaseNode.connect(n._prev, r._first);
+        BaseNode.connect(r._last, n);
+        return result;
+    }
+
+    // Helper: Inserts stuff after the node n.
+    size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T))
+    {
+        auto p = createNode(stuff, n, n._next);
+        n._next._prev = p;
+        n._next = p;
+        return 1;
+    }
+    // ditto
+    size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        if (stuff.empty) return 0;
+        size_t result;
+        Range r = createRange(stuff, result);
+        BaseNode.connect(r._last, n._next);
+        BaseNode.connect(n, r._first);
+        return result;
+    }
+
+    // Helper: Creates a chain of nodes from the range stuff.
+    Range createRange(Stuff)(ref Stuff stuff, ref size_t result)
+    {
+        BaseNode* first = createNode(stuff.front);
+        BaseNode* last = first;
+        ++result;
+        for ( stuff.popFront() ; !stuff.empty ; stuff.popFront() )
+        {
+            auto p = createNode(stuff.front, last);
+            last = last._next = p;
+            ++result;
+        }
+        return Range(first, last);
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    //Tests construction signatures
+    alias IntList = DList!int;
+    auto a0 = IntList();
+    auto a1 = IntList(0);
+    auto a2 = IntList(0, 1);
+    auto a3 = IntList([0]);
+    auto a4 = IntList([0, 1]);
+
+    assert(a0[].empty);
+    assert(equal(a1[], [0]));
+    assert(equal(a2[], [0, 1]));
+    assert(equal(a3[], [0]));
+    assert(equal(a4[], [0, 1]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    alias IntList = DList!int;
+    IntList list = IntList([0,1,2,3]);
+    assert(equal(list[],[0,1,2,3]));
+    list.insertBack([4,5,6,7]);
+    assert(equal(list[],[0,1,2,3,4,5,6,7]));
+
+    list = IntList();
+    list.insertFront([0,1,2,3]);
+    assert(equal(list[],[0,1,2,3]));
+    list.insertFront([4,5,6,7]);
+    assert(equal(list[],[4,5,6,7,0,1,2,3]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.range : take;
+
+    alias IntList = DList!int;
+    IntList list = IntList([0,1,2,3]);
+    auto range = list[];
+    for( ; !range.empty; range.popFront())
+    {
+        int item = range.front;
+        if (item == 2)
+        {
+            list.stableLinearRemove(take(range, 1));
+            break;
+        }
+    }
+    assert(equal(list[],[0,1,3]));
+
+    list = IntList([0,1,2,3]);
+    range = list[];
+    for( ; !range.empty; range.popFront())
+    {
+        int item = range.front;
+        if (item == 2)
+        {
+            list.stableLinearRemove(take(range,2));
+            break;
+        }
+    }
+    assert(equal(list[],[0,1]));
+
+    list = IntList([0,1,2,3]);
+    range = list[];
+    for( ; !range.empty; range.popFront())
+    {
+        int item = range.front;
+        if (item == 0)
+        {
+            list.stableLinearRemove(take(range,2));
+            break;
+        }
+    }
+    assert(equal(list[],[2,3]));
+
+    list = IntList([0,1,2,3]);
+    range = list[];
+    for( ; !range.empty; range.popFront())
+    {
+        int item = range.front;
+        if (item == 1)
+        {
+            list.stableLinearRemove(take(range,2));
+            break;
+        }
+    }
+    assert(equal(list[],[0,3]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto dl = DList!string(["a", "b", "d"]);
+    dl.insertAfter(dl[], "e"); // insert at the end
+    assert(equal(dl[], ["a", "b", "d", "e"]));
+    auto dlr = dl[];
+    dlr.popBack(); dlr.popBack();
+    dl.insertAfter(dlr, "c"); // insert after "b"
+    assert(equal(dl[], ["a", "b", "c", "d", "e"]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto dl = DList!string(["a", "b", "d"]);
+    dl.insertBefore(dl[], "e"); // insert at the front
+    assert(equal(dl[], ["e", "a", "b", "d"]));
+    auto dlr = dl[];
+    dlr.popFront(); dlr.popFront();
+    dl.insertBefore(dlr, "c"); // insert before "b"
+    assert(equal(dl[], ["e", "a", "c", "b", "d"]));
+}
+
+@safe unittest
+{
+    auto d = DList!int([1, 2, 3]);
+    d.front = 5; //test frontAssign
+    assert(d.front == 5);
+    auto r = d[];
+    r.back = 1;
+    assert(r.back == 1);
+}
+
+// Issue 8895
+@safe unittest
+{
+    auto a = make!(DList!int)(1,2,3,4);
+    auto b = make!(DList!int)(1,2,3,4);
+    auto c = make!(DList!int)(1,2,3,5);
+    auto d = make!(DList!int)(1,2,3,4,5);
+    assert(a == b); // this better terminate!
+    assert(!(a == c));
+    assert(!(a == d));
+}
+
+@safe unittest
+{
+    auto d = DList!int([1, 2, 3]);
+    d.front = 5; //test frontAssign
+    assert(d.front == 5);
+    auto r = d[];
+    r.back = 1;
+    assert(r.back == 1);
+}
+
+@safe unittest
+{
+    auto a = DList!int();
+    assert(a.removeFront(10) == 0);
+    a.insert([1, 2, 3]);
+    assert(a.removeFront(10) == 3);
+    assert(a[].empty);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    //Verify all flavors of ~
+    auto a = DList!int();
+    auto b = DList!int();
+    auto c = DList!int([1, 2, 3]);
+    auto d = DList!int([4, 5, 6]);
+
+    assert((a ~ b[])[].empty);
+    assert((c ~ d[])[].equal([1, 2, 3, 4, 5, 6]));
+    assert(c[].equal([1, 2, 3]));
+    assert(d[].equal([4, 5, 6]));
+
+    assert((c[] ~ d)[].equal([1, 2, 3, 4, 5, 6]));
+    assert(c[].equal([1, 2, 3]));
+    assert(d[].equal([4, 5, 6]));
+
+    a~=c[];
+    assert(a[].equal([1, 2, 3]));
+    assert(c[].equal([1, 2, 3]));
+
+    a~=d[];
+    assert(a[].equal([1, 2, 3, 4, 5, 6]));
+    assert(d[].equal([4, 5, 6]));
+
+    a~=[7, 8, 9];
+    assert(a[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+
+    //trick test:
+    auto r = c[];
+    c.removeFront();
+    c.removeBack();
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    //8905
+    auto a = DList!int([1, 2, 3, 4]);
+    auto r = a[];
+    a.stableRemoveBack();
+    a.stableInsertBack(7);
+    assert(a[].equal([1, 2, 3, 7]));
+}
+
+@safe unittest //12566
+{
+    auto dl2 = DList!int([2,7]);
+    dl2.removeFront();
+    assert(dl2[].walkLength == 1);
+    dl2.removeBack();
+    assert(dl2.empty, "not empty?!");
+}
+
+@safe unittest //13076
+{
+    DList!int list;
+    assert(list.empty);
+    list.clear();
+}
+
+@safe unittest //13425
+{
+    import std.range : drop, take;
+    auto list = DList!int([1,2,3,4,5]);
+    auto r = list[].drop(4); // r is a view of the last element of list
+    assert(r.front == 5 && r.walkLength == 1);
+    r = list.linearRemove(r.take(1));
+    assert(r.empty); // fails
+}
+
+@safe unittest //14300
+{
+    interface ITest {}
+    static class Test : ITest {}
+
+    DList!ITest().insertBack(new Test());
+}
+
+@safe unittest //15263
+{
+    import std.range : iota;
+    auto a = DList!int();
+    a.insertFront(iota(0, 5)); // can insert range with non-ref front
+    assert(a.front == 0 && a.back == 4);
+}
diff --git a/libphobos/src/std/container/package.d b/libphobos/src/std/container/package.d
new file mode 100644
index 00000000000..68fb9660cd9
--- /dev/null
+++ b/libphobos/src/std/container/package.d
@@ -0,0 +1,1059 @@
+// Written in the D programming language.
+
+/**
+This module defines generic containers.
+
+Construction:
+
+To implement the different containers both struct and class based
+approaches have been used. $(XREF_PACK _container,util,make) allows for
+uniform construction with either approach.
+
+---
+import std.container;
+// Construct a red-black tree and an array both containing the values 1, 2, 3.
+// RedBlackTree should typically be allocated using `new`
+RedBlackTree!int rbTree = new RedBlackTree!int(1, 2, 3);
+// But `new` should not be used with Array
+Array!int array = Array!int(1, 2, 3);
+// `make` hides the differences
+RedBlackTree!int rbTree2 = make!(RedBlackTree!int)(1, 2, 3);
+Array!int array2 = make!(Array!int)(1, 2, 3);
+---
+
+Note that $(D make) can infer the element type from the given arguments.
+
+---
+import std.container;
+auto rbTree = make!RedBlackTree(1, 2, 3); // RedBlackTree!int
+auto array = make!Array("1", "2", "3"); // Array!string
+---
+
+Reference_semantics:
+
+All containers have reference semantics, which means that after
+assignment both variables refer to the same underlying data.
+
+To make a copy of a _container, use the $(D c._dup) _container primitive.
+---
+import std.container, std.range;
+Array!int originalArray = make!(Array!int)(1, 2, 3);
+Array!int secondArray = originalArray;
+assert(equal(originalArray[], secondArray[]));
+
+// changing one instance changes the other one as well!
+originalArray[0] = 12;
+assert(secondArray[0] == 12);
+
+// secondArray now refers to an independent copy of originalArray
+secondArray = originalArray.dup;
+secondArray[0] = 1;
+// assert that originalArray has not been affected
+assert(originalArray[0] == 12);
+---
+
+$(B Attention:) If the _container is implemented as a class, using an
+uninitialized instance can cause a null pointer dereference.
+
+---
+import std.container;
+
+RedBlackTree!int rbTree;
+rbTree.insert(5); // null pointer dereference
+---
+
+Using an uninitialized struct-based _container will work, because the struct
+intializes itself upon use; however, up to this point the _container will not
+have an identity and assignment does not create two references to the same
+data.
+
+---
+import std.container;
+
+// create an uninitialized array
+Array!int array1;
+// array2 does _not_ refer to array1
+Array!int array2 = array1;
+array2.insertBack(42);
+// thus array1 will not be affected
+assert(array1.empty);
+
+// after initialization reference semantics work as expected
+array1 = array2;
+// now affects array2 as well
+array1.removeBack();
+assert(array2.empty);
+---
+It is therefore recommended to always construct containers using
+$(XREF_PACK _container,util,make).
+
+This is in fact necessary to put containers into another _container.
+For example, to construct an $(D Array) of ten empty $(D Array)s, use
+the following that calls $(D make) ten times.
+
+---
+import std.container, std.range;
+
+auto arrOfArrs = make!Array(generate!(() => make!(Array!int)).take(10));
+---
+
+Submodules:
+
+This module consists of the following submodules:
+
+$(UL
+    $(LI
+        The $(LINK2 std_container_array.html, std._container.array) module provides
+        an array type with deterministic control of memory, not reliant on
+        the GC unlike built-in arrays.
+    )
+    $(LI
+        The $(LINK2 std_container_binaryheap.html, std._container.binaryheap) module
+        provides a binary heap implementation that can be applied to any
+        user-provided random-access range.
+    )
+    $(LI
+        The $(LINK2 std_container_dlist.html, std._container.dlist) module provides
+        a doubly-linked list implementation.
+    )
+    $(LI
+        The $(LINK2 std_container_rbtree.html, std._container.rbtree) module
+        implements red-black trees.
+    )
+    $(LI
+        The $(LINK2 std_container_slist.html, std._container.slist) module
+        implements singly-linked lists.
+    )
+    $(LI
+        The $(LINK2 std_container_util.html, std._container.util) module contains
+        some generic tools commonly used by _container implementations.
+    )
+)
+
+The_primary_range_of_a_container:
+
+While some _containers offer direct access to their elements e.g. via
+$(D opIndex), $(D c.front) or $(D c.back), access
+and modification of a _container's contents is generally done through
+its primary $(LINK2 std_range.html, range) type,
+which is aliased as $(D C.Range). For example, the primary range type of
+$(D Array!int) is $(D Array!int.Range).
+
+If the documentation of a member function of a _container takes
+a parameter of type $(D Range), then it refers to the primary range type of
+this _container. Oftentimes $(D Take!Range) will be used, in which case
+the range refers to a span of the elements in the _container. Arguments to
+these parameters $(B must) be obtained from the same _container instance
+as the one being worked with. It is important to note that many generic range
+algorithms return the same range type as their input range.
+
+---
+import std.algorithm : equal, find;
+import std.container;
+import std.range : take;
+
+auto array = make!Array(1, 2, 3);
+
+// `find` returns an Array!int.Range advanced to the element "2"
+array.linearRemove(array[].find(2));
+
+assert(array[].equal([1]));
+
+array = make!Array(1, 2, 3);
+
+// the range given to `linearRemove` is a Take!(Array!int.Range)
+// spanning just the element "2"
+array.linearRemove(array[].find(2).take(1));
+
+assert(array[].equal([1, 3]));
+---
+
+When any $(LINK2 std_range.html, range) can be passed as an argument to
+a member function, the documention usually refers to the parameter's templated
+type as $(D Stuff).
+
+---
+import std.algorithm : equal;
+import std.container;
+import std.range : iota;
+
+auto array = make!Array(1, 2);
+
+// the range type returned by `iota` is completely unrelated to Array,
+// which is fine for Array.insertBack:
+array.insertBack(iota(3, 10));
+
+assert(array[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+---
+
+Container_primitives:
+
+Containers do not form a class hierarchy, instead they implement a
+common set of primitives (see table below). These primitives each guarantee
+a specific worst case complexity and thus allow generic code to be written
+independently of the _container implementation.
+
+For example the primitives $(D c.remove(r)) and $(D c.linearRemove(r)) both
+remove the sequence of elements in range $(D r) from the _container $(D c).
+The primitive $(D c.remove(r)) guarantees
+$(BIGOH n$(SUBSCRIPT r) log n$(SUBSCRIPT c)) complexity in the worst case and
+$(D c.linearRemove(r)) relaxes this guarantee to $(BIGOH n$(SUBSCRIPT c)).
+
+Since a sequence of elements can be removed from a $(LINK2 std_container_dlist.html, doubly linked list)
+in constant time, $(D DList) provides the primitive $(D c.remove(r))
+as well as $(D c.linearRemove(r)). On the other hand
+$(LINK2 std_container_array.html, Array) only offers $(D c.linearRemove(r)).
+
+The following table describes the common set of primitives that containers
+implement.  A _container need not implement all primitives, but if a
+primitive is implemented, it must support the syntax described in the $(B
+syntax) column with the semantics described in the $(B description) column, and
+it must not have a worst-case complexity worse than denoted in big-O notation in
+the $(BIGOH &middot;) column.  Below, $(D C) means a _container type, $(D c) is
+a value of _container type, $(D n$(SUBSCRIPT x)) represents the effective length of
+value $(D x), which could be a single element (in which case $(D n$(SUBSCRIPT x)) is
+$(D 1)), a _container, or a range.
+
+$(BOOKTABLE Container primitives,
+
+$(TR $(TH Syntax) $(TH $(BIGOH &middot;)) $(TH Description))
+
+$(TR $(TDNW $(D C(x))) $(TDNW $(D n$(SUBSCRIPT x))) $(TD Creates a
+_container of type $(D C) from either another _container or a range. The created _container must not be a null reference even if x is empty.))
+
+$(TR $(TDNW $(D c.dup)) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Returns a
+duplicate of the _container.))
+
+$(TR $(TDNW $(D c ~ x)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) $(TD
+Returns the concatenation of $(D c) and $(D r). $(D x) may be a single
+element or an input range.))
+
+$(TR $(TDNW $(D x ~ c)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) $(TD
+Returns the concatenation of $(D x) and $(D c).  $(D x) may be a
+single element or an input range type.))
+
+$(LEADINGROWN 3, Iteration)
+
+$(TR  $(TD $(D c.Range)) $(TD) $(TD The primary range
+type associated with the _container.))
+
+$(TR $(TD $(D c[])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range
+iterating over the entire _container, in a _container-defined order.))
+
+$(TR $(TDNW $(D c[a .. b])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Fetches a
+portion of the _container from key $(D a) to key $(D b).))
+
+$(LEADINGROWN 3, Capacity)
+
+$(TR $(TD $(D c.empty)) $(TD $(D 1)) $(TD Returns $(D true) if the
+_container has no elements, $(D false) otherwise.))
+
+$(TR  $(TD $(D c.length)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the
+number of elements in the _container.))
+
+$(TR $(TDNW $(D c.length = n)) $(TDNW $(D n$(SUBSCRIPT c) + n)) $(TD Forces
+the number of elements in the _container to $(D n). If the _container
+ends up growing, the added elements are initialized in a
+_container-dependent manner (usually with $(D T.init)).))
+
+$(TR $(TD $(D c.capacity)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the
+maximum number of elements that can be stored in the _container
+without triggering a reallocation.))
+
+$(TR $(TD $(D c.reserve(x))) $(TD $(D n$(SUBSCRIPT c))) $(TD Forces $(D
+capacity) to at least $(D x) without reducing it.))
+
+$(LEADINGROWN 3, Access)
+
+$(TR $(TDNW $(D c.front)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the
+first element of the _container, in a _container-defined order.))
+
+$(TR $(TDNW $(D c.moveFront)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Destructively reads and returns the first element of the
+_container. The slot is not removed from the _container; it is left
+initialized with $(D T.init). This routine need not be defined if $(D
+front) returns a $(D ref).))
+
+$(TR $(TDNW $(D c.front = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Assigns
+$(D v) to the first element of the _container.))
+
+$(TR $(TDNW $(D c.back)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the
+last element of the _container, in a _container-defined order.))
+
+$(TR $(TDNW $(D c.moveBack)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Destructively reads and returns the last element of the
+_container. The slot is not removed from the _container; it is left
+initialized with $(D T.init). This routine need not be defined if $(D
+front) returns a $(D ref).))
+
+$(TR $(TDNW $(D c.back = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Assigns
+$(D v) to the last element of the _container.))
+
+$(TR $(TDNW $(D c[x])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Provides
+indexed access into the _container. The index type is
+_container-defined. A _container may define several index types (and
+consequently overloaded indexing).))
+
+$(TR  $(TDNW $(D c.moveAt(x))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Destructively reads and returns the value at position $(D x). The slot
+is not removed from the _container; it is left initialized with $(D
+T.init).))
+
+$(TR  $(TDNW $(D c[x] = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Sets
+element at specified index into the _container.))
+
+$(TR  $(TDNW $(D c[x] $(I op)= v)) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Performs read-modify-write operation at specified index into the
+_container.))
+
+$(LEADINGROWN 3, Operations)
+
+$(TR $(TDNW $(D e in c)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Returns nonzero if e is found in $(D c).))
+
+$(TR  $(TDNW $(D c.lowerBound(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Returns a range of all elements strictly less than $(D v).))
+
+$(TR  $(TDNW $(D c.upperBound(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Returns a range of all elements strictly greater than $(D v).))
+
+$(TR  $(TDNW $(D c.equalRange(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD
+Returns a range of all elements in $(D c) that are equal to $(D v).))
+
+$(LEADINGROWN 3, Modifiers)
+
+$(TR $(TDNW $(D c ~= x)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x)))
+$(TD Appends $(D x) to $(D c). $(D x) may be a single element or an
+input range type.))
+
+$(TR  $(TDNW $(D c.clear())) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Removes all
+elements in $(D c).))
+
+$(TR  $(TDNW $(D c.insert(x))) $(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c)))
+$(TD Inserts $(D x) in $(D c) at a position (or positions) chosen by $(D c).))
+
+$(TR  $(TDNW $(D c.stableInsert(x)))
+$(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c))) $(TD Same as $(D c.insert(x)),
+but is guaranteed to not invalidate any ranges.))
+
+$(TR  $(TDNW $(D c.linearInsert(v))) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Same
+as $(D c.insert(v)) but relaxes complexity to linear.))
+
+$(TR  $(TDNW $(D c.stableLinearInsert(v))) $(TDNW $(D n$(SUBSCRIPT c)))
+$(TD Same as $(D c.stableInsert(v)) but relaxes complexity to linear.))
+
+$(TR  $(TDNW $(D c.removeAny())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Removes some element from $(D c) and returns it.))
+
+$(TR  $(TDNW $(D c.stableRemoveAny())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.removeAny()), but is guaranteed to not invalidate any
+iterators.))
+
+$(TR  $(TDNW $(D c.insertFront(v))) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Inserts $(D v) at the front of $(D c).))
+
+$(TR  $(TDNW $(D c.stableInsertFront(v))) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.insertFront(v)), but guarantees no ranges will be
+invalidated.))
+
+$(TR  $(TDNW $(D c.insertBack(v))) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Inserts $(D v) at the back of $(D c).))
+
+$(TR  $(TDNW $(D c.stableInsertBack(v))) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.insertBack(v)), but guarantees no ranges will be
+invalidated.))
+
+$(TR  $(TDNW $(D c.removeFront())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Removes the element at the front of $(D c).))
+
+$(TR  $(TDNW $(D c.stableRemoveFront())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.removeFront()), but guarantees no ranges will be
+invalidated.))
+
+$(TR  $(TDNW $(D c.removeBack())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Removes the value at the back of $(D c).))
+
+$(TR  $(TDNW $(D c.stableRemoveBack())) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.removeBack()), but guarantees no ranges will be
+invalidated.))
+
+$(TR  $(TDNW $(D c.remove(r))) $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c)))
+$(TD Removes range $(D r) from $(D c).))
+
+$(TR  $(TDNW $(D c.stableRemove(r)))
+$(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c)))
+$(TD Same as $(D c.remove(r)), but guarantees iterators are not
+invalidated.))
+
+$(TR  $(TDNW $(D c.linearRemove(r))) $(TDNW $(D n$(SUBSCRIPT c)))
+$(TD Removes range $(D r) from $(D c).))
+
+$(TR  $(TDNW $(D c.stableLinearRemove(r))) $(TDNW $(D n$(SUBSCRIPT c)))
+$(TD Same as $(D c.linearRemove(r)), but guarantees iterators are not
+invalidated.))
+
+$(TR  $(TDNW $(D c.removeKey(k))) $(TDNW $(D log n$(SUBSCRIPT c)))
+$(TD Removes an element from $(D c) by using its key $(D k).
+The key's type is defined by the _container.))
+
+$(TR  $(TDNW $(D )) $(TDNW $(D )) $(TD ))
+
+)
+
+Source: $(PHOBOSSRC std/_container/package.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+ */
+
+module std.container;
+
+public import std.container.array;
+public import std.container.binaryheap;
+public import std.container.dlist;
+public import std.container.rbtree;
+public import std.container.slist;
+
+import std.meta;
+
+
+/* The following documentation and type $(D TotalContainer) are
+intended for developers only.
+
+$(D TotalContainer) is an unimplemented container that illustrates a
+host of primitives that a container may define. It is to some extent
+the bottom of the conceptual container hierarchy. A given container
+most often will choose to only implement a subset of these primitives,
+and define its own additional ones. Adhering to the standard primitive
+names below allows generic code to work independently of containers.
+
+Things to remember: any container must be a reference type, whether
+implemented as a $(D class) or $(D struct). No primitive below
+requires the container to escape addresses of elements, which means
+that compliant containers can be defined to use reference counting or
+other deterministic memory management techniques.
+
+A container may choose to define additional specific operations. The
+only requirement is that those operations bear different names than
+the ones below, lest user code gets confused.
+
+Complexity of operations should be interpreted as "at least as good
+as". If an operation is required to have $(BIGOH n) complexity, it
+could have anything lower than that, e.g. $(BIGOH log(n)). Unless
+specified otherwise, $(D n) inside a $(BIGOH) expression stands for
+the number of elements in the container.
+ */
+struct TotalContainer(T)
+{
+/**
+If the container has a notion of key-value mapping, $(D KeyType)
+defines the type of the key of the container.
+ */
+    alias KeyType = T;
+
+/**
+If the container has a notion of multikey-value mapping, $(D
+KeyTypes[k]), where $(D k) is a zero-based unsigned number, defines
+the type of the $(D k)th key of the container.
+
+A container may define both $(D KeyType) and $(D KeyTypes), e.g. in
+the case it has the notion of primary/preferred key.
+ */
+    alias KeyTypes = AliasSeq!T;
+
+/**
+If the container has a notion of key-value mapping, $(D ValueType)
+defines the type of the value of the container. Typically, a map-style
+container mapping values of type $(D K) to values of type $(D V)
+defines $(D KeyType) to be $(D K) and $(D ValueType) to be $(D V).
+ */
+    alias ValueType = T;
+
+/**
+Defines the container's primary range, which embodies one of the
+ranges defined in $(XREFMODULE range).
+
+Generally a container may define several types of ranges.
+ */
+    struct Range
+    {
+        /++
+        Range primitives.
+        +/
+        @property bool empty()
+        {
+            assert(0);
+        }
+        /// Ditto
+        @property ref T front() //ref return optional
+        {
+            assert(0);
+        }
+        /// Ditto
+        @property void front(T value) //Only when front does not return by ref
+        {
+            assert(0);
+        }
+        /// Ditto
+        T moveFront()
+        {
+            assert(0);
+        }
+        /// Ditto
+        void popFront()
+        {
+            assert(0);
+        }
+        /// Ditto
+        @property ref T back() //ref return optional
+        {
+            assert(0);
+        }
+        /// Ditto
+        @property void back(T value) //Only when front does not return by ref
+        {
+            assert(0);
+        }
+        /// Ditto
+        T moveBack()
+        {
+            assert(0);
+        }
+        /// Ditto
+        void popBack()
+        {
+            assert(0);
+        }
+        /// Ditto
+        T opIndex(size_t i) //ref return optional
+        {
+            assert(0);
+        }
+        /// Ditto
+        void opIndexAssign(size_t i, T value) //Only when front does not return by ref
+        {
+            assert(0);
+        }
+        /// Ditto
+        T opIndexUnary(string op)(size_t i) //Only when front does not return by ref
+        {
+            assert(0);
+        }
+        /// Ditto
+        void opIndexOpAssign(string op)(size_t i, T value) //Only when front does not return by ref
+        {
+            assert(0);
+        }
+        /// Ditto
+        T moveAt(size_t i)
+        {
+            assert(0);
+        }
+        /// Ditto
+        @property size_t length()
+        {
+            assert(0);
+        }
+    }
+
+/**
+Property returning $(D true) if and only if the container has no
+elements.
+
+Complexity: $(BIGOH 1)
+ */
+    @property bool empty()
+    {
+        assert(0);
+    }
+
+/**
+Returns a duplicate of the container. The elements themselves are not
+transitively duplicated.
+
+Complexity: $(BIGOH n).
+ */
+    @property TotalContainer dup()
+    {
+        assert(0);
+    }
+
+/**
+Returns the number of elements in the container.
+
+Complexity: $(BIGOH log(n)).
+*/
+    @property size_t length()
+    {
+        assert(0);
+    }
+
+/**
+Returns the maximum number of elements the container can store without
+(a) allocating memory, (b) invalidating iterators upon insertion.
+
+Complexity: $(BIGOH log(n)).
+ */
+    @property size_t capacity()
+    {
+        assert(0);
+    }
+
+/**
+Ensures sufficient capacity to accommodate $(D n) elements.
+
+Postcondition: $(D capacity >= n)
+
+Complexity: $(BIGOH log(e - capacity)) if $(D e > capacity), otherwise
+$(BIGOH 1).
+ */
+    void reserve(size_t e)
+    {
+        assert(0);
+    }
+
+/**
+Returns a range that iterates over all elements of the container, in a
+container-defined order. The container should choose the most
+convenient and fast method of iteration for $(D opSlice()).
+
+Complexity: $(BIGOH log(n))
+ */
+    Range opSlice()
+    {
+        assert(0);
+    }
+
+    /**
+       Returns a range that iterates the container between two
+       specified positions.
+
+       Complexity: $(BIGOH log(n))
+     */
+    Range opSlice(size_t a, size_t b)
+    {
+        assert(0);
+    }
+
+/**
+Forward to $(D opSlice().front) and $(D opSlice().back), respectively.
+
+Complexity: $(BIGOH log(n))
+ */
+    @property ref T front() //ref return optional
+    {
+        assert(0);
+    }
+    /// Ditto
+    @property void front(T value) //Only when front does not return by ref
+    {
+        assert(0);
+    }
+    /// Ditto
+    T moveFront()
+    {
+        assert(0);
+    }
+    /// Ditto
+    @property ref T back() //ref return optional
+    {
+        assert(0);
+    }
+    /// Ditto
+    @property void back(T value) //Only when front does not return by ref
+    {
+        assert(0);
+    }
+    /// Ditto
+    T moveBack()
+    {
+        assert(0);
+    }
+
+/**
+Indexing operators yield or modify the value at a specified index.
+ */
+    ref T opIndex(KeyType) //ref return optional
+    {
+        assert(0);
+    }
+    /// ditto
+    void opIndexAssign(KeyType i, T value) //Only when front does not return by ref
+    {
+        assert(0);
+    }
+    /// ditto
+    T opIndexUnary(string op)(KeyType i) //Only when front does not return by ref
+    {
+        assert(0);
+    }
+    /// ditto
+    void opIndexOpAssign(string op)(KeyType i, T value) //Only when front does not return by ref
+    {
+        assert(0);
+    }
+    /// ditto
+    T moveAt(KeyType i)
+    {
+        assert(0);
+    }
+
+/**
+$(D k in container) returns true if the given key is in the container.
+ */
+    bool opBinaryRight(string op)(KeyType k) if (op == "in")
+    {
+        assert(0);
+    }
+
+/**
+Returns a range of all elements containing $(D k) (could be empty or a
+singleton range).
+ */
+    Range equalRange(KeyType k)
+    {
+        assert(0);
+    }
+
+/**
+Returns a range of all elements with keys less than $(D k) (could be
+empty or a singleton range). Only defined by containers that store
+data sorted at all times.
+ */
+    Range lowerBound(KeyType k)
+    {
+        assert(0);
+    }
+
+/**
+Returns a range of all elements with keys larger than $(D k) (could be
+empty or a singleton range).  Only defined by containers that store
+data sorted at all times.
+ */
+    Range upperBound(KeyType k)
+    {
+        assert(0);
+    }
+
+/**
+Returns a new container that's the concatenation of $(D this) and its
+argument. $(D opBinaryRight) is only defined if $(D Stuff) does not
+define $(D opBinary).
+
+Complexity: $(BIGOH n + m), where m is the number of elements in $(D
+stuff)
+ */
+    TotalContainer opBinary(string op)(Stuff rhs) if (op == "~")
+    {
+        assert(0);
+    }
+
+    /// ditto
+    TotalContainer opBinaryRight(string op)(Stuff lhs) if (op == "~")
+    {
+        assert(0);
+    }
+
+/**
+Forwards to $(D insertAfter(this[], stuff)).
+ */
+    void opOpAssign(string op)(Stuff stuff) if (op == "~")
+    {
+        assert(0);
+    }
+
+/**
+Removes all contents from the container. The container decides how $(D
+capacity) is affected.
+
+Postcondition: $(D empty)
+
+Complexity: $(BIGOH n)
+ */
+    void clear()
+    {
+        assert(0);
+    }
+
+/**
+Sets the number of elements in the container to $(D newSize). If $(D
+newSize) is greater than $(D length), the added elements are added to
+unspecified positions in the container and initialized with $(D
+.init).
+
+Complexity: $(BIGOH abs(n - newLength))
+
+Postcondition: $(D _length == newLength)
+ */
+    @property void length(size_t newLength)
+    {
+        assert(0);
+    }
+
+/**
+Inserts $(D stuff) in an unspecified position in the
+container. Implementations should choose whichever insertion means is
+the most advantageous for the container, but document the exact
+behavior. $(D stuff) can be a value convertible to the element type of
+the container, or a range of values convertible to it.
+
+The $(D stable) version guarantees that ranges iterating over the
+container are never invalidated. Client code that counts on
+non-invalidating insertion should use $(D stableInsert). Such code would
+not compile against containers that don't support it.
+
+Returns: The number of elements added.
+
+Complexity: $(BIGOH m * log(n)), where $(D m) is the number of
+elements in $(D stuff)
+ */
+    size_t insert(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+    ///ditto
+    size_t stableInsert(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+
+/**
+Same as $(D insert(stuff)) and $(D stableInsert(stuff)) respectively,
+but relax the complexity constraint to linear.
+ */
+    size_t linearInsert(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+    ///ditto
+    size_t stableLinearInsert(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+
+/**
+Picks one value in an unspecified position in the container, removes
+it from the container, and returns it. Implementations should pick the
+value that's the most advantageous for the container. The stable version
+behaves the same, but guarantees that ranges iterating over the container
+are never invalidated.
+
+Precondition: $(D !empty)
+
+Returns: The element removed.
+
+Complexity: $(BIGOH log(n)).
+ */
+    T removeAny()
+    {
+        assert(0);
+    }
+    /// ditto
+    T stableRemoveAny()
+    {
+        assert(0);
+    }
+
+/**
+Inserts $(D value) to the front or back of the container. $(D stuff)
+can be a value convertible to the container's element type or a range
+of values convertible to it. The stable version behaves the same, but
+guarantees that ranges iterating over the container are never
+invalidated.
+
+Returns: The number of elements inserted
+
+Complexity: $(BIGOH log(n)).
+ */
+    size_t insertFront(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableInsertFront(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t insertBack(Stuff)(Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableInsertBack(T value)
+    {
+        assert(0);
+    }
+
+/**
+Removes the value at the front or back of the container. The stable
+version behaves the same, but guarantees that ranges iterating over
+the container are never invalidated. The optional parameter $(D
+howMany) instructs removal of that many elements. If $(D howMany > n),
+all elements are removed and no exception is thrown.
+
+Precondition: $(D !empty)
+
+Complexity: $(BIGOH log(n)).
+ */
+    void removeFront()
+    {
+        assert(0);
+    }
+    /// ditto
+    void stableRemoveFront()
+    {
+        assert(0);
+    }
+    /// ditto
+    void removeBack()
+    {
+        assert(0);
+    }
+    /// ditto
+    void stableRemoveBack()
+    {
+        assert(0);
+    }
+
+/**
+Removes $(D howMany) values at the front or back of the
+container. Unlike the unparameterized versions above, these functions
+do not throw if they could not remove $(D howMany) elements. Instead,
+if $(D howMany > n), all elements are removed. The returned value is
+the effective number of elements removed. The stable version behaves
+the same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: The number of elements removed
+
+Complexity: $(BIGOH howMany * log(n)).
+ */
+    size_t removeFront(size_t howMany)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableRemoveFront(size_t howMany)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t removeBack(size_t howMany)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableRemoveBack(size_t howMany)
+    {
+        assert(0);
+    }
+
+/**
+Removes all values corresponding to key $(D k).
+
+Complexity: $(BIGOH m * log(n)), where $(D m) is the number of
+elements with the same key.
+
+Returns: The number of elements removed.
+ */
+    size_t removeKey(KeyType k)
+    {
+        assert(0);
+    }
+
+/**
+Inserts $(D stuff) before, after, or instead range $(D r), which must
+be a valid range previously extracted from this container. $(D stuff)
+can be a value convertible to the container's element type or a range
+of objects convertible to it. The stable version behaves the same, but
+guarantees that ranges iterating over the container are never
+invalidated.
+
+Returns: The number of values inserted.
+
+Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff)
+ */
+    size_t insertBefore(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableInsertBefore(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t insertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableInsertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t replace(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+    /// ditto
+    size_t stableReplace(Stuff)(Range r, Stuff stuff)
+    {
+        assert(0);
+    }
+
+/**
+Removes all elements belonging to $(D r), which must be a range
+obtained originally from this container. The stable version behaves the
+same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: A range spanning the remaining elements in the container that
+initially were right after $(D r).
+
+Complexity: $(BIGOH m * log(n)), where $(D m) is the number of
+elements in $(D r)
+ */
+    Range remove(Range r)
+    {
+        assert(0);
+    }
+    /// ditto
+    Range stableRemove(Range r)
+    {
+        assert(0);
+    }
+
+/**
+Same as $(D remove) above, but has complexity relaxed to linear.
+
+Returns: A range spanning the remaining elements in the container that
+initially were right after $(D r).
+
+Complexity: $(BIGOH n)
+ */
+    Range linearRemove(Range r)
+    {
+        assert(0);
+    }
+    /// ditto
+    Range stableLinearRemove(Range r)
+    {
+        assert(0);
+    }
+}
+
+unittest {
+    TotalContainer!int test;
+}
diff --git a/libphobos/src/std/container/rbtree.d b/libphobos/src/std/container/rbtree.d
new file mode 100644
index 00000000000..bbd6bb3558d
--- /dev/null
+++ b/libphobos/src/std/container/rbtree.d
@@ -0,0 +1,2058 @@
+/**
+This module implements a red-black tree container.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_rbtree.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.rbtree;
+
+///
+unittest
+{
+    import std.container.rbtree;
+    import std.algorithm: equal;
+
+    auto rbt = redBlackTree(3, 1, 4, 2, 5);
+    assert(rbt.front == 1);
+    assert(equal(rbt[], [1, 2, 3, 4, 5]));
+
+    rbt.removeKey(1, 4);
+    assert(equal(rbt[], [2, 3, 5]));
+
+    rbt.removeFront();
+    assert(equal(rbt[], [3, 5]));
+
+    rbt.insert([1, 2, 4]);
+    assert(equal(rbt[], [1, 2, 3, 4, 5]));
+
+    // Query bounds in O(log(n))
+    assert(rbt.lowerBound(3).equal([1, 2]));
+    assert(rbt.equalRange(3).equal([3]));
+    assert(rbt.upperBound(3).equal([4, 5]));
+
+    // A Red Black tree with the highest element at front:
+    import std.range: iota;
+    auto maxTree = redBlackTree!"a > b"(iota(5));
+    assert(equal(maxTree[], [4, 3, 2, 1, 0]));
+
+    // adding duplicates will not add them, but return 0
+    auto rbt2 = redBlackTree(1, 3);
+    assert(rbt2.insert(1) == 0);
+    assert(equal(rbt2[], [1, 3]));
+    assert(rbt2.insert(2) == 1);
+
+    // however you can allow duplicates
+    auto ubt = redBlackTree!true([0, 1, 0, 1]);
+    assert(equal(ubt[], [0, 0, 1, 1]));
+}
+
+import std.functional : binaryFun;
+import std.format;
+
+public import std.container.util;
+
+version(unittest) debug = RBDoChecks;
+
+//debug = RBDoChecks;
+
+/*
+ * Implementation for a Red Black node for use in a Red Black Tree (see below)
+ *
+ * this implementation assumes we have a marker Node that is the parent of the
+ * root Node.  This marker Node is not a valid Node, but marks the end of the
+ * collection.  The root is the left child of the marker Node, so it is always
+ * last in the collection.  The marker Node is passed in to the setColor
+ * function, and the Node which has this Node as its parent is assumed to be
+ * the root Node.
+ *
+ * A Red Black tree should have O(lg(n)) insertion, removal, and search time.
+ */
+struct RBNode(V)
+{
+    /*
+     * Convenience alias
+     */
+    alias Node = RBNode*;
+
+    private Node _left;
+    private Node _right;
+    private Node _parent;
+
+    /**
+     * The value held by this node
+     */
+    V value;
+
+    /**
+     * Enumeration determining what color the node is.  Null nodes are assumed
+     * to be black.
+     */
+    enum Color : byte
+    {
+        Red,
+        Black
+    }
+
+    /**
+     * The color of the node.
+     */
+    Color color;
+
+    /**
+     * Get the left child
+     */
+    @property inout(RBNode)* left() inout
+    {
+        return _left;
+    }
+
+    /**
+     * Get the right child
+     */
+    @property inout(RBNode)* right() inout
+    {
+        return _right;
+    }
+
+    /**
+     * Get the parent
+     */
+    @property inout(RBNode)* parent() inout
+    {
+        return _parent;
+    }
+
+    /**
+     * Set the left child.  Also updates the new child's parent node.  This
+     * does not update the previous child.
+     *
+     * Returns newNode
+     */
+    @property Node left(Node newNode)
+    {
+        _left = newNode;
+        if(newNode !is null)
+            newNode._parent = &this;
+        return newNode;
+    }
+
+    /**
+     * Set the right child.  Also updates the new child's parent node.  This
+     * does not update the previous child.
+     *
+     * Returns newNode
+     */
+    @property Node right(Node newNode)
+    {
+        _right = newNode;
+        if(newNode !is null)
+            newNode._parent = &this;
+        return newNode;
+    }
+
+    // assume _left is not null
+    //
+    // performs rotate-right operation, where this is T, _right is R, _left is
+    // L, _parent is P:
+    //
+    //      P         P
+    //      |   ->    |
+    //      T         L
+    //     / \       / \
+    //    L   R     a   T
+    //   / \           / \
+    //  a   b         b   R
+    //
+    /**
+     * Rotate right.  This performs the following operations:
+     *  - The left child becomes the parent of this node.
+     *  - This node becomes the new parent's right child.
+     *  - The old right child of the new parent becomes the left child of this
+     *    node.
+     */
+    Node rotateR()
+    in
+    {
+        assert(_left !is null);
+    }
+    body
+    {
+        // sets _left._parent also
+        if(isLeftNode)
+            parent.left = _left;
+        else
+            parent.right = _left;
+        Node tmp = _left._right;
+
+        // sets _parent also
+        _left.right = &this;
+
+        // sets tmp._parent also
+        left = tmp;
+
+        return &this;
+    }
+
+    // assumes _right is non null
+    //
+    // performs rotate-left operation, where this is T, _right is R, _left is
+    // L, _parent is P:
+    //
+    //      P           P
+    //      |    ->     |
+    //      T           R
+    //     / \         / \
+    //    L   R       T   b
+    //       / \     / \
+    //      a   b   L   a
+    //
+    /**
+     * Rotate left.  This performs the following operations:
+     *  - The right child becomes the parent of this node.
+     *  - This node becomes the new parent's left child.
+     *  - The old left child of the new parent becomes the right child of this
+     *    node.
+     */
+    Node rotateL()
+    in
+    {
+        assert(_right !is null);
+    }
+    body
+    {
+        // sets _right._parent also
+        if(isLeftNode)
+            parent.left = _right;
+        else
+            parent.right = _right;
+        Node tmp = _right._left;
+
+        // sets _parent also
+        _right.left = &this;
+
+        // sets tmp._parent also
+        right = tmp;
+        return &this;
+    }
+
+
+    /**
+     * Returns true if this node is a left child.
+     *
+     * Note that this should always return a value because the root has a
+     * parent which is the marker node.
+     */
+    @property bool isLeftNode() const
+    in
+    {
+        assert(_parent !is null);
+    }
+    body
+    {
+        return _parent._left is &this;
+    }
+
+    /**
+     * Set the color of the node after it is inserted.  This performs an
+     * update to the whole tree, possibly rotating nodes to keep the Red-Black
+     * properties correct.  This is an O(lg(n)) operation, where n is the
+     * number of nodes in the tree.
+     *
+     * end is the marker node, which is the parent of the topmost valid node.
+     */
+    void setColor(Node end)
+    {
+        // test against the marker node
+        if(_parent !is end)
+        {
+            if(_parent.color == Color.Red)
+            {
+                Node cur = &this;
+                while(true)
+                {
+                    // because root is always black, _parent._parent always exists
+                    if(cur._parent.isLeftNode)
+                    {
+                        // parent is left node, y is 'uncle', could be null
+                        Node y = cur._parent._parent._right;
+                        if(y !is null && y.color == Color.Red)
+                        {
+                            cur._parent.color = Color.Black;
+                            y.color = Color.Black;
+                            cur = cur._parent._parent;
+                            if(cur._parent is end)
+                            {
+                                // root node
+                                cur.color = Color.Black;
+                                break;
+                            }
+                            else
+                            {
+                                // not root node
+                                cur.color = Color.Red;
+                                if(cur._parent.color == Color.Black)
+                                    // satisfied, exit the loop
+                                    break;
+                            }
+                        }
+                        else
+                        {
+                            if(!cur.isLeftNode)
+                                cur = cur._parent.rotateL();
+                            cur._parent.color = Color.Black;
+                            cur = cur._parent._parent.rotateR();
+                            cur.color = Color.Red;
+                            // tree should be satisfied now
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        // parent is right node, y is 'uncle'
+                        Node y = cur._parent._parent._left;
+                        if(y !is null && y.color == Color.Red)
+                        {
+                            cur._parent.color = Color.Black;
+                            y.color = Color.Black;
+                            cur = cur._parent._parent;
+                            if(cur._parent is end)
+                            {
+                                // root node
+                                cur.color = Color.Black;
+                                break;
+                            }
+                            else
+                            {
+                                // not root node
+                                cur.color = Color.Red;
+                                if(cur._parent.color == Color.Black)
+                                    // satisfied, exit the loop
+                                    break;
+                            }
+                        }
+                        else
+                        {
+                            if(cur.isLeftNode)
+                                cur = cur._parent.rotateR();
+                            cur._parent.color = Color.Black;
+                            cur = cur._parent._parent.rotateL();
+                            cur.color = Color.Red;
+                            // tree should be satisfied now
+                            break;
+                        }
+                    }
+                }
+
+            }
+        }
+        else
+        {
+            //
+            // this is the root node, color it black
+            //
+            color = Color.Black;
+        }
+    }
+
+    /**
+     * Remove this node from the tree.  The 'end' node is used as the marker
+     * which is root's parent.  Note that this cannot be null!
+     *
+     * Returns the next highest valued node in the tree after this one, or end
+     * if this was the highest-valued node.
+     */
+    Node remove(Node end)
+    {
+        //
+        // remove this node from the tree, fixing the color if necessary.
+        //
+        Node x;
+        Node ret = next;
+
+        // if this node has 2 children
+        if (_left !is null && _right !is null)
+        {
+            //
+            // normally, we can just swap this node's and y's value, but
+            // because an iterator could be pointing to y and we don't want to
+            // disturb it, we swap this node and y's structure instead.  This
+            // can also be a benefit if the value of the tree is a large
+            // struct, which takes a long time to copy.
+            //
+            Node yp, yl, yr;
+            Node y = ret; // y = next
+            yp = y._parent;
+            yl = y._left;
+            yr = y._right;
+            auto yc = y.color;
+            auto isyleft = y.isLeftNode;
+
+            //
+            // replace y's structure with structure of this node.
+            //
+            if(isLeftNode)
+                _parent.left = y;
+            else
+                _parent.right = y;
+            //
+            // need special case so y doesn't point back to itself
+            //
+            y.left = _left;
+            if(_right is y)
+                y.right = &this;
+            else
+                y.right = _right;
+            y.color = color;
+
+            //
+            // replace this node's structure with structure of y.
+            //
+            left = yl;
+            right = yr;
+            if(_parent !is y)
+            {
+                if(isyleft)
+                    yp.left = &this;
+                else
+                    yp.right = &this;
+            }
+            color = yc;
+        }
+
+        // if this has less than 2 children, remove it
+        if(_left !is null)
+            x = _left;
+        else
+            x = _right;
+
+        bool deferedUnlink = false;
+        if(x is null)
+        {
+            // pretend this is a null node, defer unlinking the node
+            x = &this;
+            deferedUnlink = true;
+        }
+        else if(isLeftNode)
+            _parent.left = x;
+        else
+            _parent.right = x;
+
+        // if the color of this is black, then it needs to be fixed
+        if(color == color.Black)
+        {
+            // need to recolor the tree.
+            while(x._parent !is end && x.color == Node.Color.Black)
+            {
+                if(x.isLeftNode)
+                {
+                    // left node
+                    Node w = x._parent._right;
+                    if(w.color == Node.Color.Red)
+                    {
+                        w.color = Node.Color.Black;
+                        x._parent.color = Node.Color.Red;
+                        x._parent.rotateL();
+                        w = x._parent._right;
+                    }
+                    Node wl = w.left;
+                    Node wr = w.right;
+                    if((wl is null || wl.color == Node.Color.Black) &&
+                            (wr is null || wr.color == Node.Color.Black))
+                    {
+                        w.color = Node.Color.Red;
+                        x = x._parent;
+                    }
+                    else
+                    {
+                        if(wr is null || wr.color == Node.Color.Black)
+                        {
+                            // wl cannot be null here
+                            wl.color = Node.Color.Black;
+                            w.color = Node.Color.Red;
+                            w.rotateR();
+                            w = x._parent._right;
+                        }
+
+                        w.color = x._parent.color;
+                        x._parent.color = Node.Color.Black;
+                        w._right.color = Node.Color.Black;
+                        x._parent.rotateL();
+                        x = end.left; // x = root
+                    }
+                }
+                else
+                {
+                    // right node
+                    Node w = x._parent._left;
+                    if(w.color == Node.Color.Red)
+                    {
+                        w.color = Node.Color.Black;
+                        x._parent.color = Node.Color.Red;
+                        x._parent.rotateR();
+                        w = x._parent._left;
+                    }
+                    Node wl = w.left;
+                    Node wr = w.right;
+                    if((wl is null || wl.color == Node.Color.Black) &&
+                            (wr is null || wr.color == Node.Color.Black))
+                    {
+                        w.color = Node.Color.Red;
+                        x = x._parent;
+                    }
+                    else
+                    {
+                        if(wl is null || wl.color == Node.Color.Black)
+                        {
+                            // wr cannot be null here
+                            wr.color = Node.Color.Black;
+                            w.color = Node.Color.Red;
+                            w.rotateL();
+                            w = x._parent._left;
+                        }
+
+                        w.color = x._parent.color;
+                        x._parent.color = Node.Color.Black;
+                        w._left.color = Node.Color.Black;
+                        x._parent.rotateR();
+                        x = end.left; // x = root
+                    }
+                }
+            }
+            x.color = Node.Color.Black;
+        }
+
+        if(deferedUnlink)
+        {
+            //
+            // unlink this node from the tree
+            //
+            if(isLeftNode)
+                _parent.left = null;
+            else
+                _parent.right = null;
+        }
+
+        // clean references to help GC - Bugzilla 12915
+        _left = _right = _parent = null;
+
+        return ret;
+    }
+
+    /**
+     * Return the leftmost descendant of this node.
+     */
+    @property inout(RBNode)* leftmost() inout
+    {
+        inout(RBNode)* result = &this;
+        while(result._left !is null)
+            result = result._left;
+        return result;
+    }
+
+    /**
+     * Return the rightmost descendant of this node
+     */
+    @property inout(RBNode)* rightmost() inout
+    {
+        inout(RBNode)* result = &this;
+        while(result._right !is null)
+            result = result._right;
+        return result;
+    }
+
+    /**
+     * Returns the next valued node in the tree.
+     *
+     * You should never call this on the marker node, as it is assumed that
+     * there is a valid next node.
+     */
+    @property inout(RBNode)* next() inout
+    {
+        inout(RBNode)* n = &this;
+        if(n.right is null)
+        {
+            while(!n.isLeftNode)
+                n = n._parent;
+            return n._parent;
+        }
+        else
+            return n.right.leftmost;
+    }
+
+    /**
+     * Returns the previous valued node in the tree.
+     *
+     * You should never call this on the leftmost node of the tree as it is
+     * assumed that there is a valid previous node.
+     */
+    @property inout(RBNode)* prev() inout
+    {
+        inout(RBNode)* n = &this;
+        if(n.left is null)
+        {
+            while(n.isLeftNode)
+                n = n._parent;
+            return n._parent;
+        }
+        else
+            return n.left.rightmost;
+    }
+
+    Node dup(scope Node delegate(V v) alloc)
+    {
+        //
+        // duplicate this and all child nodes
+        //
+        // The recursion should be lg(n), so we shouldn't have to worry about
+        // stack size.
+        //
+        Node copy = alloc(value);
+        copy.color = color;
+        if(_left !is null)
+            copy.left = _left.dup(alloc);
+        if(_right !is null)
+            copy.right = _right.dup(alloc);
+        return copy;
+    }
+
+    Node dup()
+    {
+        Node copy = new RBNode!V;
+        copy.value = value;
+        copy.color = color;
+        if(_left !is null)
+            copy.left = _left.dup();
+        if(_right !is null)
+            copy.right = _right.dup();
+        return copy;
+    }
+}
+
+//constness checks
+unittest
+{
+    const RBNode!int n;
+    static assert(is(typeof(n.leftmost)));
+    static assert(is(typeof(n.rightmost)));
+    static assert(is(typeof(n.next)));
+    static assert(is(typeof(n.prev)));
+}
+
+private struct RBRange(N)
+{
+    alias Node = N;
+    alias Elem = typeof(Node.value);
+
+    private Node _begin;
+    private Node _end;
+
+    private this(Node b, Node e)
+    {
+        _begin = b;
+        _end = e;
+    }
+
+    /**
+     * Returns $(D true) if the range is _empty
+     */
+    @property bool empty() const
+    {
+        return _begin is _end;
+    }
+
+    /**
+     * Returns the first element in the range
+     */
+    @property Elem front()
+    {
+        return _begin.value;
+    }
+
+    /**
+     * Returns the last element in the range
+     */
+    @property Elem back()
+    {
+        return _end.prev.value;
+    }
+
+    /**
+     * pop the front element from the range
+     *
+     * complexity: amortized $(BIGOH 1)
+     */
+    void popFront()
+    {
+        _begin = _begin.next;
+    }
+
+    /**
+     * pop the back element from the range
+     *
+     * complexity: amortized $(BIGOH 1)
+     */
+    void popBack()
+    {
+        _end = _end.prev;
+    }
+
+    /**
+     * Trivial _save implementation, needed for $(D isForwardRange).
+     */
+    @property RBRange save()
+    {
+        return this;
+    }
+}
+
+/**
+ * Implementation of a $(LUCKY red-black tree) container.
+ *
+ * All inserts, removes, searches, and any function in general has complexity
+ * of $(BIGOH lg(n)).
+ *
+ * To use a different comparison than $(D "a < b"), pass a different operator string
+ * that can be used by $(XREF functional, binaryFun), or pass in a
+ * function, delegate, functor, or any type where $(D less(a, b)) results in a $(D bool)
+ * value.
+ *
+ * Note that less should produce a strict ordering.  That is, for two unequal
+ * elements $(D a) and $(D b), $(D less(a, b) == !less(b, a)). $(D less(a, a)) should
+ * always equal $(D false).
+ *
+ * If $(D allowDuplicates) is set to $(D true), then inserting the same element more than
+ * once continues to add more elements.  If it is $(D false), duplicate elements are
+ * ignored on insertion.  If duplicates are allowed, then new elements are
+ * inserted after all existing duplicate elements.
+ */
+final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false)
+    if(is(typeof(binaryFun!less(T.init, T.init))))
+{
+    import std.meta : allSatisfy;
+    import std.range.primitives;
+    import std.range : Take;
+    import std.traits;
+
+    alias _less = binaryFun!less;
+
+    version(unittest)
+    {
+        static if(is(typeof(less) == string))
+        {
+            private enum doUnittest = isIntegral!T && (less == "a < b" || less == "a > b");
+        }
+        else
+            enum doUnittest = false;
+
+        // note, this must be final so it does not affect the vtable layout
+        final bool arrayEqual(T[] arr)
+        {
+            if(walkLength(this[]) == arr.length)
+            {
+                foreach(v; arr)
+                {
+                    if(!(v in this))
+                        return false;
+                }
+                return true;
+            }
+            return false;
+        }
+    }
+    else
+    {
+        private enum doUnittest = false;
+    }
+
+    /**
+      * Element type for the tree
+      */
+    alias Elem = T;
+
+    // used for convenience
+    private alias RBNode = .RBNode!Elem;
+    private alias Node = RBNode.Node;
+
+    private Node   _end;
+    private Node   _begin;
+    private size_t _length;
+
+    private void _setup()
+    {
+        assert(!_end); //Make sure that _setup isn't run more than once.
+        _begin = _end = allocate();
+    }
+
+    static private Node allocate()
+    {
+        return new RBNode;
+    }
+
+    static private Node allocate(Elem v)
+    {
+        auto result = allocate();
+        result.value = v;
+        return result;
+    }
+
+    /**
+     * The range types for $(D RedBlackTree)
+     */
+    alias Range = RBRange!(RBNode*);
+    alias ConstRange = RBRange!(const(RBNode)*); /// Ditto
+    alias ImmutableRange = RBRange!(immutable(RBNode)*); /// Ditto
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        import std.range.primitives;
+        auto ts = new RedBlackTree(1, 2, 3, 4, 5);
+        assert(ts.length == 5);
+        auto r = ts[];
+
+        static if(less == "a < b")
+            auto vals = [1, 2, 3, 4, 5];
+        else
+            auto vals = [5, 4, 3, 2, 1];
+        assert(equal(r, vals));
+
+        assert(r.front == vals.front);
+        assert(r.back != r.front);
+        auto oldfront = r.front;
+        auto oldback = r.back;
+        r.popFront();
+        r.popBack();
+        assert(r.front != r.back);
+        assert(r.front != oldfront);
+        assert(r.back != oldback);
+        assert(ts.length == 5);
+    }
+
+    // find a node based on an element value
+    private inout(RBNode)* _find(Elem e) inout
+    {
+        static if(allowDuplicates)
+        {
+            inout(RBNode)* cur = _end.left;
+            inout(RBNode)* result = null;
+            while(cur)
+            {
+                if(_less(cur.value, e))
+                    cur = cur.right;
+                else if(_less(e, cur.value))
+                    cur = cur.left;
+                else
+                {
+                    // want to find the left-most element
+                    result = cur;
+                    cur = cur.left;
+                }
+            }
+            return result;
+        }
+        else
+        {
+            inout(RBNode)* cur = _end.left;
+            while(cur)
+            {
+                if(_less(cur.value, e))
+                    cur = cur.right;
+                else if(_less(e, cur.value))
+                    cur = cur.left;
+                else
+                    return cur;
+            }
+            return null;
+        }
+    }
+
+    // add an element to the tree, returns the node added, or the existing node
+    // if it has already been added and allowDuplicates is false
+    private auto _add(Elem n)
+    {
+        Node result;
+        static if(!allowDuplicates)
+            bool added = true;
+
+        if(!_end.left)
+        {
+            _end.left = _begin = result = allocate(n);
+        }
+        else
+        {
+            Node newParent = _end.left;
+            Node nxt = void;
+            while(true)
+            {
+                if(_less(n, newParent.value))
+                {
+                    nxt = newParent.left;
+                    if(nxt is null)
+                    {
+                        //
+                        // add to right of new parent
+                        //
+                        newParent.left = result = allocate(n);
+                        break;
+                    }
+                }
+                else
+                {
+                    static if(!allowDuplicates)
+                    {
+                        if(!_less(newParent.value, n))
+                        {
+                            result = newParent;
+                            added = false;
+                            break;
+                        }
+                    }
+                    nxt = newParent.right;
+                    if(nxt is null)
+                    {
+                        //
+                        // add to right of new parent
+                        //
+                        newParent.right = result = allocate(n);
+                        break;
+                    }
+                }
+                newParent = nxt;
+            }
+            if(_begin.left)
+                _begin = _begin.left;
+        }
+
+        static if(allowDuplicates)
+        {
+            result.setColor(_end);
+            debug(RBDoChecks)
+                check();
+            ++_length;
+            return result;
+        }
+        else
+        {
+            import std.typecons : Tuple;
+
+            if(added)
+            {
+                ++_length;
+                result.setColor(_end);
+            }
+            debug(RBDoChecks)
+                check();
+            return Tuple!(bool, "added", Node, "n")(added, result);
+        }
+    }
+
+
+    /**
+     * Check if any elements exist in the container.  Returns $(D false) if at least
+     * one element exists.
+     */
+    @property bool empty()
+    {
+        return _end.left is null;
+    }
+
+    /++
+        Returns the number of elements in the container.
+
+        Complexity: $(BIGOH 1).
+    +/
+    @property size_t length() const
+    {
+        return _length;
+    }
+
+    /**
+     * Duplicate this container.  The resulting container contains a shallow
+     * copy of the elements.
+     *
+     * Complexity: $(BIGOH n)
+     */
+    @property RedBlackTree dup()
+    {
+        return new RedBlackTree(_end.dup(), _length);
+    }
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        auto ts = new RedBlackTree(1, 2, 3, 4, 5);
+        assert(ts.length == 5);
+        auto ts2 = ts.dup;
+        assert(ts2.length == 5);
+        assert(equal(ts[], ts2[]));
+        ts2.insert(cast(Elem)6);
+        assert(!equal(ts[], ts2[]));
+        assert(ts.length == 5 && ts2.length == 6);
+    }
+
+    /**
+     * Fetch a range that spans all the elements in the container.
+     *
+     * Complexity: $(BIGOH 1)
+     */
+    Range opSlice()
+    {
+        return Range(_begin, _end);
+    }
+
+    /// Ditto
+    ConstRange opSlice() const
+    {
+        return ConstRange(_begin, _end);
+    }
+
+    /// Ditto
+    ImmutableRange opSlice() immutable
+    {
+        return ImmutableRange(_begin, _end);
+    }
+
+    /**
+     * The front element in the container
+     *
+     * Complexity: $(BIGOH 1)
+     */
+    Elem front()
+    {
+        return _begin.value;
+    }
+
+    /**
+     * The last element in the container
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    Elem back()
+    {
+        return _end.prev.value;
+    }
+
+    /++
+        $(D in) operator. Check to see if the given element exists in the
+        container.
+
+       Complexity: $(BIGOH log(n))
+     +/
+    bool opBinaryRight(string op)(Elem e) const if (op == "in")
+    {
+        return _find(e) !is null;
+    }
+
+    static if(doUnittest) unittest
+    {
+        auto ts = new RedBlackTree(1, 2, 3, 4, 5);
+        assert(cast(Elem)3 in ts);
+        assert(cast(Elem)6 !in ts);
+    }
+
+    /**
+     * Compares two trees for equality.
+     *
+     * Complexity: $(BIGOH n)
+     */
+    override bool opEquals(Object rhs)
+    {
+        import std.algorithm : equal;
+
+        RedBlackTree that = cast(RedBlackTree)rhs;
+        if (that is null) return false;
+
+        // If there aren't the same number of nodes, we can't be equal.
+        if (this._length != that._length) return false;
+
+        auto thisRange = this[];
+        auto thatRange = that[];
+        return equal!(function(Elem a, Elem b) => !_less(a,b) && !_less(b,a))
+                     (thisRange, thatRange);
+    }
+
+    static if(doUnittest) unittest
+    {
+        auto t1 = new RedBlackTree(1,2,3,4);
+        auto t2 = new RedBlackTree(1,2,3,4);
+        auto t3 = new RedBlackTree(1,2,3,5);
+        auto t4 = new RedBlackTree(1,2,3,4,5);
+        auto o = new Object();
+
+        assert(t1 == t1);
+        assert(t1 == t2);
+        assert(t1 != t3);
+        assert(t1 != t4);
+        assert(t1 != o);  // pathological case, must not crash
+    }
+
+    /**
+     * Removes all elements from the container.
+     *
+     * Complexity: $(BIGOH 1)
+     */
+    void clear()
+    {
+        _end.left = null;
+        _begin = _end;
+        _length = 0;
+    }
+
+    static if(doUnittest) unittest
+    {
+        auto ts = new RedBlackTree(1,2,3,4,5);
+        assert(ts.length == 5);
+        ts.clear();
+        assert(ts.empty && ts.length == 0);
+    }
+
+    /**
+     * Insert a single element in the container.  Note that this does not
+     * invalidate any ranges currently iterating the container.
+     *
+     * Returns: The number of elements inserted.
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    size_t stableInsert(Stuff)(Stuff stuff) if (isImplicitlyConvertible!(Stuff, Elem))
+    {
+        static if(allowDuplicates)
+        {
+            _add(stuff);
+            return 1;
+        }
+        else
+        {
+            return(_add(stuff).added ? 1 : 0);
+        }
+    }
+
+    /**
+     * Insert a range of elements in the container.  Note that this does not
+     * invalidate any ranges currently iterating the container.
+     *
+     * Returns: The number of elements inserted.
+     *
+     * Complexity: $(BIGOH m * log(n))
+     */
+    size_t stableInsert(Stuff)(Stuff stuff) if(isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, Elem))
+    {
+        size_t result = 0;
+        static if(allowDuplicates)
+        {
+            foreach(e; stuff)
+            {
+                ++result;
+                _add(e);
+            }
+        }
+        else
+        {
+            foreach(e; stuff)
+            {
+                if(_add(e).added)
+                    ++result;
+            }
+        }
+        return result;
+    }
+
+    /// ditto
+    alias insert = stableInsert;
+
+    static if(doUnittest) unittest
+    {
+        auto ts = new RedBlackTree(2,1,3,4,5,2,5);
+        static if(allowDuplicates)
+        {
+            assert(ts.length == 7);
+            assert(ts.stableInsert(cast(Elem[])[7, 8, 6, 9, 10, 8]) == 6);
+            assert(ts.length == 13);
+            assert(ts.stableInsert(cast(Elem)11) == 1 && ts.length == 14);
+            assert(ts.stableInsert(cast(Elem)7) == 1 && ts.length == 15);
+
+            static if(less == "a < b")
+                assert(ts.arrayEqual([1,2,2,3,4,5,5,6,7,7,8,8,9,10,11]));
+            else
+                assert(ts.arrayEqual([11,10,9,8,8,7,7,6,5,5,4,3,2,2,1]));
+        }
+        else
+        {
+            assert(ts.length == 5);
+            assert(ts.stableInsert(cast(Elem[])[7, 8, 6, 9, 10, 8]) == 5);
+            assert(ts.length == 10);
+            assert(ts.stableInsert(cast(Elem)11) == 1 && ts.length == 11);
+            assert(ts.stableInsert(cast(Elem)7) == 0 && ts.length == 11);
+
+            static if(less == "a < b")
+                assert(ts.arrayEqual([1,2,3,4,5,6,7,8,9,10,11]));
+            else
+                assert(ts.arrayEqual([11,10,9,8,7,6,5,4,3,2,1]));
+        }
+    }
+
+    /**
+     * Remove an element from the container and return its value.
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    Elem removeAny()
+    {
+        scope(success)
+            --_length;
+        auto n = _begin;
+        auto result = n.value;
+        _begin = n.remove(_end);
+        debug(RBDoChecks)
+            check();
+        return result;
+    }
+
+    static if(doUnittest) unittest
+    {
+        auto ts = new RedBlackTree(1,2,3,4,5);
+        assert(ts.length == 5);
+        auto x = ts.removeAny();
+        assert(ts.length == 4);
+        Elem[] arr;
+        foreach(Elem i; 1..6)
+            if(i != x) arr ~= i;
+        assert(ts.arrayEqual(arr));
+    }
+
+    /**
+     * Remove the front element from the container.
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    void removeFront()
+    {
+        scope(success)
+            --_length;
+        _begin = _begin.remove(_end);
+        debug(RBDoChecks)
+            check();
+    }
+
+    /**
+     * Remove the back element from the container.
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    void removeBack()
+    {
+        scope(success)
+            --_length;
+        auto lastnode = _end.prev;
+        if(lastnode is _begin)
+            _begin = _begin.remove(_end);
+        else
+            lastnode.remove(_end);
+        debug(RBDoChecks)
+            check();
+    }
+
+    static if(doUnittest) unittest
+    {
+        auto ts = new RedBlackTree(1,2,3,4,5);
+        assert(ts.length == 5);
+        ts.removeBack();
+        assert(ts.length == 4);
+
+        static if(less == "a < b")
+            assert(ts.arrayEqual([1,2,3,4]));
+        else
+            assert(ts.arrayEqual([2,3,4,5]));
+
+        ts.removeFront();
+        assert(ts.arrayEqual([2,3,4]) && ts.length == 3);
+    }
+
+    /++
+        Removes the given range from the container.
+
+        Returns: A range containing all of the elements that were after the
+                 given range.
+
+        Complexity: $(BIGOH m * log(n)) (where m is the number of elements in
+                    the range)
+     +/
+    Range remove(Range r)
+    {
+        auto b = r._begin;
+        auto e = r._end;
+        if(_begin is b)
+            _begin = e;
+        while(b !is e)
+        {
+            b = b.remove(_end);
+            --_length;
+        }
+        debug(RBDoChecks)
+            check();
+        return Range(e, _end);
+    }
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        auto ts = new RedBlackTree(1,2,3,4,5);
+        assert(ts.length == 5);
+        auto r = ts[];
+        r.popFront();
+        r.popBack();
+        assert(ts.length == 5);
+        auto r2 = ts.remove(r);
+        assert(ts.length == 2);
+        assert(ts.arrayEqual([1,5]));
+
+        static if(less == "a < b")
+            assert(equal(r2, [5]));
+        else
+            assert(equal(r2, [1]));
+    }
+
+    /++
+        Removes the given $(D Take!Range) from the container
+
+        Returns: A range containing all of the elements that were after the
+                 given range.
+
+        Complexity: $(BIGOH m * log(n)) (where m is the number of elements in
+                    the range)
+     +/
+    Range remove(Take!Range r)
+    {
+        immutable isBegin = (r.source._begin is _begin);
+        auto b = r.source._begin;
+
+        while(!r.empty)
+        {
+            r.popFront();
+            b = b.remove(_end);
+            --_length;
+        }
+
+        if(isBegin)
+            _begin = b;
+
+        return Range(b, _end);
+    }
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        import std.range : take;
+        auto ts = new RedBlackTree(1,2,3,4,5);
+        auto r = ts[];
+        r.popFront();
+        assert(ts.length == 5);
+        auto r2 = ts.remove(take(r, 0));
+
+        static if(less == "a < b")
+        {
+            assert(equal(r2, [2,3,4,5]));
+            auto r3 = ts.remove(take(r, 2));
+            assert(ts.arrayEqual([1,4,5]) && ts.length == 3);
+            assert(equal(r3, [4,5]));
+        }
+        else
+        {
+            assert(equal(r2, [4,3,2,1]));
+            auto r3 = ts.remove(take(r, 2));
+            assert(ts.arrayEqual([5,2,1]) && ts.length == 3);
+            assert(equal(r3, [2,1]));
+        }
+    }
+
+    /++
+       Removes elements from the container that are equal to the given values
+       according to the less comparator. One element is removed for each value
+       given which is in the container. If $(D allowDuplicates) is true,
+       duplicates are removed only if duplicate values are given.
+
+       Returns: The number of elements removed.
+
+       Complexity: $(BIGOH m log(n)) (where m is the number of elements to remove)
+
+       Example:
+--------------------
+auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7);
+rbt.removeKey(1, 4, 7);
+assert(equal(rbt[], [0, 1, 1, 5]));
+rbt.removeKey(1, 1, 0);
+assert(equal(rbt[], [5]));
+--------------------
+      +/
+    size_t removeKey(U...)(U elems)
+        if(allSatisfy!(isImplicitlyConvertibleToElem, U))
+    {
+        Elem[U.length] toRemove;
+
+        foreach(i, e; elems)
+            toRemove[i] = e;
+
+        return removeKey(toRemove[]);
+    }
+
+    /++ Ditto +/
+    size_t removeKey(U)(U[] elems)
+        if(isImplicitlyConvertible!(U, Elem))
+    {
+        immutable lenBefore = length;
+
+        foreach(e; elems)
+        {
+            auto beg = _firstGreaterEqual(e);
+            if(beg is _end || _less(e, beg.value))
+                // no values are equal
+                continue;
+            immutable isBegin = (beg is _begin);
+            beg = beg.remove(_end);
+            if(isBegin)
+                _begin = beg;
+            --_length;
+        }
+
+        return lenBefore - length;
+    }
+
+    /++ Ditto +/
+    size_t removeKey(Stuff)(Stuff stuff)
+        if(isInputRange!Stuff &&
+           isImplicitlyConvertible!(ElementType!Stuff, Elem) &&
+           !isDynamicArray!Stuff)
+    {
+        import std.array : array;
+        //We use array in case stuff is a Range from this RedBlackTree - either
+        //directly or indirectly.
+        return removeKey(array(stuff));
+    }
+
+    //Helper for removeKey.
+    private template isImplicitlyConvertibleToElem(U)
+    {
+        enum isImplicitlyConvertibleToElem = isImplicitlyConvertible!(U, Elem);
+    }
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        import std.range : take;
+        auto rbt = new RedBlackTree(5, 4, 3, 7, 2, 1, 7, 6, 2, 19, 45);
+
+        //The cast(Elem) is because these tests are instantiated with a variety
+        //of numeric types, and the literals are all int, which is not always
+        //implicitly convertible to Elem (e.g. short).
+        static if(allowDuplicates)
+        {
+            assert(rbt.length == 11);
+            assert(rbt.removeKey(cast(Elem)4) == 1 && rbt.length == 10);
+            assert(rbt.arrayEqual([1,2,2,3,5,6,7,7,19,45]) && rbt.length == 10);
+
+            assert(rbt.removeKey(cast(Elem)6, cast(Elem)2, cast(Elem)1) == 3);
+            assert(rbt.arrayEqual([2,3,5,7,7,19,45]) && rbt.length == 7);
+
+            assert(rbt.removeKey(cast(Elem)(42)) == 0 && rbt.length == 7);
+            assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 4);
+
+            static if(less == "a < b")
+                assert(equal(rbt[], [7,7,19,45]));
+            else
+                assert(equal(rbt[], [7,5,3,2]));
+        }
+        else
+        {
+            assert(rbt.length == 9);
+            assert(rbt.removeKey(cast(Elem)4) == 1 && rbt.length == 8);
+            assert(rbt.arrayEqual([1,2,3,5,6,7,19,45]));
+
+            assert(rbt.removeKey(cast(Elem)6, cast(Elem)2, cast(Elem)1) == 3);
+            assert(rbt.arrayEqual([3,5,7,19,45]) && rbt.length == 5);
+
+            assert(rbt.removeKey(cast(Elem)(42)) == 0 && rbt.length == 5);
+            assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 2);
+
+            static if(less == "a < b")
+                assert(equal(rbt[], [19,45]));
+            else
+                assert(equal(rbt[], [5,3]));
+        }
+    }
+
+    // find the first node where the value is > e
+    private inout(RBNode)* _firstGreater(Elem e) inout
+    {
+        // can't use _find, because we cannot return null
+        auto cur = _end.left;
+        inout(RBNode)* result = _end;
+        while(cur)
+        {
+            if(_less(e, cur.value))
+            {
+                result = cur;
+                cur = cur.left;
+            }
+            else
+                cur = cur.right;
+        }
+        return result;
+    }
+
+    // find the first node where the value is >= e
+    private inout(RBNode)* _firstGreaterEqual(Elem e) inout
+    {
+        // can't use _find, because we cannot return null.
+        auto cur = _end.left;
+        inout(RBNode)* result = _end;
+        while(cur)
+        {
+            if(_less(cur.value, e))
+                cur = cur.right;
+            else
+            {
+                result = cur;
+                cur = cur.left;
+            }
+
+        }
+        return result;
+    }
+
+    /**
+     * Get a range from the container with all elements that are > e according
+     * to the less comparator
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    Range upperBound(Elem e)
+    {
+        return Range(_firstGreater(e), _end);
+    }
+
+    /// Ditto
+    ConstRange upperBound(Elem e) const
+    {
+        return ConstRange(_firstGreater(e), _end);
+    }
+
+    /// Ditto
+    ImmutableRange upperBound(Elem e) immutable
+    {
+        return ImmutableRange(_firstGreater(e), _end);
+    }
+
+    /**
+     * Get a range from the container with all elements that are < e according
+     * to the less comparator
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    Range lowerBound(Elem e)
+    {
+        return Range(_begin, _firstGreaterEqual(e));
+    }
+
+    /// Ditto
+    ConstRange lowerBound(Elem e) const
+    {
+        return ConstRange(_begin, _firstGreaterEqual(e));
+    }
+
+    /// Ditto
+    ImmutableRange lowerBound(Elem e) immutable
+    {
+        return ImmutableRange(_begin, _firstGreaterEqual(e));
+    }
+
+    /**
+     * Get a range from the container with all elements that are == e according
+     * to the less comparator
+     *
+     * Complexity: $(BIGOH log(n))
+     */
+    auto equalRange(this This)(Elem e)
+    {
+        auto beg = _firstGreaterEqual(e);
+        alias RangeType = RBRange!(typeof(beg));
+        if(beg is _end || _less(e, beg.value))
+            // no values are equal
+            return RangeType(beg, beg);
+        static if(allowDuplicates)
+        {
+            return RangeType(beg, _firstGreater(e));
+        }
+        else
+        {
+            // no sense in doing a full search, no duplicates are allowed,
+            // so we just get the next node.
+            return RangeType(beg, beg.next);
+        }
+    }
+
+    static if(doUnittest) unittest
+    {
+        import std.algorithm : equal;
+        auto ts = new RedBlackTree(1, 2, 3, 4, 5);
+        auto rl = ts.lowerBound(3);
+        auto ru = ts.upperBound(3);
+        auto re = ts.equalRange(3);
+
+        static if(less == "a < b")
+        {
+            assert(equal(rl, [1,2]));
+            assert(equal(ru, [4,5]));
+        }
+        else
+        {
+            assert(equal(rl, [5,4]));
+            assert(equal(ru, [2,1]));
+        }
+
+        assert(equal(re, [3]));
+    }
+
+    debug(RBDoChecks)
+    {
+        /*
+         * Print the tree.  This prints a sideways view of the tree in ASCII form,
+         * with the number of indentations representing the level of the nodes.
+         * It does not print values, only the tree structure and color of nodes.
+         */
+        void printTree(Node n, int indent = 0)
+        {
+            import std.stdio;
+            if(n !is null)
+            {
+                printTree(n.right, indent + 2);
+                for(int i = 0; i < indent; i++)
+                    write(".");
+                writeln(n.color == n.color.Black ? "B" : "R");
+                printTree(n.left, indent + 2);
+            }
+            else
+            {
+                for(int i = 0; i < indent; i++)
+                    write(".");
+                writeln("N");
+            }
+            if(indent is 0)
+                writeln();
+        }
+
+        /*
+         * Check the tree for validity.  This is called after every add or remove.
+         * This should only be enabled to debug the implementation of the RB Tree.
+         */
+        void check()
+        {
+            //
+            // check implementation of the tree
+            //
+            int recurse(Node n, string path)
+            {
+                import std.stdio;
+                if(n is null)
+                    return 1;
+                if(n.parent.left !is n && n.parent.right !is n)
+                    throw new Exception("Node at path " ~ path ~ " has inconsistent pointers");
+                Node next = n.next;
+                static if(allowDuplicates)
+                {
+                    if(next !is _end && _less(next.value, n.value))
+                        throw new Exception("ordering invalid at path " ~ path);
+                }
+                else
+                {
+                    if(next !is _end && !_less(n.value, next.value))
+                        throw new Exception("ordering invalid at path " ~ path);
+                }
+                if(n.color == n.color.Red)
+                {
+                    if((n.left !is null && n.left.color == n.color.Red) ||
+                            (n.right !is null && n.right.color == n.color.Red))
+                        throw new Exception("Node at path " ~ path ~ " is red with a red child");
+                }
+
+                int l = recurse(n.left, path ~ "L");
+                int r = recurse(n.right, path ~ "R");
+                if(l != r)
+                {
+                    writeln("bad tree at:");
+                    debug printTree(n);
+                    throw new Exception("Node at path " ~ path ~ " has different number of black nodes on left and right paths");
+                }
+                return l + (n.color == n.color.Black ? 1 : 0);
+            }
+
+            try
+            {
+                recurse(_end.left, "");
+            }
+            catch(Exception e)
+            {
+                debug printTree(_end.left, 0);
+                throw e;
+            }
+        }
+    }
+
+    /**
+      Formats the RedBlackTree into a sink function. For more info see $(D
+      std.format.formatValue). Note that this only is available when the
+      element type can be formatted. Otherwise, the default toString from
+      Object is used.
+     */
+    static if(is(typeof((){FormatSpec!(char) fmt; formatValue((const(char)[]) {}, ConstRange.init, fmt);})))
+    {
+        void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const {
+            sink("RedBlackTree(");
+            sink.formatValue(this[], fmt);
+            sink(")");
+        }
+    }
+
+    /**
+     * Constructor. Pass in an array of elements, or individual elements to
+     * initialize the tree with.
+     */
+    this(Elem[] elems...)
+    {
+        _setup();
+        stableInsert(elems);
+    }
+
+    /**
+     * Constructor. Pass in a range of elements to initialize the tree with.
+     */
+    this(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, Elem))
+    {
+        _setup();
+        stableInsert(stuff);
+    }
+
+    ///
+    this()
+    {
+        _setup();
+    }
+
+    private this(Node end, size_t length)
+    {
+        _end = end;
+        _begin = end.leftmost;
+        _length = length;
+    }
+}
+
+//Verify Example for removeKey.
+pure unittest
+{
+    import std.algorithm : equal;
+    auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7);
+    rbt.removeKey(1, 4, 7);
+    assert(equal(rbt[], [0, 1, 1, 5]));
+    rbt.removeKey(1, 1, 0);
+    assert(equal(rbt[], [5]));
+}
+
+//Tests for removeKey
+pure unittest
+{
+    import std.algorithm : equal;
+    {
+        auto rbt = redBlackTree(["hello", "world", "foo", "bar"]);
+        assert(equal(rbt[], ["bar", "foo", "hello", "world"]));
+        assert(rbt.removeKey("hello") == 1);
+        assert(equal(rbt[], ["bar", "foo", "world"]));
+        assert(rbt.removeKey("hello") == 0);
+        assert(equal(rbt[], ["bar", "foo", "world"]));
+        assert(rbt.removeKey("hello", "foo", "bar") == 2);
+        assert(equal(rbt[], ["world"]));
+        assert(rbt.removeKey(["", "world", "hello"]) == 1);
+        assert(rbt.empty);
+    }
+
+    {
+        auto rbt = redBlackTree([1, 2, 12, 27, 4, 500]);
+        assert(equal(rbt[], [1, 2, 4, 12, 27, 500]));
+        assert(rbt.removeKey(1u) == 1);
+        assert(equal(rbt[], [2, 4, 12, 27, 500]));
+        assert(rbt.removeKey(cast(byte)1) == 0);
+        assert(equal(rbt[], [2, 4, 12, 27, 500]));
+        assert(rbt.removeKey(1, 12u, cast(byte)27) == 2);
+        assert(equal(rbt[], [2, 4, 500]));
+        assert(rbt.removeKey([cast(short)0, cast(short)500, cast(short)1]) == 1);
+        assert(equal(rbt[], [2, 4]));
+    }
+}
+
+pure unittest
+{
+    void test(T)()
+    {
+        auto rt1 = new RedBlackTree!(T, "a < b", false)();
+        auto rt2 = new RedBlackTree!(T, "a < b", true)();
+        auto rt3 = new RedBlackTree!(T, "a > b", false)();
+        auto rt4 = new RedBlackTree!(T, "a > b", true)();
+    }
+
+    test!long();
+    test!ulong();
+    test!int();
+    test!uint();
+    test!short();
+    test!ushort();
+    test!byte();
+    test!byte();
+}
+
+/++
+    Convenience function for creating a $(D RedBlackTree!E) from a list of
+    values.
+
+    Params:
+        allowDuplicates =  Whether duplicates should be allowed (optional, default: false)
+        less = predicate to sort by (optional)
+        elems = elements to insert into the rbtree (variadic arguments)
+        range = range elements to insert into the rbtree (alternative to elems)
+  +/
+auto redBlackTree(E)(E[] elems...)
+{
+    return new RedBlackTree!E(elems);
+}
+
+/++ Ditto +/
+auto redBlackTree(bool allowDuplicates, E)(E[] elems...)
+{
+    return new RedBlackTree!(E, "a < b", allowDuplicates)(elems);
+}
+
+/++ Ditto +/
+auto redBlackTree(alias less, E)(E[] elems...)
+    if(is(typeof(binaryFun!less(E.init, E.init))))
+{
+    return new RedBlackTree!(E, less)(elems);
+}
+
+/++ Ditto +/
+auto redBlackTree(alias less, bool allowDuplicates, E)(E[] elems...)
+    if(is(typeof(binaryFun!less(E.init, E.init))))
+{
+    //We shouldn't need to instantiate less here, but for some reason,
+    //dmd can't handle it if we don't (even though the template which
+    //takes less but not allowDuplicates works just fine).
+    return new RedBlackTree!(E, binaryFun!less, allowDuplicates)(elems);
+}
+
+
+import std.range.primitives: isInputRange, isSomeString, ElementType;
+import std.traits: isArray;
+
+/++ Ditto +/
+auto redBlackTree(Stuff)(Stuff range)
+if (isInputRange!Stuff && !isArray!(Stuff))
+{
+    return new RedBlackTree!(ElementType!Stuff)(range);
+}
+
+/++ Ditto +/
+auto redBlackTree(bool allowDuplicates, Stuff)(Stuff range)
+if (isInputRange!Stuff && !isArray!(Stuff))
+{
+    return new RedBlackTree!(ElementType!Stuff, "a < b", allowDuplicates)(range);
+}
+
+/++ Ditto +/
+auto redBlackTree(alias less, Stuff)(Stuff range)
+if( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init)))
+    && isInputRange!Stuff && !isArray!(Stuff))
+{
+    return new RedBlackTree!(ElementType!Stuff, less)(range);
+}
+
+/++ Ditto +/
+auto redBlackTree(alias less, bool allowDuplicates, Stuff)(Stuff range)
+    if( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init)))
+         && isInputRange!Stuff && !isArray!(Stuff))
+{
+    //We shouldn't need to instantiate less here, but for some reason,
+    //dmd can't handle it if we don't (even though the template which
+    //takes less but not allowDuplicates works just fine).
+    return new RedBlackTree!(ElementType!Stuff, binaryFun!less, allowDuplicates)(range);
+}
+
+///
+pure unittest
+{
+    import std.range : iota;
+
+    auto rbt1 = redBlackTree(0, 1, 5, 7);
+    auto rbt2 = redBlackTree!string("hello", "world");
+    auto rbt3 = redBlackTree!true(0, 1, 5, 7, 5);
+    auto rbt4 = redBlackTree!"a > b"(0, 1, 5, 7);
+    auto rbt5 = redBlackTree!("a > b", true)(0.1, 1.3, 5.9, 7.2, 5.9);
+
+    // also works with ranges
+    auto rbt6 = redBlackTree(iota(3));
+    auto rbt7 = redBlackTree!true(iota(3));
+    auto rbt8 = redBlackTree!"a > b"(iota(3));
+    auto rbt9 = redBlackTree!("a > b", true)(iota(3));
+}
+
+//Combinations not in examples.
+pure unittest
+{
+    auto rbt1 = redBlackTree!(true, string)("hello", "hello");
+    auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3);
+    auto rbt3 = redBlackTree!("a > b", true, string)("hello", "world");
+}
+
+//Range construction.
+pure unittest
+{
+    import std.algorithm : equal;
+    import std.range : iota;
+    auto rbt = new RedBlackTree!(int, "a > b")(iota(5));
+    assert(equal(rbt[], [4, 3, 2, 1, 0]));
+}
+
+
+// construction with arrays
+pure unittest
+{
+    import std.algorithm : equal;
+
+    auto rbt = redBlackTree!"a > b"([0, 1, 2, 3, 4]);
+    assert(equal(rbt[], [4, 3, 2, 1, 0]));
+
+    auto rbt2 = redBlackTree!"a > b"(["a", "b"]);
+    assert(equal(rbt2[], ["b", "a"]));
+
+    auto rbt3 = redBlackTree!"a > b"([1, 2]);
+    assert(equal(rbt3[], [2, 1]));
+
+    auto rbt4 = redBlackTree([0, 1, 7, 5]);
+    assert(equal(rbt4[], [0, 1, 5, 7]));
+
+    auto rbt5 = redBlackTree(["hello", "world"]);
+    assert(equal(rbt5[], ["hello", "world"]));
+
+    auto rbt6 = redBlackTree!true([0, 1, 5, 7, 5]);
+    assert(equal(rbt6[], [0, 1, 5, 5, 7]));
+
+    auto rbt7 = redBlackTree!"a > b"([0, 1, 5, 7]);
+    assert(equal(rbt7[], [7, 5, 1, 0]));
+
+    auto rbt8 = redBlackTree!("a > b", true)([0.1, 1.3, 5.9, 7.2, 5.9]);
+    assert(equal(rbt8[], [7.2, 5.9, 5.9, 1.3, 0.1]));
+}
+
+// convenience wrapper range construction
+pure unittest
+{
+    import std.algorithm : equal;
+    import std.range : chain, iota;
+
+    auto rbt = redBlackTree(iota(3));
+    assert(equal(rbt[], [0, 1, 2]));
+
+    auto rbt2 = redBlackTree!"a > b"(iota(2));
+    assert(equal(rbt2[], [1, 0]));
+
+    auto rbt3 = redBlackTree(chain([0, 1], [7, 5]));
+    assert(equal(rbt3[], [0, 1, 5, 7]));
+
+    auto rbt4 = redBlackTree(chain(["hello"], ["world"]));
+    assert(equal(rbt4[], ["hello", "world"]));
+
+    auto rbt5 = redBlackTree!true(chain([0, 1], [5, 7, 5]));
+    assert(equal(rbt5[], [0, 1, 5, 5, 7]));
+
+    auto rbt6 = redBlackTree!("a > b", true)(chain([0.1, 1.3], [5.9, 7.2, 5.9]));
+    assert(equal(rbt6[], [7.2, 5.9, 5.9, 1.3, 0.1]));
+}
+
+pure unittest
+{
+    import std.array : array;
+
+    auto rt1 = redBlackTree(5, 4, 3, 2, 1);
+    assert(rt1.length == 5);
+    assert(array(rt1[]) == [1, 2, 3, 4, 5]);
+
+    auto rt2 = redBlackTree!"a > b"(1.1, 2.1);
+    assert(rt2.length == 2);
+    assert(array(rt2[]) == [2.1, 1.1]);
+
+    auto rt3 = redBlackTree!true(5, 5, 4);
+    assert(rt3.length == 3);
+    assert(array(rt3[]) == [4, 5, 5]);
+
+    auto rt4 = redBlackTree!string("hello", "hello");
+    assert(rt4.length == 1);
+    assert(array(rt4[]) == ["hello"]);
+}
+
+unittest {
+    import std.conv : to;
+
+    auto rt1 = redBlackTree!string();
+    assert(rt1.to!string == "RedBlackTree([])");
+
+    auto rt2 = redBlackTree!string("hello");
+    assert(rt2.to!string == "RedBlackTree([\"hello\"])");
+
+    auto rt3 = redBlackTree!string("hello", "world", "!");
+    assert(rt3.to!string == "RedBlackTree([\"!\", \"hello\", \"world\"])");
+
+    // type deduction can be done automatically
+    auto rt4 = redBlackTree(["hello"]);
+    assert(rt4.to!string == "RedBlackTree([\"hello\"])");
+}
+
+//constness checks
+unittest
+{
+    const rt1 = redBlackTree(5,4,3,2,1);
+    static assert(is(typeof(rt1.length)));
+    static assert(is(typeof(5 in rt1)));
+
+    static assert(is(typeof(rt1.upperBound(3).front) == const(int)));
+    import std.algorithm : equal;
+    assert(rt1.upperBound(3).equal([4, 5]));
+    assert(rt1.lowerBound(3).equal([1, 2]));
+    assert(rt1.equalRange(3).equal([3]));
+    assert(rt1[].equal([1, 2, 3, 4, 5]));
+}
+
+//immutable checks
+unittest
+{
+    immutable rt1 = redBlackTree(5,4,3,2,1);
+    static assert(is(typeof(rt1.length)));
+
+    static assert(is(typeof(rt1.upperBound(3).front) == immutable(int)));
+    import std.algorithm : equal;
+    assert(rt1.upperBound(2).equal([3, 4, 5]));
+}
+
+// issue 15941
+unittest
+{
+    class C {}
+    RedBlackTree!(C, "cast(void*)a < cast(void*)b") tree;
+}
diff --git a/libphobos/src/std/container/slist.d b/libphobos/src/std/container/slist.d
new file mode 100644
index 00000000000..bf308fa2bc3
--- /dev/null
+++ b/libphobos/src/std/container/slist.d
@@ -0,0 +1,848 @@
+/**
+This module implements a singly-linked list container.
+It can be used as a stack.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_slist.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.slist;
+
+///
+unittest
+{
+    import std.container: SList;
+    import std.algorithm: equal;
+
+    auto s = SList!int(1, 2, 3);
+    assert(equal(s[], [1, 2, 3]));
+
+    s.removeFront();
+    assert(equal(s[], [2, 3]));
+
+    s.insertFront([5, 6]);
+    assert(equal(s[], [5, 6, 2, 3]));
+
+    // If you want to apply range operations, simply slice it.
+    import std.algorithm: countUntil;
+    import std.range: popFrontN, walkLength;
+
+    auto sl = SList!int(1, 2, 3, 4, 5);
+    assert(countUntil(sl[], 2) == 1);
+
+    auto r = sl[];
+    popFrontN(r, 2);
+    assert(walkLength(r) == 3);
+}
+
+public import std.container.util;
+
+/**
+   Implements a simple and fast singly-linked list.
+   It can be used as a stack.
+
+   $(D SList) uses reference semantics.
+ */
+struct SList(T)
+{
+    import std.exception : enforce;
+    import std.range : Take;
+    import std.range.primitives;
+    import std.traits;
+
+    private struct Node
+    {
+        Node * _next;
+        T _payload;
+    }
+    private struct NodeWithoutPayload
+    {
+        Node* _next;
+    }
+    static assert(NodeWithoutPayload._next.offsetof == Node._next.offsetof);
+
+    private Node * _root;
+
+    private void initialize() @trusted nothrow pure
+    {
+        if (_root) return;
+        _root = cast (Node*) new NodeWithoutPayload();
+    }
+
+    private ref inout(Node*) _first() @property @safe nothrow pure inout
+    {
+        assert(_root);
+        return _root._next;
+    }
+
+    private static Node * findLastNode(Node * n)
+    {
+        assert(n);
+        auto ahead = n._next;
+        while (ahead)
+        {
+            n = ahead;
+            ahead = n._next;
+        }
+        return n;
+    }
+
+    private static Node * findLastNode(Node * n, size_t limit)
+    {
+        assert(n && limit);
+        auto ahead = n._next;
+        while (ahead)
+        {
+            if (!--limit) break;
+            n = ahead;
+            ahead = n._next;
+        }
+        return n;
+    }
+
+    private static Node * findNode(Node * n, Node * findMe)
+    {
+        assert(n);
+        auto ahead = n._next;
+        while (ahead != findMe)
+        {
+            n = ahead;
+            enforce(n);
+            ahead = n._next;
+        }
+        return n;
+    }
+
+/**
+Constructor taking a number of nodes
+     */
+    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T))
+    {
+        insertFront(values);
+    }
+
+/**
+Constructor taking an input range
+     */
+    this(Stuff)(Stuff stuff)
+    if (isInputRange!Stuff
+            && isImplicitlyConvertible!(ElementType!Stuff, T)
+            && !is(Stuff == T[]))
+    {
+        insertFront(stuff);
+    }
+
+/**
+Comparison for equality.
+
+Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of
+elements in $(D rhs).
+     */
+    bool opEquals(const SList rhs) const
+    {
+        return opEquals(rhs);
+    }
+
+    /// ditto
+    bool opEquals(ref const SList rhs) const
+    {
+        if (_root is rhs._root) return true;
+        if (_root is null) return rhs._root is null || rhs._first is null;
+        if (rhs._root is null) return _root is null || _first is null;
+
+        const(Node) * n1 = _first, n2 = rhs._first;
+
+        for (;; n1 = n1._next, n2 = n2._next)
+        {
+            if (!n1) return !n2;
+            if (!n2 || n1._payload != n2._payload) return false;
+        }
+    }
+
+/**
+Defines the container's primary range, which embodies a forward range.
+     */
+    struct Range
+    {
+        private Node * _head;
+        private this(Node * p) { _head = p; }
+
+        /// Input range primitives.
+        @property bool empty() const { return !_head; }
+
+        /// ditto
+        @property ref T front()
+        {
+            assert(!empty, "SList.Range.front: Range is empty");
+            return _head._payload;
+        }
+
+        /// ditto
+        void popFront()
+        {
+            assert(!empty, "SList.Range.popFront: Range is empty");
+            _head = _head._next;
+        }
+
+        /// Forward range primitive.
+        @property Range save() { return this; }
+
+        T moveFront()
+        {
+            import std.algorithm : move;
+
+            assert(!empty, "SList.Range.moveFront: Range is empty");
+            return move(_head._payload);
+        }
+
+        bool sameHead(Range rhs)
+        {
+            return _head && _head == rhs._head;
+        }
+    }
+
+    unittest
+    {
+        static assert(isForwardRange!Range);
+    }
+
+/**
+Property returning $(D true) if and only if the container has no
+elements.
+
+Complexity: $(BIGOH 1)
+     */
+    @property bool empty() const
+    {
+        return _root is null || _first is null;
+    }
+
+/**
+Duplicates the container. The elements themselves are not transitively
+duplicated.
+
+Complexity: $(BIGOH n).
+     */
+    @property SList dup()
+    {
+        return SList(this[]);
+    }
+
+/**
+Returns a range that iterates over all elements of the container, in
+forward order.
+
+Complexity: $(BIGOH 1)
+     */
+    Range opSlice()
+    {
+        if (empty)
+            return Range(null);
+        else
+            return Range(_first);
+    }
+
+/**
+Forward to $(D opSlice().front).
+
+Complexity: $(BIGOH 1)
+     */
+    @property ref T front()
+    {
+        assert(!empty, "SList.front: List is empty");
+        return _first._payload;
+    }
+
+    unittest
+    {
+        auto s = SList!int(1, 2, 3);
+        s.front = 42;
+        assert(s == SList!int(42, 2, 3));
+    }
+
+/**
+Returns a new $(D SList) that's the concatenation of $(D this) and its
+argument. $(D opBinaryRight) is only defined if $(D Stuff) does not
+define $(D opBinary).
+     */
+    SList opBinary(string op, Stuff)(Stuff rhs)
+    if (op == "~" && is(typeof(SList(rhs))))
+    {
+        auto toAdd = SList(rhs);
+        if (empty) return toAdd;
+        // TODO: optimize
+        auto result = dup;
+        auto n = findLastNode(result._first);
+        n._next = toAdd._first;
+        return result;
+    }
+
+    /// ditto
+    SList opBinaryRight(string op, Stuff)(Stuff lhs)
+    if (op == "~" && !is(typeof(lhs.opBinary!"~"(this))) && is(typeof(SList(lhs))))
+    {
+        auto toAdd = SList(lhs);
+        if (empty) return toAdd;
+        auto result = dup;
+        result.insertFront(toAdd[]);
+        return result;
+    }
+
+/**
+Removes all contents from the $(D SList).
+
+Postcondition: $(D empty)
+
+Complexity: $(BIGOH 1)
+     */
+    void clear()
+    {
+        if (_root)
+            _first = null;
+    }
+
+/**
+Reverses SList in-place. Performs no memory allocation.
+
+Complexity: $(BIGOH n)
+     */
+    void reverse()
+    {
+        if (!empty)
+        {
+            Node* prev;
+            while (_first)
+            {
+                auto next = _first._next;
+                _first._next = prev;
+                prev = _first;
+                _first = next;
+            }
+            _first = prev;
+        }
+    }
+
+/**
+Inserts $(D stuff) to the front of the container. $(D stuff) can be a
+value convertible to $(D T) or a range of objects convertible to $(D
+T). The stable version behaves the same, but guarantees that ranges
+iterating over the container are never invalidated.
+
+Returns: The number of elements inserted
+
+Complexity: $(BIGOH m), where $(D m) is the length of $(D stuff)
+     */
+    size_t insertFront(Stuff)(Stuff stuff)
+    if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+    {
+        initialize();
+        size_t result;
+        Node * n, newRoot;
+        foreach (item; stuff)
+        {
+            auto newNode = new Node(null, item);
+            (newRoot ? n._next : newRoot) = newNode;
+            n = newNode;
+            ++result;
+        }
+        if (!n) return 0;
+        // Last node points to the old root
+        n._next = _first;
+        _first = newRoot;
+        return result;
+    }
+
+    /// ditto
+    size_t insertFront(Stuff)(Stuff stuff)
+    if (isImplicitlyConvertible!(Stuff, T))
+    {
+        initialize();
+        auto newRoot = new Node(_first, stuff);
+        _first = newRoot;
+        return 1;
+    }
+
+/// ditto
+    alias insert = insertFront;
+
+/// ditto
+    alias stableInsert = insert;
+
+    /// ditto
+    alias stableInsertFront = insertFront;
+
+/**
+Picks one value in an unspecified position in the container, removes
+it from the container, and returns it. The stable version behaves the same,
+but guarantees that ranges iterating over the container are never invalidated.
+
+Precondition: $(D !empty)
+
+Returns: The element removed.
+
+Complexity: $(BIGOH 1).
+     */
+    T removeAny()
+    {
+        import std.algorithm : move;
+
+        assert(!empty, "SList.removeAny: List is empty");
+        auto result = move(_first._payload);
+        _first = _first._next;
+        return result;
+    }
+    /// ditto
+    alias stableRemoveAny = removeAny;
+
+/**
+Removes the value at the front of the container. The stable version
+behaves the same, but guarantees that ranges iterating over the
+container are never invalidated.
+
+Precondition: $(D !empty)
+
+Complexity: $(BIGOH 1).
+     */
+    void removeFront()
+    {
+        assert(!empty, "SList.removeFront: List is empty");
+        _first = _first._next;
+    }
+
+    /// ditto
+    alias stableRemoveFront = removeFront;
+
+/**
+Removes $(D howMany) values at the front or back of the
+container. Unlike the unparameterized versions above, these functions
+do not throw if they could not remove $(D howMany) elements. Instead,
+if $(D howMany > n), all elements are removed. The returned value is
+the effective number of elements removed. The stable version behaves
+the same, but guarantees that ranges iterating over the container are
+never invalidated.
+
+Returns: The number of elements removed
+
+Complexity: $(BIGOH howMany * log(n)).
+     */
+    size_t removeFront(size_t howMany)
+    {
+        size_t result;
+        while (_first && result < howMany)
+        {
+            _first = _first._next;
+            ++result;
+        }
+        return result;
+    }
+
+    /// ditto
+    alias stableRemoveFront = removeFront;
+
+/**
+Inserts $(D stuff) after range $(D r), which must be a range
+previously extracted from this container. Given that all ranges for a
+list end at the end of the list, this function essentially appends to
+the list and uses $(D r) as a potentially fast way to reach the last
+node in the list. Ideally $(D r) is positioned near or at the last
+element of the list.
+
+$(D stuff) can be a value convertible to $(D T) or a range of objects
+convertible to $(D T). The stable version behaves the same, but
+guarantees that ranges iterating over the container are never
+invalidated.
+
+Returns: The number of values inserted.
+
+Complexity: $(BIGOH k + m), where $(D k) is the number of elements in
+$(D r) and $(D m) is the length of $(D stuff).
+
+Example:
+--------------------
+auto sl = SList!string(["a", "b", "d"]);
+sl.insertAfter(sl[], "e"); // insert at the end (slowest)
+assert(std.algorithm.equal(sl[], ["a", "b", "d", "e"]));
+sl.insertAfter(std.range.take(sl[], 2), "c"); // insert after "b"
+assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"]));
+--------------------
+     */
+
+    size_t insertAfter(Stuff)(Range r, Stuff stuff)
+    {
+        initialize();
+        if (!_first)
+        {
+            enforce(!r._head);
+            return insertFront(stuff);
+        }
+        enforce(r._head);
+        auto n = findLastNode(r._head);
+        SList tmp;
+        auto result = tmp.insertFront(stuff);
+        n._next = tmp._first;
+        return result;
+    }
+
+/**
+Similar to $(D insertAfter) above, but accepts a range bounded in
+count. This is important for ensuring fast insertions in the middle of
+the list.  For fast insertions after a specified position $(D r), use
+$(D insertAfter(take(r, 1), stuff)). The complexity of that operation
+only depends on the number of elements in $(D stuff).
+
+Precondition: $(D r.original.empty || r.maxLength > 0)
+
+Returns: The number of values inserted.
+
+Complexity: $(BIGOH k + m), where $(D k) is the number of elements in
+$(D r) and $(D m) is the length of $(D stuff).
+     */
+    size_t insertAfter(Stuff)(Take!Range r, Stuff stuff)
+    {
+        auto orig = r.source;
+        if (!orig._head)
+        {
+            // Inserting after a null range counts as insertion to the
+            // front
+            return insertFront(stuff);
+        }
+        enforce(!r.empty);
+        // Find the last valid element in the range
+        foreach (i; 1 .. r.maxLength)
+        {
+            if (!orig._head._next) break;
+            orig.popFront();
+        }
+        // insert here
+        SList tmp;
+        tmp.initialize();
+        tmp._first = orig._head._next;
+        auto result = tmp.insertFront(stuff);
+        orig._head._next = tmp._first;
+        return result;
+    }
+
+/// ditto
+    alias stableInsertAfter = insertAfter;
+
+/**
+Removes a range from the list in linear time.
+
+Returns: An empty range.
+
+Complexity: $(BIGOH n)
+     */
+    Range linearRemove(Range r)
+    {
+        if (!_first)
+        {
+            enforce(!r._head);
+            return this[];
+        }
+        auto n = findNode(_root, r._head);
+        n._next = null;
+        return Range(null);
+    }
+
+/**
+Removes a $(D Take!Range) from the list in linear time.
+
+Returns: A range comprehending the elements after the removed range.
+
+Complexity: $(BIGOH n)
+     */
+    Range linearRemove(Take!Range r)
+    {
+        auto orig = r.source;
+        // We have something to remove here
+        if (orig._head == _first)
+        {
+            // remove straight from the head of the list
+            for (; !r.empty; r.popFront())
+            {
+                removeFront();
+            }
+            return this[];
+        }
+        if (!r.maxLength)
+        {
+            // Nothing to remove, return the range itself
+            return orig;
+        }
+        // Remove from somewhere in the middle of the list
+        enforce(_first);
+        auto n1 = findNode(_root, orig._head);
+        auto n2 = findLastNode(orig._head, r.maxLength);
+        n1._next = n2._next;
+        return Range(n1._next);
+    }
+
+/// ditto
+    alias stableLinearRemove = linearRemove;
+}
+
+unittest
+{
+    import std.algorithm : equal;
+
+    auto a = SList!int(5);
+    auto b = a;
+    auto r = a[];
+    a.insertFront(1);
+    b.insertFront(2);
+    assert(equal(a[], [2, 1, 5]));
+    assert(equal(b[], [2, 1, 5]));
+    r.front = 9;
+    assert(equal(a[], [2, 1, 9]));
+    assert(equal(b[], [2, 1, 9]));
+}
+
+unittest
+{
+    auto s = SList!int(1, 2, 3);
+    auto n = s.findLastNode(s._root);
+    assert(n && n._payload == 3);
+}
+
+unittest
+{
+    import std.range.primitives;
+    auto s = SList!int(1, 2, 5, 10);
+    assert(walkLength(s[]) == 4);
+}
+
+unittest
+{
+    import std.range : take;
+    auto src = take([0, 1, 2, 3], 3);
+    auto s = SList!int(src);
+    assert(s == SList!int(0, 1, 2));
+}
+
+unittest
+{
+    auto a = SList!int();
+    auto b = SList!int();
+    auto c = a ~ b[];
+    assert(c.empty);
+}
+
+unittest
+{
+    auto a = SList!int(1, 2, 3);
+    auto b = SList!int(4, 5, 6);
+    auto c = a ~ b[];
+    assert(c == SList!int(1, 2, 3, 4, 5, 6));
+}
+
+unittest
+{
+    auto a = SList!int(1, 2, 3);
+    auto b = [4, 5, 6];
+    auto c = a ~ b;
+    assert(c == SList!int(1, 2, 3, 4, 5, 6));
+}
+
+unittest
+{
+    auto a = SList!int(1, 2, 3);
+    auto c = a ~ 4;
+    assert(c == SList!int(1, 2, 3, 4));
+}
+
+unittest
+{
+    auto a = SList!int(2, 3, 4);
+    auto b = 1 ~ a;
+    assert(b == SList!int(1, 2, 3, 4));
+}
+
+unittest
+{
+    auto a = [1, 2, 3];
+    auto b = SList!int(4, 5, 6);
+    auto c = a ~ b;
+    assert(c == SList!int(1, 2, 3, 4, 5, 6));
+}
+
+unittest
+{
+    auto s = SList!int(1, 2, 3, 4);
+    s.insertFront([ 42, 43 ]);
+    assert(s == SList!int(42, 43, 1, 2, 3, 4));
+}
+
+unittest
+{
+    auto s = SList!int(1, 2, 3);
+    assert(s.removeAny() == 1);
+    assert(s == SList!int(2, 3));
+    assert(s.stableRemoveAny() == 2);
+    assert(s == SList!int(3));
+}
+
+unittest
+{
+    import std.algorithm : equal;
+
+    auto s = SList!int(1, 2, 3);
+    s.removeFront();
+    assert(equal(s[], [2, 3]));
+    s.stableRemoveFront();
+    assert(equal(s[], [3]));
+}
+
+unittest
+{
+    auto s = SList!int(1, 2, 3, 4, 5, 6, 7);
+    assert(s.removeFront(3) == 3);
+    assert(s == SList!int(4, 5, 6, 7));
+}
+
+unittest
+{
+    auto a = SList!int(1, 2, 3);
+    auto b = SList!int(1, 2, 3);
+    assert(a.insertAfter(a[], b[]) == 3);
+}
+
+unittest
+{
+    import std.range : take;
+    auto s = SList!int(1, 2, 3, 4);
+    auto r = take(s[], 2);
+    assert(s.insertAfter(r, 5) == 1);
+    assert(s == SList!int(1, 2, 5, 3, 4));
+}
+
+unittest
+{
+    static import std.algorithm;
+    import std.range: take;
+
+    // insertAfter documentation example
+    auto sl = SList!string(["a", "b", "d"]);
+    sl.insertAfter(sl[], "e"); // insert at the end (slowest)
+    assert(std.algorithm.equal(sl[], ["a", "b", "d", "e"]));
+    sl.insertAfter(take(sl[], 2), "c"); // insert after "b"
+    assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"]));
+}
+
+unittest
+{
+    import std.range.primitives;
+    auto s = SList!int(1, 2, 3, 4, 5);
+    auto r = s[];
+    popFrontN(r, 3);
+    auto r1 = s.linearRemove(r);
+    assert(s == SList!int(1, 2, 3));
+    assert(r1.empty);
+}
+
+unittest
+{
+    auto s = SList!int(1, 2, 3, 4, 5);
+    auto r = s[];
+    auto r1 = s.linearRemove(r);
+    assert(s == SList!int());
+    assert(r1.empty);
+}
+
+unittest
+{
+    import std.algorithm : equal;
+    import std.range;
+
+    auto s = SList!int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    auto r = s[];
+    popFrontN(r, 3);
+    auto r1 = take(r, 4);
+    assert(equal(r1, [4, 5, 6, 7]));
+    auto r2 = s.linearRemove(r1);
+    assert(s == SList!int(1, 2, 3, 8, 9, 10));
+    assert(equal(r2, [8, 9, 10]));
+}
+
+unittest
+{
+    import std.range.primitives;
+    auto lst = SList!int(1, 5, 42, 9);
+    assert(!lst.empty);
+    assert(lst.front == 1);
+    assert(walkLength(lst[]) == 4);
+
+    auto lst2 = lst ~ [ 1, 2, 3 ];
+    assert(walkLength(lst2[]) == 7);
+
+    auto lst3 = lst ~ [ 7 ];
+    assert(walkLength(lst3[]) == 5);
+}
+
+unittest
+{
+    auto s = make!(SList!int)(1, 2, 3);
+}
+
+unittest
+{
+    // 5193
+    static struct Data
+    {
+        const int val;
+    }
+    SList!Data list;
+}
+
+unittest
+{
+    auto s = SList!int([1, 2, 3]);
+    s.front = 5; //test frontAssign
+    assert(s.front == 5);
+    auto r = s[];
+    r.front = 1; //test frontAssign
+    assert(r.front == 1);
+}
+
+unittest
+{
+    // issue 14920
+    SList!int s;
+    s.insertAfter(s[], 1);
+    assert(s.front == 1);
+}
+
+unittest
+{
+    // issue 15659
+    SList!int s;
+    s.clear();
+}
+
+unittest
+{
+    SList!int s;
+    s.reverse();
+}
+
+unittest
+{
+    import std.algorithm : equal;
+
+    auto s = SList!int([1, 2, 3]);
+    assert(s[].equal([1, 2, 3]));
+
+    s.reverse();
+    assert(s[].equal([3, 2, 1]));
+}
diff --git a/libphobos/src/std/container/util.d b/libphobos/src/std/container/util.d
new file mode 100644
index 00000000000..6e6d4a0fe3f
--- /dev/null
+++ b/libphobos/src/std/container/util.d
@@ -0,0 +1,181 @@
+/**
+This module contains some common utilities used by containers.
+
+This module is a submodule of $(LINK2 std_container.html, std.container).
+
+Source: $(PHOBOSSRC std/container/_util.d)
+Macros:
+WIKI = Phobos/StdContainer
+TEXTWITHCOMMAS = $0
+
+Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code
+copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
+
+License: Distributed under the Boost Software License, Version 1.0.
+(See accompanying file LICENSE_1_0.txt or copy at $(WEB
+boost.org/LICENSE_1_0.txt)).
+
+Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu)
+*/
+module std.container.util;
+
+/**
+Returns an initialized object. This function is mainly for eliminating
+construction differences between structs and classes. It allows code to not
+worry about whether the type it's constructing is a struct or a class.
+ */
+template make(T)
+if (is(T == struct) || is(T == class))
+{
+    T make(Args...)(Args arguments)
+    if (is(T == struct) && __traits(compiles, T(arguments)))
+    {
+        // constructing an std.container.Array without arguments,
+        // does not initialize its payload and is equivalent
+        // to a null reference. We therefore construct an empty container
+        // by passing an empty array to its constructor.
+        // Issue #13872.
+        static if(arguments.length == 0)
+        {
+            import std.range;
+            alias ET = ElementType!(T.Range);
+            return T(ET[].init);
+        }
+        else
+            return T(arguments);
+    }
+
+    T make(Args...)(Args arguments)
+    if (is(T == class) && __traits(compiles, new T(arguments)))
+    {
+        return new T(arguments);
+    }
+}
+
+
+///
+unittest
+{
+    import std.container;
+    import std.algorithm : equal;
+    import std.algorithm : equal;
+
+    auto arr = make!(Array!int)([4, 2, 3, 1]);
+    assert(equal(arr[], [4, 2, 3, 1]));
+
+    auto rbt = make!(RedBlackTree!(int, "a > b"))([4, 2, 3, 1]);
+    assert(equal(rbt[], [4, 3, 2, 1]));
+
+    alias makeList = make!(SList!int);
+    auto slist = makeList(1, 2, 3);
+    assert(equal(slist[], [1, 2, 3]));
+}
+
+unittest
+{
+    import std.container;
+    import std.algorithm : equal;
+
+    auto arr1 = make!(Array!dchar)();
+    assert(arr1.empty);
+    auto arr2 = make!(Array!dchar)("hello"d);
+    assert(equal(arr2[], "hello"d));
+
+    auto rtb1 = make!(RedBlackTree!dchar)();
+    assert(rtb1.empty);
+    auto rtb2 = make!(RedBlackTree!dchar)('h', 'e', 'l', 'l', 'o');
+    assert(equal(rtb2[], "ehlo"d));
+}
+
+// Issue 8895
+unittest
+{
+    import std.container;
+    import std.algorithm : equal;
+
+    auto a = make!(DList!int)(1,2,3,4);
+    auto b = make!(DList!int)(1,2,3,4);
+    auto c = make!(DList!int)(1,2,3,5);
+    auto d = make!(DList!int)(1,2,3,4,5);
+    assert(a == b); // this better terminate!
+    assert(a != c);
+    assert(a != d);
+}
+
+/**
+ * Convenience function for constructing a generic container.
+ */
+template make(alias Container, Args...)
+    if(!is(Container))
+{
+    import std.range : isInputRange;
+    import std.traits : isDynamicArray;
+
+    auto make(Range)(Range range)
+        if(!isDynamicArray!Range && isInputRange!Range)
+    {
+        import std.range : ElementType;
+        return .make!(Container!(ElementType!Range, Args))(range);
+    }
+
+    auto make(T)(T[] items...)
+    {
+        return .make!(Container!(T, Args))(items);
+    }
+}
+
+///
+unittest
+{
+    import std.container.array, std.container.rbtree, std.container.slist;
+    import std.range : iota;
+    import std.algorithm : equal;
+
+    auto arr = make!Array(iota(5));
+    assert(equal(arr[], [0, 1, 2, 3, 4]));
+
+    auto rbtmax = make!(RedBlackTree, "a > b")(iota(5));
+    assert(equal(rbtmax[], [4, 3, 2, 1, 0]));
+
+    auto rbtmin = make!RedBlackTree(4, 1, 3, 2);
+    assert(equal(rbtmin[], [1, 2, 3, 4]));
+
+    alias makeList = make!SList;
+    auto list = makeList(1, 7, 42);
+    assert(equal(list[], [1, 7, 42]));
+}
+
+unittest
+{
+    import std.container.rbtree;
+    import std.algorithm : equal;
+
+    auto rbtmin = make!(RedBlackTree, "a < b", false)(3, 2, 2, 1);
+    assert(equal(rbtmin[], [1, 2, 3]));
+}
+
+// Issue 13872
+unittest
+{
+    import std.container;
+
+    auto tree1 = make!(RedBlackTree!int)();
+    auto refToTree1 = tree1;
+    refToTree1.insert(1);
+    assert(1 in tree1);
+
+    auto array1 = make!(Array!int)();
+    auto refToArray1 = array1;
+    refToArray1.insertBack(1);
+    assert(!array1.empty);
+
+    auto slist = make!(SList!int)();
+    auto refToSlist = slist;
+    refToSlist.insert(1);
+    assert(!slist.empty);
+
+    auto dlist = make!(DList!int)();
+    auto refToDList = dlist;
+    refToDList.insert(1);
+    assert(!dlist.empty);
+}
diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d
new file mode 100644
index 00000000000..8f0b7ab29ea
--- /dev/null
+++ b/libphobos/src/std/conv.d
@@ -0,0 +1,5799 @@
+// Written in the D programming language.
+
+/**
+A one-stop shop for converting values from one type to another.
+
+Copyright: Copyright Digital Mars 2007-.
+
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   $(WEB digitalmars.com, Walter Bright),
+           $(WEB erdani.org, Andrei Alexandrescu),
+           Shin Fujishiro,
+           Adam D. Ruppe,
+           Kenji Hara
+
+Source:    $(PHOBOSSRC std/_conv.d)
+
+Macros:
+WIKI = Phobos/StdConv
+
+*/
+module std.conv;
+
+public import std.ascii : LetterCase;
+
+import std.meta;
+import std.range.primitives;
+import std.traits;
+
+// Same as std.string.format, but "self-importing".
+// Helps reduce code and imports, particularly in static asserts.
+// Also helps with missing imports errors.
+package template convFormat()
+{
+    import std.format : format;
+    alias convFormat = format;
+}
+
+/* ************* Exceptions *************** */
+
+/**
+ * Thrown on conversion errors.
+ */
+class ConvException : Exception
+{
+    @safe pure nothrow
+    this(string s, string fn = __FILE__, size_t ln = __LINE__)
+    {
+        super(s, fn, ln);
+    }
+}
+
+private string convError_unexpected(S)(S source)
+{
+    return source.empty ? "end of input" : text("'", source.front, "'");
+}
+
+private auto convError(S, T)(S source, string fn = __FILE__, size_t ln = __LINE__)
+{
+    return new ConvException(
+        text("Unexpected ", convError_unexpected(source),
+             " when converting from type "~S.stringof~" to type "~T.stringof),
+        fn, ln);
+}
+
+private auto convError(S, T)(S source, int radix, string fn = __FILE__, size_t ln = __LINE__)
+{
+    return new ConvException(
+        text("Unexpected ", convError_unexpected(source),
+             " when converting from type "~S.stringof~" base ", radix,
+             " to type "~T.stringof),
+        fn, ln);
+}
+
+@safe pure/* nothrow*/  // lazy parameter bug
+private auto parseError(lazy string msg, string fn = __FILE__, size_t ln = __LINE__)
+{
+    return new ConvException(text("Can't parse string: ", msg), fn, ln);
+}
+
+private void parseCheck(alias source)(dchar c, string fn = __FILE__, size_t ln = __LINE__)
+{
+    if (source.empty)
+        throw parseError(text("unexpected end of input when expecting", "\"", c, "\""));
+    if (source.front != c)
+        throw parseError(text("\"", c, "\" is missing"), fn, ln);
+    source.popFront();
+}
+
+private
+{
+    template isImaginary(T)
+    {
+        enum bool isImaginary = staticIndexOf!(Unqual!T,
+                ifloat, idouble, ireal) >= 0;
+    }
+    template isComplex(T)
+    {
+        enum bool isComplex = staticIndexOf!(Unqual!T,
+                cfloat, cdouble, creal) >= 0;
+    }
+    template isNarrowInteger(T)
+    {
+        enum bool isNarrowInteger = staticIndexOf!(Unqual!T,
+                byte, ubyte, short, ushort) >= 0;
+    }
+
+    T toStr(T, S)(S src)
+        if (isSomeString!T)
+    {
+        // workaround for Bugzilla 14198
+        static if (is(S == bool) && is(typeof({ T s = "string"; })))
+        {
+            return src ? "true" : "false";
+        }
+        else
+        {
+            import std.format : FormatSpec, formatValue;
+            import std.array : appender;
+
+            auto w = appender!T();
+            FormatSpec!(ElementEncodingType!T) f;
+            formatValue(w, src, f);
+            return w.data;
+        }
+    }
+
+    template isExactSomeString(T)
+    {
+        enum isExactSomeString = isSomeString!T && !is(T == enum);
+    }
+
+    template isEnumStrToStr(S, T)
+    {
+        enum isEnumStrToStr = isImplicitlyConvertible!(S, T) &&
+                              is(S == enum) && isExactSomeString!T;
+    }
+    template isNullToStr(S, T)
+    {
+        enum isNullToStr = isImplicitlyConvertible!(S, T) &&
+                           (is(Unqual!S == typeof(null))) && isExactSomeString!T;
+    }
+
+    template isRawStaticArray(T, A...)
+    {
+        enum isRawStaticArray =
+            A.length == 0 &&
+            isStaticArray!T &&
+            !is(T == class) &&
+            !is(T == interface) &&
+            !is(T == struct) &&
+            !is(T == union);
+    }
+}
+
+/**
+ * Thrown on conversion overflow errors.
+ */
+class ConvOverflowException : ConvException
+{
+    @safe pure nothrow
+    this(string s, string fn = __FILE__, size_t ln = __LINE__)
+    {
+        super(s, fn, ln);
+    }
+}
+
+/**
+
+The $(D_PARAM to) family of functions converts a value from type
+$(D_PARAM Source) to type $(D_PARAM Target). The source type is
+deduced and the target type must be specified, for example the
+expression $(D_PARAM to!int(42.0)) converts the number 42 from
+$(D_PARAM double) to $(D_PARAM int). The conversion is "safe", i.e.,
+it checks for overflow; $(D_PARAM to!int(4.2e10)) would throw the
+$(D_PARAM ConvOverflowException) exception. Overflow checks are only
+inserted when necessary, e.g., $(D_PARAM to!double(42)) does not do
+any checking because any int fits in a double.
+
+Converting a value to its own type (useful mostly for generic code)
+simply returns its argument.
+
+Example:
+-------------------------
+int a = 42;
+auto b = to!int(a); // b is int with value 42
+auto c = to!double(3.14); // c is double with value 3.14
+-------------------------
+
+Converting among numeric types is a safe way to cast them around.
+
+Conversions from floating-point types to integral types allow loss of
+precision (the fractional part of a floating-point number). The
+conversion is truncating towards zero, the same way a cast would
+truncate. (To round a floating point value when casting to an
+integral, use $(D_PARAM roundTo).)
+
+Example:
+-------------------------
+int a = 420;
+auto b = to!long(a); // same as long b = a;
+auto c = to!byte(a / 10); // fine, c = 42
+auto d = to!byte(a); // throw ConvOverflowException
+double e = 4.2e6;
+auto f = to!int(e); // f == 4200000
+e = -3.14;
+auto g = to!uint(e); // fails: floating-to-integral negative overflow
+e = 3.14;
+auto h = to!uint(e); // h = 3
+e = 3.99;
+h = to!uint(a); // h = 3
+e = -3.99;
+f = to!int(a); // f = -3
+-------------------------
+
+Conversions from integral types to floating-point types always
+succeed, but might lose accuracy. The largest integers with a
+predecessor representable in floating-point format are 2^24-1 for
+float, 2^53-1 for double, and 2^64-1 for $(D_PARAM real) (when
+$(D_PARAM real) is 80-bit, e.g. on Intel machines).
+
+Example:
+-------------------------
+int a = 16_777_215; // 2^24 - 1, largest proper integer representable as float
+assert(to!int(to!float(a)) == a);
+assert(to!int(to!float(-a)) == -a);
+a += 2;
+assert(to!int(to!float(a)) == a); // fails!
+-------------------------
+
+Conversions from string to numeric types differ from the C equivalents
+$(D_PARAM atoi()) and $(D_PARAM atol()) by checking for overflow and
+not allowing whitespace.
+
+For conversion of strings to signed types, the grammar recognized is:
+<pre>
+$(I Integer): $(I Sign UnsignedInteger)
+$(I UnsignedInteger)
+$(I Sign):
+    $(B +)
+    $(B -)
+</pre>
+
+For conversion to unsigned types, the grammar recognized is:
+<pre>
+$(I UnsignedInteger):
+    $(I DecimalDigit)
+    $(I DecimalDigit) $(I UnsignedInteger)
+</pre>
+
+Converting an array to another array type works by converting each
+element in turn. Associative arrays can be converted to associative
+arrays as long as keys and values can in turn be converted.
+
+Example:
+-------------------------
+int[] a = [1, 2, 3];
+auto b = to!(float[])(a);
+assert(b == [1.0f, 2, 3]);
+string str = "1 2 3 4 5 6";
+auto numbers = to!(double[])(split(str));
+assert(numbers == [1.0, 2, 3, 4, 5, 6]);
+int[string] c;
+c["a"] = 1;
+c["b"] = 2;
+auto d = to!(double[wstring])(c);
+assert(d["a"w] == 1 && d["b"w] == 2);
+-------------------------
+
+Conversions operate transitively, meaning that they work on arrays and
+associative arrays of any complexity:
+
+-------------------------
+int[string][double[int[]]] a;
+...
+auto b = to!(short[wstring][string[double[]]])(a);
+-------------------------
+
+This conversion works because $(D_PARAM to!short) applies to an
+$(D_PARAM int), $(D_PARAM to!wstring) applies to a $(D_PARAM
+string), $(D_PARAM to!string) applies to a $(D_PARAM double), and
+$(D_PARAM to!(double[])) applies to an $(D_PARAM int[]). The
+conversion might throw an exception because $(D_PARAM to!short)
+might fail the range check.
+
+ */
+
+/**
+   Entry point that dispatches to the appropriate conversion
+   primitive. Client code normally calls $(D _to!TargetType(value))
+   (and not some variant of $(D toImpl)).
+ */
+template to(T)
+{
+    T to(A...)(A args)
+        if (!isRawStaticArray!A)
+    {
+        return toImpl!T(args);
+    }
+
+    // Fix issue 6175
+    T to(S)(ref S arg)
+        if (isRawStaticArray!S)
+    {
+        return toImpl!T(arg);
+    }
+}
+
+// Tests for issue 6175
+@safe pure nothrow unittest
+{
+    char[9] sarr = "blablabla";
+    auto darr = to!(char[])(sarr);
+    assert(sarr.ptr == darr.ptr);
+    assert(sarr.length == darr.length);
+}
+
+// Tests for issue 7348
+@safe pure /+nothrow+/ unittest
+{
+    assert(to!string(null) == "null");
+    assert(text(null) == "null");
+}
+
+// Tests for issue 11390
+@safe pure /+nothrow+/ unittest
+{
+    const(typeof(null)) ctn;
+    immutable(typeof(null)) itn;
+    assert(to!string(ctn) == "null");
+    assert(to!string(itn) == "null");
+}
+
+// Tests for issue 8729: do NOT skip leading WS
+@safe pure unittest
+{
+    import std.exception;
+    foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        assertThrown!ConvException(to!T(" 0"));
+        assertThrown!ConvException(to!T(" 0", 8));
+    }
+    foreach (T; AliasSeq!(float, double, real))
+    {
+        assertThrown!ConvException(to!T(" 0"));
+    }
+
+    assertThrown!ConvException(to!bool(" true"));
+
+    alias NullType = typeof(null);
+    assertThrown!ConvException(to!NullType(" null"));
+
+    alias ARR = int[];
+    assertThrown!ConvException(to!ARR(" [1]"));
+
+    alias AA = int[int];
+    assertThrown!ConvException(to!AA(" [1:1]"));
+}
+
+/**
+If the source type is implicitly convertible to the target type, $(D
+to) simply performs the implicit conversion.
+ */
+T toImpl(T, S)(S value)
+    if (isImplicitlyConvertible!(S, T) &&
+        !isEnumStrToStr!(S, T) && !isNullToStr!(S, T))
+{
+    template isSignedInt(T)
+    {
+        enum isSignedInt = isIntegral!T && isSigned!T;
+    }
+    alias isUnsignedInt = isUnsigned;
+
+    // Conversion from integer to integer, and changing its sign
+    static if (isUnsignedInt!S && isSignedInt!T && S.sizeof == T.sizeof)
+    {   // unsigned to signed & same size
+        import std.exception : enforce;
+        enforce(value <= cast(S)T.max,
+                new ConvOverflowException("Conversion positive overflow"));
+    }
+    else static if (isSignedInt!S && isUnsignedInt!T)
+    {   // signed to unsigned
+        import std.exception : enforce;
+        enforce(0 <= value,
+                new ConvOverflowException("Conversion negative overflow"));
+    }
+
+    return value;
+}
+
+@safe pure nothrow unittest
+{
+    enum E { a }  // Issue 9523 - Allow identity enum conversion
+    auto e = to!E(E.a);
+    assert(e == E.a);
+}
+
+@safe pure nothrow unittest
+{
+    int a = 42;
+    auto b = to!long(a);
+    assert(a == b);
+}
+
+// Tests for issue 6377
+@safe pure unittest
+{
+    import std.exception;
+    // Conversion between same size
+    foreach (S; AliasSeq!(byte, short, int, long))
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        alias U = Unsigned!S;
+
+        foreach (Sint; AliasSeq!(S, const S, immutable S))
+        foreach (Uint; AliasSeq!(U, const U, immutable U))
+        {
+            // positive overflow
+            Uint un = Uint.max;
+            assertThrown!ConvOverflowException(to!Sint(un),
+                text(Sint.stringof, ' ', Uint.stringof, ' ', un));
+
+            // negative overflow
+            Sint sn = -1;
+            assertThrown!ConvOverflowException(to!Uint(sn),
+                text(Sint.stringof, ' ', Uint.stringof, ' ', un));
+        }
+    }();
+
+    // Conversion between different size
+    foreach (i, S1; AliasSeq!(byte, short, int, long))
+    foreach (   S2; AliasSeq!(byte, short, int, long)[i+1..$])
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        alias U1 = Unsigned!S1;
+        alias U2 = Unsigned!S2;
+
+        static assert(U1.sizeof < S2.sizeof);
+
+        // small unsigned to big signed
+        foreach (Uint; AliasSeq!(U1, const U1, immutable U1))
+        foreach (Sint; AliasSeq!(S2, const S2, immutable S2))
+        {
+            Uint un = Uint.max;
+            assertNotThrown(to!Sint(un));
+            assert(to!Sint(un) == un);
+        }
+
+        // big unsigned to small signed
+        foreach (Uint; AliasSeq!(U2, const U2, immutable U2))
+        foreach (Sint; AliasSeq!(S1, const S1, immutable S1))
+        {
+            Uint un = Uint.max;
+            assertThrown(to!Sint(un));
+        }
+
+        static assert(S1.sizeof < U2.sizeof);
+
+        // small signed to big unsigned
+        foreach (Sint; AliasSeq!(S1, const S1, immutable S1))
+        foreach (Uint; AliasSeq!(U2, const U2, immutable U2))
+        {
+            Sint sn = -1;
+            assertThrown!ConvOverflowException(to!Uint(sn));
+        }
+
+        // big signed to small unsigned
+        foreach (Sint; AliasSeq!(S2, const S2, immutable S2))
+        foreach (Uint; AliasSeq!(U1, const U1, immutable U1))
+        {
+            Sint sn = -1;
+            assertThrown!ConvOverflowException(to!Uint(sn));
+        }
+    }();
+}
+
+/*
+  Converting static arrays forwards to their dynamic counterparts.
+ */
+T toImpl(T, S)(ref S s)
+    if (isRawStaticArray!S)
+{
+    return toImpl!(T, typeof(s[0])[])(s);
+}
+
+@safe pure nothrow unittest
+{
+    char[4] test = ['a', 'b', 'c', 'd'];
+    static assert(!isInputRange!(Unqual!(char[4])));
+    assert(to!string(test) == test);
+}
+
+/**
+When source type supports member template function opCast, it is used.
+*/
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        is(typeof(S.init.opCast!T()) : T) &&
+        !isExactSomeString!T &&
+        !is(typeof(T(value))))
+{
+    return value.opCast!T();
+}
+
+@safe pure unittest
+{
+    static struct Test
+    {
+        struct T
+        {
+            this(S s) @safe pure { }
+        }
+        struct S
+        {
+            T opCast(U)() @safe pure { assert(false); }
+        }
+    }
+    to!(Test.T)(Test.S());
+
+    // make sure std.conv.to is doing the same thing as initialization
+    Test.S s;
+    Test.T t = s;
+}
+
+@safe pure unittest
+{
+    class B
+    {
+        T opCast(T)() { return 43; }
+    }
+    auto b = new B;
+    assert(to!int(b) == 43);
+
+    struct S
+    {
+        T opCast(T)() { return 43; }
+    }
+    auto s = S();
+    assert(to!int(s) == 43);
+}
+
+/**
+When target type supports 'converting construction', it is used.
+$(UL $(LI If target type is struct, $(D T(value)) is used.)
+     $(LI If target type is class, $(D new T(value)) is used.))
+*/
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        is(T == struct) && is(typeof(T(value))))
+{
+    return T(value);
+}
+
+// Bugzilla 3961
+@safe pure unittest
+{
+    struct Int
+    {
+        int x;
+    }
+    Int i = to!Int(1);
+
+    static struct Int2
+    {
+        int x;
+        this(int x) @safe pure { this.x = x; }
+    }
+    Int2 i2 = to!Int2(1);
+
+    static struct Int3
+    {
+        int x;
+        static Int3 opCall(int x) @safe pure
+        {
+            Int3 i;
+            i.x = x;
+            return i;
+        }
+    }
+    Int3 i3 = to!Int3(1);
+}
+
+// Bugzilla 6808
+@safe pure unittest
+{
+    static struct FakeBigInt
+    {
+        this(string s) @safe pure {}
+    }
+
+    string s = "101";
+    auto i3 = to!FakeBigInt(s);
+}
+
+/// ditto
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        is(T == class) && is(typeof(new T(value))))
+{
+    return new T(value);
+}
+
+@safe pure unittest
+{
+    static struct S
+    {
+        int x;
+    }
+    static class C
+    {
+        int x;
+        this(int x) @safe pure { this.x = x; }
+    }
+
+    static class B
+    {
+        int value;
+        this(S src) @safe pure { value = src.x; }
+        this(C src) @safe pure { value = src.x; }
+    }
+
+    S s = S(1);
+    auto b1 = to!B(s);  // == new B(s)
+    assert(b1.value == 1);
+
+    C c = new C(2);
+    auto b2 = to!B(c);  // == new B(c)
+    assert(b2.value == 2);
+
+    auto c2 = to!C(3);   // == new C(3)
+    assert(c2.x == 3);
+}
+
+@safe pure unittest
+{
+    struct S
+    {
+        class A
+        {
+            this(B b) @safe pure {}
+        }
+        class B : A
+        {
+            this() @safe pure { super(this); }
+        }
+    }
+
+    S.B b = new S.B();
+    S.A a = to!(S.A)(b);      // == cast(S.A)b
+                              // (do not run construction conversion like new S.A(b))
+    assert(b is a);
+
+    static class C : Object
+    {
+        this() @safe pure {}
+        this(Object o) @safe pure {}
+    }
+
+    Object oc = new C();
+    C a2 = to!C(oc);    // == new C(a)
+                        // Construction conversion overrides down-casting conversion
+    assert(a2 !is a);   //
+}
+
+/**
+Object-to-object conversions by dynamic casting throw exception when the source is
+non-null and the target is null.
+ */
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        (is(S == class) || is(S == interface)) && !is(typeof(value.opCast!T()) : T) &&
+        (is(T == class) || is(T == interface)) && !is(typeof(new T(value))))
+{
+    static if (is(T == immutable))
+    {
+            // immutable <- immutable
+            enum isModConvertible = is(S == immutable);
+    }
+    else static if (is(T == const))
+    {
+        static if (is(T == shared))
+        {
+            // shared const <- shared
+            // shared const <- shared const
+            // shared const <- immutable
+            enum isModConvertible = is(S == shared) || is(S == immutable);
+        }
+        else
+        {
+            // const <- mutable
+            // const <- immutable
+            enum isModConvertible = !is(S == shared);
+        }
+    }
+    else
+    {
+        static if (is(T == shared))
+        {
+            // shared <- shared mutable
+            enum isModConvertible = is(S == shared) && !is(S == const);
+        }
+        else
+        {
+            // (mutable) <- (mutable)
+            enum isModConvertible = is(Unqual!S == S);
+        }
+    }
+    static assert(isModConvertible, "Bad modifier conversion: "~S.stringof~" to "~T.stringof);
+
+    auto result = ()@trusted{ return cast(T) value; }();
+    if (!result && value)
+    {
+        throw new ConvException("Cannot convert object of static type "
+                ~S.classinfo.name~" and dynamic type "~value.classinfo.name
+                ~" to type "~T.classinfo.name);
+    }
+    return result;
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    // Testing object conversions
+    class A {}
+    class B : A {}
+    class C : A {}
+    A a1 = new A, a2 = new B, a3 = new C;
+    assert(to!B(a2) is a2);
+    assert(to!C(a3) is a3);
+    assertThrown!ConvException(to!B(a3));
+}
+
+// Unittest for 6288
+@safe pure unittest
+{
+    import std.exception;
+
+    alias Identity(T)      =              T;
+    alias toConst(T)       =        const T;
+    alias toShared(T)      =       shared T;
+    alias toSharedConst(T) = shared const T;
+    alias toImmutable(T)   =    immutable T;
+    template AddModifier(int n) if (0 <= n && n < 5)
+    {
+             static if (n == 0) alias AddModifier = Identity;
+        else static if (n == 1) alias AddModifier = toConst;
+        else static if (n == 2) alias AddModifier = toShared;
+        else static if (n == 3) alias AddModifier = toSharedConst;
+        else static if (n == 4) alias AddModifier = toImmutable;
+    }
+
+    interface I {}
+    interface J {}
+
+    class A {}
+    class B : A {}
+    class C : B, I, J {}
+    class D : I {}
+
+    foreach (m1; AliasSeq!(0,1,2,3,4)) // enumerate modifiers
+    foreach (m2; AliasSeq!(0,1,2,3,4)) // ditto
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        alias srcmod = AddModifier!m1;
+        alias tgtmod = AddModifier!m2;
+        //pragma(msg, srcmod!Object, " -> ", tgtmod!Object, ", convertible = ",
+        //            isImplicitlyConvertible!(srcmod!Object, tgtmod!Object));
+
+        // Compile time convertible equals to modifier convertible.
+        static if (isImplicitlyConvertible!(srcmod!Object, tgtmod!Object))
+        {
+            // Test runtime conversions: class to class, class to interface,
+            // interface to class, and interface to interface
+
+            // Check that the runtime conversion to succeed
+            srcmod!A ac = new srcmod!C();
+            srcmod!I ic = new srcmod!C();
+            assert(to!(tgtmod!C)(ac) !is null); // A(c) to C
+            assert(to!(tgtmod!I)(ac) !is null); // A(c) to I
+            assert(to!(tgtmod!C)(ic) !is null); // I(c) to C
+            assert(to!(tgtmod!J)(ic) !is null); // I(c) to J
+
+            // Check that the runtime conversion fails
+            srcmod!A ab = new srcmod!B();
+            srcmod!I id = new srcmod!D();
+            assertThrown(to!(tgtmod!C)(ab));    // A(b) to C
+            assertThrown(to!(tgtmod!I)(ab));    // A(b) to I
+            assertThrown(to!(tgtmod!C)(id));    // I(d) to C
+            assertThrown(to!(tgtmod!J)(id));    // I(d) to J
+        }
+        else
+        {
+            // Check that the conversion is rejected statically
+            static assert(!is(typeof(to!(tgtmod!C)(srcmod!A.init))));   // A to C
+            static assert(!is(typeof(to!(tgtmod!I)(srcmod!A.init))));   // A to I
+            static assert(!is(typeof(to!(tgtmod!C)(srcmod!I.init))));   // I to C
+            static assert(!is(typeof(to!(tgtmod!J)(srcmod!I.init))));   // I to J
+        }
+    }();
+}
+
+/**
+Stringize conversion from all types is supported.
+$(UL
+  $(LI String _to string conversion works for any two string types having
+       ($(D char), $(D wchar), $(D dchar)) character widths and any
+       combination of qualifiers (mutable, $(D const), or $(D immutable)).)
+  $(LI Converts array (other than strings) to string.
+       Each element is converted by calling $(D to!T).)
+  $(LI Associative array to string conversion.
+       Each element is printed by calling $(D to!T).)
+  $(LI Object to string conversion calls $(D toString) against the object or
+       returns $(D "null") if the object is null.)
+  $(LI Struct to string conversion calls $(D toString) against the struct if
+       it is defined.)
+  $(LI For structs that do not define $(D toString), the conversion to string
+       produces the list of fields.)
+  $(LI Enumerated types are converted to strings as their symbolic names.)
+  $(LI Boolean values are printed as $(D "true") or $(D "false").)
+  $(LI $(D char), $(D wchar), $(D dchar) to a string type.)
+  $(LI Unsigned or signed integers to strings.
+       $(DL $(DT [special case])
+            $(DD Convert integral value to string in $(D_PARAM radix) radix.
+            radix must be a value from 2 to 36.
+            value is treated as a signed value only if radix is 10.
+            The characters A through Z are used to represent values 10 through 36
+            and their case is determined by the $(D_PARAM letterCase) parameter.)))
+  $(LI All floating point types to all string types.)
+  $(LI Pointer to string conversions prints the pointer as a $(D size_t) value.
+       If pointer is $(D char*), treat it as C-style strings.
+       In that case, this function is $(D @system).))
+*/
+T toImpl(T, S)(S value)
+    if (!(isImplicitlyConvertible!(S, T) &&
+          !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) &&
+        !isInfinite!S && isExactSomeString!T)
+{
+    static if (isExactSomeString!S && value[0].sizeof == ElementEncodingType!T.sizeof)
+    {
+        // string-to-string with incompatible qualifier conversion
+        static if (is(ElementEncodingType!T == immutable))
+        {
+            // conversion (mutable|const) -> immutable
+            return value.idup;
+        }
+        else
+        {
+            // conversion (immutable|const) -> mutable
+            return value.dup;
+        }
+    }
+    else static if (isExactSomeString!S)
+    {
+        import std.array : appender;
+        // other string-to-string
+        //Use Appender directly instead of toStr, which also uses a formatedWrite
+        auto w = appender!T();
+        w.put(value);
+        return w.data;
+    }
+    else static if (isIntegral!S && !is(S == enum))
+    {
+        // other integral-to-string conversions with default radix
+        return toImpl!(T, S)(value, 10);
+    }
+    else static if (is(S == void[]) || is(S == const(void)[]) || is(S == immutable(void)[]))
+    {
+        import core.stdc.string : memcpy;
+        import std.exception : enforce;
+        // Converting void array to string
+        alias Char = Unqual!(ElementEncodingType!T);
+        auto raw = cast(const(ubyte)[]) value;
+        enforce(raw.length % Char.sizeof == 0,
+                new ConvException("Alignment mismatch in converting a "
+                        ~ S.stringof ~ " to a "
+                        ~ T.stringof));
+        auto result = new Char[raw.length / Char.sizeof];
+        ()@trusted{ memcpy(result.ptr, value.ptr, value.length); }();
+        return cast(T) result;
+    }
+    else static if (isPointer!S && is(S : const(char)*))
+    {
+        import core.stdc.string : strlen;
+        // It is unsafe because we cannot guarantee that the pointer is null terminated.
+        return value ? cast(T) value[0 .. strlen(value)].dup : null;
+    }
+    else static if (isSomeString!T && is(S == enum))
+    {
+        static if (isSwitchable!(OriginalType!S) && EnumMembers!S.length <= 50)
+        {
+            switch(value)
+            {
+                foreach (member; NoDuplicates!(EnumMembers!S))
+                {
+                    case member:
+                        return to!T(enumRep!(immutable(T), S, member));
+                }
+                default:
+            }
+        }
+        else
+        {
+            foreach (member; EnumMembers!S)
+            {
+                if (value == member)
+                    return to!T(enumRep!(immutable(T), S, member));
+            }
+        }
+
+        import std.format : FormatSpec, formatValue;
+        import std.array : appender;
+
+        //Default case, delegate to format
+        //Note: we don't call toStr directly, to avoid duplicate work.
+        auto app = appender!T();
+        app.put("cast(");
+        app.put(S.stringof);
+        app.put(')');
+        FormatSpec!char f;
+        formatValue(app, cast(OriginalType!S)value, f);
+        return app.data;
+    }
+    else
+    {
+        // other non-string values runs formatting
+        return toStr!T(value);
+    }
+}
+
+// Bugzilla 14042
+unittest
+{
+    immutable(char)* ptr = "hello".ptr;
+    auto result = ptr.to!(char[]);
+}
+
+/*
+    Check whether type $(D T) can be used in a switch statement.
+    This is useful for compile-time generation of switch case statements.
+*/
+private template isSwitchable(E)
+{
+    enum bool isSwitchable = is(typeof({
+        switch (E.init) { default: }
+    }));
+}
+
+//
+unittest
+{
+    static assert(isSwitchable!int);
+    static assert(!isSwitchable!double);
+    static assert(!isSwitchable!real);
+}
+
+//Static representation of the index I of the enum S,
+//In representation T.
+//T must be an immutable string (avoids un-necessary initializations).
+private template enumRep(T, S, S value)
+if (is (T == immutable) && isExactSomeString!T && is(S == enum))
+{
+    static T enumRep = toStr!T(value);
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    void dg()
+    {
+        // string to string conversion
+        alias Chars = AliasSeq!(char, wchar, dchar);
+        foreach (LhsC; Chars)
+        {
+            alias LhStrings = AliasSeq!(LhsC[], const(LhsC)[], immutable(LhsC)[]);
+            foreach (Lhs; LhStrings)
+            {
+                foreach (RhsC; Chars)
+                {
+                    alias RhStrings = AliasSeq!(RhsC[], const(RhsC)[], immutable(RhsC)[]);
+                    foreach (Rhs; RhStrings)
+                    {
+                        Lhs s1 = to!Lhs("wyda");
+                        Rhs s2 = to!Rhs(s1);
+                        //writeln(Lhs.stringof, " -> ", Rhs.stringof);
+                        assert(s1 == to!Lhs(s2));
+                    }
+                }
+            }
+        }
+
+        foreach (T; Chars)
+        {
+            foreach (U; Chars)
+            {
+                T[] s1 = to!(T[])("Hello, world!");
+                auto s2 = to!(U[])(s1);
+                assert(s1 == to!(T[])(s2));
+                auto s3 = to!(const(U)[])(s1);
+                assert(s1 == to!(T[])(s3));
+                auto s4 = to!(immutable(U)[])(s1);
+                assert(s1 == to!(T[])(s4));
+            }
+        }
+    }
+    dg();
+    assertCTFEable!dg;
+}
+
+@safe pure unittest
+{
+    // Conversion reinterpreting void array to string
+    auto a = "abcx"w;
+    const(void)[] b = a;
+    assert(b.length == 8);
+
+    auto c = to!(wchar[])(b);
+    assert(c == "abcx");
+}
+
+@system pure nothrow unittest
+{
+    // char* to string conversion
+    assert(to!string(cast(char*) null) == "");
+    assert(to!string("foo\0".ptr) == "foo");
+}
+
+@safe pure /+nothrow+/ unittest
+{
+    // Conversion representing bool value with string
+    bool b;
+    assert(to!string(b) == "false");
+    b = true;
+    assert(to!string(b) == "true");
+}
+
+@safe pure unittest
+{
+    // Conversion representing character value with string
+    alias AllChars =
+        AliasSeq!( char, const( char), immutable( char),
+                  wchar, const(wchar), immutable(wchar),
+                  dchar, const(dchar), immutable(dchar));
+    foreach (Char1; AllChars)
+    {
+        foreach (Char2; AllChars)
+        {
+            Char1 c = 'a';
+            assert(to!(Char2[])(c)[0] == c);
+        }
+        uint x = 4;
+        assert(to!(Char1[])(x) == "4");
+    }
+
+    string s = "foo";
+    string s2;
+    foreach (char c; s)
+    {
+        s2 ~= to!string(c);
+    }
+    //printf("%.*s", s2);
+    assert(s2 == "foo");
+}
+
+@safe pure nothrow unittest
+{
+    import std.exception;
+    // Conversion representing integer values with string
+
+    foreach (Int; AliasSeq!(ubyte, ushort, uint, ulong))
+    {
+        assert(to!string(Int(0)) == "0");
+        assert(to!string(Int(9)) == "9");
+        assert(to!string(Int(123)) == "123");
+    }
+
+    foreach (Int; AliasSeq!(byte, short, int, long))
+    {
+        assert(to!string(Int(0)) == "0");
+        assert(to!string(Int(9)) == "9");
+        assert(to!string(Int(123)) == "123");
+        assert(to!string(Int(-0)) == "0");
+        assert(to!string(Int(-9)) == "-9");
+        assert(to!string(Int(-123)) == "-123");
+        assert(to!string(const(Int)(6)) == "6");
+    }
+
+    assert(wtext(int.max) == "2147483647"w);
+    assert(wtext(int.min) == "-2147483648"w);
+    assert(to!string(0L) == "0");
+
+    assertCTFEable!(
+    {
+        assert(to!string(1uL << 62) == "4611686018427387904");
+        assert(to!string(0x100000000) == "4294967296");
+        assert(to!string(-138L) == "-138");
+    });
+}
+
+@safe pure /+nothrow+/ unittest
+{
+    // Conversion representing dynamic/static array with string
+    long[] b = [ 1, 3, 5 ];
+    auto s = to!string(b);
+    assert(to!string(b) == "[1, 3, 5]", s);
+}
+/*@safe pure */unittest // sprintf issue
+{
+    double[2] a = [ 1.5, 2.5 ];
+    assert(to!string(a) == "[1.5, 2.5]");
+}
+
+/*@safe pure */unittest
+{
+    // Conversion representing associative array with string
+    int[string] a = ["0":1, "1":2];
+    assert(to!string(a) == `["0":1, "1":2]` ||
+           to!string(a) == `["1":2, "0":1]`);
+}
+
+unittest
+{
+    // Conversion representing class object with string
+    class A
+    {
+        override string toString() const { return "an A"; }
+    }
+    A a;
+    assert(to!string(a) == "null");
+    a = new A;
+    assert(to!string(a) == "an A");
+
+    // Bug 7660
+    class C { override string toString() const { return "C"; } }
+    struct S { C c; alias c this; }
+    S s; s.c = new C();
+    assert(to!string(s) == "C");
+}
+
+unittest
+{
+    // Conversion representing struct object with string
+    struct S1
+    {
+        string toString() { return "wyda"; }
+    }
+    assert(to!string(S1()) == "wyda");
+
+    struct S2
+    {
+        int a = 42;
+        float b = 43.5;
+    }
+    S2 s2;
+    assert(to!string(s2) == "S2(42, 43.5)");
+
+    // Test for issue 8080
+    struct S8080
+    {
+        short[4] data;
+        alias data this;
+        string toString() { return "<S>"; }
+    }
+    S8080 s8080;
+    assert(to!string(s8080) == "<S>");
+}
+
+/+nothrow+/ unittest
+{
+    // Conversion representing enum value with string
+    enum EB : bool { a = true }
+    enum EU : uint { a = 0, b = 1, c = 2 }  // base type is unsigned
+    enum EI : int { a = -1, b = 0, c = 1 }  // base type is signed (bug 7909)
+    enum EF : real { a = 1.414, b = 1.732, c = 2.236 }
+    enum EC : char { a = 'x', b = 'y' }
+    enum ES : string { a = "aaa", b = "bbb" }
+
+    foreach (E; AliasSeq!(EB, EU, EI, EF, EC, ES))
+    {
+        assert(to! string(E.a) == "a"c);
+        assert(to!wstring(E.a) == "a"w);
+        assert(to!dstring(E.a) == "a"d);
+    }
+
+    // Test an value not corresponding to an enum member.
+    auto o = cast(EU)5;
+    assert(to! string(o) == "cast(EU)5"c);
+    assert(to!wstring(o) == "cast(EU)5"w);
+    assert(to!dstring(o) == "cast(EU)5"d);
+}
+
+unittest
+{
+    enum E
+    {
+        foo,
+        doo = foo, // check duplicate switch statements
+        bar,
+    }
+
+    //Test regression 12494
+    assert(to!string(E.foo) == "foo");
+    assert(to!string(E.doo) == "foo");
+    assert(to!string(E.bar) == "bar");
+
+    foreach (S; AliasSeq!(string, wstring, dstring, const(char[]), const(wchar[]), const(dchar[])))
+    {
+        auto s1 = to!S(E.foo);
+        auto s2 = to!S(E.foo);
+        assert(s1 == s2);
+        // ensure we don't allocate when it's unnecessary
+        assert(s1 is s2);
+    }
+
+    foreach (S; AliasSeq!(char[], wchar[], dchar[]))
+    {
+        auto s1 = to!S(E.foo);
+        auto s2 = to!S(E.foo);
+        assert(s1 == s2);
+        // ensure each mutable array is unique
+        assert(s1 !is s2);
+    }
+}
+
+/// ditto
+@trusted pure T toImpl(T, S)(S value, uint radix, LetterCase letterCase = LetterCase.upper)
+    if (isIntegral!S &&
+        isExactSomeString!T)
+in
+{
+    assert(radix >= 2 && radix <= 36);
+}
+body
+{
+    alias EEType = Unqual!(ElementEncodingType!T);
+
+    T toStringRadixConvert(size_t bufLen)(uint runtimeRadix = 0)
+    {
+        Unsigned!(Unqual!S) div = void, mValue = unsigned(value);
+
+        size_t index = bufLen;
+        EEType[bufLen] buffer = void;
+        char baseChar = letterCase == LetterCase.lower ? 'a' : 'A';
+        char mod = void;
+
+        do
+        {
+            div = cast(S)(mValue / runtimeRadix );
+            mod = cast(ubyte)(mValue % runtimeRadix);
+            mod += mod < 10 ? '0' : baseChar - 10;
+            buffer[--index] = cast(char)mod;
+            mValue = div;
+        } while (mValue);
+
+        return cast(T)buffer[index .. $].dup;
+    }
+
+    import std.array;
+    switch(radix)
+    {
+        case 10:
+            // The (value+0) is so integral promotions happen to the type
+            return toChars!(10, EEType)(value + 0).array;
+        case 16:
+            // The unsigned(unsigned(value)+0) is so unsigned integral promotions happen to the type
+            if (letterCase == letterCase.upper)
+                return toChars!(16, EEType, LetterCase.upper)(unsigned(unsigned(value) + 0)).array;
+            else
+                return toChars!(16, EEType, LetterCase.lower)(unsigned(unsigned(value) + 0)).array;
+        case 2:
+            return toChars!(2, EEType)(unsigned(unsigned(value) + 0)).array;
+        case 8:
+            return toChars!(8, EEType)(unsigned(unsigned(value) + 0)).array;
+
+        default:
+            return toStringRadixConvert!(S.sizeof * 6)(radix);
+    }
+}
+
+@safe pure nothrow unittest
+{
+    foreach (Int; AliasSeq!(uint, ulong))
+    {
+        assert(to!string(Int(16), 16) == "10");
+        assert(to!string(Int(15), 2u) == "1111");
+        assert(to!string(Int(1), 2u) == "1");
+        assert(to!string(Int(0x1234AF), 16u) == "1234AF");
+        assert(to!string(Int(0x1234BCD), 16u, LetterCase.upper) == "1234BCD");
+        assert(to!string(Int(0x1234AF), 16u, LetterCase.lower) == "1234af");
+    }
+
+    foreach (Int; AliasSeq!(int, long))
+    {
+        assert(to!string(Int(-10), 10u) == "-10");
+    }
+
+    assert(to!string(byte(-10), 16) == "F6");
+    assert(to!string(long.min) == "-9223372036854775808");
+    assert(to!string(long.max) == "9223372036854775807");
+}
+
+/**
+Narrowing numeric-numeric conversions throw when the value does not
+fit in the narrower type.
+ */
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        (isNumeric!S || isSomeChar!S || isBoolean!S) &&
+        (isNumeric!T || isSomeChar!T || isBoolean!T) && !is(T == enum))
+{
+    enum sSmallest = mostNegative!S;
+    enum tSmallest = mostNegative!T;
+    static if (sSmallest < 0)
+    {
+        // possible underflow converting from a signed
+        static if (tSmallest == 0)
+        {
+            immutable good = value >= 0;
+        }
+        else
+        {
+            static assert(tSmallest < 0);
+            immutable good = value >= tSmallest;
+        }
+        if (!good)
+            throw new ConvOverflowException("Conversion negative overflow");
+    }
+    static if (S.max > T.max)
+    {
+        // possible overflow
+        if (value > T.max)
+            throw new ConvOverflowException("Conversion positive overflow");
+    }
+    return (ref value)@trusted{ return cast(T) value; }(value);
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    dchar a = ' ';
+    assert(to!char(a) == ' ');
+    a = 300;
+    assert(collectException(to!char(a)));
+
+    dchar from0 = 'A';
+    char to0 = to!char(from0);
+
+    wchar from1 = 'A';
+    char to1 = to!char(from1);
+
+    char from2 = 'A';
+    char to2 = to!char(from2);
+
+    char from3 = 'A';
+    wchar to3 = to!wchar(from3);
+
+    char from4 = 'A';
+    dchar to4 = to!dchar(from4);
+}
+
+unittest
+{
+    import std.exception;
+
+    // Narrowing conversions from enum -> integral should be allowed, but they
+    // should throw at runtime if the enum value doesn't fit in the target
+    // type.
+    enum E1 : ulong { A = 1, B = 1UL<<48, C = 0 }
+    assert(to!int(E1.A) == 1);
+    assert(to!bool(E1.A) == true);
+    assertThrown!ConvOverflowException(to!int(E1.B)); // E1.B overflows int
+    assertThrown!ConvOverflowException(to!bool(E1.B)); // E1.B overflows bool
+    assert(to!bool(E1.C) == false);
+
+    enum E2 : long { A = -1L<<48, B = -1<<31, C = 1<<31 }
+    assertThrown!ConvOverflowException(to!int(E2.A)); // E2.A overflows int
+    assertThrown!ConvOverflowException(to!uint(E2.B)); // E2.B overflows uint
+    assert(to!int(E2.B) == -1<<31); // but does not overflow int
+    assert(to!int(E2.C) == 1<<31);  // E2.C does not overflow int
+
+    enum E3 : int { A = -1, B = 1, C = 255, D = 0 }
+    assertThrown!ConvOverflowException(to!ubyte(E3.A));
+    assertThrown!ConvOverflowException(to!bool(E3.A));
+    assert(to!byte(E3.A) == -1);
+    assert(to!byte(E3.B) == 1);
+    assert(to!ubyte(E3.C) == 255);
+    assert(to!bool(E3.B) == true);
+    assertThrown!ConvOverflowException(to!byte(E3.C));
+    assertThrown!ConvOverflowException(to!bool(E3.C));
+    assert(to!bool(E3.D) == false);
+
+}
+
+/**
+Array-to-array conversion (except when target is a string type)
+converts each element in turn by using $(D to).
+ */
+T toImpl(T, S)(S value)
+    if (!isImplicitlyConvertible!(S, T) &&
+        !isSomeString!S && isDynamicArray!S &&
+        !isExactSomeString!T && isArray!T)
+{
+    alias E = typeof(T.init[0]);
+
+    static if (isStaticArray!T)
+    {
+        import std.exception : enforce;
+        auto res = to!(E[])(value);
+        enforce!ConvException(T.length == res.length,
+            convFormat("Length mismatch when converting to static array: %s vs %s", T.length, res.length));
+        return res[0 .. T.length];
+    }
+    else
+    {
+        import std.array : appender;
+        auto w = appender!(E[])();
+        w.reserve(value.length);
+        foreach (i, ref e; value)
+        {
+            w.put(to!E(e));
+        }
+        return w.data;
+    }
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    // array to array conversions
+    uint[] a = [ 1u, 2, 3 ];
+    auto b = to!(float[])(a);
+    assert(b == [ 1.0f, 2, 3 ]);
+
+    //auto c = to!(string[])(b);
+    //assert(c[0] == "1" && c[1] == "2" && c[2] == "3");
+
+    immutable(int)[3] d = [ 1, 2, 3 ];
+    b = to!(float[])(d);
+    assert(b == [ 1.0f, 2, 3 ]);
+
+    uint[][] e = [ a, a ];
+    auto f = to!(float[][])(e);
+    assert(f[0] == b && f[1] == b);
+
+    // Test for bug 8264
+    struct Wrap
+    {
+        string wrap;
+        alias wrap this;
+    }
+    Wrap[] warr = to!(Wrap[])(["foo", "bar"]);  // should work
+
+    // Issue 12633
+    import std.conv : to;
+    const s2 = ["10", "20"];
+
+    immutable int[2] a3 = s2.to!(int[2]);
+    assert(a3 == [10, 20]);
+
+    // verify length mismatches are caught
+    immutable s4 = [1, 2, 3, 4];
+    foreach (i; [1, 4])
+    {
+        auto ex = collectException(s4[0 .. i].to!(int[2]));
+            assert(ex && ex.msg == "Length mismatch when converting to static array: 2 vs " ~ [cast(char)(i + '0')],
+                ex ? ex.msg : "Exception was not thrown!");
+    }
+}
+/*@safe pure */unittest
+{
+    auto b = [ 1.0f, 2, 3 ];
+
+    auto c = to!(string[])(b);
+    assert(c[0] == "1" && c[1] == "2" && c[2] == "3");
+}
+
+/**
+Associative array to associative array conversion converts each key
+and each value in turn.
+ */
+T toImpl(T, S)(S value)
+    if (isAssociativeArray!S &&
+        isAssociativeArray!T && !is(T == enum))
+{
+    /* This code is potentially unsafe.
+     */
+    alias K2 = KeyType!T;
+    alias V2 = ValueType!T;
+
+    // While we are "building" the AA, we need to unqualify its values, and only re-qualify at the end
+    Unqual!V2[K2] result;
+
+    foreach (k1, v1; value)
+    {
+        // Cast values temporarily to Unqual!V2 to store them to result variable
+        result[to!K2(k1)] = cast(Unqual!V2) to!V2(v1);
+    }
+    // Cast back to original type
+    return cast(T)result;
+}
+
+@safe /*pure */unittest
+{
+    // hash to hash conversions
+    int[string] a;
+    a["0"] = 1;
+    a["1"] = 2;
+    auto b = to!(double[dstring])(a);
+    assert(b["0"d] == 1 && b["1"d] == 2);
+}
+@safe /*pure */unittest // Bugzilla 8705, from doc
+{
+    import std.exception;
+    int[string][double[int[]]] a;
+    auto b = to!(short[wstring][string[double[]]])(a);
+    a = [null:["hello":int.max]];
+    assertThrown!ConvOverflowException(to!(short[wstring][string[double[]]])(a));
+}
+unittest // Extra cases for AA with qualifiers conversion
+{
+    int[][int[]] a;// = [[], []];
+    auto b = to!(immutable(short[])[immutable short[]])(a);
+
+    double[dstring][int[long[]]] c;
+    auto d = to!(immutable(short[immutable wstring])[immutable string[double[]]])(c);
+}
+
+private void testIntegralToFloating(Integral, Floating)()
+{
+    Integral a = 42;
+    auto b = to!Floating(a);
+    assert(a == b);
+    assert(a == to!Integral(b));
+}
+
+private void testFloatingToIntegral(Floating, Integral)()
+{
+    bool convFails(Source, Target, E)(Source src)
+    {
+        try
+            auto t = to!Target(src);
+        catch (E)
+            return true;
+        return false;
+    }
+
+    // convert some value
+    Floating a = 4.2e1;
+    auto b = to!Integral(a);
+    assert(is(typeof(b) == Integral) && b == 42);
+    // convert some negative value (if applicable)
+    a = -4.2e1;
+    static if (Integral.min < 0)
+    {
+        b = to!Integral(a);
+        assert(is(typeof(b) == Integral) && b == -42);
+    }
+    else
+    {
+        // no go for unsigned types
+        assert(convFails!(Floating, Integral, ConvOverflowException)(a));
+    }
+    // convert to the smallest integral value
+    a = 0.0 + Integral.min;
+    static if (Integral.min < 0)
+    {
+        a = -a; // -Integral.min not representable as an Integral
+        assert(convFails!(Floating, Integral, ConvOverflowException)(a)
+                || Floating.sizeof <= Integral.sizeof);
+    }
+    a = 0.0 + Integral.min;
+    assert(to!Integral(a) == Integral.min);
+    --a; // no more representable as an Integral
+    assert(convFails!(Floating, Integral, ConvOverflowException)(a)
+            || Floating.sizeof <= Integral.sizeof);
+    a = 0.0 + Integral.max;
+//   fwritefln(stderr, "%s a=%g, %s conv=%s", Floating.stringof, a,
+//             Integral.stringof, to!Integral(a));
+    assert(to!Integral(a) == Integral.max || Floating.sizeof <= Integral.sizeof);
+    ++a; // no more representable as an Integral
+    assert(convFails!(Floating, Integral, ConvOverflowException)(a)
+            || Floating.sizeof <= Integral.sizeof);
+    // convert a value with a fractional part
+    a = 3.14;
+    assert(to!Integral(a) == 3);
+    a = 3.99;
+    assert(to!Integral(a) == 3);
+    static if (Integral.min < 0)
+    {
+        a = -3.14;
+        assert(to!Integral(a) == -3);
+        a = -3.99;
+        assert(to!Integral(a) == -3);
+    }
+}
+
+@safe pure unittest
+{
+    alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong);
+    alias AllFloats = AliasSeq!(float, double, real);
+    alias AllNumerics = AliasSeq!(AllInts, AllFloats);
+    // test with same type
+    {
+        foreach (T; AllNumerics)
+        {
+            T a = 42;
+            auto b = to!T(a);
+            assert(is(typeof(a) == typeof(b)) && a == b);
+        }
+    }
+    // test that floating-point numbers convert properly to largest ints
+    // see http://oregonstate.edu/~peterseb/mth351/docs/351s2001_fp80x87.html
+    // look for "largest fp integer with a predecessor"
+    {
+        // float
+        int a = 16_777_215; // 2^24 - 1
+        assert(to!int(to!float(a)) == a);
+        assert(to!int(to!float(-a)) == -a);
+        // double
+        long b = 9_007_199_254_740_991; // 2^53 - 1
+        assert(to!long(to!double(b)) == b);
+        assert(to!long(to!double(-b)) == -b);
+        // real
+        // @@@ BUG IN COMPILER @@@
+//     ulong c = 18_446_744_073_709_551_615UL; // 2^64 - 1
+//     assert(to!ulong(to!real(c)) == c);
+//     assert(to!ulong(-to!real(c)) == c);
+    }
+    // test conversions floating => integral
+    {
+        // AllInts[0 .. $ - 1] should be AllInts
+        // @@@ BUG IN COMPILER @@@
+        foreach (Integral; AllInts[0 .. $ - 1])
+        {
+            foreach (Floating; AllFloats)
+            {
+                testFloatingToIntegral!(Floating, Integral)();
+            }
+        }
+    }
+    // test conversion integral => floating
+    {
+        foreach (Integral; AllInts[0 .. $ - 1])
+        {
+            foreach (Floating; AllFloats)
+            {
+                testIntegralToFloating!(Integral, Floating)();
+            }
+        }
+    }
+    // test parsing
+    {
+        foreach (T; AllNumerics)
+        {
+            // from type immutable(char)[2]
+            auto a = to!T("42");
+            assert(a == 42);
+            // from type char[]
+            char[] s1 = "42".dup;
+            a = to!T(s1);
+            assert(a == 42);
+            // from type char[2]
+            char[2] s2;
+            s2[] = "42";
+            a = to!T(s2);
+            assert(a == 42);
+            // from type immutable(wchar)[2]
+            a = to!T("42"w);
+            assert(a == 42);
+        }
+    }
+}
+/*@safe pure */unittest
+{
+    alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong);
+    alias AllFloats = AliasSeq!(float, double, real);
+    alias AllNumerics = AliasSeq!(AllInts, AllFloats);
+    // test conversions to string
+    {
+        foreach (T; AllNumerics)
+        {
+            T a = 42;
+            assert(to!string(a) == "42");
+            //assert(to!wstring(a) == "42"w);
+            //assert(to!dstring(a) == "42"d);
+            // array test
+//       T[] b = new T[2];
+//       b[0] = 42;
+//       b[1] = 33;
+//       assert(to!string(b) == "[42,33]");
+        }
+    }
+    // test array to string conversion
+    foreach (T ; AllNumerics)
+    {
+        auto a = [to!T(1), 2, 3];
+        assert(to!string(a) == "[1, 2, 3]");
+    }
+    // test enum to int conversion
+    // enum Testing { Test1, Test2 };
+    // Testing t;
+    // auto a = to!string(t);
+    // assert(a == "0");
+}
+
+
+/**
+String to non-string conversion runs parsing.
+$(UL
+  $(LI When the source is a wide string, it is first converted to a narrow
+       string and then parsed.)
+  $(LI When the source is a narrow string, normal text parsing occurs.))
+*/
+T toImpl(T, S)(S value)
+    if ( isExactSomeString!S && isDynamicArray!S &&
+        !isExactSomeString!T && is(typeof(parse!T(value))))
+{
+    scope(success)
+    {
+        if (value.length)
+        {
+            throw convError!(S, T)(value);
+        }
+    }
+    return parse!T(value);
+}
+
+/// ditto
+T toImpl(T, S)(S value, uint radix)
+    if ( isExactSomeString!S && isDynamicArray!S &&
+        !isExactSomeString!T && is(typeof(parse!T(value, radix))))
+{
+    scope(success)
+    {
+        if (value.length)
+        {
+            throw convError!(S, T)(value);
+        }
+    }
+    return parse!T(value, radix);
+}
+
+@safe pure unittest
+{
+    // Issue 6668 - ensure no collaterals thrown
+    try { to!uint("-1"); }
+    catch (ConvException e) { assert(e.next is null); }
+}
+
+@safe pure unittest
+{
+    foreach (Str; AliasSeq!(string, wstring, dstring))
+    {
+        Str a = "123";
+        assert(to!int(a) == 123);
+        assert(to!double(a) == 123);
+    }
+
+    // 6255
+    auto n = to!int("FF", 16);
+    assert(n == 255);
+}
+
+/**
+Convert a value that is implicitly convertible to the enum base type
+into an Enum value. If the value does not match any enum member values
+a ConvException is thrown.
+Enums with floating-point or string base types are not supported.
+*/
+T toImpl(T, S)(S value)
+    if (is(T == enum) && !is(S == enum)
+        && is(typeof(value == OriginalType!T.init))
+        && !isFloatingPoint!(OriginalType!T) && !isSomeString!(OriginalType!T))
+{
+    foreach (Member; EnumMembers!T)
+    {
+        if (Member == value)
+            return Member;
+    }
+    throw new ConvException(convFormat("Value (%s) does not match any member value of enum '%s'", value, T.stringof));
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    enum En8143 : int { A = 10, B = 20, C = 30, D = 20 }
+    enum En8143[][] m3 = to!(En8143[][])([[10, 30], [30, 10]]);
+    static assert(m3 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]);
+
+    En8143 en1 = to!En8143(10);
+    assert(en1 == En8143.A);
+    assertThrown!ConvException(to!En8143(5));   // matches none
+    En8143[][] m1 = to!(En8143[][])([[10, 30], [30, 10]]);
+    assert(m1 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]);
+}
+
+/***************************************************************
+ Rounded conversion from floating point to integral.
+
+Rounded conversions do not work with non-integral target types.
+ */
+
+template roundTo(Target)
+{
+    Target roundTo(Source)(Source value)
+    {
+        import std.math : trunc;
+
+        static assert(isFloatingPoint!Source);
+        static assert(isIntegral!Target);
+        return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L)));
+    }
+}
+
+///
+unittest
+{
+    assert(roundTo!int(3.14) == 3);
+    assert(roundTo!int(3.49) == 3);
+    assert(roundTo!int(3.5) == 4);
+    assert(roundTo!int(3.999) == 4);
+    assert(roundTo!int(-3.14) == -3);
+    assert(roundTo!int(-3.49) == -3);
+    assert(roundTo!int(-3.5) == -4);
+    assert(roundTo!int(-3.999) == -4);
+    assert(roundTo!(const int)(to!(const double)(-3.999)) == -4);
+}
+
+unittest
+{
+    import std.exception;
+    // boundary values
+    foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint))
+    {
+        assert(roundTo!Int(Int.min - 0.4L) == Int.min);
+        assert(roundTo!Int(Int.max + 0.4L) == Int.max);
+        assertThrown!ConvOverflowException(roundTo!Int(Int.min - 0.5L));
+        assertThrown!ConvOverflowException(roundTo!Int(Int.max + 0.5L));
+    }
+}
+
+/**
+The $(D parse) family of functions works quite like the $(D to)
+family, except that:
+$(OL
+    $(LI It only works with character ranges as input.)
+    $(LI It takes the input by reference. (This means that rvalues - such
+    as string literals - are not accepted: use $(D to) instead.))
+    $(LI It advances the input to the position following the conversion.)
+    $(LI It does not throw if it could not convert the entire input.))
+
+It still throws if an overflow occurred during conversion or if no character
+of the input was meaningfully converted.
+*/
+Target parse(Target, Source)(ref Source s)
+    if (isInputRange!Source &&
+        isSomeChar!(ElementType!Source) &&
+        is(Unqual!Target == bool))
+{
+    import std.ascii : toLower;
+    if (!s.empty)
+    {
+        auto c1 = toLower(s.front);
+        bool result = (c1 == 't');
+        if (result || c1 == 'f')
+        {
+            s.popFront();
+            foreach (c; result ? "rue" : "alse")
+            {
+                if (s.empty || toLower(s.front) != c)
+                    goto Lerr;
+                s.popFront();
+            }
+            return result;
+        }
+    }
+Lerr:
+    throw parseError("bool should be case-insensitive 'true' or 'false'");
+}
+
+///
+unittest
+{
+    import std.string : munch;
+    string test = "123 \t  76.14";
+    auto a = parse!uint(test);
+    assert(a == 123);
+    assert(test == " \t  76.14"); // parse bumps string
+    munch(test, " \t\n\r"); // skip ws
+    assert(test == "76.14");
+    auto b = parse!double(test);
+    assert(b == 76.14);
+    assert(test == "");
+}
+
+unittest
+{
+    import std.exception;
+    import std.algorithm : equal;
+    struct InputString
+    {
+        string _s;
+        @property auto front() { return _s.front; }
+        @property bool empty() { return _s.empty; }
+        void popFront() { _s.popFront(); }
+    }
+
+    auto s = InputString("trueFALSETrueFalsetRUEfALSE");
+    assert(parse!bool(s) == true);
+    assert(s.equal("FALSETrueFalsetRUEfALSE"));
+    assert(parse!bool(s) == false);
+    assert(s.equal("TrueFalsetRUEfALSE"));
+    assert(parse!bool(s) == true);
+    assert(s.equal("FalsetRUEfALSE"));
+    assert(parse!bool(s) == false);
+    assert(s.equal("tRUEfALSE"));
+    assert(parse!bool(s) == true);
+    assert(s.equal("fALSE"));
+    assert(parse!bool(s) == false);
+    assert(s.empty);
+
+    foreach (ss; ["tfalse", "ftrue", "t", "f", "tru", "fals", ""])
+    {
+        s = InputString(ss);
+        assertThrown!ConvException(parse!bool(s));
+    }
+}
+
+Target parse(Target, Source)(ref Source s)
+    if (isSomeChar!(ElementType!Source) &&
+        isIntegral!Target && !is(Target == enum))
+{
+    static if (Target.sizeof < int.sizeof)
+    {
+        // smaller types are handled like integers
+        auto v = .parse!(Select!(Target.min < 0, int, uint))(s);
+        auto result = ()@trusted{ return cast(Target) v; }();
+        if (result == v)
+            return result;
+        throw new ConvOverflowException("Overflow in integral conversion");
+    }
+    else
+    {
+        // int or larger types
+
+        static if (Target.min < 0)
+            bool sign = 0;
+        else
+            enum bool sign = 0;
+
+        enum char maxLastDigit = Target.min < 0 ? 7 : 5;
+        Unqual!(typeof(s.front)) c;
+
+        if (s.empty)
+            goto Lerr;
+
+        c = s.front;
+        static if (Target.min < 0)
+        {
+            switch (c)
+            {
+                case '-':
+                    sign = true;
+                    goto case '+';
+                case '+':
+                    s.popFront();
+                    if (s.empty)
+                        goto Lerr;
+                    c = s.front;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        c -= '0';
+        if (c <= 9)
+        {
+            Target v = cast(Target)c;
+            s.popFront();
+            while (!s.empty)
+            {
+                c = cast(typeof(c)) (s.front - '0');
+                if (c > 9)
+                    break;
+
+                if (v >= 0 && (v < Target.max/10 ||
+                    (v == Target.max/10 && c <= maxLastDigit + sign)))
+                {
+                    // Note: `v` can become negative here in case of parsing
+                    // the most negative value:
+                    v = cast(Target) (v * 10 + c);
+                    s.popFront();
+                }
+                else
+                    throw new ConvOverflowException("Overflow in integral conversion");
+            }
+
+            if (sign)
+                v = -v;
+            return v;
+        }
+Lerr:
+        throw convError!(Source, Target)(s);
+    }
+}
+
+@safe pure unittest
+{
+    string s = "123";
+    auto a = parse!int(s);
+}
+
+@safe pure unittest
+{
+    foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        {
+            assert(to!Int("0") == 0);
+
+            static if (isSigned!Int)
+            {
+                assert(to!Int("+0") == 0);
+                assert(to!Int("-0") == 0);
+            }
+        }
+
+        static if (Int.sizeof >= byte.sizeof)
+        {
+                assert(to!Int("6") == 6);
+                assert(to!Int("23") == 23);
+                assert(to!Int("68") == 68);
+                assert(to!Int("127") == 0x7F);
+
+            static if (isUnsigned!Int)
+            {
+                assert(to!Int("255") == 0xFF);
+            }
+            static if (isSigned!Int)
+            {
+                assert(to!Int("+6") == 6);
+                assert(to!Int("+23") == 23);
+                assert(to!Int("+68") == 68);
+                assert(to!Int("+127") == 0x7F);
+
+                assert(to!Int("-6") == -6);
+                assert(to!Int("-23") == -23);
+                assert(to!Int("-68") == -68);
+                assert(to!Int("-128") == -128);
+            }
+        }
+
+        static if (Int.sizeof >= short.sizeof)
+        {
+                assert(to!Int("468") == 468);
+                assert(to!Int("32767") == 0x7FFF);
+
+            static if (isUnsigned!Int)
+            {
+                assert(to!Int("65535") == 0xFFFF);
+            }
+            static if (isSigned!Int)
+            {
+                assert(to!Int("+468") == 468);
+                assert(to!Int("+32767") == 0x7FFF);
+
+                assert(to!Int("-468") == -468);
+                assert(to!Int("-32768") == -32768);
+            }
+        }
+
+        static if (Int.sizeof >= int.sizeof)
+        {
+                assert(to!Int("2147483647") == 0x7FFFFFFF);
+
+            static if (isUnsigned!Int)
+            {
+                assert(to!Int("4294967295") == 0xFFFFFFFF);
+            }
+
+            static if (isSigned!Int)
+            {
+                assert(to!Int("+2147483647") == 0x7FFFFFFF);
+
+                assert(to!Int("-2147483648") == -2147483648);
+            }
+        }
+
+        static if (Int.sizeof >= long.sizeof)
+        {
+                assert(to!Int("9223372036854775807") == 0x7FFFFFFFFFFFFFFF);
+
+            static if (isUnsigned!Int)
+            {
+                assert(to!Int("18446744073709551615") == 0xFFFFFFFFFFFFFFFF);
+            }
+
+            static if (isSigned!Int)
+            {
+                assert(to!Int("+9223372036854775807") == 0x7FFFFFFFFFFFFFFF);
+
+                assert(to!Int("-9223372036854775808") == 0x8000000000000000);
+            }
+        }
+    }
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    // parsing error check
+    foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        {
+            immutable string[] errors1 =
+            [
+                "",
+                "-",
+                "+",
+                "-+",
+                " ",
+                " 0",
+                "0 ",
+                "- 0",
+                "1-",
+                "xx",
+                "123h",
+                "-+1",
+                "--1",
+                "+-1",
+                "++1",
+            ];
+            foreach (j, s; errors1)
+                assertThrown!ConvException(to!Int(s));
+        }
+
+        // parse!SomeUnsigned cannot parse head sign.
+        static if (isUnsigned!Int)
+        {
+            immutable string[] errors2 =
+            [
+                "+5",
+                "-78",
+            ];
+            foreach (j, s; errors2)
+                assertThrown!ConvException(to!Int(s));
+        }
+    }
+
+    // positive overflow check
+    foreach (i, Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
+    {
+        immutable string[] errors =
+        [
+            "128",                  // > byte.max
+            "256",                  // > ubyte.max
+            "32768",                // > short.max
+            "65536",                // > ushort.max
+            "2147483648",           // > int.max
+            "4294967296",           // > uint.max
+            "9223372036854775808",  // > long.max
+            "18446744073709551616", // > ulong.max
+        ];
+        foreach (j, s; errors[i..$])
+            assertThrown!ConvOverflowException(to!Int(s));
+    }
+
+    // negative overflow check
+    foreach (i, Int; AliasSeq!(byte, short, int, long))
+    {
+        immutable string[] errors =
+        [
+            "-129",                 // < byte.min
+            "-32769",               // < short.min
+            "-2147483649",          // < int.min
+            "-9223372036854775809", // < long.min
+        ];
+        foreach (j, s; errors[i..$])
+            assertThrown!ConvOverflowException(to!Int(s));
+    }
+}
+
+@safe pure unittest
+{
+    void checkErrMsg(string input, dchar charInMsg, dchar charNotInMsg)
+    {
+        try
+        {
+            int x = input.to!int();
+            assert(false, "Invalid conversion did not throw");
+        }
+        catch(ConvException e)
+        {
+            // Ensure error message contains failing character, not the character
+            // beyond.
+            import std.algorithm.searching : canFind;
+            assert( e.msg.canFind(charInMsg) &&
+                   !e.msg.canFind(charNotInMsg));
+        }
+        catch(Exception e)
+        {
+            assert(false, "Did not throw ConvException");
+        }
+    }
+    checkErrMsg("@$", '@', '$');
+    checkErrMsg("@$123", '@', '$');
+    checkErrMsg("1@$23", '@', '$');
+    checkErrMsg("1@$", '@', '$');
+    checkErrMsg("1@$2", '@', '$');
+    checkErrMsg("12@$", '@', '$');
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    assertCTFEable!({ string s =  "1234abc"; assert(parse! int(s) ==  1234 && s == "abc"); });
+    assertCTFEable!({ string s = "-1234abc"; assert(parse! int(s) == -1234 && s == "abc"); });
+    assertCTFEable!({ string s =  "1234abc"; assert(parse!uint(s) ==  1234 && s == "abc"); });
+}
+
+// Issue 13931
+@safe pure unittest
+{
+    import std.exception;
+
+    assertThrown!ConvOverflowException("-21474836480".to!int());
+    assertThrown!ConvOverflowException("-92233720368547758080".to!long());
+}
+
+// Issue 14396
+@safe pure unittest
+{
+    struct StrInputRange
+    {
+        this (string s) { str = s; }
+        char front() const @property { return str[front_index]; }
+        char popFront() { return str[front_index++]; }
+        bool empty() const @property { return str.length <= front_index; }
+        string str;
+        size_t front_index = 0;
+    }
+    auto input = StrInputRange("777");
+    assert(parse!int(input) == 777);
+}
+
+/// ditto
+Target parse(Target, Source)(ref Source s, uint radix)
+    if (isSomeChar!(ElementType!Source) &&
+        isIntegral!Target && !is(Target == enum))
+in
+{
+    assert(radix >= 2 && radix <= 36);
+}
+body
+{
+    import core.checkedint : mulu, addu;
+    if (radix == 10)
+        return parse!Target(s);
+
+    immutable uint beyond = (radix < 10 ? '0' : 'a'-10) + radix;
+
+    Target v = 0;
+    bool atStart = true;
+
+    for (; !s.empty; s.popFront())
+    {
+        uint c = s.front;
+        if (c < '0')
+            break;
+        if (radix < 10)
+        {
+            if (c >= beyond)
+                break;
+        }
+        else
+        {
+            if (c > '9')
+            {
+                c |= 0x20;//poorman's tolower
+                if (c < 'a' || c >= beyond)
+                    break;
+                c -= 'a'-10-'0';
+            }
+        }
+
+        bool overflow = false;
+        auto nextv = v.mulu(radix, overflow).addu(c - '0', overflow);
+        if (overflow || nextv > Target.max)
+            goto Loverflow;
+        v = cast(Target) nextv;
+
+        atStart = false;
+    }
+    if (atStart)
+        goto Lerr;
+    return v;
+
+Loverflow:
+    throw new ConvOverflowException("Overflow in integral conversion");
+Lerr:
+    throw convError!(Source, Target)(s, radix);
+}
+
+@safe pure unittest
+{
+    string s; // parse doesn't accept rvalues
+    foreach (i; 2..37)
+    {
+        assert(parse!int(s = "0", i) == 0);
+        assert(parse!int(s = "1", i) == 1);
+        assert(parse!byte(s = "10", i) == i);
+    }
+
+    assert(parse!int(s = "0011001101101", 2) == 0b0011001101101);
+    assert(parse!int(s = "765", 8) == octal!765);
+    assert(parse!int(s = "fCDe", 16) == 0xfcde);
+
+    // 6609
+    assert(parse!int(s = "-42", 10) == -42);
+}
+
+@safe pure unittest // bugzilla 7302
+{
+    import std.range : cycle;
+    auto r = cycle("2A!");
+    auto u = parse!uint(r, 16);
+    assert(u == 42);
+    assert(r.front == '!');
+}
+
+@safe pure unittest // bugzilla 13163
+{
+    import std.exception;
+    foreach (s; ["fff", "123"])
+        assertThrown!ConvOverflowException(s.parse!ubyte(16));
+}
+
+Target parse(Target, Source)(ref Source s)
+    if (isExactSomeString!Source &&
+        is(Target == enum))
+{
+    import std.algorithm : startsWith;
+    Target result;
+    size_t longest_match = 0;
+
+    foreach (i, e; EnumMembers!Target)
+    {
+        auto ident = __traits(allMembers, Target)[i];
+        if (longest_match < ident.length && s.startsWith(ident))
+        {
+            result = e;
+            longest_match = ident.length ;
+        }
+    }
+
+    if (longest_match > 0)
+    {
+        s = s[longest_match .. $];
+        return result ;
+    }
+
+    throw new ConvException(
+        Target.stringof ~ " does not have a member named '"
+        ~ to!string(s) ~ "'");
+}
+
+unittest
+{
+    import std.exception;
+
+    enum EB : bool { a = true, b = false, c = a }
+    enum EU { a, b, c }
+    enum EI { a = -1, b = 0, c = 1 }
+    enum EF : real { a = 1.414, b = 1.732, c = 2.236 }
+    enum EC : char { a = 'a', b = 'b', c = 'c' }
+    enum ES : string { a = "aaa", b = "bbb", c = "ccc" }
+
+    foreach (E; AliasSeq!(EB, EU, EI, EF, EC, ES))
+    {
+        assert(to!E("a"c) == E.a);
+        assert(to!E("b"w) == E.b);
+        assert(to!E("c"d) == E.c);
+
+        assertThrown!ConvException(to!E("d"));
+    }
+}
+
+@safe pure unittest // bugzilla 4744
+{
+    enum A { member1, member11, member111 }
+    assert(to!A("member1"  ) == A.member1  );
+    assert(to!A("member11" ) == A.member11 );
+    assert(to!A("member111") == A.member111);
+    auto s = "member1111";
+    assert(parse!A(s) == A.member111 && s == "1");
+}
+
+Target parse(Target, Source)(ref Source p)
+    if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
+        isFloatingPoint!Target && !is(Target == enum))
+{
+    import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit;
+    import std.exception : enforce;
+    import core.stdc.math : HUGE_VAL;
+
+    static immutable real[14] negtab =
+        [ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L,
+                1e-16L,1e-8L,1e-4L,1e-2L,1e-1L,1.0L ];
+    static immutable real[13] postab =
+        [ 1e+4096L,1e+2048L,1e+1024L,1e+512L,1e+256L,1e+128L,1e+64L,1e+32L,
+                1e+16L,1e+8L,1e+4L,1e+2L,1e+1L ];
+    // static immutable string infinity = "infinity";
+    // static immutable string nans = "nans";
+
+    ConvException bailOut()(string msg = null, string fn = __FILE__, size_t ln = __LINE__)
+    {
+        if (msg == null)
+            msg = "Floating point conversion error";
+        return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln);
+    }
+
+    enforce(!p.empty, bailOut());
+
+    char sign = 0;                       /* indicating +                 */
+    switch (p.front)
+    {
+    case '-':
+        sign++;
+        p.popFront();
+        enforce(!p.empty, bailOut());
+        if (toLower(p.front) == 'i')
+            goto case 'i';
+        enforce(!p.empty, bailOut());
+        break;
+    case '+':
+        p.popFront();
+        enforce(!p.empty, bailOut());
+        break;
+    case 'i': case 'I':
+        p.popFront();
+        enforce(!p.empty, bailOut());
+        if (toLower(p.front) == 'n')
+        {
+            p.popFront();
+            enforce(!p.empty, bailOut());
+            if (toLower(p.front) == 'f')
+            {
+                // 'inf'
+                p.popFront();
+                return sign ? -Target.infinity : Target.infinity;
+            }
+        }
+        goto default;
+    default: {}
+    }
+
+    bool isHex = false;
+    bool startsWithZero = p.front == '0';
+    if(startsWithZero)
+    {
+        p.popFront();
+        if(p.empty)
+        {
+            return (sign) ? -0.0 : 0.0;
+        }
+
+        isHex = p.front == 'x' || p.front == 'X';
+    }
+
+    real ldval = 0.0;
+    char dot = 0;                        /* if decimal point has been seen */
+    int exp = 0;
+    long msdec = 0, lsdec = 0;
+    ulong msscale = 1;
+
+    if (isHex)
+    {
+        int guard = 0;
+        int anydigits = 0;
+        uint ndigits = 0;
+
+        p.popFront();
+        while (!p.empty)
+        {
+            int i = p.front;
+            while (isHexDigit(i))
+            {
+                anydigits = 1;
+                i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0';
+                if (ndigits < 16)
+                {
+                    msdec = msdec * 16 + i;
+                    if (msdec)
+                        ndigits++;
+                }
+                else if (ndigits == 16)
+                {
+                    while (msdec >= 0)
+                    {
+                        exp--;
+                        msdec <<= 1;
+                        i <<= 1;
+                        if (i & 0x10)
+                            msdec |= 1;
+                    }
+                    guard = i << 4;
+                    ndigits++;
+                    exp += 4;
+                }
+                else
+                {
+                    guard |= i;
+                    exp += 4;
+                }
+                exp -= dot;
+                p.popFront();
+                if (p.empty)
+                    break;
+                i = p.front;
+                if (i == '_')
+                {
+                    p.popFront();
+                    if (p.empty)
+                        break;
+                    i = p.front;
+                }
+            }
+            if (i == '.' && !dot)
+            {
+                p.popFront();
+                dot = 4;
+            }
+            else
+                break;
+        }
+
+        // Round up if (guard && (sticky || odd))
+        if (guard & 0x80 && (guard & 0x7F || msdec & 1))
+        {
+            msdec++;
+            if (msdec == 0)                 // overflow
+            {
+                msdec = 0x8000000000000000L;
+                exp++;
+            }
+        }
+
+        enforce(anydigits, bailOut());
+        enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
+                bailOut("Floating point parsing: exponent is required"));
+        char sexp;
+        int e;
+
+        sexp = 0;
+        p.popFront();
+        if (!p.empty)
+        {
+            switch (p.front)
+            {
+                case '-':    sexp++;
+                             goto case;
+                case '+':    p.popFront(); enforce(!p.empty,
+                                new ConvException("Error converting input"~
+                                " to floating point"));
+                             break;
+                default: {}
+            }
+        }
+        ndigits = 0;
+        e = 0;
+        while (!p.empty && isDigit(p.front))
+        {
+            if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
+            {
+                e = e * 10 + p.front - '0';
+            }
+            p.popFront();
+            ndigits = 1;
+        }
+        exp += (sexp) ? -e : e;
+        enforce(ndigits, new ConvException("Error converting input"~
+                        " to floating point"));
+
+        static if (real.mant_dig == 64)
+        {
+            if (msdec)
+            {
+                int e2 = 0x3FFF + 63;
+
+                // left justify mantissa
+                while (msdec >= 0)
+                {
+                    msdec <<= 1;
+                    e2--;
+                }
+
+                // Stuff mantissa directly into real
+                ()@trusted{ *cast(long*)&ldval = msdec; }();
+                ()@trusted{ (cast(ushort*)&ldval)[4] = cast(ushort) e2; }();
+
+                import std.math : ldexp;
+
+                // Exponent is power of 2, not power of 10
+                ldval = ldexp(ldval,exp);
+            }
+        }
+        else static if (real.mant_dig == 53)
+        {
+            if (msdec)
+            {
+                //Exponent bias + 52:
+                //After shifting 52 times left, exp must be 1
+                int e2 = 0x3FF + 52;
+
+                // right justify mantissa
+                // first 11 bits must be zero, rest is implied bit + mantissa
+                // shift one time less, do rounding, shift again
+                while ((msdec & 0xFFC0_0000_0000_0000) != 0)
+                {
+                    msdec  = ((cast(ulong)msdec) >> 1);
+                    e2++;
+                }
+
+                //Have to shift one more time
+                //and do rounding
+                if((msdec & 0xFFE0_0000_0000_0000) != 0)
+                {
+                    auto roundUp = (msdec & 0x1);
+
+                    msdec  = ((cast(ulong)msdec) >> 1);
+                    e2++;
+                    if(roundUp)
+                    {
+                        msdec += 1;
+                        //If mantissa was 0b1111... and we added +1
+                        //the mantissa should be 0b10000 (think of implicit bit)
+                        //and the exponent increased
+                        if((msdec & 0x0020_0000_0000_0000) != 0)
+                        {
+                            msdec = 0x0010_0000_0000_0000;
+                            e2++;
+                        }
+                    }
+                }
+
+
+                // left justify mantissa
+                // bit 11 must be 1
+                while ((msdec & 0x0010_0000_0000_0000) == 0)
+                {
+                    msdec <<= 1;
+                    e2--;
+                }
+
+                // Stuff mantissa directly into double
+                // (first including implicit bit)
+                ()@trusted{ *cast(long *)&ldval = msdec; }();
+                //Store exponent, now overwriting implicit bit
+                ()@trusted{ *cast(long *)&ldval &= 0x000F_FFFF_FFFF_FFFF; }();
+                ()@trusted{ *cast(long *)&ldval |= ((e2 & 0xFFFUL) << 52); }();
+
+                import std.math : ldexp;
+
+                // Exponent is power of 2, not power of 10
+                ldval = ldexp(ldval,exp);
+            }
+        }
+        else
+            static assert(false, "Floating point format of real type not supported");
+
+        goto L6;
+    }
+    else // not hex
+    {
+        if (toUpper(p.front) == 'N' && !startsWithZero)
+        {
+            // nan
+            p.popFront();
+            enforce(!p.empty && toUpper(p.front) == 'A',
+                   new ConvException("error converting input to floating point"));
+            p.popFront();
+            enforce(!p.empty && toUpper(p.front) == 'N',
+                   new ConvException("error converting input to floating point"));
+            // skip past the last 'n'
+            p.popFront();
+            return typeof(return).nan;
+        }
+
+        bool sawDigits = startsWithZero;
+
+        while (!p.empty)
+        {
+            int i = p.front;
+            while (isDigit(i))
+            {
+                sawDigits = true;        /* must have at least 1 digit   */
+                if (msdec < (0x7FFFFFFFFFFFL-10)/10)
+                    msdec = msdec * 10 + (i - '0');
+                else if (msscale < (0xFFFFFFFF-10)/10)
+                {
+                    lsdec = lsdec * 10 + (i - '0');
+                    msscale *= 10;
+                }
+                else
+                {
+                    exp++;
+                }
+                exp -= dot;
+                p.popFront();
+                if (p.empty)
+                    break;
+                i = p.front;
+                if (i == '_')
+                {
+                    p.popFront();
+                    if (p.empty)
+                        break;
+                    i = p.front;
+                }
+            }
+            if (i == '.' && !dot)
+            {
+                p.popFront();
+                dot++;
+            }
+            else
+            {
+                break;
+            }
+        }
+        enforce(sawDigits, new ConvException("no digits seen"));
+    }
+    if (!p.empty && (p.front == 'e' || p.front == 'E'))
+    {
+        char sexp;
+        int e;
+
+        sexp = 0;
+        p.popFront();
+        enforce(!p.empty, new ConvException("Unexpected end of input"));
+        switch (p.front)
+        {
+            case '-':    sexp++;
+                         goto case;
+            case '+':    p.popFront();
+                         break;
+            default: {}
+        }
+        bool sawDigits = 0;
+        e = 0;
+        while (!p.empty && isDigit(p.front))
+        {
+            if (e < 0x7FFFFFFF / 10 - 10)   // prevent integer overflow
+            {
+                e = e * 10 + p.front - '0';
+            }
+            p.popFront();
+            sawDigits = 1;
+        }
+        exp += (sexp) ? -e : e;
+        enforce(sawDigits, new ConvException("No digits seen."));
+    }
+
+    ldval = msdec;
+    if (msscale != 1)               /* if stuff was accumulated in lsdec */
+        ldval = ldval * msscale + lsdec;
+    if (ldval)
+    {
+        uint u = 0;
+        int pow = 4096;
+
+        while (exp > 0)
+        {
+            while (exp >= pow)
+            {
+                ldval *= postab[u];
+                exp -= pow;
+            }
+            pow >>= 1;
+            u++;
+        }
+        while (exp < 0)
+        {
+            while (exp <= -pow)
+            {
+                ldval *= negtab[u];
+                enforce(ldval != 0, new ConvException("Range error"));
+                exp += pow;
+            }
+            pow >>= 1;
+            u++;
+        }
+    }
+  L6: // if overflow occurred
+    enforce(ldval != HUGE_VAL, new ConvException("Range error"));
+
+  L1:
+    return (sign) ? -ldval : ldval;
+}
+
+unittest
+{
+    import std.exception;
+    import std.math : isNaN, fabs;
+
+    // Compare reals with given precision
+    bool feq(in real rx, in real ry, in real precision = 0.000001L)
+    {
+        if (rx == ry)
+            return 1;
+
+        if (isNaN(rx))
+            return cast(bool)isNaN(ry);
+
+        if (isNaN(ry))
+            return 0;
+
+        return cast(bool)(fabs(rx - ry) <= precision);
+    }
+
+    // Make given typed literal
+    F Literal(F)(F f)
+    {
+        return f;
+    }
+
+    foreach (Float; AliasSeq!(float, double, real))
+    {
+        assert(to!Float("123") == Literal!Float(123));
+        assert(to!Float("+123") == Literal!Float(+123));
+        assert(to!Float("-123") == Literal!Float(-123));
+        assert(to!Float("123e2") == Literal!Float(123e2));
+        assert(to!Float("123e+2") == Literal!Float(123e+2));
+        assert(to!Float("123e-2") == Literal!Float(123e-2));
+        assert(to!Float("123.") == Literal!Float(123.0));
+        assert(to!Float(".375") == Literal!Float(.375));
+
+        assert(to!Float("1.23375E+2") == Literal!Float(1.23375E+2));
+
+        assert(to!Float("0") is 0.0);
+        assert(to!Float("-0") is -0.0);
+
+        assert(isNaN(to!Float("nan")));
+
+        assertThrown!ConvException(to!Float("\x00"));
+    }
+
+    // min and max
+    float f = to!float("1.17549e-38");
+    assert(feq(cast(real)f, cast(real)1.17549e-38));
+    assert(feq(cast(real)f, cast(real)float.min_normal));
+    f = to!float("3.40282e+38");
+    assert(to!string(f) == to!string(3.40282e+38));
+
+    // min and max
+    double d = to!double("2.22508e-308");
+    assert(feq(cast(real)d, cast(real)2.22508e-308));
+    assert(feq(cast(real)d, cast(real)double.min_normal));
+    d = to!double("1.79769e+308");
+    assert(to!string(d) == to!string(1.79769e+308));
+    assert(to!string(d) == to!string(double.max));
+
+    assert(to!string(to!real(to!string(real.max / 2L))) == to!string(real.max / 2L));
+
+    // min and max
+    real r = to!real(to!string(real.min_normal));
+    version(NetBSD)
+    {
+        // NetBSD notice
+        // to!string returns 3.3621e-4932L. It is less than real.min_normal and it is subnormal value
+        // Simple C code
+        //     long double rd = 3.3621e-4932L;
+        //     printf("%Le\n", rd);
+        // has unexpected result: 1.681050e-4932
+        //
+        // Bug report: http://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50937
+    }
+    else
+    {
+        assert(to!string(r) == to!string(real.min_normal));
+    }
+    r = to!real(to!string(real.max));
+    assert(to!string(r) == to!string(real.max));
+}
+
+//Tests for the double implementation
+unittest
+{
+    static if(real.mant_dig == 53)
+    {
+        import core.stdc.stdlib, std.exception, std.math;
+
+        //Should be parsed exactly: 53 bit mantissa
+        string s = "0x1A_BCDE_F012_3456p10";
+        auto x = parse!real(s);
+        assert(x == 0x1A_BCDE_F012_3456p10L);
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0xA_BCDE_F012_3456);
+        assert(strtod("0x1ABCDEF0123456p10", null) == x);
+
+        //Should be parsed exactly: 10 bit mantissa
+        s = "0x3FFp10";
+        x = parse!real(s);
+        assert(x == 0x03FFp10);
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_F800_0000_0000);
+        assert(strtod("0x3FFp10", null) == x);
+
+        //60 bit mantissa, round up
+        s = "0xFFF_FFFF_FFFF_FFFFp10";
+        x = parse!real(s);
+        assert(approxEqual(x, 0xFFF_FFFF_FFFF_FFFFp10));
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x0000_0000_0000_0000);
+        assert(strtod("0xFFFFFFFFFFFFFFFp10", null) == x);
+
+        //60 bit mantissa, round down
+        s = "0xFFF_FFFF_FFFF_FF90p10";
+        x = parse!real(s);
+        assert(approxEqual(x, 0xFFF_FFFF_FFFF_FF90p10));
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_FFFF_FFFF_FFFF);
+        assert(strtod("0xFFFFFFFFFFFFF90p10", null) == x);
+
+        //61 bit mantissa, round up 2
+        s = "0x1F0F_FFFF_FFFF_FFFFp10";
+        x = parse!real(s);
+        assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FFFFp10));
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_1000_0000_0000);
+        assert(strtod("0x1F0FFFFFFFFFFFFFp10", null) == x);
+
+        //61 bit mantissa, round down 2
+        s = "0x1F0F_FFFF_FFFF_FF10p10";
+        x = parse!real(s);
+        assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FF10p10));
+        //1 bit is implicit
+        assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_0FFF_FFFF_FFFF);
+        assert(strtod("0x1F0FFFFFFFFFFF10p10", null) == x);
+
+        //Huge exponent
+        s = "0x1F_FFFF_FFFF_FFFFp900";
+        x = parse!real(s);
+        assert(strtod("0x1FFFFFFFFFFFFFp900", null) == x);
+
+        //exponent too big -> converror
+        s = "";
+        assertThrown!ConvException(x = parse!real(s));
+        assert(strtod("0x1FFFFFFFFFFFFFp1024", null) == real.infinity);
+
+        //-exponent too big -> 0
+        s = "0x1FFFFFFFFFFFFFp-2000";
+        x = parse!real(s);
+        assert(x == 0);
+        assert(strtod("0x1FFFFFFFFFFFFFp-2000", null) == x);
+    }
+}
+
+unittest
+{
+    import core.stdc.errno;
+    import core.stdc.stdlib;
+
+    errno = 0;  // In case it was set by another unittest in a different module.
+    struct longdouble
+    {
+        static if(real.mant_dig == 64)
+        {
+            ushort[5] value;
+        }
+        else static if(real.mant_dig == 53)
+        {
+            ushort[4] value;
+        }
+        else
+            static assert(false, "Not implemented");
+    }
+
+    real ld;
+    longdouble x;
+    real ld1;
+    longdouble x1;
+    int i;
+
+    static if(real.mant_dig == 64)
+        enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
+    else static if(real.mant_dig == 53)
+        enum s = "0x1.FFFFFFFFFFFFFFFEp-1000";
+    else
+        static assert(false, "Floating point format for real not supported");
+
+    auto s2 = s.idup;
+    ld = parse!real(s2);
+    assert(s2.empty);
+    x = *cast(longdouble *)&ld;
+
+    static if(real.mant_dig == 64)
+    {
+        version (CRuntime_Microsoft)
+            ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod
+        else version (CRuntime_Bionic)
+            ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod
+        else
+            ld1 = strtold(s.ptr, null);
+    }
+    else
+        ld1 = strtold(s.ptr, null);
+
+    x1 = *cast(longdouble *)&ld1;
+    assert(x1 == x && ld1 == ld);
+
+    // for (i = 4; i >= 0; i--)
+    // {
+    //     printf("%04x ", x.value[i]);
+    // }
+    // printf("\n");
+    assert(!errno);
+
+    s2 = "1.0e5";
+    ld = parse!real(s2);
+    assert(s2.empty);
+    x = *cast(longdouble *)&ld;
+    ld1 = strtold("1.0e5", null);
+    x1 = *cast(longdouble *)&ld1;
+
+    // for (i = 4; i >= 0; i--)
+    // {
+    //     printf("%04x ", x.value[i]);
+    // }
+    // printf("\n");
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    // Bugzilla 4959
+    {
+        auto s = "0 ";
+        auto x = parse!double(s);
+        assert(s == " ");
+        assert(x == 0.0);
+    }
+
+    // Bugzilla 3369
+    assert(to!float("inf") == float.infinity);
+    assert(to!float("-inf") == -float.infinity);
+
+    // Bugzilla 6160
+    assert(6_5.536e3L == to!real("6_5.536e3"));                     // 2^16
+    assert(0x1000_000_000_p10 == to!real("0x1000_000_000_p10"));    // 7.03687e+13
+
+    // Bugzilla 6258
+    assertThrown!ConvException(to!real("-"));
+    assertThrown!ConvException(to!real("in"));
+
+    // Bugzilla 7055
+    assertThrown!ConvException(to!float("INF2"));
+
+    //extra stress testing
+    auto ssOK    = ["1.", "1.1.1", "1.e5", "2e1e", "2a", "2e1_1",
+                    "inf", "-inf", "infa", "-infa", "inf2e2", "-inf2e2"];
+    auto ssKO    = ["", " ", "2e", "2e+", "2e-", "2ee", "2e++1", "2e--1", "2e_1", "+inf"];
+    foreach (s; ssOK)
+        parse!double(s);
+    foreach (s; ssKO)
+        assertThrown!ConvException(parse!double(s));
+}
+
+/**
+Parsing one character off a string returns the character and bumps the
+string up one position.
+ */
+Target parse(Target, Source)(ref Source s)
+    if (isExactSomeString!Source &&
+        staticIndexOf!(Unqual!Target, dchar, Unqual!(ElementEncodingType!Source)) >= 0)
+{
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    static if (is(Unqual!Target == dchar))
+    {
+        Target result = s.front;
+        s.popFront();
+        return result;
+    }
+    else
+    {
+        // Special case: okay so parse a Char off a Char[]
+        Target result = s[0];
+        s = s[1 .. $];
+        return result;
+    }
+}
+
+@safe pure unittest
+{
+    foreach (Str; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (Char; AliasSeq!(char, wchar, dchar))
+        {
+            static if (is(Unqual!Char == dchar) ||
+                       Char.sizeof == ElementEncodingType!Str.sizeof)
+            {
+                Str s = "aaa";
+                assert(parse!Char(s) == 'a');
+                assert(s == "aa");
+            }
+        }
+    }
+}
+
+Target parse(Target, Source)(ref Source s)
+    if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Source) &&
+        isSomeChar!Target && Target.sizeof >= ElementType!Source.sizeof && !is(Target == enum))
+{
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    Target result = s.front;
+    s.popFront();
+    return result;
+}
+
+
+/*
+    Tests for to!bool and parse!bool
+*/
+@safe pure unittest
+{
+    import std.exception;
+
+    assert (to!bool("TruE") == true);
+    assert (to!bool("faLse"d) == false);
+    assertThrown!ConvException(to!bool("maybe"));
+
+    auto t = "TrueType";
+    assert (parse!bool(t) == true);
+    assert (t == "Type");
+
+    auto f = "False killer whale"d;
+    assert (parse!bool(f) == false);
+    assert (f == " killer whale"d);
+
+    auto m = "maybe";
+    assertThrown!ConvException(parse!bool(m));
+    assert (m == "maybe");  // m shouldn't change on failure
+
+    auto s = "true";
+    auto b = parse!(const(bool))(s);
+    assert(b == true);
+}
+
+// input range to null literal conversions
+Target parse(Target, Source)(ref Source s)
+    if (isInputRange!Source &&
+        isSomeChar!(ElementType!Source) &&
+        is(Unqual!Target == typeof(null)))
+{
+    import std.ascii : toLower;
+    foreach (c; "null")
+    {
+        if (s.empty || toLower(s.front) != c)
+            throw parseError("null should be case-insensitive 'null'");
+        s.popFront();
+    }
+    return null;
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    alias NullType = typeof(null);
+    auto s1 = "null";
+    assert(parse!NullType(s1) is null);
+    assert(s1 == "");
+
+    auto s2 = "NUll"d;
+    assert(parse!NullType(s2) is null);
+    assert(s2 == "");
+
+    auto m = "maybe";
+    assertThrown!ConvException(parse!NullType(m));
+    assert(m == "maybe");  // m shouldn't change on failure
+
+    auto s = "NULL";
+    assert(parse!(const NullType)(s) is null);
+}
+
+//Used internally by parse Array/AA, to remove ascii whites
+package void skipWS(R)(ref R r)
+{
+    import std.ascii : isWhite;
+    static if (isSomeString!R)
+    {
+        //Implementation inspired from stripLeft.
+        foreach (i, dchar c; r)
+        {
+            if (!isWhite(c))
+            {
+                r = r[i .. $];
+                return;
+            }
+        }
+        r = r[0 .. 0]; //Empty string with correct type.
+        return;
+    }
+    else
+    {
+        for (; !r.empty && isWhite(r.front); r.popFront())
+        {}
+    }
+}
+
+/**
+ * Parses an array from a string given the left bracket (default $(D
+ * '[')), right bracket (default $(D ']')), and element separator (by
+ * default $(D ',')).
+ */
+Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',')
+    if (isExactSomeString!Source &&
+        isDynamicArray!Target && !is(Target == enum))
+{
+    Target result;
+
+    parseCheck!s(lbracket);
+    skipWS(s);
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front == rbracket)
+    {
+        s.popFront();
+        return result;
+    }
+    for (;; s.popFront(), skipWS(s))
+    {
+        result ~= parseElement!(ElementType!Target)(s);
+        skipWS(s);
+        if (s.empty)
+            throw convError!(Source, Target)(s);
+        if (s.front != comma)
+            break;
+    }
+    parseCheck!s(rbracket);
+
+    return result;
+}
+
+unittest
+{
+    int[] a = [1, 2, 3, 4, 5];
+    auto s = to!string(a);
+    assert(to!(int[])(s) == a);
+}
+
+unittest
+{
+    int[][] a = [ [1, 2] , [3], [4, 5] ];
+    auto s = to!string(a);
+    assert(to!(int[][])(s) == a);
+}
+
+unittest
+{
+    int[][][] ia = [ [[1,2],[3,4],[5]] , [[6],[],[7,8,9]] , [[]] ];
+
+    char[] s = to!(char[])(ia);
+    int[][][] ia2;
+
+    ia2 = to!(typeof(ia2))(s);
+    assert( ia == ia2);
+}
+
+@safe pure unittest
+{
+    auto s1 = `[['h', 'e', 'l', 'l', 'o'], "world"]`;
+    auto a1 = parse!(string[])(s1);
+    assert(a1 == ["hello", "world"]);
+
+    auto s2 = `["aaa", "bbb", "ccc"]`;
+    auto a2 = parse!(string[])(s2);
+    assert(a2 == ["aaa", "bbb", "ccc"]);
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    //Check proper failure
+    auto s = "[ 1 , 2 , 3 ]";
+    foreach (i ; 0..s.length-1)
+    {
+        auto ss = s[0 .. i];
+        assertThrown!ConvException(parse!(int[])(ss));
+    }
+    int[] arr = parse!(int[])(s);
+}
+
+@safe pure unittest
+{
+    //Checks parsing of strings with escaped characters
+    string s1 = `[
+        "Contains a\0null!",
+        "tab\there",
+        "line\nbreak",
+        "backslash \\ slash / question \?",
+        "number \x35 five",
+        "unicode \u65E5 sun",
+        "very long \U000065E5 sun"
+    ]`;
+
+    //Note: escaped characters purposefully replaced and isolated to guarantee
+    //there are no typos in the escape syntax
+    string[] s2 = [
+        "Contains a" ~ '\0' ~ "null!",
+        "tab" ~ '\t' ~ "here",
+        "line" ~ '\n' ~ "break",
+        "backslash " ~ '\\' ~ " slash / question ?",
+        "number 5 five",
+        "unicode 日 sun",
+        "very long 日 sun"
+    ];
+    assert(s2 == parse!(string[])(s1));
+    assert(s1.empty);
+}
+
+/// ditto
+Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',')
+    if (isExactSomeString!Source &&
+        isStaticArray!Target && !is(Target == enum))
+{
+    static if (hasIndirections!Target)
+        Target result = Target.init[0].init;
+    else
+        Target result = void;
+
+    parseCheck!s(lbracket);
+    skipWS(s);
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front == rbracket)
+    {
+        static if (result.length != 0)
+            goto Lmanyerr;
+        else
+        {
+            s.popFront();
+            return result;
+        }
+    }
+    for (size_t i = 0; ; s.popFront(), skipWS(s))
+    {
+        if (i == result.length)
+            goto Lmanyerr;
+        result[i++] = parseElement!(ElementType!Target)(s);
+        skipWS(s);
+        if (s.empty)
+            throw convError!(Source, Target)(s);
+        if (s.front != comma)
+        {
+            if (i != result.length)
+                goto Lfewerr;
+            break;
+        }
+    }
+    parseCheck!s(rbracket);
+
+    return result;
+
+Lmanyerr:
+    throw parseError(text("Too many elements in input, ", result.length, " elements expected."));
+
+Lfewerr:
+    throw parseError(text("Too few elements in input, ", result.length, " elements expected."));
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    auto s1 = "[1,2,3,4]";
+    auto sa1 = parse!(int[4])(s1);
+    assert(sa1 == [1,2,3,4]);
+
+    auto s2 = "[[1],[2,3],[4]]";
+    auto sa2 = parse!(int[][3])(s2);
+    assert(sa2 == [[1],[2,3],[4]]);
+
+    auto s3 = "[1,2,3]";
+    assertThrown!ConvException(parse!(int[4])(s3));
+
+    auto s4 = "[1,2,3,4,5]";
+    assertThrown!ConvException(parse!(int[4])(s4));
+}
+
+/**
+ * Parses an associative array from a string given the left bracket (default $(D
+ * '[')), right bracket (default $(D ']')), key-value separator (default $(D
+ * ':')), and element seprator (by default $(D ',')).
+ */
+Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar keyval = ':', dchar comma = ',')
+    if (isExactSomeString!Source &&
+        isAssociativeArray!Target && !is(Target == enum))
+{
+    alias KeyType = typeof(Target.init.keys[0]);
+    alias ValType = typeof(Target.init.values[0]);
+
+    Target result;
+
+    parseCheck!s(lbracket);
+    skipWS(s);
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front == rbracket)
+    {
+        s.popFront();
+        return result;
+    }
+    for (;; s.popFront(), skipWS(s))
+    {
+        auto key = parseElement!KeyType(s);
+        skipWS(s);
+        parseCheck!s(keyval);
+        skipWS(s);
+        auto val = parseElement!ValType(s);
+        skipWS(s);
+        result[key] = val;
+        if (s.empty)
+            throw convError!(Source, Target)(s);
+        if (s.front != comma)
+            break;
+    }
+    parseCheck!s(rbracket);
+
+    return result;
+}
+
+@safe pure unittest
+{
+    auto s1 = "[1:10, 2:20, 3:30]";
+    auto aa1 = parse!(int[int])(s1);
+    assert(aa1 == [1:10, 2:20, 3:30]);
+
+    auto s2 = `["aaa":10, "bbb":20, "ccc":30]`;
+    auto aa2 = parse!(int[string])(s2);
+    assert(aa2 == ["aaa":10, "bbb":20, "ccc":30]);
+
+    auto s3 = `["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]`;
+    auto aa3 = parse!(int[][string])(s3);
+    assert(aa3 == ["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]);
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    //Check proper failure
+    auto s = "[1:10, 2:20, 3:30]";
+    foreach (i ; 0 .. s.length-1)
+    {
+        auto ss = s[0 .. i];
+        assertThrown!ConvException(parse!(int[int])(ss));
+    }
+    int[int] aa = parse!(int[int])(s);
+}
+
+private dchar parseEscape(Source)(ref Source s)
+    if (isInputRange!Source && isSomeChar!(ElementType!Source))
+{
+    parseCheck!s('\\');
+    if (s.empty)
+        throw parseError("Unterminated escape sequence");
+
+    dchar getHexDigit()(ref Source s_ = s)  // workaround
+    {
+        import std.ascii : isAlpha, isHexDigit;
+        if (s_.empty)
+            throw parseError("Unterminated escape sequence");
+        s_.popFront();
+        if (s_.empty)
+            throw parseError("Unterminated escape sequence");
+        dchar c = s_.front;
+        if (!isHexDigit(c))
+            throw parseError("Hex digit is missing");
+        return isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0';
+    }
+
+    dchar result;
+
+    switch (s.front)
+    {
+        case '"':   result = '\"';  break;
+        case '\'':  result = '\'';  break;
+        case '0':   result = '\0';  break;
+        case '?':   result = '\?';  break;
+        case '\\':  result = '\\';  break;
+        case 'a':   result = '\a';  break;
+        case 'b':   result = '\b';  break;
+        case 'f':   result = '\f';  break;
+        case 'n':   result = '\n';  break;
+        case 'r':   result = '\r';  break;
+        case 't':   result = '\t';  break;
+        case 'v':   result = '\v';  break;
+        case 'x':
+            result  = getHexDigit() << 4;
+            result |= getHexDigit();
+            break;
+        case 'u':
+            result  = getHexDigit() << 12;
+            result |= getHexDigit() << 8;
+            result |= getHexDigit() << 4;
+            result |= getHexDigit();
+            break;
+        case 'U':
+            result  = getHexDigit() << 28;
+            result |= getHexDigit() << 24;
+            result |= getHexDigit() << 20;
+            result |= getHexDigit() << 16;
+            result |= getHexDigit() << 12;
+            result |= getHexDigit() << 8;
+            result |= getHexDigit() << 4;
+            result |= getHexDigit();
+            break;
+        default:
+            throw parseError("Unknown escape character " ~ to!string(s.front));
+    }
+    if (s.empty)
+        throw parseError("Unterminated escape sequence");
+
+    s.popFront();
+
+    return result;
+}
+
+@safe pure unittest
+{
+    string[] s1 = [
+        `\"`, `\'`, `\?`, `\\`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`, //Normal escapes
+        //`\141`, //@@@9621@@@ Octal escapes.
+        `\x61`,
+        `\u65E5`, `\U00012456`
+        //`\&amp;`, `\&quot;`, //@@@9621@@@ Named Character Entities.
+    ];
+
+    const(dchar)[] s2 = [
+        '\"', '\'', '\?', '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v', //Normal escapes
+        //'\141', //@@@9621@@@ Octal escapes.
+        '\x61',
+        '\u65E5', '\U00012456'
+        //'\&amp;', '\&quot;', //@@@9621@@@ Named Character Entities.
+    ];
+
+    foreach (i ; 0 .. s1.length)
+    {
+        assert(s2[i] == parseEscape(s1[i]));
+        assert(s1[i].empty);
+    }
+}
+
+@safe pure unittest
+{
+    import std.exception;
+
+    string[] ss = [
+        `hello!`,  //Not an escape
+        `\`,       //Premature termination
+        `\/`,      //Not an escape
+        `\gggg`,   //Not an escape
+        `\xzz`,    //Not an hex
+        `\x0`,     //Premature hex end
+        `\XB9`,    //Not legal hex syntax
+        `\u!!`,    //Not a unicode hex
+        `\777`,    //Octal is larger than a byte //Note: Throws, but simply because octals are unsupported
+        `\u123`,   //Premature hex end
+        `\U123123` //Premature hex end
+    ];
+    foreach (s ; ss)
+        assertThrown!ConvException(parseEscape(s));
+}
+
+// Undocumented
+Target parseElement(Target, Source)(ref Source s)
+    if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
+        isExactSomeString!Target)
+{
+    import std.array : appender;
+    auto result = appender!Target();
+
+    // parse array of chars
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front == '[')
+        return parse!Target(s);
+
+    parseCheck!s('\"');
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front == '\"')
+    {
+        s.popFront();
+        return result.data;
+    }
+    while (true)
+    {
+        if (s.empty)
+            throw parseError("Unterminated quoted string");
+        switch (s.front)
+        {
+            case '\"':
+                s.popFront();
+                return result.data;
+            case '\\':
+                result.put(parseEscape(s));
+                break;
+            default:
+                result.put(s.front);
+                s.popFront();
+                break;
+        }
+    }
+    assert(0);
+}
+
+// ditto
+Target parseElement(Target, Source)(ref Source s)
+    if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
+        isSomeChar!Target && !is(Target == enum))
+{
+    Target c;
+
+    parseCheck!s('\'');
+    if (s.empty)
+        throw convError!(Source, Target)(s);
+    if (s.front != '\\')
+    {
+        c = s.front;
+        s.popFront();
+    }
+    else
+        c = parseEscape(s);
+    parseCheck!s('\'');
+
+    return c;
+}
+
+// ditto
+Target parseElement(Target, Source)(ref Source s)
+    if (isInputRange!Source && isSomeChar!(ElementType!Source) &&
+        !isSomeString!Target && !isSomeChar!Target)
+{
+    return parse!Target(s);
+}
+
+
+/***************************************************************
+ * Convenience functions for converting any number and types of
+ * arguments into _text (the three character widths).
+ */
+string text(T...)(T args) { return textImpl!string(args); }
+///ditto
+wstring wtext(T...)(T args) { return textImpl!wstring(args); }
+///ditto
+dstring dtext(T...)(T args) { return textImpl!dstring(args); }
+
+private S textImpl(S, U...)(U args)
+{
+    static if (U.length == 0)
+    {
+        return null;
+    }
+    else
+    {
+        auto result = to!S(args[0]);
+        foreach (arg; args[1 .. $])
+            result ~= to!S(arg);
+        return result;
+    }
+}
+///
+unittest
+{
+    assert( text(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"c);
+    assert(wtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"w);
+    assert(dtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"d);
+}
+unittest
+{
+    assert(text() is null);
+    assert(wtext() is null);
+    assert(dtext() is null);
+}
+
+
+/***************************************************************
+The $(D octal) facility provides a means to declare a number in base 8.
+Using $(D octal!177) or $(D octal!"177") for 127 represented in octal
+(same as 0177 in C).
+
+The rules for strings are the usual for literals: If it can fit in an
+$(D int), it is an $(D int). Otherwise, it is a $(D long). But, if the
+user specifically asks for a $(D long) with the $(D L) suffix, always
+give the $(D long). Give an unsigned iff it is asked for with the $(D
+U) or $(D u) suffix. _Octals created from integers preserve the type
+of the passed-in integral.
+
+See_Also:
+    $(LREF parse) for parsing octal strings at runtime.
+ */
+template octal(string num)
+    if(isOctalLiteral(num))
+{
+    static if((octalFitsInInt!num && !literalIsLong!num) && !literalIsUnsigned!num)
+        enum octal = octal!int(num);
+    else static if((!octalFitsInInt!num || literalIsLong!num) && !literalIsUnsigned!num)
+        enum octal = octal!long(num);
+    else static if((octalFitsInInt!num && !literalIsLong!num) && literalIsUnsigned!num)
+        enum octal = octal!uint(num);
+    else static if((!octalFitsInInt!(num) || literalIsLong!(num)) && literalIsUnsigned!(num))
+        enum octal = octal!ulong(num);
+    else
+        static assert(false);
+}
+
+/// Ditto
+template octal(alias decimalInteger)
+    if (isIntegral!(typeof(decimalInteger)))
+{
+    enum octal = octal!(typeof(decimalInteger))(to!string(decimalInteger));
+}
+
+///
+unittest
+{
+    // same as 0177
+    auto x = octal!177;
+    // octal is a compile-time device
+    enum y = octal!160;
+    // Create an unsigned octal
+    auto z = octal!"1_000_000u";
+}
+
+/*
+    Takes a string, num, which is an octal literal, and returns its
+    value, in the type T specified.
+*/
+private T octal(T)(string num)
+{
+    assert(isOctalLiteral(num));
+
+    ulong pow = 1;
+    T value = 0;
+
+    foreach_reverse (immutable pos; 0 .. num.length)
+    {
+        char s = num[pos];
+        if (s < '0' || s > '7') // we only care about digits; skip the rest
+        // safe to skip - this is checked out in the assert so these
+        // are just suffixes
+            continue;
+
+        value += pow * (s - '0');
+        pow *= 8;
+    }
+
+    return value;
+}
+
+///
+unittest
+{
+    int a = octal!int("10");
+    assert(a == 8);
+}
+
+/*
+Take a look at int.max and int.max+1 in octal and the logic for this
+function follows directly.
+ */
+private template octalFitsInInt(string octalNum)
+{
+    // note it is important to strip the literal of all
+    // non-numbers. kill the suffix and underscores lest they mess up
+    // the number of digits here that we depend on.
+    enum bool octalFitsInInt = strippedOctalLiteral(octalNum).length < 11 ||
+        strippedOctalLiteral(octalNum).length == 11 &&
+        strippedOctalLiteral(octalNum)[0] == '1';
+}
+
+private string strippedOctalLiteral(string original)
+{
+    string stripped = "";
+    foreach (c; original)
+        if (c >= '0' && c <= '7')
+            stripped ~= c;
+    return stripped;
+}
+
+private template literalIsLong(string num)
+{
+    static if (num.length > 1)
+    // can be xxL or xxLu according to spec
+        enum literalIsLong = (num[$-1] == 'L' || num[$-2] == 'L');
+    else
+        enum literalIsLong = false;
+}
+
+private template literalIsUnsigned(string num)
+{
+    static if (num.length > 1)
+    // can be xxU or xxUL according to spec
+        enum literalIsUnsigned = (num[$-1] == 'u' || num[$-2] == 'u')
+            // both cases are allowed too
+            || (num[$-1] == 'U' || num[$-2] == 'U');
+    else
+        enum literalIsUnsigned = false;
+}
+
+/*
+Returns if the given string is a correctly formatted octal literal.
+
+The format is specified in spec/lex.html. The leading zero is allowed, but
+not required.
+ */
+private bool isOctalLiteral(string num)
+{
+    if (num.length == 0)
+        return false;
+
+    // Must start with a number. To avoid confusion, literals that
+    // start with a '0' are not allowed
+    if (num[0] == '0' && num.length > 1)
+        return false;
+    if (num[0] < '0' || num[0] > '7')
+        return false;
+
+    foreach (i, c; num)
+    {
+        if ((c < '0' || c > '7') && c != '_') // not a legal character
+        {
+            if (i < num.length - 2)
+                    return false;
+            else   // gotta check for those suffixes
+            {
+                if (c != 'U' && c != 'u' && c != 'L')
+                        return false;
+                if (i != num.length - 1)
+                {
+                    // if we're not the last one, the next one must
+                    // also be a suffix to be valid
+                    char c2 = num[$-1];
+                    if (c2 != 'U' && c2 != 'u' && c2 != 'L')
+                        return false; // spam at the end of the string
+                    if (c2 == c)
+                        return false; // repeats are disallowed
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+unittest
+{
+    // ensure that you get the right types, even with embedded underscores
+    auto w = octal!"100_000_000_000";
+    static assert(!is(typeof(w) == int));
+    auto w2 = octal!"1_000_000_000";
+    static assert(is(typeof(w2) == int));
+
+    static assert(octal!"45" == 37);
+    static assert(octal!"0" == 0);
+    static assert(octal!"7" == 7);
+    static assert(octal!"10" == 8);
+    static assert(octal!"666" == 438);
+
+    static assert(octal!45 == 37);
+    static assert(octal!0 == 0);
+    static assert(octal!7 == 7);
+    static assert(octal!10 == 8);
+    static assert(octal!666 == 438);
+
+    static assert(octal!"66_6" == 438);
+
+    static assert(octal!2520046213 == 356535435);
+    static assert(octal!"2520046213" == 356535435);
+
+    static assert(octal!17777777777 == int.max);
+
+    static assert(!__traits(compiles, octal!823));
+
+    static assert(!__traits(compiles, octal!"823"));
+
+    static assert(!__traits(compiles, octal!"_823"));
+    static assert(!__traits(compiles, octal!"spam"));
+    static assert(!__traits(compiles, octal!"77%"));
+
+    static assert(is(typeof(octal!"17777777777") == int));
+    static assert(octal!"17777777777" == int.max);
+
+    static assert(is(typeof(octal!"20000000000U") == ulong)); // Shouldn't this be uint?
+    static assert(octal!"20000000000" == uint(int.max) + 1);
+
+    static assert(is(typeof(octal!"777777777777777777777") == long));
+    static assert(octal!"777777777777777777777" == long.max);
+
+    static assert(is(typeof(octal!"1000000000000000000000U") == ulong));
+    static assert(octal!"1000000000000000000000" == ulong(long.max) + 1);
+
+    int a;
+    long b;
+
+    // biggest value that should fit in an it
+    a = octal!"17777777777";
+    assert(a == int.max);
+    // should not fit in the int
+    static assert(!__traits(compiles, a = octal!"20000000000"));
+    // ... but should fit in a long
+    b = octal!"20000000000";
+    assert(b == 1L + int.max);
+
+    b = octal!"1L";
+    assert(b == 1);
+    b = octal!1L;
+    assert(b == 1);
+}
+
+/+
+emplaceRef is a package function for phobos internal use. It works like
+emplace, but takes its argument by ref (as opposed to "by pointer").
+
+This makes it easier to use, easier to be safe, and faster in a non-inline
+build.
+
+Furthermore, emplaceRef optionally takes a type paremeter, which specifies
+the type we want to build. This helps to build qualified objects on mutable
+buffer, without breaking the type system with unsafe casts.
++/
+package void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args)
+if (is(UT == Unqual!T))
+{
+    static if (args.length == 0)
+    {
+        static assert (is(typeof({static T i;})),
+            convFormat("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof));
+        static if (is(T == class)) static assert (!isAbstractClass!T,
+            T.stringof ~ " is abstract and it can't be emplaced");
+        emplaceInitializer(chunk);
+    }
+    else static if (
+        !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */
+        ||
+        Args.length == 1 && is(typeof({T t = args[0];})) /* conversions */
+        ||
+        is(typeof(T(args))) /* general constructors */)
+    {
+        static struct S
+        {
+            T payload;
+            this(ref Args x)
+            {
+                static if (Args.length == 1)
+                    static if (is(typeof(payload = x[0])))
+                        payload = x[0];
+                    else
+                        payload = T(x[0]);
+                else
+                    payload = T(x);
+            }
+        }
+        if (__ctfe)
+        {
+            static if (is(typeof(chunk = T(args))))
+                chunk = T(args);
+            else static if (args.length == 1 && is(typeof(chunk = args[0])))
+                chunk = args[0];
+            else assert(0, "CTFE emplace doesn't support "
+                ~ T.stringof ~ " from " ~ Args.stringof);
+        }
+        else
+        {
+            S* p = () @trusted { return cast(S*) &chunk; }();
+            emplaceInitializer(*p);
+            p.__ctor(args);
+        }
+    }
+    else static if (is(typeof(chunk.__ctor(args))))
+    {
+        // This catches the rare case of local types that keep a frame pointer
+        emplaceInitializer(chunk);
+        chunk.__ctor(args);
+    }
+    else
+    {
+        //We can't emplace. Try to diagnose a disabled postblit.
+        static assert(!(Args.length == 1 && is(Args[0] : T)),
+            convFormat("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof));
+
+        //We can't emplace.
+        static assert(false,
+            convFormat("%s cannot be emplaced from %s.", T.stringof, Args[].stringof));
+    }
+}
+// ditto
+package void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args)
+if (is(UT == Unqual!UT))
+{
+    emplaceRef!(UT, UT)(chunk, args);
+}
+
+//emplace helper functions
+private void emplaceInitializer(T)(ref T chunk) @trusted pure nothrow
+{
+    static if (!hasElaborateAssign!T && isAssignable!T)
+        chunk = T.init;
+    else
+    {
+        import core.stdc.string : memcpy;
+        static immutable T init = T.init;
+        memcpy(&chunk, &init, T.sizeof);
+    }
+}
+
+// emplace
+/**
+Given a pointer $(D chunk) to uninitialized memory (but already typed
+as $(D T)), constructs an object of non-$(D class) type $(D T) at that
+address.
+
+Returns: A pointer to the newly constructed object (which is the same
+as $(D chunk)).
+ */
+T* emplace(T)(T* chunk) @safe pure nothrow
+{
+    emplaceRef!T(*chunk);
+    return chunk;
+}
+
+///
+unittest
+{
+    static struct S
+    {
+        int i = 42;
+    }
+    S[2] s2 = void;
+    emplace(&s2);
+    assert(s2[0].i == 42 && s2[1].i == 42);
+}
+
+/**
+Given a pointer $(D chunk) to uninitialized memory (but already typed
+as a non-class type $(D T)), constructs an object of type $(D T) at
+that address from arguments $(D args).
+
+This function can be $(D @trusted) if the corresponding constructor of
+$(D T) is $(D @safe).
+
+Returns: A pointer to the newly constructed object (which is the same
+as $(D chunk)).
+ */
+T* emplace(T, Args...)(T* chunk, auto ref Args args)
+if (is(T == struct) || Args.length == 1)
+{
+    emplaceRef!T(*chunk, args);
+    return chunk;
+}
+
+///
+unittest
+{
+    int a;
+    int b = 42;
+    assert(*emplace!int(&a, b) == 42);
+}
+
+private void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName) @nogc pure nothrow
+{
+    assert(chunk.length >= typeSize, "emplace: Chunk size too small.");
+    assert((cast(size_t)chunk.ptr) % typeAlignment == 0, "emplace: Chunk is not aligned.");
+}
+
+/**
+Given a raw memory area $(D chunk), constructs an object of $(D class)
+type $(D T) at that address. The constructor is passed the arguments
+$(D Args). The $(D chunk) must be as least as large as $(D T) needs
+and should have an alignment multiple of $(D T)'s alignment. (The size
+of a $(D class) instance is obtained by using $(D
+__traits(classInstanceSize, T))).
+
+This function can be $(D @trusted) if the corresponding constructor of
+$(D T) is $(D @safe).
+
+Returns: A pointer to the newly constructed object.
+ */
+T emplace(T, Args...)(void[] chunk, auto ref Args args)
+if (is(T == class))
+{
+    static assert (!isAbstractClass!T, T.stringof ~
+        " is abstract and it can't be emplaced");
+
+    enum classSize = __traits(classInstanceSize, T);
+    testEmplaceChunk(chunk, classSize, classInstanceAlignment!T, T.stringof);
+    auto result = cast(T) chunk.ptr;
+
+    // Initialize the object in its pre-ctor state
+    chunk[0 .. classSize] = typeid(T).initializer[];
+
+    // Call the ctor if any
+    static if (is(typeof(result.__ctor(args))))
+    {
+        // T defines a genuine constructor accepting args
+        // Go the classic route: write .init first, then call ctor
+        result.__ctor(args);
+    }
+    else
+    {
+        static assert(args.length == 0 && !is(typeof(&T.__ctor)),
+            "Don't know how to initialize an object of type "
+            ~ T.stringof ~ " with arguments " ~ Args.stringof);
+    }
+    return result;
+}
+
+///
+unittest
+{
+    interface I {}
+    class K : I {}
+
+    K k = void;
+    emplace(&k);
+    assert(k is null);
+
+    I i = void;
+    emplace(&i);
+    assert(i is null);
+}
+
+@nogc pure nothrow unittest
+{
+    int var = 6;
+    ubyte[__traits(classInstanceSize, __conv_EmplaceTestClass)] buf;
+    auto k = emplace!__conv_EmplaceTestClass(buf, 5, var);
+    assert(k.i == 5);
+    assert(var == 7);
+}
+
+/**
+Given a raw memory area $(D chunk), constructs an object of non-$(D
+class) type $(D T) at that address. The constructor is passed the
+arguments $(D args), if any. The $(D chunk) must be as least as large
+as $(D T) needs and should have an alignment multiple of $(D T)'s
+alignment.
+
+This function can be $(D @trusted) if the corresponding constructor of
+$(D T) is $(D @safe).
+
+Returns: A pointer to the newly constructed object.
+ */
+T* emplace(T, Args...)(void[] chunk, auto ref Args args)
+    if (!is(T == class))
+{
+    testEmplaceChunk(chunk, T.sizeof, T.alignof, T.stringof);
+    emplaceRef!(T, Unqual!T)(*cast(Unqual!T*) chunk.ptr, args);
+    return cast(T*) chunk.ptr;
+}
+
+///
+unittest
+{
+    struct S
+    {
+        int a, b;
+    }
+    auto p = new void[S.sizeof];
+    S s;
+    s.a = 42;
+    s.b = 43;
+    auto s1 = emplace!S(p, s);
+    assert(s1.a == 42 && s1.b == 43);
+}
+
+// Bulk of emplace unittests starts here
+
+unittest /* unions */
+{
+    static union U
+    {
+        string a;
+        int b;
+        struct
+        {
+            long c;
+            int[] d;
+        }
+    }
+    U u1 = void;
+    U u2 = { "hello" };
+    emplace(&u1, u2);
+    assert(u1.a == "hello");
+}
+
+version(unittest) private struct __conv_EmplaceTest
+{
+    int i = 3;
+    this(int i)
+    {
+        assert(this.i == 3 && i == 5);
+        this.i = i;
+    }
+    this(int i, ref int j)
+    {
+        assert(i == 5 && j == 6);
+        this.i = i;
+        ++j;
+    }
+
+@disable:
+    this();
+    this(this);
+    void opAssign();
+}
+
+version(unittest) private class __conv_EmplaceTestClass
+{
+    int i = 3;
+    this(int i) @nogc @safe pure nothrow
+    {
+        assert(this.i == 3 && i == 5);
+        this.i = i;
+    }
+    this(int i, ref int j) @nogc @safe pure nothrow
+    {
+        assert(i == 5 && j == 6);
+        this.i = i;
+        ++j;
+    }
+}
+
+unittest // bugzilla 15772
+{
+    abstract class Foo {}
+    class Bar: Foo {}
+    void[] memory;
+    // test in emplaceInitializer
+    static assert(!is(typeof(emplace!Foo(cast(Foo*) memory.ptr))));
+    static assert( is(typeof(emplace!Bar(cast(Bar*) memory.ptr))));
+    // test in the emplace overload that takes void[]
+    static assert(!is(typeof(emplace!Foo(memory))));
+    static assert( is(typeof(emplace!Bar(memory))));
+}
+
+unittest
+{
+    struct S { @disable this(); }
+    S s = void;
+    static assert(!__traits(compiles, emplace(&s)));
+    emplace(&s, S.init);
+}
+
+unittest
+{
+    struct S1
+    {}
+
+    struct S2
+    {
+        void opAssign(S2);
+    }
+
+    S1 s1 = void;
+    S2 s2 = void;
+    S1[2] as1 = void;
+    S2[2] as2 = void;
+    emplace(&s1);
+    emplace(&s2);
+    emplace(&as1);
+    emplace(&as2);
+}
+
+unittest
+{
+    static struct S1
+    {
+        this(this) @disable;
+    }
+    static struct S2
+    {
+        this() @disable;
+    }
+    S1[2] ss1 = void;
+    S2[2] ss2 = void;
+    emplace(&ss1);
+    static assert(!__traits(compiles, emplace(&ss2)));
+    S1 s1 = S1.init;
+    S2 s2 = S2.init;
+    static assert(!__traits(compiles, emplace(&ss1, s1)));
+    emplace(&ss2, s2);
+}
+
+unittest
+{
+    struct S
+    {
+        immutable int i;
+    }
+    S s = void;
+    S[2] ss1 = void;
+    S[2] ss2 = void;
+    emplace(&s, 5);
+    assert(s.i == 5);
+    emplace(&ss1, s);
+    assert(ss1[0].i == 5 && ss1[1].i == 5);
+    emplace(&ss2, ss1);
+    assert(ss2 == ss1);
+}
+
+//Start testing emplace-args here
+
+unittest
+{
+    interface I {}
+    class K : I {}
+
+    K k = null, k2 = new K;
+    assert(k !is k2);
+    emplace!K(&k, k2);
+    assert(k is k2);
+
+    I i = null;
+    assert(i !is k);
+    emplace!I(&i, k);
+    assert(i is k);
+}
+
+unittest
+{
+    static struct S
+    {
+        int i = 5;
+        void opAssign(S){assert(0);}
+    }
+    S[2] sa = void;
+    S[2] sb;
+    emplace(&sa, sb);
+    assert(sa[0].i == 5 && sa[1].i == 5);
+}
+
+//Start testing emplace-struct here
+
+// Test constructor branch
+unittest
+{
+    struct S
+    {
+        double x = 5, y = 6;
+        this(int a, int b)
+        {
+            assert(x == 5 && y == 6);
+            x = a;
+            y = b;
+        }
+    }
+
+    auto s1 = new void[S.sizeof];
+    auto s2 = S(42, 43);
+    assert(*emplace!S(cast(S*) s1.ptr, s2) == s2);
+    assert(*emplace!S(cast(S*) s1, 44, 45) == S(44, 45));
+}
+
+unittest
+{
+    __conv_EmplaceTest k = void;
+    emplace(&k, 5);
+    assert(k.i == 5);
+}
+
+unittest
+{
+    int var = 6;
+    __conv_EmplaceTest k = void;
+    emplace(&k, 5, var);
+    assert(k.i == 5);
+    assert(var == 7);
+}
+
+// Test matching fields branch
+unittest
+{
+    struct S { uint n; }
+    S s;
+    emplace!S(&s, 2U);
+    assert(s.n == 2);
+}
+
+unittest
+{
+    struct S { int a, b; this(int){} }
+    S s;
+    static assert(!__traits(compiles, emplace!S(&s, 2, 3)));
+}
+
+unittest
+{
+    struct S { int a, b = 7; }
+    S s1 = void, s2 = void;
+
+    emplace!S(&s1, 2);
+    assert(s1.a == 2 && s1.b == 7);
+
+    emplace!S(&s2, 2, 3);
+    assert(s2.a == 2 && s2.b == 3);
+}
+
+//opAssign
+unittest
+{
+    static struct S
+    {
+        int i = 5;
+        void opAssign(int){assert(0);}
+        void opAssign(S){assert(0);}
+    }
+    S sa1 = void;
+    S sa2 = void;
+    S sb1 = S(1);
+    emplace(&sa1, sb1);
+    emplace(&sa2, 2);
+    assert(sa1.i == 1);
+    assert(sa2.i == 2);
+}
+
+//postblit precedence
+unittest
+{
+    //Works, but breaks in "-w -O" because of @@@9332@@@.
+    //Uncomment test when 9332 is fixed.
+    static struct S
+    {
+        int i;
+
+        this(S other){assert(false);}
+        this(int i){this.i = i;}
+        this(this){}
+    }
+    S a = void;
+    assert(is(typeof({S b = a;})));    //Postblit
+    assert(is(typeof({S b = S(a);}))); //Constructor
+    auto b = S(5);
+    emplace(&a, b);
+    assert(a.i == 5);
+
+    static struct S2
+    {
+        int* p;
+        this(const S2){}
+    }
+    static assert(!is(immutable S2 : S2));
+    S2 s2 = void;
+    immutable is2 = (immutable S2).init;
+    emplace(&s2, is2);
+}
+
+//nested structs and postblit
+unittest
+{
+    static struct S
+    {
+        int* p;
+        this(int i){p = [i].ptr;}
+        this(this)
+        {
+            if (p)
+                p = [*p].ptr;
+        }
+    }
+    static struct SS
+    {
+        S s;
+        void opAssign(const SS)
+        {
+            assert(0);
+        }
+    }
+    SS ssa = void;
+    SS ssb = SS(S(5));
+    emplace(&ssa, ssb);
+    assert(*ssa.s.p == 5);
+    assert(ssa.s.p != ssb.s.p);
+}
+
+//disabled postblit
+unittest
+{
+    static struct S1
+    {
+        int i;
+        @disable this(this);
+    }
+    S1 s1 = void;
+    emplace(&s1, 1);
+    assert(s1.i == 1);
+    static assert(!__traits(compiles, emplace(&s1, S1.init)));
+
+    static struct S2
+    {
+        int i;
+        @disable this(this);
+        this(ref S2){}
+    }
+    S2 s2 = void;
+    static assert(!__traits(compiles, emplace(&s2, 1)));
+    emplace(&s2, S2.init);
+
+    static struct SS1
+    {
+        S1 s;
+    }
+    SS1 ss1 = void;
+    emplace(&ss1);
+    static assert(!__traits(compiles, emplace(&ss1, SS1.init)));
+
+    static struct SS2
+    {
+        S2 s;
+    }
+    SS2 ss2 = void;
+    emplace(&ss2);
+    static assert(!__traits(compiles, emplace(&ss2, SS2.init)));
+
+
+    // SS1 sss1 = s1;      //This doesn't compile
+    // SS1 sss1 = SS1(s1); //This doesn't compile
+    // So emplace shouldn't compile either
+    static assert(!__traits(compiles, emplace(&sss1, s1)));
+    static assert(!__traits(compiles, emplace(&sss2, s2)));
+}
+
+//Imutability
+unittest
+{
+    //Castable immutability
+    {
+        static struct S1
+        {
+            int i;
+        }
+        static assert(is( immutable(S1) : S1));
+        S1 sa = void;
+        auto sb = immutable(S1)(5);
+        emplace(&sa, sb);
+        assert(sa.i == 5);
+    }
+    //Un-castable immutability
+    {
+        static struct S2
+        {
+            int* p;
+        }
+        static assert(!is(immutable(S2) : S2));
+        S2 sa = void;
+        auto sb = immutable(S2)(null);
+        assert(!__traits(compiles, emplace(&sa, sb)));
+    }
+}
+
+unittest
+{
+    static struct S
+    {
+        immutable int i;
+        immutable(int)* j;
+    }
+    S s = void;
+    emplace(&s, 1, null);
+    emplace(&s, 2, &s.i);
+    assert(s is S(2, &s.i));
+}
+
+//Context pointer
+unittest
+{
+    int i = 0;
+    {
+        struct S1
+        {
+            void foo(){++i;}
+        }
+        S1 sa = void;
+        S1 sb;
+        emplace(&sa, sb);
+        sa.foo();
+        assert(i == 1);
+    }
+    {
+        struct S2
+        {
+            void foo(){++i;}
+            this(this){}
+        }
+        S2 sa = void;
+        S2 sb;
+        emplace(&sa, sb);
+        sa.foo();
+        assert(i == 2);
+    }
+
+    ////NOTE: THESE WILL COMPILE
+    ////But will not correctly emplace the context pointer
+    ////The problem lies with voldemort, and not emplace.
+    //{
+    //    struct S3
+    //    {
+    //        int k;
+    //        void foo(){++i;}
+    //    }
+    //}
+    //S3 s3 = void;
+    //emplace(&s3);    //S3.init has no context pointer information
+    //emplace(&s3, 1); //No way to obtain context pointer once inside emplace
+}
+
+//Alias this
+unittest
+{
+    static struct S
+    {
+        int i;
+    }
+    //By Ref
+    {
+        static struct SS1
+        {
+            int j;
+            S s;
+            alias s this;
+        }
+        S s = void;
+        SS1 ss = SS1(1, S(2));
+        emplace(&s, ss);
+        assert(s.i == 2);
+    }
+    //By Value
+    {
+        static struct SS2
+        {
+            int j;
+            S s;
+            S foo() @property{return s;}
+            alias foo this;
+        }
+        S s = void;
+        SS2 ss = SS2(1, S(2));
+        emplace(&s, ss);
+        assert(s.i == 2);
+    }
+}
+version(unittest)
+{
+    //Ambiguity
+    struct __std_conv_S
+    {
+        int i;
+        this(__std_conv_SS ss)         {assert(0);}
+        static opCall(__std_conv_SS ss)
+        {
+            __std_conv_S s; s.i = ss.j;
+            return s;
+        }
+    }
+    struct __std_conv_SS
+    {
+        int j;
+        __std_conv_S s;
+        ref __std_conv_S foo() return @property {s.i = j; return s;}
+        alias foo this;
+    }
+    static assert(is(__std_conv_SS : __std_conv_S));
+    unittest
+    {
+        __std_conv_S s = void;
+        __std_conv_SS ss = __std_conv_SS(1);
+
+        __std_conv_S sTest1 = ss; //this calls "SS alias this" (and not "S.this(SS)")
+        emplace(&s, ss); //"alias this" should take precedence in emplace over "opCall"
+        assert(s.i == 1);
+    }
+}
+
+//Nested classes
+unittest
+{
+    class A{}
+    static struct S
+    {
+        A a;
+    }
+    S s1 = void;
+    S s2 = S(new A);
+    emplace(&s1, s2);
+    assert(s1.a is s2.a);
+}
+
+//safety & nothrow & CTFE
+unittest
+{
+    //emplace should be safe for anything with no elaborate opassign
+    static struct S1
+    {
+        int i;
+    }
+    static struct S2
+    {
+        int i;
+        this(int j)@safe nothrow{i = j;}
+    }
+
+    int i;
+    S1 s1 = void;
+    S2 s2 = void;
+
+    auto pi = &i;
+    auto ps1 = &s1;
+    auto ps2 = &s2;
+
+    void foo() @safe nothrow
+    {
+        emplace(pi);
+        emplace(pi, 5);
+        emplace(ps1);
+        emplace(ps1, 5);
+        emplace(ps1, S1.init);
+        emplace(ps2);
+        emplace(ps2, 5);
+        emplace(ps2, S2.init);
+    }
+    foo();
+
+    T bar(T)() @property
+    {
+        T t/+ = void+/; //CTFE void illegal
+        emplace(&t, 5);
+        return t;
+    }
+    // CTFE
+    enum a = bar!int;
+    static assert(a == 5);
+    enum b = bar!S1;
+    static assert(b.i == 5);
+    enum c = bar!S2;
+    static assert(c.i == 5);
+    // runtime
+    auto aa = bar!int;
+    assert(aa == 5);
+    auto bb = bar!S1;
+    assert(bb.i == 5);
+    auto cc = bar!S2;
+    assert(cc.i == 5);
+}
+
+
+unittest
+{
+    struct S
+    {
+        int[2] get(){return [1, 2];}
+        alias get this;
+    }
+    struct SS
+    {
+        int[2] ii;
+    }
+    struct ISS
+    {
+        int[2] ii;
+    }
+    S s;
+    SS ss = void;
+    ISS iss = void;
+    emplace(&ss, s);
+    emplace(&iss, s);
+    assert(ss.ii == [1, 2]);
+    assert(iss.ii == [1, 2]);
+}
+
+//disable opAssign
+unittest
+{
+    static struct S
+    {
+        @disable void opAssign(S);
+    }
+    S s;
+    emplace(&s, S.init);
+}
+
+//opCall
+unittest
+{
+    int i;
+    //Without constructor
+    {
+        static struct S1
+        {
+            int i;
+            static S1 opCall(int*){assert(0);}
+        }
+        S1 s = void;
+        static assert(!__traits(compiles, emplace(&s,  1)));
+    }
+    //With constructor
+    {
+        static struct S2
+        {
+            int i = 0;
+            static S2 opCall(int*){assert(0);}
+            static S2 opCall(int){assert(0);}
+            this(int i){this.i = i;}
+        }
+        S2 s = void;
+        emplace(&s,  1);
+        assert(s.i == 1);
+    }
+    //With postblit ambiguity
+    {
+        static struct S3
+        {
+            int i = 0;
+            static S3 opCall(ref S3){assert(0);}
+        }
+        S3 s = void;
+        emplace(&s, S3.init);
+    }
+}
+
+unittest //@@@9559@@@
+{
+    import std.algorithm : map;
+    import std.typecons : Nullable;
+    import std.array: array;
+    alias I = Nullable!int;
+    auto ints = [0, 1, 2].map!(i => i & 1 ? I.init : I(i))();
+    auto asArray = array(ints);
+}
+
+unittest //http://forum.dlang.org/post/nxbdgtdlmwscocbiypjs@forum.dlang.org
+{
+    import std.array : array;
+    import std.datetime : SysTime, UTC;
+    import std.math : isNaN;
+
+    static struct A
+    {
+        double i;
+    }
+
+    static struct B
+    {
+        invariant()
+        {
+            if(j == 0)
+                assert(a.i.isNaN(), "why is 'j' zero?? and i is not NaN?");
+            else
+                assert(!a.i.isNaN());
+        }
+        SysTime when; // comment this line avoid the breakage
+        int j;
+        A a;
+    }
+
+    B b1 = B.init;
+    assert(&b1); // verify that default eyes invariants are ok;
+
+    auto b2 = B(SysTime(0, UTC()), 1, A(1));
+    assert(&b2);
+    auto b3 = B(SysTime(0, UTC()), 1, A(1));
+    assert(&b3);
+
+    auto arr = [b2, b3];
+
+    assert(arr[0].j == 1);
+    assert(arr[1].j == 1);
+    auto a2 = arr.array(); // << bang, invariant is raised, also if b2 and b3 are good
+}
+
+//static arrays
+unittest
+{
+    static struct S
+    {
+        int[2] ii;
+    }
+    static struct IS
+    {
+        immutable int[2] ii;
+    }
+    int[2] ii;
+    S  s   = void;
+    IS ims = void;
+    ubyte ub = 2;
+    emplace(&s, ub);
+    emplace(&s, ii);
+    emplace(&ims, ub);
+    emplace(&ims, ii);
+    uint[2] uu;
+    static assert(!__traits(compiles, {S ss = S(uu);}));
+    static assert(!__traits(compiles, emplace(&s, uu)));
+}
+
+unittest
+{
+    int[2]  sii;
+    int[2]  sii2;
+    uint[2] uii;
+    uint[2] uii2;
+    emplace(&sii, 1);
+    emplace(&sii, 1U);
+    emplace(&uii, 1);
+    emplace(&uii, 1U);
+    emplace(&sii, sii2);
+    //emplace(&sii, uii2); //Sorry, this implementation doesn't know how to...
+    //emplace(&uii, sii2); //Sorry, this implementation doesn't know how to...
+    emplace(&uii, uii2);
+    emplace(&sii, sii2[]);
+    //emplace(&sii, uii2[]); //Sorry, this implementation doesn't know how to...
+    //emplace(&uii, sii2[]); //Sorry, this implementation doesn't know how to...
+    emplace(&uii, uii2[]);
+}
+
+unittest
+{
+    bool allowDestruction = false;
+    struct S
+    {
+        int i;
+        this(this){}
+        ~this(){assert(allowDestruction);}
+    }
+    S s = S(1);
+    S[2] ss1 = void;
+    S[2] ss2 = void;
+    S[2] ss3 = void;
+    emplace(&ss1, s);
+    emplace(&ss2, ss1);
+    emplace(&ss3, ss2[]);
+    assert(ss1[1] == s);
+    assert(ss2[1] == s);
+    assert(ss3[1] == s);
+    allowDestruction = true;
+}
+
+unittest
+{
+    //Checks postblit, construction, and context pointer
+    int count = 0;
+    struct S
+    {
+        this(this)
+        {
+            ++count;
+        }
+        ~this()
+        {
+            --count;
+        }
+    }
+
+    S s;
+    {
+        S[4] ss = void;
+        emplace(&ss, s);
+        assert(count == 4);
+    }
+    assert(count == 0);
+}
+
+unittest
+{
+    struct S
+    {
+        int i;
+    }
+    S s;
+    S[2][2][2] sss = void;
+    emplace(&sss, s);
+}
+
+unittest //Constness
+{
+    import std.stdio;
+
+    int a = void;
+    emplaceRef!(const int)(a, 5);
+
+    immutable i = 5;
+    const(int)* p = void;
+    emplaceRef!(const int*)(p, &i);
+
+    struct S
+    {
+        int* p;
+    }
+    alias IS = immutable(S);
+    S s = void;
+    emplaceRef!IS(s, IS());
+    S[2] ss = void;
+    emplaceRef!(IS[2])(ss, IS());
+
+    IS[2] iss = IS.init;
+    emplaceRef!(IS[2])(ss, iss);
+    emplaceRef!(IS[2])(ss, iss[]);
+}
+
+pure nothrow @safe @nogc unittest
+{
+    int i;
+    emplaceRef(i);
+    emplaceRef!int(i);
+    emplaceRef(i, 5);
+    emplaceRef!int(i, 5);
+}
+
+// Test attribute propagation for UDTs
+pure nothrow @safe /* @nogc */ unittest
+{
+    static struct Safe
+    {
+        this(this) pure nothrow @safe @nogc {}
+    }
+
+    Safe safe = void;
+    emplaceRef(safe, Safe());
+
+    Safe[1] safeArr = [Safe()];
+    Safe[1] uninitializedSafeArr = void;
+    emplaceRef(uninitializedSafeArr, safe);
+    emplaceRef(uninitializedSafeArr, safeArr);
+
+    static struct Unsafe
+    {
+        this(this) @system {}
+    }
+
+    Unsafe unsafe = void;
+    static assert(!__traits(compiles, emplaceRef(unsafe, Unsafe())));
+
+    Unsafe[1] unsafeArr = [Unsafe()];
+    Unsafe[1] uninitializedUnsafeArr = void;
+    static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafe)));
+    static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafeArr)));
+}
+
+unittest
+{
+    // Issue 15313
+    static struct Node
+    {
+        int payload;
+        Node* next;
+        uint refs;
+    }
+
+    import core.stdc.stdlib : malloc;
+    void[] buf = malloc(Node.sizeof)[0 .. Node.sizeof];
+
+    import std.conv : emplace;
+    const Node* n = emplace!(const Node)(buf, 42, null, 10);
+    assert(n.payload == 42);
+    assert(n.next == null);
+    assert(n.refs == 10);
+}
+
+unittest
+{
+    int var = 6;
+    auto k = emplace!__conv_EmplaceTest(new void[__conv_EmplaceTest.sizeof], 5, var);
+    assert(k.i == 5);
+    assert(var == 7);
+}
+
+unittest
+{
+    class A
+    {
+        int x = 5;
+        int y = 42;
+        this(int z)
+        {
+            assert(x == 5 && y == 42);
+            x = y = z;
+        }
+    }
+    void[] buf;
+
+    static byte[__traits(classInstanceSize, A)] sbuf;
+    buf = sbuf[];
+    auto a = emplace!A(buf, 55);
+    assert(a.x == 55 && a.y == 55);
+
+    // emplace in bigger buffer
+    buf = new byte[](__traits(classInstanceSize, A) + 10);
+    a = emplace!A(buf, 55);
+    assert(a.x == 55 && a.y == 55);
+
+    // need ctor args
+    static assert(!is(typeof(emplace!A(buf))));
+}
+// Bulk of emplace unittests ends here
+
+unittest
+{
+    import std.algorithm : equal, map;
+    // Check fix for http://d.puremagic.com/issues/show_bug.cgi?id=2971
+    assert(equal(map!(to!int)(["42", "34", "345"]), [42, 34, 345]));
+}
+
+// Undocumented for the time being
+void toTextRange(T, W)(T value, W writer)
+    if (isIntegral!T && isOutputRange!(W, char))
+{
+    char[value.sizeof * 4] buffer = void;
+    uint i = cast(uint) (buffer.length - 1);
+
+    bool negative = value < 0;
+    Unqual!(Unsigned!T) v = negative ? -value : value;
+
+    while (v >= 10)
+    {
+        auto c = cast(uint) (v % 10);
+        v /= 10;
+        buffer[i--] = cast(char) (c + '0');
+    }
+
+    buffer[i] = cast(char) (v + '0'); //hexDigits[cast(uint) v];
+    if (negative)
+        buffer[--i] = '-';
+    put(writer, buffer[i .. $]);
+}
+
+unittest
+{
+    import std.array : appender;
+    auto result = appender!(char[])();
+    toTextRange(-1, result);
+    assert(result.data == "-1");
+}
+
+
+/**
+    Returns the corresponding _unsigned value for $(D x) (e.g. if $(D x) has type
+    $(D int), it returns $(D cast(uint) x)). The advantage compared to the cast
+    is that you do not need to rewrite the cast if $(D x) later changes type
+    (e.g from $(D int) to $(D long)).
+
+    Note that the result is always mutable even if the original type was const
+    or immutable. In order to retain the constness, use $(XREF traits, Unsigned).
+ */
+auto unsigned(T)(T x) if (isIntegral!T)
+{
+    return cast(Unqual!(Unsigned!T))x;
+}
+
+///
+unittest
+{
+    immutable int s = 42;
+    auto u1 = unsigned(s); //not qualified
+    static assert(is(typeof(u1) == uint));
+    Unsigned!(typeof(s)) u2 = unsigned(s); //same qualification
+    static assert(is(typeof(u2) == immutable uint));
+    immutable u3 = unsigned(s); //explicitly qualified
+}
+
+unittest
+{
+    foreach(T; AliasSeq!(byte, ubyte))
+    {
+        static assert(is(typeof(unsigned(cast(T)1)) == ubyte));
+        static assert(is(typeof(unsigned(cast(const T)1)) == ubyte));
+        static assert(is(typeof(unsigned(cast(immutable T)1)) == ubyte));
+    }
+
+    foreach(T; AliasSeq!(short, ushort))
+    {
+        static assert(is(typeof(unsigned(cast(T)1)) == ushort));
+        static assert(is(typeof(unsigned(cast(const T)1)) == ushort));
+        static assert(is(typeof(unsigned(cast(immutable T)1)) == ushort));
+    }
+
+    foreach(T; AliasSeq!(int, uint))
+    {
+        static assert(is(typeof(unsigned(cast(T)1)) == uint));
+        static assert(is(typeof(unsigned(cast(const T)1)) == uint));
+        static assert(is(typeof(unsigned(cast(immutable T)1)) == uint));
+    }
+
+    foreach(T; AliasSeq!(long, ulong))
+    {
+        static assert(is(typeof(unsigned(cast(T)1)) == ulong));
+        static assert(is(typeof(unsigned(cast(const T)1)) == ulong));
+        static assert(is(typeof(unsigned(cast(immutable T)1)) == ulong));
+    }
+}
+
+auto unsigned(T)(T x) if (isSomeChar!T)
+{
+    // All characters are unsigned
+    static assert(T.min == 0);
+    return cast(Unqual!T) x;
+}
+
+unittest
+{
+    foreach(T; AliasSeq!(char, wchar, dchar))
+    {
+        static assert(is(typeof(unsigned(cast(T)'A')) == T));
+        static assert(is(typeof(unsigned(cast(const T)'A')) == T));
+        static assert(is(typeof(unsigned(cast(immutable T)'A')) == T));
+    }
+}
+
+
+/**
+    Returns the corresponding _signed value for $(D x) (e.g. if $(D x) has type
+    $(D uint), it returns $(D cast(int) x)). The advantage compared to the cast
+    is that you do not need to rewrite the cast if $(D x) later changes type
+    (e.g from $(D uint) to $(D ulong)).
+
+    Note that the result is always mutable even if the original type was const
+    or immutable. In order to retain the constness, use $(XREF traits, Signed).
+ */
+auto signed(T)(T x) if (isIntegral!T)
+{
+    return cast(Unqual!(Signed!T))x;
+}
+
+///
+unittest
+{
+    immutable uint u = 42;
+    auto s1 = signed(u); //not qualified
+    static assert(is(typeof(s1) == int));
+    Signed!(typeof(u)) s2 = signed(u); //same qualification
+    static assert(is(typeof(s2) == immutable int));
+    immutable s3 = signed(u); //explicitly qualified
+}
+
+unittest
+{
+    foreach(T; AliasSeq!(byte, ubyte))
+    {
+        static assert(is(typeof(signed(cast(T)1)) == byte));
+        static assert(is(typeof(signed(cast(const T)1)) == byte));
+        static assert(is(typeof(signed(cast(immutable T)1)) == byte));
+    }
+
+    foreach(T; AliasSeq!(short, ushort))
+    {
+        static assert(is(typeof(signed(cast(T)1)) == short));
+        static assert(is(typeof(signed(cast(const T)1)) == short));
+        static assert(is(typeof(signed(cast(immutable T)1)) == short));
+    }
+
+    foreach(T; AliasSeq!(int, uint))
+    {
+        static assert(is(typeof(signed(cast(T)1)) == int));
+        static assert(is(typeof(signed(cast(const T)1)) == int));
+        static assert(is(typeof(signed(cast(immutable T)1)) == int));
+    }
+
+    foreach(T; AliasSeq!(long, ulong))
+    {
+        static assert(is(typeof(signed(cast(T)1)) == long));
+        static assert(is(typeof(signed(cast(const T)1)) == long));
+        static assert(is(typeof(signed(cast(immutable T)1)) == long));
+    }
+}
+
+unittest
+{
+    // issue 10874
+    enum Test { a = 0 }
+    ulong l = 0;
+    auto t = l.to!Test;
+}
+
+/**
+    A wrapper on top of the built-in cast operator that allows one to restrict
+    casting of the original type of the value.
+
+    A common issue with using a raw cast is that it may silently continue to
+    compile even if the value's type has changed during refactoring,
+    which breaks the initial assumption about the cast.
+
+    Params:
+        From  = The type to cast from. The programmer must ensure it is legal
+                to make this cast.
+ */
+template castFrom(From)
+{
+    /**
+        Params:
+            To    = The type _to cast _to.
+            value = The value _to cast. It must be of type $(D From),
+                    otherwise a compile-time error is emitted.
+
+        Returns:
+            the value after the cast, returned by reference if possible.
+     */
+    auto ref to(To, T)(auto ref T value) @system
+    {
+        static assert (
+            is(From == T),
+            "the value to cast is not of specified type '" ~ From.stringof ~
+                 "', it is of type '" ~ T.stringof ~ "'"
+        );
+
+        static assert (
+            is(typeof(cast(To)value)),
+            "can't cast from '" ~ From.stringof ~ "' to '" ~ To.stringof ~ "'"
+        );
+
+        return cast(To) value;
+    }
+
+    ///
+    unittest
+    {
+        // Regular cast, which has been verified to be legal by the programmer:
+        {
+            long x;
+            auto y = cast(int) x;
+        }
+
+        // However this will still compile if 'x' is changed to be a pointer:
+        {
+            long* x;
+            auto y = cast(int) x;
+        }
+
+        // castFrom provides a more reliable alternative to casting:
+        {
+            long x;
+            auto y = castFrom!long.to!int(x);
+        }
+
+        // Changing the type of 'x' will now issue a compiler error,
+        // allowing bad casts to be caught before it's too late:
+        {
+            long* x;
+            static assert (
+                !__traits(compiles, castFrom!long.to!int(x))
+            );
+
+            // if cast is still needed, must be changed to:
+            auto y = castFrom!(long*).to!int(x);
+        }
+    }
+}
+
+/**
+Check the correctness of a string for $(D hexString).
+The result is true if and only if the input string is composed of whitespace
+characters (\f\n\r\t\v lineSep paraSep nelSep) and
+an even number of hexadecimal digits (regardless of the case).
+*/
+private bool isHexLiteral(String)(in String hexData)
+{
+    import std.ascii : isHexDigit;
+    import std.uni : lineSep, paraSep, nelSep;
+    size_t i;
+    foreach(const dchar c; hexData)
+    {
+        switch (c)
+        {
+            case ' ':
+            case '\t':
+            case '\v':
+            case '\f':
+            case '\r':
+            case '\n':
+            case lineSep:
+            case paraSep:
+            case nelSep:
+                continue;
+
+            default:
+                break;
+        }
+        if (c.isHexDigit)
+            ++i;
+        else
+            return false;
+    }
+    return !(i & 1);
+}
+
+///
+unittest
+{
+    // test all the hex digits
+    static assert( ("0123456789abcdefABCDEF").isHexLiteral);
+    // empty or white strings are not valid
+    static assert( "\r\n\t".isHexLiteral);
+    // but are accepted if the count of hex digits is even
+    static assert( "A\r\n\tB".isHexLiteral);
+}
+
+unittest
+{
+    import std.ascii;
+    // empty/whites
+    static assert( "".isHexLiteral);
+    static assert( " \r".isHexLiteral);
+    static assert( whitespace.isHexLiteral);
+    static assert( ""w.isHexLiteral);
+    static assert( " \r"w.isHexLiteral);
+    static assert( ""d.isHexLiteral);
+    static assert( " \r"d.isHexLiteral);
+    static assert( "\u2028\u2029\u0085"d.isHexLiteral);
+    // odd x strings
+    static assert( !("5" ~ whitespace).isHexLiteral);
+    static assert( !"123".isHexLiteral);
+    static assert( !"1A3".isHexLiteral);
+    static assert( !"1 23".isHexLiteral);
+    static assert( !"\r\n\tC".isHexLiteral);
+    static assert( !"123"w.isHexLiteral);
+    static assert( !"1A3"w.isHexLiteral);
+    static assert( !"1 23"w.isHexLiteral);
+    static assert( !"\r\n\tC"w.isHexLiteral);
+    static assert( !"123"d.isHexLiteral);
+    static assert( !"1A3"d.isHexLiteral);
+    static assert( !"1 23"d.isHexLiteral);
+    static assert( !"\r\n\tC"d.isHexLiteral);
+    // even x strings with invalid charset
+    static assert( !"12gG".isHexLiteral);
+    static assert( !"2A  3q".isHexLiteral);
+    static assert( !"12gG"w.isHexLiteral);
+    static assert( !"2A  3q"w.isHexLiteral);
+    static assert( !"12gG"d.isHexLiteral);
+    static assert( !"2A  3q"d.isHexLiteral);
+    // valid x strings
+    static assert( ("5A" ~ whitespace).isHexLiteral);
+    static assert( ("5A 01A C FF de 1b").isHexLiteral);
+    static assert( ("0123456789abcdefABCDEF").isHexLiteral);
+    static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF").isHexLiteral);
+    static assert( ("5A 01A C FF de 1b"w).isHexLiteral);
+    static assert( ("0123456789abcdefABCDEF"w).isHexLiteral);
+    static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"w).isHexLiteral);
+    static assert( ("5A 01A C FF de 1b"d).isHexLiteral);
+    static assert( ("0123456789abcdefABCDEF"d).isHexLiteral);
+    static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"d).isHexLiteral);
+    // library version allows what's pointed by issue 10454
+    static assert( ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").isHexLiteral);
+}
+
+/**
+Converts a hex literal to a string at compile time.
+
+Takes a string made of hexadecimal digits and returns
+the matching string by converting each pair of digits to a character.
+The input string can also include white characters, which can be used
+to keep the literal string readable in the source code.
+
+The function is intended to replace the hexadecimal literal strings
+starting with $(D 'x'), which could be removed to simplify the core language.
+
+Params:
+    hexData = string to be converted.
+
+Returns:
+    a $(D string), a $(D wstring) or a $(D dstring), according to the type of hexData.
+ */
+template hexString(string hexData)
+if (hexData.isHexLiteral)
+{
+    immutable hexString = hexStrImpl(hexData);
+}
+
+/// ditto
+template hexString(wstring hexData)
+if (hexData.isHexLiteral)
+{
+    immutable hexString = hexStrImpl(hexData);
+}
+
+/// ditto
+template hexString(dstring hexData)
+if (hexData.isHexLiteral)
+{
+    immutable hexString = hexStrImpl(hexData);
+}
+
+///
+unittest
+{
+    // conversion at compile time
+    auto string1 = hexString!"304A314B";
+    assert(string1 == "0J1K");
+    auto string2 = hexString!"304A314B"w;
+    assert(string2 == "0J1K"w);
+    auto string3 = hexString!"304A314B"d;
+    assert(string3 == "0J1K"d);
+}
+
+/*
+    Takes a hexadecimal string literal and returns its representation.
+    hexData is granted to be a valid string by the caller.
+    C is granted to be a valid char type by the caller.
+*/
+@safe nothrow pure
+private auto hexStrImpl(String)(String hexData)
+{
+    import std.ascii;
+    alias C = Unqual!(ElementEncodingType!String);
+    C[] result;
+    result.length = hexData.length / 2;
+    size_t cnt;
+    ubyte v;
+    foreach(c; hexData)
+    {
+        if (c.isHexDigit)
+        {
+            ubyte x;
+            if (c >= '0' && c <= '9')
+                x = cast(ubyte)(c - '0');
+            else if (c >= 'a' && c <= 'f')
+                x = cast(ubyte)(c - ('a' - 10));
+            else if (c >= 'A' && c <= 'F')
+                x = cast(ubyte)(c - ('A' - 10));
+            if (cnt & 1)
+            {
+                v = cast(ubyte)((v << 4) | x);
+                result[cnt / 2] = v;
+            }
+            else
+                v = x;
+            ++cnt;
+        }
+    }
+    result.length = cnt / 2;
+    return result;
+}
+
+unittest
+{
+    // compile time
+    assert(hexString!"46 47 48 49 4A 4B" == "FGHIJK");
+    assert(hexString!"30\r\n\t\f\v31 32 33 32 31 30" == "0123210");
+    assert(hexString!"ab cd" == hexString!"ABCD");
+}
+
+
+/**
+ * Convert integer to a range of characters.
+ * Intended to be lightweight and fast.
+ *
+ * Params:
+ *      radix = 2, 8, 10, 16
+ *      Char = character type for output
+ *      letterCase = lower for deadbeef, upper for DEADBEEF
+ *      value = integer to convert. Can be uint or ulong. If radix is 10, can also be
+ *              int or long.
+ * Returns:
+ *      Random access range with slicing and everything
+ */
+
+auto toChars(ubyte radix = 10, Char = char, LetterCase letterCase = LetterCase.lower, T)(T value)
+    pure nothrow @nogc @safe
+    if ((radix == 2 || radix == 8 || radix == 10 || radix == 16) &&
+        (is(Unqual!T == uint) || is(Unqual!T == ulong) ||
+         radix == 10 && (is(Unqual!T == int) || is(Unqual!T == long))))
+{
+    alias UT = Unqual!T;
+
+    static if (radix == 10)
+    {
+        /* uint.max  is 42_9496_7295
+         *  int.max  is 21_4748_3647
+         * ulong.max is 1844_6744_0737_0955_1615
+         *  long.max is  922_3372_0368_5477_5807
+         */
+        static struct Result
+        {
+            void initialize(UT value)
+            {
+                bool neg = false;
+                if (value < 10)
+                {
+                    if (value >= 0)
+                    {
+                        lwr = 0;
+                        upr = 1;
+                        buf[0] = cast(char)(cast(uint)value + '0');
+                        return;
+                    }
+                    value = -value;
+                    neg = true;
+                }
+                auto i = cast(uint)buf.length - 1;
+                while (cast(Unsigned!UT)value >= 10)
+                {
+                    buf[i] = cast(ubyte)('0' + cast(Unsigned!UT)value % 10);
+                    value = unsigned(value) / 10;
+                    --i;
+                }
+                buf[i] = cast(char)(cast(uint)value + '0');
+                if (neg)
+                {
+                    buf[i - 1] = '-';
+                    --i;
+                }
+                lwr = i;
+                upr = cast(uint)buf.length;
+            }
+
+            @property size_t length() { return upr - lwr; }
+
+            @property bool empty() { return upr == lwr; }
+
+            @property Char front() { return buf[lwr]; }
+
+            void popFront() { ++lwr; }
+
+            @property Char back() { return buf[upr - 1]; }
+
+            void popBack() { --upr; }
+
+            @property Result save() { return this; }
+
+            Char opIndex(size_t i) { return buf[lwr + i]; }
+
+            Result opSlice(size_t lwr, size_t upr)
+            {
+                Result result = void;
+                result.buf = buf;
+                result.lwr = cast(uint)(this.lwr + lwr);
+                result.upr = cast(uint)(this.lwr + upr);
+                return result;
+            }
+
+          private:
+            uint lwr = void, upr = void;
+            char[(UT.sizeof == 4) ? 10 + isSigned!T : 20] buf = void;
+        }
+
+        Result result = void;
+        result.initialize(value);
+        return result;
+    }
+    else
+    {
+        static if (radix == 2)
+            enum SHIFT = 1;
+        else static if (radix == 8)
+            enum SHIFT = 3;
+        else static if (radix == 16)
+            enum SHIFT = 4;
+        else
+            static assert(0);
+        struct Result
+        {
+            this(UT value)
+            {
+                this.value = value;
+
+                ubyte len = 1;
+                while (value >>>= SHIFT)
+                   ++len;
+                this.len = len;
+            }
+
+            @property size_t length() { return len; }
+
+            @property bool empty() { return len == 0; }
+
+            @property Char front() { return opIndex(0); }
+
+            void popFront() { --len; }
+
+            @property Char back() { return opIndex(len - 1); }
+
+            void popBack()
+            {
+                value >>>= SHIFT;
+                --len;
+            }
+
+            @property Result save() { return this; }
+
+            Char opIndex(size_t i)
+            {
+                Char c = (value >>> ((len - i - 1) * SHIFT)) & ((1 << SHIFT) - 1);
+                return cast(Char)((radix < 10 || c < 10) ? c + '0'
+                                                         : (letterCase == LetterCase.upper ? c + 'A' - 10
+                                                                                           : c + 'a' - 10));
+            }
+
+            Result opSlice(size_t lwr, size_t upr)
+            {
+                Result result = void;
+                result.value = value >>> ((len - upr - 1) * SHIFT);
+                result.len = cast(ubyte)(upr - lwr);
+                return result;
+            }
+
+          private:
+            UT value;
+            ubyte len;
+        }
+
+        return Result(value);
+    }
+}
+
+
+unittest
+{
+    import std.array;
+    import std.range;
+
+    {
+        assert(toChars!2(0u).array == "0");
+        assert(toChars!2(0Lu).array == "0");
+        assert(toChars!2(1u).array == "1");
+        assert(toChars!2(1Lu).array == "1");
+
+        auto r = toChars!2(2u);
+        assert(r.length == 2);
+        assert(r[0] == '1');
+        assert(r[1..2].array == "0");
+        auto s = r.save;
+        assert(r.array == "10");
+        assert(s.retro.array == "01");
+    }
+    {
+        assert(toChars!8(0u).array == "0");
+        assert(toChars!8(0Lu).array == "0");
+        assert(toChars!8(1u).array == "1");
+        assert(toChars!8(1234567Lu).array == "4553207");
+
+        auto r = toChars!8(8u);
+        assert(r.length == 2);
+        assert(r[0] == '1');
+        assert(r[1..2].array == "0");
+        auto s = r.save;
+        assert(r.array == "10");
+        assert(s.retro.array == "01");
+    }
+    {
+        assert(toChars!10(0u).array == "0");
+        assert(toChars!10(0Lu).array == "0");
+        assert(toChars!10(1u).array == "1");
+        assert(toChars!10(1234567Lu).array == "1234567");
+        assert(toChars!10(uint.max).array == "4294967295");
+        assert(toChars!10(ulong.max).array == "18446744073709551615");
+
+        auto r = toChars(10u);
+        assert(r.length == 2);
+        assert(r[0] == '1');
+        assert(r[1..2].array == "0");
+        auto s = r.save;
+        assert(r.array == "10");
+        assert(s.retro.array == "01");
+    }
+    {
+        assert(toChars!10(0).array == "0");
+        assert(toChars!10(0L).array == "0");
+        assert(toChars!10(1).array == "1");
+        assert(toChars!10(1234567L).array == "1234567");
+        assert(toChars!10(int.max).array == "2147483647");
+        assert(toChars!10(long.max).array == "9223372036854775807");
+        assert(toChars!10(-int.max).array == "-2147483647");
+        assert(toChars!10(-long.max).array == "-9223372036854775807");
+        assert(toChars!10(int.min).array == "-2147483648");
+        assert(toChars!10(long.min).array == "-9223372036854775808");
+
+        auto r = toChars!10(10);
+        assert(r.length == 2);
+        assert(r[0] == '1');
+        assert(r[1..2].array == "0");
+        auto s = r.save;
+        assert(r.array == "10");
+        assert(s.retro.array == "01");
+    }
+    {
+        assert(toChars!(16)(0u).array == "0");
+        assert(toChars!(16)(0Lu).array == "0");
+        assert(toChars!(16)(10u).array == "a");
+        assert(toChars!(16, char, LetterCase.upper)(0x12AF34567Lu).array == "12AF34567");
+
+        auto r = toChars!(16)(16u);
+        assert(r.length == 2);
+        assert(r[0] == '1');
+        assert(r[1..2].array == "0");
+        auto s = r.save;
+        assert(r.array == "10");
+        assert(s.retro.array == "01");
+    }
+}
+
diff --git a/libphobos/src/std/cstream.d b/libphobos/src/std/cstream.d
new file mode 100644
index 00000000000..2bd161bdf0c
--- /dev/null
+++ b/libphobos/src/std/cstream.d
@@ -0,0 +1,250 @@
+// Written in the D programming language.
+
+/**
+ * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
+ *       current standards. It will be remove in October 2016.)
+ *
+ * The std.cstream module bridges core.stdc.stdio (or std.stdio) and std.stream.
+ * Both core.stdc.stdio and std.stream are publicly imported by std.cstream.
+ *
+ * Macros:
+ *      WIKI=Phobos/StdCstream
+ *
+ * Copyright: Copyright Ben Hinkle 2007 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Ben Hinkle
+ * Source:    $(PHOBOSSRC std/_cstream.d)
+ */
+/*          Copyright Ben Hinkle 2007 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+deprecated("It will be removed from Phobos in October 2016. If you still need it, go to https://github.com/DigitalMars/undeaD") module std.cstream;
+// @@@DEPRECATED_2016-10@@@
+
+public import core.stdc.stdio;
+public import std.stream;
+version(unittest) import std.stdio;
+
+import std.algorithm;
+
+/**
+ * A Stream wrapper for a C file of type FILE*.
+ */
+class CFile : Stream {
+  protected FILE* cfile;
+
+  /**
+   * Create the stream wrapper for the given C file.
+   * Params:
+   *   cfile = a valid C $(B FILE) pointer to wrap.
+   *   mode = a bitwise combination of $(B FileMode.In) for a readable file
+   *          and $(B FileMode.Out) for a writeable file.
+   *   seekable = indicates if the stream should be _seekable.
+   */
+  this(FILE* cfile, FileMode mode, bool seekable = false) {
+    super();
+    this.file = cfile;
+    readable = cast(bool)(mode & FileMode.In);
+    writeable = cast(bool)(mode & FileMode.Out);
+    this.seekable = seekable;
+  }
+
+  /**
+   * Closes the stream.
+   */
+  ~this() { close(); }
+
+  /**
+   * Property to get or set the underlying file for this stream.
+   * Setting the file marks the stream as open.
+   */
+  @property FILE* file() { return cfile; }
+
+  /**
+   * Ditto
+   */
+  @property void file(FILE* cfile) {
+    this.cfile = cfile;
+    isopen = true;
+  }
+
+  /**
+   * Overrides of the $(B Stream) methods to call the underlying $(B FILE*)
+   * C functions.
+   */
+  override void flush() { fflush(cfile); }
+
+  /**
+   * Ditto
+   */
+  override void close() {
+    if (isopen)
+      fclose(cfile);
+    isopen = readable = writeable = seekable = false;
+  }
+
+  /**
+   * Ditto
+   */
+  override bool eof() {
+    return cast(bool)(readEOF || feof(cfile));
+  }
+
+  /**
+   * Ditto
+   */
+  override char getc() {
+    return cast(char)fgetc(cfile);
+  }
+
+  /**
+   * Ditto
+   */
+  override char ungetc(char c) {
+    return cast(char)core.stdc.stdio.ungetc(c,cfile);
+  }
+
+  /**
+   * Ditto
+   */
+  override size_t readBlock(void* buffer, size_t size) {
+    size_t n = fread(buffer,1,size,cfile);
+    readEOF = cast(bool)(n == 0);
+    return n;
+  }
+
+  /**
+   * Ditto
+   */
+  override size_t writeBlock(const void* buffer, size_t size) {
+    return fwrite(buffer,1,size,cfile);
+  }
+
+  /**
+   * Ditto
+   */
+  override ulong seek(long offset, SeekPos rel) {
+    readEOF = false;
+    if (fseek(cfile,cast(int)offset,rel) != 0)
+      throw new SeekException("unable to move file pointer");
+    return ftell(cfile);
+  }
+
+  /**
+   * Ditto
+   */
+  override void writeLine(const(char)[] s) {
+    writeString(s);
+    writeString("\n");
+  }
+
+  /**
+   * Ditto
+   */
+  override void writeLineW(const(wchar)[] s) {
+    writeStringW(s);
+    writeStringW("\n");
+  }
+
+  // run a few tests
+  unittest {
+    import std.file : deleteme;
+    import std.internal.cstring : tempCString;
+
+    auto stream_file = (deleteme ~ "-stream.txt").tempCString();
+    FILE* f = fopen(stream_file,"w");
+    assert(f !is null);
+    CFile file = new CFile(f,FileMode.Out);
+    int i = 666;
+    // should be ok to write
+    assert(file.writeable);
+    file.writeLine("Testing stream.d:");
+    file.writeString("Hello, world!");
+    file.write(i);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+        assert(file.position == 19 + 13 + 4);
+    version (Posix)
+        assert(file.position == 18 + 13 + 4);
+    file.close();
+    // no operations are allowed when file is closed
+    assert(!file.readable && !file.writeable && !file.seekable);
+    f = fopen(stream_file,"r");
+    file = new CFile(f,FileMode.In,true);
+    // should be ok to read
+    assert(file.readable);
+    auto line = file.readLine();
+    auto exp = "Testing stream.d:";
+    assert(line[0] == 'T');
+    assert(line.length == exp.length);
+    assert(!std.algorithm.cmp(line, "Testing stream.d:"));
+    // jump over "Hello, "
+    file.seek(7, SeekPos.Current);
+    version (Windows)
+      assert(file.position == 19 + 7);
+    version (Posix)
+      assert(file.position == 18 + 7);
+    assert(!std.algorithm.cmp(file.readString(6), "world!"));
+    i = 0; file.read(i);
+    assert(i == 666);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+      assert(file.position == 19 + 13 + 4);
+    version (Posix)
+      assert(file.position == 18 + 13 + 4);
+    // we must be at the end of file
+    file.close();
+    f = fopen(stream_file,"w+");
+    file = new CFile(f,FileMode.In|FileMode.Out,true);
+    file.writeLine("Testing stream.d:");
+    file.writeLine("Another line");
+    file.writeLine("");
+    file.writeLine("That was blank");
+    file.position = 0;
+    char[][] lines;
+    foreach(char[] line; file) {
+      lines ~= line.dup;
+    }
+    assert( lines.length == 5 );
+    assert( lines[0] == "Testing stream.d:");
+    assert( lines[1] == "Another line");
+    assert( lines[2] == "");
+    assert( lines[3] == "That was blank");
+    file.position = 0;
+    lines = new char[][5];
+    foreach(ulong n, char[] line; file) {
+      lines[cast(size_t)(n-1)] = line.dup;
+    }
+    assert( lines[0] == "Testing stream.d:");
+    assert( lines[1] == "Another line");
+    assert( lines[2] == "");
+    assert( lines[3] == "That was blank");
+    file.close();
+    remove(stream_file);
+  }
+}
+
+/**
+ * CFile wrapper of core.stdc.stdio.stdin (not seekable).
+ */
+__gshared CFile din;
+
+/**
+ * CFile wrapper of core.stdc.stdio.stdout (not seekable).
+ */
+__gshared CFile dout;
+
+/**
+ * CFile wrapper of core.stdc.stdio.stderr (not seekable).
+ */
+__gshared CFile derr;
+
+shared static this() {
+  // open standard I/O devices
+  din = new CFile(core.stdc.stdio.stdin,FileMode.In);
+  dout = new CFile(core.stdc.stdio.stdout,FileMode.Out);
+  derr = new CFile(core.stdc.stdio.stderr,FileMode.Out);
+}
+
diff --git a/libphobos/src/std/csv.d b/libphobos/src/std/csv.d
new file mode 100644
index 00000000000..8052e5f501b
--- /dev/null
+++ b/libphobos/src/std/csv.d
@@ -0,0 +1,1692 @@
+//Written in the D programming language
+
+/**
+ * Implements functionality to read Comma Separated Values and its variants
+ * from a input range of $(D dchar).
+ *
+ * Comma Separated Values provide a simple means to transfer and store
+ * tabular data. It has been common for programs to use their own
+ * variant of the CSV format. This parser will loosely follow the
+ * $(WEB tools.ietf.org/html/rfc4180, RFC-4180). CSV input should adhered
+ * to the following criteria, differences from RFC-4180 in parentheses.
+ *
+ * $(UL
+ *     $(LI A record is separated by a new line (CRLF,LF,CR))
+ *     $(LI A final record may end with a new line)
+ *     $(LI A header may be provided as the first record in input)
+ *     $(LI A record has fields separated by a comma (customizable))
+ *     $(LI A field containing new lines, commas, or double quotes
+ *          should be enclosed in double quotes (customizable))
+ *     $(LI Double quotes in a field are escaped with a double quote)
+ *     $(LI Each record should contain the same number of fields)
+ *   )
+ *
+ * Example:
+ *
+ * -------
+ * import std.algorithm;
+ * import std.array;
+ * import std.csv;
+ * import std.stdio;
+ * import std.typecons;
+ *
+ * void main()
+ * {
+ *     auto text = "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n";
+ *
+ *     foreach(record; csvReader!(Tuple!(string, string, int))(text))
+ *     {
+ *         writefln("%s works as a %s and earns $%d per year",
+ *                  record[0], record[1], record[2]);
+ *     }
+ *
+ *     // To read the same string from the file "filename.csv":
+ *
+ *     auto file = File("filename.csv", "r");
+ *     foreach(record;
+ *         file.byLine.joiner("\n").csvReader!(Tuple!(string, string, int)))
+ *     {
+ *         writefln("%s works as a %s and earns $%d per year",
+ *                  record[0], record[1], record[2]);
+ *     }
+ }
+ * }
+ * -------
+ *
+ * When an input contains a header the $(D Contents) can be specified as an
+ * associative array. Passing null to signify that a header is present.
+ *
+ * -------
+ * auto text = "Name,Occupation,Salary\r"
+ *     "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n";
+ *
+ * foreach(record; csvReader!(string[string])
+ *         (text, null))
+ * {
+ *     writefln("%s works as a %s and earns $%s per year.",
+ *              record["Name"], record["Occupation"],
+ *              record["Salary"]);
+ * }
+ * -------
+ *
+ * This module allows content to be iterated by record stored in a struct,
+ * class, associative array, or as a range of fields. Upon detection of an
+ * error an CSVException is thrown (can be disabled). csvNextToken has been
+ * made public to allow for attempted recovery.
+ *
+ * Disabling exceptions will lift many restrictions specified above. A quote
+ * can appear in a field if the field was not quoted. If in a quoted field any
+ * quote by itself, not at the end of a field, will end processing for that
+ * field. The field is ended when there is no input, even if the quote was not
+ * closed.
+ *
+ *   See_Also:
+ *      $(WEB en.wikipedia.org/wiki/Comma-separated_values, Wikipedia
+ *      Comma-separated values)
+ *
+ *   Copyright: Copyright 2011
+ *   License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *   Authors:   Jesse Phillips
+ *   Source:    $(PHOBOSSRC std/_csv.d)
+ */
+module std.csv;
+
+import std.conv;
+import std.range.primitives;
+import std.traits;
+import std.exception;  // basicExceptionCtors
+
+/**
+ * Exception containing the row and column for when an exception was thrown.
+ *
+ * Numbering of both row and col start at one and corresponds to the location
+ * in the file rather than any specified header. Special consideration should
+ * be made when there is failure to match the header see $(LREF
+ * HeaderMismatchException) for details.
+ *
+ * When performing type conversions, $(XREF ConvException) is stored in the $(D
+ * next) field.
+ */
+class CSVException : Exception
+{
+    ///
+    size_t row, col;
+
+    // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed
+
+    this(string msg, string file = __FILE__, size_t line = __LINE__,
+         Throwable next = null) @nogc @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+
+    this(string msg, Throwable next, string file = __FILE__,
+         size_t line = __LINE__) @nogc @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+
+    this(string msg, size_t row, size_t col, Throwable next = null,
+         string file = __FILE__, size_t line = __LINE__) @nogc @safe pure nothrow
+    {
+        super(msg, next, file, line);
+        this.row = row;
+        this.col = col;
+    }
+
+    override string toString() @safe pure
+    {
+        return "(Row: " ~ to!string(row) ~
+              ", Col: " ~ to!string(col) ~ ") " ~ msg;
+    }
+}
+
+@safe pure unittest
+{
+    import std.string;
+    auto e1 = new Exception("Foobar");
+    auto e2 = new CSVException("args", e1);
+    assert(e2.next is e1);
+
+    size_t r = 13;
+    size_t c = 37;
+
+    auto e3 = new CSVException("argv", r, c);
+    assert(e3.row == r);
+    assert(e3.col == c);
+
+    auto em = e3.toString();
+    assert(em.indexOf("13") != -1);
+    assert(em.indexOf("37") != -1);
+}
+
+/**
+ * Exception thrown when a Token is identified to not be completed: a quote is
+ * found in an unquoted field, data continues after a closing quote, or the
+ * quoted field was not closed before data was empty.
+ */
+class IncompleteCellException : CSVException
+{
+    /// Data pulled from input before finding a problem
+    ///
+    /// This field is populated when using $(LREF csvReader)
+    /// but not by $(LREF csvNextToken) as this data will have
+    /// already been fed to the output range.
+    dstring partialData;
+
+    mixin basicExceptionCtors;
+}
+
+@safe pure unittest
+{
+    auto e1 = new Exception("Foobar");
+    auto e2 = new IncompleteCellException("args", e1);
+    assert(e2.next is e1);
+}
+
+/**
+ * Exception thrown under different conditions based on the type of $(D
+ * Contents).
+ *
+ * Structure, Class, and Associative Array
+ * $(UL
+ *     $(LI When a header is provided but a matching column is not found)
+ *  )
+ *
+ * Other
+ * $(UL
+ *     $(LI When a header is provided but a matching column is not found)
+ *     $(LI Order did not match that found in the input)
+ *  )
+ *
+ * Since a row and column is not meaningful when a column specified by the
+ * header is not found in the data, both row and col will be zero. Otherwise
+ * row is always one and col is the first instance found in header that
+ * occurred before the previous starting at one.
+ */
+class HeaderMismatchException : CSVException
+{
+    mixin basicExceptionCtors;
+}
+
+@safe pure unittest
+{
+    auto e1 = new Exception("Foobar");
+    auto e2 = new HeaderMismatchException("args", e1);
+    assert(e2.next is e1);
+}
+
+/**
+ * Determines the behavior for when an error is detected.
+ *
+ * Disabling exception will follow these rules:
+ * $(UL
+ *     $(LI A quote can appear in a field if the field was not quoted.)
+ *     $(LI If in a quoted field any quote by itself, not at the end of a
+ *     field, will end processing for that field.)
+ *     $(LI The field is ended when there is no input, even if the quote was
+ *     not closed.)
+ *     $(LI If the given header does not match the order in the input, the
+ *     content will return as it is found in the input.)
+ *     $(LI If the given header contains columns not found in the input they
+ *     will be ignored.)
+ *  )
+*/
+enum Malformed
+{
+    /// No exceptions are thrown due to incorrect CSV.
+    ignore,
+    /// Use exceptions when input has incorrect CSV.
+    throwException
+}
+
+/**
+ * Returns an input range for iterating over records found in $(D
+ * input).
+ *
+ * The $(D Contents) of the input can be provided if all the records are the
+ * same type such as all integer data:
+ *
+ * -------
+ * string str = `76,26,22`;
+ * int[] ans = [76,26,22];
+ * auto records = csvReader!int(str);
+ *
+ * foreach(record; records)
+ * {
+ *     assert(equal(record, ans));
+ * }
+ * -------
+ *
+ * Example using a struct with modified delimiter:
+ *
+ * -------
+ * string str = "Hello;65;63.63\nWorld;123;3673.562";
+ * struct Layout
+ * {
+ *     string name;
+ *     int value;
+ *     double other;
+ * }
+ *
+ * auto records = csvReader!Layout(str,';');
+ *
+ * foreach(record; records)
+ * {
+ *     writeln(record.name);
+ *     writeln(record.value);
+ *     writeln(record.other);
+ * }
+ * -------
+ *
+ * Specifying $(D ErrorLevel) as Malformed.ignore will lift restrictions
+ * on the format. This example shows that an exception is not thrown when
+ * finding a quote in a field not quoted.
+ *
+ * -------
+ * string str = "A \" is now part of the data";
+ * auto records = csvReader!(string,Malformed.ignore)(str);
+ * auto record = records.front;
+ *
+ * assert(record.front == str);
+ * -------
+ *
+ * Returns:
+ *        An input range R as defined by
+ *        $(XREF_PACK range,primitives,isInputRange). When $(D Contents) is a
+ *        struct, class, or an associative array, the element type of R is
+ *        $(D Contents), otherwise the element type of R is itself a range with
+ *        element type $(D Contents).
+ *
+ * Throws:
+ *       $(LREF CSVException) When a quote is found in an unquoted field,
+ *       data continues after a closing quote, the quoted field was not
+ *       closed before data was empty, a conversion failed, or when the row's
+ *       length does not match the previous length.
+ *
+ *       $(LREF HeaderMismatchException)  when a header is provided but a
+ *       matching column is not found or the order did not match that found in
+ *       the input. Read the exception documentation for specific details of
+ *       when the exception is thrown for different types of $(D Contents).
+ */
+auto csvReader(Contents = string,Malformed ErrorLevel = Malformed.throwException, Range, Separator = char)(Range input,
+                 Separator delimiter = ',', Separator quote = '"')
+               if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)
+                  && isSomeChar!(Separator)
+                  && !is(Contents T : T[U], U : string))
+{
+    return CsvReader!(Contents,ErrorLevel,Range,
+                    Unqual!(ElementType!Range),string[])
+        (input, delimiter, quote);
+}
+
+/**
+ * An optional $(D header) can be provided. The first record will be read in
+ * as the header. If $(D Contents) is a struct then the header provided is
+ * expected to correspond to the fields in the struct. When $(D Contents) is
+ * not a type which can contain the entire record, the $(D header) must be
+ * provided in the same order as the input or an exception is thrown.
+ *
+ * Read only column "b":
+ *
+ * -------
+ * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
+ * auto records = csvReader!int(str, ["b"]);
+ *
+ * auto ans = [[65],[123]];
+ * foreach(record; records)
+ * {
+ *     assert(equal(record, ans.front));
+ *     ans.popFront();
+ * }
+ * -------
+ *
+ * Read from header of different order:
+ *
+ * -------
+ * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
+ * struct Layout
+ * {
+ *     int value;
+ *     double other;
+ *     string name;
+ * }
+ *
+ * auto records = csvReader!Layout(str, ["b","c","a"]);
+ * -------
+ *
+ * The header can also be left empty if the input contains a header but
+ * all columns should be iterated. The header from the input can always
+ * be accessed from the header field.
+ *
+ * -------
+ * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
+ * auto records = csvReader(str, null);
+ *
+ * assert(records.header == ["a","b","c"]);
+ * -------
+ *
+ * Returns:
+ *        An input range R as defined by
+ *        $(XREF_PACK range,primitives,isInputRange). When $(D Contents) is a
+ *        struct, class, or an associative array, the element type of R is
+ *        $(D Contents), otherwise the element type of R is itself a range with
+ *        element type $(D Contents).
+ *
+ *        The returned range provides a header field for accessing the header
+ *        from the input in array form.
+ *
+ * -------
+ * string str = "a,b,c\nHello,65,63.63";
+ * auto records = csvReader(str, ["a"]);
+ *
+ * assert(records.header == ["a","b","c"]);
+ * -------
+ *
+ * Throws:
+ *       $(LREF CSVException) When a quote is found in an unquoted field,
+ *       data continues after a closing quote, the quoted field was not
+ *       closed before data was empty, a conversion failed, or when the row's
+ *       length does not match the previous length.
+ *
+ *       $(LREF HeaderMismatchException)  when a header is provided but a
+ *       matching column is not found or the order did not match that found in
+ *       the input. Read the exception documentation for specific details of
+ *       when the exception is thrown for different types of $(D Contents).
+ */
+auto csvReader(Contents = string,
+               Malformed ErrorLevel = Malformed.throwException,
+               Range, Header, Separator = char)
+                (Range input, Header header,
+                 Separator delimiter = ',', Separator quote = '"')
+               if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)
+                  && isSomeChar!(Separator)
+                  && isForwardRange!Header
+                  && isSomeString!(ElementType!Header))
+{
+    return CsvReader!(Contents,ErrorLevel,Range,
+                    Unqual!(ElementType!Range),Header)
+        (input, header, delimiter, quote);
+}
+
+///
+auto csvReader(Contents = string,
+               Malformed ErrorLevel = Malformed.throwException,
+               Range, Header, Separator = char)
+                (Range input, Header header,
+                 Separator delimiter = ',', Separator quote = '"')
+               if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)
+                  && isSomeChar!(Separator)
+                  && is(Header : typeof(null)))
+{
+    return CsvReader!(Contents,ErrorLevel,Range,
+                    Unqual!(ElementType!Range),string[])
+        (input, cast(string[])null, delimiter, quote);
+}
+
+// Test standard iteration over input.
+@safe pure unittest
+{
+    string str = `one,"two ""quoted"""` ~ "\n\"three\nnew line\",\nfive,six";
+    auto records = csvReader(str);
+
+    int count;
+    foreach(record; records)
+    {
+        foreach(cell; record)
+        {
+            count++;
+        }
+    }
+    assert(count == 6);
+}
+
+// Test newline on last record
+@safe pure unittest
+{
+    string str = "one,two\nthree,four\n";
+    auto records = csvReader(str);
+    records.popFront();
+    records.popFront();
+    assert(records.empty);
+}
+
+// Test shorter row length
+@safe pure unittest
+{
+    wstring str = "one,1\ntwo\nthree"w;
+    struct Layout
+    {
+        string name;
+        int value;
+    }
+
+    Layout[3] ans;
+    ans[0].name = "one";
+    ans[0].value = 1;
+    ans[1].name = "two";
+    ans[1].value = 0;
+    ans[2].name = "three";
+    ans[2].value = 0;
+
+    auto records = csvReader!(Layout,Malformed.ignore)(str);
+
+    int count;
+    foreach(record; records)
+    {
+        assert(ans[count].name == record.name);
+        assert(ans[count].value == record.value);
+        count++;
+    }
+}
+
+// Test shorter row length exception
+@safe pure unittest
+{
+    import std.exception;
+
+    struct A
+    {
+        string a,b,c;
+    }
+
+    auto strs = ["one,1\ntwo",
+                 "one\ntwo,2,二\nthree,3,三",
+                 "one\ntwo,2\nthree,3",
+                 "one,1\ntwo\nthree,3"];
+
+    foreach(str; strs)
+    {
+        auto records = csvReader!A(str);
+        assertThrown!CSVException((){foreach(record; records) { }}());
+    }
+}
+
+
+// Test structure conversion interface with unicode.
+@safe pure unittest
+{
+    import std.math : abs;
+
+    wstring str = "\U00010143Hello,65,63.63\nWorld,123,3673.562"w;
+    struct Layout
+    {
+        string name;
+        int value;
+        double other;
+    }
+
+    Layout[2] ans;
+    ans[0].name = "\U00010143Hello";
+    ans[0].value = 65;
+    ans[0].other = 63.63;
+    ans[1].name = "World";
+    ans[1].value = 123;
+    ans[1].other = 3673.562;
+
+    auto records = csvReader!Layout(str);
+
+    int count;
+    foreach(record; records)
+    {
+        assert(ans[count].name == record.name);
+        assert(ans[count].value == record.value);
+        assert(abs(ans[count].other - record.other) < 0.00001);
+        count++;
+    }
+    assert(count == ans.length);
+}
+
+// Test input conversion interface
+@safe pure unittest
+{
+    import std.algorithm;
+    string str = `76,26,22`;
+    int[] ans = [76,26,22];
+    auto records = csvReader!int(str);
+
+    foreach(record; records)
+    {
+        assert(equal(record, ans));
+    }
+}
+
+// Test struct & header interface and same unicode
+unittest
+{
+    import std.math : abs;
+
+    string str = "a,b,c\nHello,65,63.63\n➊➋➂❹,123,3673.562";
+    struct Layout
+    {
+        int value;
+        double other;
+        string name;
+    }
+
+    auto records = csvReader!Layout(str, ["b","c","a"]);
+
+    Layout[2] ans;
+    ans[0].name = "Hello";
+    ans[0].value = 65;
+    ans[0].other = 63.63;
+    ans[1].name = "➊➋➂❹";
+    ans[1].value = 123;
+    ans[1].other = 3673.562;
+
+    int count;
+    foreach (record; records)
+    {
+        assert(ans[count].name == record.name);
+        assert(ans[count].value == record.value);
+        assert(abs(ans[count].other - record.other) < 0.00001);
+        count++;
+    }
+    assert(count == ans.length);
+
+}
+
+// Test header interface
+unittest
+{
+    import std.algorithm;
+
+    string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
+    auto records = csvReader!int(str, ["b"]);
+
+    auto ans = [[65],[123]];
+    foreach(record; records)
+    {
+        assert(equal(record, ans.front));
+        ans.popFront();
+    }
+
+    try
+    {
+        csvReader(str, ["c","b"]);
+        assert(0);
+    }
+    catch(HeaderMismatchException e)
+    {
+        assert(e.col == 2);
+    }
+    auto records2 = csvReader!(string,Malformed.ignore)
+       (str, ["b","a"], ',', '"');
+
+    auto ans2 = [["Hello","65"],["World","123"]];
+    foreach(record; records2) {
+        assert(equal(record, ans2.front));
+        ans2.popFront();
+    }
+
+    str = "a,c,e\nJoe,Carpenter,300000\nFred,Fly,4";
+    records2 = csvReader!(string,Malformed.ignore)
+       (str, ["a","b","c","d"], ',', '"');
+
+    ans2 = [["Joe","Carpenter"],["Fred","Fly"]];
+    foreach(record; records2) {
+        assert(equal(record, ans2.front));
+        ans2.popFront();
+    }
+}
+
+// Test null header interface
+unittest
+{
+    string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";
+    auto records = csvReader(str, ["a"]);
+
+    assert(records.header == ["a","b","c"]);
+}
+
+// Test unchecked read
+@safe pure unittest
+{
+    string str = "one \"quoted\"";
+    foreach(record; csvReader!(string,Malformed.ignore)(str))
+    {
+        foreach(cell; record)
+        {
+            assert(cell == "one \"quoted\"");
+        }
+    }
+
+    str = "one \"quoted\",two \"quoted\" end";
+    struct Ans
+    {
+        string a,b;
+    }
+    foreach(record; csvReader!(Ans,Malformed.ignore)(str))
+    {
+        assert(record.a == "one \"quoted\"");
+        assert(record.b == "two \"quoted\" end");
+    }
+}
+
+// Test partial data returned
+@safe pure unittest
+{
+    string str = "\"one\nnew line";
+
+    try
+    {
+        foreach(record; csvReader(str))
+        {}
+        assert(0);
+    }
+    catch (IncompleteCellException ice)
+    {
+        assert(ice.partialData == "one\nnew line");
+    }
+}
+
+// Test Windows line break
+@safe pure unittest
+{
+    string str = "one,two\r\nthree";
+
+    auto records = csvReader(str);
+    auto record = records.front;
+    assert(record.front == "one");
+    record.popFront();
+    assert(record.front == "two");
+    records.popFront();
+    record = records.front;
+    assert(record.front == "three");
+}
+
+
+// Test associative array support with unicode separator
+unittest
+{
+  string str = "1❁2❁3\n34❁65❁63\n34❁65❁63";
+
+  auto records = csvReader!(string[string])(str,["3","1"],'❁');
+  int count;
+  foreach(record; records)
+  {
+      count++;
+      assert(record["1"] == "34");
+      assert(record["3"] == "63");
+  }
+  assert(count == 2);
+}
+
+// Test restricted range
+unittest
+{
+    import std.typecons;
+    struct InputRange
+    {
+        dstring text;
+
+        this(dstring txt)
+        {
+            text = txt;
+        }
+
+        @property auto empty()
+        {
+            return text.empty;
+        }
+
+        auto popFront()
+        {
+            text.popFront();
+        }
+
+        @property dchar front()
+        {
+            return text[0];
+        }
+    }
+    auto ir = InputRange("Name,Occupation,Salary\r"d~
+          "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"d);
+
+    foreach(record; csvReader(ir, cast(string[])null))
+        foreach(cell; record) {}
+    foreach(record; csvReader!(Tuple!(string, string, int))
+            (ir,cast(string[])null)) {}
+    foreach(record; csvReader!(string[string])
+            (ir,cast(string[])null)) {}
+}
+
+unittest // const/immutable dchars
+{
+    import std.algorithm: map;
+    import std.array: array;
+    const(dchar)[] c = "foo,bar\n";
+    assert(csvReader(c).map!array.array == [["foo", "bar"]]);
+    immutable(dchar)[] i = "foo,bar\n";
+    assert(csvReader(i).map!array.array == [["foo", "bar"]]);
+}
+
+/*
+ * This struct is stored on the heap for when the structures
+ * are passed around.
+ */
+private pure struct Input(Range, Malformed ErrorLevel)
+{
+    Range range;
+    size_t row, col;
+    static if (ErrorLevel == Malformed.throwException)
+        size_t rowLength;
+}
+
+/*
+ * Range for iterating CSV records.
+ *
+ * This range is returned by the $(LREF csvReader) functions. It can be
+ * created in a similar manner to allow $(D ErrorLevel) be set to $(LREF
+ * Malformed).ignore if best guess processing should take place.
+ */
+private struct CsvReader(Contents, Malformed ErrorLevel, Range, Separator, Header)
+    if (isSomeChar!Separator && isInputRange!Range
+       && is(Unqual!(ElementType!Range) == dchar)
+       && isForwardRange!Header && isSomeString!(ElementType!Header))
+{
+private:
+    Input!(Range, ErrorLevel)* _input;
+    Separator _separator;
+    Separator _quote;
+    size_t[] indices;
+    bool _empty;
+    static if (is(Contents == struct) || is(Contents == class))
+    {
+        Contents recordContent;
+        CsvRecord!(string, ErrorLevel, Range, Separator) recordRange;
+    }
+    else static if (is(Contents T : T[U], U : string))
+    {
+        Contents recordContent;
+        CsvRecord!(T, ErrorLevel, Range, Separator) recordRange;
+    }
+    else
+        CsvRecord!(Contents, ErrorLevel, Range, Separator) recordRange;
+public:
+    /**
+     * Header from the input in array form.
+     *
+     * -------
+     * string str = "a,b,c\nHello,65,63.63";
+     * auto records = csvReader(str, ["a"]);
+     *
+     * assert(records.header == ["a","b","c"]);
+     * -------
+     */
+    string[] header;
+
+    /**
+     * Constructor to initialize the input, delimiter and quote for input
+     * without a header.
+     *
+     * -------
+     * string str = `76;^26^;22`;
+     * int[] ans = [76,26,22];
+     * auto records = CsvReader!(int,Malformed.ignore,string,char,string[])
+     *       (str, ';', '^');
+     *
+     * foreach(record; records) {
+     *    assert(equal(record, ans));
+     * }
+     * -------
+     */
+    this(Range input, Separator delimiter, Separator quote)
+    {
+        _input = new Input!(Range, ErrorLevel)(input);
+        _separator = delimiter;
+        _quote = quote;
+
+        prime();
+    }
+
+    /**
+     * Constructor to initialize the input, delimiter and quote for input
+     * with a header.
+     *
+     * -------
+     * string str = `high;mean;low\n76;^26^;22`;
+     * auto records = CsvReader!(int,Malformed.ignore,string,char,string[])
+     *       (str, ["high","low"], ';', '^');
+     *
+     * int[] ans = [76,22];
+     * foreach(record; records) {
+     *    assert(equal(record, ans));
+     * }
+     * -------
+     *
+     * Throws:
+     *       $(LREF HeaderMismatchException)  when a header is provided but a
+     *       matching column is not found or the order did not match that found
+     *       in the input (non-struct).
+     */
+    this(Range input, Header colHeaders, Separator delimiter, Separator quote)
+    {
+        _input = new Input!(Range, ErrorLevel)(input);
+        _separator = delimiter;
+        _quote = quote;
+
+        size_t[string] colToIndex;
+        foreach(h; colHeaders)
+        {
+            colToIndex[h] = size_t.max;
+        }
+
+        auto r = CsvRecord!(string, ErrorLevel, Range, Separator)
+            (_input, _separator, _quote, indices);
+
+        size_t colIndex;
+        foreach(col; r)
+        {
+            header ~= col;
+            auto ptr = col in colToIndex;
+            if (ptr)
+                *ptr = colIndex;
+            colIndex++;
+        }
+        // The above loop empties the header row.
+        recordRange._empty = true;
+
+        indices.length = colToIndex.length;
+        int i;
+        foreach(h; colHeaders)
+        {
+            immutable index = colToIndex[h];
+            static if (ErrorLevel != Malformed.ignore)
+                if (index == size_t.max)
+                    throw new HeaderMismatchException
+                        ("Header not found: " ~ to!string(h));
+            indices[i++] = index;
+        }
+
+        static if (!is(Contents == struct) && !is(Contents == class))
+        {
+            static if (is(Contents T : T[U], U : string))
+            {
+                import std.algorithm : sort;
+                sort(indices);
+            }
+            else static if (ErrorLevel == Malformed.ignore)
+            {
+                import std.algorithm : sort;
+                sort(indices);
+            }
+            else
+            {
+                import std.algorithm : isSorted, findAdjacent;
+                if (!isSorted(indices))
+                {
+                    auto ex = new HeaderMismatchException
+                           ("Header in input does not match specified header.");
+                    findAdjacent!"a > b"(indices);
+                    ex.row = 1;
+                    ex.col = indices.front;
+
+                    throw ex;
+                }
+            }
+        }
+
+        popFront();
+    }
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     *
+     * Returns:
+     *      If $(D Contents) is a struct, will be filled with record data.
+     *
+     *      If $(D Contents) is a class, will be filled with record data.
+     *
+     *      If $(D Contents) is a associative array, will be filled
+     *      with record data.
+     *
+     *      If $(D Contents) is non-struct, a $(LREF CsvRecord) will be
+     *      returned.
+     */
+    @property auto front()
+    {
+        assert(!empty);
+        static if (is(Contents == struct) || is(Contents == class))
+        {
+            return recordContent;
+        }
+        else static if (is(Contents T : T[U], U : string))
+        {
+            return recordContent;
+        }
+        else
+        {
+            return recordRange;
+        }
+    }
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     */
+    @property bool empty() @safe @nogc pure nothrow const
+    {
+        return _empty;
+    }
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     *
+     * Throws:
+     *       $(LREF CSVException) When a quote is found in an unquoted field,
+     *       data continues after a closing quote, the quoted field was not
+     *       closed before data was empty, a conversion failed, or when the
+     *       row's length does not match the previous length.
+     */
+    void popFront()
+    {
+        while(!recordRange.empty)
+        {
+            recordRange.popFront();
+        }
+
+        static if (ErrorLevel == Malformed.throwException)
+            if (_input.rowLength == 0)
+                _input.rowLength = _input.col;
+
+        _input.col = 0;
+
+        if (!_input.range.empty)
+        {
+            if (_input.range.front == '\r')
+            {
+                _input.range.popFront();
+                if (!_input.range.empty && _input.range.front == '\n')
+                    _input.range.popFront();
+            }
+            else if (_input.range.front == '\n')
+                _input.range.popFront();
+        }
+
+        if (_input.range.empty)
+        {
+            _empty = true;
+            return;
+        }
+
+        prime();
+    }
+
+    private void prime()
+    {
+        if (_empty)
+            return;
+        _input.row++;
+        static if (is(Contents == struct) || is(Contents == class))
+        {
+            recordRange = typeof(recordRange)
+                                 (_input, _separator, _quote, null);
+        }
+        else
+        {
+            recordRange = typeof(recordRange)
+                                 (_input, _separator, _quote, indices);
+        }
+
+        static if (is(Contents T : T[U], U : string))
+        {
+            T[U] aa;
+            try
+            {
+                for(; !recordRange.empty; recordRange.popFront())
+                {
+                    aa[header[_input.col-1]] = recordRange.front;
+                }
+            }
+            catch(ConvException e)
+            {
+                throw new CSVException(e.msg, _input.row, _input.col, e);
+            }
+
+            recordContent = aa;
+        }
+        else static if (is(Contents == struct) || is(Contents == class))
+        {
+            static if (is(Contents == class))
+                recordContent = new typeof(recordContent)();
+            else
+                recordContent = typeof(recordContent).init;
+            size_t colIndex;
+            try
+            {
+                for(; !recordRange.empty;)
+                {
+                    auto colData = recordRange.front;
+                    scope(exit) colIndex++;
+                    if (indices.length > 0)
+                    {
+                        foreach(ti, ToType; Fields!(Contents))
+                        {
+                            if (indices[ti] == colIndex)
+                            {
+                                static if (!isSomeString!ToType) skipWS(colData);
+                                recordContent.tupleof[ti] = to!ToType(colData);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        foreach(ti, ToType; Fields!(Contents))
+                        {
+                            if (ti == colIndex)
+                            {
+                                static if (!isSomeString!ToType) skipWS(colData);
+                                recordContent.tupleof[ti] = to!ToType(colData);
+                            }
+                        }
+                    }
+                    recordRange.popFront();
+                }
+            }
+            catch(ConvException e)
+            {
+                throw new CSVException(e.msg, _input.row, colIndex, e);
+            }
+        }
+    }
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm;
+
+    string str = `76;^26^;22`;
+    int[] ans = [76,26,22];
+    auto records = CsvReader!(int,Malformed.ignore,string,char,string[])
+          (str, ';', '^');
+
+    foreach(record; records)
+    {
+        assert(equal(record, ans));
+    }
+}
+
+// Bugzilla 15545
+pure unittest
+{
+    enum failData =
+    "name, surname, age
+    Joe, Joker, 99\r";
+    bool pass = true;
+    auto r = csvReader(failData);
+    try foreach(entry; r){}
+    catch pass = false;
+    assert(pass);
+}
+
+/*
+ * This input range is accessible through $(LREF CsvReader) when the
+ * requested $(D Contents) type is neither a structure or an associative array.
+ */
+private struct CsvRecord(Contents, Malformed ErrorLevel, Range, Separator)
+    if (!is(Contents == class) && !is(Contents == struct))
+{
+    import std.array : appender;
+private:
+    Input!(Range, ErrorLevel)* _input;
+    Separator _separator;
+    Separator _quote;
+    Contents curContentsoken;
+    typeof(appender!(dchar[])()) _front;
+    bool _empty;
+    size_t[] _popCount;
+public:
+    /*
+     * params:
+     *      input = Pointer to a character input range
+     *      delimiter = Separator for each column
+     *      quote = Character used for quotation
+     *      indices = An array containing which columns will be returned.
+     *             If empty, all columns are returned. List must be in order.
+     */
+    this(Input!(Range, ErrorLevel)* input, Separator delimiter,
+         Separator quote, size_t[] indices)
+    {
+        _input = input;
+        _separator = delimiter;
+        _quote = quote;
+        _front = appender!(dchar[])();
+        _popCount = indices.dup;
+
+        // If a header was given, each call to popFront will need
+        // to eliminate so many tokens. This calculates
+        // how many will be skipped to get to the next header column
+        size_t normalizer;
+        foreach(ref c; _popCount) {
+            static if (ErrorLevel == Malformed.ignore)
+            {
+                // If we are not throwing exceptions
+                // a header may not exist, indices are sorted
+                // and will be size_t.max if not found.
+                if (c == size_t.max)
+                    break;
+            }
+            c -= normalizer;
+            normalizer += c + 1;
+        }
+
+        prime();
+    }
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     */
+    @property Contents front() @safe pure
+    {
+        assert(!empty);
+        return curContentsoken;
+    }
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     */
+    @property bool empty() @safe pure nothrow @nogc const
+    {
+        return _empty;
+    }
+
+    /*
+     * CsvRecord is complete when input
+     * is empty or starts with record break
+     */
+    private bool recordEnd()
+    {
+        if (_input.range.empty
+           || _input.range.front == '\n'
+           || _input.range.front == '\r')
+        {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Part of an input range as defined by
+     * $(XREF_PACK range,primitives,isInputRange).
+     *
+     * Throws:
+     *       $(LREF CSVException) When a quote is found in an unquoted field,
+     *       data continues after a closing quote, the quoted field was not
+     *       closed before data was empty, a conversion failed, or when the
+     *       row's length does not match the previous length.
+     */
+    void popFront()
+    {
+        static if (ErrorLevel == Malformed.throwException)
+            import std.format : format;
+        // Skip last of record when header is depleted.
+        if (_popCount.ptr && _popCount.empty)
+            while(!recordEnd())
+            {
+                prime(1);
+            }
+
+        if (recordEnd())
+        {
+            _empty = true;
+            static if (ErrorLevel == Malformed.throwException)
+                if (_input.rowLength != 0)
+                    if (_input.col != _input.rowLength)
+                        throw new CSVException(
+                           format("Row %s's length %s does not match "~
+                                  "previous length of %s.", _input.row,
+                                  _input.col, _input.rowLength));
+            return;
+        } else {
+            static if (ErrorLevel == Malformed.throwException)
+                if (_input.rowLength != 0)
+                    if (_input.col > _input.rowLength)
+                        throw new CSVException(
+                           format("Row %s's length %s does not match "~
+                                  "previous length of %s.", _input.row,
+                                  _input.col, _input.rowLength));
+        }
+
+        // Separator is left on the end of input from the last call.
+        // This cannot be moved to after the call to csvNextToken as
+        // there may be an empty record after it.
+        if (_input.range.front == _separator)
+            _input.range.popFront();
+
+        _front.shrinkTo(0);
+
+        prime();
+    }
+
+    /*
+     * Handles moving to the next skipNum token.
+     */
+    private void prime(size_t skipNum)
+    {
+        foreach(i; 0..skipNum)
+        {
+            _input.col++;
+            _front.shrinkTo(0);
+            if (_input.range.front == _separator)
+                _input.range.popFront();
+
+            try
+                csvNextToken!(Range, ErrorLevel, Separator)
+                                   (_input.range, _front, _separator, _quote,false);
+            catch(IncompleteCellException ice)
+            {
+                ice.row = _input.row;
+                ice.col = _input.col;
+                ice.partialData = _front.data.idup;
+                throw ice;
+            }
+            catch(ConvException e)
+            {
+                throw new CSVException(e.msg, _input.row, _input.col, e);
+            }
+        }
+    }
+
+    private void prime()
+    {
+        try
+        {
+            _input.col++;
+            csvNextToken!(Range, ErrorLevel, Separator)
+                (_input.range, _front, _separator, _quote,false);
+        }
+        catch(IncompleteCellException ice)
+        {
+            ice.row = _input.row;
+            ice.col = _input.col;
+            ice.partialData = _front.data.idup;
+            throw ice;
+        }
+
+        auto skipNum = _popCount.empty ? 0 : _popCount.front;
+        if (!_popCount.empty)
+            _popCount.popFront();
+
+        if (skipNum == size_t.max) {
+            while(!recordEnd())
+                prime(1);
+            _empty = true;
+            return;
+        }
+
+        if (skipNum)
+            prime(skipNum);
+
+        auto data = _front.data;
+        static if (!isSomeString!Contents) skipWS(data);
+        try curContentsoken = to!Contents(data);
+        catch(ConvException e)
+        {
+            throw new CSVException(e.msg, _input.row, _input.col, e);
+        }
+    }
+}
+
+/**
+ * Lower level control over parsing CSV
+ *
+ * This function consumes the input. After each call the input will
+ * start with either a delimiter or record break (\n, \r\n, \r) which
+ * must be removed for subsequent calls.
+ *
+ * params:
+ *       input = Any CSV input
+ *       ans   = The first field in the input
+ *       sep   = The character to represent a comma in the specification
+ *       quote = The character to represent a quote in the specification
+ *       startQuoted = Whether the input should be considered to already be in
+ * quotes
+ *
+ * Throws:
+ *       $(LREF IncompleteCellException) When a quote is found in an unquoted
+ *       field, data continues after a closing quote, or the quoted field was
+ *       not closed before data was empty.
+ */
+void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException,
+                           Separator, Output)
+                          (ref Range input, ref Output ans,
+                           Separator sep, Separator quote,
+                           bool startQuoted = false)
+                          if (isSomeChar!Separator && isInputRange!Range
+                             && is(Unqual!(ElementType!Range) == dchar)
+                             && isOutputRange!(Output, dchar))
+{
+    bool quoted = startQuoted;
+    bool escQuote;
+    if (input.empty)
+        return;
+
+    if (input.front == '\n')
+        return;
+    if (input.front == '\r')
+        return;
+
+    if (input.front == quote)
+    {
+        quoted = true;
+        input.popFront();
+    }
+
+    while(!input.empty)
+    {
+        assert(!(quoted && escQuote));
+        if (!quoted)
+        {
+            // When not quoted the token ends at sep
+            if (input.front == sep)
+                break;
+            if (input.front == '\r')
+                break;
+            if (input.front == '\n')
+                break;
+        }
+        if (!quoted && !escQuote)
+        {
+            if (input.front == quote)
+            {
+                // Not quoted, but quote found
+                static if (ErrorLevel == Malformed.throwException)
+                    throw new IncompleteCellException(
+                          "Quote located in unquoted token");
+                else static if (ErrorLevel == Malformed.ignore)
+                    ans.put(quote);
+            }
+            else
+            {
+                // Not quoted, non-quote character
+                ans.put(input.front);
+            }
+        }
+        else
+        {
+            if (input.front == quote)
+            {
+                // Quoted, quote found
+                // By turning off quoted and turning on escQuote
+                // I can tell when to add a quote to the string
+                // escQuote is turned to false when it escapes a
+                // quote or is followed by a non-quote (see outside else).
+                // They are mutually exclusive, but provide different
+                // information.
+                if (escQuote)
+                {
+                    escQuote = false;
+                    quoted = true;
+                    ans.put(quote);
+                } else
+                {
+                    escQuote = true;
+                    quoted = false;
+                }
+            }
+            else
+            {
+                // Quoted, non-quote character
+                if (escQuote)
+                {
+                    static if (ErrorLevel == Malformed.throwException)
+                        throw new IncompleteCellException(
+                          "Content continues after end quote, " ~
+                          "or needs to be escaped.");
+                    else static if (ErrorLevel == Malformed.ignore)
+                        break;
+                }
+                ans.put(input.front);
+            }
+        }
+        input.popFront();
+    }
+
+    static if (ErrorLevel == Malformed.throwException)
+        if (quoted && (input.empty || input.front == '\n' || input.front == '\r'))
+            throw new IncompleteCellException(
+                  "Data continues on future lines or trailing quote");
+
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    string str = "65,63\n123,3673";
+
+    auto a = appender!(char[])();
+
+    csvNextToken(str,a,',','"');
+    assert(a.data == "65");
+    assert(str == ",63\n123,3673");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "63");
+    assert(str == "\n123,3673");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "123");
+    assert(str == ",3673");
+}
+
+// Test csvNextToken on simplest form and correct format.
+@safe pure unittest
+{
+    import std.array;
+
+    string str = "\U00010143Hello,65,63.63\nWorld,123,3673.562";
+
+    auto a = appender!(dchar[])();
+    csvNextToken!string(str,a,',','"');
+    assert(a.data == "\U00010143Hello");
+    assert(str == ",65,63.63\nWorld,123,3673.562");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "65");
+    assert(str == ",63.63\nWorld,123,3673.562");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "63.63");
+    assert(str == "\nWorld,123,3673.562");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "World");
+    assert(str == ",123,3673.562");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "123");
+    assert(str == ",3673.562");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "3673.562");
+    assert(str == "");
+}
+
+// Test quoted tokens
+@safe pure unittest
+{
+    import std.array;
+
+    string str = `one,two,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix";
+
+    auto a = appender!(dchar[])();
+    csvNextToken!string(str,a,',','"');
+    assert(a.data == "one");
+    assert(str == `,two,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "two");
+    assert(str == `,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "three \"quoted\"");
+    assert(str == `,"",` ~ "\"five\nnew line\"\nsix");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "");
+    assert(str == ",\"five\nnew line\"\nsix");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "five\nnew line");
+    assert(str == "\nsix");
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "six");
+    assert(str == "");
+}
+
+// Test empty data is pulled at end of record.
+@safe pure unittest
+{
+    import std.array;
+
+    string str = "one,";
+    auto a = appender!(dchar[])();
+    csvNextToken(str,a,',','"');
+    assert(a.data == "one");
+    assert(str == ",");
+
+    a.shrinkTo(0);
+    csvNextToken(str,a,',','"');
+    assert(a.data == "");
+}
+
+// Test exceptions
+@safe pure unittest
+{
+    import std.array;
+
+    string str = "\"one\nnew line";
+
+    typeof(appender!(dchar[])()) a;
+    try
+    {
+        a = appender!(dchar[])();
+        csvNextToken(str,a,',','"');
+        assert(0);
+    }
+    catch (IncompleteCellException ice)
+    {
+        assert(a.data == "one\nnew line");
+        assert(str == "");
+    }
+
+    str = "Hello world\"";
+
+    try
+    {
+        a = appender!(dchar[])();
+        csvNextToken(str,a,',','"');
+        assert(0);
+    }
+    catch (IncompleteCellException ice)
+    {
+        assert(a.data == "Hello world");
+        assert(str == "\"");
+    }
+
+    str = "one, two \"quoted\" end";
+
+    a = appender!(dchar[])();
+    csvNextToken!(string,Malformed.ignore)(str,a,',','"');
+    assert(a.data == "one");
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken!(string,Malformed.ignore)(str,a,',','"');
+    assert(a.data == " two \"quoted\" end");
+}
+
+// Test modifying token delimiter
+@safe pure unittest
+{
+    import std.array;
+
+    string str = `one|two|/three "quoted"/|//`;
+
+    auto a = appender!(dchar[])();
+    csvNextToken(str,a, '|','/');
+    assert(a.data == "one"d);
+    assert(str == `|two|/three "quoted"/|//`);
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a, '|','/');
+    assert(a.data == "two"d);
+    assert(str == `|/three "quoted"/|//`);
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a, '|','/');
+    assert(a.data == `three "quoted"`);
+    assert(str == `|//`);
+
+    str.popFront();
+    a.shrinkTo(0);
+    csvNextToken(str,a, '|','/');
+    assert(a.data == ""d);
+}
+
+// Bugzilla 8908
+@safe pure unittest
+{
+    string csv = `  1.0, 2.0, 3.0
+                    4.0, 5.0, 6.0`;
+
+    static struct Data { real a, b, c; }
+    size_t i = 0;
+    foreach (data; csvReader!Data(csv)) with (data)
+    {
+        int[] row = [cast(int)a, cast(int)b, cast(int)c];
+        if (i == 0)
+            assert(row == [1, 2, 3]);
+        else
+            assert(row == [4, 5, 6]);
+        ++i;
+    }
+
+    i = 0;
+    foreach (data; csvReader!real(csv))
+    {
+        auto a = data.front;    data.popFront();
+        auto b = data.front;    data.popFront();
+        auto c = data.front;
+        int[] row = [cast(int)a, cast(int)b, cast(int)c];
+        if (i == 0)
+            assert(row == [1, 2, 3]);
+        else
+            assert(row == [4, 5, 6]);
+        ++i;
+    }
+}
diff --git a/libphobos/src/std/datetime.d b/libphobos/src/std/datetime.d
new file mode 100644
index 00000000000..6be7a3de296
--- /dev/null
+++ b/libphobos/src/std/datetime.d
@@ -0,0 +1,34554 @@
+//Written in the D programming language
+
+/++
+    Module containing Date/Time functionality.
+
+    This module provides:
+    $(UL
+        $(LI Types to represent points in time: $(LREF SysTime), $(LREF Date),
+             $(LREF TimeOfDay), and $(LREF2 .DateTime, DateTime).)
+        $(LI Types to represent intervals of time.)
+        $(LI Types to represent ranges over intervals of time.)
+        $(LI Types to represent time zones (used by $(LREF SysTime)).)
+        $(LI A platform-independent, high precision stopwatch type:
+             $(LREF StopWatch))
+        $(LI Benchmarking functions.)
+        $(LI Various helper functions.)
+    )
+
+    Closely related to std.datetime is <a href="core_time.html">$(D core.time)</a>,
+    and some of the time types used in std.datetime come from there - such as
+    $(CXREF time, Duration), $(CXREF time, TickDuration), and
+    $(CXREF time, FracSec).
+    core.time is publically imported into std.datetime, it isn't necessary
+    to import it separately.
+
+    Three of the main concepts used in this module are time points, time
+    durations, and time intervals.
+
+    A time point is a specific point in time. e.g. January 5th, 2010
+    or 5:00.
+
+    A time duration is a length of time with units. e.g. 5 days or 231 seconds.
+
+    A time interval indicates a period of time associated with a fixed point in
+    time. It is either two time points associated with each other,
+    indicating the time starting at the first point up to, but not including,
+    the second point - e.g. [January 5th, 2010 - March 10th, 2010$(RPAREN) - or
+    it is a time point and a time duration associated with one another. e.g.
+    January 5th, 2010 and 5 days, indicating [January 5th, 2010 -
+    January 10th, 2010$(RPAREN).
+
+    Various arithmetic operations are supported between time points and
+    durations (e.g. the difference between two time points is a time duration),
+    and ranges can be gotten from time intervals, so range-based operations may
+    be done on a series of time points.
+
+    The types that the typical user is most likely to be interested in are
+    $(LREF Date) (if they want dates but don't care about time), $(LREF DateTime)
+    (if they want dates and times but don't care about time zones), $(LREF SysTime)
+    (if they want the date and time from the OS and/or do care about time
+    zones), and StopWatch (a platform-independent, high precision stop watch).
+    $(LREF Date) and $(LREF DateTime) are optimized for calendar-based operations,
+    while $(LREF SysTime) is designed for dealing with time from the OS. Check out
+    their specific documentation for more details.
+
+    To get the current time, use $(LREF2 .Clock.currTime, Clock.currTime).
+    It will return the current
+    time as a $(LREF SysTime). To print it, $(D toString) is
+    sufficient, but if using $(D toISOString), $(D toISOExtString), or
+    $(D toSimpleString), use the corresponding $(D fromISOString),
+    $(D fromISOExtString), or $(D fromSimpleString) to create a
+    $(LREF SysTime) from the string.
+
+--------------------
+auto currentTime = Clock.currTime();
+auto timeString = currentTime.toISOExtString();
+auto restoredTime = SysTime.fromISOExtString(timeString);
+--------------------
+
+    Various functions take a string (or strings) to represent a unit of time
+    (e.g. $(D convert!("days", "hours")(numDays))). The valid strings to use
+    with such functions are $(D "years"), $(D "months"), $(D "weeks"),
+    $(D "days"), $(D "hours"), $(D "minutes"), $(D "seconds"),
+    $(D "msecs") (milliseconds), $(D "usecs") (microseconds),
+    $(D "hnsecs") (hecto-nanoseconds - i.e. 100 ns), or some subset thereof.
+    There are a few functions in core.time which take $(D "nsecs"), but because
+    nothing in std.datetime has precision greater than hnsecs, and very little
+    in core.time does, no functions in std.datetime accept $(D "nsecs").
+    To remember which units are abbreviated and which aren't,
+    all units seconds and greater use their full names, and all
+    sub-second units are abbreviated (since they'd be rather long if they
+    weren't).
+
+    Note:
+        $(LREF DateTimeException) is an alias for $(CXREF time, TimeException),
+        so you don't need to worry about core.time functions and std.datetime
+        functions throwing different exception types (except in the rare case
+        that they throw something other than $(CXREF time, TimeException) or
+        $(LREF DateTimeException)).
+
+    See_Also:
+        $(DDLINK intro-to-_datetime, Introduction to std.datetime,
+                 Introduction to std&#46;_datetime)<br>
+        $(WEB en.wikipedia.org/wiki/ISO_8601, ISO 8601)<br>
+        $(WEB en.wikipedia.org/wiki/Tz_database,
+              Wikipedia entry on TZ Database)<br>
+        $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones,
+              List of Time Zones)<br>
+
+    Copyright: Copyright 2010 - 2015
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+    Authors:   Jonathan M Davis and Kato Shoichi
+    Source:    $(PHOBOSSRC std/_datetime.d)
+    Macros:
+        LREF2=<a href="#$1">$(D $2)</a>
++/
+module std.datetime;
+
+public import core.time;
+
+import core.exception;
+import core.stdc.time;
+
+import std.exception;
+import std.range.primitives;
+import std.traits;
+// FIXME
+import std.functional; //: unaryFun;
+
+version(Windows)
+{
+    import core.sys.windows.windows;
+    import core.sys.windows.winsock2;
+    import std.windows.registry;
+
+    // Uncomment and run unittests to print missing Windows TZ translations.
+    // Please subscribe to Microsoft Daylight Saving Time & Time Zone Blog
+    // (https://blogs.technet.microsoft.com/dst2007/) if you feel responsible
+    // for updating the translations.
+    // version = UpdateWindowsTZTranslations;
+}
+else version(Posix)
+{
+    import core.sys.posix.stdlib;
+    import core.sys.posix.sys.time;
+}
+
+version(unittest)
+{
+    import std.stdio;
+}
+
+unittest
+{
+    initializeTests();
+}
+
+//Verify module example.
+unittest
+{
+    auto currentTime = Clock.currTime();
+    auto timeString = currentTime.toISOExtString();
+    auto restoredTime = SysTime.fromISOExtString(timeString);
+}
+
+//Verify Examples for core.time.Duration which couldn't be in core.time.
+unittest
+{
+    assert(std.datetime.Date(2010, 9, 7) + dur!"days"(5) ==
+           std.datetime.Date(2010, 9, 12));
+
+    assert(std.datetime.Date(2010, 9, 7) - std.datetime.Date(2010, 10, 3) ==
+           dur!"days"(-26));
+}
+
+
+//==============================================================================
+// Section with public enums and constants.
+//==============================================================================
+
+/++
+    Represents the 12 months of the Gregorian year (January is 1).
+  +/
+enum Month : ubyte { jan = 1, ///
+                     feb,     ///
+                     mar,     ///
+                     apr,     ///
+                     may,     ///
+                     jun,     ///
+                     jul,     ///
+                     aug,     ///
+                     sep,     ///
+                     oct,     ///
+                     nov,     ///
+                     dec      ///
+                   }
+
+/++
+    Represents the 7 days of the Gregorian week (Sunday is 0).
+  +/
+enum DayOfWeek : ubyte { sun = 0, ///
+                         mon,     ///
+                         tue,     ///
+                         wed,     ///
+                         thu,     ///
+                         fri,     ///
+                         sat      ///
+                       }
+
+/++
+    In some date calculations, adding months or years can cause the date to fall
+    on a day of the month which is not valid (e.g. February 29th 2001 or
+    June 31st 2000). If overflow is allowed (as is the default), then the month
+    will be incremented accordingly (so, February 29th 2001 would become
+    March 1st 2001, and June 31st 2000 would become July 1st 2000). If overflow
+    is not allowed, then the day will be adjusted to the last valid day in that
+    month (so, February 29th 2001 would become February 28th 2001 and
+    June 31st 2000 would become June 30th 2000).
+
+    AllowDayOverflow only applies to calculations involving months or years.
+  +/
+enum AllowDayOverflow
+{
+    /// No, don't allow day overflow.
+    no,
+
+    /// Yes, allow day overflow.
+    yes
+}
+
+/++
+    Indicates a direction in time. One example of its use is $(LREF2 .Interval, Interval)'s
+    $(LREF expand, expand) function which uses it to indicate whether the interval should
+    be expanded backwards (into the past), forwards (into the future), or both.
+  +/
+enum Direction
+{
+    /// Backward.
+    bwd,
+
+    /// Forward.
+    fwd,
+
+    /// Both backward and forward.
+    both
+}
+
+/++
+    Used to indicate whether $(D popFront) should be called immediately upon
+    creating a range. The idea is that for some functions used to generate a
+    range for an interval, $(D front) is not necessarily a time point which
+    would ever be generated by the range. To get the first time point
+    in the range to match what the function generates, then use
+    $(D PopFirst.yes) to indicate that the range should have $(D popFront)
+    called on it before the range is returned so that $(D front) is a time point
+    which the function would generate.
+
+    For instance, if the function used to generate a range of time points
+    generated successive Easters (i.e. you're iterating over all of the Easters
+    within the interval), the initial date probably isn't an Easter. Using
+    $(D PopFirst.yes) would tell the function which returned the
+    range that $(D popFront) was to be called so that front would then be
+    an Easter - the next one generated by the function (which when
+    iterating forward would be the Easter following the original $(D front),
+    while when iterating backward, it would be the Easter prior to the
+    original $(D front)). If $(D PopFirst.no) were used, then $(D front) would
+    remain the original time point and it would not necessarily be a time point
+    which would be generated by the range-generating function (which in many
+    cases is exactly what is desired -
+    e.g. if iterating over every day starting at the beginning
+    of the interval).
+  +/
+enum PopFirst
+{
+    /// No, don't call popFront() before returning the range.
+    no,
+
+    /// Yes, call popFront() before returning the range.
+    yes
+}
+
+/++
+   Used by StopWatch to indicate whether it should start immediately upon
+   construction.
+  +/
+enum AutoStart
+{
+    /// No, don't start the StopWatch when it is constructed.
+    no,
+
+    /// Yes, do start the StopWatch when it is constructed.
+    yes
+}
+
+/++
+    Array of the strings representing time units, starting with the smallest
+    unit and going to the largest. It does not include $(D "nsecs").
+
+   Includes $(D "hnsecs") (hecto-nanoseconds (100 ns)),
+   $(D "usecs") (microseconds), $(D "msecs") (milliseconds), $(D "seconds"),
+   $(D "minutes"), $(D "hours"), $(D "days"), $(D "weeks"), $(D "months"), and
+   $(D "years")
+  +/
+immutable string[] timeStrings = ["hnsecs", "usecs", "msecs", "seconds", "minutes",
+                                  "hours", "days", "weeks", "months", "years"];
+
+
+//==============================================================================
+// Section with other types.
+//==============================================================================
+
+/++
+    Exception type used by std.datetime. It's an alias to $(CXREF time, TimeException).
+    Either can be caught without concern about which
+    module it came from.
+  +/
+alias DateTimeException = TimeException;
+
+/++
+    Effectively a namespace to make it clear that the methods it contains are
+    getting the time from the system clock. It cannot be instantiated.
+ +/
+final class Clock
+{
+public:
+
+    /++
+        Returns the current time in the given time zone.
+
+        Params:
+            clockType = The $(CXREF time, ClockType) indicates which system
+                        clock to use to get the current time. Very few programs
+                        need to use anything other than the default.
+            tz = The time zone for the SysTime that's returned.
+
+        Throws:
+            $(LREF DateTimeException) if it fails to get the time.
+      +/
+    static SysTime currTime(ClockType clockType = ClockType.normal)(immutable TimeZone tz = LocalTime()) @safe
+    {
+        return SysTime(currStdTime!clockType, tz);
+    }
+
+    unittest
+    {
+        import std.format : format;
+        assert(currTime().timezone is LocalTime());
+        assert(currTime(UTC()).timezone is UTC());
+
+        // core.stdc.time.time does not always use unix time on Windows systems.
+        // In particular, dmc does not use unix time. If we can guarantee that
+        // the MS runtime uses unix time, then we may be able run this test
+        // then, but for now, we're just not going to run this test on Windows.
+        version(Posix)
+        {
+            static import std.math;
+            immutable unixTimeD = currTime().toUnixTime();
+            immutable unixTimeC = core.stdc.time.time(null);
+            assert(std.math.abs(unixTimeC - unixTimeD) <= 2);
+        }
+
+        auto norm1 = Clock.currTime;
+        auto norm2 = Clock.currTime(UTC());
+        assert(norm1 <= norm2, format("%s %s", norm1, norm2));
+        assert(abs(norm1 - norm2) <= seconds(2));
+
+        import std.meta : AliasSeq;
+        foreach(ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second))
+        {
+            scope(failure) writefln("ClockType.%s", ct);
+            auto value1 = Clock.currTime!ct;
+            auto value2 = Clock.currTime!ct(UTC());
+            assert(value1 <= value2, format("%s %s", value1, value2));
+            assert(abs(value1 - value2) <= seconds(2));
+        }
+    }
+
+
+    /++
+        Returns the number of hnsecs since midnight, January 1st, 1 A.D. for the
+        current time.
+
+        Params:
+            clockType = The $(CXREF time, ClockType) indicates which system
+                        clock to use to get the current time. Very few programs
+                        need to use anything other than the default.
+
+        Throws:
+            $(LREF DateTimeException) if it fails to get the time.
+      +/
+    static @property long currStdTime(ClockType clockType = ClockType.normal)() @trusted
+    {
+        static if(clockType != ClockType.coarse &&
+                  clockType != ClockType.normal &&
+                  clockType != ClockType.precise &&
+                  clockType != ClockType.second)
+        {
+            import std.format : format;
+            static assert(0, format("ClockType.%s is not supported by Clock.currTime or Clock.currStdTime", clockType));
+        }
+
+        version(Windows)
+        {
+            FILETIME fileTime;
+            GetSystemTimeAsFileTime(&fileTime);
+            immutable result = FILETIMEToStdTime(&fileTime);
+            static if(clockType == ClockType.second)
+            {
+                // Ideally, this would use core.std.time.time, but the C runtime
+                // has to be using unix time for that to work, and that's not
+                // guaranteed on Windows. Digital Mars does not use unix time.
+                // MS may or may not. If it does, then this can be made to use
+                // core.stdc.time for MS, but for now, we'll leave it like this.
+                return convert!("seconds", "hnsecs")(convert!("hnsecs", "seconds")(result));
+            }
+            else
+                return result;
+        }
+        else version(Posix)
+        {
+            enum hnsecsToUnixEpoch = unixTimeToStdTime(0);
+
+            version(OSX)
+            {
+                static if(clockType == ClockType.second)
+                    return unixTimeToStdTime(core.stdc.time.time(null));
+                else
+                {
+                    timeval tv;
+                    if(gettimeofday(&tv, null) != 0)
+                        throw new TimeException("Call to gettimeofday() failed");
+                    return convert!("seconds", "hnsecs")(tv.tv_sec) +
+                           convert!("usecs", "hnsecs")(tv.tv_usec) +
+                           hnsecsToUnixEpoch;
+                }
+            }
+            else version(linux)
+            {
+                static if(clockType == ClockType.second)
+                    return unixTimeToStdTime(core.stdc.time.time(null));
+                else
+                {
+                    import core.sys.linux.time;
+                    static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME_COARSE;
+                    else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
+                    else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME;
+                    else static assert(0, "Previous static if is wrong.");
+                    timespec ts;
+                    if(clock_gettime(clockArg, &ts) != 0)
+                        throw new TimeException("Call to clock_gettime() failed");
+                    return convert!("seconds", "hnsecs")(ts.tv_sec) +
+                           ts.tv_nsec / 100 +
+                           hnsecsToUnixEpoch;
+                }
+            }
+            else version(FreeBSD)
+            {
+                import core.sys.freebsd.time;
+                static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME_FAST;
+                else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
+                else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME_PRECISE;
+                else static if(clockType == ClockType.second)  alias clockArg = CLOCK_SECOND;
+                else static assert(0, "Previous static if is wrong.");
+                timespec ts;
+                if(clock_gettime(clockArg, &ts) != 0)
+                    throw new TimeException("Call to clock_gettime() failed");
+                return convert!("seconds", "hnsecs")(ts.tv_sec) +
+                       ts.tv_nsec / 100 +
+                       hnsecsToUnixEpoch;
+            }
+            else version(NetBSD)
+            {
+                static if(clockType == ClockType.second)
+                    return unixTimeToStdTime(core.stdc.time.time(null));
+                else
+                {
+                    timeval tv;
+                    if(gettimeofday(&tv, null) != 0)
+                        throw new TimeException("Call to gettimeofday() failed");
+                    return convert!("seconds", "hnsecs")(tv.tv_sec) +
+                           convert!("usecs", "hnsecs")(tv.tv_usec) +
+                           hnsecsToUnixEpoch;
+                }
+            }
+            else version(Solaris)
+            {
+                static if(clockType == ClockType.second)
+                    return unixTimeToStdTime(core.stdc.time.time(null));
+                else
+                {
+                    import core.sys.solaris.time;
+                    static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME;
+                    else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
+                    else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME;
+                    else static assert(0, "Previous static if is wrong.");
+                    timespec ts;
+                    if(clock_gettime(clockArg, &ts) != 0)
+                        throw new TimeException("Call to clock_gettime() failed");
+                    return convert!("seconds", "hnsecs")(ts.tv_sec) +
+                           ts.tv_nsec / 100 +
+                           hnsecsToUnixEpoch;
+                }
+            }
+            else static assert(0, "Unsupported OS");
+        }
+        else static assert(0, "Unsupported OS");
+    }
+
+    unittest
+    {
+        import std.math : abs;
+        import std.format : format;
+        enum limit = convert!("seconds", "hnsecs")(2);
+
+        auto norm1 = Clock.currStdTime;
+        auto norm2 = Clock.currStdTime;
+        assert(norm1 <= norm2, format("%s %s", norm1, norm2));
+        assert(abs(norm1 - norm2) <= limit);
+
+        import std.meta : AliasSeq;
+        foreach(ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second))
+        {
+            scope(failure) writefln("ClockType.%s", ct);
+            auto value1 = Clock.currStdTime!ct;
+            auto value2 = Clock.currStdTime!ct;
+            assert(value1 <= value2, format("%s %s", value1, value2));
+            assert(abs(value1 - value2) <= limit);
+        }
+    }
+
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime). Use $(D MonoTime.currTime)
+              instead. currSystemTick will be removed in January 2017.)
+
+        The current system tick. The number of ticks per second varies from
+        system to system. currSystemTick uses a monotonic clock, so it's
+        intended for precision timing by comparing relative time values, not
+        for getting the current system time.
+
+        Warning:
+            On some systems, the monotonic clock may stop counting when
+            the computer goes to sleep or hibernates. So, the monotonic
+            clock could be off if that occurs. This is known to happen
+            on Mac OS X. It has not been tested whether it occurs on
+            either Windows or Linux.
+
+        Throws:
+            $(LREF DateTimeException) if it fails to get the time.
+      +/
+    deprecated("Use core.time.MonoTime.currTime instead")
+    static @property TickDuration currSystemTick() @safe nothrow
+    {
+        return TickDuration.currSystemTick;
+    }
+
+    deprecated unittest
+    {
+        assert(Clock.currSystemTick.length > 0);
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime). To duplicate the behavior
+              of currAppTick with $(D MonoTime), store the value of
+              $(D MonoTime.currTime) when the program starts, and then subtract
+              it from the current value of $(D MonoTime.currTime) in order to
+              determine how long the program has been running. currAppTick will
+              be removed in January 2017.)
+
+        --------------------
+        immutable MonoTime startupTime;
+        shared static this()
+        {
+            startupTime = MonoTime.currTime;
+        }
+        Duration timeSinceProgramStarted()
+        {
+            return MonoTime.currTime - startupTime;
+        }
+        --------------------
+
+        The current number of system ticks since the application started.
+        The number of ticks per second varies from system to system.
+        This uses a monotonic clock.
+
+        Warning:
+            On some systems, the monotonic clock may stop counting when
+            the computer goes to sleep or hibernates. So, the monotonic
+            clock could be off if that occurs. This is known to happen
+            on Mac OS X. It has not been tested whether it occurs on
+            either Windows or on Linux.
+
+        Throws:
+            $(LREF DateTimeException) if it fails to get the time.
+      +/
+    deprecated("Use core.time.MonoTime instead. See currAppTick's documentation for details.")
+    static @property TickDuration currAppTick() @safe
+    {
+        return currSystemTick - TickDuration.appOrigin;
+    }
+
+    deprecated unittest
+    {
+        auto a = Clock.currSystemTick;
+        auto b = Clock.currAppTick;
+        assert(a.length);
+        assert(b.length);
+        assert(a > b);
+    }
+
+private:
+
+    @disable this() {}
+}
+
+//==============================================================================
+// Section with time points.
+//==============================================================================
+
+/++
+    $(D SysTime) is the type used to get the current time from the
+    system or doing anything that involves time zones. Unlike
+    $(LREF DateTime), the time zone is an integral part of $(D SysTime) (though for
+    local time applications, time zones can be ignored and
+    it will work, since it defaults to using the local time zone). It holds its
+    internal time in std time (hnsecs since midnight, January 1st, 1 A.D. UTC),
+    so it interfaces well with the system time. However, that means that, unlike
+    $(LREF DateTime), it is not optimized for calendar-based operations, and
+    getting individual units from it such as years or days is going to involve
+    conversions and be less efficient.
+
+    For calendar-based operations that don't
+    care about time zones, then $(LREF DateTime) would be the type to
+    use. For system time, use $(D SysTime).
+
+    $(LREF2 .Clock.currTime, Clock.currTime) will return the current time as a $(D SysTime).
+    To convert a $(D SysTime) to a $(LREF Date) or $(LREF DateTime), simply cast
+    it. To convert a $(LREF Date) or $(LREF DateTime) to a
+    $(D SysTime), use $(D SysTime)'s constructor, and pass in the
+    intended time zone with it (or don't pass in a $(LREF2 .TimeZone, TimeZone), and the local
+    time zone will be used). Be aware, however, that converting from a
+    $(LREF DateTime) to a $(D SysTime) will not necessarily be 100% accurate due to
+    DST (one hour of the year doesn't exist and another occurs twice).
+    To not risk any conversion errors, keep times as
+    $(D SysTime)s. Aside from DST though, there shouldn't be any conversion
+    problems.
+
+    For using time zones other than local time or UTC, use
+    $(LREF PosixTimeZone) on Posix systems (or on Windows, if providing the TZ
+    Database files), and use $(LREF WindowsTimeZone) on Windows systems.
+    The time in $(D SysTime) is kept internally in hnsecs from midnight,
+    January 1st, 1 A.D. UTC. Conversion error cannot happen when changing
+    the time zone of a $(D SysTime). $(LREF LocalTime) is the $(LREF2 .TimeZone, TimeZone) class
+    which represents the local time, and $(D UTC) is the $(LREF2 .TimeZone, TimeZone) class
+    which represents UTC. $(D SysTime) uses $(LREF LocalTime) if no $(LREF2 .TimeZone, TimeZone)
+    is provided. For more details on time zones, see the documentation for
+    $(LREF2 .TimeZone, TimeZone), $(LREF PosixTimeZone), and $(LREF WindowsTimeZone).
+
+    $(D SysTime)'s range is from approximately 29,000 B.C. to approximately
+    29,000 A.D.
+  +/
+struct SysTime
+{
+    import std.typecons : Rebindable;
+
+public:
+
+    /++
+        Params:
+            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
+                       internal std time. As $(LREF DateTime) has no concept of
+                       time zone, tz is used as its time zone.
+            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
+                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
+                       assumed to be in the given time zone.
+      +/
+    this(in DateTime dateTime, immutable TimeZone tz = null) @safe nothrow
+    {
+        try
+            this(dateTime, Duration.zero, tz);
+        catch(Exception e)
+            assert(0, "SysTime's constructor threw when it shouldn't have.");
+    }
+
+    unittest
+    {
+        import std.format : format;
+        static void test(DateTime dt, immutable TimeZone tz, long expected)
+        {
+            auto sysTime = SysTime(dt, tz);
+            assert(sysTime._stdTime == expected);
+            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
+                   format("Given DateTime: %s", dt));
+        }
+
+        test(DateTime.init, UTC(), 0);
+        test(DateTime(1, 1, 1, 12, 30, 33), UTC(), 450_330_000_000L);
+        test(DateTime(0, 12, 31, 12, 30, 33), UTC(), -413_670_000_000L);
+        test(DateTime(1, 1, 1, 0, 0, 0), UTC(), 0);
+        test(DateTime(1, 1, 1, 0, 0, 1), UTC(), 10_000_000L);
+        test(DateTime(0, 12, 31, 23, 59, 59), UTC(), -10_000_000L);
+
+        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(-60)), 36_000_000_000L);
+        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero), 0);
+        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(60)), -36_000_000_000L);
+    }
+
+    /++
+        Params:
+            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
+                       internal std time. As $(LREF DateTime) has no concept of
+                       time zone, tz is used as its time zone.
+            fracSecs = The fractional seconds portion of the time.
+            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
+                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
+                       assumed to be in the given time zone.
+
+        Throws:
+            $(LREF DateTimeException) if $(D fracSecs) is negative or if it's
+            greater than or equal to one second.
+      +/
+    this(in DateTime dateTime, in Duration fracSecs, immutable TimeZone tz = null) @safe
+    {
+        enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds."));
+        enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second."));
+        auto nonNullTZ = tz is null ? LocalTime() : tz;
+
+        immutable dateDiff = dateTime.date - Date.init;
+        immutable todDiff = dateTime.timeOfDay - TimeOfDay.init;
+
+        immutable adjustedTime = dateDiff + todDiff + fracSecs;
+        immutable standardTime = nonNullTZ.tzToUTC(adjustedTime.total!"hnsecs");
+
+        this(standardTime, nonNullTZ);
+    }
+
+    unittest
+    {
+        import std.format : format;
+        static void test(DateTime dt, Duration fracSecs, immutable TimeZone tz, long expected)
+        {
+            auto sysTime = SysTime(dt, fracSecs, tz);
+            assert(sysTime._stdTime == expected);
+            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
+                   format("Given DateTime: %s, Given Duration: %s", dt, fracSecs));
+        }
+
+        test(DateTime.init, Duration.zero, UTC(), 0);
+        test(DateTime(1, 1, 1, 12, 30, 33), Duration.zero, UTC(), 450_330_000_000L);
+        test(DateTime(0, 12, 31, 12, 30, 33), Duration.zero, UTC(), -413_670_000_000L);
+        test(DateTime(1, 1, 1, 0, 0, 0), msecs(1), UTC(), 10_000L);
+        test(DateTime(0, 12, 31, 23, 59, 59), msecs(999), UTC(), -10_000L);
+
+        test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC(), -1);
+        test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC(), -9_999_999);
+        test(DateTime(0, 12, 31, 23, 59, 59), Duration.zero, UTC(), -10_000_000);
+
+        assertThrown!DateTimeException(SysTime(DateTime.init, hnsecs(-1), UTC()));
+        assertThrown!DateTimeException(SysTime(DateTime.init, seconds(1), UTC()));
+    }
+
+    // @@@DEPRECATED_2016-08@@@
+    /++
+        $(RED Deprecated. Please use the overload which takes a
+              $(CXREF time, Duration) for the fractional seconds. This overload
+              will be removed in August 2016.)
+
+        Params:
+            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
+                       internal std time. As $(LREF DateTime) has no concept of
+                       time zone, tz is used as its time zone.
+            fracSec  = The fractional seconds portion of the time.
+            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
+                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
+                       assumed to be in the given time zone.
+
+        Throws:
+            $(LREF DateTimeException) if $(D fracSec) is negative.
+      +/
+    deprecated("Please use the overload which takes a Duration instead of a FracSec.")
+    this(in DateTime dateTime, in FracSec fracSec, immutable TimeZone tz = null) @safe
+    {
+        immutable fracHNSecs = fracSec.hnsecs;
+        enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds."));
+        _timezone = tz is null ? LocalTime() : tz;
+
+        try
+        {
+            immutable dateDiff = (dateTime.date - Date(1, 1, 1)).total!"hnsecs";
+            immutable todDiff = (dateTime.timeOfDay - TimeOfDay(0, 0, 0)).total!"hnsecs";
+
+            immutable adjustedTime = dateDiff + todDiff + fracHNSecs;
+            immutable standardTime = _timezone.tzToUTC(adjustedTime);
+
+            this(standardTime, _timezone);
+        }
+        catch(Exception e)
+            assert(0, "Date, TimeOfDay, or DateTime's constructor threw when it shouldn't have.");
+    }
+
+    deprecated unittest
+    {
+        import std.format : format;
+
+        static void test(DateTime dt,
+                         FracSec fracSec,
+                         immutable TimeZone tz,
+                         long expected)
+        {
+            auto sysTime = SysTime(dt, fracSec, tz);
+            assert(sysTime._stdTime == expected);
+            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
+                   format("Given DateTime: %s, Given FracSec: %s", dt, fracSec));
+        }
+
+        test(DateTime.init, FracSec.init, UTC(), 0);
+        test(DateTime(1, 1, 1, 12, 30, 33), FracSec.init, UTC(), 450_330_000_000L);
+        test(DateTime(0, 12, 31, 12, 30, 33), FracSec.init, UTC(), -413_670_000_000L);
+        test(DateTime(1, 1, 1, 0, 0, 0), FracSec.from!"msecs"(1), UTC(), 10_000L);
+        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"msecs"(999), UTC(), -10_000L);
+
+        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(9_999_999), UTC(), -1);
+        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(1), UTC(), -9_999_999);
+        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(0), UTC(), -10_000_000);
+
+        assertThrown!DateTimeException(SysTime(DateTime.init, FracSec.from!"hnsecs"(-1), UTC()));
+    }
+
+    /++
+        Params:
+            date = The $(LREF Date) to use to set this $(LREF SysTime)'s internal std
+                   time. As $(LREF Date) has no concept of time zone, tz is used as
+                   its time zone.
+            tz   = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
+                   $(LREF LocalTime) will be used. The given $(LREF Date) is assumed
+                   to be in the given time zone.
+      +/
+    this(in Date date, immutable TimeZone tz = null) @safe nothrow
+    {
+        _timezone = tz is null ? LocalTime() : tz;
+
+        try
+        {
+            immutable adjustedTime = (date - Date(1, 1, 1)).total!"hnsecs";
+            immutable standardTime = _timezone.tzToUTC(adjustedTime);
+
+            this(standardTime, _timezone);
+        }
+        catch(Exception e)
+            assert(0, "Date's constructor through when it shouldn't have.");
+    }
+
+    unittest
+    {
+        static void test(Date d, immutable TimeZone tz, long expected)
+        {
+            import std.format : format;
+            auto sysTime = SysTime(d, tz);
+            assert(sysTime._stdTime == expected);
+            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
+                   format("Given Date: %s", d));
+        }
+
+        test(Date.init, UTC(), 0);
+        test(Date(1, 1, 1), UTC(), 0);
+        test(Date(1, 1, 2), UTC(), 864000000000);
+        test(Date(0, 12, 31), UTC(), -864000000000);
+    }
+
+    /++
+        Note:
+            Whereas the other constructors take in the given date/time, assume
+            that it's in the given time zone, and convert it to hnsecs in UTC
+            since midnight, January 1st, 1 A.D. UTC - i.e. std time - this
+            constructor takes a std time, which is specifically already in UTC,
+            so no conversion takes place. Of course, the various getter
+            properties and functions will use the given time zone's conversion
+            function to convert the results to that time zone, but no conversion
+            of the arguments to this constructor takes place.
+
+        Params:
+            stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. UTC.
+            tz      = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
+                      $(LREF LocalTime) will be used.
+      +/
+    this(long stdTime, immutable TimeZone tz = null) @safe pure nothrow
+    {
+        _stdTime = stdTime;
+        _timezone = tz is null ? LocalTime() : tz;
+    }
+
+    unittest
+    {
+        static void test(long stdTime, immutable TimeZone tz)
+        {
+            import std.format : format;
+            auto sysTime = SysTime(stdTime, tz);
+            assert(sysTime._stdTime == stdTime);
+            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
+                   format("Given stdTime: %s", stdTime));
+        }
+
+        foreach(stdTime; [-1234567890L, -250, 0, 250, 1235657390L])
+        {
+            foreach(tz; testTZs)
+                test(stdTime, tz);
+        }
+    }
+
+    /++
+        Params:
+            rhs = The $(LREF SysTime) to assign to this one.
+      +/
+    ref SysTime opAssign(const ref SysTime rhs) return @safe pure nothrow
+    {
+        _stdTime = rhs._stdTime;
+        _timezone = rhs._timezone;
+
+        return this;
+    }
+
+    /++
+        Params:
+            rhs = The $(LREF SysTime) to assign to this one.
+      +/
+    ref SysTime opAssign(SysTime rhs) return @safe pure nothrow
+    {
+        _stdTime = rhs._stdTime;
+        _timezone = rhs._timezone;
+
+        return this;
+    }
+
+    /++
+        Checks for equality between this $(LREF SysTime) and the given
+        $(LREF SysTime).
+
+        Note that the time zone is ignored. Only the internal
+        std times (which are in UTC) are compared.
+     +/
+    bool opEquals(const SysTime rhs) @safe const pure nothrow
+    {
+        return opEquals(rhs);
+    }
+
+    /// ditto
+    bool opEquals(const ref SysTime rhs) @safe const pure nothrow
+    {
+        return _stdTime == rhs._stdTime;
+    }
+
+    unittest
+    {
+        import std.range;
+        assert(SysTime(DateTime.init, UTC()) == SysTime(0, UTC()));
+        assert(SysTime(DateTime.init, UTC()) == SysTime(0));
+        assert(SysTime(Date.init, UTC()) == SysTime(0));
+        assert(SysTime(0) == SysTime(0));
+
+        static void test(DateTime dt,
+                         immutable TimeZone tz1,
+                         immutable TimeZone tz2)
+        {
+            auto st1 = SysTime(dt);
+            st1.timezone = tz1;
+
+            auto st2 = SysTime(dt);
+            st2.timezone = tz2;
+
+            assert(st1 == st2);
+        }
+
+        foreach(tz1; testTZs)
+        {
+            foreach(tz2; testTZs)
+            {
+                foreach(dt; chain(testDateTimesBC, testDateTimesAD))
+                        test(dt, tz1, tz2);
+            }
+        }
+
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        assert(st == st);
+        assert(st == cst);
+        //assert(st == ist);
+        assert(cst == st);
+        assert(cst == cst);
+        //assert(cst == ist);
+        //assert(ist == st);
+        //assert(ist == cst);
+        //assert(ist == ist);
+    }
+
+    /++
+        Compares this $(LREF SysTime) with the given $(LREF SysTime).
+
+        Time zone is irrelevant when comparing $(LREF SysTime)s.
+
+        Returns:
+            $(BOOKTABLE,
+            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+            $(TR $(TD this == rhs) $(TD 0))
+            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+            )
+     +/
+    int opCmp(in SysTime rhs) @safe const pure nothrow
+    {
+        if(_stdTime < rhs._stdTime)
+            return -1;
+        if(_stdTime > rhs._stdTime)
+            return 1;
+
+        return 0;
+    }
+
+    unittest
+    {
+        import std.range;
+        assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0, UTC())) == 0);
+        assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0)) == 0);
+        assert(SysTime(Date.init, UTC()).opCmp(SysTime(0)) == 0);
+        assert(SysTime(0).opCmp(SysTime(0)) == 0);
+
+        static void testEqual(SysTime st,
+                              immutable TimeZone tz1,
+                              immutable TimeZone tz2)
+        {
+            auto st1 = st;
+            st1.timezone = tz1;
+
+            auto st2 = st;
+            st2.timezone = tz2;
+
+            assert(st1.opCmp(st2) == 0);
+        }
+
+        auto sts = array(map!SysTime(chain(testDateTimesBC, testDateTimesAD)));
+
+        foreach(st; sts)
+            foreach(tz1; testTZs)
+                foreach(tz2; testTZs)
+                    testEqual(st, tz1, tz2);
+
+        static void testCmp(SysTime st1,
+                            immutable TimeZone tz1,
+                            SysTime st2,
+                            immutable TimeZone tz2)
+        {
+            st1.timezone = tz1;
+            st2.timezone = tz2;
+            assert(st1.opCmp(st2) < 0);
+            assert(st2.opCmp(st1) > 0);
+        }
+
+        foreach(si, st1; sts)
+            foreach(st2; sts[si+1 .. $])
+                foreach(tz1; testTZs)
+                    foreach(tz2; testTZs)
+                        testCmp(st1, tz1, st2, tz2);
+
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
+        assert(st.opCmp(st) == 0);
+        assert(st.opCmp(cst) == 0);
+        //assert(st.opCmp(ist) == 0);
+        assert(cst.opCmp(st) == 0);
+        assert(cst.opCmp(cst) == 0);
+        //assert(cst.opCmp(ist) == 0);
+        //assert(ist.opCmp(st) == 0);
+        //assert(ist.opCmp(cst) == 0);
+        //assert(ist.opCmp(ist) == 0);
+    }
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+     +/
+    @property short year() @safe const nothrow
+    {
+        return (cast(Date)this).year;
+    }
+
+    unittest
+    {
+        import std.range;
+        static void test(SysTime sysTime, long expected)
+        {
+            import std.format : format;
+            assert(sysTime.year == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), 1);
+        test(SysTime(1, UTC()), 1);
+        test(SysTime(-1, UTC()), 0);
+
+        foreach(year; chain(testYearsBC, testYearsAD))
+        {
+            foreach(md; testMonthDays)
+            {
+                foreach(tod; testTODs)
+                {
+                    auto dt = DateTime(Date(year, md.month, md.day), tod);
+
+                    foreach(tz; testTZs)
+                    {
+                        foreach(fs; testFracSecs)
+                            test(SysTime(dt, fs, tz), year);
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.year == 1999);
+        //assert(ist.year == 1999);
+    }
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+
+        Params:
+            year = The year to set this $(LREF SysTime)'s year to.
+
+        Throws:
+            $(LREF DateTimeException) if the new year is not a leap year and the
+            resulting date would be on February 29th.
+     +/
+    @property void year(int year) @safe
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.year = year;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
+        adjTime = newDaysHNSecs + hnsecs;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).year == 1999);
+        assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).year == 2010);
+        assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).year == -7);
+    }
+
+    unittest
+    {
+        import std.range;
+        static void test(SysTime st, int year, in SysTime expected)
+        {
+            st.year = year;
+            assert(st == expected);
+        }
+
+        foreach(st; chain(testSysTimesBC, testSysTimesAD))
+        {
+            auto dt = cast(DateTime)st;
+
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second),
+                                 st.fracSecs,
+                                 st.timezone);
+                test(st, year, e);
+            }
+        }
+
+        foreach(fs; testFracSecs)
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(tod; testTODs)
+                {
+                    test(SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz), 2000,
+                         SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz));
+                    test(SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz), 1999,
+                         SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz));
+                }
+
+                foreach(tod; testTODsThrown)
+                {
+                    auto st = SysTime(DateTime(Date(2000, 2, 29), tod), fs, tz);
+                    assertThrown!DateTimeException(st.year = 1999);
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.year = 7));
+        //static assert(!__traits(compiles, ist.year = 7));
+    }
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Throws:
+            $(LREF DateTimeException) if $(D isAD) is true.
+     +/
+    @property ushort yearBC() @safe const
+    {
+        return (cast(Date)this).yearBC;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(0, 1, 1, 12, 30, 33)).yearBC == 1);
+        assert(SysTime(DateTime(-1, 1, 1, 10, 7, 2)).yearBC == 2);
+        assert(SysTime(DateTime(-100, 1, 1, 4, 59, 0)).yearBC == 101);
+    }
+
+    unittest
+    {
+        import std.format : format;
+        foreach(st; testSysTimesBC)
+        {
+            auto msg = format("SysTime: %s", st);
+            assertNotThrown!DateTimeException(st.yearBC, msg);
+            assert(st.yearBC == (st.year * -1) + 1, msg);
+        }
+
+        foreach(st; [testSysTimesAD[0], testSysTimesAD[$/2], testSysTimesAD[$-1]])
+            assertThrown!DateTimeException(st.yearBC, format("SysTime: %s", st));
+
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        st.year = 12;
+        assert(st.year == 12);
+        static assert(!__traits(compiles, cst.year = 12));
+        //static assert(!__traits(compiles, ist.year = 12));
+    }
+
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Params:
+            year = The year B.C. to set this $(LREF SysTime)'s year to.
+
+        Throws:
+            $(LREF DateTimeException) if a non-positive value is given.
+     +/
+    @property void yearBC(int year) @safe
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.yearBC = year;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
+        adjTime = newDaysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(2010, 1, 1, 7, 30, 0));
+        st.yearBC = 1;
+        assert(st == SysTime(DateTime(0, 1, 1, 7, 30, 0)));
+
+        st.yearBC = 10;
+        assert(st == SysTime(DateTime(-9, 1, 1, 7, 30, 0)));
+    }
+
+    unittest
+    {
+        import std.range;
+        static void test(SysTime st, int year, in SysTime expected)
+        {
+            import std.format : format;
+            st.yearBC = year;
+            assert(st == expected, format("SysTime: %s", st));
+        }
+
+        foreach(st; chain(testSysTimesBC, testSysTimesAD))
+        {
+            auto dt = cast(DateTime)st;
+
+            foreach(year; testYearsBC)
+            {
+                auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second),
+                                 st.fracSecs,
+                                 st.timezone);
+                test(st, (year * -1) + 1, e);
+            }
+        }
+
+        foreach(st; [testSysTimesBC[0], testSysTimesBC[$ - 1],
+                     testSysTimesAD[0], testSysTimesAD[$ - 1]])
+        {
+            foreach(year; testYearsBC)
+                assertThrown!DateTimeException(st.yearBC = year);
+        }
+
+        foreach(fs; testFracSecs)
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(tod; testTODs)
+                {
+                    test(SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz), 2001,
+                         SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz));
+                    test(SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz), 2000,
+                         SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz));
+                }
+
+                foreach(tod; testTODsThrown)
+                {
+                    auto st = SysTime(DateTime(Date(-2000, 2, 29), tod), fs, tz);
+                    assertThrown!DateTimeException(st.year = -1999);
+                }
+            }
+        }
+
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        st.yearBC = 12;
+        assert(st.yearBC == 12);
+        static assert(!__traits(compiles, cst.yearBC = 12));
+        //static assert(!__traits(compiles, ist.yearBC = 12));
+    }
+
+    /++
+        Month of a Gregorian Year.
+     +/
+    @property Month month() @safe const nothrow
+    {
+        return (cast(Date)this).month;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).month == 7);
+        assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).month == 10);
+        assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).month == 4);
+    }
+
+    unittest
+    {
+        import std.range;
+        static void test(SysTime sysTime, Month expected)
+        {
+            import std.format : format;
+            assert(sysTime.month == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), Month.jan);
+        test(SysTime(1, UTC()), Month.jan);
+        test(SysTime(-1, UTC()), Month.dec);
+
+        foreach(year; chain(testYearsBC, testYearsAD))
+        {
+            foreach(md; testMonthDays)
+            {
+                foreach(tod; testTODs)
+                {
+                    auto dt = DateTime(Date(year, md.month, md.day), tod);
+
+                    foreach(fs; testFracSecs)
+                    {
+                        foreach(tz; testTZs)
+                            test(SysTime(dt, fs, tz), md.month);
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.month == 7);
+        //assert(ist.month == 7);
+    }
+
+
+    /++
+        Month of a Gregorian Year.
+
+        Params:
+            month = The month to set this $(LREF SysTime)'s month to.
+
+        Throws:
+            $(LREF DateTimeException) if the given month is not a valid month.
+     +/
+    @property void month(Month month) @safe
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.month = month;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
+        adjTime = newDaysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        import std.range;
+
+        static void test(SysTime st, Month month, in SysTime expected)
+        {
+            st.month = cast(Month)month;
+            assert(st == expected);
+        }
+
+        foreach(st; chain(testSysTimesBC, testSysTimesAD))
+        {
+            auto dt = cast(DateTime)st;
+
+            foreach(md; testMonthDays)
+            {
+                if(st.day > maxDay(dt.year, md.month))
+                    continue;
+                auto e = SysTime(DateTime(dt.year, md.month, dt.day, dt.hour, dt.minute, dt.second),
+                                 st.fracSecs,
+                                 st.timezone);
+                test(st, md.month, e);
+            }
+        }
+
+        foreach(fs; testFracSecs)
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(tod; testTODs)
+                {
+                    foreach(year; filter!((a){return yearIsLeapYear(a);})
+                                         (chain(testYearsBC, testYearsAD)))
+                    {
+                        test(SysTime(DateTime(Date(year, 1, 29), tod), fs, tz),
+                             Month.feb,
+                             SysTime(DateTime(Date(year, 2, 29), tod), fs, tz));
+                    }
+
+                    foreach(year; chain(testYearsBC, testYearsAD))
+                    {
+                        test(SysTime(DateTime(Date(year, 1, 28), tod), fs, tz),
+                             Month.feb,
+                             SysTime(DateTime(Date(year, 2, 28), tod), fs, tz));
+                        test(SysTime(DateTime(Date(year, 7, 30), tod), fs, tz),
+                             Month.jun,
+                             SysTime(DateTime(Date(year, 6, 30), tod), fs, tz));
+                    }
+                }
+            }
+        }
+
+        foreach(fs; [testFracSecs[0], testFracSecs[$-1]])
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(tod; testTODsThrown)
+                {
+                    foreach(year; [testYearsBC[$-3], testYearsBC[$-2],
+                                   testYearsBC[$-2], testYearsAD[0],
+                                   testYearsAD[$-2], testYearsAD[$-1]])
+                    {
+                        auto day = yearIsLeapYear(year) ? 30 : 29;
+                        auto st1 = SysTime(DateTime(Date(year, 1, day), tod), fs, tz);
+                        assertThrown!DateTimeException(st1.month = Month.feb);
+
+                        auto st2 = SysTime(DateTime(Date(year, 7, 31), tod), fs, tz);
+                        assertThrown!DateTimeException(st2.month = Month.jun);
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.month = 12));
+        //static assert(!__traits(compiles, ist.month = 12));
+    }
+
+    /++
+        Day of a Gregorian Month.
+     +/
+    @property ubyte day() @safe const nothrow
+    {
+        return (cast(Date)this).day;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).day == 6);
+        assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).day == 4);
+        assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).day == 5);
+    }
+
+    unittest
+    {
+        import std.range;
+
+        static void test(SysTime sysTime, int expected)
+        {
+            import std.format : format;
+            assert(sysTime.day == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), 1);
+        test(SysTime(1, UTC()), 1);
+        test(SysTime(-1, UTC()), 31);
+
+        foreach(year; chain(testYearsBC, testYearsAD))
+        {
+            foreach(md; testMonthDays)
+            {
+                foreach(tod; testTODs)
+                {
+                    auto dt = DateTime(Date(year, md.month, md.day), tod);
+
+                    foreach(tz; testTZs)
+                    {
+                        foreach(fs; testFracSecs)
+                            test(SysTime(dt, fs, tz), md.day);
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+         assert(cst.day == 6);
+        //assert(ist.day == 6);
+    }
+
+
+    /++
+        Day of a Gregorian Month.
+
+        Params:
+            day = The day of the month to set this $(LREF SysTime)'s day to.
+
+        Throws:
+            $(LREF DateTimeException) if the given day is not a valid day of the
+            current month.
+     +/
+    @property void day(int day) @safe
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.day = day;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
+        adjTime = newDaysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        import std.format : format;
+        import std.range;
+
+        foreach(day; chain(testDays))
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+
+                if(day > maxDay(dt.year, dt.month))
+                    continue;
+                auto expected = SysTime(DateTime(dt.year, dt.month, day, dt.hour, dt.minute, dt.second),
+                                        st.fracSecs,
+                                        st.timezone);
+                st.day = day;
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        foreach(tz; testTZs)
+        {
+            foreach(tod; testTODs)
+            {
+                foreach(fs; testFracSecs)
+                {
+                    foreach(year; chain(testYearsBC, testYearsAD))
+                    {
+                        foreach(month; EnumMembers!Month)
+                        {
+                            auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz);
+                            immutable max = maxDay(year, month);
+                            auto expected = SysTime(DateTime(Date(year, month, max), tod), fs, tz);
+
+                            st.day = max;
+                            assert(st == expected, format("[%s] [%s]", st, expected));
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach(tz; testTZs)
+        {
+            foreach(tod; testTODsThrown)
+            {
+                foreach(fs; [testFracSecs[0], testFracSecs[$-1]])
+                {
+                    foreach(year; [testYearsBC[$-3], testYearsBC[$-2],
+                                   testYearsBC[$-2], testYearsAD[0],
+                                   testYearsAD[$-2], testYearsAD[$-1]])
+                    {
+                        foreach(month; EnumMembers!Month)
+                        {
+                            auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz);
+                            immutable max = maxDay(year, month);
+
+                            assertThrown!DateTimeException(st.day = max + 1);
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.day = 27));
+        //static assert(!__traits(compiles, ist.day = 27));
+    }
+
+
+    /++
+        Hours past midnight.
+     +/
+    @property ubyte hour() @safe const nothrow
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        return cast(ubyte)getUnitsFromHNSecs!"hours"(hnsecs);
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(SysTime sysTime, int expected)
+        {
+            assert(sysTime.hour == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), 0);
+        test(SysTime(1, UTC()), 0);
+        test(SysTime(-1, UTC()), 23);
+
+        foreach(tz; testTZs)
+        {
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                foreach(md; testMonthDays)
+                {
+                    foreach(hour; testHours)
+                    {
+                        foreach(minute; testMinSecs)
+                        {
+                            foreach(second; testMinSecs)
+                            {
+                                auto dt = DateTime(Date(year, md.month, md.day),
+                                                   TimeOfDay(hour, minute, second));
+
+                                foreach(fs; testFracSecs)
+                                    test(SysTime(dt, fs, tz), hour);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.hour == 12);
+        //assert(ist.hour == 12);
+    }
+
+
+    /++
+        Hours past midnight.
+
+        Params:
+            hour = The hours to set this $(LREF SysTime)'s hour to.
+
+        Throws:
+            $(LREF DateTimeException) if the given hour are not a valid hour of
+            the day.
+     +/
+    @property void hour(int hour) @safe
+    {
+        enforceValid!"hours"(hour);
+
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
+        immutable daysHNSecs = convert!("days", "hnsecs")(days);
+        immutable negative = hnsecs < 0;
+
+        if(negative)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);
+        hnsecs += convert!("hours", "hnsecs")(hour);
+
+        if(negative)
+            hnsecs -= convert!("hours", "hnsecs")(24);
+
+        adjTime = daysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        foreach(hour; chain(testHours))
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, hour, dt.minute, dt.second),
+                                        st.fracSecs,
+                                        st.timezone);
+                st.hour = hour;
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        auto st = testSysTimesAD[0];
+        assertThrown!DateTimeException(st.hour = -1);
+        assertThrown!DateTimeException(st.hour = 60);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.hour = 27));
+        //static assert(!__traits(compiles, ist.hour = 27));
+    }
+
+
+    /++
+        Minutes past the current hour.
+     +/
+    @property ubyte minute() @safe const nothrow
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);
+
+        return cast(ubyte)getUnitsFromHNSecs!"minutes"(hnsecs);
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(SysTime sysTime, int expected)
+        {
+            assert(sysTime.minute == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), 0);
+        test(SysTime(1, UTC()), 0);
+        test(SysTime(-1, UTC()), 59);
+
+        foreach(tz; testTZs)
+        {
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                foreach(md; testMonthDays)
+                {
+                    foreach(hour; testHours)
+                    {
+                        foreach(minute; testMinSecs)
+                        {
+                            foreach(second; testMinSecs)
+                            {
+                                auto dt = DateTime(Date(year, md.month, md.day),
+                                                   TimeOfDay(hour, minute, second));
+
+                                foreach(fs; testFracSecs)
+                                    test(SysTime(dt, fs, tz), minute);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.minute == 30);
+        //assert(ist.minute == 30);
+    }
+
+
+    /++
+        Minutes past the current hour.
+
+        Params:
+            minute = The minute to set this $(LREF SysTime)'s minute to.
+
+        Throws:
+            $(LREF DateTimeException) if the given minute are not a valid minute
+            of an hour.
+     +/
+    @property void minute(int minute) @safe
+    {
+        enforceValid!"minutes"(minute);
+
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
+        immutable daysHNSecs = convert!("days", "hnsecs")(days);
+        immutable negative = hnsecs < 0;
+
+        if(negative)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+        hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs);
+
+        hnsecs += convert!("hours", "hnsecs")(hour);
+        hnsecs += convert!("minutes", "hnsecs")(minute);
+
+        if(negative)
+            hnsecs -= convert!("hours", "hnsecs")(24);
+
+        adjTime = daysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        foreach(minute; testMinSecs)
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, minute, dt.second),
+                                        st.fracSecs,
+                                        st.timezone);
+                st.minute = minute;
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        auto st = testSysTimesAD[0];
+        assertThrown!DateTimeException(st.minute = -1);
+        assertThrown!DateTimeException(st.minute = 60);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.minute = 27));
+        //static assert(!__traits(compiles, ist.minute = 27));
+    }
+
+
+    /++
+        Seconds past the current minute.
+     +/
+    @property ubyte second() @safe const nothrow
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);
+        hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs);
+
+        return cast(ubyte)getUnitsFromHNSecs!"seconds"(hnsecs);
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(SysTime sysTime, int expected)
+        {
+            assert(sysTime.second == expected,
+                             format("Value given: %s", sysTime));
+        }
+
+        test(SysTime(0, UTC()), 0);
+        test(SysTime(1, UTC()), 0);
+        test(SysTime(-1, UTC()), 59);
+
+        foreach(tz; testTZs)
+        {
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                foreach(md; testMonthDays)
+                {
+                    foreach(hour; testHours)
+                    {
+                        foreach(minute; testMinSecs)
+                        {
+                            foreach(second; testMinSecs)
+                            {
+                                auto dt = DateTime(Date(year, md.month, md.day),
+                                                   TimeOfDay(hour, minute, second));
+
+                                foreach(fs; testFracSecs)
+                                    test(SysTime(dt, fs, tz), second);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.second == 33);
+        //assert(ist.second == 33);
+    }
+
+
+    /++
+        Seconds past the current minute.
+
+        Params:
+            second = The second to set this $(LREF SysTime)'s second to.
+
+        Throws:
+            $(LREF DateTimeException) if the given second are not a valid second
+            of a minute.
+     +/
+    @property void second(int second) @safe
+    {
+        enforceValid!"seconds"(second);
+
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
+        immutable daysHNSecs = convert!("days", "hnsecs")(days);
+        immutable negative = hnsecs < 0;
+
+        if(negative)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+        immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+        hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);
+
+        hnsecs += convert!("hours", "hnsecs")(hour);
+        hnsecs += convert!("minutes", "hnsecs")(minute);
+        hnsecs += convert!("seconds", "hnsecs")(second);
+
+        if(negative)
+            hnsecs -= convert!("hours", "hnsecs")(24);
+
+        adjTime = daysHNSecs + hnsecs;
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        foreach(second; testMinSecs)
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, second),
+                                        st.fracSecs,
+                                        st.timezone);
+                st.second = second;
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        auto st = testSysTimesAD[0];
+        assertThrown!DateTimeException(st.second = -1);
+        assertThrown!DateTimeException(st.second = 60);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.seconds = 27));
+        //static assert(!__traits(compiles, ist.seconds = 27));
+    }
+
+
+    /++
+        Fractional seconds past the second (i.e. the portion of a
+        $(LREF SysTime) which is less than a second).
+     +/
+    @property Duration fracSecs() @safe const nothrow
+    {
+        auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);
+
+        if(hnsecs < 0)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        return dur!"hnsecs"(removeUnitsFromHNSecs!"seconds"(hnsecs));
+    }
+
+    ///
+    unittest
+    {
+        auto dt = DateTime(1982, 4, 1, 20, 59, 22);
+        assert(SysTime(dt, msecs(213)).fracSecs == msecs(213));
+        assert(SysTime(dt, usecs(5202)).fracSecs == usecs(5202));
+        assert(SysTime(dt, hnsecs(1234567)).fracSecs == hnsecs(1234567));
+
+        // SysTime and Duration both have a precision of hnsecs (100 ns),
+        // so nsecs are going to be truncated.
+        assert(SysTime(dt, nsecs(123456789)).fracSecs == nsecs(123456700));
+    }
+
+    unittest
+    {
+        import std.range;
+
+        assert(SysTime(0, UTC()).fracSecs == Duration.zero);
+        assert(SysTime(1, UTC()).fracSecs == hnsecs(1));
+        assert(SysTime(-1, UTC()).fracSecs == hnsecs(9_999_999));
+
+        foreach(tz; testTZs)
+        {
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                foreach(md; testMonthDays)
+                {
+                    foreach(hour; testHours)
+                    {
+                        foreach(minute; testMinSecs)
+                        {
+                            foreach(second; testMinSecs)
+                            {
+                                auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second));
+                                foreach(fs; testFracSecs)
+                                    assert(SysTime(dt, fs, tz).fracSecs == fs);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.fracSecs == Duration.zero);
+        //assert(ist.fracSecs == Duration.zero);
+    }
+
+
+    /++
+        Fractional seconds past the second (i.e. the portion of a
+        $(LREF SysTime) which is less than a second).
+
+        Params:
+            fracSecs = The duration to set this $(LREF SysTime)'s fractional
+                       seconds to.
+
+        Throws:
+            $(LREF DateTimeException) if the given duration is negative or if
+            it's greater than or equal to one second.
+     +/
+    @property void fracSecs(Duration fracSecs) @safe
+    {
+        enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds."));
+        enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second."));
+
+        auto oldHNSecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(oldHNSecs);
+        immutable daysHNSecs = convert!("days", "hnsecs")(days);
+        immutable negative = oldHNSecs < 0;
+
+        if(negative)
+            oldHNSecs += convert!("hours", "hnsecs")(24);
+
+        immutable seconds = splitUnitsFromHNSecs!"seconds"(oldHNSecs);
+        immutable secondsHNSecs = convert!("seconds", "hnsecs")(seconds);
+        auto newHNSecs = fracSecs.total!"hnsecs" + secondsHNSecs;
+
+        if(negative)
+            newHNSecs -= convert!("hours", "hnsecs")(24);
+
+        adjTime = daysHNSecs + newHNSecs;
+    }
+
+    ///
+    unittest
+    {
+        auto st = SysTime(DateTime(1982, 4, 1, 20, 59, 22));
+        assert(st.fracSecs == Duration.zero);
+
+        st.fracSecs = msecs(213);
+        assert(st.fracSecs == msecs(213));
+
+        st.fracSecs = hnsecs(1234567);
+        assert(st.fracSecs == hnsecs(1234567));
+
+        // SysTime has a precision of hnsecs (100 ns), so nsecs are
+        // going to be truncated.
+        st.fracSecs = nsecs(123456789);
+        assert(st.fracSecs == hnsecs(1234567));
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        foreach(fracSec; testFracSecs)
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+                auto expected = SysTime(dt, fracSec, st.timezone);
+                st.fracSecs = fracSec;
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        auto st = testSysTimesAD[0];
+        assertThrown!DateTimeException(st.fracSecs = hnsecs(-1));
+        assertThrown!DateTimeException(st.fracSecs = seconds(1));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.fracSecs = msecs(7)));
+        //static assert(!__traits(compiles, ist.fracSecs = msecs(7)));
+    }
+
+
+    // @@@DEPRECATED_2016-08@@@
+    /++
+        $(RED Deprecated. Please use $(LREF fracSecs) instead of fracSec. It
+              uses a $(CXREF time, Duration) to represent the fractional seconds
+              instead of a $(CXREF time, FracSec). This overload will be removed
+              in August 2016.)
+
+        Fractional seconds past the second.
+     +/
+    deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). It returns a Duration instead of a FracSec, as FracSec is being deprecated.")
+    @property FracSec fracSec() @safe const nothrow
+    {
+        try
+        {
+            auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);
+
+            if(hnsecs < 0)
+                hnsecs += convert!("hours", "hnsecs")(24);
+
+            hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);
+
+            return FracSec.from!"hnsecs"(cast(int)hnsecs);
+        }
+        catch(Exception e)
+            assert(0, "FracSec.from!\"hnsecs\"() threw.");
+    }
+
+    deprecated unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(SysTime sysTime, FracSec expected, size_t line = __LINE__)
+        {
+            if(sysTime.fracSec != expected)
+                throw new AssertError(format("Value given: %s", sysTime.fracSec), __FILE__, line);
+        }
+
+        test(SysTime(0, UTC()), FracSec.from!"hnsecs"(0));
+        test(SysTime(1, UTC()), FracSec.from!"hnsecs"(1));
+        test(SysTime(-1, UTC()), FracSec.from!"hnsecs"(9_999_999));
+
+        foreach(tz; testTZs)
+        {
+            foreach(year; chain(testYearsBC, testYearsAD))
+            {
+                foreach(md; testMonthDays)
+                {
+                    foreach(hour; testHours)
+                    {
+                        foreach(minute; testMinSecs)
+                        {
+                            foreach(second; testMinSecs)
+                            {
+                                auto dt = DateTime(Date(year, md.month, md.day),
+                                                   TimeOfDay(hour, minute, second));
+
+                                foreach(fs; testFracSecs)
+                                    test(SysTime(dt, fs, tz), FracSec.from!"hnsecs"(fs.total!"hnsecs"));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.fracSec == FracSec.zero);
+        //assert(ist.fracSec == FracSec.zero);
+    }
+
+
+    // @@@DEPRECATED_2016-08@@@
+    /++
+        $(RED Deprecated. Please use $(LREF fracSecs) instead of fracSec. It
+              uses a $(CXREF time, Duration) to represent the fractional seconds
+              instead of a $(CXREF time, FracSec). This overload will be removed
+              in August 2016.)
+
+        Fractional seconds past the second.
+
+        Params:
+            fracSec = The fractional seconds to set this $(LREF SysTime)'s
+                      fractional seconds to.
+
+        Throws:
+            $(LREF DateTimeException) if $(D fracSec) is negative.
+     +/
+    deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). It takes a Duration instead of a FracSec, as FracSec is being deprecated.")
+    @property void fracSec(FracSec fracSec) @safe
+    {
+        immutable fracHNSecs = fracSec.hnsecs;
+        enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds."));
+
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
+        immutable daysHNSecs = convert!("days", "hnsecs")(days);
+        immutable negative = hnsecs < 0;
+
+        if(negative)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+        immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+        immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);
+
+        hnsecs = fracHNSecs;
+        hnsecs += convert!("hours", "hnsecs")(hour);
+        hnsecs += convert!("minutes", "hnsecs")(minute);
+        hnsecs += convert!("seconds", "hnsecs")(second);
+
+        if(negative)
+            hnsecs -= convert!("hours", "hnsecs")(24);
+
+        adjTime = daysHNSecs + hnsecs;
+    }
+
+    deprecated unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        foreach(fracSec; testFracSecs)
+        {
+            foreach(st; chain(testSysTimesBC, testSysTimesAD))
+            {
+                auto dt = cast(DateTime)st;
+                auto expected = SysTime(dt, fracSec, st.timezone);
+                st.fracSec = FracSec.from!"hnsecs"(fracSec.total!"hnsecs");
+                assert(st == expected, format("[%s] [%s]", st, expected));
+            }
+        }
+
+        auto st = testSysTimesAD[0];
+        assertThrown!DateTimeException(st.fracSec = FracSec.from!"hnsecs"(-1));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.fracSec = FracSec.from!"msecs"(7)));
+        //static assert(!__traits(compiles, ist.fracSec = FracSec.from!"msecs"(7)));
+    }
+
+
+    /++
+        The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the
+        internal representation of $(LREF SysTime).
+     +/
+    @property long stdTime() @safe const pure nothrow
+    {
+        return _stdTime;
+    }
+
+    unittest
+    {
+        assert(SysTime(0).stdTime == 0);
+        assert(SysTime(1).stdTime == 1);
+        assert(SysTime(-1).stdTime == -1);
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC()).stdTime == 330_000_502L);
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()).stdTime == 621_355_968_000_000_000L);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.stdTime > 0);
+        //assert(ist.stdTime > 0);
+    }
+
+
+    /++
+        The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the
+        internal representation of $(LREF SysTime).
+
+        Params:
+            stdTime = The number of hnsecs since January 1st, 1 A.D. UTC.
+     +/
+    @property void stdTime(long stdTime) @safe pure nothrow
+    {
+        _stdTime = stdTime;
+    }
+
+    unittest
+    {
+        static void test(long stdTime, in SysTime expected, size_t line = __LINE__)
+        {
+            auto st = SysTime(0, UTC());
+            st.stdTime = stdTime;
+            assert(st == expected);
+        }
+
+        test(0, SysTime(Date(1, 1, 1), UTC()));
+        test(1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()));
+        test(-1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()));
+        test(330_000_502L, SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC()));
+        test(621_355_968_000_000_000L, SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.stdTime = 27));
+        //static assert(!__traits(compiles, ist.stdTime = 27));
+    }
+
+
+    /++
+        The current time zone of this $(LREF SysTime). Its internal time is always
+        kept in UTC, so there are no conversion issues between time zones due to
+        DST. Functions which return all or part of the time - such as hours -
+        adjust the time to this $(LREF SysTime)'s time zone before returning.
+      +/
+    @property immutable(TimeZone) timezone() @safe const pure nothrow
+    {
+        return _timezone;
+    }
+
+
+    /++
+        The current time zone of this $(LREF SysTime). It's internal time is always
+        kept in UTC, so there are no conversion issues between time zones due to
+        DST. Functions which return all or part of the time - such as hours -
+        adjust the time to this $(LREF SysTime)'s time zone before returning.
+
+        Params:
+            timezone = The $(LREF2 .TimeZone, TimeZone) to set this $(LREF SysTime)'s time zone to.
+      +/
+    @property void timezone(immutable TimeZone timezone) @safe pure nothrow
+    {
+        if(timezone is null)
+            _timezone = LocalTime();
+        else
+            _timezone = timezone;
+    }
+
+
+    /++
+        Returns whether DST is in effect for this $(LREF SysTime).
+      +/
+    @property bool dstInEffect() @safe const nothrow
+    {
+        return _timezone.dstInEffect(_stdTime);
+        //This function's unit testing is done in the time zone classes.
+    }
+
+
+    /++
+        Returns what the offset from UTC is for this $(LREF SysTime).
+        It includes the DST offset in effect at that time (if any).
+      +/
+    @property Duration utcOffset() @safe const nothrow
+    {
+        return _timezone.utcOffsetAt(_stdTime);
+    }
+
+
+    /++
+        Returns a $(LREF SysTime) with the same std time as this one, but with
+        $(LREF LocalTime) as its time zone.
+      +/
+    SysTime toLocalTime() @safe const pure nothrow
+    {
+        return SysTime(_stdTime, LocalTime());
+    }
+
+    unittest
+    {
+        {
+            auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
+            assert(sysTime == sysTime.toLocalTime());
+            assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime);
+            assert(sysTime.toLocalTime().timezone is LocalTime());
+            assert(sysTime.toLocalTime().timezone is sysTime.timezone);
+            assert(sysTime.toLocalTime().timezone !is UTC());
+        }
+
+        {
+            auto stz = new immutable SimpleTimeZone(dur!"minutes"(-3 * 60));
+            auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27), stz);
+            assert(sysTime == sysTime.toLocalTime());
+            assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime);
+            assert(sysTime.toLocalTime().timezone is LocalTime());
+            assert(sysTime.toLocalTime().timezone !is UTC());
+            assert(sysTime.toLocalTime().timezone !is stz);
+        }
+    }
+
+
+    /++
+        Returns a $(LREF SysTime) with the same std time as this one, but with
+        $(D UTC) as its time zone.
+      +/
+    SysTime toUTC() @safe const pure nothrow
+    {
+        return SysTime(_stdTime, UTC());
+    }
+
+    unittest
+    {
+        auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
+        assert(sysTime == sysTime.toUTC());
+        assert(sysTime._stdTime == sysTime.toUTC()._stdTime);
+        assert(sysTime.toUTC().timezone is UTC());
+        assert(sysTime.toUTC().timezone !is LocalTime());
+        assert(sysTime.toUTC().timezone !is sysTime.timezone);
+    }
+
+
+    /++
+        Returns a $(LREF SysTime) with the same std time as this one, but with
+        given time zone as its time zone.
+      +/
+    SysTime toOtherTZ(immutable TimeZone tz) @safe const pure nothrow
+    {
+        if(tz is null)
+            return SysTime(_stdTime, LocalTime());
+        else
+            return SysTime(_stdTime, tz);
+    }
+
+    unittest
+    {
+        auto stz = new immutable SimpleTimeZone(dur!"minutes"(11 * 60));
+        auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
+        assert(sysTime == sysTime.toOtherTZ(stz));
+        assert(sysTime._stdTime == sysTime.toOtherTZ(stz)._stdTime);
+        assert(sysTime.toOtherTZ(stz).timezone is stz);
+        assert(sysTime.toOtherTZ(stz).timezone !is LocalTime());
+        assert(sysTime.toOtherTZ(stz).timezone !is UTC());
+    }
+
+
+    /++
+        Converts this $(LREF SysTime) to unix time (i.e. seconds from midnight,
+        January 1st, 1970 in UTC).
+
+        The C standard does not specify the representation of time_t, so it is
+        implementation defined. On POSIX systems, unix time is equivalent to
+        time_t, but that's not necessarily true on other systems (e.g. it is
+        not true for the Digital Mars C runtime). So, be careful when using unix
+        time with C functions on non-POSIX systems.
+
+        By default, the return type is time_t (which is normally an alias for
+        int on 32-bit systems and long on 64-bit systems), but if a different
+        size is required than either int or long can be passed as a template
+        argument to get the desired size.
+
+        If the return type is int, and the result can't fit in an int, then the
+        closest value that can be held in 32 bits will be used (so $(D int.max)
+        if it goes over and $(D int.min) if it goes under). However, no attempt
+        is made to deal with integer overflow if the return type is long.
+
+        Params:
+            T = The return type (int or long). It defaults to time_t, which is
+                normally 32 bits on a 32-bit system and 64 bits on a 64-bit
+                system.
+
+        Returns:
+            A signed integer representing the unix time which is equivalent to
+            this SysTime.
+      +/
+    T toUnixTime(T = time_t)() @safe const pure nothrow
+        if(is(T == int) || is(T == long))
+    {
+        return stdTimeToUnixTime!T(_stdTime);
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0);
+
+        auto pst = new immutable SimpleTimeZone(hours(-8));
+        assert(SysTime(DateTime(1970, 1, 1), pst).toUnixTime() == 28800);
+
+        auto utc = SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC());
+        assert(utc.toUnixTime() == 1_198_311_285);
+
+        auto ca = SysTime(DateTime(2007, 12, 22, 8, 14, 45), pst);
+        assert(ca.toUnixTime() == 1_198_340_085);
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0);
+        import std.meta : AliasSeq;
+        foreach(units; AliasSeq!("hnsecs", "usecs", "msecs"))
+            assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), dur!units(1), UTC()).toUnixTime() == 0);
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toUnixTime() == 1);
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toUnixTime() == 0);
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toUnixTime() == 0);
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toUnixTime() == 0);
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toUnixTime() == -1);
+    }
+
+
+    /++
+        Converts from unix time (i.e. seconds from midnight, January 1st, 1970
+        in UTC) to a $(LREF SysTime).
+
+        The C standard does not specify the representation of time_t, so it is
+        implementation defined. On POSIX systems, unix time is equivalent to
+        time_t, but that's not necessarily true on other systems (e.g. it is
+        not true for the Digital Mars C runtime). So, be careful when using unix
+        time with C functions on non-POSIX systems.
+
+        Params:
+            unixTime = Seconds from midnight, January 1st, 1970 in UTC.
+            tz = The time zone for the SysTime that's returned.
+      +/
+    static SysTime fromUnixTime(long unixTime, immutable TimeZone tz = LocalTime()) @safe pure nothrow
+    {
+        return SysTime(unixTimeToStdTime(unixTime), tz);
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime.fromUnixTime(0) ==
+               SysTime(DateTime(1970, 1, 1), UTC()));
+
+        auto pst = new immutable SimpleTimeZone(hours(-8));
+        assert(SysTime.fromUnixTime(28800) ==
+               SysTime(DateTime(1970, 1, 1), pst));
+
+        auto st1 = SysTime.fromUnixTime(1_198_311_285, UTC());
+        assert(st1 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC()));
+        assert(st1.timezone is UTC());
+        assert(st1 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst));
+
+        auto st2 = SysTime.fromUnixTime(1_198_311_285, pst);
+        assert(st2 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC()));
+        assert(st2.timezone is pst);
+        assert(st2 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst));
+    }
+
+    unittest
+    {
+        assert(SysTime.fromUnixTime(0) == SysTime(DateTime(1970, 1, 1), UTC()));
+        assert(SysTime.fromUnixTime(1) == SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()));
+        assert(SysTime.fromUnixTime(-1) == SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()));
+
+        auto st = SysTime.fromUnixTime(0);
+        auto dt = cast(DateTime)st;
+        assert(dt <= DateTime(1970, 2, 1) && dt >= DateTime(1969, 12, 31));
+        assert(st.timezone is LocalTime());
+
+        auto aest = new immutable SimpleTimeZone(hours(10));
+        assert(SysTime.fromUnixTime(-36000) == SysTime(DateTime(1970, 1, 1), aest));
+    }
+
+
+    /++
+        Returns a $(D timeval) which represents this $(LREF SysTime).
+
+        Note that like all conversions in std.datetime, this is a truncating
+        conversion.
+
+        If $(D timeval.tv_sec) is int, and the result can't fit in an int, then
+        the closest value that can be held in 32 bits will be used for
+        $(D tv_sec). (so $(D int.max) if it goes over and $(D int.min) if it
+        goes under).
+      +/
+    timeval toTimeVal() @safe const pure nothrow
+    {
+        immutable tv_sec = toUnixTime!(typeof(timeval.tv_sec))();
+        immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L);
+        immutable tv_usec = cast(typeof(timeval.tv_usec))convert!("hnsecs", "usecs")(fracHNSecs);
+        return timeval(tv_sec, tv_usec);
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(1970, 1, 1), UTC()).toTimeVal() == timeval(0, 0));
+        assert(SysTime(DateTime(1970, 1, 1), hnsecs(9), UTC()).toTimeVal() == timeval(0, 0));
+        assert(SysTime(DateTime(1970, 1, 1), hnsecs(10), UTC()).toTimeVal() == timeval(0, 1));
+        assert(SysTime(DateTime(1970, 1, 1), usecs(7), UTC()).toTimeVal() == timeval(0, 7));
+
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toTimeVal() == timeval(1, 0));
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(9), UTC()).toTimeVal() == timeval(1, 0));
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(10), UTC()).toTimeVal() == timeval(1, 1));
+        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), usecs(7), UTC()).toTimeVal() == timeval(1, 7));
+
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toTimeVal() == timeval(0, 0));
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_990), UTC()).toTimeVal() == timeval(0, -1));
+
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toTimeVal() == timeval(0, -1));
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999), UTC()).toTimeVal() == timeval(0, -999_001));
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toTimeVal() == timeval(0, -1000));
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toTimeVal() == timeval(-1, 0));
+        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeVal() == timeval(-1, -999_983));
+    }
+
+
+    /++
+        Returns a $(D tm) which represents this $(LREF SysTime).
+      +/
+    tm toTM() @safe const nothrow
+    {
+        auto dateTime = cast(DateTime)this;
+        tm timeInfo;
+
+        timeInfo.tm_sec = dateTime.second;
+        timeInfo.tm_min = dateTime.minute;
+        timeInfo.tm_hour = dateTime.hour;
+        timeInfo.tm_mday = dateTime.day;
+        timeInfo.tm_mon = dateTime.month - 1;
+        timeInfo.tm_year = dateTime.year - 1900;
+        timeInfo.tm_wday = dateTime.dayOfWeek;
+        timeInfo.tm_yday = dateTime.dayOfYear - 1;
+        timeInfo.tm_isdst = _timezone.dstInEffect(_stdTime);
+
+        version(Posix)
+        {
+            import std.utf : toUTFz;
+            timeInfo.tm_gmtoff = cast(int)convert!("hnsecs", "seconds")(adjTime - _stdTime);
+            auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName);
+            timeInfo.tm_zone = zone.toUTFz!(char*)();
+        }
+
+        return timeInfo;
+    }
+
+    unittest
+    {
+        import std.conv : to;
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+            setTZEnvVar("America/Los_Angeles");
+        }
+
+        {
+            auto timeInfo = SysTime(DateTime(1970, 1, 1)).toTM();
+
+            assert(timeInfo.tm_sec == 0);
+            assert(timeInfo.tm_min == 0);
+            assert(timeInfo.tm_hour == 0);
+            assert(timeInfo.tm_mday == 1);
+            assert(timeInfo.tm_mon == 0);
+            assert(timeInfo.tm_year == 70);
+            assert(timeInfo.tm_wday == 4);
+            assert(timeInfo.tm_yday == 0);
+
+            version(Posix)
+                assert(timeInfo.tm_isdst == 0);
+            else version(Windows)
+                assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1);
+
+            version(Posix)
+            {
+                assert(timeInfo.tm_gmtoff == -8 * 60 * 60);
+                assert(to!string(timeInfo.tm_zone) == "PST");
+            }
+        }
+
+        {
+            auto timeInfo = SysTime(DateTime(2010, 7, 4, 12, 15, 7), hnsecs(15)).toTM();
+
+            assert(timeInfo.tm_sec == 7);
+            assert(timeInfo.tm_min == 15);
+            assert(timeInfo.tm_hour == 12);
+            assert(timeInfo.tm_mday == 4);
+            assert(timeInfo.tm_mon == 6);
+            assert(timeInfo.tm_year == 110);
+            assert(timeInfo.tm_wday == 0);
+            assert(timeInfo.tm_yday == 184);
+
+            version(Posix)
+                assert(timeInfo.tm_isdst == 1);
+            else version(Windows)
+                assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1);
+
+            version(Posix)
+            {
+                assert(timeInfo.tm_gmtoff == -7 * 60 * 60);
+                assert(to!string(timeInfo.tm_zone) == "PDT");
+            }
+        }
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF SysTime). A
+        negative number will subtract.
+
+        Note that if day overflow is allowed, and the date with the adjusted
+        year/month overflows the number of days in the new month, then the month
+        will be incremented by one, and the day set to the number of days
+        overflowed. (e.g. if the day were 31 and the new month were June, then
+        the month would be incremented to July, and the new day would be 1). If
+        day overflow is not allowed, then the day will be set to the last valid
+        day in the month (e.g. June 31st would become June 30th).
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF SysTime).
+            allowOverflow = Whether the days should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref SysTime add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
+        if(units == "years" ||
+           units == "months")
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.add!units(value, allowOverflow);
+        days = date.dayOfGregorianCal - 1;
+
+        if(days < 0)
+        {
+            hnsecs -= convert!("hours", "hnsecs")(24);
+            ++days;
+        }
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
+
+        adjTime = newDaysHNSecs + hnsecs;
+
+        return this;
+    }
+
+    unittest
+    {
+        auto st1 = SysTime(DateTime(2010, 1, 1, 12, 30, 33));
+        st1.add!"months"(11);
+        assert(st1 == SysTime(DateTime(2010, 12, 1, 12, 30, 33)));
+
+        auto st2 = SysTime(DateTime(2010, 1, 1, 12, 30, 33));
+        st2.add!"months"(-11);
+        assert(st2 == SysTime(DateTime(2009, 2, 1, 12, 30, 33)));
+
+        auto st3 = SysTime(DateTime(2000, 2, 29, 12, 30, 33));
+        st3.add!"years"(1);
+        assert(st3 == SysTime(DateTime(2001, 3, 1, 12, 30, 33)));
+
+        auto st4 = SysTime(DateTime(2000, 2, 29, 12, 30, 33));
+        st4.add!"years"(1, AllowDayOverflow.no);
+        assert(st4 == SysTime(DateTime(2001, 2, 28, 12, 30, 33)));
+    }
+
+    //Test add!"years"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"years"(7);
+            assert(sysTime == SysTime(Date(2006, 7, 6)));
+            sysTime.add!"years"(-9);
+            assert(sysTime == SysTime(Date(1997, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(Date(2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(Date(1999, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234));
+            sysTime.add!"years"(7);
+            assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234)));
+            sysTime.add!"years"(-9);
+            assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(1999, 3, 1, 0, 7, 2), usecs(1207)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"years"(-7);
+            assert(sysTime == SysTime(Date(-2006, 7, 6)));
+            sysTime.add!"years"(9);
+            assert(sysTime == SysTime(Date(-1997, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(Date(-2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(Date(-1999, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234));
+            sysTime.add!"years"(-7);
+            assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234)));
+            sysTime.add!"years"(9);
+            assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(-1999, 3, 1, 3, 3, 3), hnsecs(3)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(4, 7, 6));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(Date(-1, 7, 6)));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 7, 6));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(Date(1, 7, 6)));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 7, 6));
+            sysTime.add!"years"(-8);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+            sysTime.add!"years"(8);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 7, 6));
+            sysTime.add!"years"(8);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+            sysTime.add!"years"(-8);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 2, 29));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(Date(1, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 2, 29));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(Date(-1, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"years"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"years"(-1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329)));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(DateTime(1, 3, 1, 5, 5, 5), msecs(555)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(DateTime(-1, 3, 1, 5, 5, 5), msecs(555)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(-5).add!"years"(7);
+            assert(sysTime == SysTime(DateTime(6, 3, 1, 5, 5, 5), msecs(555)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.add!"years"(4)));
+        //static assert(!__traits(compiles, ist.add!"years"(4)));
+    }
+
+    //Test add!"years"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"years"(7, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2006, 7, 6)));
+            sysTime.add!"years"(-9, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234));
+            sysTime.add!"years"(7, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234)));
+            sysTime.add!"years"(-9, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"years"(-7, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2006, 7, 6)));
+            sysTime.add!"years"(9, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234));
+            sysTime.add!"years"(-7, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234)));
+            sysTime.add!"years"(9, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(4, 7, 6));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1, 7, 6)));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 7, 6));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1, 7, 6)));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 7, 6));
+            sysTime.add!"years"(-8, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+            sysTime.add!"years"(8, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 7, 6));
+            sysTime.add!"years"(8, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 7, 6)));
+            sysTime.add!"years"(-8, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 2, 29));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 2, 29));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"years"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"years"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
+            sysTime.add!"years"(-5);
+            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
+            sysTime.add!"years"(5);
+            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329)));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 2, 28, 5, 5, 5), msecs(555)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(-5, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1, 2, 28, 5, 5, 5), msecs(555)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
+            sysTime.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(6, 2, 28, 5, 5, 5), msecs(555)));
+        }
+    }
+
+    //Test add!"months"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(3);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.add!"months"(-4);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(6);
+            assert(sysTime == SysTime(Date(2000, 1, 6)));
+            sysTime.add!"months"(-6);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(27);
+            assert(sysTime == SysTime(Date(2001, 10, 6)));
+            sysTime.add!"months"(-28);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 7, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(Date(1999, 5, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.add!"months"(12);
+            assert(sysTime == SysTime(Date(2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.add!"months"(12);
+            assert(sysTime == SysTime(Date(2001, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 31));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 8, 31)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 10, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 8, 31));
+            sysTime.add!"months"(13);
+            assert(sysTime == SysTime(Date(1999, 10, 1)));
+            sysTime.add!"months"(-13);
+            assert(sysTime == SysTime(Date(1998, 9, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.add!"months"(13);
+            assert(sysTime == SysTime(Date(1999, 1, 31)));
+            sysTime.add!"months"(-13);
+            assert(sysTime == SysTime(Date(1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(1999, 3, 3)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(1998, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(2000, 3, 2)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(1999, 1, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(2001, 3, 3)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(2000, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.add!"months"(3);
+            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.add!"months"(-4);
+            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(DateTime(2000, 3, 2, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(DateTime(1999, 1, 2, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(DateTime(2001, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(DateTime(2000, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(3);
+            assert(sysTime == SysTime(Date(-1999, 10, 6)));
+            sysTime.add!"months"(-4);
+            assert(sysTime == SysTime(Date(-1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(6);
+            assert(sysTime == SysTime(Date(-1998, 1, 6)));
+            sysTime.add!"months"(-6);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(-27);
+            assert(sysTime == SysTime(Date(-2001, 4, 6)));
+            sysTime.add!"months"(28);
+            assert(sysTime == SysTime(Date(-1999, 8, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 7, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(Date(-1999, 5, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.add!"months"(-12);
+            assert(sysTime == SysTime(Date(-2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.add!"months"(-12);
+            assert(sysTime == SysTime(Date(-2001, 3, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 31));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 8, 31)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 10, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1998, 8, 31));
+            sysTime.add!"months"(13);
+            assert(sysTime == SysTime(Date(-1997, 10, 1)));
+            sysTime.add!"months"(-13);
+            assert(sysTime == SysTime(Date(-1998, 9, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.add!"months"(13);
+            assert(sysTime == SysTime(Date(-1995, 1, 31)));
+            sysTime.add!"months"(-13);
+            assert(sysTime == SysTime(Date(-1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(-1995, 3, 3)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(-1996, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2002, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(-2000, 3, 2)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(-2001, 1, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2001, 12, 31));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(Date(-1999, 3, 3)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(Date(-2000, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.add!"months"(3);
+            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.add!"months"(-4);
+            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(DateTime(-2000, 3, 2, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(DateTime(-2001, 1, 2, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14);
+            assert(sysTime == SysTime(DateTime(-1999, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14);
+            assert(sysTime == SysTime(DateTime(-2000, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(1, 1, 1));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(Date(0, 12, 1)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(Date(1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 1, 1));
+            sysTime.add!"months"(-48);
+            assert(sysTime == SysTime(Date(0, 1, 1)));
+            sysTime.add!"months"(48);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.add!"months"(-49);
+            assert(sysTime == SysTime(Date(0, 3, 2)));
+            sysTime.add!"months"(49);
+            assert(sysTime == SysTime(Date(4, 4, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.add!"months"(-85);
+            assert(sysTime == SysTime(Date(-3, 3, 3)));
+            sysTime.add!"months"(85);
+            assert(sysTime == SysTime(Date(4, 4, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
+            sysTime.add!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17)));
+            sysTime.add!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(-85);
+            assert(sysTime == SysTime(DateTime(-3, 3, 3, 12, 11, 10), msecs(9)));
+            sysTime.add!"months"(85);
+            assert(sysTime == SysTime(DateTime(4, 4, 3, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(85);
+            assert(sysTime == SysTime(DateTime(4, 5, 1, 12, 11, 10), msecs(9)));
+            sysTime.add!"months"(-85);
+            assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(85).add!"months"(-83);
+            assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.add!"months"(4)));
+        //static assert(!__traits(compiles, ist.add!"months"(4)));
+    }
+
+    //Test add!"months"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.add!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2000, 1, 6)));
+            sysTime.add!"months"(-6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.add!"months"(27, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2001, 10, 6)));
+            sysTime.add!"months"(-28, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 4, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.add!"months"(12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.add!"months"(12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2001, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 31));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 8, 31)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 9, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 8, 31));
+            sysTime.add!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 9, 30)));
+            sysTime.add!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 8, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.add!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 1, 31)));
+            sysTime.add!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2000, 2, 29)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 12, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2001, 2, 28)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.add!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.add!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(2000, 2, 29, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1998, 12, 29, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(2001, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 10, 6)));
+            sysTime.add!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1998, 1, 6)));
+            sysTime.add!"months"(-6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.add!"months"(-27, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2001, 4, 6)));
+            sysTime.add!"months"(28, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 8, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 4, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.add!"months"(-12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2000, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.add!"months"(-12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2001, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 31));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 8, 31)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 9, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1998, 8, 31));
+            sysTime.add!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 9, 30)));
+            sysTime.add!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1998, 8, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.add!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1995, 1, 31)));
+            sysTime.add!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1995, 2, 28)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2002, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2000, 2, 29)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2002, 12, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2001, 12, 31));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 2, 28)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2001, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.add!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.add!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2000, 2, 29, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2002, 12, 29, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.add!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.add!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(1, 1, 1));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(0, 12, 1)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 1, 1));
+            sysTime.add!"months"(-48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(0, 1, 1)));
+            sysTime.add!"months"(48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.add!"months"(-49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(0, 2, 29)));
+            sysTime.add!"months"(49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 3, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.add!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-3, 2, 28)));
+            sysTime.add!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 3, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
+            sysTime.add!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17)));
+            sysTime.add!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 2, 28, 12, 11, 10), msecs(9)));
+            sysTime.add!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(4, 3, 28, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(4, 4, 30, 12, 11, 10), msecs(9)));
+            sysTime.add!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9)));
+        }
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF SysTime). A
+        negative number will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. Rolling a $(LREF SysTime) 12 months
+        gets the exact same $(LREF SysTime). However, the days can still be affected
+        due to the differing number of days in each month.
+
+        Because there are no units larger than years, there is no difference
+        between adding and rolling years.
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF SysTime).
+            allowOverflow = Whether the days should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
+        if(units == "years")
+    {
+        return add!"years"(value, allowOverflow);
+    }
+
+    ///
+    unittest
+    {
+        auto st1 = SysTime(DateTime(2010, 1, 1, 12, 33, 33));
+        st1.roll!"months"(1);
+        assert(st1 == SysTime(DateTime(2010, 2, 1, 12, 33, 33)));
+
+        auto st2 = SysTime(DateTime(2010, 1, 1, 12, 33, 33));
+        st2.roll!"months"(-1);
+        assert(st2 == SysTime(DateTime(2010, 12, 1, 12, 33, 33)));
+
+        auto st3 = SysTime(DateTime(1999, 1, 29, 12, 33, 33));
+        st3.roll!"months"(1);
+        assert(st3 == SysTime(DateTime(1999, 3, 1, 12, 33, 33)));
+
+        auto st4 = SysTime(DateTime(1999, 1, 29, 12, 33, 33));
+        st4.roll!"months"(1, AllowDayOverflow.no);
+        assert(st4 == SysTime(DateTime(1999, 2, 28, 12, 33, 33)));
+
+        auto st5 = SysTime(DateTime(2000, 2, 29, 12, 30, 33));
+        st5.roll!"years"(1);
+        assert(st5 == SysTime(DateTime(2001, 3, 1, 12, 30, 33)));
+
+        auto st6 = SysTime(DateTime(2000, 2, 29, 12, 30, 33));
+        st6.roll!"years"(1, AllowDayOverflow.no);
+        assert(st6 == SysTime(DateTime(2001, 2, 28, 12, 30, 33)));
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        st.roll!"years"(4);
+        static assert(!__traits(compiles, cst.roll!"years"(4)));
+        //static assert(!__traits(compiles, ist.roll!"years"(4)));
+    }
+
+
+    //Shares documentation with "years" overload.
+    ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
+        if(units == "months")
+    {
+        auto hnsecs = adjTime;
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --days;
+        }
+
+        auto date = Date(cast(int)days);
+        date.roll!"months"(value, allowOverflow);
+        days = date.dayOfGregorianCal - 1;
+
+        if(days < 0)
+        {
+            hnsecs -= convert!("hours", "hnsecs")(24);
+            ++days;
+        }
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
+        adjTime = newDaysHNSecs + hnsecs;
+        return this;
+    }
+
+    //Test roll!"months"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(3);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.roll!"months"(-4);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(6);
+            assert(sysTime == SysTime(Date(1999, 1, 6)));
+            sysTime.roll!"months"(-6);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(27);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.roll!"months"(-28);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 7, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(Date(1999, 5, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.roll!"months"(12);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.roll!"months"(12);
+            assert(sysTime == SysTime(Date(2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 31));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 8, 31)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(1999, 10, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 8, 31));
+            sysTime.roll!"months"(13);
+            assert(sysTime == SysTime(Date(1998, 10, 1)));
+            sysTime.roll!"months"(-13);
+            assert(sysTime == SysTime(Date(1998, 9, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.roll!"months"(13);
+            assert(sysTime == SysTime(Date(1997, 1, 31)));
+            sysTime.roll!"months"(-13);
+            assert(sysTime == SysTime(Date(1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(1997, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(1997, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(1998, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(1998, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(1999, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(1999, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.roll!"months"(3);
+            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.roll!"months"(-4);
+            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(DateTime(1998, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(DateTime(1998, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(DateTime(1999, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(DateTime(1999, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(3);
+            assert(sysTime == SysTime(Date(-1999, 10, 6)));
+            sysTime.roll!"months"(-4);
+            assert(sysTime == SysTime(Date(-1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(6);
+            assert(sysTime == SysTime(Date(-1999, 1, 6)));
+            sysTime.roll!"months"(-6);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(-27);
+            assert(sysTime == SysTime(Date(-1999, 4, 6)));
+            sysTime.roll!"months"(28);
+            assert(sysTime == SysTime(Date(-1999, 8, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 7, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(Date(-1999, 5, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.roll!"months"(-12);
+            assert(sysTime == SysTime(Date(-1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.roll!"months"(-12);
+            assert(sysTime == SysTime(Date(-2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 31));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 8, 31)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(-1999, 10, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1998, 8, 31));
+            sysTime.roll!"months"(13);
+            assert(sysTime == SysTime(Date(-1998, 10, 1)));
+            sysTime.roll!"months"(-13);
+            assert(sysTime == SysTime(Date(-1998, 9, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.roll!"months"(13);
+            assert(sysTime == SysTime(Date(-1997, 1, 31)));
+            sysTime.roll!"months"(-13);
+            assert(sysTime == SysTime(Date(-1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(-1997, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(-1997, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2002, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(-2002, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(-2002, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2001, 12, 31));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(Date(-2001, 3, 3)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(Date(-2001, 1, 3)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), hnsecs(5007));
+            sysTime.roll!"months"(3);
+            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), hnsecs(5007)));
+            sysTime.roll!"months"(-4);
+            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), hnsecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(DateTime(-2002, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(DateTime(-2002, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14);
+            assert(sysTime == SysTime(DateTime(-2001, 3, 3, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14);
+            assert(sysTime == SysTime(DateTime(-2001, 1, 3, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(1, 1, 1));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(Date(1, 12, 1)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 1, 1));
+            sysTime.roll!"months"(-48);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+            sysTime.roll!"months"(48);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.roll!"months"(-49);
+            assert(sysTime == SysTime(Date(4, 3, 2)));
+            sysTime.roll!"months"(49);
+            assert(sysTime == SysTime(Date(4, 4, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.roll!"months"(-85);
+            assert(sysTime == SysTime(Date(4, 3, 2)));
+            sysTime.roll!"months"(85);
+            assert(sysTime == SysTime(Date(4, 4, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1, 1, 1));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(Date(-1, 12, 1)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(Date(-1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 1, 1));
+            sysTime.roll!"months"(-48);
+            assert(sysTime == SysTime(Date(-4, 1, 1)));
+            sysTime.roll!"months"(48);
+            assert(sysTime == SysTime(Date(-4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 3, 31));
+            sysTime.roll!"months"(-49);
+            assert(sysTime == SysTime(Date(-4, 3, 2)));
+            sysTime.roll!"months"(49);
+            assert(sysTime == SysTime(Date(-4, 4, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 3, 31));
+            sysTime.roll!"months"(-85);
+            assert(sysTime == SysTime(Date(-4, 3, 2)));
+            sysTime.roll!"months"(85);
+            assert(sysTime == SysTime(Date(-4, 4, 2)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
+            sysTime.roll!"months"(-1);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17)));
+            sysTime.roll!"months"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(-85);
+            assert(sysTime == SysTime(DateTime(4, 3, 2, 12, 11, 10), msecs(9)));
+            sysTime.roll!"months"(85);
+            assert(sysTime == SysTime(DateTime(4, 4, 2, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(85);
+            assert(sysTime == SysTime(DateTime(-3, 5, 1, 12, 11, 10), msecs(9)));
+            sysTime.roll!"months"(-85);
+            assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(85).roll!"months"(-83);
+            assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"months"(4)));
+        //static assert(!__traits(compiles, ist.roll!"months"(4)));
+    }
+
+    //Test roll!"months"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.roll!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 1, 6)));
+            sysTime.roll!"months"(-6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"months"(27, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 10, 6)));
+            sysTime.roll!"months"(-28, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 5, 31));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 4, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.roll!"months"(12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 29));
+            sysTime.roll!"months"(12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 31));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 8, 31)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 9, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 8, 31));
+            sysTime.roll!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 9, 30)));
+            sysTime.roll!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 8, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.roll!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 1, 31)));
+            sysTime.roll!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1997, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1997, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1998, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1998, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1999, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.roll!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.roll!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1998, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1998, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 10, 6)));
+            sysTime.roll!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 6, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 1, 6)));
+            sysTime.roll!"months"(-6, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"months"(-27, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 4, 6)));
+            sysTime.roll!"months"(28, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 8, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 5, 31));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 4, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.roll!"months"(-12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 29));
+            sysTime.roll!"months"(-12, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 31));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 8, 31)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1999, 9, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1998, 8, 31));
+            sysTime.roll!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1998, 9, 30)));
+            sysTime.roll!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1998, 8, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.roll!"months"(13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 1, 31)));
+            sysTime.roll!"months"(-13, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 12, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1997, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1997, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2002, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2002, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2002, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2001, 12, 31));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2001, 2, 28)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-2001, 12, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
+            sysTime.roll!"months"(3, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
+            sysTime.roll!"months"(-4, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2002, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2002, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
+            sysTime.roll!"months"(14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2001, 2, 28, 7, 7, 7), hnsecs(422202)));
+            sysTime.roll!"months"(-14, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202)));
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(1, 1, 1));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1, 12, 1)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 1, 1));
+            sysTime.roll!"months"(-48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+            sysTime.roll!"months"(48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.roll!"months"(-49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 2, 29)));
+            sysTime.roll!"months"(49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 3, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(4, 3, 31));
+            sysTime.roll!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 2, 29)));
+            sysTime.roll!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(4, 3, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1, 1, 1));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1, 12, 1)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-1, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 1, 1));
+            sysTime.roll!"months"(-48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 1, 1)));
+            sysTime.roll!"months"(48, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 3, 31));
+            sysTime.roll!"months"(-49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 2, 29)));
+            sysTime.roll!"months"(49, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 3, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-4, 3, 31));
+            sysTime.roll!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 2, 29)));
+            sysTime.roll!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(Date(-4, 3, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
+            sysTime.roll!"months"(-1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17)));
+            sysTime.roll!"months"(1, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(4, 2, 29, 12, 11, 10), msecs(9)));
+            sysTime.roll!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(4, 3, 29, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 4, 30, 12, 11, 10), msecs(9)));
+            sysTime.roll!"months"(-85, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
+            sysTime.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no);
+            assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9)));
+        }
+    }
+
+
+    /++
+        Adds the given number of units to this $(LREF SysTime). A negative number
+        will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. For instance, rolling a $(LREF SysTime) one
+        year's worth of days gets the exact same $(LREF SysTime).
+
+        Accepted units are $(D "days"), $(D "minutes"), $(D "hours"),
+        $(D "minutes"), $(D "seconds"), $(D "msecs"), $(D "usecs"), and
+        $(D "hnsecs").
+
+        Note that when rolling msecs, usecs or hnsecs, they all add up to a
+        second. So, for example, rolling 1000 msecs is exactly the same as
+        rolling 100,000 usecs.
+
+        Params:
+            units = The units to add.
+            value = The number of $(D_PARAM units) to add to this $(LREF SysTime).
+      +/
+    ref SysTime roll(string units)(long value) @safe nothrow
+        if(units == "days")
+    {
+        auto hnsecs = adjTime;
+        auto gdays = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("hours", "hnsecs")(24);
+            --gdays;
+        }
+
+        auto date = Date(cast(int)gdays);
+        date.roll!"days"(value);
+        gdays = date.dayOfGregorianCal - 1;
+
+        if(gdays < 0)
+        {
+            hnsecs -= convert!("hours", "hnsecs")(24);
+            ++gdays;
+        }
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(gdays);
+        adjTime = newDaysHNSecs + hnsecs;
+        return  this;
+    }
+
+    ///
+    unittest
+    {
+        auto st1 = SysTime(DateTime(2010, 1, 1, 11, 23, 12));
+        st1.roll!"days"(1);
+        assert(st1 == SysTime(DateTime(2010, 1, 2, 11, 23, 12)));
+        st1.roll!"days"(365);
+        assert(st1 == SysTime(DateTime(2010, 1, 26, 11, 23, 12)));
+        st1.roll!"days"(-32);
+        assert(st1 == SysTime(DateTime(2010, 1, 25, 11, 23, 12)));
+
+        auto st2 = SysTime(DateTime(2010, 7, 4, 12, 0, 0));
+        st2.roll!"hours"(1);
+        assert(st2 == SysTime(DateTime(2010, 7, 4, 13, 0, 0)));
+
+        auto st3 = SysTime(DateTime(2010, 2, 12, 12, 0, 0));
+        st3.roll!"hours"(-1);
+        assert(st3 == SysTime(DateTime(2010, 2, 12, 11, 0, 0)));
+
+        auto st4 = SysTime(DateTime(2009, 12, 31, 0, 0, 0));
+        st4.roll!"minutes"(1);
+        assert(st4 == SysTime(DateTime(2009, 12, 31, 0, 1, 0)));
+
+        auto st5 = SysTime(DateTime(2010, 1, 1, 0, 0, 0));
+        st5.roll!"minutes"(-1);
+        assert(st5 == SysTime(DateTime(2010, 1, 1, 0, 59, 0)));
+
+        auto st6 = SysTime(DateTime(2009, 12, 31, 0, 0, 0));
+        st6.roll!"seconds"(1);
+        assert(st6 == SysTime(DateTime(2009, 12, 31, 0, 0, 1)));
+
+        auto st7 = SysTime(DateTime(2010, 1, 1, 0, 0, 0));
+        st7.roll!"seconds"(-1);
+        assert(st7 == SysTime(DateTime(2010, 1, 1, 0, 0, 59)));
+
+        auto dt = DateTime(2010, 1, 1, 0, 0, 0);
+        auto st8 = SysTime(dt);
+        st8.roll!"msecs"(1);
+        assert(st8 == SysTime(dt, msecs(1)));
+
+        auto st9 = SysTime(dt);
+        st9.roll!"msecs"(-1);
+        assert(st9 == SysTime(dt, msecs(999)));
+
+        auto st10 = SysTime(dt);
+        st10.roll!"hnsecs"(1);
+        assert(st10 == SysTime(dt, hnsecs(1)));
+
+        auto st11 = SysTime(dt);
+        st11.roll!"hnsecs"(-1);
+        assert(st11 == SysTime(dt, hnsecs(9_999_999)));
+    }
+
+    unittest
+    {
+        //Test A.D.
+        {
+            auto sysTime = SysTime(Date(1999, 2, 28));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(1999, 2, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(2000, 2, 28));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(2000, 2, 29)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(2000, 2, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 6, 30));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(1999, 6, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 31));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(1999, 7, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(1999, 7, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 1, 1));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(1999, 1, 31)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(1999, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"days"(9);
+            assert(sysTime == SysTime(Date(1999, 7, 15)));
+            sysTime.roll!"days"(-11);
+            assert(sysTime == SysTime(Date(1999, 7, 4)));
+            sysTime.roll!"days"(30);
+            assert(sysTime == SysTime(Date(1999, 7, 3)));
+            sysTime.roll!"days"(-3);
+            assert(sysTime == SysTime(Date(1999, 7, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 7, 6));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(Date(1999, 7, 30)));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+            sysTime.roll!"days"(366);
+            assert(sysTime == SysTime(Date(1999, 7, 31)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(Date(1999, 7, 17)));
+            sysTime.roll!"days"(-1096);
+            assert(sysTime == SysTime(Date(1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(1999, 2, 6));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(Date(1999, 2, 7)));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(Date(1999, 2, 6)));
+            sysTime.roll!"days"(366);
+            assert(sysTime == SysTime(Date(1999, 2, 8)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(Date(1999, 2, 10)));
+            sysTime.roll!"days"(-1096);
+            assert(sysTime == SysTime(Date(1999, 2, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(1999, 2, 1, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1999, 7, 6, 7, 9, 2), usecs(234578));
+            sysTime.roll!"days"(9);
+            assert(sysTime == SysTime(DateTime(1999, 7, 15, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-11);
+            assert(sysTime == SysTime(DateTime(1999, 7, 4, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(30);
+            assert(sysTime == SysTime(DateTime(1999, 7, 3, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-3);
+            assert(sysTime == SysTime(DateTime(1999, 7, 31, 7, 9, 2), usecs(234578)));
+        }
+
+        //Test B.C.
+        {
+            auto sysTime = SysTime(Date(-1999, 2, 28));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-1999, 2, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(-1999, 2, 28)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-2000, 2, 28));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-2000, 2, 29)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-2000, 2, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(-2000, 2, 29)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 6, 30));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-1999, 6, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(-1999, 6, 30)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 31));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-1999, 7, 1)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(-1999, 7, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 1, 1));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(Date(-1999, 1, 31)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(Date(-1999, 1, 1)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"days"(9);
+            assert(sysTime == SysTime(Date(-1999, 7, 15)));
+            sysTime.roll!"days"(-11);
+            assert(sysTime == SysTime(Date(-1999, 7, 4)));
+            sysTime.roll!"days"(30);
+            assert(sysTime == SysTime(Date(-1999, 7, 3)));
+            sysTime.roll!"days"(-3);
+            assert(sysTime == SysTime(Date(-1999, 7, 31)));
+        }
+
+        {
+            auto sysTime = SysTime(Date(-1999, 7, 6));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(Date(-1999, 7, 30)));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+            sysTime.roll!"days"(366);
+            assert(sysTime == SysTime(Date(-1999, 7, 31)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(Date(-1999, 7, 17)));
+            sysTime.roll!"days"(-1096);
+            assert(sysTime == SysTime(Date(-1999, 7, 6)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(-1999, 2, 1, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(-1999, 7, 6, 7, 9, 2), usecs(234578));
+            sysTime.roll!"days"(9);
+            assert(sysTime == SysTime(DateTime(-1999, 7, 15, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-11);
+            assert(sysTime == SysTime(DateTime(-1999, 7, 4, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(30);
+            assert(sysTime == SysTime(DateTime(-1999, 7, 3, 7, 9, 2), usecs(234578)));
+            sysTime.roll!"days"(-3);
+        }
+
+        //Test Both
+        {
+            auto sysTime = SysTime(Date(1, 7, 6));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(Date(1, 7, 13)));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(Date(1, 7, 6)));
+            sysTime.roll!"days"(-731);
+            assert(sysTime == SysTime(Date(1, 7, 19)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(Date(1, 7, 5)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 31, 0, 0, 0)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 0, 0, 0));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"days"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"days"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(DateTime(1, 7, 13, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(-731);
+            assert(sysTime == SysTime(DateTime(1, 7, 19, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(DateTime(1, 7, 5, 13, 13, 9), msecs(22)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22));
+            sysTime.roll!"days"(-365);
+            assert(sysTime == SysTime(DateTime(0, 7, 13, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(365);
+            assert(sysTime == SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(-731);
+            assert(sysTime == SysTime(DateTime(0, 7, 19, 13, 13, 9), msecs(22)));
+            sysTime.roll!"days"(730);
+            assert(sysTime == SysTime(DateTime(0, 7, 5, 13, 13, 9), msecs(22)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22));
+            sysTime.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730);
+            assert(sysTime == SysTime(DateTime(0, 7, 8, 13, 13, 9), msecs(22)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"days"(4)));
+        //static assert(!__traits(compiles, ist.roll!"days"(4)));
+    }
+
+
+    //Shares documentation with "days" version.
+    ref SysTime roll(string units)(long value) @safe nothrow
+        if(units == "hours" ||
+           units == "minutes" ||
+           units == "seconds")
+    {
+        try
+        {
+            auto hnsecs = adjTime;
+            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+            if(hnsecs < 0)
+            {
+                hnsecs += convert!("hours", "hnsecs")(24);
+                --days;
+            }
+
+            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
+            dateTime.roll!units(value);
+            --days;
+
+            hnsecs += convert!("hours", "hnsecs")(dateTime.hour);
+            hnsecs += convert!("minutes", "hnsecs")(dateTime.minute);
+            hnsecs += convert!("seconds", "hnsecs")(dateTime.second);
+
+            if(days < 0)
+            {
+                hnsecs -= convert!("hours", "hnsecs")(24);
+                ++days;
+            }
+
+            immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
+            adjTime = newDaysHNSecs + hnsecs;
+            return this;
+        }
+        catch(Exception e)
+            assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw.");
+    }
+
+    //Test roll!"hours"().
+    unittest
+    {
+        static void testST(SysTime orig, int hours, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"hours"(hours);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        immutable d = msecs(45);
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
+        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d));
+        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d));
+        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d));
+        testST(beforeAD, 6, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d));
+        testST(beforeAD, 7, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d));
+        testST(beforeAD, 8, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));
+        testST(beforeAD, 9, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));
+        testST(beforeAD, 11, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
+        testST(beforeAD, 12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
+        testST(beforeAD, 13, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
+        testST(beforeAD, 14, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d));
+        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d));
+        testST(beforeAD, 16, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));
+        testST(beforeAD, 17, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d));
+        testST(beforeAD, 18, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d));
+        testST(beforeAD, 19, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d));
+        testST(beforeAD, 20, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d));
+        testST(beforeAD, 21, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d));
+        testST(beforeAD, 22, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
+        testST(beforeAD, 23, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
+        testST(beforeAD, 24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 25, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
+        testST(beforeAD, 50, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
+        testST(beforeAD, 10_000, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
+        testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d));
+        testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d));
+        testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d));
+        testST(beforeAD, -6, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d));
+        testST(beforeAD, -7, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d));
+        testST(beforeAD, -8, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));
+        testST(beforeAD, -9, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d));
+        testST(beforeAD, -11, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
+        testST(beforeAD, -12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
+        testST(beforeAD, -13, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
+        testST(beforeAD, -14, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));
+        testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d));
+        testST(beforeAD, -16, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));
+        testST(beforeAD, -17, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d));
+        testST(beforeAD, -18, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d));
+        testST(beforeAD, -19, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d));
+        testST(beforeAD, -20, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d));
+        testST(beforeAD, -21, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d));
+        testST(beforeAD, -22, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
+        testST(beforeAD, -23, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
+        testST(beforeAD, -24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, -25, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
+        testST(beforeAD, -50, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
+        testST(beforeAD, -10_000, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 31, 0, 30, 33), d));
+        testST(SysTime(DateTime(1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(1999, 8, 1, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 12, 31, 0, 30, 33), d));
+        testST(SysTime(DateTime(2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(2000, 1, 1, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(1999, 2, 28, 0, 30, 33), d));
+        testST(SysTime(DateTime(1999, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(1999, 3, 2, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(2000, 2, 28, 0, 30, 33), d));
+        testST(SysTime(DateTime(2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(2000, 3, 1, 23, 30, 33), d));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d);
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
+        testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d));
+        testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d));
+        testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d));
+        testST(beforeBC, 6, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d));
+        testST(beforeBC, 7, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d));
+        testST(beforeBC, 8, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));
+        testST(beforeBC, 9, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));
+        testST(beforeBC, 11, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
+        testST(beforeBC, 12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
+        testST(beforeBC, 13, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
+        testST(beforeBC, 14, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d));
+        testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d));
+        testST(beforeBC, 16, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));
+        testST(beforeBC, 17, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d));
+        testST(beforeBC, 18, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d));
+        testST(beforeBC, 19, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d));
+        testST(beforeBC, 20, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d));
+        testST(beforeBC, 21, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d));
+        testST(beforeBC, 22, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
+        testST(beforeBC, 23, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
+        testST(beforeBC, 24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 25, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
+        testST(beforeBC, 50, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
+        testST(beforeBC, 10_000, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
+        testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d));
+        testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d));
+        testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d));
+        testST(beforeBC, -6, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d));
+        testST(beforeBC, -7, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d));
+        testST(beforeBC, -8, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));
+        testST(beforeBC, -9, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d));
+        testST(beforeBC, -11, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
+        testST(beforeBC, -12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
+        testST(beforeBC, -13, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
+        testST(beforeBC, -14, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));
+        testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d));
+        testST(beforeBC, -16, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));
+        testST(beforeBC, -17, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d));
+        testST(beforeBC, -18, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d));
+        testST(beforeBC, -19, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d));
+        testST(beforeBC, -20, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d));
+        testST(beforeBC, -21, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d));
+        testST(beforeBC, -22, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
+        testST(beforeBC, -23, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
+        testST(beforeBC, -24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, -25, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
+        testST(beforeBC, -50, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
+        testST(beforeBC, -10_000, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 31, 0, 30, 33), d));
+        testST(SysTime(DateTime(-1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 8, 1, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(-2001, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(-2001, 12, 31, 0, 30, 33), d));
+        testST(SysTime(DateTime(-2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(-2000, 1, 1, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(-2001, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2001, 2, 28, 0, 30, 33), d));
+        testST(SysTime(DateTime(-2001, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(-2001, 3, 2, 23, 30, 33), d));
+
+        testST(SysTime(DateTime(-2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2000, 2, 28, 0, 30, 33), d));
+        testST(SysTime(DateTime(-2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(-2000, 3, 1, 23, 30, 33), d));
+
+        //Test Both
+        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 17_546, SysTime(DateTime(-1, 1, 1, 13, 30, 33), d));
+        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -17_546, SysTime(DateTime(1, 1, 1, 11, 30, 33), d));
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"hours"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 0, 0)));
+            sysTime.roll!"hours"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"hours"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"hours"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 0, 0));
+            sysTime.roll!"hours"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0)));
+            sysTime.roll!"hours"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"hours"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"hours"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"hours"(1).roll!"hours"(-67);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 5, 59, 59), hnsecs(9_999_999)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"hours"(4)));
+        //static assert(!__traits(compiles, ist.roll!"hours"(4)));
+    }
+
+    //Test roll!"minutes"().
+    unittest
+    {
+        static void testST(SysTime orig, int minutes, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"minutes"(minutes);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        immutable d = usecs(7203);
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 32, 33), d));
+        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 33, 33), d));
+        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 34, 33), d));
+        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 35, 33), d));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 40, 33), d));
+        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d));
+        testST(beforeAD, 29, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
+        testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, 45, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d));
+        testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 75, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d));
+        testST(beforeAD, 90, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 10, 33), d));
+
+        testST(beforeAD, 689, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
+        testST(beforeAD, 690, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, 691, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
+        testST(beforeAD, 960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 1439, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d));
+        testST(beforeAD, 1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 1441, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
+        testST(beforeAD, 2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 28, 33), d));
+        testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 12, 27, 33), d));
+        testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 12, 26, 33), d));
+        testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 12, 25, 33), d));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 20, 33), d));
+        testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d));
+        testST(beforeAD, -29, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
+        testST(beforeAD, -30, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, -45, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d));
+        testST(beforeAD, -60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, -75, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d));
+        testST(beforeAD, -90, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 50, 33), d));
+
+        testST(beforeAD, -749, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
+        testST(beforeAD, -750, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(beforeAD, -751, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
+        testST(beforeAD, -960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, -1439, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
+        testST(beforeAD, -1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, -1441, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d));
+        testST(beforeAD, -2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(1999, 7, 6, 11, 0, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(1999, 7, 6, 11, 59, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(1999, 7, 6, 11, 58, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 1, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 33), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 0, 59, 33), d));
+
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(1999, 7, 5, 23, 0, 33), d));
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 33), d));
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(1999, 7, 5, 23, 58, 33), d));
+
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(1998, 12, 31, 23, 0, 33), d));
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 33), d));
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(1998, 12, 31, 23, 58, 33), d));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d);
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 32, 33), d));
+        testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 12, 33, 33), d));
+        testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 12, 34, 33), d));
+        testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 12, 35, 33), d));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 40, 33), d));
+        testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d));
+        testST(beforeBC, 29, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
+        testST(beforeBC, 30, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, 45, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d));
+        testST(beforeBC, 60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 75, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d));
+        testST(beforeBC, 90, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 10, 33), d));
+
+        testST(beforeBC, 689, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
+        testST(beforeBC, 690, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, 691, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
+        testST(beforeBC, 960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 1439, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d));
+        testST(beforeBC, 1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 1441, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d));
+        testST(beforeBC, 2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 28, 33), d));
+        testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 12, 27, 33), d));
+        testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 12, 26, 33), d));
+        testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 12, 25, 33), d));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 20, 33), d));
+        testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d));
+        testST(beforeBC, -29, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
+        testST(beforeBC, -30, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, -45, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d));
+        testST(beforeBC, -60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, -75, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d));
+        testST(beforeBC, -90, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 50, 33), d));
+
+        testST(beforeBC, -749, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
+        testST(beforeBC, -750, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(beforeBC, -751, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
+        testST(beforeBC, -960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, -1439, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d));
+        testST(beforeBC, -1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, -1441, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d));
+        testST(beforeBC, -2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 11, 0, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 11, 58, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 1, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 59, 33), d));
+
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 0, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d));
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 58, 33), d));
+
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 0, 33), d));
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d));
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 58, 33), d));
+
+        //Test Both
+        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0)), -1, SysTime(DateTime(1, 1, 1, 0, 59, 0)));
+        testST(SysTime(DateTime(0, 12, 31, 23, 59, 0)), 1, SysTime(DateTime(0, 12, 31, 23, 0, 0)));
+
+        testST(SysTime(DateTime(0, 1, 1, 0, 0, 0)), -1, SysTime(DateTime(0, 1, 1, 0, 59, 0)));
+        testST(SysTime(DateTime(-1, 12, 31, 23, 59, 0)), 1, SysTime(DateTime(-1, 12, 31, 23, 0, 0)));
+
+        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_760, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d));
+        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -1_052_760, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));
+
+        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_782, SysTime(DateTime(-1, 1, 1, 11, 52, 33), d));
+        testST(SysTime(DateTime(1, 1, 1, 13, 52, 33), d), -1_052_782, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"minutes"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 0)));
+            sysTime.roll!"minutes"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999));
+            sysTime.roll!"minutes"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999)));
+            sysTime.roll!"minutes"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 0));
+            sysTime.roll!"minutes"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0)));
+            sysTime.roll!"minutes"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"minutes"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 59), hnsecs(9_999_999)));
+            sysTime.roll!"minutes"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"minutes"(1).roll!"minutes"(-79);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 41, 59), hnsecs(9_999_999)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"minutes"(4)));
+        //static assert(!__traits(compiles, ist.roll!"minutes"(4)));
+    }
+
+    //Test roll!"seconds"().
+    unittest
+    {
+        static void testST(SysTime orig, int seconds, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"seconds"(seconds);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        immutable d = msecs(274);
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 35), d));
+        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 30, 36), d));
+        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 30, 37), d));
+        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 30, 38), d));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 43), d));
+        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 30, 48), d));
+        testST(beforeAD, 26, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
+        testST(beforeAD, 27, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
+        testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 30, 3), d));
+        testST(beforeAD, 59, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
+        testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 61, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
+
+        testST(beforeAD, 1766, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
+        testST(beforeAD, 1767, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
+        testST(beforeAD, 1768, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d));
+        testST(beforeAD, 2007, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
+        testST(beforeAD, 3599, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
+        testST(beforeAD, 3600, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, 3601, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
+        testST(beforeAD, 7200, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 31), d));
+        testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 12, 30, 30), d));
+        testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 12, 30, 29), d));
+        testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 12, 30, 28), d));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 23), d));
+        testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 12, 30, 18), d));
+        testST(beforeAD, -33, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
+        testST(beforeAD, -34, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
+        testST(beforeAD, -35, SysTime(DateTime(1999, 7, 6, 12, 30, 58), d));
+        testST(beforeAD, -59, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
+        testST(beforeAD, -60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
+        testST(beforeAD, -61, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 0, 1), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 0), d));
+        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 0, 59), d));
+
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 0, 0, 1), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 0), d));
+        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 0, 0, 59), d));
+
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(1999, 7, 5, 23, 59, 0), d));
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 59), d));
+        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(1999, 7, 5, 23, 59, 58), d));
+
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(1998, 12, 31, 23, 59, 0), d));
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 59), d));
+        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(1998, 12, 31, 23, 59, 58), d));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d);
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 35), d));
+        testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 12, 30, 36), d));
+        testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 12, 30, 37), d));
+        testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 12, 30, 38), d));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 43), d));
+        testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 12, 30, 48), d));
+        testST(beforeBC, 26, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));
+        testST(beforeBC, 27, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
+        testST(beforeBC, 30, SysTime(DateTime(-1999, 7, 6, 12, 30, 3), d));
+        testST(beforeBC, 59, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d));
+        testST(beforeBC, 60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 61, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d));
+
+        testST(beforeBC, 1766, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));
+        testST(beforeBC, 1767, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
+        testST(beforeBC, 1768, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d));
+        testST(beforeBC, 2007, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
+        testST(beforeBC, 3599, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d));
+        testST(beforeBC, 3600, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, 3601, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d));
+        testST(beforeBC, 7200, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 31), d));
+        testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 12, 30, 30), d));
+        testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 12, 30, 29), d));
+        testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 12, 30, 28), d));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 23), d));
+        testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 12, 30, 18), d));
+        testST(beforeBC, -33, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
+        testST(beforeBC, -34, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));
+        testST(beforeBC, -35, SysTime(DateTime(-1999, 7, 6, 12, 30, 58), d));
+        testST(beforeBC, -59, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d));
+        testST(beforeBC, -60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
+        testST(beforeBC, -61, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 0, 1), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 0, 59), d));
+
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 0, 1), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d));
+        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 0, 59), d));
+
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 59, 0), d));
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d));
+        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 59, 58), d));
+
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 59, 0), d));
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d));
+        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 59, 58), d));
+
+        //Test Both
+        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(1, 1, 1, 0, 0, 59), d));
+        testST(SysTime(DateTime(0, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(0, 12, 31, 23, 59, 0), d));
+
+        testST(SysTime(DateTime(0, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(0, 1, 1, 0, 0, 59), d));
+        testST(SysTime(DateTime(-1, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-1, 12, 31, 23, 59, 0), d));
+
+        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_600L, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d));
+        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -63_165_600L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));
+
+        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_617L, SysTime(DateTime(-1, 1, 1, 11, 30, 50), d));
+        testST(SysTime(DateTime(1, 1, 1, 13, 30, 50), d), -63_165_617L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+            sysTime.roll!"seconds"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59)));
+            sysTime.roll!"seconds"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999));
+            sysTime.roll!"seconds"(-1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999)));
+            sysTime.roll!"seconds"(1);
+            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59));
+            sysTime.roll!"seconds"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0)));
+            sysTime.roll!"seconds"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"seconds"(1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0), hnsecs(9_999_999)));
+            sysTime.roll!"seconds"(-1);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        }
+
+        {
+            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            sysTime.roll!"seconds"(1).roll!"seconds"(-102);
+            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 18), hnsecs(9_999_999)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"seconds"(4)));
+        //static assert(!__traits(compiles, ist.roll!"seconds"(4)));
+    }
+
+
+    //Shares documentation with "days" version.
+    ref SysTime roll(string units)(long value) @safe nothrow
+        if(units == "msecs" ||
+           units == "usecs" ||
+           units == "hnsecs")
+    {
+        auto hnsecs = adjTime;
+        immutable days = splitUnitsFromHNSecs!"days"(hnsecs);
+        immutable negative = hnsecs < 0;
+
+        if(negative)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        immutable seconds = splitUnitsFromHNSecs!"seconds"(hnsecs);
+        hnsecs += convert!(units, "hnsecs")(value);
+        hnsecs %= convert!("seconds", "hnsecs")(1);
+
+        if(hnsecs < 0)
+            hnsecs += convert!("seconds", "hnsecs")(1);
+        hnsecs += convert!("seconds", "hnsecs")(seconds);
+
+        if(negative)
+            hnsecs -= convert!("hours", "hnsecs")(24);
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
+        adjTime = newDaysHNSecs + hnsecs;
+        return this;
+    }
+
+
+    //Test roll!"msecs"().
+    unittest
+    {
+        static void testST(SysTime orig, int milliseconds, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"msecs"(milliseconds);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274));
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275)));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(276)));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(284)));
+        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(374)));
+        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275)));
+        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(1)));
+        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273)));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(272)));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(264)));
+        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(174)));
+        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273)));
+        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274));
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275)));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(276)));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(284)));
+        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(374)));
+        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275)));
+        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(1)));
+        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273)));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(272)));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(264)));
+        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(174)));
+        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273)));
+        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
+        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
+
+        //Test Both
+        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(1)));
+        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(999)));
+        testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(998)));
+        testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(445)));
+
+        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_989_999)));
+        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999)));
+        testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999)));
+        testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(5_549_999)));
+
+        {
+            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            st.roll!"msecs"(1202).roll!"msecs"(-703);
+            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(4_989_999)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.addMSecs(4)));
+        //static assert(!__traits(compiles, ist.addMSecs(4)));
+    }
+
+    //Test roll!"usecs"().
+    unittest
+    {
+        static void testST(SysTime orig, long microseconds, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"usecs"(microseconds);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274));
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(275)));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(276)));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(284)));
+        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(374)));
+        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999)));
+        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1000)));
+        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1274)));
+        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1275)));
+        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(2274)));
+        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(26_999)));
+        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_000)));
+        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_001)));
+        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(766_999)));
+        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(767_000)));
+        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(273)));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(272)));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(264)));
+        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(174)));
+        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_999)));
+        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_274)));
+        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_273)));
+        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(998_274)));
+        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(967_000)));
+        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(966_999)));
+        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(167_000)));
+        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(166_999)));
+        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274));
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(275)));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(276)));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(284)));
+        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(374)));
+        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999)));
+        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1000)));
+        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1274)));
+        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1275)));
+        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(2274)));
+        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(26_999)));
+        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_000)));
+        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_001)));
+        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(766_999)));
+        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(767_000)));
+        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(273)));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(272)));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(264)));
+        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(174)));
+        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_999)));
+        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_274)));
+        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_273)));
+        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(998_274)));
+        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(967_000)));
+        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(966_999)));
+        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(167_000)));
+        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(166_999)));
+        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
+
+        //Test Both
+        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(1)));
+        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_999)));
+        testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_998)));
+        testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_000)));
+        testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(998_000)));
+        testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(997_445)));
+        testST(beforeBoth1, -1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(666_667)));
+
+        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_989)));
+        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9)));
+        testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19)));
+        testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999)));
+        testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999)));
+        testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(25_549)));
+        testST(beforeBoth2, 1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(3_333_329)));
+
+        {
+            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            st.roll!"usecs"(9_020_027);
+            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(200_269)));
+        }
+
+        {
+            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            st.roll!"usecs"(9_020_027).roll!"usecs"(-70_034);
+            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_499_929)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"usecs"(4)));
+        //static assert(!__traits(compiles, ist.roll!"usecs"(4)));
+    }
+
+    //Test roll!"hnsecs"().
+    unittest
+    {
+        static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            orig.roll!"hnsecs"(hnsecs);
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        auto dtAD = DateTime(1999, 7, 6, 12, 30, 33);
+        auto beforeAD = SysTime(dtAD, hnsecs(274));
+        testST(beforeAD, 0, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, 1, SysTime(dtAD, hnsecs(275)));
+        testST(beforeAD, 2, SysTime(dtAD, hnsecs(276)));
+        testST(beforeAD, 10, SysTime(dtAD, hnsecs(284)));
+        testST(beforeAD, 100, SysTime(dtAD, hnsecs(374)));
+        testST(beforeAD, 725, SysTime(dtAD, hnsecs(999)));
+        testST(beforeAD, 726, SysTime(dtAD, hnsecs(1000)));
+        testST(beforeAD, 1000, SysTime(dtAD, hnsecs(1274)));
+        testST(beforeAD, 1001, SysTime(dtAD, hnsecs(1275)));
+        testST(beforeAD, 2000, SysTime(dtAD, hnsecs(2274)));
+        testST(beforeAD, 26_725, SysTime(dtAD, hnsecs(26_999)));
+        testST(beforeAD, 26_726, SysTime(dtAD, hnsecs(27_000)));
+        testST(beforeAD, 26_727, SysTime(dtAD, hnsecs(27_001)));
+        testST(beforeAD, 1_766_725, SysTime(dtAD, hnsecs(1_766_999)));
+        testST(beforeAD, 1_766_726, SysTime(dtAD, hnsecs(1_767_000)));
+        testST(beforeAD, 1_000_000, SysTime(dtAD, hnsecs(1_000_274)));
+        testST(beforeAD, 60_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, 3_600_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, 600_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, 36_000_000_000L, SysTime(dtAD, hnsecs(274)));
+
+        testST(beforeAD, -1, SysTime(dtAD, hnsecs(273)));
+        testST(beforeAD, -2, SysTime(dtAD, hnsecs(272)));
+        testST(beforeAD, -10, SysTime(dtAD, hnsecs(264)));
+        testST(beforeAD, -100, SysTime(dtAD, hnsecs(174)));
+        testST(beforeAD, -274, SysTime(dtAD));
+        testST(beforeAD, -275, SysTime(dtAD, hnsecs(9_999_999)));
+        testST(beforeAD, -1000, SysTime(dtAD, hnsecs(9_999_274)));
+        testST(beforeAD, -1001, SysTime(dtAD, hnsecs(9_999_273)));
+        testST(beforeAD, -2000, SysTime(dtAD, hnsecs(9_998_274)));
+        testST(beforeAD, -33_274, SysTime(dtAD, hnsecs(9_967_000)));
+        testST(beforeAD, -33_275, SysTime(dtAD, hnsecs(9_966_999)));
+        testST(beforeAD, -1_833_274, SysTime(dtAD, hnsecs(8_167_000)));
+        testST(beforeAD, -1_833_275, SysTime(dtAD, hnsecs(8_166_999)));
+        testST(beforeAD, -1_000_000, SysTime(dtAD, hnsecs(9_000_274)));
+        testST(beforeAD, -60_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, -3_600_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, -600_000_000L, SysTime(dtAD, hnsecs(274)));
+        testST(beforeAD, -36_000_000_000L, SysTime(dtAD, hnsecs(274)));
+
+        //Test B.C.
+        auto dtBC = DateTime(-1999, 7, 6, 12, 30, 33);
+        auto beforeBC = SysTime(dtBC, hnsecs(274));
+        testST(beforeBC, 0, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, 1, SysTime(dtBC, hnsecs(275)));
+        testST(beforeBC, 2, SysTime(dtBC, hnsecs(276)));
+        testST(beforeBC, 10, SysTime(dtBC, hnsecs(284)));
+        testST(beforeBC, 100, SysTime(dtBC, hnsecs(374)));
+        testST(beforeBC, 725, SysTime(dtBC, hnsecs(999)));
+        testST(beforeBC, 726, SysTime(dtBC, hnsecs(1000)));
+        testST(beforeBC, 1000, SysTime(dtBC, hnsecs(1274)));
+        testST(beforeBC, 1001, SysTime(dtBC, hnsecs(1275)));
+        testST(beforeBC, 2000, SysTime(dtBC, hnsecs(2274)));
+        testST(beforeBC, 26_725, SysTime(dtBC, hnsecs(26_999)));
+        testST(beforeBC, 26_726, SysTime(dtBC, hnsecs(27_000)));
+        testST(beforeBC, 26_727, SysTime(dtBC, hnsecs(27_001)));
+        testST(beforeBC, 1_766_725, SysTime(dtBC, hnsecs(1_766_999)));
+        testST(beforeBC, 1_766_726, SysTime(dtBC, hnsecs(1_767_000)));
+        testST(beforeBC, 1_000_000, SysTime(dtBC, hnsecs(1_000_274)));
+        testST(beforeBC, 60_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, 3_600_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, 600_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, 36_000_000_000L, SysTime(dtBC, hnsecs(274)));
+
+        testST(beforeBC, -1, SysTime(dtBC, hnsecs(273)));
+        testST(beforeBC, -2, SysTime(dtBC, hnsecs(272)));
+        testST(beforeBC, -10, SysTime(dtBC, hnsecs(264)));
+        testST(beforeBC, -100, SysTime(dtBC, hnsecs(174)));
+        testST(beforeBC, -274, SysTime(dtBC));
+        testST(beforeBC, -275, SysTime(dtBC, hnsecs(9_999_999)));
+        testST(beforeBC, -1000, SysTime(dtBC, hnsecs(9_999_274)));
+        testST(beforeBC, -1001, SysTime(dtBC, hnsecs(9_999_273)));
+        testST(beforeBC, -2000, SysTime(dtBC, hnsecs(9_998_274)));
+        testST(beforeBC, -33_274, SysTime(dtBC, hnsecs(9_967_000)));
+        testST(beforeBC, -33_275, SysTime(dtBC, hnsecs(9_966_999)));
+        testST(beforeBC, -1_833_274, SysTime(dtBC, hnsecs(8_167_000)));
+        testST(beforeBC, -1_833_275, SysTime(dtBC, hnsecs(8_166_999)));
+        testST(beforeBC, -1_000_000, SysTime(dtBC, hnsecs(9_000_274)));
+        testST(beforeBC, -60_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, -3_600_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, -600_000_000L, SysTime(dtBC, hnsecs(274)));
+        testST(beforeBC, -36_000_000_000L, SysTime(dtBC, hnsecs(274)));
+
+        //Test Both
+        auto dtBoth1 = DateTime(1, 1, 1, 0, 0, 0);
+        auto beforeBoth1 = SysTime(dtBoth1);
+        testST(beforeBoth1, 1, SysTime(dtBoth1, hnsecs(1)));
+        testST(beforeBoth1, 0, SysTime(dtBoth1));
+        testST(beforeBoth1, -1, SysTime(dtBoth1, hnsecs(9_999_999)));
+        testST(beforeBoth1, -2, SysTime(dtBoth1, hnsecs(9_999_998)));
+        testST(beforeBoth1, -1000, SysTime(dtBoth1, hnsecs(9_999_000)));
+        testST(beforeBoth1, -2000, SysTime(dtBoth1, hnsecs(9_998_000)));
+        testST(beforeBoth1, -2555, SysTime(dtBoth1, hnsecs(9_997_445)));
+        testST(beforeBoth1, -1_000_000, SysTime(dtBoth1, hnsecs(9_000_000)));
+        testST(beforeBoth1, -2_000_000, SysTime(dtBoth1, hnsecs(8_000_000)));
+        testST(beforeBoth1, -2_333_333, SysTime(dtBoth1, hnsecs(7_666_667)));
+        testST(beforeBoth1, -10_000_000, SysTime(dtBoth1));
+        testST(beforeBoth1, -20_000_000, SysTime(dtBoth1));
+        testST(beforeBoth1, -20_888_888, SysTime(dtBoth1, hnsecs(9_111_112)));
+
+        auto dtBoth2 = DateTime(0, 12, 31, 23, 59, 59);
+        auto beforeBoth2 = SysTime(dtBoth2, hnsecs(9_999_999));
+        testST(beforeBoth2, -1, SysTime(dtBoth2, hnsecs(9_999_998)));
+        testST(beforeBoth2, 0, SysTime(dtBoth2, hnsecs(9_999_999)));
+        testST(beforeBoth2, 1, SysTime(dtBoth2));
+        testST(beforeBoth2, 2, SysTime(dtBoth2, hnsecs(1)));
+        testST(beforeBoth2, 1000, SysTime(dtBoth2, hnsecs(999)));
+        testST(beforeBoth2, 2000, SysTime(dtBoth2, hnsecs(1999)));
+        testST(beforeBoth2, 2555, SysTime(dtBoth2, hnsecs(2554)));
+        testST(beforeBoth2, 1_000_000, SysTime(dtBoth2, hnsecs(999_999)));
+        testST(beforeBoth2, 2_000_000, SysTime(dtBoth2, hnsecs(1_999_999)));
+        testST(beforeBoth2, 2_333_333, SysTime(dtBoth2, hnsecs(2_333_332)));
+        testST(beforeBoth2, 10_000_000, SysTime(dtBoth2, hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_000_000, SysTime(dtBoth2, hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_888_888, SysTime(dtBoth2, hnsecs(888_887)));
+
+        {
+            auto st = SysTime(dtBoth2, hnsecs(9_999_999));
+            st.roll!"hnsecs"(70_777_222).roll!"hnsecs"(-222_555_292);
+            assert(st == SysTime(dtBoth2, hnsecs(8_221_929)));
+        }
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.roll!"hnsecs"(4)));
+        //static assert(!__traits(compiles, ist.roll!"hnsecs"(4)));
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF SysTime).
+
+        The legal types of arithmetic for $(LREF SysTime) using this operator
+        are
+
+        $(BOOKTABLE,
+        $(TR $(TD SysTime) $(TD +) $(TD Duration) $(TD -->) $(TD SysTime))
+        $(TR $(TD SysTime) $(TD -) $(TD Duration) $(TD -->) $(TD SysTime))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF SysTime).
+      +/
+    SysTime opBinary(string op)(Duration duration) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        SysTime retval = SysTime(this._stdTime, this._timezone);
+        immutable hnsecs = duration.total!"hnsecs";
+        mixin("retval._stdTime " ~ op ~ "= hnsecs;");
+        return retval;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(2015, 12, 31, 23, 59, 59)) + seconds(1) ==
+               SysTime(DateTime(2016, 1, 1, 0, 0, 0)));
+
+        assert(SysTime(DateTime(2015, 12, 31, 23, 59, 59)) + hours(1) ==
+               SysTime(DateTime(2016, 1, 1, 0, 59, 59)));
+
+        assert(SysTime(DateTime(2016, 1, 1, 0, 0, 0)) - seconds(1) ==
+               SysTime(DateTime(2015, 12, 31, 23, 59, 59)));
+
+        assert(SysTime(DateTime(2016, 1, 1, 0, 59, 59)) - hours(1) ==
+               SysTime(DateTime(2015, 12, 31, 23, 59, 59)));
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+
+        assert(st + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678)));
+        assert(st + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678)));
+        assert(st + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678)));
+        assert(st + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678)));
+        assert(st + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678)));
+        assert(st + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678)));
+        assert(st + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678)));
+        assert(st + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+        assert(st + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+        assert(st + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685)));
+        assert(st + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671)));
+
+        assert(st - dur!"weeks"(-7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"weeks"(7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"days"(-7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"days"(7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678)));
+        assert(st - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678)));
+        assert(st - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678)));
+        assert(st - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678)));
+        assert(st - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678)));
+        assert(st - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678)));
+        assert(st - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678)));
+        assert(st - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+        assert(st - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+        assert(st - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685)));
+        assert(st - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671)));
+
+        static void testST(in SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+            auto result = orig + dur!"hnsecs"(hnsecs);
+            if(result != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", result, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274));
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275)));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276)));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284)));
+        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374)));
+        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999)));
+        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000)));
+        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274)));
+        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275)));
+        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274)));
+        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
+        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
+        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
+        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
+        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
+        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
+        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274)));
+        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274)));
+        testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274)));
+        testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274)));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273)));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272)));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264)));
+        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174)));
+        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
+        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
+        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
+        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
+        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
+        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
+        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
+        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
+        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
+        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274)));
+        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274)));
+        testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274)));
+        testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274)));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274));
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275)));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276)));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284)));
+        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374)));
+        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999)));
+        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000)));
+        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274)));
+        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275)));
+        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274)));
+        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
+        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
+        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
+        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
+        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
+        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
+        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274)));
+        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274)));
+        testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274)));
+        testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274)));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273)));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272)));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264)));
+        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174)));
+        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
+        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
+        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
+        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
+        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
+        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
+        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
+        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
+        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
+        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274)));
+        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274)));
+        testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274)));
+        testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274)));
+
+        //Test Both
+        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
+        testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000)));
+        testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000)));
+        testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445)));
+        testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000)));
+        testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000)));
+        testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667)));
+        testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
+        testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58)));
+        testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112)));
+
+        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
+        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999)));
+        testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999)));
+        testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554)));
+        testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999)));
+        testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999)));
+        testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332)));
+        testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887)));
+
+        auto duration = dur!"seconds"(12);
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45)));
+        //assert(ist + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45)));
+        assert(cst - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21)));
+        //assert(ist - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21)));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines + and - with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    SysTime opBinary(string op)(TickDuration td) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        SysTime retval = SysTime(this._stdTime, this._timezone);
+        immutable hnsecs = td.hnsecs;
+        mixin("retval._stdTime " ~ op ~ "= hnsecs;");
+        return retval;
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+
+            assert(st + TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+            assert(st + TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+
+            assert(st - TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+            assert(st - TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+        }
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF SysTime), as well as assigning the result to this
+        $(LREF SysTime).
+
+        The legal types of arithmetic for $(LREF SysTime) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD SysTime) $(TD +) $(TD Duration) $(TD -->) $(TD SysTime))
+        $(TR $(TD SysTime) $(TD -) $(TD Duration) $(TD -->) $(TD SysTime))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF SysTime).
+      +/
+    ref SysTime opOpAssign(string op)(Duration duration) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable hnsecs = duration.total!"hnsecs";
+        mixin("_stdTime " ~ op ~ "= hnsecs;");
+        return this;
+    }
+
+    unittest
+    {
+        auto before = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(before + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33)));
+        assert(before + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33)));
+        assert(before + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33)));
+        assert(before + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33)));
+
+        assert(before + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33)));
+        assert(before + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33)));
+        assert(before + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33)));
+        assert(before + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33)));
+        assert(before + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40)));
+        assert(before + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26)));
+        assert(before + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7)));
+        assert(before + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993)));
+        assert(before + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7)));
+        assert(before + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993)));
+        assert(before + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7)));
+        assert(before + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993)));
+
+        assert(before - dur!"weeks"(-7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33)));
+        assert(before - dur!"weeks"(7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33)));
+        assert(before - dur!"days"(-7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33)));
+        assert(before - dur!"days"(7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33)));
+
+        assert(before - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33)));
+        assert(before - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33)));
+        assert(before - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33)));
+        assert(before - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33)));
+        assert(before - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40)));
+        assert(before - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26)));
+        assert(before - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7)));
+        assert(before - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993)));
+        assert(before - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7)));
+        assert(before - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993)));
+        assert(before - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7)));
+        assert(before - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993)));
+
+        static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+
+            auto r = orig += dur!"hnsecs"(hnsecs);
+            if(orig != expected)
+                throw new AssertError(format("Failed 1. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+            if(r != expected)
+                throw new AssertError(format("Failed 2. actual [%s] != expected [%s]", r, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274));
+        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)));
+        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275)));
+        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276)));
+        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284)));
+        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374)));
+        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999)));
+        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000)));
+        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274)));
+        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275)));
+        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274)));
+        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
+        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
+        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
+        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
+        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
+        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
+        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274)));
+        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274)));
+        testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274)));
+        testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274)));
+
+        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273)));
+        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272)));
+        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264)));
+        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174)));
+        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
+        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
+        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
+        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
+        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
+        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
+        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
+        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
+        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
+        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274)));
+        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274)));
+        testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274)));
+        testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274)));
+
+        //Test B.C.
+        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274));
+        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)));
+        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275)));
+        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276)));
+        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284)));
+        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374)));
+        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999)));
+        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000)));
+        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274)));
+        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275)));
+        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274)));
+        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
+        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
+        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
+        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
+        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
+        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
+        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274)));
+        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274)));
+        testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274)));
+        testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274)));
+
+        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273)));
+        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272)));
+        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264)));
+        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174)));
+        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
+        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
+        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
+        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
+        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
+        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
+        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
+        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
+        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
+        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274)));
+        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274)));
+        testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274)));
+        testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274)));
+
+        //Test Both
+        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
+        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
+        testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000)));
+        testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000)));
+        testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445)));
+        testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000)));
+        testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000)));
+        testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667)));
+        testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
+        testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58)));
+        testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112)));
+
+        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
+        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999)));
+        testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999)));
+        testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554)));
+        testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999)));
+        testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999)));
+        testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332)));
+        testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999)));
+        testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887)));
+
+        {
+            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
+            (st += dur!"hnsecs"(52)) += dur!"seconds"(-907);
+            assert(st == SysTime(DateTime(0, 12, 31, 23, 44, 53), hnsecs(51)));
+        }
+
+        auto duration = dur!"seconds"(12);
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst += duration));
+        //static assert(!__traits(compiles, ist += duration));
+        static assert(!__traits(compiles, cst -= duration));
+        //static assert(!__traits(compiles, ist -= duration));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines += and -= with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    ref SysTime opOpAssign(string op)(TickDuration td) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable hnsecs = td.hnsecs;
+        mixin("_stdTime " ~ op ~ "= hnsecs;");
+        return this;
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            {
+                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+                st += TickDuration.from!"usecs"(7);
+                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+            }
+            {
+                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+                st += TickDuration.from!"usecs"(-7);
+                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+            }
+
+            {
+                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+                st -= TickDuration.from!"usecs"(-7);
+                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
+            }
+            {
+                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
+                st -= TickDuration.from!"usecs"(7);
+                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
+            }
+        }
+    }
+
+
+    /++
+        Gives the difference between two $(LREF SysTime)s.
+
+        The legal types of arithmetic for $(LREF SysTime) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD SysTime) $(TD -) $(TD SysTime) $(TD -->) $(TD duration))
+        )
+      +/
+    Duration opBinary(string op)(in SysTime rhs) @safe const pure nothrow
+        if(op == "-")
+    {
+        return dur!"hnsecs"(_stdTime - rhs._stdTime);
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1998, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(31_536_000));
+        assert(SysTime(DateTime(1998, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(-31_536_000));
+
+        assert(SysTime(DateTime(1999, 8, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(26_78_400));
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 8, 6, 12, 30, 33)) ==
+                    dur!"seconds"(-26_78_400));
+
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 5, 12, 30, 33)) ==
+                    dur!"seconds"(86_400));
+        assert(SysTime(DateTime(1999, 7, 5, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(-86_400));
+
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 11, 30, 33)) ==
+                    dur!"seconds"(3600));
+        assert(SysTime(DateTime(1999, 7, 6, 11, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(-3600));
+
+        assert(SysTime(DateTime(1999, 7, 6, 12, 31, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(60));
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 31, 33)) ==
+                    dur!"seconds"(-60));
+
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 34)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
+                    dur!"seconds"(1));
+        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 34)) ==
+                    dur!"seconds"(-1));
+
+        {
+            auto dt = DateTime(1999, 7, 6, 12, 30, 33);
+            assert(SysTime(dt, msecs(532)) - SysTime(dt) == msecs(532));
+            assert(SysTime(dt) - SysTime(dt, msecs(532)) == msecs(-532));
+
+            assert(SysTime(dt, usecs(333_347)) - SysTime(dt) == usecs(333_347));
+            assert(SysTime(dt) - SysTime(dt, usecs(333_347)) == usecs(-333_347));
+
+            assert(SysTime(dt, hnsecs(1_234_567)) - SysTime(dt) == hnsecs(1_234_567));
+            assert(SysTime(dt) - SysTime(dt, hnsecs(1_234_567)) == hnsecs(-1_234_567));
+        }
+
+        assert(SysTime(DateTime(1, 1, 1, 12, 30, 33)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) == dur!"seconds"(45033));
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(1, 1, 1, 12, 30, 33)) == dur!"seconds"(-45033));
+        assert(SysTime(DateTime(0, 12, 31, 12, 30, 33)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) == dur!"seconds"(-41367));
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(0, 12, 31, 12, 30, 33)) == dur!"seconds"(41367));
+
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) ==
+                        dur!"hnsecs"(1));
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) ==
+                        dur!"hnsecs"(-1));
+
+        auto tz = TimeZone.getTimeZone("America/Los_Angeles");
+
+        {
+            auto dt = DateTime(2011, 1, 13, 8, 17, 2);
+            auto d = msecs(296);
+            assert(SysTime(dt, d, tz) - SysTime(dt, d, tz) == Duration.zero);
+            assert(SysTime(dt, d, tz) - SysTime(dt, d, UTC()) == hours(8));
+            assert(SysTime(dt, d, UTC()) - SysTime(dt, d, tz) == hours(-8));
+        }
+
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st - st == Duration.zero);
+        assert(cst - st == Duration.zero);
+        //assert(ist - st == Duration.zero);
+
+        assert(st - cst == Duration.zero);
+        assert(cst - cst == Duration.zero);
+        //assert(ist - cst == Duration.zero);
+
+        //assert(st - ist == Duration.zero);
+        //assert(cst - ist == Duration.zero);
+        //assert(ist - ist == Duration.zero);
+    }
+
+
+    /++
+        Returns the difference between the two $(LREF SysTime)s in months.
+
+        To get the difference in years, subtract the year property
+        of two $(LREF SysTime)s. To get the difference in days or weeks,
+        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
+        that results. Because converting between months and smaller
+        units requires a specific date (which $(CXREF time, Duration)s don't have),
+        getting the difference in months requires some math using both
+        the year and month properties, so this is a convenience function for
+        getting the difference in months.
+
+        Note that the number of days in the months or how far into the month
+        either date is is irrelevant. It is the difference in the month property
+        combined with the difference in years * 12. So, for instance,
+        December 31st and January 1st are one month apart just as December 1st
+        and January 31st are one month apart.
+
+        Params:
+            rhs = The $(LREF SysTime) to subtract from this one.
+      +/
+    int diffMonths(in SysTime rhs) @safe const nothrow
+    {
+        return (cast(Date)this).diffMonths(cast(Date)rhs);
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(Date(1999, 2, 1)).diffMonths(
+                    SysTime(Date(1999, 1, 31))) == 1);
+
+        assert(SysTime(Date(1999, 1, 31)).diffMonths(
+                    SysTime(Date(1999, 2, 1))) == -1);
+
+        assert(SysTime(Date(1999, 3, 1)).diffMonths(
+                    SysTime(Date(1999, 1, 1))) == 2);
+
+        assert(SysTime(Date(1999, 1, 1)).diffMonths(
+                    SysTime(Date(1999, 3, 31))) == -2);
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st.diffMonths(st) == 0);
+        assert(cst.diffMonths(st) == 0);
+        //assert(ist.diffMonths(st) == 0);
+
+        assert(st.diffMonths(cst) == 0);
+        assert(cst.diffMonths(cst) == 0);
+        //assert(ist.diffMonths(cst) == 0);
+
+        //assert(st.diffMonths(ist) == 0);
+        //assert(cst.diffMonths(ist) == 0);
+        //assert(ist.diffMonths(ist) == 0);
+    }
+
+
+    /++
+        Whether this $(LREF SysTime) is in a leap year.
+     +/
+    @property bool isLeapYear() @safe const nothrow
+    {
+        return (cast(Date)this).isLeapYear;
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(!st.isLeapYear);
+        assert(!cst.isLeapYear);
+        //assert(!ist.isLeapYear);
+    }
+
+
+    /++
+        Day of the week this $(LREF SysTime) is on.
+      +/
+    @property DayOfWeek dayOfWeek() @safe const nothrow
+    {
+        return getDayOfWeek(dayOfGregorianCal);
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st.dayOfWeek == DayOfWeek.tue);
+        assert(cst.dayOfWeek == DayOfWeek.tue);
+        //assert(ist.dayOfWeek == DayOfWeek.tue);
+    }
+
+
+    /++
+        Day of the year this $(LREF SysTime) is on.
+      +/
+    @property ushort dayOfYear() @safe const nothrow
+    {
+        return (cast(Date)this).dayOfYear;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 1, 1, 12, 22, 7)).dayOfYear == 1);
+        assert(SysTime(DateTime(1999, 12, 31, 7, 2, 59)).dayOfYear == 365);
+        assert(SysTime(DateTime(2000, 12, 31, 21, 20, 0)).dayOfYear == 366);
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st.dayOfYear == 187);
+        assert(cst.dayOfYear == 187);
+        //assert(ist.dayOfYear == 187);
+    }
+
+
+    /++
+        Day of the year.
+
+        Params:
+            day = The day of the year to set which day of the year this
+                  $(LREF SysTime) is on.
+      +/
+    @property void dayOfYear(int day) @safe
+    {
+        immutable hnsecs = adjTime;
+        immutable days = convert!("hnsecs", "days")(hnsecs);
+        immutable theRest = hnsecs - convert!("days", "hnsecs")(days);
+
+        auto date = Date(cast(int)days);
+        date.dayOfYear = day;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
+
+        adjTime = newDaysHNSecs + theRest;
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        st.dayOfYear = 12;
+        assert(st.dayOfYear == 12);
+        static assert(!__traits(compiles, cst.dayOfYear = 12));
+        //static assert(!__traits(compiles, ist.dayOfYear = 12));
+    }
+
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on.
+     +/
+    @property int dayOfGregorianCal() @safe const nothrow
+    {
+        immutable adjustedTime = adjTime;
+
+        //We have to add one because 0 would be midnight, January 1st, 1 A.D.,
+        //which would be the 1st day of the Gregorian Calendar, not the 0th. So,
+        //simply casting to days is one day off.
+        if(adjustedTime > 0)
+            return cast(int)getUnitsFromHNSecs!"days"(adjustedTime) + 1;
+
+        long hnsecs = adjustedTime;
+        immutable days = cast(int)splitUnitsFromHNSecs!"days"(hnsecs);
+
+        return hnsecs == 0 ? days + 1 : days;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1);
+        assert(SysTime(DateTime(1, 12, 31, 23, 59, 59)).dayOfGregorianCal == 365);
+        assert(SysTime(DateTime(2, 1, 1, 2, 2, 2)).dayOfGregorianCal == 366);
+
+        assert(SysTime(DateTime(0, 12, 31, 7, 7, 7)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 1, 1, 19, 30, 0)).dayOfGregorianCal == -365);
+        assert(SysTime(DateTime(-1, 12, 31, 4, 7, 0)).dayOfGregorianCal == -366);
+
+        assert(SysTime(DateTime(2000, 1, 1, 9, 30, 20)).dayOfGregorianCal == 730_120);
+        assert(SysTime(DateTime(2010, 12, 31, 15, 45, 50)).dayOfGregorianCal == 734_137);
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1);
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 1);
+        assert(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 1);
+
+        assert(SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1);
+        assert(SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212)).dayOfGregorianCal == 2);
+        assert(SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 32);
+        assert(SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 366);
+        assert(SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 731);
+        assert(SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1096);
+        assert(SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1462);
+        assert(SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 17_898);
+        assert(SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 35_065);
+        assert(SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_160);
+        assert(SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_525);
+        assert(SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 37_986);
+        assert(SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 72_684);
+        assert(SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 73_049);
+        assert(SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_208);
+        assert(SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_573);
+        assert(SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 145_732);
+        assert(SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 146_098);
+        assert(SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_257);
+        assert(SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_622);
+        assert(SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 364_878);
+        assert(SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 365_243);
+        assert(SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_023);
+        assert(SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_389);
+        assert(SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_596);
+        assert(SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_961);
+        assert(SysTime(DateTime(1945, 11, 12, 12, 2, 9), msecs(212)).dayOfGregorianCal == 710_347);
+        assert(SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 729_755);
+        assert(SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_120);
+        assert(SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_486);
+
+        assert(SysTime(DateTime(2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_773);
+        assert(SysTime(DateTime(2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_803);
+        assert(SysTime(DateTime(2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_804);
+        assert(SysTime(DateTime(2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_831);
+        assert(SysTime(DateTime(2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_832);
+        assert(SysTime(DateTime(2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_862);
+        assert(SysTime(DateTime(2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_863);
+        assert(SysTime(DateTime(2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_892);
+        assert(SysTime(DateTime(2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_893);
+        assert(SysTime(DateTime(2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_923);
+        assert(SysTime(DateTime(2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_924);
+        assert(SysTime(DateTime(2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_953);
+        assert(SysTime(DateTime(2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_954);
+        assert(SysTime(DateTime(2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_984);
+        assert(SysTime(DateTime(2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_985);
+        assert(SysTime(DateTime(2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_015);
+        assert(SysTime(DateTime(2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_016);
+        assert(SysTime(DateTime(2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_045);
+        assert(SysTime(DateTime(2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_046);
+        assert(SysTime(DateTime(2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_076);
+        assert(SysTime(DateTime(2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_077);
+        assert(SysTime(DateTime(2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_106);
+        assert(SysTime(DateTime(2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_107);
+        assert(SysTime(DateTime(2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_137);
+
+        assert(SysTime(DateTime(2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == 734_534);
+        assert(SysTime(DateTime(2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == 734_561);
+        assert(SysTime(DateTime(2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == 734_562);
+        assert(SysTime(DateTime(2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == 734_563);
+
+        //Test B.C.
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 12, 31, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 12, 31, 0, 0, 0)).dayOfGregorianCal == 0);
+
+        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == -366);
+        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == -366);
+        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59)).dayOfGregorianCal == -366);
+        assert(SysTime(DateTime(-1, 12, 31, 0, 0, 0)).dayOfGregorianCal == -366);
+
+        assert(SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == 0);
+        assert(SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1);
+        assert(SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -30);
+        assert(SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -31);
+
+        assert(SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -366);
+        assert(SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -367);
+        assert(SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730);
+        assert(SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731);
+        assert(SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1095);
+        assert(SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1096);
+        assert(SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1460);
+        assert(SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1461);
+        assert(SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1826);
+        assert(SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1827);
+        assert(SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -2191);
+        assert(SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -3652);
+
+        assert(SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_262);
+        assert(SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_627);
+        assert(SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -35_794);
+        assert(SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_160);
+        assert(SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_524);
+        assert(SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_889);
+        assert(SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -37_254);
+        assert(SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -38_715);
+        assert(SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_413);
+        assert(SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_778);
+        assert(SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -109_937);
+        assert(SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -110_302);
+        assert(SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_097);
+        assert(SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_462);
+        assert(SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_827);
+        assert(SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_621);
+        assert(SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_986);
+        assert(SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -183_351);
+        assert(SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_607);
+        assert(SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_972);
+        assert(SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_387);
+        assert(SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_388);
+        assert(SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_753);
+        assert(SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -585_118);
+        assert(SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_325);
+        assert(SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_690);
+        assert(SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_484);
+        assert(SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_485);
+        assert(SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_850);
+        assert(SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731_215);
+
+        assert(SysTime(DateTime(-2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_502);
+        assert(SysTime(DateTime(-2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_472);
+        assert(SysTime(DateTime(-2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_471);
+        assert(SysTime(DateTime(-2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_444);
+        assert(SysTime(DateTime(-2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_443);
+        assert(SysTime(DateTime(-2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_413);
+        assert(SysTime(DateTime(-2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_412);
+        assert(SysTime(DateTime(-2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_383);
+        assert(SysTime(DateTime(-2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_382);
+        assert(SysTime(DateTime(-2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_352);
+        assert(SysTime(DateTime(-2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_351);
+        assert(SysTime(DateTime(-2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_322);
+        assert(SysTime(DateTime(-2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_321);
+        assert(SysTime(DateTime(-2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_291);
+        assert(SysTime(DateTime(-2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_290);
+        assert(SysTime(DateTime(-2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_260);
+        assert(SysTime(DateTime(-2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_259);
+        assert(SysTime(DateTime(-2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_230);
+        assert(SysTime(DateTime(-2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_229);
+        assert(SysTime(DateTime(-2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_199);
+        assert(SysTime(DateTime(-2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_198);
+        assert(SysTime(DateTime(-2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_169);
+        assert(SysTime(DateTime(-2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_168);
+        assert(SysTime(DateTime(-2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_138);
+
+        assert(SysTime(DateTime(-2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == -735_202);
+        assert(SysTime(DateTime(-2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == -735_175);
+        assert(SysTime(DateTime(-2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == -735_174);
+        assert(SysTime(DateTime(-2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == -735_173);
+
+        // Start of Hebrew Calendar
+        assert(SysTime(DateTime(-3760, 9, 7, 0, 0, 0)).dayOfGregorianCal == -1_373_427);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.dayOfGregorianCal == 729_941);
+        //assert(ist.dayOfGregorianCal == 729_941);
+    }
+
+
+    //Test that the logic for the day of the Gregorian Calendar is consistent
+    //between Date and SysTime.
+    unittest
+    {
+        void test(Date date, SysTime st, size_t line = __LINE__)
+        {
+            import std.format : format;
+
+            if(date.dayOfGregorianCal != st.dayOfGregorianCal)
+            {
+                throw new AssertError(format("Date [%s] SysTime [%s]", date.dayOfGregorianCal, st.dayOfGregorianCal),
+                                      __FILE__, line);
+            }
+        }
+
+        //Test A.D.
+        test(Date(1, 1, 1), SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        test(Date(1, 1, 2), SysTime(DateTime(1, 1, 2, 0, 0, 0), hnsecs(500)));
+        test(Date(1, 2, 1), SysTime(DateTime(1, 2, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(2, 1, 1), SysTime(DateTime(2, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(3, 1, 1), SysTime(DateTime(3, 1, 1, 12, 13, 14)));
+        test(Date(4, 1, 1), SysTime(DateTime(4, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(5, 1, 1), SysTime(DateTime(5, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(50, 1, 1), SysTime(DateTime(50, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(97, 1, 1), SysTime(DateTime(97, 1, 1, 23, 59, 59)));
+        test(Date(100, 1, 1), SysTime(DateTime(100, 1, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(101, 1, 1), SysTime(DateTime(101, 1, 1, 23, 59, 59), hnsecs(50_000)));
+        test(Date(105, 1, 1), SysTime(DateTime(105, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(200, 1, 1), SysTime(DateTime(200, 1, 1, 0, 0, 0)));
+        test(Date(201, 1, 1), SysTime(DateTime(201, 1, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(300, 1, 1), SysTime(DateTime(300, 1, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(301, 1, 1), SysTime(DateTime(301, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(400, 1, 1), SysTime(DateTime(400, 1, 1, 12, 13, 14)));
+        test(Date(401, 1, 1), SysTime(DateTime(401, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(500, 1, 1), SysTime(DateTime(500, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(501, 1, 1), SysTime(DateTime(501, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(1000, 1, 1), SysTime(DateTime(1000, 1, 1, 23, 59, 59)));
+        test(Date(1001, 1, 1), SysTime(DateTime(1001, 1, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(1600, 1, 1), SysTime(DateTime(1600, 1, 1, 23, 59, 59), hnsecs(50_000)));
+        test(Date(1601, 1, 1), SysTime(DateTime(1601, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(1900, 1, 1), SysTime(DateTime(1900, 1, 1, 0, 0, 0)));
+        test(Date(1901, 1, 1), SysTime(DateTime(1901, 1, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(1945, 11, 12), SysTime(DateTime(1945, 11, 12, 0, 0, 0), hnsecs(50_000)));
+        test(Date(1999, 1, 1), SysTime(DateTime(1999, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(1999, 7, 6), SysTime(DateTime(1999, 7, 6, 12, 13, 14)));
+        test(Date(2000, 1, 1), SysTime(DateTime(2000, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(2001, 1, 1), SysTime(DateTime(2001, 1, 1, 12, 13, 14), hnsecs(50_000)));
+
+        test(Date(2010, 1, 1), SysTime(DateTime(2010, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(2010, 1, 31), SysTime(DateTime(2010, 1, 31, 23, 0, 0)));
+        test(Date(2010, 2, 1), SysTime(DateTime(2010, 2, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(2010, 2, 28), SysTime(DateTime(2010, 2, 28, 23, 59, 59), hnsecs(50_000)));
+        test(Date(2010, 3, 1), SysTime(DateTime(2010, 3, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(2010, 3, 31), SysTime(DateTime(2010, 3, 31, 0, 0, 0)));
+        test(Date(2010, 4, 1), SysTime(DateTime(2010, 4, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(2010, 4, 30), SysTime(DateTime(2010, 4, 30, 0, 0, 0), hnsecs(50_000)));
+        test(Date(2010, 5, 1), SysTime(DateTime(2010, 5, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(2010, 5, 31), SysTime(DateTime(2010, 5, 31, 12, 13, 14)));
+        test(Date(2010, 6, 1), SysTime(DateTime(2010, 6, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(2010, 6, 30), SysTime(DateTime(2010, 6, 30, 12, 13, 14), hnsecs(50_000)));
+        test(Date(2010, 7, 1), SysTime(DateTime(2010, 7, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(2010, 7, 31), SysTime(DateTime(2010, 7, 31, 23, 59, 59)));
+        test(Date(2010, 8, 1), SysTime(DateTime(2010, 8, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(2010, 8, 31), SysTime(DateTime(2010, 8, 31, 23, 59, 59), hnsecs(50_000)));
+        test(Date(2010, 9, 1), SysTime(DateTime(2010, 9, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(2010, 9, 30), SysTime(DateTime(2010, 9, 30, 12, 0, 0)));
+        test(Date(2010, 10, 1), SysTime(DateTime(2010, 10, 1, 0, 12, 0), hnsecs(500)));
+        test(Date(2010, 10, 31), SysTime(DateTime(2010, 10, 31, 0, 0, 12), hnsecs(50_000)));
+        test(Date(2010, 11, 1), SysTime(DateTime(2010, 11, 1, 23, 0, 0), hnsecs(9_999_999)));
+        test(Date(2010, 11, 30), SysTime(DateTime(2010, 11, 30, 0, 59, 0)));
+        test(Date(2010, 12, 1), SysTime(DateTime(2010, 12, 1, 0, 0, 59), hnsecs(500)));
+        test(Date(2010, 12, 31), SysTime(DateTime(2010, 12, 31, 0, 59, 59), hnsecs(50_000)));
+
+        test(Date(2012, 2, 1), SysTime(DateTime(2012, 2, 1, 23, 0, 59), hnsecs(9_999_999)));
+        test(Date(2012, 2, 28), SysTime(DateTime(2012, 2, 28, 23, 59, 0)));
+        test(Date(2012, 2, 29), SysTime(DateTime(2012, 2, 29, 7, 7, 7), hnsecs(7)));
+        test(Date(2012, 3, 1), SysTime(DateTime(2012, 3, 1, 7, 7, 7), hnsecs(7)));
+
+        //Test B.C.
+        test(Date(0, 12, 31), SysTime(DateTime(0, 12, 31, 0, 0, 0)));
+        test(Date(0, 12, 30), SysTime(DateTime(0, 12, 30, 0, 0, 0), hnsecs(500)));
+        test(Date(0, 12, 1), SysTime(DateTime(0, 12, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(0, 11, 30), SysTime(DateTime(0, 11, 30, 0, 0, 0), hnsecs(9_999_999)));
+
+        test(Date(-1, 12, 31), SysTime(DateTime(-1, 12, 31, 12, 13, 14)));
+        test(Date(-1, 12, 30), SysTime(DateTime(-1, 12, 30, 12, 13, 14), hnsecs(500)));
+        test(Date(-1, 1, 1), SysTime(DateTime(-1, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(-2, 12, 31), SysTime(DateTime(-2, 12, 31, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(-2, 1, 1), SysTime(DateTime(-2, 1, 1, 23, 59, 59)));
+        test(Date(-3, 12, 31), SysTime(DateTime(-3, 12, 31, 23, 59, 59), hnsecs(500)));
+        test(Date(-3, 1, 1), SysTime(DateTime(-3, 1, 1, 23, 59, 59), hnsecs(50_000)));
+        test(Date(-4, 12, 31), SysTime(DateTime(-4, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(-4, 1, 1), SysTime(DateTime(-4, 1, 1, 0, 0, 0)));
+        test(Date(-5, 12, 31), SysTime(DateTime(-5, 12, 31, 0, 0, 0), hnsecs(500)));
+        test(Date(-5, 1, 1), SysTime(DateTime(-5, 1, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(-9, 1, 1), SysTime(DateTime(-9, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+
+        test(Date(-49, 1, 1), SysTime(DateTime(-49, 1, 1, 12, 13, 14)));
+        test(Date(-50, 1, 1), SysTime(DateTime(-50, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(-97, 1, 1), SysTime(DateTime(-97, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(-99, 12, 31), SysTime(DateTime(-99, 12, 31, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(-99, 1, 1), SysTime(DateTime(-99, 1, 1, 23, 59, 59)));
+        test(Date(-100, 1, 1), SysTime(DateTime(-100, 1, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(-101, 1, 1), SysTime(DateTime(-101, 1, 1, 23, 59, 59), hnsecs(50_000)));
+        test(Date(-105, 1, 1), SysTime(DateTime(-105, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(-200, 1, 1), SysTime(DateTime(-200, 1, 1, 0, 0, 0)));
+        test(Date(-201, 1, 1), SysTime(DateTime(-201, 1, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(-300, 1, 1), SysTime(DateTime(-300, 1, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(-301, 1, 1), SysTime(DateTime(-301, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(-400, 12, 31), SysTime(DateTime(-400, 12, 31, 12, 13, 14)));
+        test(Date(-400, 1, 1), SysTime(DateTime(-400, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(-401, 1, 1), SysTime(DateTime(-401, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(-499, 1, 1), SysTime(DateTime(-499, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(-500, 1, 1), SysTime(DateTime(-500, 1, 1, 23, 59, 59)));
+        test(Date(-501, 1, 1), SysTime(DateTime(-501, 1, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(-1000, 1, 1), SysTime(DateTime(-1000, 1, 1, 23, 59, 59), hnsecs(50_000)));
+        test(Date(-1001, 1, 1), SysTime(DateTime(-1001, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(-1599, 1, 1), SysTime(DateTime(-1599, 1, 1, 0, 0, 0)));
+        test(Date(-1600, 12, 31), SysTime(DateTime(-1600, 12, 31, 0, 0, 0), hnsecs(500)));
+        test(Date(-1600, 1, 1), SysTime(DateTime(-1600, 1, 1, 0, 0, 0), hnsecs(50_000)));
+        test(Date(-1601, 1, 1), SysTime(DateTime(-1601, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(-1900, 1, 1), SysTime(DateTime(-1900, 1, 1, 12, 13, 14)));
+        test(Date(-1901, 1, 1), SysTime(DateTime(-1901, 1, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(-1999, 1, 1), SysTime(DateTime(-1999, 1, 1, 12, 13, 14), hnsecs(50_000)));
+        test(Date(-1999, 7, 6), SysTime(DateTime(-1999, 7, 6, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(-2000, 12, 31), SysTime(DateTime(-2000, 12, 31, 23, 59, 59)));
+        test(Date(-2000, 1, 1), SysTime(DateTime(-2000, 1, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(-2001, 1, 1), SysTime(DateTime(-2001, 1, 1, 23, 59, 59), hnsecs(50_000)));
+
+        test(Date(-2010, 1, 1), SysTime(DateTime(-2010, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(-2010, 1, 31), SysTime(DateTime(-2010, 1, 31, 0, 0, 0)));
+        test(Date(-2010, 2, 1), SysTime(DateTime(-2010, 2, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(-2010, 2, 28), SysTime(DateTime(-2010, 2, 28, 0, 0, 0), hnsecs(50_000)));
+        test(Date(-2010, 3, 1), SysTime(DateTime(-2010, 3, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(-2010, 3, 31), SysTime(DateTime(-2010, 3, 31, 12, 13, 14)));
+        test(Date(-2010, 4, 1), SysTime(DateTime(-2010, 4, 1, 12, 13, 14), hnsecs(500)));
+        test(Date(-2010, 4, 30), SysTime(DateTime(-2010, 4, 30, 12, 13, 14), hnsecs(50_000)));
+        test(Date(-2010, 5, 1), SysTime(DateTime(-2010, 5, 1, 12, 13, 14), hnsecs(9_999_999)));
+        test(Date(-2010, 5, 31), SysTime(DateTime(-2010, 5, 31, 23, 59, 59)));
+        test(Date(-2010, 6, 1), SysTime(DateTime(-2010, 6, 1, 23, 59, 59), hnsecs(500)));
+        test(Date(-2010, 6, 30), SysTime(DateTime(-2010, 6, 30, 23, 59, 59), hnsecs(50_000)));
+        test(Date(-2010, 7, 1), SysTime(DateTime(-2010, 7, 1, 23, 59, 59), hnsecs(9_999_999)));
+        test(Date(-2010, 7, 31), SysTime(DateTime(-2010, 7, 31, 0, 0, 0)));
+        test(Date(-2010, 8, 1), SysTime(DateTime(-2010, 8, 1, 0, 0, 0), hnsecs(500)));
+        test(Date(-2010, 8, 31), SysTime(DateTime(-2010, 8, 31, 0, 0, 0), hnsecs(50_000)));
+        test(Date(-2010, 9, 1), SysTime(DateTime(-2010, 9, 1, 0, 0, 0), hnsecs(9_999_999)));
+        test(Date(-2010, 9, 30), SysTime(DateTime(-2010, 9, 30, 12, 0, 0)));
+        test(Date(-2010, 10, 1), SysTime(DateTime(-2010, 10, 1, 0, 12, 0), hnsecs(500)));
+        test(Date(-2010, 10, 31), SysTime(DateTime(-2010, 10, 31, 0, 0, 12), hnsecs(50_000)));
+        test(Date(-2010, 11, 1), SysTime(DateTime(-2010, 11, 1, 23, 0, 0), hnsecs(9_999_999)));
+        test(Date(-2010, 11, 30), SysTime(DateTime(-2010, 11, 30, 0, 59, 0)));
+        test(Date(-2010, 12, 1), SysTime(DateTime(-2010, 12, 1, 0, 0, 59), hnsecs(500)));
+        test(Date(-2010, 12, 31), SysTime(DateTime(-2010, 12, 31, 0, 59, 59), hnsecs(50_000)));
+
+        test(Date(-2012, 2, 1), SysTime(DateTime(-2012, 2, 1, 23, 0, 59), hnsecs(9_999_999)));
+        test(Date(-2012, 2, 28), SysTime(DateTime(-2012, 2, 28, 23, 59, 0)));
+        test(Date(-2012, 2, 29), SysTime(DateTime(-2012, 2, 29, 7, 7, 7), hnsecs(7)));
+        test(Date(-2012, 3, 1), SysTime(DateTime(-2012, 3, 1, 7, 7, 7), hnsecs(7)));
+
+        test(Date(-3760, 9, 7), SysTime(DateTime(-3760, 9, 7, 0, 0, 0)));
+    }
+
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on.
+        Setting this property does not affect the time portion of $(LREF SysTime).
+
+        Params:
+            days = The day of the Gregorian Calendar to set this $(LREF SysTime)
+                   to.
+     +/
+    @property void dayOfGregorianCal(int days) @safe nothrow
+    {
+        auto hnsecs = adjTime;
+        hnsecs = removeUnitsFromHNSecs!"days"(hnsecs);
+
+        if(hnsecs < 0)
+            hnsecs += convert!("hours", "hnsecs")(24);
+
+        if(--days < 0)
+        {
+            hnsecs -= convert!("hours", "hnsecs")(24);
+            ++days;
+        }
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
+
+        adjTime = newDaysHNSecs + hnsecs;
+    }
+
+    ///
+    unittest
+    {
+        auto st = SysTime(DateTime(0, 1, 1, 12, 0, 0));
+        st.dayOfGregorianCal = 1;
+        assert(st == SysTime(DateTime(1, 1, 1, 12, 0, 0)));
+
+        st.dayOfGregorianCal = 365;
+        assert(st == SysTime(DateTime(1, 12, 31, 12, 0, 0)));
+
+        st.dayOfGregorianCal = 366;
+        assert(st == SysTime(DateTime(2, 1, 1, 12, 0, 0)));
+
+        st.dayOfGregorianCal = 0;
+        assert(st == SysTime(DateTime(0, 12, 31, 12, 0, 0)));
+
+        st.dayOfGregorianCal = -365;
+        assert(st == SysTime(DateTime(-0, 1, 1, 12, 0, 0)));
+
+        st.dayOfGregorianCal = -366;
+        assert(st == SysTime(DateTime(-1, 12, 31, 12, 0, 0)));
+
+        st.dayOfGregorianCal = 730_120;
+        assert(st == SysTime(DateTime(2000, 1, 1, 12, 0, 0)));
+
+        st.dayOfGregorianCal = 734_137;
+        assert(st == SysTime(DateTime(2010, 12, 31, 12, 0, 0)));
+    }
+
+    unittest
+    {
+        void testST(SysTime orig, int day, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+
+            orig.dayOfGregorianCal = day;
+            if(orig != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 1,
+               SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+
+        //Test B.C.
+        testST(SysTime(DateTime(0, 1, 1, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0)));
+        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 0,
+               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(1)), 0,
+               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1)));
+        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
+
+        //Test Both.
+        testST(SysTime(DateTime(-512, 7, 20, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
+        testST(SysTime(DateTime(-513, 6, 6, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
+        testST(SysTime(DateTime(-511, 5, 7, 23, 59, 59), hnsecs(9_999_999)), 1,
+               SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
+
+        testST(SysTime(DateTime(1607, 4, 8, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0)));
+        testST(SysTime(DateTime(1500, 3, 9, 23, 59, 59), hnsecs(9_999_999)), 0,
+               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+        testST(SysTime(DateTime(999, 2, 10, 23, 59, 59), hnsecs(1)), 0,
+               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1)));
+        testST(SysTime(DateTime(2007, 12, 11, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
+
+
+        auto st = SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212));
+
+        void testST2(int day, in SysTime expected, size_t line = __LINE__)
+        {
+            import std.format : format;
+
+            st.dayOfGregorianCal = day;
+            if(st != expected)
+                throw new AssertError(format("Failed. actual [%s] != expected [%s]", st, expected), __FILE__, line);
+        }
+
+        //Test A.D.
+        testST2(1, SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(2, SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212)));
+        testST2(32, SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212)));
+        testST2(366, SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(731, SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(1096, SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(1462, SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(17_898, SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(35_065, SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(36_160, SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(36_525, SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(37_986, SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(72_684, SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(73_049, SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(109_208, SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(109_573, SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(145_732, SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(146_098, SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(182_257, SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(182_622, SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(364_878, SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(365_243, SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(584_023, SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(584_389, SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(693_596, SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(693_961, SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(729_755, SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(730_120, SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(730_486, SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212)));
+
+        testST2(733_773, SysTime(DateTime(2010, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(733_803, SysTime(DateTime(2010, 1, 31, 12, 2, 9), msecs(212)));
+        testST2(733_804, SysTime(DateTime(2010, 2, 1, 12, 2, 9), msecs(212)));
+        testST2(733_831, SysTime(DateTime(2010, 2, 28, 12, 2, 9), msecs(212)));
+        testST2(733_832, SysTime(DateTime(2010, 3, 1, 12, 2, 9), msecs(212)));
+        testST2(733_862, SysTime(DateTime(2010, 3, 31, 12, 2, 9), msecs(212)));
+        testST2(733_863, SysTime(DateTime(2010, 4, 1, 12, 2, 9), msecs(212)));
+        testST2(733_892, SysTime(DateTime(2010, 4, 30, 12, 2, 9), msecs(212)));
+        testST2(733_893, SysTime(DateTime(2010, 5, 1, 12, 2, 9), msecs(212)));
+        testST2(733_923, SysTime(DateTime(2010, 5, 31, 12, 2, 9), msecs(212)));
+        testST2(733_924, SysTime(DateTime(2010, 6, 1, 12, 2, 9), msecs(212)));
+        testST2(733_953, SysTime(DateTime(2010, 6, 30, 12, 2, 9), msecs(212)));
+        testST2(733_954, SysTime(DateTime(2010, 7, 1, 12, 2, 9), msecs(212)));
+        testST2(733_984, SysTime(DateTime(2010, 7, 31, 12, 2, 9), msecs(212)));
+        testST2(733_985, SysTime(DateTime(2010, 8, 1, 12, 2, 9), msecs(212)));
+        testST2(734_015, SysTime(DateTime(2010, 8, 31, 12, 2, 9), msecs(212)));
+        testST2(734_016, SysTime(DateTime(2010, 9, 1, 12, 2, 9), msecs(212)));
+        testST2(734_045, SysTime(DateTime(2010, 9, 30, 12, 2, 9), msecs(212)));
+        testST2(734_046, SysTime(DateTime(2010, 10, 1, 12, 2, 9), msecs(212)));
+        testST2(734_076, SysTime(DateTime(2010, 10, 31, 12, 2, 9), msecs(212)));
+        testST2(734_077, SysTime(DateTime(2010, 11, 1, 12, 2, 9), msecs(212)));
+        testST2(734_106, SysTime(DateTime(2010, 11, 30, 12, 2, 9), msecs(212)));
+        testST2(734_107, SysTime(DateTime(2010, 12, 1, 12, 2, 9), msecs(212)));
+        testST2(734_137, SysTime(DateTime(2010, 12, 31, 12, 2, 9), msecs(212)));
+
+        testST2(734_534, SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212)));
+        testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212)));
+        testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212)));
+        testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212)));
+
+        testST2(734_534,  SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212)));
+
+        testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212)));
+        testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212)));
+        testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212)));
+
+        //Test B.C.
+        testST2(0, SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-1, SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212)));
+        testST2(-30, SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212)));
+        testST2(-31, SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212)));
+
+        testST2(-366, SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-367, SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212)));
+        testST2(-730, SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-731, SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-1095, SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-1096, SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-1460, SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-1461, SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-1826, SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-1827, SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-2191, SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-3652, SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212)));
+
+        testST2(-18_262, SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-18_627, SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-35_794, SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-36_160, SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-36_524, SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-36_889, SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-37_254, SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-38_715, SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-73_413, SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-73_778, SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-109_937, SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-110_302, SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-146_097, SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-146_462, SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-146_827, SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-182_621, SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-182_986, SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-183_351, SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-365_607, SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-365_972, SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-584_387, SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-584_388, SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-584_753, SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-585_118, SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-694_325, SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-694_690, SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-730_484, SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-730_485, SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212)));
+        testST2(-730_850, SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-731_215, SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212)));
+
+        testST2(-734_502, SysTime(DateTime(-2010, 1, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_472, SysTime(DateTime(-2010, 1, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_471, SysTime(DateTime(-2010, 2, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_444, SysTime(DateTime(-2010, 2, 28, 12, 2, 9), msecs(212)));
+        testST2(-734_443, SysTime(DateTime(-2010, 3, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_413, SysTime(DateTime(-2010, 3, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_412, SysTime(DateTime(-2010, 4, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_383, SysTime(DateTime(-2010, 4, 30, 12, 2, 9), msecs(212)));
+        testST2(-734_382, SysTime(DateTime(-2010, 5, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_352, SysTime(DateTime(-2010, 5, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_351, SysTime(DateTime(-2010, 6, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_322, SysTime(DateTime(-2010, 6, 30, 12, 2, 9), msecs(212)));
+        testST2(-734_321, SysTime(DateTime(-2010, 7, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_291, SysTime(DateTime(-2010, 7, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_290, SysTime(DateTime(-2010, 8, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_260, SysTime(DateTime(-2010, 8, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_259, SysTime(DateTime(-2010, 9, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_230, SysTime(DateTime(-2010, 9, 30, 12, 2, 9), msecs(212)));
+        testST2(-734_229, SysTime(DateTime(-2010, 10, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_199, SysTime(DateTime(-2010, 10, 31, 12, 2, 9), msecs(212)));
+        testST2(-734_198, SysTime(DateTime(-2010, 11, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_169, SysTime(DateTime(-2010, 11, 30, 12, 2, 9), msecs(212)));
+        testST2(-734_168, SysTime(DateTime(-2010, 12, 1, 12, 2, 9), msecs(212)));
+        testST2(-734_138, SysTime(DateTime(-2010, 12, 31, 12, 2, 9), msecs(212)));
+
+        testST2(-735_202, SysTime(DateTime(-2012, 2, 1, 12, 2, 9), msecs(212)));
+        testST2(-735_175, SysTime(DateTime(-2012, 2, 28, 12, 2, 9), msecs(212)));
+        testST2(-735_174, SysTime(DateTime(-2012, 2, 29, 12, 2, 9), msecs(212)));
+        testST2(-735_173, SysTime(DateTime(-2012, 3, 1, 12, 2, 9), msecs(212)));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        static assert(!__traits(compiles, cst.dayOfGregorianCal = 7));
+        //static assert(!__traits(compiles, ist.dayOfGregorianCal = 7));
+    }
+
+
+    /++
+        The ISO 8601 week of the year that this $(LREF SysTime) is in.
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/ISO_week_date, ISO Week Date).
+      +/
+    @property ubyte isoWeek() @safe const nothrow
+    {
+        return (cast(Date)this).isoWeek;
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st.isoWeek == 27);
+        assert(cst.isoWeek == 27);
+        //assert(ist.isoWeek == 27);
+    }
+
+
+    /++
+        $(LREF SysTime) for the last day in the month that this Date is in.
+        The time portion of endOfMonth is always 23:59:59.9999999.
+      +/
+    @property SysTime endOfMonth() @safe const nothrow
+    {
+        immutable hnsecs = adjTime;
+        immutable days = getUnitsFromHNSecs!"days"(hnsecs);
+
+        auto date = Date(cast(int)days + 1).endOfMonth;
+        auto newDays = date.dayOfGregorianCal - 1;
+        long theTimeHNSecs;
+
+        if(newDays < 0)
+        {
+            theTimeHNSecs = -1;
+            ++newDays;
+        }
+        else
+            theTimeHNSecs = convert!("days", "hnsecs")(1) - 1;
+
+        immutable newDaysHNSecs = convert!("days", "hnsecs")(newDays);
+
+        auto retval = SysTime(this._stdTime, this._timezone);
+        retval.adjTime = newDaysHNSecs + theTimeHNSecs;
+
+        return retval;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).endOfMonth ==
+               SysTime(DateTime(1999, 1, 31, 23, 59, 59),
+                       hnsecs(9_999_999)));
+
+        assert(SysTime(DateTime(1999, 2, 7, 19, 30, 0),
+                       msecs(24)).endOfMonth ==
+               SysTime(DateTime(1999, 2, 28, 23, 59, 59),
+                       hnsecs(9_999_999)));
+
+        assert(SysTime(DateTime(2000, 2, 7, 5, 12, 27),
+                       usecs(5203)).endOfMonth ==
+               SysTime(DateTime(2000, 2, 29, 23, 59, 59),
+                       hnsecs(9_999_999)));
+
+        assert(SysTime(DateTime(2000, 6, 4, 12, 22, 9),
+                       hnsecs(12345)).endOfMonth ==
+               SysTime(DateTime(2000, 6, 30, 23, 59, 59),
+                       hnsecs(9_999_999)));
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(Date(1999, 1, 1)).endOfMonth == SysTime(DateTime(1999, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 2, 1)).endOfMonth == SysTime(DateTime(1999, 2, 28, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(2000, 2, 1)).endOfMonth == SysTime(DateTime(2000, 2, 29, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 3, 1)).endOfMonth == SysTime(DateTime(1999, 3, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 4, 1)).endOfMonth == SysTime(DateTime(1999, 4, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 5, 1)).endOfMonth == SysTime(DateTime(1999, 5, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 6, 1)).endOfMonth == SysTime(DateTime(1999, 6, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 7, 1)).endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 8, 1)).endOfMonth == SysTime(DateTime(1999, 8, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 9, 1)).endOfMonth == SysTime(DateTime(1999, 9, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 10, 1)).endOfMonth == SysTime(DateTime(1999, 10, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 11, 1)).endOfMonth == SysTime(DateTime(1999, 11, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(1999, 12, 1)).endOfMonth == SysTime(DateTime(1999, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+
+        //Test B.C.
+        assert(SysTime(Date(-1999, 1, 1)).endOfMonth == SysTime(DateTime(-1999, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 2, 1)).endOfMonth == SysTime(DateTime(-1999, 2, 28, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-2000, 2, 1)).endOfMonth == SysTime(DateTime(-2000, 2, 29, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 3, 1)).endOfMonth == SysTime(DateTime(-1999, 3, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 4, 1)).endOfMonth == SysTime(DateTime(-1999, 4, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 5, 1)).endOfMonth == SysTime(DateTime(-1999, 5, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 6, 1)).endOfMonth == SysTime(DateTime(-1999, 6, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 7, 1)).endOfMonth == SysTime(DateTime(-1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 8, 1)).endOfMonth == SysTime(DateTime(-1999, 8, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 9, 1)).endOfMonth == SysTime(DateTime(-1999, 9, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 10, 1)).endOfMonth ==
+               SysTime(DateTime(-1999, 10, 31, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 11, 1)).endOfMonth ==
+               SysTime(DateTime(-1999, 11, 30, 23, 59, 59), hnsecs(9_999_999)));
+        assert(SysTime(Date(-1999, 12, 1)).endOfMonth ==
+               SysTime(DateTime(-1999, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
+        //assert(ist.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
+    }
+
+
+    /++
+        The last day in the month that this $(LREF SysTime) is in.
+      +/
+    @property ubyte daysInMonth() @safe const nothrow
+    {
+        return Date(dayOfGregorianCal).daysInMonth;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 2, 7, 19, 30, 0)).daysInMonth == 28);
+        assert(SysTime(DateTime(2000, 2, 7, 5, 12, 27)).daysInMonth == 29);
+        assert(SysTime(DateTime(2000, 6, 4, 12, 22, 9)).daysInMonth == 30);
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(DateTime(1999, 1, 1, 12, 1, 13)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 2, 1, 17, 13, 12)).daysInMonth == 28);
+        assert(SysTime(DateTime(2000, 2, 1, 13, 2, 12)).daysInMonth == 29);
+        assert(SysTime(DateTime(1999, 3, 1, 12, 13, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 4, 1, 12, 6, 13)).daysInMonth == 30);
+        assert(SysTime(DateTime(1999, 5, 1, 15, 13, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 6, 1, 13, 7, 12)).daysInMonth == 30);
+        assert(SysTime(DateTime(1999, 7, 1, 12, 13, 17)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 8, 1, 12, 3, 13)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 9, 1, 12, 13, 12)).daysInMonth == 30);
+        assert(SysTime(DateTime(1999, 10, 1, 13, 19, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(1999, 11, 1, 12, 13, 17)).daysInMonth == 30);
+        assert(SysTime(DateTime(1999, 12, 1, 12, 52, 13)).daysInMonth == 31);
+
+        //Test B.C.
+        assert(SysTime(DateTime(-1999, 1, 1, 12, 1, 13)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 2, 1, 7, 13, 12)).daysInMonth == 28);
+        assert(SysTime(DateTime(-2000, 2, 1, 13, 2, 12)).daysInMonth == 29);
+        assert(SysTime(DateTime(-1999, 3, 1, 12, 13, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 4, 1, 12, 6, 13)).daysInMonth == 30);
+        assert(SysTime(DateTime(-1999, 5, 1, 5, 13, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 6, 1, 13, 7, 12)).daysInMonth == 30);
+        assert(SysTime(DateTime(-1999, 7, 1, 12, 13, 17)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 8, 1, 12, 3, 13)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 9, 1, 12, 13, 12)).daysInMonth == 30);
+        assert(SysTime(DateTime(-1999, 10, 1, 13, 19, 12)).daysInMonth == 31);
+        assert(SysTime(DateTime(-1999, 11, 1, 12, 13, 17)).daysInMonth == 30);
+        assert(SysTime(DateTime(-1999, 12, 1, 12, 52, 13)).daysInMonth == 31);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.daysInMonth == 31);
+        //assert(ist.daysInMonth == 31);
+    }
+
+
+    /++
+        Whether the current year is a date in A.D.
+      +/
+    @property bool isAD() @safe const nothrow
+    {
+        return adjTime >= 0;
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(1, 1, 1, 12, 7, 0)).isAD);
+        assert(SysTime(DateTime(2010, 12, 31, 0, 0, 0)).isAD);
+        assert(!SysTime(DateTime(0, 12, 31, 23, 59, 59)).isAD);
+        assert(!SysTime(DateTime(-2010, 1, 1, 2, 2, 2)).isAD);
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(2010, 7, 4, 12, 0, 9)).isAD);
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).isAD);
+        assert(!SysTime(DateTime(0, 12, 31, 23, 59, 59)).isAD);
+        assert(!SysTime(DateTime(0, 1, 1, 23, 59, 59)).isAD);
+        assert(!SysTime(DateTime(-1, 1, 1, 23 ,59 ,59)).isAD);
+        assert(!SysTime(DateTime(-2010, 7, 4, 12, 2, 2)).isAD);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.isAD);
+        //assert(ist.isAD);
+    }
+
+
+    /++
+        The $(WEB en.wikipedia.org/wiki/Julian_day, Julian day)
+        for this $(LREF SysTime) at the given time. For example,
+        prior to noon, 1996-03-31 would be the Julian day number 2_450_173, so
+        this function returns 2_450_173, while from noon onward, the Julian
+        day number would be 2_450_174, so this function returns 2_450_174.
+      +/
+    @property long julianDay() @safe const nothrow
+    {
+        immutable jd = dayOfGregorianCal + 1_721_425;
+
+        return hour < 12 ? jd - 1 : jd;
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(-4713, 11, 24, 0, 0, 0)).julianDay == -1);
+        assert(SysTime(DateTime(-4713, 11, 24, 12, 0, 0)).julianDay == 0);
+
+        assert(SysTime(DateTime(0, 12, 31, 0, 0, 0)).julianDay == 1_721_424);
+        assert(SysTime(DateTime(0, 12, 31, 12, 0, 0)).julianDay == 1_721_425);
+
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).julianDay == 1_721_425);
+        assert(SysTime(DateTime(1, 1, 1, 12, 0, 0)).julianDay == 1_721_426);
+
+        assert(SysTime(DateTime(1582, 10, 15, 0, 0, 0)).julianDay == 2_299_160);
+        assert(SysTime(DateTime(1582, 10, 15, 12, 0, 0)).julianDay == 2_299_161);
+
+        assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).julianDay == 2_400_000);
+        assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).julianDay == 2_400_001);
+
+        assert(SysTime(DateTime(1982, 1, 4, 0, 0, 0)).julianDay == 2_444_973);
+        assert(SysTime(DateTime(1982, 1, 4, 12, 0, 0)).julianDay == 2_444_974);
+
+        assert(SysTime(DateTime(1996, 3, 31, 0, 0, 0)).julianDay == 2_450_173);
+        assert(SysTime(DateTime(1996, 3, 31, 12, 0, 0)).julianDay == 2_450_174);
+
+        assert(SysTime(DateTime(2010, 8, 24, 0, 0, 0)).julianDay == 2_455_432);
+        assert(SysTime(DateTime(2010, 8, 24, 12, 0, 0)).julianDay == 2_455_433);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.julianDay == 2_451_366);
+        //assert(ist.julianDay == 2_451_366);
+    }
+
+
+    /++
+        The modified $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for any time on this date (since, the modified
+        Julian day changes at midnight).
+      +/
+    @property long modJulianDay() @safe const nothrow
+    {
+        return (dayOfGregorianCal + 1_721_425) - 2_400_001;
+    }
+
+    unittest
+    {
+        assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).modJulianDay == 0);
+        assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).modJulianDay == 0);
+
+        assert(SysTime(DateTime(2010, 8, 24, 0, 0, 0)).modJulianDay == 55_432);
+        assert(SysTime(DateTime(2010, 8, 24, 12, 0, 0)).modJulianDay == 55_432);
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cst.modJulianDay == 51_365);
+        //assert(ist.modJulianDay == 51_365);
+    }
+
+
+    /++
+        Returns a $(LREF Date) equivalent to this $(LREF SysTime).
+      +/
+    Date opCast(T)() @safe const nothrow
+        if(is(Unqual!T == Date))
+    {
+        return Date(dayOfGregorianCal);
+    }
+
+    unittest
+    {
+        assert(cast(Date)SysTime(Date(1999, 7, 6)) == Date(1999, 7, 6));
+        assert(cast(Date)SysTime(Date(2000, 12, 31)) == Date(2000, 12, 31));
+        assert(cast(Date)SysTime(Date(2001, 1, 1)) == Date(2001, 1, 1));
+
+        assert(cast(Date)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == Date(1999, 7, 6));
+        assert(cast(Date)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == Date(2000, 12, 31));
+        assert(cast(Date)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == Date(2001, 1, 1));
+
+        assert(cast(Date)SysTime(Date(-1999, 7, 6)) == Date(-1999, 7, 6));
+        assert(cast(Date)SysTime(Date(-2000, 12, 31)) == Date(-2000, 12, 31));
+        assert(cast(Date)SysTime(Date(-2001, 1, 1)) == Date(-2001, 1, 1));
+
+        assert(cast(Date)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == Date(-1999, 7, 6));
+        assert(cast(Date)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == Date(-2000, 12, 31));
+        assert(cast(Date)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == Date(-2001, 1, 1));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(Date)cst != Date.init);
+        //assert(cast(Date)ist != Date.init);
+    }
+
+
+    /++
+        Returns a $(LREF DateTime) equivalent to this $(LREF SysTime).
+      +/
+    DateTime opCast(T)() @safe const nothrow
+        if(is(Unqual!T == DateTime))
+    {
+        try
+        {
+            auto hnsecs = adjTime;
+            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+            if(hnsecs < 0)
+            {
+                hnsecs += convert!("hours", "hnsecs")(24);
+                --days;
+            }
+
+            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);
+
+            return DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
+        }
+        catch(Exception e)
+            assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw.");
+    }
+
+    unittest
+    {
+        assert(cast(DateTime)SysTime(DateTime(1, 1, 6, 7, 12, 22)) == DateTime(1, 1, 6, 7, 12, 22));
+        assert(cast(DateTime)SysTime(DateTime(1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(1, 1, 6, 7, 12, 22));
+        assert(cast(DateTime)SysTime(Date(1999, 7, 6)) == DateTime(1999, 7, 6, 0, 0, 0));
+        assert(cast(DateTime)SysTime(Date(2000, 12, 31)) == DateTime(2000, 12, 31, 0, 0, 0));
+        assert(cast(DateTime)SysTime(Date(2001, 1, 1)) == DateTime(2001, 1, 1, 0, 0, 0));
+
+        assert(cast(DateTime)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == DateTime(1999, 7, 6, 12, 10, 9));
+        assert(cast(DateTime)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == DateTime(2000, 12, 31, 13, 11, 10));
+        assert(cast(DateTime)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == DateTime(2001, 1, 1, 14, 12, 11));
+
+        assert(cast(DateTime)SysTime(DateTime(-1, 1, 6, 7, 12, 22)) == DateTime(-1, 1, 6, 7, 12, 22));
+        assert(cast(DateTime)SysTime(DateTime(-1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(-1, 1, 6, 7, 12, 22));
+        assert(cast(DateTime)SysTime(Date(-1999, 7, 6)) == DateTime(-1999, 7, 6, 0, 0, 0));
+        assert(cast(DateTime)SysTime(Date(-2000, 12, 31)) == DateTime(-2000, 12, 31, 0, 0, 0));
+        assert(cast(DateTime)SysTime(Date(-2001, 1, 1)) == DateTime(-2001, 1, 1, 0, 0, 0));
+
+        assert(cast(DateTime)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == DateTime(-1999, 7, 6, 12, 10, 9));
+        assert(cast(DateTime)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == DateTime(-2000, 12, 31, 13, 11, 10));
+        assert(cast(DateTime)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == DateTime(-2001, 1, 1, 14, 12, 11));
+
+        assert(cast(DateTime)SysTime(DateTime(2011, 1, 13, 8, 17, 2), msecs(296), LocalTime()) ==
+               DateTime(2011, 1, 13, 8, 17, 2));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(DateTime)cst != DateTime.init);
+        //assert(cast(DateTime)ist != DateTime.init);
+    }
+
+
+    /++
+        Returns a $(LREF TimeOfDay) equivalent to this $(LREF SysTime).
+      +/
+    TimeOfDay opCast(T)() @safe const nothrow
+        if(is(Unqual!T == TimeOfDay))
+    {
+        try
+        {
+            auto hnsecs = adjTime;
+            hnsecs = removeUnitsFromHNSecs!"days"(hnsecs);
+
+            if(hnsecs < 0)
+                hnsecs += convert!("hours", "hnsecs")(24);
+
+            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);
+
+            return TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second);
+        }
+        catch(Exception e)
+            assert(0, "TimeOfDay's constructor threw.");
+    }
+
+    unittest
+    {
+        assert(cast(TimeOfDay)SysTime(Date(1999, 7, 6)) == TimeOfDay(0, 0, 0));
+        assert(cast(TimeOfDay)SysTime(Date(2000, 12, 31)) == TimeOfDay(0, 0, 0));
+        assert(cast(TimeOfDay)SysTime(Date(2001, 1, 1)) == TimeOfDay(0, 0, 0));
+
+        assert(cast(TimeOfDay)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9));
+        assert(cast(TimeOfDay)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10));
+        assert(cast(TimeOfDay)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11));
+
+        assert(cast(TimeOfDay)SysTime(Date(-1999, 7, 6)) == TimeOfDay(0, 0, 0));
+        assert(cast(TimeOfDay)SysTime(Date(-2000, 12, 31)) == TimeOfDay(0, 0, 0));
+        assert(cast(TimeOfDay)SysTime(Date(-2001, 1, 1)) == TimeOfDay(0, 0, 0));
+
+        assert(cast(TimeOfDay)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9));
+        assert(cast(TimeOfDay)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10));
+        assert(cast(TimeOfDay)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11));
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(TimeOfDay)cst != TimeOfDay.init);
+        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
+    }
+
+
+    //Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=4867 is fixed.
+    //This allows assignment from const(SysTime) to SysTime.
+    //It may be a good idea to keep it though, since casting from a type to itself
+    //should be allowed, and it doesn't work without this opCast() since opCast()
+    //has already been defined for other types.
+    SysTime opCast(T)() @safe const pure nothrow
+        if(is(Unqual!T == SysTime))
+    {
+        return SysTime(_stdTime, _timezone);
+    }
+
+
+    /++
+        Converts this $(LREF SysTime) to a string with the format
+        YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds and TZ is time
+        zone).
+
+        Note that the number of digits in the fractional seconds varies with the
+        number of fractional seconds. It's a maximum of 7 (which would be
+        hnsecs), but only has as many as are necessary to hold the correct value
+        (so no trailing zeroes), and if there are no fractional seconds, then
+        there is no decimal point.
+
+        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty.
+        If its time zone is $(D UTC), then it is "Z". Otherwise, it is the
+        offset from UTC (e.g. +0100 or -0700). Note that the offset from UTC
+        is $(I not) enough to uniquely identify the time zone.
+
+        Time zone offsets will be in the form +HHMM or -HHMM.
+
+        $(RED Warning:
+            Previously, toISOString did the same as $(LREF toISOExtString) and
+            generated +HH:MM or -HH:MM for the time zone when it was not
+            $(LREF LocalTime) or $(LREF UTC), which is not in conformance with
+            ISO 9601 for the non-extended string format. This has now been
+            fixed. However, for now, fromISOString will continue to accept the
+            extended format for the time zone so that any code which has been
+            writing out the result of toISOString to read in later will continue
+            to work.)
+      +/
+    string toISOString() @safe const nothrow
+    {
+        import std.format : format;
+        try
+        {
+            immutable adjustedTime = adjTime;
+            long hnsecs = adjustedTime;
+
+            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+            if(hnsecs < 0)
+            {
+                hnsecs += convert!("hours", "hnsecs")(24);
+                --days;
+            }
+
+            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
+            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is LocalTime())
+                return dateTime.toISOString() ~ fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is UTC())
+                return dateTime.toISOString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";
+
+            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);
+
+            return format("%s%s%s",
+                          dateTime.toISOString(),
+                          fracSecsToISOString(cast(int)hnsecs),
+                          SimpleTimeZone.toISOExtString(utcOffset));
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toISOString() ==
+               "20100704T070612");
+
+        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
+                       msecs(24)).toISOString() ==
+               "19981225T021500.024");
+
+        assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toISOString() ==
+               "00000105T230959");
+
+        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
+                       hnsecs(520_920)).toISOString() ==
+               "-00040105T000002.052092");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(DateTime.init, UTC()).toISOString() == "00010101T000000Z");
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOString() == "00010101T000000.0000001Z");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toISOString() == "00091204T000000");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toISOString() == "00991204T050612");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toISOString() == "09991204T134459");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toISOString() == "99990704T235959");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toISOString() == "+100001020T010101");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "00091204T000000.042");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "00991204T050612.1");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "09991204T134459.04502");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "99990704T235959.0000012");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "+100001020T010101.050789");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                                 new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOString() ==
+                        "20121221T121212-06:00");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                                 new immutable SimpleTimeZone(dur!"minutes"(420))).toISOString() ==
+                        "20121221T121212+07:00");
+
+        //Test B.C.
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOString() ==
+               "00001231T235959.9999999Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOString() == "00001231T235959.0000001Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOString() == "00001231T235959Z");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toISOString() == "00001204T001204");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toISOString() == "-00091204T000000");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toISOString() == "-00991204T050612");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toISOString() == "-09991204T134459");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toISOString() == "-99990704T235959");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toISOString() == "-100001020T010101");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOString() == "00001204T000000.007");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "-00091204T000000.042");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "-00991204T050612.1");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "-09991204T134459.04502");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "-99990704T235959.0000012");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "-100001020T010101.050789");
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(TimeOfDay)cst != TimeOfDay.init);
+        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
+    }
+
+
+
+    /++
+        Converts this $(LREF SysTime) to a string with the format
+        YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ
+        is the time zone).
+
+        Note that the number of digits in the fractional seconds varies with the
+        number of fractional seconds. It's a maximum of 7 (which would be
+        hnsecs), but only has as many as are necessary to hold the correct value
+        (so no trailing zeroes), and if there are no fractional seconds, then
+        there is no decimal point.
+
+        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty. If
+        its time zone is $(D UTC), then it is "Z". Otherwise, it is the offset
+        from UTC (e.g. +01:00 or -07:00). Note that the offset from UTC is
+        $(I not) enough to uniquely identify the time zone.
+
+        Time zone offsets will be in the form +HH:MM or -HH:MM.
+      +/
+    string toISOExtString() @safe const nothrow
+    {
+        import std.format : format;
+        try
+        {
+            immutable adjustedTime = adjTime;
+            long hnsecs = adjustedTime;
+
+            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+            if(hnsecs < 0)
+            {
+                hnsecs += convert!("hours", "hnsecs")(24);
+                --days;
+            }
+
+            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
+            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is LocalTime())
+                return dateTime.toISOExtString() ~ fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is UTC())
+                return dateTime.toISOExtString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";
+
+            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);
+
+            return format("%s%s%s",
+                          dateTime.toISOExtString(),
+                          fracSecsToISOString(cast(int)hnsecs),
+                          SimpleTimeZone.toISOExtString(utcOffset));
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toISOExtString() ==
+               "2010-07-04T07:06:12");
+
+        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
+                       msecs(24)).toISOExtString() ==
+               "1998-12-25T02:15:00.024");
+
+        assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toISOExtString() ==
+               "0000-01-05T23:09:59");
+
+        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
+                       hnsecs(520_920)).toISOExtString() ==
+               "-0004-01-05T00:00:02.052092");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(DateTime.init, UTC()).toISOExtString() == "0001-01-01T00:00:00Z");
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOExtString() ==
+               "0001-01-01T00:00:00.0000001Z");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toISOExtString() == "0009-12-04T00:00:00");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toISOExtString() == "0099-12-04T05:06:12");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toISOExtString() == "0999-12-04T13:44:59");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toISOExtString() == "9999-07-04T23:59:59");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toISOExtString() == "+10000-10-20T01:01:01");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "0009-12-04T00:00:00.042");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "0099-12-04T05:06:12.1");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() == "0999-12-04T13:44:59.04502");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() == "9999-07-04T23:59:59.0000012");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() ==
+               "+10000-10-20T01:01:01.050789");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                                new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOExtString() ==
+               "2012-12-21T12:12:12-06:00");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                       new immutable SimpleTimeZone(dur!"minutes"(420))).toISOExtString() ==
+               "2012-12-21T12:12:12+07:00");
+
+        //Test B.C.
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOExtString() ==
+               "0000-12-31T23:59:59.9999999Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOExtString() ==
+               "0000-12-31T23:59:59.0000001Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOExtString() == "0000-12-31T23:59:59Z");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toISOExtString() == "0000-12-04T00:12:04");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toISOExtString() == "-0009-12-04T00:00:00");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toISOExtString() == "-0099-12-04T05:06:12");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toISOExtString() == "-0999-12-04T13:44:59");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toISOExtString() == "-9999-07-04T23:59:59");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toISOExtString() == "-10000-10-20T01:01:01");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOExtString() == "0000-12-04T00:00:00.007");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "-0009-12-04T00:00:00.042");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "-0099-12-04T05:06:12.1");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() ==
+               "-0999-12-04T13:44:59.04502");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() ==
+               "-9999-07-04T23:59:59.0000012");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() ==
+               "-10000-10-20T01:01:01.050789");
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(TimeOfDay)cst != TimeOfDay.init);
+        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
+    }
+
+    /++
+        Converts this $(LREF SysTime) to a string with the format
+        YYYY-Mon-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ
+        is the time zone).
+
+        Note that the number of digits in the fractional seconds varies with the
+        number of fractional seconds. It's a maximum of 7 (which would be
+        hnsecs), but only has as many as are necessary to hold the correct value
+        (so no trailing zeroes), and if there are no fractional seconds, then
+        there is no decimal point.
+
+        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty. If
+        its time zone is $(D UTC), then it is "Z". Otherwise, it is the offset
+        from UTC (e.g. +01:00 or -07:00). Note that the offset from UTC is
+        $(I not) enough to uniquely identify the time zone.
+
+        Time zone offsets will be in the form +HH:MM or -HH:MM.
+      +/
+    string toSimpleString() @safe const nothrow
+    {
+        import std.format : format;
+        try
+        {
+            immutable adjustedTime = adjTime;
+            long hnsecs = adjustedTime;
+
+            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;
+
+            if(hnsecs < 0)
+            {
+                hnsecs += convert!("hours", "hnsecs")(24);
+                --days;
+            }
+
+            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
+            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
+            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
+            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is LocalTime())
+                return dateTime.toSimpleString() ~ fracSecsToISOString(cast(int)hnsecs);
+
+            if(_timezone is UTC())
+                return dateTime.toSimpleString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";
+
+            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);
+
+            return format("%s%s%s",
+                          dateTime.toSimpleString(),
+                          fracSecsToISOString(cast(int)hnsecs),
+                          SimpleTimeZone.toISOExtString(utcOffset));
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toSimpleString() ==
+               "2010-Jul-04 07:06:12");
+
+        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
+                       msecs(24)).toSimpleString() ==
+               "1998-Dec-25 02:15:00.024");
+
+        assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toSimpleString() ==
+               "0000-Jan-05 23:09:59");
+
+        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
+                       hnsecs(520_920)).toSimpleString() ==
+                "-0004-Jan-05 00:00:02.052092");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(SysTime(DateTime.init, UTC()).toString() == "0001-Jan-01 00:00:00Z");
+        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toString() == "0001-Jan-01 00:00:00.0000001Z");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toSimpleString() == "0009-Dec-04 00:00:00");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toSimpleString() == "0099-Dec-04 05:06:12");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toSimpleString() == "0999-Dec-04 13:44:59");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toSimpleString() == "9999-Jul-04 23:59:59");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toSimpleString() == "+10000-Oct-20 01:01:01");
+
+        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "0009-Dec-04 00:00:00.042");
+        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "0099-Dec-04 05:06:12.1");
+        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() ==
+               "0999-Dec-04 13:44:59.04502");
+        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() ==
+               "9999-Jul-04 23:59:59.0000012");
+        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() ==
+               "+10000-Oct-20 01:01:01.050789");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                       new immutable SimpleTimeZone(dur!"minutes"(-360))).toSimpleString() ==
+               "2012-Dec-21 12:12:12-06:00");
+
+        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
+                       new immutable SimpleTimeZone(dur!"minutes"(420))).toSimpleString() ==
+               "2012-Dec-21 12:12:12+07:00");
+
+        //Test B.C.
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toSimpleString() ==
+               "0000-Dec-31 23:59:59.9999999Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toSimpleString() ==
+               "0000-Dec-31 23:59:59.0000001Z");
+        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toSimpleString() == "0000-Dec-31 23:59:59Z");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toSimpleString() == "0000-Dec-04 00:12:04");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toSimpleString() == "-0009-Dec-04 00:00:00");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toSimpleString() == "-0099-Dec-04 05:06:12");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toSimpleString() == "-0999-Dec-04 13:44:59");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toSimpleString() == "-9999-Jul-04 23:59:59");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toSimpleString() == "-10000-Oct-20 01:01:01");
+
+        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toSimpleString() == "0000-Dec-04 00:00:00.007");
+        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "-0009-Dec-04 00:00:00.042");
+        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "-0099-Dec-04 05:06:12.1");
+        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() ==
+               "-0999-Dec-04 13:44:59.04502");
+        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() ==
+               "-9999-Jul-04 23:59:59.0000012");
+        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() ==
+               "-10000-Oct-20 01:01:01.050789");
+
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(cast(TimeOfDay)cst != TimeOfDay.init);
+        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
+    }
+
+
+    /++
+        Converts this $(LREF SysTime) to a string.
+      +/
+    string toString() @safe const nothrow
+    {
+        return toSimpleString();
+    }
+
+    unittest
+    {
+        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
+        assert(st.toString());
+        assert(cst.toString());
+        //assert(ist.toString());
+    }
+
+
+    /++
+        Creates a $(LREF SysTime) from a string with the format
+        YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds is the time
+        zone). Whitespace is stripped from the given string.
+
+        The exact format is exactly as described in $(D toISOString) except that
+        trailing zeroes are permitted - including having fractional seconds with
+        all zeroes. However, a decimal point with nothing following it is
+        invalid.
+
+        If there is no time zone in the string, then $(LREF LocalTime) is used.
+        If the time zone is "Z", then $(D UTC) is used. Otherwise, a
+        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
+        used. To get the returned $(LREF SysTime) to be a particular time
+        zone, pass in that time zone and the $(LREF SysTime) to be returned
+        will be converted to that time zone (though it will still be read in as
+        whatever time zone is in its string).
+
+        The accepted formats for time zone offsets are +HH, -HH, +HHMM, and
+        -HHMM.
+
+        $(RED Warning:
+            Previously, $(LREF toISOString) did the same as
+            $(LREF toISOExtString) and generated +HH:MM or -HH:MM for the time
+            zone when it was not $(LREF LocalTime) or $(LREF UTC), which is not
+            in conformance with ISO 9601 for the non-extended string format.
+            This has now been fixed. However, for now, fromISOString will
+            continue to accept the extended format for the time zone so that any
+            code which has been writing out the result of toISOString to read in
+            later will continue to work.)
+
+        Params:
+            isoString = A string formatted in the ISO format for dates and times.
+            tz        = The time zone to convert the given time to (no
+                        conversion occurs if null).
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO
+            format or if the resulting $(LREF SysTime) would not be valid.
+      +/
+    static SysTime fromISOString(S)(in S isoString, immutable TimeZone tz = null) @safe
+        if(isSomeString!S)
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : startsWith, find;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoString));
+        immutable skipFirst = dstr.startsWith('+', '-') != 0;
+
+        auto found = (skipFirst ? dstr[1..$] : dstr).find('.', 'Z', '+', '-');
+        auto dateTimeStr = dstr[0 .. $ - found[0].length];
+
+        dstring fracSecStr;
+        dstring zoneStr;
+
+        if(found[1] != 0)
+        {
+            if(found[1] == 1)
+            {
+                auto foundTZ = found[0].find('Z', '+', '-');
+
+                if(foundTZ[1] != 0)
+                {
+                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
+                    zoneStr = foundTZ[0];
+                }
+                else
+                    fracSecStr = found[0];
+            }
+            else
+                zoneStr = found[0];
+        }
+
+        try
+        {
+            auto dateTime = DateTime.fromISOString(dateTimeStr);
+            auto fracSec = fracSecsFromISOString(fracSecStr);
+            Rebindable!(immutable TimeZone) parsedZone;
+
+            if(zoneStr.empty)
+                parsedZone = LocalTime();
+            else if(zoneStr == "Z")
+                parsedZone = UTC();
+            else
+            {
+                try
+                    parsedZone = SimpleTimeZone.fromISOString(zoneStr);
+                catch(DateTimeException dte)
+                    parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);
+            }
+
+            auto retval = SysTime(dateTime, fracSec, parsedZone);
+
+            if(tz !is null)
+                retval.timezone = tz;
+
+            return retval;
+        }
+        catch(DateTimeException dte)
+            throw new DateTimeException(format("Invalid ISO String: %s", isoString));
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime.fromISOString("20100704T070612") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromISOString("19981225T021500.007") ==
+               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));
+
+        assert(SysTime.fromISOString("00000105T230959.00002") ==
+               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));
+
+        assert(SysTime.fromISOString("-00040105T000002") ==
+               SysTime(DateTime(-4, 1, 5, 0, 0, 2)));
+
+        assert(SysTime.fromISOString(" 20100704T070612 ") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromISOString("20100704T070612Z") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));
+
+        assert(SysTime.fromISOString("20100704T070612-0800") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(-8))));
+
+        assert(SysTime.fromISOString("20100704T070612+0800") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(8))));
+    }
+
+    unittest
+    {
+        import std.format: format;
+
+        foreach(str; ["", "20100704000000", "20100704 000000", "20100704t000000",
+                      "20100704T000000.", "20100704T000000.A", "20100704T000000.Z",
+                      "20100704T000000.00000000", "20100704T000000.00000000",
+                      "20100704T000000+", "20100704T000000-", "20100704T000000:",
+                      "20100704T000000-:", "20100704T000000+:", "20100704T000000-1:",
+                      "20100704T000000+1:", "20100704T000000+1:0",
+                      "20100704T000000-12.00", "20100704T000000+12.00",
+                      "20100704T000000-8", "20100704T000000+8",
+                      "20100704T000000-800", "20100704T000000+800",
+                      "20100704T000000-080", "20100704T000000+080",
+                      "20100704T000000-2400", "20100704T000000+2400",
+                      "20100704T000000-1260", "20100704T000000+1260",
+                      "20100704T000000.0-8", "20100704T000000.0+8",
+                      "20100704T000000.0-800", "20100704T000000.0+800",
+                      "20100704T000000.0-080", "20100704T000000.0+080",
+                      "20100704T000000.0-2400", "20100704T000000.0+2400",
+                      "20100704T000000.0-1260", "20100704T000000.0+1260",
+                      "20100704T000000-8:00", "20100704T000000+8:00",
+                      "20100704T000000-08:0", "20100704T000000+08:0",
+                      "20100704T000000-24:00", "20100704T000000+24:00",
+                      "20100704T000000-12:60", "20100704T000000+12:60",
+                      "20100704T000000.0-8:00", "20100704T000000.0+8:00",
+                      "20100704T000000.0-08:0", "20100704T000000.0+08:0",
+                      "20100704T000000.0-24:00", "20100704T000000.0+24:00",
+                      "20100704T000000.0-12:60", "20100704T000000.0+12:60",
+                      "2010-07-0400:00:00", "2010-07-04 00:00:00",
+                      "2010-07-04t00:00:00", "2010-07-04T00:00:00.",
+                      "2010-Jul-0400:00:00", "2010-Jul-04 00:00:00", "2010-Jul-04t00:00:00",
+                      "2010-Jul-04T00:00:00", "2010-Jul-04 00:00:00.",
+                      "2010-12-22T172201", "2010-Dec-22 17:22:01"])
+        {
+            assertThrown!DateTimeException(SysTime.fromISOString(str), format("[%s]", str));
+        }
+
+        static void test(string str, SysTime st, size_t line = __LINE__)
+        {
+            if(SysTime.fromISOString(str) != st)
+                throw new AssertError("unittest failure", __FILE__, line);
+        }
+
+        test("20101222T172201", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
+        test("19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("-19990706T123033", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        test("+019990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+
+        test("19070707T121212.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("19070707T121212.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("19070707T121212.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
+        test("19070707T121212.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("19070707T121212.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("19070707T121212.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+        test("19070707T121212.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+
+        auto west60 = new immutable SimpleTimeZone(hours(-1));
+        auto west90 = new immutable SimpleTimeZone(minutes(-90));
+        auto west480 = new immutable SimpleTimeZone(hours(-8));
+        auto east60 = new immutable SimpleTimeZone(hours(1));
+        auto east90 = new immutable SimpleTimeZone(minutes(90));
+        auto east480 = new immutable SimpleTimeZone(hours(8));
+
+        test("20101222T172201Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
+        test("20101222T172201-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("20101222T172201-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("20101222T172201-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
+        test("20101222T172201-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
+        test("20101222T172201+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("20101222T172201+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("20101222T172201+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("20101222T172201+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));
+
+        test("20101103T065106.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
+        test("20101222T172201.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
+        test("20101222T172201.23112-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
+        test("20101222T172201.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
+        test("20101222T172201.1-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
+        test("20101222T172201.55-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
+        test("20101222T172201.1234567+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
+        test("20101222T172201.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("20101222T172201.0000000+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("20101222T172201.45+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
+
+        // @@@DEPRECATED_2017-07@@@
+        // This isn't deprecated per se, but that text will make it so that it
+        // pops up when deprecations are moved along around July 2017. At that
+        // time, the notice on the documentation should be removed, and we may
+        // or may not change the behavior of fromISOString to no longer accept
+        // ISO extended time zones (the concern being that programs will have
+        // written out strings somewhere to read in again that they'll still be
+        // reading in for years to come and may not be able to fix, even if the
+        // code is fixed). If/when we do change the behavior, these tests will
+        // start failing and will need to be updated accordingly.
+        test("20101222T172201-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("20101222T172201-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
+        test("20101222T172201-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
+        test("20101222T172201+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("20101222T172201+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("20101222T172201+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));
+
+        test("20101222T172201.23112-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
+        test("20101222T172201.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
+        test("20101222T172201.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
+        test("20101222T172201.1234567+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
+        test("20101222T172201.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("20101222T172201.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
+    }
+
+
+    /++
+        Creates a $(LREF SysTime) from a string with the format
+        YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the
+        time zone). Whitespace is stripped from the given string.
+
+        The exact format is exactly as described in $(D toISOExtString)
+        except that trailing zeroes are permitted - including having fractional
+        seconds with all zeroes. However, a decimal point with nothing following
+        it is invalid.
+
+        If there is no time zone in the string, then $(LREF LocalTime) is used.
+        If the time zone is "Z", then $(D UTC) is used. Otherwise, a
+        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
+        used. To get the returned $(LREF SysTime) to be a particular time
+        zone, pass in that time zone and the $(LREF SysTime) to be returned
+        will be converted to that time zone (though it will still be read in as
+        whatever time zone is in its string).
+
+        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
+        -HH:MM.
+
+        Params:
+            isoExtString = A string formatted in the ISO Extended format for
+                           dates and times.
+            tz           = The time zone to convert the given time to (no
+                           conversion occurs if null).
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO
+            format or if the resulting $(LREF SysTime) would not be valid.
+      +/
+    static SysTime fromISOExtString(S)(in S isoExtString, immutable TimeZone tz = null) @safe
+        if(isSomeString!(S))
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : countUntil, find;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoExtString));
+
+        auto tIndex = dstr.countUntil('T');
+        enforce(tIndex != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        auto found = dstr[tIndex + 1 .. $].find('.', 'Z', '+', '-');
+        auto dateTimeStr = dstr[0 .. $ - found[0].length];
+
+        dstring fracSecStr;
+        dstring zoneStr;
+
+        if(found[1] != 0)
+        {
+            if(found[1] == 1)
+            {
+                auto foundTZ = found[0].find('Z', '+', '-');
+
+                if(foundTZ[1] != 0)
+                {
+                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
+                    zoneStr = foundTZ[0];
+                }
+                else
+                    fracSecStr = found[0];
+            }
+            else
+                zoneStr = found[0];
+        }
+
+        try
+        {
+            auto dateTime = DateTime.fromISOExtString(dateTimeStr);
+            auto fracSec = fracSecsFromISOString(fracSecStr);
+            Rebindable!(immutable TimeZone) parsedZone;
+
+            if(zoneStr.empty)
+                parsedZone = LocalTime();
+            else if(zoneStr == "Z")
+                parsedZone = UTC();
+            else
+                parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);
+
+            auto retval = SysTime(dateTime, fracSec, parsedZone);
+
+            if(tz !is null)
+                retval.timezone = tz;
+
+            return retval;
+        }
+        catch(DateTimeException dte)
+            throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString));
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime.fromISOExtString("2010-07-04T07:06:12") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromISOExtString("1998-12-25T02:15:00.007") ==
+               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));
+
+        assert(SysTime.fromISOExtString("0000-01-05T23:09:59.00002") ==
+               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));
+
+        assert(SysTime.fromISOExtString("-0004-01-05T00:00:02") ==
+               SysTime(DateTime(-4, 1, 5, 0, 0, 2)));
+
+        assert(SysTime.fromISOExtString(" 2010-07-04T07:06:12 ") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromISOExtString("2010-07-04T07:06:12Z") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));
+
+        assert(SysTime.fromISOExtString("2010-07-04T07:06:12-08:00") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(-8))));
+        assert(SysTime.fromISOExtString("2010-07-04T07:06:12+08:00") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(8))));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        foreach(str; ["", "20100704000000", "20100704 000000",
+                      "20100704t000000", "20100704T000000.", "20100704T000000.0",
+                      "2010-07:0400:00:00", "2010-07-04 00:00:00",
+                      "2010-07-04 00:00:00", "2010-07-04t00:00:00",
+                      "2010-07-04T00:00:00.", "2010-07-04T00:00:00.A", "2010-07-04T00:00:00.Z",
+                      "2010-07-04T00:00:00.00000000", "2010-07-04T00:00:00.00000000",
+                      "2010-07-04T00:00:00+", "2010-07-04T00:00:00-",
+                      "2010-07-04T00:00:00:", "2010-07-04T00:00:00-:", "2010-07-04T00:00:00+:",
+                      "2010-07-04T00:00:00-1:", "2010-07-04T00:00:00+1:", "2010-07-04T00:00:00+1:0",
+                      "2010-07-04T00:00:00-12.00", "2010-07-04T00:00:00+12.00",
+                      "2010-07-04T00:00:00-8", "2010-07-04T00:00:00+8",
+                      "20100704T000000-800", "20100704T000000+800",
+                      "20100704T000000-080", "20100704T000000+080",
+                      "20100704T000000-2400", "20100704T000000+2400",
+                      "20100704T000000-1260", "20100704T000000+1260",
+                      "20100704T000000.0-800", "20100704T000000.0+800",
+                      "20100704T000000.0-8", "20100704T000000.0+8",
+                      "20100704T000000.0-080", "20100704T000000.0+080",
+                      "20100704T000000.0-2400", "20100704T000000.0+2400",
+                      "20100704T000000.0-1260", "20100704T000000.0+1260",
+                      "2010-07-04T00:00:00-8:00", "2010-07-04T00:00:00+8:00",
+                      "2010-07-04T00:00:00-24:00", "2010-07-04T00:00:00+24:00",
+                      "2010-07-04T00:00:00-12:60", "2010-07-04T00:00:00+12:60",
+                      "2010-07-04T00:00:00.0-8:00", "2010-07-04T00:00:00.0+8:00",
+                      "2010-07-04T00:00:00.0-8", "2010-07-04T00:00:00.0+8",
+                      "2010-07-04T00:00:00.0-24:00", "2010-07-04T00:00:00.0+24:00",
+                      "2010-07-04T00:00:00.0-12:60", "2010-07-04T00:00:00.0+12:60",
+                      "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00",
+                      "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.0",
+                      "20101222T172201", "2010-Dec-22 17:22:01"])
+        {
+            assertThrown!DateTimeException(SysTime.fromISOExtString(str), format("[%s]", str));
+        }
+
+        static void test(string str, SysTime st, size_t line = __LINE__)
+        {
+            if(SysTime.fromISOExtString(str) != st)
+                throw new AssertError("unittest failure", __FILE__, line);
+        }
+
+        test("2010-12-22T17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
+        test("1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("-1999-07-06T12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        test("+01999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+
+        test("1907-07-07T12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("1907-07-07T12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("1907-07-07T12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
+        test("1907-07-07T12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("1907-07-07T12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("1907-07-07T12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+        test("1907-07-07T12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+
+        auto west60 = new immutable SimpleTimeZone(hours(-1));
+        auto west90 = new immutable SimpleTimeZone(minutes(-90));
+        auto west480 = new immutable SimpleTimeZone(hours(-8));
+        auto east60 = new immutable SimpleTimeZone(hours(1));
+        auto east90 = new immutable SimpleTimeZone(minutes(90));
+        auto east480 = new immutable SimpleTimeZone(hours(8));
+
+        test("2010-12-22T17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
+        test("2010-12-22T17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("2010-12-22T17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("2010-12-22T17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
+        test("2010-12-22T17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
+        test("2010-12-22T17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-12-22T17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-12-22T17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("2010-12-22T17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));
+
+        test("2010-11-03T06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
+        test("2010-12-22T17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
+        test("2010-12-22T17:22:01.23112-01:00",
+             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
+        test("2010-12-22T17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
+        test("2010-12-22T17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
+        test("2010-12-22T17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
+        test("2010-12-22T17:22:01.1234567+01:00",
+             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
+        test("2010-12-22T17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-12-22T17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("2010-12-22T17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
+    }
+
+
+    /++
+        Creates a $(LREF SysTime) from a string with the format
+        YYYY-MM-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the
+        time zone). Whitespace is stripped from the given string.
+
+        The exact format is exactly as described in $(D toSimpleString) except
+        that trailing zeroes are permitted - including having fractional seconds
+        with all zeroes. However, a decimal point with nothing following it is
+        invalid.
+
+        If there is no time zone in the string, then $(LREF LocalTime) is used. If
+        the time zone is "Z", then $(D UTC) is used. Otherwise, a
+        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
+        used. To get the returned $(LREF SysTime) to be a particular time
+        zone, pass in that time zone and the $(LREF SysTime) to be returned
+        will be converted to that time zone (though it will still be read in as
+        whatever time zone is in its string).
+
+        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
+        -HH:MM.
+
+        Params:
+            simpleString = A string formatted in the way that
+                           $(D toSimpleString) formats dates and times.
+            tz           = The time zone to convert the given time to (no
+                           conversion occurs if null).
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO format
+            or if the resulting $(LREF SysTime) would not be valid.
+      +/
+    static SysTime fromSimpleString(S)(in S simpleString, immutable TimeZone tz = null) @safe
+        if(isSomeString!(S))
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : countUntil, find;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(simpleString));
+
+        auto spaceIndex = dstr.countUntil(' ');
+        enforce(spaceIndex != -1, new DateTimeException(format("Invalid Simple String: %s", simpleString)));
+
+        auto found = dstr[spaceIndex + 1 .. $].find('.', 'Z', '+', '-');
+        auto dateTimeStr = dstr[0 .. $ - found[0].length];
+
+        dstring fracSecStr;
+        dstring zoneStr;
+
+        if(found[1] != 0)
+        {
+            if(found[1] == 1)
+            {
+                auto foundTZ = found[0].find('Z', '+', '-');
+
+                if(foundTZ[1] != 0)
+                {
+                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
+                    zoneStr = foundTZ[0];
+                }
+                else
+                    fracSecStr = found[0];
+            }
+            else
+                zoneStr = found[0];
+        }
+
+        try
+        {
+            auto dateTime = DateTime.fromSimpleString(dateTimeStr);
+            auto fracSec = fracSecsFromISOString(fracSecStr);
+            Rebindable!(immutable TimeZone) parsedZone;
+
+            if(zoneStr.empty)
+                parsedZone = LocalTime();
+            else if(zoneStr == "Z")
+                parsedZone = UTC();
+            else
+                parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);
+
+            auto retval = SysTime(dateTime, fracSec, parsedZone);
+
+            if(tz !is null)
+                retval.timezone = tz;
+
+            return retval;
+        }
+        catch(DateTimeException dte)
+            throw new DateTimeException(format("Invalid Simple String: %s", simpleString));
+    }
+
+    ///
+    unittest
+    {
+        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromSimpleString("1998-Dec-25 02:15:00.007") ==
+               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));
+
+        assert(SysTime.fromSimpleString("0000-Jan-05 23:09:59.00002") ==
+               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));
+
+        assert(SysTime.fromSimpleString("-0004-Jan-05 00:00:02") ==
+               SysTime(DateTime(-4, 1, 5, 0, 0, 2)));
+
+        assert(SysTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));
+
+        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12Z") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));
+
+        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12-08:00") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(-8))));
+
+        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12+08:00") ==
+               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
+                       new immutable SimpleTimeZone(hours(8))));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        foreach(str; ["", "20100704000000", "20100704 000000",
+                      "20100704t000000", "20100704T000000.", "20100704T000000.0",
+                      "2010-07-0400:00:00", "2010-07-04 00:00:00", "2010-07-04t00:00:00",
+                      "2010-07-04T00:00:00.", "2010-07-04T00:00:00.0",
+                      "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00", "2010-Jul-04T00:00:00",
+                      "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.A", "2010-Jul-04 00:00:00.Z",
+                      "2010-Jul-04 00:00:00.00000000", "2010-Jul-04 00:00:00.00000000",
+                      "2010-Jul-04 00:00:00+", "2010-Jul-04 00:00:00-",
+                      "2010-Jul-04 00:00:00:", "2010-Jul-04 00:00:00-:",
+                      "2010-Jul-04 00:00:00+:", "2010-Jul-04 00:00:00-1:",
+                      "2010-Jul-04 00:00:00+1:", "2010-Jul-04 00:00:00+1:0",
+                      "2010-Jul-04 00:00:00-12.00", "2010-Jul-04 00:00:00+12.00",
+                      "2010-Jul-04 00:00:00-8", "2010-Jul-04 00:00:00+8",
+                      "20100704T000000-800", "20100704T000000+800",
+                      "20100704T000000-080", "20100704T000000+080",
+                      "20100704T000000-2400", "20100704T000000+2400",
+                      "20100704T000000-1260", "20100704T000000+1260",
+                      "20100704T000000.0-800", "20100704T000000.0+800",
+                      "20100704T000000.0-8", "20100704T000000.0+8",
+                      "20100704T000000.0-080", "20100704T000000.0+080",
+                      "20100704T000000.0-2400", "20100704T000000.0+2400",
+                      "20100704T000000.0-1260", "20100704T000000.0+1260",
+                      "2010-Jul-04 00:00:00-8:00", "2010-Jul-04 00:00:00+8:00",
+                      "2010-Jul-04 00:00:00-08:0", "2010-Jul-04 00:00:00+08:0",
+                      "2010-Jul-04 00:00:00-24:00", "2010-Jul-04 00:00:00+24:00",
+                      "2010-Jul-04 00:00:00-12:60", "2010-Jul-04 00:00:00+24:60",
+                      "2010-Jul-04 00:00:00.0-8:00", "2010-Jul-04 00:00:00+8:00",
+                      "2010-Jul-04 00:00:00.0-8", "2010-Jul-04 00:00:00.0+8",
+                      "2010-Jul-04 00:00:00.0-08:0", "2010-Jul-04 00:00:00.0+08:0",
+                      "2010-Jul-04 00:00:00.0-24:00", "2010-Jul-04 00:00:00.0+24:00",
+                      "2010-Jul-04 00:00:00.0-12:60", "2010-Jul-04 00:00:00.0+24:60",
+                      "20101222T172201", "2010-12-22T172201"])
+        {
+            assertThrown!DateTimeException(SysTime.fromSimpleString(str), format("[%s]", str));
+        }
+
+        static void test(string str, SysTime st, size_t line = __LINE__)
+        {
+            if(SysTime.fromSimpleString(str) != st)
+                throw new AssertError("unittest failure", __FILE__, line);
+        }
+
+        test("2010-Dec-22 17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
+        test("1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("-1999-Jul-06 12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
+        test("+01999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test("1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+        test(" 1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
+
+        test("1907-Jul-07 12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("1907-Jul-07 12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
+        test("1907-Jul-07 12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
+        test("1907-Jul-07 12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("1907-Jul-07 12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
+        test("1907-Jul-07 12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+        test("1907-Jul-07 12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
+
+        auto west60 = new immutable SimpleTimeZone(hours(-1));
+        auto west90 = new immutable SimpleTimeZone(minutes(-90));
+        auto west480 = new immutable SimpleTimeZone(hours(-8));
+        auto east60 = new immutable SimpleTimeZone(hours(1));
+        auto east90 = new immutable SimpleTimeZone(minutes(90));
+        auto east480 = new immutable SimpleTimeZone(hours(8));
+
+        test("2010-Dec-22 17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
+        test("2010-Dec-22 17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("2010-Dec-22 17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
+        test("2010-Dec-22 17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
+        test("2010-Dec-22 17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
+        test("2010-Dec-22 17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-Dec-22 17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-Dec-22 17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("2010-Dec-22 17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));
+
+        test("2010-Nov-03 06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
+        test("2010-Dec-22 17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
+        test("2010-Dec-22 17:22:01.23112-01:00",
+             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
+        test("2010-Dec-22 17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
+        test("2010-Dec-22 17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
+        test("2010-Dec-22 17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
+        test("2010-Dec-22 17:22:01.1234567+01:00",
+            SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
+        test("2010-Dec-22 17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
+        test("2010-Dec-22 17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
+        test("2010-Dec-22 17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
+    }
+
+
+    /++
+        Returns the $(LREF SysTime) farthest in the past which is representable
+        by $(LREF SysTime).
+
+        The $(LREF SysTime) which is returned is in UTC.
+      +/
+    @property static SysTime min() @safe pure nothrow
+    {
+        return SysTime(long.min, UTC());
+    }
+
+    unittest
+    {
+        assert(SysTime.min.year < 0);
+        assert(SysTime.min < SysTime.max);
+    }
+
+
+    /++
+        Returns the $(LREF SysTime) farthest in the future which is representable
+        by $(LREF SysTime).
+
+        The $(LREF SysTime) which is returned is in UTC.
+      +/
+    @property static SysTime max() @safe pure nothrow
+    {
+        return SysTime(long.max, UTC());
+    }
+
+    unittest
+    {
+        assert(SysTime.max.year > 0);
+        assert(SysTime.max > SysTime.min);
+    }
+
+
+private:
+
+    /+
+        Returns $(D stdTime) converted to $(LREF SysTime)'s time zone.
+      +/
+    @property long adjTime() @safe const nothrow
+    {
+        return _timezone.utcToTZ(_stdTime);
+    }
+
+
+    /+
+        Converts the given hnsecs from $(LREF SysTime)'s time zone to std time.
+      +/
+    @property void adjTime(long adjTime) @safe nothrow
+    {
+        _stdTime = _timezone.tzToUTC(adjTime);
+    }
+
+
+    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5058
+    /+
+    invariant()
+    {
+        assert(_timezone !is null, "Invariant Failure: timezone is null. Were you foolish enough to use SysTime.init? (since timezone for SysTime.init can't be set at compile time).");
+    }
+    +/
+
+
+    long  _stdTime;
+    Rebindable!(immutable TimeZone) _timezone;
+}
+
+
+/++
+    Represents a date in the
+    $(WEB en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic Gregorian Calendar)
+    ranging from
+    32,768 B.C. to 32,767 A.D. Positive years are A.D. Non-positive years are
+    B.C.
+
+    Year, month, and day are kept separately internally so that $(D Date) is
+    optimized for calendar-based operations.
+
+    $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian
+    leap year calculations for its entire length. As per
+    $(WEB en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as
+    year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C. as
+    a positive integer with 1 B.C. being the year prior to 1 A.D.
+
+    Year 0 is a leap year.
+ +/
+struct Date
+{
+public:
+
+    /++
+        Throws:
+            $(LREF DateTimeException) if the resulting $(LREF Date) would not be valid.
+
+        Params:
+            year  = Year of the Gregorian Calendar. Positive values are A.D.
+                    Non-positive values are B.C. with year 0 being the year
+                    prior to 1 A.D.
+            month = Month of the year.
+            day   = Day of the month.
+     +/
+    this(int year, int month, int day) @safe pure
+    {
+        enforceValid!"months"(cast(Month)month);
+        enforceValid!"days"(year, cast(Month)month, day);
+
+        _year  = cast(short)year;
+        _month = cast(Month)month;
+        _day   = cast(ubyte)day;
+    }
+
+    unittest
+    {
+        assert(Date(1, 1, 1) == Date.init);
+
+        static void testDate(in Date date, int year, int month, int day)
+        {
+            assert(date._year == year);
+            assert(date._month == month);
+            assert(date._day == day);
+        }
+
+        testDate(Date(1999, 1 , 1), 1999, Month.jan, 1);
+        testDate(Date(1999, 7 , 1), 1999, Month.jul, 1);
+        testDate(Date(1999, 7 , 6), 1999, Month.jul, 6);
+
+        //Test A.D.
+        assertThrown!DateTimeException(Date(1, 0, 1));
+        assertThrown!DateTimeException(Date(1, 1, 0));
+        assertThrown!DateTimeException(Date(1999, 13, 1));
+        assertThrown!DateTimeException(Date(1999, 1, 32));
+        assertThrown!DateTimeException(Date(1999, 2, 29));
+        assertThrown!DateTimeException(Date(2000, 2, 30));
+        assertThrown!DateTimeException(Date(1999, 3, 32));
+        assertThrown!DateTimeException(Date(1999, 4, 31));
+        assertThrown!DateTimeException(Date(1999, 5, 32));
+        assertThrown!DateTimeException(Date(1999, 6, 31));
+        assertThrown!DateTimeException(Date(1999, 7, 32));
+        assertThrown!DateTimeException(Date(1999, 8, 32));
+        assertThrown!DateTimeException(Date(1999, 9, 31));
+        assertThrown!DateTimeException(Date(1999, 10, 32));
+        assertThrown!DateTimeException(Date(1999, 11, 31));
+        assertThrown!DateTimeException(Date(1999, 12, 32));
+
+        assertNotThrown!DateTimeException(Date(1999, 1, 31));
+        assertNotThrown!DateTimeException(Date(1999, 2, 28));
+        assertNotThrown!DateTimeException(Date(2000, 2, 29));
+        assertNotThrown!DateTimeException(Date(1999, 3, 31));
+        assertNotThrown!DateTimeException(Date(1999, 4, 30));
+        assertNotThrown!DateTimeException(Date(1999, 5, 31));
+        assertNotThrown!DateTimeException(Date(1999, 6, 30));
+        assertNotThrown!DateTimeException(Date(1999, 7, 31));
+        assertNotThrown!DateTimeException(Date(1999, 8, 31));
+        assertNotThrown!DateTimeException(Date(1999, 9, 30));
+        assertNotThrown!DateTimeException(Date(1999, 10, 31));
+        assertNotThrown!DateTimeException(Date(1999, 11, 30));
+        assertNotThrown!DateTimeException(Date(1999, 12, 31));
+
+        //Test B.C.
+        assertNotThrown!DateTimeException(Date(0, 1, 1));
+        assertNotThrown!DateTimeException(Date(-1, 1, 1));
+        assertNotThrown!DateTimeException(Date(-1, 12, 31));
+        assertNotThrown!DateTimeException(Date(-1, 2, 28));
+        assertNotThrown!DateTimeException(Date(-4, 2, 29));
+
+        assertThrown!DateTimeException(Date(-1, 2, 29));
+        assertThrown!DateTimeException(Date(-2, 2, 29));
+        assertThrown!DateTimeException(Date(-3, 2, 29));
+    }
+
+
+    /++
+        Params:
+            day = The Xth day of the Gregorian Calendar that the constructed
+                  $(LREF Date) will be for.
+     +/
+    this(int day) @safe pure nothrow
+    {
+        if(day > 0)
+        {
+            int years = (day / daysIn400Years) * 400 + 1;
+            day %= daysIn400Years;
+
+            {
+                immutable tempYears = day / daysIn100Years;
+
+                if(tempYears == 4)
+                {
+                    years += 300;
+                    day -= daysIn100Years * 3;
+                }
+                else
+                {
+                    years += tempYears * 100;
+                    day %= daysIn100Years;
+                }
+            }
+
+            years += (day / daysIn4Years) * 4;
+            day %= daysIn4Years;
+
+            {
+                immutable tempYears = day / daysInYear;
+
+                if(tempYears == 4)
+                {
+                    years += 3;
+                    day -= daysInYear * 3;
+                }
+                else
+                {
+                    years += tempYears;
+                    day %= daysInYear;
+                }
+            }
+
+            if(day == 0)
+            {
+                _year = cast(short)(years - 1);
+                _month = Month.dec;
+                _day = 31;
+            }
+            else
+            {
+                _year = cast(short)years;
+
+                try
+                    dayOfYear = day;
+                catch(Exception e)
+                    assert(0, "dayOfYear assignment threw.");
+            }
+        }
+        else if(day <= 0 && -day < daysInLeapYear)
+        {
+            _year = 0;
+
+            try
+                dayOfYear = (daysInLeapYear + day);
+            catch(Exception e)
+                assert(0, "dayOfYear assignment threw.");
+        }
+        else
+        {
+            day += daysInLeapYear - 1;
+            int years = (day / daysIn400Years) * 400 - 1;
+            day %= daysIn400Years;
+
+            {
+                immutable tempYears = day / daysIn100Years;
+
+                if(tempYears == -4)
+                {
+                    years -= 300;
+                    day += daysIn100Years * 3;
+                }
+                else
+                {
+                    years += tempYears * 100;
+                    day %= daysIn100Years;
+                }
+            }
+
+            years += (day / daysIn4Years) * 4;
+            day %= daysIn4Years;
+
+            {
+                immutable tempYears = day / daysInYear;
+
+                if(tempYears == -4)
+                {
+                    years -= 3;
+                    day += daysInYear * 3;
+                }
+                else
+                {
+                    years += tempYears;
+                    day %= daysInYear;
+                }
+            }
+
+            if(day == 0)
+            {
+                _year = cast(short)(years + 1);
+                _month = Month.jan;
+                _day = 1;
+            }
+            else
+            {
+                _year = cast(short)years;
+                immutable newDoY = (yearIsLeapYear(_year) ? daysInLeapYear : daysInYear) + day + 1;
+
+                try
+                    dayOfYear = newDoY;
+                catch(Exception e)
+                    assert(0, "dayOfYear assignment threw.");
+            }
+        }
+    }
+
+    unittest
+    {
+        import std.range;
+
+        //Test A.D.
+        foreach(gd; chain(testGregDaysBC, testGregDaysAD))
+            assert(Date(gd.day) == gd.date);
+    }
+
+
+    /++
+        Compares this $(LREF Date) with the given $(LREF Date).
+
+        Returns:
+            $(BOOKTABLE,
+            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+            $(TR $(TD this == rhs) $(TD 0))
+            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+            )
+     +/
+    int opCmp(in Date rhs) @safe const pure nothrow
+    {
+        if(_year < rhs._year)
+            return -1;
+        if(_year > rhs._year)
+            return 1;
+
+        if(_month < rhs._month)
+            return -1;
+        if(_month > rhs._month)
+            return 1;
+
+        if(_day < rhs._day)
+            return -1;
+        if(_day > rhs._day)
+            return 1;
+
+        return 0;
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(1, 1, 1).opCmp(Date.init) == 0);
+
+        assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0);
+        assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0);
+        assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0);
+
+        assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0);
+        assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0);
+
+        assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0);
+
+        assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0);
+        assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0);
+        assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0);
+        assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0);
+        assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0);
+        assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0);
+
+        assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0);
+        assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
+        assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0);
+        assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0);
+        assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0);
+        assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
+
+        //Test B.C.
+        assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0);
+        assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0);
+        assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0);
+        assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0);
+
+        assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0);
+        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0);
+
+        assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0);
+
+        assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0);
+        assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0);
+        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0);
+        assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0);
+        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
+        assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0);
+
+        assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0);
+        assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0);
+        assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
+        assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0);
+        assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0);
+        assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0);
+
+        //Test Both
+        assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0);
+        assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0);
+
+        assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0);
+        assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0);
+
+        assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0);
+        assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0);
+
+        assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0);
+        assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0);
+
+        assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0);
+        assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0);
+
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date.opCmp(date) == 0);
+        assert(date.opCmp(cdate) == 0);
+        assert(date.opCmp(idate) == 0);
+        assert(cdate.opCmp(date) == 0);
+        assert(cdate.opCmp(cdate) == 0);
+        assert(cdate.opCmp(idate) == 0);
+        assert(idate.opCmp(date) == 0);
+        assert(idate.opCmp(cdate) == 0);
+        assert(idate.opCmp(idate) == 0);
+    }
+
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+     +/
+    @property short year() @safe const pure nothrow
+    {
+        return _year;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 7, 6).year == 1999);
+        assert(Date(2010, 10, 4).year == 2010);
+        assert(Date(-7, 4, 5).year == -7);
+    }
+
+    unittest
+    {
+        assert(Date.init.year == 1);
+        assert(Date(1999, 7, 6).year == 1999);
+        assert(Date(-1999, 7, 6).year == -1999);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.year == 1999);
+        assert(idate.year == 1999);
+    }
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+
+        Params:
+            year = The year to set this Date's year to.
+
+        Throws:
+            $(LREF DateTimeException) if the new year is not a leap year and the
+            resulting date would be on February 29th.
+     +/
+    @property void year(int year) @safe pure
+    {
+        enforceValid!"days"(year, _month, _day);
+        _year = cast(short)year;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 7, 6).year == 1999);
+        assert(Date(2010, 10, 4).year == 2010);
+        assert(Date(-7, 4, 5).year == -7);
+    }
+
+    unittest
+    {
+        static void testDateInvalid(Date date, int year)
+        {
+            date.year = year;
+        }
+
+        static void testDate(Date date, int year, in Date expected)
+        {
+            date.year = year;
+            assert(date == expected);
+        }
+
+        assertThrown!DateTimeException(testDateInvalid(Date(4, 2, 29), 1));
+
+        testDate(Date(1, 1, 1), 1999, Date(1999, 1, 1));
+        testDate(Date(1, 1, 1), 0, Date(0, 1, 1));
+        testDate(Date(1, 1, 1), -1999, Date(-1999, 1, 1));
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.year = 1999));
+        static assert(!__traits(compiles, idate.year = 1999));
+    }
+
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Throws:
+            $(LREF DateTimeException) if $(D isAD) is true.
+     +/
+    @property ushort yearBC() @safe const pure
+    {
+        import std.format : format;
+
+        if(isAD)
+            throw new DateTimeException(format("Year %s is A.D.", _year));
+        return cast(ushort)((_year * -1) + 1);
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(0, 1, 1).yearBC == 1);
+        assert(Date(-1, 1, 1).yearBC == 2);
+        assert(Date(-100, 1, 1).yearBC == 101);
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((in Date date){date.yearBC;}(Date(1, 1, 1)));
+
+        auto date = Date(0, 7, 6);
+        const cdate = Date(0, 7, 6);
+        immutable idate = Date(0, 7, 6);
+        assert(date.yearBC == 1);
+        assert(cdate.yearBC == 1);
+        assert(idate.yearBC == 1);
+    }
+
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Params:
+            year = The year B.C. to set this $(LREF Date)'s year to.
+
+        Throws:
+            $(LREF DateTimeException) if a non-positive value is given.
+     +/
+    @property void yearBC(int year) @safe pure
+    {
+        if(year <= 0)
+            throw new DateTimeException("The given year is not a year B.C.");
+
+        _year = cast(short)((year - 1) * -1);
+    }
+
+    ///
+    unittest
+    {
+        auto date = Date(2010, 1, 1);
+        date.yearBC = 1;
+        assert(date == Date(0, 1, 1));
+
+        date.yearBC = 10;
+        assert(date == Date(-9, 1, 1));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((Date date){date.yearBC = -1;}(Date(1, 1, 1)));
+
+        auto date = Date(0, 7, 6);
+        const cdate = Date(0, 7, 6);
+        immutable idate = Date(0, 7, 6);
+        date.yearBC = 7;
+        assert(date.yearBC == 7);
+        static assert(!__traits(compiles, cdate.yearBC = 7));
+        static assert(!__traits(compiles, idate.yearBC = 7));
+    }
+
+
+    /++
+        Month of a Gregorian Year.
+     +/
+    @property Month month() @safe const pure nothrow
+    {
+        return _month;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 7, 6).month == 7);
+        assert(Date(2010, 10, 4).month == 10);
+        assert(Date(-7, 4, 5).month == 4);
+    }
+
+    unittest
+    {
+        assert(Date.init.month == 1);
+        assert(Date(1999, 7, 6).month == 7);
+        assert(Date(-1999, 7, 6).month == 7);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.month == 7);
+        assert(idate.month == 7);
+    }
+
+    /++
+        Month of a Gregorian Year.
+
+        Params:
+            month = The month to set this $(LREF Date)'s month to.
+
+        Throws:
+            $(LREF DateTimeException) if the given month is not a valid month or if
+            the current day would not be valid in the given month.
+     +/
+    @property void month(Month month) @safe pure
+    {
+        enforceValid!"months"(month);
+        enforceValid!"days"(_year, month, _day);
+        _month = cast(Month)month;
+    }
+
+    unittest
+    {
+        static void testDate(Date date, Month month, in Date expected = Date.init)
+        {
+            date.month = month;
+            assert(expected != Date.init);
+            assert(date == expected);
+        }
+
+        assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month)0));
+        assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month)13));
+        assertThrown!DateTimeException(testDate(Date(1, 1, 29), cast(Month)2));
+        assertThrown!DateTimeException(testDate(Date(0, 1, 30), cast(Month)2));
+
+        testDate(Date(1, 1, 1), cast(Month)7, Date(1, 7, 1));
+        testDate(Date(-1, 1, 1), cast(Month)7, Date(-1, 7, 1));
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.month = 7));
+        static assert(!__traits(compiles, idate.month = 7));
+    }
+
+
+    /++
+        Day of a Gregorian Month.
+     +/
+    @property ubyte day() @safe const pure nothrow
+    {
+        return _day;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 7, 6).day == 6);
+        assert(Date(2010, 10, 4).day == 4);
+        assert(Date(-7, 4, 5).day == 5);
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(Date date, int expected)
+        {
+            assert(date.day == expected,
+                             format("Value given: %s", date));
+        }
+
+        foreach(year; chain(testYearsBC, testYearsAD))
+        {
+            foreach(md; testMonthDays)
+                test(Date(year, md.month, md.day), md.day);
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.day == 6);
+        assert(idate.day == 6);
+    }
+
+    /++
+        Day of a Gregorian Month.
+
+        Params:
+            day = The day of the month to set this $(LREF Date)'s day to.
+
+        Throws:
+            $(LREF DateTimeException) if the given day is not a valid day of the
+            current month.
+     +/
+    @property void day(int day) @safe pure
+    {
+        enforceValid!"days"(_year, _month, day);
+        _day = cast(ubyte)day;
+    }
+
+    unittest
+    {
+        static void testDate(Date date, int day)
+        {
+            date.day = day;
+        }
+
+        //Test A.D.
+        assertThrown!DateTimeException(testDate(Date(1, 1, 1), 0));
+        assertThrown!DateTimeException(testDate(Date(1, 1, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 2, 1), 29));
+        assertThrown!DateTimeException(testDate(Date(4, 2, 1), 30));
+        assertThrown!DateTimeException(testDate(Date(1, 3, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 4, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(1, 5, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 6, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(1, 7, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 8, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 9, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(1, 10, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(1, 11, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(1, 12, 1), 32));
+
+        assertNotThrown!DateTimeException(testDate(Date(1, 1, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 2, 1), 28));
+        assertNotThrown!DateTimeException(testDate(Date(4, 2, 1), 29));
+        assertNotThrown!DateTimeException(testDate(Date(1, 3, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 4, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(1, 5, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 6, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(1, 7, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 8, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 9, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(1, 10, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(1, 11, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(1, 12, 1), 31));
+
+        {
+            auto date = Date(1, 1, 1);
+            date.day = 6;
+            assert(date == Date(1, 1, 6));
+        }
+
+        //Test B.C.
+        assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 0));
+        assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 2, 1), 29));
+        assertThrown!DateTimeException(testDate(Date(0, 2, 1), 30));
+        assertThrown!DateTimeException(testDate(Date(-1, 3, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 4, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(-1, 5, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 6, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(-1, 7, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 8, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 9, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(-1, 10, 1), 32));
+        assertThrown!DateTimeException(testDate(Date(-1, 11, 1), 31));
+        assertThrown!DateTimeException(testDate(Date(-1, 12, 1), 32));
+
+        assertNotThrown!DateTimeException(testDate(Date(-1, 1, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 2, 1), 28));
+        assertNotThrown!DateTimeException(testDate(Date(0, 2, 1), 29));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 3, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 4, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 5, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 6, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 7, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 8, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 9, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 10, 1), 31));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 11, 1), 30));
+        assertNotThrown!DateTimeException(testDate(Date(-1, 12, 1), 31));
+
+        {
+            auto date = Date(-1, 1, 1);
+            date.day = 6;
+            assert(date == Date(-1, 1, 6));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.day = 6));
+        static assert(!__traits(compiles, idate.day = 6));
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF Date). A negative
+        number will subtract.
+
+        Note that if day overflow is allowed, and the date with the adjusted
+        year/month overflows the number of days in the new month, then the month
+        will be incremented by one, and the day set to the number of days
+        overflowed. (e.g. if the day were 31 and the new month were June, then
+        the month would be incremented to July, and the new day would be 1). If
+        day overflow is not allowed, then the day will be set to the last valid
+        day in the month (e.g. June 31st would become June 30th).
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF Date).
+            allowOverflow = Whether the day should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref Date add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "years")
+    {
+        immutable newYear = _year + value;
+
+        _year += value;
+
+        if(_month == Month.feb && _day == 29 && !yearIsLeapYear(_year))
+        {
+            if(allowOverflow == AllowDayOverflow.yes)
+            {
+                _month = Month.mar;
+                _day = 1;
+            }
+            else
+                _day = 28;
+        }
+
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto d1 = Date(2010, 1, 1);
+        d1.add!"months"(11);
+        assert(d1 == Date(2010, 12, 1));
+
+        auto d2 = Date(2010, 1, 1);
+        d2.add!"months"(-11);
+        assert(d2 == Date(2009, 2, 1));
+
+        auto d3 = Date(2000, 2, 29);
+        d3.add!"years"(1);
+        assert(d3 == Date(2001, 3, 1));
+
+        auto d4 = Date(2000, 2, 29);
+        d4.add!"years"(1, AllowDayOverflow.no);
+        assert(d4 == Date(2001, 2, 28));
+    }
+
+    //Test add!"years"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"years"(7);
+            assert(date == Date(2006, 7, 6));
+            date.add!"years"(-9);
+            assert(date == Date(1997, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.add!"years"(1);
+            assert(date == Date(2000, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.add!"years"(-1);
+            assert(date == Date(1999, 3, 1));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"years"(-7);
+            assert(date == Date(-2006, 7, 6));
+            date.add!"years"(9);
+            assert(date == Date(-1997, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.add!"years"(-1);
+            assert(date == Date(-2000, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.add!"years"(1);
+            assert(date == Date(-1999, 3, 1));
+        }
+
+        //Test Both
+        {
+            auto date = Date(4, 7, 6);
+            date.add!"years"(-5);
+            assert(date == Date(-1, 7, 6));
+            date.add!"years"(5);
+            assert(date == Date(4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 7, 6);
+            date.add!"years"(5);
+            assert(date == Date(1, 7, 6));
+            date.add!"years"(-5);
+            assert(date == Date(-4, 7, 6));
+        }
+
+        {
+            auto date = Date(4, 7, 6);
+            date.add!"years"(-8);
+            assert(date == Date(-4, 7, 6));
+            date.add!"years"(8);
+            assert(date == Date(4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 7, 6);
+            date.add!"years"(8);
+            assert(date == Date(4, 7, 6));
+            date.add!"years"(-8);
+            assert(date == Date(-4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 2, 29);
+            date.add!"years"(5);
+            assert(date == Date(1, 3, 1));
+        }
+
+        {
+            auto date = Date(4, 2, 29);
+            date.add!"years"(-5);
+            assert(date == Date(-1, 3, 1));
+        }
+
+        {
+            auto date = Date(4, 2, 29);
+            date.add!"years"(-5).add!"years"(7);
+            assert(date == Date(6, 3, 1));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.add!"years"(7)));
+        static assert(!__traits(compiles, idate.add!"years"(7)));
+    }
+
+    //Test add!"years"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"years"(7, AllowDayOverflow.no);
+            assert(date == Date(2006, 7, 6));
+            date.add!"years"(-9, AllowDayOverflow.no);
+            assert(date == Date(1997, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.add!"years"(1, AllowDayOverflow.no);
+            assert(date == Date(2000, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.add!"years"(-1, AllowDayOverflow.no);
+            assert(date == Date(1999, 2, 28));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"years"(-7, AllowDayOverflow.no);
+            assert(date == Date(-2006, 7, 6));
+            date.add!"years"(9, AllowDayOverflow.no);
+            assert(date == Date(-1997, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.add!"years"(-1, AllowDayOverflow.no);
+            assert(date == Date(-2000, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.add!"years"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 2, 28));
+        }
+
+        //Test Both
+        {
+            auto date = Date(4, 7, 6);
+            date.add!"years"(-5, AllowDayOverflow.no);
+            assert(date == Date(-1, 7, 6));
+            date.add!"years"(5, AllowDayOverflow.no);
+            assert(date == Date(4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 7, 6);
+            date.add!"years"(5, AllowDayOverflow.no);
+            assert(date == Date(1, 7, 6));
+            date.add!"years"(-5, AllowDayOverflow.no);
+            assert(date == Date(-4, 7, 6));
+        }
+
+        {
+            auto date = Date(4, 7, 6);
+            date.add!"years"(-8, AllowDayOverflow.no);
+            assert(date == Date(-4, 7, 6));
+            date.add!"years"(8, AllowDayOverflow.no);
+            assert(date == Date(4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 7, 6);
+            date.add!"years"(8, AllowDayOverflow.no);
+            assert(date == Date(4, 7, 6));
+            date.add!"years"(-8, AllowDayOverflow.no);
+            assert(date == Date(-4, 7, 6));
+        }
+
+        {
+            auto date = Date(-4, 2, 29);
+            date.add!"years"(5, AllowDayOverflow.no);
+            assert(date == Date(1, 2, 28));
+        }
+
+        {
+            auto date = Date(4, 2, 29);
+            date.add!"years"(-5, AllowDayOverflow.no);
+            assert(date == Date(-1, 2, 28));
+        }
+
+        {
+            auto date = Date(4, 2, 29);
+            date.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no);
+            assert(date == Date(6, 2, 28));
+        }
+    }
+
+
+    //Shares documentation with "years" version.
+    ref Date add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "months")
+    {
+        auto years = months / 12;
+        months %= 12;
+        auto newMonth = _month + months;
+
+        if(months < 0)
+        {
+            if(newMonth < 1)
+            {
+                newMonth += 12;
+                --years;
+            }
+        }
+        else if(newMonth > 12)
+        {
+            newMonth -= 12;
+            ++years;
+        }
+
+        _year += years;
+        _month = cast(Month)newMonth;
+
+        immutable currMaxDay = maxDay(_year, _month);
+        immutable overflow = _day - currMaxDay;
+
+        if(overflow > 0)
+        {
+            if(allowOverflow == AllowDayOverflow.yes)
+            {
+                ++_month;
+                _day = cast(ubyte)overflow;
+            }
+            else
+                _day = cast(ubyte)currMaxDay;
+        }
+
+        return this;
+    }
+
+    //Test add!"months"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(3);
+            assert(date == Date(1999, 10, 6));
+            date.add!"months"(-4);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(6);
+            assert(date == Date(2000, 1, 6));
+            date.add!"months"(-6);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(27);
+            assert(date == Date(2001, 10, 6));
+            date.add!"months"(-28);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.add!"months"(1);
+            assert(date == Date(1999, 7, 1));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.add!"months"(-1);
+            assert(date == Date(1999, 5, 1));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.add!"months"(12);
+            assert(date == Date(2000, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.add!"months"(12);
+            assert(date == Date(2001, 3, 1));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date.add!"months"(1);
+            assert(date == Date(1999, 8, 31));
+            date.add!"months"(1);
+            assert(date == Date(1999, 10, 1));
+        }
+
+        {
+            auto date = Date(1998, 8, 31);
+            date.add!"months"(13);
+            assert(date == Date(1999, 10, 1));
+            date.add!"months"(-13);
+            assert(date == Date(1998, 9, 1));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.add!"months"(13);
+            assert(date == Date(1999, 1, 31));
+            date.add!"months"(-13);
+            assert(date == Date(1997, 12, 31));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(1999, 3, 3));
+            date.add!"months"(-14);
+            assert(date == Date(1998, 1, 3));
+        }
+
+        {
+            auto date = Date(1998, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(2000, 3, 2));
+            date.add!"months"(-14);
+            assert(date == Date(1999, 1, 2));
+        }
+
+        {
+            auto date = Date(1999, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(2001, 3, 3));
+            date.add!"months"(-14);
+            assert(date == Date(2000, 1, 3));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(3);
+            assert(date == Date(-1999, 10, 6));
+            date.add!"months"(-4);
+            assert(date == Date(-1999, 6, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(6);
+            assert(date == Date(-1998, 1, 6));
+            date.add!"months"(-6);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(-27);
+            assert(date == Date(-2001, 4, 6));
+            date.add!"months"(28);
+            assert(date == Date(-1999, 8, 6));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.add!"months"(1);
+            assert(date == Date(-1999, 7, 1));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.add!"months"(-1);
+            assert(date == Date(-1999, 5, 1));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.add!"months"(-12);
+            assert(date == Date(-2000, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.add!"months"(-12);
+            assert(date == Date(-2001, 3, 1));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date.add!"months"(1);
+            assert(date == Date(-1999, 8, 31));
+            date.add!"months"(1);
+            assert(date == Date(-1999, 10, 1));
+        }
+
+        {
+            auto date = Date(-1998, 8, 31);
+            date.add!"months"(13);
+            assert(date == Date(-1997, 10, 1));
+            date.add!"months"(-13);
+            assert(date == Date(-1998, 9, 1));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.add!"months"(13);
+            assert(date == Date(-1995, 1, 31));
+            date.add!"months"(-13);
+            assert(date == Date(-1997, 12, 31));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(-1995, 3, 3));
+            date.add!"months"(-14);
+            assert(date == Date(-1996, 1, 3));
+        }
+
+        {
+            auto date = Date(-2002, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(-2000, 3, 2));
+            date.add!"months"(-14);
+            assert(date == Date(-2001, 1, 2));
+        }
+
+        {
+            auto date = Date(-2001, 12, 31);
+            date.add!"months"(14);
+            assert(date == Date(-1999, 3, 3));
+            date.add!"months"(-14);
+            assert(date == Date(-2000, 1, 3));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 1, 1);
+            date.add!"months"(-1);
+            assert(date == Date(0, 12, 1));
+            date.add!"months"(1);
+            assert(date == Date(1, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 1, 1);
+            date.add!"months"(-48);
+            assert(date == Date(0, 1, 1));
+            date.add!"months"(48);
+            assert(date == Date(4, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.add!"months"(-49);
+            assert(date == Date(0, 3, 2));
+            date.add!"months"(49);
+            assert(date == Date(4, 4, 2));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.add!"months"(-85);
+            assert(date == Date(-3, 3, 3));
+            date.add!"months"(85);
+            assert(date == Date(4, 4, 3));
+        }
+
+        {
+            auto date = Date(-3, 3, 31);
+            date.add!"months"(85).add!"months"(-83);
+            assert(date == Date(-3, 6, 1));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.add!"months"(3)));
+        static assert(!__traits(compiles, idate.add!"months"(3)));
+    }
+
+    //Test add!"months"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(3, AllowDayOverflow.no);
+            assert(date == Date(1999, 10, 6));
+            date.add!"months"(-4, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(6, AllowDayOverflow.no);
+            assert(date == Date(2000, 1, 6));
+            date.add!"months"(-6, AllowDayOverflow.no);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.add!"months"(27, AllowDayOverflow.no);
+            assert(date == Date(2001, 10, 6));
+            date.add!"months"(-28, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 30));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.add!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(1999, 4, 30));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.add!"months"(12, AllowDayOverflow.no);
+            assert(date == Date(2000, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.add!"months"(12, AllowDayOverflow.no);
+            assert(date == Date(2001, 2, 28));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 8, 31));
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 9, 30));
+        }
+
+        {
+            auto date = Date(1998, 8, 31);
+            date.add!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(1999, 9, 30));
+            date.add!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(1998, 8, 30));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.add!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(1999, 1, 31));
+            date.add!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(1997, 12, 31));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(1999, 2, 28));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1997, 12, 28));
+        }
+
+        {
+            auto date = Date(1998, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(2000, 2, 29));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1998, 12, 29));
+        }
+
+        {
+            auto date = Date(1999, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(2001, 2, 28));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1999, 12, 28));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(3, AllowDayOverflow.no);
+            assert(date == Date(-1999, 10, 6));
+            date.add!"months"(-4, AllowDayOverflow.no);
+            assert(date == Date(-1999, 6, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(6, AllowDayOverflow.no);
+            assert(date == Date(-1998, 1, 6));
+            date.add!"months"(-6, AllowDayOverflow.no);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.add!"months"(-27, AllowDayOverflow.no);
+            assert(date == Date(-2001, 4, 6));
+            date.add!"months"(28, AllowDayOverflow.no);
+            assert(date == Date(-1999, 8, 6));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 6, 30));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.add!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 4, 30));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.add!"months"(-12, AllowDayOverflow.no);
+            assert(date == Date(-2000, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.add!"months"(-12, AllowDayOverflow.no);
+            assert(date == Date(-2001, 2, 28));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 8, 31));
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 9, 30));
+        }
+
+        {
+            auto date = Date(-1998, 8, 31);
+            date.add!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(-1997, 9, 30));
+            date.add!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(-1998, 8, 30));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.add!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(-1995, 1, 31));
+            date.add!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(-1997, 12, 31));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-1995, 2, 28));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-1997, 12, 28));
+        }
+
+        {
+            auto date = Date(-2002, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-2000, 2, 29));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-2002, 12, 29));
+        }
+
+        {
+            auto date = Date(-2001, 12, 31);
+            date.add!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-1999, 2, 28));
+            date.add!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-2001, 12, 28));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 1, 1);
+            date.add!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(0, 12, 1));
+            date.add!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 1, 1);
+            date.add!"months"(-48, AllowDayOverflow.no);
+            assert(date == Date(0, 1, 1));
+            date.add!"months"(48, AllowDayOverflow.no);
+            assert(date == Date(4, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.add!"months"(-49, AllowDayOverflow.no);
+            assert(date == Date(0, 2, 29));
+            date.add!"months"(49, AllowDayOverflow.no);
+            assert(date == Date(4, 3, 29));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.add!"months"(-85, AllowDayOverflow.no);
+            assert(date == Date(-3, 2, 28));
+            date.add!"months"(85, AllowDayOverflow.no);
+            assert(date == Date(4, 3, 28));
+        }
+
+        {
+            auto date = Date(-3, 3, 31);
+            date.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no);
+            assert(date == Date(-3, 5, 30));
+        }
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF Date). A negative
+        number will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. Rolling a $(LREF Date) 12 months gets
+        the exact same $(LREF Date). However, the days can still be affected due to
+        the differing number of days in each month.
+
+        Because there are no units larger than years, there is no difference
+        between adding and rolling years.
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF Date).
+            allowOverflow = Whether the day should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref Date roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "years")
+    {
+        return add!"years"(value, allowOverflow);
+    }
+
+    ///
+    unittest
+    {
+        auto d1 = Date(2010, 1, 1);
+        d1.roll!"months"(1);
+        assert(d1 == Date(2010, 2, 1));
+
+        auto d2 = Date(2010, 1, 1);
+        d2.roll!"months"(-1);
+        assert(d2 == Date(2010, 12, 1));
+
+        auto d3 = Date(1999, 1, 29);
+        d3.roll!"months"(1);
+        assert(d3 == Date(1999, 3, 1));
+
+        auto d4 = Date(1999, 1, 29);
+        d4.roll!"months"(1, AllowDayOverflow.no);
+        assert(d4 == Date(1999, 2, 28));
+
+        auto d5 = Date(2000, 2, 29);
+        d5.roll!"years"(1);
+        assert(d5 == Date(2001, 3, 1));
+
+        auto d6 = Date(2000, 2, 29);
+        d6.roll!"years"(1, AllowDayOverflow.no);
+        assert(d6 == Date(2001, 2, 28));
+    }
+
+    unittest
+    {
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.roll!"years"(3)));
+        static assert(!__traits(compiles, idate.rolYears(3)));
+    }
+
+
+    //Shares documentation with "years" version.
+    ref Date roll(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "months")
+    {
+        months %= 12;
+        auto newMonth = _month + months;
+
+        if(months < 0)
+        {
+            if(newMonth < 1)
+                newMonth += 12;
+        }
+        else
+        {
+            if(newMonth > 12)
+                newMonth -= 12;
+        }
+
+        _month = cast(Month)newMonth;
+
+        immutable currMaxDay = maxDay(_year, _month);
+        immutable overflow = _day - currMaxDay;
+
+        if(overflow > 0)
+        {
+            if(allowOverflow == AllowDayOverflow.yes)
+            {
+                ++_month;
+                _day = cast(ubyte)overflow;
+            }
+            else
+                _day = cast(ubyte)currMaxDay;
+        }
+
+        return this;
+    }
+
+    //Test roll!"months"() with AllowDayOverlow.yes
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(3);
+            assert(date == Date(1999, 10, 6));
+            date.roll!"months"(-4);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(6);
+            assert(date == Date(1999, 1, 6));
+            date.roll!"months"(-6);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(27);
+            assert(date == Date(1999, 10, 6));
+            date.roll!"months"(-28);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.roll!"months"(1);
+            assert(date == Date(1999, 7, 1));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.roll!"months"(-1);
+            assert(date == Date(1999, 5, 1));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.roll!"months"(12);
+            assert(date == Date(1999, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.roll!"months"(12);
+            assert(date == Date(2000, 2, 29));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date.roll!"months"(1);
+            assert(date == Date(1999, 8, 31));
+            date.roll!"months"(1);
+            assert(date == Date(1999, 10, 1));
+        }
+
+        {
+            auto date = Date(1998, 8, 31);
+            date.roll!"months"(13);
+            assert(date == Date(1998, 10, 1));
+            date.roll!"months"(-13);
+            assert(date == Date(1998, 9, 1));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.roll!"months"(13);
+            assert(date == Date(1997, 1, 31));
+            date.roll!"months"(-13);
+            assert(date == Date(1997, 12, 31));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(1997, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(1997, 1, 3));
+        }
+
+        {
+            auto date = Date(1998, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(1998, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(1998, 1, 3));
+        }
+
+        {
+            auto date = Date(1999, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(1999, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(1999, 1, 3));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(3);
+            assert(date == Date(-1999, 10, 6));
+            date.roll!"months"(-4);
+            assert(date == Date(-1999, 6, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(6);
+            assert(date == Date(-1999, 1, 6));
+            date.roll!"months"(-6);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(-27);
+            assert(date == Date(-1999, 4, 6));
+            date.roll!"months"(28);
+            assert(date == Date(-1999, 8, 6));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.roll!"months"(1);
+            assert(date == Date(-1999, 7, 1));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.roll!"months"(-1);
+            assert(date == Date(-1999, 5, 1));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.roll!"months"(-12);
+            assert(date == Date(-1999, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.roll!"months"(-12);
+            assert(date == Date(-2000, 2, 29));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date.roll!"months"(1);
+            assert(date == Date(-1999, 8, 31));
+            date.roll!"months"(1);
+            assert(date == Date(-1999, 10, 1));
+        }
+
+        {
+            auto date = Date(-1998, 8, 31);
+            date.roll!"months"(13);
+            assert(date == Date(-1998, 10, 1));
+            date.roll!"months"(-13);
+            assert(date == Date(-1998, 9, 1));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.roll!"months"(13);
+            assert(date == Date(-1997, 1, 31));
+            date.roll!"months"(-13);
+            assert(date == Date(-1997, 12, 31));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(-1997, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(-1997, 1, 3));
+        }
+
+        {
+            auto date = Date(-2002, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(-2002, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(-2002, 1, 3));
+        }
+
+        {
+            auto date = Date(-2001, 12, 31);
+            date.roll!"months"(14);
+            assert(date == Date(-2001, 3, 3));
+            date.roll!"months"(-14);
+            assert(date == Date(-2001, 1, 3));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 1, 1);
+            date.roll!"months"(-1);
+            assert(date == Date(1, 12, 1));
+            date.roll!"months"(1);
+            assert(date == Date(1, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 1, 1);
+            date.roll!"months"(-48);
+            assert(date == Date(4, 1, 1));
+            date.roll!"months"(48);
+            assert(date == Date(4, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.roll!"months"(-49);
+            assert(date == Date(4, 3, 2));
+            date.roll!"months"(49);
+            assert(date == Date(4, 4, 2));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.roll!"months"(-85);
+            assert(date == Date(4, 3, 2));
+            date.roll!"months"(85);
+            assert(date == Date(4, 4, 2));
+        }
+
+        {
+            auto date = Date(-1, 1, 1);
+            date.roll!"months"(-1);
+            assert(date == Date(-1, 12, 1));
+            date.roll!"months"(1);
+            assert(date == Date(-1, 1, 1));
+        }
+
+        {
+            auto date = Date(-4, 1, 1);
+            date.roll!"months"(-48);
+            assert(date == Date(-4, 1, 1));
+            date.roll!"months"(48);
+            assert(date == Date(-4, 1, 1));
+        }
+
+        {
+            auto date = Date(-4, 3, 31);
+            date.roll!"months"(-49);
+            assert(date == Date(-4, 3, 2));
+            date.roll!"months"(49);
+            assert(date == Date(-4, 4, 2));
+        }
+
+        {
+            auto date = Date(-4, 3, 31);
+            date.roll!"months"(-85);
+            assert(date == Date(-4, 3, 2));
+            date.roll!"months"(85);
+            assert(date == Date(-4, 4, 2));
+        }
+
+        {
+            auto date = Date(-3, 3, 31);
+            date.roll!"months"(85).roll!"months"(-83);
+            assert(date == Date(-3, 6, 1));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.roll!"months"(3)));
+        static assert(!__traits(compiles, idate.roll!"months"(3)));
+    }
+
+    //Test roll!"months"() with AllowDayOverlow.no
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(3, AllowDayOverflow.no);
+            assert(date == Date(1999, 10, 6));
+            date.roll!"months"(-4, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(6, AllowDayOverflow.no);
+            assert(date == Date(1999, 1, 6));
+            date.roll!"months"(-6, AllowDayOverflow.no);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"months"(27, AllowDayOverflow.no);
+            assert(date == Date(1999, 10, 6));
+            date.roll!"months"(-28, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 6));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 6, 30));
+        }
+
+        {
+            auto date = Date(1999, 5, 31);
+            date.roll!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(1999, 4, 30));
+        }
+
+        {
+            auto date = Date(1999, 2, 28);
+            date.roll!"months"(12, AllowDayOverflow.no);
+            assert(date == Date(1999, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 29);
+            date.roll!"months"(12, AllowDayOverflow.no);
+            assert(date == Date(2000, 2, 29));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 8, 31));
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1999, 9, 30));
+        }
+
+        {
+            auto date = Date(1998, 8, 31);
+            date.roll!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(1998, 9, 30));
+            date.roll!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(1998, 8, 30));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.roll!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(1997, 1, 31));
+            date.roll!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(1997, 12, 31));
+        }
+
+        {
+            auto date = Date(1997, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(1997, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1997, 12, 28));
+        }
+
+        {
+            auto date = Date(1998, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(1998, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1998, 12, 28));
+        }
+
+        {
+            auto date = Date(1999, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(1999, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(1999, 12, 28));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(3, AllowDayOverflow.no);
+            assert(date == Date(-1999, 10, 6));
+            date.roll!"months"(-4, AllowDayOverflow.no);
+            assert(date == Date(-1999, 6, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(6, AllowDayOverflow.no);
+            assert(date == Date(-1999, 1, 6));
+            date.roll!"months"(-6, AllowDayOverflow.no);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"months"(-27, AllowDayOverflow.no);
+            assert(date == Date(-1999, 4, 6));
+            date.roll!"months"(28, AllowDayOverflow.no);
+            assert(date == Date(-1999, 8, 6));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 6, 30));
+        }
+
+        {
+            auto date = Date(-1999, 5, 31);
+            date.roll!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 4, 30));
+        }
+
+        {
+            auto date = Date(-1999, 2, 28);
+            date.roll!"months"(-12, AllowDayOverflow.no);
+            assert(date == Date(-1999, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 29);
+            date.roll!"months"(-12, AllowDayOverflow.no);
+            assert(date == Date(-2000, 2, 29));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 8, 31));
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1999, 9, 30));
+        }
+
+        {
+            auto date = Date(-1998, 8, 31);
+            date.roll!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(-1998, 9, 30));
+            date.roll!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(-1998, 8, 30));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.roll!"months"(13, AllowDayOverflow.no);
+            assert(date == Date(-1997, 1, 31));
+            date.roll!"months"(-13, AllowDayOverflow.no);
+            assert(date == Date(-1997, 12, 31));
+        }
+
+        {
+            auto date = Date(-1997, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-1997, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-1997, 12, 28));
+        }
+
+        {
+            auto date = Date(-2002, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-2002, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-2002, 12, 28));
+        }
+
+        {
+            auto date = Date(-2001, 12, 31);
+            date.roll!"months"(14, AllowDayOverflow.no);
+            assert(date == Date(-2001, 2, 28));
+            date.roll!"months"(-14, AllowDayOverflow.no);
+            assert(date == Date(-2001, 12, 28));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 1, 1);
+            date.roll!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(1, 12, 1));
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(1, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 1, 1);
+            date.roll!"months"(-48, AllowDayOverflow.no);
+            assert(date == Date(4, 1, 1));
+            date.roll!"months"(48, AllowDayOverflow.no);
+            assert(date == Date(4, 1, 1));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.roll!"months"(-49, AllowDayOverflow.no);
+            assert(date == Date(4, 2, 29));
+            date.roll!"months"(49, AllowDayOverflow.no);
+            assert(date == Date(4, 3, 29));
+        }
+
+        {
+            auto date = Date(4, 3, 31);
+            date.roll!"months"(-85, AllowDayOverflow.no);
+            assert(date == Date(4, 2, 29));
+            date.roll!"months"(85, AllowDayOverflow.no);
+            assert(date == Date(4, 3, 29));
+        }
+
+        {
+            auto date = Date(-1, 1, 1);
+            date.roll!"months"(-1, AllowDayOverflow.no);
+            assert(date == Date(-1, 12, 1));
+            date.roll!"months"(1, AllowDayOverflow.no);
+            assert(date == Date(-1, 1, 1));
+        }
+
+        {
+            auto date = Date(-4, 1, 1);
+            date.roll!"months"(-48, AllowDayOverflow.no);
+            assert(date == Date(-4, 1, 1));
+            date.roll!"months"(48, AllowDayOverflow.no);
+            assert(date == Date(-4, 1, 1));
+        }
+
+        {
+            auto date = Date(-4, 3, 31);
+            date.roll!"months"(-49, AllowDayOverflow.no);
+            assert(date == Date(-4, 2, 29));
+            date.roll!"months"(49, AllowDayOverflow.no);
+            assert(date == Date(-4, 3, 29));
+        }
+
+        {
+            auto date = Date(-4, 3, 31);
+            date.roll!"months"(-85, AllowDayOverflow.no);
+            assert(date == Date(-4, 2, 29));
+            date.roll!"months"(85, AllowDayOverflow.no);
+            assert(date == Date(-4, 3, 29));
+        }
+
+        {
+            auto date = Date(-3, 3, 31);
+            date.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no);
+            assert(date == Date(-3, 5, 30));
+        }
+    }
+
+
+    /++
+        Adds the given number of units to this $(LREF Date). A negative number will
+        subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. For instance, rolling a $(LREF Date) one
+        year's worth of days gets the exact same $(LREF Date).
+
+        The only accepted units are $(D "days").
+
+        Params:
+            units = The units to add. Must be $(D "days").
+            days  = The number of days to add to this $(LREF Date).
+      +/
+    ref Date roll(string units)(long days) @safe pure nothrow
+        if(units == "days")
+    {
+        immutable limit = maxDay(_year, _month);
+        days %= limit;
+        auto newDay = _day + days;
+
+        if(days < 0)
+        {
+            if(newDay < 1)
+                newDay += limit;
+        }
+        else if(newDay > limit)
+            newDay -= limit;
+
+        _day = cast(ubyte)newDay;
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto d = Date(2010, 1, 1);
+        d.roll!"days"(1);
+        assert(d == Date(2010, 1, 2));
+        d.roll!"days"(365);
+        assert(d == Date(2010, 1, 26));
+        d.roll!"days"(-32);
+        assert(d == Date(2010, 1, 25));
+    }
+
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 2, 28);
+            date.roll!"days"(1);
+            assert(date == Date(1999, 2, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(1999, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 28);
+            date.roll!"days"(1);
+            assert(date == Date(2000, 2, 29));
+            date.roll!"days"(1);
+            assert(date == Date(2000, 2, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(2000, 2, 29));
+        }
+
+        {
+            auto date = Date(1999, 6, 30);
+            date.roll!"days"(1);
+            assert(date == Date(1999, 6, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(1999, 6, 30));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date.roll!"days"(1);
+            assert(date == Date(1999, 7, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(1999, 7, 31));
+        }
+
+        {
+            auto date = Date(1999, 1, 1);
+            date.roll!"days"(-1);
+            assert(date == Date(1999, 1, 31));
+            date.roll!"days"(1);
+            assert(date == Date(1999, 1, 1));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"days"(9);
+            assert(date == Date(1999, 7, 15));
+            date.roll!"days"(-11);
+            assert(date == Date(1999, 7, 4));
+            date.roll!"days"(30);
+            assert(date == Date(1999, 7, 3));
+            date.roll!"days"(-3);
+            assert(date == Date(1999, 7, 31));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date.roll!"days"(365);
+            assert(date == Date(1999, 7, 30));
+            date.roll!"days"(-365);
+            assert(date == Date(1999, 7, 6));
+            date.roll!"days"(366);
+            assert(date == Date(1999, 7, 31));
+            date.roll!"days"(730);
+            assert(date == Date(1999, 7, 17));
+            date.roll!"days"(-1096);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        {
+            auto date = Date(1999, 2, 6);
+            date.roll!"days"(365);
+            assert(date == Date(1999, 2, 7));
+            date.roll!"days"(-365);
+            assert(date == Date(1999, 2, 6));
+            date.roll!"days"(366);
+            assert(date == Date(1999, 2, 8));
+            date.roll!"days"(730);
+            assert(date == Date(1999, 2, 10));
+            date.roll!"days"(-1096);
+            assert(date == Date(1999, 2, 6));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 2, 28);
+            date.roll!"days"(1);
+            assert(date == Date(-1999, 2, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(-1999, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 28);
+            date.roll!"days"(1);
+            assert(date == Date(-2000, 2, 29));
+            date.roll!"days"(1);
+            assert(date == Date(-2000, 2, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(-2000, 2, 29));
+        }
+
+        {
+            auto date = Date(-1999, 6, 30);
+            date.roll!"days"(1);
+            assert(date == Date(-1999, 6, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(-1999, 6, 30));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date.roll!"days"(1);
+            assert(date == Date(-1999, 7, 1));
+            date.roll!"days"(-1);
+            assert(date == Date(-1999, 7, 31));
+        }
+
+        {
+            auto date = Date(-1999, 1, 1);
+            date.roll!"days"(-1);
+            assert(date == Date(-1999, 1, 31));
+            date.roll!"days"(1);
+            assert(date == Date(-1999, 1, 1));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"days"(9);
+            assert(date == Date(-1999, 7, 15));
+            date.roll!"days"(-11);
+            assert(date == Date(-1999, 7, 4));
+            date.roll!"days"(30);
+            assert(date == Date(-1999, 7, 3));
+            date.roll!"days"(-3);
+            assert(date == Date(-1999, 7, 31));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date.roll!"days"(365);
+            assert(date == Date(-1999, 7, 30));
+            date.roll!"days"(-365);
+            assert(date == Date(-1999, 7, 6));
+            date.roll!"days"(366);
+            assert(date == Date(-1999, 7, 31));
+            date.roll!"days"(730);
+            assert(date == Date(-1999, 7, 17));
+            date.roll!"days"(-1096);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 7, 6);
+            date.roll!"days"(-365);
+            assert(date == Date(1, 7, 13));
+            date.roll!"days"(365);
+            assert(date == Date(1, 7, 6));
+            date.roll!"days"(-731);
+            assert(date == Date(1, 7, 19));
+            date.roll!"days"(730);
+            assert(date == Date(1, 7, 5));
+        }
+
+        {
+            auto date = Date(0, 7, 6);
+            date.roll!"days"(-365);
+            assert(date == Date(0, 7, 13));
+            date.roll!"days"(365);
+            assert(date == Date(0, 7, 6));
+            date.roll!"days"(-731);
+            assert(date == Date(0, 7, 19));
+            date.roll!"days"(730);
+            assert(date == Date(0, 7, 5));
+        }
+
+        {
+            auto date = Date(0, 7, 6);
+            date.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730);
+            assert(date == Date(0, 7, 8));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.roll!"days"(12)));
+        static assert(!__traits(compiles, idate.roll!"days"(12)));
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF tiem, Duration) from
+        this $(LREF Date).
+
+        The legal types of arithmetic for $(LREF Date) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date))
+        $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF Date).
+      +/
+    Date opBinary(string op)(Duration duration) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        Date retval = this;
+        immutable days = duration.total!"days";
+        mixin("return retval._addDays(" ~ op ~ "days);");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(2015, 12, 31) + days(1) == Date(2016, 1, 1));
+        assert(Date(2004, 2, 26) + days(4) == Date(2004, 3, 1));
+
+        assert(Date(2016, 1, 1) - days(1) == Date(2015, 12, 31));
+        assert(Date(2004, 3, 1) - days(4) == Date(2004, 2, 26));
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+
+        assert(date + dur!"weeks"(7) == Date(1999, 8, 24));
+        assert(date + dur!"weeks"(-7) == Date(1999, 5, 18));
+        assert(date + dur!"days"(7) == Date(1999, 7, 13));
+        assert(date + dur!"days"(-7) == Date(1999, 6, 29));
+
+        assert(date + dur!"hours"(24) == Date(1999, 7, 7));
+        assert(date + dur!"hours"(-24) == Date(1999, 7, 5));
+        assert(date + dur!"minutes"(1440) == Date(1999, 7, 7));
+        assert(date + dur!"minutes"(-1440) == Date(1999, 7, 5));
+        assert(date + dur!"seconds"(86_400) == Date(1999, 7, 7));
+        assert(date + dur!"seconds"(-86_400) == Date(1999, 7, 5));
+        assert(date + dur!"msecs"(86_400_000) == Date(1999, 7, 7));
+        assert(date + dur!"msecs"(-86_400_000) == Date(1999, 7, 5));
+        assert(date + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7));
+        assert(date + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5));
+        assert(date + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7));
+        assert(date + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5));
+
+        assert(date - dur!"weeks"(-7) == Date(1999, 8, 24));
+        assert(date - dur!"weeks"(7) == Date(1999, 5, 18));
+        assert(date - dur!"days"(-7) == Date(1999, 7, 13));
+        assert(date - dur!"days"(7) == Date(1999, 6, 29));
+
+        assert(date - dur!"hours"(-24) == Date(1999, 7, 7));
+        assert(date - dur!"hours"(24) == Date(1999, 7, 5));
+        assert(date - dur!"minutes"(-1440) == Date(1999, 7, 7));
+        assert(date - dur!"minutes"(1440) == Date(1999, 7, 5));
+        assert(date - dur!"seconds"(-86_400) == Date(1999, 7, 7));
+        assert(date - dur!"seconds"(86_400) == Date(1999, 7, 5));
+        assert(date - dur!"msecs"(-86_400_000) == Date(1999, 7, 7));
+        assert(date - dur!"msecs"(86_400_000) == Date(1999, 7, 5));
+        assert(date - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
+        assert(date - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5));
+        assert(date - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7));
+        assert(date - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5));
+
+        auto duration = dur!"days"(12);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date + duration == Date(1999, 7, 18));
+        assert(cdate + duration == Date(1999, 7, 18));
+        assert(idate + duration == Date(1999, 7, 18));
+
+        assert(date - duration == Date(1999, 6, 24));
+        assert(cdate - duration == Date(1999, 6, 24));
+        assert(idate - duration == Date(1999, 6, 24));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines + and - with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    Date opBinary(string op)(TickDuration td) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        Date retval = this;
+        immutable days = convert!("hnsecs", "days")(td.hnsecs);
+        mixin("return retval._addDays(" ~ op ~ "days);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            auto date = Date(1999, 7, 6);
+
+            assert(date + TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 7));
+            assert(date + TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 5));
+
+            assert(date - TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
+            assert(date - TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 5));
+        }
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF Date), as well as assigning the result to this $(LREF Date).
+
+        The legal types of arithmetic for $(LREF Date) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date))
+        $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF Date).
+      +/
+    ref Date opOpAssign(string op)(Duration duration) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable days = duration.total!"days";
+        mixin("return _addDays(" ~ op ~ "days);");
+    }
+
+    unittest
+    {
+        assert(Date(1999, 7, 6) + dur!"weeks"(7) == Date(1999, 8, 24));
+        assert(Date(1999, 7, 6) + dur!"weeks"(-7) == Date(1999, 5, 18));
+        assert(Date(1999, 7, 6) + dur!"days"(7) == Date(1999, 7, 13));
+        assert(Date(1999, 7, 6) + dur!"days"(-7) == Date(1999, 6, 29));
+
+        assert(Date(1999, 7, 6) + dur!"hours"(24) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"hours"(-24) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) + dur!"minutes"(1440) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"minutes"(-1440) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) + dur!"seconds"(86_400) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"seconds"(-86_400) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) + dur!"msecs"(86_400_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"msecs"(-86_400_000) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5));
+
+        assert(Date(1999, 7, 6) - dur!"weeks"(-7) == Date(1999, 8, 24));
+        assert(Date(1999, 7, 6) - dur!"weeks"(7) == Date(1999, 5, 18));
+        assert(Date(1999, 7, 6) - dur!"days"(-7) == Date(1999, 7, 13));
+        assert(Date(1999, 7, 6) - dur!"days"(7) == Date(1999, 6, 29));
+
+        assert(Date(1999, 7, 6) - dur!"hours"(-24) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"hours"(24) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) - dur!"minutes"(-1440) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"minutes"(1440) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) - dur!"seconds"(-86_400) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"seconds"(86_400) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) - dur!"msecs"(-86_400_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"msecs"(86_400_000) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5));
+        assert(Date(1999, 7, 6) - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7));
+        assert(Date(1999, 7, 6) - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5));
+
+        {
+            auto date = Date(0, 1, 31);
+            (date += dur!"days"(507)) += dur!"days"(-2);
+            assert(date == Date(1, 6, 19));
+        }
+
+        auto duration = dur!"days"(12);
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        date += duration;
+        static assert(!__traits(compiles, cdate += duration));
+        static assert(!__traits(compiles, idate += duration));
+
+        date -= duration;
+        static assert(!__traits(compiles, cdate -= duration));
+        static assert(!__traits(compiles, idate -= duration));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines += and -= with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    ref Date opOpAssign(string op)(TickDuration td) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable days = convert!("seconds", "days")(td.seconds);
+        mixin("return _addDays(" ~ op ~ "days);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            {
+                auto date = Date(1999, 7, 6);
+                date += TickDuration.from!"usecs"(86_400_000_000);
+                assert(date == Date(1999, 7, 7));
+            }
+
+            {
+                auto date = Date(1999, 7, 6);
+                date += TickDuration.from!"usecs"(-86_400_000_000);
+                assert(date == Date(1999, 7, 5));
+            }
+
+            {
+                auto date = Date(1999, 7, 6);
+                date -= TickDuration.from!"usecs"(-86_400_000_000);
+                assert(date == Date(1999, 7, 7));
+            }
+
+            {
+                auto date = Date(1999, 7, 6);
+                date -= TickDuration.from!"usecs"(86_400_000_000);
+                assert(date == Date(1999, 7, 5));
+            }
+        }
+    }
+
+
+    /++
+        Gives the difference between two $(LREF Date)s.
+
+        The legal types of arithmetic for $(LREF Date) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD Date) $(TD -) $(TD Date) $(TD -->) $(TD duration))
+        )
+      +/
+    Duration opBinary(string op)(in Date rhs) @safe const pure nothrow
+        if(op == "-")
+    {
+        return dur!"days"(this.dayOfGregorianCal - rhs.dayOfGregorianCal);
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+
+        assert(Date(1999, 7, 6) - Date(1998, 7, 6) == dur!"days"(365));
+        assert(Date(1998, 7, 6) - Date(1999, 7, 6) == dur!"days"(-365));
+        assert(Date(1999, 6, 6) - Date(1999, 5, 6) == dur!"days"(31));
+        assert(Date(1999, 5, 6) - Date(1999, 6, 6) == dur!"days"(-31));
+        assert(Date(1999, 1, 1) - Date(1998, 12, 31) == dur!"days"(1));
+        assert(Date(1998, 12, 31) - Date(1999, 1, 1) == dur!"days"(-1));
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date - date == Duration.zero);
+        assert(cdate - date == Duration.zero);
+        assert(idate - date == Duration.zero);
+
+        assert(date - cdate == Duration.zero);
+        assert(cdate - cdate == Duration.zero);
+        assert(idate - cdate == Duration.zero);
+
+        assert(date - idate == Duration.zero);
+        assert(cdate - idate == Duration.zero);
+        assert(idate - idate == Duration.zero);
+    }
+
+
+    /++
+        Returns the difference between the two $(LREF Date)s in months.
+
+        To get the difference in years, subtract the year property
+        of two $(LREF SysTime)s. To get the difference in days or weeks,
+        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
+        that results. Because converting between months and smaller
+        units requires a specific date (which $(CXREF time, Duration)s don't have),
+        getting the difference in months requires some math using both
+        the year and month properties, so this is a convenience function for
+        getting the difference in months.
+
+        Note that the number of days in the months or how far into the month
+        either $(LREF Date) is is irrelevant. It is the difference in the month
+        property combined with the difference in years * 12. So, for instance,
+        December 31st and January 1st are one month apart just as December 1st
+        and January 31st are one month apart.
+
+        Params:
+            rhs = The $(LREF Date) to subtract from this one.
+      +/
+    int diffMonths(in Date rhs) @safe const pure nothrow
+    {
+        immutable yearDiff = _year - rhs._year;
+        immutable monthDiff = _month - rhs._month;
+
+        return yearDiff * 12 + monthDiff;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 2, 1).diffMonths(Date(1999, 1, 31)) == 1);
+        assert(Date(1999, 1, 31).diffMonths(Date(1999, 2, 1)) == -1);
+        assert(Date(1999, 3, 1).diffMonths(Date(1999, 1, 1)) == 2);
+        assert(Date(1999, 1, 1).diffMonths(Date(1999, 3, 31)) == -2);
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+
+        //Test A.D.
+        assert(date.diffMonths(Date(1998, 6, 5)) == 13);
+        assert(date.diffMonths(Date(1998, 7, 5)) == 12);
+        assert(date.diffMonths(Date(1998, 8, 5)) == 11);
+        assert(date.diffMonths(Date(1998, 9, 5)) == 10);
+        assert(date.diffMonths(Date(1998, 10, 5)) == 9);
+        assert(date.diffMonths(Date(1998, 11, 5)) == 8);
+        assert(date.diffMonths(Date(1998, 12, 5)) == 7);
+        assert(date.diffMonths(Date(1999, 1, 5)) == 6);
+        assert(date.diffMonths(Date(1999, 2, 6)) == 5);
+        assert(date.diffMonths(Date(1999, 3, 6)) == 4);
+        assert(date.diffMonths(Date(1999, 4, 6)) == 3);
+        assert(date.diffMonths(Date(1999, 5, 6)) == 2);
+        assert(date.diffMonths(Date(1999, 6, 6)) == 1);
+        assert(date.diffMonths(date) == 0);
+        assert(date.diffMonths(Date(1999, 8, 6)) == -1);
+        assert(date.diffMonths(Date(1999, 9, 6)) == -2);
+        assert(date.diffMonths(Date(1999, 10, 6)) == -3);
+        assert(date.diffMonths(Date(1999, 11, 6)) == -4);
+        assert(date.diffMonths(Date(1999, 12, 6)) == -5);
+        assert(date.diffMonths(Date(2000, 1, 6)) == -6);
+        assert(date.diffMonths(Date(2000, 2, 6)) == -7);
+        assert(date.diffMonths(Date(2000, 3, 6)) == -8);
+        assert(date.diffMonths(Date(2000, 4, 6)) == -9);
+        assert(date.diffMonths(Date(2000, 5, 6)) == -10);
+        assert(date.diffMonths(Date(2000, 6, 6)) == -11);
+        assert(date.diffMonths(Date(2000, 7, 6)) == -12);
+        assert(date.diffMonths(Date(2000, 8, 6)) == -13);
+
+        assert(Date(1998, 6, 5).diffMonths(date) == -13);
+        assert(Date(1998, 7, 5).diffMonths(date) == -12);
+        assert(Date(1998, 8, 5).diffMonths(date) == -11);
+        assert(Date(1998, 9, 5).diffMonths(date) == -10);
+        assert(Date(1998, 10, 5).diffMonths(date) == -9);
+        assert(Date(1998, 11, 5).diffMonths(date) == -8);
+        assert(Date(1998, 12, 5).diffMonths(date) == -7);
+        assert(Date(1999, 1, 5).diffMonths(date) == -6);
+        assert(Date(1999, 2, 6).diffMonths(date) == -5);
+        assert(Date(1999, 3, 6).diffMonths(date) == -4);
+        assert(Date(1999, 4, 6).diffMonths(date) == -3);
+        assert(Date(1999, 5, 6).diffMonths(date) == -2);
+        assert(Date(1999, 6, 6).diffMonths(date) == -1);
+        assert(Date(1999, 8, 6).diffMonths(date) == 1);
+        assert(Date(1999, 9, 6).diffMonths(date) == 2);
+        assert(Date(1999, 10, 6).diffMonths(date) == 3);
+        assert(Date(1999, 11, 6).diffMonths(date) == 4);
+        assert(Date(1999, 12, 6).diffMonths(date) == 5);
+        assert(Date(2000, 1, 6).diffMonths(date) == 6);
+        assert(Date(2000, 2, 6).diffMonths(date) == 7);
+        assert(Date(2000, 3, 6).diffMonths(date) == 8);
+        assert(Date(2000, 4, 6).diffMonths(date) == 9);
+        assert(Date(2000, 5, 6).diffMonths(date) == 10);
+        assert(Date(2000, 6, 6).diffMonths(date) == 11);
+        assert(Date(2000, 7, 6).diffMonths(date) == 12);
+        assert(Date(2000, 8, 6).diffMonths(date) == 13);
+
+        assert(date.diffMonths(Date(1999, 6, 30)) == 1);
+        assert(date.diffMonths(Date(1999, 7, 1)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 6)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 11)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 16)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 21)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 26)) == 0);
+        assert(date.diffMonths(Date(1999, 7, 31)) == 0);
+        assert(date.diffMonths(Date(1999, 8, 1)) == -1);
+
+        assert(date.diffMonths(Date(1990, 6, 30)) == 109);
+        assert(date.diffMonths(Date(1990, 7, 1)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 6)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 11)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 16)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 21)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 26)) == 108);
+        assert(date.diffMonths(Date(1990, 7, 31)) == 108);
+        assert(date.diffMonths(Date(1990, 8, 1)) == 107);
+
+        assert(Date(1999, 6, 30).diffMonths(date) == -1);
+        assert(Date(1999, 7, 1).diffMonths(date) == 0);
+        assert(Date(1999, 7, 6).diffMonths(date) == 0);
+        assert(Date(1999, 7, 11).diffMonths(date) == 0);
+        assert(Date(1999, 7, 16).diffMonths(date) == 0);
+        assert(Date(1999, 7, 21).diffMonths(date) == 0);
+        assert(Date(1999, 7, 26).diffMonths(date) == 0);
+        assert(Date(1999, 7, 31).diffMonths(date) == 0);
+        assert(Date(1999, 8, 1).diffMonths(date) == 1);
+
+        assert(Date(1990, 6, 30).diffMonths(date) == -109);
+        assert(Date(1990, 7, 1).diffMonths(date) == -108);
+        assert(Date(1990, 7, 6).diffMonths(date) == -108);
+        assert(Date(1990, 7, 11).diffMonths(date) == -108);
+        assert(Date(1990, 7, 16).diffMonths(date) == -108);
+        assert(Date(1990, 7, 21).diffMonths(date) == -108);
+        assert(Date(1990, 7, 26).diffMonths(date) == -108);
+        assert(Date(1990, 7, 31).diffMonths(date) == -108);
+        assert(Date(1990, 8, 1).diffMonths(date) == -107);
+
+        //Test B.C.
+        auto dateBC = Date(-1999, 7, 6);
+
+        assert(dateBC.diffMonths(Date(-2000, 6, 5)) == 13);
+        assert(dateBC.diffMonths(Date(-2000, 7, 5)) == 12);
+        assert(dateBC.diffMonths(Date(-2000, 8, 5)) == 11);
+        assert(dateBC.diffMonths(Date(-2000, 9, 5)) == 10);
+        assert(dateBC.diffMonths(Date(-2000, 10, 5)) == 9);
+        assert(dateBC.diffMonths(Date(-2000, 11, 5)) == 8);
+        assert(dateBC.diffMonths(Date(-2000, 12, 5)) == 7);
+        assert(dateBC.diffMonths(Date(-1999, 1, 5)) == 6);
+        assert(dateBC.diffMonths(Date(-1999, 2, 6)) == 5);
+        assert(dateBC.diffMonths(Date(-1999, 3, 6)) == 4);
+        assert(dateBC.diffMonths(Date(-1999, 4, 6)) == 3);
+        assert(dateBC.diffMonths(Date(-1999, 5, 6)) == 2);
+        assert(dateBC.diffMonths(Date(-1999, 6, 6)) == 1);
+        assert(dateBC.diffMonths(dateBC) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 8, 6)) == -1);
+        assert(dateBC.diffMonths(Date(-1999, 9, 6)) == -2);
+        assert(dateBC.diffMonths(Date(-1999, 10, 6)) == -3);
+        assert(dateBC.diffMonths(Date(-1999, 11, 6)) == -4);
+        assert(dateBC.diffMonths(Date(-1999, 12, 6)) == -5);
+        assert(dateBC.diffMonths(Date(-1998, 1, 6)) == -6);
+        assert(dateBC.diffMonths(Date(-1998, 2, 6)) == -7);
+        assert(dateBC.diffMonths(Date(-1998, 3, 6)) == -8);
+        assert(dateBC.diffMonths(Date(-1998, 4, 6)) == -9);
+        assert(dateBC.diffMonths(Date(-1998, 5, 6)) == -10);
+        assert(dateBC.diffMonths(Date(-1998, 6, 6)) == -11);
+        assert(dateBC.diffMonths(Date(-1998, 7, 6)) == -12);
+        assert(dateBC.diffMonths(Date(-1998, 8, 6)) == -13);
+
+        assert(Date(-2000, 6, 5).diffMonths(dateBC) == -13);
+        assert(Date(-2000, 7, 5).diffMonths(dateBC) == -12);
+        assert(Date(-2000, 8, 5).diffMonths(dateBC) == -11);
+        assert(Date(-2000, 9, 5).diffMonths(dateBC) == -10);
+        assert(Date(-2000, 10, 5).diffMonths(dateBC) == -9);
+        assert(Date(-2000, 11, 5).diffMonths(dateBC) == -8);
+        assert(Date(-2000, 12, 5).diffMonths(dateBC) == -7);
+        assert(Date(-1999, 1, 5).diffMonths(dateBC) == -6);
+        assert(Date(-1999, 2, 6).diffMonths(dateBC) == -5);
+        assert(Date(-1999, 3, 6).diffMonths(dateBC) == -4);
+        assert(Date(-1999, 4, 6).diffMonths(dateBC) == -3);
+        assert(Date(-1999, 5, 6).diffMonths(dateBC) == -2);
+        assert(Date(-1999, 6, 6).diffMonths(dateBC) == -1);
+        assert(Date(-1999, 8, 6).diffMonths(dateBC) == 1);
+        assert(Date(-1999, 9, 6).diffMonths(dateBC) == 2);
+        assert(Date(-1999, 10, 6).diffMonths(dateBC) == 3);
+        assert(Date(-1999, 11, 6).diffMonths(dateBC) == 4);
+        assert(Date(-1999, 12, 6).diffMonths(dateBC) == 5);
+        assert(Date(-1998, 1, 6).diffMonths(dateBC) == 6);
+        assert(Date(-1998, 2, 6).diffMonths(dateBC) == 7);
+        assert(Date(-1998, 3, 6).diffMonths(dateBC) == 8);
+        assert(Date(-1998, 4, 6).diffMonths(dateBC) == 9);
+        assert(Date(-1998, 5, 6).diffMonths(dateBC) == 10);
+        assert(Date(-1998, 6, 6).diffMonths(dateBC) == 11);
+        assert(Date(-1998, 7, 6).diffMonths(dateBC) == 12);
+        assert(Date(-1998, 8, 6).diffMonths(dateBC) == 13);
+
+        assert(dateBC.diffMonths(Date(-1999, 6, 30)) == 1);
+        assert(dateBC.diffMonths(Date(-1999, 7, 1)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 6)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 11)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 16)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 21)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 26)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 7, 31)) == 0);
+        assert(dateBC.diffMonths(Date(-1999, 8, 1)) == -1);
+
+        assert(dateBC.diffMonths(Date(-2008, 6, 30)) == 109);
+        assert(dateBC.diffMonths(Date(-2008, 7, 1)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 6)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 11)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 16)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 21)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 26)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 7, 31)) == 108);
+        assert(dateBC.diffMonths(Date(-2008, 8, 1)) == 107);
+
+        assert(Date(-1999, 6, 30).diffMonths(dateBC) == -1);
+        assert(Date(-1999, 7, 1).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 6).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 11).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 16).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 21).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 26).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 7, 31).diffMonths(dateBC) == 0);
+        assert(Date(-1999, 8, 1).diffMonths(dateBC) == 1);
+
+        assert(Date(-2008, 6, 30).diffMonths(dateBC) == -109);
+        assert(Date(-2008, 7, 1).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 6).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 11).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 16).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 21).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 26).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 7, 31).diffMonths(dateBC) == -108);
+        assert(Date(-2008, 8, 1).diffMonths(dateBC) == -107);
+
+        //Test Both
+        assert(Date(3, 3, 3).diffMonths(Date(-5, 5, 5)) == 94);
+        assert(Date(-5, 5, 5).diffMonths(Date(3, 3, 3)) == -94);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date.diffMonths(date) == 0);
+        assert(cdate.diffMonths(date) == 0);
+        assert(idate.diffMonths(date) == 0);
+
+        assert(date.diffMonths(cdate) == 0);
+        assert(cdate.diffMonths(cdate) == 0);
+        assert(idate.diffMonths(cdate) == 0);
+
+        assert(date.diffMonths(idate) == 0);
+        assert(cdate.diffMonths(idate) == 0);
+        assert(idate.diffMonths(idate) == 0);
+    }
+
+
+    /++
+        Whether this $(LREF Date) is in a leap year.
+     +/
+    @property bool isLeapYear() @safe const pure nothrow
+    {
+        return yearIsLeapYear(_year);
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, date.isLeapYear = true));
+        static assert(!__traits(compiles, cdate.isLeapYear = true));
+        static assert(!__traits(compiles, idate.isLeapYear = true));
+    }
+
+
+    /++
+        Day of the week this $(LREF Date) is on.
+      +/
+    @property DayOfWeek dayOfWeek() @safe const pure nothrow
+    {
+        return getDayOfWeek(dayOfGregorianCal);
+    }
+
+    unittest
+    {
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.dayOfWeek == DayOfWeek.tue);
+        static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun));
+        assert(idate.dayOfWeek == DayOfWeek.tue);
+        static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun));
+    }
+
+
+    /++
+        Day of the year this $(LREF Date) is on.
+      +/
+    @property ushort dayOfYear() @safe const pure nothrow
+    {
+        if (_month >= Month.jan && _month <= Month.dec)
+        {
+            immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
+            auto monthIndex = _month - Month.jan;
+
+            return cast(ushort)(lastDay[monthIndex] + _day);
+        }
+        assert(0, "Invalid month.");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 1, 1).dayOfYear == 1);
+        assert(Date(1999, 12, 31).dayOfYear == 365);
+        assert(Date(2000, 12, 31).dayOfYear == 366);
+    }
+
+    unittest
+    {
+        import std.range;
+
+        foreach(year; filter!((a){return !yearIsLeapYear(a);})
+                             (chain(testYearsBC, testYearsAD)))
+        {
+            foreach(doy; testDaysOfYear)
+            {
+                assert(Date(year, doy.md.month, doy.md.day).dayOfYear ==
+                                 doy.day);
+            }
+        }
+
+        foreach(year; filter!((a){return yearIsLeapYear(a);})
+                             (chain(testYearsBC, testYearsAD)))
+        {
+            foreach(doy; testDaysOfLeapYear)
+            {
+                assert(Date(year, doy.md.month, doy.md.day).dayOfYear ==
+                                 doy.day);
+            }
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.dayOfYear == 187);
+        assert(idate.dayOfYear == 187);
+    }
+
+    /++
+        Day of the year.
+
+        Params:
+            day = The day of the year to set which day of the year this
+                  $(LREF Date) is on.
+
+        Throws:
+            $(LREF DateTimeException) if the given day is an invalid day of the
+            year.
+      +/
+    @property void dayOfYear(int day) @safe pure
+    {
+        immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
+
+        if(day <= 0 || day > (isLeapYear ? daysInLeapYear : daysInYear) )
+            throw new DateTimeException("Invalid day of the year.");
+
+        foreach (i; 1..lastDay.length)
+        {
+            if (day <= lastDay[i])
+            {
+                _month = cast(Month)(cast(int)Month.jan + i - 1);
+                _day = cast(ubyte)(day - lastDay[i - 1]);
+                return;
+            }
+        }
+        assert(0, "Invalid day of the year.");
+    }
+
+    unittest
+    {
+        static void test(Date date, int day, MonthDay expected, size_t line = __LINE__)
+        {
+            date.dayOfYear = day;
+            assert(date.month == expected.month);
+            assert(date.day == expected.day);
+        }
+
+        foreach(doy; testDaysOfYear)
+        {
+            test(Date(1999, 1, 1), doy.day, doy.md);
+            test(Date(-1, 1, 1), doy.day, doy.md);
+        }
+
+        foreach(doy; testDaysOfLeapYear)
+        {
+            test(Date(2000, 1, 1), doy.day, doy.md);
+            test(Date(-4, 1, 1), doy.day, doy.md);
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.dayOfYear = 187));
+        static assert(!__traits(compiles, idate.dayOfYear = 187));
+    }
+
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
+     +/
+    @property int dayOfGregorianCal() @safe const pure nothrow
+    {
+        if(isAD)
+        {
+            if(_year == 1)
+                return dayOfYear;
+
+            int years = _year - 1;
+            auto days = (years / 400) * daysIn400Years;
+            years %= 400;
+
+            days += (years / 100) * daysIn100Years;
+            years %= 100;
+
+            days += (years / 4) * daysIn4Years;
+            years %= 4;
+
+            days += years * daysInYear;
+
+            days += dayOfYear;
+
+            return days;
+        }
+        else if(_year == 0)
+            return dayOfYear - daysInLeapYear;
+        else
+        {
+            int years = _year;
+            auto days = (years / 400) * daysIn400Years;
+            years %= 400;
+
+            days += (years / 100) * daysIn100Years;
+            years %= 100;
+
+            days += (years / 4) * daysIn4Years;
+            years %= 4;
+
+            if(years < 0)
+            {
+                days -= daysInLeapYear;
+                ++years;
+
+                days += years * daysInYear;
+
+                days -= daysInYear - dayOfYear;
+            }
+            else
+                days -= daysInLeapYear - dayOfYear;
+
+            return days;
+        }
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1, 1, 1).dayOfGregorianCal == 1);
+        assert(Date(1, 12, 31).dayOfGregorianCal == 365);
+        assert(Date(2, 1, 1).dayOfGregorianCal == 366);
+
+        assert(Date(0, 12, 31).dayOfGregorianCal == 0);
+        assert(Date(0, 1, 1).dayOfGregorianCal == -365);
+        assert(Date(-1, 12, 31).dayOfGregorianCal == -366);
+
+        assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120);
+        assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137);
+    }
+
+    unittest
+    {
+        import std.range;
+
+        foreach(gd; chain(testGregDaysBC, testGregDaysAD))
+            assert(gd.date.dayOfGregorianCal == gd.day);
+
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date.dayOfGregorianCal == 729_941);
+        assert(cdate.dayOfGregorianCal == 729_941);
+        assert(idate.dayOfGregorianCal == 729_941);
+    }
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
+
+        Params:
+            day = The day of the Gregorian Calendar to set this $(LREF Date) to.
+     +/
+    @property void dayOfGregorianCal(int day) @safe pure nothrow
+    {
+        this = Date(day);
+    }
+
+    ///
+    unittest
+    {
+        auto date = Date.init;
+        date.dayOfGregorianCal = 1;
+        assert(date == Date(1, 1, 1));
+
+        date.dayOfGregorianCal = 365;
+        assert(date == Date(1, 12, 31));
+
+        date.dayOfGregorianCal = 366;
+        assert(date == Date(2, 1, 1));
+
+        date.dayOfGregorianCal = 0;
+        assert(date == Date(0, 12, 31));
+
+        date.dayOfGregorianCal = -365;
+        assert(date == Date(-0, 1, 1));
+
+        date.dayOfGregorianCal = -366;
+        assert(date == Date(-1, 12, 31));
+
+        date.dayOfGregorianCal = 730_120;
+        assert(date == Date(2000, 1, 1));
+
+        date.dayOfGregorianCal = 734_137;
+        assert(date == Date(2010, 12, 31));
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        date.dayOfGregorianCal = 187;
+        assert(date.dayOfGregorianCal == 187);
+        static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187));
+        static assert(!__traits(compiles, idate.dayOfGregorianCal = 187));
+    }
+
+
+    /++
+        The ISO 8601 week of the year that this $(LREF Date) is in.
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
+      +/
+    @property ubyte isoWeek() @safe const pure nothrow
+    {
+        immutable weekday = dayOfWeek;
+        immutable adjustedWeekday = weekday == DayOfWeek.sun ? 7 : weekday;
+        immutable week = (dayOfYear - adjustedWeekday + 10) / 7;
+
+        try
+        {
+            if(week == 53)
+            {
+                switch(Date(_year + 1, 1, 1).dayOfWeek)
+                {
+                    case DayOfWeek.mon:
+                    case DayOfWeek.tue:
+                    case DayOfWeek.wed:
+                    case DayOfWeek.thu:
+                        return 1;
+                    case DayOfWeek.fri:
+                    case DayOfWeek.sat:
+                    case DayOfWeek.sun:
+                        return 53;
+                    default:
+                        assert(0, "Invalid ISO Week");
+                }
+            }
+            else if(week > 0)
+                return cast(ubyte)week;
+            else
+                return Date(_year - 1, 12, 31).isoWeek;
+        }
+        catch(Exception e)
+            assert(0, "Date's constructor threw.");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(2009, 12, 28).isoWeek == 53);
+        assert(Date(2009, 12, 29).isoWeek == 53);
+        assert(Date(2009, 12, 30).isoWeek == 53);
+        assert(Date(2009, 12, 31).isoWeek == 53);
+        assert(Date(2010, 1, 1).isoWeek == 53);
+        assert(Date(2010, 1, 2).isoWeek == 53);
+        assert(Date(2010, 1, 3).isoWeek == 53);
+        assert(Date(2010, 1, 4).isoWeek == 1);
+        assert(Date(2010, 1, 5).isoWeek == 1);
+        assert(Date(2010, 1, 6).isoWeek == 1);
+        assert(Date(2010, 1, 7).isoWeek == 1);
+        assert(Date(2010, 1, 8).isoWeek == 1);
+        assert(Date(2010, 1, 9).isoWeek == 1);
+        assert(Date(2010, 1, 10).isoWeek == 1);
+        assert(Date(2010, 1, 11).isoWeek == 2);
+        assert(Date(2010, 12, 31).isoWeek == 52);
+
+        assert(Date(2004, 12, 26).isoWeek == 52);
+        assert(Date(2004, 12, 27).isoWeek == 53);
+        assert(Date(2004, 12, 28).isoWeek == 53);
+        assert(Date(2004, 12, 29).isoWeek == 53);
+        assert(Date(2004, 12, 30).isoWeek == 53);
+        assert(Date(2004, 12, 31).isoWeek == 53);
+        assert(Date(2005, 1, 1).isoWeek == 53);
+        assert(Date(2005, 1, 2).isoWeek == 53);
+
+        assert(Date(2005, 12, 31).isoWeek == 52);
+        assert(Date(2007, 1, 1).isoWeek == 1);
+
+        assert(Date(2007, 12, 30).isoWeek == 52);
+        assert(Date(2007, 12, 31).isoWeek == 1);
+        assert(Date(2008, 1, 1).isoWeek == 1);
+
+        assert(Date(2008, 12, 28).isoWeek == 52);
+        assert(Date(2008, 12, 29).isoWeek == 1);
+        assert(Date(2008, 12, 30).isoWeek == 1);
+        assert(Date(2008, 12, 31).isoWeek == 1);
+        assert(Date(2009, 1, 1).isoWeek == 1);
+        assert(Date(2009, 1, 2).isoWeek == 1);
+        assert(Date(2009, 1, 3).isoWeek == 1);
+        assert(Date(2009, 1, 4).isoWeek == 1);
+
+        //Test B.C.
+        //The algorithm should work identically for both A.D. and B.C. since
+        //it doesn't really take the year into account, so B.C. testing
+        //probably isn't really needed.
+        assert(Date(0, 12, 31).isoWeek == 52);
+        assert(Date(0, 1, 4).isoWeek == 1);
+        assert(Date(0, 1, 1).isoWeek == 52);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.isoWeek == 27);
+        static assert(!__traits(compiles, cdate.isoWeek = 3));
+        assert(idate.isoWeek == 27);
+        static assert(!__traits(compiles, idate.isoWeek = 3));
+    }
+
+
+    /++
+        $(LREF Date) for the last day in the month that this $(LREF Date) is in.
+      +/
+    @property Date endOfMonth() @safe const pure nothrow
+    {
+        try
+            return Date(_year, _month, maxDay(_year, _month));
+        catch(Exception e)
+            assert(0, "Date's constructor threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31));
+        assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28));
+        assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29));
+        assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30));
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31));
+        assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28));
+        assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29));
+        assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31));
+        assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30));
+        assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31));
+        assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30));
+        assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31));
+        assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31));
+        assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30));
+        assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31));
+        assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30));
+        assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31));
+
+        //Test B.C.
+        assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31));
+        assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28));
+        assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29));
+        assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31));
+        assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30));
+        assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31));
+        assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30));
+        assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31));
+        assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31));
+        assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30));
+        assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31));
+        assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30));
+        assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31));
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30)));
+        static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30)));
+    }
+
+
+    /++
+        The last day in the month that this $(LREF Date) is in.
+      +/
+    @property ubyte daysInMonth() @safe const pure nothrow
+    {
+        return maxDay(_year, _month);
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1999, 1, 6).daysInMonth == 31);
+        assert(Date(1999, 2, 7).daysInMonth == 28);
+        assert(Date(2000, 2, 7).daysInMonth == 29);
+        assert(Date(2000, 6, 4).daysInMonth == 30);
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(1999, 1, 1).daysInMonth == 31);
+        assert(Date(1999, 2, 1).daysInMonth == 28);
+        assert(Date(2000, 2, 1).daysInMonth == 29);
+        assert(Date(1999, 3, 1).daysInMonth == 31);
+        assert(Date(1999, 4, 1).daysInMonth == 30);
+        assert(Date(1999, 5, 1).daysInMonth == 31);
+        assert(Date(1999, 6, 1).daysInMonth == 30);
+        assert(Date(1999, 7, 1).daysInMonth == 31);
+        assert(Date(1999, 8, 1).daysInMonth == 31);
+        assert(Date(1999, 9, 1).daysInMonth == 30);
+        assert(Date(1999, 10, 1).daysInMonth == 31);
+        assert(Date(1999, 11, 1).daysInMonth == 30);
+        assert(Date(1999, 12, 1).daysInMonth == 31);
+
+        //Test B.C.
+        assert(Date(-1999, 1, 1).daysInMonth == 31);
+        assert(Date(-1999, 2, 1).daysInMonth == 28);
+        assert(Date(-2000, 2, 1).daysInMonth == 29);
+        assert(Date(-1999, 3, 1).daysInMonth == 31);
+        assert(Date(-1999, 4, 1).daysInMonth == 30);
+        assert(Date(-1999, 5, 1).daysInMonth == 31);
+        assert(Date(-1999, 6, 1).daysInMonth == 30);
+        assert(Date(-1999, 7, 1).daysInMonth == 31);
+        assert(Date(-1999, 8, 1).daysInMonth == 31);
+        assert(Date(-1999, 9, 1).daysInMonth == 30);
+        assert(Date(-1999, 10, 1).daysInMonth == 31);
+        assert(Date(-1999, 11, 1).daysInMonth == 30);
+        assert(Date(-1999, 12, 1).daysInMonth == 31);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate.daysInMonth = 30));
+        static assert(!__traits(compiles, idate.daysInMonth = 30));
+    }
+
+
+    /++
+        Whether the current year is a date in A.D.
+      +/
+    @property bool isAD() @safe const pure nothrow
+    {
+        return _year > 0;
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(1, 1, 1).isAD);
+        assert(Date(2010, 12, 31).isAD);
+        assert(!Date(0, 12, 31).isAD);
+        assert(!Date(-2010, 1, 1).isAD);
+    }
+
+    unittest
+    {
+        assert(Date(2010, 7, 4).isAD);
+        assert(Date(1, 1, 1).isAD);
+        assert(!Date(0, 1, 1).isAD);
+        assert(!Date(-1, 1, 1).isAD);
+        assert(!Date(-2010, 7, 4).isAD);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.isAD);
+        assert(idate.isAD);
+    }
+
+
+    /++
+        The $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for this $(LREF Date) at noon (since the Julian day changes
+        at noon).
+      +/
+    @property long julianDay() @safe const pure nothrow
+    {
+        return dayOfGregorianCal + 1_721_425;
+    }
+
+    unittest
+    {
+        assert(Date(-4713, 11, 24).julianDay == 0);
+        assert(Date(0, 12, 31).julianDay == 1_721_425);
+        assert(Date(1, 1, 1).julianDay == 1_721_426);
+        assert(Date(1582, 10, 15).julianDay == 2_299_161);
+        assert(Date(1858, 11, 17).julianDay == 2_400_001);
+        assert(Date(1982, 1, 4).julianDay == 2_444_974);
+        assert(Date(1996, 3, 31).julianDay == 2_450_174);
+        assert(Date(2010, 8, 24).julianDay == 2_455_433);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.julianDay == 2_451_366);
+        assert(idate.julianDay == 2_451_366);
+    }
+
+
+    /++
+        The modified $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for any time on this date (since, the modified
+        Julian day changes at midnight).
+      +/
+    @property long modJulianDay() @safe const pure nothrow
+    {
+        return julianDay - 2_400_001;
+    }
+
+    unittest
+    {
+        assert(Date(1858, 11, 17).modJulianDay == 0);
+        assert(Date(2010, 8, 24).modJulianDay == 55_432);
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.modJulianDay == 51_365);
+        assert(idate.modJulianDay == 51_365);
+    }
+
+
+    /++
+        Converts this $(LREF Date) to a string with the format YYYYMMDD.
+      +/
+    string toISOString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+        {
+            if(_year >= 0)
+            {
+                if(_year < 10_000)
+                    return format("%04d%02d%02d", _year, _month, _day);
+                else
+                    return format("+%05d%02d%02d", _year, _month, _day);
+            }
+            else if(_year > -10_000)
+                return format("%05d%02d%02d", _year, _month, _day);
+            else
+                return format("%06d%02d%02d", _year, _month, _day);
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(2010, 7, 4).toISOString() == "20100704");
+        assert(Date(1998, 12, 25).toISOString() == "19981225");
+        assert(Date(0, 1, 5).toISOString() == "00000105");
+        assert(Date(-4, 1, 5).toISOString() == "-00040105");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(9, 12, 4).toISOString() == "00091204");
+        assert(Date(99, 12, 4).toISOString() == "00991204");
+        assert(Date(999, 12, 4).toISOString() == "09991204");
+        assert(Date(9999, 7, 4).toISOString() == "99990704");
+        assert(Date(10000, 10, 20).toISOString() == "+100001020");
+
+        //Test B.C.
+        assert(Date(0, 12, 4).toISOString() == "00001204");
+        assert(Date(-9, 12, 4).toISOString() == "-00091204");
+        assert(Date(-99, 12, 4).toISOString() == "-00991204");
+        assert(Date(-999, 12, 4).toISOString() == "-09991204");
+        assert(Date(-9999, 7, 4).toISOString() == "-99990704");
+        assert(Date(-10000, 10, 20).toISOString() == "-100001020");
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.toISOString() == "19990706");
+        assert(idate.toISOString() == "19990706");
+    }
+
+    /++
+        Converts this $(LREF Date) to a string with the format YYYY-MM-DD.
+      +/
+    string toISOExtString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+        {
+            if(_year >= 0)
+            {
+                if(_year < 10_000)
+                    return format("%04d-%02d-%02d", _year, _month, _day);
+                else
+                    return format("+%05d-%02d-%02d", _year, _month, _day);
+            }
+            else if(_year > -10_000)
+                return format("%05d-%02d-%02d", _year, _month, _day);
+            else
+                return format("%06d-%02d-%02d", _year, _month, _day);
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(2010, 7, 4).toISOExtString() == "2010-07-04");
+        assert(Date(1998, 12, 25).toISOExtString() == "1998-12-25");
+        assert(Date(0, 1, 5).toISOExtString() == "0000-01-05");
+        assert(Date(-4, 1, 5).toISOExtString() == "-0004-01-05");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(9, 12, 4).toISOExtString() == "0009-12-04");
+        assert(Date(99, 12, 4).toISOExtString() == "0099-12-04");
+        assert(Date(999, 12, 4).toISOExtString() == "0999-12-04");
+        assert(Date(9999, 7, 4).toISOExtString() == "9999-07-04");
+        assert(Date(10000, 10, 20).toISOExtString() == "+10000-10-20");
+
+        //Test B.C.
+        assert(Date(0, 12, 4).toISOExtString() == "0000-12-04");
+        assert(Date(-9, 12, 4).toISOExtString() == "-0009-12-04");
+        assert(Date(-99, 12, 4).toISOExtString() == "-0099-12-04");
+        assert(Date(-999, 12, 4).toISOExtString() == "-0999-12-04");
+        assert(Date(-9999, 7, 4).toISOExtString() == "-9999-07-04");
+        assert(Date(-10000, 10, 20).toISOExtString() == "-10000-10-20");
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.toISOExtString() == "1999-07-06");
+        assert(idate.toISOExtString() == "1999-07-06");
+    }
+
+    /++
+        Converts this $(LREF Date) to a string with the format YYYY-Mon-DD.
+      +/
+    string toSimpleString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+        {
+            if(_year >= 0)
+            {
+                if(_year < 10_000)
+                    return format("%04d-%s-%02d", _year, monthToString(_month), _day);
+                else
+                    return format("+%05d-%s-%02d", _year, monthToString(_month), _day);
+            }
+            else if(_year > -10_000)
+                return format("%05d-%s-%02d", _year, monthToString(_month), _day);
+            else
+                return format("%06d-%s-%02d", _year, monthToString(_month), _day);
+        }
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(Date(2010, 7, 4).toSimpleString() == "2010-Jul-04");
+        assert(Date(1998, 12, 25).toSimpleString() == "1998-Dec-25");
+        assert(Date(0, 1, 5).toSimpleString() == "0000-Jan-05");
+        assert(Date(-4, 1, 5).toSimpleString() == "-0004-Jan-05");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(Date(9, 12, 4).toSimpleString() == "0009-Dec-04");
+        assert(Date(99, 12, 4).toSimpleString() == "0099-Dec-04");
+        assert(Date(999, 12, 4).toSimpleString() == "0999-Dec-04");
+        assert(Date(9999, 7, 4).toSimpleString() == "9999-Jul-04");
+        assert(Date(10000, 10, 20).toSimpleString() == "+10000-Oct-20");
+
+        //Test B.C.
+        assert(Date(0, 12, 4).toSimpleString() == "0000-Dec-04");
+        assert(Date(-9, 12, 4).toSimpleString() == "-0009-Dec-04");
+        assert(Date(-99, 12, 4).toSimpleString() == "-0099-Dec-04");
+        assert(Date(-999, 12, 4).toSimpleString() == "-0999-Dec-04");
+        assert(Date(-9999, 7, 4).toSimpleString() == "-9999-Jul-04");
+        assert(Date(-10000, 10, 20).toSimpleString() == "-10000-Oct-20");
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(cdate.toSimpleString() == "1999-Jul-06");
+        assert(idate.toSimpleString() == "1999-Jul-06");
+    }
+
+
+    /++
+        Converts this $(LREF Date) to a string.
+      +/
+    string toString() @safe const pure nothrow
+    {
+        return toSimpleString();
+    }
+
+    unittest
+    {
+        auto date = Date(1999, 7, 6);
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        assert(date.toString());
+        assert(cdate.toString());
+        assert(idate.toString());
+    }
+
+
+    /++
+        Creates a $(LREF Date) from a string with the format YYYYMMDD. Whitespace
+        is stripped from the given string.
+
+        Params:
+            isoString = A string formatted in the ISO format for dates.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO format
+            or if the resulting $(LREF Date) would not be valid.
+      +/
+    static Date fromISOString(S)(in S isoString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.ascii : isDigit;
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : all, startsWith;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoString));
+
+        enforce(dstr.length >= 8, new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        auto day = dstr[$-2 .. $];
+        auto month = dstr[$-4 .. $-2];
+        auto year = dstr[0 .. $-4];
+
+        enforce(all!isDigit(day), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+        enforce(all!isDigit(month), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        if(year.length > 4)
+        {
+            enforce(year.startsWith('-', '+'),
+                    new DateTimeException(format("Invalid ISO String: %s", isoString)));
+            enforce(all!isDigit(year[1..$]),
+                    new DateTimeException(format("Invalid ISO String: %s", isoString)));
+        }
+        else
+            enforce(all!isDigit(year), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        return Date(to!short(year), to!ubyte(month), to!ubyte(day));
+    }
+
+    ///
+    unittest
+    {
+        assert(Date.fromISOString("20100704") == Date(2010, 7, 4));
+        assert(Date.fromISOString("19981225") == Date(1998, 12, 25));
+        assert(Date.fromISOString("00000105") == Date(0, 1, 5));
+        assert(Date.fromISOString("-00040105") == Date(-4, 1, 5));
+        assert(Date.fromISOString(" 20100704 ") == Date(2010, 7, 4));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(Date.fromISOString(""));
+        assertThrown!DateTimeException(Date.fromISOString("990704"));
+        assertThrown!DateTimeException(Date.fromISOString("0100704"));
+        assertThrown!DateTimeException(Date.fromISOString("2010070"));
+        assertThrown!DateTimeException(Date.fromISOString("2010070 "));
+        assertThrown!DateTimeException(Date.fromISOString("120100704"));
+        assertThrown!DateTimeException(Date.fromISOString("-0100704"));
+        assertThrown!DateTimeException(Date.fromISOString("+0100704"));
+        assertThrown!DateTimeException(Date.fromISOString("2010070a"));
+        assertThrown!DateTimeException(Date.fromISOString("20100a04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010a704"));
+
+        assertThrown!DateTimeException(Date.fromISOString("99-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-07-0"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-07-0 "));
+        assertThrown!DateTimeException(Date.fromISOString("12010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("-010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("+010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-07-0a"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-0a-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-a7-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010/07/04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010/7/04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010/7/4"));
+        assertThrown!DateTimeException(Date.fromISOString("2010/07/4"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-7-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-7-4"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-07-4"));
+
+        assertThrown!DateTimeException(Date.fromISOString("99Jul04"));
+        assertThrown!DateTimeException(Date.fromISOString("010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010Jul0"));
+        assertThrown!DateTimeException(Date.fromISOString("2010Jul0 "));
+        assertThrown!DateTimeException(Date.fromISOString("12010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOString("-010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOString("+010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010Jul0a"));
+        assertThrown!DateTimeException(Date.fromISOString("2010Jua04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010aul04"));
+
+        assertThrown!DateTimeException(Date.fromISOString("99-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOString("010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0 "));
+        assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-aul-04"));
+
+        assertThrown!DateTimeException(Date.fromISOString("2010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04"));
+
+        assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
+        assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6));
+        assert(Date.fromISOString("+019990706") == Date(1999, 7, 6));
+        assert(Date.fromISOString("19990706 ") == Date(1999, 7, 6));
+        assert(Date.fromISOString(" 19990706") == Date(1999, 7, 6));
+        assert(Date.fromISOString(" 19990706 ") == Date(1999, 7, 6));
+    }
+
+
+    /++
+        Creates a $(LREF Date) from a string with the format YYYY-MM-DD. Whitespace
+        is stripped from the given string.
+
+        Params:
+            isoExtString = A string formatted in the ISO Extended format for
+                           dates.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO
+            Extended format or if the resulting $(LREF Date) would not be valid.
+      +/
+    static Date fromISOExtString(S)(in S isoExtString) @safe pure
+        if(isSomeString!(S))
+    {
+        import std.ascii : isDigit;
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : all, startsWith;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoExtString));
+
+        enforce(dstr.length >= 10, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        auto day = dstr[$-2 .. $];
+        auto month = dstr[$-5 .. $-3];
+        auto year = dstr[0 .. $-6];
+
+        enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(dstr[$-6] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(all!isDigit(day),
+                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(all!isDigit(month),
+                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        if(year.length > 4)
+        {
+            enforce(year.startsWith('-', '+'),
+                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+            enforce(all!isDigit(year[1..$]),
+                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        }
+        else
+            enforce(all!isDigit(year),
+                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        return Date(to!short(year), to!ubyte(month), to!ubyte(day));
+    }
+
+    ///
+    unittest
+    {
+        assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4));
+        assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25));
+        assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5));
+        assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5));
+        assert(Date.fromISOExtString(" 2010-07-04 ") == Date(2010, 7, 4));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(Date.fromISOExtString(""));
+        assertThrown!DateTimeException(Date.fromISOExtString("990704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("0100704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010070"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010070 "));
+        assertThrown!DateTimeException(Date.fromISOExtString("120100704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("-0100704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("+0100704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010070a"));
+        assertThrown!DateTimeException(Date.fromISOExtString("20100a04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010a704"));
+
+        assertThrown!DateTimeException(Date.fromISOExtString("99-07-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0 "));
+        assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4"));
+
+        assertThrown!DateTimeException(Date.fromISOExtString("99Jul04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0 "));
+        assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010aul04"));
+
+        assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0 "));
+        assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04"));
+
+        assertThrown!DateTimeException(Date.fromISOExtString("20100704"));
+        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04"));
+
+        assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6));
+        assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6));
+        assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6));
+        assert(Date.fromISOExtString("1999-07-06 ") == Date(1999, 7, 6));
+        assert(Date.fromISOExtString(" 1999-07-06") == Date(1999, 7, 6));
+        assert(Date.fromISOExtString(" 1999-07-06 ") == Date(1999, 7, 6));
+    }
+
+
+    /++
+        Creates a $(LREF Date) from a string with the format YYYY-Mon-DD.
+        Whitespace is stripped from the given string.
+
+        Params:
+            simpleString = A string formatted in the way that toSimpleString
+                           formats dates.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the correct
+            format or if the resulting $(LREF Date) would not be valid.
+      +/
+    static Date fromSimpleString(S)(in S simpleString) @safe pure
+        if(isSomeString!(S))
+    {
+        import std.ascii : isDigit;
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : all, startsWith;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(simpleString));
+
+        enforce(dstr.length >= 11, new DateTimeException(format("Invalid string format: %s", simpleString)));
+
+        auto day = dstr[$-2 .. $];
+        auto month = monthFromString(to!string(dstr[$-6 .. $-3]));
+        auto year = dstr[0 .. $-7];
+
+        enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid string format: %s", simpleString)));
+        enforce(dstr[$-7] == '-', new DateTimeException(format("Invalid string format: %s", simpleString)));
+        enforce(all!isDigit(day), new DateTimeException(format("Invalid string format: %s", simpleString)));
+
+        if(year.length > 4)
+        {
+            enforce(year.startsWith('-', '+'),
+                    new DateTimeException(format("Invalid string format: %s", simpleString)));
+            enforce(all!isDigit(year[1..$]),
+                    new DateTimeException(format("Invalid string format: %s", simpleString)));
+        }
+        else
+            enforce(all!isDigit(year),
+                    new DateTimeException(format("Invalid string format: %s", simpleString)));
+
+        return Date(to!short(year), month, to!ubyte(day));
+    }
+
+    ///
+    unittest
+    {
+        assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4));
+        assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25));
+        assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5));
+        assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5));
+        assert(Date.fromSimpleString(" 2010-Jul-04 ") == Date(2010, 7, 4));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(Date.fromSimpleString(""));
+        assertThrown!DateTimeException(Date.fromSimpleString("990704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("0100704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010070"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010070 "));
+        assertThrown!DateTimeException(Date.fromSimpleString("120100704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("-0100704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("+0100704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010070a"));
+        assertThrown!DateTimeException(Date.fromSimpleString("20100a04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010a704"));
+
+        assertThrown!DateTimeException(Date.fromSimpleString("99-07-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("010-07-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0 "));
+        assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4"));
+
+        assertThrown!DateTimeException(Date.fromSimpleString("99Jul04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("010Jul04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0 "));
+        assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010aul04"));
+
+        assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0 "));
+        assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04"));
+
+        assertThrown!DateTimeException(Date.fromSimpleString("20100704"));
+        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04"));
+
+        assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6));
+        assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6));
+        assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6));
+        assert(Date.fromSimpleString("1999-Jul-06 ") == Date(1999, 7, 6));
+        assert(Date.fromSimpleString(" 1999-Jul-06") == Date(1999, 7, 6));
+        assert(Date.fromSimpleString(" 1999-Jul-06 ") == Date(1999, 7, 6));
+    }
+
+
+    /++
+        Returns the $(LREF Date) farthest in the past which is representable by
+        $(LREF Date).
+      +/
+    @property static Date min() @safe pure nothrow
+    {
+        auto date = Date.init;
+        date._year = short.min;
+        date._month = Month.jan;
+        date._day = 1;
+
+        return date;
+    }
+
+    unittest
+    {
+        assert(Date.min.year < 0);
+        assert(Date.min < Date.max);
+    }
+
+
+    /++
+        Returns the $(LREF Date) farthest in the future which is representable by
+        $(LREF Date).
+      +/
+    @property static Date max() @safe pure nothrow
+    {
+        auto date = Date.init;
+        date._year = short.max;
+        date._month = Month.dec;
+        date._day = 31;
+
+        return date;
+    }
+
+    unittest
+    {
+        assert(Date.max.year > 0);
+        assert(Date.max > Date.min);
+    }
+
+
+private:
+
+    /+
+        Whether the given values form a valid date.
+
+        Params:
+            year  = The year to test.
+            month = The month of the Gregorian Calendar to test.
+            day   = The day of the month to test.
+     +/
+    static bool _valid(int year, int month, int day) @safe pure nothrow
+    {
+        if(!valid!"months"(month))
+            return false;
+
+        return valid!"days"(year, month, day);
+    }
+
+    /+
+        Adds the given number of days to this $(LREF Date). A negative number will
+        subtract.
+
+        The month will be adjusted along with the day if the number of days
+        added (or subtracted) would overflow (or underflow) the current month.
+        The year will be adjusted along with the month if the increase (or
+        decrease) to the month would cause it to overflow (or underflow) the
+        current year.
+
+        $(D _addDays(numDays)) is effectively equivalent to
+        $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days).
+
+        Params:
+            days = The number of days to add to this Date.
+      +/
+    ref Date _addDays(long days) return @safe pure nothrow
+    {
+        dayOfGregorianCal = cast(int)(dayOfGregorianCal + days);
+        return this;
+    }
+
+    unittest
+    {
+        //Test A.D.
+        {
+            auto date = Date(1999, 2, 28);
+            date._addDays(1);
+            assert(date == Date(1999, 3, 1));
+            date._addDays(-1);
+            assert(date == Date(1999, 2, 28));
+        }
+
+        {
+            auto date = Date(2000, 2, 28);
+            date._addDays(1);
+            assert(date == Date(2000, 2, 29));
+            date._addDays(1);
+            assert(date == Date(2000, 3, 1));
+            date._addDays(-1);
+            assert(date == Date(2000, 2, 29));
+        }
+
+        {
+            auto date = Date(1999, 6, 30);
+            date._addDays(1);
+            assert(date == Date(1999, 7, 1));
+            date._addDays(-1);
+            assert(date == Date(1999, 6, 30));
+        }
+
+        {
+            auto date = Date(1999, 7, 31);
+            date._addDays(1);
+            assert(date == Date(1999, 8, 1));
+            date._addDays(-1);
+            assert(date == Date(1999, 7, 31));
+        }
+
+        {
+            auto date = Date(1999, 1, 1);
+            date._addDays(-1);
+            assert(date == Date(1998, 12, 31));
+            date._addDays(1);
+            assert(date == Date(1999, 1, 1));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date._addDays(9);
+            assert(date == Date(1999, 7, 15));
+            date._addDays(-11);
+            assert(date == Date(1999, 7, 4));
+            date._addDays(30);
+            assert(date == Date(1999, 8, 3));
+            date._addDays(-3);
+            assert(date == Date(1999, 7, 31));
+        }
+
+        {
+            auto date = Date(1999, 7, 6);
+            date._addDays(365);
+            assert(date == Date(2000, 7, 5));
+            date._addDays(-365);
+            assert(date == Date(1999, 7, 6));
+            date._addDays(366);
+            assert(date == Date(2000, 7, 6));
+            date._addDays(730);
+            assert(date == Date(2002, 7, 6));
+            date._addDays(-1096);
+            assert(date == Date(1999, 7, 6));
+        }
+
+        //Test B.C.
+        {
+            auto date = Date(-1999, 2, 28);
+            date._addDays(1);
+            assert(date == Date(-1999, 3, 1));
+            date._addDays(-1);
+            assert(date == Date(-1999, 2, 28));
+        }
+
+        {
+            auto date = Date(-2000, 2, 28);
+            date._addDays(1);
+            assert(date == Date(-2000, 2, 29));
+            date._addDays(1);
+            assert(date == Date(-2000, 3, 1));
+            date._addDays(-1);
+            assert(date == Date(-2000, 2, 29));
+        }
+
+        {
+            auto date = Date(-1999, 6, 30);
+            date._addDays(1);
+            assert(date == Date(-1999, 7, 1));
+            date._addDays(-1);
+            assert(date == Date(-1999, 6, 30));
+        }
+
+        {
+            auto date = Date(-1999, 7, 31);
+            date._addDays(1);
+            assert(date == Date(-1999, 8, 1));
+            date._addDays(-1);
+            assert(date == Date(-1999, 7, 31));
+        }
+
+        {
+            auto date = Date(-1999, 1, 1);
+            date._addDays(-1);
+            assert(date == Date(-2000, 12, 31));
+            date._addDays(1);
+            assert(date == Date(-1999, 1, 1));
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date._addDays(9);
+            assert(date == Date(-1999, 7, 15));
+            date._addDays(-11);
+            assert(date == Date(-1999, 7, 4));
+            date._addDays(30);
+            assert(date == Date(-1999, 8, 3));
+            date._addDays(-3);
+        }
+
+        {
+            auto date = Date(-1999, 7, 6);
+            date._addDays(365);
+            assert(date == Date(-1998, 7, 6));
+            date._addDays(-365);
+            assert(date == Date(-1999, 7, 6));
+            date._addDays(366);
+            assert(date == Date(-1998, 7, 7));
+            date._addDays(730);
+            assert(date == Date(-1996, 7, 6));
+            date._addDays(-1096);
+            assert(date == Date(-1999, 7, 6));
+        }
+
+        //Test Both
+        {
+            auto date = Date(1, 7, 6);
+            date._addDays(-365);
+            assert(date == Date(0, 7, 6));
+            date._addDays(365);
+            assert(date == Date(1, 7, 6));
+            date._addDays(-731);
+            assert(date == Date(-1, 7, 6));
+            date._addDays(730);
+            assert(date == Date(1, 7, 5));
+        }
+
+        const cdate = Date(1999, 7, 6);
+        immutable idate = Date(1999, 7, 6);
+        static assert(!__traits(compiles, cdate._addDays(12)));
+        static assert(!__traits(compiles, idate._addDays(12)));
+    }
+
+
+    @safe pure invariant()
+    {
+        import std.format : format;
+        assert(valid!"months"(_month),
+               format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day));
+        assert(valid!"days"(_year, _month, _day),
+               format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day));
+    }
+
+
+    short _year  = 1;
+    Month _month = Month.jan;
+    ubyte _day   = 1;
+}
+
+
+
+/++
+    Represents a time of day with hours, minutes, and seconds. It uses 24 hour
+    time.
++/
+struct TimeOfDay
+{
+public:
+
+    /++
+        Params:
+            hour   = Hour of the day [0 - 24$(RPAREN).
+            minute = Minute of the hour [0 - 60$(RPAREN).
+            second = Second of the minute [0 - 60$(RPAREN).
+
+        Throws:
+            $(LREF DateTimeException) if the resulting $(LREF TimeOfDay) would be not
+            be valid.
+     +/
+    this(int hour, int minute, int second = 0) @safe pure
+    {
+        enforceValid!"hours"(hour);
+        enforceValid!"minutes"(minute);
+        enforceValid!"seconds"(second);
+
+        _hour   = cast(ubyte)hour;
+        _minute = cast(ubyte)minute;
+        _second = cast(ubyte)second;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay(0, 0) == TimeOfDay.init);
+
+        {
+            auto tod = TimeOfDay(0, 0);
+            assert(tod._hour == 0);
+            assert(tod._minute == 0);
+            assert(tod._second == 0);
+        }
+
+        {
+            auto tod = TimeOfDay(12, 30, 33);
+            assert(tod._hour == 12);
+            assert(tod._minute == 30);
+            assert(tod._second == 33);
+        }
+
+        {
+            auto tod = TimeOfDay(23, 59, 59);
+            assert(tod._hour == 23);
+            assert(tod._minute == 59);
+            assert(tod._second == 59);
+        }
+
+        assertThrown!DateTimeException(TimeOfDay(24, 0, 0));
+        assertThrown!DateTimeException(TimeOfDay(0, 60, 0));
+        assertThrown!DateTimeException(TimeOfDay(0, 0, 60));
+    }
+
+
+    /++
+        Compares this $(LREF TimeOfDay) with the given $(LREF TimeOfDay).
+
+        Returns:
+            $(BOOKTABLE,
+            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+            $(TR $(TD this == rhs) $(TD 0))
+            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+            )
+     +/
+    int opCmp(in TimeOfDay rhs) @safe const pure nothrow
+    {
+        if(_hour < rhs._hour)
+            return -1;
+        if(_hour > rhs._hour)
+            return 1;
+
+        if(_minute < rhs._minute)
+            return -1;
+        if(_minute > rhs._minute)
+            return 1;
+
+        if(_second < rhs._second)
+            return -1;
+        if(_second > rhs._second)
+            return 1;
+
+        return 0;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay.init) == 0);
+
+        assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay(0, 0, 0)) == 0);
+        assert(TimeOfDay(12, 0, 0).opCmp(TimeOfDay(12, 0, 0)) == 0);
+        assert(TimeOfDay(0, 30, 0).opCmp(TimeOfDay(0, 30, 0)) == 0);
+        assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0);
+
+        assert(TimeOfDay(12, 30, 0).opCmp(TimeOfDay(12, 30, 0)) == 0);
+        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 33)) == 0);
+
+        assert(TimeOfDay(0, 30, 33).opCmp(TimeOfDay(0, 30, 33)) == 0);
+        assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0);
+
+        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(13, 30, 33)) < 0);
+        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 33)) > 0);
+        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 31, 33)) < 0);
+        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 33)) > 0);
+        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 34)) < 0);
+        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 30, 33)) > 0);
+
+        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 34)) > 0);
+        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(13, 30, 33)) < 0);
+        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 31, 33)) > 0);
+        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(13, 30, 33)) < 0);
+
+        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 34)) > 0);
+        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 31, 33)) < 0);
+
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(ctod.opCmp(itod) == 0);
+        assert(itod.opCmp(ctod) == 0);
+    }
+
+
+    /++
+        Hours past midnight.
+     +/
+    @property ubyte hour() @safe const pure nothrow
+    {
+        return _hour;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay.init.hour == 0);
+        assert(TimeOfDay(12, 0, 0).hour == 12);
+
+        const ctod = TimeOfDay(12, 0, 0);
+        immutable itod = TimeOfDay(12, 0, 0);
+        assert(ctod.hour == 12);
+        assert(itod.hour == 12);
+    }
+
+
+    /++
+        Hours past midnight.
+
+        Params:
+            hour = The hour of the day to set this $(LREF TimeOfDay)'s hour to.
+
+        Throws:
+            $(LREF DateTimeException) if the given hour would result in an invalid
+            $(LREF TimeOfDay).
+     +/
+    @property void hour(int hour) @safe pure
+    {
+        enforceValid!"hours"(hour);
+        _hour = cast(ubyte)hour;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).hour = 24;}());
+
+        auto tod = TimeOfDay(0, 0, 0);
+        tod.hour = 12;
+        assert(tod == TimeOfDay(12, 0, 0));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.hour = 12));
+        static assert(!__traits(compiles, itod.hour = 12));
+    }
+
+
+    /++
+        Minutes past the hour.
+     +/
+    @property ubyte minute() @safe const pure nothrow
+    {
+        return _minute;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay.init.minute == 0);
+        assert(TimeOfDay(0, 30, 0).minute == 30);
+
+        const ctod = TimeOfDay(0, 30, 0);
+        immutable itod = TimeOfDay(0, 30, 0);
+        assert(ctod.minute == 30);
+        assert(itod.minute == 30);
+    }
+
+
+    /++
+        Minutes past the hour.
+
+        Params:
+            minute = The minute to set this $(LREF TimeOfDay)'s minute to.
+
+        Throws:
+            $(LREF DateTimeException) if the given minute would result in an
+            invalid $(LREF TimeOfDay).
+     +/
+    @property void minute(int minute) @safe pure
+    {
+        enforceValid!"minutes"(minute);
+        _minute = cast(ubyte)minute;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).minute = 60;}());
+
+        auto tod = TimeOfDay(0, 0, 0);
+        tod.minute = 30;
+        assert(tod == TimeOfDay(0, 30, 0));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.minute = 30));
+        static assert(!__traits(compiles, itod.minute = 30));
+    }
+
+
+    /++
+        Seconds past the minute.
+     +/
+    @property ubyte second() @safe const pure nothrow
+    {
+        return _second;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay.init.second == 0);
+        assert(TimeOfDay(0, 0, 33).second == 33);
+
+        const ctod = TimeOfDay(0, 0, 33);
+        immutable itod = TimeOfDay(0, 0, 33);
+        assert(ctod.second == 33);
+        assert(itod.second == 33);
+    }
+
+
+    /++
+        Seconds past the minute.
+
+        Params:
+            second = The second to set this $(LREF TimeOfDay)'s second to.
+
+        Throws:
+            $(LREF DateTimeException) if the given second would result in an
+            invalid $(LREF TimeOfDay).
+     +/
+    @property void second(int second) @safe pure
+    {
+        enforceValid!"seconds"(second);
+        _second = cast(ubyte)second;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).second = 60;}());
+
+        auto tod = TimeOfDay(0, 0, 0);
+        tod.second = 33;
+        assert(tod == TimeOfDay(0, 0, 33));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.second = 33));
+        static assert(!__traits(compiles, itod.second = 33));
+    }
+
+
+    /++
+        Adds the given number of units to this $(LREF TimeOfDay). A negative number
+        will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. For instance, rolling a $(LREF TimeOfDay)
+        one hours's worth of minutes gets the exact same
+        $(LREF TimeOfDay).
+
+        Accepted units are $(D "hours"), $(D "minutes"), and $(D "seconds").
+
+        Params:
+            units = The units to add.
+            value = The number of $(D_PARAM units) to add to this
+                    $(LREF TimeOfDay).
+      +/
+    ref TimeOfDay roll(string units)(long value) @safe pure nothrow
+        if(units == "hours")
+    {
+        return this += dur!"hours"(value);
+    }
+
+    ///
+    unittest
+    {
+        auto tod1 = TimeOfDay(7, 12, 0);
+        tod1.roll!"hours"(1);
+        assert(tod1 == TimeOfDay(8, 12, 0));
+
+        auto tod2 = TimeOfDay(7, 12, 0);
+        tod2.roll!"hours"(-1);
+        assert(tod2 == TimeOfDay(6, 12, 0));
+
+        auto tod3 = TimeOfDay(23, 59, 0);
+        tod3.roll!"minutes"(1);
+        assert(tod3 == TimeOfDay(23, 0, 0));
+
+        auto tod4 = TimeOfDay(0, 0, 0);
+        tod4.roll!"minutes"(-1);
+        assert(tod4 == TimeOfDay(0, 59, 0));
+
+        auto tod5 = TimeOfDay(23, 59, 59);
+        tod5.roll!"seconds"(1);
+        assert(tod5 == TimeOfDay(23, 59, 0));
+
+        auto tod6 = TimeOfDay(0, 0, 0);
+        tod6.roll!"seconds"(-1);
+        assert(tod6 == TimeOfDay(0, 0, 59));
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 27, 2);
+        tod.roll!"hours"(22).roll!"hours"(-7);
+        assert(tod == TimeOfDay(3, 27, 2));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.roll!"hours"(53)));
+        static assert(!__traits(compiles, itod.roll!"hours"(53)));
+    }
+
+
+    //Shares documentation with "hours" version.
+    ref TimeOfDay roll(string units)(long value) @safe pure nothrow
+        if(units == "minutes" ||
+           units == "seconds")
+    {
+        import std.format : format;
+
+        enum memberVarStr = units[0 .. $ - 1];
+        value %= 60;
+        mixin(format("auto newVal = cast(ubyte)(_%s) + value;", memberVarStr));
+
+        if(value < 0)
+        {
+            if(newVal < 0)
+                newVal += 60;
+        }
+        else if(newVal >= 60)
+            newVal -= 60;
+
+        mixin(format("_%s = cast(ubyte)newVal;", memberVarStr));
+        return this;
+    }
+
+    //Test roll!"minutes"().
+    unittest
+    {
+        static void testTOD(TimeOfDay orig, int minutes, in TimeOfDay expected, size_t line = __LINE__)
+        {
+            orig.roll!"minutes"(minutes);
+            assert(orig == expected);
+        }
+
+        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 31, 33));
+        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 32, 33));
+        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 33, 33));
+        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 34, 33));
+        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 35, 33));
+        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 40, 33));
+        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 45, 33));
+        testTOD(TimeOfDay(12, 30, 33), 29, TimeOfDay(12, 59, 33));
+        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), 45, TimeOfDay(12, 15, 33));
+        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 75, TimeOfDay(12, 45, 33));
+        testTOD(TimeOfDay(12, 30, 33), 90, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), 100, TimeOfDay(12, 10, 33));
+
+        testTOD(TimeOfDay(12, 30, 33), 689, TimeOfDay(12, 59, 33));
+        testTOD(TimeOfDay(12, 30, 33), 690, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), 691, TimeOfDay(12, 1, 33));
+        testTOD(TimeOfDay(12, 30, 33), 960, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1439, TimeOfDay(12, 29, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1440, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1441, TimeOfDay(12, 31, 33));
+        testTOD(TimeOfDay(12, 30, 33), 2880, TimeOfDay(12, 30, 33));
+
+        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 29, 33));
+        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 28, 33));
+        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 27, 33));
+        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 26, 33));
+        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 25, 33));
+        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 20, 33));
+        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 15, 33));
+        testTOD(TimeOfDay(12, 30, 33), -29, TimeOfDay(12, 1, 33));
+        testTOD(TimeOfDay(12, 30, 33), -30, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), -45, TimeOfDay(12, 45, 33));
+        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), -75, TimeOfDay(12, 15, 33));
+        testTOD(TimeOfDay(12, 30, 33), -90, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), -100, TimeOfDay(12, 50, 33));
+
+        testTOD(TimeOfDay(12, 30, 33), -749, TimeOfDay(12, 1, 33));
+        testTOD(TimeOfDay(12, 30, 33), -750, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 30, 33), -751, TimeOfDay(12, 59, 33));
+        testTOD(TimeOfDay(12, 30, 33), -960, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), -1439, TimeOfDay(12, 31, 33));
+        testTOD(TimeOfDay(12, 30, 33), -1440, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), -1441, TimeOfDay(12, 29, 33));
+        testTOD(TimeOfDay(12, 30, 33), -2880, TimeOfDay(12, 30, 33));
+
+        testTOD(TimeOfDay(12, 0, 33), 1, TimeOfDay(12, 1, 33));
+        testTOD(TimeOfDay(12, 0, 33), 0, TimeOfDay(12, 0, 33));
+        testTOD(TimeOfDay(12, 0, 33), -1, TimeOfDay(12, 59, 33));
+
+        testTOD(TimeOfDay(11, 59, 33), 1, TimeOfDay(11, 0, 33));
+        testTOD(TimeOfDay(11, 59, 33), 0, TimeOfDay(11, 59, 33));
+        testTOD(TimeOfDay(11, 59, 33), -1, TimeOfDay(11, 58, 33));
+
+        testTOD(TimeOfDay(0, 0, 33), 1, TimeOfDay(0, 1, 33));
+        testTOD(TimeOfDay(0, 0, 33), 0, TimeOfDay(0, 0, 33));
+        testTOD(TimeOfDay(0, 0, 33), -1, TimeOfDay(0, 59, 33));
+
+        testTOD(TimeOfDay(23, 59, 33), 1, TimeOfDay(23, 0, 33));
+        testTOD(TimeOfDay(23, 59, 33), 0, TimeOfDay(23, 59, 33));
+        testTOD(TimeOfDay(23, 59, 33), -1, TimeOfDay(23, 58, 33));
+
+        auto tod = TimeOfDay(12, 27, 2);
+        tod.roll!"minutes"(97).roll!"minutes"(-102);
+        assert(tod == TimeOfDay(12, 22, 2));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.roll!"minutes"(7)));
+        static assert(!__traits(compiles, itod.roll!"minutes"(7)));
+    }
+
+    //Test roll!"seconds"().
+    unittest
+    {
+        static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__)
+        {
+            orig.roll!"seconds"(seconds);
+            assert(orig == expected);
+        }
+
+        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34));
+        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35));
+        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36));
+        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37));
+        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38));
+        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43));
+        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48));
+        testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59));
+        testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 30, 3));
+        testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 30, 34));
+
+        testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 30, 59));
+        testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(12, 30, 1));
+        testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(12, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(12, 30, 34));
+        testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(12, 30, 33));
+
+        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31));
+        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30));
+        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29));
+        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28));
+        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23));
+        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18));
+        testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 30, 59));
+        testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 30, 58));
+        testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 30, 34));
+        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 30, 32));
+
+        testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1));
+        testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 30, 59));
+
+        testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1));
+        testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0));
+        testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(12, 0, 59));
+
+        testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1));
+        testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0));
+        testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(0, 0, 59));
+
+        testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(23, 59, 0));
+        testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59));
+        testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58));
+
+        auto tod = TimeOfDay(12, 27, 2);
+        tod.roll!"seconds"(105).roll!"seconds"(-77);
+        assert(tod == TimeOfDay(12, 27, 30));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod.roll!"seconds"(7)));
+        static assert(!__traits(compiles, itod.roll!"seconds"(7)));
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF TimeOfDay).
+
+        The legal types of arithmetic for $(LREF TimeOfDay) using this operator
+        are
+
+        $(BOOKTABLE,
+        $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay))
+        $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF TimeOfDay).
+      +/
+    TimeOfDay opBinary(string op)(Duration duration) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        TimeOfDay retval = this;
+        immutable seconds = duration.total!"seconds";
+        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
+    }
+
+    ///
+    unittest
+    {
+        assert(TimeOfDay(12, 12, 12) + seconds(1) == TimeOfDay(12, 12, 13));
+        assert(TimeOfDay(12, 12, 12) + minutes(1) == TimeOfDay(12, 13, 12));
+        assert(TimeOfDay(12, 12, 12) + hours(1) == TimeOfDay(13, 12, 12));
+        assert(TimeOfDay(23, 59, 59) + seconds(1) == TimeOfDay(0, 0, 0));
+
+        assert(TimeOfDay(12, 12, 12) - seconds(1) == TimeOfDay(12, 12, 11));
+        assert(TimeOfDay(12, 12, 12) - minutes(1) == TimeOfDay(12, 11, 12));
+        assert(TimeOfDay(12, 12, 12) - hours(1) == TimeOfDay(11, 12, 12));
+        assert(TimeOfDay(0, 0, 0) - seconds(1) == TimeOfDay(23, 59, 59));
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 30, 33);
+
+        assert(tod + dur!"hours"(7) == TimeOfDay(19, 30, 33));
+        assert(tod + dur!"hours"(-7) == TimeOfDay(5, 30, 33));
+        assert(tod + dur!"minutes"(7) == TimeOfDay(12, 37, 33));
+        assert(tod + dur!"minutes"(-7) == TimeOfDay(12, 23, 33));
+        assert(tod + dur!"seconds"(7) == TimeOfDay(12, 30, 40));
+        assert(tod + dur!"seconds"(-7) == TimeOfDay(12, 30, 26));
+
+        assert(tod + dur!"msecs"(7000) == TimeOfDay(12, 30, 40));
+        assert(tod + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26));
+        assert(tod + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
+        assert(tod + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));
+        assert(tod + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40));
+        assert(tod + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26));
+
+        assert(tod - dur!"hours"(-7) == TimeOfDay(19, 30, 33));
+        assert(tod - dur!"hours"(7) == TimeOfDay(5, 30, 33));
+        assert(tod - dur!"minutes"(-7) == TimeOfDay(12, 37, 33));
+        assert(tod - dur!"minutes"(7) == TimeOfDay(12, 23, 33));
+        assert(tod - dur!"seconds"(-7) == TimeOfDay(12, 30, 40));
+        assert(tod - dur!"seconds"(7) == TimeOfDay(12, 30, 26));
+
+        assert(tod - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40));
+        assert(tod - dur!"msecs"(7000) == TimeOfDay(12, 30, 26));
+        assert(tod - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
+        assert(tod - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
+        assert(tod - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40));
+        assert(tod - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26));
+
+        auto duration = dur!"hours"(11);
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(tod + duration == TimeOfDay(23, 30, 33));
+        assert(ctod + duration == TimeOfDay(23, 30, 33));
+        assert(itod + duration == TimeOfDay(23, 30, 33));
+
+        assert(tod - duration == TimeOfDay(1, 30, 33));
+        assert(ctod - duration == TimeOfDay(1, 30, 33));
+        assert(itod - duration == TimeOfDay(1, 30, 33));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines + and - with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    TimeOfDay opBinary(string op)(TickDuration td) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        TimeOfDay retval = this;
+        immutable seconds = td.seconds;
+        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            auto tod = TimeOfDay(12, 30, 33);
+
+            assert(tod + TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
+            assert(tod + TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));
+
+            assert(tod - TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
+            assert(tod - TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
+        }
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF TimeOfDay), as well as assigning the result to this
+        $(LREF TimeOfDay).
+
+        The legal types of arithmetic for $(LREF TimeOfDay) using this operator
+        are
+
+        $(BOOKTABLE,
+        $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay))
+        $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF TimeOfDay).
+      +/
+    ref TimeOfDay opOpAssign(string op)(Duration duration) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable seconds = duration.total!"seconds";
+        mixin("return _addSeconds(" ~ op ~ "seconds);");
+    }
+
+    unittest
+    {
+        auto duration = dur!"hours"(12);
+
+        assert(TimeOfDay(12, 30, 33) + dur!"hours"(7) == TimeOfDay(19, 30, 33));
+        assert(TimeOfDay(12, 30, 33) + dur!"hours"(-7) == TimeOfDay(5, 30, 33));
+        assert(TimeOfDay(12, 30, 33) + dur!"minutes"(7) == TimeOfDay(12, 37, 33));
+        assert(TimeOfDay(12, 30, 33) + dur!"minutes"(-7) == TimeOfDay(12, 23, 33));
+        assert(TimeOfDay(12, 30, 33) + dur!"seconds"(7) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) + dur!"seconds"(-7) == TimeOfDay(12, 30, 26));
+
+        assert(TimeOfDay(12, 30, 33) + dur!"msecs"(7000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26));
+        assert(TimeOfDay(12, 30, 33) + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));
+        assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26));
+
+        assert(TimeOfDay(12, 30, 33) - dur!"hours"(-7) == TimeOfDay(19, 30, 33));
+        assert(TimeOfDay(12, 30, 33) - dur!"hours"(7) == TimeOfDay(5, 30, 33));
+        assert(TimeOfDay(12, 30, 33) - dur!"minutes"(-7) == TimeOfDay(12, 37, 33));
+        assert(TimeOfDay(12, 30, 33) - dur!"minutes"(7) == TimeOfDay(12, 23, 33));
+        assert(TimeOfDay(12, 30, 33) - dur!"seconds"(-7) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) - dur!"seconds"(7) == TimeOfDay(12, 30, 26));
+
+        assert(TimeOfDay(12, 30, 33) - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) - dur!"msecs"(7000) == TimeOfDay(12, 30, 26));
+        assert(TimeOfDay(12, 30, 33) - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
+        assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40));
+        assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26));
+
+        auto tod = TimeOfDay(19, 17, 22);
+        (tod += dur!"seconds"(9)) += dur!"seconds"(-7292);
+        assert(tod == TimeOfDay(17, 15, 59));
+
+        const ctod = TimeOfDay(12, 33, 30);
+        immutable itod = TimeOfDay(12, 33, 30);
+        static assert(!__traits(compiles, ctod += duration));
+        static assert(!__traits(compiles, itod += duration));
+        static assert(!__traits(compiles, ctod -= duration));
+        static assert(!__traits(compiles, itod -= duration));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines += and -= with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    ref TimeOfDay opOpAssign(string op)(TickDuration td) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        immutable seconds = td.seconds;
+        mixin("return _addSeconds(" ~ op ~ "seconds);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            {
+                auto tod = TimeOfDay(12, 30, 33);
+                tod += TickDuration.from!"usecs"(7_000_000);
+                assert(tod == TimeOfDay(12, 30, 40));
+            }
+
+            {
+                auto tod = TimeOfDay(12, 30, 33);
+                tod += TickDuration.from!"usecs"(-7_000_000);
+                assert(tod == TimeOfDay(12, 30, 26));
+            }
+
+            {
+                auto tod = TimeOfDay(12, 30, 33);
+                tod -= TickDuration.from!"usecs"(-7_000_000);
+                assert(tod == TimeOfDay(12, 30, 40));
+            }
+
+            {
+                auto tod = TimeOfDay(12, 30, 33);
+                tod -= TickDuration.from!"usecs"(7_000_000);
+                assert(tod == TimeOfDay(12, 30, 26));
+            }
+        }
+    }
+
+
+    /++
+        Gives the difference between two $(LREF TimeOfDay)s.
+
+        The legal types of arithmetic for $(LREF TimeOfDay) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD TimeOfDay) $(TD -) $(TD TimeOfDay) $(TD -->) $(TD duration))
+        )
+
+        Params:
+            rhs = The $(LREF TimeOfDay) to subtract from this one.
+      +/
+    Duration opBinary(string op)(in TimeOfDay rhs) @safe const pure nothrow
+        if(op == "-")
+    {
+        immutable lhsSec = _hour * 3600 + _minute * 60 + _second;
+        immutable rhsSec = rhs._hour * 3600 + rhs._minute * 60 + rhs._second;
+
+        return dur!"seconds"(lhsSec - rhsSec);
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 30, 33);
+
+        assert(TimeOfDay(7, 12, 52) - TimeOfDay(12, 30, 33) == dur!"seconds"(-19_061));
+        assert(TimeOfDay(12, 30, 33) - TimeOfDay(7, 12, 52) == dur!"seconds"(19_061));
+        assert(TimeOfDay(12, 30, 33) - TimeOfDay(14, 30, 33) == dur!"seconds"(-7200));
+        assert(TimeOfDay(14, 30, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(7200));
+        assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 34, 33) == dur!"seconds"(-240));
+        assert(TimeOfDay(12, 34, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(240));
+        assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 30, 34) == dur!"seconds"(-1));
+        assert(TimeOfDay(12, 30, 34) - TimeOfDay(12, 30, 33) == dur!"seconds"(1));
+
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(tod - tod == Duration.zero);
+        assert(ctod - tod == Duration.zero);
+        assert(itod - tod == Duration.zero);
+
+        assert(tod - ctod == Duration.zero);
+        assert(ctod - ctod == Duration.zero);
+        assert(itod - ctod == Duration.zero);
+
+        assert(tod - itod == Duration.zero);
+        assert(ctod - itod == Duration.zero);
+        assert(itod - itod == Duration.zero);
+    }
+
+
+    /++
+        Converts this $(LREF TimeOfDay) to a string with the format HHMMSS.
+      +/
+    string toISOString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+            return format("%02d%02d%02d", _hour, _minute, _second);
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(TimeOfDay(0, 0, 0).toISOString() == "000000");
+        assert(TimeOfDay(12, 30, 33).toISOString() == "123033");
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 30, 33);
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(tod.toISOString() == "123033");
+        assert(ctod.toISOString() == "123033");
+        assert(itod.toISOString() == "123033");
+    }
+
+
+    /++
+        Converts this $(LREF TimeOfDay) to a string with the format HH:MM:SS.
+      +/
+    string toISOExtString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+            return format("%02d:%02d:%02d", _hour, _minute, _second);
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(TimeOfDay(0, 0, 0).toISOExtString() == "00:00:00");
+        assert(TimeOfDay(12, 30, 33).toISOExtString() == "12:30:33");
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 30, 33);
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(tod.toISOExtString() == "12:30:33");
+        assert(ctod.toISOExtString() == "12:30:33");
+        assert(itod.toISOExtString() == "12:30:33");
+    }
+
+
+    /++
+        Converts this TimeOfDay to a string.
+      +/
+    string toString() @safe const pure nothrow
+    {
+        return toISOExtString();
+    }
+
+    unittest
+    {
+        auto tod = TimeOfDay(12, 30, 33);
+        const ctod = TimeOfDay(12, 30, 33);
+        immutable itod = TimeOfDay(12, 30, 33);
+        assert(tod.toString());
+        assert(ctod.toString());
+        assert(itod.toString());
+    }
+
+
+    /++
+        Creates a $(LREF TimeOfDay) from a string with the format HHMMSS.
+        Whitespace is stripped from the given string.
+
+        Params:
+            isoString = A string formatted in the ISO format for times.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO format
+            or if the resulting $(LREF TimeOfDay) would not be valid.
+      +/
+    static TimeOfDay fromISOString(S)(in S isoString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.ascii : isDigit;
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : all;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoString));
+
+        enforce(dstr.length == 6, new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        auto hours = dstr[0 .. 2];
+        auto minutes = dstr[2 .. 4];
+        auto seconds = dstr[4 .. $];
+
+        enforce(all!isDigit(hours), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+        enforce(all!isDigit(minutes), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+        enforce(all!isDigit(seconds), new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds));
+    }
+
+    ///
+    unittest
+    {
+        assert(TimeOfDay.fromISOString("000000") == TimeOfDay(0, 0, 0));
+        assert(TimeOfDay.fromISOString("123033") == TimeOfDay(12, 30, 33));
+        assert(TimeOfDay.fromISOString(" 123033 ") == TimeOfDay(12, 30, 33));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(TimeOfDay.fromISOString(""));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("00000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("13033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("1277"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12707"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12070"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12303a"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("1230a3"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("123a33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12a033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("1a0033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("a20033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("1200330"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("-120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("+120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("120033am"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("120033pm"));
+
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0::"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString(":0:"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("::0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:00:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:00:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("13:0:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:7"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:07"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:07:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:3a"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:a3"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:3a:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:a0:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("1a:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("a2:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:003:30"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("120:03:30"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("012:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("01:200:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("-12:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("+12:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33am"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33pm"));
+
+        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33"));
+
+        assert(TimeOfDay.fromISOString("011217") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOString("001412") == TimeOfDay(0, 14, 12));
+        assert(TimeOfDay.fromISOString("000007") == TimeOfDay(0, 0, 7));
+        assert(TimeOfDay.fromISOString("011217 ") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOString(" 011217") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOString(" 011217 ") == TimeOfDay(1, 12, 17));
+    }
+
+
+    /++
+        Creates a $(LREF TimeOfDay) from a string with the format HH:MM:SS.
+        Whitespace is stripped from the given string.
+
+        Params:
+            isoExtString = A string formatted in the ISO Extended format for times.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO
+            Extended format or if the resulting $(LREF TimeOfDay) would not be
+            valid.
+      +/
+    static TimeOfDay fromISOExtString(S)(in S isoExtString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.ascii : isDigit;
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : all;
+        import std.format : format;
+
+        auto dstr = to!dstring(strip(isoExtString));
+
+        enforce(dstr.length == 8, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        auto hours = dstr[0 .. 2];
+        auto minutes = dstr[3 .. 5];
+        auto seconds = dstr[6 .. $];
+
+        enforce(dstr[2] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(dstr[5] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(all!isDigit(hours),
+                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(all!isDigit(minutes),
+                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        enforce(all!isDigit(seconds),
+                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds));
+    }
+
+    ///
+    unittest
+    {
+        assert(TimeOfDay.fromISOExtString("00:00:00") == TimeOfDay(0, 0, 0));
+        assert(TimeOfDay.fromISOExtString("12:30:33") == TimeOfDay(12, 30, 33));
+        assert(TimeOfDay.fromISOExtString(" 12:30:33 ") == TimeOfDay(12, 30, 33));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString(""));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00000"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1277"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12707"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12070"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12303a"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1230a3"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("123a33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12a033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a0033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a20033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1200330"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+120033"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033am"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033pm"));
+
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0::"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString(":0:"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("::0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:00:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:00:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:00"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13:0:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:7"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:07"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:07:0"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:3a"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:a3"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:3a:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:a0:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a2:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:003:30"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120:03:30"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("012:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("01:200:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-12:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+12:00:33"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33am"));
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33pm"));
+
+        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033"));
+
+        assert(TimeOfDay.fromISOExtString("01:12:17") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOExtString("00:14:12") == TimeOfDay(0, 14, 12));
+        assert(TimeOfDay.fromISOExtString("00:00:07") == TimeOfDay(0, 0, 7));
+        assert(TimeOfDay.fromISOExtString("01:12:17 ") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOExtString(" 01:12:17") == TimeOfDay(1, 12, 17));
+        assert(TimeOfDay.fromISOExtString(" 01:12:17 ") == TimeOfDay(1, 12, 17));
+    }
+
+
+    /++
+        Returns midnight.
+      +/
+    @property static TimeOfDay min() @safe pure nothrow
+    {
+        return TimeOfDay.init;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay.min.hour == 0);
+        assert(TimeOfDay.min.minute == 0);
+        assert(TimeOfDay.min.second == 0);
+        assert(TimeOfDay.min < TimeOfDay.max);
+    }
+
+
+    /++
+        Returns one second short of midnight.
+      +/
+    @property static TimeOfDay max() @safe pure nothrow
+    {
+        auto tod = TimeOfDay.init;
+        tod._hour = maxHour;
+        tod._minute = maxMinute;
+        tod._second = maxSecond;
+
+        return tod;
+    }
+
+    unittest
+    {
+        assert(TimeOfDay.max.hour == 23);
+        assert(TimeOfDay.max.minute == 59);
+        assert(TimeOfDay.max.second == 59);
+        assert(TimeOfDay.max > TimeOfDay.min);
+    }
+
+
+private:
+
+    /+
+        Add seconds to the time of day. Negative values will subtract. If the
+        number of seconds overflows (or underflows), then the seconds will wrap,
+        increasing (or decreasing) the number of minutes accordingly. If the
+        number of minutes overflows (or underflows), then the minutes will wrap.
+        If the number of minutes overflows(or underflows), then the hour will
+        wrap. (e.g. adding 90 seconds to 23:59:00 would result in 00:00:30).
+
+        Params:
+            seconds = The number of seconds to add to this TimeOfDay.
+      +/
+    ref TimeOfDay _addSeconds(long seconds) return @safe pure nothrow
+    {
+        long hnsecs = convert!("seconds", "hnsecs")(seconds);
+        hnsecs += convert!("hours", "hnsecs")(_hour);
+        hnsecs += convert!("minutes", "hnsecs")(_minute);
+        hnsecs += convert!("seconds", "hnsecs")(_second);
+
+        hnsecs %= convert!("days", "hnsecs")(1);
+
+        if(hnsecs < 0)
+            hnsecs += convert!("days", "hnsecs")(1);
+
+        immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs);
+        immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
+        immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+        _hour = cast(ubyte)newHours;
+        _minute = cast(ubyte)newMinutes;
+        _second = cast(ubyte)newSeconds;
+
+        return this;
+    }
+
+    unittest
+    {
+        static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__)
+        {
+            orig._addSeconds(seconds);
+            assert(orig == expected);
+        }
+
+        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34));
+        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35));
+        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36));
+        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37));
+        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38));
+        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43));
+        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48));
+        testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59));
+        testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 31, 0));
+        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 31, 3));
+        testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 31, 32));
+        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 31, 33));
+        testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 31, 34));
+
+        testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 59, 59));
+        testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(13, 0, 0));
+        testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(13, 0, 1));
+        testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(13, 4, 0));
+        testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(13, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(13, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(13, 30, 34));
+        testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(14, 30, 33));
+
+        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31));
+        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30));
+        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29));
+        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28));
+        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23));
+        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18));
+        testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 29, 59));
+        testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 29, 58));
+        testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 29, 34));
+        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 29, 33));
+        testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 29, 32));
+
+        testTOD(TimeOfDay(12, 30, 33), -1833, TimeOfDay(12, 0, 0));
+        testTOD(TimeOfDay(12, 30, 33), -1834, TimeOfDay(11, 59, 59));
+        testTOD(TimeOfDay(12, 30, 33), -3600, TimeOfDay(11, 30, 33));
+        testTOD(TimeOfDay(12, 30, 33), -3601, TimeOfDay(11, 30, 32));
+        testTOD(TimeOfDay(12, 30, 33), -5134, TimeOfDay(11, 4, 59));
+        testTOD(TimeOfDay(12, 30, 33), -7200, TimeOfDay(10, 30, 33));
+
+        testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1));
+        testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0));
+        testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 29, 59));
+
+        testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1));
+        testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0));
+        testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(11, 59, 59));
+
+        testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1));
+        testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0));
+        testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(23, 59, 59));
+
+        testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(0, 0, 0));
+        testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59));
+        testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58));
+
+        const ctod = TimeOfDay(0, 0, 0);
+        immutable itod = TimeOfDay(0, 0, 0);
+        static assert(!__traits(compiles, ctod._addSeconds(7)));
+        static assert(!__traits(compiles, itod._addSeconds(7)));
+    }
+
+
+    /+
+        Whether the given values form a valid $(LREF TimeOfDay).
+     +/
+    static bool _valid(int hour, int minute, int second) @safe pure nothrow
+    {
+        return valid!"hours"(hour) && valid!"minutes"(minute) && valid!"seconds"(second);
+    }
+
+
+    @safe pure invariant()
+    {
+        import std.format : format;
+        assert(_valid(_hour, _minute, _second),
+               format("Invariant Failure: hour [%s] minute [%s] second [%s]", _hour, _minute, _second));
+    }
+
+    ubyte _hour;
+    ubyte _minute;
+    ubyte _second;
+
+    enum ubyte maxHour   = 24 - 1;
+    enum ubyte maxMinute = 60 - 1;
+    enum ubyte maxSecond = 60 - 1;
+}
+
+
+/++
+   Combines the $(LREF Date) and $(LREF TimeOfDay) structs to give an object
+   which holds both the date and the time. It is optimized for calendar-based
+   operations and has no concept of time zone. For an object which is
+   optimized for time operations based on the system time, use
+   $(LREF SysTime). $(LREF SysTime) has a concept of time zone and has much higher
+   precision (hnsecs). $(D DateTime) is intended primarily for calendar-based
+   uses rather than precise time operations.
+  +/
+struct DateTime
+{
+public:
+
+    /++
+        Params:
+            date = The date portion of $(LREF DateTime).
+            tod  = The time portion of $(LREF DateTime).
+      +/
+    this(in Date date, in TimeOfDay tod = TimeOfDay.init) @safe pure nothrow
+    {
+        _date = date;
+        _tod = tod;
+    }
+
+    unittest
+    {
+        {
+            auto dt = DateTime.init;
+            assert(dt._date == Date.init);
+            assert(dt._tod == TimeOfDay.init);
+        }
+
+        {
+            auto dt = DateTime(Date(1999, 7 ,6));
+            assert(dt._date == Date(1999, 7, 6));
+            assert(dt._tod == TimeOfDay.init);
+        }
+
+        {
+            auto dt = DateTime(Date(1999, 7 ,6), TimeOfDay(12, 30, 33));
+            assert(dt._date == Date(1999, 7, 6));
+            assert(dt._tod == TimeOfDay(12, 30, 33));
+        }
+    }
+
+
+    /++
+        Params:
+            year   = The year portion of the date.
+            month  = The month portion of the date.
+            day    = The day portion of the date.
+            hour   = The hour portion of the time;
+            minute = The minute portion of the time;
+            second = The second portion of the time;
+      +/
+    this(int year, int month, int day, int hour = 0, int minute = 0, int second = 0) @safe pure
+    {
+        _date = Date(year, month, day);
+        _tod = TimeOfDay(hour, minute, second);
+    }
+
+    unittest
+    {
+        {
+            auto dt = DateTime(1999, 7 ,6);
+            assert(dt._date == Date(1999, 7, 6));
+            assert(dt._tod == TimeOfDay.init);
+        }
+
+        {
+            auto dt = DateTime(1999, 7 ,6, 12, 30, 33);
+            assert(dt._date == Date(1999, 7, 6));
+            assert(dt._tod == TimeOfDay(12, 30, 33));
+        }
+    }
+
+
+    /++
+        Compares this $(LREF DateTime) with the given $(D DateTime.).
+
+        Returns:
+            $(BOOKTABLE,
+            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+            $(TR $(TD this == rhs) $(TD 0))
+            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+            )
+     +/
+    int opCmp(in DateTime rhs) @safe const pure nothrow
+    {
+        immutable dateResult = _date.opCmp(rhs._date);
+
+        if(dateResult != 0)
+            return dateResult;
+
+        return _tod.opCmp(rhs._tod);
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(DateTime(Date.init, TimeOfDay.init).opCmp(DateTime.init) == 0);
+
+        assert(DateTime(Date(1999, 1, 1)).opCmp(DateTime(Date(1999, 1, 1))) == 0);
+        assert(DateTime(Date(1, 7, 1)).opCmp(DateTime(Date(1, 7, 1))) == 0);
+        assert(DateTime(Date(1, 1, 6)).opCmp(DateTime(Date(1, 1, 6))) == 0);
+
+        assert(DateTime(Date(1999, 7, 1)).opCmp(DateTime(Date(1999, 7, 1))) == 0);
+        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) == 0);
+
+        assert(DateTime(Date(1, 7, 6)).opCmp(DateTime(Date(1, 7, 6))) == 0);
+
+        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
+        assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0);
+        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 8, 6))) < 0);
+        assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0);
+        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) < 0);
+        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 7, 6))) > 0);
+
+        assert(DateTime(Date(1999, 8, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
+        assert(DateTime(Date(2000, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);
+        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
+        assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);
+        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 8, 6))) < 0);
+        assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);
+
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0))) == 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0))) == 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0))) == 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))) == 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) == 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))) == 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp(
+                                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 7), TimeOfDay(12, 31, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
+                                  DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
+
+        //Test B.C.
+        assert(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33))) == 0);
+        assert(DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33))) == 0);
+        assert(DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33))) == 0);
+
+        assert(DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33))) == 0);
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) == 0);
+
+        assert(DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                   DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33))) == 0);
+
+        assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(-2000, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0);
+
+        //Test Both
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33))) > 0);
+
+        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(1999, 6, 6), TimeOfDay(12, 30, 33))) < 0);
+        assert(DateTime(Date(1999, 6, 8), TimeOfDay(12, 30, 33)).opCmp(
+                                  DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
+
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
+        assert(dt.opCmp(dt) == 0);
+        assert(dt.opCmp(cdt) == 0);
+        assert(dt.opCmp(idt) == 0);
+        assert(cdt.opCmp(dt) == 0);
+        assert(cdt.opCmp(cdt) == 0);
+        assert(cdt.opCmp(idt) == 0);
+        assert(idt.opCmp(dt) == 0);
+        assert(idt.opCmp(cdt) == 0);
+        assert(idt.opCmp(idt) == 0);
+    }
+
+
+    /++
+        The date portion of $(LREF DateTime).
+      +/
+    @property Date date() @safe const pure nothrow
+    {
+        return _date;
+    }
+
+    unittest
+    {
+        {
+            auto dt = DateTime.init;
+            assert(dt.date == Date.init);
+        }
+
+        {
+            auto dt = DateTime(Date(1999, 7, 6));
+            assert(dt.date == Date(1999, 7, 6));
+        }
+
+        const cdt = DateTime(1999, 7, 6);
+        immutable idt = DateTime(1999, 7, 6);
+        assert(cdt.date == Date(1999, 7, 6));
+        assert(idt.date == Date(1999, 7, 6));
+    }
+
+
+    /++
+        The date portion of $(LREF DateTime).
+
+        Params:
+            date = The Date to set this $(LREF DateTime)'s date portion to.
+      +/
+    @property void date(in Date date) @safe pure nothrow
+    {
+        _date = date;
+    }
+
+    unittest
+    {
+        auto dt = DateTime.init;
+        dt.date = Date(1999, 7, 6);
+        assert(dt._date == Date(1999, 7, 6));
+        assert(dt._tod == TimeOfDay.init);
+
+        const cdt = DateTime(1999, 7, 6);
+        immutable idt = DateTime(1999, 7, 6);
+        static assert(!__traits(compiles, cdt.date = Date(2010, 1, 1)));
+        static assert(!__traits(compiles, idt.date = Date(2010, 1, 1)));
+    }
+
+
+    /++
+        The time portion of $(LREF DateTime).
+      +/
+    @property TimeOfDay timeOfDay() @safe const pure nothrow
+    {
+        return _tod;
+    }
+
+    unittest
+    {
+        {
+            auto dt = DateTime.init;
+            assert(dt.timeOfDay == TimeOfDay.init);
+        }
+
+        {
+            auto dt = DateTime(Date.init, TimeOfDay(12, 30, 33));
+            assert(dt.timeOfDay == TimeOfDay(12, 30, 33));
+        }
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.timeOfDay == TimeOfDay(12, 30, 33));
+        assert(idt.timeOfDay == TimeOfDay(12, 30, 33));
+    }
+
+
+    /++
+        The time portion of $(LREF DateTime).
+
+        Params:
+            tod = The $(LREF TimeOfDay) to set this $(LREF DateTime)'s time portion
+                  to.
+      +/
+    @property void timeOfDay(in TimeOfDay tod) @safe pure nothrow
+    {
+        _tod = tod;
+    }
+
+    unittest
+    {
+        auto dt = DateTime.init;
+        dt.timeOfDay = TimeOfDay(12, 30, 33);
+        assert(dt._date == Date.init);
+        assert(dt._tod == TimeOfDay(12, 30, 33));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.timeOfDay = TimeOfDay(12, 30, 33)));
+        static assert(!__traits(compiles, idt.timeOfDay = TimeOfDay(12, 30, 33)));
+    }
+
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+     +/
+    @property short year() @safe const pure nothrow
+    {
+        return _date.year;
+    }
+
+    unittest
+    {
+        assert(Date.init.year == 1);
+        assert(Date(1999, 7, 6).year == 1999);
+        assert(Date(-1999, 7, 6).year == -1999);
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(idt.year == 1999);
+        assert(idt.year == 1999);
+    }
+
+
+    /++
+        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
+        are B.C.
+
+        Params:
+            year = The year to set this $(LREF DateTime)'s year to.
+
+        Throws:
+            $(LREF DateTimeException) if the new year is not a leap year and if the
+            resulting date would be on February 29th.
+     +/
+    @property void year(int year) @safe pure
+    {
+        _date.year = year;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).year == 1999);
+        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).year == 2010);
+        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).year == -7);
+    }
+
+    unittest
+    {
+        static void testDT(DateTime dt, int year, in DateTime expected, size_t line = __LINE__)
+        {
+            dt.year = year;
+            assert(dt == expected);
+        }
+
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), 1999, DateTime(Date(1999, 1, 1), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), 0, DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), -1999, DateTime(Date(-1999, 1, 1), TimeOfDay(12, 30, 33)));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.year = 7));
+        static assert(!__traits(compiles, idt.year = 7));
+    }
+
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Throws:
+            $(LREF DateTimeException) if $(D isAD) is true.
+     +/
+    @property short yearBC() @safe const pure
+    {
+        return _date.yearBC;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33)).yearBC == 1);
+        assert(DateTime(Date(-1, 1, 1), TimeOfDay(10, 7, 2)).yearBC == 2);
+        assert(DateTime(Date(-100, 1, 1), TimeOfDay(4, 59, 0)).yearBC == 101);
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((in DateTime dt){dt.yearBC;}(DateTime(Date(1, 1, 1))));
+
+        auto dt = DateTime(1999, 7, 6, 12, 30, 33);
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        dt.yearBC = 12;
+        assert(dt.yearBC == 12);
+        static assert(!__traits(compiles, cdt.yearBC = 12));
+        static assert(!__traits(compiles, idt.yearBC = 12));
+    }
+
+
+    /++
+        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.
+
+        Params:
+            year = The year B.C. to set this $(LREF DateTime)'s year to.
+
+        Throws:
+            $(LREF DateTimeException) if a non-positive value is given.
+     +/
+    @property void yearBC(int year) @safe pure
+    {
+        _date.yearBC = year;
+    }
+
+    ///
+    unittest
+    {
+        auto dt = DateTime(Date(2010, 1, 1), TimeOfDay(7, 30, 0));
+        dt.yearBC = 1;
+        assert(dt == DateTime(Date(0, 1, 1), TimeOfDay(7, 30, 0)));
+
+        dt.yearBC = 10;
+        assert(dt == DateTime(Date(-9, 1, 1), TimeOfDay(7, 30, 0)));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((DateTime dt){dt.yearBC = -1;}(DateTime(Date(1, 1, 1))));
+
+        auto dt = DateTime(1999, 7, 6, 12, 30, 33);
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        dt.yearBC = 12;
+        assert(dt.yearBC == 12);
+        static assert(!__traits(compiles, cdt.yearBC = 12));
+        static assert(!__traits(compiles, idt.yearBC = 12));
+    }
+
+
+    /++
+        Month of a Gregorian Year.
+     +/
+    @property Month month() @safe const pure nothrow
+    {
+        return _date.month;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).month == 7);
+        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).month == 10);
+        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).month == 4);
+    }
+
+    unittest
+    {
+        assert(DateTime.init.month == 1);
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7);
+        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7);
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.month == 7);
+        assert(idt.month == 7);
+    }
+
+
+    /++
+        Month of a Gregorian Year.
+
+        Params:
+            month = The month to set this $(LREF DateTime)'s month to.
+
+        Throws:
+            $(LREF DateTimeException) if the given month is not a valid month.
+     +/
+    @property void month(Month month) @safe pure
+    {
+        _date.month = month;
+    }
+
+    unittest
+    {
+        static void testDT(DateTime dt, Month month, in DateTime expected = DateTime.init, size_t line = __LINE__)
+        {
+            dt.month = month;
+            assert(expected != DateTime.init);
+            assert(dt == expected);
+        }
+
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month)0));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month)13));
+
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month)7, DateTime(Date(1, 7, 1), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month)7, DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33)));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.month = 12));
+        static assert(!__traits(compiles, idt.month = 12));
+    }
+
+
+    /++
+        Day of a Gregorian Month.
+     +/
+    @property ubyte day() @safe const pure nothrow
+    {
+        return _date.day;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).day == 6);
+        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).day == 4);
+        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).day == 5);
+    }
+
+    unittest
+    {
+        import std.range;
+        import std.format : format;
+
+        static void test(DateTime dateTime, int expected)
+        {
+            assert(dateTime.day == expected, format("Value given: %s", dateTime));
+        }
+
+        foreach(year; chain(testYearsBC, testYearsAD))
+        {
+            foreach(md; testMonthDays)
+            {
+                foreach(tod; testTODs)
+                    test(DateTime(Date(year, md.month, md.day), tod), md.day);
+            }
+        }
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.day == 6);
+        assert(idt.day == 6);
+    }
+
+
+    /++
+        Day of a Gregorian Month.
+
+        Params:
+            day = The day of the month to set this $(LREF DateTime)'s day to.
+
+        Throws:
+            $(LREF DateTimeException) if the given day is not a valid day of the
+            current month.
+     +/
+    @property void day(int day) @safe pure
+    {
+        _date.day = day;
+    }
+
+    unittest
+    {
+        static void testDT(DateTime dt, int day)
+        {
+            dt.day = day;
+        }
+
+        //Test A.D.
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 0));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 29));
+        assertThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 30));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 32));
+
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 28));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 29));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 31));
+
+        {
+            auto dt = DateTime(Date(1, 1, 1), TimeOfDay(7, 12, 22));
+            dt.day = 6;
+            assert(dt == DateTime(Date(1, 1, 6), TimeOfDay(7, 12, 22)));
+        }
+
+        //Test B.C.
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 0));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 29));
+        assertThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 30));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 32));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 31));
+        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 32));
+
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 28));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 29));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 31));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 30));
+        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 31));
+
+        auto dt = DateTime(Date(-1, 1, 1), TimeOfDay(7, 12, 22));
+        dt.day = 6;
+        assert(dt == DateTime(Date(-1, 1, 6), TimeOfDay(7, 12, 22)));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.day = 27));
+        static assert(!__traits(compiles, idt.day = 27));
+    }
+
+
+    /++
+        Hours past midnight.
+     +/
+    @property ubyte hour() @safe const pure nothrow
+    {
+        return _tod.hour;
+    }
+
+    unittest
+    {
+        assert(DateTime.init.hour == 0);
+        assert(DateTime(Date.init, TimeOfDay(12, 0, 0)).hour == 12);
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.hour == 12);
+        assert(idt.hour == 12);
+    }
+
+
+    /++
+        Hours past midnight.
+
+        Params:
+            hour = The hour of the day to set this $(LREF DateTime)'s hour to.
+
+        Throws:
+            $(LREF DateTimeException) if the given hour would result in an invalid
+            $(LREF DateTime).
+     +/
+    @property void hour(int hour) @safe pure
+    {
+        _tod.hour = hour;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).hour = 24;}());
+
+        auto dt = DateTime.init;
+        dt.hour = 12;
+        assert(dt == DateTime(1, 1, 1, 12, 0, 0));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.hour = 27));
+        static assert(!__traits(compiles, idt.hour = 27));
+    }
+
+
+    /++
+        Minutes past the hour.
+     +/
+    @property ubyte minute() @safe const pure nothrow
+    {
+        return _tod.minute;
+    }
+
+    unittest
+    {
+        assert(DateTime.init.minute == 0);
+        assert(DateTime(1, 1, 1, 0, 30, 0).minute == 30);
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.minute == 30);
+        assert(idt.minute == 30);
+    }
+
+
+    /++
+        Minutes past the hour.
+
+        Params:
+            minute = The minute to set this $(LREF DateTime)'s minute to.
+
+        Throws:
+            $(LREF DateTimeException) if the given minute would result in an
+            invalid $(LREF DateTime).
+     +/
+    @property void minute(int minute) @safe pure
+    {
+        _tod.minute = minute;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){DateTime.init.minute = 60;}());
+
+        auto dt = DateTime.init;
+        dt.minute = 30;
+        assert(dt == DateTime(1, 1, 1, 0, 30, 0));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.minute = 27));
+        static assert(!__traits(compiles, idt.minute = 27));
+    }
+
+
+    /++
+        Seconds past the minute.
+     +/
+    @property ubyte second() @safe const pure nothrow
+    {
+        return _tod.second;
+    }
+
+    unittest
+    {
+        assert(DateTime.init.second == 0);
+        assert(DateTime(1, 1, 1, 0, 0, 33).second == 33);
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.second == 33);
+        assert(idt.second == 33);
+    }
+
+
+    /++
+        Seconds past the minute.
+
+        Params:
+            second = The second to set this $(LREF DateTime)'s second to.
+
+        Throws:
+            $(LREF DateTimeException) if the given seconds would result in an
+            invalid $(LREF DateTime).
+     +/
+    @property void second(int second) @safe pure
+    {
+        _tod.second = second;
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException((){DateTime.init.second = 60;}());
+
+        auto dt = DateTime.init;
+        dt.second = 33;
+        assert(dt == DateTime(1, 1, 1, 0, 0, 33));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.second = 27));
+        static assert(!__traits(compiles, idt.second = 27));
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF DateTime). A
+        negative number will subtract.
+
+        Note that if day overflow is allowed, and the date with the adjusted
+        year/month overflows the number of days in the new month, then the month
+        will be incremented by one, and the day set to the number of days
+        overflowed. (e.g. if the day were 31 and the new month were June, then
+        the month would be incremented to July, and the new day would be 1). If
+        day overflow is not allowed, then the day will be set to the last valid
+        day in the month (e.g. June 31st would become June 30th).
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF DateTime).
+            allowOverflow = Whether the days should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref DateTime add(string units)
+                    (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "years" ||
+           units == "months")
+    {
+        _date.add!units(value, allowOverflow);
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto dt1 = DateTime(2010, 1, 1, 12, 30, 33);
+        dt1.add!"months"(11);
+        assert(dt1 == DateTime(2010, 12, 1, 12, 30, 33));
+
+        auto dt2 = DateTime(2010, 1, 1, 12, 30, 33);
+        dt2.add!"months"(-11);
+        assert(dt2 == DateTime(2009, 2, 1, 12, 30, 33));
+
+        auto dt3 = DateTime(2000, 2, 29, 12, 30, 33);
+        dt3.add!"years"(1);
+        assert(dt3 == DateTime(2001, 3, 1, 12, 30, 33));
+
+        auto dt4 = DateTime(2000, 2, 29, 12, 30, 33);
+        dt4.add!"years"(1, AllowDayOverflow.no);
+        assert(dt4 == DateTime(2001, 2, 28, 12, 30, 33));
+    }
+
+    unittest
+    {
+        auto dt = DateTime(2000, 1, 31);
+        dt.add!"years"(7).add!"months"(-4);
+        assert(dt == DateTime(2006, 10, 1));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.add!"years"(4)));
+        static assert(!__traits(compiles, idt.add!"years"(4)));
+        static assert(!__traits(compiles, cdt.add!"months"(4)));
+        static assert(!__traits(compiles, idt.add!"months"(4)));
+    }
+
+
+    /++
+        Adds the given number of years or months to this $(LREF DateTime). A
+        negative number will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. Rolling a $(LREF DateTime) 12 months
+        gets the exact same $(LREF DateTime). However, the days can still be
+        affected due to the differing number of days in each month.
+
+        Because there are no units larger than years, there is no difference
+        between adding and rolling years.
+
+        Params:
+            units         = The type of units to add ("years" or "months").
+            value         = The number of months or years to add to this
+                            $(LREF DateTime).
+            allowOverflow = Whether the days should be allowed to overflow,
+                            causing the month to increment.
+      +/
+    ref DateTime roll(string units)
+                     (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
+        if(units == "years" ||
+           units == "months")
+    {
+        _date.roll!units(value, allowOverflow);
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto dt1 = DateTime(2010, 1, 1, 12, 33, 33);
+        dt1.roll!"months"(1);
+        assert(dt1 == DateTime(2010, 2, 1, 12, 33, 33));
+
+        auto dt2 = DateTime(2010, 1, 1, 12, 33, 33);
+        dt2.roll!"months"(-1);
+        assert(dt2 == DateTime(2010, 12, 1, 12, 33, 33));
+
+        auto dt3 = DateTime(1999, 1, 29, 12, 33, 33);
+        dt3.roll!"months"(1);
+        assert(dt3 == DateTime(1999, 3, 1, 12, 33, 33));
+
+        auto dt4 = DateTime(1999, 1, 29, 12, 33, 33);
+        dt4.roll!"months"(1, AllowDayOverflow.no);
+        assert(dt4 == DateTime(1999, 2, 28, 12, 33, 33));
+
+        auto dt5 = DateTime(2000, 2, 29, 12, 30, 33);
+        dt5.roll!"years"(1);
+        assert(dt5 == DateTime(2001, 3, 1, 12, 30, 33));
+
+        auto dt6 = DateTime(2000, 2, 29, 12, 30, 33);
+        dt6.roll!"years"(1, AllowDayOverflow.no);
+        assert(dt6 == DateTime(2001, 2, 28, 12, 30, 33));
+    }
+
+    unittest
+    {
+        auto dt = DateTime(2000, 1, 31);
+        dt.roll!"years"(7).roll!"months"(-4);
+        assert(dt == DateTime(2007, 10, 1));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.roll!"years"(4)));
+        static assert(!__traits(compiles, idt.roll!"years"(4)));
+        static assert(!__traits(compiles, cdt.roll!"months"(4)));
+        static assert(!__traits(compiles, idt.roll!"months"(4)));
+    }
+
+
+    /++
+        Adds the given number of units to this $(LREF DateTime). A negative number
+        will subtract.
+
+        The difference between rolling and adding is that rolling does not
+        affect larger units. For instance, rolling a $(LREF DateTime) one
+        year's worth of days gets the exact same $(LREF DateTime).
+
+        Accepted units are $(D "days"), $(D "minutes"), $(D "hours"),
+        $(D "minutes"), and $(D "seconds").
+
+        Params:
+            units = The units to add.
+            value = The number of $(D_PARAM units) to add to this $(LREF DateTime).
+      +/
+    ref DateTime roll(string units)(long value) @safe pure nothrow
+        if(units == "days")
+    {
+        _date.roll!"days"(value);
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        auto dt1 = DateTime(2010, 1, 1, 11, 23, 12);
+        dt1.roll!"days"(1);
+        assert(dt1 == DateTime(2010, 1, 2, 11, 23, 12));
+        dt1.roll!"days"(365);
+        assert(dt1 == DateTime(2010, 1, 26, 11, 23, 12));
+        dt1.roll!"days"(-32);
+        assert(dt1 == DateTime(2010, 1, 25, 11, 23, 12));
+
+        auto dt2 = DateTime(2010, 7, 4, 12, 0, 0);
+        dt2.roll!"hours"(1);
+        assert(dt2 == DateTime(2010, 7, 4, 13, 0, 0));
+
+        auto dt3 = DateTime(2010, 1, 1, 0, 0, 0);
+        dt3.roll!"seconds"(-1);
+        assert(dt3 == DateTime(2010, 1, 1, 0, 0, 59));
+    }
+
+    unittest
+    {
+        auto dt = DateTime(2000, 1, 31);
+        dt.roll!"days"(7).roll!"days"(-4);
+        assert(dt == DateTime(2000, 1, 3));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.roll!"days"(4)));
+        static assert(!__traits(compiles, idt.roll!"days"(4)));
+    }
+
+
+    //Shares documentation with "days" version.
+    ref DateTime roll(string units)(long value) @safe pure nothrow
+        if(units == "hours" ||
+           units == "minutes" ||
+           units == "seconds")
+    {
+        _tod.roll!units(value);
+        return this;
+    }
+
+    //Test roll!"hours"().
+    unittest
+    {
+        static void testDT(DateTime orig, int hours, in DateTime expected, size_t line = __LINE__)
+        {
+            orig.roll!"hours"(hours);
+            assert(orig == expected);
+        }
+
+        //Test A.D.
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 6, DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7, DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 8, DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 9, DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 11, DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 12, DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 13, DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 14, DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 16, DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 17, DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 18, DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 19, DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 20, DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 21, DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 22, DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 23, DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 24, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 25, DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -6, DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -7, DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -8, DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -9, DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -11, DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -12, DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -13, DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -14, DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -16, DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -17, DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -18, DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -19, DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -20, DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -21, DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -22, DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -23, DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -24, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -25, DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 31), TimeOfDay(23, 30, 33)), 1, DateTime(Date(1999, 7, 31), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 8, 1), TimeOfDay(0, 30, 33)), -1, DateTime(Date(1999, 8, 1), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(1999, 12, 31), TimeOfDay(23, 30, 33)), 1, DateTime(Date(1999, 12, 31), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(2000, 1, 1), TimeOfDay(0, 30, 33)), -1, DateTime(Date(2000, 1, 1), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(1999, 2, 28), TimeOfDay(23, 30, 33)), 25, DateTime(Date(1999, 2, 28), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(1999, 3, 2), TimeOfDay(0, 30, 33)), -25, DateTime(Date(1999, 3, 2), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(2000, 2, 28), TimeOfDay(23, 30, 33)), 25, DateTime(Date(2000, 2, 28), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(2000, 3, 1), TimeOfDay(0, 30, 33)), -25, DateTime(Date(2000, 3, 1), TimeOfDay(23, 30, 33)));
+
+        //Test B.C.
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 6, DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7, DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 8, DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 9, DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 11, DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 12, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 13, DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 14, DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 16, DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 17, DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 18, DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 19, DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 20, DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 21, DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 22, DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 23, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 24, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 25, DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -6, DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -7, DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -8, DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -9, DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -11, DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -12, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -13, DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -14, DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -16, DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -17, DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -18, DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -19, DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -20, DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -21, DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -22, DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -23, DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -24, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -25, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 31), TimeOfDay(23, 30, 33)), 1, DateTime(Date(-1999, 7, 31), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-1999, 8, 1), TimeOfDay(0, 30, 33)), -1, DateTime(Date(-1999, 8, 1), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(-2001, 12, 31), TimeOfDay(23, 30, 33)), 1, DateTime(Date(-2001, 12, 31), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-2000, 1, 1), TimeOfDay(0, 30, 33)), -1, DateTime(Date(-2000, 1, 1), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(-2001, 2, 28), TimeOfDay(23, 30, 33)), 25, DateTime(Date(-2001, 2, 28), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-2001, 3, 2), TimeOfDay(0, 30, 33)), -25, DateTime(Date(-2001, 3, 2), TimeOfDay(23, 30, 33)));
+
+        testDT(DateTime(Date(-2000, 2, 28), TimeOfDay(23, 30, 33)), 25, DateTime(Date(-2000, 2, 28), TimeOfDay(0, 30, 33)));
+        testDT(DateTime(Date(-2000, 3, 1), TimeOfDay(0, 30, 33)), -25, DateTime(Date(-2000, 3, 1), TimeOfDay(23, 30, 33)));
+
+        //Test Both
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 17_546, DateTime(Date(-1, 1, 1), TimeOfDay(13, 30, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -17_546, DateTime(Date(1, 1, 1), TimeOfDay(11, 30, 33)));
+
+        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
+        dt.roll!"hours"(27).roll!"hours"(-9);
+        assert(dt == DateTime(2000, 1, 31, 3, 7, 6));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.roll!"hours"(4)));
+        static assert(!__traits(compiles, idt.roll!"hours"(4)));
+    }
+
+    //Test roll!"minutes"().
+    unittest
+    {
+        static void testDT(DateTime orig, int minutes, in DateTime expected, size_t line = __LINE__)
+        {
+            orig.roll!"minutes"(minutes);
+            assert(orig == expected);
+        }
+
+        //Test A.D.
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(1999, 7, 6), TimeOfDay(12, 32, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(1999, 7, 6), TimeOfDay(12, 34, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(1999, 7, 6), TimeOfDay(12, 35, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(1999, 7, 6), TimeOfDay(12, 40, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 29, DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 45, DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 75, DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 90, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 100, DateTime(Date(1999, 7, 6), TimeOfDay(12, 10, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 689, DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 690, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 691, DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 960, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1439, DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1440, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1441, DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2880, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(1999, 7, 6), TimeOfDay(12, 28, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(1999, 7, 6), TimeOfDay(12, 27, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(1999, 7, 6), TimeOfDay(12, 26, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(1999, 7, 6), TimeOfDay(12, 25, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(1999, 7, 6), TimeOfDay(12, 20, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -29, DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -30, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -45, DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -75, DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -90, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -100, DateTime(Date(1999, 7, 6), TimeOfDay(12, 50, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -749, DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -750, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -751, DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -960, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1439, DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1440, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1441, DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2880, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(11, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(11, 58, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(0, 1, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(0, 59, 33)));
+
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 1, DateTime(Date(1999, 7, 5), TimeOfDay(23, 0, 33)));
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 0, DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)));
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), -1, DateTime(Date(1999, 7, 5), TimeOfDay(23, 58, 33)));
+
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 1, DateTime(Date(1998, 12, 31), TimeOfDay(23, 0, 33)));
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 0, DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)));
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), -1, DateTime(Date(1998, 12, 31), TimeOfDay(23, 58, 33)));
+
+        //Test B.C.
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 32, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 33, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 34, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 35, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 40, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 29, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 45, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 75, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 90, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 100, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 10, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 689, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 690, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 691, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 960, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1439, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1440, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1441, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2880, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 28, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 27, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 26, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 25, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 20, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -29, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -30, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -45, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -75, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -90, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -100, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 50, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -749, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -750, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -751, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -960, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1439, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1440, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1441, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2880, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(11, 58, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 1, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 59, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 1, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 0, 33)));
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 0, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)));
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), -1, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 58, 33)));
+
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 1, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 0, 33)));
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 0, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)));
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), -1, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 58, 33)));
+
+        //Test Both
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1, DateTime(Date(1, 1, 1), TimeOfDay(0, 59, 0)));
+        testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0)), 1, DateTime(Date(0, 12, 31), TimeOfDay(23, 0, 0)));
+
+        testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1, DateTime(Date(0, 1, 1), TimeOfDay(0, 59, 0)));
+        testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0)), 1, DateTime(Date(-1, 12, 31), TimeOfDay(23, 0, 0)));
+
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_760, DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -1_052_760, DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));
+
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_782, DateTime(Date(-1, 1, 1), TimeOfDay(11, 52, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 52, 33)), -1_052_782, DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));
+
+        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
+        dt.roll!"minutes"(92).roll!"minutes"(-292);
+        assert(dt == DateTime(2000, 1, 31, 9, 47, 6));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.roll!"minutes"(4)));
+        static assert(!__traits(compiles, idt.roll!"minutes"(4)));
+    }
+
+    //Test roll!"seconds"().
+    unittest
+    {
+        static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__)
+        {
+            orig.roll!"seconds"(seconds);
+            assert(orig == expected);
+        }
+
+        //Test A.D.
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 35)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 36)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 37)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 38)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 43)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 48)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 26, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 27, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 3)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 59, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 61, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1766, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1767, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1768, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2007, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3599, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3600, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3601, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7200, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 31)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 30)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 29)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 28)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 23)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 18)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -33, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -34, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -35, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 58)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -59, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -61, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 1)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 59)));
+
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 1, DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 1)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 0, DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)));
+        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), -1, DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 59)));
+
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 1, DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 0)));
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 0, DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)));
+        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), -1, DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 58)));
+
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 1, DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 0)));
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 0, DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)));
+        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), -1, DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 58)));
+
+        //Test B.C.
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 35)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 36)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 37)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 38)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 43)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 48)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 26, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 27, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 3)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 59, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 61, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1766, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1767, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1768, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2007, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3599, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3600, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3601, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7200, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 31)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 30)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 29)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 28)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 23)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 18)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -33, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -34, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -35, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 58)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -59, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -61, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 1)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 59)));
+
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 1, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 1)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 0, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)));
+        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), -1, DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 59)));
+
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 1, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 0)));
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 0, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)));
+        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), -1, DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 58)));
+
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 1, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 0)));
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 0, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)));
+        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), -1, DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 58)));
+
+        //Test Both
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1, DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 59)));
+        testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)), 1, DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0)));
+
+        testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1, DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 59)));
+        testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 59)), 1, DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0)));
+
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_600L, DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -63_165_600L, DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));
+
+        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_617L, DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 50)));
+        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 50)), -63_165_617L, DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));
+
+        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
+        dt.roll!"seconds"(92).roll!"seconds"(-292);
+        assert(dt == DateTime(2000, 1, 31, 9, 7, 46));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt.roll!"seconds"(4)));
+        static assert(!__traits(compiles, idt.roll!"seconds"(4)));
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a $(CXREF time, Duration) from
+        this $(LREF DateTime).
+
+        The legal types of arithmetic for $(LREF DateTime) using this operator
+        are
+
+        $(BOOKTABLE,
+        $(TR $(TD DateTime) $(TD +) $(TD Duration) $(TD -->) $(TD DateTime))
+        $(TR $(TD DateTime) $(TD -) $(TD Duration) $(TD -->) $(TD DateTime))
+        )
+
+        Params:
+            duration = The $(CXREF time, Duration) to add to or subtract from
+                       this $(LREF DateTime).
+      +/
+    DateTime opBinary(string op)(Duration duration) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        DateTime retval = this;
+        immutable seconds = duration.total!"seconds";
+        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(2015, 12, 31, 23, 59, 59) + seconds(1) ==
+               DateTime(2016, 1, 1, 0, 0, 0));
+
+        assert(DateTime(2015, 12, 31, 23, 59, 59) + hours(1) ==
+               DateTime(2016, 1, 1, 0, 59, 59));
+
+        assert(DateTime(2016, 1, 1, 0, 0, 0) - seconds(1) ==
+               DateTime(2015, 12, 31, 23, 59, 59));
+
+        assert(DateTime(2016, 1, 1, 0, 59, 59) - hours(1) ==
+               DateTime(2015, 12, 31, 23, 59, 59));
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+
+        assert(dt + dur!"weeks"(7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
+        assert(dt + dur!"weeks"(-7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
+        assert(dt + dur!"days"(7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
+        assert(dt + dur!"days"(-7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));
+
+        assert(dt + dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        assert(dt + dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        assert(dt + dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
+        assert(dt + dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
+        assert(dt + dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt + dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt + dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt + dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt + dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt + dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt + dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt + dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+
+        assert(dt - dur!"weeks"(-7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
+        assert(dt - dur!"weeks"(7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
+        assert(dt - dur!"days"(-7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
+        assert(dt - dur!"days"(7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));
+
+        assert(dt - dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        assert(dt - dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        assert(dt - dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
+        assert(dt - dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
+        assert(dt - dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt - dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt - dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt - dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt - dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt - dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(dt - dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(dt - dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+
+        auto duration = dur!"seconds"(12);
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt + duration == DateTime(1999, 7, 6, 12, 30, 45));
+        assert(idt + duration == DateTime(1999, 7, 6, 12, 30, 45));
+        assert(cdt - duration == DateTime(1999, 7, 6, 12, 30, 21));
+        assert(idt - duration == DateTime(1999, 7, 6, 12, 30, 21));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines + and - with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    DateTime opBinary(string op)(in TickDuration td) @safe const pure nothrow
+        if(op == "+" || op == "-")
+    {
+        DateTime retval = this;
+        immutable seconds = td.seconds;
+        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+
+            assert(dt + TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+            assert(dt + TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+
+            assert(dt - TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+            assert(dt - TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        }
+    }
+
+
+    /++
+        Gives the result of adding or subtracting a duration from this
+        $(LREF DateTime), as well as assigning the result to this $(LREF DateTime).
+
+        The legal types of arithmetic for $(LREF DateTime) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD DateTime) $(TD +) $(TD duration) $(TD -->) $(TD DateTime))
+        $(TR $(TD DateTime) $(TD -) $(TD duration) $(TD -->) $(TD DateTime))
+        )
+
+        Params:
+            duration = The duration to add to or subtract from this
+                       $(LREF DateTime).
+      +/
+    ref DateTime opOpAssign(string op, D)(in D duration) @safe pure nothrow
+        if((op == "+" || op == "-") &&
+           (is(Unqual!D == Duration) ||
+            is(Unqual!D == TickDuration)))
+    {
+        import std.format : format;
+
+        DateTime retval = this;
+
+        static if(is(Unqual!D == Duration))
+            immutable hnsecs = duration.total!"hnsecs";
+        else static if(is(Unqual!D == TickDuration))
+            immutable hnsecs = duration.hnsecs;
+
+        mixin(format(`return _addSeconds(convert!("hnsecs", "seconds")(%shnsecs));`, op));
+    }
+
+    unittest
+    {
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(-7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(-7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(-7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(-7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+
+        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
+        (dt += dur!"seconds"(92)) -= dur!"days"(-500);
+        assert(dt == DateTime(2001, 6, 14, 9, 8, 38));
+
+        auto duration = dur!"seconds"(12);
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        static assert(!__traits(compiles, cdt += duration));
+        static assert(!__traits(compiles, idt += duration));
+        static assert(!__traits(compiles, cdt -= duration));
+        static assert(!__traits(compiles, idt -= duration));
+    }
+
+    // @@@DEPRECATED_2017-01@@@
+    /++
+        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
+              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
+              Use $(D Duration) instead. This overload will be removed in
+              January 2017.)
+
+        Defines += and -= with $(CXREF time, TickDuration).
+      +/
+    deprecated("Use Duration instead of TickDuration.")
+    ref DateTime opOpAssign(string op)(TickDuration td) @safe pure nothrow
+        if(op == "+" || op == "-")
+    {
+        DateTime retval = this;
+        immutable seconds = td.seconds;
+        mixin("return _addSeconds(" ~ op ~ "seconds);");
+    }
+
+    deprecated unittest
+    {
+        //This probably only runs in cases where gettimeofday() is used, but it's
+        //hard to do this test correctly with variable ticksPerSec.
+        if(TickDuration.ticksPerSec == 1_000_000)
+        {
+            {
+                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+                dt += TickDuration.from!"usecs"(7_000_000);
+                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+            }
+
+            {
+                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+                dt += TickDuration.from!"usecs"(-7_000_000);
+                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+            }
+
+            {
+                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+                dt -= TickDuration.from!"usecs"(-7_000_000);
+                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
+            }
+
+            {
+                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+                dt -= TickDuration.from!"usecs"(7_000_000);
+                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
+            }
+        }
+    }
+
+
+    /++
+        Gives the difference between two $(LREF DateTime)s.
+
+        The legal types of arithmetic for $(LREF DateTime) using this operator are
+
+        $(BOOKTABLE,
+        $(TR $(TD DateTime) $(TD -) $(TD DateTime) $(TD -->) $(TD duration))
+        )
+      +/
+    Duration opBinary(string op)(in DateTime rhs) @safe const pure nothrow
+        if(op == "-")
+    {
+        immutable dateResult = _date - rhs.date;
+        immutable todResult = _tod - rhs._tod;
+
+        return dur!"hnsecs"(dateResult.total!"hnsecs" + todResult.total!"hnsecs");
+    }
+
+    unittest
+    {
+        auto dt = DateTime(1999, 7, 6, 12, 30, 33);
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(31_536_000));
+        assert(DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(-31_536_000));
+
+        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(26_78_400));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(-26_78_400));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(86_400));
+        assert(DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(-86_400));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) ==
+                    dur!"seconds"(3600));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(-3600));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(60));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) ==
+                    dur!"seconds"(-60));
+
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
+                    dur!"seconds"(1));
+        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) -
+                     DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) ==
+                    dur!"seconds"(-1));
+
+        assert(DateTime(1, 1, 1, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(45033));
+        assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(1, 1, 1, 12, 30, 33) == dur!"seconds"(-45033));
+        assert(DateTime(0, 12, 31, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(-41367));
+        assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(0, 12, 31, 12, 30, 33) == dur!"seconds"(41367));
+
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt - dt == Duration.zero);
+        assert(cdt - dt == Duration.zero);
+        assert(idt - dt == Duration.zero);
+
+        assert(dt - cdt == Duration.zero);
+        assert(cdt - cdt == Duration.zero);
+        assert(idt - cdt == Duration.zero);
+
+        assert(dt - idt == Duration.zero);
+        assert(cdt - idt == Duration.zero);
+        assert(idt - idt == Duration.zero);
+    }
+
+
+    /++
+        Returns the difference between the two $(LREF DateTime)s in months.
+
+        To get the difference in years, subtract the year property
+        of two $(LREF SysTime)s. To get the difference in days or weeks,
+        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
+        that results. Because converting between months and smaller
+        units requires a specific date (which $(CXREF time, Duration)s don't have),
+        getting the difference in months requires some math using both
+        the year and month properties, so this is a convenience function for
+        getting the difference in months.
+
+        Note that the number of days in the months or how far into the month
+        either date is is irrelevant. It is the difference in the month property
+        combined with the difference in years * 12. So, for instance,
+        December 31st and January 1st are one month apart just as December 1st
+        and January 31st are one month apart.
+
+        Params:
+            rhs = The $(LREF DateTime) to subtract from this one.
+      +/
+    int diffMonths(in DateTime rhs) @safe const pure nothrow
+    {
+        return _date.diffMonths(rhs._date);
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(1999, 2, 1, 12, 2, 3).diffMonths(
+                    DateTime(1999, 1, 31, 23, 59, 59)) == 1);
+
+        assert(DateTime(1999, 1, 31, 0, 0, 0).diffMonths(
+                    DateTime(1999, 2, 1, 12, 3, 42)) == -1);
+
+        assert(DateTime(1999, 3, 1, 5, 30, 0).diffMonths(
+                    DateTime(1999, 1, 1, 2, 4, 7)) == 2);
+
+        assert(DateTime(1999, 1, 1, 7, 2, 4).diffMonths(
+                    DateTime(1999, 3, 31, 0, 30, 58)) == -2);
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt.diffMonths(dt) == 0);
+        assert(cdt.diffMonths(dt) == 0);
+        assert(idt.diffMonths(dt) == 0);
+
+        assert(dt.diffMonths(cdt) == 0);
+        assert(cdt.diffMonths(cdt) == 0);
+        assert(idt.diffMonths(cdt) == 0);
+
+        assert(dt.diffMonths(idt) == 0);
+        assert(cdt.diffMonths(idt) == 0);
+        assert(idt.diffMonths(idt) == 0);
+    }
+
+
+    /++
+        Whether this $(LREF DateTime) is in a leap year.
+     +/
+    @property bool isLeapYear() @safe const pure nothrow
+    {
+        return _date.isLeapYear;
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(!dt.isLeapYear);
+        assert(!cdt.isLeapYear);
+        assert(!idt.isLeapYear);
+    }
+
+
+    /++
+        Day of the week this $(LREF DateTime) is on.
+      +/
+    @property DayOfWeek dayOfWeek() @safe const pure nothrow
+    {
+        return _date.dayOfWeek;
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt.dayOfWeek == DayOfWeek.tue);
+        assert(cdt.dayOfWeek == DayOfWeek.tue);
+        assert(idt.dayOfWeek == DayOfWeek.tue);
+    }
+
+
+    /++
+        Day of the year this $(LREF DateTime) is on.
+      +/
+    @property ushort dayOfYear() @safe const pure nothrow
+    {
+        return _date.dayOfYear;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 1, 1), TimeOfDay(12, 22, 7)).dayOfYear == 1);
+        assert(DateTime(Date(1999, 12, 31), TimeOfDay(7, 2, 59)).dayOfYear == 365);
+        assert(DateTime(Date(2000, 12, 31), TimeOfDay(21, 20, 0)).dayOfYear == 366);
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt.dayOfYear == 187);
+        assert(cdt.dayOfYear == 187);
+        assert(idt.dayOfYear == 187);
+    }
+
+
+    /++
+        Day of the year.
+
+        Params:
+            day = The day of the year to set which day of the year this
+                  $(LREF DateTime) is on.
+      +/
+    @property void dayOfYear(int day) @safe pure
+    {
+        _date.dayOfYear = day;
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        dt.dayOfYear = 12;
+        assert(dt.dayOfYear == 12);
+        static assert(!__traits(compiles, cdt.dayOfYear = 12));
+        static assert(!__traits(compiles, idt.dayOfYear = 12));
+    }
+
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on.
+     +/
+    @property int dayOfGregorianCal() @safe const pure nothrow
+    {
+        return _date.dayOfGregorianCal;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).dayOfGregorianCal == 1);
+        assert(DateTime(Date(1, 12, 31), TimeOfDay(23, 59, 59)).dayOfGregorianCal == 365);
+        assert(DateTime(Date(2, 1, 1), TimeOfDay(2, 2, 2)).dayOfGregorianCal == 366);
+
+        assert(DateTime(Date(0, 12, 31), TimeOfDay(7, 7, 7)).dayOfGregorianCal == 0);
+        assert(DateTime(Date(0, 1, 1), TimeOfDay(19, 30, 0)).dayOfGregorianCal == -365);
+        assert(DateTime(Date(-1, 12, 31), TimeOfDay(4, 7, 0)).dayOfGregorianCal == -366);
+
+        assert(DateTime(Date(2000, 1, 1), TimeOfDay(9, 30, 20)).dayOfGregorianCal == 730_120);
+        assert(DateTime(Date(2010, 12, 31), TimeOfDay(15, 45, 50)).dayOfGregorianCal == 734_137);
+    }
+
+    unittest
+    {
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.dayOfGregorianCal == 729_941);
+        assert(idt.dayOfGregorianCal == 729_941);
+    }
+
+
+    /++
+        The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on.
+        Setting this property does not affect the time portion of
+        $(LREF DateTime).
+
+        Params:
+            days = The day of the Gregorian Calendar to set this $(LREF DateTime)
+                   to.
+     +/
+    @property void dayOfGregorianCal(int days) @safe pure nothrow
+    {
+        _date.dayOfGregorianCal = days;
+    }
+
+    ///
+    unittest
+    {
+        auto dt = DateTime(Date.init, TimeOfDay(12, 0, 0));
+        dt.dayOfGregorianCal = 1;
+        assert(dt == DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = 365;
+        assert(dt == DateTime(Date(1, 12, 31), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = 366;
+        assert(dt == DateTime(Date(2, 1, 1), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = 0;
+        assert(dt == DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = -365;
+        assert(dt == DateTime(Date(-0, 1, 1), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = -366;
+        assert(dt == DateTime(Date(-1, 12, 31), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = 730_120;
+        assert(dt == DateTime(Date(2000, 1, 1), TimeOfDay(12, 0, 0)));
+
+        dt.dayOfGregorianCal = 734_137;
+        assert(dt == DateTime(Date(2010, 12, 31), TimeOfDay(12, 0, 0)));
+    }
+
+    unittest
+    {
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        static assert(!__traits(compiles, cdt.dayOfGregorianCal = 7));
+        static assert(!__traits(compiles, idt.dayOfGregorianCal = 7));
+    }
+
+
+    /++
+        The ISO 8601 week of the year that this $(LREF DateTime) is in.
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
+      +/
+    @property ubyte isoWeek() @safe const pure nothrow
+    {
+        return _date.isoWeek;
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt.isoWeek == 27);
+        assert(cdt.isoWeek == 27);
+        assert(idt.isoWeek == 27);
+    }
+
+
+    /++
+        $(LREF DateTime) for the last day in the month that this $(LREF DateTime) is
+        in. The time portion of endOfMonth is always 23:59:59.
+      +/
+    @property DateTime endOfMonth() @safe const pure nothrow
+    {
+        try
+            return DateTime(_date.endOfMonth, TimeOfDay(23, 59, 59));
+        catch(Exception e)
+            assert(0, "DateTime constructor threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).endOfMonth ==
+               DateTime(Date(1999, 1, 31), TimeOfDay(23, 59, 59)));
+
+        assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).endOfMonth ==
+               DateTime(Date(1999, 2, 28), TimeOfDay(23, 59, 59)));
+
+        assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).endOfMonth ==
+               DateTime(Date(2000, 2, 29), TimeOfDay(23, 59, 59)));
+
+        assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).endOfMonth ==
+               DateTime(Date(2000, 6, 30), TimeOfDay(23, 59, 59)));
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(DateTime(1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(1999, 1, 31, 23, 59, 59));
+        assert(DateTime(1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(1999, 2, 28, 23, 59, 59));
+        assert(DateTime(2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(2000, 2, 29, 23, 59, 59));
+        assert(DateTime(1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(1999, 3, 31, 23, 59, 59));
+        assert(DateTime(1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(1999, 4, 30, 23, 59, 59));
+        assert(DateTime(1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(1999, 5, 31, 23, 59, 59));
+        assert(DateTime(1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(1999, 6, 30, 23, 59, 59));
+        assert(DateTime(1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
+        assert(DateTime(1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(1999, 8, 31, 23, 59, 59));
+        assert(DateTime(1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(1999, 9, 30, 23, 59, 59));
+        assert(DateTime(1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(1999, 10, 31, 23, 59, 59));
+        assert(DateTime(1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(1999, 11, 30, 23, 59, 59));
+        assert(DateTime(1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(1999, 12, 31, 23, 59, 59));
+
+        //Test B.C.
+        assert(DateTime(-1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(-1999, 1, 31, 23, 59, 59));
+        assert(DateTime(-1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(-1999, 2, 28, 23, 59, 59));
+        assert(DateTime(-2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(-2000, 2, 29, 23, 59, 59));
+        assert(DateTime(-1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(-1999, 3, 31, 23, 59, 59));
+        assert(DateTime(-1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(-1999, 4, 30, 23, 59, 59));
+        assert(DateTime(-1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(-1999, 5, 31, 23, 59, 59));
+        assert(DateTime(-1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(-1999, 6, 30, 23, 59, 59));
+        assert(DateTime(-1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(-1999, 7, 31, 23, 59, 59));
+        assert(DateTime(-1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(-1999, 8, 31, 23, 59, 59));
+        assert(DateTime(-1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(-1999, 9, 30, 23, 59, 59));
+        assert(DateTime(-1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(-1999, 10, 31, 23, 59, 59));
+        assert(DateTime(-1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(-1999, 11, 30, 23, 59, 59));
+        assert(DateTime(-1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(-1999, 12, 31, 23, 59, 59));
+
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
+        assert(idt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
+    }
+
+
+    /++
+        The last day in the month that this $(LREF DateTime) is in.
+      +/
+    @property ubyte daysInMonth() @safe const pure nothrow
+    {
+        return _date.daysInMonth;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).daysInMonth == 31);
+        assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).daysInMonth == 28);
+        assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).daysInMonth == 29);
+        assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).daysInMonth == 30);
+    }
+
+    unittest
+    {
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.daysInMonth == 31);
+        assert(idt.daysInMonth == 31);
+    }
+
+
+    /++
+        Whether the current year is a date in A.D.
+      +/
+    @property bool isAD() @safe const pure nothrow
+    {
+        return _date.isAD;
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 7, 0)).isAD);
+        assert(DateTime(Date(2010, 12, 31), TimeOfDay(0, 0, 0)).isAD);
+        assert(!DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)).isAD);
+        assert(!DateTime(Date(-2010, 1, 1), TimeOfDay(2, 2, 2)).isAD);
+    }
+
+    unittest
+    {
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.isAD);
+        assert(idt.isAD);
+    }
+
+
+    /++
+        The $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for this
+        $(LREF DateTime) at the given time. For example, prior to noon,
+        1996-03-31 would be the Julian day number 2_450_173, so this function
+        returns 2_450_173, while from noon onward, the julian day number would
+        be 2_450_174, so this function returns 2_450_174.
+      +/
+    @property long julianDay() @safe const pure nothrow
+    {
+        if(_tod._hour < 12)
+            return _date.julianDay - 1;
+        else
+            return _date.julianDay;
+    }
+
+    unittest
+    {
+        assert(DateTime(Date(-4713, 11, 24), TimeOfDay(0, 0, 0)).julianDay == -1);
+        assert(DateTime(Date(-4713, 11, 24), TimeOfDay(12, 0, 0)).julianDay == 0);
+
+        assert(DateTime(Date(0, 12, 31), TimeOfDay(0, 0, 0)).julianDay == 1_721_424);
+        assert(DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0)).julianDay == 1_721_425);
+
+        assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).julianDay == 1_721_425);
+        assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0)).julianDay == 1_721_426);
+
+        assert(DateTime(Date(1582, 10, 15), TimeOfDay(0, 0, 0)).julianDay == 2_299_160);
+        assert(DateTime(Date(1582, 10, 15), TimeOfDay(12, 0, 0)).julianDay == 2_299_161);
+
+        assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).julianDay == 2_400_000);
+        assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).julianDay == 2_400_001);
+
+        assert(DateTime(Date(1982, 1, 4), TimeOfDay(0, 0, 0)).julianDay == 2_444_973);
+        assert(DateTime(Date(1982, 1, 4), TimeOfDay(12, 0, 0)).julianDay == 2_444_974);
+
+        assert(DateTime(Date(1996, 3, 31), TimeOfDay(0, 0, 0)).julianDay == 2_450_173);
+        assert(DateTime(Date(1996, 3, 31), TimeOfDay(12, 0, 0)).julianDay == 2_450_174);
+
+        assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).julianDay == 2_455_432);
+        assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).julianDay == 2_455_433);
+
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.julianDay == 2_451_366);
+        assert(idt.julianDay == 2_451_366);
+    }
+
+
+    /++
+        The modified $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for any
+        time on this date (since, the modified Julian day changes at midnight).
+      +/
+    @property long modJulianDay() @safe const pure nothrow
+    {
+        return _date.modJulianDay;
+    }
+
+    unittest
+    {
+        assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).modJulianDay == 0);
+        assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).modJulianDay == 0);
+
+        assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).modJulianDay == 55_432);
+        assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).modJulianDay == 55_432);
+
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(cdt.modJulianDay == 51_365);
+        assert(idt.modJulianDay == 51_365);
+    }
+
+
+    /++
+        Converts this $(LREF DateTime) to a string with the format YYYYMMDDTHHMMSS.
+      +/
+    string toISOString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+            return format("%sT%s", _date.toISOString(), _tod.toISOString());
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOString() ==
+               "20100704T070612");
+
+        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOString() ==
+               "19981225T021500");
+
+        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOString() ==
+               "00000105T230959");
+
+        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOString() ==
+               "-00040105T000002");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "00091204T000000");
+        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "00991204T050612");
+        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "09991204T134459");
+        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "99990704T235959");
+        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "+100001020T010101");
+
+        //Test B.C.
+        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOString() == "00001204T001204");
+        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "-00091204T000000");
+        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "-00991204T050612");
+        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "-09991204T134459");
+        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "-99990704T235959");
+        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "-100001020T010101");
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.toISOString() == "19990706T123033");
+        assert(idt.toISOString() == "19990706T123033");
+    }
+
+
+    /++
+        Converts this $(LREF DateTime) to a string with the format
+        YYYY-MM-DDTHH:MM:SS.
+      +/
+    string toISOExtString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+            return format("%sT%s", _date.toISOExtString(), _tod.toISOExtString());
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOExtString() ==
+               "2010-07-04T07:06:12");
+
+        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOExtString() ==
+               "1998-12-25T02:15:00");
+
+        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOExtString() ==
+               "0000-01-05T23:09:59");
+
+        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOExtString() ==
+               "-0004-01-05T00:00:02");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "0009-12-04T00:00:00");
+        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "0099-12-04T05:06:12");
+        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "0999-12-04T13:44:59");
+        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "9999-07-04T23:59:59");
+        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "+10000-10-20T01:01:01");
+
+        //Test B.C.
+        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOExtString() == "0000-12-04T00:12:04");
+        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "-0009-12-04T00:00:00");
+        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "-0099-12-04T05:06:12");
+        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "-0999-12-04T13:44:59");
+        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "-9999-07-04T23:59:59");
+        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "-10000-10-20T01:01:01");
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.toISOExtString() == "1999-07-06T12:30:33");
+        assert(idt.toISOExtString() == "1999-07-06T12:30:33");
+    }
+
+    /++
+        Converts this $(LREF DateTime) to a string with the format
+        YYYY-Mon-DD HH:MM:SS.
+      +/
+    string toSimpleString() @safe const pure nothrow
+    {
+        import std.format : format;
+        try
+            return format("%s %s", _date.toSimpleString(), _tod.toString());
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toSimpleString() ==
+               "2010-Jul-04 07:06:12");
+
+        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toSimpleString() ==
+               "1998-Dec-25 02:15:00");
+
+        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toSimpleString() ==
+               "0000-Jan-05 23:09:59");
+
+        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toSimpleString() ==
+               "-0004-Jan-05 00:00:02");
+    }
+
+    unittest
+    {
+        //Test A.D.
+        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "0009-Dec-04 00:00:00");
+        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "0099-Dec-04 05:06:12");
+        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "0999-Dec-04 13:44:59");
+        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "9999-Jul-04 23:59:59");
+        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "+10000-Oct-20 01:01:01");
+
+        //Test B.C.
+        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toSimpleString() == "0000-Dec-04 00:12:04");
+        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "-0009-Dec-04 00:00:00");
+        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "-0099-Dec-04 05:06:12");
+        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "-0999-Dec-04 13:44:59");
+        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "-9999-Jul-04 23:59:59");
+        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "-10000-Oct-20 01:01:01");
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        assert(cdt.toSimpleString() == "1999-Jul-06 12:30:33");
+        assert(idt.toSimpleString() == "1999-Jul-06 12:30:33");
+    }
+
+
+    /++
+        Converts this $(LREF DateTime) to a string.
+      +/
+    string toString() @safe const pure nothrow
+    {
+        return toSimpleString();
+    }
+
+    unittest
+    {
+        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
+        assert(dt.toString());
+        assert(cdt.toString());
+        assert(idt.toString());
+    }
+
+
+
+    /++
+        Creates a $(LREF DateTime) from a string with the format YYYYMMDDTHHMMSS.
+        Whitespace is stripped from the given string.
+
+        Params:
+            isoString = A string formatted in the ISO format for dates and times.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO format
+            or if the resulting $(LREF DateTime) would not be valid.
+      +/
+    static DateTime fromISOString(S)(in S isoString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : countUntil;
+        import std.format : format;
+
+        immutable dstr = to!dstring(strip(isoString));
+
+        enforce(dstr.length >= 15, new DateTimeException(format("Invalid ISO String: %s", isoString)));
+        auto t = dstr.countUntil('T');
+
+        enforce(t != -1, new DateTimeException(format("Invalid ISO String: %s", isoString)));
+
+        immutable date = Date.fromISOString(dstr[0..t]);
+        immutable tod = TimeOfDay.fromISOString(dstr[t+1 .. $]);
+
+        return DateTime(date, tod);
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime.fromISOString("20100704T070612") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+
+        assert(DateTime.fromISOString("19981225T021500") ==
+               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));
+
+        assert(DateTime.fromISOString("00000105T230959") ==
+               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));
+
+        assert(DateTime.fromISOString("-00040105T000002") ==
+               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));
+
+        assert(DateTime.fromISOString(" 20100704T070612 ") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(DateTime.fromISOString(""));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000."));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-12-22T172201"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Dec-22 17:22:01"));
+
+        assert(DateTime.fromISOString("20101222T172201") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
+        assert(DateTime.fromISOString("19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOString("-19990706T123033") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOString("+019990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOString("19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOString(" 19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOString(" 19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+    }
+
+
+    /++
+        Creates a $(LREF DateTime) from a string with the format
+        YYYY-MM-DDTHH:MM:SS. Whitespace is stripped from the given string.
+
+        Params:
+            isoExtString = A string formatted in the ISO Extended format for dates
+                           and times.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the ISO
+            Extended format or if the resulting $(LREF DateTime) would not be
+            valid.
+      +/
+    static DateTime fromISOExtString(S)(in S isoExtString) @safe pure
+        if(isSomeString!(S))
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : countUntil;
+        import std.format : format;
+
+        immutable dstr = to!dstring(strip(isoExtString));
+
+        enforce(dstr.length >= 15, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+        auto t = dstr.countUntil('T');
+
+        enforce(t != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
+
+        immutable date = Date.fromISOExtString(dstr[0..t]);
+        immutable tod = TimeOfDay.fromISOExtString(dstr[t+1 .. $]);
+
+        return DateTime(date, tod);
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime.fromISOExtString("2010-07-04T07:06:12") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+
+        assert(DateTime.fromISOExtString("1998-12-25T02:15:00") ==
+               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));
+
+        assert(DateTime.fromISOExtString("0000-01-05T23:09:59") ==
+               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));
+
+        assert(DateTime.fromISOExtString("-0004-01-05T00:00:02") ==
+               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));
+
+        assert(DateTime.fromISOExtString(" 2010-07-04T07:06:12 ") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(DateTime.fromISOExtString(""));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704000000"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704 000000"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704t000000"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000."));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07:0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOExtString("20101222T172201"));
+        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Dec-22 17:22:01"));
+
+        assert(DateTime.fromISOExtString("2010-12-22T17:22:01") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
+        assert(DateTime.fromISOExtString("1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOExtString("-1999-07-06T12:30:33") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOExtString("+01999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOExtString("1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+    }
+
+
+    /++
+        Creates a $(LREF DateTime) from a string with the format
+        YYYY-Mon-DD HH:MM:SS. Whitespace is stripped from the given string.
+
+        Params:
+            simpleString = A string formatted in the way that toSimpleString
+                           formats dates and times.
+
+        Throws:
+            $(LREF DateTimeException) if the given string is not in the correct
+            format or if the resulting $(LREF DateTime) would not be valid.
+      +/
+    static DateTime fromSimpleString(S)(in S simpleString) @safe pure
+        if(isSomeString!(S))
+    {
+        import std.string : strip;
+        import std.conv : to;
+        import std.algorithm : countUntil;
+        import std.format : format;
+
+        immutable dstr = to!dstring(strip(simpleString));
+
+        enforce(dstr.length >= 15, new DateTimeException(format("Invalid string format: %s", simpleString)));
+        auto t = dstr.countUntil(' ');
+
+        enforce(t != -1, new DateTimeException(format("Invalid string format: %s", simpleString)));
+
+        immutable date = Date.fromSimpleString(dstr[0..t]);
+        immutable tod = TimeOfDay.fromISOExtString(dstr[t+1 .. $]);
+
+        return DateTime(date, tod);
+    }
+
+    ///
+    unittest
+    {
+        assert(DateTime.fromSimpleString("2010-Jul-04 07:06:12") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+        assert(DateTime.fromSimpleString("1998-Dec-25 02:15:00") ==
+               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));
+        assert(DateTime.fromSimpleString("0000-Jan-05 23:09:59") ==
+               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));
+        assert(DateTime.fromSimpleString("-0004-Jan-05 00:00:02") ==
+               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));
+        assert(DateTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") ==
+               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
+    }
+
+    unittest
+    {
+        assertThrown!DateTimeException(DateTime.fromISOString(""));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000"));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000."));
+        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00"));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00."));
+        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0"));
+
+        assertThrown!DateTimeException(DateTime.fromSimpleString("20101222T172201"));
+        assertThrown!DateTimeException(DateTime.fromSimpleString("2010-12-22T172201"));
+
+        assert(DateTime.fromSimpleString("2010-Dec-22 17:22:01") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
+        assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromSimpleString("-1999-Jul-06 12:30:33") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromSimpleString("+01999-Jul-06 12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+        assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
+    }
+
+
+    /++
+        Returns the $(LREF DateTime) farthest in the past which is representable by
+        $(LREF DateTime).
+      +/
+    @property static DateTime min() @safe pure nothrow
+    out(result)
+    {
+        assert(result._date == Date.min);
+        assert(result._tod == TimeOfDay.min);
+    }
+    body
+    {
+        auto dt = DateTime.init;
+        dt._date._year = short.min;
+        dt._date._month = Month.jan;
+        dt._date._day = 1;
+
+        return dt;
+    }
+
+    unittest
+    {
+        assert(DateTime.min.year < 0);
+        assert(DateTime.min < DateTime.max);
+    }
+
+
+    /++
+        Returns the $(LREF DateTime) farthest in the future which is representable
+        by $(LREF DateTime).
+      +/
+    @property static DateTime max() @safe pure nothrow
+    out(result)
+    {
+        assert(result._date == Date.max);
+        assert(result._tod == TimeOfDay.max);
+    }
+    body
+    {
+        auto dt = DateTime.init;
+        dt._date._year = short.max;
+        dt._date._month = Month.dec;
+        dt._date._day = 31;
+        dt._tod._hour = TimeOfDay.maxHour;
+        dt._tod._minute = TimeOfDay.maxMinute;
+        dt._tod._second = TimeOfDay.maxSecond;
+
+        return dt;
+    }
+
+    unittest
+    {
+        assert(DateTime.max.year > 0);
+        assert(DateTime.max > DateTime.min);
+    }
+
+
+private:
+
+    /+
+        Add seconds to the time of day. Negative values will subtract. If the
+        number of seconds overflows (or underflows), then the seconds will wrap,
+        increasing (or decreasing) the number of minutes accordingly. The
+        same goes for any larger units.
+
+        Params:
+            seconds = The number of seconds to add to this $(LREF DateTime).
+      +/
+    ref DateTime _addSeconds(long seconds) return @safe pure nothrow
+    {
+        long hnsecs = convert!("seconds", "hnsecs")(seconds);
+        hnsecs += convert!("hours", "hnsecs")(_tod._hour);
+        hnsecs += convert!("minutes", "hnsecs")(_tod._minute);
+        hnsecs += convert!("seconds", "hnsecs")(_tod._second);
+
+        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
+
+        if(hnsecs < 0)
+        {
+            hnsecs += convert!("days", "hnsecs")(1);
+            --days;
+        }
+
+        _date._addDays(days);
+
+        immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs);
+        immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
+        immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs);
+
+        _tod._hour = cast(ubyte)newHours;
+        _tod._minute = cast(ubyte)newMinutes;
+        _tod._second = cast(ubyte)newSeconds;
+
+        return this;
+    }
+
+    unittest
+    {
+        static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__)
+        {
+            orig._addSeconds(seconds);
+            assert(orig == expected);
+        }
+
+        //Test A.D.
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 0, DateTime(1999, 7, 6, 12, 30, 33));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1, DateTime(1999, 7, 6, 12, 30, 34));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 2, DateTime(1999, 7, 6, 12, 30, 35));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3, DateTime(1999, 7, 6, 12, 30, 36));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 4, DateTime(1999, 7, 6, 12, 30, 37));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 5, DateTime(1999, 7, 6, 12, 30, 38));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 10, DateTime(1999, 7, 6, 12, 30, 43));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 15, DateTime(1999, 7, 6, 12, 30, 48));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 26, DateTime(1999, 7, 6, 12, 30, 59));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 27, DateTime(1999, 7, 6, 12, 31, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 30, DateTime(1999, 7, 6, 12, 31, 3));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 59, DateTime(1999, 7, 6, 12, 31, 32));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 60, DateTime(1999, 7, 6, 12, 31, 33));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 61, DateTime(1999, 7, 6, 12, 31, 34));
+
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1766, DateTime(1999, 7, 6, 12, 59, 59));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1767, DateTime(1999, 7, 6, 13, 0, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1768, DateTime(1999, 7, 6, 13, 0, 1));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 2007, DateTime(1999, 7, 6, 13, 4, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3599, DateTime(1999, 7, 6, 13, 30, 32));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3600, DateTime(1999, 7, 6, 13, 30, 33));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3601, DateTime(1999, 7, 6, 13, 30, 34));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), 7200, DateTime(1999, 7, 6, 14, 30, 33));
+        testDT(DateTime(1999, 7, 6, 23, 0, 0), 432_123, DateTime(1999, 7, 11, 23, 2, 3));
+
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1, DateTime(1999, 7, 6, 12, 30, 32));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -2, DateTime(1999, 7, 6, 12, 30, 31));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3, DateTime(1999, 7, 6, 12, 30, 30));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -4, DateTime(1999, 7, 6, 12, 30, 29));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -5, DateTime(1999, 7, 6, 12, 30, 28));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -10, DateTime(1999, 7, 6, 12, 30, 23));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -15, DateTime(1999, 7, 6, 12, 30, 18));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -33, DateTime(1999, 7, 6, 12, 30, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -34, DateTime(1999, 7, 6, 12, 29, 59));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -35, DateTime(1999, 7, 6, 12, 29, 58));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -59, DateTime(1999, 7, 6, 12, 29, 34));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -60, DateTime(1999, 7, 6, 12, 29, 33));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -61, DateTime(1999, 7, 6, 12, 29, 32));
+
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1833, DateTime(1999, 7, 6, 12, 0, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1834, DateTime(1999, 7, 6, 11, 59, 59));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3600, DateTime(1999, 7, 6, 11, 30, 33));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3601, DateTime(1999, 7, 6, 11, 30, 32));
+        testDT(DateTime(1999, 7, 6, 12, 30, 33), -5134, DateTime(1999, 7, 6, 11, 4, 59));
+        testDT(DateTime(1999, 7, 6, 23, 0, 0), -432_123, DateTime(1999, 7, 1, 22, 57, 57));
+
+        testDT(DateTime(1999, 7, 6, 12, 30, 0), 1, DateTime(1999, 7, 6, 12, 30, 1));
+        testDT(DateTime(1999, 7, 6, 12, 30, 0), 0, DateTime(1999, 7, 6, 12, 30, 0));
+        testDT(DateTime(1999, 7, 6, 12, 30, 0), -1, DateTime(1999, 7, 6, 12, 29, 59));
+
+        testDT(DateTime(1999, 7, 6, 12, 0, 0), 1, DateTime(1999, 7, 6, 12, 0, 1));
+        testDT(DateTime(1999, 7, 6, 12, 0, 0), 0, DateTime(1999, 7, 6, 12, 0, 0));
+        testDT(DateTime(1999, 7, 6, 12, 0, 0), -1, DateTime(1999, 7, 6, 11, 59, 59));
+
+        testDT(DateTime(1999, 7, 6, 0, 0, 0), 1, DateTime(1999, 7, 6, 0, 0, 1));
+        testDT(DateTime(1999, 7, 6, 0, 0, 0), 0, DateTime(1999, 7, 6, 0, 0, 0));
+        testDT(DateTime(1999, 7, 6, 0, 0, 0), -1, DateTime(1999, 7, 5, 23, 59, 59));
+
+        testDT(DateTime(1999, 7, 5, 23, 59, 59), 1, DateTime(1999, 7, 6, 0, 0, 0));
+        testDT(DateTime(1999, 7, 5, 23, 59, 59), 0, DateTime(1999, 7, 5, 23, 59, 59));
+        testDT(DateTime(1999, 7, 5, 23, 59, 59), -1, DateTime(1999, 7, 5, 23, 59, 58));
+
+        testDT(DateTime(1998, 12, 31, 23, 59, 59), 1, DateTime(1999, 1, 1, 0, 0, 0));
+        testDT(DateTime(1998, 12, 31, 23, 59, 59), 0, DateTime(1998, 12, 31, 23, 59, 59));
+        testDT(DateTime(1998, 12, 31, 23, 59, 59), -1, DateTime(1998, 12, 31, 23, 59, 58));
+
+        testDT(DateTime(1998, 1, 1, 0, 0, 0), 1, DateTime(1998, 1, 1, 0, 0, 1));
+        testDT(DateTime(1998, 1, 1, 0, 0, 0), 0, DateTime(1998, 1, 1, 0, 0, 0));
+        testDT(DateTime(1998, 1, 1, 0, 0, 0), -1, DateTime(1997, 12, 31, 23, 59, 59));
+
+        //Test B.C.
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 0, DateTime(-1999, 7, 6, 12, 30, 33));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1, DateTime(-1999, 7, 6, 12, 30, 34));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2, DateTime(-1999, 7, 6, 12, 30, 35));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3, DateTime(-1999, 7, 6, 12, 30, 36));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 4, DateTime(-1999, 7, 6, 12, 30, 37));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 5, DateTime(-1999, 7, 6, 12, 30, 38));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 10, DateTime(-1999, 7, 6, 12, 30, 43));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 15, DateTime(-1999, 7, 6, 12, 30, 48));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 26, DateTime(-1999, 7, 6, 12, 30, 59));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 27, DateTime(-1999, 7, 6, 12, 31, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 30, DateTime(-1999, 7, 6, 12, 31, 3));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 59, DateTime(-1999, 7, 6, 12, 31, 32));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 60, DateTime(-1999, 7, 6, 12, 31, 33));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 61, DateTime(-1999, 7, 6, 12, 31, 34));
+
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1766, DateTime(-1999, 7, 6, 12, 59, 59));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1767, DateTime(-1999, 7, 6, 13, 0, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1768, DateTime(-1999, 7, 6, 13, 0, 1));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2007, DateTime(-1999, 7, 6, 13, 4, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3599, DateTime(-1999, 7, 6, 13, 30, 32));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3600, DateTime(-1999, 7, 6, 13, 30, 33));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3601, DateTime(-1999, 7, 6, 13, 30, 34));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 7200, DateTime(-1999, 7, 6, 14, 30, 33));
+        testDT(DateTime(-1999, 7, 6, 23, 0, 0), 432_123, DateTime(-1999, 7, 11, 23, 2, 3));
+
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1, DateTime(-1999, 7, 6, 12, 30, 32));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -2, DateTime(-1999, 7, 6, 12, 30, 31));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3, DateTime(-1999, 7, 6, 12, 30, 30));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -4, DateTime(-1999, 7, 6, 12, 30, 29));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5, DateTime(-1999, 7, 6, 12, 30, 28));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -10, DateTime(-1999, 7, 6, 12, 30, 23));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -15, DateTime(-1999, 7, 6, 12, 30, 18));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -33, DateTime(-1999, 7, 6, 12, 30, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -34, DateTime(-1999, 7, 6, 12, 29, 59));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -35, DateTime(-1999, 7, 6, 12, 29, 58));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -59, DateTime(-1999, 7, 6, 12, 29, 34));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -60, DateTime(-1999, 7, 6, 12, 29, 33));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -61, DateTime(-1999, 7, 6, 12, 29, 32));
+
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1833, DateTime(-1999, 7, 6, 12, 0, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1834, DateTime(-1999, 7, 6, 11, 59, 59));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3600, DateTime(-1999, 7, 6, 11, 30, 33));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3601, DateTime(-1999, 7, 6, 11, 30, 32));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5134, DateTime(-1999, 7, 6, 11, 4, 59));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -7200, DateTime(-1999, 7, 6, 10, 30, 33));
+        testDT(DateTime(-1999, 7, 6, 23, 0, 0), -432_123, DateTime(-1999, 7, 1, 22, 57, 57));
+
+        testDT(DateTime(-1999, 7, 6, 12, 30, 0), 1, DateTime(-1999, 7, 6, 12, 30, 1));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 0), 0, DateTime(-1999, 7, 6, 12, 30, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 30, 0), -1, DateTime(-1999, 7, 6, 12, 29, 59));
+
+        testDT(DateTime(-1999, 7, 6, 12, 0, 0), 1, DateTime(-1999, 7, 6, 12, 0, 1));
+        testDT(DateTime(-1999, 7, 6, 12, 0, 0), 0, DateTime(-1999, 7, 6, 12, 0, 0));
+        testDT(DateTime(-1999, 7, 6, 12, 0, 0), -1, DateTime(-1999, 7, 6, 11, 59, 59));
+
+        testDT(DateTime(-1999, 7, 6, 0, 0, 0), 1, DateTime(-1999, 7, 6, 0, 0, 1));
+        testDT(DateTime(-1999, 7, 6, 0, 0, 0), 0, DateTime(-1999, 7, 6, 0, 0, 0));
+        testDT(DateTime(-1999, 7, 6, 0, 0, 0), -1, DateTime(-1999, 7, 5, 23, 59, 59));
+
+        testDT(DateTime(-1999, 7, 5, 23, 59, 59), 1, DateTime(-1999, 7, 6, 0, 0, 0));
+        testDT(DateTime(-1999, 7, 5, 23, 59, 59), 0, DateTime(-1999, 7, 5, 23, 59, 59));
+        testDT(DateTime(-1999, 7, 5, 23, 59, 59), -1, DateTime(-1999, 7, 5, 23, 59, 58));
+
+        testDT(DateTime(-2000, 12, 31, 23, 59, 59), 1, DateTime(-1999, 1, 1, 0, 0, 0));
+        testDT(DateTime(-2000, 12, 31, 23, 59, 59), 0, DateTime(-2000, 12, 31, 23, 59, 59));
+        testDT(DateTime(-2000, 12, 31, 23, 59, 59), -1, DateTime(-2000, 12, 31, 23, 59, 58));
+
+        testDT(DateTime(-2000, 1, 1, 0, 0, 0), 1, DateTime(-2000, 1, 1, 0, 0, 1));
+        testDT(DateTime(-2000, 1, 1, 0, 0, 0), 0, DateTime(-2000, 1, 1, 0, 0, 0));
+        testDT(DateTime(-2000, 1, 1, 0, 0, 0), -1, DateTime(-2001, 12, 31, 23, 59, 59));
+
+        //Test Both
+        testDT(DateTime(1, 1, 1, 0, 0, 0), -1, DateTime(0, 12, 31, 23, 59, 59));
+        testDT(DateTime(0, 12, 31, 23, 59, 59), 1, DateTime(1, 1, 1, 0, 0, 0));
+
+        testDT(DateTime(0, 1, 1, 0, 0, 0), -1, DateTime(-1, 12, 31, 23, 59, 59));
+        testDT(DateTime(-1, 12, 31, 23, 59, 59), 1, DateTime(0, 1, 1, 0, 0, 0));
+
+        testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_600L, DateTime(1, 1, 1, 13, 30, 33));
+        testDT(DateTime(1, 1, 1, 13, 30, 33), -63_165_600L, DateTime(-1, 1, 1, 11, 30, 33));
+
+        testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_617L, DateTime(1, 1, 1, 13, 30, 50));
+        testDT(DateTime(1, 1, 1, 13, 30, 50), -63_165_617L, DateTime(-1, 1, 1, 11, 30, 33));
+
+        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
+        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
+        static assert(!__traits(compiles, cdt._addSeconds(4)));
+        static assert(!__traits(compiles, idt._addSeconds(4)));
+    }
+
+
+    Date      _date;
+    TimeOfDay _tod;
+}
+
+
+//==============================================================================
+// Section with intervals.
+//==============================================================================
+
+/++
+    Represents an interval of time.
+
+    An $(D Interval) has a starting point and an end point. The interval of time
+    is therefore the time starting at the starting point up to, but not
+    including, the end point. e.g.
+
+    $(BOOKTABLE,
+    $(TR $(TD [January 5th, 2010 - March 10th, 2010$(RPAREN)))
+    $(TR $(TD [05:00:30 - 12:00:00$(RPAREN)))
+    $(TR $(TD [1982-01-04T08:59:00 - 2010-07-04T12:00:00$(RPAREN)))
+    )
+
+    A range can be obtained from an $(D Interval), allowing iteration over
+    that interval, with the exact time points which are iterated over depending
+    on the function which generates the range.
+  +/
+struct Interval(TP)
+{
+public:
+
+    /++
+        Params:
+            begin = The time point which begins the interval.
+            end   = The time point which ends (but is not included in) the
+                    interval.
+
+        Throws:
+            $(LREF DateTimeException) if $(D_PARAM end) is before $(D_PARAM begin).
+
+        Example:
+--------------------
+Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+--------------------
+      +/
+    this(U)(in TP begin, in U end) pure
+        if(is(Unqual!TP == Unqual!U))
+    {
+        if(!_valid(begin, end))
+            throw new DateTimeException("Arguments would result in an invalid Interval.");
+
+        _begin = cast(TP)begin;
+        _end = cast(TP)end;
+    }
+
+
+    /++
+        Params:
+            begin    = The time point which begins the interval.
+            duration = The duration from the starting point to the end point.
+
+        Throws:
+            $(LREF DateTimeException) if the resulting $(D end) is before
+            $(D begin).
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) ==
+       Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5)));
+--------------------
+      +/
+    this(D)(in TP begin, in D duration) pure
+        if(__traits(compiles, begin + duration))
+    {
+        _begin = cast(TP)begin;
+        _end = begin + duration;
+
+        if(!_valid(_begin, _end))
+            throw new DateTimeException("Arguments would result in an invalid Interval.");
+    }
+
+
+    /++
+        Params:
+            rhs = The $(LREF2 .Interval, Interval) to assign to this one.
+      +/
+    ref Interval opAssign(const ref Interval rhs) pure nothrow
+    {
+        _begin = cast(TP)rhs._begin;
+        _end = cast(TP)rhs._end;
+        return this;
+    }
+
+
+    /++
+        Params:
+            rhs = The $(LREF2 .Interval, Interval) to assign to this one.
+      +/
+    ref Interval opAssign(Interval rhs) pure nothrow
+    {
+        _begin = cast(TP)rhs._begin;
+        _end = cast(TP)rhs._end;
+        return this;
+    }
+
+
+    /++
+        The starting point of the interval. It is included in the interval.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin ==
+       Date(1996, 1, 2));
+--------------------
+      +/
+    @property TP begin() const pure nothrow
+    {
+        return cast(TP)_begin;
+    }
+
+
+    /++
+        The starting point of the interval. It is included in the interval.
+
+        Params:
+            timePoint = The time point to set $(D begin) to.
+
+        Throws:
+            $(LREF DateTimeException) if the resulting interval would be invalid.
+      +/
+    @property void begin(TP timePoint) pure
+    {
+        if(!_valid(timePoint, _end))
+            throw new DateTimeException("Arguments would result in an invalid Interval.");
+
+        _begin = timePoint;
+    }
+
+
+    /++
+        The end point of the interval. It is excluded from the interval.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end ==
+       Date(2012, 3, 1));
+--------------------
+      +/
+    @property TP end() const pure nothrow
+    {
+        return cast(TP)_end;
+    }
+
+
+    /++
+        The end point of the interval. It is excluded from the interval.
+
+        Params:
+            timePoint = The time point to set end to.
+
+        Throws:
+            $(LREF DateTimeException) if the resulting interval would be invalid.
+      +/
+    @property void end(TP timePoint) pure
+    {
+        if(!_valid(_begin, timePoint))
+            throw new DateTimeException("Arguments would result in an invalid Interval.");
+
+        _end = timePoint;
+    }
+
+
+    /++
+        Returns the duration between $(D begin) and $(D end).
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length ==
+       dur!"days"(5903));
+--------------------
+      +/
+    @property auto length() const pure nothrow
+    {
+        return _end - _begin;
+    }
+
+
+    /++
+        Whether the interval's length is 0, that is, whether $(D begin == end).
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty);
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty);
+--------------------
+      +/
+    @property bool empty() const pure nothrow
+    {
+        return _begin == _end;
+    }
+
+
+    /++
+        Whether the given time point is within this interval.
+
+        Params:
+            timePoint = The time point to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Date(1994, 12, 24)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Date(2000, 1, 5)));
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Date(2012, 3, 1)));
+--------------------
+      +/
+    bool contains(in TP timePoint) const pure
+    {
+        _enforceNotEmpty();
+
+        return timePoint >= _begin && timePoint < _end;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+--------------------
+      +/
+    bool contains(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        return interval._begin >= _begin &&
+               interval._begin < _end &&
+               interval._end <= _end;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Always returns false (unless this interval is empty), because an
+        interval going to positive infinity can never be contained in a finite
+        interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+--------------------
+      +/
+    bool contains(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Always returns false (unless this interval is empty), because an
+        interval beginning at negative infinity can never be contained in a
+        finite interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+--------------------
+      +/
+    bool contains(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether this interval is before the given time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is before
+                        it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Date(1994, 12, 24)));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Date(2000, 1, 5)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Date(2012, 3, 1)));
+--------------------
+      +/
+    bool isBefore(in TP timePoint) const pure
+    {
+        _enforceNotEmpty();
+
+        return _end <= timePoint;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect with it.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1))));
+--------------------
+      +/
+    bool isBefore(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        return _end <= interval._begin;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect with it.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            PosInfInterval!Date(Date(2013, 3, 7))));
+--------------------
+      +/
+    bool isBefore(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _end <= interval._begin;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect with it.
+
+        Always returns false (unless this interval is empty) because a finite
+        interval can never be before an interval beginning at negative infinity.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+--------------------
+      +/
+    bool isBefore(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is after
+                        it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Date(1994, 12, 24)));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Date(2000, 1, 5)));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Date(2012, 3, 1)));
+--------------------
+      +/
+    bool isAfter(in TP timePoint) const pure
+    {
+        _enforceNotEmpty();
+
+        return timePoint < _begin;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+--------------------
+      +/
+    bool isAfter(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        return _begin >= interval._end;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Always returns false (unless this interval is empty) because a finite
+        interval can never be after an interval going to positive infinity.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+--------------------
+      +/
+    bool isAfter(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+--------------------
+      +/
+    bool isAfter(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _begin >= interval._end;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+--------------------
+      +/
+    bool intersects(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        return interval._begin < _end && interval._end > _begin;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool intersects(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _end > interval._begin;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
+            NegInfInterval!Date(Date(2000, 1, 2))));
+--------------------
+      +/
+    bool intersects(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _begin < interval._end;
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect or if
+            either interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
+       Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
+--------------------
+      +/
+    Interval intersection(in Interval interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        auto begin = _begin > interval._begin ? _begin : interval._begin;
+        auto end = _end < interval._end ? _end : interval._end;
+
+        return Interval(begin, end);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect or if
+            this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            PosInfInterval!Date(Date(1999, 1, 12))) ==
+       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+--------------------
+      +/
+    Interval intersection(in PosInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        return Interval(_begin > interval._begin ? _begin : interval._begin, _end);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect or if
+            this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            NegInfInterval!Date(Date(1999, 7, 6))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
+--------------------
+      +/
+    Interval intersection(in NegInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        return Interval(_begin, _end < interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isAdjacent(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        return _begin == interval._end || _end == interval._begin;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isAdjacent(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _end == interval._begin;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+
+assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
+            NegInfInterval!Date(Date(2000, 1, 2))));
+--------------------
+      +/
+    bool isAdjacent(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return _begin == interval._end;
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect and are
+            not adjacent or if either interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
+--------------------
+      +/
+    Interval merge(in Interval interval) const
+    {
+        import std.format : format;
+
+        enforce(this.isAdjacent(interval) || this.intersects(interval),
+                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));
+
+        auto begin = _begin < interval._begin ? _begin : interval._begin;
+        auto end = _end > interval._end ? _end : interval._end;
+
+        return Interval(begin, end);
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect and are
+            not adjacent or if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            PosInfInterval!Date(Date(2012, 3, 1))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval!TP merge(in PosInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.isAdjacent(interval) || this.intersects(interval),
+                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));
+
+        return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect and are not
+            adjacent or if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            NegInfInterval!Date(Date(1996, 1, 2))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       NegInfInterval!Date(Date(2013, 1 , 12)));
+--------------------
+      +/
+    NegInfInterval!TP merge(in NegInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.isAdjacent(interval) || this.intersects(interval),
+                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));
+
+        return NegInfInterval!TP(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if either interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) ==
+       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
+--------------------
+      +/
+    Interval span(in Interval interval) const pure
+    {
+        _enforceNotEmpty();
+        interval._enforceNotEmpty();
+
+        auto begin = _begin < interval._begin ? _begin : interval._begin;
+        auto end = _end > interval._end ? _end : interval._end;
+
+        return Interval(begin, end);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            PosInfInterval!Date(Date(2050, 1, 1))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval!TP span(in PosInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Example:
+--------------------
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            NegInfInterval!Date(Date(1602, 5, 21))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       NegInfInterval!Date(Date(2013, 1 , 12)));
+--------------------
+      +/
+    NegInfInterval!TP span(in NegInfInterval!TP interval) const pure
+    {
+        _enforceNotEmpty();
+
+        return NegInfInterval!TP(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Shifts the interval forward or backwards in time by the given duration
+        (a positive duration shifts the interval forward; a negative duration
+        shifts it backward). Effectively, it does $(D begin += duration) and
+        $(D end += duration).
+
+        Params:
+            duration = The duration to shift the interval by.
+
+        Throws:
+            $(LREF DateTimeException) this interval is empty or if the resulting
+            interval would be invalid.
+
+        Example:
+--------------------
+auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
+auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
+
+interval1.shift(dur!"days"(50));
+assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25)));
+
+interval2.shift(dur!"days"(-50));
+assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15)));
+--------------------
+      +/
+    void shift(D)(D duration) pure
+        if(__traits(compiles, begin + duration))
+    {
+        _enforceNotEmpty();
+
+        auto begin = _begin + duration;
+        auto end = _end + duration;
+
+        if(!_valid(begin, end))
+            throw new DateTimeException("Argument would result in an invalid Interval.");
+
+        _begin = begin;
+        _end = end;
+    }
+
+
+    static if(__traits(compiles, begin.add!"months"(1)) &&
+              __traits(compiles, begin.add!"years"(1)))
+    {
+        /++
+            Shifts the interval forward or backwards in time by the given number
+            of years and/or months (a positive number of years and months shifts
+            the interval forward; a negative number shifts it backward).
+            It adds the years the given years and months to both begin and end.
+            It effectively calls $(D add!"years"()) and then $(D add!"months"())
+            on begin and end with the given number of years and months.
+
+            Params:
+                years         = The number of years to shift the interval by.
+                months        = The number of months to shift the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D begin) and $(D end), causing their month
+                                to increment.
+
+            Throws:
+                $(LREF DateTimeException) if this interval is empty or if the
+                resulting interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+interval1.shift(2);
+assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1)));
+
+interval2.shift(-2);
+assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1)));
+--------------------
+          +/
+        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
+            if(isIntegral!T)
+        {
+            _enforceNotEmpty();
+
+            auto begin = _begin;
+            auto end = _end;
+
+            begin.add!"years"(years, allowOverflow);
+            begin.add!"months"(months, allowOverflow);
+            end.add!"years"(years, allowOverflow);
+            end.add!"months"(months, allowOverflow);
+
+            enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval."));
+
+            _begin = begin;
+            _end = end;
+        }
+    }
+
+
+    /++
+        Expands the interval forwards and/or backwards in time. Effectively,
+        it does $(D begin -= duration) and/or $(D end += duration). Whether
+        it expands forwards and/or backwards in time is determined by
+        $(D_PARAM dir).
+
+        Params:
+            duration = The duration to expand the interval by.
+            dir      = The direction in time to expand the interval.
+
+        Throws:
+            $(LREF DateTimeException) this interval is empty or if the resulting
+            interval would be invalid.
+
+        Example:
+--------------------
+auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+interval1.expand(2);
+assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));
+
+interval2.expand(-2);
+assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
+--------------------
+      +/
+    void expand(D)(D duration, Direction dir = Direction.both) pure
+        if(__traits(compiles, begin + duration))
+    {
+        _enforceNotEmpty();
+
+        switch(dir)
+        {
+            case Direction.both:
+            {
+                auto begin = _begin - duration;
+                auto end = _end + duration;
+
+                if(!_valid(begin, end))
+                    throw new DateTimeException("Argument would result in an invalid Interval.");
+
+                _begin = begin;
+                _end = end;
+
+                return;
+            }
+            case Direction.fwd:
+            {
+                auto end = _end + duration;
+
+                if(!_valid(_begin, end))
+                    throw new DateTimeException("Argument would result in an invalid Interval.");
+                _end = end;
+
+                return;
+            }
+            case Direction.bwd:
+            {
+                auto begin = _begin - duration;
+
+                if(!_valid(begin, _end))
+                    throw new DateTimeException("Argument would result in an invalid Interval.");
+                _begin = begin;
+
+                return;
+            }
+            default:
+                assert(0, "Invalid Direction.");
+        }
+    }
+
+    static if(__traits(compiles, begin.add!"months"(1)) &&
+              __traits(compiles, begin.add!"years"(1)))
+    {
+        /++
+            Expands the interval forwards and/or backwards in time. Effectively,
+            it subtracts the given number of months/years from $(D begin) and
+            adds them to $(D end). Whether it expands forwards and/or backwards
+            in time is determined by $(D_PARAM dir).
+
+            Params:
+                years         = The number of years to expand the interval by.
+                months        = The number of months to expand the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D begin) and $(D end), causing their month
+                                to increment.
+                dir           = The direction in time to expand the interval.
+
+            Throws:
+                $(LREF DateTimeException) if this interval is empty or if the
+                resulting interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+interval1.expand(2);
+assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));
+
+interval2.expand(-2);
+assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
+--------------------
+          +/
+        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes, Direction dir = Direction.both)
+            if(isIntegral!T)
+        {
+            _enforceNotEmpty();
+
+            switch(dir)
+            {
+                case Direction.both:
+                {
+                    auto begin = _begin;
+                    auto end = _end;
+
+                    begin.add!"years"(-years, allowOverflow);
+                    begin.add!"months"(-months, allowOverflow);
+                    end.add!"years"(years, allowOverflow);
+                    end.add!"months"(months, allowOverflow);
+
+                    enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval."));
+                    _begin = begin;
+                    _end = end;
+
+                    return;
+                }
+                case Direction.fwd:
+                {
+                    auto end = _end;
+
+                    end.add!"years"(years, allowOverflow);
+                    end.add!"months"(months, allowOverflow);
+
+                    enforce(_valid(_begin, end), new DateTimeException("Argument would result in an invalid Interval."));
+                    _end = end;
+
+                    return;
+                }
+                case Direction.bwd:
+                {
+                    auto begin = _begin;
+
+                    begin.add!"years"(-years, allowOverflow);
+                    begin.add!"months"(-months, allowOverflow);
+
+                    enforce(_valid(begin, _end), new DateTimeException("Argument would result in an invalid Interval."));
+                    _begin = begin;
+
+                    return;
+                }
+                default:
+                    assert(0, "Invalid Direction.");
+            }
+        }
+    }
+
+
+    /++
+        Returns a range which iterates forward over the interval, starting
+        at $(D begin), using $(D_PARAM func) to generate each successive time
+        point.
+
+        The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is
+        used to generate the next $(D front) when $(D popFront) is called. If
+        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
+        before the range is returned (so that $(D front) is a time point which
+        $(D_PARAM func) would generate).
+
+        If $(D_PARAM func) ever generates a time point less than or equal to the
+        current $(D front) of the range, then a $(LREF DateTimeException) will be
+        thrown. The range will be empty and iteration complete when
+        $(D_PARAM func) generates a time point equal to or beyond the $(D end)
+        of the interval.
+
+        There are helper functions in this module which generate common
+        delegates to pass to $(D fwdRange). Their documentation starts with
+        "Range-generating function," making them easily searchable.
+
+        Params:
+            func     = The function used to generate the time points of the
+                       range over the interval.
+            popFirst = Whether $(D popFront) should be called on the range
+                       before returning it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Warning:
+            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
+            would be a function pointer to a pure function, but forcing
+            $(D_PARAM func) to be pure is far too restrictive to be useful, and
+            in order to have the ease of use of having functions which generate
+            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
+            delegate.
+
+            If $(D_PARAM func) retains state which changes as it is called, then
+            some algorithms will not work correctly, because the range's
+            $(D save) will have failed to have really saved the range's state.
+            To avoid such bugs, don't pass a delegate which is
+            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
+            same time point with two different calls, it must return the same
+            result both times.
+
+            Of course, none of the functions in this module have this problem,
+            so it's only relevant if when creating a custom delegate.
+
+        Example:
+--------------------
+auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
+auto func = delegate (in Date date) //For iterating over even-numbered days.
+            {
+                if((date.day & 1) == 0)
+                    return date + dur!"days"(2);
+
+                return date + dur!"days"(1);
+            };
+auto range = interval.fwdRange(func);
+
+ //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
+assert(range.front == Date(2010, 9, 1));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 2));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 4));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 6));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 8));
+
+range.popFront();
+assert(range.empty);
+--------------------
+      +/
+    IntervalRange!(TP, Direction.fwd) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
+    {
+        _enforceNotEmpty();
+
+        auto range = IntervalRange!(TP, Direction.fwd)(this, func);
+
+        if(popFirst == PopFirst.yes)
+            range.popFront();
+
+        return range;
+    }
+
+
+    /++
+        Returns a range which iterates backwards over the interval, starting
+        at $(D end), using $(D_PARAM func) to generate each successive time
+        point.
+
+        The range's $(D front) is the interval's $(D end). $(D_PARAM func) is
+        used to generate the next $(D front) when $(D popFront) is called. If
+        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
+        before the range is returned (so that $(D front) is a time point which
+        $(D_PARAM func) would generate).
+
+        If $(D_PARAM func) ever generates a time point greater than or equal to
+        the current $(D front) of the range, then a $(LREF DateTimeException) will
+        be thrown. The range will be empty and iteration complete when
+        $(D_PARAM func) generates a time point equal to or less than the
+        $(D begin) of the interval.
+
+        There are helper functions in this module which generate common
+        delegates to pass to $(D bwdRange). Their documentation starts with
+        "Range-generating function," making them easily searchable.
+
+        Params:
+            func     = The function used to generate the time points of the
+                       range over the interval.
+            popFirst = Whether $(D popFront) should be called on the range
+                       before returning it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Warning:
+            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
+            would be a function pointer to a pure function, but forcing
+            $(D_PARAM func) to be pure is far too restrictive to be useful, and
+            in order to have the ease of use of having functions which generate
+            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
+            delegate.
+
+            If $(D_PARAM func) retains state which changes as it is called, then
+            some algorithms will not work correctly, because the range's
+            $(D save) will have failed to have really saved the range's state.
+            To avoid such bugs, don't pass a delegate which is
+            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
+            same time point with two different calls, it must return the same
+            result both times.
+
+            Of course, none of the functions in this module have this problem,
+            so it's only relevant for custom delegates.
+
+        Example:
+--------------------
+auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
+auto func = delegate (in Date date) //For iterating over even-numbered days.
+            {
+                if((date.day & 1) == 0)
+                    return date - dur!"days"(2);
+
+                return date - dur!"days"(1);
+            };
+auto range = interval.bwdRange(func);
+
+//An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
+assert(range.front == Date(2010, 9, 9));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 8));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 6));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 4));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 2));
+
+range.popFront();
+assert(range.empty);
+--------------------
+      +/
+    IntervalRange!(TP, Direction.bwd) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
+    {
+        _enforceNotEmpty();
+
+        auto range = IntervalRange!(TP, Direction.bwd)(this, func);
+
+        if(popFirst == PopFirst.yes)
+            range.popFront();
+
+        return range;
+    }
+
+
+    /+
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString()
+    {
+        return _toStringImpl();
+    }
+
+
+    /++
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString() const nothrow
+    {
+        return _toStringImpl();
+    }
+
+
+private:
+
+    /+
+        Since we have two versions of toString, we have _toStringImpl
+        so that they can share implementations.
+      +/
+    string _toStringImpl() const nothrow
+    {
+        import std.format : format;
+        try
+            return format("[%s - %s)", _begin, _end);
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+      +/
+    void _enforceNotEmpty(size_t line = __LINE__) const pure
+    {
+        if(empty)
+            throw new DateTimeException("Invalid operation for an empty Interval.", __FILE__, line);
+    }
+
+
+    /+
+        Whether the given values form a valid time interval.
+
+        Params:
+            begin = The starting point of the interval.
+            end   = The end point of the interval.
+     +/
+    static bool _valid(in TP begin, in TP end) pure nothrow
+    {
+        return begin <= end;
+    }
+
+
+    pure invariant()
+    {
+        assert(_valid(_begin, _end), "Invariant Failure: begin is not before or equal to end.");
+    }
+
+
+    TP _begin;
+    TP _end;
+}
+
+//Test Interval's constructors.
+unittest
+{
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 1, 1), Date(1, 1, 1)));
+
+    Interval!Date(Date.init, Date.init);
+    Interval!TimeOfDay(TimeOfDay.init, TimeOfDay.init);
+    Interval!DateTime(DateTime.init, DateTime.init);
+    Interval!SysTime(SysTime(0), SysTime(0));
+
+    Interval!DateTime(DateTime.init, dur!"days"(7));
+
+    //Verify Examples.
+    Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+    assert(Interval!Date(Date(1996, 1, 2), dur!"weeks"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 23)));
+    assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5)));
+    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"hours"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 15, 0, 0)));
+    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"minutes"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 3, 0)));
+    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"seconds"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3)));
+    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"msecs"(3000)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3)));
+}
+
+//Test Interval's begin.
+unittest
+{
+    assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).begin == Date(1, 1, 1));
+    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).begin == Date(2010, 1, 1));
+    assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).begin == Date(1997, 12, 31));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(cInterval.begin == Date(2010, 7, 4));
+    assert(iInterval.begin == Date(2010, 7, 4));
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin == Date(1996, 1, 2));
+}
+
+//Test Interval's end.
+unittest
+{
+    assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1));
+    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1));
+    assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).end == Date(1998, 1, 1));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(cInterval.end == Date(2012, 1, 7));
+    assert(iInterval.end == Date(2012, 1, 7));
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end == Date(2012, 3, 1));
+}
+
+//Test Interval's length.
+unittest
+{
+    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).length == dur!"days"(0));
+    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).length == dur!"days"(90));
+    assert(Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).length == dur!"seconds"(42_727));
+    assert(Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).length == dur!"seconds"(129_127));
+    assert(Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)), SysTime(DateTime(2010, 1, 2, 12, 22, 7))).length == dur!"seconds"(129_127));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(cInterval.length != Duration.zero);
+    assert(iInterval.length != Duration.zero);
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length == dur!"days"(5903));
+}
+
+//Test Interval's empty.
+unittest
+{
+    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).empty);
+    assert(!Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).empty);
+    assert(!Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).empty);
+    assert(!Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).empty);
+    assert(!Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)), SysTime(DateTime(2010, 1, 2, 12, 22, 7))).empty);
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!cInterval.empty);
+    assert(!iInterval.empty);
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty);
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty);
+}
+
+//Test Interval's contains(time point).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(Date(2010, 7, 4)));
+
+    assert(!interval.contains(Date(2009, 7, 4)));
+    assert(!interval.contains(Date(2010, 7, 3)));
+    assert(interval.contains(Date(2010, 7, 4)));
+    assert(interval.contains(Date(2010, 7, 5)));
+    assert(interval.contains(Date(2011, 7, 1)));
+    assert(interval.contains(Date(2012, 1, 6)));
+    assert(!interval.contains(Date(2012, 1, 7)));
+    assert(!interval.contains(Date(2012, 1, 8)));
+    assert(!interval.contains(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(interval.contains(cdate));
+    assert(cInterval.contains(cdate));
+    assert(iInterval.contains(cdate));
+
+    //Verify Examples.
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
+}
+
+//Test Interval's contains(Interval).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(interval.contains(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(interval.contains(interval));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!interval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!interval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).contains(interval));
+    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).contains(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).contains(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).contains(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).contains(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).contains(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).contains(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).contains(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).contains(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).contains(interval));
+    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).contains(interval));
+    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).contains(interval));
+
+    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(interval.contains(interval));
+    assert(interval.contains(cInterval));
+    assert(interval.contains(iInterval));
+    assert(!interval.contains(posInfInterval));
+    assert(!interval.contains(cPosInfInterval));
+    assert(!interval.contains(iPosInfInterval));
+    assert(!interval.contains(negInfInterval));
+    assert(!interval.contains(cNegInfInterval));
+    assert(!interval.contains(iNegInfInterval));
+    assert(cInterval.contains(interval));
+    assert(cInterval.contains(cInterval));
+    assert(cInterval.contains(iInterval));
+    assert(!cInterval.contains(posInfInterval));
+    assert(!cInterval.contains(cPosInfInterval));
+    assert(!cInterval.contains(iPosInfInterval));
+    assert(!cInterval.contains(negInfInterval));
+    assert(!cInterval.contains(cNegInfInterval));
+    assert(!cInterval.contains(iNegInfInterval));
+    assert(iInterval.contains(interval));
+    assert(iInterval.contains(cInterval));
+    assert(iInterval.contains(iInterval));
+    assert(!iInterval.contains(posInfInterval));
+    assert(!iInterval.contains(cPosInfInterval));
+    assert(!iInterval.contains(iPosInfInterval));
+    assert(!iInterval.contains(negInfInterval));
+    assert(!iInterval.contains(cNegInfInterval));
+    assert(!iInterval.contains(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
+}
+
+//Test Interval's isBefore(time point).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(Date(2010, 7, 4)));
+
+    assert(!interval.isBefore(Date(2009, 7, 3)));
+    assert(!interval.isBefore(Date(2010, 7, 3)));
+    assert(!interval.isBefore(Date(2010, 7, 4)));
+    assert(!interval.isBefore(Date(2010, 7, 5)));
+    assert(!interval.isBefore(Date(2011, 7, 1)));
+    assert(!interval.isBefore(Date(2012, 1, 6)));
+    assert(interval.isBefore(Date(2012, 1, 7)));
+    assert(interval.isBefore(Date(2012, 1, 8)));
+    assert(interval.isBefore(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!interval.isBefore(cdate));
+    assert(!cInterval.isBefore(cdate));
+    assert(!iInterval.isBefore(cdate));
+
+    //Verify Examples.
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
+}
+
+//Test Interval's isBefore(Interval).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(interval.isBefore(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!interval.isBefore(interval));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(interval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(interval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isBefore(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isBefore(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isBefore(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isBefore(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isBefore(interval));
+    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isBefore(interval));
+    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isBefore(interval));
+
+    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.isBefore(interval));
+    assert(!interval.isBefore(cInterval));
+    assert(!interval.isBefore(iInterval));
+    assert(!interval.isBefore(posInfInterval));
+    assert(!interval.isBefore(cPosInfInterval));
+    assert(!interval.isBefore(iPosInfInterval));
+    assert(!interval.isBefore(negInfInterval));
+    assert(!interval.isBefore(cNegInfInterval));
+    assert(!interval.isBefore(iNegInfInterval));
+    assert(!cInterval.isBefore(interval));
+    assert(!cInterval.isBefore(cInterval));
+    assert(!cInterval.isBefore(iInterval));
+    assert(!cInterval.isBefore(posInfInterval));
+    assert(!cInterval.isBefore(cPosInfInterval));
+    assert(!cInterval.isBefore(iPosInfInterval));
+    assert(!cInterval.isBefore(negInfInterval));
+    assert(!cInterval.isBefore(cNegInfInterval));
+    assert(!cInterval.isBefore(iNegInfInterval));
+    assert(!iInterval.isBefore(interval));
+    assert(!iInterval.isBefore(cInterval));
+    assert(!iInterval.isBefore(iInterval));
+    assert(!iInterval.isBefore(posInfInterval));
+    assert(!iInterval.isBefore(cPosInfInterval));
+    assert(!iInterval.isBefore(iPosInfInterval));
+    assert(!iInterval.isBefore(negInfInterval));
+    assert(!iInterval.isBefore(cNegInfInterval));
+    assert(!iInterval.isBefore(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2013, 3, 7))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
+}
+
+//Test Interval's isAfter(time point).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(Date(2010, 7, 4)));
+
+    assert(interval.isAfter(Date(2009, 7, 4)));
+    assert(interval.isAfter(Date(2010, 7, 3)));
+    assert(!interval.isAfter(Date(2010, 7, 4)));
+    assert(!interval.isAfter(Date(2010, 7, 5)));
+    assert(!interval.isAfter(Date(2011, 7, 1)));
+    assert(!interval.isAfter(Date(2012, 1, 6)));
+    assert(!interval.isAfter(Date(2012, 1, 7)));
+    assert(!interval.isAfter(Date(2012, 1, 8)));
+    assert(!interval.isAfter(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!interval.isAfter(cdate));
+    assert(!cInterval.isAfter(cdate));
+    assert(!iInterval.isAfter(cdate));
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));
+}
+
+//Test Interval's isAfter(Interval).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(interval.isAfter(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!interval.isAfter(interval));
+    assert(interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAfter(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAfter(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAfter(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAfter(interval));
+    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAfter(interval));
+    assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAfter(interval));
+
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.isAfter(interval));
+    assert(!interval.isAfter(cInterval));
+    assert(!interval.isAfter(iInterval));
+    assert(!interval.isAfter(posInfInterval));
+    assert(!interval.isAfter(cPosInfInterval));
+    assert(!interval.isAfter(iPosInfInterval));
+    assert(!interval.isAfter(negInfInterval));
+    assert(!interval.isAfter(cNegInfInterval));
+    assert(!interval.isAfter(iNegInfInterval));
+    assert(!cInterval.isAfter(interval));
+    assert(!cInterval.isAfter(cInterval));
+    assert(!cInterval.isAfter(iInterval));
+    assert(!cInterval.isAfter(posInfInterval));
+    assert(!cInterval.isAfter(cPosInfInterval));
+    assert(!cInterval.isAfter(iPosInfInterval));
+    assert(!cInterval.isAfter(negInfInterval));
+    assert(!cInterval.isAfter(cNegInfInterval));
+    assert(!cInterval.isAfter(iNegInfInterval));
+    assert(!iInterval.isAfter(interval));
+    assert(!iInterval.isAfter(cInterval));
+    assert(!iInterval.isAfter(iInterval));
+    assert(!iInterval.isAfter(posInfInterval));
+    assert(!iInterval.isAfter(cPosInfInterval));
+    assert(!iInterval.isAfter(iPosInfInterval));
+    assert(!iInterval.isAfter(negInfInterval));
+    assert(!iInterval.isAfter(cNegInfInterval));
+    assert(!iInterval.isAfter(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 1, 2))));
+}
+
+//Test Interval's intersects().
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(interval.intersects(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(interval.intersects(interval));
+    assert(!interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!interval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!interval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersects(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersects(interval));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersects(interval));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersects(interval));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersects(interval));
+    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersects(interval));
+    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersects(interval));
+
+    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(interval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(interval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(interval.intersects(interval));
+    assert(interval.intersects(cInterval));
+    assert(interval.intersects(iInterval));
+    assert(interval.intersects(posInfInterval));
+    assert(interval.intersects(cPosInfInterval));
+    assert(interval.intersects(iPosInfInterval));
+    assert(interval.intersects(negInfInterval));
+    assert(interval.intersects(cNegInfInterval));
+    assert(interval.intersects(iNegInfInterval));
+    assert(cInterval.intersects(interval));
+    assert(cInterval.intersects(cInterval));
+    assert(cInterval.intersects(iInterval));
+    assert(cInterval.intersects(posInfInterval));
+    assert(cInterval.intersects(cPosInfInterval));
+    assert(cInterval.intersects(iPosInfInterval));
+    assert(cInterval.intersects(negInfInterval));
+    assert(cInterval.intersects(cNegInfInterval));
+    assert(cInterval.intersects(iNegInfInterval));
+    assert(iInterval.intersects(interval));
+    assert(iInterval.intersects(cInterval));
+    assert(iInterval.intersects(iInterval));
+    assert(iInterval.intersects(posInfInterval));
+    assert(iInterval.intersects(cPosInfInterval));
+    assert(iInterval.intersects(iPosInfInterval));
+    assert(iInterval.intersects(negInfInterval));
+    assert(iInterval.intersects(cNegInfInterval));
+    assert(iInterval.intersects(iNegInfInterval));
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 1, 2))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2000, 1, 2))));
+}
+
+//Test Interval's intersection().
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersection(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersection(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersection(interval));
+    assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersection(interval));
+
+    assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 7))));
+    assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 3))));
+    assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 4))));
+
+    assert(interval.intersection(interval) == interval);
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
+    assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
+    assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+    assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+
+    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersection(interval) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersection(interval) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersection(interval) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+
+    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
+    assert(interval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+
+    assert(interval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
+    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6)));
+    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.intersection(interval).empty);
+    assert(!interval.intersection(cInterval).empty);
+    assert(!interval.intersection(iInterval).empty);
+    assert(!interval.intersection(posInfInterval).empty);
+    assert(!interval.intersection(cPosInfInterval).empty);
+    assert(!interval.intersection(iPosInfInterval).empty);
+    assert(!interval.intersection(negInfInterval).empty);
+    assert(!interval.intersection(cNegInfInterval).empty);
+    assert(!interval.intersection(iNegInfInterval).empty);
+    assert(!cInterval.intersection(interval).empty);
+    assert(!cInterval.intersection(cInterval).empty);
+    assert(!cInterval.intersection(iInterval).empty);
+    assert(!cInterval.intersection(posInfInterval).empty);
+    assert(!cInterval.intersection(cPosInfInterval).empty);
+    assert(!cInterval.intersection(iPosInfInterval).empty);
+    assert(!cInterval.intersection(negInfInterval).empty);
+    assert(!cInterval.intersection(cNegInfInterval).empty);
+    assert(!cInterval.intersection(iNegInfInterval).empty);
+    assert(!iInterval.intersection(interval).empty);
+    assert(!iInterval.intersection(cInterval).empty);
+    assert(!iInterval.intersection(iInterval).empty);
+    assert(!iInterval.intersection(posInfInterval).empty);
+    assert(!iInterval.intersection(cPosInfInterval).empty);
+    assert(!iInterval.intersection(iPosInfInterval).empty);
+    assert(!iInterval.intersection(negInfInterval).empty);
+    assert(!iInterval.intersection(cNegInfInterval).empty);
+    assert(!iInterval.intersection(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
+}
+
+//Test Interval's isAdjacent().
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    static void testInterval(in Interval!Date interval1, in Interval!Date interval2)
+    {
+        interval1.isAdjacent(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!interval.isAdjacent(interval));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(interval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAdjacent(interval));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAdjacent(interval));
+    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAdjacent(interval));
+    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAdjacent(interval));
+
+    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.isAdjacent(interval));
+    assert(!interval.isAdjacent(cInterval));
+    assert(!interval.isAdjacent(iInterval));
+    assert(!interval.isAdjacent(posInfInterval));
+    assert(!interval.isAdjacent(cPosInfInterval));
+    assert(!interval.isAdjacent(iPosInfInterval));
+    assert(!interval.isAdjacent(negInfInterval));
+    assert(!interval.isAdjacent(cNegInfInterval));
+    assert(!interval.isAdjacent(iNegInfInterval));
+    assert(!cInterval.isAdjacent(interval));
+    assert(!cInterval.isAdjacent(cInterval));
+    assert(!cInterval.isAdjacent(iInterval));
+    assert(!cInterval.isAdjacent(posInfInterval));
+    assert(!cInterval.isAdjacent(cPosInfInterval));
+    assert(!cInterval.isAdjacent(iPosInfInterval));
+    assert(!cInterval.isAdjacent(negInfInterval));
+    assert(!cInterval.isAdjacent(cNegInfInterval));
+    assert(!cInterval.isAdjacent(iNegInfInterval));
+    assert(!iInterval.isAdjacent(interval));
+    assert(!iInterval.isAdjacent(cInterval));
+    assert(!iInterval.isAdjacent(iInterval));
+    assert(!iInterval.isAdjacent(posInfInterval));
+    assert(!iInterval.isAdjacent(cPosInfInterval));
+    assert(!iInterval.isAdjacent(iPosInfInterval));
+    assert(!iInterval.isAdjacent(negInfInterval));
+    assert(!iInterval.isAdjacent(cNegInfInterval));
+    assert(!iInterval.isAdjacent(iNegInfInterval));
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1))));
+
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2))));
+    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(2000, 1, 2))));
+}
+
+//Test Interval's merge().
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    static void testInterval(I)(in Interval!Date interval1, in I interval2)
+    {
+        interval1.merge(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)), interval));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)), interval));
+
+    assertThrown!DateTimeException(testInterval(interval, PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assertThrown!DateTimeException(testInterval(interval, NegInfInterval!Date(Date(2010, 7, 3))));
+
+    assert(interval.merge(interval) == interval);
+    assert(interval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(interval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+
+    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).merge(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+
+    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.merge(interval).empty);
+    assert(!interval.merge(cInterval).empty);
+    assert(!interval.merge(iInterval).empty);
+    assert(!interval.merge(posInfInterval).empty);
+    assert(!interval.merge(cPosInfInterval).empty);
+    assert(!interval.merge(iPosInfInterval).empty);
+    assert(!interval.merge(negInfInterval).empty);
+    assert(!interval.merge(cNegInfInterval).empty);
+    assert(!interval.merge(iNegInfInterval).empty);
+    assert(!cInterval.merge(interval).empty);
+    assert(!cInterval.merge(cInterval).empty);
+    assert(!cInterval.merge(iInterval).empty);
+    assert(!cInterval.merge(posInfInterval).empty);
+    assert(!cInterval.merge(cPosInfInterval).empty);
+    assert(!cInterval.merge(iPosInfInterval).empty);
+    assert(!cInterval.merge(negInfInterval).empty);
+    assert(!cInterval.merge(cNegInfInterval).empty);
+    assert(!cInterval.merge(iNegInfInterval).empty);
+    assert(!iInterval.merge(interval).empty);
+    assert(!iInterval.merge(cInterval).empty);
+    assert(!iInterval.merge(iInterval).empty);
+    assert(!iInterval.merge(posInfInterval).empty);
+    assert(!iInterval.merge(cPosInfInterval).empty);
+    assert(!iInterval.merge(iPosInfInterval).empty);
+    assert(!iInterval.merge(negInfInterval).empty);
+    assert(!iInterval.merge(cNegInfInterval).empty);
+    assert(!iInterval.merge(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(2012, 3, 1))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1996, 1, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
+}
+
+//Test Interval's span().
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    static void testInterval(in Interval!Date interval1, in Interval!Date interval2)
+    {
+        interval1.span(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
+    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(interval.span(interval) == interval);
+    assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(interval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(interval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9)));
+
+    assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).span(interval) ==
+                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).span(interval) ==
+                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).span(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).span(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).span(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).span(interval) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).span(interval) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9)));
+
+    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!interval.span(interval).empty);
+    assert(!interval.span(cInterval).empty);
+    assert(!interval.span(iInterval).empty);
+    assert(!interval.span(posInfInterval).empty);
+    assert(!interval.span(cPosInfInterval).empty);
+    assert(!interval.span(iPosInfInterval).empty);
+    assert(!interval.span(negInfInterval).empty);
+    assert(!interval.span(cNegInfInterval).empty);
+    assert(!interval.span(iNegInfInterval).empty);
+    assert(!cInterval.span(interval).empty);
+    assert(!cInterval.span(cInterval).empty);
+    assert(!cInterval.span(iInterval).empty);
+    assert(!cInterval.span(posInfInterval).empty);
+    assert(!cInterval.span(cPosInfInterval).empty);
+    assert(!cInterval.span(iPosInfInterval).empty);
+    assert(!cInterval.span(negInfInterval).empty);
+    assert(!cInterval.span(cNegInfInterval).empty);
+    assert(!cInterval.span(iNegInfInterval).empty);
+    assert(!iInterval.span(interval).empty);
+    assert(!iInterval.span(cInterval).empty);
+    assert(!iInterval.span(iInterval).empty);
+    assert(!iInterval.span(posInfInterval).empty);
+    assert(!iInterval.span(cPosInfInterval).empty);
+    assert(!iInterval.span(iPosInfInterval).empty);
+    assert(!iInterval.span(negInfInterval).empty);
+    assert(!iInterval.span(cNegInfInterval).empty);
+    assert(!iInterval.span(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(2050, 1, 1))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1602, 5, 21))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
+}
+
+//Test Interval's shift(duration).
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+    static void testIntervalFail(Interval!Date interval, in Duration duration)
+    {
+        interval.shift(duration);
+    }
+
+    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1)));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.shift(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), Interval!Date(Date(2010, 7, 26), Date(2012, 1, 29)));
+    testInterval(interval, dur!"days"(-22), Interval!Date(Date(2010, 6, 12), Date(2011, 12, 16)));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
+    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
+
+    interval1.shift(dur!"days"(50));
+    assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25)));
+
+    interval2.shift(dur!"days"(-50));
+    assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15)));
+}
+
+//Test Interval's shift(int, int, AllowDayOverflow).
+unittest
+{
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+        static void testIntervalFail(Interval!Date interval, int years, int months)
+        {
+            interval.shift(years, months);
+        }
+
+        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0));
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
+        {
+            interval.shift(years, months, allow);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, Interval!Date(Date(2015, 7, 4), Date(2017, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7)));
+
+        auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Interval!Date(Date(2001, 3, 1), Date(2011, 7, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Interval!Date(Date(2000, 12, 29), Date(2011, 5, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Interval!Date(Date(1998, 12, 29), Date(2009, 5, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Interval!Date(Date(1999, 3, 1), Date(2009, 7, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, Interval!Date(Date(2001, 2, 28), Date(2011, 6, 30)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, Interval!Date(Date(2000, 12, 29), Date(2011, 4, 30)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, Interval!Date(Date(1998, 12, 29), Date(2009, 4, 30)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, Interval!Date(Date(1999, 2, 28), Date(2009, 6, 30)));
+    }
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.shift(5)));
+    static assert(!__traits(compiles, iInterval.shift(5)));
+
+    //Verify Examples.
+    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+    interval1.shift(2);
+    assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1)));
+
+    interval2.shift(-2);
+    assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1)));
+}
+
+//Test Interval's expand(Duration).
+unittest
+{
+    auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7));
+
+    static void testIntervalFail(I)(I interval, in Duration duration)
+    {
+        interval.expand(duration);
+    }
+
+    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1)));
+    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)), dur!"days"(-5)));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.expand(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), Interval!Date(Date(2000, 6, 12), Date(2012, 1, 29)));
+    testInterval(interval, dur!"days"(-22), Interval!Date(Date(2000, 7, 26), Date(2011, 12, 16)));
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+    interval1.expand(dur!"days"(2));
+    assert(interval1 == Interval!Date(Date(1995, 12, 31), Date(2012, 3, 3)));
+
+    interval2.expand(dur!"days"(-2));
+    assert(interval2 == Interval!Date(Date(1996, 1, 4), Date(2012, 2, 28)));
+}
+
+//Test Interval's expand(int, int, AllowDayOverflow, Direction)
+unittest
+{
+    {
+        auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7));
+
+        static void testIntervalFail(Interval!Date interval, int years, int months)
+        {
+            interval.expand(years, months);
+        }
+
+        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0));
+        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)), -5, 0));
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, Direction dir, in I expected, size_t line = __LINE__)
+        {
+            interval.expand(years, months, allow, dir);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1995, 7, 4), Date(2017, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7)));
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 7, 4), Date(2017, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 7, 4), Date(2007, 1, 7)));
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1995, 7, 4), Date(2012, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2005, 7, 4), Date(2012, 1, 7)));
+
+        auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1998, 12, 29), Date(2011, 7, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1999, 3, 1), Date(2011, 5, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2001, 3, 1), Date(2009, 5, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2000, 12, 29), Date(2009, 7, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(1998, 12, 29), Date(2011, 6, 30)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(1999, 2, 28), Date(2011, 4, 30)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(2001, 2, 28), Date(2009, 4, 30)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(2000, 12, 29), Date(2009, 6, 30)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 7, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 5, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 5, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 7, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 6, 30)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 4, 30)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 4, 30)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 6, 30)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1999, 3, 1), Date(2010, 5, 31)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2001, 3, 1), Date(2010, 5, 31)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(1999, 2, 28), Date(2010, 5, 31)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(2001, 2, 28), Date(2010, 5, 31)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31)));
+    }
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.expand(5)));
+    static assert(!__traits(compiles, iInterval.expand(5)));
+
+    //Verify Examples.
+    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
+
+    interval1.expand(2);
+    assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));
+
+    interval2.expand(-2);
+    assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
+}
+
+//Test Interval's fwdRange.
+unittest
+{
+    {
+        auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21));
+
+        static void testInterval1(Interval!Date interval)
+        {
+            interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+        }
+
+        assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+        static void testInterval2(Interval!Date interval)
+        {
+            interval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront();
+        }
+
+        assertThrown!DateTimeException(testInterval2(interval));
+
+        assert(!interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+        assert(interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).empty);
+
+        assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front ==
+                    Date(2010, 9, 12));
+
+        assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front ==
+                    Date(2010, 9, 17));
+    }
+
+    //Verify Examples.
+    {
+        auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
+        auto func = delegate (in Date date)
+                    {
+                        if((date.day & 1) == 0)
+                            return date + dur!"days"(2);
+
+                        return date + dur!"days"(1);
+                    };
+        auto range = interval.fwdRange(func);
+
+        assert(range.front == Date(2010, 9, 1)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 2));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 4));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 6));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 8));
+
+        range.popFront();
+        assert(range.empty);
+    }
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!cInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+    assert(!iInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+}
+
+//Test Interval's bwdRange.
+unittest
+{
+    {
+        auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21));
+
+        static void testInterval1(Interval!Date interval)
+        {
+            interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+        }
+
+        assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+        static void testInterval2(Interval!Date interval)
+        {
+            interval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront();
+        }
+
+        assertThrown!DateTimeException(testInterval2(interval));
+
+        assert(!interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
+        assert(interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).empty);
+
+        assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front ==
+                    Date(2010, 10, 1));
+
+        assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front ==
+                    Date(2010, 9, 24));
+    }
+
+    //Verify Examples.
+    {
+        auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
+        auto func = delegate (in Date date)
+                    {
+                        if((date.day & 1) == 0)
+                            return date - dur!"days"(2);
+
+                        return date - dur!"days"(1);
+                    };
+        auto range = interval.bwdRange(func);
+
+        assert(range.front == Date(2010, 9, 9)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 8));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 6));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 4));
+
+        range.popFront();
+        assert(range.front == Date(2010, 9, 2));
+
+        range.popFront();
+        assert(range.empty);
+    }
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!cInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+    assert(!iInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+}
+
+//Test Interval's toString().
+unittest
+{
+    assert(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).toString() == "[2010-Jul-04 - 2012-Jan-07)");
+
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(cInterval.toString());
+    assert(iInterval.toString());
+}
+
+
+/++
+    Represents an interval of time which has positive infinity as its end point.
+
+    Any ranges which iterate over a $(D PosInfInterval) are infinite. So, the
+    main purpose of using $(D PosInfInterval) is to create an infinite range
+    which starts at a fixed point in time and goes to positive infinity.
+  +/
+struct PosInfInterval(TP)
+{
+public:
+
+    /++
+        Params:
+            begin = The time point which begins the interval.
+
+        Example:
+--------------------
+auto interval = PosInfInterval!Date(Date(1996, 1, 2));
+--------------------
+      +/
+    this(in TP begin) pure nothrow
+    {
+        _begin = cast(TP)begin;
+    }
+
+
+    /++
+        Params:
+            rhs = The $(D PosInfInterval) to assign to this one.
+      +/
+    ref PosInfInterval opAssign(const ref PosInfInterval rhs) pure nothrow
+    {
+        _begin = cast(TP)rhs._begin;
+        return this;
+    }
+
+
+    /++
+        Params:
+            rhs = The $(D PosInfInterval) to assign to this one.
+      +/
+    ref PosInfInterval opAssign(PosInfInterval rhs) pure nothrow
+    {
+        _begin = cast(TP)rhs._begin;
+        return this;
+    }
+
+
+    /++
+        The starting point of the interval. It is included in the interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2));
+--------------------
+      +/
+    @property TP begin() const pure nothrow
+    {
+        return cast(TP)_begin;
+    }
+
+
+    /++
+        The starting point of the interval. It is included in the interval.
+
+        Params:
+            timePoint = The time point to set $(D begin) to.
+      +/
+    @property void begin(TP timePoint) pure nothrow
+    {
+        _begin = timePoint;
+    }
+
+
+    /++
+        Whether the interval's length is 0. Always returns false.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty);
+--------------------
+      +/
+    @property bool empty() const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether the given time point is within this interval.
+
+        Params:
+            timePoint = The time point to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24)));
+assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5)));
+--------------------
+      +/
+    bool contains(TP timePoint) const pure nothrow
+    {
+        return timePoint >= _begin;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+--------------------
+      +/
+    bool contains(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return interval._begin >= _begin;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            PosInfInterval!Date(Date(1995, 7, 2))));
+--------------------
+      +/
+    bool contains(in PosInfInterval interval) const pure nothrow
+    {
+        return interval._begin >= _begin;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Always returns false because an interval going to positive infinity
+        can never contain an interval beginning at negative infinity.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+--------------------
+      +/
+    bool contains(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is before the given time point.
+
+        Always returns false because an interval going to positive infinity
+        can never be before any time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is before
+                        it.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24)));
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5)));
+--------------------
+      +/
+    bool isBefore(in TP timePoint) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Always returns false (unless the given interval is empty) because an
+        interval going to positive infinity can never be before any other
+        interval.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+--------------------
+      +/
+    bool isBefore(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Always returns false because an interval going to positive infinity can
+        never be before any other interval.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
+            PosInfInterval!Date(Date(1992, 5, 4))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
+            PosInfInterval!Date(Date(2013, 3, 7))));
+--------------------
+      +/
+    bool isBefore(in PosInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Always returns false because an interval going to positive infinity can
+        never be before any other interval.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+--------------------
+      +/
+    bool isBefore(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is after
+                        it.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24)));
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5)));
+--------------------
+      +/
+    bool isAfter(in TP timePoint) const pure nothrow
+    {
+        return timePoint < _begin;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+--------------------
+      +/
+    bool isAfter(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return _begin >= interval._end;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Always returns false because an interval going to positive infinity can
+        never be after another interval going to positive infinity.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            PosInfInterval!Date(Date(1990, 1, 7))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+--------------------
+      +/
+    bool isAfter(in PosInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
+            NegInfInterval!Date(Date(2000, 7, 1))));
+--------------------
+      +/
+    bool isAfter(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return _begin >= interval._end;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+--------------------
+      +/
+    bool intersects(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return interval._end > _begin;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Always returns true because two intervals going to positive infinity
+        always overlap.
+
+        Params:
+            interval = The interval to check for intersection with this
+                       interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            PosInfInterval!Date(Date(1990, 1, 7))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+--------------------
+      +/
+    bool intersects(in PosInfInterval interval) const pure nothrow
+    {
+        return true;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this
+                       interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
+            NegInfInterval!Date(Date(2000, 7, 1))));
+--------------------
+      +/
+    bool intersects(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return _begin < interval._end;
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect or if
+            the given interval is empty.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
+       Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
+--------------------
+      +/
+    Interval!TP intersection(in Interval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        auto begin = _begin > interval._begin ? _begin : interval._begin;
+
+        return Interval!TP(begin, interval._end);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            PosInfInterval!Date(Date(1999, 1, 12))) ==
+       PosInfInterval!Date(Date(1999, 1 , 12)));
+--------------------
+      +/
+    PosInfInterval intersection(in PosInfInterval interval) const pure nothrow
+    {
+        return PosInfInterval(_begin < interval._begin ? interval._begin : _begin);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            NegInfInterval!Date(Date(1999, 7, 6))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12)));
+--------------------
+      +/
+    Interval!TP intersection(in NegInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        return Interval!TP(_begin, interval._end);
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
+            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+
+assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+--------------------
+      +/
+    bool isAdjacent(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return _begin == interval._end;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Always returns false because two intervals going to positive infinity
+        can never be adjacent to one another.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Example:
+--------------------
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
+            PosInfInterval!Date(Date(1990, 1, 7))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
+            PosInfInterval!Date(Date(1996, 1, 2))));
+--------------------
+      +/
+    bool isAdjacent(in PosInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
+            NegInfInterval!Date(Date(1996, 1, 2))));
+
+assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
+            NegInfInterval!Date(Date(2000, 7, 1))));
+--------------------
+      +/
+    bool isAdjacent(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return _begin == interval._end;
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect and are
+            not adjacent or if the given interval is empty.
+
+        Note:
+            There is no overload for $(D merge) which takes a
+            $(D NegInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval merge(in Interval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.isAdjacent(interval) || this.intersects(interval),
+                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));
+
+        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Note:
+            There is no overload for $(D merge) which takes a
+            $(D NegInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
+            PosInfInterval!Date(Date(1999, 1, 12))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval merge(in PosInfInterval interval) const pure nothrow
+    {
+        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Note:
+            There is no overload for $(D span) which takes a
+            $(D NegInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
+            Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) ==
+       PosInfInterval!Date(Date(500, 8, 9)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval span(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this
+                       interval.
+
+        Note:
+            There is no overload for $(D span) which takes a
+            $(D NegInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       PosInfInterval!Date(Date(1990, 7 , 6)));
+
+assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
+            PosInfInterval!Date(Date(1999, 1, 12))) ==
+       PosInfInterval!Date(Date(1996, 1 , 2)));
+--------------------
+      +/
+    PosInfInterval span(in PosInfInterval interval) const pure nothrow
+    {
+        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
+    }
+
+
+    /++
+        Shifts the $(D begin) of this interval forward or backwards in time by
+        the given duration (a positive duration shifts the interval forward; a
+        negative duration shifts it backward). Effectively, it does
+        $(D begin += duration).
+
+        Params:
+            duration = The duration to shift the interval by.
+
+        Example:
+--------------------
+auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+interval1.shift(dur!"days"(50));
+assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));
+
+interval2.shift(dur!"days"(-50));
+assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
+--------------------
+      +/
+    void shift(D)(D duration) pure nothrow
+        if(__traits(compiles, begin + duration))
+    {
+        _begin += duration;
+    }
+
+
+    static if(__traits(compiles, begin.add!"months"(1)) &&
+              __traits(compiles, begin.add!"years"(1)))
+    {
+        /++
+            Shifts the $(D begin) of this interval forward or backwards in time
+            by the given number of years and/or months (a positive number of years
+            and months shifts the interval forward; a negative number shifts it
+            backward). It adds the years the given years and months to
+            $(D begin). It effectively calls $(D add!"years"()) and then
+            $(D add!"months"()) on $(D begin) with the given number of years and
+            months.
+
+            Params:
+                years         = The number of years to shift the interval by.
+                months        = The number of months to shift the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D begin), causing its month to increment.
+
+            Throws:
+                $(LREF DateTimeException) if this interval is empty or if the
+                resulting interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+interval1.shift(dur!"days"(50));
+assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));
+
+interval2.shift(dur!"days"(-50));
+assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
+--------------------
+          +/
+        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
+            if(isIntegral!T)
+        {
+            auto begin = _begin;
+
+            begin.add!"years"(years, allowOverflow);
+            begin.add!"months"(months, allowOverflow);
+
+            _begin = begin;
+        }
+    }
+
+
+    /++
+        Expands the interval backwards in time. Effectively, it does
+        $(D begin -= duration).
+
+        Params:
+            duration = The duration to expand the interval by.
+
+        Example:
+--------------------
+auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+interval1.expand(dur!"days"(2));
+assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31)));
+
+interval2.expand(dur!"days"(-2));
+assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4)));
+--------------------
+      +/
+    void expand(D)(D duration) pure nothrow
+        if(__traits(compiles, begin + duration))
+    {
+        _begin -= duration;
+    }
+
+
+    static if(__traits(compiles, begin.add!"months"(1)) &&
+              __traits(compiles, begin.add!"years"(1)))
+    {
+        /++
+            Expands the interval forwards and/or backwards in time. Effectively,
+            it subtracts the given number of months/years from $(D begin).
+
+            Params:
+                years         = The number of years to expand the interval by.
+                months        = The number of months to expand the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D begin), causing its month to increment.
+
+            Throws:
+                $(LREF DateTimeException) if this interval is empty or if the
+                resulting interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+interval1.expand(2);
+assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2)));
+
+interval2.expand(-2);
+assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2)));
+--------------------
+          +/
+        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
+            if(isIntegral!T)
+        {
+            auto begin = _begin;
+
+            begin.add!"years"(-years, allowOverflow);
+            begin.add!"months"(-months, allowOverflow);
+
+            _begin = begin;
+
+            return;
+        }
+    }
+
+
+    /++
+        Returns a range which iterates forward over the interval, starting
+        at $(D begin), using $(D_PARAM func) to generate each successive time
+        point.
+
+        The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is
+        used to generate the next $(D front) when $(D popFront) is called. If
+        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
+        before the range is returned (so that $(D front) is a time point which
+        $(D_PARAM func) would generate).
+
+        If $(D_PARAM func) ever generates a time point less than or equal to the
+        current $(D front) of the range, then a $(LREF DateTimeException) will be
+        thrown.
+
+        There are helper functions in this module which generate common
+        delegates to pass to $(D fwdRange). Their documentation starts with
+        "Range-generating function," to make them easily searchable.
+
+        Params:
+            func     = The function used to generate the time points of the
+                       range over the interval.
+            popFirst = Whether $(D popFront) should be called on the range
+                       before returning it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Warning:
+            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
+            would be a function pointer to a pure function, but forcing
+            $(D_PARAM func) to be pure is far too restrictive to be useful, and
+            in order to have the ease of use of having functions which generate
+            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
+            delegate.
+
+            If $(D_PARAM func) retains state which changes as it is called, then
+            some algorithms will not work correctly, because the range's
+            $(D save) will have failed to have really saved the range's state.
+            To avoid such bugs, don't pass a delegate which is
+            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
+            same time point with two different calls, it must return the same
+            result both times.
+
+            Of course, none of the functions in this module have this problem,
+            so it's only relevant for custom delegates.
+
+        Example:
+--------------------
+auto interval = PosInfInterval!Date(Date(2010, 9, 1));
+auto func = delegate (in Date date) //For iterating over even-numbered days.
+            {
+                if((date.day & 1) == 0)
+                    return date + dur!"days"(2);
+
+                return date + dur!"days"(1);
+            };
+auto range = interval.fwdRange(func);
+
+//An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
+assert(range.front == Date(2010, 9, 1));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 2));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 4));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 6));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 8));
+
+range.popFront();
+assert(!range.empty);
+--------------------
+      +/
+    PosInfIntervalRange!(TP) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
+    {
+        auto range = PosInfIntervalRange!(TP)(this, func);
+
+        if(popFirst == PopFirst.yes)
+            range.popFront();
+
+        return range;
+    }
+
+
+    /+
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString()
+    {
+        return _toStringImpl();
+    }
+
+
+    /++
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString() const nothrow
+    {
+        return _toStringImpl();
+    }
+
+private:
+
+    /+
+        Since we have two versions of toString(), we have _toStringImpl()
+        so that they can share implementations.
+      +/
+    string _toStringImpl() const nothrow
+    {
+        import std.format : format;
+        try
+            return format("[%s - ∞)", _begin);
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+
+    TP _begin;
+}
+
+//Test PosInfInterval's constructor.
+unittest
+{
+    PosInfInterval!Date(Date.init);
+    PosInfInterval!TimeOfDay(TimeOfDay.init);
+    PosInfInterval!DateTime(DateTime.init);
+    PosInfInterval!SysTime(SysTime(0));
+
+    //Verify Examples.
+    auto interval = PosInfInterval!Date(Date(1996, 1, 2));
+}
+
+//Test PosInfInterval's begin.
+unittest
+{
+    assert(PosInfInterval!Date(Date(1, 1, 1)).begin == Date(1, 1, 1));
+    assert(PosInfInterval!Date(Date(2010, 1, 1)).begin == Date(2010, 1, 1));
+    assert(PosInfInterval!Date(Date(1997, 12, 31)).begin == Date(1997, 12, 31));
+
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(cPosInfInterval.begin != Date.init);
+    assert(iPosInfInterval.begin != Date.init);
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2));
+}
+
+//Test PosInfInterval's empty.
+unittest
+{
+    assert(!PosInfInterval!Date(Date(2010, 1, 1)).empty);
+    assert(!PosInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty);
+    assert(!PosInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty);
+    assert(!PosInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty);
+
+    const cPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    assert(!cPosInfInterval.empty);
+    assert(!iPosInfInterval.empty);
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty);
+}
+
+//Test PosInfInterval's contains(time point).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    assert(!posInfInterval.contains(Date(2009, 7, 4)));
+    assert(!posInfInterval.contains(Date(2010, 7, 3)));
+    assert(posInfInterval.contains(Date(2010, 7, 4)));
+    assert(posInfInterval.contains(Date(2010, 7, 5)));
+    assert(posInfInterval.contains(Date(2011, 7, 1)));
+    assert(posInfInterval.contains(Date(2012, 1, 6)));
+    assert(posInfInterval.contains(Date(2012, 1, 7)));
+    assert(posInfInterval.contains(Date(2012, 1, 8)));
+    assert(posInfInterval.contains(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(posInfInterval.contains(cdate));
+    assert(cPosInfInterval.contains(cdate));
+    assert(iPosInfInterval.contains(cdate));
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5)));
+}
+
+//Test PosInfInterval's contains(Interval).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.contains(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(posInfInterval.contains(posInfInterval));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(PosInfInterval!Date(Date(2010, 7, 3)).contains(posInfInterval));
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).contains(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 5)).contains(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 6)).contains(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 7)).contains(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 8)).contains(posInfInterval));
+
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(posInfInterval.contains(interval));
+    assert(posInfInterval.contains(cInterval));
+    assert(posInfInterval.contains(iInterval));
+    assert(posInfInterval.contains(posInfInterval));
+    assert(posInfInterval.contains(cPosInfInterval));
+    assert(posInfInterval.contains(iPosInfInterval));
+    assert(!posInfInterval.contains(negInfInterval));
+    assert(!posInfInterval.contains(cNegInfInterval));
+    assert(!posInfInterval.contains(iNegInfInterval));
+    assert(cPosInfInterval.contains(interval));
+    assert(cPosInfInterval.contains(cInterval));
+    assert(cPosInfInterval.contains(iInterval));
+    assert(cPosInfInterval.contains(posInfInterval));
+    assert(cPosInfInterval.contains(cPosInfInterval));
+    assert(cPosInfInterval.contains(iPosInfInterval));
+    assert(!cPosInfInterval.contains(negInfInterval));
+    assert(!cPosInfInterval.contains(cNegInfInterval));
+    assert(!cPosInfInterval.contains(iNegInfInterval));
+    assert(iPosInfInterval.contains(interval));
+    assert(iPosInfInterval.contains(cInterval));
+    assert(iPosInfInterval.contains(iInterval));
+    assert(iPosInfInterval.contains(posInfInterval));
+    assert(iPosInfInterval.contains(cPosInfInterval));
+    assert(iPosInfInterval.contains(iPosInfInterval));
+    assert(!iPosInfInterval.contains(negInfInterval));
+    assert(!iPosInfInterval.contains(cNegInfInterval));
+    assert(!iPosInfInterval.contains(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1995, 7, 2))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
+}
+
+//Test PosInfInterval's isBefore(time point).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    assert(!posInfInterval.isBefore(Date(2009, 7, 3)));
+    assert(!posInfInterval.isBefore(Date(2010, 7, 3)));
+    assert(!posInfInterval.isBefore(Date(2010, 7, 4)));
+    assert(!posInfInterval.isBefore(Date(2010, 7, 5)));
+    assert(!posInfInterval.isBefore(Date(2011, 7, 1)));
+    assert(!posInfInterval.isBefore(Date(2012, 1, 6)));
+    assert(!posInfInterval.isBefore(Date(2012, 1, 7)));
+    assert(!posInfInterval.isBefore(Date(2012, 1, 8)));
+    assert(!posInfInterval.isBefore(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(!posInfInterval.isBefore(cdate));
+    assert(!cPosInfInterval.isBefore(cdate));
+    assert(!iPosInfInterval.isBefore(cdate));
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24)));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5)));
+}
+
+//Test PosInfInterval's isBefore(Interval).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.isBefore(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!posInfInterval.isBefore(posInfInterval));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isBefore(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isBefore(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isBefore(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isBefore(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isBefore(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isBefore(posInfInterval));
+
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.isBefore(interval));
+    assert(!posInfInterval.isBefore(cInterval));
+    assert(!posInfInterval.isBefore(iInterval));
+    assert(!posInfInterval.isBefore(posInfInterval));
+    assert(!posInfInterval.isBefore(cPosInfInterval));
+    assert(!posInfInterval.isBefore(iPosInfInterval));
+    assert(!posInfInterval.isBefore(negInfInterval));
+    assert(!posInfInterval.isBefore(cNegInfInterval));
+    assert(!posInfInterval.isBefore(iNegInfInterval));
+    assert(!cPosInfInterval.isBefore(interval));
+    assert(!cPosInfInterval.isBefore(cInterval));
+    assert(!cPosInfInterval.isBefore(iInterval));
+    assert(!cPosInfInterval.isBefore(posInfInterval));
+    assert(!cPosInfInterval.isBefore(cPosInfInterval));
+    assert(!cPosInfInterval.isBefore(iPosInfInterval));
+    assert(!cPosInfInterval.isBefore(negInfInterval));
+    assert(!cPosInfInterval.isBefore(cNegInfInterval));
+    assert(!cPosInfInterval.isBefore(iNegInfInterval));
+    assert(!iPosInfInterval.isBefore(interval));
+    assert(!iPosInfInterval.isBefore(cInterval));
+    assert(!iPosInfInterval.isBefore(iInterval));
+    assert(!iPosInfInterval.isBefore(posInfInterval));
+    assert(!iPosInfInterval.isBefore(cPosInfInterval));
+    assert(!iPosInfInterval.isBefore(iPosInfInterval));
+    assert(!iPosInfInterval.isBefore(negInfInterval));
+    assert(!iPosInfInterval.isBefore(cNegInfInterval));
+    assert(!iPosInfInterval.isBefore(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(1992, 5, 4))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(2013, 3, 7))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
+}
+
+//Test PosInfInterval's isAfter(time point).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    assert(posInfInterval.isAfter(Date(2009, 7, 3)));
+    assert(posInfInterval.isAfter(Date(2010, 7, 3)));
+    assert(!posInfInterval.isAfter(Date(2010, 7, 4)));
+    assert(!posInfInterval.isAfter(Date(2010, 7, 5)));
+    assert(!posInfInterval.isAfter(Date(2011, 7, 1)));
+    assert(!posInfInterval.isAfter(Date(2012, 1, 6)));
+    assert(!posInfInterval.isAfter(Date(2012, 1, 7)));
+    assert(!posInfInterval.isAfter(Date(2012, 1, 8)));
+    assert(!posInfInterval.isAfter(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(!posInfInterval.isAfter(cdate));
+    assert(!cPosInfInterval.isAfter(cdate));
+    assert(!iPosInfInterval.isAfter(cdate));
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24)));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5)));
+}
+
+//Test PosInfInterval's isAfter(Interval).
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.isAfter(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!posInfInterval.isAfter(posInfInterval));
+    assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAfter(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAfter(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAfter(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAfter(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAfter(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAfter(posInfInterval));
+
+    assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.isAfter(interval));
+    assert(!posInfInterval.isAfter(cInterval));
+    assert(!posInfInterval.isAfter(iInterval));
+    assert(!posInfInterval.isAfter(posInfInterval));
+    assert(!posInfInterval.isAfter(cPosInfInterval));
+    assert(!posInfInterval.isAfter(iPosInfInterval));
+    assert(!posInfInterval.isAfter(negInfInterval));
+    assert(!posInfInterval.isAfter(cNegInfInterval));
+    assert(!posInfInterval.isAfter(iNegInfInterval));
+    assert(!cPosInfInterval.isAfter(interval));
+    assert(!cPosInfInterval.isAfter(cInterval));
+    assert(!cPosInfInterval.isAfter(iInterval));
+    assert(!cPosInfInterval.isAfter(posInfInterval));
+    assert(!cPosInfInterval.isAfter(cPosInfInterval));
+    assert(!cPosInfInterval.isAfter(iPosInfInterval));
+    assert(!cPosInfInterval.isAfter(negInfInterval));
+    assert(!cPosInfInterval.isAfter(cNegInfInterval));
+    assert(!cPosInfInterval.isAfter(iNegInfInterval));
+    assert(!iPosInfInterval.isAfter(interval));
+    assert(!iPosInfInterval.isAfter(cInterval));
+    assert(!iPosInfInterval.isAfter(iInterval));
+    assert(!iPosInfInterval.isAfter(posInfInterval));
+    assert(!iPosInfInterval.isAfter(cPosInfInterval));
+    assert(!iPosInfInterval.isAfter(iPosInfInterval));
+    assert(!iPosInfInterval.isAfter(negInfInterval));
+    assert(!iPosInfInterval.isAfter(cNegInfInterval));
+    assert(!iPosInfInterval.isAfter(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1990, 1, 7))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(1996, 1, 2))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(2000, 7, 1))));
+}
+
+//Test PosInfInterval's intersects().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.intersects(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(posInfInterval.intersects(posInfInterval));
+    assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(PosInfInterval!Date(Date(2010, 7, 3)).intersects(posInfInterval));
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).intersects(posInfInterval));
+    assert(PosInfInterval!Date(Date(2010, 7, 5)).intersects(posInfInterval));
+    assert(PosInfInterval!Date(Date(2012, 1, 6)).intersects(posInfInterval));
+    assert(PosInfInterval!Date(Date(2012, 1, 7)).intersects(posInfInterval));
+    assert(PosInfInterval!Date(Date(2012, 1, 8)).intersects(posInfInterval));
+
+    assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(posInfInterval.intersects(interval));
+    assert(posInfInterval.intersects(cInterval));
+    assert(posInfInterval.intersects(iInterval));
+    assert(posInfInterval.intersects(posInfInterval));
+    assert(posInfInterval.intersects(cPosInfInterval));
+    assert(posInfInterval.intersects(iPosInfInterval));
+    assert(posInfInterval.intersects(negInfInterval));
+    assert(posInfInterval.intersects(cNegInfInterval));
+    assert(posInfInterval.intersects(iNegInfInterval));
+    assert(cPosInfInterval.intersects(interval));
+    assert(cPosInfInterval.intersects(cInterval));
+    assert(cPosInfInterval.intersects(iInterval));
+    assert(cPosInfInterval.intersects(posInfInterval));
+    assert(cPosInfInterval.intersects(cPosInfInterval));
+    assert(cPosInfInterval.intersects(iPosInfInterval));
+    assert(cPosInfInterval.intersects(negInfInterval));
+    assert(cPosInfInterval.intersects(cNegInfInterval));
+    assert(cPosInfInterval.intersects(iNegInfInterval));
+    assert(iPosInfInterval.intersects(interval));
+    assert(iPosInfInterval.intersects(cInterval));
+    assert(iPosInfInterval.intersects(iInterval));
+    assert(iPosInfInterval.intersects(posInfInterval));
+    assert(iPosInfInterval.intersects(cPosInfInterval));
+    assert(iPosInfInterval.intersects(iPosInfInterval));
+    assert(iPosInfInterval.intersects(negInfInterval));
+    assert(iPosInfInterval.intersects(cNegInfInterval));
+    assert(iPosInfInterval.intersects(iNegInfInterval));
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1990, 1, 7))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(1996, 1, 2))));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(2000, 7, 1))));
+}
+
+//Test PosInfInterval's intersection().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(I, J)(in I interval1, in J interval2)
+    {
+        interval1.intersection(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 3))));
+    assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 4))));
+
+    assert(posInfInterval.intersection(posInfInterval) ==
+                posInfInterval);
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2013, 7, 3)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)));
+    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
+                Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)));
+
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 5)));
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2012, 1, 6)));
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2012, 1, 7)));
+    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2012, 1, 8)));
+
+    assert(PosInfInterval!Date(Date(2010, 7, 3)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2010, 7, 5)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 5)));
+    assert(PosInfInterval!Date(Date(2012, 1, 6)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2012, 1, 6)));
+    assert(PosInfInterval!Date(Date(2012, 1, 7)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2012, 1, 7)));
+    assert(PosInfInterval!Date(Date(2012, 1, 8)).intersection(posInfInterval) ==
+                PosInfInterval!Date(Date(2012, 1, 8)));
+
+    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
+    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6)));
+    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
+    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.intersection(interval).empty);
+    assert(!posInfInterval.intersection(cInterval).empty);
+    assert(!posInfInterval.intersection(iInterval).empty);
+    assert(!posInfInterval.intersection(posInfInterval).empty);
+    assert(!posInfInterval.intersection(cPosInfInterval).empty);
+    assert(!posInfInterval.intersection(iPosInfInterval).empty);
+    assert(!posInfInterval.intersection(negInfInterval).empty);
+    assert(!posInfInterval.intersection(cNegInfInterval).empty);
+    assert(!posInfInterval.intersection(iNegInfInterval).empty);
+    assert(!cPosInfInterval.intersection(interval).empty);
+    assert(!cPosInfInterval.intersection(cInterval).empty);
+    assert(!cPosInfInterval.intersection(iInterval).empty);
+    assert(!cPosInfInterval.intersection(posInfInterval).empty);
+    assert(!cPosInfInterval.intersection(cPosInfInterval).empty);
+    assert(!cPosInfInterval.intersection(iPosInfInterval).empty);
+    assert(!cPosInfInterval.intersection(negInfInterval).empty);
+    assert(!cPosInfInterval.intersection(cNegInfInterval).empty);
+    assert(!cPosInfInterval.intersection(iNegInfInterval).empty);
+    assert(!iPosInfInterval.intersection(interval).empty);
+    assert(!iPosInfInterval.intersection(cInterval).empty);
+    assert(!iPosInfInterval.intersection(iInterval).empty);
+    assert(!iPosInfInterval.intersection(posInfInterval).empty);
+    assert(!iPosInfInterval.intersection(cPosInfInterval).empty);
+    assert(!iPosInfInterval.intersection(iPosInfInterval).empty);
+    assert(!iPosInfInterval.intersection(negInfInterval).empty);
+    assert(!iPosInfInterval.intersection(cNegInfInterval).empty);
+    assert(!iPosInfInterval.intersection(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1999, 1 , 12)));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12)));
+}
+
+//Test PosInfInterval's isAdjacent().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.isAdjacent(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!posInfInterval.isAdjacent(posInfInterval));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAdjacent(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAdjacent(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAdjacent(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAdjacent(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAdjacent(posInfInterval));
+    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAdjacent(posInfInterval));
+
+    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.isAdjacent(interval));
+    assert(!posInfInterval.isAdjacent(cInterval));
+    assert(!posInfInterval.isAdjacent(iInterval));
+    assert(!posInfInterval.isAdjacent(posInfInterval));
+    assert(!posInfInterval.isAdjacent(cPosInfInterval));
+    assert(!posInfInterval.isAdjacent(iPosInfInterval));
+    assert(!posInfInterval.isAdjacent(negInfInterval));
+    assert(!posInfInterval.isAdjacent(cNegInfInterval));
+    assert(!posInfInterval.isAdjacent(iNegInfInterval));
+    assert(!cPosInfInterval.isAdjacent(interval));
+    assert(!cPosInfInterval.isAdjacent(cInterval));
+    assert(!cPosInfInterval.isAdjacent(iInterval));
+    assert(!cPosInfInterval.isAdjacent(posInfInterval));
+    assert(!cPosInfInterval.isAdjacent(cPosInfInterval));
+    assert(!cPosInfInterval.isAdjacent(iPosInfInterval));
+    assert(!cPosInfInterval.isAdjacent(negInfInterval));
+    assert(!cPosInfInterval.isAdjacent(cNegInfInterval));
+    assert(!cPosInfInterval.isAdjacent(iNegInfInterval));
+    assert(!iPosInfInterval.isAdjacent(interval));
+    assert(!iPosInfInterval.isAdjacent(cInterval));
+    assert(!iPosInfInterval.isAdjacent(iInterval));
+    assert(!iPosInfInterval.isAdjacent(posInfInterval));
+    assert(!iPosInfInterval.isAdjacent(cPosInfInterval));
+    assert(!iPosInfInterval.isAdjacent(iPosInfInterval));
+    assert(!iPosInfInterval.isAdjacent(negInfInterval));
+    assert(!iPosInfInterval.isAdjacent(cNegInfInterval));
+    assert(!iPosInfInterval.isAdjacent(iNegInfInterval));
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
+    assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1990, 1, 7))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1996, 1, 2))));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2))));
+    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(2000, 7, 1))));
+}
+
+//Test PosInfInterval's merge().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.merge(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+
+    assert(posInfInterval.merge(posInfInterval) ==
+                posInfInterval);
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 1)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(PosInfInterval!Date(Date(2010, 7, 3)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2010, 7, 5)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 6)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 7)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 8)).merge(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3)))));
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4)))));
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5)))));
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6)))));
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7)))));
+    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8)))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.merge(interval).empty);
+    assert(!posInfInterval.merge(cInterval).empty);
+    assert(!posInfInterval.merge(iInterval).empty);
+    assert(!posInfInterval.merge(posInfInterval).empty);
+    assert(!posInfInterval.merge(cPosInfInterval).empty);
+    assert(!posInfInterval.merge(iPosInfInterval).empty);
+    static assert(!__traits(compiles, posInfInterval.merge(negInfInterval)));
+    static assert(!__traits(compiles, posInfInterval.merge(cNegInfInterval)));
+    static assert(!__traits(compiles, posInfInterval.merge(iNegInfInterval)));
+    assert(!cPosInfInterval.merge(interval).empty);
+    assert(!cPosInfInterval.merge(cInterval).empty);
+    assert(!cPosInfInterval.merge(iInterval).empty);
+    assert(!cPosInfInterval.merge(posInfInterval).empty);
+    assert(!cPosInfInterval.merge(cPosInfInterval).empty);
+    assert(!cPosInfInterval.merge(iPosInfInterval).empty);
+    static assert(!__traits(compiles, cPosInfInterval.merge(negInfInterval)));
+    static assert(!__traits(compiles, cPosInfInterval.merge(cNegInfInterval)));
+    static assert(!__traits(compiles, cPosInfInterval.merge(iNegInfInterval)));
+    assert(!iPosInfInterval.merge(interval).empty);
+    assert(!iPosInfInterval.merge(cInterval).empty);
+    assert(!iPosInfInterval.merge(iInterval).empty);
+    assert(!iPosInfInterval.merge(posInfInterval).empty);
+    assert(!iPosInfInterval.merge(cPosInfInterval).empty);
+    assert(!iPosInfInterval.merge(iPosInfInterval).empty);
+    static assert(!__traits(compiles, iPosInfInterval.merge(negInfInterval)));
+    static assert(!__traits(compiles, iPosInfInterval.merge(cNegInfInterval)));
+    static assert(!__traits(compiles, iPosInfInterval.merge(iNegInfInterval)));
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+}
+
+//Test PosInfInterval's span().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
+    {
+        posInfInterval.span(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(posInfInterval.span(posInfInterval) ==
+                posInfInterval);
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 1)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 1)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8))) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    assert(PosInfInterval!Date(Date(2010, 7, 3)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 3)));
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2010, 7, 5)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 6)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 7)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+    assert(PosInfInterval!Date(Date(2012, 1, 8)).span(posInfInterval) ==
+                PosInfInterval!Date(Date(2010, 7, 4)));
+
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3)))));
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4)))));
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5)))));
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6)))));
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7)))));
+    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8)))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!posInfInterval.span(interval).empty);
+    assert(!posInfInterval.span(cInterval).empty);
+    assert(!posInfInterval.span(iInterval).empty);
+    assert(!posInfInterval.span(posInfInterval).empty);
+    assert(!posInfInterval.span(cPosInfInterval).empty);
+    assert(!posInfInterval.span(iPosInfInterval).empty);
+    static assert(!__traits(compiles, posInfInterval.span(negInfInterval)));
+    static assert(!__traits(compiles, posInfInterval.span(cNegInfInterval)));
+    static assert(!__traits(compiles, posInfInterval.span(iNegInfInterval)));
+    assert(!cPosInfInterval.span(interval).empty);
+    assert(!cPosInfInterval.span(cInterval).empty);
+    assert(!cPosInfInterval.span(iInterval).empty);
+    assert(!cPosInfInterval.span(posInfInterval).empty);
+    assert(!cPosInfInterval.span(cPosInfInterval).empty);
+    assert(!cPosInfInterval.span(iPosInfInterval).empty);
+    static assert(!__traits(compiles, cPosInfInterval.span(negInfInterval)));
+    static assert(!__traits(compiles, cPosInfInterval.span(cNegInfInterval)));
+    static assert(!__traits(compiles, cPosInfInterval.span(iNegInfInterval)));
+    assert(!iPosInfInterval.span(interval).empty);
+    assert(!iPosInfInterval.span(cInterval).empty);
+    assert(!iPosInfInterval.span(iInterval).empty);
+    assert(!iPosInfInterval.span(posInfInterval).empty);
+    assert(!iPosInfInterval.span(cPosInfInterval).empty);
+    assert(!iPosInfInterval.span(iPosInfInterval).empty);
+    static assert(!__traits(compiles, iPosInfInterval.span(negInfInterval)));
+    static assert(!__traits(compiles, iPosInfInterval.span(cNegInfInterval)));
+    static assert(!__traits(compiles, iPosInfInterval.span(iNegInfInterval)));
+
+    //Verify Examples.
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) == PosInfInterval!Date(Date(500, 8, 9)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
+    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1996, 1 , 2)));
+}
+
+//Test PosInfInterval's shift().
+unittest
+{
+    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.shift(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2010, 7, 26)));
+    testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2010, 6, 12)));
+
+    const cInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+    interval1.shift(dur!"days"(50));
+    assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));
+
+    interval2.shift(dur!"days"(-50));
+    assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
+}
+
+//Test PosInfInterval's shift(int, int, AllowDayOverflow).
+unittest
+{
+    {
+        auto interval = PosInfInterval!Date(Date(2010, 7, 4));
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
+        {
+            interval.shift(years, months, allow);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2015, 7, 4)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4)));
+
+        auto interval2 = PosInfInterval!Date(Date(2000, 1, 29));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28)));
+    }
+
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    static assert(!__traits(compiles, cPosInfInterval.shift(1)));
+    static assert(!__traits(compiles, iPosInfInterval.shift(1)));
+
+    //Verify Examples.
+    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+    interval1.shift(2);
+    assert(interval1 == PosInfInterval!Date(Date(1998, 1, 2)));
+
+    interval2.shift(-2);
+    assert(interval2 == PosInfInterval!Date(Date(1994, 1, 2)));
+}
+
+//Test PosInfInterval's expand().
+unittest
+{
+    auto interval = PosInfInterval!Date(Date(2000, 7, 4));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.expand(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2000, 6, 12)));
+    testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2000, 7, 26)));
+
+    const cInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+    interval1.expand(dur!"days"(2));
+    assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31)));
+
+    interval2.expand(dur!"days"(-2));
+    assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4)));
+}
+
+//Test PosInfInterval's expand(int, int, AllowDayOverflow).
+unittest
+{
+    {
+        auto interval = PosInfInterval!Date(Date(2000, 7, 4));
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
+        {
+            interval.expand(years, months, allow);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(1995, 7, 4)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4)));
+
+        auto interval2 = PosInfInterval!Date(Date(2000, 1, 29));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29)));
+    }
+
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    static assert(!__traits(compiles, cPosInfInterval.expand(1)));
+    static assert(!__traits(compiles, iPosInfInterval.expand(1)));
+
+    //Verify Examples.
+    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
+    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));
+
+    interval1.expand(2);
+    assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2)));
+
+    interval2.expand(-2);
+    assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2)));
+}
+
+//Test PosInfInterval's fwdRange().
+unittest
+{
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 9, 19));
+
+    static void testInterval(PosInfInterval!Date posInfInterval)
+    {
+        posInfInterval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront();
+    }
+
+    assertThrown!DateTimeException(testInterval(posInfInterval));
+
+    assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front ==
+                Date(2010, 9, 12));
+
+    assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front ==
+                Date(2010, 9, 17));
+
+    //Verify Examples.
+    auto interval = PosInfInterval!Date(Date(2010, 9, 1));
+    auto func = delegate (in Date date)
+                {
+                    if((date.day & 1) == 0)
+                        return date + dur!"days"(2);
+
+                    return date + dur!"days"(1);
+                };
+    auto range = interval.fwdRange(func);
+
+    assert(range.front == Date(2010, 9, 1)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 2));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 4));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 6));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 8));
+
+    range.popFront();
+    assert(!range.empty);
+
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(!cPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+    assert(!iPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
+}
+
+//Test PosInfInterval's toString().
+unittest
+{
+    assert(PosInfInterval!Date(Date(2010, 7, 4)).toString() == "[2010-Jul-04 - ∞)");
+
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    assert(cPosInfInterval.toString());
+    assert(iPosInfInterval.toString());
+}
+
+
+/++
+    Represents an interval of time which has negative infinity as its starting
+    point.
+
+    Any ranges which iterate over a $(D NegInfInterval) are infinite. So, the
+    main purpose of using $(D NegInfInterval) is to create an infinite range
+    which starts at negative infinity and goes to a fixed end point.
+    Iterate over it in reverse.
+  +/
+struct NegInfInterval(TP)
+{
+public:
+
+    /++
+        Params:
+            end = The time point which ends the interval.
+
+        Example:
+--------------------
+auto interval = PosInfInterval!Date(Date(1996, 1, 2));
+--------------------
+      +/
+    this(in TP end) pure nothrow
+    {
+        _end = cast(TP)end;
+    }
+
+
+    /++
+        Params:
+            rhs = The $(D NegInfInterval) to assign to this one.
+      +/
+    ref NegInfInterval opAssign(const ref NegInfInterval rhs) pure nothrow
+    {
+        _end = cast(TP)rhs._end;
+        return this;
+    }
+
+
+    /++
+        Params:
+            rhs = The $(D NegInfInterval) to assign to this one.
+      +/
+    ref NegInfInterval opAssign(NegInfInterval rhs) pure nothrow
+    {
+        _end = cast(TP)rhs._end;
+        return this;
+    }
+
+
+    /++
+        The end point of the interval. It is excluded from the interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1));
+--------------------
+      +/
+    @property TP end() const pure nothrow
+    {
+        return cast(TP)_end;
+    }
+
+
+    /++
+        The end point of the interval. It is excluded from the interval.
+
+        Params:
+            timePoint = The time point to set end to.
+      +/
+    @property void end(TP timePoint) pure nothrow
+    {
+        _end = timePoint;
+    }
+
+
+    /++
+        Whether the interval's length is 0. Always returns false.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty);
+--------------------
+      +/
+    @property bool empty() const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether the given time point is within this interval.
+
+        Params:
+            timePoint = The time point to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
+assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
+--------------------
+      +/
+    bool contains(TP timePoint) const pure nothrow
+    {
+        return timePoint < _end;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+--------------------
+      +/
+    bool contains(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return interval._end <= _end;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Always returns false because an interval beginning at negative
+        infinity can never contain an interval going to positive infinity.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+--------------------
+      +/
+    bool contains(in PosInfInterval!TP interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether the given interval is completely within this interval.
+
+        Params:
+            interval = The interval to check for inclusion in this interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
+            NegInfInterval!Date(Date(2013, 7, 9))));
+--------------------
+      +/
+    bool contains(in NegInfInterval interval) const pure nothrow
+    {
+        return interval._end <= _end;
+    }
+
+
+    /++
+        Whether this interval is before the given time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is
+                        before it.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
+assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
+--------------------
+      +/
+    bool isBefore(in TP timePoint) const pure nothrow
+    {
+        return timePoint >= _end;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+--------------------
+      +/
+    bool isBefore(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return _end <= interval._begin;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isBefore(in PosInfInterval!TP interval) const pure nothrow
+    {
+        return _end <= interval._begin;
+    }
+
+
+    /++
+        Whether this interval is before the given interval and does not
+        intersect it.
+
+        Always returns false because an interval beginning at negative
+        infinity can never be before another interval beginning at negative
+        infinity.
+
+        Params:
+            interval = The interval to check for against this interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
+            NegInfInterval!Date(Date(2013, 7, 9))));
+--------------------
+      +/
+    bool isBefore(in NegInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given time point.
+
+        Always returns false because an interval beginning at negative infinity
+        can never be after any time point.
+
+        Params:
+            timePoint = The time point to check whether this interval is after
+                        it.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));
+--------------------
+      +/
+    bool isAfter(in TP timePoint) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not
+        intersect it.
+
+        Always returns false (unless the given interval is empty) because an
+        interval beginning at negative infinity can never be after any other
+        interval.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+--------------------
+      +/
+    bool isAfter(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Always returns false because an interval beginning at negative infinity
+        can never be after any other interval.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isAfter(in PosInfInterval!TP interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether this interval is after the given interval and does not intersect
+        it.
+
+        Always returns false because an interval beginning at negative infinity
+        can never be after any other interval.
+
+        Params:
+            interval = The interval to check against this interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
+            NegInfInterval!Date(Date(2013, 7, 9))));
+--------------------
+      +/
+    bool isAfter(in NegInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+--------------------
+      +/
+    bool intersects(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return interval._begin < _end;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Params:
+            interval = The interval to check for intersection with this
+                       interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool intersects(in PosInfInterval!TP interval) const pure nothrow
+    {
+        return interval._begin < _end;
+    }
+
+
+    /++
+        Whether the given interval overlaps this interval.
+
+        Always returns true because two intervals beginning at negative infinity
+        always overlap.
+
+        Params:
+            interval = The interval to check for intersection with this interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
+            NegInfInterval!Date(Date(2013, 7, 9))));
+--------------------
+      +/
+    bool intersects(in NegInfInterval!TP interval) const pure nothrow
+    {
+        return true;
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect or if
+            the given interval is empty.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       Interval!Date(Date(1990, 7 , 6), Date(2000, 8, 2)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
+       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+--------------------
+      +/
+    Interval!TP intersection(in Interval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        auto end = _end < interval._end ? _end : interval._end;
+
+        return Interval!TP(interval._begin, end);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            PosInfInterval!Date(Date(1990, 7, 6))) ==
+       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            PosInfInterval!Date(Date(1999, 1, 12))) ==
+       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+--------------------
+      +/
+    Interval!TP intersection(in PosInfInterval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));
+
+        return Interval!TP(interval._begin, _end);
+    }
+
+
+    /++
+        Returns the intersection of two intervals
+
+        Params:
+            interval = The interval to intersect with this interval.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            NegInfInterval!Date(Date(1999, 7, 6))) ==
+       NegInfInterval!Date(Date(1999, 7 , 6)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+--------------------
+      +/
+    NegInfInterval intersection(in NegInfInterval interval) const nothrow
+    {
+        return NegInfInterval(_end < interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+--------------------
+      +/
+    bool isAdjacent(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return interval._begin == _end;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            PosInfInterval!Date(Date(1999, 5, 4))));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            PosInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isAdjacent(in PosInfInterval!TP interval) const pure nothrow
+    {
+        return interval._begin == _end;
+    }
+
+
+    /++
+        Whether the given interval is adjacent to this interval.
+
+        Always returns false because two intervals beginning at negative
+        infinity can never be adjacent to one another.
+
+        Params:
+            interval = The interval to check whether its adjecent to this
+                       interval.
+
+        Example:
+--------------------
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            NegInfInterval!Date(Date(1996, 5, 4))));
+
+assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
+            NegInfInterval!Date(Date(2012, 3, 1))));
+--------------------
+      +/
+    bool isAdjacent(in NegInfInterval interval) const pure nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Throws:
+            $(LREF DateTimeException) if the two intervals do not intersect and are
+            not adjacent or if the given interval is empty.
+
+        Note:
+            There is no overload for $(D merge) which takes a
+            $(D PosInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
+            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
+       NegInfInterval!Date(Date(2015, 9 , 2)));
+--------------------
+      +/
+    NegInfInterval merge(in Interval!TP interval) const
+    {
+        import std.format : format;
+
+        enforce(this.isAdjacent(interval) || this.intersects(interval),
+                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));
+
+        return NegInfInterval(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Returns the union of two intervals
+
+        Params:
+            interval = The interval to merge with this interval.
+
+        Note:
+            There is no overload for $(D merge) which takes a
+            $(D PosInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
+            NegInfInterval!Date(Date(1999, 7, 6))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       NegInfInterval!Date(Date(2013, 1 , 12)));
+--------------------
+      +/
+    NegInfInterval merge(in NegInfInterval interval) const pure nothrow
+    {
+        return NegInfInterval(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this
+                       interval.
+
+        Throws:
+            $(LREF DateTimeException) if the given interval is empty.
+
+        Note:
+            There is no overload for $(D span) which takes a
+            $(D PosInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
+            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
+            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
+       NegInfInterval!Date(Date(2015, 9 , 2)));
+
+assert(NegInfInterval!Date(Date(1600, 1, 7)).span(
+            Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) ==
+       NegInfInterval!Date(Date(2017, 7 , 1)));
+--------------------
+      +/
+    NegInfInterval span(in Interval!TP interval) const pure
+    {
+        interval._enforceNotEmpty();
+
+        return NegInfInterval(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Returns an interval that covers from the earliest time point of two
+        intervals up to (but not including) the latest time point of two
+        intervals.
+
+        Params:
+            interval = The interval to create a span together with this
+                       interval.
+
+        Note:
+            There is no overload for $(D span) which takes a
+            $(D PosInfInterval), because an interval
+            going from negative infinity to positive infinity
+            is not possible.
+
+        Example:
+--------------------
+assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
+            NegInfInterval!Date(Date(1999, 7, 6))) ==
+       NegInfInterval!Date(Date(2012, 3 , 1)));
+
+assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
+            NegInfInterval!Date(Date(2013, 1, 12))) ==
+       NegInfInterval!Date(Date(2013, 1 , 12)));
+--------------------
+      +/
+    NegInfInterval span(in NegInfInterval interval) const pure nothrow
+    {
+        return NegInfInterval(_end > interval._end ? _end : interval._end);
+    }
+
+
+    /++
+        Shifts the $(D end) of this interval forward or backwards in time by the
+        given duration (a positive duration shifts the interval forward; a
+        negative duration shifts it backward). Effectively, it does
+        $(D end += duration).
+
+        Params:
+            duration = The duration to shift the interval by.
+
+        Example:
+--------------------
+auto interval1 = NegInfInterval!Date(Date(2012, 4, 5));
+auto interval2 = NegInfInterval!Date(Date(2012, 4, 5));
+
+interval1.shift(dur!"days"(50));
+assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25)));
+
+interval2.shift(dur!"days"(-50));
+assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15)));
+--------------------
+      +/
+    void shift(D)(D duration) pure nothrow
+        if(__traits(compiles, end + duration))
+    {
+        _end += duration;
+    }
+
+
+    static if(__traits(compiles, end.add!"months"(1)) &&
+              __traits(compiles, end.add!"years"(1)))
+    {
+        /++
+            Shifts the $(D end) of this interval forward or backwards in time by
+            the given number of years and/or months (a positive number of years
+            and months shifts the interval forward; a negative number shifts it
+            backward). It adds the years the given years and months to end. It
+            effectively calls $(D add!"years"()) and then $(D add!"months"())
+            on end with the given number of years and months.
+
+            Params:
+                years         = The number of years to shift the interval by.
+                months        = The number of months to shift the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D end), causing its month to increment.
+
+            Throws:
+                $(LREF DateTimeException) if empty is true or if the resulting
+                interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+interval1.shift(2);
+assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));
+
+interval2.shift(-2);
+assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
+--------------------
+          +/
+        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
+            if(isIntegral!T)
+        {
+            auto end = _end;
+
+            end.add!"years"(years, allowOverflow);
+            end.add!"months"(months, allowOverflow);
+
+            _end = end;
+        }
+    }
+
+
+    /++
+        Expands the interval forwards in time. Effectively, it does
+        $(D end += duration).
+
+        Params:
+            duration = The duration to expand the interval by.
+
+        Example:
+--------------------
+auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+interval1.expand(dur!"days"(2));
+assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3)));
+
+interval2.expand(dur!"days"(-2));
+assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28)));
+--------------------
+      +/
+    void expand(D)(D duration) pure nothrow
+        if(__traits(compiles, end + duration))
+    {
+        _end += duration;
+    }
+
+
+    static if(__traits(compiles, end.add!"months"(1)) &&
+              __traits(compiles, end.add!"years"(1)))
+    {
+        /++
+            Expands the interval forwards and/or backwards in time. Effectively,
+            it adds the given number of months/years to end.
+
+            Params:
+                years         = The number of years to expand the interval by.
+                months        = The number of months to expand the interval by.
+                allowOverflow = Whether the days should be allowed to overflow
+                                on $(D end), causing their month to increment.
+
+            Throws:
+                $(LREF DateTimeException) if empty is true or if the resulting
+                interval would be invalid.
+
+            Example:
+--------------------
+auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+interval1.expand(2);
+assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));
+
+interval2.expand(-2);
+assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
+--------------------
+          +/
+        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
+            if(isIntegral!T)
+        {
+            auto end = _end;
+
+            end.add!"years"(years, allowOverflow);
+            end.add!"months"(months, allowOverflow);
+
+            _end = end;
+
+            return;
+        }
+    }
+
+
+    /++
+        Returns a range which iterates backwards over the interval, starting
+        at $(D end), using $(D_PARAM func) to generate each successive time
+        point.
+
+        The range's $(D front) is the interval's $(D end). $(D_PARAM func) is
+        used to generate the next $(D front) when $(D popFront) is called. If
+        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
+        before the range is returned (so that $(D front) is a time point which
+        $(D_PARAM func) would generate).
+
+        If $(D_PARAM func) ever generates a time point greater than or equal to
+        the current $(D front) of the range, then a $(LREF DateTimeException) will
+        be thrown.
+
+        There are helper functions in this module which generate common
+        delegates to pass to $(D bwdRange). Their documentation starts with
+        "Range-generating function," to make them easily searchable.
+
+        Params:
+            func     = The function used to generate the time points of the
+                       range over the interval.
+            popFirst = Whether $(D popFront) should be called on the range
+                       before returning it.
+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+
+        Warning:
+            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
+            would be a function pointer to a pure function, but forcing
+            $(D_PARAM func) to be pure is far too restrictive to be useful, and
+            in order to have the ease of use of having functions which generate
+            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
+            delegate.
+
+            If $(D_PARAM func) retains state which changes as it is called, then
+            some algorithms will not work correctly, because the range's
+            $(D save) will have failed to have really saved the range's state.
+            To avoid such bugs, don't pass a delegate which is
+            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
+            same time point with two different calls, it must return the same
+            result both times.
+
+            Of course, none of the functions in this module have this problem,
+            so it's only relevant for custom delegates.
+
+        Example:
+--------------------
+auto interval = NegInfInterval!Date(Date(2010, 9, 9));
+auto func = delegate (in Date date) //For iterating over even-numbered days.
+            {
+                if((date.day & 1) == 0)
+                    return date - dur!"days"(2);
+
+                return date - dur!"days"(1);
+            };
+auto range = interval.bwdRange(func);
+
+assert(range.front == Date(2010, 9, 9)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
+
+range.popFront();
+assert(range.front == Date(2010, 9, 8));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 6));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 4));
+
+range.popFront();
+assert(range.front == Date(2010, 9, 2));
+
+range.popFront();
+assert(!range.empty);
+--------------------
+      +/
+    NegInfIntervalRange!(TP) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
+    {
+        auto range = NegInfIntervalRange!(TP)(this, func);
+
+        if(popFirst == PopFirst.yes)
+            range.popFront();
+
+        return range;
+    }
+
+
+    /+
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString()
+    {
+        return _toStringImpl();
+    }
+
+
+    /++
+        Converts this interval to a string.
+      +/
+    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
+    //have versions of toString() with extra modifiers, so we define one version
+    //with modifiers and one without.
+    string toString() const nothrow
+    {
+        return _toStringImpl();
+    }
+
+private:
+
+    /+
+        Since we have two versions of toString(), we have _toStringImpl()
+        so that they can share implementations.
+      +/
+    string _toStringImpl() const nothrow
+    {
+        import std.format : format;
+        try
+            return format("[-∞ - %s)", _end);
+        catch(Exception e)
+            assert(0, "format() threw.");
+    }
+
+
+    TP _end;
+}
+
+//Test NegInfInterval's constructor.
+unittest
+{
+    NegInfInterval!Date(Date.init);
+    NegInfInterval!TimeOfDay(TimeOfDay.init);
+    NegInfInterval!DateTime(DateTime.init);
+    NegInfInterval!SysTime(SysTime(0));
+}
+
+//Test NegInfInterval's end.
+unittest
+{
+    assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1));
+    assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1));
+    assert(NegInfInterval!Date(Date(1998, 1, 1)).end == Date(1998, 1, 1));
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(cNegInfInterval.end != Date.init);
+    assert(iNegInfInterval.end != Date.init);
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1));
+}
+
+//Test NegInfInterval's empty.
+unittest
+{
+    assert(!NegInfInterval!Date(Date(2010, 1, 1)).empty);
+    assert(!NegInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty);
+    assert(!NegInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty);
+    assert(!NegInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty);
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!cNegInfInterval.empty);
+    assert(!iNegInfInterval.empty);
+
+    //Verify Examples.
+    assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty);
+}
+
+//Test NegInfInterval's contains(time point).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    assert(negInfInterval.contains(Date(2009, 7, 4)));
+    assert(negInfInterval.contains(Date(2010, 7, 3)));
+    assert(negInfInterval.contains(Date(2010, 7, 4)));
+    assert(negInfInterval.contains(Date(2010, 7, 5)));
+    assert(negInfInterval.contains(Date(2011, 7, 1)));
+    assert(negInfInterval.contains(Date(2012, 1, 6)));
+    assert(!negInfInterval.contains(Date(2012, 1, 7)));
+    assert(!negInfInterval.contains(Date(2012, 1, 8)));
+    assert(!negInfInterval.contains(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(negInfInterval.contains(cdate));
+    assert(cNegInfInterval.contains(cdate));
+    assert(iNegInfInterval.contains(cdate));
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
+}
+
+//Test NegInfInterval's contains(Interval).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
+    {
+        negInfInterval.contains(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(negInfInterval.contains(negInfInterval));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!NegInfInterval!Date(Date(2010, 7, 3)).contains(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 4)).contains(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 5)).contains(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 6)).contains(negInfInterval));
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).contains(negInfInterval));
+    assert(NegInfInterval!Date(Date(2012, 1, 8)).contains(negInfInterval));
+
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(negInfInterval.contains(interval));
+    assert(negInfInterval.contains(cInterval));
+    assert(negInfInterval.contains(iInterval));
+    assert(!negInfInterval.contains(posInfInterval));
+    assert(!negInfInterval.contains(cPosInfInterval));
+    assert(!negInfInterval.contains(iPosInfInterval));
+    assert(negInfInterval.contains(negInfInterval));
+    assert(negInfInterval.contains(cNegInfInterval));
+    assert(negInfInterval.contains(iNegInfInterval));
+    assert(cNegInfInterval.contains(interval));
+    assert(cNegInfInterval.contains(cInterval));
+    assert(cNegInfInterval.contains(iInterval));
+    assert(!cNegInfInterval.contains(posInfInterval));
+    assert(!cNegInfInterval.contains(cPosInfInterval));
+    assert(!cNegInfInterval.contains(iPosInfInterval));
+    assert(cNegInfInterval.contains(negInfInterval));
+    assert(cNegInfInterval.contains(cNegInfInterval));
+    assert(cNegInfInterval.contains(iNegInfInterval));
+    assert(iNegInfInterval.contains(interval));
+    assert(iNegInfInterval.contains(cInterval));
+    assert(iNegInfInterval.contains(iInterval));
+    assert(!iNegInfInterval.contains(posInfInterval));
+    assert(!iNegInfInterval.contains(cPosInfInterval));
+    assert(!iNegInfInterval.contains(iPosInfInterval));
+    assert(iNegInfInterval.contains(negInfInterval));
+    assert(iNegInfInterval.contains(cNegInfInterval));
+    assert(iNegInfInterval.contains(iNegInfInterval));
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4))));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(2013, 7, 9))));
+}
+
+//Test NegInfInterval's isBefore(time point).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    assert(!negInfInterval.isBefore(Date(2009, 7, 4)));
+    assert(!negInfInterval.isBefore(Date(2010, 7, 3)));
+    assert(!negInfInterval.isBefore(Date(2010, 7, 4)));
+    assert(!negInfInterval.isBefore(Date(2010, 7, 5)));
+    assert(!negInfInterval.isBefore(Date(2011, 7, 1)));
+    assert(!negInfInterval.isBefore(Date(2012, 1, 6)));
+    assert(negInfInterval.isBefore(Date(2012, 1, 7)));
+    assert(negInfInterval.isBefore(Date(2012, 1, 8)));
+    assert(negInfInterval.isBefore(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.isBefore(cdate));
+    assert(!cNegInfInterval.isBefore(cdate));
+    assert(!iNegInfInterval.isBefore(cdate));
+
+    //Verify Examples.
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
+}
+
+//Test NegInfInterval's isBefore(Interval).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
+    {
+        negInfInterval.isBefore(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!negInfInterval.isBefore(negInfInterval));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isBefore(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isBefore(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isBefore(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isBefore(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isBefore(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isBefore(negInfInterval));
+
+    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.isBefore(interval));
+    assert(!negInfInterval.isBefore(cInterval));
+    assert(!negInfInterval.isBefore(iInterval));
+    assert(!negInfInterval.isBefore(posInfInterval));
+    assert(!negInfInterval.isBefore(cPosInfInterval));
+    assert(!negInfInterval.isBefore(iPosInfInterval));
+    assert(!negInfInterval.isBefore(negInfInterval));
+    assert(!negInfInterval.isBefore(cNegInfInterval));
+    assert(!negInfInterval.isBefore(iNegInfInterval));
+    assert(!cNegInfInterval.isBefore(interval));
+    assert(!cNegInfInterval.isBefore(cInterval));
+    assert(!cNegInfInterval.isBefore(iInterval));
+    assert(!cNegInfInterval.isBefore(posInfInterval));
+    assert(!cNegInfInterval.isBefore(cPosInfInterval));
+    assert(!cNegInfInterval.isBefore(iPosInfInterval));
+    assert(!cNegInfInterval.isBefore(negInfInterval));
+    assert(!cNegInfInterval.isBefore(cNegInfInterval));
+    assert(!cNegInfInterval.isBefore(iNegInfInterval));
+    assert(!iNegInfInterval.isBefore(interval));
+    assert(!iNegInfInterval.isBefore(cInterval));
+    assert(!iNegInfInterval.isBefore(iInterval));
+    assert(!iNegInfInterval.isBefore(posInfInterval));
+    assert(!iNegInfInterval.isBefore(cPosInfInterval));
+    assert(!iNegInfInterval.isBefore(iPosInfInterval));
+    assert(!iNegInfInterval.isBefore(negInfInterval));
+    assert(!iNegInfInterval.isBefore(cNegInfInterval));
+    assert(!iNegInfInterval.isBefore(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(2013, 7, 9))));
+}
+
+//Test NegInfInterval's isAfter(time point).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    assert(!negInfInterval.isAfter(Date(2009, 7, 4)));
+    assert(!negInfInterval.isAfter(Date(2010, 7, 3)));
+    assert(!negInfInterval.isAfter(Date(2010, 7, 4)));
+    assert(!negInfInterval.isAfter(Date(2010, 7, 5)));
+    assert(!negInfInterval.isAfter(Date(2011, 7, 1)));
+    assert(!negInfInterval.isAfter(Date(2012, 1, 6)));
+    assert(!negInfInterval.isAfter(Date(2012, 1, 7)));
+    assert(!negInfInterval.isAfter(Date(2012, 1, 8)));
+    assert(!negInfInterval.isAfter(Date(2013, 1, 7)));
+
+    const cdate = Date(2010, 7, 6);
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.isAfter(cdate));
+    assert(!cNegInfInterval.isAfter(cdate));
+    assert(!iNegInfInterval.isAfter(cdate));
+}
+
+//Test NegInfInterval's isAfter(Interval).
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
+    {
+        negInfInterval.isAfter(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!negInfInterval.isAfter(negInfInterval));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAfter(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAfter(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAfter(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAfter(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAfter(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAfter(negInfInterval));
+
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.isAfter(interval));
+    assert(!negInfInterval.isAfter(cInterval));
+    assert(!negInfInterval.isAfter(iInterval));
+    assert(!negInfInterval.isAfter(posInfInterval));
+    assert(!negInfInterval.isAfter(cPosInfInterval));
+    assert(!negInfInterval.isAfter(iPosInfInterval));
+    assert(!negInfInterval.isAfter(negInfInterval));
+    assert(!negInfInterval.isAfter(cNegInfInterval));
+    assert(!negInfInterval.isAfter(iNegInfInterval));
+    assert(!cNegInfInterval.isAfter(interval));
+    assert(!cNegInfInterval.isAfter(cInterval));
+    assert(!cNegInfInterval.isAfter(iInterval));
+    assert(!cNegInfInterval.isAfter(posInfInterval));
+    assert(!cNegInfInterval.isAfter(cPosInfInterval));
+    assert(!cNegInfInterval.isAfter(iPosInfInterval));
+    assert(!cNegInfInterval.isAfter(negInfInterval));
+    assert(!cNegInfInterval.isAfter(cNegInfInterval));
+    assert(!cNegInfInterval.isAfter(iNegInfInterval));
+    assert(!iNegInfInterval.isAfter(interval));
+    assert(!iNegInfInterval.isAfter(cInterval));
+    assert(!iNegInfInterval.isAfter(iInterval));
+    assert(!iNegInfInterval.isAfter(posInfInterval));
+    assert(!iNegInfInterval.isAfter(cPosInfInterval));
+    assert(!iNegInfInterval.isAfter(iPosInfInterval));
+    assert(!iNegInfInterval.isAfter(negInfInterval));
+    assert(!iNegInfInterval.isAfter(cNegInfInterval));
+    assert(!iNegInfInterval.isAfter(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(2013, 7, 9))));
+}
+
+//Test NegInfInterval's intersects().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
+    {
+        negInfInterval.intersects(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(negInfInterval.intersects(negInfInterval));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(NegInfInterval!Date(Date(2010, 7, 3)).intersects(negInfInterval));
+    assert(NegInfInterval!Date(Date(2010, 7, 4)).intersects(negInfInterval));
+    assert(NegInfInterval!Date(Date(2010, 7, 5)).intersects(negInfInterval));
+    assert(NegInfInterval!Date(Date(2012, 1, 6)).intersects(negInfInterval));
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).intersects(negInfInterval));
+    assert(NegInfInterval!Date(Date(2012, 1, 8)).intersects(negInfInterval));
+
+    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(negInfInterval.intersects(interval));
+    assert(negInfInterval.intersects(cInterval));
+    assert(negInfInterval.intersects(iInterval));
+    assert(negInfInterval.intersects(posInfInterval));
+    assert(negInfInterval.intersects(cPosInfInterval));
+    assert(negInfInterval.intersects(iPosInfInterval));
+    assert(negInfInterval.intersects(negInfInterval));
+    assert(negInfInterval.intersects(cNegInfInterval));
+    assert(negInfInterval.intersects(iNegInfInterval));
+    assert(cNegInfInterval.intersects(interval));
+    assert(cNegInfInterval.intersects(cInterval));
+    assert(cNegInfInterval.intersects(iInterval));
+    assert(cNegInfInterval.intersects(posInfInterval));
+    assert(cNegInfInterval.intersects(cPosInfInterval));
+    assert(cNegInfInterval.intersects(iPosInfInterval));
+    assert(cNegInfInterval.intersects(negInfInterval));
+    assert(cNegInfInterval.intersects(cNegInfInterval));
+    assert(cNegInfInterval.intersects(iNegInfInterval));
+    assert(iNegInfInterval.intersects(interval));
+    assert(iNegInfInterval.intersects(cInterval));
+    assert(iNegInfInterval.intersects(iInterval));
+    assert(iNegInfInterval.intersects(posInfInterval));
+    assert(iNegInfInterval.intersects(cPosInfInterval));
+    assert(iNegInfInterval.intersects(iPosInfInterval));
+    assert(iNegInfInterval.intersects(negInfInterval));
+    assert(iNegInfInterval.intersects(cNegInfInterval));
+    assert(iNegInfInterval.intersects(iNegInfInterval));
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 5, 4))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2013, 7, 9))));
+}
+
+//Test NegInfInterval's intersection().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(I, J)(in I interval1, in J interval2)
+    {
+        interval1.intersection(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 7))));
+    assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(negInfInterval.intersection(negInfInterval) ==
+                negInfInterval);
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
+
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2010, 7, 3)));
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2010, 7, 4)));
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2010, 7, 5)));
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 6)));
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+
+    assert(NegInfInterval!Date(Date(2010, 7, 3)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2010, 7, 3)));
+    assert(NegInfInterval!Date(Date(2010, 7, 4)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2010, 7, 4)));
+    assert(NegInfInterval!Date(Date(2010, 7, 5)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2010, 7, 5)));
+    assert(NegInfInterval!Date(Date(2012, 1, 6)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 6)));
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 8)).intersection(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+
+    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
+                Interval!Date(Date(2010, 7, 3), Date(2012, 1 ,7)));
+    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
+                Interval!Date(Date(2010, 7, 4), Date(2012, 1 ,7)));
+    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
+                Interval!Date(Date(2010, 7, 5), Date(2012, 1 ,7)));
+    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
+                Interval!Date(Date(2012, 1, 6), Date(2012, 1 ,7)));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.intersection(interval).empty);
+    assert(!negInfInterval.intersection(cInterval).empty);
+    assert(!negInfInterval.intersection(iInterval).empty);
+    assert(!negInfInterval.intersection(posInfInterval).empty);
+    assert(!negInfInterval.intersection(cPosInfInterval).empty);
+    assert(!negInfInterval.intersection(iPosInfInterval).empty);
+    assert(!negInfInterval.intersection(negInfInterval).empty);
+    assert(!negInfInterval.intersection(cNegInfInterval).empty);
+    assert(!negInfInterval.intersection(iNegInfInterval).empty);
+    assert(!cNegInfInterval.intersection(interval).empty);
+    assert(!cNegInfInterval.intersection(cInterval).empty);
+    assert(!cNegInfInterval.intersection(iInterval).empty);
+    assert(!cNegInfInterval.intersection(posInfInterval).empty);
+    assert(!cNegInfInterval.intersection(cPosInfInterval).empty);
+    assert(!cNegInfInterval.intersection(iPosInfInterval).empty);
+    assert(!cNegInfInterval.intersection(negInfInterval).empty);
+    assert(!cNegInfInterval.intersection(cNegInfInterval).empty);
+    assert(!cNegInfInterval.intersection(iNegInfInterval).empty);
+    assert(!iNegInfInterval.intersection(interval).empty);
+    assert(!iNegInfInterval.intersection(cInterval).empty);
+    assert(!iNegInfInterval.intersection(iInterval).empty);
+    assert(!iNegInfInterval.intersection(posInfInterval).empty);
+    assert(!iNegInfInterval.intersection(cPosInfInterval).empty);
+    assert(!iNegInfInterval.intersection(iPosInfInterval).empty);
+    assert(!iNegInfInterval.intersection(negInfInterval).empty);
+    assert(!iNegInfInterval.intersection(cNegInfInterval).empty);
+    assert(!iNegInfInterval.intersection(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1990, 7 , 6), Date(2000, 8, 2)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(1999, 7 , 6)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+}
+
+//Test NegInfInterval's isAdjacent().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
+    {
+        negInfInterval.isAdjacent(interval);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(!negInfInterval.isAdjacent(negInfInterval));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
+    assert(negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
+    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));
+
+    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAdjacent(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAdjacent(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAdjacent(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAdjacent(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAdjacent(negInfInterval));
+    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAdjacent(negInfInterval));
+
+    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
+    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
+    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
+    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
+    assert(negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
+    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.isAdjacent(interval));
+    assert(!negInfInterval.isAdjacent(cInterval));
+    assert(!negInfInterval.isAdjacent(iInterval));
+    assert(!negInfInterval.isAdjacent(posInfInterval));
+    assert(!negInfInterval.isAdjacent(cPosInfInterval));
+    assert(!negInfInterval.isAdjacent(iPosInfInterval));
+    assert(!negInfInterval.isAdjacent(negInfInterval));
+    assert(!negInfInterval.isAdjacent(cNegInfInterval));
+    assert(!negInfInterval.isAdjacent(iNegInfInterval));
+    assert(!cNegInfInterval.isAdjacent(interval));
+    assert(!cNegInfInterval.isAdjacent(cInterval));
+    assert(!cNegInfInterval.isAdjacent(iInterval));
+    assert(!cNegInfInterval.isAdjacent(posInfInterval));
+    assert(!cNegInfInterval.isAdjacent(cPosInfInterval));
+    assert(!cNegInfInterval.isAdjacent(iPosInfInterval));
+    assert(!cNegInfInterval.isAdjacent(negInfInterval));
+    assert(!cNegInfInterval.isAdjacent(cNegInfInterval));
+    assert(!cNegInfInterval.isAdjacent(iNegInfInterval));
+    assert(!iNegInfInterval.isAdjacent(interval));
+    assert(!iNegInfInterval.isAdjacent(cInterval));
+    assert(!iNegInfInterval.isAdjacent(iInterval));
+    assert(!iNegInfInterval.isAdjacent(posInfInterval));
+    assert(!iNegInfInterval.isAdjacent(cPosInfInterval));
+    assert(!iNegInfInterval.isAdjacent(iPosInfInterval));
+    assert(!iNegInfInterval.isAdjacent(negInfInterval));
+    assert(!iNegInfInterval.isAdjacent(cNegInfInterval));
+    assert(!iNegInfInterval.isAdjacent(iNegInfInterval));
+
+    //Verify Examples.
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4))));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1))));
+
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 5, 4))));
+    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(2012, 3, 1))));
+}
+
+//Test NegInfInterval's merge().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(I, J)(in I interval1, in J interval2)
+    {
+        interval1.merge(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));
+
+    assert(negInfInterval.merge(negInfInterval) ==
+                negInfInterval);
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                NegInfInterval!Date(Date(2013, 7, 3)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    assert(NegInfInterval!Date(Date(2010, 7, 3)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2010, 7, 4)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2010, 7, 5)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 6)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 8)).merge(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3)))));
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4)))));
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5)))));
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6)))));
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7)))));
+    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8)))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.merge(interval).empty);
+    assert(!negInfInterval.merge(cInterval).empty);
+    assert(!negInfInterval.merge(iInterval).empty);
+    static assert(!__traits(compiles, negInfInterval.merge(posInfInterval)));
+    static assert(!__traits(compiles, negInfInterval.merge(cPosInfInterval)));
+    static assert(!__traits(compiles, negInfInterval.merge(iPosInfInterval)));
+    assert(!negInfInterval.merge(negInfInterval).empty);
+    assert(!negInfInterval.merge(cNegInfInterval).empty);
+    assert(!negInfInterval.merge(iNegInfInterval).empty);
+    assert(!cNegInfInterval.merge(interval).empty);
+    assert(!cNegInfInterval.merge(cInterval).empty);
+    assert(!cNegInfInterval.merge(iInterval).empty);
+    static assert(!__traits(compiles, cNegInfInterval.merge(posInfInterval)));
+    static assert(!__traits(compiles, cNegInfInterval.merge(cPosInfInterval)));
+    static assert(!__traits(compiles, cNegInfInterval.merge(iPosInfInterval)));
+    assert(!cNegInfInterval.merge(negInfInterval).empty);
+    assert(!cNegInfInterval.merge(cNegInfInterval).empty);
+    assert(!cNegInfInterval.merge(iNegInfInterval).empty);
+    assert(!iNegInfInterval.merge(interval).empty);
+    assert(!iNegInfInterval.merge(cInterval).empty);
+    assert(!iNegInfInterval.merge(iInterval).empty);
+    static assert(!__traits(compiles, iNegInfInterval.merge(posInfInterval)));
+    static assert(!__traits(compiles, iNegInfInterval.merge(cPosInfInterval)));
+    static assert(!__traits(compiles, iNegInfInterval.merge(iPosInfInterval)));
+    assert(!iNegInfInterval.merge(negInfInterval).empty);
+    assert(!iNegInfInterval.merge(cNegInfInterval).empty);
+    assert(!iNegInfInterval.merge(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == NegInfInterval!Date(Date(2015, 9 , 2)));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
+}
+
+//Test NegInfInterval's span().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(I, J)(in I interval1, in J interval2)
+    {
+        interval1.span(interval2);
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
+
+    assert(negInfInterval.span(negInfInterval) ==
+                negInfInterval);
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
+                NegInfInterval!Date(Date(2013, 7, 3)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
+                NegInfInterval!Date(Date(2012, 1, 9)));
+
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7))) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8))) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    assert(NegInfInterval!Date(Date(2010, 7, 3)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2010, 7, 4)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2010, 7, 5)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 6)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 7)));
+    assert(NegInfInterval!Date(Date(2012, 1, 8)).span(negInfInterval) ==
+                NegInfInterval!Date(Date(2012, 1, 8)));
+
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3)))));
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4)))));
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5)))));
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6)))));
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7)))));
+    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8)))));
+
+    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!negInfInterval.span(interval).empty);
+    assert(!negInfInterval.span(cInterval).empty);
+    assert(!negInfInterval.span(iInterval).empty);
+    static assert(!__traits(compiles, negInfInterval.span(posInfInterval)));
+    static assert(!__traits(compiles, negInfInterval.span(cPosInfInterval)));
+    static assert(!__traits(compiles, negInfInterval.span(iPosInfInterval)));
+    assert(!negInfInterval.span(negInfInterval).empty);
+    assert(!negInfInterval.span(cNegInfInterval).empty);
+    assert(!negInfInterval.span(iNegInfInterval).empty);
+    assert(!cNegInfInterval.span(interval).empty);
+    assert(!cNegInfInterval.span(cInterval).empty);
+    assert(!cNegInfInterval.span(iInterval).empty);
+    static assert(!__traits(compiles, cNegInfInterval.span(posInfInterval)));
+    static assert(!__traits(compiles, cNegInfInterval.span(cPosInfInterval)));
+    static assert(!__traits(compiles, cNegInfInterval.span(iPosInfInterval)));
+    assert(!cNegInfInterval.span(negInfInterval).empty);
+    assert(!cNegInfInterval.span(cNegInfInterval).empty);
+    assert(!cNegInfInterval.span(iNegInfInterval).empty);
+    assert(!iNegInfInterval.span(interval).empty);
+    assert(!iNegInfInterval.span(cInterval).empty);
+    assert(!iNegInfInterval.span(iInterval).empty);
+    static assert(!__traits(compiles, iNegInfInterval.span(posInfInterval)));
+    static assert(!__traits(compiles, iNegInfInterval.span(cPosInfInterval)));
+    static assert(!__traits(compiles, iNegInfInterval.span(iPosInfInterval)));
+    assert(!iNegInfInterval.span(negInfInterval).empty);
+    assert(!iNegInfInterval.span(cNegInfInterval).empty);
+    assert(!iNegInfInterval.span(iNegInfInterval).empty);
+
+    //Verify Examples.
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == NegInfInterval!Date(Date(2015, 9 , 2)));
+    assert(NegInfInterval!Date(Date(1600, 1, 7)).span(Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) == NegInfInterval!Date(Date(2017, 7 , 1)));
+
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(2012, 3 , 1)));
+    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
+}
+
+//Test NegInfInterval's shift().
+unittest
+{
+    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.shift(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29)));
+    testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16)));
+
+    const cInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = NegInfInterval!Date(Date(2012, 4, 5));
+    auto interval2 = NegInfInterval!Date(Date(2012, 4, 5));
+
+    interval1.shift(dur!"days"(50));
+    assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25)));
+
+    interval2.shift(dur!"days"(-50));
+    assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15)));
+}
+
+//Test NegInfInterval's shift(int, int, AllowDayOverflow).
+unittest
+{
+    {
+        auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+
+        static void testIntervalFail(I)(I interval, int years, int months)
+        {
+            interval.shift(years, months);
+        }
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
+        {
+            interval.shift(years, months, allow);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7)));
+
+        auto interval2 = NegInfInterval!Date(Date(2010, 5, 31));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 6, 30)));
+    }
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    static assert(!__traits(compiles, cNegInfInterval.shift(1)));
+    static assert(!__traits(compiles, iNegInfInterval.shift(1)));
+
+    //Verify Examples.
+    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+    interval1.shift(2);
+    assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));
+
+    interval2.shift(-2);
+    assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
+}
+
+//Test NegInfInterval's expand().
+unittest
+{
+    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
+    {
+        interval.expand(duration);
+        assert(interval == expected);
+    }
+
+    testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29)));
+    testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16)));
+
+    const cInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
+    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));
+
+    //Verify Examples.
+    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+    interval1.expand(dur!"days"(2));
+    assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3)));
+
+    interval2.expand(dur!"days"(-2));
+    assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28)));
+}
+
+//Test NegInfInterval's expand(int, int, AllowDayOverflow).
+unittest
+{
+    {
+        auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+
+        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
+        {
+            interval.expand(years, months, allow);
+            assert(interval == expected);
+        }
+
+        testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7)));
+        testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7)));
+
+        auto interval2 = NegInfInterval!Date(Date(2010, 5, 31));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1)));
+
+        testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30)));
+        testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30)));
+        testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30)));
+        testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date( Date(2009, 6, 30)));
+    }
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    static assert(!__traits(compiles, cNegInfInterval.expand(1)));
+    static assert(!__traits(compiles, iNegInfInterval.expand(1)));
+
+    //Verify Examples.
+    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
+    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));
+
+    interval1.expand(2);
+    assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));
+
+    interval2.expand(-2);
+    assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
+}
+
+//Test NegInfInterval's bwdRange().
+unittest
+{
+    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+    static void testInterval(NegInfInterval!Date negInfInterval)
+    {
+        negInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront();
+    }
+
+    assertThrown!DateTimeException(testInterval(negInfInterval));
+
+    assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front ==
+                Date(2010, 10, 1));
+
+    assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front ==
+                Date(2010, 9, 24));
+
+    //Verify Examples.
+    auto interval = NegInfInterval!Date(Date(2010, 9, 9));
+    auto func = delegate (in Date date)
+                {
+                    if((date.day & 1) == 0)
+                        return date - dur!"days"(2);
+
+                    return date - dur!"days"(1);
+                };
+    auto range = interval.bwdRange(func);
+
+    //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
+    assert(range.front == Date(2010, 9, 9));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 8));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 6));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 4));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 2));
+
+    range.popFront();
+    assert(!range.empty);
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(!cNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
+    assert(!iNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
+}
+
+//Test NegInfInterval's toString().
+unittest
+{
+    assert(NegInfInterval!Date(Date(2012, 1, 7)).toString() == "[-∞ - 2012-Jan-07)");
+
+    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+    assert(cNegInfInterval.toString());
+    assert(iNegInfInterval.toString());
+}
+
+
+/++
+    Range-generating function.
+
+    Returns a delegate which returns the next time point with the given
+    $(D DayOfWeek) in a range.
+
+    Using this delegate allows iteration over successive time points which
+    are all the same day of the week. e.g. passing $(D DayOfWeek.mon) to
+    $(D everyDayOfWeek) would result in a delegate which could be used to
+    iterate over all of the Mondays in a range.
+
+    Params:
+        dir       = The direction to iterate in. If passing the return value to
+                    $(D fwdRange), use $(D Direction.fwd). If passing it to
+                    $(D bwdRange), use $(D Direction.bwd).
+        dayOfWeek = The week that each time point in the range will be.
+  +/
+static TP delegate(in TP) everyDayOfWeek(TP, Direction dir = Direction.fwd)(DayOfWeek dayOfWeek) nothrow
+    if(isTimePoint!TP &&
+       (dir == Direction.fwd || dir == Direction.bwd) &&
+       __traits(hasMember, TP, "dayOfWeek") &&
+       !__traits(isStaticFunction, TP.dayOfWeek) &&
+       is(typeof(TP.dayOfWeek) == DayOfWeek))
+{
+    TP func(in TP tp)
+    {
+        TP retval = cast(TP)tp;
+        immutable days = daysToDayOfWeek(retval.dayOfWeek, dayOfWeek);
+
+        static if(dir == Direction.fwd)
+            immutable adjustedDays = days == 0 ? 7 : days;
+        else
+            immutable adjustedDays = days == 0 ? -7 : days - 7;
+
+        return retval += dur!"days"(adjustedDays);
+    }
+
+    return &func;
+}
+
+///
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27));
+    auto func = everyDayOfWeek!Date(DayOfWeek.mon);
+    auto range = interval.fwdRange(func);
+
+    //A Thursday. Using PopFirst.yes would have made this Date(2010, 9, 6).
+    assert(range.front == Date(2010, 9, 2));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 6));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 13));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 20));
+
+    range.popFront();
+    assert(range.empty);
+}
+
+unittest
+{
+    auto funcFwd = everyDayOfWeek!Date(DayOfWeek.mon);
+    auto funcBwd = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.mon);
+
+    assert(funcFwd(Date(2010, 8, 28)) == Date(2010, 8, 30));
+    assert(funcFwd(Date(2010, 8, 29)) == Date(2010, 8, 30));
+    assert(funcFwd(Date(2010, 8, 30)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 8, 31)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 1)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 2)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 3)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 4)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 5)) == Date(2010, 9, 6));
+    assert(funcFwd(Date(2010, 9, 6)) == Date(2010, 9, 13));
+    assert(funcFwd(Date(2010, 9, 7)) == Date(2010, 9, 13));
+
+    assert(funcBwd(Date(2010, 8, 28)) == Date(2010, 8, 23));
+    assert(funcBwd(Date(2010, 8, 29)) == Date(2010, 8, 23));
+    assert(funcBwd(Date(2010, 8, 30)) == Date(2010, 8, 23));
+    assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 1)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 2)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 3)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 4)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 5)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 6)) == Date(2010, 8, 30));
+    assert(funcBwd(Date(2010, 9, 7)) == Date(2010, 9, 6));
+
+    static assert(!__traits(compiles, everyDayOfWeek!(TimeOfDay)(DayOfWeek.mon)));
+    assert(everyDayOfWeek!(DateTime)(DayOfWeek.mon) !is null);
+    assert(everyDayOfWeek!(SysTime)(DayOfWeek.mon) !is null);
+}
+
+
+/++
+    Range-generating function.
+
+    Returns a delegate which returns the next time point with the given month
+    which would be reached by adding months to the given time point.
+
+    So, using this delegate allows iteration over successive time points
+    which are in the same month but different years. For example,
+    iterate over each successive December 25th in an interval by starting with a
+    date which had the 25th as its day and passed $(D Month.dec) to
+    $(D everyMonth) to create the delegate.
+
+    Since it wouldn't really make sense to be iterating over a specific month
+    and end up with some of the time points in the succeeding month or two years
+    after the previous time point, $(D AllowDayOverflow.no) is always used when
+    calculating the next time point.
+
+    Params:
+        dir   = The direction to iterate in. If passing the return value to
+                $(D fwdRange), use $(D Direction.fwd). If passing it to
+                $(D bwdRange), use $(D Direction.bwd).
+        month = The month that each time point in the range will be in.
+  +/
+static TP delegate(in TP) everyMonth(TP, Direction dir = Direction.fwd)(int month)
+    if(isTimePoint!TP &&
+       (dir == Direction.fwd || dir == Direction.bwd) &&
+       __traits(hasMember, TP, "month") &&
+       !__traits(isStaticFunction, TP.month) &&
+       is(typeof(TP.month) == Month))
+{
+    enforceValid!"months"(month);
+
+    TP func(in TP tp)
+    {
+        TP retval = cast(TP)tp;
+        immutable months = monthsToMonth(retval.month, month);
+
+        static if(dir == Direction.fwd)
+            immutable adjustedMonths = months == 0 ? 12 : months;
+        else
+            immutable adjustedMonths = months == 0 ? -12 : months - 12;
+
+        retval.add!"months"(adjustedMonths, AllowDayOverflow.no);
+
+        if(retval.month != month)
+        {
+            retval.add!"months"(-1);
+            assert(retval.month == month);
+        }
+
+        return retval;
+    }
+
+    return &func;
+}
+
+///
+unittest
+{
+    auto interval = Interval!Date(Date(2000, 1, 30), Date(2004, 8, 5));
+    auto func = everyMonth!(Date)(Month.feb);
+    auto range = interval.fwdRange(func);
+
+    //Using PopFirst.yes would have made this Date(2010, 2, 29).
+    assert(range.front == Date(2000, 1, 30));
+
+    range.popFront();
+    assert(range.front == Date(2000, 2, 29));
+
+    range.popFront();
+    assert(range.front == Date(2001, 2, 28));
+
+    range.popFront();
+    assert(range.front == Date(2002, 2, 28));
+
+    range.popFront();
+    assert(range.front == Date(2003, 2, 28));
+
+    range.popFront();
+    assert(range.front == Date(2004, 2, 28));
+
+    range.popFront();
+    assert(range.empty);
+}
+
+unittest
+{
+    auto funcFwd = everyMonth!Date(Month.jun);
+    auto funcBwd = everyMonth!(Date, Direction.bwd)(Month.jun);
+
+    assert(funcFwd(Date(2010, 5, 31)) == Date(2010, 6, 30));
+    assert(funcFwd(Date(2010, 6, 30)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 7, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 8, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 9, 30)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 10, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 11, 30)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2010, 12, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2011, 1, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2011, 2, 28)) == Date(2011, 6, 28));
+    assert(funcFwd(Date(2011, 3, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2011, 4, 30)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2011, 5, 31)) == Date(2011, 6, 30));
+    assert(funcFwd(Date(2011, 6, 30)) == Date(2012, 6, 30));
+    assert(funcFwd(Date(2011, 7, 31)) == Date(2012, 6, 30));
+
+    assert(funcBwd(Date(2010, 5, 31)) == Date(2009, 6, 30));
+    assert(funcBwd(Date(2010, 6, 30)) == Date(2009, 6, 30));
+    assert(funcBwd(Date(2010, 7, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2010, 9, 30)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2010, 10, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2010, 11, 30)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2010, 12, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 1, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 2, 28)) == Date(2010, 6, 28));
+    assert(funcBwd(Date(2011, 3, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 4, 30)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 5, 31)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 6, 30)) == Date(2010, 6, 30));
+    assert(funcBwd(Date(2011, 7, 30)) == Date(2011, 6, 30));
+
+    static assert(!__traits(compiles, everyMonth!(TimeOfDay)(Month.jan)));
+    assert(everyMonth!(DateTime)(Month.jan) !is null);
+    assert(everyMonth!(SysTime)(Month.jan) !is null);
+}
+
+
+/++
+    Range-generating function.
+
+    Returns a delegate which returns the next time point which is the given
+    duration later.
+
+    Using this delegate allows iteration over successive time points which
+    are apart by the given duration e.g. passing $(D dur!"days"(3)) to
+    $(D everyDuration) would result in a delegate which could be used to iterate
+    over a range of days which are each 3 days apart.
+
+    Params:
+        dir      = The direction to iterate in. If passing the return value to
+                   $(D fwdRange), use $(D Direction.fwd). If passing it to
+                   $(D bwdRange), use $(D Direction.bwd).
+        duration = The duration which separates each successive time point in
+                   the range.
+  +/
+static TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D)
+                                       (D duration) nothrow
+    if(isTimePoint!TP &&
+       __traits(compiles, TP.init + duration) &&
+       (dir == Direction.fwd || dir == Direction.bwd))
+{
+    TP func(in TP tp)
+    {
+        static if(dir == Direction.fwd)
+            return tp + duration;
+        else
+            return tp - duration;
+    }
+
+    return &func;
+}
+
+///
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27));
+    auto func = everyDuration!Date(dur!"days"(8));
+    auto range = interval.fwdRange(func);
+
+    //Using PopFirst.yes would have made this Date(2010, 9, 10).
+    assert(range.front == Date(2010, 9, 2));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 10));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 18));
+
+    range.popFront();
+    assert(range.front == Date(2010, 9, 26));
+
+    range.popFront();
+    assert(range.empty);
+}
+
+unittest
+{
+    auto funcFwd = everyDuration!Date(dur!"days"(27));
+    auto funcBwd = everyDuration!(Date, Direction.bwd)(dur!"days"(27));
+
+    assert(funcFwd(Date(2009, 12, 25)) == Date(2010, 1, 21));
+    assert(funcFwd(Date(2009, 12, 26)) == Date(2010, 1, 22));
+    assert(funcFwd(Date(2009, 12, 27)) == Date(2010, 1, 23));
+    assert(funcFwd(Date(2009, 12, 28)) == Date(2010, 1, 24));
+
+    assert(funcBwd(Date(2010, 1, 21)) == Date(2009, 12, 25));
+    assert(funcBwd(Date(2010, 1, 22)) == Date(2009, 12, 26));
+    assert(funcBwd(Date(2010, 1, 23)) == Date(2009, 12, 27));
+    assert(funcBwd(Date(2010, 1, 24)) == Date(2009, 12, 28));
+
+    assert(everyDuration!Date(dur!"hnsecs"(1)) !is null);
+    assert(everyDuration!TimeOfDay(dur!"hnsecs"(1)) !is null);
+    assert(everyDuration!DateTime(dur!"hnsecs"(1)) !is null);
+    assert(everyDuration!SysTime(dur!"hnsecs"(1)) !is null);
+}
+
+
+/++
+    Range-generating function.
+
+    Returns a delegate which returns the next time point which is the given
+    number of years, month, and duration later.
+
+    The difference between this version of $(D everyDuration) and the version
+    which just takes a $(CXREF time, Duration) is that this one also takes the number of
+    years and months (along with an $(D AllowDayOverflow) to indicate whether
+    adding years and months should allow the days to overflow).
+
+    Note that if iterating forward, $(D add!"years"()) is called on the given
+    time point, then $(D add!"months"()), and finally the duration is added
+    to it. However, if iterating backwards, the duration is added first, then
+    $(D add!"months"()) is called, and finally $(D add!"years"()) is called.
+    That way, going backwards generates close to the same time points that
+    iterating forward does, but since adding years and months is not entirely
+    reversible (due to possible day overflow, regardless of whether
+    $(D AllowDayOverflow.yes) or $(D AllowDayOverflow.no) is used), it can't be
+    guaranteed that iterating backwards will give the same time points as
+    iterating forward would have (even assuming that the end of the range is a
+    time point which would be returned by the delegate when iterating forward
+    from $(D begin)).
+
+    Params:
+        dir           = The direction to iterate in. If passing the return
+                        value to $(D fwdRange), use $(D Direction.fwd). If
+                        passing it to $(D bwdRange), use $(D Direction.bwd).
+        years         = The number of years to add to the time point passed to
+                        the delegate.
+        months        = The number of months to add to the time point passed to
+                        the delegate.
+        allowOverflow = Whether the days should be allowed to overflow on
+                        $(D begin) and $(D end), causing their month to
+                        increment.
+        duration      = The duration to add to the time point passed to the
+                        delegate.
+  +/
+static TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D)
+                                       (int years,
+                                        int months = 0,
+                                        AllowDayOverflow allowOverflow = AllowDayOverflow.yes,
+                                        D duration = dur!"days"(0)) nothrow
+    if(isTimePoint!TP &&
+       __traits(compiles, TP.init + duration) &&
+       __traits(compiles, TP.init.add!"years"(years)) &&
+       __traits(compiles, TP.init.add!"months"(months)) &&
+       (dir == Direction.fwd || dir == Direction.bwd))
+{
+    TP func(in TP tp)
+    {
+        static if(dir == Direction.fwd)
+        {
+            TP retval = cast(TP)tp;
+
+            retval.add!"years"(years, allowOverflow);
+            retval.add!"months"(months, allowOverflow);
+
+            return retval + duration;
+        }
+        else
+        {
+            TP retval = tp - duration;
+
+            retval.add!"months"(-months, allowOverflow);
+            retval.add!"years"(-years, allowOverflow);
+
+            return retval;
+        }
+    }
+
+    return &func;
+}
+
+///
+unittest
+{
+    auto interval = Interval!Date(Date(2010, 9, 2), Date(2025, 9, 27));
+    auto func = everyDuration!Date(4, 1, AllowDayOverflow.yes, dur!"days"(2));
+    auto range = interval.fwdRange(func);
+
+    //Using PopFirst.yes would have made this Date(2014, 10, 12).
+    assert(range.front == Date(2010, 9, 2));
+
+    range.popFront();
+    assert(range.front == Date(2014, 10, 4));
+
+    range.popFront();
+    assert(range.front == Date(2018, 11, 6));
+
+    range.popFront();
+    assert(range.front == Date(2022, 12, 8));
+
+    range.popFront();
+    assert(range.empty);
+}
+
+unittest
+{
+    {
+        auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"days"(3));
+        auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3));
+
+        assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28));
+        assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1));
+        assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2));
+        assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3));
+        assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 4));
+
+        assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25));
+        assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26));
+        assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27));
+        assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28));
+        assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1));
+    }
+
+    {
+        auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.no, dur!"days"(3));
+        auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3));
+
+        assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28));
+        assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1));
+        assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2));
+        assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3));
+        assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 3));
+
+        assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25));
+        assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26));
+        assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27));
+        assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28));
+        assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1));
+    }
+
+    assert(everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
+    static assert(!__traits(compiles, everyDuration!TimeOfDay(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1))));
+    assert(everyDuration!DateTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
+    assert(everyDuration!SysTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
+}
+
+
+//TODO Add function to create a range generating function based on a date recurrence pattern string.
+//     This may or may not involve creating a date recurrence pattern class of some sort - probably
+//     yes if we want to make it easy to build them. However, there is a standard recurrence
+//     pattern string format which we'd want to support with a range generator (though if we have
+//     the class/struct, we'd probably want a version of the range generating function which took
+//     that rather than a string).
+
+
+//==============================================================================
+// Section with ranges.
+//==============================================================================
+
+
+/++
+    A range over an $(LREF2 .Interval, Interval).
+
+    $(D IntervalRange) is only ever constructed by $(LREF2 .Interval, Interval). However, when
+    it is constructed, it is given a function, $(D func), which is used to
+    generate the time points which are iterated over. $(D func) takes a time
+    point and returns a time point of the same type. For instance,
+    to iterate over all of the days in
+    the interval $(D Interval!Date), pass a function to $(LREF2 .Interval, Interval)'s $(D fwdRange)
+    where that function took a $(LREF Date) and returned a $(LREF Date) which was one
+    day later. That function would then be used by $(D IntervalRange)'s
+    $(D popFront) to iterate over the $(LREF Date)s in the interval.
+
+    If $(D dir == Direction.fwd), then a range iterates forward in time, whereas
+    if $(D dir == Direction.bwd), then it iterates backwards in time. So, if
+    $(D dir == Direction.fwd) then $(D front == interval.begin), whereas if
+    $(D dir == Direction.bwd) then $(D front == interval.end). $(D func) must
+    generate a time point going in the proper direction of iteration, or a
+    $(LREF DateTimeException) will be thrown. So, to iterate forward in
+    time, the time point that $(D func) generates must be later in time than the
+    one passed to it. If it's either identical or earlier in time, then a
+    $(LREF DateTimeException) will be thrown. To iterate backwards, then
+    the generated time point must be before the time point which was passed in.
+
+    If the generated time point is ever passed the edge of the range in the
+    proper direction, then the edge of that range will be used instead. So, if
+    iterating forward, and the generated time point is past the interval's
+    $(D end), then $(D front) becomes $(D end). If iterating backwards, and the
+    generated time point is before $(D begin), then $(D front) becomes
+    $(D begin). In either case, the range would then be empty.
+
+    Also note that while normally the $(D begin) of an interval is included in
+    it and its $(D end) is excluded from it, if $(D dir == Direction.bwd), then
+    $(D begin) is treated as excluded and $(D end) is treated as included. This
+    allows for the same behavior in both directions. This works because none of
+    $(LREF2 .Interval, Interval)'s functions which care about whether $(D begin) or $(D end) is
+    included or excluded are ever called by $(D IntervalRange). $(D interval)
+    returns a normal interval, regardless of whether $(D dir == Direction.fwd)
+    or if $(D dir == Direction.bwd), so any $(LREF2 .Interval, Interval) functions which are
+    called on it which care about whether $(D begin) or $(D end) are included or
+    excluded will treat $(D begin) as included and $(D end) as excluded.
+  +/
+struct IntervalRange(TP, Direction dir)
+    if(isTimePoint!TP && dir != Direction.both)
+{
+public:
+
+    /++
+        Params:
+            rhs = The $(D IntervalRange) to assign to this one.
+      +/
+    ref IntervalRange opAssign(ref IntervalRange rhs) pure nothrow
+    {
+        _interval = rhs._interval;
+        _func = rhs._func;
+        return this;
+    }
+
+
+    /++ Ditto +/
+    ref IntervalRange opAssign(IntervalRange rhs) pure nothrow
+    {
+        return this = rhs;
+    }
+
+
+    /++
+        Whether this $(D IntervalRange) is empty.
+      +/
+    @property bool empty() const pure nothrow
+    {
+        return _interval.empty;
+    }
+
+
+    /++
+        The first time point in the range.
+
+        Throws:
+            $(LREF DateTimeException) if the range is empty.
+      +/
+    @property TP front() const pure
+    {
+        _enforceNotEmpty();
+
+        static if(dir == Direction.fwd)
+            return _interval.begin;
+        else
+            return _interval.end;
+    }
+
+
+    /++
+        Pops $(D front) from the range, using $(D func) to generate the next
+        time point in the range. If the generated time point is beyond the edge
+        of the range, then $(D front) is set to that edge, and the range is then
+        empty. So, if iterating forwards, and the generated time point is
+        greater than the interval's $(D end), then $(D front) is set to
+        $(D end). If iterating backwards, and the generated time point is less
+        than the interval's $(D begin), then $(D front) is set to $(D begin).
+
+        Throws:
+            $(LREF DateTimeException) if the range is empty or if the generated
+            time point is in the wrong direction (i.e. if iterating
+            forward and the generated time point is before $(D front), or if
+            iterating backwards and the generated time point is after
+            $(D front)).
+      +/
+    void popFront()
+    {
+        _enforceNotEmpty();
+
+        static if(dir == Direction.fwd)
+        {
+            auto begin = _func(_interval.begin);
+
+            if(begin > _interval.end)
+                begin = _interval.end;
+
+            _enforceCorrectDirection(begin);
+
+            _interval.begin = begin;
+        }
+        else
+        {
+            auto end = _func(_interval.end);
+
+            if(end < _interval.begin)
+                end = _interval.begin;
+
+            _enforceCorrectDirection(end);
+
+            _interval.end = end;
+        }
+    }
+
+
+    /++
+        Returns a copy of $(D this).
+      +/
+    @property IntervalRange save() pure nothrow
+    {
+        return this;
+    }
+
+
+    /++
+        The interval that this $(D IntervalRange) currently covers.
+      +/
+    @property Interval!TP interval() const pure nothrow
+    {
+        return cast(Interval!TP)_interval;
+    }
+
+
+    /++
+        The function used to generate the next time point in the range.
+      +/
+    TP delegate(in TP) func() pure nothrow @property
+    {
+        return _func;
+    }
+
+
+    /++
+        The $(D Direction) that this range iterates in.
+      +/
+    @property Direction direction() const pure nothrow
+    {
+        return dir;
+    }
+
+
+private:
+
+    /+
+        Params:
+            interval = The interval that this range covers.
+            func     = The function used to generate the time points which are
+                       iterated over.
+      +/
+    this(in Interval!TP interval, TP delegate(in TP) func) pure nothrow
+    {
+        _func = func;
+        _interval = interval;
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if this interval is empty.
+      +/
+    void _enforceNotEmpty(size_t line = __LINE__) const pure
+    {
+        if(empty)
+            throw new DateTimeException("Invalid operation for an empty IntervalRange.", __FILE__, line);
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if $(D_PARAM newTP) is in the wrong
+            direction.
+      +/
+    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
+    {
+        import std.format : format;
+
+        static if(dir == Direction.fwd)
+        {
+            enforce(newTP > _interval._begin,
+                    new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]",
+                                                 interval._begin,
+                                                 newTP),
+                                                 __FILE__,
+                                                 line));
+        }
+        else
+        {
+            enforce(newTP < _interval._end,
+                    new DateTimeException(format("Generated time point is after previous end: prev [%s] new [%s]",
+                                                 interval._end,
+                                                 newTP),
+                                                 __FILE__,
+                                                 line));
+        }
+    }
+
+
+    Interval!TP        _interval;
+    TP delegate(in TP) _func;
+}
+
+//Test that IntervalRange satisfies the range predicates that it's supposed to satisfy.
+unittest
+{
+    static assert(isInputRange!(IntervalRange!(Date, Direction.fwd)));
+    static assert(isForwardRange!(IntervalRange!(Date, Direction.fwd)));
+
+    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
+    //static assert(!isOutputRange!(IntervalRange!(Date, Direction.fwd), Date));
+
+    static assert(!isBidirectionalRange!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!isRandomAccessRange!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!hasSwappableElements!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!hasAssignableElements!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!hasLength!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!isInfinite!(IntervalRange!(Date, Direction.fwd)));
+    static assert(!hasSlicing!(IntervalRange!(Date, Direction.fwd)));
+
+    static assert(is(ElementType!(IntervalRange!(Date, Direction.fwd)) == Date));
+    static assert(is(ElementType!(IntervalRange!(TimeOfDay, Direction.fwd)) == TimeOfDay));
+    static assert(is(ElementType!(IntervalRange!(DateTime, Direction.fwd)) == DateTime));
+    static assert(is(ElementType!(IntervalRange!(SysTime, Direction.fwd)) == SysTime));
+}
+
+//Test construction of IntervalRange.
+unittest
+{
+    {
+        Date dateFunc(in Date date)
+        {
+            return date;
+        }
+
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+
+        auto ir = IntervalRange!(Date, Direction.fwd)(interval, &dateFunc);
+    }
+
+    {
+        TimeOfDay todFunc(in TimeOfDay tod)
+        {
+            return tod;
+        }
+
+        auto interval = Interval!TimeOfDay(TimeOfDay(12, 1, 7), TimeOfDay(14, 0, 0));
+
+        auto ir = IntervalRange!(TimeOfDay, Direction.fwd)(interval, &todFunc);
+    }
+
+    {
+        DateTime dtFunc(in DateTime dt)
+        {
+            return dt;
+        }
+
+        auto interval = Interval!DateTime(DateTime(2010, 7, 4, 12, 1, 7), DateTime(2012, 1, 7, 14, 0, 0));
+
+        auto ir = IntervalRange!(DateTime, Direction.fwd)(interval, &dtFunc);
+    }
+
+    {
+        SysTime stFunc(in SysTime st)
+        {
+            return cast(SysTime)st;
+        }
+
+        auto interval = Interval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7)), SysTime(DateTime(2012, 1, 7, 14, 0, 0)));
+
+        auto ir = IntervalRange!(SysTime, Direction.fwd)(interval, &stFunc);
+    }
+}
+
+//Test IntervalRange's empty().
+unittest
+{
+    //fwd
+    {
+        auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+
+        assert(!range.empty);
+        range.popFront();
+        assert(range.empty);
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+        assert(!cRange.empty);
+
+        //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if
+        //empty works with it. However, since an immutable range is pretty useless, it's no great loss.
+    }
+
+    //bwd
+    {
+        auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+
+        assert(!range.empty);
+        range.popFront();
+        assert(range.empty);
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+        assert(!cRange.empty);
+
+        //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if
+        //empty works with it. However, since an immutable range is pretty useless, it's no great loss.
+    }
+}
+
+//Test IntervalRange's front.
+unittest
+{
+    //fwd
+    {
+        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+        assertThrown!DateTimeException((in IntervalRange!(Date, Direction.fwd) range){range.front;}(emptyRange));
+
+        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed));
+        assert(range.front == Date(2010, 7, 4));
+
+        auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+        assert(poppedRange.front == Date(2010, 7, 7));
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+        assert(cRange.front != Date.init);
+    }
+
+    //bwd
+    {
+        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+        assertThrown!DateTimeException((in IntervalRange!(Date, Direction.bwd) range){range.front;}(emptyRange));
+
+        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed));
+        assert(range.front == Date(2012, 1, 7));
+
+        auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+        assert(poppedRange.front == Date(2012, 1, 4));
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+        assert(cRange.front != Date.init);
+    }
+}
+
+//Test IntervalRange's popFront().
+unittest
+{
+    //fwd
+    {
+        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+        assertThrown!DateTimeException((IntervalRange!(Date, Direction.fwd) range){range.popFront();}(emptyRange));
+
+        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+        auto expected = range.front;
+
+        foreach(date; range)
+        {
+            assert(date == expected);
+            expected += dur!"days"(7);
+        }
+
+        assert(walkLength(range) == 79);
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+        assert(cRange.front != Date.init);
+    }
+
+    //bwd
+    {
+        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+        assertThrown!DateTimeException((IntervalRange!(Date, Direction.bwd) range){range.popFront();}(emptyRange));
+
+        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+        auto expected = range.front;
+
+        foreach(date; range)
+        {
+            assert(date == expected);
+            expected += dur!"days"(-7);
+        }
+
+        assert(walkLength(range) == 79);
+
+        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+        static assert(!__traits(compiles, cRange.popFront()));
+    }
+}
+
+//Test IntervalRange's save.
+unittest
+{
+    //fwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+        auto range = interval.fwdRange(func);
+
+        assert(range.save == range);
+    }
+
+    //bwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+        auto range = interval.bwdRange(func);
+
+        assert(range.save == range);
+    }
+}
+
+//Test IntervalRange's interval.
+unittest
+{
+    //fwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+        auto range = interval.fwdRange(func);
+
+        assert(range.interval == interval);
+
+        const cRange = range;
+        assert(!cRange.interval.empty);
+    }
+
+    //bwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+        auto range = interval.bwdRange(func);
+
+        assert(range.interval == interval);
+
+        const cRange = range;
+        assert(!cRange.interval.empty);
+    }
+}
+
+//Test IntervalRange's func.
+unittest
+{
+    //fwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+        auto range = interval.fwdRange(func);
+
+        assert(range.func == func);
+    }
+
+    //bwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+        auto range = interval.bwdRange(func);
+
+        assert(range.func == func);
+    }
+}
+
+//Test IntervalRange's direction.
+unittest
+{
+    //fwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+        auto range = interval.fwdRange(func);
+
+        assert(range.direction == Direction.fwd);
+
+        const cRange = range;
+        assert(cRange.direction == Direction.fwd);
+    }
+
+    //bwd
+    {
+        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
+        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+        auto range = interval.bwdRange(func);
+
+        assert(range.direction == Direction.bwd);
+
+        const cRange = range;
+        assert(cRange.direction == Direction.bwd);
+    }
+}
+
+
+/++
+    A range over a $(D PosInfInterval). It is an infinite range.
+
+    $(D PosInfIntervalRange) is only ever constructed by $(D PosInfInterval).
+    However, when it is constructed, it is given a function, $(D func), which
+    is used to generate the time points which are iterated over. $(D func)
+    takes a time point and returns a time point of the same type. For
+    instance, to iterate
+    over all of the days in the interval $(D PosInfInterval!Date), pass a function to
+    $(D PosInfInterval)'s $(D fwdRange) where that function took a $(LREF Date) and
+    returned a $(LREF Date) which was one day later. That function would then be
+    used by $(D PosInfIntervalRange)'s $(D popFront) to iterate over the
+    $(LREF Date)s in the interval - though obviously, since the range is infinite,
+    use a function such as $(D std.range.take) with it rather than
+    iterating over $(I all) of the dates.
+
+    As the interval goes to positive infinity, the range is always iterated over
+    forwards, never backwards. $(D func) must generate a time point going in
+    the proper direction of iteration, or a $(LREF DateTimeException) will be
+    thrown. So, the time points that $(D func) generates must be later in time
+    than the one passed to it. If it's either identical or earlier in time, then
+    a $(LREF DateTimeException) will be thrown.
+  +/
+struct PosInfIntervalRange(TP)
+    if(isTimePoint!TP)
+{
+public:
+
+    /++
+        Params:
+            rhs = The $(D PosInfIntervalRange) to assign to this one.
+      +/
+    ref PosInfIntervalRange opAssign(ref PosInfIntervalRange rhs) pure nothrow
+    {
+        _interval = rhs._interval;
+        _func = rhs._func;
+
+        return this;
+    }
+
+
+    /++ Ditto +/
+    ref PosInfIntervalRange opAssign(PosInfIntervalRange rhs) pure nothrow
+    {
+        return this = rhs;
+    }
+
+
+    /++
+        This is an infinite range, so it is never empty.
+      +/
+    enum bool empty = false;
+
+
+    /++
+        The first time point in the range.
+      +/
+    @property TP front() const pure nothrow
+    {
+        return _interval.begin;
+    }
+
+
+    /++
+        Pops $(D front) from the range, using $(D func) to generate the next
+        time point in the range.
+
+        Throws:
+            $(LREF DateTimeException) if the generated time point is less than
+            $(D front).
+      +/
+    void popFront()
+    {
+        auto begin = _func(_interval.begin);
+
+        _enforceCorrectDirection(begin);
+
+        _interval.begin = begin;
+    }
+
+
+    /++
+        Returns a copy of $(D this).
+      +/
+    @property PosInfIntervalRange save() pure nothrow
+    {
+        return this;
+    }
+
+
+    /++
+        The interval that this range currently covers.
+      +/
+    @property PosInfInterval!TP interval() const pure nothrow
+    {
+        return cast(PosInfInterval!TP)_interval;
+    }
+
+
+    /++
+        The function used to generate the next time point in the range.
+      +/
+    TP delegate(in TP) func() pure nothrow @property
+    {
+        return _func;
+    }
+
+
+private:
+
+    /+
+        Params:
+            interval = The interval that this range covers.
+            func     = The function used to generate the time points which are
+                       iterated over.
+      +/
+    this(in PosInfInterval!TP interval, TP delegate(in TP) func) pure nothrow
+    {
+        _func = func;
+        _interval = interval;
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if $(D_PARAME newTP) is in the wrong
+            direction.
+      +/
+    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
+    {
+        import std.format : format;
+
+        enforce(newTP > _interval._begin,
+                new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]",
+                                             interval._begin,
+                                             newTP),
+                                             __FILE__,
+                                             line));
+    }
+
+
+    PosInfInterval!TP  _interval;
+    TP delegate(in TP) _func;
+}
+
+//Test that PosInfIntervalRange satisfies the range predicates that it's supposed to satisfy.
+unittest
+{
+    static assert(isInputRange!(PosInfIntervalRange!Date));
+    static assert(isForwardRange!(PosInfIntervalRange!Date));
+    static assert(isInfinite!(PosInfIntervalRange!Date));
+
+    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
+    //static assert(!isOutputRange!(PosInfIntervalRange!Date, Date));
+    static assert(!isBidirectionalRange!(PosInfIntervalRange!Date));
+    static assert(!isRandomAccessRange!(PosInfIntervalRange!Date));
+    static assert(!hasSwappableElements!(PosInfIntervalRange!Date));
+    static assert(!hasAssignableElements!(PosInfIntervalRange!Date));
+    static assert(!hasLength!(PosInfIntervalRange!Date));
+    static assert(!hasSlicing!(PosInfIntervalRange!Date));
+
+    static assert(is(ElementType!(PosInfIntervalRange!Date) == Date));
+    static assert(is(ElementType!(PosInfIntervalRange!TimeOfDay) == TimeOfDay));
+    static assert(is(ElementType!(PosInfIntervalRange!DateTime) == DateTime));
+    static assert(is(ElementType!(PosInfIntervalRange!SysTime) == SysTime));
+}
+
+//Test construction of PosInfIntervalRange.
+unittest
+{
+    {
+        Date dateFunc(in Date date)
+        {
+            return date;
+        }
+
+        auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
+
+        auto ir = PosInfIntervalRange!Date(posInfInterval, &dateFunc);
+    }
+
+    {
+        TimeOfDay todFunc(in TimeOfDay tod)
+        {
+            return tod;
+        }
+
+        auto posInfInterval = PosInfInterval!TimeOfDay(TimeOfDay(12, 1, 7));
+
+        auto ir = PosInfIntervalRange!(TimeOfDay)(posInfInterval, &todFunc);
+    }
+
+    {
+        DateTime dtFunc(in DateTime dt)
+        {
+            return dt;
+        }
+
+        auto posInfInterval = PosInfInterval!DateTime(DateTime(2010, 7, 4, 12, 1, 7));
+
+        auto ir = PosInfIntervalRange!(DateTime)(posInfInterval, &dtFunc);
+    }
+
+    {
+        SysTime stFunc(in SysTime st)
+        {
+            return cast(SysTime)st;
+        }
+
+        auto posInfInterval = PosInfInterval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7)));
+
+        auto ir = PosInfIntervalRange!(SysTime)(posInfInterval, &stFunc);
+    }
+}
+
+//Test PosInfIntervalRange's front.
+unittest
+{
+    auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed));
+    assert(range.front == Date(2010, 7, 4));
+
+    auto poppedRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+    assert(poppedRange.front == Date(2010, 7, 7));
+
+    const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+    assert(cRange.front != Date.init);
+}
+
+//Test PosInfIntervalRange's popFront().
+unittest
+{
+    import std.range;
+    auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
+    auto expected = range.front;
+
+    foreach(date; take(range, 79))
+    {
+        assert(date == expected);
+        expected += dur!"days"(7);
+    }
+
+    const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
+    static assert(!__traits(compiles, cRange.popFront()));
+}
+
+//Test PosInfIntervalRange's save.
+unittest
+{
+    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+    auto range = interval.fwdRange(func);
+
+    assert(range.save == range);
+}
+
+//Test PosInfIntervalRange's interval.
+unittest
+{
+    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+    auto range = interval.fwdRange(func);
+
+    assert(range.interval == interval);
+
+    const cRange = range;
+    assert(!cRange.interval.empty);
+}
+
+//Test PosInfIntervalRange's func.
+unittest
+{
+    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
+    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
+    auto range = interval.fwdRange(func);
+
+    assert(range.func == func);
+}
+
+
+/++
+    A range over a $(D NegInfInterval). It is an infinite range.
+
+    $(D NegInfIntervalRange) is only ever constructed by $(D NegInfInterval).
+    However, when it is constructed, it is given a function, $(D func), which
+    is used to generate the time points which are iterated over. $(D func)
+    takes a time point and returns a time point of the same type. For
+    instance, to iterate
+    over all of the days in the interval $(D NegInfInterval!Date), pass a function to
+    $(D NegInfInterval)'s $(D bwdRange) where that function took a $(LREF Date) and
+    returned a $(LREF Date) which was one day earlier. That function would then be
+    used by $(D NegInfIntervalRange)'s $(D popFront) to iterate over the
+    $(LREF Date)s in the interval - though obviously, since the range is infinite,
+    use a function such as $(D std.range.take) with it rather than
+    iterating over $(I all) of the dates.
+
+    As the interval goes to negative infinity, the range is always iterated over
+    backwards, never forwards. $(D func) must generate a time point going in
+    the proper direction of iteration, or a $(LREF DateTimeException) will be
+    thrown. So, the time points that $(D func) generates must be earlier in time
+    than the one passed to it. If it's either identical or later in time, then a
+    $(LREF DateTimeException) will be thrown.
+
+    Also note that while normally the $(D end) of an interval is excluded from
+    it, $(D NegInfIntervalRange) treats it as if it were included. This allows
+    for the same behavior as with $(D PosInfIntervalRange). This works
+    because none of $(D NegInfInterval)'s functions which care about whether
+    $(D end) is included or excluded are ever called by
+    $(D NegInfIntervalRange). $(D interval) returns a normal interval, so any
+    $(D NegInfInterval) functions which are called on it which care about
+    whether $(D end) is included or excluded will treat $(D end) as excluded.
+  +/
+struct NegInfIntervalRange(TP)
+    if(isTimePoint!TP)
+{
+public:
+
+    /++
+        Params:
+            rhs = The $(D NegInfIntervalRange) to assign to this one.
+      +/
+    ref NegInfIntervalRange opAssign(ref NegInfIntervalRange rhs) pure nothrow
+    {
+        _interval = rhs._interval;
+        _func = rhs._func;
+
+        return this;
+    }
+
+
+    /++ Ditto +/
+    ref NegInfIntervalRange opAssign(NegInfIntervalRange rhs) pure nothrow
+    {
+        return this = rhs;
+    }
+
+
+    /++
+        This is an infinite range, so it is never empty.
+      +/
+    enum bool empty = false;
+
+
+    /++
+        The first time point in the range.
+      +/
+    @property TP front() const pure nothrow
+    {
+        return _interval.end;
+    }
+
+
+    /++
+        Pops $(D front) from the range, using $(D func) to generate the next
+        time point in the range.
+
+        Throws:
+            $(LREF DateTimeException) if the generated time point is greater than
+            $(D front).
+      +/
+    void popFront()
+    {
+        auto end = _func(_interval.end);
+
+        _enforceCorrectDirection(end);
+
+        _interval.end = end;
+    }
+
+
+    /++
+        Returns a copy of $(D this).
+      +/
+    @property NegInfIntervalRange save() pure nothrow
+    {
+        return this;
+    }
+
+
+    /++
+        The interval that this range currently covers.
+      +/
+    @property NegInfInterval!TP interval() const pure nothrow
+    {
+        return cast(NegInfInterval!TP)_interval;
+    }
+
+
+    /++
+        The function used to generate the next time point in the range.
+      +/
+    TP delegate(in TP) func() pure nothrow @property
+    {
+        return _func;
+    }
+
+
+private:
+
+    /+
+        Params:
+            interval = The interval that this range covers.
+            func     = The function used to generate the time points which are
+                       iterated over.
+      +/
+    this(in NegInfInterval!TP interval, TP delegate(in TP) func) pure nothrow
+    {
+        _func = func;
+        _interval = interval;
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if $(D_PARAM newTP) is in the wrong
+            direction.
+      +/
+    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
+    {
+        import std.format : format;
+
+        enforce(newTP < _interval._end,
+                new DateTimeException(format("Generated time point is before previous end: prev [%s] new [%s]",
+                                             interval._end,
+                                             newTP),
+                                             __FILE__,
+                                             line));
+    }
+
+
+    NegInfInterval!TP  _interval;
+    TP delegate(in TP) _func;
+}
+
+//Test that NegInfIntervalRange satisfies the range predicates that it's supposed to satisfy.
+unittest
+{
+    static assert(isInputRange!(NegInfIntervalRange!Date));
+    static assert(isForwardRange!(NegInfIntervalRange!Date));
+    static assert(isInfinite!(NegInfIntervalRange!Date));
+
+    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
+    //static assert(!isOutputRange!(NegInfIntervalRange!Date, Date));
+    static assert(!isBidirectionalRange!(NegInfIntervalRange!Date));
+    static assert(!isRandomAccessRange!(NegInfIntervalRange!Date));
+    static assert(!hasSwappableElements!(NegInfIntervalRange!Date));
+    static assert(!hasAssignableElements!(NegInfIntervalRange!Date));
+    static assert(!hasLength!(NegInfIntervalRange!Date));
+    static assert(!hasSlicing!(NegInfIntervalRange!Date));
+
+    static assert(is(ElementType!(NegInfIntervalRange!Date) == Date));
+    static assert(is(ElementType!(NegInfIntervalRange!TimeOfDay) == TimeOfDay));
+    static assert(is(ElementType!(NegInfIntervalRange!DateTime) == DateTime));
+}
+
+//Test construction of NegInfIntervalRange.
+unittest
+{
+    {
+        Date dateFunc(in Date date)
+        {
+            return date;
+        }
+
+        auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
+
+        auto ir = NegInfIntervalRange!Date(negInfInterval, &dateFunc);
+    }
+
+    {
+        TimeOfDay todFunc(in TimeOfDay tod)
+        {
+            return tod;
+        }
+
+        auto negInfInterval = NegInfInterval!TimeOfDay(TimeOfDay(14, 0, 0));
+
+        auto ir = NegInfIntervalRange!(TimeOfDay)(negInfInterval, &todFunc);
+    }
+
+    {
+        DateTime dtFunc(in DateTime dt)
+        {
+            return dt;
+        }
+
+        auto negInfInterval = NegInfInterval!DateTime(DateTime(2012, 1, 7, 14, 0, 0));
+
+        auto ir = NegInfIntervalRange!(DateTime)(negInfInterval, &dtFunc);
+    }
+
+    {
+        SysTime stFunc(in SysTime st)
+        {
+            return cast(SysTime)(st);
+        }
+
+        auto negInfInterval = NegInfInterval!SysTime(SysTime(DateTime(2012, 1, 7, 14, 0, 0)));
+
+        auto ir = NegInfIntervalRange!(SysTime)(negInfInterval, &stFunc);
+    }
+}
+
+//Test NegInfIntervalRange's front.
+unittest
+{
+    auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed));
+    assert(range.front == Date(2012, 1, 7));
+
+    auto poppedRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+    assert(poppedRange.front == Date(2012, 1, 4));
+
+    const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+    assert(cRange.front != Date.init);
+}
+
+//Test NegInfIntervalRange's popFront().
+unittest
+{
+    import std.range;
+
+    auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
+    auto expected = range.front;
+
+    foreach(date; take(range, 79))
+    {
+        assert(date == expected);
+        expected += dur!"days"(-7);
+    }
+
+    const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
+    static assert(!__traits(compiles, cRange.popFront()));
+}
+
+//Test NegInfIntervalRange's save.
+unittest
+{
+    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+    auto range = interval.bwdRange(func);
+
+    assert(range.save == range);
+}
+
+//Test NegInfIntervalRange's interval.
+unittest
+{
+    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+    auto range = interval.bwdRange(func);
+
+    assert(range.interval == interval);
+
+    const cRange = range;
+    assert(!cRange.interval.empty);
+}
+
+//Test NegInfIntervalRange's func.
+unittest
+{
+    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
+    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
+    auto range = interval.bwdRange(func);
+
+    assert(range.func == func);
+}
+
+
+//==============================================================================
+// Section with time zones.
+//==============================================================================
+
+/++
+    Represents a time zone. It is used with $(LREF SysTime) to indicate the time
+    zone of a $(LREF SysTime).
+  +/
+abstract class TimeZone
+{
+public:
+
+    /++
+        The name of the time zone per the TZ Database. This is the name used to
+        get a $(LREF2 .TimeZone, TimeZone) by name with $(D TimeZone.getTimeZone).
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
+              Database)<br>
+            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
+              Time Zones)
+      +/
+    @property string name() @safe const nothrow
+    {
+        return _name;
+    }
+
+
+    /++
+        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
+        when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.
+
+        However, on Windows, it may be the unabbreviated name (e.g. Pacific
+        Standard Time). Regardless, it is not the same as name.
+      +/
+    @property string stdName() @safe const nothrow
+    {
+        return _stdName;
+    }
+
+
+    /++
+        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
+        when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.
+
+        However, on Windows, it may be the unabbreviated name (e.g. Pacific
+        Daylight Time). Regardless, it is not the same as name.
+      +/
+    @property string dstName() @safe const nothrow
+    {
+        return _dstName;
+    }
+
+
+    /++
+        Whether this time zone has Daylight Savings Time at any point in time.
+        Note that for some time zone types it may not have DST for current dates
+        but will still return true for $(D hasDST) because the time zone did at
+        some point have DST.
+      +/
+    @property abstract bool hasDST() @safe const nothrow;
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and returns whether DST is effect in this
+        time zone at the given point in time.
+
+        Params:
+            stdTime = The UTC time that needs to be checked for DST in this time
+                      zone.
+      +/
+    abstract bool dstInEffect(long stdTime) @safe const nothrow;
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and converts it to this time zone's time.
+
+        Params:
+            stdTime = The UTC time that needs to be adjusted to this time zone's
+                      time.
+      +/
+    abstract long utcToTZ(long stdTime) @safe const nothrow;
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in this time zone's time and converts it to UTC (i.e. std time).
+
+        Params:
+            adjTime = The time in this time zone that needs to be adjusted to
+                      UTC time.
+      +/
+    abstract long tzToUTC(long adjTime) @safe const nothrow;
+
+
+    /++
+        Returns what the offset from UTC is at the given std time.
+        It includes the DST offset in effect at that time (if any).
+
+        Params:
+            stdTime = The UTC time for which to get the offset from UTC for this
+                      time zone.
+      +/
+    Duration utcOffsetAt(long stdTime) @safe const nothrow
+    {
+        return dur!"hnsecs"(utcToTZ(stdTime) - stdTime);
+    }
+
+
+    /++
+        $(RED Please use either PosixTimeZone.getTimeZone or
+              WindowsTimeZone.getTimeZone ($(LREF parseTZConversions) can be
+              used to convert time zone names if necessary). This function will
+              be deprecated in 2.072, because Microsoft changes their time zones
+              too often for us to compile the conversions into Phobos and have
+              them be properly up-to-date.)
+
+        Returns a $(LREF2 .TimeZone, TimeZone) with the give name per the TZ Database.
+
+        This returns a $(LREF PosixTimeZone) on Posix systems and a
+        $(LREF WindowsTimeZone) on Windows systems. For
+        $(LREF PosixTimeZone) on Windows, call $(D PosixTimeZone.getTimeZone)
+        directly and give it the location of the TZ Database time zone files on
+        disk.
+
+        On Windows, the given TZ Database name is converted to the corresponding
+        time zone name on Windows prior to calling
+        $(D WindowsTimeZone.getTimeZone). This function allows for
+        the same time zone names on both Windows and Posix systems.
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
+              Database)<br>
+            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
+              Time Zones)<br>
+            $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
+                  Windows <-> TZ Database Name Conversion Table)
+
+        Params:
+            name = The TZ Database name of the desired time zone
+
+        Throws:
+            $(LREF DateTimeException) if the given time zone could not be found.
+      +/
+    static immutable(TimeZone) getTimeZone(string name) @safe
+    {
+        version(Posix)
+            return PosixTimeZone.getTimeZone(name);
+        else version(Windows)
+        {
+            import std.format : format;
+            auto windowsTZName = tzDatabaseNameToWindowsTZName(name);
+            if(windowsTZName != null)
+            {
+                try
+                    return WindowsTimeZone.getTimeZone(windowsTZName);
+                catch(DateTimeException dte)
+                {
+                    auto oldName = _getOldName(windowsTZName);
+                    if(oldName != null)
+                        return WindowsTimeZone.getTimeZone(oldName);
+                    throw dte;
+                }
+            }
+            else
+                throw new DateTimeException(format("%s does not have an equivalent Windows time zone.", name));
+        }
+    }
+
+    ///
+    unittest
+    {
+        auto tz = TimeZone.getTimeZone("America/Los_Angeles");
+    }
+
+    // The purpose of this is to handle the case where a Windows time zone is
+    // new and exists on an up-to-date Windows box but does not exist on Windows
+    // boxes which have not been properly updated. The "date added" is included
+    // on the theory that we'll be able to remove them at some point in the
+    // the future once enough time has passed, and that way, we know how much
+    // time has passed.
+    private static string _getOldName(string windowsTZName) @safe pure nothrow
+    {
+        switch(windowsTZName)
+        {
+            case "Belarus Standard Time": return "Kaliningrad Standard Time"; // Added 2014-10-08
+            case "Russia Time Zone 10": return "Magadan Standard Time"; // Added 2014-10-08
+            case "Russia Time Zone 11": return "Magadan Standard Time"; // Added 2014-10-08
+            case "Russia Time Zone 3": return "Russian Standard Time"; // Added 2014-10-08
+            default: return null;
+        }
+    }
+
+    //Since reading in the time zone files could be expensive, most unit tests
+    //are consolidated into this one unittest block which minimizes how often it
+    //reads a time zone file.
+    unittest
+    {
+        import std.path : chainPath;
+        import std.file : exists, isFile;
+        import std.conv : to;
+        import std.format : format;
+
+
+        version(Posix) scope(exit) clearTZEnvVar();
+
+        static immutable(TimeZone) testTZ(string tzName,
+                                          string stdName,
+                                          string dstName,
+                                          Duration utcOffset,
+                                          Duration dstOffset,
+                                          bool north = true)
+        {
+            scope(failure) writefln("Failed time zone: %s", tzName);
+
+            immutable tz = TimeZone.getTimeZone(tzName);
+            immutable hasDST = dstOffset != dur!"hnsecs"(0);
+
+            version(Posix)
+                assert(tz.name == tzName);
+            else version(Windows)
+                assert(tz.name == stdName);
+
+            //assert(tz.stdName == stdName);  //Locale-dependent
+            //assert(tz.dstName == dstName);  //Locale-dependent
+            assert(tz.hasDST == hasDST);
+
+            immutable stdDate = DateTime(2010, north ? 1 : 7, 1, 6, 0, 0);
+            immutable dstDate = DateTime(2010, north ? 7 : 1, 1, 6, 0, 0);
+            auto std = SysTime(stdDate, tz);
+            auto dst = SysTime(dstDate, tz);
+            auto stdUTC = SysTime(stdDate - utcOffset, UTC());
+            auto dstUTC = SysTime(stdDate - utcOffset + dstOffset, UTC());
+
+            assert(!std.dstInEffect);
+            assert(dst.dstInEffect == hasDST);
+            assert(tz.utcOffsetAt(std.stdTime) == utcOffset);
+            assert(tz.utcOffsetAt(dst.stdTime) == utcOffset + dstOffset);
+
+            assert(cast(DateTime)std == stdDate);
+            assert(cast(DateTime)dst == dstDate);
+            assert(std == stdUTC);
+
+            version(Posix)
+            {
+                setTZEnvVar(tzName);
+
+                static void testTM(in SysTime st)
+                {
+                    time_t unixTime = st.toUnixTime();
+                    tm* osTimeInfo = localtime(&unixTime);
+                    tm ourTimeInfo = st.toTM();
+
+                    assert(ourTimeInfo.tm_sec == osTimeInfo.tm_sec);
+                    assert(ourTimeInfo.tm_min == osTimeInfo.tm_min);
+                    assert(ourTimeInfo.tm_hour == osTimeInfo.tm_hour);
+                    assert(ourTimeInfo.tm_mday == osTimeInfo.tm_mday);
+                    assert(ourTimeInfo.tm_mon == osTimeInfo.tm_mon);
+                    assert(ourTimeInfo.tm_year == osTimeInfo.tm_year);
+                    assert(ourTimeInfo.tm_wday == osTimeInfo.tm_wday);
+                    assert(ourTimeInfo.tm_yday == osTimeInfo.tm_yday);
+                    assert(ourTimeInfo.tm_isdst == osTimeInfo.tm_isdst);
+                    assert(ourTimeInfo.tm_gmtoff == osTimeInfo.tm_gmtoff);
+                    assert(to!string(ourTimeInfo.tm_zone) ==
+                                     to!string(osTimeInfo.tm_zone));
+                }
+
+                testTM(std);
+                testTM(dst);
+
+                //Apparently, right/ does not exist on Mac OS X. I don't know
+                //whether or not it exists on FreeBSD. It's rather pointless
+                //normally, since the Posix standard requires that leap seconds
+                //be ignored, so it does make some sense that right/ wouldn't
+                //be there, but since PosixTimeZone _does_ use leap seconds if
+                //the time zone file does, we'll test that functionality if the
+                //appropriate files exist.
+                if (chainPath(PosixTimeZone.defaultTZDatabaseDir, "right", tzName).exists)
+                {
+                    auto leapTZ = PosixTimeZone.getTimeZone("right/" ~ tzName);
+
+                    assert(leapTZ.name == "right/" ~ tzName);
+                    //assert(leapTZ.stdName == stdName);  //Locale-dependent
+                    //assert(leapTZ.dstName == dstName);  //Locale-dependent
+                    assert(leapTZ.hasDST == hasDST);
+
+                    auto leapSTD = SysTime(std.stdTime, leapTZ);
+                    auto leapDST = SysTime(dst.stdTime, leapTZ);
+
+                    assert(!leapSTD.dstInEffect);
+                    assert(leapDST.dstInEffect == hasDST);
+
+                    assert(leapSTD.stdTime == std.stdTime);
+                    assert(leapDST.stdTime == dst.stdTime);
+
+                    //Whenever a leap second is added/removed,
+                    //this will have to be adjusted.
+                    //enum leapDiff = convert!("seconds", "hnsecs")(25);
+                    //assert(leapSTD.adjTime - leapDiff == std.adjTime);
+                    //assert(leapDST.adjTime - leapDiff == dst.adjTime);
+                }
+            }
+
+            return tz;
+        }
+
+        auto dstSwitches = [/+America/Los_Angeles+/ tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
+                            /+America/New_York+/    tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
+                            ///+America/Santiago+/    tuple(DateTime(2011, 8, 21),  DateTime(2011, 5, 8), 0, 0),
+                            /+Europe/London+/       tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 1, 2),
+                            /+Europe/Paris+/        tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 2, 3),
+                            /+Australia/Adelaide+/  tuple(DateTime(2012, 10, 7),  DateTime(2012, 4, 1), 2, 3)];
+
+        version(Posix)
+        {
+            version(FreeBSD)      enum utcZone = "Etc/UTC";
+            else version(NetBSD)  enum utcZone = "UTC";
+            else version(linux)   enum utcZone = "UTC";
+            else version(OSX)     enum utcZone = "UTC";
+            else static assert(0, "The location of the UTC timezone file on this Posix platform must be set.");
+
+            auto tzs = [testTZ("America/Los_Angeles", "PST", "PDT", dur!"hours"(-8), dur!"hours"(1)),
+                        testTZ("America/New_York", "EST", "EDT", dur!"hours"(-5), dur!"hours"(1)),
+                        //testTZ("America/Santiago", "CLT", "CLST", dur!"hours"(-4), dur!"hours"(1), false),
+                        testTZ("Europe/London", "GMT", "BST", dur!"hours"(0), dur!"hours"(1)),
+                        testTZ("Europe/Paris", "CET", "CEST", dur!"hours"(1), dur!"hours"(1)),
+                        //Per www.timeanddate.com, it should be "CST" and "CDT",
+                        //but the OS insists that it's "CST" for both. We should
+                        //probably figure out how to report an error in the TZ
+                        //database and report it.
+                        testTZ("Australia/Adelaide", "CST", "CST",
+                               dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];
+
+            testTZ(utcZone, "UTC", "UTC", dur!"hours"(0), dur!"hours"(0));
+            assertThrown!DateTimeException(PosixTimeZone.getTimeZone("hello_world"));
+        }
+        else version(Windows)
+        {
+            auto tzs = [testTZ("America/Los_Angeles", "Pacific Standard Time",
+                               "Pacific Daylight Time", dur!"hours"(-8), dur!"hours"(1)),
+                        testTZ("America/New_York", "Eastern Standard Time",
+                               "Eastern Daylight Time", dur!"hours"(-5), dur!"hours"(1)),
+                        //testTZ("America/Santiago", "Pacific SA Standard Time",
+                               //"Pacific SA Daylight Time", dur!"hours"(-4), dur!"hours"(1), false),
+                        testTZ("Europe/London", "GMT Standard Time",
+                               "GMT Daylight Time", dur!"hours"(0), dur!"hours"(1)),
+                        testTZ("Europe/Paris", "Romance Standard Time",
+                               "Romance Daylight Time", dur!"hours"(1), dur!"hours"(1)),
+                        testTZ("Australia/Adelaide", "Cen. Australia Standard Time",
+                               "Cen. Australia Daylight Time",
+                               dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];
+
+            testTZ("Atlantic/Reykjavik", "Greenwich Standard Time",
+                   "Greenwich Daylight Time", dur!"hours"(0), dur!"hours"(0));
+            assertThrown!DateTimeException(WindowsTimeZone.getTimeZone("hello_world"));
+        }
+        else
+            assert(0, "OS not supported.");
+
+        foreach(i; 0 .. tzs.length)
+        {
+            auto tz = tzs[i];
+            immutable spring = dstSwitches[i][2];
+            immutable fall = dstSwitches[i][3];
+            auto stdOffset = SysTime(dstSwitches[i][0] + dur!"days"(-1), tz).utcOffset;
+            auto dstOffset = stdOffset + dur!"hours"(1);
+
+            //Verify that creating a SysTime in the given time zone results
+            //in a SysTime with the correct std time during and surrounding
+            //a DST switch.
+            foreach(hour; -12 .. 13)
+            {
+                auto st = SysTime(dstSwitches[i][0] + dur!"hours"(hour), tz);
+                immutable targetHour = hour < 0 ? hour + 24 : hour;
+
+                static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
+                {
+                    enforce(st.hour == hour,
+                            new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
+                                            __FILE__, line));
+                }
+
+                void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
+                {
+                    AssertError msg(string tag)
+                    {
+                        return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
+                                                      tag, st, tz.name, st.utcOffset, stdOffset, dstOffset),
+                                               __FILE__, line);
+                    }
+
+                    enforce(st.dstInEffect == dstInEffect, msg("1"));
+                    enforce(st.utcOffset == offset, msg("2"));
+                    enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
+                }
+
+                if(hour == spring)
+                {
+                    testHour(st, spring + 1, tz.name);
+                    testHour(st + dur!"minutes"(1), spring + 1, tz.name);
+                }
+                else
+                {
+                    testHour(st, targetHour, tz.name);
+                    testHour(st + dur!"minutes"(1), targetHour, tz.name);
+                }
+
+                if(hour < spring)
+                    testOffset1(stdOffset, false);
+                else
+                    testOffset1(dstOffset, true);
+
+                st = SysTime(dstSwitches[i][1] + dur!"hours"(hour), tz);
+                testHour(st, targetHour, tz.name);
+
+                //Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
+                if(hour == fall - 1)
+                    testHour(st + dur!"hours"(1), targetHour, tz.name);
+
+                    if(hour < fall)
+                        testOffset1(dstOffset, true);
+                    else
+                        testOffset1(stdOffset, false);
+            }
+
+            //Verify that converting a time in UTC to a time in another
+            //time zone results in the correct time during and surrounding
+            //a DST switch.
+            bool first = true;
+            auto springSwitch = SysTime(dstSwitches[i][0] + dur!"hours"(spring), UTC()) - stdOffset;
+            auto fallSwitch = SysTime(dstSwitches[i][1] + dur!"hours"(fall), UTC()) - dstOffset;
+            //@@@BUG@@@ 3659 makes this necessary.
+            auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);
+
+            foreach(hour; -24 .. 25)
+            {
+                auto utc = SysTime(dstSwitches[i][0] + dur!"hours"(hour), UTC());
+                auto local = utc.toOtherTZ(tz);
+
+                void testOffset2(Duration offset, size_t line = __LINE__)
+                {
+                    AssertError msg(string tag)
+                    {
+                        return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tz.name, utc, local),
+                                               __FILE__, line);
+                    }
+
+                    enforce((utc + offset).hour == local.hour, msg("1"));
+                    enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
+                }
+
+                if(utc < springSwitch)
+                    testOffset2(stdOffset);
+                else
+                    testOffset2(dstOffset);
+
+                utc = SysTime(dstSwitches[i][1] + dur!"hours"(hour), UTC());
+                local = utc.toOtherTZ(tz);
+
+                if(utc == fallSwitch || utc == fallSwitchMinus1)
+                {
+                    if(first)
+                    {
+                        testOffset2(dstOffset);
+                        first = false;
+                    }
+                    else
+                        testOffset2(stdOffset);
+                }
+                else if(utc > fallSwitch)
+                    testOffset2(stdOffset);
+                else
+                    testOffset2(dstOffset);
+            }
+        }
+    }
+
+
+    /++
+        Returns a list of the names of the time zones installed on the system.
+
+        Providing a sub-name narrows down the list of time zones (which
+        can number in the thousands). For example,
+        passing in "America" as the sub-name returns only the time zones which
+        begin with "America".
+
+        On Windows, this function will convert the Windows time zone names to
+        the corresponding TZ Database names with
+        $(D windowsTZNameToTZDatabaseName). To get the actual Windows time
+        zone names, use $(D WindowsTimeZone.getInstalledTZNames) directly.
+
+        Params:
+            subName = The first part of the time zones desired.
+
+        Throws:
+            $(D FileException) on Posix systems if it fails to read from disk.
+            $(LREF DateTimeException) on Windows systems if it fails to read the
+            registry.
+      +/
+    static string[] getInstalledTZNames(string subName = "") @safe
+    {
+        version(Posix)
+            return PosixTimeZone.getInstalledTZNames(subName);
+        else version(Windows)
+        {
+            import std.array : appender;
+            import std.algorithm : startsWith, sort;
+
+            auto windowsNames = WindowsTimeZone.getInstalledTZNames();
+            auto retval = appender!(string[])();
+
+            foreach(winName; windowsNames)
+            {
+                auto tzName = windowsTZNameToTZDatabaseName(winName);
+                if(tzName !is null && tzName.startsWith(subName))
+                    retval.put(tzName);
+            }
+
+            sort(retval.data);
+            return retval.data;
+        }
+    }
+
+    unittest
+    {
+        static void testPZSuccess(string tzName)
+        {
+            scope(failure) writefln("TZName which threw: %s", tzName);
+            TimeZone.getTimeZone(tzName);
+        }
+
+        auto tzNames = getInstalledTZNames();
+        // This was not previously tested, and it's currently failing, so I'm
+        // leaving it commented out until I can sort it out.
+        //assert(equal(tzNames, tzNames.uniq()));
+
+        foreach(tzName; tzNames)
+            assertNotThrown!DateTimeException(testPZSuccess(tzName));
+    }
+
+
+private:
+
+    /+
+        Params:
+            name    = The TZ Database name for the time zone.
+            stdName = The abbreviation for the time zone during std time.
+            dstName = The abbreviation for the time zone during DST.
+      +/
+    this(string name, string stdName, string dstName) @safe immutable pure
+    {
+        _name = name;
+        _stdName = stdName;
+        _dstName = dstName;
+    }
+
+
+    immutable string _name;
+    immutable string _stdName;
+    immutable string _dstName;
+}
+
+
+/++
+    A TimeZone which represents the current local time zone on
+    the system running your program.
+
+    This uses the underlying C calls to adjust the time rather than using
+    specific D code based off of system settings to calculate the time such as
+    $(LREF PosixTimeZone) and $(LREF WindowsTimeZone) do. That also means that it will
+    use whatever the current time zone is on the system, even if the system's
+    time zone changes while the program is running.
+  +/
+final class LocalTime : TimeZone
+{
+public:
+
+    /++
+        $(LREF LocalTime) is a singleton class. $(LREF LocalTime) returns its only
+        instance.
+      +/
+    static immutable(LocalTime) opCall() @trusted pure nothrow
+    {
+        alias FuncType = @safe pure nothrow immutable(LocalTime) function();
+        return (cast(FuncType)&singleton)();
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            The name of the time zone per the TZ Database. This is the name used to
+            get a $(LREF2 .TimeZone, TimeZone) by name with $(D TimeZone.getTimeZone).
+
+            Note that this always returns the empty string. This is because time
+            zones cannot be uniquely identified by the attributes given by the
+            OS (such as the $(D stdName) and $(D dstName)), and neither Posix
+            systems nor Windows systems provide an easy way to get the TZ
+            Database name of the local time zone.
+
+            See_Also:
+                $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
+                  Database)<br>
+                $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
+                  of Time Zones)
+          +/
+        @property override string name() @safe const nothrow;
+    }
+
+
+    /++
+        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
+        when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.
+
+        However, on Windows, it may be the unabbreviated name (e.g. Pacific
+        Standard Time). Regardless, it is not the same as name.
+
+        This property is overridden because the local time of the system could
+        change while the program is running and we need to determine it
+        dynamically rather than it being fixed like it would be with most time
+        zones.
+      +/
+    @property override string stdName() @trusted const nothrow
+    {
+        version(Posix)
+        {
+            import std.conv : to;
+            try
+                return to!string(tzname[0]);
+            catch(Exception e)
+                assert(0, "to!string(tzname[0]) failed.");
+        }
+        else version(Windows)
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            //Cannot use to!string() like this should, probably due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5016
+            //return to!string(tzInfo.StandardName);
+
+            wchar[32] str;
+
+            foreach(i, ref wchar c; str)
+                c = tzInfo.StandardName[i];
+
+            string retval;
+
+            try
+            {
+                foreach(dchar c; str)
+                {
+                    if(c == '\0')
+                        break;
+
+                    retval ~= c;
+                }
+
+                return retval;
+            }
+            catch(Exception e)
+                assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
+        }
+    }
+
+    unittest
+{
+        assert(LocalTime().stdName !is null);
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            setTZEnvVar("America/Los_Angeles");
+            assert(LocalTime().stdName == "PST");
+
+            setTZEnvVar("America/New_York");
+            assert(LocalTime().stdName == "EST");
+        }
+    }
+
+
+    /++
+        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
+        when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.
+
+        However, on Windows, it may be the unabbreviated name (e.g. Pacific
+        Daylight Time). Regardless, it is not the same as name.
+
+        This property is overridden because the local time of the system could
+        change while the program is running and we need to determine it
+        dynamically rather than it being fixed like it would be with most time
+        zones.
+      +/
+    @property override string dstName() @trusted const nothrow
+    {
+        version(Posix)
+        {
+            import std.conv : to;
+            try
+                return to!string(tzname[1]);
+            catch(Exception e)
+                assert(0, "to!string(tzname[1]) failed.");
+        }
+        else version(Windows)
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            //Cannot use to!string() like this should, probably due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5016
+            //return to!string(tzInfo.DaylightName);
+
+            wchar[32] str;
+
+            foreach(i, ref wchar c; str)
+                c = tzInfo.DaylightName[i];
+
+            string retval;
+
+            try
+            {
+                foreach(dchar c; str)
+                {
+                    if(c == '\0')
+                        break;
+
+                    retval ~= c;
+                }
+
+                return retval;
+            }
+            catch(Exception e)
+                assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
+        }
+    }
+
+    unittest
+{
+        assert(LocalTime().dstName !is null);
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            version(FreeBSD)
+            {
+                // A bug on FreeBSD 9+ makes it so that this test fails.
+                // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862
+            }
+            else version(NetBSD)
+            {
+                // The same bug on NetBSD 7+
+            }
+            else
+            {
+                setTZEnvVar("America/Los_Angeles");
+                assert(LocalTime().dstName == "PDT");
+            }
+
+            setTZEnvVar("America/New_York");
+            assert(LocalTime().dstName == "EDT");
+        }
+    }
+
+
+    /++
+        Whether this time zone has Daylight Savings Time at any point in time.
+        Note that for some time zone types it may not have DST for current
+        dates but will still return true for $(D hasDST) because the time zone
+        did at some point have DST.
+      +/
+    @property override bool hasDST() @trusted const nothrow
+    {
+        version(Posix)
+        {
+            static if(is(typeof(daylight)))
+                return cast(bool)(daylight);
+            else
+            {
+                try
+                {
+                    auto currYear = (cast(Date)Clock.currTime()).year;
+                    auto janOffset = SysTime(Date(currYear, 1, 4), cast(immutable)this).stdTime -
+                                     SysTime(Date(currYear, 1, 4), UTC()).stdTime;
+                    auto julyOffset = SysTime(Date(currYear, 7, 4), cast(immutable)this).stdTime -
+                                      SysTime(Date(currYear, 7, 4), UTC()).stdTime;
+
+                    return janOffset != julyOffset;
+                }
+                catch(Exception e)
+                    assert(0, "Clock.currTime() threw.");
+            }
+        }
+        else version(Windows)
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            return tzInfo.DaylightDate.wMonth != 0;
+        }
+    }
+
+    unittest
+{
+        LocalTime().hasDST;
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            setTZEnvVar("America/Los_Angeles");
+            assert(LocalTime().hasDST);
+
+            setTZEnvVar("America/New_York");
+            assert(LocalTime().hasDST);
+
+            setTZEnvVar("UTC");
+            assert(!LocalTime().hasDST);
+        }
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and returns whether DST is in effect in this
+        time zone at the given point in time.
+
+        Params:
+            stdTime = The UTC time that needs to be checked for DST in this time
+                      zone.
+      +/
+    override bool dstInEffect(long stdTime) @trusted const nothrow
+    {
+        time_t unixTime = stdTimeToUnixTime(stdTime);
+
+        version(Posix)
+        {
+            tm* timeInfo = localtime(&unixTime);
+
+            return cast(bool)(timeInfo.tm_isdst);
+        }
+        else version(Windows)
+        {
+            //Apparently Windows isn't smart enough to deal with negative time_t.
+            if(unixTime >= 0)
+            {
+                tm* timeInfo = localtime(&unixTime);
+
+                if(timeInfo)
+                    return cast(bool)(timeInfo.tm_isdst);
+            }
+
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            return WindowsTimeZone._dstInEffect(&tzInfo, stdTime);
+        }
+    }
+
+    unittest
+    {
+        auto currTime = Clock.currStdTime;
+        LocalTime().dstInEffect(currTime);
+    }
+
+
+    /++
+        Returns hnsecs in the local time zone using the standard C function
+        calls on Posix systems and the standard Windows system calls on Windows
+        systems to adjust the time to the appropriate time zone from std time.
+
+        Params:
+            stdTime = The UTC time that needs to be adjusted to this time zone's
+                      time.
+
+        See_Also:
+            $(D TimeZone.utcToTZ)
+      +/
+    override long utcToTZ(long stdTime) @trusted const nothrow
+    {
+        version(Posix)
+        {
+            time_t unixTime = stdTimeToUnixTime(stdTime);
+            tm* timeInfo = localtime(&unixTime);
+
+            return stdTime + convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
+        }
+        else version(Windows)
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            return WindowsTimeZone._utcToTZ(&tzInfo, stdTime, hasDST);
+        }
+    }
+
+    unittest
+    {
+        LocalTime().utcToTZ(0);
+    }
+
+
+    /++
+        Returns std time using the standard C function calls on Posix systems
+        and the standard Windows system calls on Windows systems to adjust the
+        time to UTC from the appropriate time zone.
+
+        See_Also:
+            $(D TimeZone.tzToUTC)
+
+        Params:
+            adjTime = The time in this time zone that needs to be adjusted to
+                      UTC time.
+      +/
+    override long tzToUTC(long adjTime) @trusted const nothrow
+    {
+        version(Posix)
+        {
+            time_t unixTime = stdTimeToUnixTime(adjTime);
+
+            immutable past = unixTime - cast(time_t)convert!("days", "seconds")(1);
+            tm* timeInfo = localtime(past < unixTime ? &past : &unixTime);
+            immutable pastOffset = timeInfo.tm_gmtoff;
+
+            immutable future = unixTime + cast(time_t)convert!("days", "seconds")(1);
+            timeInfo = localtime(future > unixTime ? &future : &unixTime);
+            immutable futureOffset = timeInfo.tm_gmtoff;
+
+            if(pastOffset == futureOffset)
+                return adjTime - convert!("seconds", "hnsecs")(pastOffset);
+
+            if(pastOffset < futureOffset)
+                unixTime -= cast(time_t)convert!("hours", "seconds")(1);
+
+            unixTime -= pastOffset;
+            timeInfo = localtime(&unixTime);
+
+            return adjTime - convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
+        }
+        else version(Windows)
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            return WindowsTimeZone._tzToUTC(&tzInfo, adjTime, hasDST);
+        }
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
+        assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);
+
+        assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
+        assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            auto tzInfos = [tuple("America/Los_Angeles", DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
+                            tuple("America/New_York",    DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
+                            //tuple("America/Santiago",    DateTime(2011, 8, 21), DateTime(2011, 5, 8), 0, 0),
+                            tuple("Atlantic/Azores",     DateTime(2011, 3, 27), DateTime(2011, 10, 30), 0, 1),
+                            tuple("Europe/London",       DateTime(2012, 3, 25), DateTime(2012, 10, 28), 1, 2),
+                            tuple("Europe/Paris",        DateTime(2012, 3, 25), DateTime(2012, 10, 28), 2, 3),
+                            tuple("Australia/Adelaide",  DateTime(2012, 10, 7), DateTime(2012, 4, 1), 2, 3)];
+
+            foreach(i; 0 .. tzInfos.length)
+            {
+                auto tzName = tzInfos[i][0];
+                setTZEnvVar(tzName);
+                immutable spring = tzInfos[i][3];
+                immutable fall = tzInfos[i][4];
+                auto stdOffset = SysTime(tzInfos[i][1] + dur!"hours"(-12)).utcOffset;
+                auto dstOffset = stdOffset + dur!"hours"(1);
+
+                //Verify that creating a SysTime in the given time zone results
+                //in a SysTime with the correct std time during and surrounding
+                //a DST switch.
+                foreach(hour; -12 .. 13)
+                {
+                    auto st = SysTime(tzInfos[i][1] + dur!"hours"(hour));
+                    immutable targetHour = hour < 0 ? hour + 24 : hour;
+
+                    static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
+                    {
+                        enforce(st.hour == hour,
+                                new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
+                                                __FILE__, line));
+                    }
+
+                    void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
+                    {
+                        AssertError msg(string tag)
+                        {
+                            return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
+                                                          tag, st, tzName, st.utcOffset, stdOffset, dstOffset),
+                                                   __FILE__, line);
+                        }
+
+                        enforce(st.dstInEffect == dstInEffect, msg("1"));
+                        enforce(st.utcOffset == offset, msg("2"));
+                        enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
+                    }
+
+                    if(hour == spring)
+                    {
+                        testHour(st, spring + 1, tzName);
+                        testHour(st + dur!"minutes"(1), spring + 1, tzName);
+                    }
+                    else
+                    {
+                        testHour(st, targetHour, tzName);
+                        testHour(st + dur!"minutes"(1), targetHour, tzName);
+                    }
+
+                    if(hour < spring)
+                        testOffset1(stdOffset, false);
+                    else
+                        testOffset1(dstOffset, true);
+
+                    st = SysTime(tzInfos[i][2] + dur!"hours"(hour));
+                    testHour(st, targetHour, tzName);
+
+                    //Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
+                    if(hour == fall - 1)
+                        testHour(st + dur!"hours"(1), targetHour, tzName);
+
+                    if(hour < fall)
+                        testOffset1(dstOffset, true);
+                    else
+                        testOffset1(stdOffset, false);
+                }
+
+                //Verify that converting a time in UTC to a time in another
+                //time zone results in the correct time during and surrounding
+                //a DST switch.
+                bool first = true;
+                auto springSwitch = SysTime(tzInfos[i][1] + dur!"hours"(spring), UTC()) - stdOffset;
+                auto fallSwitch = SysTime(tzInfos[i][2] + dur!"hours"(fall), UTC()) - dstOffset;
+                //@@@BUG@@@ 3659 makes this necessary.
+                auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);
+
+                foreach(hour; -24 .. 25)
+                {
+                    auto utc = SysTime(tzInfos[i][1] + dur!"hours"(hour), UTC());
+                    auto local = utc.toLocalTime();
+
+                    void testOffset2(Duration offset, size_t line = __LINE__)
+                    {
+                        AssertError msg(string tag)
+                        {
+                            return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tzName, utc, local),
+                                                   __FILE__, line);
+                        }
+
+                        enforce((utc + offset).hour == local.hour, msg("1"));
+                        enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
+                    }
+
+                    if(utc < springSwitch)
+                        testOffset2(stdOffset);
+                    else
+                        testOffset2(dstOffset);
+
+                    utc = SysTime(tzInfos[i][2] + dur!"hours"(hour), UTC());
+                    local = utc.toLocalTime();
+
+                    if(utc == fallSwitch || utc == fallSwitchMinus1)
+                    {
+                        if(first)
+                        {
+                            testOffset2(dstOffset);
+                            first = false;
+                        }
+                        else
+                            testOffset2(stdOffset);
+                    }
+                    else if(utc > fallSwitch)
+                        testOffset2(stdOffset);
+                    else
+                        testOffset2(dstOffset);
+                }
+            }
+        }
+    }
+
+
+private:
+
+    this() @safe immutable pure
+    {
+        super("", "", "");
+    }
+
+
+    // This is done so that we can maintain purity in spite of doing an impure
+    // operation the first time that LocalTime() is called.
+    static immutable(LocalTime) singleton() @trusted
+    {
+        import std.concurrency : initOnce;
+        static instance = new immutable(LocalTime)();
+        static shared bool guard;
+        initOnce!guard({tzset(); return true;}());
+        return instance;
+    }
+}
+
+
+/++
+    A $(LREF2 .TimeZone, TimeZone) which represents UTC.
+  +/
+final class UTC : TimeZone
+{
+public:
+
+    /++
+        $(D UTC) is a singleton class. $(D UTC) returns its only instance.
+      +/
+    static immutable(UTC) opCall() @safe pure nothrow
+    {
+        return _utc;
+    }
+
+
+    /++
+        Always returns false.
+      +/
+    @property override bool hasDST() @safe const nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Always returns false.
+      +/
+    override bool dstInEffect(long stdTime) @safe const nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Returns the given hnsecs without changing them at all.
+
+        Params:
+            stdTime = The UTC time that needs to be adjusted to this time zone's
+                      time.
+
+        See_Also:
+            $(D TimeZone.utcToTZ)
+      +/
+    override long utcToTZ(long stdTime) @safe const nothrow
+    {
+        return stdTime;
+    }
+
+    unittest
+    {
+        assert(UTC().utcToTZ(0) == 0);
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            setTZEnvVar("UTC");
+            auto std = SysTime(Date(2010, 1, 1));
+            auto dst = SysTime(Date(2010, 7, 1));
+            assert(UTC().utcToTZ(std.stdTime) == std.stdTime);
+            assert(UTC().utcToTZ(dst.stdTime) == dst.stdTime);
+        }
+    }
+
+
+    /++
+        Returns the given hnsecs without changing them at all.
+
+        See_Also:
+            $(D TimeZone.tzToUTC)
+
+        Params:
+            adjTime = The time in this time zone that needs to be adjusted to
+                      UTC time.
+      +/
+    override long tzToUTC(long adjTime) @safe const nothrow
+    {
+        return adjTime;
+    }
+
+    unittest
+    {
+        assert(UTC().tzToUTC(0) == 0);
+
+        version(Posix)
+        {
+            scope(exit) clearTZEnvVar();
+
+            setTZEnvVar("UTC");
+            auto std = SysTime(Date(2010, 1, 1));
+            auto dst = SysTime(Date(2010, 7, 1));
+            assert(UTC().tzToUTC(std.stdTime) == std.stdTime);
+            assert(UTC().tzToUTC(dst.stdTime) == dst.stdTime);
+        }
+    }
+
+
+    /++
+        Returns a $(CXREF time, Duration) of 0.
+
+        Params:
+            stdTime = The UTC time for which to get the offset from UTC for this
+                      time zone.
+      +/
+    override Duration utcOffsetAt(long stdTime) @safe const nothrow
+    {
+        return dur!"hnsecs"(0);
+    }
+
+
+private:
+
+    this() @safe immutable pure
+    {
+        super("UTC", "UTC", "UTC");
+    }
+
+
+    static immutable UTC _utc = new immutable(UTC)();
+}
+
+
+/++
+    Represents a time zone with an offset (in minutes, west is negative) from
+    UTC but no DST.
+
+    It's primarily used as the time zone in the result of $(LREF SysTime)'s
+    $(D fromISOString), $(D fromISOExtString), and $(D fromSimpleString).
+
+    $(D name) and $(D dstName) are always the empty string since this time zone
+    has no DST, and while it may be meant to represent a time zone which is in
+    the TZ Database, obviously it's not likely to be following the exact rules
+    of any of the time zones in the TZ Database, so it makes no sense to set it.
+  +/
+final class SimpleTimeZone : TimeZone
+{
+public:
+
+    /++
+        Always returns false.
+      +/
+    @property override bool hasDST() @safe const nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Always returns false.
+      +/
+    override bool dstInEffect(long stdTime) @safe const nothrow
+    {
+        return false;
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and converts it to this time zone's time.
+
+        Params:
+            stdTime = The UTC time that needs to be adjusted to this time zone's
+                      time.
+      +/
+    override long utcToTZ(long stdTime) @safe const nothrow
+    {
+        return stdTime + _utcOffset.total!"hnsecs";
+    }
+
+    unittest
+    {
+        auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
+        auto east = new immutable SimpleTimeZone(dur!"hours"(8));
+
+        assert(west.utcToTZ(0) == -288_000_000_000L);
+        assert(east.utcToTZ(0) == 288_000_000_000L);
+        assert(west.utcToTZ(54_321_234_567_890L) == 54_033_234_567_890L);
+
+        const cstz = west;
+        assert(cstz.utcToTZ(50002) == west.utcToTZ(50002));
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in this time zone's time and converts it to UTC (i.e. std time).
+
+        Params:
+            adjTime = The time in this time zone that needs to be adjusted to
+                      UTC time.
+      +/
+    override long tzToUTC(long adjTime) @safe const nothrow
+    {
+        return adjTime - _utcOffset.total!"hnsecs";
+    }
+
+    unittest
+    {
+        auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
+        auto east = new immutable SimpleTimeZone(dur!"hours"(8));
+
+        assert(west.tzToUTC(-288_000_000_000L) == 0);
+        assert(east.tzToUTC(288_000_000_000L) == 0);
+        assert(west.tzToUTC(54_033_234_567_890L) == 54_321_234_567_890L);
+
+        const cstz = west;
+        assert(cstz.tzToUTC(20005) == west.tzToUTC(20005));
+    }
+
+
+    /++
+        Returns utcOffset as a $(CXREF time, Duration).
+
+        Params:
+            stdTime = The UTC time for which to get the offset from UTC for this
+                      time zone.
+      +/
+    override Duration utcOffsetAt(long stdTime) @safe const nothrow
+    {
+        return _utcOffset;
+    }
+
+
+    /++
+        Params:
+            utcOffset = This time zone's offset from UTC with west of UTC being
+                        negative (it is added to UTC to get the adjusted time).
+            stdName   = The $(D stdName) for this time zone.
+      +/
+    this(Duration utcOffset, string stdName = "") @safe immutable pure
+    {
+        //FIXME This probably needs to be changed to something like (-12 - 13).
+        enforce!DateTimeException(abs(utcOffset) < dur!"minutes"(1440),
+                                    "Offset from UTC must be within range (-24:00 - 24:00).");
+
+        super("", stdName, "");
+        this._utcOffset = utcOffset;
+    }
+
+    // Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+    deprecated("Please use the overload which takes a Duration.")
+    this(int utcOffset, string stdName = "") @safe immutable pure
+    {
+        this(dur!"minutes"(utcOffset), stdName);
+    }
+
+    unittest
+    {
+        auto stz = new immutable SimpleTimeZone(dur!"hours"(-8), "PST");
+        assert(stz.name == "");
+        assert(stz.stdName == "PST");
+        assert(stz.dstName == "");
+        assert(stz.utcOffset == dur!"hours"(-8));
+    }
+
+
+    /++
+        The amount of time the offset from UTC is (negative is west of UTC,
+        positive is east).
+      +/
+    @property Duration utcOffset() @safe const pure nothrow
+    {
+        return _utcOffset;
+    }
+
+
+private:
+
+    /+
+        Returns a time zone as a string with an offset from UTC.
+
+        Time zone offsets will be in the form +HHMM or -HHMM.
+
+        Params:
+            utcOffset = The number of minutes offset from UTC (negative means
+                        west).
+      +/
+    static string toISOString(Duration utcOffset) @safe pure
+    {
+        import std.format : format;
+        immutable absOffset = abs(utcOffset);
+        enforce!DateTimeException(absOffset < dur!"minutes"(1440),
+                                  "Offset from UTC must be within range (-24:00 - 24:00).");
+        int hours;
+        int minutes;
+        absOffset.split!("hours", "minutes")(hours, minutes);
+        return format(utcOffset < Duration.zero ? "-%02d%02d" : "+%02d%02d", hours, minutes);
+    }
+
+    unittest
+    {
+        static string testSTZInvalid(Duration offset)
+        {
+            return SimpleTimeZone.toISOString(offset);
+        }
+
+        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
+        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));
+
+        assert(toISOString(dur!"minutes"(0)) == "+0000");
+        assert(toISOString(dur!"minutes"(1)) == "+0001");
+        assert(toISOString(dur!"minutes"(10)) == "+0010");
+        assert(toISOString(dur!"minutes"(59)) == "+0059");
+        assert(toISOString(dur!"minutes"(60)) == "+0100");
+        assert(toISOString(dur!"minutes"(90)) == "+0130");
+        assert(toISOString(dur!"minutes"(120)) == "+0200");
+        assert(toISOString(dur!"minutes"(480)) == "+0800");
+        assert(toISOString(dur!"minutes"(1439)) == "+2359");
+
+        assert(toISOString(dur!"minutes"(-1)) == "-0001");
+        assert(toISOString(dur!"minutes"(-10)) == "-0010");
+        assert(toISOString(dur!"minutes"(-59)) == "-0059");
+        assert(toISOString(dur!"minutes"(-60)) == "-0100");
+        assert(toISOString(dur!"minutes"(-90)) == "-0130");
+        assert(toISOString(dur!"minutes"(-120)) == "-0200");
+        assert(toISOString(dur!"minutes"(-480)) == "-0800");
+        assert(toISOString(dur!"minutes"(-1439)) == "-2359");
+    }
+
+
+    /+
+        Returns a time zone as a string with an offset from UTC.
+
+        Time zone offsets will be in the form +HH:MM or -HH:MM.
+
+        Params:
+            utcOffset = The number of minutes offset from UTC (negative means
+                        west).
+      +/
+    static string toISOExtString(Duration utcOffset) @safe pure
+    {
+        import std.format : format;
+
+        immutable absOffset = abs(utcOffset);
+        enforce!DateTimeException(absOffset < dur!"minutes"(1440),
+                                  "Offset from UTC must be within range (-24:00 - 24:00).");
+        int hours;
+        int minutes;
+        absOffset.split!("hours", "minutes")(hours, minutes);
+        return format(utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d", hours, minutes);
+    }
+
+    unittest
+    {
+        static string testSTZInvalid(Duration offset)
+        {
+            return SimpleTimeZone.toISOExtString(offset);
+        }
+
+        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
+        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));
+
+        assert(toISOExtString(dur!"minutes"(0)) == "+00:00");
+        assert(toISOExtString(dur!"minutes"(1)) == "+00:01");
+        assert(toISOExtString(dur!"minutes"(10)) == "+00:10");
+        assert(toISOExtString(dur!"minutes"(59)) == "+00:59");
+        assert(toISOExtString(dur!"minutes"(60)) == "+01:00");
+        assert(toISOExtString(dur!"minutes"(90)) == "+01:30");
+        assert(toISOExtString(dur!"minutes"(120)) == "+02:00");
+        assert(toISOExtString(dur!"minutes"(480)) == "+08:00");
+        assert(toISOExtString(dur!"minutes"(1439)) == "+23:59");
+
+        assert(toISOExtString(dur!"minutes"(-1)) == "-00:01");
+        assert(toISOExtString(dur!"minutes"(-10)) == "-00:10");
+        assert(toISOExtString(dur!"minutes"(-59)) == "-00:59");
+        assert(toISOExtString(dur!"minutes"(-60)) == "-01:00");
+        assert(toISOExtString(dur!"minutes"(-90)) == "-01:30");
+        assert(toISOExtString(dur!"minutes"(-120)) == "-02:00");
+        assert(toISOExtString(dur!"minutes"(-480)) == "-08:00");
+        assert(toISOExtString(dur!"minutes"(-1439)) == "-23:59");
+    }
+
+
+    /+
+        Takes a time zone as a string with an offset from UTC and returns a
+        $(LREF SimpleTimeZone) which matches.
+
+        The accepted formats for time zone offsets are +HH, -HH, +HHMM, and
+        -HHMM.
+
+        Params:
+            isoString = A string which represents a time zone in the ISO format.
+      +/
+    static immutable(SimpleTimeZone) fromISOString(S)(S isoString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.ascii : isDigit;
+        import std.conv : to;
+        import std.algorithm : startsWith, countUntil, all;
+        import std.format : format;
+
+        auto dstr = to!dstring(isoString);
+
+        enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String");
+
+        auto sign = dstr.startsWith('-') ? -1 : 1;
+
+        dstr.popFront();
+        enforce!DateTimeException(all!isDigit(dstr), format("Invalid ISO String: %s", dstr));
+
+        int hours;
+        int minutes;
+
+        if(dstr.length == 2)
+            hours = to!int(dstr);
+        else if(dstr.length == 4)
+        {
+            hours = to!int(dstr[0 .. 2]);
+            minutes = to!int(dstr[2 .. 4]);
+        }
+        else
+            throw new DateTimeException(format("Invalid ISO String: %s", dstr));
+
+        enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr));
+
+        return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        foreach(str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
+                      "-24:00", "+24:00", "-24", "+24", "-2400", "+2400",
+                      "1", "+1", "-1", "+9", "-9",
+                      "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
+                      "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
+                      "000", "00000", "0160", "-0160",
+                      " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
+                      " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
+                      " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
+                      " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
+                      "+ab:cd", "+abcd", "+0Z:00", "+Z", "+00Z",
+                      "-ab:cd", "+abcd", "-0Z:00", "-Z", "-00Z",
+                      "01:00", "12:00", "23:59"])
+        {
+            assertThrown!DateTimeException(SimpleTimeZone.fromISOString(str), format("[%s]", str));
+        }
+
+        static void test(string str, Duration utcOffset, size_t line = __LINE__)
+        {
+            if(SimpleTimeZone.fromISOString(str).utcOffset !=
+               (new immutable SimpleTimeZone(utcOffset)).utcOffset)
+            {
+                throw new AssertError("unittest failure", __FILE__, line);
+            }
+        }
+
+        test("+0000", Duration.zero);
+        test("+0001", minutes(1));
+        test("+0010", minutes(10));
+        test("+0059", minutes(59));
+        test("+0100", hours(1));
+        test("+0130", hours(1) + minutes(30));
+        test("+0200", hours(2));
+        test("+0800", hours(8));
+        test("+2359", hours(23) + minutes(59));
+
+        test("-0001", minutes(-1));
+        test("-0010", minutes(-10));
+        test("-0059", minutes(-59));
+        test("-0100", hours(-1));
+        test("-0130", hours(-1) - minutes(30));
+        test("-0200", hours(-2));
+        test("-0800", hours(-8));
+        test("-2359", hours(-23) - minutes(59));
+
+        test("+00", Duration.zero);
+        test("+01", hours(1));
+        test("+02", hours(2));
+        test("+12", hours(12));
+        test("+23", hours(23));
+
+        test("-00", Duration.zero);
+        test("-01", hours(-1));
+        test("-02", hours(-2));
+        test("-12", hours(-12));
+        test("-23", hours(-23));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        static void test(in string isoString, int expectedOffset, size_t line = __LINE__)
+        {
+            auto stz = SimpleTimeZone.fromISOExtString(isoString);
+            if(stz.utcOffset != dur!"minutes"(expectedOffset))
+                throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);
+
+            auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
+            if(result != isoString)
+                throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoString), __FILE__, line);
+        }
+
+        test("+00:00", 0);
+        test("+00:01", 1);
+        test("+00:10", 10);
+        test("+00:59", 59);
+        test("+01:00", 60);
+        test("+01:30", 90);
+        test("+02:00", 120);
+        test("+08:00", 480);
+        test("+08:00", 480);
+        test("+23:59", 1439);
+
+        test("-00:01", -1);
+        test("-00:10", -10);
+        test("-00:59", -59);
+        test("-01:00", -60);
+        test("-01:30", -90);
+        test("-02:00", -120);
+        test("-08:00", -480);
+        test("-08:00", -480);
+        test("-23:59", -1439);
+    }
+
+
+    /+
+        Takes a time zone as a string with an offset from UTC and returns a
+        $(LREF SimpleTimeZone) which matches.
+
+        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
+        -HH:MM.
+
+        Params:
+            isoExtString = A string which represents a time zone in the ISO format.
+      +/
+    static immutable(SimpleTimeZone) fromISOExtString(S)(S isoExtString) @safe pure
+        if(isSomeString!S)
+    {
+        import std.ascii : isDigit;
+        import std.conv : to;
+        import std.algorithm : startsWith, countUntil, all;
+        import std.format : format;
+
+        auto dstr = to!dstring(isoExtString);
+
+        enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String");
+
+        auto sign = dstr.startsWith('-') ? -1 : 1;
+
+        dstr.popFront();
+        enforce!DateTimeException(!dstr.empty, "Invalid ISO String");
+
+        immutable colon = dstr.countUntil(':');
+
+        dstring hoursStr;
+        dstring minutesStr;
+
+        if(colon != -1)
+        {
+            hoursStr = dstr[0 .. colon];
+            minutesStr = dstr[colon + 1 .. $];
+            enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", dstr));
+        }
+        else
+            hoursStr = dstr;
+
+        enforce!DateTimeException(hoursStr.length == 2, format("Invalid ISO String: %s", dstr));
+        enforce!DateTimeException(all!isDigit(hoursStr), format("Invalid ISO String: %s", dstr));
+        enforce!DateTimeException(all!isDigit(minutesStr), format("Invalid ISO String: %s", dstr));
+
+        immutable hours = to!int(hoursStr);
+        immutable minutes = minutesStr.empty ? 0 : to!int(minutesStr);
+        enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr));
+
+        return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        foreach(str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
+                      "-24:00", "+24:00", "-24", "+24", "-2400", "-2400",
+                      "1", "+1", "-1", "+9", "-9",
+                      "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
+                      "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
+                      "000", "00000", "0160", "-0160",
+                      " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
+                      " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
+                      " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
+                      " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
+                      "+ab:cd", "abcd", "+0Z:00", "+Z", "+00Z",
+                      "-ab:cd", "abcd", "-0Z:00", "-Z", "-00Z",
+                      "0100", "1200", "2359"])
+        {
+            assertThrown!DateTimeException(SimpleTimeZone.fromISOExtString(str), format("[%s]", str));
+        }
+
+        static void test(string str, Duration utcOffset, size_t line = __LINE__)
+        {
+            if(SimpleTimeZone.fromISOExtString(str).utcOffset !=
+               (new immutable SimpleTimeZone(utcOffset)).utcOffset)
+            {
+                throw new AssertError("unittest failure", __FILE__, line);
+            }
+        }
+
+        test("+00:00", Duration.zero);
+        test("+00:01", minutes(1));
+        test("+00:10", minutes(10));
+        test("+00:59", minutes(59));
+        test("+01:00", hours(1));
+        test("+01:30", hours(1) + minutes(30));
+        test("+02:00", hours(2));
+        test("+08:00", hours(8));
+        test("+23:59", hours(23) + minutes(59));
+
+        test("-00:01", minutes(-1));
+        test("-00:10", minutes(-10));
+        test("-00:59", minutes(-59));
+        test("-01:00", hours(-1));
+        test("-01:30", hours(-1) - minutes(30));
+        test("-02:00", hours(-2));
+        test("-08:00", hours(-8));
+        test("-23:59", hours(-23) - minutes(59));
+
+        test("+00", Duration.zero);
+        test("+01", hours(1));
+        test("+02", hours(2));
+        test("+12", hours(12));
+        test("+23", hours(23));
+
+        test("-00", Duration.zero);
+        test("-01", hours(-1));
+        test("-02", hours(-2));
+        test("-12", hours(-12));
+        test("-23", hours(-23));
+    }
+
+    unittest
+    {
+        import std.format : format;
+
+        static void test(in string isoExtString, int expectedOffset, size_t line = __LINE__)
+        {
+            auto stz = SimpleTimeZone.fromISOExtString(isoExtString);
+            if(stz.utcOffset != dur!"minutes"(expectedOffset))
+                throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);
+
+            auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
+            if(result != isoExtString)
+                throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoExtString), __FILE__, line);
+        }
+
+        test("+00:00", 0);
+        test("+00:01", 1);
+        test("+00:10", 10);
+        test("+00:59", 59);
+        test("+01:00", 60);
+        test("+01:30", 90);
+        test("+02:00", 120);
+        test("+08:00", 480);
+        test("+08:00", 480);
+        test("+23:59", 1439);
+
+        test("-00:01", -1);
+        test("-00:10", -10);
+        test("-00:59", -59);
+        test("-01:00", -60);
+        test("-01:30", -90);
+        test("-02:00", -120);
+        test("-08:00", -480);
+        test("-08:00", -480);
+        test("-23:59", -1439);
+    }
+
+
+    immutable Duration _utcOffset;
+}
+
+
+/++
+    Represents a time zone from a TZ Database time zone file. Files from the TZ
+    Database are how Posix systems hold their time zone information.
+    Unfortunately, Windows does not use the TZ Database. To use the TZ Database,
+    use $(D PosixTimeZone) (which reads its information from the TZ Database
+    files on disk) on Windows by providing the TZ Database files and telling
+    $(D PosixTimeZone.getTimeZone) where the directory holding them is.
+
+    To get a $(D PosixTimeZone), either call $(D PosixTimeZone.getTimeZone)
+    (which allows specifying the location the time zone files) or call
+    $(D TimeZone.getTimeZone) (which will give a $(D PosixTimeZone) on Posix
+    systems and a $(LREF WindowsTimeZone) on Windows systems).
+
+    Note:
+        Unless your system's local time zone deals with leap seconds (which is
+        highly unlikely), then the only way to get a time zone which
+        takes leap seconds into account is to use $(D PosixTimeZone) with a
+        time zone whose name starts with "right/". Those time zone files do
+        include leap seconds, and $(D PosixTimeZone) will take them into account
+        (though posix systems which use a "right/" time zone as their local time
+        zone will $(I not) take leap seconds into account even though they're
+        in the file).
+
+    See_Also:
+        $(WEB www.iana.org/time-zones, Home of the TZ Database files)<br>
+        $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ Database)<br>
+        $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of Time
+          Zones)
+  +/
+final class PosixTimeZone : TimeZone
+{
+    import std.stdio : File;
+    import std.path : extension;
+    import std.file : isDir, isFile, exists, dirEntries, SpanMode, DirEntry;
+    import std.string : strip, representation;
+    import std.algorithm : countUntil, canFind, startsWith;
+public:
+
+    /++
+        Whether this time zone has Daylight Savings Time at any point in time.
+        Note that for some time zone types it may not have DST for current
+        dates but will still return true for $(D hasDST) because the time zone
+        did at some point have DST.
+      +/
+    @property override bool hasDST() @safe const nothrow
+    {
+        return _hasDST;
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and returns whether DST is in effect in this
+        time zone at the given point in time.
+
+        Params:
+            stdTime = The UTC time that needs to be checked for DST in this time
+                      zone.
+      +/
+    override bool dstInEffect(long stdTime) @safe const nothrow
+    {
+        assert(!_transitions.empty);
+
+        immutable unixTime = stdTimeToUnixTime(stdTime);
+        immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);
+
+        if(found == -1)
+            return _transitions.back.ttInfo.isDST;
+
+        immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];
+
+        return transition.ttInfo.isDST;
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in UTC time (i.e. std time) and converts it to this time zone's time.
+
+        Params:
+            stdTime = The UTC time that needs to be adjusted to this time zone's
+                      time.
+      +/
+    override long utcToTZ(long stdTime) @safe const nothrow
+    {
+        assert(!_transitions.empty);
+
+        immutable leapSecs = calculateLeapSeconds(stdTime);
+        immutable unixTime = stdTimeToUnixTime(stdTime);
+        immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);
+
+        if(found == -1)
+            return stdTime + convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
+
+        immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];
+
+        return stdTime + convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
+    }
+
+
+    /++
+        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
+        in this time zone's time and converts it to UTC (i.e. std time).
+
+        Params:
+            adjTime = The time in this time zone that needs to be adjusted to
+                      UTC time.
+      +/
+    override long tzToUTC(long adjTime) @safe const nothrow
+    {
+        assert(!_transitions.empty);
+
+        immutable leapSecs = calculateLeapSeconds(adjTime);
+        time_t unixTime = stdTimeToUnixTime(adjTime);
+        immutable past = unixTime - convert!("days", "seconds")(1);
+        immutable future = unixTime + convert!("days", "seconds")(1);
+
+        immutable pastFound = countUntil!"b < a.timeT"(_transitions, past);
+
+        if(pastFound == -1)
+            return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
+
+        immutable futureFound = countUntil!"b < a.timeT"(_transitions[pastFound .. $], future);
+        immutable pastTrans = pastFound == 0 ? _transitions[0] : _transitions[pastFound - 1];
+
+        if(futureFound == 0)
+            return adjTime - convert!("seconds", "hnsecs")(pastTrans.ttInfo.utcOffset + leapSecs);
+
+        immutable futureTrans = futureFound == -1 ? _transitions.back
+                                                  : _transitions[pastFound + futureFound - 1];
+        immutable pastOffset = pastTrans.ttInfo.utcOffset;
+
+        if(pastOffset < futureTrans.ttInfo.utcOffset)
+            unixTime -= convert!("hours", "seconds")(1);
+
+        immutable found = countUntil!"b < a.timeT"(_transitions[pastFound .. $], unixTime - pastOffset);
+
+        if(found == -1)
+            return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
+
+        immutable transition = found == 0 ? pastTrans : _transitions[pastFound + found - 1];
+
+        return adjTime - convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
+    }
+
+
+    version(Android)
+    {
+        // Android concatenates all time zone data into a single file and stores it here.
+        enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/";
+    }
+    else version(Posix)
+    {
+        /++
+            The default directory where the TZ Database files are. It's empty
+            for Windows, since Windows doesn't have them.
+          +/
+        enum defaultTZDatabaseDir = "/usr/share/zoneinfo/";
+    }
+    else version(Windows)
+    {
+        /++ The default directory where the TZ Database files are. It's empty
+            for Windows, since Windows doesn't have them.
+          +/
+        enum defaultTZDatabaseDir = "";
+    }
+
+
+    /++
+        Returns a $(LREF2 .TimeZone, TimeZone) with the give name per the TZ Database. The time
+        zone information is fetched from the TZ Database time zone files in the
+        given directory.
+
+        See_Also:
+            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
+              Database)<br>
+            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
+              Time Zones)
+
+        Params:
+            name          = The TZ Database name of the desired time zone
+            tzDatabaseDir = The directory where the TZ Database files are
+                            located. Because these files are not located on
+                            Windows systems, provide them
+                            and give their location here to
+                            use $(LREF PosixTimeZone)s.
+
+        Throws:
+            $(LREF DateTimeException) if the given time zone could not be found or
+            $(D FileException) if the TZ Database file could not be opened.
+      +/
+    //TODO make it possible for tzDatabaseDir to be gzipped tar file rather than an uncompressed
+    //     directory.
+    static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = defaultTZDatabaseDir) @trusted
+    {
+        import std.algorithm : sort;
+        import std.range : retro;
+        import std.format : format;
+        import std.path : asNormalizedPath, chainPath;
+        import std.conv : to;
+
+        name = strip(name);
+
+        enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
+        enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));
+
+        version(Android)
+        {
+            auto tzfileOffset = name in tzdataIndex(tzDatabaseDir);
+            enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name)));
+            string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata";
+            const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string;
+        }
+        else
+            const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string;
+
+        enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file)));
+        enforce(file.isFile, new DateTimeException(format("%s is not a file.", file)));
+
+        auto tzFile = File(file);
+        version(Android) tzFile.seek(*tzfileOffset);
+        immutable gmtZone = name.representation().canFind("GMT");
+
+        try
+        {
+            _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");
+
+            immutable char tzFileVersion = readVal!char(tzFile);
+            _enforceValidTZFile(tzFileVersion == '\0' || tzFileVersion == '2' || tzFileVersion == '3');
+
+            {
+                auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
+                bool allZeroes = true;
+
+                foreach(val; zeroBlock)
+                {
+                    if(val != 0)
+                    {
+                        allZeroes = false;
+                        break;
+                    }
+                }
+
+                _enforceValidTZFile(allZeroes);
+            }
+
+
+            //The number of UTC/local indicators stored in the file.
+            auto tzh_ttisgmtcnt = readVal!int(tzFile);
+
+            //The number of standard/wall indicators stored in the file.
+            auto tzh_ttisstdcnt = readVal!int(tzFile);
+
+            //The number of leap seconds for which data is stored in the file.
+            auto tzh_leapcnt = readVal!int(tzFile);
+
+            //The number of "transition times" for which data is stored in the file.
+            auto tzh_timecnt = readVal!int(tzFile);
+
+            //The number of "local time types" for which data is stored in the file (must not be zero).
+            auto tzh_typecnt = readVal!int(tzFile);
+            _enforceValidTZFile(tzh_typecnt != 0);
+
+            //The number of characters of "timezone abbreviation strings" stored in the file.
+            auto tzh_charcnt = readVal!int(tzFile);
+
+            //time_ts where DST transitions occur.
+            auto transitionTimeTs = new long[](tzh_timecnt);
+            foreach(ref transition; transitionTimeTs)
+                transition = readVal!int(tzFile);
+
+            //Indices into ttinfo structs indicating the changes
+            //to be made at the corresponding DST transition.
+            auto ttInfoIndices = new ubyte[](tzh_timecnt);
+            foreach(ref ttInfoIndex; ttInfoIndices)
+                ttInfoIndex = readVal!ubyte(tzFile);
+
+            //ttinfos which give info on DST transitions.
+            auto tempTTInfos = new TempTTInfo[](tzh_typecnt);
+            foreach(ref ttInfo; tempTTInfos)
+                ttInfo = readVal!TempTTInfo(tzFile);
+
+            //The array of time zone abbreviation characters.
+            auto tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);
+
+            auto leapSeconds = new LeapSecond[](tzh_leapcnt);
+            foreach(ref leapSecond; leapSeconds)
+            {
+                //The time_t when the leap second occurs.
+                auto timeT = readVal!int(tzFile);
+
+                //The total number of leap seconds to be applied after
+                //the corresponding leap second.
+                auto total = readVal!int(tzFile);
+
+                leapSecond = LeapSecond(timeT, total);
+            }
+
+            //Indicate whether each corresponding DST transition were specified
+            //in standard time or wall clock time.
+            auto transitionIsStd = new bool[](tzh_ttisstdcnt);
+            foreach(ref isStd; transitionIsStd)
+                isStd = readVal!bool(tzFile);
+
+            //Indicate whether each corresponding DST transition associated with
+            //local time types are specified in UTC or local time.
+            auto transitionInUTC = new bool[](tzh_ttisgmtcnt);
+            foreach(ref inUTC; transitionInUTC)
+                inUTC = readVal!bool(tzFile);
+
+            _enforceValidTZFile(!tzFile.eof);
+
+            //If version 2 or 3, the information is duplicated in 64-bit.
+            if(tzFileVersion == '2' || tzFileVersion == '3')
+            {
+                _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");
+
+                immutable char tzFileVersion2 = readVal!(char)(tzFile);
+                _enforceValidTZFile(tzFileVersion2 == '2' || tzFileVersion2 == '3');
+
+                {
+                    auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
+                    bool allZeroes = true;
+
+                    foreach(val; zeroBlock)
+                    {
+                        if(val != 0)
+                        {
+                            allZeroes = false;
+                            break;
+                        }
+                    }
+
+                    _enforceValidTZFile(allZeroes);
+                }
+
+
+                //The number of UTC/local indicators stored in the file.
+                tzh_ttisgmtcnt = readVal!int(tzFile);
+
+                //The number of standard/wall indicators stored in the file.
+                tzh_ttisstdcnt = readVal!int(tzFile);
+
+                //The number of leap seconds for which data is stored in the file.
+                tzh_leapcnt = readVal!int(tzFile);
+
+                //The number of "transition times" for which data is stored in the file.
+                tzh_timecnt = readVal!int(tzFile);
+
+                //The number of "local time types" for which data is stored in the file (must not be zero).
+                tzh_typecnt = readVal!int(tzFile);
+                _enforceValidTZFile(tzh_typecnt != 0);
+
+                //The number of characters of "timezone abbreviation strings" stored in the file.
+                tzh_charcnt = readVal!int(tzFile);
+
+                //time_ts where DST transitions occur.
+                transitionTimeTs = new long[](tzh_timecnt);
+                foreach(ref transition; transitionTimeTs)
+                    transition = readVal!long(tzFile);
+
+                //Indices into ttinfo structs indicating the changes
+                //to be made at the corresponding DST transition.
+                ttInfoIndices = new ubyte[](tzh_timecnt);
+                foreach(ref ttInfoIndex; ttInfoIndices)
+                    ttInfoIndex = readVal!ubyte(tzFile);
+
+                //ttinfos which give info on DST transitions.
+                tempTTInfos = new TempTTInfo[](tzh_typecnt);
+                foreach(ref ttInfo; tempTTInfos)
+                    ttInfo = readVal!TempTTInfo(tzFile);
+
+                //The array of time zone abbreviation characters.
+                tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);
+
+                leapSeconds = new LeapSecond[](tzh_leapcnt);
+                foreach(ref leapSecond; leapSeconds)
+                {
+                    //The time_t when the leap second occurs.
+                    auto timeT = readVal!long(tzFile);
+
+                    //The total number of leap seconds to be applied after
+                    //the corresponding leap second.
+                    auto total = readVal!int(tzFile);
+
+                    leapSecond = LeapSecond(timeT, total);
+                }
+
+                //Indicate whether each corresponding DST transition were specified
+                //in standard time or wall clock time.
+                transitionIsStd = new bool[](tzh_ttisstdcnt);
+                foreach(ref isStd; transitionIsStd)
+                    isStd = readVal!bool(tzFile);
+
+                //Indicate whether each corresponding DST transition associated with
+                //local time types are specified in UTC or local time.
+                transitionInUTC = new bool[](tzh_ttisgmtcnt);
+                foreach(ref inUTC; transitionInUTC)
+                    inUTC = readVal!bool(tzFile);
+            }
+
+            _enforceValidTZFile(tzFile.readln().strip().empty);
+
+            auto posixEnvStr = tzFile.readln().strip();
+
+            version(Android)
+            {
+                // Android uses a single file for all timezone data, so the file
+                // doesn't end here.
+            }
+            else
+            {
+                _enforceValidTZFile(tzFile.readln().strip().empty);
+                _enforceValidTZFile(tzFile.eof);
+            }
+
+
+            auto transitionTypes = new TransitionType*[](tempTTInfos.length);
+
+            foreach(i, ref ttype; transitionTypes)
+            {
+                bool isStd = false;
+
+                if(i < transitionIsStd.length && !transitionIsStd.empty)
+                    isStd = transitionIsStd[i];
+
+                bool inUTC = false;
+
+                if(i < transitionInUTC.length && !transitionInUTC.empty)
+                    inUTC = transitionInUTC[i];
+
+                ttype = new TransitionType(isStd, inUTC);
+            }
+
+            auto ttInfos = new immutable(TTInfo)*[](tempTTInfos.length);
+            foreach(i, ref ttInfo; ttInfos)
+            {
+                auto tempTTInfo = tempTTInfos[i];
+
+                if(gmtZone)
+                    tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff;
+
+                auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $];
+                string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup;
+
+                ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev);
+            }
+
+            auto tempTransitions = new TempTransition[](transitionTimeTs.length);
+            foreach(i, ref tempTransition; tempTransitions)
+            {
+                immutable ttiIndex = ttInfoIndices[i];
+                auto transitionTimeT = transitionTimeTs[i];
+                auto ttype = transitionTypes[ttiIndex];
+                auto ttInfo = ttInfos[ttiIndex];
+
+                tempTransition = TempTransition(transitionTimeT, ttInfo, ttype);
+            }
+
+            if(tempTransitions.empty)
+            {
+                _enforceValidTZFile(ttInfos.length == 1 && transitionTypes.length == 1);
+                tempTransitions ~= TempTransition(0, ttInfos[0], transitionTypes[0]);
+            }
+
+            sort!"a.timeT < b.timeT"(tempTransitions);
+            sort!"a.timeT < b.timeT"(leapSeconds);
+
+            auto transitions = new Transition[](tempTransitions.length);
+            foreach(i, ref transition; transitions)
+            {
+                auto tempTransition = tempTransitions[i];
+                auto transitionTimeT = tempTransition.timeT;
+                auto ttInfo = tempTransition.ttInfo;
+                auto ttype = tempTransition.ttype;
+
+                _enforceValidTZFile(i == 0 || transitionTimeT > tempTransitions[i - 1].timeT);
+
+                transition = Transition(transitionTimeT, ttInfo);
+            }
+
+            string stdName;
+            string dstName;
+            bool hasDST = false;
+
+            foreach(transition; retro(transitions))
+            {
+                auto ttInfo = transition.ttInfo;
+
+                if(ttInfo.isDST)
+                {
+                    if(dstName.empty)
+                        dstName = ttInfo.abbrev;
+
+                    hasDST = true;
+                }
+                else
+                {
+                    if(stdName.empty)
+                        stdName = ttInfo.abbrev;
+                }
+
+                if(!stdName.empty && !dstName.empty)
+                    break;
+            }
+
+            return new immutable PosixTimeZone(transitions.idup, leapSeconds.idup, name, stdName, dstName, hasDST);
+        }
+        catch(DateTimeException dte)
+            throw dte;
+        catch(Exception e)
+            throw new DateTimeException("Not a valid TZ data file", __FILE__, __LINE__, e);
+    }
+
+    ///
+    unittest
+    {
+        version(Posix)
+        {
+            auto tz = PosixTimeZone.getTimeZone("America/Los_Angeles");
+
+            assert(tz.name == "America/Los_Angeles");
+            assert(tz.stdName == "PST");
+            assert(tz.dstName == "PDT");
+        }
+    }
+
+    /++
+        Returns a list of the names of the time zones installed on the system.
+
+        Providing a sub-name narrows down the list of time zones (which
+        can number in the thousands). For example,
+        passing in "America" as the sub-name returns only the time zones which
+        begin with "America".
+
+        Params:
+            subName       = The first part of the desired time zones.
+            tzDatabaseDir = The directory where the TZ Database files are
+                            located.
+
+        Throws:
+            $(D FileException) if it fails to read from disk.
+      +/
+    static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @trusted
+    {
+        import std.array : appender;
+        import std.algorithm : sort;
+        import std.format : format;
+
+        version(Posix)
+            subName = strip(subName);
+        else version(Windows)
+        {
+            import std.array : replace;
+            import std.path : dirSeparator;
+            subName = replace(strip(subName), "/", dirSeparator);
+        }
+
+        enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
+        enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));
+
+        auto timezones = appender!(string[])();
+
+        version(Android)
+        {
+            import std.algorithm : copy, filter;
+            tzdataIndex(tzDatabaseDir)
+            .byKey
+            .filter!(a => a.startsWith(subName))
+            .copy(timezones);
+        }
+        else
+        {
+            foreach(DirEntry dentry; dirEntries(tzDatabaseDir, SpanMode.depth))
+            {
+                if(dentry.isFile)
+                {
+                    auto tzName = dentry.name[tzDatabaseDir.length .. $];
+
+                    if(!tzName.extension().empty ||
+                       !tzName.startsWith(subName) ||
+                       tzName == "leapseconds" ||
+                       tzName == "+VERSION")
+                    {
+                        continue;
+                    }
+
+                    timezones.put(tzName);
+                }
+            }
+        }
+
+        sort(timezones.data);
+
+        return timezones.data;
+    }
+
+    version(Posix) unittest
+    {
+        static void testPTZSuccess(string tzName)
+        {
+            scope(failure) writefln("TZName which threw: %s", tzName);
+
+            PosixTimeZone.getTimeZone(tzName);
+        }
+
+        static void testPTZFailure(string tzName)
+        {
+            scope(success) writefln("TZName which was supposed to throw: %s", tzName);
+
+            PosixTimeZone.getTimeZone(tzName);
+        }
+
+        auto tzNames = getInstalledTZNames();
+
+        foreach(tzName; tzNames)
+            assertNotThrown!DateTimeException(testPTZSuccess(tzName));
+
+        // No timezone directories on Android, just a single tzdata file
+        version(Android) {} else
+        foreach(DirEntry dentry; dirEntries(defaultTZDatabaseDir, SpanMode.depth))
+        {
+            if(dentry.isFile)
+            {
+                auto tzName = dentry.name[defaultTZDatabaseDir.length .. $];
+
+                if(!canFind(tzNames, tzName))
+                    assertThrown!DateTimeException(testPTZFailure(tzName));
+            }
+        }
+    }
+
+
+private:
+
+    /+
+        Holds information on when a time transition occures (usually a
+        transition to or from DST) as well as a pointer to the $(D TTInfo) which
+        holds information on the utc offset past the transition.
+      +/
+    struct Transition
+    {
+        this(long timeT, immutable (TTInfo)* ttInfo) @safe pure
+        {
+            this.timeT = timeT;
+            this.ttInfo = ttInfo;
+        }
+
+        long    timeT;
+        immutable (TTInfo)* ttInfo;
+    }
+
+
+    /+
+        Holds information on when a leap second occurs.
+      +/
+    struct LeapSecond
+    {
+        this(long timeT, int total) @safe pure
+        {
+            this.timeT = timeT;
+            this.total = total;
+        }
+
+        long timeT;
+        int total;
+    }
+
+    /+
+        Holds information on the utc offset after a transition as well as
+        whether DST is in effect after that transition.
+      +/
+    struct TTInfo
+    {
+        this(in TempTTInfo tempTTInfo, string abbrev) @safe immutable pure
+        {
+            utcOffset = tempTTInfo.tt_gmtoff;
+            isDST = tempTTInfo.tt_isdst;
+            this.abbrev = abbrev;
+        }
+
+        immutable int    utcOffset;  /// Offset from UTC.
+        immutable bool   isDST;      /// Whether DST is in effect.
+        immutable string abbrev;     /// The current abbreviation for the time zone.
+    }
+
+
+    /+
+        Struct used to hold information relating to $(D TTInfo) while organizing
+        the time zone information prior to putting it in its final form.
+      +/
+    struct TempTTInfo
+    {
+        this(int gmtOff, bool isDST, ubyte abbrInd) @safe pure
+        {
+            tt_gmtoff = gmtOff;
+            tt_isdst = isDST;
+            tt_abbrind = abbrInd;
+        }
+
+        int   tt_gmtoff;
+        bool  tt_isdst;
+        ubyte tt_abbrind;
+    }
+
+
+    /+
+        Struct used to hold information relating to $(D Transition) while
+        organizing the time zone information prior to putting it in its final
+        form.
+      +/
+    struct TempTransition
+    {
+        this(long timeT, immutable (TTInfo)* ttInfo, TransitionType* ttype) @safe pure
+        {
+            this.timeT = timeT;
+            this.ttInfo = ttInfo;
+            this.ttype = ttype;
+        }
+
+        long                timeT;
+        immutable (TTInfo)* ttInfo;
+        TransitionType*     ttype;
+    }
+
+
+    /+
+        Struct used to hold information relating to $(D Transition) and
+        $(D TTInfo) while organizing the time zone information prior to putting
+        it in its final form.
+      +/
+    struct TransitionType
+    {
+        this(bool isStd, bool inUTC) @safe pure
+        {
+            this.isStd = isStd;
+            this.inUTC = inUTC;
+        }
+
+        /// Whether the transition is in std time (as opposed to wall clock time).
+        bool isStd;
+
+        /// Whether the transition is in UTC (as opposed to local time).
+        bool inUTC;
+    }
+
+
+    /+
+        Reads an int from a TZ file.
+      +/
+    static T readVal(T)(ref File tzFile) @trusted
+        if((isIntegral!T || isSomeChar!T) || is(Unqual!T == bool))
+    {
+        import std.bitmanip;
+        T[1] buff;
+
+        _enforceValidTZFile(!tzFile.eof);
+        tzFile.rawRead(buff);
+
+        return bigEndianToNative!T(cast(ubyte[T.sizeof])buff);
+    }
+
+    /+
+        Reads an array of values from a TZ file.
+      +/
+    static T readVal(T)(ref File tzFile, size_t length) @trusted
+        if(isArray!T)
+    {
+        auto buff = new T(length);
+
+        _enforceValidTZFile(!tzFile.eof);
+        tzFile.rawRead(buff);
+
+        return buff;
+    }
+
+
+    /+
+        Reads a $(D TempTTInfo) from a TZ file.
+      +/
+    static T readVal(T)(ref File tzFile) @safe
+        if(is(T == TempTTInfo))
+    {
+        return TempTTInfo(readVal!int(tzFile),
+                          readVal!bool(tzFile),
+                          readVal!ubyte(tzFile));
+    }
+
+
+    /+
+        Throws:
+            $(LREF DateTimeException) if $(D result) is false.
+      +/
+    static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure
+    {
+        if(!result)
+            throw new DateTimeException("Not a valid tzdata file.", __FILE__, line);
+    }
+
+
+    int calculateLeapSeconds(long stdTime) @safe const pure nothrow
+    {
+        if(_leapSeconds.empty)
+            return 0;
+
+        immutable unixTime = stdTimeToUnixTime(stdTime);
+
+        if(_leapSeconds.front.timeT >= unixTime)
+            return 0;
+
+        immutable found = countUntil!"b < a.timeT"(_leapSeconds, unixTime);
+
+        if(found == -1)
+            return _leapSeconds.back.total;
+
+        immutable leapSecond = found == 0 ? _leapSeconds[0] : _leapSeconds[found - 1];
+
+        return leapSecond.total;
+    }
+
+
+    this(immutable Transition[] transitions,
+         immutable LeapSecond[] leapSeconds,
+         string name,
+         string stdName,
+         string dstName,
+         bool hasDST) @safe immutable pure
+    {
+        if(dstName.empty && !stdName.empty)
+            dstName = stdName;
+        else if(stdName.empty && !dstName.empty)
+            stdName = dstName;
+
+        super(name, stdName, dstName);
+
+        if(!transitions.empty)
+        {
+            foreach(i, transition; transitions[0 .. $-1])
+                _enforceValidTZFile(transition.timeT < transitions[i + 1].timeT);
+        }
+
+        foreach(i, leapSecond; leapSeconds)
+            _enforceValidTZFile(i == leapSeconds.length - 1 || leapSecond.timeT < leapSeconds[i + 1].timeT);
+
+        _transitions = transitions;
+        _leapSeconds = leapSeconds;
+        _hasDST = hasDST;
+    }
+
+    // Android concatenates the usual timezone directories into a single file,
+    // tzdata, along with an index to jump to each timezone's offset.  In older
+    // versions of Android, the index was stored in a separate file, zoneinfo.idx,
+    // whereas now it's stored at the beginning of tzdata.
+    version(Android)
+    {
+        // Keep track of whether there's a separate index, zoneinfo.idx.  Only
+        // check this after calling tzdataIndex, as it's initialized there.
+        static shared bool separate_index;
+
+        // Extracts the name of each time zone and the offset where its data is
+        // located in the tzdata file from the index and caches it for later.
+        static const(uint[string]) tzdataIndex(string tzDir)
+        {
+            import std.concurrency : initOnce;
+
+            static __gshared uint[string] _tzIndex;
+
+            // _tzIndex is initialized once and then shared across all threads.
+            initOnce!_tzIndex(
+            {
+                import std.conv : to;
+                import std.format : format;
+                import std.path : asNormalizedPath, chainPath;
+
+                enum indexEntrySize = 52;
+                const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string;
+                const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string;
+                File tzFile;
+                uint indexEntries, dataOffset;
+                uint[string] initIndex;
+
+                // Check for the combined file tzdata, which stores the index
+                // and the time zone data together.
+                if(combinedFile.exists() && combinedFile.isFile)
+                {
+                    tzFile = File(combinedFile);
+                    _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata");
+                    auto tzDataVersion = readVal!(char[])(tzFile, 6);
+                    _enforceValidTZFile(tzDataVersion[5] == '\0');
+
+                    uint indexOffset = readVal!uint(tzFile);
+                    dataOffset = readVal!uint(tzFile);
+                    readVal!uint(tzFile);
+
+                    indexEntries = (dataOffset - indexOffset)/indexEntrySize;
+                    separate_index = false;
+                }
+                else if(indexFile.exists() && indexFile.isFile)
+                {
+                    tzFile = File(indexFile);
+                    indexEntries = to!(uint)(tzFile.size/indexEntrySize);
+                    separate_index = true;
+                }
+                else
+                    throw new DateTimeException(format("Both timezone files %s and %s do not exist.",
+                        combinedFile, indexFile));
+
+                foreach(Unused; 0 .. indexEntries) {
+                    string tzName = to!string(readVal!(char[])(tzFile, 40).ptr);
+                    uint tzOffset = readVal!uint(tzFile);
+                    readVal!(uint[])(tzFile, 2);
+                    initIndex[tzName] = dataOffset + tzOffset;
+                }
+                initIndex.rehash;
+                return initIndex;
+            }());
+            return _tzIndex;
+        }
+    }
+
+    /// List of times when the utc offset changes.
+    immutable Transition[] _transitions;
+
+    /// List of leap second occurrences.
+    immutable LeapSecond[] _leapSeconds;
+
+    /// Whether DST is in effect for this time zone at any point in time.
+    immutable bool _hasDST;
+}
+
+
+
+version(StdDdoc)
+{
+    /++
+        $(BLUE This class is Windows-Only.)
+
+        Represents a time zone from the Windows registry. Unfortunately, Windows
+        does not use the TZ Database. To use the TZ Database, use
+        $(LREF PosixTimeZone) (which reads its information from the TZ Database
+        files on disk) on Windows by providing the TZ Database files and telling
+        $(D PosixTimeZone.getTimeZone) where the directory holding them is.
+
+        The TZ Database files and Windows' time zone information frequently
+        do not match. Windows has many errors with regards to when DST switches
+        occur (especially for historical dates). Also, the TZ Database files
+        include far more time zones than Windows does. So, for accurate
+        time zone information, use the TZ Database files with
+        $(LREF PosixTimeZone) rather than $(D WindowsTimeZone). However, because
+        $(D WindowsTimeZone) uses Windows system calls to deal with the time,
+        it's far more likely to match the behavior of other Windows programs.
+        Be aware of the differences when selecting a method.
+
+        $(D WindowsTimeZone) does not exist on Posix systems.
+
+        To get a $(D WindowsTimeZone), either call
+        $(D WindowsTimeZone.getTimeZone) or call $(D TimeZone.getTimeZone)
+        (which will give a $(LREF PosixTimeZone) on Posix systems and a
+         $(D WindowsTimeZone) on Windows systems).
+
+        See_Also:
+            $(WEB www.iana.org/time-zones, Home of the TZ Database files)
+      +/
+    final class WindowsTimeZone : TimeZone
+    {
+    public:
+
+        /++
+            Whether this time zone has Daylight Savings Time at any point in
+            time. Note that for some time zone types it may not have DST for
+            current dates but will still return true for $(D hasDST) because the
+            time zone did at some point have DST.
+          +/
+        @property override bool hasDST() @safe const nothrow;
+
+
+        /++
+            Takes the number of hnsecs (100 ns) since midnight, January 1st,
+            1 A.D. in UTC time (i.e. std time) and returns whether DST is in
+            effect in this time zone at the given point in time.
+
+            Params:
+                stdTime = The UTC time that needs to be checked for DST in this
+                          time zone.
+          +/
+        override bool dstInEffect(long stdTime) @safe const nothrow;
+
+
+        /++
+            Takes the number of hnsecs (100 ns) since midnight, January 1st,
+            1 A.D. in UTC time (i.e. std time) and converts it to this time
+                zone's time.
+
+            Params:
+                stdTime = The UTC time that needs to be adjusted to this time
+                          zone's time.
+          +/
+        override long utcToTZ(long stdTime) @safe const nothrow;
+
+
+        /++
+            Takes the number of hnsecs (100 ns) since midnight, January 1st,
+            1 A.D. in this time zone's time and converts it to UTC (i.e. std
+            time).
+
+            Params:
+                adjTime = The time in this time zone that needs to be adjusted
+                          to UTC time.
+          +/
+        override long tzToUTC(long adjTime) @safe const nothrow;
+
+
+        /++
+            Returns a $(LREF2 .TimeZone, TimeZone) with the given name per the Windows time
+            zone names. The time zone information is fetched from the Windows
+            registry.
+
+            See_Also:
+                $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
+                  Database)<br>
+                $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
+                  of Time Zones)
+
+            Params:
+                name = The TZ Database name of the desired time zone.
+
+            Throws:
+                $(LREF DateTimeException) if the given time zone could not be
+                found.
+
+            Example:
+    --------------------
+    auto tz = TimeZone.getTimeZone("America/Los_Angeles");
+    --------------------
+          +/
+        static immutable(WindowsTimeZone) getTimeZone(string name) @safe;
+
+
+        /++
+            Returns a list of the names of the time zones installed on the
+            system. The list returned by WindowsTimeZone contains the Windows
+            TZ names, not the TZ Database names. However,
+            $(D TimeZone.getinstalledTZNames) will return the TZ Database names
+            which are equivalent to the Windows TZ names.
+          +/
+        static string[] getInstalledTZNames() @safe;
+
+    private:
+
+        version(Windows) {}
+        else
+            alias TIME_ZONE_INFORMATION = void*;
+
+        static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) nothrow;
+        static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) nothrow;
+        static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) nothrow;
+
+        this() immutable pure
+        {
+            super("", "", "");
+        }
+    }
+
+}
+else version(Windows)
+{
+    final class WindowsTimeZone : TimeZone
+    {
+        import std.format : format;
+        import std.conv : to;
+        import std.algorithm : sort;
+        import std.array : appender;
+
+    public:
+
+        @property override bool hasDST() @safe const nothrow
+        {
+            return _tzInfo.DaylightDate.wMonth != 0;
+        }
+
+
+        override bool dstInEffect(long stdTime) @safe const nothrow
+        {
+            return _dstInEffect(&_tzInfo, stdTime);
+        }
+
+
+        override long utcToTZ(long stdTime) @safe const nothrow
+        {
+            return _utcToTZ(&_tzInfo, stdTime, hasDST);
+        }
+
+
+        override long tzToUTC(long adjTime) @safe const nothrow
+        {
+            return _tzToUTC(&_tzInfo, adjTime, hasDST);
+        }
+
+
+        static immutable(WindowsTimeZone) getTimeZone(string name) @trusted
+        {
+            import std.utf : toUTF16;
+
+            scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);
+
+            foreach (tzKeyName; baseKey.keyNames)
+            {
+                if (tzKeyName != name)
+                    continue;
+
+                scope tzKey = baseKey.getKey(tzKeyName);
+
+                scope stdVal = tzKey.getValue("Std");
+                auto stdName = stdVal.value_SZ;
+
+                scope dstVal = tzKey.getValue("Dlt");
+                auto dstName = dstVal.value_SZ;
+
+                scope tziVal = tzKey.getValue("TZI");
+                auto binVal = tziVal.value_BINARY;
+                assert(binVal.length == REG_TZI_FORMAT.sizeof);
+                auto tziFmt = cast(REG_TZI_FORMAT*)binVal.ptr;
+
+                TIME_ZONE_INFORMATION tzInfo;
+
+                auto wstdName = toUTF16(stdName);
+                auto wdstName = toUTF16(dstName);
+                auto wstdNameLen = wstdName.length > 32 ? 32 : wstdName.length;
+                auto wdstNameLen = wdstName.length > 32 ? 32 : wdstName.length;
+
+                tzInfo.Bias = tziFmt.Bias;
+                tzInfo.StandardName[0 .. wstdNameLen] = wstdName[0 .. wstdNameLen];
+                tzInfo.StandardName[wstdNameLen .. $] = '\0';
+                tzInfo.StandardDate = tziFmt.StandardDate;
+                tzInfo.StandardBias = tziFmt.StandardBias;
+                tzInfo.DaylightName[0 .. wdstNameLen] = wdstName[0 .. wdstNameLen];
+                tzInfo.DaylightName[wdstNameLen .. $] = '\0';
+                tzInfo.DaylightDate = tziFmt.DaylightDate;
+                tzInfo.DaylightBias = tziFmt.DaylightBias;
+
+                return new immutable WindowsTimeZone(name, tzInfo);
+            }
+            throw new DateTimeException(format("Failed to find time zone: %s", name));
+        }
+
+        static string[] getInstalledTZNames() @trusted
+        {
+            auto timezones = appender!(string[])();
+
+            scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);
+
+            foreach (tzKeyName; baseKey.keyNames)
+            {
+                timezones.put(tzKeyName);
+            }
+            sort(timezones.data);
+
+            return timezones.data;
+        }
+
+        unittest
+        {
+            static void testWTZSuccess(string tzName)
+            {
+                scope(failure) writefln("TZName which threw: %s", tzName);
+
+                WindowsTimeZone.getTimeZone(tzName);
+            }
+
+            auto tzNames = getInstalledTZNames();
+
+            foreach(tzName; tzNames)
+                assertNotThrown!DateTimeException(testWTZSuccess(tzName));
+        }
+
+
+    private:
+
+        static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) @trusted nothrow
+        {
+            try
+            {
+                if(tzInfo.DaylightDate.wMonth == 0)
+                    return false;
+
+                auto utcDateTime = cast(DateTime)SysTime(stdTime, UTC());
+
+                //The limits of what SystemTimeToTzSpecificLocalTime will accept.
+                if(utcDateTime.year < 1601)
+                {
+                    if(utcDateTime.month == Month.feb && utcDateTime.day == 29)
+                        utcDateTime.day = 28;
+
+                    utcDateTime.year = 1601;
+                }
+                else if(utcDateTime.year > 30_827)
+                {
+                    if(utcDateTime.month == Month.feb && utcDateTime.day == 29)
+                        utcDateTime.day = 28;
+
+                    utcDateTime.year = 30_827;
+                }
+
+                //SystemTimeToTzSpecificLocalTime doesn't act correctly at the
+                //beginning or end of the year (bleh). Unless some bizarre time
+                //zone changes DST on January 1st or December 31st, this should
+                //fix the problem.
+                if(utcDateTime.month == Month.jan)
+                {
+                    if(utcDateTime.day == 1)
+                        utcDateTime.day = 2;
+                }
+                else if(utcDateTime.month == Month.dec && utcDateTime.day == 31)
+                    utcDateTime.day = 30;
+
+                SYSTEMTIME utcTime = void;
+                SYSTEMTIME otherTime = void;
+
+                utcTime.wYear = utcDateTime.year;
+                utcTime.wMonth = utcDateTime.month;
+                utcTime.wDay = utcDateTime.day;
+                utcTime.wHour = utcDateTime.hour;
+                utcTime.wMinute = utcDateTime.minute;
+                utcTime.wSecond = utcDateTime.second;
+                utcTime.wMilliseconds = 0;
+
+                immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*)tzInfo,
+                                                                   &utcTime,
+                                                                   &otherTime);
+                assert(result);
+
+                immutable otherDateTime = DateTime(otherTime.wYear,
+                                                   otherTime.wMonth,
+                                                   otherTime.wDay,
+                                                   otherTime.wHour,
+                                                   otherTime.wMinute,
+                                                   otherTime.wSecond);
+                immutable diff = utcDateTime - otherDateTime;
+                immutable minutes = diff.total!"minutes" - tzInfo.Bias;
+
+                if(minutes == tzInfo.DaylightBias)
+                    return true;
+
+                assert(minutes == tzInfo.StandardBias);
+
+                return false;
+            }
+            catch(Exception e)
+                assert(0, "DateTime's constructor threw.");
+        }
+
+        unittest
+        {
+            TIME_ZONE_INFORMATION tzInfo;
+            GetTimeZoneInformation(&tzInfo);
+
+            foreach(year; [1600, 1601, 30_827, 30_828])
+                WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime);
+        }
+
+
+        static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) @safe nothrow
+        {
+            if(hasDST && WindowsTimeZone._dstInEffect(tzInfo, stdTime))
+                return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);
+
+            return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
+        }
+
+
+        static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) @trusted nothrow
+        {
+            if(hasDST)
+            {
+                try
+                {
+                    bool dstInEffectForLocalDateTime(DateTime localDateTime)
+                    {
+                        //The limits of what SystemTimeToTzSpecificLocalTime will accept.
+                        if(localDateTime.year < 1601)
+                        {
+                            if(localDateTime.month == Month.feb && localDateTime.day == 29)
+                                localDateTime.day = 28;
+
+                            localDateTime.year = 1601;
+                        }
+                        else if(localDateTime.year > 30_827)
+                        {
+                            if(localDateTime.month == Month.feb && localDateTime.day == 29)
+                                localDateTime.day = 28;
+
+                            localDateTime.year = 30_827;
+                        }
+
+                        //SystemTimeToTzSpecificLocalTime doesn't act correctly at the
+                        //beginning or end of the year (bleh). Unless some bizarre time
+                        //zone changes DST on January 1st or December 31st, this should
+                        //fix the problem.
+                        if(localDateTime.month == Month.jan)
+                        {
+                            if(localDateTime.day == 1)
+                                localDateTime.day = 2;
+                        }
+                        else if(localDateTime.month == Month.dec && localDateTime.day == 31)
+                            localDateTime.day = 30;
+
+                        SYSTEMTIME utcTime = void;
+                        SYSTEMTIME localTime = void;
+
+                        localTime.wYear = localDateTime.year;
+                        localTime.wMonth = localDateTime.month;
+                        localTime.wDay = localDateTime.day;
+                        localTime.wHour = localDateTime.hour;
+                        localTime.wMinute = localDateTime.minute;
+                        localTime.wSecond = localDateTime.second;
+                        localTime.wMilliseconds = 0;
+
+                        immutable result = TzSpecificLocalTimeToSystemTime(cast(TIME_ZONE_INFORMATION*)tzInfo,
+                                                                           &localTime,
+                                                                           &utcTime);
+                        assert(result);
+
+                        immutable utcDateTime = DateTime(utcTime.wYear,
+                                                         utcTime.wMonth,
+                                                         utcTime.wDay,
+                                                         utcTime.wHour,
+                                                         utcTime.wMinute,
+                                                         utcTime.wSecond);
+
+                        immutable diff = localDateTime - utcDateTime;
+                        immutable minutes = -tzInfo.Bias - diff.total!"minutes";
+
+                        if(minutes == tzInfo.DaylightBias)
+                            return true;
+
+                        assert(minutes == tzInfo.StandardBias);
+
+                        return false;
+                    }
+
+                    auto localDateTime = cast(DateTime)SysTime(adjTime, UTC());
+                    auto localDateTimeBefore = localDateTime - dur!"hours"(1);
+                    auto localDateTimeAfter = localDateTime + dur!"hours"(1);
+
+                    auto dstInEffectNow = dstInEffectForLocalDateTime(localDateTime);
+                    auto dstInEffectBefore = dstInEffectForLocalDateTime(localDateTimeBefore);
+                    auto dstInEffectAfter = dstInEffectForLocalDateTime(localDateTimeAfter);
+
+                    bool isDST;
+
+                    if(dstInEffectBefore && dstInEffectNow && dstInEffectAfter)
+                        isDST = true;
+                    else if(!dstInEffectBefore && !dstInEffectNow && !dstInEffectAfter)
+                        isDST = false;
+                    else if(!dstInEffectBefore && dstInEffectAfter)
+                        isDST = false;
+                    else if(dstInEffectBefore && !dstInEffectAfter)
+                        isDST = dstInEffectNow;
+                    else
+                        assert(0, "Bad Logic.");
+
+                    if(isDST)
+                        return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);
+                }
+                catch(Exception e)
+                    assert(0, "SysTime's constructor threw.");
+            }
+
+            return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
+        }
+
+
+        this(string name, TIME_ZONE_INFORMATION tzInfo) @safe immutable pure
+        {
+            super(name, to!string(tzInfo.StandardName.ptr), to!string(tzInfo.DaylightName.ptr));
+            _tzInfo = tzInfo;
+        }
+
+
+        TIME_ZONE_INFORMATION _tzInfo;
+    }
+}
+
+
+version(StdDdoc)
+{
+    /++
+        $(BLUE This function is Posix-Only.)
+
+        Sets the local time zone on Posix systems with the TZ
+        Database name by setting the TZ environment variable.
+
+        Unfortunately, there is no way to do it on Windows using the TZ
+        Database name, so this function only exists on Posix systems.
+      +/
+    void setTZEnvVar(string tzDatabaseName) @safe nothrow;
+
+
+    /++
+        $(BLUE This function is Posix-Only.)
+
+        Clears the TZ environment variable.
+      +/
+    void clearTZEnvVar() @safe nothrow;
+}
+else version(Posix)
+{
+    void setTZEnvVar(string tzDatabaseName) @trusted nothrow
+    {
+        import std.internal.cstring : tempCString;
+        import std.path : asNormalizedPath, chainPath;
+        import core.sys.posix.stdlib : setenv;
+        import core.sys.posix.time : tzset;
+
+        version(Android)
+            auto value = asNormalizedPath(tzDatabaseName);
+        else
+            auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName));
+        setenv("TZ", value.tempCString(), 1);
+        tzset();
+    }
+
+
+    void clearTZEnvVar() @trusted nothrow
+    {
+        import core.sys.posix.stdlib : unsetenv;
+        import core.sys.posix.time : tzset;
+
+        unsetenv("TZ");
+        tzset();
+    }
+}
+
+
+/++
+    Provides the conversions between the IANA time zone database time zone names
+    (which POSIX systems use) and the time zone names that Windows uses.
+
+    Windows uses a different set of time zone names than the IANA time zone
+    database does, and how they correspond to one another changes over time
+    (particularly when Microsoft updates Windows).
+    $(WEB http://unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml)
+    provides the current conversions (which may or may not match up with what's
+    on a particular Windows box depending on how up-to-date it is), and
+    parseTZConversions reads in those conversions from windowsZones.xml so that
+    a D program can use those conversions.
+
+    However, it should be noted that the time zone information on Windows is
+    frequently less accurate than that in the IANA time zone database, and if
+    someone really wants accurate time zone information, they should use the
+    IANA time zone database files with $(LREF PosixTimeZone) on Windows rather
+    than $(LREF WindowsTimeZone), whereas $(LREF WindowsTimeZone) makes more
+    sense when trying to match what Windows will think the time is in a specific
+    time zone.
+
+    Also, the IANA time zone database has a lot more time zones than Windows
+    does.
+
+    Params:
+        windowsZonesXMLFileText The text from
+        $(WEB http://unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml)
+
+    Throws:
+        Exception if there is an error while parsing the given XML.
+
+--------------------
+    // Parse the conversions from a local file.
+    auto text = std.file.readText("path/to/windowsZones.xml");
+    auto conversions = parseTZConversions(text);
+
+    // Alternatively, grab the XML file from the web at runtime
+    // and parse it so that it's guaranteed to be up-to-date, though
+    // that has the downside that the code needs to worry about the
+    // site being down or unicode.org changing the URL.
+    auto url = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml";
+    auto conversions2 = parseTZConversions(std.net.curl.get(url));
+--------------------
+  +/
+struct TZConversions
+{
+    /++
+        The key is the Windows time zone name, and the value is a list of
+        IANA TZ database names which are close (currently only ever one, but
+        it allows for multiple in case it's ever necessary).
+      +/
+    string[][string] toWindows;
+
+    /++
+        The key is the IANA time zone database name, and the value is a list of
+        Windows time zone names which are close (usually only one, but it could
+        be multiple).
+      +/
+    string[][string] fromWindows;
+}
+
+/++ ditto +/
+TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure
+{
+    // This is a bit hacky, since it doesn't properly read XML, but it avoids
+    // needing to pull in std.xml (which we're theoretically replacing at some
+    // point anyway.
+    import std.algorithm : find, sort, uniq;
+    import std.array : array, split;
+    import std.string : lineSplitter;
+
+    string[][string] win2Nix;
+    string[][string] nix2Win;
+
+    immutable f1 = `<mapZone other="`;
+    immutable f2 = `type="`;
+
+    foreach(line; windowsZonesXMLText.lineSplitter())
+    {
+        // Sample line:
+        // <mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
+
+        line = line.find(f1);
+        if(line.empty)
+            continue;
+        line = line[f1.length .. $];
+        auto next = line.find('"');
+        enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
+        auto win = line[0 .. $ - next.length];
+        line = next.find(f2);
+        enforce(!line.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
+        line = line[f2.length .. $];
+        next = line.find('"');
+        enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
+        auto nixes = line[0 .. $ - next.length].split();
+
+        if(auto n = win in win2Nix)
+            *n ~= nixes;
+        else
+            win2Nix[win] = nixes;
+
+        foreach(nix; nixes)
+        {
+            if(auto w = nix in nix2Win)
+                *w ~= win;
+            else
+                nix2Win[nix] = [win];
+        }
+    }
+
+    foreach(key, ref value; nix2Win)
+        value = value.sort().uniq().array();
+    foreach(key, ref value; win2Nix)
+        value = value.sort().uniq().array();
+
+    return TZConversions(nix2Win, win2Nix);
+}
+
+unittest
+{
+    import std.algorithm;
+
+    // Reduced text from http://unicode.org/cldr/data/common/supplemental/windowsZones.xml
+    auto sampleFileText =
+`<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
+<!--
+Copyright © 1991-2013 Unicode, Inc.
+CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
+For terms of use, see http://www.unicode.org/copyright.html
+-->
+
+<supplementalData>
+    <version number="$Revision$"/>
+
+    <windowsZones>
+        <mapTimezones otherVersion="7df0005" typeVersion="2015g">
+
+            <!-- (UTC-12:00) International Date Line West -->
+            <mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
+            <mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
+
+            <!-- (UTC-11:00) Coordinated Universal Time-11 -->
+            <mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
+            <mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
+            <mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
+            <mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
+            <mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
+
+            <!-- (UTC-10:00) Hawaii -->
+            <mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
+            <mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
+            <mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
+            <mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
+            <mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
+            <mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
+
+            <!-- (UTC-09:00) Alaska -->
+            <mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
+            <mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"/>
+        </mapTimezones>
+    </windowsZones>
+</supplementalData>`;
+
+    auto tzConversions = parseTZConversions(sampleFileText);
+    assert(tzConversions.toWindows.length == 15);
+    assert(tzConversions.toWindows["America/Anchorage"] == ["Alaskan Standard Time"]);
+    assert(tzConversions.toWindows["America/Juneau"] == ["Alaskan Standard Time"]);
+    assert(tzConversions.toWindows["America/Nome"] == ["Alaskan Standard Time"]);
+    assert(tzConversions.toWindows["America/Sitka"] == ["Alaskan Standard Time"]);
+    assert(tzConversions.toWindows["America/Yakutat"] == ["Alaskan Standard Time"]);
+    assert(tzConversions.toWindows["Etc/GMT+10"] == ["Hawaiian Standard Time"]);
+    assert(tzConversions.toWindows["Etc/GMT+11"] == ["UTC-11"]);
+    assert(tzConversions.toWindows["Etc/GMT+12"] == ["Dateline Standard Time"]);
+    assert(tzConversions.toWindows["Pacific/Honolulu"] == ["Hawaiian Standard Time"]);
+    assert(tzConversions.toWindows["Pacific/Johnston"] == ["Hawaiian Standard Time"]);
+    assert(tzConversions.toWindows["Pacific/Midway"] == ["UTC-11"]);
+    assert(tzConversions.toWindows["Pacific/Niue"] == ["UTC-11"]);
+    assert(tzConversions.toWindows["Pacific/Pago_Pago"] == ["UTC-11"]);
+    assert(tzConversions.toWindows["Pacific/Rarotonga"] == ["Hawaiian Standard Time"]);
+    assert(tzConversions.toWindows["Pacific/Tahiti"] == ["Hawaiian Standard Time"]);
+
+    assert(tzConversions.fromWindows.length == 4);
+    assert(tzConversions.fromWindows["Alaskan Standard Time"] ==
+           ["America/Anchorage", "America/Juneau", "America/Nome", "America/Sitka", "America/Yakutat"]);
+    assert(tzConversions.fromWindows["Dateline Standard Time"] == ["Etc/GMT+12"]);
+    assert(tzConversions.fromWindows["Hawaiian Standard Time"] ==
+           ["Etc/GMT+10", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Rarotonga", "Pacific/Tahiti"]);
+    assert(tzConversions.fromWindows["UTC-11"] ==
+           ["Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago"]);
+
+    foreach(key, value; tzConversions.fromWindows)
+    {
+        assert(value.isSorted, key);
+        assert(equal(value.uniq(), value), key);
+    }
+}
+
+
+/++
+    $(RED Please use $(LREF parseTZConversions) instead. This function will be
+          deprecated in 2.072, because Microsoft changes their time zones too
+          often for us to compile the conversions into Phobos and have them be
+          properly up-to-date.)
+
+    Converts the given TZ Database name to the corresponding Windows time zone
+    name.
+
+    Note that in a few cases, a TZ Dabatase name corresponds to two different
+    Windows time zone names. So, while in most cases converting from one to the
+    other and back again will result in the same time zone name started
+    with, in a few case, it'll get a different name.
+
+    Also, there are far more TZ Database names than Windows time zones, so some
+    of the more exotic TZ Database names don't have corresponding Windows time
+    zone names.
+
+    Returns null if the given time zone name cannot be converted.
+
+    See_Also:
+        $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
+              Windows <-> TZ Database Name Conversion Table)
+
+    Params:
+        tzName = The TZ Database name to convert.
+  +/
+string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc
+{
+    switch(tzName)
+    {
+        case "Africa/Abidjan": return "Greenwich Standard Time";
+        case "Africa/Accra": return "Greenwich Standard Time";
+        case "Africa/Addis_Ababa": return "E. Africa Standard Time";
+        case "Africa/Algiers": return "W. Central Africa Standard Time";
+        case "Africa/Asmera": return "E. Africa Standard Time";
+        case "Africa/Bamako": return "Greenwich Standard Time";
+        case "Africa/Bangui": return "W. Central Africa Standard Time";
+        case "Africa/Banjul": return "Greenwich Standard Time";
+        case "Africa/Bissau": return "Greenwich Standard Time";
+        case "Africa/Blantyre": return "South Africa Standard Time";
+        case "Africa/Brazzaville": return "W. Central Africa Standard Time";
+        case "Africa/Bujumbura": return "South Africa Standard Time";
+        case "Africa/Cairo": return "Egypt Standard Time";
+        case "Africa/Casablanca": return "Morocco Standard Time";
+        case "Africa/Ceuta": return "Romance Standard Time";
+        case "Africa/Conakry": return "Greenwich Standard Time";
+        case "Africa/Dakar": return "Greenwich Standard Time";
+        case "Africa/Dar_es_Salaam": return "E. Africa Standard Time";
+        case "Africa/Djibouti": return "E. Africa Standard Time";
+        case "Africa/Douala": return "W. Central Africa Standard Time";
+        case "Africa/El_Aaiun": return "Morocco Standard Time";
+        case "Africa/Freetown": return "Greenwich Standard Time";
+        case "Africa/Gaborone": return "South Africa Standard Time";
+        case "Africa/Harare": return "South Africa Standard Time";
+        case "Africa/Johannesburg": return "South Africa Standard Time";
+        case "Africa/Juba": return "E. Africa Standard Time";
+        case "Africa/Kampala": return "E. Africa Standard Time";
+        case "Africa/Khartoum": return "E. Africa Standard Time";
+        case "Africa/Kigali": return "South Africa Standard Time";
+        case "Africa/Kinshasa": return "W. Central Africa Standard Time";
+        case "Africa/Lagos": return "W. Central Africa Standard Time";
+        case "Africa/Libreville": return "W. Central Africa Standard Time";
+        case "Africa/Lome": return "Greenwich Standard Time";
+        case "Africa/Luanda": return "W. Central Africa Standard Time";
+        case "Africa/Lubumbashi": return "South Africa Standard Time";
+        case "Africa/Lusaka": return "South Africa Standard Time";
+        case "Africa/Malabo": return "W. Central Africa Standard Time";
+        case "Africa/Maputo": return "South Africa Standard Time";
+        case "Africa/Maseru": return "South Africa Standard Time";
+        case "Africa/Mbabane": return "South Africa Standard Time";
+        case "Africa/Mogadishu": return "E. Africa Standard Time";
+        case "Africa/Monrovia": return "Greenwich Standard Time";
+        case "Africa/Nairobi": return "E. Africa Standard Time";
+        case "Africa/Ndjamena": return "W. Central Africa Standard Time";
+        case "Africa/Niamey": return "W. Central Africa Standard Time";
+        case "Africa/Nouakchott": return "Greenwich Standard Time";
+        case "Africa/Ouagadougou": return "Greenwich Standard Time";
+        case "Africa/Porto-Novo": return "W. Central Africa Standard Time";
+        case "Africa/Sao_Tome": return "Greenwich Standard Time";
+        case "Africa/Tripoli": return "Libya Standard Time";
+        case "Africa/Tunis": return "W. Central Africa Standard Time";
+        case "Africa/Windhoek": return "Namibia Standard Time";
+        case "America/Anchorage": return "Alaskan Standard Time";
+        case "America/Anguilla": return "SA Western Standard Time";
+        case "America/Antigua": return "SA Western Standard Time";
+        case "America/Araguaina": return "SA Eastern Standard Time";
+        case "America/Argentina/La_Rioja": return "Argentina Standard Time";
+        case "America/Argentina/Rio_Gallegos": return "Argentina Standard Time";
+        case "America/Argentina/Salta": return "Argentina Standard Time";
+        case "America/Argentina/San_Juan": return "Argentina Standard Time";
+        case "America/Argentina/San_Luis": return "Argentina Standard Time";
+        case "America/Argentina/Tucuman": return "Argentina Standard Time";
+        case "America/Argentina/Ushuaia": return "Argentina Standard Time";
+        case "America/Aruba": return "SA Western Standard Time";
+        case "America/Asuncion": return "Paraguay Standard Time";
+        case "America/Bahia": return "Bahia Standard Time";
+        case "America/Bahia_Banderas": return "Central Standard Time (Mexico)";
+        case "America/Barbados": return "SA Western Standard Time";
+        case "America/Belem": return "SA Eastern Standard Time";
+        case "America/Belize": return "Central America Standard Time";
+        case "America/Blanc-Sablon": return "SA Western Standard Time";
+        case "America/Boa_Vista": return "SA Western Standard Time";
+        case "America/Bogota": return "SA Pacific Standard Time";
+        case "America/Boise": return "Mountain Standard Time";
+        case "America/Buenos_Aires": return "Argentina Standard Time";
+        case "America/Cambridge_Bay": return "Mountain Standard Time";
+        case "America/Campo_Grande": return "Central Brazilian Standard Time";
+        case "America/Cancun": return "Eastern Standard Time (Mexico)";
+        case "America/Caracas": return "Venezuela Standard Time";
+        case "America/Catamarca": return "Argentina Standard Time";
+        case "America/Cayenne": return "SA Eastern Standard Time";
+        case "America/Cayman": return "SA Pacific Standard Time";
+        case "America/Chicago": return "Central Standard Time";
+        case "America/Chihuahua": return "Mountain Standard Time (Mexico)";
+        case "America/Coral_Harbour": return "SA Pacific Standard Time";
+        case "America/Cordoba": return "Argentina Standard Time";
+        case "America/Costa_Rica": return "Central America Standard Time";
+        case "America/Creston": return "US Mountain Standard Time";
+        case "America/Cuiaba": return "Central Brazilian Standard Time";
+        case "America/Curacao": return "SA Western Standard Time";
+        case "America/Danmarkshavn": return "UTC";
+        case "America/Dawson": return "Pacific Standard Time";
+        case "America/Dawson_Creek": return "US Mountain Standard Time";
+        case "America/Denver": return "Mountain Standard Time";
+        case "America/Detroit": return "Eastern Standard Time";
+        case "America/Dominica": return "SA Western Standard Time";
+        case "America/Edmonton": return "Mountain Standard Time";
+        case "America/Eirunepe": return "SA Pacific Standard Time";
+        case "America/El_Salvador": return "Central America Standard Time";
+        case "America/Fortaleza": return "SA Eastern Standard Time";
+        case "America/Glace_Bay": return "Atlantic Standard Time";
+        case "America/Godthab": return "Greenland Standard Time";
+        case "America/Goose_Bay": return "Atlantic Standard Time";
+        case "America/Grand_Turk": return "SA Western Standard Time";
+        case "America/Grenada": return "SA Western Standard Time";
+        case "America/Guadeloupe": return "SA Western Standard Time";
+        case "America/Guatemala": return "Central America Standard Time";
+        case "America/Guayaquil": return "SA Pacific Standard Time";
+        case "America/Guyana": return "SA Western Standard Time";
+        case "America/Halifax": return "Atlantic Standard Time";
+        case "America/Havana": return "Eastern Standard Time";
+        case "America/Hermosillo": return "US Mountain Standard Time";
+        case "America/Indiana/Knox": return "Central Standard Time";
+        case "America/Indiana/Marengo": return "US Eastern Standard Time";
+        case "America/Indiana/Petersburg": return "Eastern Standard Time";
+        case "America/Indiana/Tell_City": return "Central Standard Time";
+        case "America/Indiana/Vevay": return "US Eastern Standard Time";
+        case "America/Indiana/Vincennes": return "Eastern Standard Time";
+        case "America/Indiana/Winamac": return "Eastern Standard Time";
+        case "America/Indianapolis": return "US Eastern Standard Time";
+        case "America/Inuvik": return "Mountain Standard Time";
+        case "America/Iqaluit": return "Eastern Standard Time";
+        case "America/Jamaica": return "SA Pacific Standard Time";
+        case "America/Jujuy": return "Argentina Standard Time";
+        case "America/Juneau": return "Alaskan Standard Time";
+        case "America/Kentucky/Monticello": return "Eastern Standard Time";
+        case "America/Kralendijk": return "SA Western Standard Time";
+        case "America/La_Paz": return "SA Western Standard Time";
+        case "America/Lima": return "SA Pacific Standard Time";
+        case "America/Los_Angeles": return "Pacific Standard Time";
+        case "America/Louisville": return "Eastern Standard Time";
+        case "America/Lower_Princes": return "SA Western Standard Time";
+        case "America/Maceio": return "SA Eastern Standard Time";
+        case "America/Managua": return "Central America Standard Time";
+        case "America/Manaus": return "SA Western Standard Time";
+        case "America/Marigot": return "SA Western Standard Time";
+        case "America/Martinique": return "SA Western Standard Time";
+        case "America/Matamoros": return "Central Standard Time";
+        case "America/Mazatlan": return "Mountain Standard Time (Mexico)";
+        case "America/Mendoza": return "Argentina Standard Time";
+        case "America/Menominee": return "Central Standard Time";
+        case "America/Merida": return "Central Standard Time (Mexico)";
+        case "America/Mexico_City": return "Central Standard Time (Mexico)";
+        case "America/Moncton": return "Atlantic Standard Time";
+        case "America/Monterrey": return "Central Standard Time (Mexico)";
+        case "America/Montevideo": return "Montevideo Standard Time";
+        case "America/Montreal": return "Eastern Standard Time";
+        case "America/Montserrat": return "SA Western Standard Time";
+        case "America/Nassau": return "Eastern Standard Time";
+        case "America/New_York": return "Eastern Standard Time";
+        case "America/Nipigon": return "Eastern Standard Time";
+        case "America/Nome": return "Alaskan Standard Time";
+        case "America/Noronha": return "UTC-02";
+        case "America/North_Dakota/Beulah": return "Central Standard Time";
+        case "America/North_Dakota/Center": return "Central Standard Time";
+        case "America/North_Dakota/New_Salem": return "Central Standard Time";
+        case "America/Ojinaga": return "Mountain Standard Time";
+        case "America/Panama": return "SA Pacific Standard Time";
+        case "America/Pangnirtung": return "Eastern Standard Time";
+        case "America/Paramaribo": return "SA Eastern Standard Time";
+        case "America/Phoenix": return "US Mountain Standard Time";
+        case "America/Port-au-Prince": return "Haiti Standard Time";
+        case "America/Port_of_Spain": return "SA Western Standard Time";
+        case "America/Porto_Velho": return "SA Western Standard Time";
+        case "America/Puerto_Rico": return "SA Western Standard Time";
+        case "America/Rainy_River": return "Central Standard Time";
+        case "America/Rankin_Inlet": return "Central Standard Time";
+        case "America/Recife": return "SA Eastern Standard Time";
+        case "America/Regina": return "Canada Central Standard Time";
+        case "America/Resolute": return "Central Standard Time";
+        case "America/Rio_Branco": return "SA Pacific Standard Time";
+        case "America/Santa_Isabel": return "Pacific Standard Time (Mexico)";
+        case "America/Santarem": return "SA Eastern Standard Time";
+        case "America/Santiago": return "Pacific SA Standard Time";
+        case "America/Santo_Domingo": return "SA Western Standard Time";
+        case "America/Sao_Paulo": return "E. South America Standard Time";
+        case "America/Scoresbysund": return "Azores Standard Time";
+        case "America/Sitka": return "Alaskan Standard Time";
+        case "America/St_Barthelemy": return "SA Western Standard Time";
+        case "America/St_Johns": return "Newfoundland Standard Time";
+        case "America/St_Kitts": return "SA Western Standard Time";
+        case "America/St_Lucia": return "SA Western Standard Time";
+        case "America/St_Thomas": return "SA Western Standard Time";
+        case "America/St_Vincent": return "SA Western Standard Time";
+        case "America/Swift_Current": return "Canada Central Standard Time";
+        case "America/Tegucigalpa": return "Central America Standard Time";
+        case "America/Thule": return "Atlantic Standard Time";
+        case "America/Thunder_Bay": return "Eastern Standard Time";
+        case "America/Tijuana": return "Pacific Standard Time";
+        case "America/Toronto": return "Eastern Standard Time";
+        case "America/Tortola": return "SA Western Standard Time";
+        case "America/Vancouver": return "Pacific Standard Time";
+        case "America/Whitehorse": return "Pacific Standard Time";
+        case "America/Winnipeg": return "Central Standard Time";
+        case "America/Yakutat": return "Alaskan Standard Time";
+        case "America/Yellowknife": return "Mountain Standard Time";
+        case "Antarctica/Casey": return "W. Australia Standard Time";
+        case "Antarctica/Davis": return "SE Asia Standard Time";
+        case "Antarctica/DumontDUrville": return "West Pacific Standard Time";
+        case "Antarctica/Macquarie": return "Central Pacific Standard Time";
+        case "Antarctica/Mawson": return "West Asia Standard Time";
+        case "Antarctica/McMurdo": return "New Zealand Standard Time";
+        case "Antarctica/Palmer": return "Pacific SA Standard Time";
+        case "Antarctica/Rothera": return "SA Eastern Standard Time";
+        case "Antarctica/Syowa": return "E. Africa Standard Time";
+        case "Antarctica/Vostok": return "Central Asia Standard Time";
+        case "Arctic/Longyearbyen": return "W. Europe Standard Time";
+        case "Asia/Aden": return "Arab Standard Time";
+        case "Asia/Almaty": return "Central Asia Standard Time";
+        case "Asia/Amman": return "Jordan Standard Time";
+        case "Asia/Anadyr": return "Russia Time Zone 11";
+        case "Asia/Aqtau": return "West Asia Standard Time";
+        case "Asia/Aqtobe": return "West Asia Standard Time";
+        case "Asia/Ashgabat": return "West Asia Standard Time";
+        case "Asia/Baghdad": return "Arabic Standard Time";
+        case "Asia/Bahrain": return "Arab Standard Time";
+        case "Asia/Baku": return "Azerbaijan Standard Time";
+        case "Asia/Bangkok": return "SE Asia Standard Time";
+        case "Asia/Barnaul": return "Altai Standard Time";
+        case "Asia/Beirut": return "Middle East Standard Time";
+        case "Asia/Bishkek": return "Central Asia Standard Time";
+        case "Asia/Brunei": return "Singapore Standard Time";
+        case "Asia/Calcutta": return "India Standard Time";
+        case "Asia/Chita": return "Transbaikal Standard Time";
+        case "Asia/Choibalsan": return "Ulaanbaatar Standard Time";
+        case "Asia/Colombo": return "Sri Lanka Standard Time";
+        case "Asia/Damascus": return "Syria Standard Time";
+        case "Asia/Dhaka": return "Bangladesh Standard Time";
+        case "Asia/Dili": return "Tokyo Standard Time";
+        case "Asia/Dubai": return "Arabian Standard Time";
+        case "Asia/Dushanbe": return "West Asia Standard Time";
+        case "Asia/Hong_Kong": return "China Standard Time";
+        case "Asia/Hovd": return "SE Asia Standard Time";
+        case "Asia/Irkutsk": return "North Asia East Standard Time";
+        case "Asia/Jakarta": return "SE Asia Standard Time";
+        case "Asia/Jayapura": return "Tokyo Standard Time";
+        case "Asia/Jerusalem": return "Israel Standard Time";
+        case "Asia/Kabul": return "Afghanistan Standard Time";
+        case "Asia/Kamchatka": return "Russia Time Zone 11";
+        case "Asia/Karachi": return "Pakistan Standard Time";
+        case "Asia/Katmandu": return "Nepal Standard Time";
+        case "Asia/Khandyga": return "Yakutsk Standard Time";
+        case "Asia/Krasnoyarsk": return "North Asia Standard Time";
+        case "Asia/Kuala_Lumpur": return "Singapore Standard Time";
+        case "Asia/Kuching": return "Singapore Standard Time";
+        case "Asia/Kuwait": return "Arab Standard Time";
+        case "Asia/Macau": return "China Standard Time";
+        case "Asia/Magadan": return "Magadan Standard Time";
+        case "Asia/Makassar": return "Singapore Standard Time";
+        case "Asia/Manila": return "Singapore Standard Time";
+        case "Asia/Muscat": return "Arabian Standard Time";
+        case "Asia/Nicosia": return "GTB Standard Time";
+        case "Asia/Novokuznetsk": return "North Asia Standard Time";
+        case "Asia/Novosibirsk": return "N. Central Asia Standard Time";
+        case "Asia/Omsk": return "N. Central Asia Standard Time";
+        case "Asia/Oral": return "West Asia Standard Time";
+        case "Asia/Phnom_Penh": return "SE Asia Standard Time";
+        case "Asia/Pontianak": return "SE Asia Standard Time";
+        case "Asia/Pyongyang": return "North Korea Standard Time";
+        case "Asia/Qatar": return "Arab Standard Time";
+        case "Asia/Qyzylorda": return "Central Asia Standard Time";
+        case "Asia/Rangoon": return "Myanmar Standard Time";
+        case "Asia/Riyadh": return "Arab Standard Time";
+        case "Asia/Saigon": return "SE Asia Standard Time";
+        case "Asia/Sakhalin": return "Sakhalin Standard Time";
+        case "Asia/Samarkand": return "West Asia Standard Time";
+        case "Asia/Seoul": return "Korea Standard Time";
+        case "Asia/Shanghai": return "China Standard Time";
+        case "Asia/Singapore": return "Singapore Standard Time";
+        case "Asia/Srednekolymsk": return "Russia Time Zone 10";
+        case "Asia/Taipei": return "Taipei Standard Time";
+        case "Asia/Tashkent": return "West Asia Standard Time";
+        case "Asia/Tbilisi": return "Georgian Standard Time";
+        case "Asia/Tehran": return "Iran Standard Time";
+        case "Asia/Thimphu": return "Bangladesh Standard Time";
+        case "Asia/Tokyo": return "Tokyo Standard Time";
+        case "Asia/Ulaanbaatar": return "Ulaanbaatar Standard Time";
+        case "Asia/Urumqi": return "Central Asia Standard Time";
+        case "Asia/Ust-Nera": return "Vladivostok Standard Time";
+        case "Asia/Vientiane": return "SE Asia Standard Time";
+        case "Asia/Vladivostok": return "Vladivostok Standard Time";
+        case "Asia/Yakutsk": return "Yakutsk Standard Time";
+        case "Asia/Yekaterinburg": return "Ekaterinburg Standard Time";
+        case "Asia/Yerevan": return "Caucasus Standard Time";
+        case "Atlantic/Azores": return "Azores Standard Time";
+        case "Atlantic/Bermuda": return "Atlantic Standard Time";
+        case "Atlantic/Canary": return "GMT Standard Time";
+        case "Atlantic/Cape_Verde": return "Cape Verde Standard Time";
+        case "Atlantic/Faeroe": return "GMT Standard Time";
+        case "Atlantic/Madeira": return "GMT Standard Time";
+        case "Atlantic/Reykjavik": return "Greenwich Standard Time";
+        case "Atlantic/South_Georgia": return "UTC-02";
+        case "Atlantic/St_Helena": return "Greenwich Standard Time";
+        case "Atlantic/Stanley": return "SA Eastern Standard Time";
+        case "Australia/Adelaide": return "Cen. Australia Standard Time";
+        case "Australia/Brisbane": return "E. Australia Standard Time";
+        case "Australia/Broken_Hill": return "Cen. Australia Standard Time";
+        case "Australia/Currie": return "Tasmania Standard Time";
+        case "Australia/Darwin": return "AUS Central Standard Time";
+        case "Australia/Hobart": return "Tasmania Standard Time";
+        case "Australia/Lindeman": return "E. Australia Standard Time";
+        case "Australia/Melbourne": return "AUS Eastern Standard Time";
+        case "Australia/Perth": return "W. Australia Standard Time";
+        case "Australia/Sydney": return "AUS Eastern Standard Time";
+        case "CST6CDT": return "Central Standard Time";
+        case "EST5EDT": return "Eastern Standard Time";
+        case "Etc/GMT": return "UTC";
+        case "Etc/GMT+1": return "Cape Verde Standard Time";
+        case "Etc/GMT+10": return "Hawaiian Standard Time";
+        case "Etc/GMT+11": return "UTC-11";
+        case "Etc/GMT+12": return "Dateline Standard Time";
+        case "Etc/GMT+2": return "UTC-02";
+        case "Etc/GMT+3": return "SA Eastern Standard Time";
+        case "Etc/GMT+4": return "SA Western Standard Time";
+        case "Etc/GMT+5": return "SA Pacific Standard Time";
+        case "Etc/GMT+6": return "Central America Standard Time";
+        case "Etc/GMT+7": return "US Mountain Standard Time";
+        case "Etc/GMT-1": return "W. Central Africa Standard Time";
+        case "Etc/GMT-10": return "West Pacific Standard Time";
+        case "Etc/GMT-11": return "Central Pacific Standard Time";
+        case "Etc/GMT-12": return "UTC+12";
+        case "Etc/GMT-13": return "Tonga Standard Time";
+        case "Etc/GMT-14": return "Line Islands Standard Time";
+        case "Etc/GMT-2": return "South Africa Standard Time";
+        case "Etc/GMT-3": return "E. Africa Standard Time";
+        case "Etc/GMT-4": return "Arabian Standard Time";
+        case "Etc/GMT-5": return "West Asia Standard Time";
+        case "Etc/GMT-6": return "Central Asia Standard Time";
+        case "Etc/GMT-7": return "SE Asia Standard Time";
+        case "Etc/GMT-8": return "Singapore Standard Time";
+        case "Etc/GMT-9": return "Tokyo Standard Time";
+        case "Europe/Amsterdam": return "W. Europe Standard Time";
+        case "Europe/Andorra": return "W. Europe Standard Time";
+        case "Europe/Astrakhan": return "Astrakhan Standard Time";
+        case "Europe/Athens": return "GTB Standard Time";
+        case "Europe/Belgrade": return "Central Europe Standard Time";
+        case "Europe/Berlin": return "W. Europe Standard Time";
+        case "Europe/Bratislava": return "Central Europe Standard Time";
+        case "Europe/Brussels": return "Romance Standard Time";
+        case "Europe/Bucharest": return "GTB Standard Time";
+        case "Europe/Budapest": return "Central Europe Standard Time";
+        case "Europe/Busingen": return "W. Europe Standard Time";
+        case "Europe/Chisinau": return "GTB Standard Time";
+        case "Europe/Copenhagen": return "Romance Standard Time";
+        case "Europe/Dublin": return "GMT Standard Time";
+        case "Europe/Gibraltar": return "W. Europe Standard Time";
+        case "Europe/Guernsey": return "GMT Standard Time";
+        case "Europe/Helsinki": return "FLE Standard Time";
+        case "Europe/Isle_of_Man": return "GMT Standard Time";
+        case "Europe/Istanbul": return "Turkey Standard Time";
+        case "Europe/Jersey": return "GMT Standard Time";
+        case "Europe/Kaliningrad": return "Kaliningrad Standard Time";
+        case "Europe/Kiev": return "FLE Standard Time";
+        case "Europe/Lisbon": return "GMT Standard Time";
+        case "Europe/Ljubljana": return "Central Europe Standard Time";
+        case "Europe/London": return "GMT Standard Time";
+        case "Europe/Luxembourg": return "W. Europe Standard Time";
+        case "Europe/Madrid": return "Romance Standard Time";
+        case "Europe/Malta": return "W. Europe Standard Time";
+        case "Europe/Mariehamn": return "FLE Standard Time";
+        case "Europe/Minsk": return "Belarus Standard Time";
+        case "Europe/Monaco": return "W. Europe Standard Time";
+        case "Europe/Moscow": return "Russian Standard Time";
+        case "Europe/Oslo": return "W. Europe Standard Time";
+        case "Europe/Paris": return "Romance Standard Time";
+        case "Europe/Podgorica": return "Central Europe Standard Time";
+        case "Europe/Prague": return "Central Europe Standard Time";
+        case "Europe/Riga": return "FLE Standard Time";
+        case "Europe/Rome": return "W. Europe Standard Time";
+        case "Europe/Samara": return "Russia Time Zone 3";
+        case "Europe/San_Marino": return "W. Europe Standard Time";
+        case "Europe/Sarajevo": return "Central European Standard Time";
+        case "Europe/Simferopol": return "Russian Standard Time";
+        case "Europe/Skopje": return "Central European Standard Time";
+        case "Europe/Sofia": return "FLE Standard Time";
+        case "Europe/Stockholm": return "W. Europe Standard Time";
+        case "Europe/Tallinn": return "FLE Standard Time";
+        case "Europe/Tirane": return "Central Europe Standard Time";
+        case "Europe/Uzhgorod": return "FLE Standard Time";
+        case "Europe/Vaduz": return "W. Europe Standard Time";
+        case "Europe/Vatican": return "W. Europe Standard Time";
+        case "Europe/Vienna": return "W. Europe Standard Time";
+        case "Europe/Vilnius": return "FLE Standard Time";
+        case "Europe/Volgograd": return "Russian Standard Time";
+        case "Europe/Warsaw": return "Central European Standard Time";
+        case "Europe/Zagreb": return "Central European Standard Time";
+        case "Europe/Zaporozhye": return "FLE Standard Time";
+        case "Europe/Zurich": return "W. Europe Standard Time";
+        case "Indian/Antananarivo": return "E. Africa Standard Time";
+        case "Indian/Chagos": return "Central Asia Standard Time";
+        case "Indian/Christmas": return "SE Asia Standard Time";
+        case "Indian/Cocos": return "Myanmar Standard Time";
+        case "Indian/Comoro": return "E. Africa Standard Time";
+        case "Indian/Kerguelen": return "West Asia Standard Time";
+        case "Indian/Mahe": return "Mauritius Standard Time";
+        case "Indian/Maldives": return "West Asia Standard Time";
+        case "Indian/Mauritius": return "Mauritius Standard Time";
+        case "Indian/Mayotte": return "E. Africa Standard Time";
+        case "Indian/Reunion": return "Mauritius Standard Time";
+        case "MST7MDT": return "Mountain Standard Time";
+        case "PST8PDT": return "Pacific Standard Time";
+        case "Pacific/Apia": return "Samoa Standard Time";
+        case "Pacific/Auckland": return "New Zealand Standard Time";
+        case "Pacific/Bougainville": return "Central Pacific Standard Time";
+        case "Pacific/Easter": return "Easter Island Standard Time";
+        case "Pacific/Efate": return "Central Pacific Standard Time";
+        case "Pacific/Enderbury": return "Tonga Standard Time";
+        case "Pacific/Fakaofo": return "Tonga Standard Time";
+        case "Pacific/Fiji": return "Fiji Standard Time";
+        case "Pacific/Funafuti": return "UTC+12";
+        case "Pacific/Galapagos": return "Central America Standard Time";
+        case "Pacific/Guadalcanal": return "Central Pacific Standard Time";
+        case "Pacific/Guam": return "West Pacific Standard Time";
+        case "Pacific/Honolulu": return "Hawaiian Standard Time";
+        case "Pacific/Johnston": return "Hawaiian Standard Time";
+        case "Pacific/Kiritimati": return "Line Islands Standard Time";
+        case "Pacific/Kosrae": return "Central Pacific Standard Time";
+        case "Pacific/Kwajalein": return "UTC+12";
+        case "Pacific/Majuro": return "UTC+12";
+        case "Pacific/Midway": return "UTC-11";
+        case "Pacific/Nauru": return "UTC+12";
+        case "Pacific/Niue": return "UTC-11";
+        case "Pacific/Noumea": return "Central Pacific Standard Time";
+        case "Pacific/Pago_Pago": return "UTC-11";
+        case "Pacific/Palau": return "Tokyo Standard Time";
+        case "Pacific/Ponape": return "Central Pacific Standard Time";
+        case "Pacific/Port_Moresby": return "West Pacific Standard Time";
+        case "Pacific/Rarotonga": return "Hawaiian Standard Time";
+        case "Pacific/Saipan": return "West Pacific Standard Time";
+        case "Pacific/Tahiti": return "Hawaiian Standard Time";
+        case "Pacific/Tarawa": return "UTC+12";
+        case "Pacific/Tongatapu": return "Tonga Standard Time";
+        case "Pacific/Truk": return "West Pacific Standard Time";
+        case "Pacific/Wake": return "UTC+12";
+        case "Pacific/Wallis": return "UTC+12";
+        default: return null;
+    }
+}
+
+version(Windows) version(UpdateWindowsTZTranslations) unittest
+{
+    import std.stdio : stderr;
+
+    foreach(tzName; TimeZone.getInstalledTZNames())
+    {
+        if (tzDatabaseNameToWindowsTZName(tzName) is null)
+            stderr.writeln("Missing TZName to Windows translation: ", tzName);
+    }
+}
+
+
+/++
+    $(RED Please use $(LREF parseTZConversions) instead. This function will be
+          deprecated in 2.072, because Microsoft changes their time zones too
+          often for us to compile the conversions into Phobos and have them be
+          properly up-to-date.)
+
+    Converts the given Windows time zone name to a corresponding TZ Database
+    name.
+
+    Returns null if the given time zone name cannot be converted.
+
+    See_Also:
+        $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
+              Windows <-> TZ Database Name Conversion Table)
+
+    Params:
+        tzName = The TZ Database name to convert.
+  +/
+string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc
+{
+    switch(tzName)
+    {
+        case "AUS Central Standard Time": return "Australia/Darwin";
+        case "AUS Eastern Standard Time": return "Australia/Sydney";
+        case "Afghanistan Standard Time": return "Asia/Kabul";
+        case "Haiti Standard Time": return "America/Port-au-Prince";
+        case "Alaskan Standard Time": return "America/Anchorage";
+        case "Altai Standard Time": return "Asia/Barnaul";
+        case "Arab Standard Time": return "Asia/Riyadh";
+        case "Arabian Standard Time": return "Asia/Dubai";
+        case "Arabic Standard Time": return "Asia/Baghdad";
+        case "Argentina Standard Time": return "America/Buenos_Aires";
+        case "Astrakhan Standard Time": return "Europe/Astrakhan";
+        case "Atlantic Standard Time": return "America/Halifax";
+        case "Azerbaijan Standard Time": return "Asia/Baku";
+        case "Azores Standard Time": return "Atlantic/Azores";
+        case "Bahia Standard Time": return "America/Bahia";
+        case "Bangladesh Standard Time": return "Asia/Dhaka";
+        case "Belarus Standard Time": return "Europe/Minsk";
+        case "Canada Central Standard Time": return "America/Regina";
+        case "Cape Verde Standard Time": return "Atlantic/Cape_Verde";
+        case "Caucasus Standard Time": return "Asia/Yerevan";
+        case "Cen. Australia Standard Time": return "Australia/Adelaide";
+        case "Central America Standard Time": return "America/Guatemala";
+        case "Central Asia Standard Time": return "Asia/Almaty";
+        case "Central Brazilian Standard Time": return "America/Cuiaba";
+        case "Central Europe Standard Time": return "Europe/Budapest";
+        case "Central European Standard Time": return "Europe/Warsaw";
+        case "Central Pacific Standard Time": return "Pacific/Guadalcanal";
+        case "Central Standard Time": return "America/Chicago";
+        case "Central Standard Time (Mexico)": return "America/Mexico_City";
+        case "China Standard Time": return "Asia/Shanghai";
+        case "Dateline Standard Time": return "Etc/GMT+12";
+        case "E. Africa Standard Time": return "Africa/Nairobi";
+        case "E. Australia Standard Time": return "Australia/Brisbane";
+        // This doesn't appear to be in the current stuff from MS, but the autotester
+        // is failing without it (probably because its time zone data hasn't been
+        // updated recently enough).
+        case "E. Europe Standard Time": return "Europe/Minsk";
+        case "E. South America Standard Time": return "America/Sao_Paulo";
+        case "Easter Island Standard Time": return "Pacific/Easter";
+        case "Eastern Standard Time": return "America/New_York";
+        case "Eastern Standard Time (Mexico)": return "America/Cancun";
+        case "Egypt Standard Time": return "Africa/Cairo";
+        case "Ekaterinburg Standard Time": return "Asia/Yekaterinburg";
+        case "FLE Standard Time": return "Europe/Kiev";
+        case "Fiji Standard Time": return "Pacific/Fiji";
+        case "GMT Standard Time": return "Europe/London";
+        case "GTB Standard Time": return "Europe/Athens";
+        case "Georgian Standard Time": return "Asia/Tbilisi";
+        case "Greenland Standard Time": return "America/Godthab";
+        case "Greenwich Standard Time": return "Atlantic/Reykjavik";
+        case "Hawaiian Standard Time": return "Pacific/Honolulu";
+        case "India Standard Time": return "Asia/Calcutta";
+        case "Iran Standard Time": return "Asia/Tehran";
+        case "Israel Standard Time": return "Asia/Jerusalem";
+        case "Jordan Standard Time": return "Asia/Amman";
+        case "Kaliningrad Standard Time": return "Europe/Kaliningrad";
+        // Same as with E. Europe Standard Time.
+        case "Kamchatka Standard Time": return "Asia/Kamchatka";
+        case "Korea Standard Time": return "Asia/Seoul";
+        case "Libya Standard Time": return "Africa/Tripoli";
+        case "Line Islands Standard Time": return "Pacific/Kiritimati";
+        case "Magadan Standard Time": return "Asia/Magadan";
+        case "Mauritius Standard Time": return "Indian/Mauritius";
+        // Same as with E. Europe Standard Time.
+        case "Mexico Standard Time": return "America/Mexico_City";
+        // Same as with E. Europe Standard Time.
+        case "Mexico Standard Time 2": return "America/Chihuahua";
+        // Same as with E. Europe Standard Time.
+        case "Mid-Atlantic Standard Time": return "Etc/GMT+2";
+        case "Middle East Standard Time": return "Asia/Beirut";
+        case "Montevideo Standard Time": return "America/Montevideo";
+        case "Morocco Standard Time": return "Africa/Casablanca";
+        case "Mountain Standard Time": return "America/Denver";
+        case "Mountain Standard Time (Mexico)": return "America/Chihuahua";
+        case "Myanmar Standard Time": return "Asia/Rangoon";
+        case "N. Central Asia Standard Time": return "Asia/Novosibirsk";
+        case "Namibia Standard Time": return "Africa/Windhoek";
+        case "Nepal Standard Time": return "Asia/Katmandu";
+        case "New Zealand Standard Time": return "Pacific/Auckland";
+        case "Newfoundland Standard Time": return "America/St_Johns";
+        case "North Asia East Standard Time": return "Asia/Irkutsk";
+        case "North Asia Standard Time": return "Asia/Krasnoyarsk";
+        case "North Korea Standard Time": return "Asia/Pyongyang";
+        case "Pacific SA Standard Time": return "America/Santiago";
+        case "Pacific Standard Time": return "America/Los_Angeles";
+        case "Pacific Standard Time (Mexico)": return "America/Santa_Isabel";
+        case "Pakistan Standard Time": return "Asia/Karachi";
+        case "Paraguay Standard Time": return "America/Asuncion";
+        case "Romance Standard Time": return "Europe/Paris";
+        case "Russia Time Zone 10": return "Asia/Srednekolymsk";
+        case "Russia Time Zone 11": return "Asia/Anadyr";
+        case "Russia Time Zone 3": return "Europe/Samara";
+        case "Russian Standard Time": return "Europe/Moscow";
+        case "SA Eastern Standard Time": return "America/Cayenne";
+        case "SA Pacific Standard Time": return "America/Bogota";
+        case "SA Western Standard Time": return "America/La_Paz";
+        case "SE Asia Standard Time": return "Asia/Bangkok";
+        case "Sakhalin Standard Time": return "Asia/Sakhalin";
+        case "Samoa Standard Time": return "Pacific/Apia";
+        case "Singapore Standard Time": return "Asia/Singapore";
+        case "South Africa Standard Time": return "Africa/Johannesburg";
+        case "Sri Lanka Standard Time": return "Asia/Colombo";
+        case "Syria Standard Time": return "Asia/Damascus";
+        case "Taipei Standard Time": return "Asia/Taipei";
+        case "Tasmania Standard Time": return "Australia/Hobart";
+        case "Tokyo Standard Time": return "Asia/Tokyo";
+        case "Tonga Standard Time": return "Pacific/Tongatapu";
+        case "Transbaikal Standard Time": return "Asia/Chita";
+        case "Turkey Standard Time": return "Europe/Istanbul";
+        case "US Eastern Standard Time": return "America/Indianapolis";
+        case "US Mountain Standard Time": return "America/Phoenix";
+        case "UTC": return "Etc/GMT";
+        case "UTC+12": return "Etc/GMT-12";
+        case "UTC-02": return "Etc/GMT+2";
+        case "UTC-11": return "Etc/GMT+11";
+        case "Ulaanbaatar Standard Time": return "Asia/Ulaanbaatar";
+        case "Venezuela Standard Time": return "America/Caracas";
+        case "Vladivostok Standard Time": return "Asia/Vladivostok";
+        case "W. Australia Standard Time": return "Australia/Perth";
+        case "W. Central Africa Standard Time": return "Africa/Lagos";
+        case "W. Europe Standard Time": return "Europe/Berlin";
+        case "West Asia Standard Time": return "Asia/Tashkent";
+        case "West Pacific Standard Time": return "Pacific/Port_Moresby";
+        case "Yakutsk Standard Time": return "Asia/Yakutsk";
+        default: return null;
+    }
+}
+
+version(Windows) version(UpdateWindowsTZTranslations) unittest
+{
+    import std.stdio : stderr;
+
+    foreach(winName; WindowsTimeZone.getInstalledTZNames())
+    {
+        if (windowsTZNameToTZDatabaseName(winName) is null)
+            stderr.writeln("Missing Windows to TZName translation: ", winName);
+    }
+}
+
+
+//==============================================================================
+// Section with StopWatch and Benchmark Code.
+//==============================================================================
+
+/++
+   $(D StopWatch) measures time as precisely as possible.
+
+   This class uses a high-performance counter. On Windows systems, it uses
+   $(D QueryPerformanceCounter), and on Posix systems, it uses
+   $(D clock_gettime) if available, and $(D gettimeofday) otherwise.
+
+   But the precision of $(D StopWatch) differs from system to system. It is
+   impossible to for it to be the same from system to system since the precision
+   of the system clock varies from system to system, and other system-dependent
+   and situation-dependent stuff (such as the overhead of a context switch
+   between threads) can also affect $(D StopWatch)'s accuracy.
+  +/
+@safe struct StopWatch
+{
+public:
+
+    /++
+       Auto start with constructor.
+      +/
+    this(AutoStart autostart)
+    {
+        if(autostart)
+            start();
+    }
+
+    @safe unittest
+    {
+        auto sw = StopWatch(AutoStart.yes);
+        sw.stop();
+    }
+
+
+    ///
+    bool opEquals(const StopWatch rhs) const pure nothrow
+    {
+        return opEquals(rhs);
+    }
+
+    /// ditto
+    bool opEquals(const ref StopWatch rhs) const pure nothrow
+    {
+        return _timeStart == rhs._timeStart &&
+               _timeMeasured == rhs._timeMeasured;
+    }
+
+
+    /++
+       Resets the stop watch.
+      +/
+    void reset()
+    {
+        if(_flagStarted)
+        {
+            // Set current system time if StopWatch is measuring.
+            _timeStart = TickDuration.currSystemTick;
+        }
+        else
+        {
+            // Set zero if StopWatch is not measuring.
+            _timeStart.length = 0;
+        }
+
+        _timeMeasured.length = 0;
+    }
+
+    ///
+    @safe unittest
+    {
+        StopWatch sw;
+        sw.start();
+        sw.stop();
+        sw.reset();
+        assert(sw.peek().to!("seconds", real)() == 0);
+    }
+
+
+    /++
+       Starts the stop watch.
+      +/
+    void start()
+    {
+        assert(!_flagStarted);
+        _flagStarted = true;
+        _timeStart = TickDuration.currSystemTick;
+    }
+
+    @trusted unittest
+    {
+        StopWatch sw;
+        sw.start();
+        auto t1 = sw.peek();
+        bool doublestart = true;
+        try
+            sw.start();
+        catch(AssertError e)
+            doublestart = false;
+        assert(!doublestart);
+        sw.stop();
+        assert((t1 - sw.peek()).to!("seconds", real)() <= 0);
+    }
+
+
+    /++
+       Stops the stop watch.
+      +/
+    void stop()
+    {
+        assert(_flagStarted);
+        _flagStarted = false;
+        _timeMeasured += TickDuration.currSystemTick - _timeStart;
+    }
+
+    @trusted unittest
+    {
+        StopWatch sw;
+        sw.start();
+        sw.stop();
+        auto t1 = sw.peek();
+        bool doublestop = true;
+        try
+            sw.stop();
+        catch(AssertError e)
+            doublestop = false;
+        assert(!doublestop);
+        assert((t1 - sw.peek()).to!("seconds", real)() == 0);
+    }
+
+
+    /++
+       Peek at the amount of time which has passed since the stop watch was
+       started.
+      +/
+    TickDuration peek() const
+    {
+        if(_flagStarted)
+            return TickDuration.currSystemTick - _timeStart + _timeMeasured;
+
+        return _timeMeasured;
+    }
+
+    @safe unittest
+    {
+        StopWatch sw;
+        sw.start();
+        auto t1 = sw.peek();
+        sw.stop();
+        auto t2 = sw.peek();
+        auto t3 = sw.peek();
+        assert(t1 <= t2);
+        assert(t2 == t3);
+    }
+
+
+    /++
+       Set the amount of time which has been measured since the stop watch was
+       started.
+      +/
+    void setMeasured(TickDuration d)
+    {
+        reset();
+        _timeMeasured = d;
+    }
+
+     @safe unittest
+    {
+        StopWatch sw;
+        TickDuration t0;
+        t0.length = 100;
+        sw.setMeasured(t0);
+        auto t1 = sw.peek();
+        assert(t0 == t1);
+    }
+
+
+    /++
+       Confirm whether this stopwatch is measuring time.
+      +/
+    bool running() @property const pure nothrow
+    {
+        return _flagStarted;
+    }
+
+    @safe unittest
+    {
+        StopWatch sw1;
+        assert(!sw1.running);
+        sw1.start();
+        assert(sw1.running);
+        sw1.stop();
+        assert(!sw1.running);
+        StopWatch sw2 = AutoStart.yes;
+        assert(sw2.running);
+        sw2.stop();
+        assert(!sw2.running);
+        sw2.start();
+        assert(sw2.running);
+    }
+
+
+
+
+private:
+
+    // true if observing.
+    bool _flagStarted = false;
+
+    // TickDuration at the time of StopWatch starting measurement.
+    TickDuration _timeStart;
+
+    // Total time that StopWatch ran.
+    TickDuration _timeMeasured;
+}
+
+///
+@safe unittest
+{
+    void writeln(S...)(S args){}
+    static void bar() {}
+
+    StopWatch sw;
+    enum n = 100;
+    TickDuration[n] times;
+    TickDuration last = TickDuration.from!"seconds"(0);
+    foreach(i; 0..n)
+    {
+       sw.start(); //start/resume mesuring.
+       foreach(unused; 0..1_000_000)
+           bar();
+       sw.stop();  //stop/pause measuring.
+       //Return value of peek() after having stopped are the always same.
+       writeln((i + 1) * 1_000_000, " times done, lap time: ",
+               sw.peek().msecs, "[ms]");
+       times[i] = sw.peek() - last;
+       last = sw.peek();
+    }
+    real sum = 0;
+    // To get the number of seconds,
+    // use properties of TickDuration.
+    // (seconds, msecs, usecs, hnsecs)
+    foreach(t; times)
+       sum += t.hnsecs;
+    writeln("Average time: ", sum/n, " hnsecs");
+}
+
+
+/++
+    Benchmarks code for speed assessment and comparison.
+
+    Params:
+        fun = aliases of callable objects (e.g. function names). Each should
+              take no arguments.
+        n   = The number of times each function is to be executed.
+
+    Returns:
+        The amount of time (as a $(CXREF time, TickDuration)) that it took to
+        call each function $(D n) times. The first value is the length of time
+        that it took to call $(D fun[0]) $(D n) times. The second value is the
+        length of time it took to call $(D fun[1]) $(D n) times. Etc.
+
+    Note that casting the TickDurations to $(CXREF time, Duration)s will make
+    the results easier to deal with (and it may change in the future that
+    benchmark will return an array of Durations rather than TickDurations).
+
+    See_Also:
+        $(LREF measureTime)
+  +/
+TickDuration[fun.length] benchmark(fun...)(uint n)
+{
+    TickDuration[fun.length] result;
+    StopWatch sw;
+    sw.start();
+
+    foreach(i, unused; fun)
+    {
+        sw.reset();
+        foreach(j; 0 .. n)
+            fun[i]();
+        result[i] = sw.peek();
+    }
+
+    return result;
+}
+
+///
+unittest
+{
+    import std.conv : to;
+    int a;
+    void f0() {}
+    void f1() {auto b = a;}
+    void f2() {auto b = to!string(a);}
+    auto r = benchmark!(f0, f1, f2)(10_000);
+    auto f0Result = to!Duration(r[0]); // time f0 took to run 10,000 times
+    auto f1Result = to!Duration(r[1]); // time f1 took to run 10,000 times
+    auto f2Result = to!Duration(r[2]); // time f2 took to run 10,000 times
+}
+
+@safe unittest
+{
+    int a;
+    void f0() {}
+    //void f1() {auto b = to!(string)(a);}
+    void f2() {auto b = (a);}
+    auto r = benchmark!(f0, f2)(100);
+}
+
+
+/++
+   Return value of benchmark with two functions comparing.
+  +/
+@safe struct ComparingBenchmarkResult
+{
+    /++
+       Evaluation value
+
+       This returns the evaluation value of performance as the ratio of
+       baseFunc's time over targetFunc's time. If performance is high, this
+       returns a high value.
+      +/
+    @property real point() const pure nothrow
+    {
+        return _baseTime.length / cast(const real)_targetTime.length;
+    }
+
+
+    /++
+       The time required of the base function
+      +/
+    @property public TickDuration baseTime() const pure nothrow
+    {
+        return _baseTime;
+    }
+
+
+    /++
+       The time required of the target function
+      +/
+    @property public TickDuration targetTime() const pure nothrow
+    {
+        return _targetTime;
+    }
+
+private:
+
+    this(TickDuration baseTime, TickDuration targetTime) pure nothrow
+    {
+        _baseTime = baseTime;
+        _targetTime = targetTime;
+    }
+
+    TickDuration _baseTime;
+    TickDuration _targetTime;
+}
+
+
+/++
+   Benchmark with two functions comparing.
+
+   Params:
+       baseFunc   = The function to become the base of the speed.
+       targetFunc = The function that wants to measure speed.
+       times      = The number of times each function is to be executed.
+  +/
+ComparingBenchmarkResult comparingBenchmark(alias baseFunc,
+                                            alias targetFunc,
+                                            int times = 0xfff)()
+{
+    auto t = benchmark!(baseFunc, targetFunc)(times);
+    return ComparingBenchmarkResult(t[0], t[1]);
+}
+
+///
+@safe unittest
+{
+    void f1x() {}
+    void f2x() {}
+    @safe void f1o() {}
+    @safe void f2o() {}
+    auto b1 = comparingBenchmark!(f1o, f2o, 1)(); // OK
+    //writeln(b1.point);
+}
+
+//Bug# 8450
+unittest
+{
+    @safe    void safeFunc() {}
+    @trusted void trustFunc() {}
+    @system  void sysFunc() {}
+    auto safeResult  = comparingBenchmark!((){safeFunc();}, (){safeFunc();})();
+    auto trustResult = comparingBenchmark!((){trustFunc();}, (){trustFunc();})();
+    auto sysResult   = comparingBenchmark!((){sysFunc();}, (){sysFunc();})();
+    auto mixedResult1  = comparingBenchmark!((){safeFunc();}, (){trustFunc();})();
+    auto mixedResult2  = comparingBenchmark!((){trustFunc();}, (){sysFunc();})();
+    auto mixedResult3  = comparingBenchmark!((){safeFunc();}, (){sysFunc();})();
+}
+
+
+//==============================================================================
+// Section with public helper functions and templates.
+//==============================================================================
+
+
+/++
+    Whether the given type defines all of the necessary functions for it to
+    function as a time point.
+
+    1. $(D T) must define a static property named $(D min) which is the smallest
+       value of $(D T) as $(Unqual!T).
+
+    2. $(D T) must define a static property named $(D max) which is the largest
+       value of $(D T) as $(Unqual!T).
+
+    3. $(D T) must define an $(D opBinary) for addition and subtraction that
+       accepts $(CXREF time, Duration) and returns $(D Unqual!T).
+
+    4. $(D T) must define an $(D opOpAssign) for addition and subtraction that
+       accepts $(CXREF time, Duration) and returns $(D ref Unqual!T).
+
+    5. $(D T) must define a $(D opBinary) for subtraction which accepts $(D T)
+       and returns returns $(CXREF time, Duration).
+  +/
+template isTimePoint(T)
+{
+    enum isTimePoint = hasMin &&
+                       hasMax &&
+                       hasOverloadedOpBinaryWithDuration &&
+                       hasOverloadedOpAssignWithDuration &&
+                       hasOverloadedOpBinaryWithSelf &&
+                       !is(U == Duration);
+
+private:
+
+    alias U = Unqual!T;
+
+    enum hasMin = __traits(hasMember, T, "min") &&
+                  is(typeof(T.min) == U) &&
+                  is(typeof({static assert(__traits(isStaticFunction, T.min));}));
+
+    enum hasMax = __traits(hasMember, T, "max") &&
+                  is(typeof(T.max) == U) &&
+                  is(typeof({static assert(__traits(isStaticFunction, T.max));}));
+
+    enum hasOverloadedOpBinaryWithDuration = is(typeof(T.init + Duration.init) == U) &&
+                                             is(typeof(T.init - Duration.init) == U);
+
+    enum hasOverloadedOpAssignWithDuration = is(typeof(U.init += Duration.init) == U) &&
+                                             is(typeof(U.init -= Duration.init) == U) &&
+                                             is(typeof(
+                                             {
+                                                 // Until the overload with TickDuration is removed, this is ambiguous.
+                                                 //alias add = U.opOpAssign!"+";
+                                                 //alias sub = U.opOpAssign!"-";
+                                                 U u;
+                                                 auto ref add() { return u += Duration.init; }
+                                                 auto ref sub() { return u -= Duration.init; }
+                                                 alias FA = FunctionAttribute;
+                                                 static assert((functionAttributes!add & FA.ref_) != 0);
+                                                 static assert((functionAttributes!sub & FA.ref_) != 0);
+                                             }));
+
+    enum hasOverloadedOpBinaryWithSelf = is(typeof(T.init - T.init) == Duration);
+}
+
+///
+unittest
+{
+    static assert(isTimePoint!Date);
+    static assert(isTimePoint!DateTime);
+    static assert(isTimePoint!SysTime);
+    static assert(isTimePoint!TimeOfDay);
+
+    static assert(!isTimePoint!int);
+    static assert(!isTimePoint!Duration);
+    static assert(!isTimePoint!(Interval!SysTime));
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+
+    foreach(TP; AliasSeq!(Date, DateTime, SysTime, TimeOfDay))
+    {
+        static assert(isTimePoint!(const TP), TP.stringof);
+        static assert(isTimePoint!(immutable TP), TP.stringof);
+    }
+
+    foreach(T; AliasSeq!(float, string, Duration, Interval!Date, PosInfInterval!Date, NegInfInterval!Date))
+        static assert(!isTimePoint!T, T.stringof);
+}
+
+
+/++
+    Whether the given Gregorian Year is a leap year.
+
+    Params:
+        year = The year to to be tested.
+ +/
+static bool yearIsLeapYear(int year) @safe pure nothrow
+{
+    if(year % 400 == 0)
+        return true;
+
+    if(year % 100 == 0)
+        return false;
+
+    return year % 4 == 0;
+}
+
+unittest
+{
+    import std.format : format;
+    foreach(year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999,
+                   2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011])
+    {
+        assert(!yearIsLeapYear(year), format("year: %s.", year));
+        assert(!yearIsLeapYear(-year), format("year: %s.", year));
+    }
+
+    foreach(year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
+    {
+        assert(yearIsLeapYear(year), format("year: %s.", year));
+        assert(yearIsLeapYear(-year), format("year: %s.", year));
+    }
+}
+
+/++
+    Converts from unix time (which uses midnight, January 1st, 1970 UTC as its
+    epoch and seconds as its units) to "std time" (which uses midnight,
+    January 1st, 1 A.D. UTC and hnsecs as its units).
+
+    The C standard does not specify the representation of time_t, so it is
+    implementation defined. On POSIX systems, unix time is equivalent to
+    time_t, but that's not necessarily true on other systems (e.g. it is
+    not true for the Digital Mars C runtime). So, be careful when using unix
+    time with C functions on non-POSIX systems.
+
+    "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO
+    8601 and is what $(LREF SysTime) uses internally. However, holding the time
+    as an integer in hnescs since that epoch technically isn't actually part of
+    the standard, much as it's based on it, so the name "std time" isn't
+    particularly good, but there isn't an official name for it. C# uses "ticks"
+    for the same thing, but they aren't actually clock ticks, and the term
+    "ticks" $(I is) used for actual clock ticks for $(CXREF time, MonoTime), so
+    it didn't make sense to use the term ticks here. So, for better or worse,
+    std.datetime uses the term "std time" for this.
+
+    Params:
+        unixTime = The unix time to convert.
+
+    See_Also:
+        SysTime.fromUnixTime
+  +/
+long unixTimeToStdTime(long unixTime) @safe pure nothrow
+{
+    return 621_355_968_000_000_000L + convert!("seconds", "hnsecs")(unixTime);
+}
+
+///
+unittest
+{
+    // Midnight, January 1st, 1970
+    assert(unixTimeToStdTime(0) == 621_355_968_000_000_000L);
+    assert(SysTime(unixTimeToStdTime(0)) ==
+           SysTime(DateTime(1970, 1, 1), UTC()));
+
+    assert(unixTimeToStdTime(int.max) == 642_830_804_470_000_000L);
+    assert(SysTime(unixTimeToStdTime(int.max)) ==
+           SysTime(DateTime(2038, 1, 19, 3, 14, 07), UTC()));
+
+    assert(unixTimeToStdTime(-127_127) == 621_354_696_730_000_000L);
+    assert(SysTime(unixTimeToStdTime(-127_127)) ==
+           SysTime(DateTime(1969, 12, 30, 12, 41, 13), UTC()));
+}
+
+unittest
+{
+    // Midnight, January 2nd, 1970
+    assert(unixTimeToStdTime(86_400) == 621_355_968_000_000_000L + 864_000_000_000L);
+    // Midnight, December 31st, 1969
+    assert(unixTimeToStdTime(-86_400) == 621_355_968_000_000_000L - 864_000_000_000L);
+
+    assert(unixTimeToStdTime(0) == (Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs");
+    assert(unixTimeToStdTime(0) == (DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs");
+
+    foreach(dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)])
+        assert(unixTimeToStdTime((dt - DateTime(1970, 1, 1)).total!"seconds") == (dt - DateTime.init).total!"hnsecs");
+}
+
+
+/++
+    Converts std time (which uses midnight, January 1st, 1 A.D. UTC as its epoch
+    and hnsecs as its units) to unix time (which uses midnight, January 1st,
+    1970 UTC as its epoch and seconds as its units).
+
+    The C standard does not specify the representation of time_t, so it is
+    implementation defined. On POSIX systems, unix time is equivalent to
+    time_t, but that's not necessarily true on other systems (e.g. it is
+    not true for the Digital Mars C runtime). So, be careful when using unix
+    time with C functions on non-POSIX systems.
+
+    "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO
+    8601 and is what $(LREF SysTime) uses internally. However, holding the time
+    as an integer in hnescs since that epoch technically isn't actually part of
+    the standard, much as it's based on it, so the name "std time" isn't
+    particularly good, but there isn't an official name for it. C# uses "ticks"
+    for the same thing, but they aren't actually clock ticks, and the term
+    "ticks" $(I is) used for actual clock ticks for $(CXREF time, MonoTime), so
+    it didn't make sense to use the term ticks here. So, for better or worse,
+    std.datetime uses the term "std time" for this.
+
+    By default, the return type is time_t (which is normally an alias for
+    int on 32-bit systems and long on 64-bit systems), but if a different
+    size is required than either int or long can be passed as a template
+    argument to get the desired size.
+
+    If the return type is int, and the result can't fit in an int, then the
+    closest value that can be held in 32 bits will be used (so $(D int.max)
+    if it goes over and $(D int.min) if it goes under). However, no attempt
+    is made to deal with integer overflow if the return type is long.
+
+    Params:
+        T = The return type (int or long). It defaults to time_t, which is
+            normally 32 bits on a 32-bit system and 64 bits on a 64-bit
+            system.
+        stdTime = The std time to convert.
+
+    Returns:
+        A signed integer representing the unix time which is equivalent to
+        the given std time.
+
+    See_Also:
+        SysTime.toUnixTime
+  +/
+T stdTimeToUnixTime(T = time_t)(long stdTime) @safe pure nothrow
+    if(is(T == int) || is(T == long))
+{
+    immutable unixTime = convert!("hnsecs", "seconds")(stdTime - 621_355_968_000_000_000L);
+
+    static assert(is(time_t == int) || is(time_t == long),
+                  "Currently, std.datetime only supports systems where time_t is int or long");
+
+    static if(is(T == long))
+        return unixTime;
+    else static if(is(T == int))
+    {
+        if(unixTime > int.max)
+            return int.max;
+        return unixTime < int.min ? int.min : cast(int)unixTime;
+    }
+    else static assert(0, "Bug in template constraint. Only int and long allowed.");
+}
+
+///
+unittest
+{
+    // Midnight, January 1st, 1970 UTC
+    assert(stdTimeToUnixTime(621_355_968_000_000_000L) == 0);
+
+    // 2038-01-19 03:14:07 UTC
+    assert(stdTimeToUnixTime(642_830_804_470_000_000L) == int.max);
+}
+
+unittest
+{
+    enum unixEpochAsStdTime = (Date(1970, 1, 1) - Date.init).total!"hnsecs";
+
+    assert(stdTimeToUnixTime(unixEpochAsStdTime) == 0);  //Midnight, January 1st, 1970
+    assert(stdTimeToUnixTime(unixEpochAsStdTime + 864_000_000_000L) == 86_400);  //Midnight, January 2nd, 1970
+    assert(stdTimeToUnixTime(unixEpochAsStdTime - 864_000_000_000L) == -86_400);  //Midnight, December 31st, 1969
+
+    assert(stdTimeToUnixTime((Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs") == 0);
+    assert(stdTimeToUnixTime((DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs") == 0);
+
+    foreach(dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)])
+        assert(stdTimeToUnixTime((dt - DateTime.init).total!"hnsecs") == (dt - DateTime(1970, 1, 1)).total!"seconds");
+
+    enum max = convert!("seconds", "hnsecs")(int.max);
+    enum min = convert!("seconds", "hnsecs")(int.min);
+    enum one = convert!("seconds", "hnsecs")(1);
+
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max) == int.max);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max) == int.max);
+
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + one) == int.max + 1L);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + one) == int.max);
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + 9_999_999) == int.max);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + 9_999_999) == int.max);
+
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min) == int.min);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min) == int.min);
+
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - one) == int.min - 1L);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - one) == int.min);
+    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - 9_999_999) == int.min);
+    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - 9_999_999) == int.min);
+}
+
+
+version(StdDdoc)
+{
+    version(Windows) {}
+    else
+    {
+        alias SYSTEMTIME = void*;
+        alias FILETIME = void*;
+    }
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a $(D SYSTEMTIME) struct to a $(LREF SysTime).
+
+        Params:
+            st = The $(D SYSTEMTIME) struct to convert.
+            tz = The time zone that the time in the $(D SYSTEMTIME) struct is
+                 assumed to be (if the $(D SYSTEMTIME) was supplied by a Windows
+                 system call, the $(D SYSTEMTIME) will either be in local time
+                 or UTC, depending on the call).
+
+        Throws:
+            $(LREF DateTimeException) if the given $(D SYSTEMTIME) will not fit in
+            a $(LREF SysTime), which is highly unlikely to happen given that
+            $(D SysTime.max) is in 29,228 A.D. and the maximum $(D SYSTEMTIME)
+            is in 30,827 A.D.
+      +/
+    SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe;
+
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a $(LREF SysTime) to a $(D SYSTEMTIME) struct.
+
+        The $(D SYSTEMTIME) which is returned will be set using the given
+        $(LREF SysTime)'s time zone, so to get the $(D SYSTEMTIME) in
+        UTC, set the $(LREF SysTime)'s time zone to UTC.
+
+        Params:
+            sysTime = The $(LREF SysTime) to convert.
+
+        Throws:
+            $(LREF DateTimeException) if the given $(LREF SysTime) will not fit in a
+            $(D SYSTEMTIME). This will only happen if the $(LREF SysTime)'s date is
+            prior to 1601 A.D.
+      +/
+    SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe;
+
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a $(D FILETIME) struct to the number of hnsecs since midnight,
+        January 1st, 1 A.D.
+
+        Params:
+            ft = The $(D FILETIME) struct to convert.
+
+        Throws:
+            $(LREF DateTimeException) if the given $(D FILETIME) cannot be
+            represented as the return value.
+      +/
+    long FILETIMEToStdTime(const FILETIME* ft) @safe;
+
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a $(D FILETIME) struct to a $(LREF SysTime).
+
+        Params:
+            ft = The $(D FILETIME) struct to convert.
+            tz = The time zone that the $(LREF SysTime) will be in ($(D FILETIME)s
+                 are in UTC).
+
+        Throws:
+            $(LREF DateTimeException) if the given $(D FILETIME) will not fit in a
+            $(LREF SysTime).
+      +/
+    SysTime FILETIMEToSysTime(const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe;
+
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a number of hnsecs since midnight, January 1st, 1 A.D. to a
+        $(D FILETIME) struct.
+
+        Params:
+            stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. UTC.
+
+        Throws:
+            $(LREF DateTimeException) if the given value will not fit in a
+            $(D FILETIME).
+      +/
+    FILETIME stdTimeToFILETIME(long stdTime) @safe;
+
+
+    /++
+        $(BLUE This function is Windows-Only.)
+
+        Converts a $(LREF SysTime) to a $(D FILETIME) struct.
+
+        $(D FILETIME)s are always in UTC.
+
+        Params:
+            sysTime = The $(LREF SysTime) to convert.
+
+        Throws:
+            $(LREF DateTimeException) if the given $(LREF SysTime) will not fit in a
+            $(D FILETIME).
+      +/
+    FILETIME SysTimeToFILETIME(SysTime sysTime) @safe;
+}
+else version(Windows)
+{
+    SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe
+    {
+        const max = SysTime.max;
+
+        static void throwLaterThanMax()
+        {
+            throw new DateTimeException("The given SYSTEMTIME is for a date greater than SysTime.max.");
+        }
+
+        if(st.wYear > max.year)
+            throwLaterThanMax();
+        else if(st.wYear == max.year)
+        {
+            if(st.wMonth > max.month)
+                throwLaterThanMax();
+            else if(st.wMonth == max.month)
+            {
+                if(st.wDay > max.day)
+                    throwLaterThanMax();
+                else if(st.wDay == max.day)
+                {
+                    if(st.wHour > max.hour)
+                        throwLaterThanMax();
+                    else if(st.wHour == max.hour)
+                    {
+                        if(st.wMinute > max.minute)
+                            throwLaterThanMax();
+                        else if(st.wMinute == max.minute)
+                        {
+                            if(st.wSecond > max.second)
+                                throwLaterThanMax();
+                            else if(st.wSecond == max.second)
+                            {
+                                if(st.wMilliseconds > max.fracSecs.total!"msecs")
+                                    throwLaterThanMax();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        auto dt = DateTime(st.wYear, st.wMonth, st.wDay,
+                           st.wHour, st.wMinute, st.wSecond);
+
+        return SysTime(dt, msecs(st.wMilliseconds), tz);
+    }
+
+    unittest
+    {
+        auto sysTime = Clock.currTime(UTC());
+        SYSTEMTIME st = void;
+        GetSystemTime(&st);
+        auto converted = SYSTEMTIMEToSysTime(&st, UTC());
+
+        assert(abs((converted - sysTime)) <= dur!"seconds"(2));
+    }
+
+
+    SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe
+    {
+        immutable dt = cast(DateTime)sysTime;
+
+        if(dt.year < 1601)
+            throw new DateTimeException("SYSTEMTIME cannot hold dates prior to the year 1601.");
+
+        SYSTEMTIME st;
+
+        st.wYear = dt.year;
+        st.wMonth = dt.month;
+        st.wDayOfWeek = dt.dayOfWeek;
+        st.wDay = dt.day;
+        st.wHour = dt.hour;
+        st.wMinute = dt.minute;
+        st.wSecond = dt.second;
+        st.wMilliseconds = cast(ushort)sysTime.fracSecs.total!"msecs";
+
+        return st;
+    }
+
+    unittest
+    {
+        SYSTEMTIME st = void;
+        GetSystemTime(&st);
+        auto sysTime = SYSTEMTIMEToSysTime(&st, UTC());
+
+        SYSTEMTIME result = SysTimeToSYSTEMTIME(sysTime);
+
+        assert(st.wYear == result.wYear);
+        assert(st.wMonth == result.wMonth);
+        assert(st.wDayOfWeek == result.wDayOfWeek);
+        assert(st.wDay == result.wDay);
+        assert(st.wHour == result.wHour);
+        assert(st.wMinute == result.wMinute);
+        assert(st.wSecond == result.wSecond);
+        assert(st.wMilliseconds == result.wMilliseconds);
+    }
+
+    private enum hnsecsFrom1601 = 504_911_232_000_000_000L;
+
+    long FILETIMEToStdTime(const FILETIME* ft) @safe
+    {
+        ULARGE_INTEGER ul;
+        ul.HighPart = ft.dwHighDateTime;
+        ul.LowPart = ft.dwLowDateTime;
+        ulong tempHNSecs = ul.QuadPart;
+
+        if(tempHNSecs > long.max - hnsecsFrom1601)
+            throw new DateTimeException("The given FILETIME cannot be represented as a stdTime value.");
+
+        return cast(long)tempHNSecs + hnsecsFrom1601;
+    }
+
+    SysTime FILETIMEToSysTime(const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe
+    {
+        auto sysTime = SysTime(FILETIMEToStdTime(ft), UTC());
+        sysTime.timezone = tz;
+
+        return sysTime;
+    }
+
+    unittest
+    {
+        auto sysTime = Clock.currTime(UTC());
+        SYSTEMTIME st = void;
+        GetSystemTime(&st);
+
+        FILETIME ft = void;
+        SystemTimeToFileTime(&st, &ft);
+
+        auto converted = FILETIMEToSysTime(&ft);
+
+        assert(abs((converted - sysTime)) <= dur!"seconds"(2));
+    }
+
+
+    FILETIME stdTimeToFILETIME(long stdTime) @safe
+    {
+        if(stdTime < hnsecsFrom1601)
+            throw new DateTimeException("The given stdTime value cannot be represented as a FILETIME.");
+
+        ULARGE_INTEGER ul;
+        ul.QuadPart = cast(ulong)stdTime - hnsecsFrom1601;
+
+        FILETIME ft;
+        ft.dwHighDateTime = ul.HighPart;
+        ft.dwLowDateTime = ul.LowPart;
+
+        return ft;
+    }
+
+    FILETIME SysTimeToFILETIME(SysTime sysTime) @safe
+    {
+        return stdTimeToFILETIME(sysTime.stdTime);
+    }
+
+    unittest
+    {
+        SYSTEMTIME st = void;
+        GetSystemTime(&st);
+
+        FILETIME ft = void;
+        SystemTimeToFileTime(&st, &ft);
+        auto sysTime = FILETIMEToSysTime(&ft, UTC());
+
+        FILETIME result = SysTimeToFILETIME(sysTime);
+
+        assert(ft.dwLowDateTime == result.dwLowDateTime);
+        assert(ft.dwHighDateTime == result.dwHighDateTime);
+    }
+}
+
+
+/++
+    Type representing the DOS file date/time format.
+  +/
+alias DosFileTime = uint;
+
+/++
+    Converts from DOS file date/time to $(LREF SysTime).
+
+    Params:
+        dft = The DOS file time to convert.
+        tz  = The time zone which the DOS file time is assumed to be in.
+
+    Throws:
+        $(LREF DateTimeException) if the $(D DosFileTime) is invalid.
+  +/
+SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime()) @safe
+{
+    uint dt = cast(uint)dft;
+
+    if(dt == 0)
+        throw new DateTimeException("Invalid DosFileTime.");
+
+    int year = ((dt >> 25) & 0x7F) + 1980;
+    int month = ((dt >> 21) & 0x0F);       // 1..12
+    int dayOfMonth = ((dt >> 16) & 0x1F);  // 1..31
+    int hour = (dt >> 11) & 0x1F;          // 0..23
+    int minute = (dt >> 5) & 0x3F;         // 0..59
+    int second = (dt << 1) & 0x3E;         // 0..58 (in 2 second increments)
+
+    try
+        return SysTime(DateTime(year, month, dayOfMonth, hour, minute, second), tz);
+    catch(DateTimeException dte)
+        throw new DateTimeException("Invalid DosFileTime", __FILE__, __LINE__, dte);
+}
+
+unittest
+{
+    assert(DosFileTimeToSysTime(0b00000000001000010000000000000000) ==
+                    SysTime(DateTime(1980, 1, 1, 0, 0, 0)));
+
+    assert(DosFileTimeToSysTime(0b11111111100111111011111101111101) ==
+                    SysTime(DateTime(2107, 12, 31, 23, 59, 58)));
+
+    assert(DosFileTimeToSysTime(0x3E3F8456) ==
+                    SysTime(DateTime(2011, 1, 31, 16, 34, 44)));
+}
+
+
+/++
+    Converts from $(LREF SysTime) to DOS file date/time.
+
+    Params:
+        sysTime = The $(LREF SysTime) to convert.
+
+    Throws:
+        $(LREF DateTimeException) if the given $(LREF SysTime) cannot be converted to
+        a $(D DosFileTime).
+  +/
+DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe
+{
+    auto dateTime = cast(DateTime)sysTime;
+
+    if(dateTime.year < 1980)
+        throw new DateTimeException("DOS File Times cannot hold dates prior to 1980.");
+
+    if(dateTime.year > 2107)
+        throw new DateTimeException("DOS File Times cannot hold dates past 2107.");
+
+    uint retval = 0;
+    retval = (dateTime.year - 1980) << 25;
+    retval |= (dateTime.month & 0x0F) << 21;
+    retval |= (dateTime.day & 0x1F) << 16;
+    retval |= (dateTime.hour & 0x1F) << 11;
+    retval |= (dateTime.minute & 0x3F) << 5;
+    retval |= (dateTime.second >> 1) & 0x1F;
+
+    return cast(DosFileTime)retval;
+}
+
+unittest
+{
+    assert(SysTimeToDosFileTime(SysTime(DateTime(1980, 1, 1, 0, 0, 0))) ==
+                    0b00000000001000010000000000000000);
+
+    assert(SysTimeToDosFileTime(SysTime(DateTime(2107, 12, 31, 23, 59, 58))) ==
+                    0b11111111100111111011111101111101);
+
+    assert(SysTimeToDosFileTime(SysTime(DateTime(2011, 1, 31, 16, 34, 44))) ==
+                    0x3E3F8456);
+}
+
+
+/++
+    The given array of $(D char) or random-access range of $(D char) or
+    $(D ubyte) is expected to be in the format specified in
+    $(WEB tools.ietf.org/html/rfc5322, RFC 5322) section 3.3 with the
+    grammar rule $(I date-time). It is the date-time format commonly used in
+    internet messages such as e-mail and HTTP. The corresponding
+    $(LREF SysTime) will be returned.
+
+    RFC 822 was the original spec (hence the function's name), whereas RFC 5322
+    is the current spec.
+
+    The day of the week is ignored beyond verifying that it's a valid day of the
+    week, as the day of the week can be inferred from the date. It is not
+    checked whether the given day of the week matches the actual day of the week
+    of the given date (though it is technically invalid per the spec if the
+    day of the week doesn't match the actual day of the week of the given date).
+
+    If the time zone is $(D "-0000") (or considered to be equivalent to
+    $(D "-0000") by section 4.3 of the spec), a $(LREF SimpleTimeZone) with a
+    utc offset of $(D 0) is used rather than $(LREF UTC), whereas $(D "+0000")
+    uses $(LREF UTC).
+
+    Note that because $(LREF SysTime) does not currently support having a second
+    value of 60 (as is sometimes done for leap seconds), if the date-time value
+    does have a value of 60 for the seconds, it is treated as 59.
+
+    The one area in which this function violates RFC 5322 is that it accepts
+    $(D "\n") in folding whitespace in the place of $(D "\r\n"), because the
+    HTTP spec requires it.
+
+    Throws:
+        $(LREF DateTimeException) if the given string doesn't follow the grammar
+        for a date-time field or if the resulting $(LREF SysTime) is invalid.
+  +/
+SysTime parseRFC822DateTime()(in char[] value) @safe
+{
+    import std.string : representation;
+    return parseRFC822DateTime(value.representation);
+}
+
+/++ Ditto +/
+SysTime parseRFC822DateTime(R)(R value) @safe
+    if(isRandomAccessRange!R && hasSlicing!R && hasLength!R &&
+       (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte)))
+{
+    import std.functional : not;
+    import std.ascii : isDigit, isAlpha, isPrintable;
+    import std.typecons : Rebindable;
+    import std.string : capitalize, format;
+    import std.conv : to;
+    import std.algorithm : find, all;
+
+    void stripAndCheckLen(R valueBefore, size_t minLen, size_t line = __LINE__)
+    {
+        value = _stripCFWS(valueBefore);
+        if(value.length < minLen)
+            throw new DateTimeException("date-time value too short", __FILE__, line);
+    }
+    stripAndCheckLen(value, "7Dec1200:00A".length);
+
+    static if(isArray!R && (is(ElementEncodingType!R == char) || is(ElementEncodingType!R == ubyte)))
+    {
+        static string sliceAsString(R str) @trusted
+        {
+            return cast(string)str;
+        }
+    }
+    else
+    {
+        char[4] temp;
+        char[] sliceAsString(R str) @trusted
+        {
+            size_t i = 0;
+            foreach(c; str)
+                temp[i++] = cast(char)c;
+            return temp[0 .. str.length];
+        }
+    }
+
+    // day-of-week
+    if(isAlpha(value[0]))
+    {
+        auto dowStr = sliceAsString(value[0 .. 3]);
+        switch(dowStr)
+        {
+            foreach(dow; EnumMembers!DayOfWeek)
+            {
+                enum dowC = capitalize(to!string(dow));
+                case dowC:
+                    goto afterDoW;
+            }
+            default: throw new DateTimeException(format("Invalid day-of-week: %s", dowStr));
+        }
+afterDoW: stripAndCheckLen(value[3 .. value.length], ",7Dec1200:00A".length);
+        if(value[0] != ',')
+            throw new DateTimeException("day-of-week missing comma");
+        stripAndCheckLen(value[1 .. value.length], "7Dec1200:00A".length);
+    }
+
+    // day
+    immutable digits = isDigit(value[1]) ? 2 : 1;
+    immutable day = _convDigits!short(value[0 .. digits]);
+    if(day == -1)
+        throw new DateTimeException("Invalid day");
+    stripAndCheckLen(value[digits .. value.length], "Dec1200:00A".length);
+
+    // month
+    Month month;
+    {
+        auto monStr = sliceAsString(value[0 .. 3]);
+        switch(monStr)
+        {
+            foreach(mon; EnumMembers!Month)
+            {
+                enum monC = capitalize(to!string(mon));
+                case monC:
+                {
+                    month = mon;
+                    goto afterMon;
+                }
+            }
+            default: throw new DateTimeException(format("Invalid month: %s", monStr));
+        }
+afterMon: stripAndCheckLen(value[3 .. value.length], "1200:00A".length);
+    }
+
+    // year
+    auto found = value[2 .. value.length].find!(not!(std.ascii.isDigit))();
+    size_t yearLen = value.length - found.length;
+    if(found.length == 0)
+        throw new DateTimeException("Invalid year");
+    if(found[0] == ':')
+        yearLen -= 2;
+    auto year = _convDigits!short(value[0 .. yearLen]);
+    if(year < 1900)
+    {
+        if(year == -1)
+            throw new DateTimeException("Invalid year");
+        if(yearLen < 4)
+        {
+            if(yearLen == 3)
+                year += 1900;
+            else if(yearLen == 2)
+                year += year < 50 ? 2000 : 1900;
+            else
+                throw new DateTimeException("Invalid year. Too few digits.");
+        }
+        else
+            throw new DateTimeException("Invalid year. Cannot be earlier than 1900.");
+    }
+    stripAndCheckLen(value[yearLen .. value.length], "00:00A".length);
+
+    // hour
+    immutable hour = _convDigits!short(value[0 .. 2]);
+    stripAndCheckLen(value[2 .. value.length], ":00A".length);
+    if(value[0] != ':')
+        throw new DateTimeException("Invalid hour");
+    stripAndCheckLen(value[1 .. value.length], "00A".length);
+
+    // minute
+    immutable minute = _convDigits!short(value[0 .. 2]);
+    stripAndCheckLen(value[2 .. value.length], "A".length);
+
+    // second
+    short second;
+    if(value[0] == ':')
+    {
+        stripAndCheckLen(value[1 .. value.length], "00A".length);
+        second = _convDigits!short(value[0 .. 2]);
+        // this is just if/until SysTime is sorted out to fully support leap seconds
+        if(second == 60)
+            second = 59;
+        stripAndCheckLen(value[2 .. value.length], "A".length);
+    }
+
+    immutable(TimeZone) parseTZ(int sign)
+    {
+        if(value.length < 5)
+            throw new DateTimeException("Invalid timezone");
+        immutable zoneHours = _convDigits!short(value[1 .. 3]);
+        immutable zoneMinutes = _convDigits!short(value[3 .. 5]);
+        if(zoneHours == -1 || zoneMinutes == -1 || zoneMinutes > 59)
+            throw new DateTimeException("Invalid timezone");
+        value = value[5 .. value.length];
+        immutable utcOffset = (dur!"hours"(zoneHours) + dur!"minutes"(zoneMinutes)) * sign;
+        if(utcOffset == Duration.zero)
+        {
+            return sign == 1 ? cast(immutable(TimeZone))UTC()
+                             : cast(immutable(TimeZone))new immutable SimpleTimeZone(Duration.zero);
+        }
+        return new immutable(SimpleTimeZone)(utcOffset);
+    }
+
+    // zone
+    Rebindable!(immutable TimeZone) tz;
+    if(value[0] == '-')
+        tz = parseTZ(-1);
+    else if(value[0] == '+')
+        tz = parseTZ(1);
+    else
+    {
+        // obs-zone
+        immutable tzLen = value.length - find(value, ' ', '\t', '(')[0].length;
+        switch(sliceAsString(value[0 .. tzLen <= 4 ? tzLen : 4]))
+        {
+            case "UT": case "GMT": tz = UTC(); break;
+            case "EST": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break;
+            case "EDT": tz = new immutable SimpleTimeZone(dur!"hours"(-4)); break;
+            case "CST": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break;
+            case "CDT": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break;
+            case "MST": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break;
+            case "MDT": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break;
+            case "PST": tz = new immutable SimpleTimeZone(dur!"hours"(-8)); break;
+            case "PDT": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break;
+            case "J": case "j": throw new DateTimeException("Invalid timezone");
+            default:
+            {
+                if(all!(std.ascii.isAlpha)(value[0 .. tzLen]))
+                {
+                    tz = new immutable SimpleTimeZone(Duration.zero);
+                    break;
+                }
+                throw new DateTimeException("Invalid timezone");
+            }
+        }
+        value = value[tzLen .. value.length];
+    }
+
+    // This is kind of arbitrary. Technically, nothing but CFWS is legal past
+    // the end of the timezone, but we don't want to be picky about that in a
+    // function that's just parsing rather than validating. So, the idea here is
+    // that if the next character is printable (and not part of CFWS), then it
+    // might be part of the timezone and thus affect what the timezone was
+    // supposed to be, so we'll throw, but otherwise, we'll just ignore it.
+    if(!value.empty && isPrintable(value[0]) && value[0] != ' ' && value[0] != '(')
+        throw new DateTimeException("Invalid timezone");
+
+    try
+        return SysTime(DateTime(year, month, day, hour, minute, second), tz);
+    catch(DateTimeException dte)
+        throw new DateTimeException("date-time format is correct, but the resulting SysTime is invalid.", dte);
+}
+
+///
+unittest
+{
+    auto tz = new immutable SimpleTimeZone(hours(-8));
+    assert(parseRFC822DateTime("Sat, 6 Jan 1990 12:14:19 -0800") ==
+           SysTime(DateTime(1990, 1, 6, 12, 14, 19), tz));
+
+    assert(parseRFC822DateTime("9 Jul 2002 13:11 +0000") ==
+           SysTime(DateTime(2002, 7, 9, 13, 11, 0), UTC()));
+
+    auto badStr = "29 Feb 2001 12:17:16 +0200";
+    assertThrown!DateTimeException(parseRFC822DateTime(badStr));
+}
+
+version(unittest) void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__)
+{
+    import std.string;
+    import std.format : format;
+    auto value = cr(str);
+    auto result = parseRFC822DateTime(value);
+    if(result != expected)
+        throw new AssertError(format("wrong result. expected [%s], actual[%s]", expected, result), __FILE__, line);
+}
+
+version(unittest) void testBadParse822(alias cr)(string str, size_t line = __LINE__)
+{
+    try
+        parseRFC822DateTime(cr(str));
+    catch(DateTimeException)
+        return;
+    throw new AssertError("No DateTimeException was thrown", __FILE__, line);
+}
+
+unittest
+{
+    import std.algorithm;
+    import std.ascii;
+    import std.format : format;
+    import std.meta;
+    import std.range;
+    import std.string;
+    import std.typecons;
+
+    static struct Rand3Letters
+    {
+        enum empty = false;
+        @property auto front() { return _mon; }
+        void popFront()
+        {
+            import std.random;
+            _mon = rndGen.map!(a => letters[a % letters.length])().take(3).array().assumeUnique();
+        }
+        string _mon;
+        static auto start() { Rand3Letters retval; retval.popFront(); return retval; }
+    }
+
+    foreach(cr; AliasSeq!(function(string a){return cast(char[])a;},
+                          function(string a){return cast(ubyte[])a;},
+                          function(string a){return a;},
+                          function(string a){return map!(b => cast(char)b)(a.representation);}))
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        scope(failure) writeln(typeof(cr).stringof);
+        alias test = testParse822!cr;
+        alias testBad = testBadParse822!cr;
+
+        immutable std1 = DateTime(2012, 12, 21, 13, 14, 15);
+        immutable std2 = DateTime(2012, 12, 21, 13, 14, 0);
+        immutable dst1 = DateTime(1976, 7, 4, 5, 4, 22);
+        immutable dst2 = DateTime(1976, 7, 4, 5, 4, 0);
+
+        test("21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC()));
+        test("21 Dec 2012 13:14 +0000", SysTime(std2, UTC()));
+        test("Fri, 21 Dec 2012 13:14 +0000", SysTime(std2, UTC()));
+        test("Fri, 21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC()));
+
+        test("04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
+        test("04 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
+        test("Sun, 04 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
+        test("Sun, 04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
+
+        test("4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
+        test("4 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
+        test("Sun, 4 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
+        test("Sun, 4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
+
+        auto badTZ = new immutable SimpleTimeZone(Duration.zero);
+        test("21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ));
+        test("21 Dec 2012 13:14 -0000", SysTime(std2, badTZ));
+        test("Fri, 21 Dec 2012 13:14 -0000", SysTime(std2, badTZ));
+        test("Fri, 21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ));
+
+        test("04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
+        test("04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
+        test("Sun, 04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
+        test("Sun, 04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
+
+        test("4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
+        test("4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
+        test("Sun, 4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
+        test("Sun, 4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
+
+        auto pst = new immutable SimpleTimeZone(dur!"hours"(-8));
+        auto pdt = new immutable SimpleTimeZone(dur!"hours"(-7));
+        test("21 Dec 2012 13:14:15 -0800", SysTime(std1, pst));
+        test("21 Dec 2012 13:14 -0800", SysTime(std2, pst));
+        test("Fri, 21 Dec 2012 13:14 -0800", SysTime(std2, pst));
+        test("Fri, 21 Dec 2012 13:14:15 -0800", SysTime(std1, pst));
+
+        test("04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
+        test("04 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
+        test("Sun, 04 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
+        test("Sun, 04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
+
+        test("4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
+        test("4 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
+        test("Sun, 4 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
+        test("Sun, 4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
+
+        auto cet = new immutable SimpleTimeZone(dur!"hours"(1));
+        auto cest = new immutable SimpleTimeZone(dur!"hours"(2));
+        test("21 Dec 2012 13:14:15 +0100", SysTime(std1, cet));
+        test("21 Dec 2012 13:14 +0100", SysTime(std2, cet));
+        test("Fri, 21 Dec 2012 13:14 +0100", SysTime(std2, cet));
+        test("Fri, 21 Dec 2012 13:14:15 +0100", SysTime(std1, cet));
+
+        test("04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
+        test("04 Jul 1976 05:04 +0200", SysTime(dst2, cest));
+        test("Sun, 04 Jul 1976 05:04 +0200", SysTime(dst2, cest));
+        test("Sun, 04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
+
+        test("4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
+        test("4 Jul 1976 05:04 +0200", SysTime(dst2, cest));
+        test("Sun, 4 Jul 1976 05:04 +0200", SysTime(dst2, cest));
+        test("Sun, 4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
+
+        // dst and std times are switched in the Southern Hemisphere which is why the
+        // time zone names and DateTime variables don't match.
+        auto cstStd = new immutable SimpleTimeZone(dur!"hours"(9) + dur!"minutes"(30));
+        auto cstDST = new immutable SimpleTimeZone(dur!"hours"(10) + dur!"minutes"(30));
+        test("21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST));
+        test("21 Dec 2012 13:14 +1030", SysTime(std2, cstDST));
+        test("Fri, 21 Dec 2012 13:14 +1030", SysTime(std2, cstDST));
+        test("Fri, 21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST));
+
+        test("04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
+        test("04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
+        test("Sun, 04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
+        test("Sun, 04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
+
+        test("4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
+        test("4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
+        test("Sun, 4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
+        test("Sun, 4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
+
+        foreach(int i, mon; _monthNames)
+        {
+            test(format("17 %s 2012 00:05:02 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 2), UTC()));
+            test(format("17 %s 2012 00:05 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 0), UTC()));
+        }
+
+        import std.uni;
+        foreach(mon; chain(_monthNames[].map!(a => toLower(a))(),
+                           _monthNames[].map!(a => toUpper(a))(),
+                           ["Jam", "Jen", "Fec", "Fdb", "Mas", "Mbr", "Aps", "Aqr", "Mai", "Miy",
+                            "Jum", "Jbn", "Jup", "Jal", "Aur", "Apg", "Sem", "Sap", "Ocm", "Odt",
+                            "Nom", "Nav", "Dem", "Dac"],
+                           Rand3Letters.start().take(20)))
+        {
+            scope(failure) writefln("Month: %s", mon);
+            testBad(format("17 %s 2012 00:05:02 +0000", mon));
+            testBad(format("17 %s 2012 00:05 +0000", mon));
+        }
+
+        immutable string[7] daysOfWeekNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+        {
+            auto start = SysTime(DateTime(2012, 11, 11, 9, 42, 0), UTC());
+            int day = 11;
+
+            foreach(int i, dow; daysOfWeekNames)
+            {
+                auto curr = start + dur!"days"(i);
+                test(format("%s, %s Nov 2012 09:42:00 +0000", dow, day), curr);
+                test(format("%s, %s Nov 2012 09:42 +0000", dow, day++), curr);
+
+                // Whether the day of the week matches the date is ignored.
+                test(format("%s, 11 Nov 2012 09:42:00 +0000", dow), start);
+                test(format("%s, 11 Nov 2012 09:42 +0000", dow), start);
+            }
+        }
+
+        foreach(dow; chain(daysOfWeekNames[].map!(a => toLower(a))(),
+                           daysOfWeekNames[].map!(a => toUpper(a))(),
+                           ["Sum", "Spn", "Mom", "Man", "Tuf", "Tae", "Wem", "Wdd", "The", "Tur",
+                            "Fro", "Fai", "San", "Sut"],
+                           Rand3Letters.start().take(20)))
+        {
+            scope(failure) writefln("Day of Week: %s", dow);
+            testBad(format("%s, 11 Nov 2012 09:42:00 +0000", dow));
+            testBad(format("%s, 11 Nov 2012 09:42 +0000", dow));
+        }
+
+        testBad("31 Dec 1899 23:59:59 +0000");
+        test("01 Jan 1900 00:00:00 +0000", SysTime(Date(1900, 1, 1), UTC()));
+        test("01 Jan 1900 00:00:00 -0000", SysTime(Date(1900, 1, 1),
+                                                   new immutable SimpleTimeZone(Duration.zero)));
+        test("01 Jan 1900 00:00:00 -0700", SysTime(Date(1900, 1, 1),
+                                                   new immutable SimpleTimeZone(dur!"hours"(-7))));
+
+        {
+            auto st1 = SysTime(Date(1900, 1, 1), UTC());
+            auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11)));
+            foreach(i; 1900 .. 2102)
+            {
+                test(format("1 Jan %05d 00:00 +0000", i), st1);
+                test(format("1 Jan %05d 00:00 -1100", i), st2);
+                st1.add!"years"(1);
+                st2.add!"years"(1);
+            }
+            st1.year = 9998;
+            st2.year = 9998;
+            foreach(i; 9998 .. 11_002)
+            {
+                test(format("1 Jan %05d 00:00 +0000", i), st1);
+                test(format("1 Jan %05d 00:00 -1100", i), st2);
+                st1.add!"years"(1);
+                st2.add!"years"(1);
+            }
+        }
+
+        testBad("12 Feb 1907 23:17:09 0000");
+        testBad("12 Feb 1907 23:17:09 +000");
+        testBad("12 Feb 1907 23:17:09 -000");
+        testBad("12 Feb 1907 23:17:09 +00000");
+        testBad("12 Feb 1907 23:17:09 -00000");
+        testBad("12 Feb 1907 23:17:09 +A");
+        testBad("12 Feb 1907 23:17:09 +PST");
+        testBad("12 Feb 1907 23:17:09 -A");
+        testBad("12 Feb 1907 23:17:09 -PST");
+
+        // test trailing stuff that gets ignored
+        {
+            foreach(c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1)))
+            {
+                scope(failure) writefln("c: %d", c);
+                test(format("21 Dec 2012 13:14:15 +0000%c", cast(char)c), SysTime(std1, UTC()));
+                test(format("21 Dec 2012 13:14:15 +0000%c  ", cast(char)c), SysTime(std1, UTC()));
+                test(format("21 Dec 2012 13:14:15 +0000%chello", cast(char)c), SysTime(std1, UTC()));
+            }
+        }
+
+        // test trailing stuff that doesn't get ignored
+        {
+            foreach(c; chain(iota(33, '('), iota('(' + 1, 127)))
+            {
+                scope(failure) writefln("c: %d", c);
+                testBad(format("21 Dec 2012 13:14:15 +0000%c", cast(char)c));
+                testBad(format("21 Dec 2012 13:14:15 +0000%c   ", cast(char)c));
+                testBad(format("21 Dec 2012 13:14:15 +0000%chello", cast(char)c));
+            }
+        }
+
+        testBad("32 Jan 2012 12:13:14 -0800");
+        testBad("31 Jan 2012 24:13:14 -0800");
+        testBad("31 Jan 2012 12:60:14 -0800");
+        testBad("31 Jan 2012 12:13:61 -0800");
+        testBad("31 Jan 2012 12:13:14 -0860");
+        test("31 Jan 2012 12:13:14 -0859",
+             SysTime(DateTime(2012, 1, 31, 12, 13, 14),
+                     new immutable SimpleTimeZone(dur!"hours"(-8) + dur!"minutes"(-59))));
+
+        // leap-seconds
+        test("21 Dec 2012 15:59:60 -0800", SysTime(DateTime(2012, 12, 21, 15, 59, 59), pst));
+
+        // FWS
+        test("Sun,4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
+        test("Sun,4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
+        test("Sun,4 Jul 1976 05:04 +0930 (foo)", SysTime(dst2, cstStd));
+        test("Sun,4 Jul 1976 05:04:22 +0930 (foo)", SysTime(dst1, cstStd));
+        test("Sun,4  \r\n  Jul  \r\n  1976  \r\n  05:04  \r\n  +0930  \r\n  (foo)", SysTime(dst2, cstStd));
+        test("Sun,4  \r\n  Jul  \r\n  1976  \r\n  05:04:22  \r\n  +0930  \r\n  (foo)", SysTime(dst1, cstStd));
+
+        auto str = "01 Jan 2012 12:13:14 -0800 ";
+        test(str, SysTime(DateTime(2012, 1, 1, 12, 13, 14), new immutable SimpleTimeZone(hours(-8))));
+        foreach(i; 0 .. str.length)
+        {
+            auto currStr = str.dup;
+            currStr[i] = 'x';
+            scope(failure) writefln("failed: %s", currStr);
+            testBad(cast(string)currStr);
+        }
+        foreach(i; 2 .. str.length)
+        {
+            auto currStr = str[0 .. $ - i];
+            scope(failure) writefln("failed: %s", currStr);
+            testBad(cast(string)currStr);
+            testBad((cast(string)currStr) ~ "                                    ");
+        }
+    }();
+}
+
+// Obsolete Format per section 4.3 of RFC 5322.
+unittest
+{
+    import std.algorithm;
+    import std.ascii;
+    import std.format : format;
+    import std.meta;
+    import std.range;
+    import std.string;
+    import std.typecons;
+
+    auto std1 = SysTime(DateTime(2012, 12, 21, 13, 14, 15), UTC());
+    auto std2 = SysTime(DateTime(2012, 12, 21, 13, 14, 0), UTC());
+    auto std3 = SysTime(DateTime(1912, 12, 21, 13, 14, 15), UTC());
+    auto std4 = SysTime(DateTime(1912, 12, 21, 13, 14, 0), UTC());
+    auto dst1 = SysTime(DateTime(1976, 7, 4, 5, 4, 22), UTC());
+    auto dst2 = SysTime(DateTime(1976, 7, 4, 5, 4, 0), UTC());
+    auto tooLate1 = SysTime(Date(10_000, 1, 1), UTC());
+    auto tooLate2 = SysTime(DateTime(12_007, 12, 31, 12, 22, 19), UTC());
+
+    foreach(cr; AliasSeq!(function(string a){return cast(char[])a;},
+                          function(string a){return cast(ubyte[])a;},
+                          function(string a){return a;},
+                          function(string a){return map!(b => cast(char)b)(a.representation);}))
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        scope(failure) writeln(typeof(cr).stringof);
+        alias test = testParse822!cr;
+        {
+            auto list = ["", " ", " \r\n\t", "\t\r\n (hello world( frien(dog)) silly \r\n )  \t\t \r\n ()",
+                         " \n ", "\t\n\t", " \n\t (foo) \n (bar) \r\n (baz) \n "];
+
+            foreach(i, cfws; list)
+            {
+                scope(failure) writefln("i: %s", i);
+
+                test(format("%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1);
+                test(format("%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1);
+
+                test(format("%1$s04%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04:22 +0000%1$s", cfws), dst1);
+
+                test(format("%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+
+                test(format("%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1);
+                test(format("%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1);
+
+                test(format("%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+
+                test(format("%1$s4%1$sJul%1$s76 05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s4%1$sJul%1$s76 05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+
+                test(format("%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3);
+                test(format("%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4);
+                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3);
+
+                test(format("%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+
+                test(format("%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+                test(format("%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
+                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
+
+                test(format("%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1);
+                test(format("%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2);
+                test(format("%1$sSat%1$s,%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1);
+                test(format("%1$sSun%1$s,%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2);
+            }
+        }
+
+        // test years of 1, 2, and 3 digits.
+        {
+            auto st1 = SysTime(Date(2000, 1, 1), UTC());
+            auto st2 = SysTime(Date(2000, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12)));
+            foreach(i; 0 .. 50)
+            {
+                test(format("1 Jan %02d 00:00 GMT", i), st1);
+                test(format("1 Jan %02d 00:00 -1200", i), st2);
+                st1.add!"years"(1);
+                st2.add!"years"(1);
+            }
+        }
+
+        {
+            auto st1 = SysTime(Date(1950, 1, 1), UTC());
+            auto st2 = SysTime(Date(1950, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12)));
+            foreach(i; 50 .. 100)
+            {
+                test(format("1 Jan %02d 00:00 GMT", i), st1);
+                test(format("1 Jan %02d 00:00 -1200", i), st2);
+                st1.add!"years"(1);
+                st2.add!"years"(1);
+            }
+        }
+
+        {
+            auto st1 = SysTime(Date(1900, 1, 1), UTC());
+            auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11)));
+            foreach(i; 0 .. 1000)
+            {
+                test(format("1 Jan %03d 00:00 GMT", i), st1);
+                test(format("1 Jan %03d 00:00 -1100", i), st2);
+                st1.add!"years"(1);
+                st2.add!"years"(1);
+            }
+        }
+
+        foreach(i; 0 .. 10)
+        {
+            auto str1 = cr(format("1 Jan %d 00:00 GMT", i));
+            auto str2 = cr(format("1 Jan %d 00:00 -1200", i));
+            assertThrown!DateTimeException(parseRFC822DateTime(str1));
+            assertThrown!DateTimeException(parseRFC822DateTime(str1));
+        }
+
+        // test time zones
+        {
+            auto dt = DateTime(1982, 05, 03, 12, 22, 04);
+            test("Wed, 03 May 1982 12:22:04 UT", SysTime(dt, UTC()));
+            test("Wed, 03 May 1982 12:22:04 GMT", SysTime(dt, UTC()));
+            test("Wed, 03 May 1982 12:22:04 EST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5))));
+            test("Wed, 03 May 1982 12:22:04 EDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-4))));
+            test("Wed, 03 May 1982 12:22:04 CST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6))));
+            test("Wed, 03 May 1982 12:22:04 CDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5))));
+            test("Wed, 03 May 1982 12:22:04 MST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7))));
+            test("Wed, 03 May 1982 12:22:04 MDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6))));
+            test("Wed, 03 May 1982 12:22:04 PST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-8))));
+            test("Wed, 03 May 1982 12:22:04 PDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7))));
+
+            auto badTZ = new immutable SimpleTimeZone(Duration.zero);
+            foreach(dchar c; filter!(a => a != 'j' && a != 'J')(letters))
+            {
+                scope(failure) writefln("c: %s", c);
+                test(format("Wed, 03 May 1982 12:22:04 %s", c), SysTime(dt, badTZ));
+                test(format("Wed, 03 May 1982 12:22:04%s", c), SysTime(dt, badTZ));
+            }
+
+            foreach(dchar c; ['j', 'J'])
+            {
+                scope(failure) writefln("c: %s", c);
+                assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04 %s", c))));
+                assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04%s", c))));
+            }
+
+            foreach(string s; ["AAA", "GQW", "DDT", "PDA", "GT", "GM"])
+            {
+                scope(failure) writefln("s: %s", s);
+                test(format("Wed, 03 May 1982 12:22:04 %s", s), SysTime(dt, badTZ));
+            }
+
+            // test trailing stuff that gets ignored
+            {
+                foreach(c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1)))
+                {
+                    scope(failure) writefln("c: %d", c);
+                    test(format("21Dec1213:14:15+0000%c", cast(char)c), std1);
+                    test(format("21Dec1213:14:15+0000%c  ", cast(char)c), std1);
+                    test(format("21Dec1213:14:15+0000%chello", cast(char)c), std1);
+                }
+            }
+
+            // test trailing stuff that doesn't get ignored
+            {
+                foreach(c; chain(iota(33, '('), iota('(' + 1, 127)))
+                {
+                    scope(failure) writefln("c: %d", c);
+                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c", cast(char)c))));
+                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c  ", cast(char)c))));
+                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%chello", cast(char)c))));
+                }
+            }
+        }
+
+        // test that the checks for minimum length work correctly and avoid
+        // any RangeErrors.
+        test("7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
+                                     new immutable SimpleTimeZone(Duration.zero)));
+        test("Fri,7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
+                                         new immutable SimpleTimeZone(Duration.zero)));
+        test("7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
+                                        new immutable SimpleTimeZone(Duration.zero)));
+        test("Fri,7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
+                                            new immutable SimpleTimeZone(Duration.zero)));
+
+        auto tooShortMsg = collectExceptionMsg!DateTimeException(parseRFC822DateTime(""));
+        foreach(str; ["Fri,7Dec1200:00:00", "7Dec1200:00:00"])
+        {
+            foreach(i; 0 .. str.length)
+            {
+                auto value = str[0 .. $ - i];
+                scope(failure) writeln(value);
+                assert(collectExceptionMsg!DateTimeException(parseRFC822DateTime(value)) == tooShortMsg);
+            }
+        }
+    }();
+}
+
+
+/++
+    Whether all of the given strings are valid units of time.
+
+    $(D "nsecs") is not considered a valid unit of time. Nothing in std.datetime
+    can handle precision greater than hnsecs, and the few functions in core.time
+    which deal with "nsecs" deal with it explicitly.
+  +/
+bool validTimeUnits(string[] units...) @safe pure nothrow
+{
+    import std.algorithm : canFind;
+    foreach(str; units)
+    {
+        if(!canFind(timeStrings[], str))
+            return false;
+    }
+
+    return true;
+}
+
+
+/++
+    Compares two time unit strings. $(D "years") are the largest units and
+    $(D "hnsecs") are the smallest.
+
+    Returns:
+        $(BOOKTABLE,
+        $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+        $(TR $(TD this == rhs) $(TD 0))
+        $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+        )
+
+    Throws:
+        $(LREF DateTimeException) if either of the given strings is not a valid
+        time unit string.
+ +/
+int cmpTimeUnits(string lhs, string rhs) @safe pure
+{
+    import std.format : format;
+    import std.algorithm : countUntil;
+
+    auto tstrings = timeStrings;
+    immutable indexOfLHS = countUntil(tstrings, lhs);
+    immutable indexOfRHS = countUntil(tstrings, rhs);
+
+    enforce(indexOfLHS != -1, format("%s is not a valid TimeString", lhs));
+    enforce(indexOfRHS != -1, format("%s is not a valid TimeString", rhs));
+
+    if(indexOfLHS < indexOfRHS)
+        return -1;
+    if(indexOfLHS > indexOfRHS)
+        return 1;
+
+    return 0;
+}
+
+unittest
+{
+    foreach(i, outerUnits; timeStrings)
+    {
+        assert(cmpTimeUnits(outerUnits, outerUnits) == 0);
+
+        //For some reason, $ won't compile.
+        foreach(innerUnits; timeStrings[i+1 .. timeStrings.length])
+            assert(cmpTimeUnits(outerUnits, innerUnits) == -1);
+    }
+
+    foreach(i, outerUnits; timeStrings)
+    {
+        foreach(innerUnits; timeStrings[0 .. i])
+            assert(cmpTimeUnits(outerUnits, innerUnits) == 1);
+    }
+}
+
+
+/++
+    Compares two time unit strings at compile time. $(D "years") are the largest
+    units and $(D "hnsecs") are the smallest.
+
+    This template is used instead of $(D cmpTimeUnits) because exceptions
+    can't be thrown at compile time and $(D cmpTimeUnits) must enforce that
+    the strings it's given are valid time unit strings. This template uses a
+    template constraint instead.
+
+    Returns:
+        $(BOOKTABLE,
+        $(TR $(TD this &lt; rhs) $(TD &lt; 0))
+        $(TR $(TD this == rhs) $(TD 0))
+        $(TR $(TD this &gt; rhs) $(TD &gt; 0))
+        )
+ +/
+template CmpTimeUnits(string lhs, string rhs)
+    if(validTimeUnits(lhs, rhs))
+{
+    enum CmpTimeUnits = cmpTimeUnitsCTFE(lhs, rhs);
+}
+
+
+/+
+    Helper function for $(D CmpTimeUnits).
+ +/
+private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow
+{
+    import std.algorithm : countUntil;
+    auto tstrings = timeStrings;
+    immutable indexOfLHS = countUntil(tstrings, lhs);
+    immutable indexOfRHS = countUntil(tstrings, rhs);
+
+    if(indexOfLHS < indexOfRHS)
+        return -1;
+    if(indexOfLHS > indexOfRHS)
+        return 1;
+
+    return 0;
+}
+
+unittest
+{
+    import std.format : format;
+    import std.meta;
+    import std.string;
+    import std.typecons;
+
+    static string genTest(size_t index)
+    {
+        auto currUnits = timeStrings[index];
+        auto test = format(`assert(CmpTimeUnits!("%s", "%s") == 0);`, currUnits, currUnits);
+
+        foreach(units; timeStrings[index + 1 .. $])
+            test ~= format(`assert(CmpTimeUnits!("%s", "%s") == -1);`, currUnits, units);
+
+        foreach(units; timeStrings[0 .. index])
+            test ~= format(`assert(CmpTimeUnits!("%s", "%s") == 1);`, currUnits, units);
+
+        return test;
+    }
+
+    static assert(timeStrings.length == 10);
+    foreach(n; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
+        mixin(genTest(n));
+}
+
+
+/++
+    Returns whether the given value is valid for the given unit type when in a
+    time point. Naturally, a duration is not held to a particular range, but
+    the values in a time point are (e.g. a month must be in the range of
+    1 - 12 inclusive).
+
+    Params:
+        units = The units of time to validate.
+        value = The number to validate.
+  +/
+bool valid(string units)(int value) @safe pure nothrow
+    if(units == "months" ||
+       units == "hours" ||
+       units == "minutes" ||
+       units == "seconds")
+{
+    static if(units == "months")
+        return value >= Month.jan && value <= Month.dec;
+    else static if(units == "hours")
+        return value >= 0 && value <= TimeOfDay.maxHour;
+    else static if(units == "minutes")
+        return value >= 0 && value <= TimeOfDay.maxMinute;
+    else static if(units == "seconds")
+        return value >= 0 && value <= TimeOfDay.maxSecond;
+}
+
+///
+unittest
+{
+    assert(valid!"hours"(12));
+    assert(!valid!"hours"(32));
+    assert(valid!"months"(12));
+    assert(!valid!"months"(13));
+}
+
+
+/++
+    Returns whether the given day is valid for the given year and month.
+
+    Params:
+        units = The units of time to validate.
+        year  = The year of the day to validate.
+        month = The month of the day to validate.
+        day   = The day to validate.
+  +/
+bool valid(string units)(int year, int month, int day) @safe pure nothrow
+    if(units == "days")
+{
+    return day > 0 && day <= maxDay(year, month);
+}
+
+
+/++
+    Params:
+        units = The units of time to validate.
+        value = The number to validate.
+        file  = The file that the $(LREF DateTimeException) will list if thrown.
+        line  = The line number that the $(LREF DateTimeException) will list if
+                thrown.
+
+    Throws:
+        $(LREF DateTimeException) if $(D valid!units(value)) is false.
+  +/
+void enforceValid(string units)(int value, string file = __FILE__, size_t line = __LINE__) @safe pure
+    if(units == "months" ||
+       units == "hours" ||
+       units == "minutes" ||
+       units == "seconds")
+{
+    import std.format : format;
+
+    static if(units == "months")
+    {
+        if(!valid!units(value))
+            throw new DateTimeException(format("%s is not a valid month of the year.", value), file, line);
+    }
+    else static if(units == "hours")
+    {
+        if(!valid!units(value))
+            throw new DateTimeException(format("%s is not a valid hour of the day.", value), file, line);
+    }
+    else static if(units == "minutes")
+    {
+        if(!valid!units(value))
+            throw new DateTimeException(format("%s is not a valid minute of an hour.", value), file, line);
+    }
+    else static if(units == "seconds")
+    {
+        if(!valid!units(value))
+            throw new DateTimeException(format("%s is not a valid second of a minute.", value), file, line);
+    }
+}
+
+
+/++
+    Params:
+        units = The units of time to validate.
+        year  = The year of the day to validate.
+        month = The month of the day to validate.
+        day   = The day to validate.
+        file  = The file that the $(LREF DateTimeException) will list if thrown.
+        line  = The line number that the $(LREF DateTimeException) will list if
+                thrown.
+
+    Throws:
+        $(LREF DateTimeException) if $(D valid!"days"(year, month, day)) is false.
+  +/
+void enforceValid(string units)
+                 (int year, Month month, int day, string file = __FILE__, size_t line = __LINE__) @safe pure
+    if(units == "days")
+{
+    import std.format : format;
+    if(!valid!"days"(year, month, day))
+        throw new DateTimeException(format("%s is not a valid day in %s in %s", day, month, year), file, line);
+}
+
+
+/++
+    Returns the number of months from the current months of the year to the
+    given month of the year. If they are the same, then the result is 0.
+
+    Params:
+        currMonth = The current month of the year.
+        month     = The month of the year to get the number of months to.
+  +/
+static int monthsToMonth(int currMonth, int month) @safe pure
+{
+    enforceValid!"months"(currMonth);
+    enforceValid!"months"(month);
+
+    if(currMonth == month)
+        return 0;
+
+    if(currMonth < month)
+        return month - currMonth;
+
+    return (Month.dec - currMonth) + month;
+}
+
+unittest
+{
+    assert(monthsToMonth(Month.jan, Month.jan) == 0);
+    assert(monthsToMonth(Month.jan, Month.feb) == 1);
+    assert(monthsToMonth(Month.jan, Month.mar) == 2);
+    assert(monthsToMonth(Month.jan, Month.apr) == 3);
+    assert(monthsToMonth(Month.jan, Month.may) == 4);
+    assert(monthsToMonth(Month.jan, Month.jun) == 5);
+    assert(monthsToMonth(Month.jan, Month.jul) == 6);
+    assert(monthsToMonth(Month.jan, Month.aug) == 7);
+    assert(monthsToMonth(Month.jan, Month.sep) == 8);
+    assert(monthsToMonth(Month.jan, Month.oct) == 9);
+    assert(monthsToMonth(Month.jan, Month.nov) == 10);
+    assert(monthsToMonth(Month.jan, Month.dec) == 11);
+
+    assert(monthsToMonth(Month.may, Month.jan) == 8);
+    assert(monthsToMonth(Month.may, Month.feb) == 9);
+    assert(monthsToMonth(Month.may, Month.mar) == 10);
+    assert(monthsToMonth(Month.may, Month.apr) == 11);
+    assert(monthsToMonth(Month.may, Month.may) == 0);
+    assert(monthsToMonth(Month.may, Month.jun) == 1);
+    assert(monthsToMonth(Month.may, Month.jul) == 2);
+    assert(monthsToMonth(Month.may, Month.aug) == 3);
+    assert(monthsToMonth(Month.may, Month.sep) == 4);
+    assert(monthsToMonth(Month.may, Month.oct) == 5);
+    assert(monthsToMonth(Month.may, Month.nov) == 6);
+    assert(monthsToMonth(Month.may, Month.dec) == 7);
+
+    assert(monthsToMonth(Month.oct, Month.jan) == 3);
+    assert(monthsToMonth(Month.oct, Month.feb) == 4);
+    assert(monthsToMonth(Month.oct, Month.mar) == 5);
+    assert(monthsToMonth(Month.oct, Month.apr) == 6);
+    assert(monthsToMonth(Month.oct, Month.may) == 7);
+    assert(monthsToMonth(Month.oct, Month.jun) == 8);
+    assert(monthsToMonth(Month.oct, Month.jul) == 9);
+    assert(monthsToMonth(Month.oct, Month.aug) == 10);
+    assert(monthsToMonth(Month.oct, Month.sep) == 11);
+    assert(monthsToMonth(Month.oct, Month.oct) == 0);
+    assert(monthsToMonth(Month.oct, Month.nov) == 1);
+    assert(monthsToMonth(Month.oct, Month.dec) == 2);
+
+    assert(monthsToMonth(Month.dec, Month.jan) == 1);
+    assert(monthsToMonth(Month.dec, Month.feb) == 2);
+    assert(monthsToMonth(Month.dec, Month.mar) == 3);
+    assert(monthsToMonth(Month.dec, Month.apr) == 4);
+    assert(monthsToMonth(Month.dec, Month.may) == 5);
+    assert(monthsToMonth(Month.dec, Month.jun) == 6);
+    assert(monthsToMonth(Month.dec, Month.jul) == 7);
+    assert(monthsToMonth(Month.dec, Month.aug) == 8);
+    assert(monthsToMonth(Month.dec, Month.sep) == 9);
+    assert(monthsToMonth(Month.dec, Month.oct) == 10);
+    assert(monthsToMonth(Month.dec, Month.nov) == 11);
+    assert(monthsToMonth(Month.dec, Month.dec) == 0);
+}
+
+
+/++
+    Returns the number of days from the current day of the week to the given
+    day of the week. If they are the same, then the result is 0.
+
+    Params:
+        currDoW = The current day of the week.
+        dow     = The day of the week to get the number of days to.
+  +/
+static int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow
+{
+    if(currDoW == dow)
+        return 0;
+
+    if(currDoW < dow)
+        return dow - currDoW;
+
+    return (DayOfWeek.sat - currDoW) + dow + 1;
+}
+
+unittest
+{
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6);
+
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5);
+
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4);
+
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3);
+
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2);
+
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0);
+    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1);
+
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6);
+    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0);
+}
+
+
+version(StdDdoc)
+{
+    /++
+        Function for starting to a stop watch time when the function is called
+        and stopping it when its return value goes out of scope and is destroyed.
+
+        When the value that is returned by this function is destroyed,
+        $(D func) will run. $(D func) is a unary function that takes a
+        $(CXREF time, TickDuration).
+
+        Example:
+--------------------
+{
+    auto mt = measureTime!((TickDuration a)
+        { /+ do something when the scope is exited +/ });
+    // do something that needs to be timed
+}
+--------------------
+
+        which is functionally equivalent to
+
+--------------------
+{
+    auto sw = StopWatch(AutoStart.yes);
+    scope(exit)
+    {
+        TickDuration a = sw.peek();
+        /+ do something when the scope is exited +/
+    }
+    // do something that needs to be timed
+}
+--------------------
+
+        See_Also:
+            $(LREF benchmark)
+      +/
+    auto measureTime(alias func)();
+}
+else
+{
+    @safe auto measureTime(alias func)()
+        if(isSafe!((){StopWatch sw; unaryFun!func(sw.peek());}))
+    {
+        struct Result
+        {
+            private StopWatch _sw = void;
+            this(AutoStart as)
+            {
+                _sw = StopWatch(as);
+            }
+            ~this()
+            {
+                unaryFun!(func)(_sw.peek());
+            }
+        }
+        return Result(AutoStart.yes);
+    }
+
+    auto measureTime(alias func)()
+        if(!isSafe!((){StopWatch sw; unaryFun!func(sw.peek());}))
+    {
+        struct Result
+        {
+            private StopWatch _sw = void;
+            this(AutoStart as)
+            {
+                _sw = StopWatch(as);
+            }
+            ~this()
+            {
+                unaryFun!(func)(_sw.peek());
+            }
+        }
+        return Result(AutoStart.yes);
+    }
+}
+
+// Verify Example.
+unittest
+{
+    {
+        auto mt = measureTime!((TickDuration a)
+            { /+ do something when the scope is exited +/ });
+        // do something that needs to be timed
+    }
+
+    {
+        auto sw = StopWatch(AutoStart.yes);
+        scope(exit)
+        {
+            TickDuration a = sw.peek();
+            /+ do something when the scope is exited +/
+        }
+        // do something that needs to be timed
+    }
+}
+
+@safe unittest
+{
+    import std.math : isNaN;
+
+    @safe static void func(TickDuration td)
+    {
+        assert(!td.to!("seconds", real)().isNaN());
+    }
+
+    auto mt = measureTime!(func)();
+
+    /+
+    with (measureTime!((a){assert(a.seconds);}))
+    {
+        // doSomething();
+        // @@@BUG@@@ doesn't work yet.
+    }
+    +/
+}
+
+unittest
+{
+    import std.math : isNaN;
+
+    static void func(TickDuration td)
+    {
+        assert(!td.to!("seconds", real)().isNaN());
+    }
+
+    auto mt = measureTime!(func)();
+
+    /+
+    with (measureTime!((a){assert(a.seconds);}))
+    {
+        // doSomething();
+        // @@@BUG@@@ doesn't work yet.
+    }
+    +/
+}
+
+//Bug# 8450
+unittest
+{
+    @safe    void safeFunc() {}
+    @trusted void trustFunc() {}
+    @system  void sysFunc() {}
+    auto safeResult  = measureTime!((a){safeFunc();})();
+    auto trustResult = measureTime!((a){trustFunc();})();
+    auto sysResult   = measureTime!((a){sysFunc();})();
+}
+
+//==============================================================================
+// Private Section.
+//==============================================================================
+private:
+
+//==============================================================================
+// Section with private enums and constants.
+//==============================================================================
+
+enum daysInYear     = 365;  // The number of days in a non-leap year.
+enum daysInLeapYear = 366;  // The numbef or days in a leap year.
+enum daysIn4Years   = daysInYear * 3 + daysInLeapYear;  /// Number of days in 4 years.
+enum daysIn100Years = daysIn4Years * 25 - 1;  // The number of days in 100 years.
+enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years.
+
+/+
+    Array of integers representing the last days of each month in a year.
+  +/
+immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
+
+/+
+    Array of integers representing the last days of each month in a leap year.
+  +/
+immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
+
+/+
+    Array of the short (three letter) names of each month.
+  +/
+immutable string[12] _monthNames = ["Jan",
+                                    "Feb",
+                                    "Mar",
+                                    "Apr",
+                                    "May",
+                                    "Jun",
+                                    "Jul",
+                                    "Aug",
+                                    "Sep",
+                                    "Oct",
+                                    "Nov",
+                                    "Dec"];
+
+
+//==============================================================================
+// Section with private helper functions and templates.
+//==============================================================================
+
+/+
+    Template to help with converting between time units.
+ +/
+template hnsecsPer(string units)
+    if(CmpTimeUnits!(units, "months") < 0)
+{
+    static if(units == "hnsecs")
+        enum hnsecsPer = 1L;
+    else static if(units == "usecs")
+        enum hnsecsPer = 10L;
+    else static if(units == "msecs")
+        enum hnsecsPer = 1000 * hnsecsPer!"usecs";
+    else static if(units == "seconds")
+        enum hnsecsPer = 1000 * hnsecsPer!"msecs";
+    else static if(units == "minutes")
+        enum hnsecsPer = 60 * hnsecsPer!"seconds";
+    else static if(units == "hours")
+        enum hnsecsPer = 60 * hnsecsPer!"minutes";
+    else static if(units == "days")
+        enum hnsecsPer = 24 * hnsecsPer!"hours";
+    else static if(units == "weeks")
+        enum hnsecsPer = 7 * hnsecsPer!"days";
+}
+
+
+/+
+    Splits out a particular unit from hnsecs and gives the value for that
+    unit and the remaining hnsecs. It really shouldn't be used unless unless
+    all units larger than the given units have already been split out.
+
+    Params:
+        units  = The units to split out.
+        hnsecs = The current total hnsecs. Upon returning, it is the hnsecs left
+                 after splitting out the given units.
+
+    Returns:
+        The number of the given units from converting hnsecs to those units.
+  +/
+long splitUnitsFromHNSecs(string units)(ref long hnsecs) @safe pure nothrow
+    if(validTimeUnits(units) &&
+       CmpTimeUnits!(units, "months") < 0)
+{
+    immutable value = convert!("hnsecs", units)(hnsecs);
+    hnsecs -= convert!(units, "hnsecs")(value);
+
+    return value;
+}
+
+unittest
+{
+    auto hnsecs = 2595000000007L;
+    immutable days = splitUnitsFromHNSecs!"days"(hnsecs);
+    assert(days == 3);
+    assert(hnsecs == 3000000007);
+
+    immutable minutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
+    assert(minutes == 5);
+    assert(hnsecs == 7);
+}
+
+
+/+
+    This function is used to split out the units without getting the remaining
+    hnsecs.
+
+    See_Also:
+        $(LREF splitUnitsFromHNSecs)
+
+    Params:
+        units  = The units to split out.
+        hnsecs = The current total hnsecs.
+
+    Returns:
+        The split out value.
+  +/
+long getUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow
+    if(validTimeUnits(units) &&
+       CmpTimeUnits!(units, "months") < 0)
+{
+    return convert!("hnsecs", units)(hnsecs);
+}
+
+unittest
+{
+    auto hnsecs = 2595000000007L;
+    immutable days = getUnitsFromHNSecs!"days"(hnsecs);
+    assert(days == 3);
+    assert(hnsecs == 2595000000007L);
+}
+
+
+/+
+    This function is used to split out the units without getting the units but
+    just the remaining hnsecs.
+
+    See_Also:
+        $(LREF splitUnitsFromHNSecs)
+
+    Params:
+        units  = The units to split out.
+        hnsecs = The current total hnsecs.
+
+    Returns:
+        The remaining hnsecs.
+  +/
+long removeUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow
+    if(validTimeUnits(units) &&
+       CmpTimeUnits!(units, "months") < 0)
+{
+    immutable value = convert!("hnsecs", units)(hnsecs);
+
+    return hnsecs - convert!(units, "hnsecs")(value);
+}
+
+unittest
+{
+    auto hnsecs = 2595000000007L;
+    auto returned = removeUnitsFromHNSecs!"days"(hnsecs);
+    assert(returned == 3000000007);
+    assert(hnsecs == 2595000000007L);
+}
+
+
+/+
+    The maximum valid Day in the given month in the given year.
+
+    Params:
+        year  = The year to get the day for.
+        month = The month of the Gregorian Calendar to get the day for.
+ +/
+static ubyte maxDay(int year, int month) @safe pure nothrow
+in
+{
+    assert(valid!"months"(month));
+}
+body
+{
+    switch(month)
+    {
+        case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec:
+            return 31;
+        case Month.feb:
+            return yearIsLeapYear(year) ? 29 : 28;
+        case Month.apr, Month.jun, Month.sep, Month.nov:
+            return 30;
+        default:
+            assert(0, "Invalid month.");
+    }
+}
+
+unittest
+{
+    //Test A.D.
+    assert(maxDay(1999, 1) == 31);
+    assert(maxDay(1999, 2) == 28);
+    assert(maxDay(1999, 3) == 31);
+    assert(maxDay(1999, 4) == 30);
+    assert(maxDay(1999, 5) == 31);
+    assert(maxDay(1999, 6) == 30);
+    assert(maxDay(1999, 7) == 31);
+    assert(maxDay(1999, 8) == 31);
+    assert(maxDay(1999, 9) == 30);
+    assert(maxDay(1999, 10) == 31);
+    assert(maxDay(1999, 11) == 30);
+    assert(maxDay(1999, 12) == 31);
+
+    assert(maxDay(2000, 1) == 31);
+    assert(maxDay(2000, 2) == 29);
+    assert(maxDay(2000, 3) == 31);
+    assert(maxDay(2000, 4) == 30);
+    assert(maxDay(2000, 5) == 31);
+    assert(maxDay(2000, 6) == 30);
+    assert(maxDay(2000, 7) == 31);
+    assert(maxDay(2000, 8) == 31);
+    assert(maxDay(2000, 9) == 30);
+    assert(maxDay(2000, 10) == 31);
+    assert(maxDay(2000, 11) == 30);
+    assert(maxDay(2000, 12) == 31);
+
+    //Test B.C.
+    assert(maxDay(-1999, 1) == 31);
+    assert(maxDay(-1999, 2) == 28);
+    assert(maxDay(-1999, 3) == 31);
+    assert(maxDay(-1999, 4) == 30);
+    assert(maxDay(-1999, 5) == 31);
+    assert(maxDay(-1999, 6) == 30);
+    assert(maxDay(-1999, 7) == 31);
+    assert(maxDay(-1999, 8) == 31);
+    assert(maxDay(-1999, 9) == 30);
+    assert(maxDay(-1999, 10) == 31);
+    assert(maxDay(-1999, 11) == 30);
+    assert(maxDay(-1999, 12) == 31);
+
+    assert(maxDay(-2000, 1) == 31);
+    assert(maxDay(-2000, 2) == 29);
+    assert(maxDay(-2000, 3) == 31);
+    assert(maxDay(-2000, 4) == 30);
+    assert(maxDay(-2000, 5) == 31);
+    assert(maxDay(-2000, 6) == 30);
+    assert(maxDay(-2000, 7) == 31);
+    assert(maxDay(-2000, 8) == 31);
+    assert(maxDay(-2000, 9) == 30);
+    assert(maxDay(-2000, 10) == 31);
+    assert(maxDay(-2000, 11) == 30);
+    assert(maxDay(-2000, 12) == 31);
+}
+
+
+/+
+    Returns the day of the week for the given day of the Gregorian Calendar.
+
+    Params:
+        day = The day of the Gregorian Calendar for which to get the day of
+              the week.
+  +/
+DayOfWeek getDayOfWeek(int day) @safe pure nothrow
+{
+    //January 1st, 1 A.D. was a Monday
+    if(day >= 0)
+        return cast(DayOfWeek)(day % 7);
+    else
+    {
+        immutable dow = cast(DayOfWeek)((day % 7) + 7);
+
+        if(dow == 7)
+            return DayOfWeek.sun;
+        else
+            return dow;
+    }
+}
+
+unittest
+{
+    //Test A.D.
+    assert(getDayOfWeek(SysTime(Date(1, 1, 1)).dayOfGregorianCal) == DayOfWeek.mon);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 2)).dayOfGregorianCal) == DayOfWeek.tue);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 3)).dayOfGregorianCal) == DayOfWeek.wed);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 4)).dayOfGregorianCal) == DayOfWeek.thu);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 5)).dayOfGregorianCal) == DayOfWeek.fri);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 6)).dayOfGregorianCal) == DayOfWeek.sat);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 7)).dayOfGregorianCal) == DayOfWeek.sun);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 8)).dayOfGregorianCal) == DayOfWeek.mon);
+    assert(getDayOfWeek(SysTime(Date(1, 1, 9)).dayOfGregorianCal) == DayOfWeek.tue);
+    assert(getDayOfWeek(SysTime(Date(2, 1, 1)).dayOfGregorianCal) == DayOfWeek.tue);
+    assert(getDayOfWeek(SysTime(Date(3, 1, 1)).dayOfGregorianCal) == DayOfWeek.wed);
+    assert(getDayOfWeek(SysTime(Date(4, 1, 1)).dayOfGregorianCal) == DayOfWeek.thu);
+    assert(getDayOfWeek(SysTime(Date(5, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat);
+    assert(getDayOfWeek(SysTime(Date(2000, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 22)).dayOfGregorianCal) == DayOfWeek.sun);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 23)).dayOfGregorianCal) == DayOfWeek.mon);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 24)).dayOfGregorianCal) == DayOfWeek.tue);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 25)).dayOfGregorianCal) == DayOfWeek.wed);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 26)).dayOfGregorianCal) == DayOfWeek.thu);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 27)).dayOfGregorianCal) == DayOfWeek.fri);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 28)).dayOfGregorianCal) == DayOfWeek.sat);
+    assert(getDayOfWeek(SysTime(Date(2010, 8, 29)).dayOfGregorianCal) == DayOfWeek.sun);
+
+    //Test B.C.
+    assert(getDayOfWeek(SysTime(Date(0, 12, 31)).dayOfGregorianCal) == DayOfWeek.sun);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 30)).dayOfGregorianCal) == DayOfWeek.sat);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 29)).dayOfGregorianCal) == DayOfWeek.fri);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 28)).dayOfGregorianCal) == DayOfWeek.thu);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 27)).dayOfGregorianCal) == DayOfWeek.wed);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 26)).dayOfGregorianCal) == DayOfWeek.tue);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 25)).dayOfGregorianCal) == DayOfWeek.mon);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 24)).dayOfGregorianCal) == DayOfWeek.sun);
+    assert(getDayOfWeek(SysTime(Date(0, 12, 23)).dayOfGregorianCal) == DayOfWeek.sat);
+}
+
+
+/+
+    Returns the string representation of the given month.
+  +/
+string monthToString(Month month) @safe pure
+{
+    import std.format : format;
+    assert(month >= Month.jan && month <= Month.dec, format("Invalid month: %s", month));
+    return _monthNames[month - Month.jan];
+}
+
+unittest
+{
+    assert(monthToString(Month.jan) == "Jan");
+    assert(monthToString(Month.feb) == "Feb");
+    assert(monthToString(Month.mar) == "Mar");
+    assert(monthToString(Month.apr) == "Apr");
+    assert(monthToString(Month.may) == "May");
+    assert(monthToString(Month.jun) == "Jun");
+    assert(monthToString(Month.jul) == "Jul");
+    assert(monthToString(Month.aug) == "Aug");
+    assert(monthToString(Month.sep) == "Sep");
+    assert(monthToString(Month.oct) == "Oct");
+    assert(monthToString(Month.nov) == "Nov");
+    assert(monthToString(Month.dec) == "Dec");
+}
+
+
+/+
+    Returns the Month corresponding to the given string.
+
+    Params:
+        monthStr = The string representation of the month to get the Month for.
+
+    Throws:
+        $(LREF DateTimeException) if the given month is not a valid month string.
+  +/
+Month monthFromString(string monthStr) @safe pure
+{
+    import std.format : format;
+    switch(monthStr)
+    {
+        case "Jan":
+            return Month.jan;
+        case "Feb":
+            return Month.feb;
+        case "Mar":
+            return Month.mar;
+        case "Apr":
+            return Month.apr;
+        case "May":
+            return Month.may;
+        case "Jun":
+            return Month.jun;
+        case "Jul":
+            return Month.jul;
+        case "Aug":
+            return Month.aug;
+        case "Sep":
+            return Month.sep;
+        case "Oct":
+            return Month.oct;
+        case "Nov":
+            return Month.nov;
+        case "Dec":
+            return Month.dec;
+        default:
+            throw new DateTimeException(format("Invalid month %s", monthStr));
+    }
+}
+
+unittest
+{
+    foreach(badStr; ["Ja", "Janu", "Januar", "Januarys", "JJanuary", "JANUARY",
+                     "JAN", "january", "jaNuary", "jaN", "jaNuaRy", "jAn"])
+    {
+        scope(failure) writeln(badStr);
+        assertThrown!DateTimeException(monthFromString(badStr));
+    }
+
+    foreach(month; EnumMembers!Month)
+    {
+        scope(failure) writeln(month);
+        assert(monthFromString(monthToString(month)) == month);
+    }
+}
+
+
+/+
+    The time units which are one step smaller than the given units.
+  +/
+template nextSmallerTimeUnits(string units)
+    if(validTimeUnits(units) &&
+       timeStrings.front != units)
+{
+    import std.algorithm : countUntil;
+    enum nextSmallerTimeUnits = timeStrings[countUntil(timeStrings, units) - 1];
+}
+
+unittest
+{
+    assert(nextSmallerTimeUnits!"years" == "months");
+    assert(nextSmallerTimeUnits!"months" == "weeks");
+    assert(nextSmallerTimeUnits!"weeks" == "days");
+    assert(nextSmallerTimeUnits!"days" == "hours");
+    assert(nextSmallerTimeUnits!"hours" == "minutes");
+    assert(nextSmallerTimeUnits!"minutes" == "seconds");
+    assert(nextSmallerTimeUnits!"seconds" == "msecs");
+    assert(nextSmallerTimeUnits!"msecs" == "usecs");
+    assert(nextSmallerTimeUnits!"usecs" == "hnsecs");
+    static assert(!__traits(compiles, nextSmallerTimeUnits!"hnsecs"));
+}
+
+
+/+
+    The time units which are one step larger than the given units.
+  +/
+template nextLargerTimeUnits(string units)
+    if(validTimeUnits(units) &&
+       timeStrings.back != units)
+{
+    import std.algorithm : countUntil;
+    enum nextLargerTimeUnits = timeStrings[countUntil(timeStrings, units) + 1];
+}
+
+unittest
+{
+    assert(nextLargerTimeUnits!"hnsecs" == "usecs");
+    assert(nextLargerTimeUnits!"usecs" == "msecs");
+    assert(nextLargerTimeUnits!"msecs" == "seconds");
+    assert(nextLargerTimeUnits!"seconds" == "minutes");
+    assert(nextLargerTimeUnits!"minutes" == "hours");
+    assert(nextLargerTimeUnits!"hours" == "days");
+    assert(nextLargerTimeUnits!"days" == "weeks");
+    assert(nextLargerTimeUnits!"weeks" == "months");
+    assert(nextLargerTimeUnits!"months" == "years");
+    static assert(!__traits(compiles, nextLargerTimeUnits!"years"));
+}
+
+
+/+
+    Returns the given hnsecs as an ISO string of fractional seconds.
+  +/
+static string fracSecsToISOString(int hnsecs) @safe pure nothrow
+{
+    import std.format : format;
+    assert(hnsecs >= 0);
+
+    try
+    {
+        if(hnsecs == 0)
+            return "";
+
+        string isoString = format(".%07d", hnsecs);
+
+        while(isoString[$ - 1] == '0')
+            isoString.popBack();
+
+        return isoString;
+    }
+    catch(Exception e)
+        assert(0, "format() threw.");
+}
+
+unittest
+{
+    assert(fracSecsToISOString(0) == "");
+    assert(fracSecsToISOString(1) == ".0000001");
+    assert(fracSecsToISOString(10) == ".000001");
+    assert(fracSecsToISOString(100) == ".00001");
+    assert(fracSecsToISOString(1000) == ".0001");
+    assert(fracSecsToISOString(10_000) == ".001");
+    assert(fracSecsToISOString(100_000) == ".01");
+    assert(fracSecsToISOString(1_000_000) == ".1");
+    assert(fracSecsToISOString(1_000_001) == ".1000001");
+    assert(fracSecsToISOString(1_001_001) == ".1001001");
+    assert(fracSecsToISOString(1_071_601) == ".1071601");
+    assert(fracSecsToISOString(1_271_641) == ".1271641");
+    assert(fracSecsToISOString(9_999_999) == ".9999999");
+    assert(fracSecsToISOString(9_999_990) == ".999999");
+    assert(fracSecsToISOString(9_999_900) == ".99999");
+    assert(fracSecsToISOString(9_999_000) == ".9999");
+    assert(fracSecsToISOString(9_990_000) == ".999");
+    assert(fracSecsToISOString(9_900_000) == ".99");
+    assert(fracSecsToISOString(9_000_000) == ".9");
+    assert(fracSecsToISOString(999) == ".0000999");
+    assert(fracSecsToISOString(9990) == ".000999");
+    assert(fracSecsToISOString(99_900) == ".00999");
+    assert(fracSecsToISOString(999_000) == ".0999");
+}
+
+
+/+
+    Returns a Duration corresponding to to the given ISO string of
+    fractional seconds.
+  +/
+static Duration fracSecsFromISOString(S)(in S isoString) @trusted pure
+    if(isSomeString!S)
+{
+    import std.ascii : isDigit;
+    import std.string : representation;
+    import std.conv : to;
+    import std.algorithm : all;
+
+    if(isoString.empty)
+        return Duration.zero;
+
+    auto str = isoString.representation;
+
+    enforce(str[0] == '.', new DateTimeException("Invalid ISO String"));
+    str.popFront();
+
+    enforce(!str.empty && str.length <= 7, new DateTimeException("Invalid ISO String"));
+    enforce(all!isDigit(str), new DateTimeException("Invalid ISO String"));
+
+    dchar[7] fullISOString = void;
+    foreach(i, ref dchar c; fullISOString)
+    {
+        if(i < str.length)
+            c = str[i];
+        else
+            c = '0';
+    }
+
+    return hnsecs(to!int(fullISOString[]));
+}
+
+unittest
+{
+    static void testFSInvalid(string isoString)
+    {
+        fracSecsFromISOString(isoString);
+    }
+
+    assertThrown!DateTimeException(testFSInvalid("."));
+    assertThrown!DateTimeException(testFSInvalid("0."));
+    assertThrown!DateTimeException(testFSInvalid("0"));
+    assertThrown!DateTimeException(testFSInvalid("0000000"));
+    assertThrown!DateTimeException(testFSInvalid(".00000000"));
+    assertThrown!DateTimeException(testFSInvalid(".00000001"));
+    assertThrown!DateTimeException(testFSInvalid("T"));
+    assertThrown!DateTimeException(testFSInvalid("T."));
+    assertThrown!DateTimeException(testFSInvalid(".T"));
+
+    assert(fracSecsFromISOString("") == Duration.zero);
+    assert(fracSecsFromISOString(".0000001") == hnsecs(1));
+    assert(fracSecsFromISOString(".000001") == hnsecs(10));
+    assert(fracSecsFromISOString(".00001") == hnsecs(100));
+    assert(fracSecsFromISOString(".0001") == hnsecs(1000));
+    assert(fracSecsFromISOString(".001") == hnsecs(10_000));
+    assert(fracSecsFromISOString(".01") == hnsecs(100_000));
+    assert(fracSecsFromISOString(".1") == hnsecs(1_000_000));
+    assert(fracSecsFromISOString(".1000001") == hnsecs(1_000_001));
+    assert(fracSecsFromISOString(".1001001") == hnsecs(1_001_001));
+    assert(fracSecsFromISOString(".1071601") == hnsecs(1_071_601));
+    assert(fracSecsFromISOString(".1271641") == hnsecs(1_271_641));
+    assert(fracSecsFromISOString(".9999999") == hnsecs(9_999_999));
+    assert(fracSecsFromISOString(".9999990") == hnsecs(9_999_990));
+    assert(fracSecsFromISOString(".999999") == hnsecs(9_999_990));
+    assert(fracSecsFromISOString(".9999900") == hnsecs(9_999_900));
+    assert(fracSecsFromISOString(".99999") == hnsecs(9_999_900));
+    assert(fracSecsFromISOString(".9999000") == hnsecs(9_999_000));
+    assert(fracSecsFromISOString(".9999") == hnsecs(9_999_000));
+    assert(fracSecsFromISOString(".9990000") == hnsecs(9_990_000));
+    assert(fracSecsFromISOString(".999") == hnsecs(9_990_000));
+    assert(fracSecsFromISOString(".9900000") == hnsecs(9_900_000));
+    assert(fracSecsFromISOString(".9900") == hnsecs(9_900_000));
+    assert(fracSecsFromISOString(".99") == hnsecs(9_900_000));
+    assert(fracSecsFromISOString(".9000000") == hnsecs(9_000_000));
+    assert(fracSecsFromISOString(".9") == hnsecs(9_000_000));
+    assert(fracSecsFromISOString(".0000999") == hnsecs(999));
+    assert(fracSecsFromISOString(".0009990") == hnsecs(9990));
+    assert(fracSecsFromISOString(".000999") == hnsecs(9990));
+    assert(fracSecsFromISOString(".0099900") == hnsecs(99_900));
+    assert(fracSecsFromISOString(".00999") == hnsecs(99_900));
+    assert(fracSecsFromISOString(".0999000") == hnsecs(999_000));
+    assert(fracSecsFromISOString(".0999") == hnsecs(999_000));
+}
+
+
+/+
+    Strips what RFC 5322, section 3.2.2 refers to as CFWS from the left-hand
+    side of the given range (it strips comments delimited by $(D '(') and
+    $(D ')') as well as folding whitespace).
+
+    It is assumed that the given range contains the value of a header field and
+    no terminating CRLF for the line (though the CRLF for folding whitespace is
+    of course expected and stripped) and thus that the only case of CR or LF is
+    in folding whitespace.
+
+    If a comment does not terminate correctly (e.g. mismatched parens) or if the
+    the FWS is malformed, then the range will be empty when stripCWFS is done.
+    However, only minimal validation of the content is done (e.g. quoted pairs
+    within a comment aren't validated beyond \$LPAREN or \$RPAREN, because
+    they're inside a comment, and thus their value doesn't matter anyway). It's
+    only when the content does not conform to the grammar rules for FWS and thus
+    literally cannot be parsed that content is considered invalid, and an empty
+    range is returned.
+
+    Note that _stripCFWS is eager, not lazy. It does not create a new range.
+    Rather, it pops off the CFWS from the range and returns it.
+  +/
+R _stripCFWS(R)(R range)
+    if(isRandomAccessRange!R && hasSlicing!R && hasLength!R &&
+       (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte)))
+{
+    immutable e = range.length;
+    outer: for(size_t i = 0; i < e; )
+    {
+        switch(range[i])
+        {
+            case ' ': case '\t':
+            {
+                ++i;
+                break;
+            }
+            case '\r':
+            {
+                if(i + 2 < e && range[i + 1] == '\n' && (range[i + 2] == ' ' || range[i + 2] == '\t'))
+                {
+                    i += 3;
+                    break;
+                }
+                break outer;
+            }
+            case '\n':
+            {
+                if(i + 1 < e && (range[i + 1] == ' ' || range[i + 1] == '\t'))
+                {
+                    i += 2;
+                    break;
+                }
+                break outer;
+            }
+            case '(':
+            {
+                ++i;
+                size_t commentLevel = 1;
+                while(i < e)
+                {
+                    if(range[i] == '(')
+                        ++commentLevel;
+                    else if(range[i] == ')')
+                    {
+                        ++i;
+                        if(--commentLevel == 0)
+                            continue outer;
+                        continue;
+                    }
+                    else if(range[i] == '\\')
+                    {
+                        if(++i == e)
+                            break outer;
+                    }
+                    ++i;
+                }
+                break outer;
+            }
+            default: return range[i .. e];
+        }
+    }
+    return range[e .. e];
+}
+
+unittest
+{
+    import std.algorithm;
+    import std.meta;
+    import std.string;
+    import std.typecons;
+
+    foreach(cr; AliasSeq!(function(string a){return cast(ubyte[])a;},
+                          function(string a){return map!(b => cast(char)b)(a.representation);}))
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        scope(failure) writeln(typeof(cr).stringof);
+
+        assert(_stripCFWS(cr("")).empty);
+        assert(_stripCFWS(cr("\r")).empty);
+        assert(_stripCFWS(cr("\r\n")).empty);
+        assert(_stripCFWS(cr("\r\n ")).empty);
+        assert(_stripCFWS(cr(" \t\r\n")).empty);
+        assert(equal(_stripCFWS(cr(" \t\r\n hello")), cr("hello")));
+        assert(_stripCFWS(cr(" \t\r\nhello")).empty);
+        assert(_stripCFWS(cr(" \t\r\n\v")).empty);
+        assert(equal(_stripCFWS(cr("\v \t\r\n\v")), cr("\v \t\r\n\v")));
+        assert(_stripCFWS(cr("()")).empty);
+        assert(_stripCFWS(cr("(hello world)")).empty);
+        assert(_stripCFWS(cr("(hello world)(hello world)")).empty);
+        assert(_stripCFWS(cr("(hello world\r\n foo\r where's\nwaldo)")).empty);
+        assert(_stripCFWS(cr(" \t (hello \tworld\r\n foo\r where's\nwaldo)\t\t ")).empty);
+        assert(_stripCFWS(cr("      ")).empty);
+        assert(_stripCFWS(cr("\t\t\t")).empty);
+        assert(_stripCFWS(cr("\t \r\n\r \n")).empty);
+        assert(_stripCFWS(cr("(hello world) (can't find waldo) (he's lost)")).empty);
+        assert(_stripCFWS(cr("(hello\\) world) (can't \\(find waldo) (he's \\(\\)lost)")).empty);
+        assert(_stripCFWS(cr("(((((")).empty);
+        assert(_stripCFWS(cr("(((()))")).empty);
+        assert(_stripCFWS(cr("(((())))")).empty);
+        assert(equal(_stripCFWS(cr("(((()))))")), cr(")")));
+        assert(equal(_stripCFWS(cr(")))))")), cr(")))))")));
+        assert(equal(_stripCFWS(cr("()))))")), cr("))))")));
+        assert(equal(_stripCFWS(cr(" hello hello ")), cr("hello hello ")));
+        assert(equal(_stripCFWS(cr("\thello (world)")), cr("hello (world)")));
+        assert(equal(_stripCFWS(cr(" \r\n \\((\\))  foo")), cr("\\((\\))  foo")));
+        assert(equal(_stripCFWS(cr(" \r\n (\\((\\)))  foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" \r\n (\\(()))  foo")), cr(")  foo")));
+        assert(_stripCFWS(cr(" \r\n (((\\)))  foo")).empty);
+
+        assert(_stripCFWS(cr("(hello)(hello)")).empty);
+        assert(_stripCFWS(cr(" \r\n (hello)\r\n (hello)")).empty);
+        assert(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n ")).empty);
+        assert(_stripCFWS(cr("\t\t\t\t(hello)\t\t\t\t(hello)\t\t\t\t")).empty);
+        assert(equal(_stripCFWS(cr(" \r\n (hello)\r\n (hello) \r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\r\n\t(hello)\t\r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\t\r\n\t(hello)\t\r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n \r\n (hello) \r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n \r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \r\n \r\n (hello)\t\r\n (hello) \r\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \r\n\t\r\n\t(hello)\t\r\n (hello) \r\n hello")), cr("hello")));
+
+        assert(equal(_stripCFWS(cr(" (\r\n ( \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\t\r\n ( \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n\t( \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n (\t\r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n (\r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n (\r\n\t) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\t\r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n\t) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) \r\n foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\t\r\n foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\r\n foo")), cr("foo")));
+
+        assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\t\r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n \r\n\t( \r\n \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n \r\n( \r\n \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n\t) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\t\r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\r\n ) foo")), cr("foo")));
+
+        assert(equal(_stripCFWS(cr(" ( \r\n bar \r\n ( \r\n bar \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n () \r\n ( \r\n () \r\n ) \r\n ) foo")), cr("foo")));
+        assert(equal(_stripCFWS(cr(" ( \r\n \\\\ \r\n ( \r\n \\\\ \r\n ) \r\n ) foo")), cr("foo")));
+
+        assert(_stripCFWS(cr("(hello)(hello)")).empty);
+        assert(_stripCFWS(cr(" \n (hello)\n (hello) \n ")).empty);
+        assert(_stripCFWS(cr(" \n (hello) \n (hello) \n ")).empty);
+        assert(equal(_stripCFWS(cr(" \n (hello)\n (hello) \n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr("\t\n\t(hello)\n\t(hello)\t\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr("\t\n\t(hello)\t\n\t(hello)\t\n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \n (hello) \n \n (hello) \n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n \n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \n \n (hello)\t\n (hello) \n hello")), cr("hello")));
+        assert(equal(_stripCFWS(cr(" \n\t\n\t(hello)\t\n (hello) \n hello")), cr("hello")));
+    }();
+}
+
+// This is so that we don't have to worry about std.conv.to throwing. It also
+// doesn't have to worry about quite as many cases as std.conv.to, since it
+// doesn't have to worry about a sign on the value or about whether it fits.
+T _convDigits(T, R)(R str)
+    if(isIntegral!T && isSigned!T) // The constraints on R were already covered by parseRFC822DateTime.
+{
+    import std.ascii : isDigit;
+
+    assert(!str.empty);
+    T num = 0;
+    foreach(i; 0 .. str.length)
+    {
+        if(i != 0)
+            num *= 10;
+        if(!isDigit(str[i]))
+            return -1;
+        num += str[i] - '0';
+    }
+    return num;
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.range;
+    foreach(i; chain(iota(0, 101), [250, 999, 1000, 1001, 2345, 9999]))
+    {
+        scope(failure) writeln(i);
+        assert(_convDigits!int(to!string(i)) == i);
+    }
+    foreach(str; ["-42", "+42", "1a", "1 ", " ", " 42 "])
+    {
+        scope(failure) writeln(str);
+        assert(_convDigits!int(str) == -1);
+    }
+}
+
+
+version(unittest)
+{
+    import std.typecons;
+    import std.algorithm;
+    //Variables to help in testing.
+    Duration currLocalDiffFromUTC;
+    immutable (TimeZone)[] testTZs;
+
+    //All of these helper arrays are sorted in ascending order.
+    auto testYearsBC = [-1999, -1200, -600, -4, -1, 0];
+    auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012];
+
+    //I'd use a Tuple, but I get forward reference errors if I try.
+    struct MonthDay
+    {
+        Month month;
+        short day;
+
+        this(int m, short d)
+        {
+            month = cast(Month)m;
+            day = d;
+        }
+    }
+
+    MonthDay[] testMonthDays = [MonthDay(1, 1),
+                                MonthDay(1, 2),
+                                MonthDay(3, 17),
+                                MonthDay(7, 4),
+                                MonthDay(10, 27),
+                                MonthDay(12, 30),
+                                MonthDay(12, 31)];
+
+    auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31];
+
+    auto testTODs = [TimeOfDay(0, 0, 0),
+                     TimeOfDay(0, 0, 1),
+                     TimeOfDay(0, 1, 0),
+                     TimeOfDay(1, 0, 0),
+                     TimeOfDay(13, 13, 13),
+                     TimeOfDay(23, 59, 59)];
+
+    auto testHours = [0, 1, 12, 22, 23];
+    auto testMinSecs = [0, 1, 30, 58, 59];
+
+    //Throwing exceptions is incredibly expensive, so we want to use a smaller
+    //set of values for tests using assertThrown.
+    auto testTODsThrown = [TimeOfDay(0, 0, 0),
+                           TimeOfDay(13, 13, 13),
+                           TimeOfDay(23, 59, 59)];
+
+    Date[] testDatesBC;
+    Date[] testDatesAD;
+
+    DateTime[] testDateTimesBC;
+    DateTime[] testDateTimesAD;
+
+    Duration[] testFracSecs;
+
+    SysTime[] testSysTimesBC;
+    SysTime[] testSysTimesAD;
+
+    //I'd use a Tuple, but I get forward reference errors if I try.
+    struct GregDay { int day; Date date; }
+    auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), //Start of the Hebrew Calendar
+                           GregDay(-735_233, Date(-2012, 1, 1)),
+                           GregDay(-735_202, Date(-2012, 2, 1)),
+                           GregDay(-735_175, Date(-2012, 2, 28)),
+                           GregDay(-735_174, Date(-2012, 2, 29)),
+                           GregDay(-735_173, Date(-2012, 3, 1)),
+                           GregDay(-734_502, Date(-2010, 1, 1)),
+                           GregDay(-734_472, Date(-2010, 1, 31)),
+                           GregDay(-734_471, Date(-2010, 2, 1)),
+                           GregDay(-734_444, Date(-2010, 2, 28)),
+                           GregDay(-734_443, Date(-2010, 3, 1)),
+                           GregDay(-734_413, Date(-2010, 3, 31)),
+                           GregDay(-734_412, Date(-2010, 4, 1)),
+                           GregDay(-734_383, Date(-2010, 4, 30)),
+                           GregDay(-734_382, Date(-2010, 5, 1)),
+                           GregDay(-734_352, Date(-2010, 5, 31)),
+                           GregDay(-734_351, Date(-2010, 6, 1)),
+                           GregDay(-734_322, Date(-2010, 6, 30)),
+                           GregDay(-734_321, Date(-2010, 7, 1)),
+                           GregDay(-734_291, Date(-2010, 7, 31)),
+                           GregDay(-734_290, Date(-2010, 8, 1)),
+                           GregDay(-734_260, Date(-2010, 8, 31)),
+                           GregDay(-734_259, Date(-2010, 9, 1)),
+                           GregDay(-734_230, Date(-2010, 9, 30)),
+                           GregDay(-734_229, Date(-2010, 10, 1)),
+                           GregDay(-734_199, Date(-2010, 10, 31)),
+                           GregDay(-734_198, Date(-2010, 11, 1)),
+                           GregDay(-734_169, Date(-2010, 11, 30)),
+                           GregDay(-734_168, Date(-2010, 12, 1)),
+                           GregDay(-734_139, Date(-2010, 12, 30)),
+                           GregDay(-734_138, Date(-2010, 12, 31)),
+                           GregDay(-731_215, Date(-2001, 1, 1)),
+                           GregDay(-730_850, Date(-2000, 1, 1)),
+                           GregDay(-730_849, Date(-2000, 1, 2)),
+                           GregDay(-730_486, Date(-2000, 12, 30)),
+                           GregDay(-730_485, Date(-2000, 12, 31)),
+                           GregDay(-730_484, Date(-1999, 1, 1)),
+                           GregDay(-694_690, Date(-1901, 1, 1)),
+                           GregDay(-694_325, Date(-1900, 1, 1)),
+                           GregDay(-585_118, Date(-1601, 1, 1)),
+                           GregDay(-584_753, Date(-1600, 1, 1)),
+                           GregDay(-584_388, Date(-1600, 12, 31)),
+                           GregDay(-584_387, Date(-1599, 1, 1)),
+                           GregDay(-365_972, Date(-1001, 1, 1)),
+                           GregDay(-365_607, Date(-1000, 1, 1)),
+                           GregDay(-183_351, Date(-501, 1, 1)),
+                           GregDay(-182_986, Date(-500, 1, 1)),
+                           GregDay(-182_621, Date(-499, 1, 1)),
+                           GregDay(-146_827, Date(-401, 1, 1)),
+                           GregDay(-146_462, Date(-400, 1, 1)),
+                           GregDay(-146_097, Date(-400, 12, 31)),
+                           GregDay(-110_302, Date(-301, 1, 1)),
+                           GregDay(-109_937, Date(-300, 1, 1)),
+                           GregDay(-73_778, Date(-201, 1, 1)),
+                           GregDay(-73_413, Date(-200, 1, 1)),
+                           GregDay(-38_715, Date(-105, 1, 1)),
+                           GregDay(-37_254, Date(-101, 1, 1)),
+                           GregDay(-36_889, Date(-100, 1, 1)),
+                           GregDay(-36_524, Date(-99, 1, 1)),
+                           GregDay(-36_160, Date(-99, 12, 31)),
+                           GregDay(-35_794, Date(-97, 1, 1)),
+                           GregDay(-18_627, Date(-50, 1, 1)),
+                           GregDay(-18_262, Date(-49, 1, 1)),
+                           GregDay(-3652, Date(-9, 1, 1)),
+                           GregDay(-2191, Date(-5, 1, 1)),
+                           GregDay(-1827, Date(-5, 12, 31)),
+                           GregDay(-1826, Date(-4, 1, 1)),
+                           GregDay(-1825, Date(-4, 1, 2)),
+                           GregDay(-1462, Date(-4, 12, 30)),
+                           GregDay(-1461, Date(-4, 12, 31)),
+                           GregDay(-1460, Date(-3, 1, 1)),
+                           GregDay(-1096, Date(-3, 12, 31)),
+                           GregDay(-1095, Date(-2, 1, 1)),
+                           GregDay(-731, Date(-2, 12, 31)),
+                           GregDay(-730, Date(-1, 1, 1)),
+                           GregDay(-367, Date(-1, 12, 30)),
+                           GregDay(-366, Date(-1, 12, 31)),
+                           GregDay(-365, Date(0, 1, 1)),
+                           GregDay(-31, Date(0, 11, 30)),
+                           GregDay(-30, Date(0, 12, 1)),
+                           GregDay(-1, Date(0, 12, 30)),
+                           GregDay(0, Date(0, 12, 31))];
+
+    auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)),
+                           GregDay(2, Date(1, 1, 2)),
+                           GregDay(32, Date(1, 2, 1)),
+                           GregDay(365, Date(1, 12, 31)),
+                           GregDay(366, Date(2, 1, 1)),
+                           GregDay(731, Date(3, 1, 1)),
+                           GregDay(1096, Date(4, 1, 1)),
+                           GregDay(1097, Date(4, 1, 2)),
+                           GregDay(1460, Date(4, 12, 30)),
+                           GregDay(1461, Date(4, 12, 31)),
+                           GregDay(1462, Date(5, 1, 1)),
+                           GregDay(17_898, Date(50, 1, 1)),
+                           GregDay(35_065, Date(97, 1, 1)),
+                           GregDay(36_160, Date(100, 1, 1)),
+                           GregDay(36_525, Date(101, 1, 1)),
+                           GregDay(37_986, Date(105, 1, 1)),
+                           GregDay(72_684, Date(200, 1, 1)),
+                           GregDay(73_049, Date(201, 1, 1)),
+                           GregDay(109_208, Date(300, 1, 1)),
+                           GregDay(109_573, Date(301, 1, 1)),
+                           GregDay(145_732, Date(400, 1, 1)),
+                           GregDay(146_098, Date(401, 1, 1)),
+                           GregDay(182_257, Date(500, 1, 1)),
+                           GregDay(182_622, Date(501, 1, 1)),
+                           GregDay(364_878, Date(1000, 1, 1)),
+                           GregDay(365_243, Date(1001, 1, 1)),
+                           GregDay(584_023, Date(1600, 1, 1)),
+                           GregDay(584_389, Date(1601, 1, 1)),
+                           GregDay(693_596, Date(1900, 1, 1)),
+                           GregDay(693_961, Date(1901, 1, 1)),
+                           GregDay(729_755, Date(1999, 1, 1)),
+                           GregDay(730_120, Date(2000, 1, 1)),
+                           GregDay(730_121, Date(2000, 1, 2)),
+                           GregDay(730_484, Date(2000, 12, 30)),
+                           GregDay(730_485, Date(2000, 12, 31)),
+                           GregDay(730_486, Date(2001, 1, 1)),
+                           GregDay(733_773, Date(2010, 1, 1)),
+                           GregDay(733_774, Date(2010, 1, 2)),
+                           GregDay(733_803, Date(2010, 1, 31)),
+                           GregDay(733_804, Date(2010, 2, 1)),
+                           GregDay(733_831, Date(2010, 2, 28)),
+                           GregDay(733_832, Date(2010, 3, 1)),
+                           GregDay(733_862, Date(2010, 3, 31)),
+                           GregDay(733_863, Date(2010, 4, 1)),
+                           GregDay(733_892, Date(2010, 4, 30)),
+                           GregDay(733_893, Date(2010, 5, 1)),
+                           GregDay(733_923, Date(2010, 5, 31)),
+                           GregDay(733_924, Date(2010, 6, 1)),
+                           GregDay(733_953, Date(2010, 6, 30)),
+                           GregDay(733_954, Date(2010, 7, 1)),
+                           GregDay(733_984, Date(2010, 7, 31)),
+                           GregDay(733_985, Date(2010, 8, 1)),
+                           GregDay(734_015, Date(2010, 8, 31)),
+                           GregDay(734_016, Date(2010, 9, 1)),
+                           GregDay(734_045, Date(2010, 9, 30)),
+                           GregDay(734_046, Date(2010, 10, 1)),
+                           GregDay(734_076, Date(2010, 10, 31)),
+                           GregDay(734_077, Date(2010, 11, 1)),
+                           GregDay(734_106, Date(2010, 11, 30)),
+                           GregDay(734_107, Date(2010, 12, 1)),
+                           GregDay(734_136, Date(2010, 12, 30)),
+                           GregDay(734_137, Date(2010, 12, 31)),
+                           GregDay(734_503, Date(2012, 1, 1)),
+                           GregDay(734_534, Date(2012, 2, 1)),
+                           GregDay(734_561, Date(2012, 2, 28)),
+                           GregDay(734_562, Date(2012, 2, 29)),
+                           GregDay(734_563, Date(2012, 3, 1)),
+                           GregDay(734_858, Date(2012, 12, 21))];
+
+    //I'd use a Tuple, but I get forward reference errors if I try.
+    struct DayOfYear { int day; MonthDay md; }
+    auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)),
+                           DayOfYear(2, MonthDay(1, 2)),
+                           DayOfYear(3, MonthDay(1, 3)),
+                           DayOfYear(31, MonthDay(1, 31)),
+                           DayOfYear(32, MonthDay(2, 1)),
+                           DayOfYear(59, MonthDay(2, 28)),
+                           DayOfYear(60, MonthDay(3, 1)),
+                           DayOfYear(90, MonthDay(3, 31)),
+                           DayOfYear(91, MonthDay(4, 1)),
+                           DayOfYear(120, MonthDay(4, 30)),
+                           DayOfYear(121, MonthDay(5, 1)),
+                           DayOfYear(151, MonthDay(5, 31)),
+                           DayOfYear(152, MonthDay(6, 1)),
+                           DayOfYear(181, MonthDay(6, 30)),
+                           DayOfYear(182, MonthDay(7, 1)),
+                           DayOfYear(212, MonthDay(7, 31)),
+                           DayOfYear(213, MonthDay(8, 1)),
+                           DayOfYear(243, MonthDay(8, 31)),
+                           DayOfYear(244, MonthDay(9, 1)),
+                           DayOfYear(273, MonthDay(9, 30)),
+                           DayOfYear(274, MonthDay(10, 1)),
+                           DayOfYear(304, MonthDay(10, 31)),
+                           DayOfYear(305, MonthDay(11, 1)),
+                           DayOfYear(334, MonthDay(11, 30)),
+                           DayOfYear(335, MonthDay(12, 1)),
+                           DayOfYear(363, MonthDay(12, 29)),
+                           DayOfYear(364, MonthDay(12, 30)),
+                           DayOfYear(365, MonthDay(12, 31))];
+
+    auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)),
+                               DayOfYear(2, MonthDay(1, 2)),
+                               DayOfYear(3, MonthDay(1, 3)),
+                               DayOfYear(31, MonthDay(1, 31)),
+                               DayOfYear(32, MonthDay(2, 1)),
+                               DayOfYear(59, MonthDay(2, 28)),
+                               DayOfYear(60, MonthDay(2, 29)),
+                               DayOfYear(61, MonthDay(3, 1)),
+                               DayOfYear(91, MonthDay(3, 31)),
+                               DayOfYear(92, MonthDay(4, 1)),
+                               DayOfYear(121, MonthDay(4, 30)),
+                               DayOfYear(122, MonthDay(5, 1)),
+                               DayOfYear(152, MonthDay(5, 31)),
+                               DayOfYear(153, MonthDay(6, 1)),
+                               DayOfYear(182, MonthDay(6, 30)),
+                               DayOfYear(183, MonthDay(7, 1)),
+                               DayOfYear(213, MonthDay(7, 31)),
+                               DayOfYear(214, MonthDay(8, 1)),
+                               DayOfYear(244, MonthDay(8, 31)),
+                               DayOfYear(245, MonthDay(9, 1)),
+                               DayOfYear(274, MonthDay(9, 30)),
+                               DayOfYear(275, MonthDay(10, 1)),
+                               DayOfYear(305, MonthDay(10, 31)),
+                               DayOfYear(306, MonthDay(11, 1)),
+                               DayOfYear(335, MonthDay(11, 30)),
+                               DayOfYear(336, MonthDay(12, 1)),
+                               DayOfYear(364, MonthDay(12, 29)),
+                               DayOfYear(365, MonthDay(12, 30)),
+                               DayOfYear(366, MonthDay(12, 31))];
+
+    void initializeTests()
+    {
+        immutable lt = LocalTime().utcToTZ(0);
+        currLocalDiffFromUTC = dur!"hnsecs"(lt);
+
+        immutable otherTZ = lt < 0 ? TimeZone.getTimeZone("Australia/Sydney")
+                                   : TimeZone.getTimeZone("America/Denver");
+        immutable ot = otherTZ.utcToTZ(0);
+
+        auto diffs = [0L, lt, ot];
+        auto diffAA = [0L : Rebindable!(immutable TimeZone)(UTC())];
+        diffAA[lt] = Rebindable!(immutable TimeZone)(LocalTime());
+        diffAA[ot] = Rebindable!(immutable TimeZone)(otherTZ);
+
+        sort(diffs);
+        testTZs = [diffAA[diffs[0]], diffAA[diffs[1]], diffAA[diffs[2]]];
+
+        testFracSecs = [Duration.zero, hnsecs(1), hnsecs(5007), hnsecs(9999999)];
+
+        foreach(year; testYearsBC)
+        {
+            foreach(md; testMonthDays)
+                testDatesBC ~= Date(year, md.month, md.day);
+        }
+
+        foreach(year; testYearsAD)
+        {
+            foreach(md; testMonthDays)
+                testDatesAD ~= Date(year, md.month, md.day);
+        }
+
+        foreach(dt; testDatesBC)
+        {
+            foreach(tod; testTODs)
+                testDateTimesBC ~= DateTime(dt, tod);
+        }
+
+        foreach(dt; testDatesAD)
+        {
+            foreach(tod; testTODs)
+                testDateTimesAD ~= DateTime(dt, tod);
+        }
+
+        foreach(dt; testDateTimesBC)
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(fs; testFracSecs)
+                    testSysTimesBC ~= SysTime(dt, fs, tz);
+            }
+        }
+
+        foreach(dt; testDateTimesAD)
+        {
+            foreach(tz; testTZs)
+            {
+                foreach(fs; testFracSecs)
+                    testSysTimesAD ~= SysTime(dt, fs, tz);
+            }
+        }
+    }
+}
+
+
+unittest
+{
+    /* Issue 6642 */
+    static assert(!hasUnsharedAliasing!Date);
+    static assert(!hasUnsharedAliasing!TimeOfDay);
+    static assert(!hasUnsharedAliasing!DateTime);
+    static assert(!hasUnsharedAliasing!SysTime);
+}
+
+
+
+// This script is for regenerating tzDatabaseNameToWindowsTZName and
+// windowsTZNameToTZDatabaseName from
+// http://unicode.org/cldr/data/common/supplemental/windowsZones.xml
+
+/+
+#!/bin/rdmd
+
+import std.algorithm;
+import std.array;
+import std.conv;
+import std.datetime;
+import std.exception;
+import std.path;
+import std.stdio;
+import std.string;
+
+int main(string[] args)
+{
+    if(args.length != 4 || args[1].baseName != "windowsZones.xml")
+    {
+        stderr.writeln("genTZs.d windowsZones.xml <nix2WinFile> <win2NixFile>");
+        return -1;
+    }
+
+    string[][string] win2Nix;
+    string[][string] nix2Win;
+    immutable f1 = `<mapZone other="`;
+    immutable f2 = `type="`;
+
+    auto file = File(args[1]);
+    foreach(line; file.byLine())
+    {
+        line = line.find(f1);
+        if(line.empty)
+            continue;
+        line = line[f1.length .. $];
+        auto next = line.find('"');
+        auto win = to!string(line[0 .. $ - next.length]);
+        line = next.find(f2);
+        line = line[f2.length .. $];
+        next = line.find('"');
+        auto nixes = to!string(line[0 .. $ - next.length]).split();
+
+        if(auto l = win in win2Nix)
+            *l ~= nixes;
+        else
+            win2Nix[win] = nixes;
+        foreach(nix; nixes)
+        {
+            if(auto w = nix in nix2Win)
+                *w ~= win;
+            else
+                nix2Win[nix] = [win];
+        }
+    }
+
+    foreach(nix; nix2Win.byKey())
+    {
+        auto wins = nix2Win[nix];
+        nix2Win[nix] = wins.sort().uniq().array();
+    }
+
+    foreach(win; win2Nix.byKey())
+    {
+        auto nixes = win2Nix[win];
+        win2Nix[win] = nixes.sort().uniq().array();
+    }
+
+    // AFAIK, there should be no cases of a TZ Database time zone converting to
+    // multiple windows time zones.
+    foreach(nix, wins; nix2Win)
+        enforce(wins.length == 1, format("%s -> %s", nix, wins));
+
+    // We'll try to eliminate multiples by favoring a conversion if it's already
+    // in Phobos, but if it's new, then the correct one will have to be chosen
+    // manually from the results.
+    string[] haveMultiple;
+    foreach(win, nixes; win2Nix)
+    {
+        if(nixes.length > 1)
+            haveMultiple ~= win;
+    }
+    bool[string] haveConflicts;
+    foreach(win; haveMultiple)
+    {
+        if(auto curr = windowsTZNameToTZDatabaseName(win))
+        {
+            if(auto other = curr in nix2Win)
+            {
+                if((*other)[0] == win)
+                {
+                    win2Nix[win] = [curr];
+                    continue;
+                }
+            }
+        }
+        haveConflicts[win] = true;
+        writefln("Warning: %s -> %s", win, win2Nix[win]);
+    }
+
+
+    string[] nix2WinLines = [
+        `string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc`,
+        `{`,
+        `    switch(tzName)`,
+        `    {`];
+
+    foreach(nix; nix2Win.keys.sort())
+        nix2WinLines ~= format(`        case "%s": return "%s";`, nix, nix2Win[nix][0]);
+
+    nix2WinLines ~= [
+        `        default: return null;`,
+        `    }`,
+        `}`];
+
+
+    string[] win2NixLines = [
+        `string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc`,
+        `{`,
+        `    switch(tzName)`,
+        `    {`];
+    foreach(win; win2Nix.keys.sort())
+    {
+        immutable hasMultiple = cast(bool)(win in haveConflicts);
+        foreach(nix; win2Nix[win])
+            win2NixLines ~= format(`        case "%s": return "%s";%s`, win, nix, hasMultiple ? " FIXME" : "");
+    }
+
+    win2NixLines ~= [
+        `        default: return null;`,
+        `    }`,
+        `}`];
+
+
+    auto nix2WinFile = args[2];
+    std.file.write(nix2WinFile, nix2WinLines.join("\n"));
+
+    auto win2NixFile = args[3];
+    std.file.write(win2NixFile, win2NixLines.join("\n"));
+
+    return 0;
+}
++/
diff --git a/libphobos/src/std/demangle.d b/libphobos/src/std/demangle.d
new file mode 100644
index 00000000000..c90500a806b
--- /dev/null
+++ b/libphobos/src/std/demangle.d
@@ -0,0 +1,91 @@
+// Written in the D programming language.
+
+/**
+ * Demangle D mangled names.
+ *
+ * Macros:
+ *  WIKI = Phobos/StdDemangle
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright),
+ *                        Thomas K$(UUML)hne, Frits van Bommel
+ * Source:    $(PHOBOSSRC std/_demangle.d)
+ */
+/*
+ *          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.demangle;
+
+/+
+private class MangleException : Exception
+{
+    this()
+    {
+        super("MangleException");
+    }
+}
++/
+
+/*****************************
+ * Demangle D mangled names.
+ *
+ * If it is not a D mangled name, it returns its argument name.
+ * Example:
+ *        This program reads standard in and writes it to standard out,
+ *        pretty-printing any found D mangled names.
+-------------------
+import core.stdc.stdio : stdin;
+import std.stdio;
+import std.ascii;
+import std.demangle;
+
+void test(int x, float y) { }
+
+int main()
+{
+    string buffer;
+    bool inword;
+    int c;
+
+    writefln("Try typing in: %s", test.mangleof);
+    while ((c = fgetc(stdin)) != EOF)
+    {
+        if (inword)
+        {
+            if (c == '_' || isAlphaNum(c))
+                buffer ~= cast(char) c;
+            else
+            {
+                inword = false;
+                write(demangle(buffer), cast(char) c);
+            }
+        }
+        else
+        {   if (c == '_' || isAlpha(c))
+            {
+                inword = true;
+                buffer.length = 0;
+                buffer ~= cast(char) c;
+            }
+            else
+                write(cast(char) c);
+        }
+    }
+    if (inword)
+        write(demangle(buffer));
+    return 0;
+}
+-------------------
+ */
+
+string demangle(string name)
+{
+    import core.demangle : demangle;
+    import std.exception : assumeUnique;
+    auto ret = demangle(name);
+    return assumeUnique(ret);
+}
diff --git a/libphobos/src/std/digest/crc.d b/libphobos/src/std/digest/crc.d
new file mode 100644
index 00000000000..930cbb84f3c
--- /dev/null
+++ b/libphobos/src/std/digest/crc.d
@@ -0,0 +1,462 @@
+/**
+Cyclic Redundancy Check (32-bit) implementation.
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Template API) $(TD $(MYREF CRC32)
+)
+)
+$(TR $(TDNW OOP API) $(TD $(MYREF CRC32Digest))
+)
+$(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of))
+)
+)
+)
+
+ *
+ * This module conforms to the APIs defined in $(D std.digest.digest). To understand the
+ * differences between the template and the OOP API, see $(D std.digest.digest).
+ *
+ * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone
+ * module.
+ *
+ * Note:
+ * CRCs are usually printed with the MSB first. When using
+ * $(XREF_PACK digest,digest,toHexString) the result will be in an unexpected
+ * order. Use $(XREF_PACK digest,digest,toHexString)'s optional order parameter
+ * to specify decreasing order for the correct result. The $(LREF crcHexString)
+ * alias can also be used for this purpose.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *
+ * Authors:   Pavel "EvilOne" Minayev, Alex Rønne Petersen, Johannes Pfau
+ *
+ * References:
+ *      $(LINK2 http://en.wikipedia.org/wiki/Cyclic_redundancy_check, Wikipedia on CRC)
+ *
+ * Source: $(PHOBOSSRC std/digest/_crc.d)
+ *
+ * Macros:
+ * WIKI = Phobos/StdUtilDigestCRC32
+ *
+ * Standards:
+ * Implements the 'common' IEEE CRC32 variant
+ * (LSB-first order, Initial value uint.max, complement result)
+ *
+ * CTFE:
+ * Digests do not work in CTFE
+ */
+/*
+ * Copyright (c) 2001 - 2002
+ * Pavel "EvilOne" Minayev
+ * Copyright (c) 2012
+ * Alex Rønne Petersen
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.digest.crc;
+
+public import std.digest.digest;
+
+version(unittest)
+    import std.exception;
+
+
+///
+unittest
+{
+    //Template API
+    import std.digest.crc;
+
+    ubyte[4] hash = crc32Of("The quick brown fox jumps over the lazy dog");
+    assert(crcHexString(hash) == "414FA339");
+
+    //Feeding data
+    ubyte[1024] data;
+    CRC32 crc;
+    crc.put(data[]);
+    crc.start(); //Start again
+    crc.put(data[]);
+    hash = crc.finish();
+}
+
+///
+unittest
+{
+    //OOP API
+    import std.digest.crc;
+
+    auto crc = new CRC32Digest();
+    ubyte[] hash = crc.digest("The quick brown fox jumps over the lazy dog");
+    assert(crcHexString(hash) == "414FA339"); //352441c2
+
+    //Feeding data
+    ubyte[1024] data;
+    crc.put(data[]);
+    crc.reset(); //Start again
+    crc.put(data[]);
+    hash = crc.finish();
+}
+
+private immutable uint[256] crc32_table =
+[
+    0x00000000,0x77073096,0xee0e612c,0x990951ba,0x076dc419,0x706af48f,0xe963a535,
+    0x9e6495a3,0x0edb8832,0x79dcb8a4,0xe0d5e91e,0x97d2d988,0x09b64c2b,0x7eb17cbd,
+    0xe7b82d07,0x90bf1d91,0x1db71064,0x6ab020f2,0xf3b97148,0x84be41de,0x1adad47d,
+    0x6ddde4eb,0xf4d4b551,0x83d385c7,0x136c9856,0x646ba8c0,0xfd62f97a,0x8a65c9ec,
+    0x14015c4f,0x63066cd9,0xfa0f3d63,0x8d080df5,0x3b6e20c8,0x4c69105e,0xd56041e4,
+    0xa2677172,0x3c03e4d1,0x4b04d447,0xd20d85fd,0xa50ab56b,0x35b5a8fa,0x42b2986c,
+    0xdbbbc9d6,0xacbcf940,0x32d86ce3,0x45df5c75,0xdcd60dcf,0xabd13d59,0x26d930ac,
+    0x51de003a,0xc8d75180,0xbfd06116,0x21b4f4b5,0x56b3c423,0xcfba9599,0xb8bda50f,
+    0x2802b89e,0x5f058808,0xc60cd9b2,0xb10be924,0x2f6f7c87,0x58684c11,0xc1611dab,
+    0xb6662d3d,0x76dc4190,0x01db7106,0x98d220bc,0xefd5102a,0x71b18589,0x06b6b51f,
+    0x9fbfe4a5,0xe8b8d433,0x7807c9a2,0x0f00f934,0x9609a88e,0xe10e9818,0x7f6a0dbb,
+    0x086d3d2d,0x91646c97,0xe6635c01,0x6b6b51f4,0x1c6c6162,0x856530d8,0xf262004e,
+    0x6c0695ed,0x1b01a57b,0x8208f4c1,0xf50fc457,0x65b0d9c6,0x12b7e950,0x8bbeb8ea,
+    0xfcb9887c,0x62dd1ddf,0x15da2d49,0x8cd37cf3,0xfbd44c65,0x4db26158,0x3ab551ce,
+    0xa3bc0074,0xd4bb30e2,0x4adfa541,0x3dd895d7,0xa4d1c46d,0xd3d6f4fb,0x4369e96a,
+    0x346ed9fc,0xad678846,0xda60b8d0,0x44042d73,0x33031de5,0xaa0a4c5f,0xdd0d7cc9,
+    0x5005713c,0x270241aa,0xbe0b1010,0xc90c2086,0x5768b525,0x206f85b3,0xb966d409,
+    0xce61e49f,0x5edef90e,0x29d9c998,0xb0d09822,0xc7d7a8b4,0x59b33d17,0x2eb40d81,
+    0xb7bd5c3b,0xc0ba6cad,0xedb88320,0x9abfb3b6,0x03b6e20c,0x74b1d29a,0xead54739,
+    0x9dd277af,0x04db2615,0x73dc1683,0xe3630b12,0x94643b84,0x0d6d6a3e,0x7a6a5aa8,
+    0xe40ecf0b,0x9309ff9d,0x0a00ae27,0x7d079eb1,0xf00f9344,0x8708a3d2,0x1e01f268,
+    0x6906c2fe,0xf762575d,0x806567cb,0x196c3671,0x6e6b06e7,0xfed41b76,0x89d32be0,
+    0x10da7a5a,0x67dd4acc,0xf9b9df6f,0x8ebeeff9,0x17b7be43,0x60b08ed5,0xd6d6a3e8,
+    0xa1d1937e,0x38d8c2c4,0x4fdff252,0xd1bb67f1,0xa6bc5767,0x3fb506dd,0x48b2364b,
+    0xd80d2bda,0xaf0a1b4c,0x36034af6,0x41047a60,0xdf60efc3,0xa867df55,0x316e8eef,
+    0x4669be79,0xcb61b38c,0xbc66831a,0x256fd2a0,0x5268e236,0xcc0c7795,0xbb0b4703,
+    0x220216b9,0x5505262f,0xc5ba3bbe,0xb2bd0b28,0x2bb45a92,0x5cb36a04,0xc2d7ffa7,
+    0xb5d0cf31,0x2cd99e8b,0x5bdeae1d,0x9b64c2b0,0xec63f226,0x756aa39c,0x026d930a,
+    0x9c0906a9,0xeb0e363f,0x72076785,0x05005713,0x95bf4a82,0xe2b87a14,0x7bb12bae,
+    0x0cb61b38,0x92d28e9b,0xe5d5be0d,0x7cdcefb7,0x0bdbdf21,0x86d3d2d4,0xf1d4e242,
+    0x68ddb3f8,0x1fda836e,0x81be16cd,0xf6b9265b,0x6fb077e1,0x18b74777,0x88085ae6,
+    0xff0f6a70,0x66063bca,0x11010b5c,0x8f659eff,0xf862ae69,0x616bffd3,0x166ccf45,
+    0xa00ae278,0xd70dd2ee,0x4e048354,0x3903b3c2,0xa7672661,0xd06016f7,0x4969474d,
+    0x3e6e77db,0xaed16a4a,0xd9d65adc,0x40df0b66,0x37d83bf0,0xa9bcae53,0xdebb9ec5,
+    0x47b2cf7f,0x30b5ffe9,0xbdbdf21c,0xcabac28a,0x53b39330,0x24b4a3a6,0xbad03605,
+    0xcdd70693,0x54de5729,0x23d967bf,0xb3667a2e,0xc4614ab8,0x5d681b02,0x2a6f2b94,
+    0xb40bbe37,0xc30c8ea1,0x5a05df1b,0x2d02ef8d
+];
+
+/**
+ * Template API CRC32 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ */
+struct CRC32
+{
+    private:
+        // magic initialization constants
+        uint _state = uint.max;
+
+    public:
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         */
+        void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc
+        {
+            foreach (val; data)
+                _state = (_state >> 8) ^ crc32_table[cast(ubyte)_state ^ val];
+        }
+        ///
+        unittest
+        {
+            CRC32 dig;
+            dig.put(cast(ubyte)0); //single ubyte
+            dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+            ubyte[10] buf;
+            dig.put(buf); //buffer
+        }
+
+        /**
+         * Used to initialize the CRC32 digest.
+         *
+         * Note:
+         * For this CRC32 Digest implementation calling start after default construction
+         * is not necessary. Calling start is only necessary to reset the Digest.
+         *
+         * Generic code which deals with different Digest types should always call start though.
+         */
+        void start() @safe pure nothrow @nogc
+        {
+            this = CRC32.init;
+        }
+        ///
+        unittest
+        {
+            CRC32 digest;
+            //digest.start(); //Not necessary
+            digest.put(0);
+        }
+
+        /**
+         * Returns the finished CRC32 hash. This also calls $(LREF start) to
+         * reset the internal state.
+         */
+        ubyte[4] finish() @safe pure nothrow @nogc
+        {
+            auto tmp = peek();
+            start();
+            return tmp;
+        }
+        ///
+        unittest
+        {
+            //Simple example
+            CRC32 hash;
+            hash.put(cast(ubyte)0);
+            ubyte[4] result = hash.finish();
+        }
+
+        /**
+         * Works like $(D finish) but does not reset the internal state, so it's possible
+         * to continue putting data into this CRC32 after a call to peek.
+         */
+        ubyte[4] peek() const @safe pure nothrow @nogc
+        {
+            import std.bitmanip : nativeToLittleEndian;
+            //Complement, LSB first / Little Endian, see http://rosettacode.org/wiki/CRC-32
+            return nativeToLittleEndian(~_state);
+        }
+}
+
+///
+unittest
+{
+    //Simple example, hashing a string using crc32Of helper function
+    ubyte[4] hash = crc32Of("abc");
+    //Let's get a hash string
+    assert(crcHexString(hash) == "352441C2");
+}
+
+///
+unittest
+{
+    //Using the basic API
+    CRC32 hash;
+    ubyte[1024] data;
+    //Initialize data here...
+    hash.put(data);
+    ubyte[4] result = hash.finish();
+}
+
+///
+unittest
+{
+    //Let's use the template features:
+    //Note: When passing a CRC32 to a function, it must be passed by reference!
+    void doSomething(T)(ref T hash) if(isDigest!T)
+    {
+      hash.put(cast(ubyte)0);
+    }
+    CRC32 crc;
+    crc.start();
+    doSomething(crc);
+    assert(crcHexString(crc.finish()) == "D202EF8D");
+}
+
+unittest
+{
+    assert(isDigest!CRC32);
+}
+
+unittest
+{
+    ubyte[4] digest;
+
+    CRC32 crc;
+    crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    assert(crc.peek() == cast(ubyte[])x"bd50274c");
+    crc.start();
+    crc.put(cast(ubyte[])"");
+    assert(crc.finish() == cast(ubyte[])x"00000000");
+
+    digest = crc32Of("");
+    assert(digest == cast(ubyte[])x"00000000");
+
+    //Test vector from http://rosettacode.org/wiki/CRC-32
+    assert(crcHexString(crc32Of("The quick brown fox jumps over the lazy dog")) == "414FA339");
+
+    digest = crc32Of("a");
+    assert(digest == cast(ubyte[])x"43beb7e8");
+
+    digest = crc32Of("abc");
+    assert(digest == cast(ubyte[])x"c2412435");
+
+    digest = crc32Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    assert(digest == cast(ubyte[])x"5f3f1a17");
+
+    digest = crc32Of("message digest");
+    assert(digest == cast(ubyte[])x"7f9d1520");
+
+    digest = crc32Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    assert(digest == cast(ubyte[])x"d2e6c21f");
+
+    digest = crc32Of("1234567890123456789012345678901234567890"~
+                    "1234567890123456789012345678901234567890");
+    assert(digest == cast(ubyte[])x"724aa97c");
+
+    assert(crcHexString(cast(ubyte[4])x"c3fcd3d7") == "D7D3FCC3");
+}
+
+/**
+ * This is a convenience alias for $(XREF_PACK digest,digest,digest) using the
+ * CRC32 implementation.
+ *
+ * Params:
+ *      data = $(D InputRange) of $(D ElementType) implicitly convertible to
+ *             $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) or one or more arrays
+ *             of any type.
+ *
+ * Returns:
+ *      CRC32 of data
+ */
+//simple alias doesn't work here, hope this gets inlined...
+ubyte[4] crc32Of(T...)(T data)
+{
+    return digest!(CRC32, T)(data);
+}
+
+///
+unittest
+{
+    ubyte[] data = [4,5,7,25];
+    assert(data.crc32Of == [167, 180, 199, 131]);
+
+    import std.utf : byChar;
+    assert("hello"d.byChar.crc32Of == [134, 166, 16, 54]);
+
+    ubyte[4] hash = "abc".crc32Of();
+    assert(hash == digest!CRC32("ab", "c"));
+
+    import std.range : iota;
+    enum ubyte S = 5, F = 66;
+    assert(iota(S, F).crc32Of == [59, 140, 234, 154]);
+}
+
+/**
+ * This is a convenience alias for $(XREF_PACK digest,digest,toHexString)
+ * producing the usual CRC32 string output.
+ */
+public alias crcHexString = toHexString!(Order.decreasing);
+///ditto
+public alias crcHexString = toHexString!(Order.decreasing, 16);
+
+
+/**
+ * OOP API CRC32 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ *
+ * This is an alias for $(D $(XREF_PACK digest,digest,WrapperDigest)!CRC32), see
+ * there for more information.
+ */
+alias CRC32Digest = WrapperDigest!CRC32;
+
+///
+unittest
+{
+    //Simple example, hashing a string using Digest.digest helper function
+    auto crc = new CRC32Digest();
+    ubyte[] hash = crc.digest("abc");
+    //Let's get a hash string
+    assert(crcHexString(hash) == "352441C2");
+}
+
+///
+unittest
+{
+     //Let's use the OOP features:
+    void test(Digest dig)
+    {
+      dig.put(cast(ubyte)0);
+    }
+    auto crc = new CRC32Digest();
+    test(crc);
+
+    //Let's use a custom buffer:
+    ubyte[4] buf;
+    ubyte[] result = crc.finish(buf[]);
+    assert(crcHexString(result) == "D202EF8D");
+}
+
+///
+unittest
+{
+    //Simple example
+    auto hash = new CRC32Digest();
+    hash.put(cast(ubyte)0);
+    ubyte[] result = hash.finish();
+}
+
+///
+unittest
+{
+    //using a supplied buffer
+    ubyte[4] buf;
+    auto hash = new CRC32Digest();
+    hash.put(cast(ubyte)0);
+    ubyte[] result = hash.finish(buf[]);
+    //The result is now in result (and in buf. If you pass a buffer which is bigger than
+    //necessary, result will have the correct length, but buf will still have it's original
+    //length)
+}
+
+unittest
+{
+    import std.range;
+
+    auto crc = new CRC32Digest();
+
+    crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    assert(crc.peek() == cast(ubyte[])x"bd50274c");
+    crc.reset();
+    crc.put(cast(ubyte[])"");
+    assert(crc.finish() == cast(ubyte[])x"00000000");
+
+    crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    ubyte[20] result;
+    auto result2 = crc.finish(result[]);
+    assert(result[0 .. 4] == result2 && result2 == cast(ubyte[])x"bd50274c");
+
+    debug
+        assertThrown!Error(crc.finish(result[0 .. 3]));
+
+    assert(crc.length == 4);
+
+    assert(crc.digest("") == cast(ubyte[])x"00000000");
+
+    assert(crc.digest("a") == cast(ubyte[])x"43beb7e8");
+
+    assert(crc.digest("abc") == cast(ubyte[])x"c2412435");
+
+    assert(crc.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
+           == cast(ubyte[])x"5f3f1a17");
+
+    assert(crc.digest("message digest") == cast(ubyte[])x"7f9d1520");
+
+    assert(crc.digest("abcdefghijklmnopqrstuvwxyz")
+           == cast(ubyte[])x"bd50274c");
+
+    assert(crc.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+           == cast(ubyte[])x"d2e6c21f");
+
+    assert(crc.digest("1234567890123456789012345678901234567890",
+                                   "1234567890123456789012345678901234567890")
+           == cast(ubyte[])x"724aa97c");
+
+    ubyte[] onemilliona = new ubyte[1000000];
+    onemilliona[] = 'a';
+    auto digest = crc32Of(onemilliona);
+    assert(digest == cast(ubyte[])x"BCBF25DC");
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    digest = crc32Of(oneMillionRange);
+    assert(digest == cast(ubyte[])x"BCBF25DC");
+}
diff --git a/libphobos/src/std/digest/digest.d b/libphobos/src/std/digest/digest.d
new file mode 100644
index 00000000000..b5a39297978
--- /dev/null
+++ b/libphobos/src/std/digest/digest.d
@@ -0,0 +1,993 @@
+/**
+ * This module describes the _digest APIs used in Phobos. All digests follow
+ * these APIs. Additionally, this module contains useful helper methods which
+ * can be used with every _digest type.
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Template API) $(TD $(MYREF isDigest) $(MYREF DigestType) $(MYREF hasPeek)
+  $(MYREF hasBlockSize)
+  $(MYREF ExampleDigest) $(MYREF _digest) $(MYREF hexDigest) $(MYREF makeDigest)
+)
+)
+$(TR $(TDNW OOP API) $(TD $(MYREF Digest)
+)
+)
+$(TR $(TDNW Helper functions) $(TD $(MYREF toHexString))
+)
+$(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDigest))
+)
+)
+)
+
+ * APIs:
+ * There are two APIs for digests: The template API and the OOP API. The template API uses structs
+ * and template helpers like $(LREF isDigest). The OOP API implements digests as classes inheriting
+ * the $(LREF Digest) interface. All digests are named so that the template API struct is called "$(B x)"
+ * and the OOP API class is called "$(B x)Digest". For example we have $(D MD5) <--> $(D MD5Digest),
+ * $(D CRC32) <--> $(D CRC32Digest), etc.
+ *
+ * The template API is slightly more efficient. It does not have to allocate memory dynamically,
+ * all memory is allocated on the stack. The OOP API has to allocate in the finish method if no
+ * buffer was provided. If you provide a buffer to the OOP APIs finish function, it doesn't allocate,
+ * but the $(LREF Digest) classes still have to be created using $(D new) which allocates them using the GC.
+ *
+ * The OOP API is useful to change the _digest function and/or _digest backend at 'runtime'. The benefit here
+ * is that switching e.g. Phobos MD5Digest and an OpenSSLMD5Digest implementation is ABI compatible.
+ *
+ * If just one specific _digest type and backend is needed, the template API is usually a good fit.
+ * In this simplest case, the template API can even be used without templates: Just use the "$(B x)" structs
+ * directly.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:
+ * Johannes Pfau
+ *
+ * Source:    $(PHOBOSSRC std/_digest/_digest.d)
+ *
+ * CTFE:
+ * Digests do not work in CTFE
+ *
+ * TODO:
+ * Digesting single bits (as opposed to bytes) is not implemented. This will be done as another
+ * template constraint helper (hasBitDigesting!T) and an additional interface (BitDigest)
+ */
+/*          Copyright Johannes Pfau 2012.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.digest.digest;
+
+import std.meta : allSatisfy;
+import std.traits;
+public import std.ascii : LetterCase;
+
+
+///
+unittest
+{
+    import std.digest.crc;
+
+    //Simple example
+    char[8] hexHash = hexDigest!CRC32("The quick brown fox jumps over the lazy dog");
+    assert(hexHash == "39A34F41");
+
+    //Simple example, using the API manually
+    CRC32 context = makeDigest!CRC32();
+    context.put(cast(ubyte[])"The quick brown fox jumps over the lazy dog");
+    ubyte[4] hash = context.finish();
+    assert(toHexString(hash) == "39A34F41");
+}
+
+///
+unittest
+{
+    //Generating the hashes of a file, idiomatic D way
+    import std.digest.crc, std.digest.sha, std.digest.md;
+    import std.stdio;
+
+    // Digests a file and prints the result.
+    void digestFile(Hash)(string filename) if(isDigest!Hash)
+    {
+        auto file = File(filename);
+        auto result = digest!Hash(file.byChunk(4096 * 1024));
+        writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result));
+    }
+
+    void main(string[] args)
+    {
+        foreach (name; args[1 .. $])
+        {
+            digestFile!MD5(name);
+            digestFile!SHA1(name);
+            digestFile!CRC32(name);
+        }
+    }
+}
+///
+unittest
+{
+    //Generating the hashes of a file using the template API
+    import std.digest.crc, std.digest.sha, std.digest.md;
+    import std.stdio;
+    // Digests a file and prints the result.
+    void digestFile(Hash)(ref Hash hash, string filename) if(isDigest!Hash)
+    {
+        File file = File(filename);
+
+        //As digests imlement OutputRange, we could use std.algorithm.copy
+        //Let's do it manually for now
+        foreach (buffer; file.byChunk(4096 * 1024))
+            hash.put(buffer);
+
+        auto result = hash.finish();
+        writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result));
+    }
+
+    void uMain(string[] args)
+    {
+        MD5 md5;
+        SHA1 sha1;
+        CRC32 crc32;
+
+        md5.start();
+        sha1.start();
+        crc32.start();
+
+        foreach (arg; args[1 .. $])
+        {
+            digestFile(md5, arg);
+            digestFile(sha1, arg);
+            digestFile(crc32, arg);
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.digest.crc, std.digest.sha, std.digest.md;
+    import std.stdio;
+
+    // Digests a file and prints the result.
+    void digestFile(Digest hash, string filename)
+    {
+        File file = File(filename);
+
+        //As digests implement OutputRange, we could use std.algorithm.copy
+        //Let's do it manually for now
+        foreach (buffer; file.byChunk(4096 * 1024))
+          hash.put(buffer);
+
+        ubyte[] result = hash.finish();
+        writefln("%s (%s) = %s", typeid(hash).toString(), filename, toHexString(result));
+    }
+
+    void umain(string[] args)
+    {
+        auto md5 = new MD5Digest();
+        auto sha1 = new SHA1Digest();
+        auto crc32 = new CRC32Digest();
+
+        foreach (arg; args[1 .. $])
+        {
+          digestFile(md5, arg);
+          digestFile(sha1, arg);
+          digestFile(crc32, arg);
+        }
+    }
+}
+
+version(StdDdoc)
+    version = ExampleDigest;
+
+version(ExampleDigest)
+{
+    /**
+     * This documents the general structure of a Digest in the template API.
+     * All digest implementations should implement the following members and therefore pass
+     * the $(LREF isDigest) test.
+     *
+     * Note:
+     * $(UL
+     * $(LI A digest must be a struct (value type) to pass the $(LREF isDigest) test.)
+     * $(LI A digest passing the $(LREF isDigest) test is always an $(D OutputRange))
+     * )
+     */
+    struct ExampleDigest
+    {
+        public:
+            /**
+             * Use this to feed the digest with data.
+             * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+             * interface for $(D ubyte) and $(D const(ubyte)[]).
+             * The following usages of $(D put) must work for any type which
+             * passes $(LREF isDigest):
+             * Example:
+             * ----
+             * ExampleDigest dig;
+             * dig.put(cast(ubyte)0); //single ubyte
+             * dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+             * ubyte[10] buf;
+             * dig.put(buf); //buffer
+             * ----
+             */
+            @trusted void put(scope const(ubyte)[] data...)
+            {
+
+            }
+
+            /**
+             * This function is used to (re)initialize the digest.
+             * It must be called before using the digest and it also works as a 'reset' function
+             * if the digest has already processed data.
+             */
+            @trusted void start()
+            {
+
+            }
+
+            /**
+             * The finish function returns the final hash sum and resets the Digest.
+             *
+             * Note:
+             * The actual type returned by finish depends on the digest implementation.
+             * $(D ubyte[16]) is just used as an example. It is guaranteed that the type is a
+             * static array of ubytes.
+             *
+             * $(UL
+             * $(LI Use $(LREF DigestType) to obtain the actual return type.)
+             * $(LI Use $(LREF digestLength) to obtain the length of the ubyte array.)
+             * )
+             */
+            @trusted ubyte[16] finish()
+            {
+                return (ubyte[16]).init;
+            }
+    }
+}
+
+///
+unittest
+{
+    //Using the OutputRange feature
+    import std.algorithm : copy;
+    import std.range : repeat;
+    import std.digest.md;
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    auto ctx = makeDigest!MD5();
+    copy(oneMillionRange, &ctx); //Note: You must pass a pointer to copy!
+    assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21");
+}
+
+/**
+ * Use this to check if a type is a digest. See $(LREF ExampleDigest) to see what
+ * a type must provide to pass this check.
+ *
+ * Note:
+ * This is very useful as a template constraint (see examples)
+ *
+ * BUGS:
+ * $(UL
+ * $(LI Does not yet verify that put takes scope parameters.)
+ * $(LI Should check that finish() returns a ubyte[num] array)
+ * )
+ */
+template isDigest(T)
+{
+    import std.range : isOutputRange;
+    enum bool isDigest = isOutputRange!(T, const(ubyte)[]) && isOutputRange!(T, ubyte) &&
+        is(T == struct) &&
+        is(typeof(
+        {
+            T dig = void; //Can define
+            dig.put(cast(ubyte)0, cast(ubyte)0); //varags
+            dig.start(); //has start
+            auto value = dig.finish(); //has finish
+        }));
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    static assert(isDigest!CRC32);
+}
+///
+unittest
+{
+    import std.digest.crc;
+    void myFunction(T)() if(isDigest!T)
+    {
+        T dig;
+        dig.start();
+        auto result = dig.finish();
+    }
+    myFunction!CRC32();
+}
+
+/**
+ * Use this template to get the type which is returned by a digest's $(LREF finish) method.
+ */
+template DigestType(T)
+{
+    static if(isDigest!T)
+    {
+        alias DigestType =
+            ReturnType!(typeof(
+            {
+                T dig = void;
+                return dig.finish();
+            }));
+    }
+    else
+        static assert(false, T.stringof ~ " is not a digest! (fails isDigest!T)");
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    assert(is(DigestType!(CRC32) == ubyte[4]));
+}
+///
+unittest
+{
+    import std.digest.crc;
+    CRC32 dig;
+    dig.start();
+    DigestType!CRC32 result = dig.finish();
+}
+
+/**
+ * Used to check if a digest supports the $(D peek) method.
+ * Peek has exactly the same function signatures as finish, but it doesn't reset
+ * the digest's internal state.
+ *
+ * Note:
+ * $(UL
+ * $(LI This is very useful as a template constraint (see examples))
+ * $(LI This also checks if T passes $(LREF isDigest))
+ * )
+ */
+template hasPeek(T)
+{
+    enum bool hasPeek = isDigest!T &&
+        is(typeof(
+        {
+            T dig = void; //Can define
+            DigestType!T val = dig.peek();
+        }));
+}
+
+///
+unittest
+{
+    import std.digest.crc, std.digest.md;
+    assert(!hasPeek!(MD5));
+    assert(hasPeek!CRC32);
+}
+///
+unittest
+{
+    import std.digest.crc;
+    void myFunction(T)() if(hasPeek!T)
+    {
+        T dig;
+        dig.start();
+        auto result = dig.peek();
+    }
+    myFunction!CRC32();
+}
+
+/**
+ * Checks whether the digest has a $(D blockSize) member, which contains the
+ * digest's internal block size in bits. It is primarily used by $(XREF digest.hmac, HMAC).
+ */
+
+template hasBlockSize(T)
+if (isDigest!T)
+{
+    enum bool hasBlockSize = __traits(compiles, { size_t blockSize = T.blockSize; });
+}
+
+///
+unittest
+{
+    import std.digest.md, std.digest.hmac;
+    static assert(hasBlockSize!MD5        && MD5.blockSize      == 512);
+    static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == 512);
+}
+
+package template isDigestibleRange(Range)
+{
+    import std.digest.md;
+    import std.range : isInputRange, ElementType;
+    enum bool isDigestibleRange = isInputRange!Range && is(typeof(
+          {
+          MD5 ha; //Could use any conformant hash
+          ElementType!Range val;
+          ha.put(val);
+          }));
+}
+
+/**
+ * This is a convenience function to calculate a hash using the template API.
+ * Every digest passing the $(LREF isDigest) test can be used with this function.
+ *
+ * Params:
+ *  range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num])
+ */
+DigestType!Hash digest(Hash, Range)(auto ref Range range) if(!isArray!Range
+    && isDigestibleRange!Range)
+{
+    import std.algorithm : copy;
+    Hash hash;
+    hash.start();
+    copy(range, &hash);
+    return hash.finish();
+}
+
+///
+unittest
+{
+    import std.digest.md;
+    import std.range : repeat;
+    auto testRange = repeat!ubyte(cast(ubyte)'a', 100);
+    auto md5 = digest!MD5(testRange);
+}
+
+/**
+ * This overload of the digest function handles arrays.
+ *
+ * Params:
+ *  data= one or more arrays of any type
+ */
+DigestType!Hash digest(Hash, T...)(scope const T data) if(allSatisfy!(isArray, typeof(data)))
+{
+    Hash hash;
+    hash.start();
+    foreach(datum; data)
+        hash.put(cast(const(ubyte[]))datum);
+    return hash.finish();
+}
+
+///
+unittest
+{
+    import std.digest.md, std.digest.sha, std.digest.crc;
+    auto md5   = digest!MD5(  "The quick brown fox jumps over the lazy dog");
+    auto sha1  = digest!SHA1( "The quick brown fox jumps over the lazy dog");
+    auto crc32 = digest!CRC32("The quick brown fox jumps over the lazy dog");
+    assert(toHexString(crc32) == "39A34F41");
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog");
+    assert(toHexString(crc32) == "39A34F41");
+}
+
+/**
+ * This is a convenience function similar to $(LREF digest), but it returns the string
+ * representation of the hash. Every digest passing the $(LREF isDigest) test can be used with this
+ * function.
+ *
+ * Params:
+ *  order= the order in which the bytes are processed (see $(LREF toHexString))
+ *  range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num])
+ */
+char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, Range)(ref Range range)
+    if(!isArray!Range && isDigestibleRange!Range)
+{
+    return toHexString!order(digest!Hash(range));
+}
+
+///
+unittest
+{
+    import std.digest.md;
+    import std.range : repeat;
+    auto testRange = repeat!ubyte(cast(ubyte)'a', 100);
+    assert(hexDigest!MD5(testRange) == "36A92CC94A9E0FA21F625F8BFB007ADF");
+}
+
+/**
+ * This overload of the hexDigest function handles arrays.
+ *
+ * Params:
+ *  order= the order in which the bytes are processed (see $(LREF toHexString))
+ *  data= one or more arrays of any type
+ */
+char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, T...)(scope const T data)
+    if(allSatisfy!(isArray, typeof(data)))
+{
+    return toHexString!order(digest!Hash(data));
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    assert(hexDigest!(CRC32, Order.decreasing)("The quick brown fox jumps over the lazy dog") == "414FA339");
+}
+///
+unittest
+{
+    import std.digest.crc;
+    assert(hexDigest!(CRC32, Order.decreasing)("The quick ", "brown ", "fox jumps over the lazy dog") == "414FA339");
+}
+
+/**
+ * This is a convenience function which returns an initialized digest, so it's not necessary to call
+ * start manually.
+ */
+Hash makeDigest(Hash)()
+{
+    Hash hash;
+    hash.start();
+    return hash;
+}
+
+///
+unittest
+{
+    import std.digest.md;
+    auto md5 = makeDigest!MD5();
+    md5.put(0);
+    assert(toHexString(md5.finish()) == "93B885ADFE0DA089CDF634904FD59F71");
+}
+
+/*+*************************** End of template part, welcome to OOP land **************************/
+
+/**
+ * This describes the OOP API. To understand when to use the template API and when to use the OOP API,
+ * see the module documentation at the top of this page.
+ *
+ * The Digest interface is the base interface which is implemented by all digests.
+ *
+ * Note:
+ * A Digest implementation is always an $(D OutputRange)
+ */
+interface Digest
+{
+    public:
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         *
+         * Example:
+         * ----
+         * void test(Digest dig)
+         * {
+         *     dig.put(cast(ubyte)0); //single ubyte
+         *     dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+         *     ubyte[10] buf;
+         *     dig.put(buf); //buffer
+         * }
+         * ----
+         */
+        @trusted nothrow void put(scope const(ubyte)[] data...);
+
+        /**
+         * Resets the internal state of the digest.
+         * Note:
+         * $(LREF finish) calls this internally, so it's not necessary to call
+         * $(D reset) manually after a call to $(LREF finish).
+         */
+        @trusted nothrow void reset();
+
+        /**
+         * This is the length in bytes of the hash value which is returned by $(LREF finish).
+         * It's also the required size of a buffer passed to $(LREF finish).
+         */
+        @trusted nothrow @property size_t length() const;
+
+        /**
+         * The finish function returns the hash value. It takes an optional buffer to copy the data
+         * into. If a buffer is passed, it must be at least $(LREF length) bytes big.
+         */
+        @trusted nothrow ubyte[] finish();
+        ///ditto
+        nothrow ubyte[] finish(ubyte[] buf);
+        //@@@BUG@@@ http://d.puremagic.com/issues/show_bug.cgi?id=6549
+        /*in
+        {
+            assert(buf.length >= this.length);
+        }*/
+
+        /**
+         * This is a convenience function to calculate the hash of a value using the OOP API.
+         */
+        final @trusted nothrow ubyte[] digest(scope const(void[])[] data...)
+        {
+            this.reset();
+            foreach(datum; data)
+                this.put(cast(ubyte[])datum);
+            return this.finish();
+        }
+}
+
+///
+unittest
+{
+    //Using the OutputRange feature
+    import std.algorithm : copy;
+    import std.range : repeat;
+    import std.digest.md;
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    auto ctx = new MD5Digest();
+    copy(oneMillionRange, ctx);
+    assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21");
+}
+
+///
+unittest
+{
+    import std.digest.md, std.digest.sha, std.digest.crc;
+    ubyte[] md5   = (new MD5Digest()).digest("The quick brown fox jumps over the lazy dog");
+    ubyte[] sha1  = (new SHA1Digest()).digest("The quick brown fox jumps over the lazy dog");
+    ubyte[] crc32 = (new CRC32Digest()).digest("The quick brown fox jumps over the lazy dog");
+    assert(crcHexString(crc32) == "414FA339");
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    ubyte[] crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog");
+    assert(crcHexString(crc32) == "414FA339");
+}
+
+unittest
+{
+    import std.range : isOutputRange;
+    assert(!isDigest!(Digest));
+    assert(isOutputRange!(Digest, ubyte));
+}
+
+///
+unittest
+{
+    void test(Digest dig)
+    {
+        dig.put(cast(ubyte)0); //single ubyte
+        dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+        ubyte[10] buf;
+        dig.put(buf); //buffer
+    }
+}
+
+/*+*************************** End of OOP part, helper functions follow ***************************/
+
+/**
+ * See $(LREF toHexString)
+ */
+enum Order : bool
+{
+    increasing, ///
+    decreasing ///
+}
+
+
+/**
+ * Used to convert a hash value (a static or dynamic array of ubytes) to a string.
+ * Can be used with the OOP and with the template API.
+ *
+ * The additional order parameter can be used to specify the order of the input data.
+ * By default the data is processed in increasing order, starting at index 0. To process it in the
+ * opposite order, pass Order.decreasing as a parameter.
+ *
+ * The additional letterCase parameter can be used to specify the case of the output data.
+ * By default the output is in upper case. To change it to the lower case
+ * pass LetterCase.lower as a parameter.
+ *
+ * Note:
+ * The function overloads returning a string allocate their return values
+ * using the GC. The versions returning static arrays use pass-by-value for
+ * the return value, effectively avoiding dynamic allocation.
+ */
+char[num*2] toHexString(Order order = Order.increasing, size_t num, LetterCase letterCase = LetterCase.upper)
+(in ubyte[num] digest)
+{
+    static if (letterCase == LetterCase.upper)
+    {
+        import std.ascii : hexDigits = hexDigits;
+    }
+    else
+    {
+        import std.ascii : hexDigits = lowerHexDigits;
+    }
+
+
+    char[num*2] result;
+    size_t i;
+
+    static if(order == Order.increasing)
+    {
+        foreach(u; digest)
+        {
+            result[i++] = hexDigits[u >> 4];
+            result[i++] = hexDigits[u & 15];
+        }
+    }
+    else
+    {
+        size_t j = num - 1;
+        while(i < num*2)
+        {
+            result[i++] = hexDigits[digest[j] >> 4];
+            result[i++] = hexDigits[digest[j] & 15];
+            j--;
+        }
+    }
+    return result;
+}
+
+///ditto
+char[num*2] toHexString(LetterCase letterCase, Order order = Order.increasing, size_t num)(in ubyte[num] digest)
+{
+    return toHexString!(order, num, letterCase)(digest);
+}
+
+///ditto
+string toHexString(Order order = Order.increasing, LetterCase letterCase = LetterCase.upper)
+(in ubyte[] digest)
+{
+    static if (letterCase == LetterCase.upper)
+    {
+        import std.ascii : hexDigits = hexDigits;
+    }
+    else
+    {
+        import std.ascii : hexDigits = lowerHexDigits;
+    }
+
+    auto result = new char[digest.length*2];
+    size_t i;
+
+    static if(order == Order.increasing)
+    {
+        foreach(u; digest)
+        {
+            result[i++] = hexDigits[u >> 4];
+            result[i++] = hexDigits[u & 15];
+        }
+    }
+    else
+    {
+        import std.range : retro;
+        foreach(u; retro(digest))
+        {
+            result[i++] = hexDigits[u >> 4];
+            result[i++] = hexDigits[u & 15];
+        }
+    }
+    import std.exception : assumeUnique;
+    return assumeUnique(result);
+}
+
+///ditto
+string toHexString(LetterCase letterCase, Order order = Order.increasing)(in ubyte[] digest)
+{
+    return toHexString!(order, letterCase)(digest);
+}
+
+//For more example unittests, see Digest.digest, digest
+
+///
+unittest
+{
+    import std.digest.crc;
+    //Test with template API:
+    auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog");
+    //Lower case variant:
+    assert(toHexString!(LetterCase.lower)(crc32) == "39a34f41");
+    //Usually CRCs are printed in this order, though:
+    assert(toHexString!(Order.decreasing)(crc32) == "414FA339");
+    assert(toHexString!(LetterCase.lower, Order.decreasing)(crc32) == "414fa339");
+}
+
+///
+unittest
+{
+    import std.digest.crc;
+    // With OOP API
+    auto crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog");
+    //Usually CRCs are printed in this order, though:
+    assert(toHexString!(Order.decreasing)(crc32) == "414FA339");
+}
+
+unittest
+{
+    ubyte[16] data;
+    assert(toHexString(data) == "00000000000000000000000000000000");
+
+    assert(toHexString(cast(ubyte[4])[42, 43, 44, 45]) == "2A2B2C2D");
+    assert(toHexString(cast(ubyte[])[42, 43, 44, 45]) == "2A2B2C2D");
+    assert(toHexString!(Order.decreasing)(cast(ubyte[4])[42, 43, 44, 45]) == "2D2C2B2A");
+    assert(toHexString!(Order.decreasing, LetterCase.lower)(cast(ubyte[4])[42, 43, 44, 45]) == "2d2c2b2a");
+    assert(toHexString!(Order.decreasing)(cast(ubyte[])[42, 43, 44, 45]) == "2D2C2B2A");
+}
+
+/*+*********************** End of public helper part, private helpers follow ***********************/
+
+/*
+ * Used to convert from a ubyte[] slice to a ref ubyte[N].
+ * This helper is used internally in the WrapperDigest template to wrap the template API's
+ * finish function.
+ */
+ref T[N] asArray(size_t N, T)(ref T[] source, string errorMsg = "")
+{
+     assert(source.length >= N, errorMsg);
+     return *cast(T[N]*)source.ptr;
+}
+
+/**
+ * This helper is used internally in the WrapperDigest template, but it might be
+ * useful for other purposes as well. It returns the length (in bytes) of the hash value
+ * produced by T.
+ */
+template digestLength(T) if(isDigest!T)
+{
+    enum size_t digestLength = (ReturnType!(T.finish)).length;
+}
+
+/**
+ * Wraps a template API hash struct into a Digest interface.
+ * Modules providing digest implementations will usually provide
+ * an alias for this template (e.g. MD5Digest, SHA1Digest, ...).
+ */
+class WrapperDigest(T) if(isDigest!T) : Digest
+{
+    protected:
+        T _digest;
+
+    public final:
+        /**
+         * Initializes the digest.
+         */
+        this()
+        {
+            _digest.start();
+        }
+
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         */
+        @trusted nothrow void put(scope const(ubyte)[] data...)
+        {
+            _digest.put(data);
+        }
+
+        /**
+         * Resets the internal state of the digest.
+         * Note:
+         * $(LREF finish) calls this internally, so it's not necessary to call
+         * $(D reset) manually after a call to $(LREF finish).
+         */
+        @trusted nothrow void reset()
+        {
+            _digest.start();
+        }
+
+        /**
+         * This is the length in bytes of the hash value which is returned by $(LREF finish).
+         * It's also the required size of a buffer passed to $(LREF finish).
+         */
+        @trusted nothrow @property size_t length() const pure
+        {
+            return digestLength!T;
+        }
+
+        /**
+         * The finish function returns the hash value. It takes an optional buffer to copy the data
+         * into. If a buffer is passed, it must have a length at least $(LREF length) bytes.
+         *
+         * Example:
+         * --------
+         *
+         * import std.digest.md;
+         * ubyte[16] buf;
+         * auto hash = new WrapperDigest!MD5();
+         * hash.put(cast(ubyte)0);
+         * auto result = hash.finish(buf[]);
+         * //The result is now in result (and in buf). If you pass a buffer which is bigger than
+         * //necessary, result will have the correct length, but buf will still have it's original
+         * //length
+         * --------
+         */
+        nothrow ubyte[] finish(ubyte[] buf)
+        in
+        {
+            assert(buf.length >= this.length);
+        }
+        body
+        {
+            enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~
+                "big, check " ~ typeof(this).stringof ~ ".length!";
+            asArray!(digestLength!T)(buf, msg) = _digest.finish();
+            return buf[0 .. digestLength!T];
+        }
+
+        ///ditto
+        @trusted nothrow ubyte[] finish()
+        {
+            enum len = digestLength!T;
+            auto buf = new ubyte[len];
+            asArray!(digestLength!T)(buf) = _digest.finish();
+            return buf;
+        }
+
+        version(StdDdoc)
+        {
+            /**
+             * Works like $(D finish) but does not reset the internal state, so it's possible
+             * to continue putting data into this WrapperDigest after a call to peek.
+             *
+             * These functions are only available if $(D hasPeek!T) is true.
+             */
+            @trusted ubyte[] peek(ubyte[] buf) const;
+            ///ditto
+            @trusted ubyte[] peek() const;
+        }
+        else static if(hasPeek!T)
+        {
+            @trusted ubyte[] peek(ubyte[] buf) const
+            in
+            {
+                assert(buf.length >= this.length);
+            }
+            body
+            {
+                enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~
+                    "big, check " ~ typeof(this).stringof ~ ".length!";
+                asArray!(digestLength!T)(buf, msg) = _digest.peek();
+                return buf[0 .. digestLength!T];
+            }
+
+            @trusted ubyte[] peek() const
+            {
+                enum len = digestLength!T;
+                auto buf = new ubyte[len];
+                asArray!(digestLength!T)(buf) = _digest.peek();
+                return buf;
+            }
+        }
+}
+
+///
+unittest
+{
+    import std.digest.md;
+    //Simple example
+    auto hash = new WrapperDigest!MD5();
+    hash.put(cast(ubyte)0);
+    auto result = hash.finish();
+}
+
+///
+unittest
+{
+    //using a supplied buffer
+    import std.digest.md;
+    ubyte[16] buf;
+    auto hash = new WrapperDigest!MD5();
+    hash.put(cast(ubyte)0);
+    auto result = hash.finish(buf[]);
+    //The result is now in result (and in buf). If you pass a buffer which is bigger than
+    //necessary, result will have the correct length, but buf will still have it's original
+    //length
+}
diff --git a/libphobos/src/std/digest/hmac.d b/libphobos/src/std/digest/hmac.d
new file mode 100644
index 00000000000..fcaa0c35641
--- /dev/null
+++ b/libphobos/src/std/digest/hmac.d
@@ -0,0 +1,317 @@
+// Written in the D programming language.
+
+/**
+This package implements the hash-based message authentication code (_HMAC)
+algorithm as defined in $(WEB tools.ietf.org/html/rfc2104, RFC2104). See also
+the corresponding $(WEB en.wikipedia.org/wiki/Hash-based_message_authentication_code, Wikipedia article).
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+Macros:
+WIKI = Phobos/StdDigestHMAC
+SUBMODULE = $(LINK2 std_digest_$1.html, std.digest.$1)
+SUBREF = $(LINK2 std_digest_$1.html#.$2, $(TT $2))$(NBSP)
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Source: $(PHOBOSSRC std/digest/_hmac.d)
+ */
+
+module std.digest.hmac;
+
+import std.digest.digest : isDigest, hasBlockSize, isDigestibleRange, DigestType;
+import std.meta : allSatisfy;
+
+/**
+ * Template API HMAC implementation.
+ *
+ * This implements an _HMAC over the digest H. If H doesn't provide
+ * information about the block size, it can be supplied explicitly using
+ * the second overload.
+ *
+ * This type conforms to $(XREF digest.digest, isDigest).
+ */
+
+version(StdDdoc)
+/// Computes an HMAC over data read from stdin.
+unittest
+{
+    import std.stdio, std.digest.hmac, std.digest.sha;
+    import std.string : representation;
+
+    auto secret = "secret".representation;
+    stdin.byChunk(4096)
+         .hmac!SHA1(secret)
+         .toHexString!(LetterCase.lower)
+         .writeln;
+}
+
+template HMAC(H)
+if (isDigest!H && hasBlockSize!H)
+{
+    alias HMAC = HMAC!(H, H.blockSize);
+}
+
+/**
+ * Overload of HMAC to be used if H doesn't provide information about its
+ * block size.
+ */
+
+struct HMAC(H, size_t hashBlockSize)
+if (hashBlockSize % 8 == 0)
+{
+    enum blockSize = hashBlockSize;
+
+    private H digest;
+    private ubyte[blockSize / 8] key;
+
+    /**
+     * Constructs the HMAC digest using the specified secret.
+     */
+
+    this(scope const(ubyte)[] secret)
+    {
+        // if secret is too long, shorten it by computing its hash
+        typeof(digest.finish()) buffer = void;
+        if (secret.length > blockSize / 8)
+        {
+            digest.start();
+            digest.put(secret);
+            buffer = digest.finish();
+            secret = buffer[];
+        }
+
+        // if secret is too short, it will be padded with zeroes
+        // (the key buffer is already zero-initialized)
+        import std.algorithm.mutation : copy;
+        secret.copy(key[]);
+
+        start();
+    }
+
+    ///
+    @safe pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+        hmac.put("Hello, world".representation);
+        static immutable expected = [130, 32, 235, 44, 208, 141, 150, 232, 211, 214, 162, 195, 188, 127, 52, 89, 100, 68, 90, 216];
+        assert(hmac.finish() == expected);
+    }
+
+    /**
+     * Reinitializes the digest, making it ready for reuse.
+     *
+     * Note:
+     * The constructor leaves the digest in an initialized state, so that this
+     * method only needs to be called if an unfinished digest is to be reused.
+     *
+     * Returns:
+     * A reference to the digest for convenient chaining.
+     */
+
+    ref HMAC!(H, blockSize) start() return
+    {
+        ubyte[blockSize / 8] ipad = void;
+        foreach (immutable i; 0 .. blockSize / 8)
+            ipad[i] = key[i] ^ 0x36;
+
+        digest.start();
+        digest.put(ipad[]);
+
+        return this;
+    }
+
+    ///
+    @safe pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        string data1 = "Hello, world", data2 = "Hola mundo";
+        auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+        hmac.put(data1.representation);
+        hmac.start();                   // reset digest
+        hmac.put(data2.representation); // start over
+        static immutable expected = [122, 151, 232, 240, 249, 80, 19, 178, 186, 77, 110, 23, 208, 52, 11, 88, 34, 151, 192, 255];
+        assert(hmac.finish() == expected);
+    }
+
+    /**
+     * Feeds a piece of data into the hash computation. This method allows the
+     * type to be used as an $(XREF range, OutputRange).
+     *
+     * Returns:
+     * A reference to the digest for convenient chaining.
+     */
+
+    ref HMAC!(H, blockSize) put(in ubyte[] data...) return
+    {
+        digest.put(data);
+        return this;
+    }
+
+    ///
+    @safe pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        string data1 = "Hello, world", data2 = "Hola mundo";
+        auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+        hmac.put(data1.representation)
+            .put(data2.representation);
+        static immutable expected = [197, 57, 52, 3, 13, 194, 13, 36, 117, 228, 8, 11, 111, 51, 165, 3, 123, 31, 251, 113];
+        assert(hmac.finish() == expected);
+    }
+
+    /**
+     * Resets the digest and returns the finished hash.
+     */
+
+    DigestType!H finish()
+    {
+        ubyte[blockSize / 8] opad = void;
+        foreach (immutable i; 0 .. blockSize / 8)
+            opad[i] = key[i] ^ 0x5c;
+
+        auto tmp = digest.finish();
+
+        digest.start();
+        digest.put(opad[]);
+        digest.put(tmp);
+        auto result = digest.finish();
+        start();    // reset the digest
+        return result;
+    }
+
+    ///
+    @safe pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        string data1 = "Hello, world", data2 = "Hola mundo";
+        auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+        auto digest = hmac.put(data1.representation)
+                          .put(data2.representation)
+                          .finish();
+        static immutable expected = [197, 57, 52, 3, 13, 194, 13, 36, 117, 228, 8, 11, 111, 51, 165, 3, 123, 31, 251, 113];
+        assert(digest == expected);
+    }
+}
+
+template hmac(H)
+if (isDigest!H && hasBlockSize!H)
+{
+    alias hmac = hmac!(H, H.blockSize);
+}
+
+template hmac(H, size_t blockSize)
+if (isDigest!H)
+{
+    /**
+     * Constructs an HMAC digest with the specified secret.
+     *
+     * Returns:
+     * An instance of HMAC that can be fed data as desired, and finished
+     * to compute the final hash when done.
+     */
+
+    auto hmac(scope const(ubyte)[] secret)
+    {
+        return HMAC!(H, blockSize)(secret);
+    }
+
+    ///
+    @safe pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        string data1 = "Hello, world", data2 = "Hola mundo";
+        auto digest = hmac!SHA1("My s3cR3T keY".representation)
+                          .put(data1.representation)
+                          .put(data2.representation)
+                          .finish();
+        static immutable expected = [197, 57, 52, 3, 13, 194, 13, 36, 117, 228, 8, 11, 111, 51, 165, 3, 123, 31, 251, 113];
+        assert(digest == expected);
+    }
+
+    /**
+     * Computes an _HMAC digest over the given range of data with the
+     * specified secret.
+     *
+     * Returns:
+     * The final _HMAC hash.
+     */
+
+    DigestType!H hmac(T...)(scope T data, scope const(ubyte)[] secret)
+    if (allSatisfy!(isDigestibleRange, typeof(data)))
+    {
+        import std.algorithm.mutation : copy;
+        auto hash = HMAC!(H, blockSize)(secret);
+        foreach (datum; data)
+            copy(datum, &hash);
+        return hash.finish();
+    }
+
+    ///
+    @system pure nothrow @nogc unittest
+    {
+        import std.digest.sha, std.digest.hmac;
+        import std.string : representation;
+        import std.algorithm : map;
+        string data = "Hello, world";
+        auto digest = data.representation
+                      .map!(a => cast(ubyte)(a+1))
+                      .hmac!SHA1("My s3cR3T keY".representation);
+        static assert(is(typeof(digest) == ubyte[20]));
+        static immutable expected = [163, 208, 118, 179, 216, 93, 17, 10, 84, 200, 87, 104, 244, 111, 136, 214, 167, 210, 58, 10];
+        assert(digest == expected);
+    }
+}
+
+version(unittest)
+{
+    import std.digest.digest : toHexString, LetterCase;
+    alias hex = toHexString!(LetterCase.lower);
+}
+
+@safe pure nothrow @nogc
+unittest
+{
+    import std.digest.md : MD5;
+    import std.range : isOutputRange;
+    static assert(isOutputRange!(HMAC!MD5, ubyte));
+    static assert(isDigest!(HMAC!MD5));
+    static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == MD5.blockSize);
+}
+
+@system pure nothrow
+unittest
+{
+    import std.digest.md : MD5;
+    import std.digest.sha : SHA1, SHA256;
+
+    ubyte[] nada;
+    assert(hmac!MD5   (nada, nada).hex == "74e6f7298a9c2d168935f58c001bad88");
+    assert(hmac!SHA1  (nada, nada).hex == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d");
+    assert(hmac!SHA256(nada, nada).hex == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad");
+
+    import std.string : representation;
+    auto key      = "key".representation,
+         long_key = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".representation,
+         data1    = "The quick brown fox ".representation,
+         data2    = "jumps over the lazy dog".representation,
+         data     = data1 ~ data2;
+
+    assert(data.hmac!MD5   (key).hex == "80070713463e7749b90c2dc24911e275");
+    assert(data.hmac!SHA1  (key).hex == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
+    assert(data.hmac!SHA256(key).hex == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
+
+    assert(data.hmac!MD5   (long_key).hex == "e1728d68e05beae186ea768561963778");
+    assert(data.hmac!SHA1  (long_key).hex == "560d3cd77316e57ab4bba0c186966200d2b37ba3");
+    assert(data.hmac!SHA256(long_key).hex == "a1b0065a5d1edd93152c677e1bc1b1e3bc70d3a76619842e7f733f02b8135c04");
+
+    assert(hmac!MD5   (key).put(data1).put(data2).finish == data.hmac!MD5   (key));
+    assert(hmac!SHA1  (key).put(data1).put(data2).finish == data.hmac!SHA1  (key));
+    assert(hmac!SHA256(key).put(data1).put(data2).finish == data.hmac!SHA256(key));
+}
diff --git a/libphobos/src/std/digest/md.d b/libphobos/src/std/digest/md.d
new file mode 100644
index 00000000000..7f28bff4e0b
--- /dev/null
+++ b/libphobos/src/std/digest/md.d
@@ -0,0 +1,591 @@
+/**
+ * Computes MD5 hashes of arbitrary data. MD5 hashes are 16 byte quantities that are like a
+ * checksum or CRC, but are more robust.
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Template API) $(TD $(MYREF MD5)
+)
+)
+$(TR $(TDNW OOP API) $(TD $(MYREF MD5Digest))
+)
+$(TR $(TDNW Helpers) $(TD $(MYREF md5Of))
+)
+)
+)
+
+ * This module conforms to the APIs defined in $(D std.digest.digest). To understand the
+ * differences between the template and the OOP API, see $(D std.digest.digest).
+ *
+ * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone
+ * module.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *
+ * CTFE:
+ * Digests do not work in CTFE
+ *
+ * Authors:
+ * Piotr Szturmaj, Kai Nacke, Johannes Pfau $(BR)
+ * The routines and algorithms are derived from the $(I RSA Data Security, Inc. MD5 Message-Digest Algorithm).
+ *
+ * References:
+ *      $(LINK2 http://en.wikipedia.org/wiki/Md5, Wikipedia on MD5)
+ *
+ * Source: $(PHOBOSSRC std/digest/_md.d)
+ *
+ * Macros:
+ * WIKI = Phobos/StdMd5
+ */
+
+/* md5.d - RSA Data Security, Inc., MD5 message-digest algorithm
+ * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm.
+ */
+module std.digest.md;
+
+public import std.digest.digest;
+
+///
+unittest
+{
+    //Template API
+    import std.digest.md;
+
+    //Feeding data
+    ubyte[1024] data;
+    MD5 md5;
+    md5.start();
+    md5.put(data[]);
+    md5.start(); //Start again
+    md5.put(data[]);
+    auto hash = md5.finish();
+}
+
+///
+unittest
+{
+    //OOP API
+    import std.digest.md;
+
+    auto md5 = new MD5Digest();
+    ubyte[] hash = md5.digest("abc");
+    assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72");
+
+    //Feeding data
+    ubyte[1024] data;
+    md5.put(data[]);
+    md5.reset(); //Start again
+    md5.put(data[]);
+    hash = md5.finish();
+}
+
+//rotateLeft rotates x left n bits
+private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc
+{
+    // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol.
+    // No assembler required.
+    return (x << n) | (x >> (32-n));
+}
+
+/**
+ * Template API MD5 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ */
+struct MD5
+{
+    private:
+        // magic initialization constants
+        uint[4] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476]; // state (ABCD)
+        ulong _count; //number of bits, modulo 2^64
+        ubyte[64] _buffer; // input buffer
+
+        static immutable ubyte[64] _padding =
+        [
+          0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+        ];
+
+        // F, G, H and I are basic MD5 functions
+        static @safe pure nothrow @nogc
+        {
+            uint F(uint x, uint y, uint z) { return (x & y) | (~x & z); }
+            uint G(uint x, uint y, uint z) { return (x & z) | (y & ~z); }
+            uint H(uint x, uint y, uint z) { return x ^ y ^ z; }
+            uint I(uint x, uint y, uint z) { return y ^ (x | ~z); }
+        }
+
+
+        /*
+         * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+         * Rotation is separate from addition to prevent recomputation.
+         */
+        static void FF(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac)
+            @safe pure nothrow @nogc
+        {
+            a += F (b, c, d) + x + ac;
+            a = rotateLeft(a, s);
+            a += b;
+        }
+
+        static void GG(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac)
+            @safe pure nothrow @nogc
+        {
+            a += G (b, c, d) + x + ac;
+            a = rotateLeft(a, s);
+            a += b;
+        }
+
+        static void HH(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac)
+            @safe pure nothrow @nogc
+        {
+            a += H (b, c, d) + x + ac;
+            a = rotateLeft(a, s);
+            a += b;
+        }
+
+        static void II(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac)
+            @safe pure nothrow @nogc
+        {
+            a += I (b, c, d) + x + ac;
+            a = rotateLeft(a, s);
+            a += b;
+        }
+
+        /*
+         * MD5 basic transformation. Transforms state based on block.
+         */
+
+        //Constants for MD5Transform routine.
+        enum
+        {
+            S11 = 7,
+            S12 = 12,
+            S13 = 17,
+            S14 = 22,
+            S21 = 5,
+            S22 = 9,
+            S23 = 14,
+            S24 = 20,
+            S31 = 4,
+            S32 = 11,
+            S33 = 16,
+            S34 = 23,
+            S41 = 6,
+            S42 = 10,
+            S43 = 15,
+            S44 = 21,
+        }
+
+        private void transform(const(ubyte[64])* block) pure nothrow @nogc
+        {
+            uint a = _state[0],
+                 b = _state[1],
+                 c = _state[2],
+                 d = _state[3];
+
+            uint[16] x = void;
+
+            version(BigEndian)
+            {
+                import std.bitmanip : littleEndianToNative;
+
+                for(size_t i = 0; i < 16; i++)
+                {
+                    x[i] = littleEndianToNative!uint(*cast(ubyte[4]*)&(*block)[i*4]);
+                }
+            }
+            else
+            {
+                (cast(ubyte*)x.ptr)[0 .. 64] = (cast(ubyte*)block)[0 .. 64];
+            }
+
+            //Round 1
+            FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+            FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+            FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+            FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+            FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+            FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+            FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+            FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+            FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+            FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+            FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+            FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+            FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+            FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+            FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+            FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+            //Round 2
+            GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+            GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+            GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+            GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+            GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+            GG (d, a, b, c, x[10], S22,  0x2441453); /* 22 */
+            GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+            GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+            GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+            GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+            GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+            GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+            GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+            GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+            GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+            GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+            //Round 3
+            HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+            HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+            HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+            HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+            HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+            HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+            HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+            HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+            HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+            HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+            HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+            HH (b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
+            HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+            HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+            HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+            HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+            //Round 4
+            II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+            II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+            II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+            II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+            II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+            II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+            II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+            II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+            II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+            II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+            II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+            II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+            II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+            II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+            II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+            II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+            _state[0] += a;
+            _state[1] += b;
+            _state[2] += c;
+            _state[3] += d;
+
+            //Zeroize sensitive information.
+            x[] = 0;
+        }
+
+    public:
+        enum blockSize = 512;
+
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         *
+         * Example:
+         * ----
+         * MD5 dig;
+         * dig.put(cast(ubyte)0); //single ubyte
+         * dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+         * ubyte[10] buf;
+         * dig.put(buf); //buffer
+         * ----
+         */
+        void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc
+        {
+            uint i, index, partLen;
+            auto inputLen = data.length;
+
+            //Compute number of bytes mod 64
+            index = (cast(uint)_count >> 3) & (64 - 1);
+
+            //Update number of bits
+            _count += inputLen * 8;
+
+            partLen = 64 - index;
+
+            //Transform as many times as possible
+            if (inputLen >= partLen)
+            {
+                (&_buffer[index])[0 .. partLen] = data.ptr[0 .. partLen];
+                transform(&_buffer);
+
+                for(i = partLen; i + 63 < inputLen; i += 64)
+                {
+                    transform(cast(const(ubyte[64])*)(data[i .. i + 64].ptr));
+                }
+
+                index = 0;
+            }
+            else
+            {
+                i = 0;
+            }
+
+            /* Buffer remaining input */
+            if (inputLen - i)
+                (&_buffer[index])[0 .. inputLen-i] = (&data[i])[0 .. inputLen-i];
+        }
+
+        /**
+         * Used to (re)initialize the MD5 digest.
+         *
+         * Note:
+         * For this MD5 Digest implementation calling start after default construction
+         * is not necessary. Calling start is only necessary to reset the Digest.
+         *
+         * Generic code which deals with different Digest types should always call start though.
+         *
+         * Example:
+         * --------
+         * MD5 digest;
+         * //digest.start(); //Not necessary
+         * digest.put(0);
+         * --------
+         */
+        void start() @safe pure nothrow @nogc
+        {
+            this = MD5.init;
+        }
+
+        /**
+         * Returns the finished MD5 hash. This also calls $(LREF start) to
+         * reset the internal state.
+          */
+        ubyte[16] finish() @trusted pure nothrow @nogc
+        {
+            import std.bitmanip : nativeToLittleEndian;
+
+            ubyte[16] data = void;
+            ubyte[8] bits = void;
+            uint index, padLen;
+
+            //Save number of bits
+            bits[0 .. 8] = nativeToLittleEndian(_count)[];
+
+            //Pad out to 56 mod 64
+            index = (cast(uint)_count >> 3) & (64 - 1);
+            padLen = (index < 56) ? (56 - index) : (120 - index);
+            put(_padding[0 .. padLen]);
+
+            //Append length (before padding)
+            put(bits);
+
+            //Store state in digest
+            data[0 .. 4]   = nativeToLittleEndian(_state[0])[];
+            data[4 .. 8]   = nativeToLittleEndian(_state[1])[];
+            data[8 .. 12]  = nativeToLittleEndian(_state[2])[];
+            data[12 .. 16] = nativeToLittleEndian(_state[3])[];
+
+            /* Zeroize sensitive information. */
+            start();
+            return data;
+        }
+        ///
+        unittest
+        {
+            //Simple example
+            MD5 hash;
+            hash.start();
+            hash.put(cast(ubyte)0);
+            ubyte[16] result = hash.finish();
+        }
+}
+
+///
+unittest
+{
+    //Simple example, hashing a string using md5Of helper function
+    ubyte[16] hash = md5Of("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72");
+}
+
+///
+unittest
+{
+    //Using the basic API
+    MD5 hash;
+    hash.start();
+    ubyte[1024] data;
+    //Initialize data here...
+    hash.put(data);
+    ubyte[16] result = hash.finish();
+}
+
+///
+unittest
+{
+    //Let's use the template features:
+    void doSomething(T)(ref T hash) if(isDigest!T)
+    {
+        hash.put(cast(ubyte)0);
+    }
+    MD5 md5;
+    md5.start();
+    doSomething(md5);
+    assert(toHexString(md5.finish()) == "93B885ADFE0DA089CDF634904FD59F71");
+}
+
+unittest
+{
+    assert(isDigest!MD5);
+}
+
+unittest
+{
+    import std.range;
+
+    ubyte[16] digest;
+
+    MD5 md5;
+    md5.put(cast(ubyte[])"abcdef");
+    md5.start();
+    md5.put(cast(ubyte[])"");
+    assert(md5.finish() == cast(ubyte[])x"d41d8cd98f00b204e9800998ecf8427e");
+
+    digest = md5Of("");
+    assert(digest == cast(ubyte[])x"d41d8cd98f00b204e9800998ecf8427e");
+
+    digest = md5Of("a");
+    assert(digest == cast(ubyte[])x"0cc175b9c0f1b6a831c399e269772661");
+
+    digest = md5Of("abc");
+    assert(digest == cast(ubyte[])x"900150983cd24fb0d6963f7d28e17f72");
+
+    digest = md5Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    assert(digest == cast(ubyte[])x"8215ef0796a20bcaaae116d3876c664a");
+
+    digest = md5Of("message digest");
+    assert(digest == cast(ubyte[])x"f96b697d7cb7938d525a2f31aaf161d0");
+
+    digest = md5Of("abcdefghijklmnopqrstuvwxyz");
+    assert(digest == cast(ubyte[])x"c3fcd3d76192e4007dfb496cca67e13b");
+
+    digest = md5Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    assert(digest == cast(ubyte[])x"d174ab98d277d9f5a5611c2c9f419d9f");
+
+    digest = md5Of("1234567890123456789012345678901234567890"~
+                    "1234567890123456789012345678901234567890");
+    assert(digest == cast(ubyte[])x"57edf4a22be3c955ac49da2e2107b67a");
+
+    assert(toHexString(cast(ubyte[16])x"c3fcd3d76192e4007dfb496cca67e13b")
+        == "C3FCD3D76192E4007DFB496CCA67E13B");
+
+    ubyte[] onemilliona = new ubyte[1000000];
+    onemilliona[] = 'a';
+    digest = md5Of(onemilliona);
+    assert(digest == cast(ubyte[])x"7707D6AE4E027C70EEA2A935C2296F21");
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    digest = md5Of(oneMillionRange);
+    assert(digest == cast(ubyte[])x"7707D6AE4E027C70EEA2A935C2296F21");
+}
+
+/**
+ * This is a convenience alias for $(XREF_PACK digest,digest,digest) using the
+ * MD5 implementation.
+ */
+//simple alias doesn't work here, hope this gets inlined...
+auto md5Of(T...)(T data)
+{
+    return digest!(MD5, T)(data);
+}
+
+///
+unittest
+{
+    ubyte[16] hash = md5Of("abc");
+    assert(hash == digest!MD5("abc"));
+}
+
+/**
+ * OOP API MD5 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ *
+ * This is an alias for $(D $(XREF_PACK digest,digest,WrapperDigest)!MD5), see
+ * there for more information.
+ */
+alias MD5Digest = WrapperDigest!MD5;
+
+///
+unittest
+{
+    //Simple example, hashing a string using Digest.digest helper function
+    auto md5 = new MD5Digest();
+    ubyte[] hash = md5.digest("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72");
+}
+
+///
+unittest
+{
+     //Let's use the OOP features:
+    void test(Digest dig)
+    {
+      dig.put(cast(ubyte)0);
+    }
+    auto md5 = new MD5Digest();
+    test(md5);
+
+    //Let's use a custom buffer:
+    ubyte[16] buf;
+    ubyte[] result = md5.finish(buf[]);
+    assert(toHexString(result) == "93B885ADFE0DA089CDF634904FD59F71");
+}
+
+unittest
+{
+    auto md5 = new MD5Digest();
+
+    md5.put(cast(ubyte[])"abcdef");
+    md5.reset();
+    md5.put(cast(ubyte[])"");
+    assert(md5.finish() == cast(ubyte[])x"d41d8cd98f00b204e9800998ecf8427e");
+
+    md5.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    ubyte[20] result;
+    auto result2 = md5.finish(result[]);
+    assert(result[0 .. 16] == result2 && result2 == cast(ubyte[])x"c3fcd3d76192e4007dfb496cca67e13b");
+
+    debug
+    {
+        import std.exception;
+        assertThrown!Error(md5.finish(result[0 .. 15]));
+    }
+
+    assert(md5.length == 16);
+
+    assert(md5.digest("") == cast(ubyte[])x"d41d8cd98f00b204e9800998ecf8427e");
+
+    assert(md5.digest("a") == cast(ubyte[])x"0cc175b9c0f1b6a831c399e269772661");
+
+    assert(md5.digest("abc") == cast(ubyte[])x"900150983cd24fb0d6963f7d28e17f72");
+
+    assert(md5.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
+           == cast(ubyte[])x"8215ef0796a20bcaaae116d3876c664a");
+
+    assert(md5.digest("message digest") == cast(ubyte[])x"f96b697d7cb7938d525a2f31aaf161d0");
+
+    assert(md5.digest("abcdefghijklmnopqrstuvwxyz")
+           == cast(ubyte[])x"c3fcd3d76192e4007dfb496cca67e13b");
+
+    assert(md5.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+           == cast(ubyte[])x"d174ab98d277d9f5a5611c2c9f419d9f");
+
+    assert(md5.digest("1234567890123456789012345678901234567890",
+                                   "1234567890123456789012345678901234567890")
+           == cast(ubyte[])x"57edf4a22be3c955ac49da2e2107b67a");
+}
diff --git a/libphobos/src/std/digest/ripemd.d b/libphobos/src/std/digest/ripemd.d
new file mode 100644
index 00000000000..19881e66b1c
--- /dev/null
+++ b/libphobos/src/std/digest/ripemd.d
@@ -0,0 +1,763 @@
+/**
+ * Computes RIPEMD-160 hashes of arbitrary data. RIPEMD-160 hashes are 20 byte quantities
+ * that are like a checksum or CRC, but are more robust.
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Template API) $(TD $(MYREF RIPEMD160)
+)
+)
+$(TR $(TDNW OOP API) $(TD $(MYREF RIPEMD160Digest))
+)
+$(TR $(TDNW Helpers) $(TD $(MYREF ripemd160Of))
+)
+)
+)
+
+ * This module conforms to the APIs defined in $(D std.digest.digest). To understand the
+ * differences between the template and the OOP API, see $(D std.digest.digest).
+ *
+ * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone
+ * module.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *
+ * CTFE:
+ * Digests do not work in CTFE
+ *
+ * Authors:
+ * Kai Nacke $(BR)
+ * The algorithm was designed by Hans Dobbertin, Antoon Bosselaers, and Bart Preneel. $(BR)
+ * The D implementation is a direct translation of the ANSI C implementation by Antoon Bosselaers.
+ *
+ * References:
+ * $(UL
+ * $(LI $(LINK2 http://homes.esat.kuleuven.be/~bosselae/ripemd160.html, The hash function RIPEMD-160))
+ * $(LI $(LINK2 http://en.wikipedia.org/wiki/RIPEMD-160, Wikipedia on RIPEMD-160))
+ * )
+ *
+ * Source: $(PHOBOSSRC std/digest/_ripemd.d)
+ *
+ * Macros:
+ * WIKI = Phobos/StdRipemd
+ */
+
+module std.digest.ripemd;
+
+public import std.digest.digest;
+
+///
+unittest
+{
+    //Template API
+    import std.digest.md;
+
+    ubyte[20] hash = ripemd160Of("abc");
+    assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC");
+
+    //Feeding data
+    ubyte[1024] data;
+    RIPEMD160 md;
+    md.start();
+    md.put(data[]);
+    md.start(); //Start again
+    md.put(data[]);
+    hash = md.finish();
+}
+
+///
+unittest
+{
+    //OOP API
+    import std.digest.md;
+
+    auto md = new RIPEMD160Digest();
+    ubyte[] hash = md.digest("abc");
+    assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC");
+
+    //Feeding data
+    ubyte[1024] data;
+    md.put(data[]);
+    md.reset(); //Start again
+    md.put(data[]);
+    hash = md.finish();
+}
+
+//rotateLeft rotates x left n bits
+private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc
+{
+    // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol.
+    // No assembler required.
+    return (x << n) | (x >> (32-n));
+}
+
+/**
+ * Template API RIPEMD160 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ */
+struct RIPEMD160
+{
+    private:
+        // magic initialization constants
+        uint[5] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0]; // state (ABCDE)
+        ulong _count; //number of bits, modulo 2^64
+        ubyte[64] _buffer; // input buffer
+
+        static immutable ubyte[64] _padding =
+        [
+          0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+        ];
+
+        // F, G, H, I and J are basic RIPEMD160 functions
+        static @safe pure nothrow @nogc
+        {
+            uint F(uint x, uint y, uint z) { return x ^ y ^ z; }
+            uint G(uint x, uint y, uint z) { return (x & y) | (~x & z); }
+            uint H(uint x, uint y, uint z) { return (x | ~y) ^ z; }
+            uint I(uint x, uint y, uint z) { return (x & z) | (y & ~z); }
+            uint J(uint x, uint y, uint z) { return x ^ (y | ~z); }
+        }
+
+        /*
+         * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+         * Rotation is separate from addition to prevent recomputation.
+         */
+
+        /* the ten basic operations FF() through III() */
+        static void FF(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += F(b, c, d) + x;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void GG(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += G(b, c, d) + x + 0x5a827999UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void HH(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += H(b, c, d) + x + 0x6ed9eba1UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void II(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += I(b, c, d) + x + 0x8f1bbcdcUL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void JJ(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += J(b, c, d) + x + 0xa953fd4eUL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        /*
+         * FFF, GGG, HHH, and III transformations for parallel rounds 1, 2, 3, and 4.
+         * Rotation is separate from addition to prevent recomputation.
+         */
+
+        static void FFF(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += F(b, c, d) + x;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void GGG(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += G(b, c, d) + x + 0x7a6d76e9UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void HHH(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += H(b, c, d) + x + 0x6d703ef3UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void III(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += I(b, c, d) + x + 0x5c4dd124UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        static void JJJ(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s)
+            @safe pure nothrow @nogc
+        {
+            a += J(b, c, d) + x + 0x50a28be6UL;
+            a = rotateLeft(a, s) + e;
+            c = rotateLeft(c, 10);
+        }
+
+        /*
+         * RIPEMD160 basic transformation. Transforms state based on block.
+         */
+
+        private void transform(const(ubyte[64])* block)
+            pure nothrow @nogc
+        {
+            uint aa = _state[0],
+                 bb = _state[1],
+                 cc = _state[2],
+                 dd = _state[3],
+                 ee = _state[4];
+            uint aaa = _state[0],
+                 bbb = _state[1],
+                 ccc = _state[2],
+                 ddd = _state[3],
+                 eee = _state[4];
+
+            uint[16] x = void;
+
+            version(BigEndian)
+            {
+                import std.bitmanip : littleEndianToNative;
+
+                for(size_t i = 0; i < 16; i++)
+                {
+                    x[i] = littleEndianToNative!uint(*cast(ubyte[4]*)&(*block)[i*4]);
+                }
+            }
+            else
+            {
+                (cast(ubyte*)x.ptr)[0 .. 64] = (cast(ubyte*)block)[0 .. 64];
+            }
+
+            /* round 1 */
+            FF(aa, bb, cc, dd, ee, x[ 0], 11);
+            FF(ee, aa, bb, cc, dd, x[ 1], 14);
+            FF(dd, ee, aa, bb, cc, x[ 2], 15);
+            FF(cc, dd, ee, aa, bb, x[ 3], 12);
+            FF(bb, cc, dd, ee, aa, x[ 4],  5);
+            FF(aa, bb, cc, dd, ee, x[ 5],  8);
+            FF(ee, aa, bb, cc, dd, x[ 6],  7);
+            FF(dd, ee, aa, bb, cc, x[ 7],  9);
+            FF(cc, dd, ee, aa, bb, x[ 8], 11);
+            FF(bb, cc, dd, ee, aa, x[ 9], 13);
+            FF(aa, bb, cc, dd, ee, x[10], 14);
+            FF(ee, aa, bb, cc, dd, x[11], 15);
+            FF(dd, ee, aa, bb, cc, x[12],  6);
+            FF(cc, dd, ee, aa, bb, x[13],  7);
+            FF(bb, cc, dd, ee, aa, x[14],  9);
+            FF(aa, bb, cc, dd, ee, x[15],  8);
+
+            /* round 2 */
+            GG(ee, aa, bb, cc, dd, x[ 7],  7);
+            GG(dd, ee, aa, bb, cc, x[ 4],  6);
+            GG(cc, dd, ee, aa, bb, x[13],  8);
+            GG(bb, cc, dd, ee, aa, x[ 1], 13);
+            GG(aa, bb, cc, dd, ee, x[10], 11);
+            GG(ee, aa, bb, cc, dd, x[ 6],  9);
+            GG(dd, ee, aa, bb, cc, x[15],  7);
+            GG(cc, dd, ee, aa, bb, x[ 3], 15);
+            GG(bb, cc, dd, ee, aa, x[12],  7);
+            GG(aa, bb, cc, dd, ee, x[ 0], 12);
+            GG(ee, aa, bb, cc, dd, x[ 9], 15);
+            GG(dd, ee, aa, bb, cc, x[ 5],  9);
+            GG(cc, dd, ee, aa, bb, x[ 2], 11);
+            GG(bb, cc, dd, ee, aa, x[14],  7);
+            GG(aa, bb, cc, dd, ee, x[11], 13);
+            GG(ee, aa, bb, cc, dd, x[ 8], 12);
+
+            /* round 3 */
+            HH(dd, ee, aa, bb, cc, x[ 3], 11);
+            HH(cc, dd, ee, aa, bb, x[10], 13);
+            HH(bb, cc, dd, ee, aa, x[14],  6);
+            HH(aa, bb, cc, dd, ee, x[ 4],  7);
+            HH(ee, aa, bb, cc, dd, x[ 9], 14);
+            HH(dd, ee, aa, bb, cc, x[15],  9);
+            HH(cc, dd, ee, aa, bb, x[ 8], 13);
+            HH(bb, cc, dd, ee, aa, x[ 1], 15);
+            HH(aa, bb, cc, dd, ee, x[ 2], 14);
+            HH(ee, aa, bb, cc, dd, x[ 7],  8);
+            HH(dd, ee, aa, bb, cc, x[ 0], 13);
+            HH(cc, dd, ee, aa, bb, x[ 6],  6);
+            HH(bb, cc, dd, ee, aa, x[13],  5);
+            HH(aa, bb, cc, dd, ee, x[11], 12);
+            HH(ee, aa, bb, cc, dd, x[ 5],  7);
+            HH(dd, ee, aa, bb, cc, x[12],  5);
+
+            /* round 4 */
+            II(cc, dd, ee, aa, bb, x[ 1], 11);
+            II(bb, cc, dd, ee, aa, x[ 9], 12);
+            II(aa, bb, cc, dd, ee, x[11], 14);
+            II(ee, aa, bb, cc, dd, x[10], 15);
+            II(dd, ee, aa, bb, cc, x[ 0], 14);
+            II(cc, dd, ee, aa, bb, x[ 8], 15);
+            II(bb, cc, dd, ee, aa, x[12],  9);
+            II(aa, bb, cc, dd, ee, x[ 4],  8);
+            II(ee, aa, bb, cc, dd, x[13],  9);
+            II(dd, ee, aa, bb, cc, x[ 3], 14);
+            II(cc, dd, ee, aa, bb, x[ 7],  5);
+            II(bb, cc, dd, ee, aa, x[15],  6);
+            II(aa, bb, cc, dd, ee, x[14],  8);
+            II(ee, aa, bb, cc, dd, x[ 5],  6);
+            II(dd, ee, aa, bb, cc, x[ 6],  5);
+            II(cc, dd, ee, aa, bb, x[ 2], 12);
+
+            /* round 5 */
+            JJ(bb, cc, dd, ee, aa, x[ 4],  9);
+            JJ(aa, bb, cc, dd, ee, x[ 0], 15);
+            JJ(ee, aa, bb, cc, dd, x[ 5],  5);
+            JJ(dd, ee, aa, bb, cc, x[ 9], 11);
+            JJ(cc, dd, ee, aa, bb, x[ 7],  6);
+            JJ(bb, cc, dd, ee, aa, x[12],  8);
+            JJ(aa, bb, cc, dd, ee, x[ 2], 13);
+            JJ(ee, aa, bb, cc, dd, x[10], 12);
+            JJ(dd, ee, aa, bb, cc, x[14],  5);
+            JJ(cc, dd, ee, aa, bb, x[ 1], 12);
+            JJ(bb, cc, dd, ee, aa, x[ 3], 13);
+            JJ(aa, bb, cc, dd, ee, x[ 8], 14);
+            JJ(ee, aa, bb, cc, dd, x[11], 11);
+            JJ(dd, ee, aa, bb, cc, x[ 6],  8);
+            JJ(cc, dd, ee, aa, bb, x[15],  5);
+            JJ(bb, cc, dd, ee, aa, x[13],  6);
+
+            /* parallel round 1 */
+            JJJ(aaa, bbb, ccc, ddd, eee, x[ 5],  8);
+            JJJ(eee, aaa, bbb, ccc, ddd, x[14],  9);
+            JJJ(ddd, eee, aaa, bbb, ccc, x[ 7],  9);
+            JJJ(ccc, ddd, eee, aaa, bbb, x[ 0], 11);
+            JJJ(bbb, ccc, ddd, eee, aaa, x[ 9], 13);
+            JJJ(aaa, bbb, ccc, ddd, eee, x[ 2], 15);
+            JJJ(eee, aaa, bbb, ccc, ddd, x[11], 15);
+            JJJ(ddd, eee, aaa, bbb, ccc, x[ 4],  5);
+            JJJ(ccc, ddd, eee, aaa, bbb, x[13],  7);
+            JJJ(bbb, ccc, ddd, eee, aaa, x[ 6],  7);
+            JJJ(aaa, bbb, ccc, ddd, eee, x[15],  8);
+            JJJ(eee, aaa, bbb, ccc, ddd, x[ 8], 11);
+            JJJ(ddd, eee, aaa, bbb, ccc, x[ 1], 14);
+            JJJ(ccc, ddd, eee, aaa, bbb, x[10], 14);
+            JJJ(bbb, ccc, ddd, eee, aaa, x[ 3], 12);
+            JJJ(aaa, bbb, ccc, ddd, eee, x[12],  6);
+
+            /* parallel round 2 */
+            III(eee, aaa, bbb, ccc, ddd, x[ 6],  9);
+            III(ddd, eee, aaa, bbb, ccc, x[11], 13);
+            III(ccc, ddd, eee, aaa, bbb, x[ 3], 15);
+            III(bbb, ccc, ddd, eee, aaa, x[ 7],  7);
+            III(aaa, bbb, ccc, ddd, eee, x[ 0], 12);
+            III(eee, aaa, bbb, ccc, ddd, x[13],  8);
+            III(ddd, eee, aaa, bbb, ccc, x[ 5],  9);
+            III(ccc, ddd, eee, aaa, bbb, x[10], 11);
+            III(bbb, ccc, ddd, eee, aaa, x[14],  7);
+            III(aaa, bbb, ccc, ddd, eee, x[15],  7);
+            III(eee, aaa, bbb, ccc, ddd, x[ 8], 12);
+            III(ddd, eee, aaa, bbb, ccc, x[12],  7);
+            III(ccc, ddd, eee, aaa, bbb, x[ 4],  6);
+            III(bbb, ccc, ddd, eee, aaa, x[ 9], 15);
+            III(aaa, bbb, ccc, ddd, eee, x[ 1], 13);
+            III(eee, aaa, bbb, ccc, ddd, x[ 2], 11);
+
+            /* parallel round 3 */
+            HHH(ddd, eee, aaa, bbb, ccc, x[15],  9);
+            HHH(ccc, ddd, eee, aaa, bbb, x[ 5],  7);
+            HHH(bbb, ccc, ddd, eee, aaa, x[ 1], 15);
+            HHH(aaa, bbb, ccc, ddd, eee, x[ 3], 11);
+            HHH(eee, aaa, bbb, ccc, ddd, x[ 7],  8);
+            HHH(ddd, eee, aaa, bbb, ccc, x[14],  6);
+            HHH(ccc, ddd, eee, aaa, bbb, x[ 6],  6);
+            HHH(bbb, ccc, ddd, eee, aaa, x[ 9], 14);
+            HHH(aaa, bbb, ccc, ddd, eee, x[11], 12);
+            HHH(eee, aaa, bbb, ccc, ddd, x[ 8], 13);
+            HHH(ddd, eee, aaa, bbb, ccc, x[12],  5);
+            HHH(ccc, ddd, eee, aaa, bbb, x[ 2], 14);
+            HHH(bbb, ccc, ddd, eee, aaa, x[10], 13);
+            HHH(aaa, bbb, ccc, ddd, eee, x[ 0], 13);
+            HHH(eee, aaa, bbb, ccc, ddd, x[ 4],  7);
+            HHH(ddd, eee, aaa, bbb, ccc, x[13],  5);
+
+            /* parallel round 4 */
+            GGG(ccc, ddd, eee, aaa, bbb, x[ 8], 15);
+            GGG(bbb, ccc, ddd, eee, aaa, x[ 6],  5);
+            GGG(aaa, bbb, ccc, ddd, eee, x[ 4],  8);
+            GGG(eee, aaa, bbb, ccc, ddd, x[ 1], 11);
+            GGG(ddd, eee, aaa, bbb, ccc, x[ 3], 14);
+            GGG(ccc, ddd, eee, aaa, bbb, x[11], 14);
+            GGG(bbb, ccc, ddd, eee, aaa, x[15],  6);
+            GGG(aaa, bbb, ccc, ddd, eee, x[ 0], 14);
+            GGG(eee, aaa, bbb, ccc, ddd, x[ 5],  6);
+            GGG(ddd, eee, aaa, bbb, ccc, x[12],  9);
+            GGG(ccc, ddd, eee, aaa, bbb, x[ 2], 12);
+            GGG(bbb, ccc, ddd, eee, aaa, x[13],  9);
+            GGG(aaa, bbb, ccc, ddd, eee, x[ 9], 12);
+            GGG(eee, aaa, bbb, ccc, ddd, x[ 7],  5);
+            GGG(ddd, eee, aaa, bbb, ccc, x[10], 15);
+            GGG(ccc, ddd, eee, aaa, bbb, x[14],  8);
+
+            /* parallel round 5 */
+            FFF(bbb, ccc, ddd, eee, aaa, x[12] ,  8);
+            FFF(aaa, bbb, ccc, ddd, eee, x[15] ,  5);
+            FFF(eee, aaa, bbb, ccc, ddd, x[10] , 12);
+            FFF(ddd, eee, aaa, bbb, ccc, x[ 4] ,  9);
+            FFF(ccc, ddd, eee, aaa, bbb, x[ 1] , 12);
+            FFF(bbb, ccc, ddd, eee, aaa, x[ 5] ,  5);
+            FFF(aaa, bbb, ccc, ddd, eee, x[ 8] , 14);
+            FFF(eee, aaa, bbb, ccc, ddd, x[ 7] ,  6);
+            FFF(ddd, eee, aaa, bbb, ccc, x[ 6] ,  8);
+            FFF(ccc, ddd, eee, aaa, bbb, x[ 2] , 13);
+            FFF(bbb, ccc, ddd, eee, aaa, x[13] ,  6);
+            FFF(aaa, bbb, ccc, ddd, eee, x[14] ,  5);
+            FFF(eee, aaa, bbb, ccc, ddd, x[ 0] , 15);
+            FFF(ddd, eee, aaa, bbb, ccc, x[ 3] , 13);
+            FFF(ccc, ddd, eee, aaa, bbb, x[ 9] , 11);
+            FFF(bbb, ccc, ddd, eee, aaa, x[11] , 11);
+
+            /* combine results */
+            ddd += cc + _state[1];               /* final result for _state[0] */
+            _state[1] = _state[2] + dd + eee;
+            _state[2] = _state[3] + ee + aaa;
+            _state[3] = _state[4] + aa + bbb;
+            _state[4] = _state[0] + bb + ccc;
+            _state[0] = ddd;
+
+            //Zeroize sensitive information.
+            x[] = 0;
+        }
+
+    public:
+        enum blockSize = 512;
+
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         *
+         * Example:
+         * ----
+         * RIPEMD160 dig;
+         * dig.put(cast(ubyte)0); //single ubyte
+         * dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+         * ubyte[10] buf;
+         * dig.put(buf); //buffer
+         * ----
+         */
+        void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc
+        {
+            uint i, index, partLen;
+            auto inputLen = data.length;
+
+            //Compute number of bytes mod 64
+            index = (cast(uint)_count >> 3) & (64 - 1);
+
+            //Update number of bits
+            _count += inputLen * 8;
+
+            partLen = 64 - index;
+
+            //Transform as many times as possible
+            if (inputLen >= partLen)
+            {
+                (&_buffer[index])[0 .. partLen] = data.ptr[0 .. partLen];
+                transform(&_buffer);
+
+                for(i = partLen; i + 63 < inputLen; i += 64)
+                {
+                    transform(cast(const(ubyte[64])*)(data[i .. i + 64].ptr));
+                }
+
+                index = 0;
+            }
+            else
+            {
+                i = 0;
+            }
+
+            /* Buffer remaining input */
+            if (inputLen - i)
+                (&_buffer[index])[0 .. inputLen-i] = (&data[i])[0 .. inputLen-i];
+        }
+
+        /**
+         * Used to (re)initialize the RIPEMD160 digest.
+         *
+         * Note:
+         * For this RIPEMD160 Digest implementation calling start after default construction
+         * is not necessary. Calling start is only necessary to reset the Digest.
+         *
+         * Generic code which deals with different Digest types should always call start though.
+         *
+         * Example:
+         * --------
+         * RIPEMD160 digest;
+         * //digest.start(); //Not necessary
+         * digest.put(0);
+         * --------
+         */
+        void start() @safe pure nothrow @nogc
+        {
+            this = RIPEMD160.init;
+        }
+
+        /**
+         * Returns the finished RIPEMD160 hash. This also calls $(LREF start) to
+         * reset the internal state.
+         *
+         * Example:
+         * --------
+         * //Simple example
+         * RIPEMD160 hash;
+         * hash.start();
+         * hash.put(cast(ubyte)0);
+         * ubyte[20] result = hash.finish();
+         * assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D");
+         * --------
+         */
+        ubyte[20] finish() @trusted pure nothrow @nogc
+        {
+            import std.bitmanip : nativeToLittleEndian;
+
+            ubyte[20] data = void;
+            ubyte[8] bits = void;
+            uint index, padLen;
+
+            //Save number of bits
+            bits[0 .. 8] = nativeToLittleEndian(_count)[];
+
+            //Pad out to 56 mod 64
+            index = (cast(uint)_count >> 3) & (64 - 1);
+            padLen = (index < 56) ? (56 - index) : (120 - index);
+            put(_padding[0 .. padLen]);
+
+            //Append length (before padding)
+            put(bits);
+
+            //Store state in digest
+            data[0 .. 4]   = nativeToLittleEndian(_state[0])[];
+            data[4 .. 8]   = nativeToLittleEndian(_state[1])[];
+            data[8 .. 12]  = nativeToLittleEndian(_state[2])[];
+            data[12 .. 16] = nativeToLittleEndian(_state[3])[];
+            data[16 .. 20] = nativeToLittleEndian(_state[4])[];
+
+            /* Zeroize sensitive information. */
+            start();
+            return data;
+        }
+}
+
+///
+unittest
+{
+    //Simple example, hashing a string using ripemd160Of helper function
+    ubyte[20] hash = ripemd160Of("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC");
+}
+
+///
+unittest
+{
+    //Using the basic API
+    RIPEMD160 hash;
+    hash.start();
+    ubyte[1024] data;
+    //Initialize data here...
+    hash.put(data);
+    ubyte[20] result = hash.finish();
+}
+
+///
+unittest
+{
+    //Let's use the template features:
+    void doSomething(T)(ref T hash) if(isDigest!T)
+    {
+        hash.put(cast(ubyte)0);
+    }
+    RIPEMD160 md;
+    md.start();
+    doSomething(md);
+    assert(toHexString(md.finish()) == "C81B94933420221A7AC004A90242D8B1D3E5070D");
+}
+
+///
+unittest
+{
+    //Simple example
+    RIPEMD160 hash;
+    hash.start();
+    hash.put(cast(ubyte)0);
+    ubyte[20] result = hash.finish();
+    assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D");
+}
+
+unittest
+{
+    assert(isDigest!RIPEMD160);
+}
+
+unittest
+{
+    import std.range;
+
+    ubyte[20] digest;
+
+    RIPEMD160 md;
+    md.put(cast(ubyte[])"abcdef");
+    md.start();
+    md.put(cast(ubyte[])"");
+    assert(md.finish() == cast(ubyte[])x"9c1185a5c5e9fc54612808977ee8f548b2258d31");
+
+    digest = ripemd160Of("");
+    assert(digest == cast(ubyte[])x"9c1185a5c5e9fc54612808977ee8f548b2258d31");
+
+    digest = ripemd160Of("a");
+    assert(digest == cast(ubyte[])x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe");
+
+    digest = ripemd160Of("abc");
+    assert(digest == cast(ubyte[])x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc");
+
+    digest = ripemd160Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    assert(digest == cast(ubyte[])x"12a053384a9c0c88e405a06c27dcf49ada62eb2b");
+
+    digest = ripemd160Of("message digest");
+    assert(digest == cast(ubyte[])x"5d0689ef49d2fae572b881b123a85ffa21595f36");
+
+    digest = ripemd160Of("abcdefghijklmnopqrstuvwxyz");
+    assert(digest == cast(ubyte[])x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc");
+
+    digest = ripemd160Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    assert(digest == cast(ubyte[])x"b0e20b6e3116640286ed3a87a5713079b21f5189");
+
+    digest = ripemd160Of("1234567890123456789012345678901234567890"~
+                    "1234567890123456789012345678901234567890");
+    assert(digest == cast(ubyte[])x"9b752e45573d4b39f4dbd3323cab82bf63326bfb");
+
+    assert(toHexString(cast(ubyte[20])x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc")
+        == "F71C27109C692C1B56BBDCEB5B9D2865B3708DBC");
+
+    ubyte[] onemilliona = new ubyte[1000000];
+    onemilliona[] = 'a';
+    digest = ripemd160Of(onemilliona);
+    assert(digest == cast(ubyte[])x"52783243c1697bdbe16d37f97f68f08325dc1528");
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    digest = ripemd160Of(oneMillionRange);
+    assert(digest == cast(ubyte[])x"52783243c1697bdbe16d37f97f68f08325dc1528");
+}
+
+/**
+ * This is a convenience alias for $(XREF_PACK digest,digest,digest) using the
+ * RIPEMD160 implementation.
+ */
+//simple alias doesn't work here, hope this gets inlined...
+auto ripemd160Of(T...)(T data)
+{
+    return digest!(RIPEMD160, T)(data);
+}
+
+///
+unittest
+{
+    ubyte[20] hash = ripemd160Of("abc");
+    assert(hash == digest!RIPEMD160("abc"));
+}
+
+/**
+ * OOP API RIPEMD160 implementation.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ *
+ * This is an alias for $(D $(XREF_PACK digest,digest,WrapperDigest)!RIPEMD160),
+ * see there for more information.
+ */
+alias RIPEMD160Digest = WrapperDigest!RIPEMD160;
+
+///
+unittest
+{
+    //Simple example, hashing a string using Digest.digest helper function
+    auto md = new RIPEMD160Digest();
+    ubyte[] hash = md.digest("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC");
+}
+
+///
+unittest
+{
+    //Let's use the OOP features:
+    void test(Digest dig)
+    {
+      dig.put(cast(ubyte)0);
+    }
+    auto md = new RIPEMD160Digest();
+    test(md);
+
+    //Let's use a custom buffer:
+    ubyte[20] buf;
+    ubyte[] result = md.finish(buf[]);
+    assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D");
+}
+
+unittest
+{
+    auto md = new RIPEMD160Digest();
+
+    md.put(cast(ubyte[])"abcdef");
+    md.reset();
+    md.put(cast(ubyte[])"");
+    assert(md.finish() == cast(ubyte[])x"9c1185a5c5e9fc54612808977ee8f548b2258d31");
+
+    md.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    ubyte[20] result;
+    auto result2 = md.finish(result[]);
+    assert(result[0 .. 20] == result2 && result2 == cast(ubyte[])x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc");
+
+    debug
+    {
+        import std.exception;
+        assertThrown!Error(md.finish(result[0 .. 19]));
+    }
+
+    assert(md.length == 20);
+
+    assert(md.digest("") == cast(ubyte[])x"9c1185a5c5e9fc54612808977ee8f548b2258d31");
+
+    assert(md.digest("a") == cast(ubyte[])x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe");
+
+    assert(md.digest("abc") == cast(ubyte[])x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc");
+
+    assert(md.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
+           == cast(ubyte[])x"12a053384a9c0c88e405a06c27dcf49ada62eb2b");
+
+    assert(md.digest("message digest") == cast(ubyte[])x"5d0689ef49d2fae572b881b123a85ffa21595f36");
+
+    assert(md.digest("abcdefghijklmnopqrstuvwxyz")
+           == cast(ubyte[])x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc");
+
+    assert(md.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+           == cast(ubyte[])x"b0e20b6e3116640286ed3a87a5713079b21f5189");
+
+    assert(md.digest("1234567890123456789012345678901234567890",
+                                   "1234567890123456789012345678901234567890")
+           == cast(ubyte[])x"9b752e45573d4b39f4dbd3323cab82bf63326bfb");
+
+    assert(md.digest(new ubyte[160/8]) // 160 zero bits
+           == cast(ubyte[])x"5c00bd4aca04a9057c09b20b05f723f2e23deb65");
+}
diff --git a/libphobos/src/std/digest/sha.d b/libphobos/src/std/digest/sha.d
new file mode 100644
index 00000000000..82dd0b1679d
--- /dev/null
+++ b/libphobos/src/std/digest/sha.d
@@ -0,0 +1,1263 @@
+// Written in the D programming language.
+/**
+ * Computes SHA1 and SHA2 hashes of arbitrary data. SHA hashes are 20 to 64 byte
+ * quantities (depending on the SHA algorithm) that are like a checksum or CRC,
+ * but are more robust.
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Template API) $(TD $(MYREF SHA1)
+)
+)
+$(TR $(TDNW OOP API) $(TD $(MYREF SHA1Digest))
+)
+$(TR $(TDNW Helpers) $(TD $(MYREF sha1Of))
+)
+)
+)
+
+ * SHA2 comes in several different versions, all supported by this module:
+ * SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224 and SHA-512/256.
+ *
+ * This module conforms to the APIs defined in $(D std.digest.digest). To understand the
+ * differences between the template and the OOP API, see $(D std.digest.digest).
+ *
+ * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone
+ * module.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *
+ * CTFE:
+ * Digests do not work in CTFE
+ *
+ * Authors:
+ * The routines and algorithms are derived from the
+ * $(I Secure Hash Signature Standard (SHS) (FIPS PUB 180-2)). $(BR )
+ * Kai Nacke, Johannes Pfau, Nick Sabalausky
+ *
+ * References:
+ * $(UL
+ * $(LI $(LINK2 http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf, FIPS PUB180-2))
+ * $(LI $(LINK2 http://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1/, Fast implementation of SHA1))
+ * $(LI $(LINK2 http://en.wikipedia.org/wiki/Secure_Hash_Algorithm, Wikipedia article about SHA))
+ * )
+ *
+ * Source: $(PHOBOSSRC std/digest/_sha.d)
+ *
+ * Macros:
+ *      WIKI = Phobos/StdSha1
+ */
+
+/*          Copyright Kai Nacke 2012.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.digest.sha;
+
+///
+unittest
+{
+    //Template API
+    import std.digest.sha;
+
+    ubyte[20] hash1 = sha1Of("abc");
+    assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+    ubyte[28] hash224 = sha224Of("abc");
+    assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+
+    //Feeding data
+    ubyte[1024] data;
+    SHA1 sha1;
+    sha1.start();
+    sha1.put(data[]);
+    sha1.start(); //Start again
+    sha1.put(data[]);
+    hash1 = sha1.finish();
+}
+
+///
+unittest
+{
+    //OOP API
+    import std.digest.sha;
+
+    auto sha1 = new SHA1Digest();
+    ubyte[] hash1 = sha1.digest("abc");
+    assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+    auto sha224 = new SHA224Digest();
+    ubyte[] hash224 = sha224.digest("abc");
+    assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+
+    //Feeding data
+    ubyte[1024] data;
+    sha1.put(data[]);
+    sha1.reset(); //Start again
+    sha1.put(data[]);
+    hash1 = sha1.finish();
+}
+
+version(D_PIC)
+{
+    // Do not use (Bug9378).
+}
+else version(Win64)
+{
+    // wrong calling convention
+}
+else version(D_InlineAsm_X86)
+{
+    private version = USE_SSSE3;
+}
+else version(D_InlineAsm_X86_64)
+{
+    private version = USE_SSSE3;
+}
+
+version(LittleEndian) import core.bitop : bswap;
+
+
+version(unittest)
+{
+    import std.exception;
+}
+
+
+public import std.digest.digest;
+
+/*
+ * Helper methods for encoding the buffer.
+ * Can be removed if the optimizer can inline the methods from std.bitmanip.
+ */
+private ubyte[8] nativeToBigEndian(ulong val) @trusted pure nothrow @nogc
+{
+    version(LittleEndian)
+        immutable ulong res = (cast(ulong)  bswap(cast(uint) val)) << 32 | bswap(cast(uint) (val >> 32));
+    else
+        immutable ulong res = val;
+    return *cast(ubyte[8]*) &res;
+}
+
+private ubyte[4] nativeToBigEndian(uint val) @trusted pure nothrow @nogc
+{
+    version(LittleEndian)
+        immutable uint res = bswap(val);
+    else
+        immutable uint res = val;
+    return *cast(ubyte[4]*) &res;
+}
+
+private ulong bigEndianToNative(ubyte[8] val) @trusted pure nothrow @nogc
+{
+    version(LittleEndian)
+    {
+        import std.bitmanip : bigEndianToNative;
+        return bigEndianToNative!ulong(val);
+    }
+    else
+        return *cast(ulong*) &val;
+}
+
+private uint bigEndianToNative(ubyte[4] val) @trusted pure nothrow @nogc
+{
+    version(LittleEndian)
+        return bswap(*cast(uint*) &val);
+    else
+        return *cast(uint*) &val;
+}
+
+//rotateLeft rotates x left n bits
+private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc
+{
+    // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol.
+    // No assembler required.
+    return (x << n) | (x >> (32-n));
+}
+
+//rotateRight rotates x right n bits
+private uint rotateRight(uint x, uint n) @safe pure nothrow @nogc
+{
+    return (x >> n) | (x << (32-n));
+}
+private ulong rotateRight(ulong x, uint n) @safe pure nothrow @nogc
+{
+    return (x >> n) | (x << (64-n));
+}
+
+/**
+ * Template API SHA1/SHA2 implementation. Supports: SHA-1, SHA-224, SHA-256,
+ * SHA-384, SHA-512, SHA-512/224 and SHA-512/256.
+ *
+ * The hashBlockSize and digestSize are in bits. However, it's likely easier to
+ * simply use the convenience aliases: SHA1, SHA224, SHA256, SHA384, SHA512,
+ * SHA512_224 and SHA512_256.
+ *
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ */
+struct SHA(uint hashBlockSize, uint digestSize)
+{
+    enum blockSize = hashBlockSize;
+
+    static assert(blockSize == 512 || blockSize == 1024,
+        "Invalid SHA blockSize, must be 512 or 1024");
+    static assert(digestSize == 160 || digestSize == 224 || digestSize == 256 || digestSize == 384 || digestSize == 512,
+        "Invalid SHA digestSize, must be 224, 256, 384 or 512");
+    static assert(!(blockSize == 512 && digestSize > 256),
+        "Invalid SHA digestSize for a blockSize of 512. The digestSize must be 160, 224 or 256.");
+    static assert(!(blockSize == 1024 && digestSize < 224),
+        "Invalid SHA digestSize for a blockSize of 1024. The digestSize must be 224, 256, 384 or 512.");
+
+    static if(digestSize==160) /* SHA-1 */
+    {
+        version(USE_SSSE3)
+        {
+            import core.cpuid : ssse3;
+            import std.internal.digest.sha_SSSE3 : transformSSSE3;
+
+            static void transform(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc
+            {
+               return ssse3 ? transformSSSE3(state, block) : transformX86(state, block);
+            }
+        }
+        else
+        {
+            alias transform = transformX86;
+        }
+    }
+    else static if(blockSize == 512) /* SHA-224, SHA-256 */
+        alias transform = transformSHA2!uint;
+    else static if(blockSize == 1024) /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */
+        alias transform = transformSHA2!ulong;
+    else
+        static assert(0);
+
+    private:
+        /* magic initialization constants - state (ABCDEFGH) */
+        static if(blockSize == 512 && digestSize == 160) /* SHA-1 */
+        {
+            uint[5] state =
+            [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0];
+        }
+        else static if(blockSize == 512 && digestSize == 224) /* SHA-224 */
+        {
+            uint[8] state = [
+                0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
+                0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
+            ];
+        }
+        else static if(blockSize == 512 && digestSize == 256) /* SHA-256 */
+        {
+            uint[8] state = [
+                0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+                0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
+            ];
+        }
+        else static if(blockSize == 1024 && digestSize == 224) /* SHA-512/224 */
+        {
+            ulong[8] state = [
+                0x8C3D37C8_19544DA2, 0x73E19966_89DCD4D6,
+                0x1DFAB7AE_32FF9C82, 0x679DD514_582F9FCF,
+                0x0F6D2B69_7BD44DA8, 0x77E36F73_04C48942,
+                0x3F9D85A8_6A1D36C8, 0x1112E6AD_91D692A1,
+            ];
+        }
+        else static if(blockSize == 1024 && digestSize == 256) /* SHA-512/256 */
+        {
+            ulong[8] state = [
+                0x22312194_FC2BF72C, 0x9F555FA3_C84C64C2,
+                0x2393B86B_6F53B151, 0x96387719_5940EABD,
+                0x96283EE2_A88EFFE3, 0xBE5E1E25_53863992,
+                0x2B0199FC_2C85B8AA, 0x0EB72DDC_81C52CA2,
+            ];
+        }
+        else static if(blockSize == 1024 && digestSize == 384) /* SHA-384 */
+        {
+            ulong[8] state = [
+                0xcbbb9d5d_c1059ed8, 0x629a292a_367cd507,
+                0x9159015a_3070dd17, 0x152fecd8_f70e5939,
+                0x67332667_ffc00b31, 0x8eb44a87_68581511,
+                0xdb0c2e0d_64f98fa7, 0x47b5481d_befa4fa4,
+            ];
+        }
+        else static if(blockSize == 1024 && digestSize == 512) /* SHA-512 */
+        {
+            ulong[8] state = [
+                0x6a09e667_f3bcc908, 0xbb67ae85_84caa73b,
+                0x3c6ef372_fe94f82b, 0xa54ff53a_5f1d36f1,
+                0x510e527f_ade682d1, 0x9b05688c_2b3e6c1f,
+                0x1f83d9ab_fb41bd6b, 0x5be0cd19_137e2179,
+            ];
+        }
+        else
+            static assert(0);
+
+        /* constants */
+        static if(blockSize == 512)
+        {
+            static immutable uint[64] constants = [
+                0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+                0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+                0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+                0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+                0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+                0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+                0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+                0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+            ];
+        }
+        else static if(blockSize == 1024)
+        {
+            static immutable ulong[80] constants = [
+                0x428a2f98_d728ae22, 0x71374491_23ef65cd, 0xb5c0fbcf_ec4d3b2f, 0xe9b5dba5_8189dbbc,
+                0x3956c25b_f348b538, 0x59f111f1_b605d019, 0x923f82a4_af194f9b, 0xab1c5ed5_da6d8118,
+                0xd807aa98_a3030242, 0x12835b01_45706fbe, 0x243185be_4ee4b28c, 0x550c7dc3_d5ffb4e2,
+                0x72be5d74_f27b896f, 0x80deb1fe_3b1696b1, 0x9bdc06a7_25c71235, 0xc19bf174_cf692694,
+                0xe49b69c1_9ef14ad2, 0xefbe4786_384f25e3, 0x0fc19dc6_8b8cd5b5, 0x240ca1cc_77ac9c65,
+                0x2de92c6f_592b0275, 0x4a7484aa_6ea6e483, 0x5cb0a9dc_bd41fbd4, 0x76f988da_831153b5,
+                0x983e5152_ee66dfab, 0xa831c66d_2db43210, 0xb00327c8_98fb213f, 0xbf597fc7_beef0ee4,
+                0xc6e00bf3_3da88fc2, 0xd5a79147_930aa725, 0x06ca6351_e003826f, 0x14292967_0a0e6e70,
+                0x27b70a85_46d22ffc, 0x2e1b2138_5c26c926, 0x4d2c6dfc_5ac42aed, 0x53380d13_9d95b3df,
+                0x650a7354_8baf63de, 0x766a0abb_3c77b2a8, 0x81c2c92e_47edaee6, 0x92722c85_1482353b,
+                0xa2bfe8a1_4cf10364, 0xa81a664b_bc423001, 0xc24b8b70_d0f89791, 0xc76c51a3_0654be30,
+                0xd192e819_d6ef5218, 0xd6990624_5565a910, 0xf40e3585_5771202a, 0x106aa070_32bbd1b8,
+                0x19a4c116_b8d2d0c8, 0x1e376c08_5141ab53, 0x2748774c_df8eeb99, 0x34b0bcb5_e19b48a8,
+                0x391c0cb3_c5c95a63, 0x4ed8aa4a_e3418acb, 0x5b9cca4f_7763e373, 0x682e6ff3_d6b2b8a3,
+                0x748f82ee_5defb2fc, 0x78a5636f_43172f60, 0x84c87814_a1f0ab72, 0x8cc70208_1a6439ec,
+                0x90befffa_23631e28, 0xa4506ceb_de82bde9, 0xbef9a3f7_b2c67915, 0xc67178f2_e372532b,
+                0xca273ece_ea26619c, 0xd186b8c7_21c0c207, 0xeada7dd6_cde0eb1e, 0xf57d4f7f_ee6ed178,
+                0x06f067aa_72176fba, 0x0a637dc5_a2c898a6, 0x113f9804_bef90dae, 0x1b710b35_131c471b,
+                0x28db77f5_23047d84, 0x32caab7b_40c72493, 0x3c9ebe0a_15c9bebc, 0x431d67c4_9c100d4c,
+                0x4cc5d4be_cb3e42b6, 0x597f299c_fc657e2a, 0x5fcb6fab_3ad6faec, 0x6c44198c_4a475817,
+            ];
+        }
+        else
+            static assert(0);
+
+        /*
+         * number of bits, modulo 2^64 (ulong[1]) or 2^128 (ulong[2]),
+         * should just use ucent instead of ulong[2] once it's available
+         */
+        ulong[blockSize/512] count;
+        ubyte[blockSize/8]   buffer; /* input buffer */
+
+        static immutable ubyte[128] padding =
+        [
+          0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+          0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+        ];
+
+        /*
+         * Basic SHA1/SHA2 functions.
+         */
+        static @safe pure nothrow @nogc
+        {
+            /* All SHA1/SHA2 */
+            T Ch(T)(T x, T y, T z) { return z ^ (x & (y ^ z)); }
+            T Maj(T)(T x, T y, T z) { return (x & y) | (z & (x ^ y)); }
+
+            /* SHA-1 */
+            uint Parity(uint x, uint y, uint z) { return x ^ y ^ z; }
+
+            /* SHA-224, SHA-256 */
+            uint BigSigma0(uint x) { return rotateRight(x, 2) ^ rotateRight(x, 13) ^ rotateRight(x, 22); }
+            uint BigSigma1(uint x) { return rotateRight(x, 6) ^ rotateRight(x, 11) ^ rotateRight(x, 25); }
+            uint SmSigma0(uint x) { return rotateRight(x, 7) ^ rotateRight(x, 18) ^ x >> 3; }
+            uint SmSigma1(uint x) { return rotateRight(x, 17) ^ rotateRight(x, 19) ^ x >> 10; }
+
+            /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */
+            ulong BigSigma0(ulong x) { return rotateRight(x, 28) ^ rotateRight(x, 34) ^ rotateRight(x, 39); }
+            ulong BigSigma1(ulong x) { return rotateRight(x, 14) ^ rotateRight(x, 18) ^ rotateRight(x, 41); }
+            ulong SmSigma0(ulong x) { return rotateRight(x, 1) ^ rotateRight(x, 8) ^ x >> 7; }
+            ulong SmSigma1(ulong x) { return rotateRight(x, 19) ^ rotateRight(x, 61) ^ x >> 6; }
+        }
+
+        /*
+         * SHA1 basic transformation. Transforms state based on block.
+         */
+        static void T_0_15(int i, const(ubyte[64])* input, ref uint[16] W, uint A, ref uint B, uint C, uint D,
+            uint E, ref uint T) pure nothrow @nogc
+        {
+            uint Wi = W[i] = bigEndianToNative(*cast(ubyte[4]*)&((*input)[i*4]));
+            T = Ch(B, C, D) + E + rotateLeft(A, 5) + Wi + 0x5a827999;
+            B = rotateLeft(B, 30);
+        }
+
+        static void T_16_19(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, ref uint T)
+            pure nothrow @nogc
+        {
+            W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
+            T = Ch(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x5a827999;
+            B = rotateLeft(B, 30);
+        }
+
+        static void T_20_39(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
+            ref uint T) pure nothrow @nogc
+        {
+            W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
+            T = Parity(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x6ed9eba1;
+            B = rotateLeft(B, 30);
+        }
+
+        static void T_40_59(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
+            ref uint T) pure nothrow @nogc
+        {
+            W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
+            T = Maj(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x8f1bbcdc;
+            B = rotateLeft(B, 30);
+        }
+
+        static void T_60_79(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E,
+            ref uint T) pure nothrow @nogc
+        {
+            W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1);
+            T = Parity(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0xca62c1d6;
+            B = rotateLeft(B, 30);
+        }
+
+        private static void transformX86(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc
+        {
+            uint A, B, C, D, E, T;
+            uint[16] W = void;
+
+            A = (*state)[0];
+            B = (*state)[1];
+            C = (*state)[2];
+            D = (*state)[3];
+            E = (*state)[4];
+
+            T_0_15 ( 0, block, W, A, B, C, D, E, T);
+            T_0_15 ( 1, block, W, T, A, B, C, D, E);
+            T_0_15 ( 2, block, W, E, T, A, B, C, D);
+            T_0_15 ( 3, block, W, D, E, T, A, B, C);
+            T_0_15 ( 4, block, W, C, D, E, T, A, B);
+            T_0_15 ( 5, block, W, B, C, D, E, T, A);
+            T_0_15 ( 6, block, W, A, B, C, D, E, T);
+            T_0_15 ( 7, block, W, T, A, B, C, D, E);
+            T_0_15 ( 8, block, W, E, T, A, B, C, D);
+            T_0_15 ( 9, block, W, D, E, T, A, B, C);
+            T_0_15 (10, block, W, C, D, E, T, A, B);
+            T_0_15 (11, block, W, B, C, D, E, T, A);
+            T_0_15 (12, block, W, A, B, C, D, E, T);
+            T_0_15 (13, block, W, T, A, B, C, D, E);
+            T_0_15 (14, block, W, E, T, A, B, C, D);
+            T_0_15 (15, block, W, D, E, T, A, B, C);
+            T_16_19(16, W, C, D, E, T, A, B);
+            T_16_19(17, W, B, C, D, E, T, A);
+            T_16_19(18, W, A, B, C, D, E, T);
+            T_16_19(19, W, T, A, B, C, D, E);
+            T_20_39(20, W, E, T, A, B, C, D);
+            T_20_39(21, W, D, E, T, A, B, C);
+            T_20_39(22, W, C, D, E, T, A, B);
+            T_20_39(23, W, B, C, D, E, T, A);
+            T_20_39(24, W, A, B, C, D, E, T);
+            T_20_39(25, W, T, A, B, C, D, E);
+            T_20_39(26, W, E, T, A, B, C, D);
+            T_20_39(27, W, D, E, T, A, B, C);
+            T_20_39(28, W, C, D, E, T, A, B);
+            T_20_39(29, W, B, C, D, E, T, A);
+            T_20_39(30, W, A, B, C, D, E, T);
+            T_20_39(31, W, T, A, B, C, D, E);
+            T_20_39(32, W, E, T, A, B, C, D);
+            T_20_39(33, W, D, E, T, A, B, C);
+            T_20_39(34, W, C, D, E, T, A, B);
+            T_20_39(35, W, B, C, D, E, T, A);
+            T_20_39(36, W, A, B, C, D, E, T);
+            T_20_39(37, W, T, A, B, C, D, E);
+            T_20_39(38, W, E, T, A, B, C, D);
+            T_20_39(39, W, D, E, T, A, B, C);
+            T_40_59(40, W, C, D, E, T, A, B);
+            T_40_59(41, W, B, C, D, E, T, A);
+            T_40_59(42, W, A, B, C, D, E, T);
+            T_40_59(43, W, T, A, B, C, D, E);
+            T_40_59(44, W, E, T, A, B, C, D);
+            T_40_59(45, W, D, E, T, A, B, C);
+            T_40_59(46, W, C, D, E, T, A, B);
+            T_40_59(47, W, B, C, D, E, T, A);
+            T_40_59(48, W, A, B, C, D, E, T);
+            T_40_59(49, W, T, A, B, C, D, E);
+            T_40_59(50, W, E, T, A, B, C, D);
+            T_40_59(51, W, D, E, T, A, B, C);
+            T_40_59(52, W, C, D, E, T, A, B);
+            T_40_59(53, W, B, C, D, E, T, A);
+            T_40_59(54, W, A, B, C, D, E, T);
+            T_40_59(55, W, T, A, B, C, D, E);
+            T_40_59(56, W, E, T, A, B, C, D);
+            T_40_59(57, W, D, E, T, A, B, C);
+            T_40_59(58, W, C, D, E, T, A, B);
+            T_40_59(59, W, B, C, D, E, T, A);
+            T_60_79(60, W, A, B, C, D, E, T);
+            T_60_79(61, W, T, A, B, C, D, E);
+            T_60_79(62, W, E, T, A, B, C, D);
+            T_60_79(63, W, D, E, T, A, B, C);
+            T_60_79(64, W, C, D, E, T, A, B);
+            T_60_79(65, W, B, C, D, E, T, A);
+            T_60_79(66, W, A, B, C, D, E, T);
+            T_60_79(67, W, T, A, B, C, D, E);
+            T_60_79(68, W, E, T, A, B, C, D);
+            T_60_79(69, W, D, E, T, A, B, C);
+            T_60_79(70, W, C, D, E, T, A, B);
+            T_60_79(71, W, B, C, D, E, T, A);
+            T_60_79(72, W, A, B, C, D, E, T);
+            T_60_79(73, W, T, A, B, C, D, E);
+            T_60_79(74, W, E, T, A, B, C, D);
+            T_60_79(75, W, D, E, T, A, B, C);
+            T_60_79(76, W, C, D, E, T, A, B);
+            T_60_79(77, W, B, C, D, E, T, A);
+            T_60_79(78, W, A, B, C, D, E, T);
+            T_60_79(79, W, T, A, B, C, D, E);
+
+            (*state)[0] += E;
+            (*state)[1] += T;
+            (*state)[2] += A;
+            (*state)[3] += B;
+            (*state)[4] += C;
+
+            /* Zeroize sensitive information. */
+            W[] = 0;
+        }
+
+        /*
+         * SHA2 basic transformation. Transforms state based on block.
+         */
+        static void T_SHA2_0_15(Word)(int i, const(ubyte[blockSize/8])* input, ref Word[16] W,
+            Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K)
+            pure nothrow @nogc
+        {
+            Word Wi = W[i] = bigEndianToNative(*cast(ubyte[Word.sizeof]*)&((*input)[i*Word.sizeof]));
+            Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + Wi;
+            Word T2 = BigSigma0(A) + Maj(A, B, C);
+            D += T1;
+            H = T1 + T2;
+        }
+
+        static void T_SHA2_16_79(Word)(int i, ref Word[16] W,
+            Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K)
+            pure nothrow @nogc
+        {
+            W[i&15] = SmSigma1(W[(i-2)&15]) + W[(i-7)&15] + SmSigma0(W[(i-15)&15]) + W[i&15];
+            Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + W[i&15];
+            Word T2 = BigSigma0(A) + Maj(A, B, C);
+            D += T1;
+            H = T1 + T2;
+        }
+
+        private static void transformSHA2(Word)(Word[8]* state, const(ubyte[blockSize/8])* block)
+            pure nothrow @nogc
+        {
+            Word A, B, C, D, E, F, G, H;
+            Word[16] W = void;
+
+            A = (*state)[0];
+            B = (*state)[1];
+            C = (*state)[2];
+            D = (*state)[3];
+            E = (*state)[4];
+            F = (*state)[5];
+            G = (*state)[6];
+            H = (*state)[7];
+
+            T_SHA2_0_15!Word ( 0, block, W, A, B, C, D, E, F, G, H, constants[ 0]);
+            T_SHA2_0_15!Word ( 1, block, W, H, A, B, C, D, E, F, G, constants[ 1]);
+            T_SHA2_0_15!Word ( 2, block, W, G, H, A, B, C, D, E, F, constants[ 2]);
+            T_SHA2_0_15!Word ( 3, block, W, F, G, H, A, B, C, D, E, constants[ 3]);
+            T_SHA2_0_15!Word ( 4, block, W, E, F, G, H, A, B, C, D, constants[ 4]);
+            T_SHA2_0_15!Word ( 5, block, W, D, E, F, G, H, A, B, C, constants[ 5]);
+            T_SHA2_0_15!Word ( 6, block, W, C, D, E, F, G, H, A, B, constants[ 6]);
+            T_SHA2_0_15!Word ( 7, block, W, B, C, D, E, F, G, H, A, constants[ 7]);
+            T_SHA2_0_15!Word ( 8, block, W, A, B, C, D, E, F, G, H, constants[ 8]);
+            T_SHA2_0_15!Word ( 9, block, W, H, A, B, C, D, E, F, G, constants[ 9]);
+            T_SHA2_0_15!Word (10, block, W, G, H, A, B, C, D, E, F, constants[10]);
+            T_SHA2_0_15!Word (11, block, W, F, G, H, A, B, C, D, E, constants[11]);
+            T_SHA2_0_15!Word (12, block, W, E, F, G, H, A, B, C, D, constants[12]);
+            T_SHA2_0_15!Word (13, block, W, D, E, F, G, H, A, B, C, constants[13]);
+            T_SHA2_0_15!Word (14, block, W, C, D, E, F, G, H, A, B, constants[14]);
+            T_SHA2_0_15!Word (15, block, W, B, C, D, E, F, G, H, A, constants[15]);
+            T_SHA2_16_79!Word(16, W, A, B, C, D, E, F, G, H, constants[16]);
+            T_SHA2_16_79!Word(17, W, H, A, B, C, D, E, F, G, constants[17]);
+            T_SHA2_16_79!Word(18, W, G, H, A, B, C, D, E, F, constants[18]);
+            T_SHA2_16_79!Word(19, W, F, G, H, A, B, C, D, E, constants[19]);
+            T_SHA2_16_79!Word(20, W, E, F, G, H, A, B, C, D, constants[20]);
+            T_SHA2_16_79!Word(21, W, D, E, F, G, H, A, B, C, constants[21]);
+            T_SHA2_16_79!Word(22, W, C, D, E, F, G, H, A, B, constants[22]);
+            T_SHA2_16_79!Word(23, W, B, C, D, E, F, G, H, A, constants[23]);
+            T_SHA2_16_79!Word(24, W, A, B, C, D, E, F, G, H, constants[24]);
+            T_SHA2_16_79!Word(25, W, H, A, B, C, D, E, F, G, constants[25]);
+            T_SHA2_16_79!Word(26, W, G, H, A, B, C, D, E, F, constants[26]);
+            T_SHA2_16_79!Word(27, W, F, G, H, A, B, C, D, E, constants[27]);
+            T_SHA2_16_79!Word(28, W, E, F, G, H, A, B, C, D, constants[28]);
+            T_SHA2_16_79!Word(29, W, D, E, F, G, H, A, B, C, constants[29]);
+            T_SHA2_16_79!Word(30, W, C, D, E, F, G, H, A, B, constants[30]);
+            T_SHA2_16_79!Word(31, W, B, C, D, E, F, G, H, A, constants[31]);
+            T_SHA2_16_79!Word(32, W, A, B, C, D, E, F, G, H, constants[32]);
+            T_SHA2_16_79!Word(33, W, H, A, B, C, D, E, F, G, constants[33]);
+            T_SHA2_16_79!Word(34, W, G, H, A, B, C, D, E, F, constants[34]);
+            T_SHA2_16_79!Word(35, W, F, G, H, A, B, C, D, E, constants[35]);
+            T_SHA2_16_79!Word(36, W, E, F, G, H, A, B, C, D, constants[36]);
+            T_SHA2_16_79!Word(37, W, D, E, F, G, H, A, B, C, constants[37]);
+            T_SHA2_16_79!Word(38, W, C, D, E, F, G, H, A, B, constants[38]);
+            T_SHA2_16_79!Word(39, W, B, C, D, E, F, G, H, A, constants[39]);
+            T_SHA2_16_79!Word(40, W, A, B, C, D, E, F, G, H, constants[40]);
+            T_SHA2_16_79!Word(41, W, H, A, B, C, D, E, F, G, constants[41]);
+            T_SHA2_16_79!Word(42, W, G, H, A, B, C, D, E, F, constants[42]);
+            T_SHA2_16_79!Word(43, W, F, G, H, A, B, C, D, E, constants[43]);
+            T_SHA2_16_79!Word(44, W, E, F, G, H, A, B, C, D, constants[44]);
+            T_SHA2_16_79!Word(45, W, D, E, F, G, H, A, B, C, constants[45]);
+            T_SHA2_16_79!Word(46, W, C, D, E, F, G, H, A, B, constants[46]);
+            T_SHA2_16_79!Word(47, W, B, C, D, E, F, G, H, A, constants[47]);
+            T_SHA2_16_79!Word(48, W, A, B, C, D, E, F, G, H, constants[48]);
+            T_SHA2_16_79!Word(49, W, H, A, B, C, D, E, F, G, constants[49]);
+            T_SHA2_16_79!Word(50, W, G, H, A, B, C, D, E, F, constants[50]);
+            T_SHA2_16_79!Word(51, W, F, G, H, A, B, C, D, E, constants[51]);
+            T_SHA2_16_79!Word(52, W, E, F, G, H, A, B, C, D, constants[52]);
+            T_SHA2_16_79!Word(53, W, D, E, F, G, H, A, B, C, constants[53]);
+            T_SHA2_16_79!Word(54, W, C, D, E, F, G, H, A, B, constants[54]);
+            T_SHA2_16_79!Word(55, W, B, C, D, E, F, G, H, A, constants[55]);
+            T_SHA2_16_79!Word(56, W, A, B, C, D, E, F, G, H, constants[56]);
+            T_SHA2_16_79!Word(57, W, H, A, B, C, D, E, F, G, constants[57]);
+            T_SHA2_16_79!Word(58, W, G, H, A, B, C, D, E, F, constants[58]);
+            T_SHA2_16_79!Word(59, W, F, G, H, A, B, C, D, E, constants[59]);
+            T_SHA2_16_79!Word(60, W, E, F, G, H, A, B, C, D, constants[60]);
+            T_SHA2_16_79!Word(61, W, D, E, F, G, H, A, B, C, constants[61]);
+            T_SHA2_16_79!Word(62, W, C, D, E, F, G, H, A, B, constants[62]);
+            T_SHA2_16_79!Word(63, W, B, C, D, E, F, G, H, A, constants[63]);
+
+            static if(is(Word==ulong))
+            {
+                T_SHA2_16_79!Word(64, W, A, B, C, D, E, F, G, H, constants[64]);
+                T_SHA2_16_79!Word(65, W, H, A, B, C, D, E, F, G, constants[65]);
+                T_SHA2_16_79!Word(66, W, G, H, A, B, C, D, E, F, constants[66]);
+                T_SHA2_16_79!Word(67, W, F, G, H, A, B, C, D, E, constants[67]);
+                T_SHA2_16_79!Word(68, W, E, F, G, H, A, B, C, D, constants[68]);
+                T_SHA2_16_79!Word(69, W, D, E, F, G, H, A, B, C, constants[69]);
+                T_SHA2_16_79!Word(70, W, C, D, E, F, G, H, A, B, constants[70]);
+                T_SHA2_16_79!Word(71, W, B, C, D, E, F, G, H, A, constants[71]);
+                T_SHA2_16_79!Word(72, W, A, B, C, D, E, F, G, H, constants[72]);
+                T_SHA2_16_79!Word(73, W, H, A, B, C, D, E, F, G, constants[73]);
+                T_SHA2_16_79!Word(74, W, G, H, A, B, C, D, E, F, constants[74]);
+                T_SHA2_16_79!Word(75, W, F, G, H, A, B, C, D, E, constants[75]);
+                T_SHA2_16_79!Word(76, W, E, F, G, H, A, B, C, D, constants[76]);
+                T_SHA2_16_79!Word(77, W, D, E, F, G, H, A, B, C, constants[77]);
+                T_SHA2_16_79!Word(78, W, C, D, E, F, G, H, A, B, constants[78]);
+                T_SHA2_16_79!Word(79, W, B, C, D, E, F, G, H, A, constants[79]);
+            }
+
+            (*state)[0] += A;
+            (*state)[1] += B;
+            (*state)[2] += C;
+            (*state)[3] += D;
+            (*state)[4] += E;
+            (*state)[5] += F;
+            (*state)[6] += G;
+            (*state)[7] += H;
+
+            /* Zeroize sensitive information. */
+            W[] = 0;
+        }
+
+    public:
+        /**
+         * SHA initialization. Begins an SHA1/SHA2 operation.
+         *
+         * Note:
+         * For this SHA Digest implementation calling start after default construction
+         * is not necessary. Calling start is only necessary to reset the Digest.
+         *
+         * Generic code which deals with different Digest types should always call start though.
+         *
+         * Example:
+         * --------
+         * SHA1 digest;
+         * //digest.start(); //Not necessary
+         * digest.put(0);
+         * --------
+         */
+        void start() @safe pure nothrow @nogc
+        {
+            this = typeof(this).init;
+        }
+
+        /**
+         * Use this to feed the digest with data.
+         * Also implements the $(XREF_PACK range,primitives,isOutputRange)
+         * interface for $(D ubyte) and $(D const(ubyte)[]).
+         */
+        void put(scope const(ubyte)[] input...) @trusted pure nothrow @nogc
+        {
+            enum blockSizeInBytes = blockSize/8;
+            uint i, index, partLen;
+            auto inputLen = input.length;
+
+            /* Compute number of bytes mod block size (64 or 128 bytes) */
+            index = (cast(uint)count[0] >> 3) & (blockSizeInBytes - 1);
+
+            /* Update number of bits */
+            static if(blockSize==512)
+                count[0] += inputLen * 8;
+            else static if(blockSize==1024)
+            {
+                /* ugly hack to work around lack of ucent */
+                auto oldCount0 = count[0];
+                count[0] += inputLen * 8;
+                if(count[0] < oldCount0)
+                    count[1]++;
+            }
+            else
+                static assert(0);
+
+            partLen = blockSizeInBytes - index;
+
+            /* Transform as many times as possible. */
+            if (inputLen >= partLen)
+            {
+                (&buffer[index])[0 .. partLen] = input.ptr[0 .. partLen];
+                transform (&state, &buffer);
+
+                for (i = partLen; i + blockSizeInBytes-1 < inputLen; i += blockSizeInBytes)
+                   transform(&state, cast(ubyte[blockSizeInBytes]*)(input.ptr + i));
+
+                index = 0;
+            }
+            else
+                i = 0;
+
+            /* Buffer remaining input */
+            if (inputLen - i)
+                (&buffer[index])[0 .. inputLen-i] = (&input[i])[0 .. inputLen-i];
+        }
+        ///
+        unittest
+        {
+            typeof(this) dig;
+            dig.put(cast(ubyte)0); //single ubyte
+            dig.put(cast(ubyte)0, cast(ubyte)0); //variadic
+            ubyte[10] buf;
+            dig.put(buf); //buffer
+        }
+
+
+        /**
+         * Returns the finished SHA hash. This also calls $(LREF start) to
+         * reset the internal state.
+         */
+        ubyte[digestSize/8] finish() @trusted pure nothrow @nogc
+        {
+            static if(blockSize==512)
+            {
+                ubyte[32] data = void;
+                uint index, padLen;
+
+                /* Save number of bits */
+                ubyte[8] bits = nativeToBigEndian(count[0]);
+
+                /* Pad out to 56 mod 64. */
+                index = (cast(uint)count[0] >> 3) & (64 - 1);
+                padLen = (index < 56) ? (56 - index) : (120 - index);
+                put(padding[0 .. padLen]);
+
+                /* Append length (before padding) */
+                put(bits);
+
+                /* Store state in digest */
+                for (auto i = 0; i < ((digestSize == 160)? 5 : 8); i++)
+                    data[i*4..(i+1)*4] = nativeToBigEndian(state[i])[];
+
+                /* Zeroize sensitive information. */
+                start();
+                return data[0..digestSize/8];
+            }
+            else static if(blockSize==1024)
+            {
+                ubyte[64] data = void;
+                uint index, padLen;
+
+                /* Save number of bits */
+                ubyte[16] bits;
+                bits[ 0..8] = nativeToBigEndian(count[1]);
+                bits[8..16] = nativeToBigEndian(count[0]);
+
+                /* Pad out to 112 mod 128. */
+                index = (cast(uint)count[0] >> 3) & (128 - 1);
+                padLen = (index < 112) ? (112 - index) : (240 - index);
+                put(padding[0 .. padLen]);
+
+                /* Append length (before padding) */
+                put(bits);
+
+                /* Store state in digest */
+                for (auto i = 0; i < 8; i++)
+                    data[i*8..(i+1)*8] = nativeToBigEndian(state[i])[];
+
+                /* Zeroize sensitive information. */
+                start();
+                return data[0..digestSize/8];
+            }
+            else
+                static assert(0);
+        }
+        ///
+        unittest
+        {
+            //Simple example
+            SHA1 hash;
+            hash.start();
+            hash.put(cast(ubyte)0);
+            ubyte[20] result = hash.finish();
+        }
+}
+
+alias SHA1 = SHA!(512, 160);  /// SHA alias for SHA-1, hash is ubyte[20]
+alias SHA224 = SHA!(512, 224);  /// SHA alias for SHA-224, hash is ubyte[28]
+alias SHA256 = SHA!(512, 256);  /// SHA alias for SHA-256, hash is ubyte[32]
+alias SHA384 = SHA!(1024, 384); /// SHA alias for SHA-384, hash is ubyte[48]
+alias SHA512 = SHA!(1024, 512); /// SHA alias for SHA-512, hash is ubyte[64]
+alias SHA512_224 = SHA!(1024, 224); /// SHA alias for SHA-512/224, hash is ubyte[28]
+alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte[32]
+
+///
+unittest
+{
+    //Simple example, hashing a string using sha1Of helper function
+    ubyte[20] hash = sha1Of("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+    //The same, but using SHA-224
+    ubyte[28] hash224 = sha224Of("abc");
+    assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+}
+
+///
+unittest
+{
+    //Using the basic API
+    SHA1 hash;
+    hash.start();
+    ubyte[1024] data;
+    //Initialize data here...
+    hash.put(data);
+    ubyte[20] result = hash.finish();
+}
+
+///
+unittest
+{
+    //Let's use the template features:
+    //Note: When passing a SHA1 to a function, it must be passed by reference!
+    void doSomething(T)(ref T hash) if(isDigest!T)
+    {
+      hash.put(cast(ubyte)0);
+    }
+    SHA1 sha;
+    sha.start();
+    doSomething(sha);
+    assert(toHexString(sha.finish()) == "5BA93C9DB0CFF93F52B521D7420E43F6EDA2784F");
+}
+
+unittest
+{
+    assert(isDigest!SHA1);
+    assert(isDigest!SHA224);
+    assert(isDigest!SHA256);
+    assert(isDigest!SHA384);
+    assert(isDigest!SHA512);
+    assert(isDigest!SHA512_224);
+    assert(isDigest!SHA512_256);
+}
+
+unittest
+{
+    import std.range;
+
+    ubyte[20] digest;
+    ubyte[28] digest224;
+    ubyte[32] digest256;
+    ubyte[48] digest384;
+    ubyte[64] digest512;
+    ubyte[28] digest512_224;
+    ubyte[32] digest512_256;
+
+    SHA1 sha;
+    sha.put(cast(ubyte[])"abcdef");
+    sha.start();
+    sha.put(cast(ubyte[])"");
+    assert(sha.finish() == cast(ubyte[])x"da39a3ee5e6b4b0d3255bfef95601890afd80709");
+
+    SHA224 sha224;
+    sha224.put(cast(ubyte[])"abcdef");
+    sha224.start();
+    sha224.put(cast(ubyte[])"");
+    assert(sha224.finish() == cast(ubyte[])x"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f");
+
+    SHA256 sha256;
+    sha256.put(cast(ubyte[])"abcdef");
+    sha256.start();
+    sha256.put(cast(ubyte[])"");
+    assert(sha256.finish() == cast(ubyte[])x"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+
+    SHA384 sha384;
+    sha384.put(cast(ubyte[])"abcdef");
+    sha384.start();
+    sha384.put(cast(ubyte[])"");
+    assert(sha384.finish() == cast(ubyte[])x"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b");
+
+    SHA512 sha512;
+    sha512.put(cast(ubyte[])"abcdef");
+    sha512.start();
+    sha512.put(cast(ubyte[])"");
+    assert(sha512.finish() == cast(ubyte[])x"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
+
+    SHA512_224 sha512_224;
+    sha512_224.put(cast(ubyte[])"abcdef");
+    sha512_224.start();
+    sha512_224.put(cast(ubyte[])"");
+    assert(sha512_224.finish() == cast(ubyte[])x"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4");
+
+    SHA512_256 sha512_256;
+    sha512_256.put(cast(ubyte[])"abcdef");
+    sha512_256.start();
+    sha512_256.put(cast(ubyte[])"");
+    assert(sha512_256.finish() == cast(ubyte[])x"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a");
+
+    digest        = sha1Of      ("");
+    digest224     = sha224Of    ("");
+    digest256     = sha256Of    ("");
+    digest384     = sha384Of    ("");
+    digest512     = sha512Of    ("");
+    digest512_224 = sha512_224Of("");
+    digest512_256 = sha512_256Of("");
+    assert(digest == cast(ubyte[])x"da39a3ee5e6b4b0d3255bfef95601890afd80709");
+    assert(digest224 == cast(ubyte[])x"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f");
+    assert(digest256 == cast(ubyte[])x"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+    assert(digest384 == cast(ubyte[])x"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b");
+    assert(digest512 == cast(ubyte[])x"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
+    assert(digest512_224 == cast(ubyte[])x"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4");
+    assert(digest512_256 == cast(ubyte[])x"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a");
+
+    digest        = sha1Of      ("a");
+    digest224     = sha224Of    ("a");
+    digest256     = sha256Of    ("a");
+    digest384     = sha384Of    ("a");
+    digest512     = sha512Of    ("a");
+    digest512_224 = sha512_224Of("a");
+    digest512_256 = sha512_256Of("a");
+    assert(digest == cast(ubyte[])x"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8");
+    assert(digest224 == cast(ubyte[])x"abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5");
+    assert(digest256 == cast(ubyte[])x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb");
+    assert(digest384 == cast(ubyte[])x"54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31");
+    assert(digest512 == cast(ubyte[])x"1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75");
+    assert(digest512_224 == cast(ubyte[])x"d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327");
+    assert(digest512_256 == cast(ubyte[])x"455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8");
+
+    digest        = sha1Of      ("abc");
+    digest224     = sha224Of    ("abc");
+    digest256     = sha256Of    ("abc");
+    digest384     = sha384Of    ("abc");
+    digest512     = sha512Of    ("abc");
+    digest512_224 = sha512_224Of("abc");
+    digest512_256 = sha512_256Of("abc");
+    assert(digest == cast(ubyte[])x"a9993e364706816aba3e25717850c26c9cd0d89d");
+    assert(digest224 == cast(ubyte[])x"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7");
+    assert(digest256 == cast(ubyte[])x"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
+    assert(digest384 == cast(ubyte[])x"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");
+    assert(digest512 == cast(ubyte[])x"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");
+    assert(digest512_224 == cast(ubyte[])x"4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa");
+    assert(digest512_256 == cast(ubyte[])x"53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23");
+
+    digest        = sha1Of      ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest224     = sha224Of    ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest256     = sha256Of    ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest384     = sha384Of    ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest512     = sha512Of    ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest512_224 = sha512_224Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    digest512_256 = sha512_256Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    assert(digest == cast(ubyte[])x"84983e441c3bd26ebaae4aa1f95129e5e54670f1");
+    assert(digest224 == cast(ubyte[])x"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525");
+    assert(digest256 == cast(ubyte[])x"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
+    assert(digest384 == cast(ubyte[])x"3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b");
+    assert(digest512 == cast(ubyte[])x"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445");
+    assert(digest512_224 == cast(ubyte[])x"e5302d6d54bb242275d1e7622d68df6eb02dedd13f564c13dbda2174");
+    assert(digest512_256 == cast(ubyte[])x"bde8e1f9f19bb9fd3406c90ec6bc47bd36d8ada9f11880dbc8a22a7078b6a461");
+
+    digest        = sha1Of      ("message digest");
+    digest224     = sha224Of    ("message digest");
+    digest256     = sha256Of    ("message digest");
+    digest384     = sha384Of    ("message digest");
+    digest512     = sha512Of    ("message digest");
+    digest512_224 = sha512_224Of("message digest");
+    digest512_256 = sha512_256Of("message digest");
+    assert(digest == cast(ubyte[])x"c12252ceda8be8994d5fa0290a47231c1d16aae3");
+    assert(digest224 == cast(ubyte[])x"2cb21c83ae2f004de7e81c3c7019cbcb65b71ab656b22d6d0c39b8eb");
+    assert(digest256 == cast(ubyte[])x"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650");
+    assert(digest384 == cast(ubyte[])x"473ed35167ec1f5d8e550368a3db39be54639f828868e9454c239fc8b52e3c61dbd0d8b4de1390c256dcbb5d5fd99cd5");
+    assert(digest512 == cast(ubyte[])x"107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c");
+    assert(digest512_224 == cast(ubyte[])x"ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564");
+    assert(digest512_256 == cast(ubyte[])x"0cf471fd17ed69d990daf3433c89b16d63dec1bb9cb42a6094604ee5d7b4e9fb");
+
+    digest        = sha1Of      ("abcdefghijklmnopqrstuvwxyz");
+    digest224     = sha224Of    ("abcdefghijklmnopqrstuvwxyz");
+    digest256     = sha256Of    ("abcdefghijklmnopqrstuvwxyz");
+    digest384     = sha384Of    ("abcdefghijklmnopqrstuvwxyz");
+    digest512     = sha512Of    ("abcdefghijklmnopqrstuvwxyz");
+    digest512_224 = sha512_224Of("abcdefghijklmnopqrstuvwxyz");
+    digest512_256 = sha512_256Of("abcdefghijklmnopqrstuvwxyz");
+    assert(digest == cast(ubyte[])x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89");
+    assert(digest224 == cast(ubyte[])x"45a5f72c39c5cff2522eb3429799e49e5f44b356ef926bcf390dccc2");
+    assert(digest256 == cast(ubyte[])x"71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73");
+    assert(digest384 == cast(ubyte[])x"feb67349df3db6f5924815d6c3dc133f091809213731fe5c7b5f4999e463479ff2877f5f2936fa63bb43784b12f3ebb4");
+    assert(digest512 == cast(ubyte[])x"4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1");
+    assert(digest512_224 == cast(ubyte[])x"ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8");
+    assert(digest512_256 == cast(ubyte[])x"fc3189443f9c268f626aea08a756abe7b726b05f701cb08222312ccfd6710a26");
+
+    digest        = sha1Of      ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest224     = sha224Of    ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest256     = sha256Of    ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest384     = sha384Of    ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest512     = sha512Of    ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest512_224 = sha512_224Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    digest512_256 = sha512_256Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+    assert(digest == cast(ubyte[])x"761c457bf73b14d27e9e9265c46f4b4dda11f940");
+    assert(digest224 == cast(ubyte[])x"bff72b4fcb7d75e5632900ac5f90d219e05e97a7bde72e740db393d9");
+    assert(digest256 == cast(ubyte[])x"db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0");
+    assert(digest384 == cast(ubyte[])x"1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84");
+    assert(digest512 == cast(ubyte[])x"1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894");
+    assert(digest512_224 == cast(ubyte[])x"a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3");
+    assert(digest512_256 == cast(ubyte[])x"cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8");
+
+    digest        = sha1Of      ("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest224     = sha224Of    ("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest256     = sha256Of    ("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest384     = sha384Of    ("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest512     = sha512Of    ("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest512_224 = sha512_224Of("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    digest512_256 = sha512_256Of("1234567890123456789012345678901234567890"~
+                                 "1234567890123456789012345678901234567890");
+    assert(digest == cast(ubyte[])x"50abf5706a150990a08b2c5ea40fa0e585554732");
+    assert(digest224 == cast(ubyte[])x"b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e");
+    assert(digest256 == cast(ubyte[])x"f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e");
+    assert(digest384 == cast(ubyte[])x"b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026");
+    assert(digest512 == cast(ubyte[])x"72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843");
+    assert(digest512_224 == cast(ubyte[])x"ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2");
+    assert(digest512_256 == cast(ubyte[])x"2c9fdbc0c90bdd87612ee8455474f9044850241dc105b1e8b94b8ddf5fac9148");
+
+    ubyte[] onemilliona = new ubyte[1000000];
+    onemilliona[] = 'a';
+    digest = sha1Of(onemilliona);
+    digest224 = sha224Of(onemilliona);
+    digest256 = sha256Of(onemilliona);
+    digest384 = sha384Of(onemilliona);
+    digest512 = sha512Of(onemilliona);
+    digest512_224 = sha512_224Of(onemilliona);
+    digest512_256 = sha512_256Of(onemilliona);
+    assert(digest == cast(ubyte[])x"34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+    assert(digest224 == cast(ubyte[])x"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67");
+    assert(digest256 == cast(ubyte[])x"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
+    assert(digest384 == cast(ubyte[])x"9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985");
+    assert(digest512 == cast(ubyte[])x"e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
+    assert(digest512_224 == cast(ubyte[])x"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287");
+    assert(digest512_256 == cast(ubyte[])x"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21");
+
+    auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000);
+    digest = sha1Of(oneMillionRange);
+    digest224 = sha224Of(oneMillionRange);
+    digest256 = sha256Of(oneMillionRange);
+    digest384 = sha384Of(oneMillionRange);
+    digest512 = sha512Of(oneMillionRange);
+    digest512_224 = sha512_224Of(oneMillionRange);
+    digest512_256 = sha512_256Of(oneMillionRange);
+    assert(digest == cast(ubyte[])x"34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+    assert(digest224 == cast(ubyte[])x"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67");
+    assert(digest256 == cast(ubyte[])x"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
+    assert(digest384 == cast(ubyte[])x"9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985");
+    assert(digest512 == cast(ubyte[])x"e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
+    assert(digest512_224 == cast(ubyte[])x"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287");
+    assert(digest512_256 == cast(ubyte[])x"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21");
+
+    assert(toHexString(cast(ubyte[20])x"a9993e364706816aba3e25717850c26c9cd0d89d")
+        == "A9993E364706816ABA3E25717850C26C9CD0D89D");
+}
+
+/**
+ * These are convenience aliases for $(XREF_PACK digest,digest,digest) using the
+ * SHA implementation.
+ */
+//simple alias doesn't work here, hope this gets inlined...
+auto sha1Of(T...)(T data)
+{
+    return digest!(SHA1, T)(data);
+}
+///ditto
+auto sha224Of(T...)(T data)
+{
+    return digest!(SHA224, T)(data);
+}
+///ditto
+auto sha256Of(T...)(T data)
+{
+    return digest!(SHA256, T)(data);
+}
+///ditto
+auto sha384Of(T...)(T data)
+{
+    return digest!(SHA384, T)(data);
+}
+///ditto
+auto sha512Of(T...)(T data)
+{
+    return digest!(SHA512, T)(data);
+}
+///ditto
+auto sha512_224Of(T...)(T data)
+{
+    return digest!(SHA512_224, T)(data);
+}
+///ditto
+auto sha512_256Of(T...)(T data)
+{
+    return digest!(SHA512_256, T)(data);
+}
+
+///
+unittest
+{
+    ubyte[20] hash = sha1Of("abc");
+    assert(hash == digest!SHA1("abc"));
+
+    ubyte[28] hash224 = sha224Of("abc");
+    assert(hash224 == digest!SHA224("abc"));
+
+    ubyte[32] hash256 = sha256Of("abc");
+    assert(hash256 == digest!SHA256("abc"));
+
+    ubyte[48] hash384 = sha384Of("abc");
+    assert(hash384 == digest!SHA384("abc"));
+
+    ubyte[64] hash512 = sha512Of("abc");
+    assert(hash512 == digest!SHA512("abc"));
+
+    ubyte[28] hash512_224 = sha512_224Of("abc");
+    assert(hash512_224 == digest!SHA512_224("abc"));
+
+    ubyte[32] hash512_256 = sha512_256Of("abc");
+    assert(hash512_256 == digest!SHA512_256("abc"));
+}
+
+unittest
+{
+    string a = "Mary has ", b = "a little lamb";
+    int[] c = [ 1, 2, 3, 4, 5 ];
+    string d = toHexString(sha1Of(a, b, c));
+    version(LittleEndian)
+        assert(d == "CDBB611D00AC2387B642D3D7BDF4C3B342237110", d);
+    else
+        assert(d == "A0F1196C7A379C09390476D9CA4AA11B71FD11C8", d);
+}
+
+/**
+ * OOP API SHA1 and SHA2 implementations.
+ * See $(D std.digest.digest) for differences between template and OOP API.
+ *
+ * This is an alias for $(D $(XREF_PACK digest,digest,WrapperDigest)!SHA1), see
+ * there for more information.
+ */
+alias SHA1Digest = WrapperDigest!SHA1;
+alias SHA224Digest = WrapperDigest!SHA224; ///ditto
+alias SHA256Digest = WrapperDigest!SHA256; ///ditto
+alias SHA384Digest = WrapperDigest!SHA384; ///ditto
+alias SHA512Digest = WrapperDigest!SHA512; ///ditto
+alias SHA512_224Digest = WrapperDigest!SHA512_224; ///ditto
+alias SHA512_256Digest = WrapperDigest!SHA512_256; ///ditto
+
+///
+unittest
+{
+    //Simple example, hashing a string using Digest.digest helper function
+    auto sha = new SHA1Digest();
+    ubyte[] hash = sha.digest("abc");
+    //Let's get a hash string
+    assert(toHexString(hash) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+    //The same, but using SHA-224
+    auto sha224 = new SHA224Digest();
+    ubyte[] hash224 = sha224.digest("abc");
+    //Let's get a hash string
+    assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+}
+
+///
+unittest
+{
+    //Let's use the OOP features:
+    void test(Digest dig)
+    {
+      dig.put(cast(ubyte)0);
+    }
+    auto sha = new SHA1Digest();
+    test(sha);
+
+    //Let's use a custom buffer:
+    ubyte[20] buf;
+    ubyte[] result = sha.finish(buf[]);
+    assert(toHexString(result) == "5BA93C9DB0CFF93F52B521D7420E43F6EDA2784F");
+}
+
+unittest
+{
+    auto sha = new SHA1Digest();
+
+    sha.put(cast(ubyte[])"abcdef");
+    sha.reset();
+    sha.put(cast(ubyte[])"");
+    assert(sha.finish() == cast(ubyte[])x"da39a3ee5e6b4b0d3255bfef95601890afd80709");
+
+    sha.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz");
+    ubyte[22] result;
+    auto result2 = sha.finish(result[]);
+    assert(result[0 .. 20] == result2 && result2 == cast(ubyte[])x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89");
+
+    debug
+        assertThrown!Error(sha.finish(result[0 .. 15]));
+
+    assert(sha.length == 20);
+
+    assert(sha.digest("") == cast(ubyte[])x"da39a3ee5e6b4b0d3255bfef95601890afd80709");
+
+    assert(sha.digest("a") == cast(ubyte[])x"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8");
+
+    assert(sha.digest("abc") == cast(ubyte[])x"a9993e364706816aba3e25717850c26c9cd0d89d");
+
+    assert(sha.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
+           == cast(ubyte[])x"84983e441c3bd26ebaae4aa1f95129e5e54670f1");
+
+    assert(sha.digest("message digest") == cast(ubyte[])x"c12252ceda8be8994d5fa0290a47231c1d16aae3");
+
+    assert(sha.digest("abcdefghijklmnopqrstuvwxyz")
+           == cast(ubyte[])x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89");
+
+    assert(sha.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+           == cast(ubyte[])x"761c457bf73b14d27e9e9265c46f4b4dda11f940");
+
+    assert(sha.digest("1234567890123456789012345678901234567890",
+                                   "1234567890123456789012345678901234567890")
+           == cast(ubyte[])x"50abf5706a150990a08b2c5ea40fa0e585554732");
+
+    ubyte[] onemilliona = new ubyte[1000000];
+    onemilliona[] = 'a';
+    assert(sha.digest(onemilliona) == cast(ubyte[])x"34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+}
diff --git a/libphobos/src/std/encoding.d b/libphobos/src/std/encoding.d
new file mode 100644
index 00000000000..9c2d667b333
--- /dev/null
+++ b/libphobos/src/std/encoding.d
@@ -0,0 +1,3382 @@
+// Written in the D programming language.
+
+/**
+Classes and functions for handling and transcoding between various encodings.
+
+For cases where the _encoding is known at compile-time, functions are provided
+for arbitrary _encoding and decoding of characters, arbitrary transcoding
+between strings of different type, as well as validation and sanitization.
+
+Encodings currently supported are UTF-8, UTF-16, UTF-32, ASCII, ISO-8859-1
+(also known as LATIN-1), ISO-8859-2 (LATIN-2), WINDOWS-1250 and WINDOWS-1252.
+
+$(UL
+$(LI The type $(D AsciiChar) represents an ASCII character.)
+$(LI The type $(D AsciiString) represents an ASCII string.)
+$(LI The type $(D Latin1Char) represents an ISO-8859-1 character.)
+$(LI The type $(D Latin1String) represents an ISO-8859-1 string.)
+$(LI The type $(D Latin2Char) represents an ISO-8859-2 character.)
+$(LI The type $(D Latin2String) represents an ISO-8859-2 string.)
+$(LI The type $(D Windows1250Char) represents a Windows-1250 character.)
+$(LI The type $(D Windows1250String) represents a Windows-1250 string.)
+$(LI The type $(D Windows1252Char) represents a Windows-1252 character.)
+$(LI The type $(D Windows1252String) represents a Windows-1252 string.))
+
+For cases where the _encoding is not known at compile-time, but is
+known at run-time, we provide the abstract class $(D EncodingScheme)
+and its subclasses.  To construct a run-time encoder/decoder, one does
+e.g.
+
+----------------------------------------------------
+    auto e = EncodingScheme.create("utf-8");
+----------------------------------------------------
+
+This library supplies $(D EncodingScheme) subclasses for ASCII,
+ISO-8859-1 (also known as LATIN-1), ISO-8859-2 (LATIN-2), WINDOWS-1250,
+WINDOWS-1252, UTF-8, and (on little-endian architectures) UTF-16LE and
+UTF-32LE; or (on big-endian architectures) UTF-16BE and UTF-32BE.
+
+This library provides a mechanism whereby other modules may add $(D
+EncodingScheme) subclasses for any other _encoding.
+
+Macros:
+    WIKI=Phobos/StdEncoding
+
+Copyright: Copyright Janice Caron 2008 - 2009.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   Janice Caron
+Source:    $(PHOBOSSRC std/_encoding.d)
+*/
+/*
+         Copyright Janice Caron 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.encoding;
+
+import std.traits;
+import std.range.primitives;
+
+unittest
+{
+    static ubyte[][] validStrings =
+    [
+        // Plain ASCII
+        cast(ubyte[])"hello",
+
+        // First possible sequence of a certain length
+        [ 0x00 ],                       // U+00000000   one byte
+        [ 0xC2, 0x80 ],                 // U+00000080   two bytes
+        [ 0xE0, 0xA0, 0x80 ],           // U+00000800   three bytes
+        [ 0xF0, 0x90, 0x80, 0x80 ],     // U+00010000   three bytes
+
+        // Last possible sequence of a certain length
+        [ 0x7F ],                       // U+0000007F   one byte
+        [ 0xDF, 0xBF ],                 // U+000007FF   two bytes
+        [ 0xEF, 0xBF, 0xBF ],           // U+0000FFFF   three bytes
+
+        // Other boundary conditions
+        [ 0xED, 0x9F, 0xBF ],
+        // U+0000D7FF   Last character before surrogates
+        [ 0xEE, 0x80, 0x80 ],
+        // U+0000E000   First character after surrogates
+        [ 0xEF, 0xBF, 0xBD ],
+        // U+0000FFFD   Unicode replacement character
+        [ 0xF4, 0x8F, 0xBF, 0xBF ],
+        // U+0010FFFF   Very last character
+
+        // Non-character code points
+        /*  NOTE: These are legal in UTF, and may be converted from
+            one UTF to another, however they do not represent Unicode
+            characters. These code points have been reserved by
+            Unicode as non-character code points. They are permissible
+            for data exchange within an application, but they are are
+            not permitted to be used as characters. Since this module
+            deals with UTF, and not with Unicode per se, we choose to
+            accept them here. */
+        [ 0xDF, 0xBE ],                 // U+0000FFFE
+        [ 0xDF, 0xBF ],                 // U+0000FFFF
+    ];
+
+    static ubyte[][] invalidStrings =
+    [
+        // First possible sequence of a certain length, but greater
+        // than U+10FFFF
+        [ 0xF8, 0x88, 0x80, 0x80, 0x80 ],           // U+00200000   five bytes
+        [ 0xFC, 0x84, 0x80, 0x80, 0x80, 0x80 ],     // U+04000000   six bytes
+
+        // Last possible sequence of a certain length, but greater than U+10FFFF
+        [ 0xF7, 0xBF, 0xBF, 0xBF ],                 // U+001FFFFF   four bytes
+        [ 0xFB, 0xBF, 0xBF, 0xBF, 0xBF ],           // U+03FFFFFF   five bytes
+        [ 0xFD, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF ],     // U+7FFFFFFF   six bytes
+
+        // Other boundary conditions
+        [ 0xF4, 0x90, 0x80, 0x80 ],                 // U+00110000
+                                                    // First code
+                                                    // point after
+                                                    // last character
+
+        // Unexpected continuation bytes
+        [ 0x80 ],
+        [ 0xBF ],
+        [ 0x20, 0x80, 0x20 ],
+        [ 0x20, 0xBF, 0x20 ],
+        [ 0x80, 0x9F, 0xA0 ],
+
+        // Lonely start bytes
+        [ 0xC0 ],
+        [ 0xCF ],
+        [ 0x20, 0xC0, 0x20 ],
+        [ 0x20, 0xCF, 0x20 ],
+        [ 0xD0 ],
+        [ 0xDF ],
+        [ 0x20, 0xD0, 0x20 ],
+        [ 0x20, 0xDF, 0x20 ],
+        [ 0xE0 ],
+        [ 0xEF ],
+        [ 0x20, 0xE0, 0x20 ],
+        [ 0x20, 0xEF, 0x20 ],
+        [ 0xF0 ],
+        [ 0xF1 ],
+        [ 0xF2 ],
+        [ 0xF3 ],
+        [ 0xF4 ],
+        [ 0xF5 ],   // If this were legal it would start a character > U+10FFFF
+        [ 0xF6 ],   // If this were legal it would start a character > U+10FFFF
+        [ 0xF7 ],   // If this were legal it would start a character > U+10FFFF
+
+        [ 0xEF, 0xBF ],             // Three byte sequence with third byte missing
+        [ 0xF7, 0xBF, 0xBF ],       // Four byte sequence with fourth byte missing
+        [ 0xEF, 0xBF, 0xF7, 0xBF, 0xBF ],   // Concatenation of the above
+
+        // Impossible bytes
+        [ 0xF8 ],
+        [ 0xF9 ],
+        [ 0xFA ],
+        [ 0xFB ],
+        [ 0xFC ],
+        [ 0xFD ],
+        [ 0xFE ],
+        [ 0xFF ],
+        [ 0x20, 0xF8, 0x20 ],
+        [ 0x20, 0xF9, 0x20 ],
+        [ 0x20, 0xFA, 0x20 ],
+        [ 0x20, 0xFB, 0x20 ],
+        [ 0x20, 0xFC, 0x20 ],
+        [ 0x20, 0xFD, 0x20 ],
+        [ 0x20, 0xFE, 0x20 ],
+        [ 0x20, 0xFF, 0x20 ],
+
+        // Overlong sequences, all representing U+002F
+        /*  With a safe UTF-8 decoder, all of the following five overlong
+            representations of the ASCII character slash ("/") should be
+            rejected like a malformed UTF-8 sequence */
+        [ 0xC0, 0xAF ],
+        [ 0xE0, 0x80, 0xAF ],
+        [ 0xF0, 0x80, 0x80, 0xAF ],
+        [ 0xF8, 0x80, 0x80, 0x80, 0xAF ],
+        [ 0xFC, 0x80, 0x80, 0x80, 0x80, 0xAF ],
+
+        // Maximum overlong sequences
+        /*  Below you see the highest Unicode value that is still resulting in
+            an overlong sequence if represented with the given number of bytes.
+            This is a boundary test for safe UTF-8 decoders. All five
+            characters should be rejected like malformed UTF-8 sequences. */
+        [ 0xC1, 0xBF ],                             // U+0000007F
+        [ 0xE0, 0x9F, 0xBF ],                       // U+000007FF
+        [ 0xF0, 0x8F, 0xBF, 0xBF ],                 // U+0000FFFF
+        [ 0xF8, 0x87, 0xBF, 0xBF, 0xBF ],           // U+001FFFFF
+        [ 0xFC, 0x83, 0xBF, 0xBF, 0xBF, 0xBF ],     // U+03FFFFFF
+
+        // Overlong representation of the NUL character
+        /*  The following five sequences should also be rejected like malformed
+            UTF-8 sequences and should not be treated like the ASCII NUL
+            character. */
+        [ 0xC0, 0x80 ],
+        [ 0xE0, 0x80, 0x80 ],
+        [ 0xF0, 0x80, 0x80, 0x80 ],
+        [ 0xF8, 0x80, 0x80, 0x80, 0x80 ],
+        [ 0xFC, 0x80, 0x80, 0x80, 0x80, 0x80 ],
+
+        // Illegal code positions
+        /*  The following UTF-8 sequences should be rejected like malformed
+            sequences, because they never represent valid ISO 10646 characters
+            and a UTF-8 decoder that accepts them might introduce security
+            problems comparable to overlong UTF-8 sequences. */
+        [ 0xED, 0xA0, 0x80 ],       // U+D800
+        [ 0xED, 0xAD, 0xBF ],       // U+DB7F
+        [ 0xED, 0xAE, 0x80 ],       // U+DB80
+        [ 0xED, 0xAF, 0xBF ],       // U+DBFF
+        [ 0xED, 0xB0, 0x80 ],       // U+DC00
+        [ 0xED, 0xBE, 0x80 ],       // U+DF80
+        [ 0xED, 0xBF, 0xBF ],       // U+DFFF
+    ];
+
+    static string[] sanitizedStrings =
+    [
+        "\uFFFD","\uFFFD",
+        "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD"," \uFFFD ",
+        " \uFFFD ","\uFFFD\uFFFD\uFFFD","\uFFFD","\uFFFD"," \uFFFD "," \uFFFD ",
+        "\uFFFD","\uFFFD"," \uFFFD "," \uFFFD ","\uFFFD","\uFFFD"," \uFFFD ",
+        " \uFFFD ","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD",
+        "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD\uFFFD","\uFFFD","\uFFFD",
+        "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD"," \uFFFD ",
+        " \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD ",
+        " \uFFFD ","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD",
+        "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD",
+        "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD",
+    ];
+
+    // Make sure everything that should be valid, is
+    foreach(a;validStrings)
+    {
+        string s = cast(string)a;
+        assert(isValid(s),"Failed to validate: "~makeReadable(s));
+    }
+
+    // Make sure everything that shouldn't be valid, isn't
+    foreach(a;invalidStrings)
+    {
+        string s = cast(string)a;
+        assert(!isValid(s),"Incorrectly validated: "~makeReadable(s));
+    }
+
+    // Make sure we can sanitize everything bad
+    assert(invalidStrings.length == sanitizedStrings.length);
+    for(int i=0; i<invalidStrings.length; ++i)
+    {
+        string s = cast(string)invalidStrings[i];
+        string t = sanitize(s);
+        assert(isValid(t));
+        assert(t == sanitizedStrings[i]);
+        ubyte[] u = cast(ubyte[])t;
+        validStrings ~= u;
+    }
+
+    // Make sure all transcodings work in both directions, using both forward
+    // and reverse iteration
+    foreach(a; validStrings)
+    {
+        string s = cast(string)a;
+        string s2;
+        wstring ws, ws2;
+        dstring ds, ds2;
+
+        transcode(s,ws);
+        assert(isValid(ws));
+        transcode(ws,s2);
+        assert(s == s2);
+
+        transcode(s,ds);
+        assert(isValid(ds));
+        transcode(ds,s2);
+        assert(s == s2);
+
+        transcode(ws,s);
+        assert(isValid(s));
+        transcode(s,ws2);
+        assert(ws == ws2);
+
+        transcode(ws,ds);
+        assert(isValid(ds));
+        transcode(ds,ws2);
+        assert(ws == ws2);
+
+        transcode(ds,s);
+        assert(isValid(s));
+        transcode(s,ds2);
+        assert(ds == ds2);
+
+        transcode(ds,ws);
+        assert(isValid(ws));
+        transcode(ws,ds2);
+        assert(ds == ds2);
+
+        transcodeReverse(s,ws);
+        assert(isValid(ws));
+        transcodeReverse(ws,s2);
+        assert(s == s2);
+
+        transcodeReverse(s,ds);
+        assert(isValid(ds));
+        transcodeReverse(ds,s2);
+        assert(s == s2);
+
+        transcodeReverse(ws,s);
+        assert(isValid(s));
+        transcodeReverse(s,ws2);
+        assert(ws == ws2);
+
+        transcodeReverse(ws,ds);
+        assert(isValid(ds));
+        transcodeReverse(ds,ws2);
+        assert(ws == ws2);
+
+        transcodeReverse(ds,s);
+        assert(isValid(s));
+        transcodeReverse(s,ds2);
+        assert(ds == ds2);
+
+        transcodeReverse(ds,ws);
+        assert(isValid(ws));
+        transcodeReverse(ws,ds2);
+        assert(ds == ds2);
+    }
+
+    // Make sure the non-UTF encodings work too
+    {
+        auto s = "\u20AC100";
+        Windows1252String t;
+        transcode(s,t);
+        assert(t == cast(Windows1252Char[])[0x80, '1', '0', '0']);
+        string u;
+        transcode(s,u);
+        assert(s == u);
+        Latin1String v;
+        transcode(s,v);
+        assert(cast(string)v == "?100");
+        AsciiString w;
+        transcode(v,w);
+        assert(cast(string)w == "?100");
+        s = "\u017Dlu\u0165ou\u010Dk\u00FD k\u016F\u0148";
+        Latin2String x;
+        transcode(s,x);
+        assert(x == cast(Latin2Char[])[0xae, 'l', 'u', 0xbb, 'o', 'u', 0xe8, 'k', 0xfd, ' ', 'k', 0xf9, 0xf2]);
+        Windows1250String y;
+        transcode(s,y);
+        assert(y == cast(Windows1250Char[])[0x8e, 'l', 'u', 0x9d, 'o', 'u', 0xe8, 'k', 0xfd, ' ', 'k', 0xf9, 0xf2]);
+    }
+
+    // Make sure we can count properly
+    {
+        assert(encodedLength!(char)('A') == 1);
+        assert(encodedLength!(char)('\u00E3') == 2);
+        assert(encodedLength!(char)('\u2028') == 3);
+        assert(encodedLength!(char)('\U0010FFF0') == 4);
+        assert(encodedLength!(wchar)('A') == 1);
+        assert(encodedLength!(wchar)('\U0010FFF0') == 2);
+    }
+
+    // Make sure we can write into mutable arrays
+    {
+        char[4] buffer;
+        auto n = encode(cast(dchar)'\u00E3',buffer);
+        assert(n == 2);
+        assert(buffer[0] == 0xC3);
+        assert(buffer[1] == 0xA3);
+    }
+}
+
+//=============================================================================
+
+/** Special value returned by $(D safeDecode) */
+enum dchar INVALID_SEQUENCE = cast(dchar) 0xFFFFFFFF;
+
+template EncoderFunctions()
+{
+    // Various forms of read
+
+    template ReadFromString()
+    {
+        @property bool canRead() { return s.length != 0; }
+        E peek() { return s[0]; }
+        E read() { E t = s[0]; s = s[1..$]; return t; }
+    }
+
+    template ReverseReadFromString()
+    {
+        @property bool canRead() { return s.length != 0; }
+        E peek() { return s[$-1]; }
+        E read() { E t = s[$-1]; s = s[0..$-1]; return t; }
+    }
+
+    // Various forms of Write
+
+    template WriteToString()
+    {
+        E[] s;
+        void write(E c) { s ~= c; }
+    }
+
+    template WriteToArray()
+    {
+        void write(E c) { array[0] = c; array = array[1..$]; }
+    }
+
+    template WriteToDelegate()
+    {
+        void write(E c) { dg(c); }
+    }
+
+    // Functions we will export
+
+    template EncodeViaWrite()
+    {
+        mixin encodeViaWrite;
+        void encode(dchar c) { encodeViaWrite(c); }
+    }
+
+    template SkipViaRead()
+    {
+        mixin skipViaRead;
+        void skip() { skipViaRead(); }
+    }
+
+    template DecodeViaRead()
+    {
+        mixin decodeViaRead;
+        dchar decode() { return decodeViaRead(); }
+    }
+
+    template SafeDecodeViaRead()
+    {
+        mixin safeDecodeViaRead;
+        dchar safeDecode() { return safeDecodeViaRead(); }
+    }
+
+    template DecodeReverseViaRead()
+    {
+        mixin decodeReverseViaRead;
+        dchar decodeReverse() { return decodeReverseViaRead(); }
+    }
+
+    // Encoding to different destinations
+
+    template EncodeToString()
+    {
+        mixin WriteToString;
+        mixin EncodeViaWrite;
+    }
+
+    template EncodeToArray()
+    {
+        mixin WriteToArray;
+        mixin EncodeViaWrite;
+    }
+
+    template EncodeToDelegate()
+    {
+        mixin WriteToDelegate;
+        mixin EncodeViaWrite;
+    }
+
+    // Decoding functions
+
+    template SkipFromString()
+    {
+        mixin ReadFromString;
+        mixin SkipViaRead;
+    }
+
+    template DecodeFromString()
+    {
+        mixin ReadFromString;
+        mixin DecodeViaRead;
+    }
+
+    template SafeDecodeFromString()
+    {
+        mixin ReadFromString;
+        mixin SafeDecodeViaRead;
+    }
+
+    template DecodeReverseFromString()
+    {
+        mixin ReverseReadFromString;
+        mixin DecodeReverseViaRead;
+    }
+
+    //=========================================================================
+
+    // Below are the functions we will ultimately expose to the user
+
+    E[] encode(dchar c)
+    {
+        mixin EncodeToString e;
+        e.encode(c);
+        return e.s;
+    }
+
+    void encode(dchar c, ref E[] array)
+    {
+        mixin EncodeToArray e;
+        e.encode(c);
+    }
+
+    void encode(dchar c, void delegate(E) dg)
+    {
+        mixin EncodeToDelegate e;
+        e.encode(c);
+    }
+
+    void skip(ref const(E)[] s)
+    {
+        mixin SkipFromString e;
+        e.skip();
+    }
+
+    dchar decode(S)(ref S s)
+    {
+        mixin DecodeFromString e;
+        return e.decode();
+    }
+
+    dchar safeDecode(S)(ref S s)
+    {
+        mixin SafeDecodeFromString e;
+        return e.safeDecode();
+    }
+
+    dchar decodeReverse(ref const(E)[] s)
+    {
+        mixin DecodeReverseFromString e;
+        return e.decodeReverse();
+    }
+}
+
+//=========================================================================
+
+struct CodePoints(E)
+{
+    const(E)[] s;
+
+    this(const(E)[] s)
+    in
+    {
+        assert(isValid(s));
+    }
+    body
+    {
+        this.s = s;
+    }
+
+    int opApply(scope int delegate(ref dchar) dg)
+    {
+        int result = 0;
+        while (s.length != 0)
+        {
+            dchar c = decode(s);
+            result = dg(c);
+            if (result != 0) break;
+        }
+        return result;
+    }
+
+    int opApply(scope int delegate(ref size_t, ref dchar) dg)
+    {
+        size_t i = 0;
+        int result = 0;
+        while (s.length != 0)
+        {
+            size_t len = s.length;
+            dchar c = decode(s);
+            size_t j = i; // We don't want the delegate corrupting i
+            result = dg(j,c);
+            if (result != 0) break;
+            i += len - s.length;
+        }
+        return result;
+    }
+
+    int opApplyReverse(scope int delegate(ref dchar) dg)
+    {
+        int result = 0;
+        while (s.length != 0)
+        {
+            dchar c = decodeReverse(s);
+            result = dg(c);
+            if (result != 0) break;
+        }
+        return result;
+    }
+
+    int opApplyReverse(scope int delegate(ref size_t, ref dchar) dg)
+    {
+        int result = 0;
+        while (s.length != 0)
+        {
+            dchar c = decodeReverse(s);
+            size_t i = s.length;
+            result = dg(i,c);
+            if (result != 0) break;
+        }
+        return result;
+    }
+}
+
+struct CodeUnits(E)
+{
+    E[] s;
+
+    this(dchar d)
+    in
+    {
+        assert(isValidCodePoint(d));
+    }
+    body
+    {
+        s = encode!(E)(d);
+    }
+
+    int opApply(scope int delegate(ref E) dg)
+    {
+        int result = 0;
+        foreach(E c;s)
+        {
+            result = dg(c);
+            if (result != 0) break;
+        }
+        return result;
+    }
+
+    int opApplyReverse(scope int delegate(ref E) dg)
+    {
+        int result = 0;
+        foreach_reverse(E c;s)
+        {
+            result = dg(c);
+            if (result != 0) break;
+        }
+        return result;
+    }
+}
+
+//=============================================================================
+
+template EncoderInstance(E)
+{
+    static assert(false,"Cannot instantiate EncoderInstance for type "
+        ~ E.stringof);
+}
+
+private template GenericEncoder()
+{
+    bool canEncode(dchar c)
+    {
+        if (c < m_charMapStart || (c > m_charMapEnd && c < 0x100)) return true;
+        if (c >= 0xFFFD) return false;
+
+        auto idx = 0;
+        while (idx < bstMap.length)
+        {
+            if (bstMap[idx][0] == c) return true;
+            idx = bstMap[idx][0] > c ? 2 * idx + 1 : 2 * idx + 2; // next BST index
+        }
+
+        return false;
+    }
+
+    bool isValidCodeUnit(E c)
+    {
+        if (c < m_charMapStart || c > m_charMapEnd) return true;
+        return charMap[c-m_charMapStart] != 0xFFFD;
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        return 1;
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        if (c < m_charMapStart || (c > m_charMapEnd && c < 0x100)) {}
+        else if (c >= 0xFFFD) { c = '?'; }
+        else
+        {
+            auto idx = 0;
+            while (idx < bstMap.length)
+            {
+                if (bstMap[idx][0] == c)
+                {
+                    write(cast(E)bstMap[idx][1]);
+                    return;
+                }
+                idx = bstMap[idx][0] > c ? 2 * idx + 1 : 2 * idx + 2; // next BST index
+            }
+            c = '?';
+        }
+        write(cast(E)c);
+    }
+
+    void skipViaRead()()
+    {
+        read();
+    }
+
+    dchar decodeViaRead()()
+    {
+        E c = read();
+        return (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c;
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        E c = read();
+        dchar d = (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c;
+        return d == 0xFFFD ? INVALID_SEQUENCE : d;
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        E c = read();
+        return (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c;
+    }
+
+    @property EString replacementSequence()
+    {
+        return cast(EString)("?");
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+//          ASCII
+//=============================================================================
+
+/** Defines various character sets. */
+enum AsciiChar : ubyte { init }
+/// Ditto
+alias AsciiString = immutable(AsciiChar)[];
+
+template EncoderInstance(CharType : AsciiChar)
+{
+    alias E = AsciiChar;
+    alias EString = AsciiString;
+
+    @property string encodingName()
+    {
+        return "ASCII";
+    }
+
+    bool canEncode(dchar c)
+    {
+        return c < 0x80;
+    }
+
+    bool isValidCodeUnit(AsciiChar c)
+    {
+        return c < 0x80;
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        return 1;
+    }
+
+    void encodeX(Range)(dchar c, Range r)
+    {
+        if (!canEncode(c)) c = '?';
+        r.write(cast(AsciiChar) c);
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        if (!canEncode(c)) c = '?';
+        write(cast(AsciiChar)c);
+    }
+
+    void skipViaRead()()
+    {
+        read();
+    }
+
+    dchar decodeViaRead()()
+    {
+        return read();
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        dchar c = read();
+        return canEncode(c) ? c : INVALID_SEQUENCE;
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        return read();
+    }
+
+    @property EString replacementSequence()
+    {
+        return cast(EString)("?");
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+//          ISO-8859-1
+//=============================================================================
+
+/** Defines an Latin1-encoded character. */
+enum Latin1Char : ubyte { init }
+/**
+Defines an Latin1-encoded string (as an array of $(D
+immutable(Latin1Char))).
+ */
+alias Latin1String = immutable(Latin1Char)[];
+
+template EncoderInstance(CharType : Latin1Char)
+{
+    alias E = Latin1Char;
+    alias EString = Latin1String;
+
+    @property string encodingName()
+    {
+        return "ISO-8859-1";
+    }
+
+    bool canEncode(dchar c)
+    {
+        return c < 0x100;
+    }
+
+    bool isValidCodeUnit(Latin1Char c)
+    {
+        return true;
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        return 1;
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        if (!canEncode(c)) c = '?';
+        write(cast(Latin1Char)c);
+    }
+
+    void skipViaRead()()
+    {
+        read();
+    }
+
+    dchar decodeViaRead()()
+    {
+        return read();
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        return read();
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        return read();
+    }
+
+    @property EString replacementSequence()
+    {
+        return cast(EString)("?");
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+//          ISO-8859-2
+//=============================================================================
+
+/// Defines a Latin2-encoded character.
+enum Latin2Char : ubyte { init }
+
+/**
+ * Defines an Latin2-encoded string (as an array of $(D
+ * immutable(Latin2Char))).
+ */
+alias Latin2String = immutable(Latin2Char)[];
+
+private template EncoderInstance(CharType : Latin2Char)
+{
+    import std.typecons : Tuple, tuple;
+
+    alias E = Latin2Char;
+    alias EString = Latin2String;
+
+    @property string encodingName()
+    {
+        return "ISO-8859-2";
+    }
+
+    private dchar m_charMapStart = 0xa1;
+    private dchar m_charMapEnd = 0xff;
+
+    private immutable wstring charMap =
+        "\u0104\u02D8\u0141\u00A4\u013D\u015A\u00A7\u00A8"~
+        "\u0160\u015E\u0164\u0179\u00AD\u017D\u017B\u00B0"~
+        "\u0105\u02DB\u0142\u00B4\u013E\u015B\u02C7\u00B8"~
+        "\u0161\u015F\u0165\u017A\u02DD\u017E\u017C\u0154"~
+        "\u00C1\u00C2\u0102\u00C4\u0139\u0106\u00C7\u010C"~
+        "\u00C9\u0118\u00CB\u011A\u00CD\u00CE\u010E\u0110"~
+        "\u0143\u0147\u00D3\u00D4\u0150\u00D6\u00D7\u0158"~
+        "\u016E\u00DA\u0170\u00DC\u00DD\u0162\u00DF\u0155"~
+        "\u00E1\u00E2\u0103\u00E4\u013A\u0107\u00E7\u010D"~
+        "\u00E9\u0119\u00EB\u011B\u00ED\u00EE\u010F\u0111"~
+        "\u0144\u0148\u00F3\u00F4\u0151\u00F6\u00F7\u0159"~
+        "\u016F\u00FA\u0171\u00FC\u00FD\u0163\u02D9";
+
+    private immutable Tuple!(wchar, char)[] bstMap = [
+        tuple('\u0148','\xF2'), tuple('\u00F3','\xF3'), tuple('\u0165','\xBB'),
+        tuple('\u00D3','\xD3'), tuple('\u010F','\xEF'), tuple('\u015B','\xB6'),
+        tuple('\u017C','\xBF'), tuple('\u00C1','\xC1'), tuple('\u00E1','\xE1'),
+        tuple('\u0103','\xE3'), tuple('\u013A','\xE5'), tuple('\u0155','\xE0'),
+        tuple('\u0161','\xB9'), tuple('\u0171','\xFB'), tuple('\u02D8','\xA2'),
+        tuple('\u00AD','\xAD'), tuple('\u00C9','\xC9'), tuple('\u00DA','\xDA'),
+        tuple('\u00E9','\xE9'), tuple('\u00FA','\xFA'), tuple('\u0107','\xE6'),
+        tuple('\u0119','\xEA'), tuple('\u0142','\xB3'), tuple('\u0151','\xF5'),
+        tuple('\u0159','\xF8'), tuple('\u015F','\xBA'), tuple('\u0163','\xFE'),
+        tuple('\u016F','\xF9'), tuple('\u017A','\xBC'), tuple('\u017E','\xBE'),
+        tuple('\u02DB','\xB2'), tuple('\u00A7','\xA7'), tuple('\u00B4','\xB4'),
+        tuple('\u00C4','\xC4'), tuple('\u00CD','\xCD'), tuple('\u00D6','\xD6'),
+        tuple('\u00DD','\xDD'), tuple('\u00E4','\xE4'), tuple('\u00ED','\xED'),
+        tuple('\u00F6','\xF6'), tuple('\u00FD','\xFD'), tuple('\u0105','\xB1'),
+        tuple('\u010D','\xE8'), tuple('\u0111','\xF0'), tuple('\u011B','\xEC'),
+        tuple('\u013E','\xB5'), tuple('\u0144','\xF1'), tuple('\u0150','\xD5'),
+        tuple('\u0154','\xC0'), tuple('\u0158','\xD8'), tuple('\u015A','\xA6'),
+        tuple('\u015E','\xAA'), tuple('\u0160','\xA9'), tuple('\u0162','\xDE'),
+        tuple('\u0164','\xAB'), tuple('\u016E','\xD9'), tuple('\u0170','\xDB'),
+        tuple('\u0179','\xAC'), tuple('\u017B','\xAF'), tuple('\u017D','\xAE'),
+        tuple('\u02C7','\xB7'), tuple('\u02D9','\xFF'), tuple('\u02DD','\xBD'),
+        tuple('\u00A4','\xA4'), tuple('\u00A8','\xA8'), tuple('\u00B0','\xB0'),
+        tuple('\u00B8','\xB8'), tuple('\u00C2','\xC2'), tuple('\u00C7','\xC7'),
+        tuple('\u00CB','\xCB'), tuple('\u00CE','\xCE'), tuple('\u00D4','\xD4'),
+        tuple('\u00D7','\xD7'), tuple('\u00DC','\xDC'), tuple('\u00DF','\xDF'),
+        tuple('\u00E2','\xE2'), tuple('\u00E7','\xE7'), tuple('\u00EB','\xEB'),
+        tuple('\u00EE','\xEE'), tuple('\u00F4','\xF4'), tuple('\u00F7','\xF7'),
+        tuple('\u00FC','\xFC'), tuple('\u0102','\xC3'), tuple('\u0104','\xA1'),
+        tuple('\u0106','\xC6'), tuple('\u010C','\xC8'), tuple('\u010E','\xCF'),
+        tuple('\u0110','\xD0'), tuple('\u0118','\xCA'), tuple('\u011A','\xCC'),
+        tuple('\u0139','\xC5'), tuple('\u013D','\xA5'), tuple('\u0141','\xA3'),
+        tuple('\u0143','\xD1'), tuple('\u0147','\xD2')
+    ];
+
+    mixin GenericEncoder!();
+}
+
+//=============================================================================
+//          WINDOWS-1250
+//=============================================================================
+
+/// Defines a Windows1250-encoded character.
+enum Windows1250Char : ubyte { init }
+
+/**
+ * Defines an Windows1250-encoded string (as an array of $(D
+ * immutable(Windows1250Char))).
+ */
+alias Windows1250String = immutable(Windows1250Char)[];
+
+private template EncoderInstance(CharType : Windows1250Char)
+{
+    import std.typecons : Tuple, tuple;
+
+    alias E = Windows1250Char;
+    alias EString = Windows1250String;
+
+    @property string encodingName()
+    {
+        return "windows-1250";
+    }
+
+    private dchar m_charMapStart = 0x80;
+    private dchar m_charMapEnd = 0xff;
+
+    private immutable wstring charMap =
+        "\u20AC\uFFFD\u201A\uFFFD\u201E\u2026\u2020\u2021"~
+        "\uFFFD\u2030\u0160\u2039\u015A\u0164\u017D\u0179"~
+        "\uFFFD\u2018\u2019\u201C\u201D\u2022\u2013\u2014"~
+        "\uFFFD\u2122\u0161\u203A\u015B\u0165\u017E\u017A"~
+        "\u00A0\u02C7\u02D8\u0141\u00A4\u0104\u00A6\u00A7"~
+        "\u00A8\u00A9\u015E\u00AB\u00AC\u00AD\u00AE\u017B"~
+        "\u00B0\u00B1\u02DB\u0142\u00B4\u00B5\u00B6\u00B7"~
+        "\u00B8\u0105\u015F\u00BB\u013D\u02DD\u013E\u017C"~
+        "\u0154\u00C1\u00C2\u0102\u00C4\u0139\u0106\u00C7"~
+        "\u010C\u00C9\u0118\u00CB\u011A\u00CD\u00CE\u010E"~
+        "\u0110\u0143\u0147\u00D3\u00D4\u0150\u00D6\u00D7"~
+        "\u0158\u016E\u00DA\u0170\u00DC\u00DD\u0162\u00DF"~
+        "\u0155\u00E1\u00E2\u0103\u00E4\u013A\u0107\u00E7"~
+        "\u010D\u00E9\u0119\u00EB\u011B\u00ED\u00EE\u010F"~
+        "\u0111\u0144\u0148\u00F3\u00F4\u0151\u00F6\u00F7"~
+        "\u0159\u016F\u00FA\u0171\u00FC\u00FD\u0163\u02D9";
+
+    private immutable Tuple!(wchar, char)[] bstMap = [
+        tuple('\u011A','\xCC'), tuple('\u00DC','\xDC'), tuple('\u0179','\x8F'),
+        tuple('\u00B7','\xB7'), tuple('\u00FC','\xFC'), tuple('\u0158','\xD8'),
+        tuple('\u201C','\x93'), tuple('\u00AC','\xAC'), tuple('\u00CB','\xCB'),
+        tuple('\u00EB','\xEB'), tuple('\u010C','\xC8'), tuple('\u0143','\xD1'),
+        tuple('\u0162','\xDE'), tuple('\u02D9','\xFF'), tuple('\u2039','\x8B'),
+        tuple('\u00A7','\xA7'), tuple('\u00B1','\xB1'), tuple('\u00C2','\xC2'),
+        tuple('\u00D4','\xD4'), tuple('\u00E2','\xE2'), tuple('\u00F4','\xF4'),
+        tuple('\u0104','\xA5'), tuple('\u0110','\xD0'), tuple('\u013D','\xBC'),
+        tuple('\u0150','\xD5'), tuple('\u015E','\xAA'), tuple('\u016E','\xD9'),
+        tuple('\u017D','\x8E'), tuple('\u2014','\x97'), tuple('\u2021','\x87'),
+        tuple('\u20AC','\x80'), tuple('\u00A4','\xA4'), tuple('\u00A9','\xA9'),
+        tuple('\u00AE','\xAE'), tuple('\u00B5','\xB5'), tuple('\u00BB','\xBB'),
+        tuple('\u00C7','\xC7'), tuple('\u00CE','\xCE'), tuple('\u00D7','\xD7'),
+        tuple('\u00DF','\xDF'), tuple('\u00E7','\xE7'), tuple('\u00EE','\xEE'),
+        tuple('\u00F7','\xF7'), tuple('\u0102','\xC3'), tuple('\u0106','\xC6'),
+        tuple('\u010E','\xCF'), tuple('\u0118','\xCA'), tuple('\u0139','\xC5'),
+        tuple('\u0141','\xA3'), tuple('\u0147','\xD2'), tuple('\u0154','\xC0'),
+        tuple('\u015A','\x8C'), tuple('\u0160','\x8A'), tuple('\u0164','\x8D'),
+        tuple('\u0170','\xDB'), tuple('\u017B','\xAF'), tuple('\u02C7','\xA1'),
+        tuple('\u02DD','\xBD'), tuple('\u2019','\x92'), tuple('\u201E','\x84'),
+        tuple('\u2026','\x85'), tuple('\u203A','\x9B'), tuple('\u2122','\x99'),
+        tuple('\u00A0','\xA0'), tuple('\u00A6','\xA6'), tuple('\u00A8','\xA8'),
+        tuple('\u00AB','\xAB'), tuple('\u00AD','\xAD'), tuple('\u00B0','\xB0'),
+        tuple('\u00B4','\xB4'), tuple('\u00B6','\xB6'), tuple('\u00B8','\xB8'),
+        tuple('\u00C1','\xC1'), tuple('\u00C4','\xC4'), tuple('\u00C9','\xC9'),
+        tuple('\u00CD','\xCD'), tuple('\u00D3','\xD3'), tuple('\u00D6','\xD6'),
+        tuple('\u00DA','\xDA'), tuple('\u00DD','\xDD'), tuple('\u00E1','\xE1'),
+        tuple('\u00E4','\xE4'), tuple('\u00E9','\xE9'), tuple('\u00ED','\xED'),
+        tuple('\u00F3','\xF3'), tuple('\u00F6','\xF6'), tuple('\u00FA','\xFA'),
+        tuple('\u00FD','\xFD'), tuple('\u0103','\xE3'), tuple('\u0105','\xB9'),
+        tuple('\u0107','\xE6'), tuple('\u010D','\xE8'), tuple('\u010F','\xEF'),
+        tuple('\u0111','\xF0'), tuple('\u0119','\xEA'), tuple('\u011B','\xEC'),
+        tuple('\u013A','\xE5'), tuple('\u013E','\xBE'), tuple('\u0142','\xB3'),
+        tuple('\u0144','\xF1'), tuple('\u0148','\xF2'), tuple('\u0151','\xF5'),
+        tuple('\u0155','\xE0'), tuple('\u0159','\xF8'), tuple('\u015B','\x9C'),
+        tuple('\u015F','\xBA'), tuple('\u0161','\x9A'), tuple('\u0163','\xFE'),
+        tuple('\u0165','\x9D'), tuple('\u016F','\xF9'), tuple('\u0171','\xFB'),
+        tuple('\u017A','\x9F'), tuple('\u017C','\xBF'), tuple('\u017E','\x9E'),
+        tuple('\u02D8','\xA2'), tuple('\u02DB','\xB2'), tuple('\u2013','\x96'),
+        tuple('\u2018','\x91'), tuple('\u201A','\x82'), tuple('\u201D','\x94'),
+        tuple('\u2020','\x86'), tuple('\u2022','\x95'), tuple('\u2030','\x89')
+    ];
+
+    mixin GenericEncoder!();
+}
+
+//=============================================================================
+//          WINDOWS-1252
+//=============================================================================
+
+/// Defines a Windows1252-encoded character.
+enum Windows1252Char : ubyte { init }
+
+/**
+ * Defines an Windows1252-encoded string (as an array of $(D
+ * immutable(Windows1252Char))).
+ */
+alias Windows1252String = immutable(Windows1252Char)[];
+
+template EncoderInstance(CharType : Windows1252Char)
+{
+    import std.typecons : Tuple, tuple;
+
+    alias E = Windows1252Char;
+    alias EString = Windows1252String;
+
+    @property string encodingName()
+    {
+        return "windows-1252";
+    }
+
+    private dchar m_charMapStart = 0x80;
+    private dchar m_charMapEnd = 0x9f;
+
+    private immutable wstring charMap =
+        "\u20AC\uFFFD\u201A\u0192\u201E\u2026\u2020\u2021"~
+        "\u02C6\u2030\u0160\u2039\u0152\uFFFD\u017D\uFFFD"~
+        "\uFFFD\u2018\u2019\u201C\u201D\u2022\u2013\u2014"~
+        "\u02DC\u2122\u0161\u203A\u0153\uFFFD\u017E\u0178";
+
+    private immutable Tuple!(wchar, char)[] bstMap = [
+        tuple('\u201C','\x93'), tuple('\u0192','\x83'), tuple('\u2039','\x8B'),
+        tuple('\u0161','\x9A'), tuple('\u2014','\x97'), tuple('\u2021','\x87'),
+        tuple('\u20AC','\x80'), tuple('\u0153','\x9C'), tuple('\u017D','\x8E'),
+        tuple('\u02DC','\x98'), tuple('\u2019','\x92'), tuple('\u201E','\x84'),
+        tuple('\u2026','\x85'), tuple('\u203A','\x9B'), tuple('\u2122','\x99'),
+        tuple('\u0152','\x8C'), tuple('\u0160','\x8A'), tuple('\u0178','\x9F'),
+        tuple('\u017E','\x9E'), tuple('\u02C6','\x88'), tuple('\u2013','\x96'),
+        tuple('\u2018','\x91'), tuple('\u201A','\x82'), tuple('\u201D','\x94'),
+        tuple('\u2020','\x86'), tuple('\u2022','\x95'), tuple('\u2030','\x89')
+    ];
+
+    mixin GenericEncoder!();
+}
+
+//=============================================================================
+//          UTF-8
+//=============================================================================
+
+template EncoderInstance(CharType : char)
+{
+    alias E = char;
+    alias EString = immutable(char)[];
+
+    @property string encodingName()
+    {
+        return "UTF-8";
+    }
+
+    bool canEncode(dchar c)
+    {
+        return isValidCodePoint(c);
+    }
+
+    bool isValidCodeUnit(char c)
+    {
+        return (c < 0xC0 || (c >= 0xC2 && c < 0xF5));
+    }
+
+    immutable ubyte[128] tailTable =
+    [
+        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+        2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+        3,3,3,3,3,3,3,3,4,4,4,4,5,5,6,0,
+    ];
+
+    private int tails(char c)
+    in
+    {
+        assert(c >= 0x80);
+    }
+    body
+    {
+        return tailTable[c-0x80];
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        if (c < 0x80) return 1;
+        if (c < 0x800) return 2;
+        if (c < 0x10000) return 3;
+        return 4;
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        if (c < 0x80)
+        {
+            write(cast(char)c);
+        }
+        else if (c < 0x800)
+        {
+            write(cast(char)((c >> 6) + 0xC0));
+            write(cast(char)((c & 0x3F) + 0x80));
+        }
+        else if (c < 0x10000)
+        {
+            write(cast(char)((c >> 12) + 0xE0));
+            write(cast(char)(((c >> 6) & 0x3F) + 0x80));
+            write(cast(char)((c & 0x3F) + 0x80));
+        }
+        else
+        {
+            write(cast(char)((c >> 18) + 0xF0));
+            write(cast(char)(((c >> 12) & 0x3F) + 0x80));
+            write(cast(char)(((c >> 6) & 0x3F) + 0x80));
+            write(cast(char)((c & 0x3F) + 0x80));
+        }
+    }
+
+    void skipViaRead()()
+    {
+        auto c = read();
+        if (c < 0xC0) return;
+        int n = tails(cast(char) c);
+        for (size_t i=0; i<n; ++i)
+        {
+            read();
+        }
+    }
+
+    dchar decodeViaRead()()
+    {
+        dchar c = read();
+        if (c < 0xC0) return c;
+        int n = tails(cast(char) c);
+        c &= (1 << (6 - n)) - 1;
+        for (size_t i=0; i<n; ++i)
+        {
+            c = (c << 6) + (read() & 0x3F);
+        }
+        return c;
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        dchar c = read();
+        if (c < 0x80) return c;
+        int n = tails(cast(char) c);
+        if (n == 0) return INVALID_SEQUENCE;
+
+        if (!canRead) return INVALID_SEQUENCE;
+        size_t d = peek();
+        bool err =
+        (
+            (c < 0xC2)                              // fail overlong 2-byte sequences
+        ||  (c > 0xF4)                              // fail overlong 4-6-byte sequences
+        ||  (c == 0xE0 && ((d & 0xE0) == 0x80))     // fail overlong 3-byte sequences
+        ||  (c == 0xED && ((d & 0xE0) == 0xA0))     // fail surrogates
+        ||  (c == 0xF0 && ((d & 0xF0) == 0x80))     // fail overlong 4-byte sequences
+        ||  (c == 0xF4 && ((d & 0xF0) >= 0x90))     // fail code points > 0x10FFFF
+        );
+
+        c &= (1 << (6 - n)) - 1;
+        for (size_t i=0; i<n; ++i)
+        {
+            if (!canRead) return INVALID_SEQUENCE;
+            d = peek();
+            if ((d & 0xC0) != 0x80) return INVALID_SEQUENCE;
+            c = (c << 6) + (read() & 0x3F);
+        }
+
+        return err ? INVALID_SEQUENCE : c;
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        dchar c = read();
+        if (c < 0x80) return c;
+        size_t shift = 0;
+        c &= 0x3F;
+        for (size_t i=0; i<4; ++i)
+        {
+            shift += 6;
+            auto d = read();
+            size_t n = tails(cast(char) d);
+            size_t mask = n == 0 ? 0x3F : (1 << (6 - n)) - 1;
+            c += ((d & mask) << shift);
+            if (n != 0) break;
+        }
+        return c;
+    }
+
+    @property EString replacementSequence()
+    {
+        return "\uFFFD";
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+//          UTF-16
+//=============================================================================
+
+template EncoderInstance(CharType : wchar)
+{
+    alias E = wchar;
+    alias EString = immutable(wchar)[];
+
+    @property string encodingName()
+    {
+        return "UTF-16";
+    }
+
+    bool canEncode(dchar c)
+    {
+        return isValidCodePoint(c);
+    }
+
+    bool isValidCodeUnit(wchar c)
+    {
+        return true;
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        return (c < 0x10000) ? 1 : 2;
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        if (c < 0x10000)
+        {
+            write(cast(wchar)c);
+        }
+        else
+        {
+            size_t n = c - 0x10000;
+            write(cast(wchar)(0xD800 + (n >> 10)));
+            write(cast(wchar)(0xDC00 + (n & 0x3FF)));
+        }
+    }
+
+    void skipViaRead()()
+    {
+        wchar c = read();
+        if (c < 0xD800 || c >= 0xE000) return;
+        read();
+    }
+
+    dchar decodeViaRead()()
+    {
+        wchar c = read();
+        if (c < 0xD800 || c >= 0xE000) return cast(dchar)c;
+        wchar d = read();
+        c &= 0x3FF;
+        d &= 0x3FF;
+        return 0x10000 + (c << 10) + d;
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        wchar c = read();
+        if (c < 0xD800 || c >= 0xE000) return cast(dchar)c;
+        if (c >= 0xDC00) return INVALID_SEQUENCE;
+        if (!canRead) return INVALID_SEQUENCE;
+        wchar d = peek();
+        if (d < 0xDC00 || d >= 0xE000) return INVALID_SEQUENCE;
+        d = read();
+        c &= 0x3FF;
+        d &= 0x3FF;
+        return 0x10000 + (c << 10) + d;
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        wchar c = read();
+        if (c < 0xD800 || c >= 0xE000) return cast(dchar)c;
+        wchar d = read();
+        c &= 0x3FF;
+        d &= 0x3FF;
+        return 0x10000 + (d << 10) + c;
+    }
+
+    @property EString replacementSequence()
+    {
+        return "\uFFFD"w;
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+//          UTF-32
+//=============================================================================
+
+template EncoderInstance(CharType : dchar)
+{
+    alias E = dchar;
+    alias EString = immutable(dchar)[];
+
+    @property string encodingName()
+    {
+        return "UTF-32";
+    }
+
+    bool canEncode(dchar c)
+    {
+        return isValidCodePoint(c);
+    }
+
+    bool isValidCodeUnit(dchar c)
+    {
+        return isValidCodePoint(c);
+    }
+
+    size_t encodedLength(dchar c)
+    in
+    {
+        assert(canEncode(c));
+    }
+    body
+    {
+        return 1;
+    }
+
+    void encodeViaWrite()(dchar c)
+    {
+        write(c);
+    }
+
+    void skipViaRead()()
+    {
+        read();
+    }
+
+    dchar decodeViaRead()()
+    {
+        return cast(dchar)read();
+    }
+
+    dchar safeDecodeViaRead()()
+    {
+        dchar c = read();
+        return isValidCodePoint(c) ? c : INVALID_SEQUENCE;
+    }
+
+    dchar decodeReverseViaRead()()
+    {
+        return cast(dchar)read();
+    }
+
+    @property EString replacementSequence()
+    {
+        return "\uFFFD"d;
+    }
+
+    mixin EncoderFunctions;
+}
+
+//=============================================================================
+// Below are forwarding functions which expose the function to the user
+
+/**
+Returns true if c is a valid code point
+
+ Note that this includes the non-character code points U+FFFE and U+FFFF,
+ since these are valid code points (even though they are not valid
+ characters).
+
+ Supersedes:
+ This function supersedes $(D std.utf.startsValidDchar()).
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c = the code point to be tested
+ */
+bool isValidCodePoint(dchar c)
+{
+    return c < 0xD800 || (c >= 0xE000 && c < 0x110000);
+}
+
+/**
+ Returns the name of an encoding.
+
+ The type of encoding cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding type.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+ */
+@property string encodingName(T)()
+{
+    return EncoderInstance!(T).encodingName;
+}
+
+///
+unittest
+{
+    assert(encodingName!(char) == "UTF-8");
+    assert(encodingName!(wchar) == "UTF-16");
+    assert(encodingName!(dchar) == "UTF-32");
+    assert(encodingName!(AsciiChar) == "ASCII");
+    assert(encodingName!(Latin1Char) == "ISO-8859-1");
+    assert(encodingName!(Latin2Char) == "ISO-8859-2");
+    assert(encodingName!(Windows1250Char) == "windows-1250");
+    assert(encodingName!(Windows1252Char) == "windows-1252");
+}
+
+/**
+ Returns true iff it is possible to represent the specified codepoint
+ in the encoding.
+
+ The type of encoding cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding type.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+ */
+bool canEncode(E)(dchar c)
+{
+    return EncoderInstance!(E).canEncode(c);
+}
+
+///
+unittest
+{
+    assert( canEncode!(Latin1Char)('A'));
+    assert( canEncode!(Latin2Char)('A'));
+    assert(!canEncode!(AsciiChar)('\u00A0'));
+    assert( canEncode!(Latin1Char)('\u00A0'));
+    assert( canEncode!(Latin2Char)('\u00A0'));
+    assert( canEncode!(Windows1250Char)('\u20AC'));
+    assert(!canEncode!(Windows1250Char)('\u20AD'));
+    assert(!canEncode!(Windows1250Char)('\uFFFD'));
+    assert( canEncode!(Windows1252Char)('\u20AC'));
+    assert(!canEncode!(Windows1252Char)('\u20AD'));
+    assert(!canEncode!(Windows1252Char)('\uFFFD'));
+    assert(!canEncode!(char)(cast(dchar)0x110000));
+}
+
+/**
+ Returns true if the code unit is legal. For example, the byte 0x80 would
+ not be legal in ASCII, because ASCII code units must always be in the range
+ 0x00 to 0x7F.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c = the code unit to be tested
+ */
+bool isValidCodeUnit(E)(E c)
+{
+    return EncoderInstance!(E).isValidCodeUnit(c);
+}
+
+///
+unittest
+{
+    assert(!isValidCodeUnit(cast(char)0xC0));
+    assert(!isValidCodeUnit(cast(char)0xFF));
+    assert( isValidCodeUnit(cast(wchar)0xD800));
+    assert(!isValidCodeUnit(cast(dchar)0xD800));
+    assert(!isValidCodeUnit(cast(AsciiChar)0xA0));
+    assert( isValidCodeUnit(cast(Windows1250Char)0x80));
+    assert(!isValidCodeUnit(cast(Windows1250Char)0x81));
+    assert( isValidCodeUnit(cast(Windows1252Char)0x80));
+    assert(!isValidCodeUnit(cast(Windows1252Char)0x81));
+}
+
+/**
+ Returns true if the string is encoded correctly
+
+ Supersedes:
+ This function supersedes std.utf.validate(), however note that this
+ function returns a bool indicating whether the input was valid or not,
+ whereas the older function would throw an exception.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be tested
+ */
+bool isValid(E)(const(E)[] s)
+{
+    return s.length == validLength(s);
+}
+
+///
+unittest
+{
+    assert( isValid("\u20AC100"));
+    assert(!isValid(cast(char[3])[167, 133, 175]));
+}
+
+/**
+ Returns the length of the longest possible substring, starting from
+ the first code unit, which is validly encoded.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be tested
+ */
+size_t validLength(E)(const(E)[] s)
+{
+    size_t result, before = void;
+    while ((before = s.length) > 0)
+    {
+        if (EncoderInstance!(E).safeDecode(s) == INVALID_SEQUENCE)
+            break;
+        result += before - s.length;
+    }
+    return result;
+}
+
+/**
+ Sanitizes a string by replacing malformed code unit sequences with valid
+ code unit sequences. The result is guaranteed to be valid for this encoding.
+
+ If the input string is already valid, this function returns the original,
+ otherwise it constructs a new string by replacing all illegal code unit
+ sequences with the encoding's replacement character, Invalid sequences will
+ be replaced with the Unicode replacement character (U+FFFD) if the
+ character repertoire contains it, otherwise invalid sequences will be
+ replaced with '?'.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be sanitized
+ */
+immutable(E)[] sanitize(E)(immutable(E)[] s)
+{
+    size_t n = validLength(s);
+    if (n == s.length) return s;
+
+    auto repSeq = EncoderInstance!(E).replacementSequence;
+
+    // Count how long the string needs to be.
+    // Overestimating is not a problem
+    size_t len = s.length;
+    const(E)[] t = s[n..$];
+    while (t.length != 0)
+    {
+        dchar c = EncoderInstance!(E).safeDecode(t);
+        assert(c == INVALID_SEQUENCE);
+        len += repSeq.length;
+        t = t[validLength(t)..$];
+    }
+
+    // Now do the write
+    E[] array = new E[len];
+    array[0..n] = s[0..n];
+    size_t offset = n;
+
+    t = s[n..$];
+    while (t.length != 0)
+    {
+        dchar c = EncoderInstance!(E).safeDecode(t);
+        assert(c == INVALID_SEQUENCE);
+        array[offset..offset+repSeq.length] = repSeq[];
+        offset += repSeq.length;
+        n = validLength(t);
+        array[offset..offset+n] = t[0..n];
+        offset += n;
+        t = t[n..$];
+    }
+    return cast(immutable(E)[])array[0..offset];
+}
+
+///
+unittest
+{
+    assert(sanitize("hello \xF0\x80world") == "hello \xEF\xBF\xBDworld");
+}
+
+/**
+ Returns the length of the first encoded sequence.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+ s = the string to be sliced
+ */
+size_t firstSequence(E)(const(E)[] s)
+in
+{
+    assert(s.length != 0);
+    const(E)[] u = s;
+    assert(safeDecode(u) != INVALID_SEQUENCE);
+}
+body
+{
+    auto before = s.length;
+    EncoderInstance!(E).skip(s);
+    return before - s.length;
+}
+
+///
+unittest
+{
+    assert(firstSequence("\u20AC1000") == "\u20AC".length);
+    assert(firstSequence("hel") == "h".length);
+}
+
+/**
+ Returns the length of the last encoded sequence.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be sliced
+ */
+size_t lastSequence(E)(const(E)[] s)
+in
+{
+    assert(s.length != 0);
+    assert(isValid(s));
+}
+body
+{
+    const(E)[] t = s;
+    EncoderInstance!(E).decodeReverse(s);
+    return t.length - s.length;
+}
+
+///
+unittest
+{
+    assert(lastSequence("1000\u20AC") == "\u20AC".length);
+    assert(lastSequence("hellö") == "ö".length);
+}
+
+/**
+ Returns the array index at which the (n+1)th code point begins.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ Supersedes:
+ This function supersedes std.utf.toUTFindex().
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be counted
+    n = the current code point index
+ */
+ptrdiff_t index(E)(const(E)[] s,int n)
+in
+{
+    assert(isValid(s));
+    assert(n >= 0);
+}
+body
+{
+    const(E)[] t = s;
+    for (size_t i=0; i<n; ++i) EncoderInstance!(E).skip(s);
+    return t.length - s.length;
+}
+
+///
+unittest
+{
+    assert(index("\u20AC100",1) == 3);
+    assert(index("hällo",2) == 3);
+}
+
+/**
+ Decodes a single code point.
+
+ This function removes one or more code units from the start of a string,
+ and returns the decoded code point which those code units represent.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ Supersedes:
+ This function supersedes std.utf.decode(), however, note that the
+ function codePoints() supersedes it more conveniently.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string whose first code point is to be decoded
+ */
+dchar decode(S)(ref S s)
+in
+{
+    assert(s.length != 0);
+    auto u = s;
+    assert(safeDecode(u) != INVALID_SEQUENCE);
+}
+body
+{
+    return EncoderInstance!(typeof(s[0])).decode(s);
+}
+
+/**
+ Decodes a single code point from the end of a string.
+
+ This function removes one or more code units from the end of a string,
+ and returns the decoded code point which those code units represent.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string whose first code point is to be decoded
+ */
+dchar decodeReverse(E)(ref const(E)[] s)
+in
+{
+    assert(s.length != 0);
+    assert(isValid(s));
+}
+body
+{
+    return EncoderInstance!(E).decodeReverse(s);
+}
+
+/**
+ Decodes a single code point. The input does not have to be valid.
+
+ This function removes one or more code units from the start of a string,
+ and returns the decoded code point which those code units represent.
+
+ This function will accept an invalidly encoded string as input.
+ If an invalid sequence is found at the start of the string, this
+ function will remove it, and return the value INVALID_SEQUENCE.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string whose first code point is to be decoded
+ */
+dchar safeDecode(S)(ref S s)
+in
+{
+    assert(s.length != 0);
+}
+body
+{
+    return EncoderInstance!(typeof(s[0])).safeDecode(s);
+}
+
+/**
+ Returns the number of code units required to encode a single code point.
+
+ The input to this function MUST be a valid code point.
+ This is enforced by the function's in-contract.
+
+ The type of the output cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding as a template parameter.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c = the code point to be encoded
+ */
+size_t encodedLength(E)(dchar c)
+in
+{
+    assert(isValidCodePoint(c));
+}
+body
+{
+    return EncoderInstance!(E).encodedLength(c);
+}
+
+/**
+ Encodes a single code point.
+
+ This function encodes a single code point into one or more code units.
+ It returns a string containing those code units.
+
+ The input to this function MUST be a valid code point.
+ This is enforced by the function's in-contract.
+
+ The type of the output cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding as a template parameter.
+
+ Supersedes:
+ This function supersedes std.utf.encode(), however, note that the
+ function codeUnits() supersedes it more conveniently.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c = the code point to be encoded
+ */
+E[] encode(E)(dchar c)
+in
+{
+    assert(isValidCodePoint(c));
+}
+body
+{
+    return EncoderInstance!(E).encode(c);
+}
+
+/**
+ Encodes a single code point into an array.
+
+ This function encodes a single code point into one or more code units
+ The code units are stored in a user-supplied fixed-size array,
+ which must be passed by reference.
+
+ The input to this function MUST be a valid code point.
+ This is enforced by the function's in-contract.
+
+ The type of the output cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding as a template parameter.
+
+ Supersedes:
+ This function supersedes std.utf.encode(), however, note that the
+ function codeUnits() supersedes it more conveniently.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c     = the code point to be encoded
+    array = the destination array
+
+ Returns:
+          the number of code units written to the array
+ */
+size_t encode(E)(dchar c, E[] array)
+in
+{
+    assert(isValidCodePoint(c));
+}
+body
+{
+    E[] t = array;
+    EncoderInstance!(E).encode(c,t);
+    return array.length - t.length;
+}
+
+// /**
+//  * Encodes a single code point into a Buffer.
+//  *
+//  * This function encodes a single code point into one or more code units
+//  * The code units are stored in a growable buffer.
+//  *
+//  * The input to this function MUST be a valid code point.
+//  * This is enforced by the function's in-contract.
+//  *
+//  * The type of the output cannot be deduced. Therefore, it is necessary to
+//  * explicitly specify the encoding as a template parameter.
+//  *
+//  * Supersedes:
+//  * This function supersedes std.utf.encode(), however, note that the
+//  * function codeUnits() supersedes it more conveniently.
+//  *
+//  * Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252
+//  *
+//  * Params:
+//  *    c = the code point to be encoded
+//  */
+// deprecated void encode(E)(dchar c, ref Buffer!(E) buffer)
+// in
+// {
+//     assert(isValidCodePoint(c));
+// }
+// body
+// {
+//     EncoderInstance!(E).encode(c,buffer);
+// }
+
+/*
+Encodes $(D c) in units of type $(D E) and writes the result to the
+output range $(D R). Returns the number of $(D E)s written.
+ */
+size_t encode(E, R)(dchar c, auto ref R range)
+if (isNativeOutputRange!(R, E))
+{
+    static if (is(Unqual!E == char))
+    {
+        if (c <= 0x7F)
+        {
+            put(range, cast(char) c);
+            return 1;
+        }
+        if (c <= 0x7FF)
+        {
+            put(range, cast(char)(0xC0 | (c >> 6)));
+            put(range, cast(char)(0x80 | (c & 0x3F)));
+            return 2;
+        }
+        if (c <= 0xFFFF)
+        {
+            put(range, cast(char)(0xE0 | (c >> 12)));
+            put(range, cast(char)(0x80 | ((c >> 6) & 0x3F)));
+            put(range, cast(char)(0x80 | (c & 0x3F)));
+            return 3;
+        }
+        if (c <= 0x10FFFF)
+        {
+            put(range, cast(char)(0xF0 | (c >> 18)));
+            put(range, cast(char)(0x80 | ((c >> 12) & 0x3F)));
+            put(range, cast(char)(0x80 | ((c >> 6) & 0x3F)));
+            put(range, cast(char)(0x80 | (c & 0x3F)));
+            return 4;
+        }
+        else
+        {
+            assert(0);
+        }
+    }
+    else static if (is(Unqual!E == wchar))
+    {
+        if (c <= 0xFFFF)
+        {
+            range.put(cast(wchar) c);
+            return 1;
+        }
+        range.put(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800));
+        range.put(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00));
+        return 2;
+    }
+    else static if (is(Unqual!E == dchar))
+    {
+        range.put(c);
+        return 1;
+    }
+    else
+    {
+        static assert(0);
+    }
+}
+
+unittest
+{
+    import std.array;
+    Appender!(char[]) r;
+    assert(encode!(char)('T', r) == 1);
+    assert(encode!(wchar)('T', r) == 1);
+    assert(encode!(dchar)('T', r) == 1);
+}
+
+/**
+ Encodes a single code point to a delegate.
+
+ This function encodes a single code point into one or more code units.
+ The code units are passed one at a time to the supplied delegate.
+
+ The input to this function MUST be a valid code point.
+ This is enforced by the function's in-contract.
+
+ The type of the output cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding as a template parameter.
+
+ Supersedes:
+ This function supersedes std.utf.encode(), however, note that the
+ function codeUnits() supersedes it more conveniently.
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c  = the code point to be encoded
+    dg = the delegate to invoke for each code unit
+ */
+void encode(E)(dchar c, void delegate(E) dg)
+in
+{
+    assert(isValidCodePoint(c));
+}
+body
+{
+    EncoderInstance!(E).encode(c,dg);
+}
+
+/**
+Encodes the contents of $(D s) in units of type $(D Tgt), writing the result to an
+output range.
+
+Returns: The number of $(D Tgt) elements written.
+Params:
+Tgt = Element type of $(D range).
+s = Input array.
+range = Output range.
+ */
+size_t encode(Tgt, Src, R)(in Src[] s, R range)
+{
+    size_t result;
+    foreach (c; s)
+    {
+        result += encode!(Tgt)(c, range);
+    }
+    return result;
+}
+
+/**
+ Returns a foreachable struct which can bidirectionally iterate over all
+ code points in a string.
+
+ The input to this function MUST be validly encoded.
+ This is enforced by the function's in-contract.
+
+ You can foreach either
+ with or without an index. If an index is specified, it will be initialized
+ at each iteration with the offset into the string at which the code point
+ begins.
+
+ Supersedes:
+ This function supersedes std.utf.decode().
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = the string to be decoded
+
+ Example:
+ --------------------------------------------------------
+ string s = "hello world";
+ foreach(c;codePoints(s))
+ {
+     // do something with c (which will always be a dchar)
+ }
+ --------------------------------------------------------
+
+ Note that, currently, foreach(c:codePoints(s)) is superior to foreach(c;s)
+ in that the latter will fall over on encountering U+FFFF.
+ */
+CodePoints!(E) codePoints(E)(immutable(E)[] s)
+in
+{
+    assert(isValid(s));
+}
+body
+{
+    return CodePoints!(E)(s);
+}
+
+///
+unittest
+{
+    string s = "hello";
+    string t;
+    foreach(c;codePoints(s))
+    {
+        t ~= cast(char)c;
+    }
+    assert(s == t);
+}
+
+/**
+ Returns a foreachable struct which can bidirectionally iterate over all
+ code units in a code point.
+
+ The input to this function MUST be a valid code point.
+ This is enforced by the function's in-contract.
+
+ The type of the output cannot be deduced. Therefore, it is necessary to
+ explicitly specify the encoding type in the template parameter.
+
+ Supersedes:
+ This function supersedes std.utf.encode().
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    c = the code point to be encoded
+ */
+CodeUnits!(E) codeUnits(E)(dchar c)
+in
+{
+    assert(isValidCodePoint(c));
+}
+body
+{
+    return CodeUnits!(E)(c);
+}
+
+///
+unittest
+{
+    char[] a;
+    foreach(c;codeUnits!(char)(cast(dchar)'\u20AC'))
+    {
+        a ~= c;
+    }
+    assert(a.length == 3);
+    assert(a[0] == 0xE2);
+    assert(a[1] == 0x82);
+    assert(a[2] == 0xAC);
+}
+
+/**
+ Convert a string from one encoding to another.
+
+ Supersedes:
+ This function supersedes std.utf.toUTF8(), std.utf.toUTF16() and
+ std.utf.toUTF32()
+ (but note that to!() supersedes it more conveniently).
+
+ Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250,
+ WINDOWS-1252
+
+ Params:
+    s = Source string. $(B Must) be validly encoded.
+        This is enforced by the function's in-contract.
+    r = Destination string
+
+ See_Also:
+    $(XREF conv, to)
+ */
+void transcode(Src,Dst)(immutable(Src)[] s,out immutable(Dst)[] r)
+in
+{
+    assert(isValid(s));
+}
+body
+{
+    static if(is(Src==Dst))
+    {
+        r = s;
+    }
+    else static if(is(Src==AsciiChar))
+    {
+        transcode!(char,Dst)(cast(string)s,r);
+    }
+    else
+    {
+        static if(is(Dst == wchar))
+        {
+            immutable minReservePlace = 2;
+        }
+        else static if(is(Dst == dchar))
+        {
+            immutable minReservePlace = 1;
+        }
+        else
+        {
+            immutable minReservePlace = 6;
+        }
+
+        Dst[] buffer = new Dst[s.length];
+        Dst[] tmpBuffer = buffer;
+        const(Src)[] t = s;
+
+        while (t.length != 0)
+        {
+            if(tmpBuffer.length < minReservePlace)
+            {
+                size_t prevLength = buffer.length;
+                buffer.length += t.length + minReservePlace;
+                tmpBuffer = buffer[prevLength - tmpBuffer.length .. $];
+            }
+            EncoderInstance!(Dst).encode(decode(t), tmpBuffer);
+        }
+
+        r = cast(immutable)buffer[0 .. buffer.length - tmpBuffer.length];
+    }
+}
+
+///
+unittest
+{
+    wstring ws;
+    // transcode from UTF-8 to UTF-16
+    transcode("hello world",ws);
+    assert(ws == "hello world"w);
+
+    Latin1String ls;
+    // transcode from UTF-16 to ISO-8859-1
+    transcode(ws, ls);
+    assert(ws == "hello world");
+}
+
+unittest
+{
+    import std.meta;
+    import std.range;
+    {
+        import std.conv : to;
+
+        string asciiCharString = to!string(iota(0, 128, 1));
+
+        alias Types = AliasSeq!(string, Latin1String, Latin2String, AsciiString, Windows1250String, Windows1252String, dstring, wstring);
+        foreach(S; Types)
+            foreach(D; Types)
+            {
+                string str;
+                S sStr;
+                D dStr;
+                transcode(asciiCharString, sStr);
+                transcode(sStr, dStr);
+                transcode(dStr, str);
+                assert(asciiCharString == str);
+            }
+    }
+    {
+        string czechChars = "Příliš žluťoučký kůň úpěl ďábelské ódy.";
+        alias Types = AliasSeq!(string, dstring, wstring);
+        foreach(S; Types)
+            foreach(D; Types)
+            {
+                string str;
+                S sStr;
+                D dStr;
+                transcode(czechChars, sStr);
+                transcode(sStr, dStr);
+                transcode(dStr, str);
+                assert(czechChars == str);
+            }
+    }
+}
+
+//=============================================================================
+
+/** The base class for exceptions thrown by this module */
+class EncodingException : Exception { this(string msg) { super(msg); } }
+
+class UnrecognizedEncodingException : EncodingException
+{
+    private this(string msg) { super(msg); }
+}
+
+/** Abstract base class of all encoding schemes */
+abstract class EncodingScheme
+{
+    import std.uni : toLower;
+
+    /**
+     * Registers a subclass of EncodingScheme.
+     *
+     * This function allows user-defined subclasses of EncodingScheme to
+     * be declared in other modules.
+     *
+     * Example:
+     * ----------------------------------------------
+     * class Amiga1251 : EncodingScheme
+     * {
+     *     shared static this()
+     *     {
+     *         EncodingScheme.register("path.to.Amiga1251");
+     *     }
+     * }
+     * ----------------------------------------------
+     */
+    static void register(string className)
+    {
+        auto scheme = cast(EncodingScheme)ClassInfo.find(className).create();
+        if (scheme is null)
+            throw new EncodingException("Unable to create class "~className);
+        foreach(encodingName;scheme.names())
+        {
+            supported[toLower(encodingName)] = className;
+        }
+    }
+
+    /**
+     * Obtains a subclass of EncodingScheme which is capable of encoding
+     * and decoding the named encoding scheme.
+     *
+     * This function is only aware of EncodingSchemes which have been
+     * registered with the register() function.
+     *
+     * Example:
+     * ---------------------------------------------------
+     * auto scheme = EncodingScheme.create("Amiga-1251");
+     * ---------------------------------------------------
+     */
+    static EncodingScheme create(string encodingName)
+    {
+        auto p = toLower(encodingName) in supported;
+        if (p is null)
+            throw new EncodingException("Unrecognized Encoding: "~encodingName);
+        string className = *p;
+        auto scheme = cast(EncodingScheme)ClassInfo.find(className).create();
+        if (scheme is null) throw new EncodingException("Unable to create class "~className);
+        return scheme;
+    }
+
+    const
+    {
+        /**
+         * Returns the standard name of the encoding scheme
+         */
+        abstract override string toString();
+
+        /**
+         * Returns an array of all known names for this encoding scheme
+         */
+        abstract string[] names();
+
+        /**
+         * Returns true if the character c can be represented
+         * in this encoding scheme.
+         */
+        abstract bool canEncode(dchar c);
+
+        /**
+         * Returns the number of ubytes required to encode this code point.
+         *
+         * The input to this function MUST be a valid code point.
+         *
+         * Params:
+         *    c = the code point to be encoded
+         *
+         * Returns:
+         *    the number of ubytes required.
+         */
+        abstract size_t encodedLength(dchar c);
+
+        /**
+         * Encodes a single code point into a user-supplied, fixed-size buffer.
+         *
+         * This function encodes a single code point into one or more ubytes.
+         * The supplied buffer must be code unit aligned.
+         * (For example, UTF-16LE or UTF-16BE must be wchar-aligned,
+         * UTF-32LE or UTF-32BE must be dchar-aligned, etc.)
+         *
+         * The input to this function MUST be a valid code point.
+         *
+         * Params:
+         *    c      = the code point to be encoded
+         *    buffer = the destination array
+         *
+         * Returns:
+         *    the number of ubytes written.
+         */
+        abstract size_t encode(dchar c, ubyte[] buffer);
+
+        /**
+         * Decodes a single code point.
+         *
+         * This function removes one or more ubytes from the start of an array,
+         * and returns the decoded code point which those ubytes represent.
+         *
+         * The input to this function MUST be validly encoded.
+         *
+         * Params:
+         *    s = the array whose first code point is to be decoded
+         */
+        abstract dchar decode(ref const(ubyte)[] s);
+
+        /**
+         * Decodes a single code point. The input does not have to be valid.
+         *
+         * This function removes one or more ubytes from the start of an array,
+         * and returns the decoded code point which those ubytes represent.
+         *
+         * This function will accept an invalidly encoded array as input.
+         * If an invalid sequence is found at the start of the string, this
+         * function will remove it, and return the value INVALID_SEQUENCE.
+         *
+         * Params:
+         *    s = the array whose first code point is to be decoded
+         */
+        abstract dchar safeDecode(ref const(ubyte)[] s);
+
+        /**
+         * Returns the sequence of ubytes to be used to represent
+         * any character which cannot be represented in the encoding scheme.
+         *
+         * Normally this will be a representation of some substitution
+         * character, such as U+FFFD or '?'.
+         */
+        abstract @property immutable(ubyte)[] replacementSequence();
+    }
+
+    /**
+     * Returns true if the array is encoded correctly
+     *
+     * Params:
+     *    s = the array to be tested
+     */
+    bool isValid(const(ubyte)[] s)
+    {
+        while (s.length != 0)
+        {
+            dchar d = safeDecode(s);
+            if (d == INVALID_SEQUENCE)
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the length of the longest possible substring, starting from
+     * the first element, which is validly encoded.
+     *
+     * Params:
+     *    s = the array to be tested
+     */
+    size_t validLength(const(ubyte)[] s)
+    {
+        const(ubyte)[] r = s;
+        const(ubyte)[] t = s;
+        while (s.length != 0)
+        {
+            if (safeDecode(s) == INVALID_SEQUENCE) break;
+            t = s;
+        }
+        return r.length - t.length;
+    }
+
+    /**
+     * Sanitizes an array by replacing malformed ubyte sequences with valid
+     * ubyte sequences. The result is guaranteed to be valid for this
+     * encoding scheme.
+     *
+     * If the input array is already valid, this function returns the
+     * original, otherwise it constructs a new array by replacing all illegal
+     * sequences with the encoding scheme's replacement sequence.
+     *
+     * Params:
+     *    s = the string to be sanitized
+     */
+    immutable(ubyte)[] sanitize(immutable(ubyte)[] s)
+    {
+        auto n = validLength(s);
+        if (n == s.length) return s;
+
+        auto repSeq = replacementSequence;
+
+        // Count how long the string needs to be.
+        // Overestimating is not a problem
+        auto len = s.length;
+        const(ubyte)[] t = s[n..$];
+        while (t.length != 0)
+        {
+            dchar c = safeDecode(t);
+            assert(c == INVALID_SEQUENCE);
+            len += repSeq.length;
+            t = t[validLength(t)..$];
+        }
+
+        // Now do the write
+        ubyte[] array = new ubyte[len];
+        array[0..n] = s[0..n];
+        auto offset = n;
+
+        t = s[n..$];
+        while (t.length != 0)
+        {
+            dchar c = safeDecode(t);
+            assert(c == INVALID_SEQUENCE);
+            array[offset..offset+repSeq.length] = repSeq[];
+            offset += repSeq.length;
+            n = validLength(t);
+            array[offset..offset+n] = t[0..n];
+            offset += n;
+            t = t[n..$];
+        }
+        return cast(immutable(ubyte)[])array[0..offset];
+    }
+
+    /**
+     * Returns the length of the first encoded sequence.
+     *
+     * The input to this function MUST be validly encoded.
+     * This is enforced by the function's in-contract.
+     *
+     * Params:
+     *    s = the array to be sliced
+     */
+    size_t firstSequence(const(ubyte)[] s)
+    in
+    {
+        assert(s.length != 0);
+        const(ubyte)[] u = s;
+        assert(safeDecode(u) != INVALID_SEQUENCE);
+    }
+    body
+    {
+        const(ubyte)[] t = s;
+        decode(s);
+        return t.length - s.length;
+    }
+
+    /**
+     * Returns the total number of code points encoded in a ubyte array.
+     *
+     * The input to this function MUST be validly encoded.
+     * This is enforced by the function's in-contract.
+     *
+     * Params:
+     *    s = the string to be counted
+     */
+    size_t count(const(ubyte)[] s)
+    in
+    {
+        assert(isValid(s));
+    }
+    body
+    {
+        size_t n = 0;
+        while (s.length != 0)
+        {
+            decode(s);
+            ++n;
+        }
+        return n;
+    }
+
+    /**
+     * Returns the array index at which the (n+1)th code point begins.
+     *
+     * The input to this function MUST be validly encoded.
+     * This is enforced by the function's in-contract.
+     *
+     * Params:
+     *    s = the string to be counted
+     *    n = the current code point index
+     */
+    ptrdiff_t index(const(ubyte)[] s, size_t n)
+    in
+    {
+        assert(isValid(s));
+        assert(n >= 0);
+    }
+    body
+    {
+        const(ubyte)[] t = s;
+        for (size_t i=0; i<n; ++i) decode(s);
+        return t.length - s.length;
+    }
+
+    __gshared string[string] supported;
+}
+
+/**
+ EncodingScheme to handle ASCII
+
+ This scheme recognises the following names:
+                 "ANSI_X3.4-1968",
+                 "ANSI_X3.4-1986",
+                 "ASCII",
+                 "IBM367",
+                 "ISO646-US",
+                 "ISO_646.irv:1991",
+                 "US-ASCII",
+                 "cp367",
+                 "csASCII"
+                 "iso-ir-6",
+                 "us"
+ */
+class EncodingSchemeASCII : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeASCII");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "ANSI_X3.4-1968",
+                "ANSI_X3.4-1986",
+                "ASCII",
+                "IBM367",
+                "ISO646-US",
+                "ISO_646.irv:1991",
+                "US-ASCII",
+                "cp367",
+                "csASCII",
+                "iso-ir-6",
+                "us"
+            ];
+        }
+
+        override string toString()
+        {
+            return "ASCII";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(AsciiChar)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(AsciiChar)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(AsciiChar[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(AsciiChar)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(AsciiChar)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"?";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle Latin-1
+
+ This scheme recognises the following names:
+                 "CP819",
+                 "IBM819",
+                 "ISO-8859-1",
+                 "ISO_8859-1",
+                 "ISO_8859-1:1987",
+                 "csISOLatin1",
+                 "iso-ir-100",
+                 "l1",
+                 "latin1"
+ */
+class EncodingSchemeLatin1 : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeLatin1");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "CP819",
+                "IBM819",
+                "ISO-8859-1",
+                "ISO_8859-1",
+                "ISO_8859-1:1987",
+                "csISOLatin1",
+                "iso-ir-100",
+                "l1",
+                "latin1"
+            ];
+        }
+
+        override string toString()
+        {
+            return "ISO-8859-1";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(Latin1Char)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(Latin1Char)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(Latin1Char[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Latin1Char)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Latin1Char)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"?";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle Latin-2
+
+ This scheme recognises the following names:
+                 "Latin 2",
+                 "ISO-8859-2",
+                 "ISO_8859-2",
+                 "ISO_8859-2:1999",
+                 "Windows-28592"
+ */
+class EncodingSchemeLatin2 : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeLatin2");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "Latin 2",
+                "ISO-8859-2",
+                "ISO_8859-2",
+                "ISO_8859-2:1999",
+                "windows-28592"
+            ];
+        }
+
+        override string toString()
+        {
+            return "ISO-8859-2";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(Latin2Char)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(Latin2Char)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(Latin2Char[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Latin2Char)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Latin2Char)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"?";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle Windows-1250
+
+ This scheme recognises the following names:
+                 "windows-1250"
+ */
+class EncodingSchemeWindows1250 : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeWindows1250");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "windows-1250"
+            ];
+        }
+
+        override string toString()
+        {
+            return "windows-1250";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(Windows1250Char)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(Windows1250Char)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(Windows1250Char[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Windows1250Char)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Windows1250Char)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"?";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle Windows-1252
+
+ This scheme recognises the following names:
+                 "windows-1252"
+ */
+class EncodingSchemeWindows1252 : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeWindows1252");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "windows-1252"
+            ];
+        }
+
+        override string toString()
+        {
+            return "windows-1252";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(Windows1252Char)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(Windows1252Char)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(Windows1252Char[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Windows1252Char)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(Windows1252Char)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"?";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle UTF-8
+
+ This scheme recognises the following names:
+                 "UTF-8"
+ */
+class EncodingSchemeUtf8 : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeUtf8");
+    }
+
+    const
+    {
+        override string[] names()
+        {
+            return
+            [
+                "UTF-8"
+            ];
+        }
+
+        override string toString()
+        {
+            return "UTF-8";
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(char)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(char)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(char[])buffer;
+            return std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(char)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        {
+            auto t = cast(const(char)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"\uFFFD";
+        }
+    }
+}
+
+/**
+ EncodingScheme to handle UTF-16 in native byte order
+
+ This scheme recognises the following names:
+                 "UTF-16LE" (little-endian architecture only)
+                 "UTF-16BE" (big-endian architecture only)
+ */
+class EncodingSchemeUtf16Native : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeUtf16Native");
+    }
+
+    const
+    {
+        version(LittleEndian) { enum string NAME = "UTF-16LE"; }
+        version(BigEndian)    { enum string NAME = "UTF-16BE"; }
+
+        override string[] names()
+        {
+            return [ NAME ];
+        }
+
+        override string toString()
+        {
+            return NAME;
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(wchar)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(wchar)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(wchar[])buffer;
+            return wchar.sizeof * std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        in
+        {
+            assert((s.length & 1) == 0);
+        }
+        body
+        {
+            auto t = cast(const(wchar)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length * wchar.sizeof..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        in
+        {
+            assert((s.length & 1) == 0);
+        }
+        body
+        {
+            auto t = cast(const(wchar)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length * wchar.sizeof..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"\uFFFD"w;
+        }
+    }
+}
+unittest
+{
+    version(LittleEndian)
+    {
+        auto efrom = EncodingScheme.create("utf-16le");
+        ubyte[6] sample = [154,1, 155,1, 156,1];
+    }
+    version(BigEndian)
+    {
+        auto efrom = EncodingScheme.create("utf-16be");
+        ubyte[6] sample = [1,154, 1,155, 1,156];
+    }
+    const(ubyte)[] ub = cast(const(ubyte)[])sample;
+    dchar dc = efrom.safeDecode(ub);
+    assert(dc == 410);
+    assert(ub.length == 4);
+}
+
+/**
+ EncodingScheme to handle UTF-32 in native byte order
+
+ This scheme recognises the following names:
+                 "UTF-32LE" (little-endian architecture only)
+                 "UTF-32BE" (big-endian architecture only)
+ */
+class EncodingSchemeUtf32Native : EncodingScheme
+{
+    shared static this()
+    {
+        EncodingScheme.register("std.encoding.EncodingSchemeUtf32Native");
+    }
+
+    const
+    {
+        version(LittleEndian) { enum string NAME = "UTF-32LE"; }
+        version(BigEndian)    { enum string NAME = "UTF-32BE"; }
+
+        override string[] names()
+        {
+            return [ NAME ];
+        }
+
+        override string toString()
+        {
+            return NAME;
+        }
+
+        override bool canEncode(dchar c)
+        {
+            return std.encoding.canEncode!(dchar)(c);
+        }
+
+        override size_t encodedLength(dchar c)
+        {
+            return std.encoding.encodedLength!(dchar)(c);
+        }
+
+        override size_t encode(dchar c, ubyte[] buffer)
+        {
+            auto r = cast(dchar[])buffer;
+            return dchar.sizeof * std.encoding.encode(c,r);
+        }
+
+        override dchar decode(ref const(ubyte)[] s)
+        in
+        {
+            assert((s.length & 3) == 0);
+        }
+        body
+        {
+            auto t = cast(const(dchar)[]) s;
+            dchar c = std.encoding.decode(t);
+            s = s[$-t.length * dchar.sizeof..$];
+            return c;
+        }
+
+        override dchar safeDecode(ref const(ubyte)[] s)
+        in
+        {
+            assert((s.length & 3) == 0);
+        }
+        body
+        {
+            auto t = cast(const(dchar)[]) s;
+            dchar c = std.encoding.safeDecode(t);
+            s = s[$-t.length * dchar.sizeof..$];
+            return c;
+        }
+
+        override @property immutable(ubyte)[] replacementSequence()
+        {
+            return cast(immutable(ubyte)[])"\uFFFD"d;
+        }
+    }
+}
+unittest
+{
+    version(LittleEndian)
+    {
+        auto efrom = EncodingScheme.create("utf-32le");
+        ubyte[12] sample = [154,1,0,0, 155,1,0,0, 156,1,0,0];
+    }
+    version(BigEndian)
+    {
+        auto efrom = EncodingScheme.create("utf-32be");
+        ubyte[12] sample = [0,0,1,154, 0,0,1,155, 0,0,1,156];
+    }
+    const(ubyte)[] ub = cast(const(ubyte)[])sample;
+    dchar dc = efrom.safeDecode(ub);
+    assert(dc == 410);
+    assert(ub.length == 8);
+}
+
+//=============================================================================
+
+
+// Helper functions
+version(unittest)
+{
+    void transcodeReverse(Src,Dst)(immutable(Src)[] s, out immutable(Dst)[] r)
+    {
+        static if(is(Src==Dst))
+        {
+            return s;
+        }
+        else static if(is(Src==AsciiChar))
+        {
+            transcodeReverse!(char,Dst)(cast(string)s,r);
+        }
+        else
+        {
+            foreach_reverse(d;codePoints(s))
+            {
+                foreach_reverse(c;codeUnits!(Dst)(d))
+                {
+                    r = c ~ r;
+                }
+            }
+        }
+    }
+
+    string makeReadable(string s)
+    {
+        string r = "\"";
+        foreach(char c;s)
+        {
+            if (c >= 0x20 && c < 0x80)
+            {
+                r ~= c;
+            }
+            else
+            {
+                r ~= "\\x";
+                r ~= toHexDigit(c >> 4);
+                r ~= toHexDigit(c);
+            }
+        }
+        r ~= "\"";
+        return r;
+    }
+
+    string makeReadable(wstring s)
+    {
+        string r = "\"";
+        foreach(wchar c;s)
+        {
+            if (c >= 0x20 && c < 0x80)
+            {
+                r ~= cast(char) c;
+            }
+            else
+            {
+                r ~= "\\u";
+                r ~= toHexDigit(c >> 12);
+                r ~= toHexDigit(c >> 8);
+                r ~= toHexDigit(c >> 4);
+                r ~= toHexDigit(c);
+            }
+        }
+        r ~= "\"w";
+        return r;
+    }
+
+    string makeReadable(dstring s)
+    {
+        string r = "\"";
+        foreach(dchar c; s)
+        {
+            if (c >= 0x20 && c < 0x80)
+            {
+                r ~= cast(char) c;
+            }
+            else if (c < 0x10000)
+            {
+                r ~= "\\u";
+                r ~= toHexDigit(c >> 12);
+                r ~= toHexDigit(c >> 8);
+                r ~= toHexDigit(c >> 4);
+                r ~= toHexDigit(c);
+            }
+            else
+            {
+                r ~= "\\U00";
+                r ~= toHexDigit(c >> 20);
+                r ~= toHexDigit(c >> 16);
+                r ~= toHexDigit(c >> 12);
+                r ~= toHexDigit(c >> 8);
+                r ~= toHexDigit(c >> 4);
+                r ~= toHexDigit(c);
+            }
+        }
+        r ~= "\"d";
+        return r;
+    }
+
+    char toHexDigit(int n)
+    {
+        return "0123456789ABCDEF"[n & 0xF];
+    }
+}
diff --git a/libphobos/src/std/exception.d b/libphobos/src/std/exception.d
new file mode 100644
index 00000000000..ffcad84f2c9
--- /dev/null
+++ b/libphobos/src/std/exception.d
@@ -0,0 +1,2256 @@
+// Written in the D programming language.
+
+/++
+    This module defines functions related to exceptions and general error
+    handling. It also defines functions intended to aid in unit testing.
+
+    Synopsis of some of std.exception's functions:
+    --------------------
+    string synopsis()
+    {
+        FILE* f = enforce(fopen("some/file"));
+        // f is not null from here on
+        FILE* g = enforce!WriteException(fopen("some/other/file", "w"));
+        // g is not null from here on
+
+        Exception e = collectException(write(g, readln(f)));
+        if (e)
+        {
+            ... an exception occurred...
+            ... We have the exception to play around with...
+        }
+
+        string msg = collectExceptionMsg(write(g, readln(f)));
+        if (msg)
+        {
+            ... an exception occurred...
+            ... We have the message from the exception but not the exception...
+        }
+
+        char[] line;
+        enforce(readln(f, line));
+        return assumeUnique(line);
+    }
+    --------------------
+
+    Macros:
+        WIKI = Phobos/StdException
+
+    Copyright: Copyright Andrei Alexandrescu 2008-, Jonathan M Davis 2011-.
+    License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
+    Authors:   $(WEB erdani.org, Andrei Alexandrescu) and Jonathan M Davis
+    Source:    $(PHOBOSSRC std/_exception.d)
+
+ +/
+module std.exception;
+
+import std.range;
+import std.traits;
+
+import core.stdc.errno;
+import core.stdc.string;
+
+/++
+    Asserts that the given expression does $(I not) throw the given type
+    of $(D Throwable). If a $(D Throwable) of the given type is thrown,
+    it is caught and does not escape assertNotThrown. Rather, an
+    $(D AssertError) is thrown. However, any other $(D Throwable)s will escape.
+
+    Params:
+        T          = The $(D Throwable) to test for.
+        expression = The expression to test.
+        msg        = Optional message to output on test failure.
+                     If msg is empty, and the thrown exception has a
+                     non-empty msg field, the exception's msg field
+                     will be output on test failure.
+        file       = The file where the error occurred.
+                     Defaults to $(D __FILE__).
+        line       = The line where the error occurred.
+                     Defaults to $(D __LINE__).
+
+    Throws:
+        $(D AssertError) if the given $(D Throwable) is thrown.
+
+    Returns:
+        the result of `expression`.
+ +/
+auto assertNotThrown(T : Throwable = Exception, E)
+                    (lazy E expression,
+                     string msg = null,
+                     string file = __FILE__,
+                     size_t line = __LINE__)
+{
+    import core.exception : AssertError;
+    try
+    {
+        return expression();
+    }
+    catch (T t)
+    {
+        immutable message = msg.length == 0 ? t.msg : msg;
+        immutable tail = message.length == 0 ? "." : ": " ~ message;
+        throw new AssertError("assertNotThrown failed: " ~ T.stringof ~ " was thrown" ~ tail, file, line, t);
+    }
+}
+///
+unittest
+{
+    import core.exception : AssertError;
+
+    import std.string;
+    assertNotThrown!StringException(enforce!StringException(true, "Error!"));
+
+    //Exception is the default.
+    assertNotThrown(enforce!StringException(true, "Error!"));
+
+    assert(collectExceptionMsg!AssertError(assertNotThrown!StringException(
+               enforce!StringException(false, "Error!"))) ==
+           `assertNotThrown failed: StringException was thrown: Error!`);
+}
+unittest
+{
+    import core.exception : AssertError;
+    import std.string;
+    assert(collectExceptionMsg!AssertError(assertNotThrown!StringException(
+               enforce!StringException(false, ""), "Error!")) ==
+           `assertNotThrown failed: StringException was thrown: Error!`);
+
+    assert(collectExceptionMsg!AssertError(assertNotThrown!StringException(
+               enforce!StringException(false, ""))) ==
+           `assertNotThrown failed: StringException was thrown.`);
+
+    assert(collectExceptionMsg!AssertError(assertNotThrown!StringException(
+               enforce!StringException(false, ""), "")) ==
+           `assertNotThrown failed: StringException was thrown.`);
+}
+
+unittest
+{
+    import core.exception : AssertError;
+
+    void throwEx(Throwable t) { throw t; }
+    bool nothrowEx() { return true; }
+
+    try
+    {
+        assert(assertNotThrown!Exception(nothrowEx()));
+    }
+    catch (AssertError) assert(0);
+
+    try
+    {
+        assert(assertNotThrown!Exception(nothrowEx(), "It's a message"));
+    }
+    catch (AssertError) assert(0);
+
+    try
+    {
+        assert(assertNotThrown!AssertError(nothrowEx()));
+    }
+    catch (AssertError) assert(0);
+
+    try
+    {
+        assert(assertNotThrown!AssertError(nothrowEx(), "It's a message"));
+    }
+    catch (AssertError) assert(0);
+
+    {
+        bool thrown = false;
+        try
+        {
+            assertNotThrown!Exception(
+                throwEx(new Exception("It's an Exception")));
+        }
+        catch (AssertError) thrown = true;
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+        {
+            assertNotThrown!Exception(
+                throwEx(new Exception("It's an Exception")), "It's a message");
+        }
+        catch (AssertError) thrown = true;
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+        {
+            assertNotThrown!AssertError(
+                throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)));
+        }
+        catch (AssertError) thrown = true;
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+        {
+            assertNotThrown!AssertError(
+                throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)),
+                        "It's a message");
+        }
+        catch (AssertError) thrown = true;
+        assert(thrown);
+    }
+}
+
+/++
+    Asserts that the given expression throws the given type of $(D Throwable).
+    The $(D Throwable) is caught and does not escape assertThrown. However,
+    any other $(D Throwable)s $(I will) escape, and if no $(D Throwable)
+    of the given type is thrown, then an $(D AssertError) is thrown.
+
+    Params:
+        T          = The $(D Throwable) to test for.
+        expression = The expression to test.
+        msg        = Optional message to output on test failure.
+        file       = The file where the error occurred.
+                     Defaults to $(D __FILE__).
+        line       = The line where the error occurred.
+                     Defaults to $(D __LINE__).
+
+    Throws:
+        $(D AssertError) if the given $(D Throwable) is not thrown.
+  +/
+void assertThrown(T : Throwable = Exception, E)
+                 (lazy E expression,
+                  string msg = null,
+                  string file = __FILE__,
+                  size_t line = __LINE__)
+{
+    import core.exception : AssertError;
+
+    try
+        expression();
+    catch (T)
+        return;
+    throw new AssertError("assertThrown failed: No " ~ T.stringof ~ " was thrown"
+                                 ~ (msg.length == 0 ? "." : ": ") ~ msg,
+                          file, line);
+}
+///
+unittest
+{
+    import core.exception : AssertError;
+    import std.string;
+
+    assertThrown!StringException(enforce!StringException(false, "Error!"));
+
+    //Exception is the default.
+    assertThrown(enforce!StringException(false, "Error!"));
+
+    assert(collectExceptionMsg!AssertError(assertThrown!StringException(
+               enforce!StringException(true, "Error!"))) ==
+           `assertThrown failed: No StringException was thrown.`);
+}
+
+unittest
+{
+    import core.exception : AssertError;
+
+    void throwEx(Throwable t) { throw t; }
+    void nothrowEx() { }
+
+    try
+    {
+        assertThrown!Exception(throwEx(new Exception("It's an Exception")));
+    }
+    catch (AssertError) assert(0);
+
+    try
+    {
+        assertThrown!Exception(throwEx(new Exception("It's an Exception")),
+                               "It's a message");
+    }
+    catch(AssertError) assert(0);
+
+    try
+    {
+        assertThrown!AssertError(throwEx(new AssertError("It's an AssertError",
+                                                         __FILE__, __LINE__)));
+    }
+    catch (AssertError) assert(0);
+
+    try
+    {
+        assertThrown!AssertError(throwEx(new AssertError("It's an AssertError",
+                                                         __FILE__, __LINE__)),
+                                 "It's a message");
+    }
+    catch (AssertError) assert(0);
+
+
+    {
+        bool thrown = false;
+        try
+            assertThrown!Exception(nothrowEx());
+        catch(AssertError)
+            thrown = true;
+
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+            assertThrown!Exception(nothrowEx(), "It's a message");
+        catch(AssertError)
+            thrown = true;
+
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+            assertThrown!AssertError(nothrowEx());
+        catch(AssertError)
+            thrown = true;
+
+        assert(thrown);
+    }
+
+    {
+        bool thrown = false;
+        try
+            assertThrown!AssertError(nothrowEx(), "It's a message");
+        catch(AssertError)
+            thrown = true;
+
+        assert(thrown);
+    }
+}
+
+
+/++
+    Enforces that the given value is true.
+
+    Params:
+        value = The value to test.
+        E = Exception type to throw if the value evalues to false.
+        msg = The error message to put in the exception if it is thrown.
+        file = The source file of the caller.
+        line = The line number of the caller.
+
+    Returns: $(D value), if `cast(bool)value` is true. Otherwise,
+    $(D new Exception(msg)) is thrown.
+
+    Note:
+        $(D enforce) is used to throw exceptions and is therefore intended to
+        aid in error handling. It is $(I not) intended for verifying the logic
+        of your program. That is what $(D assert) is for. Also, do not use
+        $(D enforce) inside of contracts (i.e. inside of $(D in) and $(D out)
+        blocks and $(D invariant)s), because they will be compiled out when
+        compiling with $(I -release). Use $(D assert) in contracts.
+
+    Example:
+    --------------------
+    auto f = enforce(fopen("data.txt"));
+    auto line = readln(f);
+    enforce(line.length, "Expected a non-empty line.");
+    --------------------
+ +/
+T enforce(E : Throwable = Exception, T)(T value, lazy const(char)[] msg = null, string file = __FILE__, size_t line = __LINE__)
+    if (is(typeof({ if (!value) {} })))
+{
+    if (!value) bailOut!E(file, line, msg);
+    return value;
+}
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Use the overload of enforce that takes file and line as function arguments.")
+T enforce(T, string file, size_t line = __LINE__)
+    (T value, lazy const(char)[] msg = null)
+    if (is(typeof({ if (!value) {} })))
+{
+    if (!value) bailOut(file, line, msg);
+    return value;
+}
+
+/++
+    Enforces that the given value is true.
+
+    Params:
+        value = The value to test.
+        dg = The delegate to be called if the value evaluates to false.
+        file = The source file of the caller.
+        line = The line number of the caller.
+
+    Returns: $(D value), if `cast(bool)value` is true. Otherwise, the given
+    delegate is called.
+
+    The safety and purity of this function are inferred from $(D Dg)'s safety
+    and purity.
+ +/
+T enforce(T, Dg, string file = __FILE__, size_t line = __LINE__)
+    (T value, scope Dg dg)
+    if (isSomeFunction!Dg && is(typeof( dg() )) &&
+        is(typeof({ if (!value) {} })))
+{
+    if (!value) dg();
+    return value;
+}
+
+private void bailOut(E : Throwable = Exception)(string file, size_t line, in char[] msg)
+{
+    static if (is(typeof(new E(string.init, string.init, size_t.init))))
+    {
+        throw new E(msg.ptr ? msg.idup : "Enforcement failed", file, line);
+    }
+    else static if (is(typeof(new E(string.init, size_t.init))))
+    {
+        throw new E(file, line);
+    }
+    else
+    {
+        static assert(0, "Expected this(string, string, size_t) or this(string, size_t)" ~
+            " constructor for " ~ __traits(identifier, E));
+    }
+}
+
+unittest
+{
+    assert (enforce(123) == 123);
+
+    try
+    {
+        enforce(false, "error");
+        assert (false);
+    }
+    catch (Exception e)
+    {
+        assert (e.msg == "error");
+        assert (e.file == __FILE__);
+        assert (e.line == __LINE__-7);
+    }
+}
+
+unittest
+{
+    // Issue 10510
+    extern(C) void cFoo() { }
+    enforce(false, &cFoo);
+}
+
+// purity and safety inference test
+unittest
+{
+    import std.meta : AliasSeq;
+
+    foreach (EncloseSafe; AliasSeq!(false, true))
+    foreach (EnclosePure; AliasSeq!(false, true))
+    {
+        foreach (BodySafe; AliasSeq!(false, true))
+        foreach (BodyPure; AliasSeq!(false, true))
+        {
+            enum code =
+                "delegate void() " ~
+                (EncloseSafe ? "@safe " : "") ~
+                (EnclosePure ? "pure " : "") ~
+                "{ enforce(true, { " ~
+                        "int n; " ~
+                        (BodySafe ? "" : "auto p = &n + 10; "    ) ~    // unsafe code
+                        (BodyPure ? "" : "static int g; g = 10; ") ~    // impure code
+                    "}); " ~
+                "}";
+            enum expect =
+                (BodySafe || !EncloseSafe) && (!EnclosePure || BodyPure);
+
+            version(none)
+            pragma(msg, "safe = ", EncloseSafe?1:0, "/", BodySafe?1:0, ", ",
+                        "pure = ", EnclosePure?1:0, "/", BodyPure?1:0, ", ",
+                        "expect = ", expect?"OK":"NG", ", ",
+                        "code = ", code);
+
+            static assert(__traits(compiles, mixin(code)()) == expect);
+        }
+    }
+}
+
+// Test for bugzilla 8637
+unittest
+{
+    struct S
+    {
+        static int g;
+        ~this() {}  // impure & unsafe destructor
+        bool opCast(T:bool)() {
+            int* p = cast(int*)0;   // unsafe operation
+            int n = g;              // impure operation
+            return true;
+        }
+    }
+    S s;
+
+    enforce(s);
+    enforce(s, {});
+    enforce(s, new Exception(""));
+
+    errnoEnforce(s);
+
+    alias E1 = Exception;
+    static class E2 : Exception
+    {
+        this(string fn, size_t ln) { super("", fn, ln); }
+    }
+    static class E3 : Exception
+    {
+        this(string msg) { super(msg, __FILE__, __LINE__); }
+    }
+    enforce!E1(s);
+    enforce!E2(s);
+}
+
+deprecated unittest
+{
+    struct S
+    {
+        static int g;
+        ~this() {}  // impure & unsafe destructor
+        bool opCast(T:bool)() {
+            int* p = cast(int*)0;   // unsafe operation
+            int n = g;              // impure operation
+            return true;
+        }
+    }
+    S s;
+
+    enforce!(S, __FILE__, __LINE__)(s, "");
+}
+
+unittest
+{
+    // Issue 14685
+
+    class E : Exception
+    {
+        this() { super("Not found"); }
+    }
+    static assert(!__traits(compiles, { enforce!E(false); }));
+}
+
+/++
+    Enforces that the given value is true.
+
+    Params:
+        value = The value to test.
+        ex = The exception to throw if the value evaluates to false.
+
+    Returns: $(D value), if `cast(bool)value` is true. Otherwise, $(D ex) is
+    thrown.
+
+    Example:
+    --------------------
+    auto f = enforce(fopen("data.txt"));
+    auto line = readln(f);
+    enforce(line.length, new IOException); // expect a non-empty line
+    --------------------
+ +/
+T enforce(T)(T value, lazy Throwable ex)
+{
+    if (!value) throw ex();
+    return value;
+}
+
+unittest
+{
+    assertNotThrown(enforce(true, new Exception("this should not be thrown")));
+    assertThrown(enforce(false, new Exception("this should be thrown")));
+}
+
+/++
+    Enforces that the given value is true, throwing an `ErrnoException` if it
+    is not.
+
+    Params:
+        value = The value to test.
+        msg = The message to include in the `ErrnoException` if it is thrown.
+
+    Returns: $(D value), if `cast(bool)value` is true. Otherwise,
+    $(D new ErrnoException(msg)) is thrown.  It is assumed that the last
+    operation set $(D errno) to an error code corresponding with the failed
+    condition.
+
+    Example:
+    --------------------
+    auto f = errnoEnforce(fopen("data.txt"));
+    auto line = readln(f);
+    enforce(line.length); // expect a non-empty line
+    --------------------
+ +/
+T errnoEnforce(T, string file = __FILE__, size_t line = __LINE__)
+    (T value, lazy string msg = null)
+{
+    if (!value) throw new ErrnoException(msg, file, line);
+    return value;
+}
+
+
+/++
+    If $(D !value) is $(D false), $(D value) is returned. Otherwise,
+    $(D new E(msg, file, line)) is thrown. Or if $(D E) doesn't take a message
+    and can be constructed with $(D new E(file, line)), then
+    $(D new E(file, line)) will be thrown.
+
+    This is legacy name, it is recommended to use $(D enforce!E) instead.
+
+    Example:
+    --------------------
+    auto f = enforceEx!FileMissingException(fopen("data.txt"));
+    auto line = readln(f);
+    enforceEx!DataCorruptionException(line.length);
+    --------------------
+ +/
+template enforceEx(E : Throwable)
+    if (is(typeof(new E("", __FILE__, __LINE__))))
+{
+    /++ Ditto +/
+    T enforceEx(T)(T value, lazy string msg = "", string file = __FILE__, size_t line = __LINE__)
+    {
+        if (!value) throw new E(msg, file, line);
+        return value;
+    }
+}
+
+/++ Ditto +/
+template enforceEx(E : Throwable)
+    if (is(typeof(new E(__FILE__, __LINE__))) && !is(typeof(new E("", __FILE__, __LINE__))))
+{
+    /++ Ditto +/
+    T enforceEx(T)(T value, string file = __FILE__, size_t line = __LINE__)
+    {
+        if (!value) throw new E(file, line);
+        return value;
+    }
+}
+
+unittest
+{
+    import std.array : empty;
+    import core.exception : OutOfMemoryError;
+    assertNotThrown(enforceEx!Exception(true));
+    assertNotThrown(enforceEx!Exception(true, "blah"));
+    assertNotThrown(enforceEx!OutOfMemoryError(true));
+
+    {
+        auto e = collectException(enforceEx!Exception(false));
+        assert(e !is null);
+        assert(e.msg.empty);
+        assert(e.file == __FILE__);
+        assert(e.line == __LINE__ - 4);
+    }
+
+    {
+        auto e = collectException(enforceEx!Exception(false, "hello", "file", 42));
+        assert(e !is null);
+        assert(e.msg == "hello");
+        assert(e.file == "file");
+        assert(e.line == 42);
+    }
+
+    {
+        auto e = collectException!Error(enforceEx!OutOfMemoryError(false));
+        assert(e !is null);
+        assert(e.msg == "Memory allocation failed");
+        assert(e.file == __FILE__);
+        assert(e.line == __LINE__ - 4);
+    }
+
+    {
+        auto e = collectException!Error(enforceEx!OutOfMemoryError(false, "file", 42));
+        assert(e !is null);
+        assert(e.msg == "Memory allocation failed");
+        assert(e.file == "file");
+        assert(e.line == 42);
+    }
+
+    static assert(!is(typeof(enforceEx!int(true))));
+}
+
+unittest
+{
+    alias enf = enforceEx!Exception;
+    assertNotThrown(enf(true));
+    assertThrown(enf(false, "blah"));
+}
+
+
+/++
+    Catches and returns the exception thrown from the given expression.
+    If no exception is thrown, then null is returned and $(D result) is
+    set to the result of the expression.
+
+    Note that while $(D collectException) $(I can) be used to collect any
+    $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to
+    catch anything that is neither an $(D Exception) nor a type derived from
+    $(D Exception). So, do not use $(D collectException) to collect
+    non-$(D Exception)s unless you're sure that that's what you really want to
+    do.
+
+    Params:
+        T          = The type of exception to catch.
+        expression = The expression which may throw an exception.
+        result     = The result of the expression if no exception is thrown.
++/
+T collectException(T = Exception, E)(lazy E expression, ref E result)
+{
+    try
+    {
+        result = expression();
+    }
+    catch (T e)
+    {
+        return e;
+    }
+    return null;
+}
+///
+unittest
+{
+    int b;
+    int foo() { throw new Exception("blah"); }
+    assert(collectException(foo(), b));
+
+    int[] a = new int[3];
+    import core.exception : RangeError;
+    assert(collectException!RangeError(a[4], b));
+}
+
+/++
+    Catches and returns the exception thrown from the given expression.
+    If no exception is thrown, then null is returned. $(D E) can be
+    $(D void).
+
+    Note that while $(D collectException) $(I can) be used to collect any
+    $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to
+    catch anything that is neither an $(D Exception) nor a type derived from
+    $(D Exception). So, do not use $(D collectException) to collect
+    non-$(D Exception)s unless you're sure that that's what you really want to
+    do.
+
+    Params:
+        T          = The type of exception to catch.
+        expression = The expression which may throw an exception.
++/
+T collectException(T : Throwable = Exception, E)(lazy E expression)
+{
+    try
+    {
+        expression();
+    }
+    catch (T t)
+    {
+        return t;
+    }
+    return null;
+}
+
+unittest
+{
+    int foo() { throw new Exception("blah"); }
+    assert(collectException(foo()));
+}
+
+/++
+    Catches the exception thrown from the given expression and returns the
+    msg property of that exception. If no exception is thrown, then null is
+    returned. $(D E) can be $(D void).
+
+    If an exception is thrown but it has an empty message, then
+    $(D emptyExceptionMsg) is returned.
+
+    Note that while $(D collectExceptionMsg) $(I can) be used to collect any
+    $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to
+    catch anything that is neither an $(D Exception) nor a type derived from
+    $(D Exception). So, do not use $(D collectExceptionMsg) to collect
+    non-$(D Exception)s unless you're sure that that's what you really want to
+    do.
+
+    Params:
+        T          = The type of exception to catch.
+        expression = The expression which may throw an exception.
++/
+string collectExceptionMsg(T = Exception, E)(lazy E expression)
+{
+    import std.array : empty;
+    try
+    {
+        expression();
+
+        return cast(string)null;
+    }
+    catch(T e)
+        return e.msg.empty ? emptyExceptionMsg : e.msg;
+}
+///
+unittest
+{
+    void throwFunc() { throw new Exception("My Message."); }
+    assert(collectExceptionMsg(throwFunc()) == "My Message.");
+
+    void nothrowFunc() {}
+    assert(collectExceptionMsg(nothrowFunc()) is null);
+
+    void throwEmptyFunc() { throw new Exception(""); }
+    assert(collectExceptionMsg(throwEmptyFunc()) == emptyExceptionMsg);
+}
+
+/++
+    Value that collectExceptionMsg returns when it catches an exception
+    with an empty exception message.
+ +/
+enum emptyExceptionMsg = "<Empty Exception Message>";
+
+/**
+ * Casts a mutable array to an immutable array in an idiomatic
+ * manner. Technically, $(D assumeUnique) just inserts a cast,
+ * but its name documents assumptions on the part of the
+ * caller. $(D assumeUnique(arr)) should only be called when
+ * there are no more active mutable aliases to elements of $(D
+ * arr). To strengthen this assumption, $(D assumeUnique(arr))
+ * also clears $(D arr) before returning. Essentially $(D
+ * assumeUnique(arr)) indicates commitment from the caller that there
+ * is no more mutable access to any of $(D arr)'s elements
+ * (transitively), and that all future accesses will be done through
+ * the immutable array returned by $(D assumeUnique).
+ *
+ * Typically, $(D assumeUnique) is used to return arrays from
+ * functions that have allocated and built them.
+ *
+ * Params:
+ *  array = The array to cast to immutable.
+ *
+ * Returns: The immutable array.
+ *
+ * Example:
+ *
+ * ----
+ * string letters()
+ * {
+ *   char[] result = new char['z' - 'a' + 1];
+ *   foreach (i, ref e; result)
+ *   {
+ *     e = cast(char)('a' + i);
+ *   }
+ *   return assumeUnique(result);
+ * }
+ * ----
+ *
+ * The use in the example above is correct because $(D result)
+ * was private to $(D letters) and is inaccessible in writing
+ * after the function returns. The following example shows an
+ * incorrect use of $(D assumeUnique).
+ *
+ * Bad:
+ *
+ * ----
+ * private char[] buffer;
+ * string letters(char first, char last)
+ * {
+ *   if (first >= last) return null; // fine
+ *   auto sneaky = buffer;
+ *   sneaky.length = last - first + 1;
+ *   foreach (i, ref e; sneaky)
+ *   {
+ *     e = cast(char)('a' + i);
+ *   }
+ *   return assumeUnique(sneaky); // BAD
+ * }
+ * ----
+ *
+ * The example above wreaks havoc on client code because it is
+ * modifying arrays that callers considered immutable. To obtain an
+ * immutable array from the writable array $(D buffer), replace
+ * the last line with:
+ * ----
+ * return to!(string)(sneaky); // not that sneaky anymore
+ * ----
+ *
+ * The call will duplicate the array appropriately.
+ *
+ * Note that checking for uniqueness during compilation is
+ * possible in certain cases, especially when a function is
+ * marked as a pure function. The following example does not
+ * need to call assumeUnique because the compiler can infer the
+ * uniqueness of the array in the pure function:
+ * ----
+ * string letters() pure
+ * {
+ *   char[] result = new char['z' - 'a' + 1];
+ *   foreach (i, ref e; result)
+ *   {
+ *     e = cast(char)('a' + i);
+ *   }
+ *   return result;
+ * }
+ * ----
+ *
+ * For more on infering uniqueness see the $(B unique) and
+ * $(B lent) keywords in the
+ * $(WEB archjava.fluid.cs.cmu.edu/papers/oopsla02.pdf, ArchJava)
+ * language.
+ *
+ * The downside of using $(D assumeUnique)'s
+ * convention-based usage is that at this time there is no
+ * formal checking of the correctness of the assumption;
+ * on the upside, the idiomatic use of $(D assumeUnique) is
+ * simple and rare enough to be tolerable.
+ *
+ */
+immutable(T)[] assumeUnique(T)(T[] array) pure nothrow
+{
+    return .assumeUnique(array);    // call ref version
+}
+/// ditto
+immutable(T)[] assumeUnique(T)(ref T[] array) pure nothrow
+{
+    auto result = cast(immutable(T)[]) array;
+    array = null;
+    return result;
+}
+
+unittest
+{
+    int[] arr = new int[1];
+    auto arr1 = assumeUnique(arr);
+    assert(is(typeof(arr1) == immutable(int)[]) && arr == null);
+}
+
+immutable(T[U]) assumeUnique(T, U)(ref T[U] array) pure nothrow
+{
+    auto result = cast(immutable(T[U])) array;
+    array = null;
+    return result;
+}
+
+// @@@BUG@@@
+version(none) unittest
+{
+    int[string] arr = ["a":1];
+    auto arr1 = assumeUnique(arr);
+    assert(is(typeof(arr1) == immutable(int[string])) && arr == null);
+}
+
+/**
+ * Wraps a possibly-throwing expression in a $(D nothrow) wrapper so that it
+ * can be called by a $(D nothrow) function.
+ *
+ * This wrapper function documents commitment on the part of the caller that
+ * the appropriate steps have been taken to avoid whatever conditions may
+ * trigger an exception during the evaluation of $(D expr).  If it turns out
+ * that the expression $(I does) throw at runtime, the wrapper will throw an
+ * $(D AssertError).
+ *
+ * (Note that $(D Throwable) objects such as $(D AssertError) that do not
+ * subclass $(D Exception) may be thrown even from $(D nothrow) functions,
+ * since they are considered to be serious runtime problems that cannot be
+ * recovered from.)
+ *
+ * Params:
+ *  expr = The expression asserted not to throw.
+ *  msg = The message to include in the `AssertError` if the assumption turns
+ *      out to be false.
+ *  file = The source file name of the caller.
+ *  line = The line number of the caller.
+ *
+ * Returns:
+ *  The value of `expr`, if any.
+ */
+T assumeWontThrow(T)(lazy T expr,
+                     string msg = null,
+                     string file = __FILE__,
+                     size_t line = __LINE__) nothrow
+{
+    import core.exception : AssertError;
+    try
+    {
+        return expr;
+    }
+    catch(Exception e)
+    {
+        import std.range.primitives : empty;
+        immutable tail = msg.empty ? "." : ": " ~ msg;
+        throw new AssertError("assumeWontThrow failed: Expression did throw" ~
+                              tail, file, line);
+    }
+}
+
+///
+unittest
+{
+    import std.math : sqrt;
+
+    // This function may throw.
+    int squareRoot(int x)
+    {
+        if (x < 0)
+            throw new Exception("Tried to take root of negative number");
+        return cast(int)sqrt(cast(double)x);
+    }
+
+    // This function never throws.
+    int computeLength(int x, int y) nothrow
+    {
+        // Since x*x + y*y is always positive, we can safely assume squareRoot
+        // won't throw, and use it to implement this nothrow function. If it
+        // does throw (e.g., if x*x + y*y overflows a 32-bit value), then the
+        // program will terminate.
+        return assumeWontThrow(squareRoot(x*x + y*y));
+    }
+
+    assert(computeLength(3, 4) == 5);
+}
+
+unittest
+{
+    import core.exception : AssertError;
+
+    void alwaysThrows()
+    {
+        throw new Exception("I threw up");
+    }
+    void bad() nothrow
+    {
+        assumeWontThrow(alwaysThrows());
+    }
+    assertThrown!AssertError(bad());
+}
+
+/**
+Checks whether a given source object contains pointers or references to a given
+target object.
+
+Params:
+    source = The source object
+    target = The target object
+
+Returns: $(D true) if $(D source)'s representation embeds a pointer
+that points to $(D target)'s representation or somewhere inside
+it.
+
+If $(D source) is or contains a dynamic array, then, then these functions will check
+if there is overlap between the dynamic array and $(D target)'s representation.
+
+If $(D source) is a class, then it will be handled as a pointer.
+
+If $(D target) is a pointer, a dynamic array or a class, then these functions will only
+check if $(D source) points to $(D target), $(I not) what $(D target) references.
+
+If $(D source) is or contains a union, then there may be either false positives or
+false negatives:
+
+$(D doesPointTo) will return $(D true) if it is absolutely certain
+$(D source) points to $(D target). It may produce false negatives, but never
+false positives. This function should be prefered when trying to validate
+input data.
+
+$(D mayPointTo) will return $(D false) if it is absolutely certain
+$(D source) does not point to $(D target). It may produce false positives, but never
+false negatives. This function should be prefered for defensively choosing a
+code path.
+
+Note: Evaluating $(D doesPointTo(x, x)) checks whether $(D x) has
+internal pointers. This should only be done as an assertive test,
+as the language is free to assume objects don't have internal pointers
+(TDPL 7.1.3.5).
+*/
+bool doesPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow
+    if (__traits(isRef, source) || isDynamicArray!S ||
+        isPointer!S || is(S == class))
+{
+    static if (isPointer!S || is(S == class) || is(S == interface))
+    {
+        const m = *cast(void**) &source;
+        const b = cast(void*) &target;
+        const e = b + target.sizeof;
+        return b <= m && m < e;
+    }
+    else static if (is(S == struct) || is(S == union))
+    {
+        foreach (i, Subobj; typeof(source.tupleof))
+            static if (!isUnionAliased!(S, i))
+                if (doesPointTo(source.tupleof[i], target)) return true;
+        return false;
+    }
+    else static if (isStaticArray!S)
+    {
+        foreach (size_t i; 0 .. S.length)
+            if (doesPointTo(source[i], target)) return true;
+        return false;
+    }
+    else static if (isDynamicArray!S)
+    {
+        import std.array : overlap;
+        return overlap(cast(void[])source, cast(void[])(&target)[0 .. 1]).length != 0;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+// for shared objects
+/// ditto
+bool doesPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow
+{
+    return doesPointTo!(shared S, shared T, void)(source, target);
+}
+
+/// ditto
+bool mayPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow
+    if (__traits(isRef, source) || isDynamicArray!S ||
+        isPointer!S || is(S == class))
+{
+    static if (isPointer!S || is(S == class) || is(S == interface))
+    {
+        const m = *cast(void**) &source;
+        const b = cast(void*) &target;
+        const e = b + target.sizeof;
+        return b <= m && m < e;
+    }
+    else static if (is(S == struct) || is(S == union))
+    {
+        foreach (i, Subobj; typeof(source.tupleof))
+            if (mayPointTo(source.tupleof[i], target)) return true;
+        return false;
+    }
+    else static if (isStaticArray!S)
+    {
+        foreach (size_t i; 0 .. S.length)
+            if (mayPointTo(source[i], target)) return true;
+        return false;
+    }
+    else static if (isDynamicArray!S)
+    {
+        import std.array : overlap;
+        return overlap(cast(void[])source, cast(void[])(&target)[0 .. 1]).length != 0;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+// for shared objects
+/// ditto
+bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow
+{
+    return mayPointTo!(shared S, shared T, void)(source, target);
+}
+
+/// Pointers
+unittest
+{
+    int  i = 0;
+    int* p = null;
+    assert(!p.doesPointTo(i));
+    p = &i;
+    assert( p.doesPointTo(i));
+}
+
+/// Structs and Unions
+unittest
+{
+    struct S
+    {
+        int v;
+        int* p;
+    }
+    int i;
+    auto s = S(0, &i);
+
+    // structs and unions "own" their members
+    // pointsTo will answer true if one of the members pointsTo.
+    assert(!s.doesPointTo(s.v)); //s.v is just v member of s, so not pointed.
+    assert( s.p.doesPointTo(i)); //i is pointed by s.p.
+    assert( s  .doesPointTo(i)); //which means i is pointed by s itself.
+
+    // Unions will behave exactly the same. Points to will check each "member"
+    // individually, even if they share the same memory
+}
+
+/// Arrays (dynamic and static)
+unittest
+{
+    int i;
+    int[]  slice = [0, 1, 2, 3, 4];
+    int[5] arr   = [0, 1, 2, 3, 4];
+    int*[]  slicep = [&i];
+    int*[1] arrp   = [&i];
+
+    // A slice points to all of its members:
+    assert( slice.doesPointTo(slice[3]));
+    assert(!slice[0 .. 2].doesPointTo(slice[3])); // Object 3 is outside of the
+                                                  // slice [0 .. 2]
+
+    // Note that a slice will not take into account what its members point to.
+    assert( slicep[0].doesPointTo(i));
+    assert(!slicep   .doesPointTo(i));
+
+    // static arrays are objects that own their members, just like structs:
+    assert(!arr.doesPointTo(arr[0])); // arr[0] is just a member of arr, so not
+                                      // pointed.
+    assert( arrp[0].doesPointTo(i));  // i is pointed by arrp[0].
+    assert( arrp   .doesPointTo(i));  // which means i is pointed by arrp
+                                      // itself.
+
+    // Notice the difference between static and dynamic arrays:
+    assert(!arr  .doesPointTo(arr[0]));
+    assert( arr[].doesPointTo(arr[0]));
+    assert( arrp  .doesPointTo(i));
+    assert(!arrp[].doesPointTo(i));
+}
+
+/// Classes
+unittest
+{
+    class C
+    {
+        this(int* p){this.p = p;}
+        int* p;
+    }
+    int i;
+    C a = new C(&i);
+    C b = a;
+
+    // Classes are a bit particular, as they are treated like simple pointers
+    // to a class payload.
+    assert( a.p.doesPointTo(i)); // a.p points to i.
+    assert(!a  .doesPointTo(i)); // Yet a itself does not point i.
+
+    //To check the class payload itself, iterate on its members:
+    ()
+    {
+        foreach (index, _; Fields!C)
+            if (doesPointTo(a.tupleof[index], i))
+                return;
+        assert(0);
+    }();
+
+    // To check if a class points a specific payload, a direct memmory check
+    // can be done:
+    auto aLoc = cast(ubyte[__traits(classInstanceSize, C)]*) a;
+    assert(b.doesPointTo(*aLoc)); // b points to where a is pointing
+}
+
+unittest
+{
+    struct S1 { int a; S1 * b; }
+    S1 a1;
+    S1 * p = &a1;
+    assert(doesPointTo(p, a1));
+
+    S1 a2;
+    a2.b = &a1;
+    assert(doesPointTo(a2, a1));
+
+    struct S3 { int[10] a; }
+    S3 a3;
+    auto a4 = a3.a[2 .. 3];
+    assert(doesPointTo(a4, a3));
+
+    auto a5 = new double[4];
+    auto a6 = a5[1 .. 2];
+    assert(!doesPointTo(a5, a6));
+
+    auto a7 = new double[3];
+    auto a8 = new double[][1];
+    a8[0] = a7;
+    assert(!doesPointTo(a8[0], a8[0]));
+
+    // don't invoke postblit on subobjects
+    {
+        static struct NoCopy { this(this) { assert(0); } }
+        static struct Holder { NoCopy a, b, c; }
+        Holder h;
+        cast(void)doesPointTo(h, h);
+    }
+
+    shared S3 sh3;
+    shared sh3sub = sh3.a[];
+    assert(doesPointTo(sh3sub, sh3));
+
+    int[] darr = [1, 2, 3, 4];
+
+    //dynamic arrays don't point to each other, or slices of themselves
+    assert(!doesPointTo(darr, darr));
+    assert(!doesPointTo(darr[0 .. 1], darr));
+
+    //But they do point their elements
+    foreach (i; 0 .. 4)
+        assert(doesPointTo(darr, darr[i]));
+    assert(doesPointTo(darr[0..3], darr[2]));
+    assert(!doesPointTo(darr[0..3], darr[3]));
+}
+
+unittest
+{
+    //tests with static arrays
+    //Static arrays themselves are just objects, and don't really *point* to anything.
+    //They aggregate their contents, much the same way a structure aggregates its attributes.
+    //*However* The elements inside the static array may themselves point to stuff.
+
+    //Standard array
+    int[2] k;
+    assert(!doesPointTo(k, k)); //an array doesn't point to itself
+    //Technically, k doesn't point its elements, although it does alias them
+    assert(!doesPointTo(k, k[0]));
+    assert(!doesPointTo(k, k[1]));
+    //But an extracted slice will point to the same array.
+    assert(doesPointTo(k[], k));
+    assert(doesPointTo(k[], k[1]));
+
+    //An array of pointers
+    int*[2] pp;
+    int a;
+    int b;
+    pp[0] = &a;
+    assert( doesPointTo(pp, a));  //The array contains a pointer to a
+    assert(!doesPointTo(pp, b));  //The array does NOT contain a pointer to b
+    assert(!doesPointTo(pp, pp)); //The array does not point itslef
+
+    //A struct containing a static array of pointers
+    static struct S
+    {
+        int*[2] p;
+    }
+    S s;
+    s.p[0] = &a;
+    assert( doesPointTo(s, a)); //The struct contains an array that points a
+    assert(!doesPointTo(s, b)); //But doesn't point b
+    assert(!doesPointTo(s, s)); //The struct doesn't actually point itslef.
+
+    //An array containing structs that have pointers
+    static struct SS
+    {
+        int* p;
+    }
+    SS[2] ss = [SS(&a), SS(null)];
+    assert( doesPointTo(ss, a));  //The array contains a struct that points to a
+    assert(!doesPointTo(ss, b));  //The array doesn't contains a struct that points to b
+    assert(!doesPointTo(ss, ss)); //The array doesn't point itself.
+}
+
+
+unittest //Unions
+{
+    int i;
+    union U //Named union
+    {
+        size_t asInt = 0;
+        int*   asPointer;
+    }
+    struct S
+    {
+        union //Anonymous union
+        {
+            size_t asInt = 0;
+            int*   asPointer;
+        }
+    }
+
+    U u;
+    S s;
+    assert(!doesPointTo(u, i));
+    assert(!doesPointTo(s, i));
+    assert(!mayPointTo(u, i));
+    assert(!mayPointTo(s, i));
+
+    u.asPointer = &i;
+    s.asPointer = &i;
+    assert(!doesPointTo(u, i));
+    assert(!doesPointTo(s, i));
+    assert( mayPointTo(u, i));
+    assert( mayPointTo(s, i));
+
+    u.asInt = cast(size_t)&i;
+    s.asInt = cast(size_t)&i;
+    assert(!doesPointTo(u, i));
+    assert(!doesPointTo(s, i));
+    assert( mayPointTo(u, i));
+    assert( mayPointTo(s, i));
+}
+
+unittest //Classes
+{
+    int i;
+    static class A
+    {
+        int* p;
+    }
+    A a = new A, b = a;
+    assert(!doesPointTo(a, b)); //a does not point to b
+    a.p = &i;
+    assert(!doesPointTo(a, i)); //a does not point to i
+}
+unittest //alias this test
+{
+    static int i;
+    static int j;
+    struct S
+    {
+        int* p;
+        @property int* foo(){return &i;}
+        alias foo this;
+    }
+    assert(is(S : int*));
+    S s = S(&j);
+    assert(!doesPointTo(s, i));
+    assert( doesPointTo(s, j));
+    assert( doesPointTo(cast(int*)s, i));
+    assert(!doesPointTo(cast(int*)s, j));
+}
+unittest //more alias this opCast
+{
+    void* p;
+    class A
+    {
+        void* opCast(T)() if (is(T == void*))
+        {
+            return p;
+        }
+        alias foo = opCast!(void*);
+        alias foo this;
+    }
+    assert(!doesPointTo(A.init, p));
+    assert(!mayPointTo(A.init, p));
+}
+
+// Explicitly undocumented. It will be removed in May 2016. @@@DEPRECATED_2016-05@@@
+deprecated ("pointsTo is ambiguous. Please use either of doesPointTo or mayPointTo")
+alias pointsTo = doesPointTo;
+
+/+
+Returns true if the field at index $(D i) in ($D T) shares its address with another field.
+
+Note: This does not merelly check if the field is a member of an union, but also that
+it is not a single child.
++/
+package enum isUnionAliased(T, size_t i) = isUnionAliasedImpl!T(T.tupleof[i].offsetof);
+private bool isUnionAliasedImpl(T)(size_t offset)
+{
+    int count = 0;
+    foreach (i, U; typeof(T.tupleof))
+        if (T.tupleof[i].offsetof == offset)
+            ++count;
+    return count >= 2;
+}
+//
+unittest
+{
+    static struct S
+    {
+        int a0; //Not aliased
+        union
+        {
+            int a1; //Not aliased
+        }
+        union
+        {
+            int a2; //Aliased
+            int a3; //Aliased
+        }
+        union A4
+        {
+            int b0; //Not aliased
+        }
+        A4 a4;
+        union A5
+        {
+            int b0; //Aliased
+            int b1; //Aliased
+        }
+        A5 a5;
+    }
+
+    static assert(!isUnionAliased!(S, 0)); //a0;
+    static assert(!isUnionAliased!(S, 1)); //a1;
+    static assert( isUnionAliased!(S, 2)); //a2;
+    static assert( isUnionAliased!(S, 3)); //a3;
+    static assert(!isUnionAliased!(S, 4)); //a4;
+        static assert(!isUnionAliased!(S.A4, 0)); //a4.b0;
+    static assert(!isUnionAliased!(S, 5)); //a5;
+        static assert( isUnionAliased!(S.A5, 0)); //a5.b0;
+        static assert( isUnionAliased!(S.A5, 1)); //a5.b1;
+}
+
+/*********************
+ * Thrown if errors that set $(D errno) occur.
+ */
+class ErrnoException : Exception
+{
+    final @property uint errno() { return _errno; } /// Operating system error code.
+    private uint _errno;
+    this(string msg, string file = null, size_t line = 0) @trusted
+    {
+        _errno = .errno;
+        version (CRuntime_Glibc)
+        {
+            char[1024] buf = void;
+            auto s = core.stdc.string.strerror_r(errno, buf.ptr, buf.length);
+        }
+        else
+        {
+            auto s = core.stdc.string.strerror(errno);
+        }
+        super(msg ~ " (" ~ s[0..s.strlen].idup ~ ")", file, line);
+    }
+}
+
+/++
+    ML-style functional exception handling. Runs the supplied expression and
+    returns its result. If the expression throws a $(D Throwable), runs the
+    supplied error handler instead and return its result. The error handler's
+    type must be the same as the expression's type.
+
+    Params:
+        E            = The type of $(D Throwable)s to catch. Defaults to $(D Exception)
+        T1           = The type of the expression.
+        T2           = The return type of the error handler.
+        expression   = The expression to run and return its result.
+        errorHandler = The handler to run if the expression throwed.
+
+    Returns:
+        expression, if it does not throw. Otherwise, returns the result of
+        errorHandler.
+
+    Example:
+    --------------------
+    //Revert to a default value upon an error:
+    assert("x".to!int().ifThrown(0) == 0);
+    --------------------
+
+    You can also chain multiple calls to ifThrown, each capturing errors from the
+    entire preceding expression.
+
+    Example:
+    --------------------
+    //Chaining multiple calls to ifThrown to attempt multiple things in a row:
+    string s="true";
+    assert(s.to!int().
+            ifThrown(cast(int)s.to!double()).
+            ifThrown(cast(int)s.to!bool())
+            == 1);
+
+    //Respond differently to different types of errors
+    assert(enforce("x".to!int() < 1).to!string()
+            .ifThrown!ConvException("not a number")
+            .ifThrown!Exception("number too small")
+            == "not a number");
+    --------------------
+
+    The expression and the errorHandler must have a common type they can both
+    be implicitly casted to, and that type will be the type of the compound
+    expression.
+
+    Example:
+    --------------------
+    //null and new Object have a common type(Object).
+    static assert(is(typeof(null.ifThrown(new Object())) == Object));
+    static assert(is(typeof((new Object()).ifThrown(null)) == Object));
+
+    //1 and new Object do not have a common type.
+    static assert(!__traits(compiles, 1.ifThrown(new Object())));
+    static assert(!__traits(compiles, (new Object()).ifThrown(1)));
+    --------------------
+
+    If you need to use the actual thrown exception, you can use a delegate.
+    Example:
+    --------------------
+    //Use a lambda to get the thrown object.
+    assert("%s".format().ifThrown!Exception(e => e.classinfo.name) == "std.format.FormatException");
+    --------------------
+    +/
+//lazy version
+CommonType!(T1, T2) ifThrown(E : Throwable = Exception, T1, T2)(lazy scope T1 expression, lazy scope T2 errorHandler)
+{
+    static assert(!is(typeof(return) == void),
+            "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ").");
+    try
+    {
+        return expression();
+    }
+    catch(E)
+    {
+        return errorHandler();
+    }
+}
+
+///ditto
+//delegate version
+CommonType!(T1, T2) ifThrown(E : Throwable, T1, T2)(lazy scope T1 expression, scope T2 delegate(E) errorHandler)
+{
+    static assert(!is(typeof(return) == void),
+            "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ").");
+    try
+    {
+        return expression();
+    }
+    catch(E e)
+    {
+        return errorHandler(e);
+    }
+}
+
+///ditto
+//delegate version, general overload to catch any Exception
+CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate(Exception) errorHandler)
+{
+    static assert(!is(typeof(return) == void),
+            "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ").");
+    try
+    {
+        return expression();
+    }
+    catch(Exception e)
+    {
+        return errorHandler(e);
+    }
+}
+
+//Verify Examples
+unittest
+{
+    import std.string;
+    import std.conv;
+    //Revert to a default value upon an error:
+    assert("x".to!int().ifThrown(0) == 0);
+
+    //Chaining multiple calls to ifThrown to attempt multiple things in a row:
+    string s="true";
+    assert(s.to!int().
+            ifThrown(cast(int)s.to!double()).
+            ifThrown(cast(int)s.to!bool())
+            == 1);
+
+    //Respond differently to different types of errors
+    assert(enforce("x".to!int() < 1).to!string()
+            .ifThrown!ConvException("not a number")
+            .ifThrown!Exception("number too small")
+            == "not a number");
+
+    //null and new Object have a common type(Object).
+    static assert(is(typeof(null.ifThrown(new Object())) == Object));
+    static assert(is(typeof((new Object()).ifThrown(null)) == Object));
+
+    //1 and new Object do not have a common type.
+    static assert(!__traits(compiles, 1.ifThrown(new Object())));
+    static assert(!__traits(compiles, (new Object()).ifThrown(1)));
+
+    //Use a lambda to get the thrown object.
+    assert("%s".format().ifThrown(e => e.classinfo.name) == "std.format.FormatException");
+}
+
+unittest
+{
+    import std.string;
+    import std.conv;
+    import core.exception;
+    //Basic behaviour - all versions.
+    assert("1".to!int().ifThrown(0) == 1);
+    assert("x".to!int().ifThrown(0) == 0);
+    assert("1".to!int().ifThrown!ConvException(0) == 1);
+    assert("x".to!int().ifThrown!ConvException(0) == 0);
+    assert("1".to!int().ifThrown(e=>0) == 1);
+    assert("x".to!int().ifThrown(e=>0) == 0);
+    static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled
+    {
+        assert("1".to!int().ifThrown!ConvException(e=>0) == 1);
+        assert("x".to!int().ifThrown!ConvException(e=>0) == 0);
+    }
+
+    //Exceptions other than stated not caught.
+    assert("x".to!int().ifThrown!StringException(0).collectException!ConvException() !is null);
+    static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled
+    {
+        assert("x".to!int().ifThrown!StringException(e=>0).collectException!ConvException() !is null);
+    }
+
+    //Default does not include errors.
+    int throwRangeError() { throw new RangeError; }
+    assert(throwRangeError().ifThrown(0).collectException!RangeError() !is null);
+    assert(throwRangeError().ifThrown(e=>0).collectException!RangeError() !is null);
+
+    //Incompatible types are not accepted.
+    static assert(!__traits(compiles, 1.ifThrown(new Object())));
+    static assert(!__traits(compiles, (new Object()).ifThrown(1)));
+    static assert(!__traits(compiles, 1.ifThrown(e=>new Object())));
+    static assert(!__traits(compiles, (new Object()).ifThrown(e=>1)));
+}
+
+version(unittest) package
+@property void assertCTFEable(alias dg)()
+{
+    static assert({ cast(void)dg(); return true; }());
+    cast(void)dg();
+}
+
+/** This $(D enum) is used to select the primitives of the range to handle by the
+  $(LREF handle) range wrapper. The values of the $(D enum) can be $(D OR)'d to
+  select multiple primitives to be handled.
+
+  $(D RangePrimitive.access) is a shortcut for the access primitives; $(D front),
+  $(D back) and $(D opIndex).
+
+  $(D RangePrimitive.pop) is a shortcut for the mutating primitives;
+  $(D popFront) and $(D popBack).
+ */
+enum RangePrimitive
+{
+    front    = 0b00_0000_0001, ///
+    back     = 0b00_0000_0010, /// Ditto
+    popFront = 0b00_0000_0100, /// Ditto
+    popBack  = 0b00_0000_1000, /// Ditto
+    empty    = 0b00_0001_0000, /// Ditto
+    save     = 0b00_0010_0000, /// Ditto
+    length   = 0b00_0100_0000, /// Ditto
+    opDollar = 0b00_1000_0000, /// Ditto
+    opIndex  = 0b01_0000_0000, /// Ditto
+    opSlice  = 0b10_0000_0000, /// Ditto
+    access   = front | back | opIndex, /// Ditto
+    pop      = popFront | popBack, /// Ditto
+}
+
+/** Handle exceptions thrown from range primitives.
+
+Use the $(LREF RangePrimitive) enum to specify which primitives to _handle.
+Multiple range primitives can be handled at once by using the $(D OR) operator
+or the pseudo-primitives $(D RangePrimitive.access) and $(D RangePrimitive.pop).
+All handled primitives must have return types or values compatible with the
+user-supplied handler.
+
+Params:
+    E = The type of $(D Throwable) to _handle.
+    primitivesToHandle = Set of range primitives to _handle.
+    handler = The callable that is called when a handled primitive throws a
+    $(D Throwable) of type $(D E). The handler must accept arguments of
+    the form $(D E, ref IRange) and its return value is used as the primitive's
+    return value whenever $(D E) is thrown. For $(D opIndex), the handler can
+    optionally recieve a third argument; the index that caused the exception.
+    input = The range to _handle.
+
+Returns: A wrapper $(D struct) that preserves the range interface of $(D input).
+
+opSlice:
+Infinite ranges with slicing support must return an instance of
+$(XREF range, Take) when sliced with a specific lower and upper
+bound (see $(XREF_PACK range,primitives,hasSlicing)); $(D handle) deals with
+this by $(D take)ing 0 from the return value of the handler function and
+returning that when an exception is caught.
+*/
+auto handle(E : Throwable, RangePrimitive primitivesToHandle, alias handler, Range)(Range input)
+    if (isInputRange!Range)
+{
+    static struct Handler
+    {
+        private Range range;
+
+        static if (isForwardRange!Range)
+        {
+            @property typeof(this) save()
+            {
+                static if (primitivesToHandle & RangePrimitive.save)
+                {
+                    try
+                    {
+                        return typeof(this)(range.save);
+                    }
+                    catch(E exception)
+                    {
+                        return typeof(this)(handler(exception, this.range));
+                    }
+                }
+                else
+                    return typeof(this)(range.save);
+            }
+        }
+
+        static if (isInfinite!Range)
+        {
+            enum bool empty = false;
+        }
+        else
+        {
+            @property bool empty()
+            {
+                static if (primitivesToHandle & RangePrimitive.empty)
+                {
+                    try
+                    {
+                        return this.range.empty;
+                    }
+                    catch(E exception)
+                    {
+                        return handler(exception, this.range);
+                    }
+                }
+                else
+                    return this.range.empty;
+            }
+        }
+
+        @property auto ref front()
+        {
+            static if (primitivesToHandle & RangePrimitive.front)
+            {
+                try
+                {
+                    return this.range.front;
+                }
+                catch(E exception)
+                {
+                    return handler(exception, this.range);
+                }
+            }
+            else
+                return this.range.front;
+        }
+
+        void popFront()
+        {
+            static if (primitivesToHandle & RangePrimitive.popFront)
+            {
+                try
+                {
+                    this.range.popFront();
+                }
+                catch(E exception)
+                {
+                    handler(exception, this.range);
+                }
+            }
+            else
+                this.range.popFront();
+        }
+
+        static if (isBidirectionalRange!Range)
+        {
+            @property auto ref back()
+            {
+                static if (primitivesToHandle & RangePrimitive.back)
+                {
+                    try
+                    {
+                        return this.range.back;
+                    }
+                    catch(E exception)
+                    {
+                        return handler(exception, this.range);
+                    }
+                }
+                else
+                    return this.range.back;
+            }
+
+            void popBack()
+            {
+                static if (primitivesToHandle & RangePrimitive.popBack)
+                {
+                    try
+                    {
+                        this.range.popBack();
+                    }
+                    catch(E exception)
+                    {
+                        handler(exception, this.range);
+                    }
+                }
+                else
+                    this.range.popBack();
+            }
+        }
+
+        static if (isRandomAccessRange!Range)
+        {
+            auto ref opIndex(size_t index)
+            {
+                static if (primitivesToHandle & RangePrimitive.opIndex)
+                {
+                    try
+                    {
+                        return this.range[index];
+                    }
+                    catch(E exception)
+                    {
+                        static if (__traits(compiles, handler(exception, this.range, index)))
+                            return handler(exception, this.range, index);
+                        else
+                            return handler(exception, this.range);
+                    }
+                }
+                else
+                    return this.range[index];
+            }
+        }
+
+        static if (hasLength!Range)
+        {
+            @property auto length()
+            {
+                static if (primitivesToHandle & RangePrimitive.length)
+                {
+                    try
+                    {
+                        return this.range.length;
+                    }
+                    catch(E exception)
+                    {
+                        return handler(exception, this.range);
+                    }
+                }
+                else
+                    return this.range.length;
+            }
+        }
+
+        static if (hasSlicing!Range)
+        {
+            static if (hasLength!Range)
+            {
+                typeof(this) opSlice(size_t lower, size_t upper)
+                {
+                    static if (primitivesToHandle & RangePrimitive.opSlice)
+                    {
+                        try
+                        {
+                            return typeof(this)(this.range[lower .. upper]);
+                        }
+                        catch(E exception)
+                        {
+                            return typeof(this)(handler(exception, this.range));
+                        }
+                    }
+                    else
+                        return typeof(this)(this.range[lower .. upper]);
+                }
+            }
+            else static if (is(typeof(Range.init[size_t.init .. $])))
+            {
+                static struct DollarToken {}
+                enum opDollar = DollarToken.init;
+
+                typeof(this) opSlice(size_t lower, DollarToken)
+                {
+                    static if (primitivesToHandle & RangePrimitive.opSlice)
+                    {
+                        try
+                        {
+                            return typeof(this)(this.range[lower .. $]);
+                        }
+                        catch(E exception)
+                        {
+                            return typeof(this)(handler(exception, this.range));
+                        }
+                    }
+                    else
+                        return typeof(this)(this.range[lower .. $]);
+                }
+
+                Take!Handler opSlice(size_t lower, size_t upper)
+                {
+                    static if (primitivesToHandle & RangePrimitive.opSlice)
+                    {
+                        try
+                        {
+                            return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1);
+                        }
+                        catch(E exception)
+                        {
+                            return takeExactly(typeof(this)(handler(exception, this.range)), 0);
+                        }
+                    }
+                    else
+                        return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1);
+                }
+            }
+        }
+    }
+
+    return Handler(input);
+}
+
+///
+pure @safe unittest
+{
+    import std.algorithm : equal, map, splitter;
+    import std.conv : to, ConvException;
+
+    auto s = "12,1337z32,54,2,7,9,1z,6,8";
+
+    // The next line composition will throw when iterated
+    // as some elements of the input do not convert to integer
+    auto r = s.splitter(',').map!(a => to!int(a));
+
+    // Substitute 0 for cases of ConvException
+    auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0);
+    assert(h.equal([12, 0, 54, 2, 7, 9, 0, 6, 8]));
+}
+
+///
+pure @safe unittest
+{
+    import std.algorithm : equal;
+    import std.range : retro;
+    import std.utf : UTFException;
+
+    auto str = "hello\xFFworld"; // 0xFF is an invalid UTF-8 code unit
+
+    auto handled = str.handle!(UTFException, RangePrimitive.access,
+            (e, r) => ' '); // Replace invalid code points with spaces
+
+    assert(handled.equal("hello world")); // `front` is handled,
+    assert(handled.retro.equal("dlrow olleh")); // as well as `back`
+}
+
+pure nothrow @safe unittest
+{
+    static struct ThrowingRange
+    {
+        pure @safe:
+        @property bool empty()
+        {
+            throw new Exception("empty has thrown");
+        }
+
+        @property int front()
+        {
+            throw new Exception("front has thrown");
+        }
+
+        @property int back()
+        {
+            throw new Exception("back has thrown");
+        }
+
+        void popFront()
+        {
+            throw new Exception("popFront has thrown");
+        }
+
+        void popBack()
+        {
+            throw new Exception("popBack has thrown");
+        }
+
+        int opIndex(size_t)
+        {
+            throw new Exception("opIndex has thrown");
+        }
+
+        ThrowingRange opSlice(size_t, size_t)
+        {
+            throw new Exception("opSlice has thrown");
+        }
+
+        @property size_t length()
+        {
+            throw new Exception("length has thrown");
+        }
+
+        alias opDollar = length;
+
+        @property ThrowingRange save()
+        {
+            throw new Exception("save has thrown");
+        }
+    }
+
+    static assert(isInputRange!ThrowingRange);
+    static assert(isForwardRange!ThrowingRange);
+    static assert(isBidirectionalRange!ThrowingRange);
+    static assert(hasSlicing!ThrowingRange);
+    static assert(hasLength!ThrowingRange);
+
+    auto f = ThrowingRange();
+    auto fb = f.handle!(Exception, RangePrimitive.front | RangePrimitive.back,
+            (e, r) => -1)();
+    assert(fb.front == -1);
+    assert(fb.back == -1);
+    assertThrown(fb.popFront());
+    assertThrown(fb.popBack());
+    assertThrown(fb.empty);
+    assertThrown(fb.save);
+    assertThrown(fb[0]);
+
+    auto accessRange = f.handle!(Exception, RangePrimitive.access,
+            (e, r) => -1);
+    assert(accessRange.front == -1);
+    assert(accessRange.back == -1);
+    assert(accessRange[0] == -1);
+    assertThrown(accessRange.popFront());
+    assertThrown(accessRange.popBack());
+
+    auto pfb = f.handle!(Exception, RangePrimitive.pop, (e, r) => -1)();
+
+    pfb.popFront(); // this would throw otherwise
+    pfb.popBack(); // this would throw otherwise
+
+    auto em = f.handle!(Exception,
+            RangePrimitive.empty, (e, r) => false)();
+
+    assert(!em.empty);
+
+    auto arr = f.handle!(Exception,
+            RangePrimitive.opIndex, (e, r) => 1337)();
+
+    assert(arr[0] == 1337);
+
+    auto arr2 = f.handle!(Exception,
+            RangePrimitive.opIndex, (e, r, i) => i)();
+
+    assert(arr2[0] == 0);
+    assert(arr2[1337] == 1337);
+
+    auto save = f.handle!(Exception,
+        RangePrimitive.save,
+        function(Exception e, ref ThrowingRange r) {
+            return ThrowingRange();
+        })();
+
+    save.save;
+
+    auto slice = f.handle!(Exception,
+        RangePrimitive.opSlice, (e, r) => ThrowingRange())();
+
+    auto sliced = slice[0 .. 1337]; // this would throw otherwise
+
+    static struct Infinite
+    {
+        pure @safe:
+        enum bool empty = false;
+        int front() { assert(false); }
+        void popFront() { assert(false); }
+        Infinite save() @property { assert(false); }
+        static struct DollarToken {}
+        enum opDollar = DollarToken.init;
+        Take!Infinite opSlice(size_t, size_t) { assert(false); }
+        Infinite opSlice(size_t, DollarToken)
+        {
+            throw new Exception("opSlice has thrown");
+        }
+    }
+
+    static assert(isInputRange!Infinite);
+    static assert(isInfinite!Infinite);
+    static assert(hasSlicing!Infinite);
+
+    assertThrown(Infinite()[0 .. $]);
+
+    auto infinite = Infinite.init.handle!(Exception,
+        RangePrimitive.opSlice, (e, r) => Infinite())();
+
+    auto infSlice = infinite[0 .. $]; // this would throw otherwise
+}
+
+
+/++
+    Convenience mixin for trivially sub-classing exceptions
+
+    Even trivially sub-classing an exception involves writing boilerplate code
+    for the constructor to: 1) correctly pass in the source file and line number
+    the exception was thrown from; 2) be usable with $(LREF enforce) which
+    expects exception constructors to take arguments in a fixed order. This
+    mixin provides that boilerplate code.
+
+    Note however that you need to mark the $(B mixin) line with at least a
+    minimal (i.e. just $(B ///)) DDoc comment if you want the mixed-in
+    constructors to be documented in the newly created Exception subclass.
+
+    $(RED Current limitation): Due to
+    $(LINK2 https://issues.dlang.org/show_bug.cgi?id=11500, bug #11500),
+    currently the constructors specified in this mixin cannot be overloaded with
+    any other custom constructors. Thus this mixin can currently only be used
+    when no such custom constructors need to be explicitly specified.
+ +/
+mixin template basicExceptionCtors()
+{
+    /++
+        Params:
+            msg  = The message for the exception.
+            file = The file where the exception occurred.
+            line = The line number where the exception occurred.
+            next = The previous exception in the chain of exceptions, if any.
+    +/
+    this(string msg, string file = __FILE__, size_t line = __LINE__,
+         Throwable next = null) @nogc @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+
+    /++
+        Params:
+            msg  = The message for the exception.
+            next = The previous exception in the chain of exceptions.
+            file = The file where the exception occurred.
+            line = The line number where the exception occurred.
+    +/
+    this(string msg, Throwable next, string file = __FILE__,
+         size_t line = __LINE__) @nogc @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+}
+
+///
+unittest
+{
+    class MeaCulpa: Exception
+    {
+        ///
+        mixin basicExceptionCtors;
+    }
+
+    try
+        throw new MeaCulpa("test");
+    catch (MeaCulpa e)
+    {
+        assert(e.msg == "test");
+        assert(e.file == __FILE__);
+        assert(e.line == __LINE__ - 5);
+    }
+}
+
+@safe pure nothrow unittest
+{
+    class TestException : Exception { mixin basicExceptionCtors; }
+    auto e = new Exception("msg");
+    auto te1 = new TestException("foo");
+    auto te2 = new TestException("foo", e);
+}
+
+unittest
+{
+    class TestException : Exception { mixin basicExceptionCtors; }
+    auto e = new Exception("!!!");
+
+    auto te1 = new TestException("message", "file", 42, e);
+    assert(te1.msg == "message");
+    assert(te1.file == "file");
+    assert(te1.line == 42);
+    assert(te1.next is e);
+
+    auto te2 = new TestException("message", e, "file", 42);
+    assert(te2.msg == "message");
+    assert(te2.file == "file");
+    assert(te2.line == 42);
+    assert(te2.next is e);
+
+    auto te3 = new TestException("foo");
+    assert(te3.msg == "foo");
+    assert(te3.file == __FILE__);
+    assert(te3.line == __LINE__ - 3);
+    assert(te3.next is null);
+
+    auto te4 = new TestException("foo", e);
+    assert(te4.msg == "foo");
+    assert(te4.file == __FILE__);
+    assert(te4.line == __LINE__ - 3);
+    assert(te4.next is e);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d
new file mode 100644
index 00000000000..cc59abb5bd2
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d
@@ -0,0 +1,294 @@
+module std.experimental.allocator.building_blocks.affix_allocator;
+
+/**
+
+Allocator that adds some extra data before (of type $(D Prefix)) and/or after
+(of type $(D Suffix)) any allocation made with its parent allocator. This is
+useful for uses where additional allocation-related information is needed, such
+as mutexes, reference counts, or walls for debugging memory corruption errors.
+
+If $(D Prefix) is not $(D void), $(D Allocator) must guarantee an alignment at
+least as large as $(D Prefix.alignof).
+
+Suffixes are slower to get at because of alignment rounding, so prefixes should
+be preferred. However, small prefixes blunt the alignment so if a large
+alignment with a small affix is needed, suffixes should be chosen.
+
+The following methods are defined if $(D Allocator) defines them, and forward to it: $(D deallocateAll), $(D empty), $(D owns).
+ */
+struct AffixAllocator(Allocator, Prefix, Suffix = void)
+{
+    import std.conv, std.experimental.allocator.common, std.traits;
+    import std.algorithm : min;
+
+    static assert(
+        !stateSize!Prefix || Allocator.alignment >= Prefix.alignof,
+        "AffixAllocator does not work with allocators offering a smaller"
+        ~ " alignment than the prefix alignment.");
+    static assert(alignment % Suffix.alignof == 0,
+        "This restriction could be relaxed in the future.");
+
+    /**
+    If $(D Prefix) is $(D void), the alignment is that of the parent. Otherwise, the alignment is the same as the $(D Prefix)'s alignment.
+    */
+    enum uint alignment = isPowerOf2(stateSize!Prefix)
+        ? min(stateSize!Prefix, Allocator.alignment)
+        : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment);
+
+    /**
+    If the parent allocator $(D Allocator) is stateful, an instance of it is
+    stored as a member. Otherwise, $(D AffixAllocator) uses
+    `Allocator.instance`. In either case, the name $(D _parent) is uniformly
+    used for accessing the parent allocator.
+    */
+    static if (stateSize!Allocator) Allocator parent;
+    else alias parent = Allocator.instance;
+
+    private template Impl()
+    {
+        size_t goodAllocSize(size_t s)
+        {
+            auto a = actualAllocationSize(s);
+            return roundUpToMultipleOf(parent.goodAllocSize(a)
+                    - stateSize!Prefix - stateSize!Suffix,
+                this.alignment);
+        }
+
+        private size_t actualAllocationSize(size_t s) const
+        {
+            assert(s > 0);
+            static if (!stateSize!Suffix)
+            {
+                return s + stateSize!Prefix;
+            }
+            else
+            {
+                return
+                    roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof)
+                    + stateSize!Suffix;
+            }
+        }
+
+        private void[] actualAllocation(void[] b) const
+        {
+            assert(b !is null);
+            return (b.ptr - stateSize!Prefix)
+                [0 .. actualAllocationSize(b.length)];
+        }
+
+        void[] allocate(size_t bytes)
+        {
+            if (!bytes) return null;
+            auto result = parent.allocate(actualAllocationSize(bytes));
+            if (result is null) return null;
+            static if (stateSize!Prefix)
+            {
+                assert(result.ptr.alignedAt(Prefix.alignof));
+                emplace!Prefix(cast(Prefix*)result.ptr);
+            }
+            static if (stateSize!Suffix)
+            {
+                auto suffixP = result.ptr + result.length - Suffix.sizeof;
+                assert(suffixP.alignedAt(Suffix.alignof));
+                emplace!Suffix(cast(Suffix*)(suffixP));
+            }
+            return result[stateSize!Prefix .. stateSize!Prefix + bytes];
+        }
+
+        static if (hasMember!(Allocator, "allocateAll"))
+        void[] allocateAll()
+        {
+            auto result = parent.allocateAll();
+            if (result is null) return null;
+            if (result.length < actualAllocationSize(1))
+            {
+                deallocate(result);
+                return null;
+            }
+            static if (stateSize!Prefix)
+            {
+                assert(result.length > stateSize!Prefix);
+                emplace!Prefix(cast(Prefix*)result.ptr);
+                result = result[stateSize!Prefix .. $];
+            }
+            static if (stateSize!Suffix)
+            {
+                assert(result.length > stateSize!Suffix);
+                // Ehm, find a properly aligned place for the suffix
+                auto p = (result.ptr + result.length - stateSize!Suffix)
+                    .alignDownTo(Suffix.alignof);
+                assert(p > result.ptr);
+                emplace!Suffix(cast(Suffix*) p);
+                result = result[0 .. p - result.ptr];
+            }
+            return result;
+        }
+
+        static if (hasMember!(Allocator, "owns"))
+        Ternary owns(void[] b)
+        {
+            if (b is null) return Ternary.no;
+            return parent.owns(actualAllocation(b));
+        }
+
+        static if (hasMember!(Allocator, "resolveInternalPointer"))
+        void[] resolveInternalPointer(void* p)
+        {
+            auto p1 = parent.resolveInternalPointer(p);
+            if (p1 is null) return p1;
+            p1 = p1[stateSize!Prefix .. $];
+            auto p2 = (p1.ptr + p1.length - stateSize!Suffix)
+                    .alignDownTo(Suffix.alignof);
+            return p1[0 .. p2 - p1.ptr];
+        }
+
+        static if (!stateSize!Suffix && hasMember!(Allocator, "expand"))
+        bool expand(ref void[] b, size_t delta)
+        {
+            if (!b.ptr)
+            {
+                b = allocate(delta);
+                return b.length == delta;
+            }
+            auto t = actualAllocation(b);
+            const result = parent.expand(t, delta);
+            if (!result) return false;
+            b = b.ptr[0 .. b.length + delta];
+            return true;
+        }
+
+        static if (hasMember!(Allocator, "reallocate"))
+        bool reallocate(ref void[] b, size_t s)
+        {
+            if (b is null)
+            {
+                b = allocate(s);
+                return b.length == s;
+            }
+            auto t = actualAllocation(b);
+            const result = parent.reallocate(t, actualAllocationSize(s));
+            if (!result) return false; // no harm done
+            b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s];
+            return true;
+        }
+
+        static if (hasMember!(Allocator, "deallocate"))
+        bool deallocate(void[] b)
+        {
+            if (!b.ptr) return true;
+            return parent.deallocate(actualAllocation(b));
+        }
+
+        /* The following methods are defined if $(D ParentAllocator) defines
+        them, and forward to it: $(D deallocateAll), $(D empty).*/
+        mixin(forwardToMember("parent",
+            "deallocateAll", "empty"));
+
+        // Extra functions
+        static if (stateSize!Prefix)
+            static ref Prefix prefix(void[] b)
+            {
+                assert(b.ptr && b.ptr.alignedAt(Prefix.alignof));
+                return (cast(Prefix*)b.ptr)[-1];
+            }
+        static if (stateSize!Suffix)
+            ref Suffix suffix(void[] b)
+            {
+                assert(b.ptr);
+                auto p = b.ptr - stateSize!Prefix
+                    + actualAllocationSize(b.length);
+                assert(p && p.alignedAt(Suffix.alignof));
+                return (cast(Suffix*) p)[-1];
+            }
+    }
+
+    version (StdDdoc)
+    {
+        /**
+        Standard allocator methods. Each is defined if and only if the parent
+        allocator defines the homonym method (except for $(D goodAllocSize),
+        which may use the global default). Also, the methods will be $(D
+        shared) if the parent allocator defines them as such.
+        */
+        size_t goodAllocSize(size_t);
+        /// Ditto
+        void[] allocate(size_t);
+        /// Ditto
+        Ternary owns(void[]);
+        /// Ditto
+        bool expand(ref void[] b, size_t delta);
+        /// Ditto
+        bool reallocate(ref void[] b, size_t s);
+        /// Ditto
+        bool deallocate(void[] b);
+        /// Ditto
+        bool deallocateAll();
+        /// Ditto
+        Ternary empty();
+
+        /**
+        The `instance` singleton is defined if and only if the parent allocator has no state and defines its own `it` object.
+        */
+        static AffixAllocator instance;
+
+        /**
+        Affix access functions offering mutable references to the affixes of a block previously allocated with this allocator. $(D b) may not be null. They are defined if and only if the corresponding affix is not $(D void).
+
+        Precondition: $(D b !is null)
+        */
+        static ref Prefix prefix(void[] b);
+        /// Ditto
+        static ref Suffix suffix(void[] b);
+    }
+    else static if (is(typeof(Allocator.instance) == shared))
+    {
+        static shared AffixAllocator instance;
+        shared { mixin Impl!(); }
+    }
+    else
+    {
+        mixin Impl!();
+        static if (stateSize!Allocator == 0)
+            static __gshared AffixAllocator instance;
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    // One word before and after each allocation.
+    alias A = AffixAllocator!(Mallocator, size_t, size_t);
+    auto b = A.instance.allocate(11);
+    A.instance.prefix(b) = 0xCAFE_BABE;
+    A.instance.suffix(b) = 0xDEAD_BEEF;
+    assert(A.instance.prefix(b) == 0xCAFE_BABE
+        && A.instance.suffix(b) == 0xDEAD_BEEF);
+}
+
+unittest
+{
+    import std.experimental.allocator.building_blocks.bitmapped_block
+        : BitmappedBlock;
+    import std.experimental.allocator.common : testAllocator;
+    testAllocator!({
+        auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong)
+            (BitmappedBlock!128(new void[128 * 4096]));
+        return a;
+    });
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    alias A = AffixAllocator!(Mallocator, size_t);
+    auto b = A.instance.allocate(10);
+    A.instance.prefix(b) = 10;
+    assert(A.instance.prefix(b) == 10);
+
+    import std.experimental.allocator.building_blocks.null_allocator
+        : NullAllocator;
+    alias B = AffixAllocator!(NullAllocator, size_t);
+    b = B.instance.allocate(100);
+    assert(b is null);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d
new file mode 100644
index 00000000000..cfb0a8f5f08
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d
@@ -0,0 +1,621 @@
+module std.experimental.allocator.building_blocks.allocator_list;
+
+import std.experimental.allocator.common;
+import std.experimental.allocator.building_blocks.null_allocator;
+import std.experimental.allocator.gc_allocator;
+version(unittest) import std.stdio;
+
+// Turn this on for debugging
+// debug = allocator_list;
+
+/**
+
+Given an $(LUCKY object factory) of type `Factory` or a factory function
+`factoryFunction`, and optionally also `BookkeepingAllocator` as a supplemental
+allocator for bookkeeping, `AllocatorList` creates an allocator that lazily
+creates as many allocators are needed for satisfying client allocation requests.
+
+An embedded list builds a most-recently-used strategy: the most recent
+allocators used in calls to either `allocate`, `owns` (successful calls
+only), or `deallocate` are tried for new allocations in order of their most
+recent use. Thus, although core operations take in theory $(BIGOH k) time for
+$(D k) allocators in current use, in many workloads the factor is sublinear.
+Details of the actual strategy may change in future releases.
+
+`AllocatorList` is primarily intended for coarse-grained handling of
+allocators, i.e. the number of allocators in the list is expected to be
+relatively small compared to the number of allocations handled by each
+allocator. However, the per-allocator overhead is small so using
+`AllocatorList` with a large number of allocators should be satisfactory as long
+as the most-recently-used strategy is fast enough for the application.
+
+`AllocatorList` makes an effort to return allocated memory back when no
+longer used. It does so by destroying empty allocators. However, in order to
+avoid thrashing (excessive creation/destruction of allocators under certain use
+patterns), it keeps unused allocators for a while.
+
+Params:
+factoryFunction = A function or template function (including function literals).
+New allocators are created by calling `factoryFunction(n)` with strictly
+positive numbers `n`. Delegates that capture their enviroment are not created
+amid concerns regarding garbage creation for the environment. When the factory
+needs state, a `Factory` object should be used.
+
+BookkeepingAllocator = Allocator used for storing bookkeeping data. The size of
+bookkeeping data is proportional to the number of allocators. If $(D
+BookkeepingAllocator) is $(D NullAllocator), then $(D AllocatorList) is
+"ouroboros-style", i.e. it keeps the bookkeeping data in memory obtained from
+the allocators themselves. Note that for ouroboros-style management, the size
+$(D n) passed to $(D make) will be occasionally different from the size
+requested by client code.
+
+Factory = Type of a factory object that returns new allocators on a need
+basis. For an object $(D sweatshop) of type $(D Factory), `sweatshop(n)` should
+return an allocator able to allocate at least `n` bytes (i.e. `Factory` must
+define `opCall(size_t)` to return an allocator object). Usually the capacity of
+allocators created should be much larger than $(D n) such that an allocator can
+be used for many subsequent allocations. $(D n) is passed only to ensure the
+minimum necessary for the next allocation. The factory object is allowed to hold
+state, which will be stored inside `AllocatorList` as a direct `public` member
+called `factory`.
+
+*/
+struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator)
+{
+    import std.traits : hasMember;
+    import std.conv : emplace;
+    import std.algorithm : min, move;
+    import std.experimental.allocator.building_blocks.stats_collector
+        : StatsCollector, Options;
+
+    private enum ouroboros = is(BookkeepingAllocator == NullAllocator);
+
+    /**
+    Alias for `typeof(Factory()(1))`, i.e. the type of the individual
+    allocators.
+    */
+    alias Allocator = typeof(Factory.init(1));
+    // Allocator used internally
+    private alias SAllocator = StatsCollector!(Allocator, Options.bytesUsed);
+
+    private static struct Node
+    {
+        // Allocator in this node
+        SAllocator a;
+        Node* next;
+
+        @disable this(this);
+
+        // Is this node unused?
+        void setUnused() { next = &this; }
+        bool unused() const { return next is &this; }
+
+        // Just forward everything to the allocator
+        alias a this;
+    }
+
+    /**
+    If $(D BookkeepingAllocator) is not $(D NullAllocator), $(D bkalloc) is
+    defined and accessible.
+    */
+
+    // State is stored in an array, but it has a list threaded through it by
+    // means of "nextIdx".
+
+    // state {
+    static if (!ouroboros)
+    {
+        static if (stateSize!BookkeepingAllocator) BookkeepingAllocator bkalloc;
+        else alias bkalloc = BookkeepingAllocator.instance;
+    }
+    static if (stateSize!Factory)
+    {
+        Factory factory;
+    }
+    private Node[] allocators;
+    private Node* root;
+    // }
+
+    static if (stateSize!Factory)
+    {
+        private auto make(size_t n) { return factory(n); }
+    }
+    else
+    {
+        private auto make(size_t n) { Factory f; return f(n); }
+    }
+
+    /**
+    Constructs an `AllocatorList` given a factory object. This constructor is
+    defined only if `Factory` has state.
+    */
+    static if (stateSize!Factory)
+    this(ref Factory plant)
+    {
+        factory = plant;
+    }
+    /// Ditto
+    static if (stateSize!Factory)
+    this(Factory plant)
+    {
+        factory = plant;
+    }
+
+    static if (hasMember!(Allocator, "deallocateAll")
+        && hasMember!(Allocator, "owns"))
+    ~this()
+    {
+        deallocateAll;
+    }
+
+    /**
+    The alignment offered.
+    */
+    enum uint alignment = Allocator.alignment;
+
+    /**
+    Allocate a block of size $(D s). First tries to allocate from the existing
+    list of already-created allocators. If neither can satisfy the request,
+    creates a new allocator by calling $(D make(s)) and delegates the request
+    to it. However, if the allocation fresh off a newly created allocator
+    fails, subsequent calls to $(D allocate) will not cause more calls to $(D
+    make).
+    */
+    void[] allocate(size_t s)
+    {
+        for (auto p = &root, n = *p; n; p = &n.next, n = *p)
+        {
+            auto result = n.allocate(s);
+            if (result.length != s) continue;
+            // Bring to front if not already
+            if (root != n)
+            {
+                *p = n.next;
+                n.next = root;
+                root = n;
+                return result;
+            }
+        }
+        // Can't allocate from the current pool. Check if we just added a new
+        // allocator, in that case it won't do any good to add yet another.
+        if (root && root.empty == Ternary.yes)
+        {
+            // no can do
+            return null;
+        }
+        // Add a new allocator
+        if (auto a = addAllocator(s))
+        {
+            auto result = a.allocate(s);
+            assert(owns(result) == Ternary.yes || !result.ptr);
+            return result;
+        }
+        return null;
+    }
+
+    private void moveAllocators(void[] newPlace)
+    {
+        assert(newPlace.ptr.alignedAt(Node.alignof));
+        assert(newPlace.length % Node.sizeof == 0);
+        auto newAllocators = cast(Node[]) newPlace;
+        assert(allocators.length <= newAllocators.length);
+
+        // Move allocators
+        foreach (i, ref e; allocators)
+        {
+            if (e.unused)
+            {
+                newAllocators[i].setUnused;
+                continue;
+            }
+            import core.stdc.string : memcpy;
+            memcpy(&newAllocators[i].a, &e.a, e.a.sizeof);
+            if (e.next)
+            {
+                newAllocators[i].next = newAllocators.ptr
+                    + (e.next - allocators.ptr);
+            }
+            else
+            {
+                newAllocators[i].next = null;
+            }
+        }
+
+        // Mark the unused portion as unused
+        foreach (i; allocators.length .. newAllocators.length)
+        {
+            newAllocators[i].setUnused;
+        }
+        auto toFree = allocators;
+
+        // Change state {
+        root = newAllocators.ptr + (root - allocators.ptr);
+        allocators = newAllocators;
+        // }
+
+        // Free the olden buffer
+        static if (ouroboros)
+        {
+            static if (hasMember!(Allocator, "deallocate")
+                    && hasMember!(Allocator, "owns"))
+                deallocate(toFree);
+        }
+        else
+        {
+            bkalloc.deallocate(toFree);
+        }
+    }
+
+    static if (ouroboros)
+    private Node* addAllocator(size_t atLeastBytes)
+    {
+        void[] t = allocators;
+        static if (hasMember!(Allocator, "expand")
+            && hasMember!(Allocator, "owns"))
+        {
+            immutable bool expanded = t && this.expand(t, Node.sizeof);
+        }
+        else
+        {
+            enum expanded = false;
+        }
+        if (expanded)
+        {
+            import std.c.string : memcpy;
+            assert(t.length % Node.sizeof == 0);
+            assert(t.ptr.alignedAt(Node.alignof));
+            allocators = cast(Node[]) t;
+            allocators[$ - 1].setUnused;
+            auto newAlloc = SAllocator(make(atLeastBytes));
+            memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof);
+            emplace(&newAlloc);
+        }
+        else
+        {
+            immutable toAlloc = (allocators.length + 1) * Node.sizeof
+                + atLeastBytes + 128;
+            auto newAlloc = SAllocator(make(toAlloc));
+            auto newPlace = newAlloc.allocate(
+                (allocators.length + 1) * Node.sizeof);
+            if (!newPlace) return null;
+            moveAllocators(newPlace);
+            import core.stdc.string : memcpy;
+            memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof);
+            emplace(&newAlloc);
+            assert(allocators[$ - 1].owns(allocators) == Ternary.yes);
+        }
+        // Insert as new root
+        if (root != &allocators[$ - 1])
+        {
+            allocators[$ - 1].next = root;
+            root = &allocators[$ - 1];
+        }
+        else
+        {
+            // This is the first one
+            root.next = null;
+        }
+        assert(!root.unused);
+        return root;
+    }
+
+    static if (!ouroboros)
+    private Node* addAllocator(size_t atLeastBytes)
+    {
+        void[] t = allocators;
+        static if (hasMember!(BookkeepingAllocator, "expand"))
+            immutable bool expanded = bkalloc.expand(t, Node.sizeof);
+        else
+            immutable bool expanded = false;
+        if (expanded)
+        {
+            assert(t.length % Node.sizeof == 0);
+            assert(t.ptr.alignedAt(Node.alignof));
+            allocators = cast(Node[]) t;
+            allocators[$ - 1].setUnused;
+        }
+        else
+        {
+            // Could not expand, create a new block
+            t = bkalloc.allocate((allocators.length + 1) * Node.sizeof);
+            assert(t.length % Node.sizeof == 0);
+            if (!t.ptr) return null;
+            moveAllocators(t);
+        }
+        assert(allocators[$ - 1].unused);
+        auto newAlloc = SAllocator(make(atLeastBytes));
+        import core.stdc.string : memcpy;
+        memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof);
+        emplace(&newAlloc);
+        // Creation succeeded, insert as root
+        if (allocators.length == 1)
+            allocators[$ - 1].next = null;
+        else
+            allocators[$ - 1].next = root;
+        assert(allocators[$ - 1].a.bytesUsed == 0);
+        root = &allocators[$ - 1];
+        return root;
+    }
+
+    /**
+    Defined only if `Allocator` defines `owns`. Tries each allocator in
+    turn, in most-recently-used order. If the owner is found, it is moved to
+    the front of the list as a side effect under the assumption it will be used
+    soon.
+
+    Returns: `Ternary.yes` if one allocator was found to return `Ternary.yes`,
+    `Ternary.no` if all component allocators returned `Ternary.no`, and
+    `Ternary.unknown` if no allocator returned `Ternary.yes` and at least one
+    returned  `Ternary.unknown`.
+    */
+    static if (hasMember!(Allocator, "owns"))
+    Ternary owns(void[] b)
+    {
+        auto result = Ternary.no;
+        for (auto p = &root, n = *p; n; p = &n.next, n = *p)
+        {
+            immutable t = n.owns(b);
+            if (t != Ternary.yes)
+            {
+                if (t == Ternary.unknown) result = t;
+                continue;
+            }
+            // Move the owner to front, speculating it'll be used
+            if (n != root)
+            {
+                *p = n.next;
+                n.next = root;
+                root = n;
+            }
+            return Ternary.yes;
+        }
+        return result;
+    }
+
+    /**
+    Defined only if $(D Allocator.expand) is defined. Finds the owner of $(D b)
+    and calls $(D expand) for it. The owner is not brought to the head of the
+    list.
+    */
+    static if (hasMember!(Allocator, "expand")
+        && hasMember!(Allocator, "owns"))
+    bool expand(ref void[] b, size_t delta)
+    {
+        if (!b.ptr)
+        {
+            b = allocate(delta);
+            return b.length == delta;
+        }
+        for (auto p = &root, n = *p; n; p = &n.next, n = *p)
+        {
+            if (n.owns(b) == Ternary.yes) return n.expand(b, delta);
+        }
+        return false;
+    }
+
+    /**
+    Defined only if $(D Allocator.reallocate) is defined. Finds the owner of
+    $(D b) and calls $(D reallocate) for it. If that fails, calls the global
+    $(D reallocate), which allocates a new block and moves memory.
+    */
+    static if (hasMember!(Allocator, "reallocate"))
+    bool reallocate(ref void[] b, size_t s)
+    {
+        // First attempt to reallocate within the existing node
+        if (!b.ptr)
+        {
+            b = allocate(s);
+            return b.length == s;
+        }
+        for (auto p = &root, n = *p; n; p = &n.next, n = *p)
+        {
+            if (n.owns(b) == Ternary.yes) return n.reallocate(b, s);
+        }
+        // Failed, but we may find new memory in a new node.
+        return .reallocate(this, b, s);
+    }
+
+    /**
+     Defined if $(D Allocator.deallocate) and $(D Allocator.owns) are defined.
+    */
+    static if (hasMember!(Allocator, "deallocate")
+        && hasMember!(Allocator, "owns"))
+    bool deallocate(void[] b)
+    {
+        if (!b.ptr) return true;
+        assert(allocators.length);
+        assert(owns(b) == Ternary.yes);
+        bool result;
+        for (auto p = &root, n = *p; ; p = &n.next, n = *p)
+        {
+            assert(n);
+            if (n.owns(b) != Ternary.yes) continue;
+            result = n.deallocate(b);
+            // Bring to front
+            if (n != root)
+            {
+                *p = n.next;
+                n.next = root;
+                root = n;
+            }
+            if (n.empty != Ternary.yes) return result;
+            break;
+        }
+        // Hmmm... should we return this allocator back to the wild? Let's
+        // decide if there are TWO empty allocators we can release ONE. This
+        // is to avoid thrashing.
+        // Note that loop starts from the second element.
+        for (auto p = &root.next, n = *p; n; p = &n.next, n = *p)
+        {
+            if (n.unused || n.empty != Ternary.yes) continue;
+            // Used and empty baby, nuke it!
+            n.a.destroy;
+            *p = n.next;
+            n.setUnused;
+            break;
+        }
+        return result;
+    }
+
+    /**
+    Defined only if $(D Allocator.owns) and $(D Allocator.deallocateAll) are
+    defined.
+    */
+    static if (ouroboros && hasMember!(Allocator, "deallocateAll")
+        && hasMember!(Allocator, "owns"))
+    bool deallocateAll()
+    {
+        Node* special;
+        foreach (ref n; allocators)
+        {
+            if (n.unused) continue;
+            if (n.owns(allocators) == Ternary.yes)
+            {
+                special = &n;
+                continue;
+            }
+            n.a.deallocateAll;
+            n.a.destroy;
+        }
+        assert(special || !allocators.ptr);
+        if (special)
+        {
+            special.deallocate(allocators);
+        }
+        allocators = null;
+        root = null;
+        return true;
+    }
+
+    static if (!ouroboros && hasMember!(Allocator, "deallocateAll")
+        && hasMember!(Allocator, "owns"))
+    bool deallocateAll()
+    {
+        foreach (ref n; allocators)
+        {
+            if (n.unused) continue;
+            n.a.deallocateAll;
+            n.a.destroy;
+        }
+        bkalloc.deallocate(allocators);
+        allocators = null;
+        root = null;
+        return true;
+    }
+
+    /**
+     Returns `Ternary.yes` if no allocators are currently active,
+    `Ternary.no` otherwise. This methods never returns `Ternary.unknown`.
+    */
+    Ternary empty() const
+    {
+        return Ternary(!allocators.length);
+    }
+}
+
+/// Ditto
+template AllocatorList(alias factoryFunction,
+    BookkeepingAllocator = GCAllocator)
+{
+    alias A = typeof(factoryFunction(1));
+    static assert(
+        // is a template function (including literals)
+        is(typeof({A function(size_t) @system x = factoryFunction!size_t;}))
+        ||
+        // or a function (including literals)
+        is(typeof({A function(size_t) @system x = factoryFunction;}))
+        ,
+        "Only function names and function literals that take size_t"
+            ~ " and return an allocator are accepted, not "
+            ~ typeof(factoryFunction).stringof
+    );
+    static struct Factory
+    {
+        A opCall(size_t n) { return factoryFunction(n); }
+    }
+    alias AllocatorList = .AllocatorList!(Factory, BookkeepingAllocator);
+}
+
+///
+version(Posix) unittest
+{
+    import std.algorithm : max;
+    import std.experimental.allocator.building_blocks.region : Region;
+    import std.experimental.allocator.mmap_allocator : MmapAllocator;
+    import std.experimental.allocator.building_blocks.segregator : Segregator;
+    import std.experimental.allocator.building_blocks.free_list
+        : ContiguousFreeList;
+
+    // Ouroboros allocator list based upon 4MB regions, fetched directly from
+    // mmap. All memory is released upon destruction.
+    alias A1 = AllocatorList!((n) => Region!MmapAllocator(max(n, 1024 * 4096)),
+        NullAllocator);
+
+    // Allocator list based upon 4MB regions, fetched from the garbage
+    // collector. All memory is released upon destruction.
+    alias A2 = AllocatorList!((n) => Region!GCAllocator(max(n, 1024 * 4096)));
+
+    // Ouroboros allocator list based upon 4MB regions, fetched from the garbage
+    // collector. Memory is left to the collector.
+    alias A3 = AllocatorList!(
+        (n) => Region!NullAllocator(new void[max(n, 1024 * 4096)]),
+        NullAllocator);
+
+    // Allocator list that creates one freelist for all objects
+    alias A4 =
+        Segregator!(
+            64, AllocatorList!(
+                (n) => ContiguousFreeList!(NullAllocator, 0, 64)(
+                    GCAllocator.instance.allocate(4096))),
+            GCAllocator);
+
+    A4 a;
+    auto small = a.allocate(64);
+    assert(small);
+    a.deallocate(small);
+    auto b1 = a.allocate(1024 * 8192);
+    assert(b1 !is null); // still works due to overdimensioning
+    b1 = a.allocate(1024 * 10);
+    assert(b1.length == 1024 * 10);
+}
+
+unittest
+{
+    // Create an allocator based upon 4MB regions, fetched from the GC heap.
+    import std.algorithm : max;
+    import std.experimental.allocator.building_blocks.region : Region;
+    AllocatorList!((n) => Region!GCAllocator(new void[max(n, 1024 * 4096)]),
+        NullAllocator) a;
+    const b1 = a.allocate(1024 * 8192);
+    assert(b1 !is null); // still works due to overdimensioning
+    const b2 = a.allocate(1024 * 10);
+    assert(b2.length == 1024 * 10);
+    a.deallocateAll();
+}
+
+unittest
+{
+    // Create an allocator based upon 4MB regions, fetched from the GC heap.
+    import std.algorithm : max;
+    import std.experimental.allocator.building_blocks.region : Region;
+    AllocatorList!((n) => Region!()(new void[max(n, 1024 * 4096)])) a;
+    auto b1 = a.allocate(1024 * 8192);
+    assert(b1 !is null); // still works due to overdimensioning
+    b1 = a.allocate(1024 * 10);
+    assert(b1.length == 1024 * 10);
+    a.deallocateAll();
+}
+
+unittest
+{
+    import std.algorithm : max;
+    import std.experimental.allocator.building_blocks.region : Region;
+    AllocatorList!((n) => Region!()(new void[max(n, 1024 * 4096)])) a;
+    auto b1 = a.allocate(1024 * 8192);
+    assert(b1 !is null);
+    b1 = a.allocate(1024 * 10);
+    assert(b1.length == 1024 * 10);
+    a.allocate(1024 * 4095);
+    a.deallocateAll();
+    assert(a.empty == Ternary.yes);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d
new file mode 100644
index 00000000000..e9d24e9c401
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d
@@ -0,0 +1,1439 @@
+module std.experimental.allocator.building_blocks.bitmapped_block;
+
+import std.experimental.allocator.common;
+import std.experimental.allocator.building_blocks.null_allocator;
+
+/**
+
+$(D BitmappedBlock) implements a simple heap consisting of one contiguous area
+of memory organized in blocks, each of size $(D theBlockSize). A block is a unit
+of allocation. A bitmap serves as bookkeeping data, more precisely one bit per
+block indicating whether that block is currently allocated or not.
+
+Passing $(D NullAllocator) as $(D ParentAllocator) (the default) means user code
+manages allocation of the memory block from the outside; in that case
+$(D BitmappedBlock) must be constructed with a $(D void[]) preallocated block and
+has no responsibility regarding the lifetime of its support underlying storage.
+If another allocator type is passed, $(D BitmappedBlock) defines a destructor that
+uses the parent allocator to release the memory block. That makes the combination of $(D AllocatorList), $(D BitmappedBlock), and a back-end allocator such as $(D MmapAllocator) a simple and scalable solution for memory allocation.
+
+There are advantages to storing bookkeeping data separated from the payload
+(as opposed to e.g. using $(D AffixAllocator) to store metadata together with
+each allocation). The layout is more compact (overhead is one bit per block),
+searching for a free block during allocation enjoys better cache locality, and
+deallocation does not touch memory around the payload being deallocated (which
+is often cold).
+
+Allocation requests are handled on a first-fit basis. Although linear in
+complexity, allocation is in practice fast because of the compact bookkeeping
+representation, use of simple and fast bitwise routines, and caching of the
+first available block position. A known issue with this general approach is
+fragmentation, partially mitigated by coalescing. Since $(D BitmappedBlock) does
+not need to maintain the allocated size, freeing memory implicitly coalesces
+free blocks together. Also, tuning $(D blockSize) has a considerable impact on
+both internal and external fragmentation.
+
+The size of each block can be selected either during compilation or at run
+time. Statically-known block sizes are frequent in practice and yield slightly
+better performance. To choose a block size statically, pass it as the $(D
+blockSize) parameter as in $(D BitmappedBlock!(Allocator, 4096)). To choose a block
+size parameter, use $(D BitmappedBlock!(Allocator, chooseAtRuntime)) and pass the
+block size to the constructor.
+
+*/
+struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
+    ParentAllocator = NullAllocator)
+{
+    import std.typecons;
+    import std.traits : hasMember;
+    version(unittest) import std.stdio;
+    import std.conv : text;
+
+    unittest
+    {
+        import std.experimental.allocator.mallocator : AlignedMallocator;
+        import std.algorithm : max;
+        auto m = AlignedMallocator.instance.alignedAllocate(1024 * 64,
+            max(theAlignment, cast(uint) size_t.sizeof));
+        scope(exit) AlignedMallocator.instance.deallocate(m);
+        testAllocator!(() => BitmappedBlock(m));
+    }
+    static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment);
+    static assert(theBlockSize == chooseAtRuntime
+        || theBlockSize % theAlignment == 0,
+        "Block size must be a multiple of the alignment");
+
+    /**
+    If $(D blockSize == chooseAtRuntime), $(D BitmappedBlock) offers a read/write
+    property $(D blockSize). It must be set before any use of the allocator.
+    Otherwise (i.e. $(D theBlockSize) is a legit constant), $(D blockSize) is
+    an alias for $(D theBlockSize). Whether constant or variable, must also be
+    a multiple of $(D alignment). This constraint is $(D assert)ed statically
+    and dynamically.
+    */
+    static if (theBlockSize != chooseAtRuntime)
+    {
+        alias blockSize = theBlockSize;
+    }
+    else
+    {
+        @property uint blockSize() { return _blockSize; }
+        @property void blockSize(uint s)
+        {
+            assert(!_control && s % alignment == 0);
+            _blockSize = s;
+        }
+        private uint _blockSize;
+    }
+
+    static if (is(ParentAllocator == NullAllocator))
+    {
+        private enum parentAlignment = platformAlignment;
+    }
+    else
+    {
+        private alias parentAlignment = ParentAllocator.alignment;
+        static assert(parentAlignment >= ulong.alignof);
+    }
+
+    /**
+    The _alignment offered is user-configurable statically through parameter
+    $(D theAlignment), defaulted to $(D platformAlignment).
+    */
+    alias alignment = theAlignment;
+
+    // state {
+    /**
+    The _parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator)
+    {
+        ParentAllocator parent;
+    }
+    else
+    {
+        alias parent = ParentAllocator.instance;
+    }
+    private uint _blocks;
+    private BitVector _control;
+    private void[] _payload;
+    private size_t _startIdx;
+    // }
+
+    private size_t totalAllocation(size_t capacity)
+    {
+        auto blocks = capacity.divideRoundUp(blockSize);
+        auto leadingUlongs = blocks.divideRoundUp(64);
+        import std.algorithm : min;
+        immutable initialAlignment = min(parentAlignment,
+            1U << trailingZeros(leadingUlongs * 8));
+        auto maxSlack = alignment <= initialAlignment
+            ? 0
+            : alignment - initialAlignment;
+        //writeln(maxSlack);
+        return leadingUlongs * 8 + maxSlack + blockSize * blocks;
+    }
+
+    /**
+    Constructs a block allocator given a hunk of memory, or a desired capacity
+    in bytes.
+
+    $(UL
+    $(LI If $(D ParentAllocator) is $(D NullAllocator), only the constructor
+    taking $(D data) is defined and the user is responsible for freeing $(D
+    data) if desired.)
+    $(LI Otherwise, both constructors are defined. The $(D data)-based
+    constructor assumes memory has been allocated with the parent allocator.
+    The $(D capacity)-based constructor uses $(D ParentAllocator) to allocate
+    an appropriate contiguous hunk of memory. Regardless of the constructor
+    used, the destructor releases the memory by using $(D
+    ParentAllocator.deallocate).)
+    )
+    */
+    this(void[] data)
+    {
+        immutable a = data.ptr.effectiveAlignment;
+        assert(a >= size_t.alignof || !data.ptr,
+            "Data must be aligned properly");
+
+        immutable ulong totalBits = data.length * 8;
+        immutable ulong bitsPerBlock = blockSize * 8 + 1;
+        // Get a first estimate
+        import std.conv : to;
+        _blocks = to!uint(totalBits / bitsPerBlock);
+
+        // Reality is a bit more complicated, iterate until a good number of
+        // blocks found.
+        for (; _blocks; --_blocks)
+        {
+            immutable controlWords = _blocks.divideRoundUp(64);
+            auto payload = data[controlWords * 8 .. $].roundStartToMultipleOf(
+                alignment);
+            if (payload.length < _blocks * blockSize)
+            {
+                // Overestimated
+                continue;
+            }
+            _control = BitVector((cast(ulong*)data.ptr)[0 .. controlWords]);
+            _control[] = 0;
+            _payload = payload;
+            break;
+        }
+    }
+
+    /// Ditto
+    static if (!is(ParentAllocator == NullAllocator))
+    this(size_t capacity)
+    {
+        size_t toAllocate = totalAllocation(capacity);
+        auto data = parent.allocate(toAllocate);
+        this(data);
+        assert(_blocks * blockSize >= capacity);
+    }
+
+    /**
+    If $(D ParentAllocator) is not $(D NullAllocator) and defines $(D
+    deallocate), the destructor is defined to deallocate the block held.
+    */
+    static if (!is(ParentAllocator == NullAllocator)
+        && hasMember!(ParentAllocator, "deallocate"))
+    ~this()
+    {
+        auto start = _control.rep.ptr, end = _payload.ptr + _payload.length;
+        parent.deallocate(start[0 .. end - start]);
+    }
+
+    /*
+    Adjusts the memoized _startIdx to the leftmost control word that has at
+    least one zero bit. Assumes all control words to the left of $(D
+    _control[_startIdx]) are already occupied.
+    */
+    private void adjustStartIdx()
+    {
+        while (_startIdx < _control.rep.length
+            && _control.rep[_startIdx] == ulong.max)
+        {
+            ++_startIdx;
+        }
+    }
+
+    /*
+    Returns the blocks corresponding to the control bits starting at word index
+    wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks.
+    */
+    private void[] blocksFor(size_t wordIdx, uint msbIdx, size_t howManyBlocks)
+    {
+        assert(msbIdx <= 63);
+        const start = (wordIdx * 64 + msbIdx) * blockSize;
+        const end = start + blockSize * howManyBlocks;
+        if (end <= _payload.length) return _payload[start .. end];
+        // This could happen if we have more control bits than available memory.
+        // That's possible because the control bits are rounded up to fit in
+        // 64-bit words.
+        return null;
+    }
+
+    /**
+    Returns the actual bytes allocated when $(D n) bytes are requested, i.e.
+    $(D n.roundUpToMultipleOf(blockSize)).
+    */
+    size_t goodAllocSize(size_t n)
+    {
+        return n.roundUpToMultipleOf(blockSize);
+    }
+
+    /**
+    Allocates $(D s) bytes of memory and returns it, or $(D null) if memory
+    could not be allocated.
+
+    The following information might be of help with choosing the appropriate
+    block size. Actual allocation occurs in sizes multiple of the block size.
+    Allocating one block is the fastest because only one 0 bit needs to be
+    found in the metadata. Allocating 2 through 64 blocks is the next cheapest
+    because it affects a maximum of two $(D ulong)s in the metadata.
+    Allocations greater than 64 blocks require a multiword search through the
+    metadata.
+    */
+    @trusted void[] allocate(const size_t s)
+    {
+        const blocks = s.divideRoundUp(blockSize);
+        //writefln("Allocating %s blocks each of size %s", blocks, blockSize);
+        void[] result = void;
+
+    switcharoo:
+        switch (blocks)
+        {
+        case 1:
+            // inline code here for speed
+            // find the next available block
+            foreach (i; _startIdx .. _control.rep.length)
+            {
+                const w = _control.rep[i];
+                if (w == ulong.max) continue;
+                uint j = leadingOnes(w);
+                assert(j < 64);
+                assert((_control.rep[i] & ((1UL << 63) >> j)) == 0);
+                _control.rep[i] |= (1UL << 63) >> j;
+                if (i == _startIdx)
+                {
+                    adjustStartIdx();
+                }
+                result = blocksFor(i, j, 1);
+                break switcharoo;
+            }
+            goto case 0; // fall through
+        case 0:
+            return null;
+        case 2: .. case 64:
+            result = smallAlloc(cast(uint) blocks);
+            break;
+        default:
+            result = hugeAlloc(blocks);
+            break;
+        }
+        return result.ptr ? result.ptr[0 .. s] : null;
+    }
+
+    /**
+    Allocates a block with specified alignment $(D a). The alignment must be a
+    power of 2. If $(D a <= alignment), function forwards to $(D allocate).
+    Otherwise, it attempts to overallocate and then adjust the result for
+    proper alignment. In the worst case the slack memory is around two blocks.
+    */
+    void[] alignedAllocate(size_t n, uint a)
+    {
+        assert(a.isPowerOf2);
+        if (a <= alignment) return allocate(n);
+
+        // Overallocate to make sure we can get an aligned block
+        auto b = allocate((n + a - alignment).roundUpToMultipleOf(blockSize));
+        if (!b.ptr) return null;
+        auto result = b.roundStartToMultipleOf(a);
+        assert(result.length >= n);
+        result = result.ptr[0 .. n]; // final result
+
+        // Free any blocks that might be slack at the beginning
+        auto slackHeadingBlocks = (result.ptr - b.ptr) / blockSize;
+        if (slackHeadingBlocks)
+        {
+            deallocate(b[0 .. slackHeadingBlocks * blockSize]);
+        }
+
+        // Free any blocks that might be slack at the end
+        auto slackTrailingBlocks = ((b.ptr + b.length)
+            - (result.ptr + result.length)) / blockSize;
+        if (slackTrailingBlocks)
+        {
+            deallocate(b[$ - slackTrailingBlocks * blockSize .. $]);
+        }
+
+        return result;
+    }
+
+    /**
+    If the $(D BitmappedBlock) object is empty (has no active allocation), allocates
+    all memory within and returns a slice to it. Otherwise, returns $(D null)
+    (i.e. no attempt is made to allocate the largest available block).
+    */
+    void[] allocateAll()
+    {
+        if (empty != Ternary.yes) return null;
+        _control[] = 1;
+        return _payload;
+    }
+
+    /**
+    Returns `Ternary.yes` if `b` belongs to the `BitmappedBlock` object,
+    `Ternary.no` otherwise. Never returns `Ternary.unkown`. (This
+    method is somewhat tolerant in that accepts an interior slice.)
+    */
+    Ternary owns(void[] b) const
+    {
+        //if (!b.ptr) return Ternary.no;
+        assert(b.ptr !is null || b.length == 0, "Corrupt block.");
+        return Ternary(b.ptr >= _payload.ptr
+            && b.ptr + b.length <= _payload.ptr + _payload.length);
+    }
+
+    /*
+    Tries to allocate "blocks" blocks at the exact position indicated by the
+    position wordIdx/msbIdx (msbIdx counts from MSB, i.e. MSB has index 0). If
+    it succeeds, fills "result" with the result and returns tuple(size_t.max,
+    0). Otherwise, returns a tuple with the next position to search.
+    */
+    private Tuple!(size_t, uint) allocateAt(size_t wordIdx, uint msbIdx,
+            size_t blocks, ref void[] result)
+    {
+        assert(blocks > 0);
+        assert(wordIdx < _control.rep.length);
+        assert(msbIdx <= 63);
+        if (msbIdx + blocks <= 64)
+        {
+            // Allocation should fit this control word
+            if (setBitsIfZero(_control.rep[wordIdx],
+                    cast(uint) (64 - msbIdx - blocks), 63 - msbIdx))
+            {
+                // Success
+                result = blocksFor(wordIdx, msbIdx, blocks);
+                return tuple(size_t.max, 0u);
+            }
+            // Can't allocate, make a suggestion
+            return msbIdx + blocks == 64
+                ? tuple(wordIdx + 1, 0u)
+                : tuple(wordIdx, cast(uint) (msbIdx + blocks));
+        }
+        // Allocation spans two control words or more
+        immutable mask = ulong.max >> msbIdx;
+        if (_control.rep[wordIdx] & mask)
+        {
+            // We can't allocate the rest of this control word,
+            // return a suggestion.
+            return tuple(wordIdx + 1, 0u);
+        }
+        // We can allocate the rest of this control word, but we first need to
+        // make sure we can allocate the tail.
+        if (wordIdx + 1 == _control.rep.length)
+        {
+            // No more memory
+            return tuple(_control.rep.length, 0u);
+        }
+        auto hint = allocateAt(wordIdx + 1, 0, blocks - 64 + msbIdx, result);
+        if (hint[0] == size_t.max)
+        {
+            // We did it!
+            _control.rep[wordIdx] |= mask;
+            result = blocksFor(wordIdx, msbIdx, blocks);
+            return tuple(size_t.max, 0u);
+        }
+        // Failed, return a suggestion that skips this whole run.
+        return hint;
+    }
+
+    /* Allocates as many blocks as possible at the end of the blocks indicated
+    by wordIdx. Returns the number of blocks allocated. */
+    private uint allocateAtTail(size_t wordIdx)
+    {
+        assert(wordIdx < _control.rep.length);
+        const available = trailingZeros(_control.rep[wordIdx]);
+        _control.rep[wordIdx] |= ulong.max >> available;
+        return available;
+    }
+
+    private void[] smallAlloc(uint blocks)
+    {
+        assert(blocks >= 2 && blocks <= 64, text(blocks));
+        foreach (i; _startIdx .. _control.rep.length)
+        {
+            // Test within the current 64-bit word
+            const v = _control.rep[i];
+            if (v == ulong.max) continue;
+            auto j = findContigOnes(~v, blocks);
+            if (j < 64)
+            {
+                // yay, found stuff
+                setBits(_control.rep[i], 64 - j - blocks, 63 - j);
+                return blocksFor(i, j, blocks);
+            }
+            // Next, try allocations that cross a word
+            auto available = trailingZeros(v);
+            if (available == 0) continue;
+            if (i + 1 >= _control.rep.length) break;
+            assert(available < blocks); // otherwise we should have found it
+            auto needed = blocks - available;
+            assert(needed > 0 && needed < 64);
+            if (allocateAtFront(i + 1, needed))
+            {
+                // yay, found a block crossing two words
+                _control.rep[i] |= (1UL << available) - 1;
+                return blocksFor(i, 64 - available, blocks);
+            }
+        }
+        return null;
+    }
+
+    private void[] hugeAlloc(size_t blocks)
+    {
+        assert(blocks > 64);
+        if (_startIdx == _control._rep.length)
+        {
+            assert(_control.allAre1);
+            return null;
+        }
+        auto i = _control.findZeros(blocks, _startIdx * 64);
+        if (i == i.max) return null;
+        // Allocate those bits
+        _control[i .. i + blocks] = 1;
+        return _payload[cast(size_t) (i * blockSize)
+            .. cast(size_t) ((i + blocks) * blockSize)];
+        //void[] result;
+        //auto pos = tuple(_startIdx, 0);
+        //for (;;)
+        //{
+        //    if (pos[0] >= _control.rep.length)
+        //    {
+        //        // No more memory
+        //        return null;
+        //    }
+        //    pos = allocateAt(pos[0], pos[1], blocks, result);
+        //    if (pos[0] == size_t.max)
+        //    {
+        //        // Found and allocated
+        //        return result;
+        //    }
+        //}
+    }
+
+    // Rounds sizeInBytes to a multiple of blockSize.
+    private size_t bytes2blocks(size_t sizeInBytes)
+    {
+        return (sizeInBytes + blockSize - 1) / blockSize;
+    }
+
+    /* Allocates given blocks at the beginning blocks indicated by wordIdx.
+    Returns true if allocation was possible, false otherwise. */
+    private bool allocateAtFront(size_t wordIdx, uint blocks)
+    {
+        assert(wordIdx < _control.rep.length && blocks >= 1 && blocks <= 64);
+        const mask = (1UL << (64 - blocks)) - 1;
+        if (_control.rep[wordIdx] > mask) return false;
+        // yay, works
+        _control.rep[wordIdx] |= ~mask;
+        return true;
+    }
+
+    /**
+    Expands an allocated block in place.
+    */
+    @trusted bool expand(ref void[] b, immutable size_t delta)
+    {
+        // Dispose with trivial corner cases
+        if (delta == 0) return true;
+        if (b is null)
+        {
+            b = allocate(delta);
+            return b !is null;
+        }
+
+        /* To simplify matters, refuse to expand buffers that don't start at a block start (this may be the case for blocks allocated with alignedAllocate).
+        */
+        if ((b.ptr - _payload.ptr) % blockSize) return false;
+
+        const blocksOld = bytes2blocks(b.length);
+        const blocksNew = bytes2blocks(b.length + delta);
+        assert(blocksOld <= blocksNew);
+
+        // Possibly we have enough slack at the end of the block!
+        if (blocksOld == blocksNew)
+        {
+            b = b.ptr[0 .. b.length + delta];
+            return true;
+        }
+
+        assert((b.ptr - _payload.ptr) % blockSize == 0);
+        const blockIdx = (b.ptr - _payload.ptr) / blockSize;
+        const blockIdxAfter = blockIdx + blocksOld;
+        //writefln("blockIdx: %s, blockIdxAfter: %s", blockIdx, blockIdxAfter);
+
+        // Try the maximum
+        const wordIdx = blockIdxAfter / 64,
+            msbIdx = cast(uint) (blockIdxAfter % 64);
+        void[] p;
+        auto hint = allocateAt(wordIdx, msbIdx,  blocksNew - blocksOld, p);
+        if (hint[0] != size_t.max)
+        {
+            return false;
+        }
+        // Expansion successful
+        assert(p.ptr == b.ptr + blocksOld * blockSize,
+            text(p.ptr, " != ", b.ptr + blocksOld * blockSize));
+        b = b.ptr[0 .. b.length + delta];
+        return true;
+    }
+
+    /**
+    Reallocates a previously-allocated block. Contractions occur in place.
+    */
+    @system bool reallocate(ref void[] b, size_t newSize)
+    {
+        if (!b.ptr)
+        {
+            b = allocate(newSize);
+            return b.length == newSize;
+        }
+        if (newSize == 0)
+        {
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        if (newSize < b.length)
+        {
+            // Shrink. Will shrink in place by deallocating the trailing part.
+            auto newCapacity = bytes2blocks(newSize) * blockSize;
+            deallocate(b[newCapacity .. $]);
+            b = b[0 .. newSize];
+            return true;
+        }
+        // Go the slow route
+        return .reallocate(this, b, newSize);
+    }
+
+    /**
+    Reallocates a block previously allocated with $(D alignedAllocate). Contractions do not occur in place.
+    */
+    @system bool alignedReallocate(ref void[] b, size_t newSize, uint a)
+    {
+        if (newSize == 0)
+        {
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        // Go the slow route
+        return .alignedReallocate(this, b, newSize, a);
+    }
+
+    /**
+    Deallocates a block previously allocated with this allocator.
+    */
+    bool deallocate(void[] b)
+    {
+        if (b is null) return true;
+        // Adjust pointer, might be inside a block after alignedAllocate
+        //auto p = (b.ptr - _payload.ptr) / blockSize
+
+        // Locate position
+        immutable pos = b.ptr - _payload.ptr;
+        immutable blockIdx = pos / blockSize;
+
+        // Adjust pointer, might be inside a block due to alignedAllocate
+        auto begin = _payload.ptr + blockIdx * blockSize,
+            end = b.ptr + b.length;
+        b = begin[0 .. end - begin];
+        // Round up size to multiple of block size
+        auto blocks = b.length.divideRoundUp(blockSize);
+
+        // Get into details
+        auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64);
+        if (_startIdx > wordIdx) _startIdx = wordIdx;
+
+        // Three stages: heading bits, full words, leftover bits
+        if (msbIdx)
+        {
+            if (blocks + msbIdx <= 64)
+            {
+                resetBits(_control.rep[wordIdx],
+                    cast(uint) (64 - msbIdx - blocks),
+                    63 - msbIdx);
+                return true;
+            }
+            else
+            {
+                _control.rep[wordIdx] &= ulong.max << 64 - msbIdx;
+                blocks -= 64 - msbIdx;
+                ++wordIdx;
+                msbIdx = 0;
+            }
+        }
+
+        // Stage 2: reset one word at a time
+        for (; blocks >= 64; blocks -= 64)
+        {
+            _control.rep[wordIdx++] = 0;
+        }
+
+        // Stage 3: deal with leftover bits, if any
+        assert(wordIdx <= _control.rep.length);
+        if (blocks)
+        {
+            _control.rep[wordIdx] &= ulong.max >> blocks;
+        }
+        return true;
+    }
+
+    /**
+    Forcibly deallocates all memory allocated by this allocator, making it
+    available for further allocations. Does not return memory to $(D
+    ParentAllocator).
+    */
+    bool deallocateAll()
+    {
+        _control[] = 0;
+        _startIdx = 0;
+        return true;
+    }
+
+    /**
+    Returns `Ternary.yes` if no memory is currently allocated with this
+    allocator, otherwise `Ternary.no`. This method never returns
+    `Ternary.unknown`.
+    */
+    Ternary empty()
+    {
+        return Ternary(_control.allAre0());
+    }
+
+    void dump()
+    {
+        import std.stdio;
+        writefln("%s @ %s {", typeid(this), cast(void*) _control._rep.ptr);
+        scope(exit) writeln("}");
+        assert(_payload.length == blockSize * _blocks);
+        assert(_control.length >= _blocks);
+        writefln("  _startIdx=%s; blockSize=%s; blocks=%s",
+            _startIdx, blockSize, _blocks);
+        if (!_control.length) return;
+        uint blockCount = 1;
+        bool inAllocatedStore = _control[0];
+        void* start = _payload.ptr;
+        for (size_t i = 1;; ++i)
+        {
+            if (i >= _blocks || _control[i] != inAllocatedStore)
+            {
+                writefln("  %s block at 0x%s, length: %s (%s*%s)",
+                    inAllocatedStore ? "Busy" : "Free",
+                    cast(void*) start,
+                    blockCount * blockSize,
+                    blockCount, blockSize);
+                if (i >= _blocks) break;
+                assert(i < _control.length);
+                inAllocatedStore = _control[i];
+                start = _payload.ptr + blockCount * blockSize;
+                blockCount = 1;
+            }
+            else
+            {
+                ++blockCount;
+            }
+        }
+    }
+}
+
+///
+unittest
+{
+    // Create a block allocator on top of a 10KB stack region.
+    import std.experimental.allocator.building_blocks.region : InSituRegion;
+    import std.traits : hasMember;
+    InSituRegion!(10_240, 64) r;
+    auto a = BitmappedBlock!(64, 64)(r.allocateAll());
+    static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll"));
+    const b = a.allocate(100);
+    assert(b.length == 100);
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    testAllocator!(() => BitmappedBlock!(64, 8, GCAllocator)(1024 * 64));
+}
+
+unittest
+{
+    static void testAllocateAll(size_t bs)(uint blocks, uint blocksAtATime)
+    {
+        import std.algorithm : min;
+        assert(bs);
+        import std.experimental.allocator.gc_allocator : GCAllocator;
+        auto a = BitmappedBlock!(bs, min(bs, platformAlignment))(
+            GCAllocator.instance.allocate((blocks * bs * 8 + blocks) / 8)
+        );
+        import std.conv : text;
+        assert(blocks >= a._blocks, text(blocks, " < ", a._blocks));
+        blocks = a._blocks;
+
+        // test allocation of 0 bytes
+        auto x = a.allocate(0);
+        assert(x is null);
+        // test allocation of 1 byte
+        x = a.allocate(1);
+        assert(x.length == 1 || blocks == 0,
+            text(x.ptr, " ", x.length, " ", a));
+        a.deallocateAll();
+
+        //writeln("Control words: ", a._control.length);
+        //writeln("Payload bytes: ", a._payload.length);
+        bool twice = true;
+
+    begin:
+        foreach (i; 0 .. blocks / blocksAtATime)
+        {
+            auto b = a.allocate(bs * blocksAtATime);
+            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+        }
+        assert(a.allocate(bs * blocksAtATime) is null);
+        assert(a.allocate(1) is null);
+
+        // Now deallocate all and do it again!
+        a.deallocateAll();
+
+        // Test deallocation
+
+        auto v = new void[][blocks / blocksAtATime];
+        foreach (i; 0 .. blocks / blocksAtATime)
+        {
+            auto b = a.allocate(bs * blocksAtATime);
+            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+            v[i] = b;
+        }
+        assert(a.allocate(bs * blocksAtATime) is null);
+        assert(a.allocate(1) is null);
+
+        foreach (i; 0 .. blocks / blocksAtATime)
+        {
+            a.deallocate(v[i]);
+        }
+
+        foreach (i; 0 .. blocks / blocksAtATime)
+        {
+            auto b = a.allocate(bs * blocksAtATime);
+            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+            v[i] = b;
+        }
+
+        foreach (i; 0 .. v.length)
+        {
+            a.deallocate(v[i]);
+        }
+
+        if (twice)
+        {
+            twice = false;
+            goto begin;
+        }
+
+        a.deallocateAll;
+
+        // test expansion
+        if (blocks >= blocksAtATime)
+        {
+            foreach (i; 0 .. blocks / blocksAtATime - 1)
+            {
+                auto b = a.allocate(bs * blocksAtATime);
+                assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+                (cast(ubyte[]) b)[] = 0xff;
+                a.expand(b, blocksAtATime * bs)
+                    || assert(0, text(i));
+                (cast(ubyte[]) b)[] = 0xfe;
+                assert(b.length == bs * blocksAtATime * 2, text(i, ": ", b.length));
+                a.reallocate(b, blocksAtATime * bs) || assert(0);
+                assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+            }
+        }
+    }
+
+    testAllocateAll!(1)(0, 1);
+    testAllocateAll!(1)(8, 1);
+    testAllocateAll!(4096)(128, 1);
+
+    testAllocateAll!(1)(0, 2);
+    testAllocateAll!(1)(128, 2);
+    testAllocateAll!(4096)(128, 2);
+
+    testAllocateAll!(1)(0, 4);
+    testAllocateAll!(1)(128, 4);
+    testAllocateAll!(4096)(128, 4);
+
+    testAllocateAll!(1)(0, 3);
+    testAllocateAll!(1)(24, 3);
+    testAllocateAll!(3008)(100, 1);
+    testAllocateAll!(3008)(100, 3);
+
+    testAllocateAll!(1)(0, 128);
+    testAllocateAll!(1)(128 * 1, 128);
+    testAllocateAll!(128 * 20)(13 * 128, 128);
+}
+
+// Test totalAllocation
+unittest
+{
+    BitmappedBlock!(8, 8, NullAllocator) h1;
+    assert(h1.totalAllocation(1) >= 8);
+    assert(h1.totalAllocation(64) >= 64);
+    //writeln(h1.totalAllocation(8 * 64));
+    assert(h1.totalAllocation(8 * 64) >= 8 * 64);
+    assert(h1.totalAllocation(8 * 63) >= 8 * 63);
+    assert(h1.totalAllocation(8 * 64 + 1) >= 8 * 65);
+
+    BitmappedBlock!(64, 8, NullAllocator) h2;
+    assert(h2.totalAllocation(1) >= 64);
+    assert(h2.totalAllocation(64 * 64) >= 64 * 64);
+
+    BitmappedBlock!(4096, 4096, NullAllocator) h3;
+    assert(h3.totalAllocation(1) >= 4096);
+    assert(h3.totalAllocation(64 * 4096) >= 64 * 4096);
+    assert(h3.totalAllocation(64 * 4096 + 1) >= 65 * 4096);
+}
+
+// BitmappedBlockWithInternalPointers
+/**
+
+A $(D BitmappedBlock) with additional structure for supporting $(D
+resolveInternalPointer). To that end, $(D BitmappedBlockWithInternalPointers) adds a
+bitmap (one bit per block) that marks object starts. The bitmap itself has
+variable size and is allocated together with regular allocations.
+
+The time complexity of $(D resolveInternalPointer) is $(BIGOH k), where $(D k)
+is the size of the object within which the internal pointer is looked up.
+
+*/
+struct BitmappedBlockWithInternalPointers(
+    size_t theBlockSize, uint theAlignment = platformAlignment,
+    ParentAllocator = NullAllocator)
+{
+    import std.conv : text;
+    unittest
+    {
+        import std.experimental.allocator.mallocator : AlignedMallocator;
+        auto m = AlignedMallocator.instance.alignedAllocate(1024 * 64,
+            theAlignment);
+        scope(exit) AlignedMallocator.instance.deallocate(m);
+        testAllocator!(() => BitmappedBlockWithInternalPointers(m));
+    }
+
+    // state {
+    private BitmappedBlock!(theBlockSize, theAlignment, NullAllocator) _heap;
+    private BitVector _allocStart;
+    // }
+
+    /**
+    Constructors accepting desired capacity or a preallocated buffer, similar
+    in semantics to those of $(D BitmappedBlock).
+    */
+    this(void[] data)
+    {
+        _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)(data);
+    }
+
+    /// Ditto
+    static if (!is(ParentAllocator == NullAllocator))
+    this(size_t capacity)
+    {
+        // Add room for the _allocStart vector
+        _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)
+            (capacity + capacity.divideRoundUp(64));
+    }
+
+    // Makes sure there's enough room for _allocStart
+    private bool ensureRoomForAllocStart(size_t len)
+    {
+        if (_allocStart.length >= len) return true;
+        // Must ensure there's room
+        immutable oldLength = _allocStart.rep.length;
+        immutable bits = len.roundUpToMultipleOf(64);
+        void[] b = _allocStart.rep;
+        if (!_heap.reallocate(b, bits / 8)) return false;
+        assert(b.length * 8 == bits, text(b.length * 8, " != ", bits));
+        _allocStart = BitVector(cast(ulong[]) b);
+        assert(_allocStart.rep.length * 64 == bits);
+        _allocStart.rep[oldLength .. $] = ulong.max;
+        return true;
+    }
+
+    /**
+    Allocator primitives.
+    */
+    alias alignment = theAlignment;
+
+    /// Ditto
+    size_t goodAllocSize(size_t n)
+    {
+        return n.roundUpToMultipleOf(_heap.blockSize);
+    }
+
+    /// Ditto
+    void[] allocate(size_t bytes)
+    {
+        auto r = _heap.allocate(bytes);
+        if (!r.ptr) return r;
+        immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize;
+        immutable blocks =
+            (r.length + _heap.blockSize - 1) / _heap.blockSize;
+        if (!ensureRoomForAllocStart(block + blocks))
+        {
+            // Failed, free r and bailout
+            _heap.deallocate(r);
+            return null;
+        }
+        assert(block < _allocStart.length);
+        assert(block + blocks <= _allocStart.length);
+        // Mark the _allocStart bits
+        assert(blocks > 0);
+        _allocStart[block] = 1;
+        _allocStart[block + 1 .. block + blocks] = 0;
+        assert(block + blocks == _allocStart.length
+            || _allocStart[block + blocks] == 1);
+        return r;
+    }
+
+    /// Ditto
+    void[] allocateAll()
+    {
+        auto r = _heap.allocateAll();
+        if (!r.ptr) return r;
+        // Carve space at the end for _allocStart
+        auto p = alignDownTo(r.ptr + r.length - 8, ulong.alignof);
+        r = r[0 .. p - r.ptr];
+        // Initialize _allocStart
+        _allocStart = BitVector(cast(ulong[]) p[0 .. 8]);
+        _allocStart[] = 0;
+        immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize;
+        assert(block < _allocStart.length);
+        _allocStart[block] = 1;
+        return r;
+    }
+
+    /// Ditto
+    bool expand(ref void[] b, size_t bytes)
+    {
+        if (!bytes) return true;
+        if (b is null)
+        {
+            b = allocate(bytes);
+            return b !is null;
+        }
+        immutable oldBlocks =
+            (b.length + _heap.blockSize - 1) / _heap.blockSize;
+        assert(oldBlocks);
+        immutable newBlocks =
+            (b.length + bytes + _heap.blockSize - 1) / _heap.blockSize;
+        assert(newBlocks >= oldBlocks);
+        immutable block = (b.ptr - _heap._payload.ptr) / _heap.blockSize;
+        assert(_allocStart[block]);
+        if (!ensureRoomForAllocStart(block + newBlocks)
+                || !_heap.expand(b, bytes))
+        {
+            return false;
+        }
+        // Zero only the expanded bits
+        _allocStart[block + oldBlocks .. block + newBlocks] = 0;
+        assert(_allocStart[block]);
+        return true;
+    }
+
+    /// Ditto
+    bool deallocate(void[] b)
+    {
+        // No need to touch _allocStart here - except for the first bit, it's
+        // meaningless in freed memory. The first bit is already 1.
+        return _heap.deallocate(b);
+        // TODO: one smart thing to do is reduce memory occupied by
+        // _allocStart if we're freeing the rightmost block.
+    }
+
+    /// Ditto
+    void[] resolveInternalPointer(void* p)
+    {
+        if (p < _heap._payload.ptr
+            || p >= _heap._payload.ptr + _heap._payload.length)
+        {
+            return null;
+        }
+        // Find block start
+        auto block = (p - _heap._payload.ptr) / _heap.blockSize;
+        if (block >= _allocStart.length) return null;
+        // This may happen during marking, so comment it out.
+        // if (!_heap._control[block]) return null;
+        // Within an allocation, must find the 1 just to the left of it
+        auto i = _allocStart.find1Backward(block);
+        if (i == i.max) return null;
+        auto j = _allocStart.find1(i + 1);
+        return _heap._payload.ptr[cast(size_t) (_heap.blockSize * i)
+            .. cast(size_t) (_heap.blockSize * j)];
+    }
+
+    /// Ditto
+    Ternary empty()
+    {
+        return _heap.empty;
+    }
+
+    // Currently unused
+    private void markAllAsUnused()
+    {
+        // Mark all deallocated memory with 1 so we minimize damage created by
+        // false pointers. TODO: improve speed.
+        foreach (i, ref e; _allocStart.rep)
+        {
+            // Set to 1 all bits in _allocStart[i] that were 0 in control, and
+            // leave the others unchanged.
+            // (0, 0) => 1; (0, 1) => 0; (1, 0) => 1; (1, 1) => 1
+            e |= ~_heap._control.rep[i];
+        }
+        // Now zero all control bits
+        _heap._control[] = 0;
+        // EXCEPT for the _allocStart block itself
+        markAsUsed(_allocStart.rep);
+    }
+
+    // Currently unused
+    private bool markAsUsed(void[] b)
+    {
+        // Locate position
+        immutable pos = b.ptr - _heap._payload.ptr;
+        assert(pos % _heap.blockSize == 0);
+        auto blockIdx = pos / _heap.blockSize;
+        if (_heap._control[blockIdx]) return false;
+        // Round up size to multiple of block size
+        auto blocks = b.length.divideRoundUp(_heap.blockSize);
+        _heap._control[blockIdx .. blockIdx + blocks] = 1;
+        return true;
+    }
+
+    // Currently unused
+    private void doneMarking()
+    {
+        // Nothing to do, what's free stays free.
+    }
+}
+
+unittest
+{
+    auto h = BitmappedBlockWithInternalPointers!(4096)(new void[4096 * 1024]);
+    auto b = h.allocate(123);
+    assert(b.length == 123);
+    const p = h.resolveInternalPointer(b.ptr + 17);
+    assert(p.ptr is b.ptr);
+    assert(p.length >= b.length);
+    b = h.allocate(4096);
+    assert(h.resolveInternalPointer(b.ptr) is b);
+    assert(h.resolveInternalPointer(b.ptr + 11) is b);
+    assert(h.resolveInternalPointer(b.ptr - 40_970) is null);
+
+    assert(h.expand(b, 1));
+    assert(b.length == 4097);
+    assert(h.resolveInternalPointer(b.ptr + 4096).ptr is b.ptr);
+}
+
+/**
+Returns the number of most significant ones before a zero can be found in $(D
+x). If $(D x) contains no zeros (i.e. is equal to $(D ulong.max)), returns 64.
+*/
+private uint leadingOnes(ulong x)
+{
+    uint result = 0;
+    while (cast(long) x < 0)
+    {
+        ++result;
+        x <<= 1;
+    }
+    return result;
+}
+
+unittest
+{
+    assert(leadingOnes(0) == 0);
+    assert(leadingOnes(~0UL) == 64);
+    assert(leadingOnes(0xF000_0000_0000_0000) == 4);
+    assert(leadingOnes(0xE400_0000_0000_0000) == 3);
+    assert(leadingOnes(0xC700_0200_0000_0000) == 2);
+    assert(leadingOnes(0x8000_0030_0000_0000) == 1);
+    assert(leadingOnes(0x2000_0000_0000_0000) == 0);
+}
+
+/**
+Finds a run of contiguous ones in $(D x) of length at least $(D n).
+*/
+private uint findContigOnes(ulong x, uint n)
+{
+    while (n > 1)
+    {
+        immutable s = n >> 1;
+        x &= x << s;
+        n -= s;
+    }
+    return leadingOnes(~x);
+}
+
+unittest
+{
+    assert(findContigOnes(0x0000_0000_0000_0300, 2) == 54);
+
+    assert(findContigOnes(~0UL, 1) == 0);
+    assert(findContigOnes(~0UL, 2) == 0);
+    assert(findContigOnes(~0UL, 32) == 0);
+    assert(findContigOnes(~0UL, 64) == 0);
+    assert(findContigOnes(0UL, 1) == 64);
+
+    assert(findContigOnes(0x4000_0000_0000_0000, 1) == 1);
+    assert(findContigOnes(0x0000_0F00_0000_0000, 4) == 20);
+}
+
+/*
+Unconditionally sets the bits from lsb through msb in w to zero.
+*/
+private void setBits(ref ulong w, uint lsb, uint msb)
+{
+    assert(lsb <= msb && msb < 64);
+    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+    w |= mask;
+}
+
+unittest
+{
+    ulong w;
+    w = 0; setBits(w, 0, 63); assert(w == ulong.max);
+    w = 0; setBits(w, 1, 63); assert(w == ulong.max - 1);
+    w = 6; setBits(w, 0, 1); assert(w == 7);
+    w = 6; setBits(w, 3, 3); assert(w == 14);
+}
+
+/* Are bits from lsb through msb in w zero? If so, make then 1
+and return the resulting w. Otherwise, just return 0.
+*/
+private bool setBitsIfZero(ref ulong w, uint lsb, uint msb)
+{
+    assert(lsb <= msb && msb < 64);
+    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+    if (w & mask) return false;
+    w |= mask;
+    return true;
+}
+
+// Assigns bits in w from lsb through msb to zero.
+private void resetBits(ref ulong w, uint lsb, uint msb)
+{
+    assert(lsb <= msb && msb < 64);
+    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+    w &= ~mask;
+}
+
+/*
+Bit disposition is MSB=0 (leftmost, big endian).
+*/
+private struct BitVector
+{
+    ulong[] _rep;
+
+    auto rep() { return _rep; }
+
+    this(ulong[] data) { _rep = data; }
+
+    void opSliceAssign(bool b) { _rep[] = b ? ulong.max : 0; }
+
+    void opSliceAssign(bool b, ulong x, ulong y)
+    {
+        assert(x <= y && y <= _rep.length * 64);
+        if (x == y) return;
+        --y;
+        assert(x / 64 <= size_t.max);
+        immutable i1 = cast(size_t) (x / 64);
+        immutable uint b1 = 63 - x % 64;
+        assert(y / 64 <= size_t.max);
+        immutable i2 = cast(size_t) (y / 64);
+        immutable uint b2 = 63 - y % 64;
+        assert(i1 <= i2 && i2 < _rep.length);
+        if (i1 == i2)
+        {
+            // Inside the same word
+            assert(b1 >= b2);
+            if (b) setBits(_rep[i1], b2, b1);
+            else resetBits(_rep[i1], b2, b1);
+        }
+        else
+        {
+            // Spans multiple words
+            assert(i1 < i2);
+            if (b) setBits(_rep[i1], 0, b1);
+            else resetBits(_rep[i1], 0, b1);
+            _rep[i1 + 1 .. i2] = b;
+            if (b) setBits(_rep[i2], b2, 63);
+            else resetBits(_rep[i2], b2, 63);
+        }
+    }
+
+    bool opIndex(ulong x)
+    {
+        assert(x < length);
+        return (_rep[cast(size_t) (x / 64)]
+            & (0x8000_0000_0000_0000UL >> (x % 64))) != 0;
+    }
+
+    void opIndexAssign(bool b, ulong x)
+    {
+        assert(x / 64 <= size_t.max);
+        immutable i = cast(size_t) (x / 64);
+        immutable j = 0x8000_0000_0000_0000UL >> (x % 64);
+        if (b) _rep[i] |= j;
+        else _rep[i] &= ~j;
+    }
+
+    ulong length() const
+    {
+        return _rep.length * 64;
+    }
+
+    /* Returns the index of the first 1 to the right of i (including i itself),
+    or length if not found.
+    */
+    ulong find1(ulong i)
+    {
+        assert(i < length);
+        assert(i / 64 <= size_t.max);
+        auto w = cast(size_t) (i / 64);
+        immutable b = i % 64; // 0 through 63, 0 when i == 0
+        immutable mask = ulong.max >> b;
+        if (auto current = _rep[w] & mask)
+        {
+            // Great, found
+            return w * 64 + leadingOnes(~current);
+        }
+        // The current word doesn't have the solution, find the leftmost 1
+        // going to the right.
+        for (++w; w < _rep.length; ++w)
+        {
+            if (auto current = _rep[w])
+            {
+                return w * 64 + leadingOnes(~current);
+            }
+        }
+        return length;
+    }
+
+    /* Returns the index of the first 1 to the left of i (including i itself),
+    or ulong.max if not found.
+    */
+    ulong find1Backward(ulong i)
+    {
+        assert(i < length);
+        auto w = cast(size_t) (i / 64);
+        immutable b = 63 - (i % 64); // 0 through 63, 63 when i == 0
+        immutable mask = ~((1UL << b) - 1);
+        assert(mask != 0);
+        // First, let's see if the current word has a bit larger than ours.
+        if (auto currentWord = _rep[w] & mask)
+        {
+            // Great, this word contains the result.
+            return w * 64 + 63 - currentWord.trailingZeros;
+        }
+        // The current word doesn't have the solution, find the rightmost 1
+        // going to the left.
+        while (w >= 1)
+        {
+            --w;
+            if (auto currentWord = _rep[w])
+                return w * 64 + (63 - currentWord.trailingZeros);
+        }
+        return ulong.max;
+    }
+
+    /// Are all bits zero?
+    bool allAre0() const
+    {
+        foreach (w; _rep) if (w) return false;
+        return true;
+    }
+
+    /// Are all bits one?
+    bool allAre1() const
+    {
+        foreach (w; _rep) if (w != ulong.max) return false;
+        return true;
+    }
+
+    ulong findZeros(immutable size_t howMany, ulong start)
+    {
+        assert(start < length);
+        assert(howMany > 64);
+        auto i = cast(size_t) (start / 64);
+        while (_rep[i] & 1)
+        {
+            // No trailing zeros in this word, try the next one
+            if (++i == _rep.length) return ulong.max;
+            start = i * 64;
+        }
+        // Adjust start to have only trailing zeros after it
+        auto prefixLength = 64;
+        while (_rep[i] & (ulong.max >> (64 - prefixLength)))
+        {
+            assert(prefixLength > 0);
+            --prefixLength;
+            ++start;
+        }
+
+        assert(howMany > prefixLength);
+        auto needed = howMany - prefixLength;
+        for (++i; needed >= 64; needed -= 64, ++i)
+        {
+            if (i >= _rep.length) return ulong.max;
+            if (_rep[i] != 0) return findZeros(howMany, i * 64);
+        }
+        // Leftover < 64 bits
+        assert(needed < 64);
+        if (!needed) return start;
+        if (i >= _rep.length) return ulong.max;
+        if (leadingOnes(~_rep[i]) >= needed) return start;
+        return findZeros(howMany, i * 64);
+    }
+}
+
+unittest
+{
+    auto v = BitVector(new ulong[10]);
+    assert(v.length == 640);
+
+    v[] = 0;
+    v[53] = 1;
+    assert(v[52] == 0);
+    assert(v[53] == 1);
+    assert(v[54] == 0);
+
+    v[] = 0;
+    v[53 .. 55] = 1;
+    assert(v[52] == 0);
+    assert(v[53] == 1);
+    assert(v[54] == 1);
+    assert(v[55] == 0);
+
+    v[] = 0;
+    v[2 .. 65] = 1;
+    assert(v.rep[0] == 0x3FFF_FFFF_FFFF_FFFF);
+    assert(v.rep[1] == 0x8000_0000_0000_0000);
+    assert(v.rep[2] == 0);
+
+    v[] = 0;
+    assert(v.find1Backward(0) == ulong.max);
+    assert(v.find1Backward(43) == ulong.max);
+    assert(v.find1Backward(83) == ulong.max);
+
+    v[0] = 1;
+    assert(v.find1Backward(0) == 0);
+    assert(v.find1Backward(43) == 0);
+    import std.conv : text;
+    assert(v.find1Backward(83) == 0, text(v.find1Backward(83)));
+
+    v[0] = 0;
+    v[101] = 1;
+    assert(v.find1Backward(0) == ulong.max);
+    assert(v.find1Backward(43) == ulong.max);
+    assert(v.find1Backward(83) == ulong.max);
+    assert(v.find1Backward(100) == ulong.max);
+    assert(v.find1Backward(101) == 101);
+    assert(v.find1Backward(553) == 101);
+
+    v[0 .. v.length] = 0;
+    v[v.length .. v.length] = 0;
+    v[0 .. 0] = 0;
+
+    v[] = 0;
+    assert(v.find1(0) == v.length);
+    v[139] = 1;
+    assert(v.find1(0) == 139);
+    assert(v.find1(100) == 139);
+    assert(v.find1(138) == 139);
+    assert(v.find1(139) == 139);
+    assert(v.find1(140) == v.length);
+
+    v[] = 0;
+    assert(v.findZeros(100, 0) == 0);
+    foreach (i; 0 .. 500)
+        assert(v.findZeros(100, i) == i, text(v.findZeros(100, i), " != ", i));
+    assert(v.findZeros(540, 99) == 99);
+    assert(v.findZeros(99, 540) == 540);
+    assert(v.findZeros(540, 100) == 100);
+    assert(v.findZeros(640, 0) == 0);
+    assert(v.findZeros(641, 1) == ulong.max);
+    assert(v.findZeros(641, 100) == ulong.max);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d
new file mode 100644
index 00000000000..1ec6630ac78
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d
@@ -0,0 +1,250 @@
+module std.experimental.allocator.building_blocks.bucketizer;
+
+/**
+
+A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in
+the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]),
+$(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]).
+
+$(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to
+them appropriately. The size of the array is $(D (max + 1 - min) / step), which
+must be an exact division.
+
+Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal
+for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use.
+
+*/
+struct Bucketizer(Allocator, size_t min, size_t max, size_t step)
+{
+    import std.traits : hasMember;
+    import common = std.experimental.allocator.common : roundUpToMultipleOf,
+        reallocate,
+        Ternary;
+
+    static assert((max - (min - 1)) % step == 0,
+        "Invalid limits when instantiating " ~ Bucketizer.stringof);
+
+    //static if (min == chooseAtRuntime) size_t _min;
+    //else alias _min = min;
+    //static if (max == chooseAtRuntime) size_t _max;
+    //else alias _max = max;
+    //static if (step == chooseAtRuntime) size_t _step;
+    //else alias _step = step;
+
+    // state {
+    /**
+    The array of allocators is publicly available for e.g. initialization and
+    inspection.
+    */
+    Allocator[(max + 1 - min) / step] buckets;
+    // }
+
+    private Allocator* allocatorFor(size_t n)
+    {
+        const i = (n - min) / step;
+        return i < buckets.length ? buckets.ptr + i : null;
+    }
+
+    /**
+    The alignment offered is the same as $(D Allocator.alignment).
+    */
+    enum uint alignment = Allocator.alignment;
+
+    /**
+    Rounds up to the maximum size of the bucket in which $(D bytes) falls.
+    */
+    size_t goodAllocSize(size_t bytes) const
+    {
+        // round up bytes such that bytes - min + 1 is a multiple of step
+        assert(bytes >= min);
+        const min_1 = min - 1;
+        return min_1 + roundUpToMultipleOf(bytes - min_1, step);
+    }
+
+    /**
+    Directs the call to either one of the $(D buckets) allocators.
+    */
+    void[] allocate(size_t bytes)
+    {
+        if (!bytes) return null;
+        if (auto a = allocatorFor(bytes))
+        {
+            const actual = goodAllocSize(bytes);
+            auto result = a.allocate(actual);
+            return result.ptr ? result.ptr[0 .. bytes] : null;
+        }
+        return null;
+    }
+
+    /**
+    Directs the call to either one of the $(D buckets) allocators. Defined only
+    if `Allocator` defines `alignedAllocate`.
+    */
+    static if (hasMember!(Allocator, "alignedAllocate"))
+    void[] alignedAllocate(size_t bytes, uint a)
+    {
+        if (!bytes) return null;
+        if (auto a = allocatorFor(b.length))
+        {
+            const actual = goodAllocSize(bytes);
+            auto result = a.alignedAllocate(actual);
+            return result.ptr ? result.ptr[0 .. bytes] : null;
+        }
+        return null;
+    }
+
+    /**
+    This method allows expansion within the respective bucket range. It succeeds
+    if both $(D b.length) and $(D b.length + delta) fall in a range of the form
+    $(D [min + k * step, min + (k + 1) * step - 1]).
+    */
+    bool expand(ref void[] b, size_t delta)
+    {
+        if (!b.ptr)
+        {
+            b = allocate(delta);
+            return b.length == delta;
+        }
+        assert(b.length >= min && b.length <= max);
+        const available = goodAllocSize(b.length);
+        const desired = b.length + delta;
+        if (available < desired) return false;
+        b = b.ptr[0 .. desired];
+        return true;
+    }
+
+    /**
+    This method allows reallocation within the respective bucket range. If both
+    $(D b.length) and $(D size) fall in a range of the form $(D [min + k *
+    step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise,
+    reallocation with moving is attempted.
+    */
+    bool reallocate(ref void[] b, size_t size)
+    {
+        if (size == 0)
+        {
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        if (size >= b.length)
+        {
+            return expand(b, size - b.length);
+        }
+        assert(b.length >= min && b.length <= max);
+        if (goodAllocSize(size) == goodAllocSize(b.length))
+        {
+            b = b.ptr[0 .. size];
+            return true;
+        }
+        // Move cross buckets
+        return common.reallocate(this, b, size);
+    }
+
+    /**
+    Similar to `reallocate`, with alignment. Defined only if `Allocator`
+    defines `alignedReallocate`.
+    */
+    static if (hasMember!(Allocator, "alignedReallocate"))
+    bool alignedReallocate(ref void[] b, size_t size, uint a)
+    {
+        if (size == 0)
+        {
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        if (size >= b.length)
+        {
+            return expand(b, size - b.length);
+        }
+        assert(b.length >= min && b.length <= max);
+        if (goodAllocSize(size) == goodAllocSize(b.length))
+        {
+            b = b.ptr[0 .. size];
+            return true;
+        }
+        // Move cross buckets
+        return .alignedReallocate(this, b, size, a);
+    }
+
+    /**
+    Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it.
+    */
+    static if (hasMember!(Allocator, "owns"))
+    Ternary owns(void[] b)
+    {
+        if (!b.ptr) return Ternary.no;
+        if (auto a = allocatorFor(b.length))
+        {
+            const actual = goodAllocSize(b.length);
+            return a.owns(b.ptr[0 .. actual]);
+        }
+        return Ternary.no;
+    }
+
+    /**
+    This method is only defined if $(D Allocator) defines $(D deallocate).
+    */
+    static if (hasMember!(Allocator, "deallocate"))
+    bool deallocate(void[] b)
+    {
+        if (!b.ptr) return true;
+        if (auto a = allocatorFor(b.length))
+        {
+            a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);
+        }
+        return true;
+    }
+
+    /**
+    This method is only defined if all allocators involved define $(D
+    deallocateAll), and calls it for each bucket in turn. Returns `true` if all
+    allocators could deallocate all.
+    */
+    static if (hasMember!(Allocator, "deallocateAll"))
+    bool deallocateAll()
+    {
+        bool result = true;
+        foreach (ref a; buckets)
+        {
+            if (!a.deallocateAll()) result = false;
+        }
+        return result;
+    }
+
+    /**
+    This method is only defined if all allocators involved define $(D
+    resolveInternalPointer), and tries it for each bucket in turn.
+    */
+    static if (hasMember!(Allocator, "resolveInternalPointer"))
+    void[] resolveInternalPointer(void* p)
+    {
+        foreach (ref a; buckets)
+        {
+            if (auto r = a.resolveInternalPointer(p)) return r;
+        }
+        return null;
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    import std.experimental.allocator.building_blocks.region : Region;
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.common : unbounded, Ternary;
+    import std.algorithm : max;
+    Bucketizer!(
+        FreeList!(
+            AllocatorList!(
+                (size_t n) => Region!Mallocator(max(n, 1024 * 1024))),
+            0, unbounded),
+        65, 512, 64) a;
+    auto b = a.allocate(400);
+    assert(b.length == 400);
+    assert(a.owns(b) == Ternary.yes);
+    a.deallocate(b);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d
new file mode 100644
index 00000000000..1e47db6367a
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d
@@ -0,0 +1,357 @@
+module std.experimental.allocator.building_blocks.fallback_allocator;
+
+import std.experimental.allocator.common;
+
+/**
+$(D FallbackAllocator) is the allocator equivalent of an "or" operator in
+algebra. An allocation request is first attempted with the $(D Primary)
+allocator. If that returns $(D null), the request is forwarded to the $(D
+Fallback) allocator. All other requests are dispatched appropriately to one of
+the two allocators.
+
+In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the
+$(D owns) method. This is needed in order to decide which allocator was
+responsible for a given allocation.
+
+$(D FallbackAllocator) is useful for fast, special-purpose allocators backed up
+by general-purpose allocators. The example below features a stack region backed
+up by the $(D GCAllocator).
+*/
+struct FallbackAllocator(Primary, Fallback)
+{
+    import std.algorithm.comparison : min;
+    import std.traits : hasMember;
+
+    unittest
+    {
+        testAllocator!(() => FallbackAllocator());
+    }
+
+    /// The primary allocator.
+    static if (stateSize!Primary) Primary primary;
+    else alias primary = Primary.instance;
+
+    /// The fallback allocator.
+    static if (stateSize!Fallback) Fallback fallback;
+    else alias fallback = Fallback.instance;
+
+    /**
+    If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator)
+    defines a static instance called `instance`.
+    */
+    static if (!stateSize!Primary && !stateSize!Fallback)
+    {
+        static FallbackAllocator instance;
+    }
+
+    /**
+    The alignment offered is the minimum of the two allocators' alignment.
+    */
+    enum uint alignment = min(Primary.alignment, Fallback.alignment);
+
+    /**
+    Allocates memory trying the primary allocator first. If it returns $(D
+    null), the fallback allocator is tried.
+    */
+    void[] allocate(size_t s)
+    {
+        auto result = primary.allocate(s);
+        return result.length == s ? result : fallback.allocate(s);
+    }
+
+    /**
+    $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the
+    allocators also offers it. It attempts to allocate using either or both.
+    */
+    static if (hasMember!(Primary, "alignedAllocate")
+        || hasMember!(Fallback, "alignedAllocate"))
+    void[] alignedAllocate(size_t s, uint a)
+    {
+        static if (hasMember!(Primary, "alignedAllocate"))
+        {{
+            auto result = primary.alignedAllocate(s, a);
+            if (result.length == s) return result;
+        }}
+        static if (hasMember!(Fallback, "alignedAllocate"))
+        {{
+            auto result = fallback.alignedAllocate(s, a);
+            if (result.length == s) return result;
+        }}
+        return null;
+    }
+
+    /**
+
+    $(D expand) is defined if and only if at least one of the allocators
+    defines $(D expand). It works as follows. If $(D primary.owns(b)), then the
+    request is forwarded to $(D primary.expand) if it is defined, or fails
+    (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then
+    the request is forwarded to $(D fallback.expand) if it is defined, or fails
+    (returning $(D false)) otherwise.
+
+    */
+    static if (hasMember!(Primary, "owns")
+        && (hasMember!(Primary, "expand") || hasMember!(Fallback, "expand")))
+    bool expand(ref void[] b, size_t delta)
+    {
+        if (!delta) return true;
+        if (!b.ptr)
+        {
+            b = allocate(delta);
+            return b.length == delta;
+        }
+        if (primary.owns(b) == Ternary.yes)
+        {
+            static if (hasMember!(Primary, "expand"))
+                return primary.expand(b, delta);
+            else
+                return false;
+        }
+        static if (hasMember!(Fallback, "expand"))
+            return fallback.expand(b, delta);
+        else
+            return false;
+    }
+
+    /**
+
+    $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D
+    primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is
+    made to move the allocation from $(D primary) to $(D fallback).
+
+    If $(D primary) does not own $(D b), then $(D fallback.reallocate(b,
+    newSize)) is attempted. If that fails, an attempt is made to move the
+    allocation from $(D fallback) to $(D primary).
+
+    */
+    static if (hasMember!(Primary, "owns"))
+    bool reallocate(ref void[] b, size_t newSize)
+    {
+        bool crossAllocatorMove(From, To)(ref From from, ref To to)
+        {
+            auto b1 = to.allocate(newSize);
+            if (b1.length != newSize) return false;
+            if (b.length < newSize) b1[0 .. b.length] = b[];
+            else b1[] = b[0 .. newSize];
+            static if (hasMember!(From, "deallocate"))
+                from.deallocate(b);
+            b = b1;
+            return true;
+        }
+
+        if (b is null || primary.owns(b) == Ternary.yes)
+        {
+            return primary.reallocate(b, newSize)
+                // Move from primary to fallback
+                || crossAllocatorMove(primary, fallback);
+        }
+        return fallback.reallocate(b, newSize)
+            // Interesting. Move from fallback to primary.
+            || crossAllocatorMove(fallback, primary);
+    }
+
+    static if (hasMember!(Primary, "owns")
+        && (hasMember!(Primary, "alignedAllocate")
+            || hasMember!(Fallback, "alignedAllocate")))
+    bool alignedReallocate(ref void[] b, size_t newSize, uint a)
+    {
+        bool crossAllocatorMove(From, To)(ref From from, ref To to)
+        {
+            static if (!hasMember!(To, "alignedAllocate"))
+            {
+                return false;
+            }
+            else
+            {
+                auto b1 = to.alignedAllocate(newSize, a);
+                if (b1.length != newSize) return false;
+                if (b.length < newSize) b1[0 .. b.length] = b[];
+                else b1[] = b[0 .. newSize];
+                static if (hasMember!(From, "deallocate"))
+                    from.deallocate(b);
+                b = b1;
+                return true;
+            }
+        }
+
+        static if (hasMember!(Primary, "alignedAllocate"))
+        {
+            if (b is null || primary.owns(b) == Ternary.yes)
+            {
+                return primary.alignedReallocate(b, newSize, a)
+                    || crossAllocatorMove(primary, fallback);
+            }
+        }
+        static if (hasMember!(Fallback, "alignedAllocate"))
+        {
+            return fallback.alignedReallocate(b, newSize, a)
+                || crossAllocatorMove(fallback, primary);
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+    $(D owns) is defined if and only if both allocators define $(D owns).
+    Returns $(D primary.owns(b) | fallback.owns(b)).
+    */
+    static if (hasMember!(Primary, "owns") && hasMember!(Fallback, "owns"))
+    Ternary owns(void[] b)
+    {
+        return primary.owns(b) | fallback.owns(b);
+    }
+
+    /**
+    $(D resolveInternalPointer) is defined if and only if both allocators
+    define it.
+    */
+    static if (hasMember!(Primary, "resolveInternalPointer")
+        && hasMember!(Fallback, "resolveInternalPointer"))
+    void[] resolveInternalPointer(void* p)
+    {
+        if (auto r = primary.resolveInternalPointer(p)) return r;
+        if (auto r = fallback.resolveInternalPointer(p)) return r;
+        return null;
+    }
+
+    /**
+    $(D deallocate) is defined if and only if at least one of the allocators
+    define    $(D deallocate). It works as follows. If $(D primary.owns(b)),
+    then the request is forwarded to $(D primary.deallocate) if it is defined,
+    or is a no-op otherwise. If $(D primary) does not own $(D b), then the
+    request is forwarded to $(D fallback.deallocate) if it is defined, or is a
+    no-op otherwise.
+    */
+    static if (hasMember!(Primary, "owns") &&
+        (hasMember!(Primary, "deallocate")
+            || hasMember!(Fallback, "deallocate")))
+    bool deallocate(void[] b)
+    {
+        if (primary.owns(b) == Ternary.yes)
+        {
+            static if (hasMember!(Primary, "deallocate"))
+                return primary.deallocate(b);
+            else
+                return false;
+        }
+        else
+        {
+            static if (hasMember!(Fallback, "deallocate"))
+                return fallback.deallocate(b);
+            else
+                return false;
+        }
+    }
+
+    /**
+    $(D empty) is defined if both allocators also define it.
+
+    Returns: $(D primary.empty & fallback.empty)
+    */
+    static if (hasMember!(Primary, "empty") && hasMember!(Fallback, "empty"))
+    Ternary empty()
+    {
+        return primary.empty & fallback.empty;
+    }
+}
+
+unittest
+{
+    import std.experimental.allocator.building_blocks.region : InSituRegion;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.conv : text;
+    FallbackAllocator!(InSituRegion!16_384, GCAllocator) a;
+    // This allocation uses the stack
+    auto b1 = a.allocate(1024);
+    assert(b1.length == 1024, text(b1.length));
+    assert(a.primary.owns(b1) == Ternary.yes);
+    // This large allocation will go to the Mallocator
+    auto b2 = a.allocate(1024 * 1024);
+    assert(a.primary.owns(b2) == Ternary.no);
+    a.deallocate(b1);
+    a.deallocate(b2);
+}
+
+/*
+Forwards an argument from one function to another
+*/
+private auto ref forward(alias arg)()
+{
+    static if (__traits(isRef, arg))
+    {
+        return arg;
+    }
+    else
+    {
+        import std.algorithm : move;
+        return move(arg);
+    }
+}
+
+unittest
+{
+    void fun(T)(auto ref T, string) { /* ... */ }
+    void gun(T...)(auto ref T args)
+    {
+        fun(forward!(args[0]), forward!(args[1]));
+    }
+    gun(42, "hello");
+    int x;
+    gun(x, "hello");
+}
+
+unittest
+{
+    static void checkByRef(T)(auto ref T value)
+    {
+        static assert(__traits(isRef, value));
+    }
+
+    static void checkByVal(T)(auto ref T value)
+    {
+        static assert(!__traits(isRef, value));
+    }
+
+    static void test1(ref int a) { checkByRef(forward!a); }
+    static void test2(int a) { checkByVal(forward!a); }
+    static void test3() { int a; checkByVal(forward!a); }
+}
+
+/**
+Convenience function that uses type deduction to return the appropriate
+$(D FallbackAllocator) instance. To initialize with allocators that don't have
+state, use their $(D it) static member.
+*/
+FallbackAllocator!(Primary, Fallback)
+fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f)
+{
+    alias R = FallbackAllocator!(Primary, Fallback);
+    import std.algorithm : move;
+
+    static if (stateSize!Primary)
+        static if (stateSize!Fallback)
+            return R(forward!p, forward!f);
+        else
+            return R(forward!p);
+    else
+        static if (stateSize!Fallback)
+            return R(forward!f);
+        else
+            return R();
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.building_blocks.region : Region;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    auto a = fallbackAllocator(Region!GCAllocator(1024), GCAllocator.instance);
+    auto b1 = a.allocate(1020);
+    assert(b1.length == 1020);
+    assert(a.primary.owns(b1) == Ternary.yes);
+    auto b2 = a.allocate(10);
+    assert(b2.length == 10);
+    assert(a.primary.owns(b2) == Ternary.no);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/free_list.d b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d
new file mode 100644
index 00000000000..1154263e1d2
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d
@@ -0,0 +1,1086 @@
+module std.experimental.allocator.building_blocks.free_list;
+
+import std.experimental.allocator.common;
+import std.typecons : Flag, Yes, No;
+
+/**
+
+$(WEB en.wikipedia.org/wiki/Free_list, Free list allocator), stackable on top of
+another allocator. Allocation requests between $(D min) and $(D max) bytes are
+rounded up to $(D max) and served from a singly-linked list of buffers
+deallocated in the past. All other allocations are directed to $(D
+ParentAllocator). Due to the simplicity of free list management, allocations
+from the free list are fast.
+
+One instantiation is of particular interest: $(D FreeList!(0, unbounded)) puts
+every deallocation in the freelist, and subsequently serves any allocation from
+the freelist (if not empty). There is no checking of size matching, which would
+be incorrect for a freestanding allocator but is both correct and fast when an
+owning allocator on top of the free list allocator (such as $(D Segregator)) is
+already in charge of handling size checking.
+
+The following methods are defined if $(D ParentAllocator) defines them, and
+forward to it: $(D expand), $(D owns), $(D reallocate).
+
+*/
+struct FreeList(ParentAllocator,
+    size_t minSize, size_t maxSize = minSize,
+    Flag!"adaptive" adaptive = No.adaptive)
+{
+    import std.conv : text;
+    import std.exception : enforce;
+    import std.traits : hasMember;
+
+    static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
+    static assert(maxSize >= (void*).sizeof,
+        "Maximum size must accommodate a pointer.");
+
+    private enum unchecked = minSize == 0 && maxSize == unbounded;
+
+    private enum hasTolerance = !unchecked && (minSize != maxSize
+        || maxSize == chooseAtRuntime);
+
+    static if (minSize == chooseAtRuntime)
+    {
+        /**
+        Returns the smallest allocation size eligible for allocation from the
+        freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias
+        for $(D minSize).)
+        */
+        @property size_t min() const
+        {
+            assert(_min != chooseAtRuntime);
+            return _min;
+        }
+        /**
+        If $(D FreeList) has been instantiated with $(D minSize ==
+        chooseAtRuntime), then the $(D min) property is writable. Setting it
+        must precede any allocation.
+
+        Params:
+        low = new value for $(D min)
+
+        Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and
+        $(D max) has not yet been initialized. Also, no allocation has been
+        yet done with this allocator.
+
+        Postcondition: $(D min == low)
+        */
+        @property void min(size_t low)
+        {
+            assert(low <= max || max == chooseAtRuntime);
+            minimize;
+            _min = low;
+        }
+    }
+    else
+    {
+        alias min = minSize;
+    }
+
+    static if (maxSize == chooseAtRuntime)
+    {
+        /**
+        Returns the largest allocation size eligible for allocation from the
+        freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias
+        for $(D maxSize).) All allocation requests for sizes greater than or
+        equal to $(D min) and less than or equal to $(D max) are rounded to $(D
+        max) and forwarded to the parent allocator. When the block fitting the
+        same constraint gets deallocated, it is put in the freelist with the
+        allocated size assumed to be $(D max).
+        */
+        @property size_t max() const { return _max; }
+
+        /**
+        If $(D FreeList) has been instantiated with $(D maxSize ==
+        chooseAtRuntime), then the $(D max) property is writable. Setting it
+        must precede any allocation.
+
+        Params:
+        high = new value for $(D max)
+
+        Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and
+        $(D min) has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator.
+
+        Postcondition: $(D max == high)
+        */
+        @property void max(size_t high)
+        {
+            assert((high >= min || min == chooseAtRuntime)
+                && high >= (void*).sizeof);
+            minimize;
+            _max = high;
+        }
+
+        ///
+        unittest
+        {
+            FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
+            a.min = 64;
+            a.max = 128;
+            assert(a.min == 64);
+            assert(a.max == 128);
+        }
+    }
+    else
+    {
+        alias max = maxSize;
+    }
+
+    private bool tooSmall(size_t n) const
+    {
+        static if (minSize == 0) return false;
+        else return n < min;
+    }
+
+    private bool tooLarge(size_t n) const
+    {
+        static if (maxSize == unbounded) return false;
+        else return n > max;
+    }
+
+    private bool freeListEligible(size_t n) const
+    {
+        static if (unchecked)
+        {
+            return true;
+        }
+        else
+        {
+            static if (minSize == 0)
+            {
+                if (!n) return false;
+            }
+            static if (minSize == maxSize && minSize != chooseAtRuntime)
+                return n == maxSize;
+            else
+                return !tooSmall(n) && !tooLarge(n);
+        }
+    }
+
+    static if (!unchecked)
+    private void[] blockFor(Node* p)
+    {
+        assert(p);
+        return (cast(void*) p)[0 .. max];
+    }
+
+    // statistics {
+    static if (adaptive == Yes.adaptive)
+    {
+        private enum double windowLength = 1000.0;
+        private enum double tooFewMisses = 0.01;
+        private double probMiss = 1.0; // start with a high miss probability
+        private uint accumSamples, accumMisses;
+
+        void updateStats()
+        {
+            assert(accumSamples >= accumMisses);
+            /*
+            Given that for the past windowLength samples we saw misses with
+            estimated probability probMiss, and assuming the new sample wasMiss or
+            not, what's the new estimated probMiss?
+            */
+            probMiss = (probMiss * windowLength + accumMisses)
+                / (windowLength + accumSamples);
+            assert(probMiss <= 1.0);
+            accumSamples = 0;
+            accumMisses = 0;
+            // If probability to miss is under x%, yank one off the freelist
+            static if (!unchecked)
+            {
+                if (probMiss < tooFewMisses && _root)
+                {
+                    auto b = blockFor(_root);
+                    _root = _root.next;
+                    parent.deallocate(b);
+                }
+            }
+        }
+    }
+    // } statistics
+
+    private struct Node { Node* next; }
+    static assert(ParentAllocator.alignment >= Node.alignof);
+
+    // state {
+    /**
+    The parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator) ParentAllocator parent;
+    else alias parent = ParentAllocator.instance;
+    private Node* root;
+    static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime;
+    static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime;
+    // }
+
+    /**
+    Alignment offered.
+    */
+    alias alignment = ParentAllocator.alignment;
+
+    /**
+    If $(D maxSize == unbounded), returns  $(D parent.goodAllocSize(bytes)).
+    Otherwise, returns $(D max) for sizes in the interval $(D [min, max]), and
+    $(D parent.goodAllocSize(bytes)) otherwise.
+
+    Precondition:
+    If set at runtime, $(D min) and/or $(D max) must be initialized
+    appropriately.
+
+    Postcondition:
+    $(D result >= bytes)
+    */
+    size_t goodAllocSize(size_t bytes)
+    {
+        assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime);
+        static if (maxSize != unbounded)
+        {
+            if (freeListEligible(bytes))
+            {
+                assert(parent.goodAllocSize(max) == max,
+                    text("Wrongly configured freelist: maximum should be ",
+                        parent.goodAllocSize(max), " instead of ", max));
+                return max;
+            }
+        }
+        return parent.goodAllocSize(bytes);
+    }
+
+    private void[] allocateEligible(size_t bytes)
+    {
+        assert(bytes);
+        if (root)
+        {
+            // faster
+            auto result = (cast(ubyte*) root)[0 .. bytes];
+            root = root.next;
+            return result;
+        }
+        // slower
+        static if (hasTolerance)
+        {
+            immutable toAllocate = max;
+        }
+        else
+        {
+            alias toAllocate = bytes;
+        }
+        assert(toAllocate == max || max == unbounded);
+        auto result = parent.allocate(toAllocate);
+        static if (hasTolerance)
+        {
+            if (result) result = result.ptr[0 .. bytes];
+        }
+        static if (adaptive == Yes.adaptive)
+        {
+            ++accumMisses;
+            updateStats;
+        }
+        return result;
+    }
+
+    /**
+    Allocates memory either off of the free list or from the parent allocator.
+    If $(D n) is within $(D [min, max]) or if the free list is unchecked
+    ($(D minSize == 0 && maxSize == size_t.max)), then the free list is
+    consulted first. If not empty (hit), the block at the front of the free
+    list is removed from the list and returned. Otherwise (miss), a new block
+    of $(D max) bytes is allocated, truncated to $(D n) bytes, and returned.
+
+    Params:
+    n = number of bytes to allocate
+
+    Returns:
+    The allocated block, or $(D null).
+
+    Precondition:
+    If set at runtime, $(D min) and/or $(D max) must be initialized
+    appropriately.
+
+    Postcondition: $(D result.length == bytes || result is null)
+    */
+    void[] allocate(size_t n)
+    {
+        static if (adaptive == Yes.adaptive) ++accumSamples;
+        assert(n < size_t.max / 2);
+        // fast path
+        if (freeListEligible(n))
+        {
+            return allocateEligible(n);
+        }
+        // slower
+        static if (adaptive == Yes.adaptive)
+        {
+            updateStats;
+        }
+        return parent.allocate(n);
+    }
+
+    // Forwarding methods
+    mixin(forwardToMember("parent",
+        "expand", "owns", "reallocate"));
+
+    /**
+    If $(D block.length) is within $(D [min, max]) or if the free list is
+    unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the
+    block at the front of the free list. For all others, forwards to $(D
+    parent.deallocate) if $(D Parent.deallocate) is defined.
+
+    Params:
+    block = Block to deallocate.
+
+    Precondition:
+    If set at runtime, $(D min) and/or $(D max) must be initialized
+    appropriately. The block must have been allocated with this
+    freelist, and no dynamic changing of $(D min) or $(D max) is allowed to
+    occur between allocation and deallocation.
+    */
+    bool deallocate(void[] block)
+    {
+        if (freeListEligible(block.length))
+        {
+            if (min == 0)
+            {
+                // In this case a null pointer might have made it this far.
+                if (block is null) return true;
+            }
+            auto t = root;
+            root = cast(Node*) block.ptr;
+            root.next = t;
+            return true;
+        }
+        static if (hasMember!(ParentAllocator, "deallocate"))
+            return parent.deallocate(block);
+        else
+            return false;
+    }
+
+    /**
+    Defined only if $(D ParentAllocator) defines $(D deallocateAll). If so,
+    forwards to it and resets the freelist.
+    */
+    static if (hasMember!(ParentAllocator, "deallocateAll"))
+    bool deallocateAll()
+    {
+        root = null;
+        return parent.deallocateAll();
+    }
+
+    /**
+    Nonstandard function that minimizes the memory usage of the freelist by
+    freeing each element in turn. Defined only if $(D ParentAllocator) defines
+    $(D deallocate).
+    */
+    static if (hasMember!(ParentAllocator, "deallocate") && !unchecked)
+    void minimize()
+    {
+        while (root)
+        {
+            auto nuke = blockFor(root);
+            root = root.next;
+            parent.deallocate(nuke);
+        }
+    }
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    FreeList!(GCAllocator, 0, 8) fl;
+    assert(fl.root is null);
+    auto b1 = fl.allocate(7);
+    //assert(fl._root !is null);
+    fl.allocate(8);
+    assert(fl.root is null);
+    fl.deallocate(b1);
+    assert(fl.root !is null);
+    fl.allocate(8);
+    assert(fl.root is null);
+}
+
+/**
+Free list built on top of exactly one contiguous block of memory. The block is
+assumed to have been allocated with $(D ParentAllocator), and is released in
+$(D ContiguousFreeList)'s destructor (unless $(D ParentAllocator) is $(D
+NullAllocator)).
+
+$(D ContiguousFreeList) has most advantages of $(D FreeList) but fewer
+disadvantages. It has better cache locality because items are closer to one
+another. It imposes less fragmentation on its parent allocator.
+
+The disadvantages of $(D ContiguousFreeList) over $(D FreeList) are its pay
+upfront model (as opposed to $(D FreeList)'s pay-as-you-go approach), and a
+hard limit on the number of nodes in the list. Thus, a large number of long-
+lived objects may occupy the entire block, making it unavailable for serving
+allocations from the free list. However, an absolute cap on the free list size
+may be beneficial.
+
+The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not
+available for $(D ContiguousFreeList).
+*/
+struct ContiguousFreeList(ParentAllocator,
+     size_t minSize, size_t maxSize = minSize)
+{
+    import std.experimental.allocator.building_blocks.null_allocator
+        : NullAllocator;
+    import std.experimental.allocator.building_blocks.stats_collector
+        : StatsCollector, Options;
+    import std.traits : hasMember;
+
+    alias Impl = FreeList!(NullAllocator, minSize, maxSize);
+    enum unchecked = minSize == 0 && maxSize == unbounded;
+    alias Node = Impl.Node;
+
+    alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed);
+
+    // state {
+    /**
+    The parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    SParent parent;
+    FreeList!(NullAllocator, minSize, maxSize) fl;
+    void[] support;
+    size_t allocated;
+    // }
+
+    /// Alignment offered.
+    enum uint alignment = (void*).alignof;
+
+    private void initialize(void[] buffer, size_t itemSize = fl.max)
+    {
+        assert(itemSize != unbounded && itemSize != chooseAtRuntime);
+        assert(buffer.ptr.alignedAt(alignment));
+        immutable available = buffer.length / itemSize;
+        if (available == 0) return;
+        support = buffer;
+        fl.root = cast(Node*) buffer.ptr;
+        auto past = cast(Node*) (buffer.ptr + available * itemSize);
+        for (auto n = fl.root; ; )
+        {
+            auto next = cast(Node*) (cast(ubyte*) n + itemSize);
+            if (next == past)
+            {
+                n.next = null;
+                break;
+            }
+            assert(next < past);
+            assert(n < next);
+            n.next = next;
+            n = next;
+        }
+    }
+
+    /**
+    Constructors setting up the memory structured as a free list.
+
+    Params:
+    buffer = Buffer to structure as a free list. If $(D ParentAllocator) is not
+    $(D NullAllocator), the buffer is assumed to be allocated by $(D parent)
+    and will be freed in the destructor.
+    parent = Parent allocator. For construction from stateless allocators, use
+    their `instance` static member.
+    bytes = Bytes (not items) to be allocated for the free list. Memory will be
+    allocated during construction and deallocated in the destructor.
+    max = Maximum size eligible for freelisting. Construction with this
+    parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize
+    == unbounded).
+    min = Minimum size eligible for freelisting. Construction with this
+    parameter is defined only if $(D minSize == chooseAtRuntime). If this
+    condition is met and no $(D min) parameter is present, $(D min) is
+    initialized with $(D max).
+    */
+    static if (!stateSize!ParentAllocator)
+    this(void[] buffer)
+    {
+        initialize(buffer);
+    }
+
+    /// ditto
+    static if (stateSize!ParentAllocator)
+    this(ParentAllocator parent, void[] buffer)
+    {
+        initialize(buffer);
+        this.parent = SParent(parent);
+    }
+
+    /// ditto
+    static if (!stateSize!ParentAllocator)
+    this(size_t bytes)
+    {
+        initialize(ParentAllocator.instance.allocate(bytes));
+    }
+
+    /// ditto
+    static if (stateSize!ParentAllocator)
+    this(ParentAllocator parent, size_t bytes)
+    {
+        initialize(parent.allocate(bytes));
+        this.parent = SParent(parent);
+    }
+
+    /// ditto
+    static if (!stateSize!ParentAllocator
+        && (maxSize == chooseAtRuntime || maxSize == unbounded))
+    this(size_t bytes, size_t max)
+    {
+        static if (maxSize == chooseAtRuntime) fl.max = max;
+        static if (minSize == chooseAtRuntime) fl.min = max;
+        initialize(parent.allocate(bytes), max);
+    }
+
+    /// ditto
+    static if (stateSize!ParentAllocator
+        && (maxSize == chooseAtRuntime || maxSize == unbounded))
+    this(ParentAllocator parent, size_t bytes, size_t max)
+    {
+        static if (maxSize == chooseAtRuntime) fl.max = max;
+        static if (minSize == chooseAtRuntime) fl.min = max;
+        initialize(parent.allocate(bytes), max);
+        this.parent = SParent(parent);
+    }
+
+    /// ditto
+    static if (!stateSize!ParentAllocator
+        && (maxSize == chooseAtRuntime || maxSize == unbounded)
+        && minSize == chooseAtRuntime)
+    this(size_t bytes, size_t min, size_t max)
+    {
+        static if (maxSize == chooseAtRuntime) fl.max = max;
+        fl.min = min;
+        initialize(parent.allocate(bytes), max);
+        static if (stateSize!ParentAllocator)
+            this.parent = SParent(parent);
+    }
+
+    /// ditto
+    static if (stateSize!ParentAllocator
+        && (maxSize == chooseAtRuntime || maxSize == unbounded)
+        && minSize == chooseAtRuntime)
+    this(ParentAllocator parent, size_t bytes, size_t min, size_t max)
+    {
+        static if (maxSize == chooseAtRuntime) fl.max = max;
+        fl.min = min;
+        initialize(parent.allocate(bytes), max);
+        static if (stateSize!ParentAllocator)
+            this.parent = SParent(parent);
+    }
+
+    /**
+    If $(D n) is eligible for freelisting, returns $(D max). Otherwise, returns
+    $(D parent.goodAllocSize(n)).
+
+    Precondition:
+    If set at runtime, $(D min) and/or $(D max) must be initialized
+    appropriately.
+
+    Postcondition:
+    $(D result >= bytes)
+    */
+    size_t goodAllocSize(size_t n)
+    {
+        if (fl.freeListEligible(n)) return fl.max;
+        return parent.goodAllocSize(n);
+    }
+
+    /**
+    Allocate $(D n) bytes of memory. If $(D n) is eligible for freelist and the
+    freelist is not empty, pops the memory off the free list. In all other
+    cases, uses the parent allocator.
+    */
+    void[] allocate(size_t n)
+    {
+        auto result = fl.allocate(n);
+        if (result)
+        {
+            // Only case we care about: eligible sizes allocated from us
+            ++allocated;
+            return result;
+        }
+        // All others, allocate from parent
+        return parent.allocate(n);
+    }
+
+    /**
+    Defined if `ParentAllocator` defines it. Checks whether the block
+    belongs to this allocator.
+    */
+    static if (hasMember!(SParent, "owns") || unchecked)
+    Ternary owns(void[] b)
+    {
+        if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length)
+            return Ternary.yes;
+        static if (unchecked)
+            return Ternary.no;
+        else
+            return parent.owns(b);
+    }
+
+    /**
+    Deallocates $(D b). If it's of eligible size, it's put on the free list.
+    Otherwise, it's returned to $(D parent).
+
+    Precondition: $(D b) has been allocated with this allocator, or is $(D
+    null).
+    */
+    bool deallocate(void[] b)
+    {
+        if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length)
+        {
+            // we own this guy
+            import std.conv : text;
+            assert(fl.freeListEligible(b.length), text(b.length));
+            assert(allocated);
+            --allocated;
+            // Put manually in the freelist
+            auto t = fl.root;
+            fl.root = cast(Node*) b.ptr;
+            fl.root.next = t;
+            return true;
+        }
+        return parent.deallocate(b);
+    }
+
+    /**
+    Deallocates everything from the parent.
+    */
+    static if (hasMember!(ParentAllocator, "deallocateAll")
+        && stateSize!ParentAllocator)
+    bool deallocateAll()
+    {
+        bool result = fl.deallocateAll && parent.deallocateAll;
+        allocated = 0;
+        return result;
+    }
+
+    /**
+    Returns `Ternary.yes` if no memory is currently allocated with this
+    allocator, `Ternary.no` otherwise. This method never returns
+    `Ternary.unknown`.
+    */
+    Ternary empty()
+    {
+        return Ternary(allocated == 0 && parent.bytesUsed == 0);
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+
+    alias ScalableFreeList = AllocatorList!((n) =>
+        ContiguousFreeList!(GCAllocator, 0, unbounded)(4096)
+    );
+}
+
+unittest
+{
+    import std.experimental.allocator.building_blocks.null_allocator
+        : NullAllocator;
+    alias A = ContiguousFreeList!(NullAllocator, 0, 64);
+    auto a = A(new void[1024]);
+
+    assert(a.empty == Ternary.yes);
+
+    assert(a.goodAllocSize(15) == 64);
+    assert(a.goodAllocSize(65) == NullAllocator.instance.goodAllocSize(65));
+
+    auto b = a.allocate(100);
+    assert(a.empty == Ternary.yes);
+    assert(b.length == 0);
+    a.deallocate(b);
+    b = a.allocate(64);
+    assert(a.empty == Ternary.no);
+    assert(b.length == 64);
+    assert(a.owns(b) == Ternary.yes);
+    assert(a.owns(null) == Ternary.no);
+    a.deallocate(b);
+}
+
+unittest
+{
+    import std.experimental.allocator.building_blocks.region : Region;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    alias A = ContiguousFreeList!(Region!GCAllocator, 0, 64);
+    auto a = A(Region!GCAllocator(1024 * 4), 1024);
+
+    assert(a.empty == Ternary.yes);
+
+    assert(a.goodAllocSize(15) == 64);
+    assert(a.goodAllocSize(65) == a.parent.goodAllocSize(65));
+
+    auto b = a.allocate(100);
+    assert(a.empty == Ternary.no);
+    assert(a.allocated == 0);
+    assert(b.length == 100);
+    a.deallocate(b);
+    assert(a.empty == Ternary.yes);
+    b = a.allocate(64);
+    assert(a.empty == Ternary.no);
+    assert(b.length == 64);
+    assert(a.owns(b) == Ternary.yes);
+    assert(a.owns(null) == Ternary.no);
+    a.deallocate(b);
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    alias A = ContiguousFreeList!(GCAllocator, 64, 64);
+    auto a = A(1024);
+    const b = a.allocate(100);
+    assert(b.length == 100);
+}
+
+/**
+FreeList shared across threads. Allocation and deallocation are lock-free. The
+parameters have the same semantics as for $(D FreeList).
+
+$(D expand) is defined to forward to $(ParentAllocator.expand) (it must be also
+$(D shared)).
+*/
+struct SharedFreeList(ParentAllocator,
+    size_t minSize, size_t maxSize = minSize, size_t approxMaxNodes = unbounded)
+{
+    import std.conv : text;
+    import std.exception : enforce;
+    import std.traits : hasMember;
+
+    static assert(approxMaxNodes, "approxMaxNodes must not be null.");
+    static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
+    static assert(maxSize >= (void*).sizeof,
+        "Maximum size must accommodate a pointer.");
+
+    private import core.atomic;
+
+    static if (minSize != chooseAtRuntime)
+    {
+        alias min = minSize;
+    }
+    else
+    {
+        private shared size_t _min = chooseAtRuntime;
+        @property size_t min() const shared
+        {
+            assert(_min != chooseAtRuntime);
+            return _min;
+        }
+        @property void min(size_t x) shared
+        {
+            enforce(x <= max);
+            enforce(cas(&_min, chooseAtRuntime, x),
+                "SharedFreeList.min must be initialized exactly once.");
+        }
+        static if (maxSize == chooseAtRuntime)
+        {
+            // Both bounds can be set, provide one function for setting both in
+            // one shot.
+            void setBounds(size_t low, size_t high) shared
+            {
+                enforce(low <= high && high >= (void*).sizeof);
+                enforce(cas(&_min, chooseAtRuntime, low),
+                    "SharedFreeList.min must be initialized exactly once.");
+                enforce(cas(&_max, chooseAtRuntime, high),
+                    "SharedFreeList.max must be initialized exactly once.");
+            }
+        }
+    }
+
+    private bool tooSmall(size_t n) const shared
+    {
+        static if (minSize == 0) return false;
+        else static if (minSize == chooseAtRuntime) return n < _min;
+        else return n < minSize;
+    }
+
+    static if (maxSize != chooseAtRuntime)
+    {
+        alias max = maxSize;
+    }
+    else
+    {
+        private shared size_t _max = chooseAtRuntime;
+        @property size_t max() const shared { return _max; }
+        @property void max(size_t x) shared
+        {
+            enforce(x >= _min && x >= (void*).sizeof);
+            enforce(cas(&_max, chooseAtRuntime, x),
+                "SharedFreeList.max must be initialized exactly once.");
+        }
+    }
+
+    private bool tooLarge(size_t n) const shared
+    {
+        static if (maxSize == unbounded) return false;
+        else static if (maxSize == chooseAtRuntime) return n > _max;
+        else return n > maxSize;
+    }
+
+    private bool freeListEligible(size_t n) const shared
+    {
+        static if (minSize == maxSize && minSize != chooseAtRuntime)
+            return n == maxSize;
+        else return !tooSmall(n) && !tooLarge(n);
+    }
+
+    static if (approxMaxNodes != chooseAtRuntime)
+    {
+        alias approxMaxLength = approxMaxNodes;
+    }
+    else
+    {
+        private shared size_t _approxMaxLength = chooseAtRuntime;
+        @property size_t approxMaxLength() const shared { return _approxMaxLength; }
+        @property void approxMaxLength(size_t x) shared { _approxMaxLength = enforce(x); }
+    }
+
+    static if (approxMaxNodes != unbounded)
+    {
+        private shared size_t nodes;
+        private void incNodes() shared
+        {
+            atomicOp!("+=")(nodes, 1);
+        }
+        private void decNodes() shared
+        {
+            assert(nodes);
+            atomicOp!("-=")(nodes, 1);
+        }
+        private bool nodesFull() shared
+        {
+            return nodes >= approxMaxLength;
+        }
+    }
+    else
+    {
+        private static void incNodes() { }
+        private static void decNodes() { }
+        private enum bool nodesFull = false;
+    }
+
+    version (StdDdoc)
+    {
+        /**
+        Properties for getting (and possibly setting) the bounds. Setting bounds
+        is allowed only once , and before any allocation takes place. Otherwise,
+        the primitives have the same semantics as those of $(D FreeList).
+        */
+        @property size_t min();
+        /// Ditto
+        @property void min(size_t newMinSize);
+        /// Ditto
+        @property size_t max();
+        /// Ditto
+        @property void max(size_t newMaxSize);
+        /// Ditto
+        void setBounds(size_t newMin, size_t newMax);
+        ///
+        unittest
+        {
+            SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
+            // Set the maxSize first so setting the minSize doesn't throw
+            a.max = 128;
+            a.min = 64;
+            a.setBounds(64, 128); // equivalent
+            assert(a.max == 128);
+            assert(a.min == 64);
+        }
+
+        /**
+        Properties for getting (and possibly setting) the approximate maximum length of a shared freelist.
+        */
+        @property size_t approxMaxLength() const shared;
+        /// ditto
+        @property void approxMaxLength(size_t x) shared;
+        ///
+        unittest
+        {
+            SharedFreeList!(Mallocator, 50, 50, chooseAtRuntime) a;
+            // Set the maxSize first so setting the minSize doesn't throw
+            a.approxMaxLength = 128;
+            assert(a.approxMaxLength  == 128);
+            a.approxMaxLength = 1024;
+            assert(a.approxMaxLength  == 1024);
+            a.approxMaxLength = 1;
+            assert(a.approxMaxLength  == 1);
+        }
+    }
+
+    /**
+    The parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator) shared ParentAllocator parent;
+    else alias parent = ParentAllocator.instance;
+
+    mixin(forwardToMember("parent", "expand"));
+
+    private struct Node { Node* next; }
+    static assert(ParentAllocator.alignment >= Node.alignof);
+    private Node* _root;
+
+    /// Standard primitives.
+    enum uint alignment = ParentAllocator.alignment;
+
+    /// Ditto
+    size_t goodAllocSize(size_t bytes) shared
+    {
+        if (freeListEligible(bytes)) return maxSize == unbounded ? bytes : max;
+        return parent.goodAllocSize(bytes);
+    }
+
+    /// Ditto
+    static if (hasMember!(ParentAllocator, "owns"))
+    Ternary owns(void[] b) shared const
+    {
+        return parent.owns(b);
+    }
+
+    /// Ditto
+    static if (hasMember!(ParentAllocator, "reallocate"))
+    bool reallocate(void[] b, size_t s)
+    {
+        return parent.reallocate(b, s);
+    }
+
+    /// Ditto
+    void[] allocate(size_t bytes) shared
+    {
+        assert(bytes < size_t.max / 2);
+        if (!freeListEligible(bytes)) return parent.allocate(bytes);
+        if (maxSize != unbounded) bytes = max;
+        // Pop off the freelist
+        shared Node* oldRoot = void, next = void;
+        do
+        {
+            oldRoot = _root; // atomic load
+            if (!oldRoot) return allocateFresh(bytes);
+            next = oldRoot.next; // atomic load
+        }
+        while (!cas(&_root, oldRoot, next));
+        // great, snatched the root
+        decNodes();
+        return (cast(ubyte*) oldRoot)[0 .. bytes];
+    }
+
+    private void[] allocateFresh(const size_t bytes) shared
+    {
+        assert(bytes == max || max == unbounded);
+        return parent.allocate(bytes);
+    }
+
+    /// Ditto
+    bool deallocate(void[] b) shared
+    {
+        if (!nodesFull && freeListEligible(b.length))
+        {
+            auto newRoot = cast(shared Node*) b.ptr;
+            shared Node* oldRoot;
+            do
+            {
+                oldRoot = _root;
+                newRoot.next = oldRoot;
+            }
+            while (!cas(&_root, oldRoot, newRoot));
+            incNodes();
+            return true;
+        }
+        static if (hasMember!(ParentAllocator, "deallocate"))
+            return parent.deallocate(b);
+        else
+            return false;
+    }
+
+    /// Ditto
+    bool deallocateAll() shared
+    {
+        bool result = false;
+        static if (hasMember!(ParentAllocator, "deallocateAll"))
+        {
+            result = parent.deallocateAll();
+        }
+        else static if (hasMember!(ParentAllocator, "deallocate"))
+        {
+            result = true;
+            for (auto n = _root; n; n = n.next)
+            {
+                if (!parent.deallocate((cast(ubyte*)n)[0 .. max]))
+                    result = false;
+            }
+        }
+        _root = null;
+        return result;
+    }
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.concurrency : receiveOnly, send, spawn, thisTid, Tid;
+    import std.range : repeat;
+    import std.experimental.allocator.mallocator : Mallocator;
+
+    static shared SharedFreeList!(Mallocator, 64, 128, 10) a;
+
+    assert(a.goodAllocSize(1) == platformAlignment);
+
+    auto b = a.allocate(100);
+    a.deallocate(b);
+
+    static void fun(Tid tid, int i)
+    {
+        scope(exit) tid.send(true);
+        auto b = cast(ubyte[]) a.allocate(100);
+        b[] = cast(ubyte) i;
+
+        assert(b.equal(repeat(cast(ubyte) i, b.length)));
+        a.deallocate(b);
+    }
+
+    Tid[] tids;
+    foreach (i; 0 .. 20)
+    {
+        tids ~= spawn(&fun, thisTid, i);
+    }
+
+    foreach (i; 0 .. 20)
+    {
+        assert(receiveOnly!bool);
+    }
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
+    a.allocate(64);
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime, chooseAtRuntime) a;
+    a.allocate(64);
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    shared SharedFreeList!(Mallocator, 30, 40) a;
+    a.allocate(64);
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    shared SharedFreeList!(Mallocator, 30, 40, chooseAtRuntime) a;
+    a.allocate(64);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d b/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d
new file mode 100644
index 00000000000..ed9c29a83c6
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d
@@ -0,0 +1,402 @@
+module std.experimental.allocator.building_blocks.free_tree;
+
+import std.experimental.allocator.common;
+
+//debug = std_experimental_allocator_free_tree;
+
+/**
+
+The Free Tree allocator, stackable on top of any other allocator, bears
+similarity with the free list allocator. Instead of a singly-linked list of
+previously freed blocks, it maintains a binary search tree. This allows the
+Free Tree allocator to manage blocks of arbitrary lengths and search them
+efficiently.
+
+Common uses of $(D FreeTree) include:
+
+$(UL
+$(LI Adding $(D deallocate) capability to an allocator that lacks it (such as simple regions).)
+$(LI Getting the benefits of multiple adaptable freelists that do not need to
+be tuned for one specific size but insted automatically adapts itself to
+frequently used sizes.)
+)
+
+The free tree has special handling of duplicates (a singly-linked list per
+node) in anticipation of large number of duplicates. Allocation time from the
+free tree is expected to be $(BIGOH log n) where $(D n) is the number of
+distinct sizes (not total nodes) kept in the free tree.
+
+Allocation requests first search the tree for a buffer of suitable size
+deallocated in the past. If a match is found, the node is removed from the tree
+and the memory is returned. Otherwise, the allocation is directed to $(D
+ParentAllocator). If at this point $(D ParentAllocator) also fails to allocate,
+$(D FreeTree) frees everything and then tries the parent allocator again.
+
+Upon deallocation, the deallocated block is inserted in the internally
+maintained free tree (not returned to the parent). The free tree is not kept
+balanced. Instead, it has a last-in-first-out flavor because newly inserted
+blocks are rotated to the root of the tree. That way allocations are cache
+friendly and also frequently used sizes are more likely to be found quickly,
+whereas seldom used sizes migrate to the leaves of the tree.
+
+$(D FreeTree) rounds up small allocations to at least $(D 4 * size_t.sizeof),
+which on 64-bit system is one cache line size. If very small objects need to
+be efficiently allocated, the $(D FreeTree) should be fronted with an
+appropriate small object allocator.
+
+The following methods are defined if $(D ParentAllocator) defines them, and forward to it: $(D allocateAll), $(D expand), $(D owns), $(D reallocate).
+*/
+struct FreeTree(ParentAllocator)
+{
+    static assert(ParentAllocator.alignment % size_t.alignof == 0,
+        "FreeTree must be on top of a word-aligned allocator");
+
+    import std.algorithm : min, max, swap;
+    import std.traits : hasMember;
+
+    // State {
+    static if (stateSize!ParentAllocator) private ParentAllocator parent;
+    else private alias parent = ParentAllocator.instance;
+    private Node* root; // that's the entire added state
+    // }
+
+    private struct Node
+    {
+        Node*[2] kid;
+        Node* sibling;
+        size_t size;
+        ref Node* left() { return kid[0]; }
+        ref Node* right() { return kid[1]; }
+    }
+
+    // Removes "which" from the tree, returns the memory it occupied
+    private void[] remove(ref Node* which)
+    {
+        assert(which);
+        assert(!which.sibling);
+        auto result = (cast(ubyte*) which)[0 .. which.size];
+        if (!which.right) which = which.left;
+        else if (!which.left) which = which.right;
+        else
+        {
+            // result has two kids
+            static bool toggler;
+            // Crude randomization: alternate left/right choices
+            toggler = !toggler;
+            auto newRoot = which.kid[toggler], orphan = which.kid[!toggler];
+            which = newRoot;
+            for (Node* n = void; (n = newRoot.kid[!toggler]) !is null; )
+            {
+                newRoot = n;
+            }
+            newRoot.kid[!toggler] = orphan;
+        }
+        return result;
+    }
+
+    private void[] findAndRemove(ref Node* n, size_t s)
+    {
+        if (!n) return null;
+        if (s == n.size)
+        {
+            if (auto sis = n.sibling)
+            {
+                // Nice, give away one from the freelist
+                auto result = (cast(ubyte*) sis)[0 .. sis.size];
+                n.sibling = sis.sibling;
+                return result;
+            }
+            return remove(n);
+        }
+        return findAndRemove(n.kid[s > n.size], s);
+    }
+
+    debug(std_experimental_allocator_free_tree)
+    private void dump()
+    {
+        import std.stdio : writef, writefln, writeln;
+        writeln(typeof(this).stringof, "@", &this, " {");
+        scope(exit) writeln("}");
+
+        if (!root) return;
+
+        static void recurse(Node* n, uint indent = 4)
+        {
+            if (!n)
+            {
+                writefln("%*s(null)", indent, "");
+                return;
+            }
+            for (auto sis = n; sis; sis = sis.sibling)
+            {
+                writef("%*s%x (%s bytes) ", indent, "",
+                    cast(void*) n, n.size);
+            }
+            writeln;
+            if (!n.left && !n.right) return;
+            recurse(n.left, indent + 4);
+            recurse(n.right, indent + 4);
+        }
+        recurse(root);
+    }
+
+    private string formatSizes()
+    {
+        string result = "(";
+        void recurse(Node* n)
+        {
+            if (!n)
+            {
+                result ~= "_";
+                return;
+            }
+            import std.conv : to;
+            result ~= to!string(n.size);
+            for (auto sis = n.sibling; sis; sis = sis.sibling)
+            {
+                result ~= "+moar";
+            }
+            if (n.left || n.right)
+            {
+                result ~= " (";
+                recurse(n.left);
+                result ~= ' ';
+                recurse(n.right);
+                result ~= ")";
+            }
+        }
+        recurse(root);
+        return result ~= ")";
+    }
+
+    private static void rotate(ref Node* parent, bool toRight)
+    {
+        assert(parent);
+        auto opposing = parent.kid[!toRight];
+        if (!opposing) return;
+        parent.kid[!toRight] = opposing.kid[toRight];
+        opposing.kid[toRight] = parent;
+        parent = opposing;
+    }
+
+    // Inserts which into the tree, making it the new root
+    private void insertAsRoot(Node* which)
+    {
+        assert(which);
+        debug(std_experimental_allocator_free_tree)
+        {
+            assertValid;
+            scope(exit) assertValid;
+        }
+
+        static void recurse(ref Node* where, Node* which)
+        {
+            if (!where)
+            {
+                where = which;
+                which.left = null;
+                which.right = null;
+                which.sibling = null;
+                return;
+            }
+            if (which.size == where.size)
+            {
+                // Special handling of duplicates
+                which.sibling = where.sibling;
+                where.sibling = which;
+                which.left = null;
+                which.right = null;
+                return;
+            }
+            bool goRight = which.size > where.size;
+            recurse(where.kid[goRight], which);
+            rotate(where, !goRight);
+        }
+        recurse(root, which);
+    }
+
+    private void assertValid()
+    {
+        debug(std_experimental_allocator_free_tree)
+        {
+            static bool isBST(Node* n, size_t lb = 0, size_t ub = size_t.max)
+            {
+                if (!n) return true;
+                for (auto sis = n.sibling; sis; sis = sis.sibling)
+                {
+                    assert(n.size == sis.size);
+                    assert(sis.left is null);
+                    assert(sis.right is null);
+                }
+                return lb < n.size && n.size <= ub
+                    && isBST(n.left, lb, min(ub, n.size))
+                    && isBST(n.right, max(lb, n.size), ub);
+            }
+            if (isBST(root)) return;
+            dump;
+            assert(0);
+        }
+    }
+
+    /**
+    The $(D FreeTree) is word aligned.
+    */
+    enum uint alignment = size_t.alignof;
+
+    /**
+    The $(D FreeTree) allocator is noncopyable.
+    */
+    this(this) @disable;
+
+    /**
+    The destructor of $(D FreeTree) releases all memory back to the parent
+    allocator.
+    */
+    static if (hasMember!(ParentAllocator, "deallocate"))
+    ~this()
+    {
+        clear;
+    }
+
+    /**
+    Returns $(D parent.goodAllocSize(max(Node.sizeof, s))).
+    */
+    static if (stateSize!ParentAllocator)
+        size_t goodAllocSize(size_t s)
+        {
+            return parent.goodAllocSize(max(Node.sizeof, s));
+        }
+    else
+        static size_t goodAllocSize(size_t s)
+        {
+            return parent.goodAllocSize(max(Node.sizeof, s));
+        }
+
+    /**
+
+    Allocates $(D n) bytes of memory. First consults the free tree, and returns
+    from it if a suitably sized block is found. Otherwise, the parent allocator
+    is tried. If allocation from the parent succeeds, the allocated block is
+    returned. Otherwise, the free tree tries an alternate strategy: If $(D
+    ParentAllocator) defines $(D deallocate), $(D FreeTree) releases all of its
+    contents and tries again.
+
+    TODO: Splitting and coalescing should be implemented if $(D ParentAllocator) does not defined $(D deallocate).
+
+    */
+    void[] allocate(size_t n)
+    {
+        assertValid;
+        auto result = findAndRemove(root, goodAllocSize(n));
+        if (result.ptr) return result.ptr[0 .. n];
+
+        // Parent ran out of juice, desperation mode on
+        static if (hasMember!(ParentAllocator, "deallocate"))
+        {
+            clear;
+            return parent.allocate(n);
+        }
+        else
+        {
+            // TODO: get smart here
+            return null;
+        }
+    }
+
+    // Forwarding methods
+    mixin(forwardToMember("parent",
+        "allocateAll", "expand", "owns", "reallocate"));
+
+    /** Places $(D b) into the free tree. */
+    bool deallocate(void[] b)
+    {
+        if (!b.ptr) return true;
+        auto which = cast(Node*) b.ptr;
+        which.size = goodAllocSize(b.length);
+        // deliberately don't initialize which.left and which.right
+        assert(which.size >= Node.sizeof);
+        insertAsRoot(which);
+        return true;
+    }
+
+    unittest // test a few simple configurations
+    {
+        import std.experimental.allocator.gc_allocator;
+        FreeTree!GCAllocator a;
+        auto b1 = a.allocate(10000);
+        auto b2 = a.allocate(20000);
+        auto b3 = a.allocate(30000);
+        assert(b1.ptr && b2.ptr && b3.ptr);
+        a.deallocate(b1);
+        a.deallocate(b3);
+        a.deallocate(b2);
+        assert(a.formatSizes == "(20480 (12288 32768))", a.formatSizes);
+
+        b1 = a.allocate(10000);
+        assert(a.formatSizes == "(20480 (_ 32768))", a.formatSizes);
+        b1 = a.allocate(30000);
+        assert(a.formatSizes == "(20480)", a.formatSizes);
+        b1 = a.allocate(20000);
+        assert(a.formatSizes == "(_)", a.formatSizes);
+    }
+
+    unittest // build a complex free tree
+    {
+        import std.experimental.allocator.gc_allocator, std.range;
+        FreeTree!GCAllocator a;
+        uint[] sizes = [3008,704,1856,576,1632,672,832,1856,1120,2656,1216,672,
+            448,992,2400,1376,2688,2656,736,1440];
+        void[][] allocs;
+        foreach (s; sizes)
+            allocs ~= a.allocate(s);
+        foreach_reverse (b; allocs)
+        {
+            assert(b.ptr);
+            a.deallocate(b);
+        }
+        a.assertValid;
+        allocs = null;
+        foreach (s; sizes)
+            allocs ~= a.allocate(s);
+        assert(a.root is null);
+        a.assertValid;
+    }
+
+    /** Defined if $(D ParentAllocator.deallocate) exists, and returns to it
+    all memory held in the free tree. */
+    static if (hasMember!(ParentAllocator, "deallocate"))
+    void clear()
+    {
+        void recurse(Node* n)
+        {
+            if (!n) return;
+            recurse(n.left);
+            recurse(n.right);
+            parent.deallocate((cast(ubyte*) n)[0 .. n.size]);
+        }
+        recurse(root);
+        root = null;
+    }
+
+    /**
+
+    Defined if $(D ParentAllocator.deallocateAll) exists, and forwards to it.
+    Also nullifies the free tree (it's assumed the parent frees all memory
+    stil managed by the free tree).
+
+    */
+    static if (hasMember!(ParentAllocator, "deallocateAll"))
+    bool deallocateAll()
+    {
+        // This is easy, just nuke the root and deallocate all from the
+        // parent
+        root = null;
+        return parent.deallocateAll;
+    }
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator;
+    testAllocator!(() => FreeTree!GCAllocator());
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d
new file mode 100644
index 00000000000..334755cf2e6
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d
@@ -0,0 +1,774 @@
+module std.experimental.allocator.building_blocks.kernighan_ritchie;
+import std.experimental.allocator.building_blocks.null_allocator;
+
+//debug = KRRegion;
+debug(KRRegion) import std.stdio;
+version(unittest) import std.conv : text;
+
+// KRRegion
+/**
+$(D KRRegion) draws inspiration from the $(LINK2
+std_experimental_allocator_region.html, region allocation strategy) and also the
+$(WEB stackoverflow.com/questions/13159564/explain-this-implementation-of-malloc-from-the-kr-book,
+famed allocator) described by Brian Kernighan and Dennis Ritchie in section 8.7
+of the book $(WEB amazon.com/exec/obidos/ASIN/0131103628/classicempire, "The C
+Programming Language"), Second Edition, Prentice Hall, 1988.
+
+$(H4 `KRRegion` = `Region` + Kernighan-Ritchie Allocator)
+
+Initially, `KRRegion` starts in "region" mode: allocations are served from
+the memory chunk in a region fashion. Thus, as long as there is enough memory
+left, $(D KRRegion.allocate) has the performance profile of a region allocator.
+Deallocation inserts (in $(BIGOH 1) time) the deallocated blocks in an
+unstructured freelist, which is not read in region mode.
+
+Once the region cannot serve an $(D allocate) request, $(D KRRegion) switches
+to "free list" mode. It sorts the list of previously deallocated blocks by
+address and serves allocation requests off that free list. The allocation and
+deallocation follow the pattern described by Kernighan and Ritchie.
+
+The recommended use of `KRRegion` is as a $(I region with deallocation). If the
+`KRRegion` is dimensioned appropriately, it could often not enter free list
+mode during its lifetime. Thus it is as fast as a simple region, whilst
+offering deallocation at a small cost. When the region memory is  exhausted,
+the previously deallocated memory is still usable, at a performance  cost. If
+the region is not excessively large and fragmented, the linear  allocation and
+deallocation cost may still be compensated for by the good locality
+characteristics.
+
+If the chunk of memory managed is large, it may be desirable to switch
+management to free list from the beginning. That way, memory may be used in a
+more compact manner than region mode. To force free list mode, call $(D
+switchToFreeList) shortly after construction or when deemed appropriate.
+
+The smallest size that can be allocated is two words (16 bytes on 64-bit
+systems, 8 bytes on 32-bit systems). This is because the free list management
+needs two words (one for the length, the other for the next pointer in the
+singly-linked list).
+
+The $(D ParentAllocator) type parameter is the type of the allocator used to
+allocate the memory chunk underlying the $(D KRRegion) object. Choosing the
+default ($(D NullAllocator)) means the user is responsible for passing a buffer
+at construction (and for deallocating it if necessary). Otherwise, $(D KRRegion)
+automatically deallocates the buffer during destruction. For that reason, if
+$(D ParentAllocator) is not $(D NullAllocator), then $(D KRRegion) is not
+copyable.
+
+$(H4 Implementation Details)
+
+In free list mode, $(D KRRegion) embeds a free blocks list onto the chunk of
+memory. The free list is circular, coalesced, and sorted by address at all
+times. Allocations and deallocations take time proportional to the number of
+previously deallocated blocks. (In practice the cost may be lower, e.g. if
+memory is deallocated in reverse order of allocation, all operations take
+constant time.) Memory utilization is good (small control structure and no
+per-allocation overhead). The disadvantages of freelist mode include proneness
+to fragmentation, a minimum allocation size of two words, and linear worst-case
+allocation and deallocation times.
+
+Similarities of `KRRegion` (in free list mode) with the
+Kernighan-Ritchie allocator:
+
+$(UL
+$(LI Free blocks have variable size and are linked in a singly-linked list.)
+$(LI The freelist is maintained in increasing address order, which makes
+coalescing easy.)
+$(LI The strategy for finding the next available block is first fit.)
+$(LI The free list is circular, with the last node pointing back to the first.)
+$(LI Coalescing is carried during deallocation.)
+)
+
+Differences from the Kernighan-Ritchie allocator:
+
+$(UL
+$(LI Once the chunk is exhausted, the Kernighan-Ritchie allocator allocates
+another chunk using operating system primitives. For better composability, $(D
+KRRegion) just gets full (returns $(D null) on new allocation requests). The
+decision to allocate more blocks is deferred to a higher-level entity. For an
+example, see the example below using $(D AllocatorList) in conjunction with $(D
+KRRegion).)
+$(LI Allocated blocks do not hold a size prefix. This is because in D the size
+information is available in client code at deallocation time.)
+)
+
+*/
+struct KRRegion(ParentAllocator = NullAllocator)
+{
+    import std.experimental.allocator.common : stateSize, alignedAt, Ternary;
+    import std.traits : hasMember;
+
+    private static struct Node
+    {
+        import std.typecons : tuple, Tuple;
+
+        Node* next;
+        size_t size;
+
+        this(this) @disable;
+
+        void[] payload() inout
+        {
+            return (cast(ubyte*) &this)[0 .. size];
+        }
+
+        bool adjacent(in Node* right) const
+        {
+            assert(right);
+            auto p = payload;
+            return p.ptr + p.length == right;
+        }
+
+        bool coalesce()
+        {
+            if (!adjacent(next)) return false;
+            size += next.size;
+            next = next.next;
+            return true;
+        }
+
+        Tuple!(void[], Node*) allocateHere(size_t bytes)
+        {
+            assert(bytes >= Node.sizeof);
+            assert(bytes % Node.alignof == 0);
+            assert(next);
+            assert(!adjacent(next));
+            if (size < bytes) return typeof(return)();
+            assert(size >= bytes);
+            immutable leftover = size - bytes;
+            if (leftover >= Node.sizeof)
+            {
+                // There's room for another node
+                auto newNode = cast(Node*) ((cast(ubyte*) &this) + bytes);
+                newNode.size = leftover;
+                newNode.next = next == &this ? newNode : next;
+                assert(next);
+                return tuple(payload, newNode);
+            }
+            // No slack space, just return next node
+            return tuple(payload, next == &this ? null : next);
+        }
+    }
+
+    // state {
+    /**
+    If $(D ParentAllocator) holds state, $(D parent) is a public member of type
+    $(D KRRegion). Otherwise, $(D parent) is an $(D alias) for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator) ParentAllocator parent;
+    else alias parent = ParentAllocator.instance;
+    private void[] payload;
+    private Node* root;
+    private bool regionMode = true;
+    // }
+
+    auto byNodePtr()
+    {
+        static struct Range
+        {
+            Node* start, current;
+            @property bool empty() { return !current; }
+            @property Node* front() { return current; }
+            void popFront()
+            {
+                assert(current && current.next);
+                current = current.next;
+                if (current == start) current = null;
+            }
+            @property Range save() { return this; }
+        }
+        import std.range : isForwardRange;
+        static assert(isForwardRange!Range);
+        return Range(root, root);
+    }
+
+    string toString()
+    {
+        import std.format : format;
+        string s = "KRRegion@";
+        s ~= format("%s-%s(0x%s[%s] %s", &this, &this + 1,
+            payload.ptr, payload.length,
+            regionMode ? "(region)" : "(freelist)");
+
+        Node* lastNode = null;
+        if (!regionMode)
+        {
+            foreach (node; byNodePtr)
+            {
+                s ~= format(", %sfree(0x%s[%s])",
+                    lastNode && lastNode.adjacent(node) ? "+" : "",
+                    cast(void*) node, node.size);
+                lastNode = node;
+            }
+        }
+        else
+        {
+            for (auto node = root; node; node = node.next)
+            {
+                s ~= format(", %sfree(0x%s[%s])",
+                    lastNode && lastNode.adjacent(node) ? "+" : "",
+                    cast(void*) node, node.size);
+                lastNode = node;
+            }
+        }
+
+        s ~= ')';
+        return s;
+    }
+
+    private void assertValid(string s)
+    {
+        assert(!regionMode);
+        if (!payload.ptr)
+        {
+            assert(!root, s);
+            return;
+        }
+        if (!root)
+        {
+            return;
+        }
+        assert(root >= payload.ptr, s);
+        assert(root < payload.ptr + payload.length, s);
+
+        // Check that the list terminates
+        size_t n;
+        foreach (node; byNodePtr)
+        {
+            assert(node.next);
+            assert(!node.adjacent(node.next));
+            assert(n++ < payload.length / Node.sizeof, s);
+        }
+    }
+
+    private Node* sortFreelist(Node* root)
+    {
+        // Find a monotonic run
+        auto last = root;
+        for (;;)
+        {
+            if (!last.next) return root;
+            if (last > last.next) break;
+            assert(last < last.next);
+            last = last.next;
+        }
+        auto tail = last.next;
+        last.next = null;
+        tail = sortFreelist(tail);
+        return merge(root, tail);
+    }
+
+    private Node* merge(Node* left, Node* right)
+    {
+        assert(left != right);
+        if (!left) return right;
+        if (!right) return left;
+        if (left < right)
+        {
+            auto result = left;
+            result.next = merge(left.next, right);
+            return result;
+        }
+        auto result = right;
+        result.next = merge(left, right.next);
+        return result;
+    }
+
+    private void coalesceAndMakeCircular()
+    {
+        for (auto n = root;;)
+        {
+            assert(!n.next || n < n.next);
+            if (!n.next)
+            {
+                // Convert to circular
+                n.next = root;
+                break;
+            }
+            if (n.coalesce) continue; // possibly another coalesce
+            n = n.next;
+        }
+    }
+
+    /**
+    Create a $(D KRRegion). If $(D ParentAllocator) is not $(D NullAllocator),
+    $(D KRRegion)'s destructor will call $(D parent.deallocate).
+
+    Params:
+    b = Block of memory to serve as support for the allocator. Memory must be
+    larger than two words and word-aligned.
+    n = Capacity desired. This constructor is defined only if $(D
+    ParentAllocator) is not $(D NullAllocator).
+    */
+    this(void[] b)
+    {
+        if (b.length < Node.sizeof)
+        {
+            // Init as empty
+            assert(root is null);
+            assert(payload is null);
+            return;
+        }
+        assert(b.length >= Node.sizeof);
+        assert(b.ptr.alignedAt(Node.alignof));
+        assert(b.length >= 2 * Node.sizeof);
+        payload = b;
+        root = cast(Node*) b.ptr;
+        // Initialize the free list with all list
+        assert(regionMode);
+        root.next = null;
+        root.size = b.length;
+        debug(KRRegion) writefln("KRRegion@%s: init with %s[%s]", &this,
+            b.ptr, b.length);
+    }
+
+    /// Ditto
+    static if (!is(ParentAllocator == NullAllocator))
+    this(size_t n)
+    {
+        assert(n > Node.sizeof);
+        this(parent.allocate(n));
+    }
+
+    /// Ditto
+    static if (!is(ParentAllocator == NullAllocator)
+        && hasMember!(ParentAllocator, "deallocate"))
+    ~this()
+    {
+        parent.deallocate(payload);
+    }
+
+    /**
+    Forces free list mode. If already in free list mode, does nothing.
+    Otherwise, sorts the free list accumulated so far and switches strategy for
+    future allocations to KR style.
+    */
+    void switchToFreeList()
+    {
+        if (!regionMode) return;
+        regionMode = false;
+        if (!root) return;
+        root = sortFreelist(root);
+        coalesceAndMakeCircular;
+    }
+
+    /*
+    Noncopyable
+    */
+    @disable this(this);
+
+    /**
+    Word-level alignment.
+    */
+    enum alignment = Node.alignof;
+
+    /**
+    Allocates $(D n) bytes. Allocation searches the list of available blocks
+    until a free block with $(D n) or more bytes is found (first fit strategy).
+    The block is split (if larger) and returned.
+
+    Params: n = number of bytes to _allocate
+
+    Returns: A word-aligned buffer of $(D n) bytes, or $(D null).
+    */
+    void[] allocate(size_t n)
+    {
+        if (!n || !root) return null;
+        const actualBytes = goodAllocSize(n);
+
+        // Try the region first
+        if (regionMode)
+        {
+            // Only look at the head of the freelist
+            if (root.size >= actualBytes)
+            {
+                // Enough room for allocation
+                void* result = root;
+                immutable balance = root.size - actualBytes;
+                if (balance >= Node.sizeof)
+                {
+                    auto newRoot = cast(Node*) (result + actualBytes);
+                    newRoot.next = root.next;
+                    newRoot.size = balance;
+                    root = newRoot;
+                }
+                else
+                {
+                    root = null;
+                    switchToFreeList;
+                }
+                return result[0 .. n];
+            }
+            // Not enough memory, switch to freelist mode and fall through
+            switchToFreeList;
+        }
+
+        // Try to allocate from next after the iterating node
+        for (auto pnode = root;;)
+        {
+            assert(!pnode.adjacent(pnode.next));
+            auto k = pnode.next.allocateHere(actualBytes);
+            if (k[0] !is null)
+            {
+                // awes
+                assert(k[0].length >= n);
+                if (root == pnode.next) root = k[1];
+                pnode.next = k[1];
+                return k[0][0 .. n];
+            }
+            pnode = pnode.next;
+            if (pnode == root) break;
+        }
+        return null;
+    }
+
+    /**
+    Deallocates $(D b), which is assumed to have been previously allocated with
+    this allocator. Deallocation performs a linear search in the free list to
+    preserve its sorting order. It follows that blocks with higher addresses in
+    allocators with many free blocks are slower to deallocate.
+
+    Params: b = block to be deallocated
+    */
+    bool deallocate(void[] b)
+    {
+        debug(KRRegion) writefln("KRRegion@%s: deallocate(%s[%s])", &this,
+            b.ptr, b.length);
+        if (!b.ptr) return true;
+        assert(owns(b) == Ternary.yes);
+        assert(b.ptr.alignedAt(Node.alignof));
+
+        // Insert back in the freelist, keeping it sorted by address. Do not
+        // coalesce at this time. Instead, do it lazily during allocation.
+        auto n = cast(Node*) b.ptr;
+        n.size = goodAllocSize(b.length);
+
+        if (regionMode)
+        {
+            assert(root);
+            // Insert right after root
+            n.next = root.next;
+            root.next = n;
+            return true;
+        }
+
+        if (!root)
+        {
+            // What a sight for sore eyes
+            root = n;
+            root.next = root;
+            return true;
+        }
+
+        version(assert) foreach (test; byNodePtr)
+        {
+            assert(test != n);
+        }
+        // Linear search
+        auto pnode = root;
+        do
+        {
+            assert(pnode && pnode.next);
+            assert(pnode != n);
+            assert(pnode.next != n);
+            if (pnode < pnode.next)
+            {
+                if (pnode >= n || n >= pnode.next) continue;
+                // Insert in between pnode and pnode.next
+                n.next = pnode.next;
+                pnode.next = n;
+                n.coalesce;
+                pnode.coalesce;
+                root = pnode;
+                return true;
+            }
+            else if (pnode < n)
+            {
+                // Insert at the end of the list
+                n.next = pnode.next;
+                pnode.next = n;
+                pnode.coalesce;
+                root = pnode;
+                return true;
+            }
+            else if (n < pnode.next)
+            {
+                // Insert at the front of the list
+                n.next = pnode.next;
+                pnode.next = n;
+                n.coalesce;
+                root = n;
+                return true;
+            }
+        }
+        while ((pnode = pnode.next) != root);
+        assert(0, "Wrong parameter passed to deallocate");
+    }
+
+    /**
+    Allocates all memory available to this allocator. If the allocator is empty,
+    returns the entire available block of memory. Otherwise, it still performs
+    a best-effort allocation: if there is no fragmentation (e.g. $(D allocate)
+    has been used but not $(D deallocate)), allocates and returns the only
+    available block of memory.
+
+    The operation takes time proportional to the number of adjacent free blocks
+    at the front of the free list. These blocks get coalesced, whether
+    $(D allocateAll) succeeds or fails due to fragmentation.
+    */
+    void[] allocateAll()
+    {
+        //debug(KRRegion) assertValid("allocateAll");
+        //debug(KRRegion) scope(exit) assertValid("allocateAll");
+        if (regionMode) switchToFreeList;
+        if (root && root.next == root)
+            return allocate(root.size);
+        return null;
+    }
+
+    ///
+    unittest
+    {
+        import std.experimental.allocator.gc_allocator : GCAllocator;
+        auto alloc = KRRegion!GCAllocator(1024 * 64);
+        const b1 = alloc.allocate(2048);
+        assert(b1.length == 2048);
+        const b2 = alloc.allocateAll;
+        assert(b2.length == 1024 * 62);
+    }
+
+    /**
+    Deallocates all memory currently allocated, making the allocator ready for
+    other allocations. This is a $(BIGOH 1) operation.
+    */
+    bool deallocateAll()
+    {
+        debug(KRRegion) assertValid("deallocateAll");
+        debug(KRRegion) scope(exit) assertValid("deallocateAll");
+        root = cast(Node*) payload.ptr;
+        // Initialize the free list with all list
+        if (root)
+        {
+            root.next = root;
+            root.size = payload.length;
+        }
+        return true;
+    }
+
+    /**
+    Checks whether the allocator is responsible for the allocation of $(D b).
+    It does a simple $(BIGOH 1) range check. $(D b) should be a buffer either
+    allocated with $(D this) or obtained through other means.
+    */
+    Ternary owns(void[] b)
+    {
+        debug(KRRegion) assertValid("owns");
+        debug(KRRegion) scope(exit) assertValid("owns");
+        return Ternary(b.ptr >= payload.ptr
+            && b.ptr < payload.ptr + payload.length);
+    }
+
+    /**
+    Adjusts $(D n) to a size suitable for allocation (two words or larger,
+    word-aligned).
+    */
+    static size_t goodAllocSize(size_t n)
+    {
+        import std.experimental.allocator.common : roundUpToMultipleOf;
+        return n <= Node.sizeof
+            ? Node.sizeof : n.roundUpToMultipleOf(alignment);
+    }
+
+    /**
+    Returns: `Ternary.yes` if the allocator is empty, `Ternary.no` otherwise.
+    Never returns `Ternary.unknown`.
+    */
+    Ternary empty()
+    {
+        return Ternary(root && root.size == payload.length);
+    }
+}
+
+/**
+$(D KRRegion) is preferable to $(D Region) as a front for a general-purpose
+allocator if $(D deallocate) is needed, yet the actual deallocation traffic is
+relatively low. The example below shows a $(D KRRegion) using stack storage
+fronting the GC allocator.
+*/
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.fallback_allocator
+        : fallbackAllocator;
+    import std.experimental.allocator.common : Ternary;
+    // KRRegion fronting a general-purpose allocator
+    ubyte[1024 * 128] buf;
+    auto alloc = fallbackAllocator(KRRegion!()(buf), GCAllocator.instance);
+    auto b = alloc.allocate(100);
+    assert(b.length == 100);
+    assert(alloc.primary.owns(b) == Ternary.yes);
+}
+
+/**
+The code below defines a scalable allocator consisting of 1 MB (or larger)
+blocks fetched from the garbage-collected heap. Each block is organized as a
+KR-style heap. More blocks are allocated and freed on a need basis.
+
+This is the closest example to the allocator introduced in the K$(AMP)R book.
+It should perform slightly better because instead of searching through one
+large free list, it searches through several shorter lists in LRU order. Also,
+it actually returns memory to the operating system when possible.
+*/
+unittest
+{
+    import std.algorithm : max;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.mmap_allocator : MmapAllocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    AllocatorList!(n => KRRegion!MmapAllocator(max(n * 16, 1024 * 1024))) alloc;
+}
+
+unittest
+{
+    import std.algorithm : max;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import   std.experimental.allocator.common : Ternary;
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    /*
+    Create a scalable allocator consisting of 1 MB (or larger) blocks fetched
+    from the garbage-collected heap. Each block is organized as a KR-style
+    heap. More blocks are allocated and freed on a need basis.
+    */
+    AllocatorList!(n => KRRegion!Mallocator(max(n * 16, 1024 * 1024)),
+        NullAllocator) alloc;
+    void[][50] array;
+    foreach (i; 0 .. array.length)
+    {
+        auto length = i * 10_000 + 1;
+        array[i] = alloc.allocate(length);
+        assert(array[i].ptr);
+        assert(array[i].length == length);
+    }
+    import std.random : randomShuffle;
+    randomShuffle(array[]);
+    foreach (i; 0 .. array.length)
+    {
+        assert(array[i].ptr);
+        assert(alloc.owns(array[i]) == Ternary.yes);
+        alloc.deallocate(array[i]);
+    }
+}
+
+unittest
+{
+    import std.algorithm : max;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.common : Ternary;
+    import std.experimental.allocator.mmap_allocator : MmapAllocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    /*
+    Create a scalable allocator consisting of 1 MB (or larger) blocks fetched
+    from the garbage-collected heap. Each block is organized as a KR-style
+    heap. More blocks are allocated and freed on a need basis.
+    */
+    AllocatorList!((n) {
+        auto result = KRRegion!MmapAllocator(max(n * 2, 1024 * 1024));
+        return result;
+    }) alloc;
+    void[][99] array;
+    foreach (i; 0 .. array.length)
+    {
+        auto length = i * 10_000 + 1;
+        array[i] = alloc.allocate(length);
+        assert(array[i].ptr);
+        foreach (j; 0 .. i)
+        {
+            assert(array[i].ptr != array[j].ptr);
+        }
+        assert(array[i].length == length);
+    }
+    import std.random : randomShuffle;
+    randomShuffle(array[]);
+    foreach (i; 0 .. array.length)
+    {
+        assert(alloc.owns(array[i]) == Ternary.yes);
+        alloc.deallocate(array[i]);
+    }
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    import std.algorithm : max;
+    import std.experimental.allocator.common : testAllocator;
+    testAllocator!(() => AllocatorList!(
+        n => KRRegion!GCAllocator(max(n * 16, 1024 * 1024)))());
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+
+    auto alloc = KRRegion!GCAllocator(1024 * 1024);
+
+    void[][] array;
+    foreach (i; 1 .. 4)
+    {
+        array ~= alloc.allocate(i);
+        assert(array[$ - 1].length == i);
+    }
+    alloc.deallocate(array[1]);
+    alloc.deallocate(array[0]);
+    alloc.deallocate(array[2]);
+    assert(alloc.allocateAll().length == 1024 * 1024);
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.common : Ternary;
+    auto alloc = KRRegion!()(GCAllocator.instance.allocate(1024 * 1024));
+    const store = alloc.allocate(KRRegion!().sizeof);
+    auto p = cast(KRRegion!()* ) store.ptr;
+    import std.conv : emplace;
+    import std.algorithm : move;
+    import core.stdc.string : memcpy;
+
+    memcpy(p, &alloc, alloc.sizeof);
+    emplace(&alloc);
+
+    void[][100] array;
+    foreach (i; 0 .. array.length)
+    {
+        auto length = 100 * i + 1;
+        array[i] = p.allocate(length);
+        assert(array[i].length == length, text(array[i].length));
+        assert(p.owns(array[i]) == Ternary.yes);
+    }
+    import std.random : randomShuffle;
+    randomShuffle(array[]);
+    foreach (i; 0 .. array.length)
+    {
+        assert(p.owns(array[i]) == Ternary.yes);
+        p.deallocate(array[i]);
+    }
+    auto b = p.allocateAll();
+    assert(b.length == 1024 * 1024 - KRRegion!().sizeof, text(b.length));
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    auto alloc = KRRegion!()(GCAllocator.instance.allocate(1024 * 1024));
+    auto p = alloc.allocateAll();
+    assert(p.length == 1024 * 1024);
+    alloc.deallocateAll();
+    p = alloc.allocateAll();
+    assert(p.length == 1024 * 1024);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d
new file mode 100644
index 00000000000..f77ea00ffae
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d
@@ -0,0 +1,82 @@
+module std.experimental.allocator.building_blocks.null_allocator;
+
+/*
+  _   _       _ _          _ _                 _
+ | \ | |     | | |   /\   | | |               | |
+ |  \| |_   _| | |  /  \  | | | ___   ___ __ _| |_ ___  _ __
+ | . ` | | | | | | / /\ \ | | |/ _ \ / __/ _` | __/ _ \| '__|
+ | |\  | |_| | | |/ ____ \| | | (_) | (_| (_| | || (_) | |
+ |_| \_|\__,_|_|_/_/    \_\_|_|\___/ \___\__,_|\__\___/|_|
+*/
+
+/**
+$(D NullAllocator) is an emphatically empty implementation of the allocator
+interface. Although it has no direct use, it is useful as a "terminator" in
+composite allocators.
+*/
+struct NullAllocator
+{
+    import std.experimental.allocator.common : Ternary;
+    /**
+    $(D NullAllocator) advertises a relatively large _alignment equal to 64 KB.
+    This is because $(D NullAllocator) never actually needs to honor this
+    alignment and because composite allocators using $(D NullAllocator)
+    shouldn't be unnecessarily constrained.
+    */
+    enum uint alignment = 64 * 1024;
+    /// Returns $(D n).
+    //size_t goodAllocSize(size_t n) shared const
+    //{ return .goodAllocSize(this, n); }
+    /// Always returns $(D null).
+    void[] allocate(size_t) shared { return null; }
+    /// Always returns $(D null).
+    void[] alignedAllocate(size_t, uint) shared { return null; }
+    /// Always returns $(D null).
+    void[] allocateAll() shared { return null; }
+    /**
+    These methods return $(D false).
+    Precondition: $(D b is null). This is because there is no other possible
+    legitimate input.
+    */
+    bool expand(ref void[] b, size_t) shared
+    { assert(b is null); return false; }
+    /// Ditto
+    bool reallocate(ref void[] b, size_t) shared
+    { assert(b is null); return false; }
+    /// Ditto
+    bool alignedReallocate(ref void[] b, size_t, uint) shared
+    { assert(b is null); return false; }
+    /// Returns $(D Ternary.no).
+    Ternary owns(void[]) shared const { return Ternary.no; }
+    /**
+    Returns $(D null).
+    */
+    void[] resolveInternalPointer(void*) shared const { return null; }
+    /**
+    No-op.
+    Precondition: $(D b is null)
+    */
+    bool deallocate(void[] b) shared { assert(b is null); return true; }
+    /**
+    No-op.
+    */
+    bool deallocateAll() shared { return true; }
+    /**
+    Returns $(D Ternary.yes).
+    */
+    Ternary empty() shared const { return Ternary.yes; }
+    /**
+    Returns the $(D shared) global instance of the $(D NullAllocator).
+    */
+    static shared NullAllocator instance;
+}
+
+unittest
+{
+    auto b = NullAllocator.instance.allocate(100);
+    assert(b is null);
+    NullAllocator.instance.deallocate(b);
+    NullAllocator.instance.deallocateAll();
+    import std.experimental.allocator.common : Ternary;
+    assert(NullAllocator.instance.owns(null) == Ternary.no);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/package.d b/libphobos/src/std/experimental/allocator/building_blocks/package.d
new file mode 100644
index 00000000000..b1908592cf1
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/package.d
@@ -0,0 +1,313 @@
+/**
+$(H2 Assembling Your Own Allocator)
+
+In addition to defining the interfaces above, this package also implements
+untyped composable memory allocators. They are $(I untyped) because they deal
+exclusively in $(D void[]) and have no notion of what type the memory allocated
+would be destined for. They are $(I composable) because the included allocators
+are building blocks that can be assembled in complex nontrivial allocators.
+
+$(P Unlike the allocators for the C and C++ programming languages, which manage
+the allocated size internally, these allocators require that the client
+maintains (or knows $(I a priori)) the allocation size for each piece of memory
+allocated. Put simply, the client must pass the allocated size upon
+deallocation. Storing the size in the _allocator has significant negative
+performance implications, and is virtually always redundant because client code
+needs knowledge of the allocated size in order to avoid buffer overruns. (See
+more discussion in a $(WEB open-
+std.org/JTC1/SC22/WG21/docs/papers/2013/n3536.html, proposal) for sized
+deallocation in C++.) For this reason, allocators herein traffic in $(D void[])
+as opposed to $(D void*).)
+
+$(P In order to be usable as an _allocator, a type should implement the
+following methods with their respective semantics. Only $(D alignment) and  $(D
+allocate) are required. If any of the other methods is missing, the _allocator
+is assumed to not have that capability (for example some allocators do not offer
+manual deallocation of memory). Allocators should NOT implement
+unsupported methods to always fail. For example, an allocator that lacks the
+capability to implement `alignedAllocate` should not define it at all (as
+opposed to defining it to always return `null` or throw an exception). The
+missing implementation statically informs other components about the
+allocator's capabilities and allows them to make design decisions accordingly.)
+
+$(BOOKTABLE ,
+$(TR $(TH Method name) $(TH Semantics))
+
+$(TR $(TDC uint alignment;, $(POST $(RES) > 0)) $(TD Returns the minimum
+alignment of all data returned by the allocator. An allocator may implement $(D
+alignment) as a statically-known $(D enum) value only. Applications that need
+dynamically-chosen alignment values should use the $(D alignedAllocate) and $(D
+alignedReallocate) APIs.))
+
+$(TR $(TDC size_t goodAllocSize(size_t n);, $(POST $(RES) >= n)) $(TD Allocators
+customarily allocate memory in discretely-sized chunks. Therefore, a request for
+$(D n) bytes may result in a larger allocation. The extra memory allocated goes
+unused and adds to the so-called $(WEB goo.gl/YoKffF,internal fragmentation).
+The function $(D goodAllocSize(n)) returns the actual number of bytes that would
+be allocated upon a request for $(D n) bytes. This module defines a default
+implementation that returns $(D n) rounded up to a multiple of the allocator's
+alignment.))
+
+$(TR $(TDC void[] allocate(size_t s);, $(POST $(RES) is null || $(RES).length ==
+s)) $(TD If $(D s == 0), the call may return any empty slice (including $(D
+null)). Otherwise, the call allocates $(D s) bytes of memory and returns the
+allocated block, or $(D null) if the request could not be satisfied.))
+
+$(TR $(TDC void[] alignedAllocate(size_t s, uint a);, $(POST $(RES) is null ||
+$(RES).length == s)) $(TD Similar to `allocate`, with the additional
+guarantee that the memory returned is aligned to at least `a` bytes. `a`
+must be a power of 2.))
+
+$(TR $(TDC void[] allocateAll();) $(TD Offers all of allocator's memory to the
+caller, so it's usually defined by fixed-size allocators. If the allocator is
+currently NOT managing any memory, then $(D allocateAll()) shall allocate and
+return all memory available to the allocator, and subsequent calls to all
+allocation primitives should not succeed (e..g $(D allocate) shall return $(D
+null) etc). Otherwise, $(D allocateAll) only works on a best-effort basis, and
+the allocator is allowed to return $(D null) even if does have available memory.
+Memory allocated with $(D allocateAll) is not otherwise special (e.g. can be
+reallocated or deallocated with the usual primitives, if defined).))
+
+$(TR $(TDC bool expand(ref void[] b, size_t delta);, $(POST !$(RES) || b.length
+== $(I old)(b).length + delta)) $(TD Expands $(D b) by $(D delta) bytes. If $(D
+delta == 0), succeeds without changing $(D b). If $(D b is null), the call
+evaluates $(D b = allocate(delta)) and returns $(D b !is null). Otherwise, $(D
+b) must be a buffer previously allocated with the same allocator. If expansion
+was successful, $(D expand) changes $(D b)'s length to $(D b.length + delta) and
+returns $(D true). Upon failure, the call effects no change upon the allocator
+object, leaves $(D b) unchanged, and returns $(D false).))
+
+$(TR $(TDC bool reallocate(ref void[] b, size_t s);, $(POST !$(RES) || b.length
+== s)) $(TD Reallocates $(D b) to size $(D s), possibly moving memory around.
+$(D b) must be $(D null) or a buffer allocated with the same allocator. If
+reallocation was successful, $(D reallocate) changes $(D b) appropriately and
+returns $(D true). Upon failure, the call effects no change upon the allocator
+object, leaves $(D b) unchanged, and returns $(D false). An allocator should
+implement $(D reallocate) if it can derive some advantage from doing so;
+otherwise, this module defines a $(D reallocate) free function implemented in
+terms of $(D expand), $(D allocate), and $(D deallocate).))
+
+$(TR $(TDC bool alignedReallocate(ref void[] b,$(BR) size_t s, uint a);, $(POST
+!$(RES) || b.length == s)) $(TD Similar to $(D reallocate), but guarantees the
+reallocated memory is aligned at $(D a) bytes. The buffer must have been
+originated with a call to $(D alignedAllocate). $(D a) must be a power of 2
+greater than $(D (void*).sizeof). An allocator should implement $(D
+alignedReallocate) if it can derive some advantage from doing so; otherwise,
+this module defines a $(D alignedReallocate) free function implemented in terms
+of $(D expand), $(D alignedAllocate), and $(D deallocate).))
+
+$(TR $(TDC Ternary owns(void[] b);) $(TD Returns `Ternary.yes` if `b` has been
+allocated with this allocator. An allocator should define this method only if it
+can decide on ownership precisely and fast (in constant time, logarithmic time,
+or linear time with a low multiplication factor). Traditional allocators such as
+the C heap do not define such functionality. If $(D b is null), the allocator
+shall return `Ternary.no`, i.e. no allocator owns the `null` slice.))
+
+$(TR $(TDC void[] resolveInternalPointer(void* p);) $(TD If $(D p) is a pointer
+somewhere inside a block allocated with this allocator, returns a pointer to the
+beginning of the allocated block. Otherwise, returns $(D null). If the pointer
+points immediately after an allocated block, the result is implementation
+defined.))
+
+$(TR $(TDC bool deallocate(void[] b);) $(TD If $(D b is null), does
+nothing and returns `true`. Otherwise, deallocates memory previously allocated
+with this allocator and returns `true` if successful, `false` otherwise. An
+implementation that would not support deallocation (i.e. would always return
+`false` should not define this primitive at all.)))
+
+$(TR $(TDC bool deallocateAll();, $(POST empty)) $(TD Deallocates all memory
+allocated with this allocator. If an allocator implements this method, it must
+specify whether its destructor calls it, too.))
+
+$(TR $(TDC Ternary empty();) $(TD Returns `Ternary.yes` if and only if the
+allocator holds no memory (i.e. no allocation has occurred, or all allocations
+have been deallocated).))
+
+$(TR $(TDC static Allocator instance;, $(POST instance $(I is a valid)
+Allocator $(I object))) $(TD Some allocators are $(I monostate), i.e. have only
+an instance and hold only global state. (Notable examples are C's own
+`malloc`-based allocator and D's garbage-collected heap.) Such allocators must
+define a static $(D instance) instance that serves as the symbolic placeholder
+for the global instance of the allocator. An allocator should not hold state
+and define `instance` simultaneously. Depending on whether the allocator is
+thread-safe or not, this instance may be $(D shared).))
+)
+
+$(H2 Sample Assembly)
+
+The example below features an _allocator modeled after $(WEB goo.gl/m7329l,
+jemalloc), which uses a battery of free-list allocators spaced so as to keep
+internal fragmentation to a minimum. The $(D FList) definitions specify no
+bounds for the freelist because the $(D Segregator) does all size selection in
+advance.
+
+Sizes through 3584 bytes are handled via freelists of staggered sizes. Sizes
+from 3585 bytes through 4072 KB are handled by a $(D BitmappedBlock) with a
+block size of 4 KB. Sizes above that are passed direct to the $(D Mallocator).
+
+----
+    alias FList = FreeList!(GCAllocator, 0, unbounded);
+    alias A = Segregator!(
+        8, FreeList!(GCAllocator, 0, 8),
+        128, Bucketizer!(FList, 1, 128, 16),
+        256, Bucketizer!(FList, 129, 256, 32),
+        512, Bucketizer!(FList, 257, 512, 64),
+        1024, Bucketizer!(FList, 513, 1024, 128),
+        2048, Bucketizer!(FList, 1025, 2048, 256),
+        3584, Bucketizer!(FList, 2049, 3584, 512),
+        4072 * 1024, AllocatorList!(
+            () => BitmappedBlock!(GCAllocator, 4096)(4072 * 1024)),
+        GCAllocator
+    );
+    A tuMalloc;
+    auto b = tuMalloc.allocate(500);
+    assert(b.length == 500);
+    auto c = tuMalloc.allocate(113);
+    assert(c.length == 113);
+    assert(tuMalloc.expand(c, 14));
+    tuMalloc.deallocate(b);
+    tuMalloc.deallocate(c);
+----
+
+$(H2 Allocating memory for sharing across threads)
+
+One allocation pattern used in multithreaded applications is to share memory
+across threads, and to deallocate blocks in a different thread than the one that
+allocated it.
+
+All allocators in this module accept and return $(D void[]) (as opposed to
+$(D shared void[])). This is because at the time of allocation, deallocation, or
+reallocation, the memory is effectively not $(D shared) (if it were, it would
+reveal a bug at the application level).
+
+The issue remains of calling $(D a.deallocate(b)) from a different thread than
+the one that allocated $(D b). It follows that both threads must have access to
+the same instance $(D a) of the respective allocator type. By definition of D,
+this is possible only if $(D a) has the $(D shared) qualifier. It follows that
+the allocator type must implement $(D allocate) and $(D deallocate) as $(D
+shared) methods. That way, the allocator commits to allowing usable $(D shared)
+instances.
+
+Conversely, allocating memory with one non-$(D shared) allocator, passing it
+across threads (by casting the obtained buffer to $(D shared)), and later
+deallocating it in a different thread (either with a different allocator object
+or with the same allocator object after casting it to $(D shared)) is illegal.
+
+$(H2 Building Blocks)
+
+$(P The table below gives a synopsis of predefined allocator building blocks,
+with their respective modules. Either `import` the needed modules individually,
+or `import` `std.experimental.building_blocks`, which imports them all
+`public`ly. The building blocks can be assembled in unbounded ways and also
+combined with your own. For a collection of typical and useful preassembled
+allocators and for inspiration in defining more such assemblies, refer to
+$(LINK2 std_experimental_allocator_showcase.html,
+`std.experimental.allocator.building_blocks.showcase`).)
+
+$(BOOKTABLE,
+$(TR $(TH Allocator$(BR)) $(TH Description))
+
+$(TR $(TDC2 NullAllocator, null_allocator) $(TD Very good at doing absolutely nothing. A good
+starting point for defining other allocators or for studying the API.))
+
+$(TR $(TDC3 GCAllocator, gc_allocator) $(TD The system-provided garbage-collector allocator.
+This should be the default fallback allocator tapping into system memory. It
+offers manual $(D free) and dutifully collects litter.))
+
+$(TR $(TDC3 Mallocator, mallocator) $(TD The C heap _allocator, a.k.a. $(D
+malloc)/$(D realloc)/$(D free). Use sparingly and only for code that is unlikely
+to leak.))
+
+$(TR $(TDC3 AlignedMallocator, mallocator) $(TD Interface to OS-specific _allocators that
+support specifying alignment:
+$(WEB man7.org/linux/man-pages/man3/posix_memalign.3.html, $(D posix_memalign))
+on Posix and $(WEB msdn.microsoft.com/en-us/library/fs9stz4e(v=vs.80).aspx,
+$(D __aligned_xxx)) on Windows.))
+
+$(TR $(TDC2 AffixAllocator, affix_allocator) $(TD Allocator that allows and manages allocating
+extra prefix and/or a suffix bytes for each block allocated.))
+
+$(TR $(TDC2 BitmappedBlock, bitmapped_block) $(TD Organizes one contiguous chunk of memory in
+equal-size blocks and tracks allocation status at the cost of one bit per
+block.))
+
+$(TR $(TDC2 FallbackAllocator, fallback_allocator) $(TD Allocator that combines two other allocators
+ - primary and fallback. Allocation requests are first tried with primary, and
+ upon failure are passed to the fallback. Useful for small and fast allocators
+ fronting general-purpose ones.))
+
+$(TR $(TDC2 FreeList, free_list) $(TD Allocator that implements a $(WEB
+wikipedia.org/wiki/Free_list, free list) on top of any other allocator. The
+preferred size, tolerance, and maximum elements are configurable at compile- and
+run time.))
+
+$(TR $(TDC2 SharedFreeList, free_list) $(TD Same features as $(D FreeList), but packaged as
+a $(D shared) structure that is accessible to several threads.))
+
+$(TR $(TDC2 FreeTree, free_tree) $(TD Allocator similar to $(D FreeList) that uses a
+binary search tree to adaptively store not one, but many free lists.))
+
+$(TR $(TDC2 Region, region) $(TD Region allocator organizes a chunk of memory as a
+simple bump-the-pointer allocator.))
+
+$(TR $(TDC2 InSituRegion, region) $(TD Region holding its own allocation, most often on
+the stack. Has statically-determined size.))
+
+$(TR $(TDC2 SbrkRegion, region) $(TD Region using $(D $(LUCKY sbrk)) for allocating
+memory.))
+
+$(TR $(TDC3 MmapAllocator, mmap_allocator) $(TD Allocator using $(D $(LUCKY mmap)) directly.))
+
+$(TR $(TDC2 StatsCollector, stats_collector) $(TD Collect statistics about any other
+allocator.))
+
+$(TR $(TDC2 Quantizer, quantizer) $(TD Allocates in coarse-grained quantas, thus
+improving performance of reallocations by often reallocating in place. The drawback is higher memory consumption because of allocated and unused memory.))
+
+$(TR $(TDC2 AllocatorList, allocator_list) $(TD Given an allocator factory, lazily creates as
+many allocators as needed to satisfy allocation requests. The allocators are
+stored in a linked list. Requests for allocation are satisfied by searching the
+list in a linear manner.))
+
+$(TR $(TDC2 Segregator, segregator) $(TD Segregates allocation requests by size
+and dispatches them to distinct allocators.))
+
+$(TR $(TDC2 Bucketizer, bucketizer) $(TD Divides allocation sizes in discrete buckets and
+uses an array of allocators, one per bucket, to satisfy requests.))
+
+$(COMMENT $(TR $(TDC2 InternalPointersTree) $(TD Adds support for resolving internal
+pointers on top of another allocator.)))
+)
+
+Macros:
+MYREF = $(LINK2 std_experimental_allocator_building_blocks_$2.html, $1)&nbsp;
+MYREF2 = $(LINK2 std_experimental_allocator_building_blocks_$2.html#$1, $1)&nbsp;
+MYREF3 = $(LINK2 std_experimental_allocator_$2.html#$1, $1)&nbsp;
+TDC = $(TDNW $(D $1)$+)
+TDC2 = $(TDNW $(D $(MYREF2 $1,$+))$(BR)$(SMALL
+$(D std.experimental.allocator.building_blocks.$2)))
+TDC3 = $(TDNW $(D $(MYREF3 $1,$+))$(BR)$(SMALL
+$(D std.experimental.allocator.$2)))
+RES = $(I result)
+POST = $(BR)$(SMALL $(I Post:) $(BLUE $(D $0)))
+*/
+
+module std.experimental.allocator.building_blocks;
+
+public import
+    std.experimental.allocator.building_blocks.affix_allocator,
+    std.experimental.allocator.building_blocks.allocator_list,
+    std.experimental.allocator.building_blocks.bucketizer,
+    std.experimental.allocator.building_blocks.fallback_allocator,
+    std.experimental.allocator.building_blocks.free_list,
+    std.experimental.allocator.building_blocks.free_tree,
+    std.experimental.allocator.gc_allocator,
+    std.experimental.allocator.building_blocks.bitmapped_block,
+    std.experimental.allocator.building_blocks.kernighan_ritchie,
+    std.experimental.allocator.mallocator,
+    std.experimental.allocator.mmap_allocator,
+    std.experimental.allocator.building_blocks.null_allocator,
+    std.experimental.allocator.building_blocks.quantizer,
+    std.experimental.allocator.building_blocks.region,
+    std.experimental.allocator.building_blocks.segregator,
+    std.experimental.allocator.building_blocks.stats_collector;
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d
new file mode 100644
index 00000000000..bfcf977f8bf
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d
@@ -0,0 +1,235 @@
+module std.experimental.allocator.building_blocks.quantizer;
+
+import std.experimental.allocator.common;
+
+/**
+This allocator sits on top of $(D ParentAllocator) and quantizes allocation
+sizes, usually from arbitrary positive numbers to a small set of round numbers
+(e.g. powers of two, page sizes etc). This technique is commonly used to:
+
+$(UL
+$(LI Preallocate more memory than requested such that later on, when
+reallocation is needed (e.g. to grow an array), expansion can be done quickly
+in place. Reallocation to smaller sizes is also fast (in-place) when the new
+size requested is within the same quantum as the existing size. Code that's
+reallocation-heavy can therefore benefit from fronting a generic allocator
+with a $(D Quantizer). These advantages are present even if
+$(D ParentAllocator) does not support reallocation at all.)
+$(LI Improve behavior of allocators sensitive to allocation sizes, such as $(D
+FreeList) and $(D FreeTree). Rounding allocation requests up makes for smaller
+free lists/trees at the cost of slack memory (internal fragmentation).)
+)
+
+The following methods are forwarded to the parent allocator if present:
+$(D allocateAll), $(D owns), $(D deallocateAll), $(D empty).
+
+Preconditions: $(D roundingFunction) must satisfy three constraints. These are
+not enforced (save for the use of $(D assert)) for the sake of efficiency.
+$(OL
+$(LI $(D roundingFunction(n) >= n) for all $(D n) of type $(D size_t);)
+$(LI $(D roundingFunction) must be monotonically increasing, i.e. $(D
+roundingFunction(n1) <= roundingFunction(n2)) for all $(D n1 < n2);)
+$(LI $(D roundingFunction) must be $(D pure), i.e. always return the same
+value for a given $(D n).)
+)
+*/
+struct Quantizer(ParentAllocator, alias roundingFunction)
+{
+    import std.traits : hasMember;
+
+    /**
+    The parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator)
+    {
+        ParentAllocator parent;
+    }
+    else
+    {
+        alias parent = ParentAllocator.instance;
+        static __gshared Quantizer instance;
+    }
+
+    /**
+    Returns $(D roundingFunction(n)).
+    */
+    size_t goodAllocSize(size_t n)
+    {
+        auto result = roundingFunction(n);
+        assert(result >= n);
+        return result;
+    }
+
+    /**
+    Alignment is identical to that of the parent.
+    */
+    enum alignment = ParentAllocator.alignment;
+
+    /**
+    Gets a larger buffer $(D buf) by calling
+    $(D parent.allocate(goodAllocSize(n))). If $(D buf) is $(D null), returns
+    $(D null). Otherwise, returns $(D buf[0 .. n]).
+    */
+    void[] allocate(size_t n)
+    {
+        auto result = parent.allocate(goodAllocSize(n));
+        return result.ptr ? result.ptr[0 .. n] : null;
+    }
+
+    /**
+    Defined only if $(D parent.alignedAllocate) exists and works similarly to
+    $(D allocate) by forwarding to
+    $(D parent.alignedAllocate(goodAllocSize(n), a)).
+    */
+    static if (hasMember!(ParentAllocator, "alignedAllocate"))
+    void[] alignedAllocate(size_t n, uint)
+    {
+        auto result = parent.alignedAllocate(goodAllocSize(n));
+        return result.ptr ? result.ptr[0 .. n] : null;
+    }
+
+    /**
+    First checks whether there's enough slack memory preallocated for $(D b)
+    by evaluating $(D b.length + delta <= goodAllocSize(b.length)). If that's
+    the case, expands $(D b) in place. Otherwise, attempts to use
+    $(D parent.expand) appropriately if present.
+    */
+    bool expand(ref void[] b, size_t delta)
+    {
+        if (!b.ptr)
+        {
+            b = allocate(delta);
+            return b.length == delta;
+        }
+        immutable allocated = goodAllocSize(b.length),
+            needed = b.length + delta,
+            neededAllocation = goodAllocSize(needed);
+        assert(b.length <= allocated);
+        assert(needed <= neededAllocation);
+        assert(allocated <= neededAllocation);
+        // Second test needed because expand must work for null pointers, too.
+        if (allocated == neededAllocation)
+        {
+            // Nice!
+            b = b.ptr[0 .. needed];
+            return true;
+        }
+        // Hail Mary
+        static if (hasMember!(ParentAllocator, "expand"))
+        {
+            // Expand to the appropriate quantum
+            auto original = b.ptr[0 .. allocated];
+            assert(goodAllocSize(needed) >= allocated);
+            if (!parent.expand(original, neededAllocation - allocated))
+                return false;
+            // Dial back the size
+            b = original.ptr[0 .. needed];
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+    Expands or shrinks allocated block to an allocated size of $(D
+    goodAllocSize(s)). Expansion occurs in place under the conditions required
+    by $(D expand). Shrinking occurs in place if $(D goodAllocSize(b.length)
+    == goodAllocSize(s)).
+    */
+    bool reallocate(ref void[] b, size_t s)
+    {
+        if (!b.ptr)
+        {
+            b = allocate(s);
+            return b.length == s;
+        }
+        if (s >= b.length && expand(b, s - b.length)) return true;
+        immutable toAllocate = goodAllocSize(s),
+            allocated = goodAllocSize(b.length);
+        // Are the lengths within the same quantum?
+        if (allocated == toAllocate)
+        {
+            // Reallocation (whether up or down) will be done in place
+            b = b.ptr[0 .. s];
+            return true;
+        }
+        // Defer to parent (or global) with quantized size
+        auto original = b.ptr[0 .. allocated];
+        if (!parent.reallocate(original, toAllocate)) return false;
+        b = original.ptr[0 .. s];
+        return true;
+    }
+
+    /**
+    Defined only if $(D ParentAllocator.alignedAllocate) exists. Expansion
+    occurs in place under the conditions required by $(D expand). Shrinking
+    occurs in place if $(D goodAllocSize(b.length) == goodAllocSize(s)).
+    */
+    static if (hasMember!(ParentAllocator, "alignedAllocate"))
+    bool alignedReallocate(ref void[] b, size_t s, uint a)
+    {
+        if (!b.ptr)
+        {
+            b = alignedAllocate(s);
+            return b.length == s;
+        }
+        if (s >= b.length && expand(b, s - b.length)) return true;
+        immutable toAllocate = goodAllocSize(s),
+            allocated = goodAllocSize(b.length);
+        // Are the lengths within the same quantum?
+        if (allocated == toAllocate)
+        {
+            assert(b.ptr); // code above must have caught this
+            // Reallocation (whether up or down) will be done in place
+            b = b.ptr[0 .. s];
+            return true;
+        }
+        // Defer to parent (or global) with quantized size
+        auto original = b.ptr[0 .. allocated];
+        if (!parent.alignedReallocate(original, toAllocate, a)) return false;
+        b = original.ptr[0 .. s];
+        return true;
+    }
+
+    /**
+    Defined if $(D ParentAllocator.deallocate) exists and forwards to
+    $(D parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)])).
+    */
+    static if (hasMember!(ParentAllocator, "deallocate"))
+    bool deallocate(void[] b)
+    {
+        if (!b.ptr) return true;
+        return parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);
+    }
+
+    // Forwarding methods
+    mixin(forwardToMember("parent",
+        "allocateAll", "owns", "deallocateAll", "empty"));
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.building_blocks.free_tree : FreeTree;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    // Quantize small allocations to a multiple of cache line, large ones to a
+    // multiple of page size
+    alias MyAlloc = Quantizer!(
+        FreeTree!GCAllocator,
+        n => n.roundUpToMultipleOf(n <= 16_384 ? 64 : 4096));
+    MyAlloc alloc;
+    const buf = alloc.allocate(256);
+    assert(buf.ptr);
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    alias MyAlloc = Quantizer!(GCAllocator,
+        (size_t n) => n.roundUpToMultipleOf(64));
+    testAllocator!(() => MyAlloc());
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/region.d b/libphobos/src/std/experimental/allocator/building_blocks/region.d
new file mode 100644
index 00000000000..57af3744ede
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/region.d
@@ -0,0 +1,804 @@
+module std.experimental.allocator.building_blocks.region;
+
+import std.experimental.allocator.common;
+import std.experimental.allocator.building_blocks.null_allocator;
+import std.typecons : Flag, Yes, No;
+
+/**
+A $(D Region) allocator allocates memory straight from one contiguous chunk.
+There is no deallocation, and once the region is full, allocation requests
+return $(D null). Therefore, $(D Region)s are often used (a) in conjunction with
+more sophisticated allocators; or (b) for batch-style very fast allocations
+that deallocate everything at once.
+
+The region only stores three pointers, corresponding to the current position in
+the store and the limits. One allocation entails rounding up the allocation
+size for alignment purposes, bumping the current pointer, and comparing it
+against the limit.
+
+If $(D ParentAllocator) is different from $(D NullAllocator), $(D Region)
+deallocates the chunk of memory during destruction.
+
+The $(D minAlign) parameter establishes alignment. If $(D minAlign > 1), the
+sizes of all allocation requests are rounded up to a multiple of $(D minAlign).
+Applications aiming at maximum speed may want to choose $(D minAlign = 1) and
+control alignment externally.
+
+*/
+struct Region(ParentAllocator = NullAllocator,
+    uint minAlign = platformAlignment,
+    Flag!"growDownwards" growDownwards = No.growDownwards)
+{
+    static assert(minAlign.isGoodStaticAlignment);
+    static assert(ParentAllocator.alignment >= minAlign);
+
+    import std.traits : hasMember;
+
+    // state {
+    /**
+    The _parent allocator. Depending on whether $(D ParentAllocator) holds state
+    or not, this is a member variable or an alias for
+    `ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator)
+    {
+        ParentAllocator parent;
+    }
+    else
+    {
+        alias parent = ParentAllocator.instance;
+    }
+    private void* _current, _begin, _end;
+    // }
+
+    /**
+    Constructs a region backed by a user-provided store. Assumes $(D store) is
+    aligned at $(D minAlign). Also assumes the memory was allocated with $(D
+    ParentAllocator) (if different from $(D NullAllocator)).
+
+    Params:
+    store = User-provided store backing up the region. $(D store) must be
+    aligned at $(D minAlign) (enforced with $(D assert)). If $(D
+    ParentAllocator) is different from $(D NullAllocator), memory is assumed to
+    have been allocated with $(D ParentAllocator).
+    n = Bytes to allocate using $(D ParentAllocator). This constructor is only
+    defined If $(D ParentAllocator) is different from $(D NullAllocator). If
+    $(D parent.allocate(n)) returns $(D null), the region will be initialized
+    as empty (correctly initialized but unable to allocate).
+    */
+    this(void[] store)
+    {
+        store = store.roundUpToAlignment(alignment);
+        store = store[0 .. $.roundDownToAlignment(alignment)];
+        assert(store.ptr.alignedAt(minAlign));
+        assert(store.length % minAlign == 0);
+        _begin = store.ptr;
+        _end = store.ptr + store.length;
+        static if (growDownwards)
+            _current = _end;
+        else
+            _current = store.ptr;
+    }
+
+    /// Ditto
+    static if (!is(ParentAllocator == NullAllocator))
+    this(size_t n)
+    {
+        this(parent.allocate(n.roundUpToAlignment(alignment)));
+    }
+
+    /*
+    TODO: The postblit of $(D BasicRegion) is disabled because such objects
+    should not be copied around naively.
+    */
+    //@disable this(this);
+
+    /**
+    If `ParentAllocator` is not `NullAllocator` and defines `deallocate`, the region defines a destructor that uses `ParentAllocator.delete` to free the
+    memory chunk.
+    */
+    static if (!is(ParentAllocator == NullAllocator)
+        && hasMember!(ParentAllocator, "deallocate"))
+    ~this()
+    {
+        parent.deallocate(_begin[0 .. _end - _begin]);
+    }
+
+
+    /**
+    Alignment offered.
+    */
+    alias alignment = minAlign;
+
+    /**
+    Allocates $(D n) bytes of memory. The shortest path involves an alignment
+    adjustment (if $(D alignment > 1)), an increment, and a comparison.
+
+    Params:
+    n = number of bytes to allocate
+
+    Returns:
+    A properly-aligned buffer of size $(D n) or $(D null) if request could not
+    be satisfied.
+    */
+    void[] allocate(size_t n)
+    {
+        static if (growDownwards)
+        {
+            if (available < n) return null;
+            static if (minAlign > 1)
+                const rounded = n.roundUpToAlignment(alignment);
+            else
+                alias rounded = n;
+            assert(available >= rounded);
+            auto result = (_current - rounded)[0 .. n];
+            assert(result.ptr >= _begin);
+            _current = result.ptr;
+            assert(owns(result) == Ternary.yes);
+            return result;
+        }
+        else
+        {
+            auto result = _current[0 .. n];
+            static if (minAlign > 1)
+                const rounded = n.roundUpToAlignment(alignment);
+            else
+                alias rounded = n;
+            _current += rounded;
+            if (_current <= _end) return result;
+            // Slow path, backtrack
+            _current -= rounded;
+            return null;
+        }
+    }
+
+    /**
+    Allocates $(D n) bytes of memory aligned at alignment $(D a).
+
+    Params:
+    n = number of bytes to allocate
+    a = alignment for the allocated block
+
+    Returns:
+    Either a suitable block of $(D n) bytes aligned at $(D a), or $(D null).
+    */
+    void[] alignedAllocate(size_t n, uint a)
+    {
+        assert(a.isPowerOf2);
+        static if (growDownwards)
+        {
+            const available = _current - _begin;
+            if (available < n) return null;
+            auto result = (_current - n).alignDownTo(a)[0 .. n];
+            if (result.ptr >= _begin)
+            {
+                _current = result.ptr;
+                return result;
+            }
+        }
+        else
+        {
+            // Just bump the pointer to the next good allocation
+            auto save = _current;
+            _current = _current.alignUpTo(a);
+            auto result = allocate(n);
+            if (result.ptr)
+            {
+                assert(result.length == n);
+                return result;
+            }
+            // Failed, rollback
+            _current = save;
+        }
+        return null;
+    }
+
+    /// Allocates and returns all memory available to this region.
+    void[] allocateAll()
+    {
+        static if (growDownwards)
+        {
+            auto result = _begin[0 .. available];
+            _current = _begin;
+        }
+        else
+        {
+            auto result = _current[0 .. available];
+            _current = _end;
+        }
+        return result;
+    }
+
+    /**
+    Expands an allocated block in place. Expansion will succeed only if the
+    block is the last allocated. Defined only if `growDownwards` is
+    `No.growDownwards`.
+    */
+    static if (growDownwards == No.growDownwards)
+    bool expand(ref void[] b, size_t delta)
+    {
+        assert(owns(b) == Ternary.yes || b.ptr is null);
+        assert(b.ptr + b.length <= _current || b.ptr is null);
+        if (!b.ptr)
+        {
+            b = allocate(delta);
+            return b.length == delta;
+        }
+        auto newLength = b.length + delta;
+        if (_current < b.ptr + b.length + alignment)
+        {
+            // This was the last allocation! Allocate some more and we're done.
+            if (this.goodAllocSize(b.length) == this.goodAllocSize(newLength)
+                || allocate(delta).length == delta)
+            {
+                b = b.ptr[0 .. newLength];
+                assert(_current < b.ptr + b.length + alignment);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+    Deallocates $(D b). This works only if $(D b) was obtained as the last call
+    to $(D allocate); otherwise (i.e. another allocation has occurred since) it
+    does nothing. This semantics is tricky and therefore $(D deallocate) is
+    defined only if $(D Region) is instantiated with $(D Yes.defineDeallocate)
+    as the third template argument.
+
+    Params:
+    b = Block previously obtained by a call to $(D allocate) against this
+    allocator ($(D null) is allowed).
+    */
+    bool deallocate(void[] b)
+    {
+        assert(owns(b) == Ternary.yes || b.ptr is null);
+        static if (growDownwards)
+        {
+            if (b.ptr == _current)
+            {
+                _current += this.goodAllocSize(b.length);
+                return true;
+            }
+        }
+        else
+        {
+            if (b.ptr + this.goodAllocSize(b.length) == _current)
+            {
+                assert(b.ptr !is null || _current is null);
+                _current = b.ptr;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+    Deallocates all memory allocated by this region, which can be subsequently
+    reused for new allocations.
+    */
+    bool deallocateAll()
+    {
+        static if (growDownwards)
+        {
+            _current = _end;
+        }
+        else
+        {
+            _current = _begin;
+        }
+        return true;
+    }
+
+    /**
+    Queries whether $(D b) has been allocated with this region.
+
+    Params:
+    b = Arbitrary block of memory ($(D null) is allowed; $(D owns(null))
+    returns $(D false)).
+
+    Returns:
+    $(D true) if $(D b) has been allocated with this region, $(D false)
+    otherwise.
+    */
+    Ternary owns(void[] b) const
+    {
+        return Ternary(b.ptr >= _begin && b.ptr + b.length <= _end);
+    }
+
+    /**
+    Returns `Ternary.yes` if no memory has been allocated in this region,
+    `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
+    */
+    Ternary empty() const
+    {
+        return Ternary(_current == _begin);
+    }
+
+    /// Nonstandard property that returns bytes available for allocation.
+    size_t available() const
+    {
+        static if (growDownwards)
+        {
+            return _current - _begin;
+        }
+        else
+        {
+            return _end - _current;
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    import std.algorithm : max;
+    // Create a scalable list of regions. Each gets at least 1MB at a time by
+    // using malloc.
+    auto batchAllocator = AllocatorList!(
+        (size_t n) => Region!Mallocator(max(n, 1024 * 1024))
+    )();
+    auto b = batchAllocator.allocate(101);
+    assert(b.length == 101);
+    // This will cause a second allocation
+    b = batchAllocator.allocate(2 * 1024 * 1024);
+    assert(b.length == 2 * 1024 * 1024);
+    // Destructor will free the memory
+}
+
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    // Create a 64 KB region allocated with malloc
+    auto reg = Region!(Mallocator, Mallocator.alignment,
+        Yes.growDownwards)(1024 * 64);
+    const b = reg.allocate(101);
+    assert(b.length == 101);
+    // Destructor will free the memory
+}
+
+/**
+
+$(D InSituRegion) is a convenient region that carries its storage within itself
+(in the form of a statically-sized array).
+
+The first template argument is the size of the region and the second is the
+needed alignment. Depending on the alignment requested and platform details,
+the actual available storage may be smaller than the compile-time parameter. To
+make sure that at least $(D n) bytes are available in the region, use
+$(D InSituRegion!(n + a - 1, a)).
+
+Given that the most frequent use of `InSituRegion` is as a stack allocator, it
+allocates starting at the end on systems where stack grows downwards, such that
+hot memory is used first.
+
+*/
+struct InSituRegion(size_t size, size_t minAlign = platformAlignment)
+{
+    import std.algorithm : max;
+    import std.conv : to;
+    import std.traits : hasMember;
+
+    static assert(minAlign.isGoodStaticAlignment);
+    static assert(size >= minAlign);
+
+    version (X86) enum growDownwards = Yes.growDownwards;
+    else version (X86_64) enum growDownwards = Yes.growDownwards;
+    else version (ARM) enum growDownwards = Yes.growDownwards;
+    else version (AArch64) enum growDownwards = Yes.growDownwards;
+    else version (PPC) enum growDownwards = Yes.growDownwards;
+    else version (PPC64) enum growDownwards = Yes.growDownwards;
+    else version (MIPS) enum growDownwards = Yes.growDownwards;
+    else version (MIPS64) enum growDownwards = Yes.growDownwards;
+    else version (SPARC) enum growDownwards = Yes.growDownwards;
+    else version (SystemZ) enum growDownwards = Yes.growDownwards;
+    else static assert(0, "Dunno how the stack grows on this architecture.");
+
+    @disable this(this);
+
+    // state {
+    private Region!(NullAllocator, minAlign, growDownwards) _impl;
+    union
+    {
+        private ubyte[size] _store = void;
+        private double _forAlignmentOnly1 = void;
+    }
+    // }
+
+    /**
+    An alias for $(D minAlign), which must be a valid alignment (nonzero power
+    of 2). The start of the region and all allocation requests will be rounded
+    up to a multiple of the alignment.
+
+    ----
+    InSituRegion!(4096) a1;
+    assert(a1.alignment == platformAlignment);
+    InSituRegion!(4096, 64) a2;
+    assert(a2.alignment == 64);
+    ----
+    */
+    alias alignment = minAlign;
+
+    private void lazyInit()
+    {
+        assert(!_impl._current);
+        //static if (alignment > double.alignof)
+        //{
+        //    auto p = _store.ptr.alignUpTo(alignment);
+        //}
+        //else
+        //    auto p = _store.ptr;
+        _impl = typeof(_impl)(_store);
+        assert(_impl._current.alignedAt(alignment));
+    }
+
+    /**
+    Allocates $(D bytes) and returns them, or $(D null) if the region cannot
+    accommodate the request. For efficiency reasons, if $(D bytes == 0) the
+    function returns an empty non-null slice.
+    */
+    void[] allocate(size_t n)
+    {
+        // Fast path
+    entry:
+        auto result = _impl.allocate(n);
+        if (result.length == n) return result;
+        // Slow path
+        if (_impl._current) return null; // no more room
+        lazyInit;
+        assert(_impl._current);
+        goto entry;
+    }
+
+    /**
+    As above, but the memory allocated is aligned at $(D a) bytes.
+    */
+    void[] alignedAllocate(size_t n, uint a)
+    {
+        // Fast path
+    entry:
+        auto result = _impl.alignedAllocate(n, a);
+        if (result.length == n) return result;
+        // Slow path
+        if (_impl._current) return null; // no more room
+        lazyInit;
+        assert(_impl._current);
+        goto entry;
+    }
+
+    /**
+    Deallocates $(D b). This works only if $(D b) was obtained as the last call
+    to $(D allocate); otherwise (i.e. another allocation has occurred since) it
+    does nothing. This semantics is tricky and therefore $(D deallocate) is
+    defined only if $(D Region) is instantiated with $(D Yes.defineDeallocate)
+    as the third template argument.
+
+    Params:
+    b = Block previously obtained by a call to $(D allocate) against this
+    allocator ($(D null) is allowed).
+    */
+    bool deallocate(void[] b)
+    {
+        if (!_impl._current) return b is null;
+        return _impl.deallocate(b);
+    }
+
+    /**
+    Returns `Ternary.yes` if `b` is the result of a previous allocation,
+    `Ternary.no` otherwise.
+    */
+    Ternary owns(void[] b)
+    {
+        if (!_impl._current) return Ternary.no;
+        return _impl.owns(b);
+    }
+
+    /**
+    Expands an allocated block in place. Expansion will succeed only if the
+    block is the last allocated.
+    */
+    static if (hasMember!(typeof(_impl), "expand"))
+    bool expand(ref void[] b, size_t delta)
+    {
+        if (!_impl._current) lazyInit;
+        return _impl.expand(b, delta);
+    }
+
+    /**
+    Deallocates all memory allocated with this allocator.
+    */
+    bool deallocateAll()
+    {
+        // We don't care to lazily init the region
+        return _impl.deallocateAll;
+    }
+
+    /**
+    Allocates all memory available with this allocator.
+    */
+    void[] allocateAll()
+    {
+        if (!_impl._current) lazyInit;
+        return _impl.allocateAll;
+    }
+
+    /**
+    Nonstandard function that returns the bytes available for allocation.
+    */
+    size_t available()
+    {
+        if (!_impl._current) lazyInit;
+        return _impl.available;
+    }
+}
+
+///
+unittest
+{
+    // 128KB region, allocated to x86's cache line
+    InSituRegion!(128 * 1024, 16) r1;
+    auto a1 = r1.allocate(101);
+    assert(a1.length == 101);
+
+    // 128KB region, with fallback to the garbage collector.
+    import std.experimental.allocator.building_blocks.fallback_allocator
+        : FallbackAllocator;
+    import std.experimental.allocator.building_blocks.free_list
+        : FreeList;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.bitmapped_block
+        : BitmappedBlock;
+    FallbackAllocator!(InSituRegion!(128 * 1024), GCAllocator) r2;
+    const a2 = r2.allocate(102);
+    assert(a2.length == 102);
+
+    // Reap with GC fallback.
+    InSituRegion!(128 * 1024, 8) tmp3;
+    FallbackAllocator!(BitmappedBlock!(64, 8), GCAllocator) r3;
+    r3.primary = BitmappedBlock!(64, 8)(tmp3.allocateAll());
+    const a3 = r3.allocate(103);
+    assert(a3.length == 103);
+
+    // Reap/GC with a freelist for small objects up to 16 bytes.
+    InSituRegion!(128 * 1024, 64) tmp4;
+    FreeList!(FallbackAllocator!(BitmappedBlock!(64, 64), GCAllocator), 0, 16) r4;
+    r4.parent.primary = BitmappedBlock!(64, 64)(tmp4.allocateAll());
+    const a4 = r4.allocate(104);
+    assert(a4.length == 104);
+}
+
+unittest
+{
+    InSituRegion!(4096, 1) r1;
+    auto a = r1.allocate(2001);
+    assert(a.length == 2001);
+    import std.conv : text;
+    assert(r1.available == 2095, text(r1.available));
+
+    InSituRegion!(65_536, 1024*4) r2;
+    assert(r2.available <= 65_536);
+    a = r2.allocate(2001);
+    assert(a.length == 2001);
+}
+
+private extern(C) void* sbrk(long);
+private extern(C) int brk(shared void*);
+
+/**
+
+Allocator backed by $(D $(LUCKY sbrk)) for Posix systems. Due to the fact that
+$(D sbrk) is not thread-safe $(WEB lifecs.likai.org/2010/02/sbrk-is-not-thread-
+safe.html, by design), $(D SbrkRegion) uses a mutex internally. This implies
+that uncontrolled calls to $(D brk) and $(D sbrk) may affect the workings of $(D
+SbrkRegion) adversely.
+
+*/
+version(Posix) struct SbrkRegion(uint minAlign = platformAlignment)
+{
+    private import core.sys.posix.pthread : pthread_mutex_init, pthread_mutex_destroy,
+        pthread_mutex_t, pthread_mutex_lock, pthread_mutex_unlock,
+        PTHREAD_MUTEX_INITIALIZER;
+    private static shared pthread_mutex_t sbrkMutex = PTHREAD_MUTEX_INITIALIZER;
+
+    // workaround for https://issues.dlang.org/show_bug.cgi?id=14617
+    version(OSX)
+    {
+        shared static this()
+        {
+            pthread_mutex_init(cast(pthread_mutex_t*) &sbrkMutex, null) == 0
+                || assert(0);
+        }
+        shared static ~this()
+        {
+            pthread_mutex_destroy(cast(pthread_mutex_t*) &sbrkMutex) == 0
+                || assert(0);
+        }
+    }
+
+    static assert(minAlign.isGoodStaticAlignment);
+    static assert(size_t.sizeof == (void*).sizeof);
+    private shared void* _brkInitial, _brkCurrent;
+
+    /**
+    Instance shared by all callers.
+    */
+    static shared SbrkRegion instance;
+
+    /**
+    Standard allocator primitives.
+    */
+    enum uint alignment = minAlign;
+
+    /// Ditto
+    void[] allocate(size_t bytes) shared
+    {
+        static if (minAlign > 1)
+            const rounded = bytes.roundUpToMultipleOf(alignment);
+        else
+            alias rounded = bytes;
+        pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0);
+        scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0
+            || assert(0);
+        // Assume sbrk returns the old break. Most online documentation confirms
+        // that, except for http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf,
+        // which claims the returned value is not portable.
+        auto p = sbrk(rounded);
+        if (p == cast(void*) -1)
+        {
+            return null;
+        }
+        if (!_brkInitial)
+        {
+            _brkInitial = cast(shared) p;
+            assert(cast(size_t) _brkInitial % minAlign == 0,
+                "Too large alignment chosen for " ~ typeof(this).stringof);
+        }
+        _brkCurrent = cast(shared) (p + rounded);
+        return p[0 .. bytes];
+    }
+
+    /// Ditto
+    void[] alignedAllocate(size_t bytes, uint a) shared
+    {
+        pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0);
+        scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0
+            || assert(0);
+        if (!_brkInitial)
+        {
+            // This is one extra call, but it'll happen only once.
+            _brkInitial = cast(shared) sbrk(0);
+            assert(cast(size_t) _brkInitial % minAlign == 0,
+                "Too large alignment chosen for " ~ typeof(this).stringof);
+            (_brkInitial != cast(void*) -1) || assert(0);
+            _brkCurrent = _brkInitial;
+        }
+        immutable size_t delta = cast(shared void*) roundUpToMultipleOf(
+            cast(size_t) _brkCurrent, a) - _brkCurrent;
+        // Still must make sure the total size is aligned to the allocator's
+        // alignment.
+        immutable rounded = (bytes + delta).roundUpToMultipleOf(alignment);
+
+        auto p = sbrk(rounded);
+        if (p == cast(void*) -1)
+        {
+            return null;
+        }
+        _brkCurrent = cast(shared) (p + rounded);
+        return p[delta .. delta + bytes];
+    }
+
+    /**
+
+    The $(D expand) method may only succeed if the argument is the last block
+    allocated. In that case, $(D expand) attempts to push the break pointer to
+    the right.
+
+    */
+    bool expand(ref void[] b, size_t delta) shared
+    {
+        if (b is null) return (b = allocate(delta)) !is null;
+        assert(_brkInitial && _brkCurrent); // otherwise where did b come from?
+        pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0);
+        scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0
+            || assert(0);
+        if (_brkCurrent != b.ptr + b.length) return false;
+        // Great, can expand the last block
+        static if (minAlign > 1)
+            const rounded = delta.roundUpToMultipleOf(alignment);
+        else
+            alias rounded = bytes;
+        auto p = sbrk(rounded);
+        if (p == cast(void*) -1)
+        {
+            return false;
+        }
+        _brkCurrent = cast(shared) (p + rounded);
+        b = b.ptr[0 .. b.length + delta];
+        return true;
+    }
+
+    /// Ditto
+    Ternary owns(void[] b) shared
+    {
+        // No need to lock here.
+        assert(!_brkCurrent || b.ptr + b.length <= _brkCurrent);
+        return Ternary(_brkInitial && b.ptr >= _brkInitial);
+    }
+
+    /**
+
+    The $(D deallocate) method only works (and returns $(D true))  on systems
+    that support reducing the  break address (i.e. accept calls to $(D sbrk)
+    with negative offsets). OSX does not accept such. In addition the argument
+    must be the last block allocated.
+
+    */
+    bool deallocate(void[] b) shared
+    {
+        static if (minAlign > 1)
+            const rounded = b.length.roundUpToMultipleOf(alignment);
+        else
+            const rounded = b.length;
+        pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0);
+        scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0
+            || assert(0);
+        if (_brkCurrent != b.ptr + rounded) return false;
+        assert(b.ptr >= _brkInitial);
+        if (sbrk(-rounded) == cast(void*) -1)
+            return false;
+        _brkCurrent = cast(shared) b.ptr;
+        return true;
+    }
+
+    /**
+    The $(D deallocateAll) method only works (and returns $(D true)) on systems
+    that support reducing the  break address (i.e. accept calls to $(D sbrk)
+    with negative offsets). OSX does not accept such.
+    */
+    bool deallocateAll() shared
+    {
+        pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0);
+        scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0
+            || assert(0);
+        return !_brkInitial || brk(_brkInitial) == 0;
+    }
+
+    /// Standard allocator API.
+    Ternary empty()
+    {
+        // Also works when they're both null.
+        return Ternary(_brkCurrent == _brkInitial);
+    }
+}
+
+version(Posix) unittest
+{
+    // Let's test the assumption that sbrk(n) returns the old address
+    const p1 = sbrk(0);
+    const p2 = sbrk(4096);
+    assert(p1 == p2);
+    const p3 = sbrk(0);
+    assert(p3 == p2 + 4096);
+    // Try to reset brk, but don't make a fuss if it doesn't work
+    sbrk(-4096);
+}
+
+version(Posix) unittest
+{
+    alias alloc = SbrkRegion!(8).instance;
+    auto a = alloc.alignedAllocate(2001, 4096);
+    assert(a.length == 2001);
+    auto b = alloc.allocate(2001);
+    assert(b.length == 2001);
+    assert(alloc.owns(a) == Ternary.yes);
+    assert(alloc.owns(b) == Ternary.yes);
+    // reducing the brk does not work on OSX
+    version(OSX) {} else
+    {
+        assert(alloc.deallocate(b));
+        assert(alloc.deallocateAll);
+    }
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d
new file mode 100644
index 00000000000..6f9d6871a7f
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d
@@ -0,0 +1,204 @@
+module std.experimental.allocator.building_blocks.scoped_allocator;
+
+import std.experimental.allocator.common;
+
+/**
+
+$(D ScopedAllocator) delegates all allocation requests to $(D ParentAllocator).
+When destroyed, the $(D ScopedAllocator) object automatically calls $(D
+deallocate) for all memory allocated through its lifetime. (The $(D
+deallocateAll) function is also implemented with the same semantics.)
+
+$(D deallocate) is also supported, which is where most implementation effort
+and overhead of $(D ScopedAllocator) go. If $(D deallocate) is not needed, a
+simpler design combining $(D AllocatorList) with $(D Region) is recommended.
+
+*/
+struct ScopedAllocator(ParentAllocator)
+{
+    unittest
+    {
+        testAllocator!(() => ScopedAllocator());
+    }
+
+    private import std.experimental.allocator.building_blocks.affix_allocator
+        : AffixAllocator;
+    private import std.traits : hasMember;
+
+    private struct Node
+    {
+        Node* prev;
+        Node* next;
+        size_t length;
+    }
+
+    alias Allocator = AffixAllocator!(ParentAllocator, Node);
+
+    // state {
+    /**
+    If $(D ParentAllocator) is stateful, $(D parent) is a property giving access
+    to an $(D AffixAllocator!ParentAllocator). Otherwise, $(D parent) is an alias for `AffixAllocator!ParentAllocator.instance`.
+    */
+    static if (stateSize!ParentAllocator)
+    {
+        Allocator parent;
+    }
+    else
+    {
+        alias parent = Allocator.instance;
+    }
+    private Node* root;
+    // }
+
+    /**
+    $(D ScopedAllocator) is not copyable.
+    */
+    @disable this(this);
+
+    /**
+    $(D ScopedAllocator)'s destructor releases all memory allocated during its
+    lifetime.
+    */
+    ~this()
+    {
+        deallocateAll;
+    }
+
+    /// Alignment offered
+    enum alignment = Allocator.alignment;
+
+    /**
+    Forwards to $(D parent.goodAllocSize) (which accounts for the management
+    overhead).
+    */
+    size_t goodAllocSize(size_t n)
+    {
+        return parent.goodAllocSize(n);
+    }
+
+    /**
+    Allocates memory. For management it actually allocates extra memory from
+    the parent.
+    */
+    void[] allocate(size_t n)
+    {
+        auto b = parent.allocate(n);
+        if (!b.ptr) return b;
+        Node* toInsert = & parent.prefix(b);
+        toInsert.prev = null;
+        toInsert.next = root;
+        toInsert.length = n;
+        root = toInsert;
+        return b;
+    }
+
+    /**
+    Forwards to $(D parent.expand(b, delta)).
+    */
+    static if (hasMember!(Allocator, "expand"))
+    bool expand(ref void[] b, size_t delta)
+    {
+        auto result = parent.expand(b, delta);
+        if (result && b.ptr)
+        {
+            parent.prefix(b).length = b.length;
+        }
+        return result;
+    }
+
+    /**
+    Reallocates $(D b) to new size $(D s).
+    */
+    bool reallocate(ref void[] b, size_t s)
+    {
+        // Remove from list
+        if (b.ptr)
+        {
+            Node* n = & parent.prefix(b);
+            if (n.prev) n.prev.next = n.next;
+            else root = n.next;
+            if (n.next) n.next.prev = n.prev;
+        }
+        auto result = parent.reallocate(b, s);
+        // Add back to list
+        if (b.ptr)
+        {
+            Node* n = & parent.prefix(b);
+            n.prev = null;
+            n.next = root;
+            n.length = s;
+            root = n;
+        }
+        return result;
+    }
+
+    /**
+    Forwards to $(D parent.owns(b)).
+    */
+    static if (hasMember!(Allocator, "owns"))
+    Ternary owns(void[] b)
+    {
+        return parent.owns(b);
+    }
+
+    /**
+    Deallocates $(D b).
+    */
+    static if (hasMember!(Allocator, "deallocate"))
+    bool deallocate(void[] b)
+    {
+        // Remove from list
+        if (b.ptr)
+        {
+            Node* n = & parent.prefix(b);
+            if (n.prev) n.prev.next = n.next;
+            else root = n.next;
+            if (n.next) n.next.prev = n.prev;
+        }
+        return parent.deallocate(b);
+    }
+
+    /**
+    Deallocates all memory allocated.
+    */
+    bool deallocateAll()
+    {
+        bool result = true;
+        for (auto n = root; n; )
+        {
+            void* p = n + 1;
+            auto length = n.length;
+            n = n.next;
+            if (!parent.deallocate(p[0 .. length]))
+                result = false;
+        }
+        root = null;
+        return result;
+    }
+
+    /**
+    Returns `Ternary.yes` if this allocator is not responsible for any memory,
+    `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
+    */
+    Ternary empty() const
+    {
+        return Ternary(root is null);
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    ScopedAllocator!Mallocator alloc;
+    assert(alloc.empty == Ternary.yes);
+    const b = alloc.allocate(10);
+    assert(b.length == 10);
+    assert(alloc.empty == Ternary.no);
+}
+
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    testAllocator!(() => ScopedAllocator!GCAllocator());
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/segregator.d b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d
new file mode 100644
index 00000000000..f4c276af1e4
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d
@@ -0,0 +1,364 @@
+module std.experimental.allocator.building_blocks.segregator;
+
+import std.experimental.allocator.common;
+
+/**
+Dispatches allocations (and deallocations) between two allocators ($(D
+SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as
+follows. All allocations smaller than or equal to $(D threshold) will be
+dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator).
+
+If both allocators are $(D shared), the $(D Segregator) will also offer $(D
+shared) methods.
+*/
+struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
+{
+    import std.algorithm : min;
+    import std.traits : hasMember;
+
+    static if (stateSize!SmallAllocator) private SmallAllocator _small;
+    else private alias _small = SmallAllocator.instance;
+    static if (stateSize!LargeAllocator) private LargeAllocator _large;
+    else private alias _large = LargeAllocator.instance;
+
+    version (StdDdoc)
+    {
+        /**
+        The alignment offered is the minimum of the two allocators' alignment.
+        */
+        enum uint alignment;
+        /**
+        This method is defined only if at least one of the allocators defines
+        it. The good allocation size is obtained from $(D SmallAllocator) if $(D
+        s <= threshold), or $(D LargeAllocator) otherwise. (If one of the
+        allocators does not define $(D goodAllocSize), the default
+        implementation in this module applies.)
+        */
+        static size_t goodAllocSize(size_t s);
+        /**
+        The memory is obtained from $(D SmallAllocator) if $(D s <= threshold),
+        or $(D LargeAllocator) otherwise.
+        */
+        void[] allocate(size_t);
+        /**
+        This method is defined if both allocators define it, and forwards to
+        $(D SmallAllocator) or $(D LargeAllocator) appropriately.
+        */
+        void[] alignedAllocate(size_t, uint);
+        /**
+        This method is defined only if at least one of the allocators defines
+        it. If $(D SmallAllocator) defines $(D expand) and $(D b.length +
+        delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(
+        LargeAllocator) defines $(D expand) and $(D b.length > threshold), the
+        call is forwarded to $(D LargeAllocator). Otherwise, the call returns
+        $(D false).
+        */
+        bool expand(ref void[] b, size_t delta);
+        /**
+        This method is defined only if at least one of the allocators defines
+        it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <=
+        threshold && s <= threshold), the call is forwarded to $(D
+        SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D
+        b.length > threshold && s > threshold), the call is forwarded to $(D
+        LargeAllocator). Otherwise, the call returns $(D false).
+        */
+        bool reallocate(ref void[] b, size_t s);
+        /**
+        This method is defined only if at least one of the allocators defines
+        it, and work similarly to $(D reallocate).
+        */
+        bool alignedReallocate(ref void[] b, size_t s);
+        /**
+        This method is defined only if both allocators define it. The call is
+        forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D
+        LargeAllocator) otherwise.
+        */
+        Ternary owns(void[] b);
+        /**
+        This function is defined only if both allocators define it, and forwards
+        appropriately depending on $(D b.length).
+        */
+        bool deallocate(void[] b);
+        /**
+        This function is defined only if both allocators define it, and calls
+        $(D deallocateAll) for them in turn.
+        */
+        bool deallocateAll();
+        /**
+        This function is defined only if both allocators define it, and returns
+        the conjunction of $(D empty) calls for the two.
+        */
+        Ternary empty();
+    }
+
+    /**
+    Composite allocators involving nested instantiations of $(D Segregator) make
+    it difficult to access individual sub-allocators stored within. $(D
+    allocatorForSize) simplifies the task by supplying the allocator nested
+    inside a $(D Segregator) that is responsible for a specific size $(D s).
+
+    Example:
+    ----
+    alias A = Segregator!(300,
+        Segregator!(200, A1, A2),
+        A3);
+    A a;
+    static assert(typeof(a.allocatorForSize!10) == A1);
+    static assert(typeof(a.allocatorForSize!250) == A2);
+    static assert(typeof(a.allocatorForSize!301) == A3);
+    ----
+    */
+    ref auto allocatorForSize(size_t s)()
+    {
+        static if (s <= threshold)
+            static if (is(SmallAllocator == Segregator!(Args), Args...))
+                return _small.allocatorForSize!s;
+            else return _small;
+        else
+            static if (is(LargeAllocator == Segregator!(Args), Args...))
+                return _large.allocatorForSize!s;
+            else return _large;
+    }
+
+    enum uint alignment = min(SmallAllocator.alignment,
+        LargeAllocator.alignment);
+
+    private template Impl()
+    {
+        size_t goodAllocSize(size_t s)
+        {
+            return s <= threshold
+                ? _small.goodAllocSize(s)
+                : _large.goodAllocSize(s);
+        }
+
+        void[] allocate(size_t s)
+        {
+            return s <= threshold ? _small.allocate(s) : _large.allocate(s);
+        }
+
+        static if (hasMember!(SmallAllocator, "alignedAllocate")
+                && hasMember!(LargeAllocator, "alignedAllocate"))
+        void[] alignedAllocate(size_t s, uint a)
+        {
+            return s <= threshold
+                ? _small.alignedAllocate(s, a)
+                : _large.alignedAllocate(s, a);
+        }
+
+        static if (hasMember!(SmallAllocator, "expand")
+                || hasMember!(LargeAllocator, "expand"))
+        bool expand(ref void[] b, size_t delta)
+        {
+            if (b.length + delta <= threshold)
+            {
+                // Old and new allocations handled by _small
+                static if (hasMember!(SmallAllocator, "expand"))
+                    return _small.expand(b, delta);
+                else
+                    return false;
+            }
+            if (b.length > threshold)
+            {
+                // Old and new allocations handled by _large
+                static if (hasMember!(LargeAllocator, "expand"))
+                    return _large.expand(b, delta);
+                else
+                    return false;
+            }
+            // Oops, cross-allocator transgression
+            return false;
+        }
+
+        static if (hasMember!(SmallAllocator, "reallocate")
+                || hasMember!(LargeAllocator, "reallocate"))
+        bool reallocate(ref void[] b, size_t s)
+        {
+            static if (hasMember!(SmallAllocator, "reallocate"))
+                if (b.length <= threshold && s <= threshold)
+                {
+                    // Old and new allocations handled by _small
+                    return _small.reallocate(b, s);
+                }
+            static if (hasMember!(LargeAllocator, "reallocate"))
+                if (b.length > threshold && s > threshold)
+                {
+                    // Old and new allocations handled by _large
+                    return _large.reallocate(b, s);
+                }
+            // Cross-allocator transgression
+            return .reallocate(this, b, s);
+        }
+
+        static if (hasMember!(SmallAllocator, "alignedReallocate")
+                || hasMember!(LargeAllocator, "alignedReallocate"))
+        bool reallocate(ref void[] b, size_t s)
+        {
+            static if (hasMember!(SmallAllocator, "alignedReallocate"))
+                if (b.length <= threshold && s <= threshold)
+                {
+                    // Old and new allocations handled by _small
+                    return _small.alignedReallocate(b, s);
+                }
+            static if (hasMember!(LargeAllocator, "alignedReallocate"))
+                if (b.length > threshold && s > threshold)
+                {
+                    // Old and new allocations handled by _large
+                    return _large.alignedReallocate(b, s);
+                }
+            // Cross-allocator transgression
+            return .alignedReallocate(this, b, s);
+        }
+
+        static if (hasMember!(SmallAllocator, "owns")
+                && hasMember!(LargeAllocator, "owns"))
+        Ternary owns(void[] b)
+        {
+            return Ternary(b.length <= threshold
+                ? _small.owns(b) : _large.owns(b));
+        }
+
+        static if (hasMember!(SmallAllocator, "deallocate")
+                && hasMember!(LargeAllocator, "deallocate"))
+        bool deallocate(void[] data)
+        {
+            return data.length <= threshold
+                ? _small.deallocate(data)
+                : _large.deallocate(data);
+        }
+
+        static if (hasMember!(SmallAllocator, "deallocateAll")
+                && hasMember!(LargeAllocator, "deallocateAll"))
+        bool deallocateAll()
+        {
+            // Use & insted of && to evaluate both
+            return _small.deallocateAll() & _large.deallocateAll();
+        }
+
+        static if (hasMember!(SmallAllocator, "empty")
+                && hasMember!(LargeAllocator, "empty"))
+        Ternary empty()
+        {
+            return _small.empty && _large.empty;
+        }
+
+        static if (hasMember!(SmallAllocator, "resolveInternalPointer")
+                && hasMember!(LargeAllocator, "resolveInternalPointer"))
+        void[] resolveInternalPointer(void* p)
+        {
+            if (auto r = _small.resolveInternalPointer(p)) return r;
+            return _large.resolveInternalPointer(p);
+        }
+    }
+
+    private enum sharedMethods =
+        !stateSize!SmallAllocator
+        && !stateSize!LargeAllocator
+        && is(typeof(SmallAllocator.instance) == shared)
+        && is(typeof(LargeAllocator.instance) == shared);
+    //pragma(msg, sharedMethods);
+
+    static if (sharedMethods)
+    {
+        static shared Segregator instance;
+        shared { mixin Impl!(); }
+    }
+    else
+    {
+        static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
+            static __gshared Segregator instance;
+        mixin Impl!();
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    alias A =
+        Segregator!(
+            1024 * 4,
+            Segregator!(
+                128, FreeList!(Mallocator, 0, 128),
+                GCAllocator),
+            Segregator!(
+                1024 * 1024, Mallocator,
+                GCAllocator)
+            );
+    A a;
+    auto b = a.allocate(200);
+    assert(b.length == 200);
+    a.deallocate(b);
+}
+
+/**
+A $(D Segregator) with more than three arguments expands to a composition of
+elemental $(D Segregator)s, as illustrated by the following example:
+
+----
+alias A =
+    Segregator!(
+        n1, A1,
+        n2, A2,
+        n3, A3,
+        A4
+    );
+----
+
+With this definition, allocation requests for $(D n1) bytes or less are directed
+to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are
+directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive)
+are directed to $(D A3); and requests for more than $(D n3) bytes are directed
+to $(D A4). If some particular range should not be handled, $(D NullAllocator)
+may be used appropriately.
+
+*/
+template Segregator(Args...) if (Args.length > 3)
+{
+    // Binary search
+    private enum cutPoint = ((Args.length - 2) / 4) * 2;
+    static if (cutPoint >= 2)
+    {
+        alias Segregator = .Segregator!(
+            Args[cutPoint],
+            .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]),
+            .Segregator!(Args[cutPoint + 2 .. $])
+        );
+    }
+    else
+    {
+        // Favor small sizes
+        alias Segregator = .Segregator!(
+            Args[0],
+            Args[1],
+            .Segregator!(Args[2 .. $])
+        );
+    }
+
+    // Linear search
+    //alias Segregator = .Segregator!(
+    //    Args[0], Args[1],
+    //    .Segregator!(Args[2 .. $])
+    //);
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    alias A =
+        Segregator!(
+            128, FreeList!(Mallocator, 0, 128),
+            1024 * 4, GCAllocator,
+            1024 * 1024, Mallocator,
+            GCAllocator
+        );
+    A a;
+    auto b = a.allocate(201);
+    assert(b.length == 201);
+    a.deallocate(b);
+}
diff --git a/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d b/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d
new file mode 100644
index 00000000000..fda45aa0cda
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d
@@ -0,0 +1,738 @@
+// Written in the D programming language.
+/**
+Allocator that collects useful statistics about allocations, both global and per
+calling point. The statistics collected can be configured statically by choosing
+combinations of `Options` appropriately.
+
+Example:
+----
+import std.experimental.allocator.gc_allocator : GCAllocator;
+import std.experimental.allocator.building_blocks.free_list : FreeList;
+alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed);
+----
+*/
+module std.experimental.allocator.building_blocks.stats_collector;
+
+import std.experimental.allocator.common;
+
+/**
+_Options for $(D StatsCollector) defined below. Each enables during
+compilation one specific counter, statistic, or other piece of information.
+*/
+enum Options : ulong
+{
+    /**
+    Counts the number of calls to $(D owns).
+    */
+    numOwns = 1u << 0,
+    /**
+    Counts the number of calls to $(D allocate). All calls are counted,
+    including requests for zero bytes or failed requests.
+    */
+    numAllocate = 1u << 1,
+    /**
+    Counts the number of calls to $(D allocate) that succeeded, i.e. they
+    returned a block as large as requested. (N.B. requests for zero bytes count
+    as successful.)
+    */
+    numAllocateOK = 1u << 2,
+    /**
+    Counts the number of calls to $(D expand), regardless of arguments or
+    result.
+    */
+    numExpand = 1u << 3,
+    /**
+    Counts the number of calls to $(D expand) that resulted in a successful
+    expansion.
+    */
+    numExpandOK = 1u << 4,
+    /**
+    Counts the number of calls to $(D reallocate), regardless of arguments or
+    result.
+    */
+    numReallocate = 1u << 5,
+    /**
+    Counts the number of calls to $(D reallocate) that succeeded.
+    (Reallocations to zero bytes count as successful.)
+    */
+    numReallocateOK = 1u << 6,
+    /**
+    Counts the number of calls to $(D reallocate) that resulted in an in-place
+    reallocation (no memory moved). If this number is close to the total number
+    of reallocations, that indicates the allocator finds room at the current
+    block's end in a large fraction of the cases, but also that internal
+    fragmentation may be high (the size of the unit of allocation is large
+    compared to the typical allocation size of the application).
+    */
+    numReallocateInPlace = 1u << 7,
+    /**
+    Counts the number of calls to $(D deallocate).
+    */
+    numDeallocate = 1u << 8,
+    /**
+    Counts the number of calls to $(D deallocateAll).
+    */
+    numDeallocateAll = 1u << 9,
+    /**
+    Chooses all $(D numXxx) flags.
+    */
+    numAll = (1u << 10) - 1,
+    /**
+    Tracks bytes currently allocated by this allocator. This number goes up
+    and down as memory is allocated and deallocated, and is zero if the
+    allocator currently has no active allocation.
+    */
+    bytesUsed = 1u << 10,
+    /**
+    Tracks total cumulative bytes allocated by means of $(D allocate),
+    $(D expand), and $(D reallocate) (when resulting in an expansion). This
+    number always grows and indicates allocation traffic. To compute bytes
+    deallocated cumulatively, subtract $(D bytesUsed) from $(D bytesAllocated).
+    */
+    bytesAllocated = 1u << 11,
+    /**
+    Tracks the sum of all $(D delta) values in calls of the form
+    $(D expand(b, delta)) that succeed (return $(D true)).
+    */
+    bytesExpanded = 1u << 12,
+    /**
+    Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of
+    the form $(D realloc(b, s)) that succeed (return $(D true)). In per-call
+    statistics, also unambiguously counts the bytes deallocated with
+    $(D deallocate).
+    */
+    bytesContracted = 1u << 13,
+    /**
+    Tracks the sum of all bytes moved as a result of calls to $(D realloc) that
+    were unable to reallocate in place. A large number (relative to $(D
+    bytesAllocated)) indicates that the application should use larger
+    preallocations.
+    */
+    bytesMoved = 1u << 14,
+    /**
+    Tracks the sum of all bytes NOT moved as result of calls to $(D realloc)
+    that managed to reallocate in place. A large number (relative to $(D
+    bytesAllocated)) indicates that the application is expansion-intensive and
+    is saving a good amount of moves. However, if this number is relatively
+    small and $(D bytesSlack) is high, it means the application is
+    overallocating for little benefit.
+    */
+    bytesNotMoved = 1u << 15,
+    /**
+    Measures the sum of extra bytes allocated beyond the bytes requested, i.e.
+    the $(WEB goo.gl/YoKffF, internal fragmentation). This is the current
+    effective number of slack bytes, and it goes up and down with time.
+    */
+    bytesSlack = 1u << 16,
+    /**
+    Measures the maximum bytes allocated over the time. This is useful for
+    dimensioning allocators.
+    */
+    bytesHighTide = 1u << 17,
+    /**
+    Chooses all $(D byteXxx) flags.
+    */
+    bytesAll = ((1u << 18) - 1) & ~numAll,
+    /**
+    Combines all flags above.
+    */
+    all = (1u << 18) - 1
+}
+
+/**
+
+Allocator that collects extra data about allocations. Since each piece of
+information adds size and time overhead, statistics can be individually enabled
+or disabled through compile-time $(D flags).
+
+All stats of the form $(D numXxx) record counts of events occurring, such as
+calls to functions and specific results. The stats of the form $(D bytesXxx)
+collect cumulative sizes.
+
+In addition, the data $(D callerSize), $(D callerModule), $(D callerFile), $(D
+callerLine), and $(D callerTime) is associated with each specific allocation.
+This data prefixes each allocation.
+
+*/
+struct StatsCollector(Allocator, ulong flags = Options.all,
+    ulong perCallFlags = 0)
+{
+private:
+    import std.traits : hasMember, Signed;
+
+    static string define(string type, string[] names...)
+    {
+        string result;
+        foreach (v; names)
+            result ~= "static if (flags & Options."~v~") {"
+                ~ "private "~type~" _"~v~";"
+                ~ "public const("~type~") "~v~"() const { return _"~v~"; }"
+                ~ "}";
+        return result;
+    }
+
+    void add(string counter)(Signed!size_t n)
+    {
+        mixin("static if (flags & Options." ~ counter
+            ~ ") _" ~ counter ~ " += n;");
+        static if (counter == "bytesUsed" && (flags & Options.bytesHighTide))
+        {
+            if (bytesHighTide < bytesUsed ) _bytesHighTide = bytesUsed;
+        }
+    }
+
+    void up(string counter)() { add!counter(1); }
+    void down(string counter)() { add!counter(-1); }
+
+    version (StdDdoc)
+    {
+        /**
+        Read-only properties enabled by the homonym $(D flags) chosen by the
+        user.
+
+        Example:
+        ----
+        StatsCollector!(Mallocator,
+            Options.bytesUsed | Options.bytesAllocated) a;
+        auto d1 = a.allocate(10);
+        auto d2 = a.allocate(11);
+        a.deallocate(d1);
+        assert(a.bytesAllocated == 21);
+        assert(a.bytesUsed == 11);
+        a.deallocate(d2);
+        assert(a.bytesAllocated == 21);
+        assert(a.bytesUsed == 0);
+        ----
+        */
+        @property ulong numOwns() const;
+        /// Ditto
+        @property ulong numAllocate() const;
+        /// Ditto
+        @property ulong numAllocateOK() const;
+        /// Ditto
+        @property ulong numExpand() const;
+        /// Ditto
+        @property ulong numExpandOK() const;
+        /// Ditto
+        @property ulong numReallocate() const;
+        /// Ditto
+        @property ulong numReallocateOK() const;
+        /// Ditto
+        @property ulong numReallocateInPlace() const;
+        /// Ditto
+        @property ulong numDeallocate() const;
+        /// Ditto
+        @property ulong numDeallocateAll() const;
+        /// Ditto
+        @property ulong bytesUsed() const;
+        /// Ditto
+        @property ulong bytesAllocated() const;
+        /// Ditto
+        @property ulong bytesExpanded() const;
+        /// Ditto
+        @property ulong bytesContracted() const;
+        /// Ditto
+        @property ulong bytesMoved() const;
+        /// Ditto
+        @property ulong bytesNotMoved() const;
+        /// Ditto
+        @property ulong bytesSlack() const;
+        /// Ditto
+        @property ulong bytesHighTide() const;
+    }
+
+public:
+    /**
+    The parent allocator is publicly accessible either as a direct member if it
+    holds state, or as an alias to `Allocator.instance` otherwise. One may use
+    it for making calls that won't count toward statistics collection.
+    */
+    static if (stateSize!Allocator) Allocator parent;
+    else alias parent = Allocator.instance;
+
+private:
+    // Per-allocator state
+    mixin(define("ulong",
+        "numOwns",
+        "numAllocate",
+        "numAllocateOK",
+        "numExpand",
+        "numExpandOK",
+        "numReallocate",
+        "numReallocateOK",
+        "numReallocateInPlace",
+        "numDeallocate",
+        "numDeallocateAll",
+        "bytesUsed",
+        "bytesAllocated",
+        "bytesExpanded",
+        "bytesContracted",
+        "bytesMoved",
+        "bytesNotMoved",
+        "bytesSlack",
+        "bytesHighTide",
+    ));
+
+public:
+
+    /// Alignment offered is equal to $(D Allocator.alignment).
+    alias alignment = Allocator.alignment;
+
+    /**
+    Increments $(D numOwns) (per instance and and per call) and forwards to $(D
+    parent.owns(b)).
+    */
+    static if (hasMember!(Allocator, "owns"))
+    {
+        static if ((perCallFlags & Options.numOwns) == 0)
+        Ternary owns(void[] b)
+        { return ownsImpl(b); }
+        else
+        Ternary owns(string f = __FILE, uint n = line)(void[] b)
+        { return ownsImpl!(f, n)(b); }
+    }
+
+    private Ternary ownsImpl(string f = null, uint n = 0)(void[] b)
+    {
+        up!"numOwns";
+        addPerCall!(f, n, "numOwns")(1);
+        return parent.owns(b);
+    }
+
+    /**
+    Forwards to $(D parent.allocate). Affects per instance: $(D numAllocate),
+    $(D bytesUsed), $(D bytesAllocated), $(D bytesSlack), $(D numAllocateOK),
+    and $(D bytesHighTide). Affects per call: $(D numAllocate), $(D
+    numAllocateOK), and $(D bytesAllocated).
+    */
+    static if (!(perCallFlags
+        & (Options.numAllocate | Options.numAllocateOK
+            | Options.bytesAllocated)))
+    {
+        void[] allocate(size_t n)
+        { return allocateImpl(n); }
+    }
+    else
+    {
+        void[] allocate(string f = __FILE__, ulong n = __LINE__)
+            (size_t bytes)
+        { return allocateImpl!(f, n)(bytes); }
+    }
+
+    private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes)
+    {
+        auto result = parent.allocate(bytes);
+        add!"bytesUsed"(result.length);
+        add!"bytesAllocated"(result.length);
+        immutable slack = this.goodAllocSize(result.length) - result.length;
+        add!"bytesSlack"(slack);
+        up!"numAllocate";
+        add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK
+        addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated")
+            (1, result.length == bytes, result.length);
+        return result;
+    }
+
+    /**
+    Defined whether or not $(D Allocator.expand) is defined. Affects
+    per instance: $(D numExpand), $(D numExpandOK), $(D bytesExpanded),
+    $(D bytesSlack), $(D bytesAllocated), and $(D bytesUsed). Affects per call:
+    $(D numExpand), $(D numExpandOK), $(D bytesExpanded), and
+    $(D bytesAllocated).
+    */
+    static if (!(perCallFlags
+        & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded)))
+    {
+        bool expand(ref void[] b, size_t delta)
+        { return expandImpl(b, delta); }
+    }
+    else
+    {
+        bool expand(string f = __FILE__, uint n = __LINE__)
+            (ref void[] b, size_t delta)
+        { return expandImpl!(f, n)(b, delta); }
+    }
+
+    private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s)
+    {
+        up!"numExpand";
+        Signed!size_t slack = 0;
+        static if (!hasMember!(Allocator, "expand"))
+        {
+            auto result = s == 0;
+        }
+        else
+        {
+            immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
+            auto result = parent.expand(b, s);
+            if (result)
+            {
+                up!"numExpandOK";
+                add!"bytesUsed"(s);
+                add!"bytesAllocated"(s);
+                add!"bytesExpanded"(s);
+                slack = Signed!size_t(this.goodAllocSize(b.length) - b.length
+                    - bytesSlackB4);
+                add!"bytesSlack"(slack);
+            }
+        }
+        immutable xtra = result ? s : 0;
+        addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded",
+            "bytesAllocated")
+            (1, result, xtra, xtra);
+        return result;
+    }
+
+    /**
+    Defined whether or not $(D Allocator.reallocate) is defined. Affects
+    per instance: $(D numReallocate), $(D numReallocateOK), $(D
+    numReallocateInPlace), $(D bytesNotMoved), $(D bytesAllocated), $(D
+    bytesSlack), $(D bytesExpanded), and $(D bytesContracted). Affects per call:
+    $(D numReallocate), $(D numReallocateOK), $(D numReallocateInPlace),
+    $(D bytesNotMoved), $(D bytesExpanded), $(D bytesContracted), and
+    $(D bytesMoved).
+    */
+    static if (!(perCallFlags
+        & (Options.numReallocate | Options.numReallocateOK
+            | Options.numReallocateInPlace | Options.bytesNotMoved
+            | Options.bytesExpanded | Options.bytesContracted
+            | Options.bytesMoved)))
+    {
+        bool reallocate(ref void[] b, size_t s)
+        { return reallocateImpl(b, s); }
+    }
+    else
+    {
+        bool reallocate(string f = __FILE__, ulong n = __LINE__)
+            (ref void[] b, size_t s)
+        { return reallocateImpl!(f, n)(b, s); }
+    }
+
+    private bool reallocateImpl(string f = null, uint n = 0)
+        (ref void[] b, size_t s)
+    {
+        up!"numReallocate";
+        const bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
+        const oldB = b.ptr;
+        const oldLength = b.length;
+
+        const result = parent.reallocate(b, s);
+
+        Signed!size_t slack = 0;
+        bool wasInPlace = false;
+        Signed!size_t delta = 0;
+
+        if (result)
+        {
+            up!"numReallocateOK";
+            slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4;
+            add!"bytesSlack"(slack);
+            add!"bytesUsed"(Signed!size_t(b.length - oldLength));
+            if (oldB == b.ptr)
+            {
+                // This was an in-place reallocation, yay
+                wasInPlace = true;
+                up!"numReallocateInPlace";
+                add!"bytesNotMoved"(oldLength);
+                delta = b.length - oldLength;
+                if (delta >= 0)
+                {
+                    // Expansion
+                    add!"bytesAllocated"(delta);
+                    add!"bytesExpanded"(delta);
+                }
+                else
+                {
+                    // Contraction
+                    add!"bytesContracted"(-delta);
+                }
+            }
+            else
+            {
+                // This was a allocate-move-deallocate cycle
+                add!"bytesAllocated"(b.length);
+                add!"bytesMoved"(oldLength);
+            }
+        }
+        addPerCall!(f, n, "numReallocate", "numReallocateOK",
+            "numReallocateInPlace", "bytesNotMoved",
+            "bytesExpanded", "bytesContracted", "bytesMoved")
+            (1, result, wasInPlace, wasInPlace ? oldLength : 0,
+                delta >= 0 ? delta : 0, delta < 0 ? -delta : 0,
+                wasInPlace ? 0 : oldLength);
+        return result;
+    }
+
+    /**
+    Defined whether or not $(D Allocator.deallocate) is defined. Affects
+    per instance: $(D numDeallocate), $(D bytesUsed), and $(D bytesSlack).
+    Affects per call: $(D numDeallocate) and $(D bytesContracted).
+    */
+    static if (!(perCallFlags &
+            (Options.numDeallocate | Options.bytesContracted)))
+        bool deallocate(void[] b)
+        { return deallocateImpl(b); }
+    else
+        bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b)
+        { return deallocateImpl!(f, n)(b); }
+
+    private bool deallocateImpl(string f = null, uint n = 0)(void[] b)
+    {
+        up!"numDeallocate";
+        add!"bytesUsed"(-Signed!size_t(b.length));
+        add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length));
+        addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length);
+        static if (hasMember!(Allocator, "deallocate"))
+            return parent.deallocate(b);
+        else
+            return false;
+    }
+
+    static if (hasMember!(Allocator, "deallocateAll"))
+    {
+        /**
+        Defined only if $(D Allocator.deallocateAll) is defined. Affects
+        per instance and per call $(D numDeallocateAll).
+        */
+        static if (!(perCallFlags & Options.numDeallocateAll))
+            bool deallocateAll()
+            { return deallocateAllImpl(); }
+        else
+            bool deallocateAll(string f = __FILE__, uint n = __LINE__)()
+            { return deallocateAllImpl!(f, n)(); }
+
+        private bool deallocateAllImpl(string f = null, uint n = 0)()
+        {
+            up!"numDeallocateAll";
+            addPerCall!(f, n, "numDeallocateAll")(1);
+            static if ((flags & Options.bytesUsed))
+                _bytesUsed = 0;
+            return parent.deallocateAll();
+        }
+    }
+
+    /**
+    Defined only if $(D Options.bytesUsed) is defined. Returns $(D bytesUsed ==
+    0).
+    */
+    static if (flags & Options.bytesUsed)
+    Ternary empty()
+    {
+        return Ternary(_bytesUsed == 0);
+    }
+
+    /**
+    Reports per instance statistics to $(D output) (e.g. $(D stdout)). The
+    format is simple: one kind and value per line, separated by a colon, e.g.
+    $(D bytesAllocated:7395404)
+    */
+    void reportStatistics(R)(auto ref R output)
+    {
+        import std.traits : EnumMembers;
+        import std.conv : to;
+        foreach (e; EnumMembers!Options)
+        {
+            static if ((flags & e) && e != Options.numAll
+                    && e != Options.bytesAll && e != Options.all)
+                output.write(e.to!string, ":", mixin(e.to!string), '\n');
+        }
+    }
+
+    static if (perCallFlags)
+    {
+        /**
+        Defined if $(D perCallFlags) is nonzero.
+        */
+        struct PerCallStatistics
+        {
+            /// The file and line of the call.
+            string file;
+            /// Ditto
+            uint line;
+            /// The options corresponding to the statistics collected.
+            Options[] opts;
+            /// The values of the statistics. Has the same length as $(D opts).
+            ulong[] values;
+            // Next in the chain.
+            private PerCallStatistics* next;
+
+            /**
+            Format to a string such as:
+            $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]).
+            */
+            string toString() const
+            {
+                import std.conv : text, to;
+                auto result = text(file, "(", line, "): [");
+                foreach (i, opt; opts)
+                {
+                    if (i) result ~= ", ";
+                    result ~= opt.to!string;
+                    result ~= ':';
+                    result ~= values[i].to!string;
+                }
+                return result ~= "]";
+            }
+        }
+        private static PerCallStatistics* root;
+
+        /**
+        Defined if $(D perCallFlags) is nonzero. Iterates all monitored
+        file/line instances. The order of iteration is not meaningful (items
+        are inserted at the front of a list upon the first call), so
+        preprocessing the statistics after collection might be appropriate.
+        */
+        static auto byFileLine()
+        {
+            static struct Voldemort
+            {
+                PerCallStatistics* current;
+                bool empty() { return !current; }
+                ref PerCallStatistics front() { return *current; }
+                void popFront() { current = current.next; }
+                auto save() { return this; }
+            }
+            return Voldemort(root);
+        }
+
+        /**
+        Defined if $(D perCallFlags) is nonzero. Outputs (e.g. to a $(D File))
+        a simple report of the collected per-call statistics.
+        */
+        static void reportPerCallStatistics(R)(auto ref R output)
+        {
+            output.write("Stats for: ", StatsCollector.stringof, '\n');
+            foreach (ref stat; byFileLine)
+            {
+                output.write(stat, '\n');
+            }
+        }
+
+        private PerCallStatistics* statsAt(string f, uint n, opts...)()
+        {
+            import std.range : repeat;
+            import std.array : array;
+
+            static PerCallStatistics s = { f, n, [ opts ],
+                repeat(0UL, opts.length).array };
+            static bool inserted;
+
+            if (!inserted)
+            {
+                // Insert as root
+                s.next = root;
+                root = &s;
+                inserted = true;
+            }
+            return &s;
+        }
+
+        private void addPerCall(string f, uint n, names...)(ulong[] values...)
+        {
+            import std.array : join;
+            enum uint mask = mixin("Options."~[names].join("|Options."));
+            static if (perCallFlags & mask)
+            {
+                // Per allocation info
+                auto ps = mixin("statsAt!(f, n,"
+                    ~ "Options."~[names].join(", Options.")
+                ~")");
+                foreach (i; 0 .. names.length)
+                {
+                    ps.values[i] += values[i];
+                }
+            }
+        }
+    }
+    else
+    {
+        private void addPerCall(string f, uint n, names...)(ulong[]...)
+        {
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all);
+
+    Allocator alloc;
+    auto b = alloc.allocate(10);
+    alloc.reallocate(b, 20);
+    alloc.deallocate(b);
+
+    import std.file : deleteme, remove;
+    import std.stdio : File;
+    import std.range : walkLength;
+
+    auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt";
+    scope(exit) remove(f);
+    Allocator.reportPerCallStatistics(File(f, "w"));
+    alloc.reportStatistics(File(f, "a"));
+    assert(File(f).byLine.walkLength == 22);
+}
+
+unittest
+{
+    void test(Allocator)()
+    {
+        import std.range : walkLength;
+        import std.stdio : writeln;
+        Allocator a;
+        auto b1 = a.allocate(100);
+        assert(a.numAllocate == 1);
+        assert(a.expand(b1, 0));
+        assert(a.reallocate(b1, b1.length + 1));
+        auto b2 = a.allocate(101);
+        assert(a.numAllocate == 2);
+        assert(a.bytesAllocated == 202);
+        assert(a.bytesUsed == 202);
+        auto b3 = a.allocate(202);
+        assert(a.numAllocate == 3);
+        assert(a.bytesAllocated == 404);
+
+        a.deallocate(b2);
+        assert(a.numDeallocate == 1);
+        a.deallocate(b1);
+        assert(a.numDeallocate == 2);
+        a.deallocate(b3);
+        assert(a.numDeallocate == 3);
+        assert(a.numAllocate == a.numDeallocate);
+        assert(a.bytesUsed == 0);
+
+        //import std.stdio;
+        //Allocator.reportPerCallStatistics(stdout);
+        //a.reportStatistics(stdout);
+     }
+
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    test!(StatsCollector!(GCAllocator, Options.all, Options.all));
+    test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all,
+        Options.all));
+}
+
+unittest
+{
+    void test(Allocator)()
+    {
+        import std.range : walkLength;
+        import std.stdio : writeln;
+        Allocator a;
+        auto b1 = a.allocate(100);
+        assert(a.expand(b1, 0));
+        assert(a.reallocate(b1, b1.length + 1));
+        auto b2 = a.allocate(101);
+        auto b3 = a.allocate(202);
+
+        a.deallocate(b2);
+        a.deallocate(b1);
+        a.deallocate(b3);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    test!(StatsCollector!(GCAllocator, 0, 0));
+}
diff --git a/libphobos/src/std/experimental/allocator/common.d b/libphobos/src/std/experimental/allocator/common.d
new file mode 100644
index 00000000000..eb2f928ffab
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/common.d
@@ -0,0 +1,721 @@
+/**
+Utility and ancillary artifacts of `std.experimental.allocator`. This module
+shouldn't be used directly; its functionality will be migrated into more
+appropriate parts of `std`.
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu), Timon Gehr (`Ternary`)
+*/
+module std.experimental.allocator.common;
+import std.algorithm, std.traits;
+
+/**
+Ternary type with three thruth values.
+*/
+struct Ternary
+{
+    @safe @nogc nothrow pure:
+
+    package ubyte value = 6;
+    package static Ternary make(ubyte b)
+    {
+        Ternary r = void;
+        r.value = b;
+        return r;
+    }
+
+    /**
+    In addition to `false` and `true`, `Ternary` offers `unknown`.
+    */
+    enum no = make(0);
+    /// ditto
+    enum yes = make(2);
+    /// ditto
+    enum unknown = make(6);
+
+    /**
+     Construct and assign from a `bool`, receiving `no` for `false` and `yes`
+     for `true`.
+    */
+    this(bool b) { value = b << 1; }
+
+    /// ditto
+    void opAssign(bool b) { value = b << 1; }
+
+    /**
+    Construct a ternary value from another ternary value
+    */
+    this(const Ternary b) { value = b.value; }
+
+    /**
+    $(TABLE Truth table for logical operations,
+      $(TR $(TH `a`) $(TH `b`) $(TH `$(TILDE)a`) $(TH `a | b`) $(TH `a & b`) $(TH `a ^ b`))
+      $(TR $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `no`))
+      $(TR $(TD `no`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `no`) $(TD `yes`))
+      $(TR $(TD `no`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `no`) $(TD `unknown`))
+      $(TR $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `yes`))
+      $(TR $(TD `yes`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `yes`) $(TD `no`))
+      $(TR $(TD `yes`) $(TD `unknown`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`))
+      $(TR $(TD `unknown`) $(TD `no`) $(TD `unknown`) $(TD `unknown`) $(TD `no`) $(TD `unknown`))
+      $(TR $(TD `unknown`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`))
+      $(TR $(TD `unknown`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `unknown`) $(TD `unknown`))
+    )
+    */
+    Ternary opUnary(string s)() if (s == "~")
+    {
+        return make(386 >> value & 6);
+    }
+
+    /// ditto
+    Ternary opBinary(string s)(Ternary rhs) if (s == "|")
+    {
+        return make(25_512 >> value + rhs.value & 6);
+    }
+
+    /// ditto
+    Ternary opBinary(string s)(Ternary rhs) if (s == "&")
+    {
+        return make(26_144 >> value + rhs.value & 6);
+    }
+
+    /// ditto
+    Ternary opBinary(string s)(Ternary rhs) if (s == "^")
+    {
+        return make(26_504 >> value + rhs.value & 6);
+    }
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    alias f = Ternary.no, t = Ternary.yes, u = Ternary.unknown;
+    Ternary[27] truthTableAnd =
+    [
+        t, t, t,
+        t, u, u,
+        t, f, f,
+        u, t, u,
+        u, u, u,
+        u, f, f,
+        f, t, f,
+        f, u, f,
+        f, f, f,
+    ];
+
+    Ternary[27] truthTableOr =
+    [
+        t, t, t,
+        t, u, t,
+        t, f, t,
+        u, t, t,
+        u, u, u,
+        u, f, u,
+        f, t, t,
+        f, u, u,
+        f, f, f,
+    ];
+
+    Ternary[27] truthTableXor =
+    [
+        t, t, f,
+        t, u, u,
+        t, f, t,
+        u, t, u,
+        u, u, u,
+        u, f, u,
+        f, t, t,
+        f, u, u,
+        f, f, f,
+    ];
+
+    for (auto i = 0; i != truthTableAnd.length; i += 3)
+    {
+        assert((truthTableAnd[i] & truthTableAnd[i + 1])
+            == truthTableAnd[i + 2]);
+        assert((truthTableOr[i] | truthTableOr[i + 1])
+            == truthTableOr[i + 2]);
+        assert((truthTableXor[i] ^ truthTableXor[i + 1])
+            == truthTableXor[i + 2]);
+    }
+
+    Ternary a;
+    assert(a == Ternary.unknown);
+    static assert(!is(typeof({ if (a) {} })));
+    assert(!is(typeof({ auto b = Ternary(3); })));
+    a = true;
+    assert(a == Ternary.yes);
+    a = false;
+    assert(a == Ternary.no);
+    a = Ternary.unknown;
+    assert(a == Ternary.unknown);
+    Ternary b;
+    b = a;
+    assert(b == a);
+    assert(~Ternary.yes == Ternary.no);
+    assert(~Ternary.no == Ternary.yes);
+    assert(~Ternary.unknown == Ternary.unknown);
+}
+
+/**
+Returns the size in bytes of the state that needs to be allocated to hold an
+object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not
+nested and have no nonstatic member variables.
+ */
+template stateSize(T)
+{
+    static if (is(T == class) || is(T == interface))
+        enum stateSize = __traits(classInstanceSize, T);
+    else static if (is(T == struct) || is(T == union))
+        enum stateSize = Fields!T.length || isNested!T ? T.sizeof : 0;
+    else static if (is(T == void))
+        enum size_t stateSize = 0;
+    else
+        enum stateSize = T.sizeof;
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    static assert(stateSize!void == 0);
+    struct A {}
+    static assert(stateSize!A == 0);
+    struct B { int x; }
+    static assert(stateSize!B == 4);
+    interface I1 {}
+    //static assert(stateSize!I1 == 2 * size_t.sizeof);
+    class C1 {}
+    static assert(stateSize!C1 == 3 * size_t.sizeof);
+    class C2 { char c; }
+    static assert(stateSize!C2 == 4 * size_t.sizeof);
+    static class C3 { char c; }
+    static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof);
+}
+
+/**
+$(D chooseAtRuntime) is a compile-time constant of type $(D size_t) that several
+parameterized structures in this module recognize to mean deferral to runtime of
+the exact value. For example, $(D BitmappedBlock!(Allocator, 4096)) (described in
+detail below) defines a block allocator with block size of 4096 bytes, whereas
+$(D BitmappedBlock!(Allocator, chooseAtRuntime)) defines a block allocator that has a
+field storing the block size, initialized by the user.
+*/
+enum chooseAtRuntime = size_t.max - 1;
+
+/**
+$(D unbounded) is a compile-time constant of type $(D size_t) that several
+parameterized structures in this module recognize to mean "infinite" bounds for
+the parameter. For example, $(D Freelist) (described in detail below) accepts a
+$(D maxNodes) parameter limiting the number of freelist items. If $(D unbounded)
+is passed for $(D maxNodes), then there is no limit and no checking for the
+number of nodes.
+*/
+enum unbounded = size_t.max;
+
+/**
+The alignment that is guaranteed to accommodate any D object allocation on the
+current platform.
+*/
+enum uint platformAlignment = std.algorithm.max(double.alignof, real.alignof);
+
+/**
+The default good size allocation is deduced as $(D n) rounded up to the
+allocator's alignment.
+*/
+size_t goodAllocSize(A)(auto ref A a, size_t n)
+{
+    return n.roundUpToMultipleOf(a.alignment);
+}
+
+/**
+Returns s rounded up to a multiple of base.
+*/
+@safe @nogc nothrow pure
+package size_t roundUpToMultipleOf(size_t s, uint base)
+{
+    assert(base);
+    auto rem = s % base;
+    return rem ? s + base - rem : s;
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(10.roundUpToMultipleOf(11) == 11);
+    assert(11.roundUpToMultipleOf(11) == 11);
+    assert(12.roundUpToMultipleOf(11) == 22);
+    assert(118.roundUpToMultipleOf(11) == 121);
+}
+
+/**
+Returns `n` rounded up to a multiple of alignment, which must be a power of 2.
+*/
+@safe @nogc nothrow pure
+package size_t roundUpToAlignment(size_t n, uint alignment)
+{
+    assert(alignment.isPowerOf2);
+    immutable uint slack = cast(uint) n & (alignment - 1);
+    const result = slack
+        ? n + alignment - slack
+        : n;
+    assert(result >= n);
+    return result;
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(10.roundUpToAlignment(4) == 12);
+    assert(11.roundUpToAlignment(2) == 12);
+    assert(12.roundUpToAlignment(8) == 16);
+    assert(118.roundUpToAlignment(64) == 128);
+}
+
+/**
+Returns `n` rounded down to a multiple of alignment, which must be a power of 2.
+*/
+@safe @nogc nothrow pure
+package size_t roundDownToAlignment(size_t n, uint alignment)
+{
+    assert(alignment.isPowerOf2);
+    return n & ~size_t(alignment - 1);
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(10.roundDownToAlignment(4) == 8);
+    assert(11.roundDownToAlignment(2) == 10);
+    assert(12.roundDownToAlignment(8) == 8);
+    assert(63.roundDownToAlignment(64) == 0);
+}
+
+/**
+Advances the beginning of `b` to start at alignment `a`. The resulting buffer
+may therefore be shorter. Returns the adjusted buffer, or null if obtaining a
+non-empty buffer is impossible.
+*/
+@nogc nothrow pure
+package void[] roundUpToAlignment(void[] b, uint a)
+{
+    auto e = b.ptr + b.length;
+    auto p = cast(void*) roundUpToAlignment(cast(size_t) b.ptr, a);
+    if (e <= p) return null;
+    return p[0 .. e - p];
+}
+
+@nogc nothrow pure
+unittest
+{
+    void[] empty;
+    assert(roundUpToAlignment(empty, 4) == null);
+    char[128] buf;
+    // At least one pointer inside buf is 128-aligned
+    assert(roundUpToAlignment(buf, 128) !is null);
+}
+
+/**
+Like `a / b` but rounds the result up, not down.
+*/
+@safe @nogc nothrow pure
+package size_t divideRoundUp(size_t a, size_t b)
+{
+    assert(b);
+    return (a + b - 1) / b;
+}
+
+/**
+Returns `s` rounded up to a multiple of `base`.
+*/
+@nogc nothrow pure
+package void[] roundStartToMultipleOf(void[] s, uint base)
+{
+    assert(base);
+    auto p = cast(void*) roundUpToMultipleOf(
+        cast(size_t) s.ptr, base);
+    auto end = s.ptr + s.length;
+    return p[0 .. end - p];
+}
+
+nothrow pure
+unittest
+{
+    void[] p;
+    assert(roundStartToMultipleOf(p, 16) is null);
+    p = new ulong[10];
+    assert(roundStartToMultipleOf(p, 16) is p);
+}
+
+/**
+Returns $(D s) rounded up to the nearest power of 2.
+*/
+@safe @nogc nothrow pure
+package size_t roundUpToPowerOf2(size_t s)
+{
+    import std.meta : AliasSeq;
+    assert(s <= (size_t.max >> 1) + 1);
+    --s;
+    static if (size_t.sizeof == 4)
+        alias Shifts = AliasSeq!(1, 2, 4, 8, 16);
+    else
+        alias Shifts = AliasSeq!(1, 2, 4, 8, 16, 32);
+    foreach (i; Shifts)
+    {
+        s |= s >> i;
+    }
+    return s + 1;
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(0.roundUpToPowerOf2 == 0);
+    assert(1.roundUpToPowerOf2 == 1);
+    assert(2.roundUpToPowerOf2 == 2);
+    assert(3.roundUpToPowerOf2 == 4);
+    assert(7.roundUpToPowerOf2 == 8);
+    assert(8.roundUpToPowerOf2 == 8);
+    assert(10.roundUpToPowerOf2 == 16);
+    assert(11.roundUpToPowerOf2 == 16);
+    assert(12.roundUpToPowerOf2 == 16);
+    assert(118.roundUpToPowerOf2 == 128);
+    assert((size_t.max >> 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1);
+    assert(((size_t.max >> 1) + 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1);
+}
+
+/**
+Returns the number of trailing zeros of $(D x).
+*/
+@safe @nogc nothrow pure
+package uint trailingZeros(ulong x)
+{
+    uint result;
+    while (result < 64 && !(x & (1UL << result)))
+    {
+        ++result;
+    }
+    return result;
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(trailingZeros(0) == 64);
+    assert(trailingZeros(1) == 0);
+    assert(trailingZeros(2) == 1);
+    assert(trailingZeros(3) == 0);
+    assert(trailingZeros(4) == 2);
+}
+
+/**
+Returns `true` if `ptr` is aligned at `alignment`.
+*/
+@nogc nothrow pure
+package bool alignedAt(void* ptr, uint alignment)
+{
+    return cast(size_t) ptr % alignment == 0;
+}
+
+/**
+Returns the effective alignment of `ptr`, i.e. the largest power of two that is
+a divisor of `ptr`.
+*/
+@nogc nothrow pure
+package uint effectiveAlignment(void* ptr)
+{
+    return 1U << trailingZeros(cast(size_t) ptr);
+}
+
+@nogc nothrow pure
+unittest
+{
+    int x;
+    assert(effectiveAlignment(&x) >= int.alignof);
+}
+
+/**
+Aligns a pointer down to a specified alignment. The resulting pointer is less
+than or equal to the given pointer.
+*/
+@nogc nothrow pure
+package void* alignDownTo(void* ptr, uint alignment)
+{
+    assert(alignment.isPowerOf2);
+    return cast(void*) (cast(size_t) ptr & ~(alignment - 1UL));
+}
+
+/**
+Aligns a pointer up to a specified alignment. The resulting pointer is greater
+than or equal to the given pointer.
+*/
+@nogc nothrow pure
+package void* alignUpTo(void* ptr, uint alignment)
+{
+    assert(alignment.isPowerOf2);
+    immutable uint slack = cast(size_t) ptr & (alignment - 1U);
+    return slack ? ptr + alignment - slack : ptr;
+}
+
+// Credit: Matthias Bentrup
+/**
+Returns `true` if `x` is a nonzero power of two.
+*/
+@safe @nogc nothrow pure
+package bool isPowerOf2(uint x)
+{
+    return (x & -x) > (x - 1);
+}
+
+@safe @nogc nothrow pure
+unittest
+{
+    assert(!isPowerOf2(0));
+    assert(isPowerOf2(1));
+    assert(isPowerOf2(2));
+    assert(!isPowerOf2(3));
+    assert(isPowerOf2(4));
+    assert(!isPowerOf2(5));
+    assert(!isPowerOf2(6));
+    assert(!isPowerOf2(7));
+    assert(isPowerOf2(8));
+    assert(!isPowerOf2(9));
+    assert(!isPowerOf2(10));
+    assert(isPowerOf2(1UL << 31));
+}
+
+@safe @nogc nothrow pure
+package bool isGoodStaticAlignment(uint x)
+{
+    return x.isPowerOf2;
+}
+
+@safe @nogc nothrow pure
+package bool isGoodDynamicAlignment(uint x)
+{
+    return x.isPowerOf2 && x >= (void*).sizeof;
+}
+
+/*
+If $(D b.length + delta <= a.goodAllocSize(b.length)), $(D expand) just adjusts
+$(D b) and returns $(D true). Otherwise, returns $(D false).
+
+$(D expand) does not attempt to use $(D Allocator.reallocate) even if
+defined. This is deliberate so allocators may use it internally within their own
+implementation of $(D expand).
+
+*/
+//bool expand(Allocator)(ref Allocator a, ref void[] b, size_t delta)
+//{
+//    if (!b.ptr)
+//    {
+//        b = a.allocate(delta);
+//        return b.length == delta;
+//    }
+//    if (delta == 0) return true;
+//    immutable length = b.length + delta;
+//    if (length <= a.goodAllocSize(b.length))
+//    {
+//        b = b.ptr[0 .. length];
+//        return true;
+//    }
+//    return false;
+//}
+
+/**
+The default $(D reallocate) function first attempts to use $(D expand). If $(D
+Allocator.expand) is not defined or returns $(D false), $(D reallocate)
+allocates a new block of memory of appropriate size and copies data from the old
+block to the new block. Finally, if $(D Allocator) defines $(D deallocate), $(D
+reallocate) uses it to free the old memory block.
+
+$(D reallocate) does not attempt to use $(D Allocator.reallocate) even if
+defined. This is deliberate so allocators may use it internally within their own
+implementation of $(D reallocate).
+
+*/
+bool reallocate(Allocator)(ref Allocator a, ref void[] b, size_t s)
+{
+    if (b.length == s) return true;
+    static if (hasMember!(Allocator, "expand"))
+    {
+        if (b.length <= s && a.expand(b, s - b.length)) return true;
+    }
+    auto newB = a.allocate(s);
+    if (newB.length != s) return false;
+    if (newB.length <= b.length) newB[] = b[0 .. newB.length];
+    else newB[0 .. b.length] = b[];
+    static if (hasMember!(Allocator, "deallocate"))
+        a.deallocate(b);
+    b = newB;
+    return true;
+}
+
+/**
+
+The default $(D alignedReallocate) function first attempts to use $(D expand).
+If $(D Allocator.expand) is not defined or returns $(D false),  $(D
+alignedReallocate) allocates a new block of memory of appropriate size and
+copies data from the old block to the new block. Finally, if $(D Allocator)
+defines $(D deallocate), $(D alignedReallocate) uses it to free the old memory
+block.
+
+$(D alignedReallocate) does not attempt to use $(D Allocator.reallocate) even if
+defined. This is deliberate so allocators may use it internally within their own
+implementation of $(D reallocate).
+
+*/
+bool alignedReallocate(Allocator)(ref Allocator alloc,
+        ref void[] b, size_t s, uint a)
+{
+    static if (hasMember!(Allocator, "expand"))
+    {
+        if (b.length <= s && b.ptr.alignedAt(a)
+            && alloc.expand(b, s - b.length)) return true;
+    }
+    else
+    {
+        if (b.length == s) return true;
+    }
+    auto newB = alloc.alignedAllocate(s, a);
+    if (newB.length <= b.length) newB[] = b[0 .. newB.length];
+    else newB[0 .. b.length] = b[];
+    static if (hasMember!(Allocator, "deallocate"))
+        alloc.deallocate(b);
+    b = newB;
+    return true;
+}
+
+/**
+Forwards each of the methods in `funs` (if defined) to `member`.
+*/
+/*package*/ string forwardToMember(string member, string[] funs...)
+{
+    string result = "    import std.traits : hasMember, Parameters;\n";
+    foreach (fun; funs)
+    {
+        result ~= "
+    static if (hasMember!(typeof("~member~"), `"~fun~"`))
+    auto ref "~fun~"(Parameters!(typeof("~member~"."~fun~")) args)
+    {
+        return "~member~"."~fun~"(args);
+    }\n";
+    }
+    return result;
+}
+
+package void testAllocator(alias make)()
+{
+    import std.conv : text;
+    import std.stdio : writeln, stderr;
+    alias A = typeof(make());
+    scope(failure) stderr.writeln("testAllocator failed for ", A.stringof);
+
+    auto a = make();
+
+    // Test alignment
+    static assert(A.alignment.isPowerOf2);
+
+    // Test goodAllocSize
+    assert(a.goodAllocSize(1) >= A.alignment,
+        text(a.goodAllocSize(1), " < ", A.alignment));
+    assert(a.goodAllocSize(11) >= 11.roundUpToMultipleOf(A.alignment));
+    assert(a.goodAllocSize(111) >= 111.roundUpToMultipleOf(A.alignment));
+
+    // Test allocate
+    assert(a.allocate(0) is null);
+
+    auto b1 = a.allocate(1);
+    assert(b1.length == 1);
+    auto b2 = a.allocate(2);
+    assert(b2.length == 2);
+    assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr);
+
+    // Test alignedAllocate
+    static if (hasMember!(A, "alignedAllocate"))
+    {{
+        auto b3 = a.alignedAllocate(1, 256);
+        assert(b3.length <= 1);
+        assert(b3.ptr.alignedAt(256));
+        assert(a.alignedReallocate(b3, 2, 512));
+        assert(b3.ptr.alignedAt(512));
+        static if (hasMember!(A, "alignedDeallocate"))
+        {
+            a.alignedDeallocate(b3);
+        }
+    }}
+    else
+    {
+        static assert(!hasMember!(A, "alignedDeallocate"));
+        // This seems to be a bug in the compiler:
+        //static assert(!hasMember!(A, "alignedReallocate"), A.stringof);
+    }
+
+    static if (hasMember!(A, "allocateAll"))
+    {{
+        auto aa = make();
+        if (aa.allocateAll().ptr)
+        {
+            // Can't get any more memory
+            assert(!aa.allocate(1).ptr);
+        }
+        auto ab = make();
+        const b4 = ab.allocateAll();
+        assert(b4.length);
+        // Can't get any more memory
+        assert(!ab.allocate(1).ptr);
+    }}
+
+    static if (hasMember!(A, "expand"))
+    {{
+        assert(a.expand(b1, 0));
+        auto len = b1.length;
+        if (a.expand(b1, 102))
+        {
+            assert(b1.length == len + 102, text(b1.length, " != ", len + 102));
+        }
+        auto aa = make();
+        void[] b5 = null;
+        assert(aa.expand(b5, 0));
+        assert(b5 is null);
+        assert(aa.expand(b5, 1));
+        assert(b5.length == 1);
+    }}
+
+    void[] b6 = null;
+    assert(a.reallocate(b6, 0));
+    assert(b6.length == 0);
+    assert(a.reallocate(b6, 1));
+    assert(b6.length == 1, text(b6.length));
+
+    // Test owns
+    static if (hasMember!(A, "owns"))
+    {{
+        assert(a.owns(null) == Ternary.no);
+        assert(a.owns(b1) == Ternary.yes);
+        assert(a.owns(b2) == Ternary.yes);
+        assert(a.owns(b6) == Ternary.yes);
+    }}
+
+    static if (hasMember!(A, "resolveInternalPointer"))
+    {{
+        assert(a.resolveInternalPointer(null) is null);
+        auto p = a.resolveInternalPointer(b1.ptr);
+        assert(p.ptr is b1.ptr && p.length >= b1.length);
+        p = a.resolveInternalPointer(b1.ptr + b1.length / 2);
+        assert(p.ptr is b1.ptr && p.length >= b1.length);
+        p = a.resolveInternalPointer(b2.ptr);
+        assert(p.ptr is b2.ptr && p.length >= b2.length);
+        p = a.resolveInternalPointer(b2.ptr + b2.length / 2);
+        assert(p.ptr is b2.ptr && p.length >= b2.length);
+        p = a.resolveInternalPointer(b6.ptr);
+        assert(p.ptr is b6.ptr && p.length >= b6.length);
+        p = a.resolveInternalPointer(b6.ptr + b6.length / 2);
+        assert(p.ptr is b6.ptr && p.length >= b6.length);
+        static int[10] b7 = [ 1, 2, 3 ];
+        assert(a.resolveInternalPointer(b7.ptr) is null);
+        assert(a.resolveInternalPointer(b7.ptr + b7.length / 2) is null);
+        assert(a.resolveInternalPointer(b7.ptr + b7.length) is null);
+        int[3] b8 = [ 1, 2, 3 ];
+        assert(a.resolveInternalPointer(b8.ptr).ptr is null);
+        assert(a.resolveInternalPointer(b8.ptr + b8.length / 2) is null);
+        assert(a.resolveInternalPointer(b8.ptr + b8.length) is null);
+    }}
+}
diff --git a/libphobos/src/std/experimental/allocator/gc_allocator.d b/libphobos/src/std/experimental/allocator/gc_allocator.d
new file mode 100644
index 00000000000..4ccbd8cc1c7
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/gc_allocator.d
@@ -0,0 +1,161 @@
+module std.experimental.allocator.gc_allocator;
+import std.experimental.allocator.common;
+
+/**
+D's built-in garbage-collected allocator.
+ */
+struct GCAllocator
+{
+    import core.memory : GC;
+    unittest { testAllocator!(() => GCAllocator.instance); }
+
+    /**
+    The alignment is a static constant equal to $(D platformAlignment), which
+    ensures proper alignment for any D data type.
+    */
+    enum uint alignment = platformAlignment;
+
+    /**
+    Standard allocator methods per the semantics defined above. The $(D
+    deallocate) and $(D reallocate) methods are $(D @system) because they may
+    move memory around, leaving dangling pointers in user code.
+    */
+    @trusted void[] allocate(size_t bytes) shared
+    {
+        if (!bytes) return null;
+        auto p = GC.malloc(bytes);
+        return p ? p[0 .. bytes] : null;
+    }
+
+    /// Ditto
+    @system bool expand(ref void[] b, size_t delta) shared
+    {
+        if (delta == 0) return true;
+        if (b is null)
+        {
+            b = allocate(delta);
+            return b.ptr != null; // we assume allocate will achieve the correct size.
+        }
+        immutable curLength = GC.sizeOf(b.ptr);
+        assert(curLength != 0); // we have a valid GC pointer here
+        immutable desired = b.length + delta;
+        if(desired > curLength) // check to see if the current block can't hold the data
+        {
+            immutable sizeRequest = desired - curLength;
+            immutable newSize = GC.extend(b.ptr, sizeRequest, sizeRequest);
+            if (newSize == 0)
+            {
+                // expansion unsuccessful
+                return false;
+            }
+            assert(newSize >= desired);
+        }
+        b = b.ptr[0 .. desired];
+        return true;
+    }
+
+    /// Ditto
+    @system bool reallocate(ref void[] b, size_t newSize) shared
+    {
+        import core.exception : OutOfMemoryError;
+        try
+        {
+            auto p = cast(ubyte*) GC.realloc(b.ptr, newSize);
+            b = p[0 .. newSize];
+        }
+        catch (OutOfMemoryError)
+        {
+            // leave the block in place, tell caller
+            return false;
+        }
+        return true;
+    }
+
+    /// Ditto
+    void[] resolveInternalPointer(void* p) shared
+    {
+        auto r = GC.addrOf(p);
+        if (!r) return null;
+        return r[0 .. GC.sizeOf(r)];
+    }
+
+    /// Ditto
+    @system bool deallocate(void[] b) shared
+    {
+        GC.free(b.ptr);
+        return true;
+    }
+
+    /// Ditto
+    size_t goodAllocSize(size_t n) shared
+    {
+        if(n == 0)
+            return 0;
+        if(n <= 16)
+            return 16;
+
+        import core.bitop: bsr;
+
+        auto largestBit = bsr(n-1) + 1;
+        if (largestBit <= 12) // 4096 or less
+            return size_t(1) << largestBit;
+
+        // larger, we use a multiple of 4096.
+        return ((n + 4095) / 4096) * 4096;
+    }
+
+    /**
+    Returns the global instance of this allocator type. The garbage collected
+    allocator is thread-safe, therefore all of its methods and `instance` itself
+    are $(D shared).
+    */
+
+    static shared GCAllocator instance;
+
+    // Leave it undocummented for now.
+    @trusted void collect() shared
+    {
+        GC.collect();
+    }
+}
+
+///
+unittest
+{
+    auto buffer = GCAllocator.instance.allocate(1024 * 1024 * 4);
+    // deallocate upon scope's end (alternatively: leave it to collection)
+    scope(exit) GCAllocator.instance.deallocate(buffer);
+    //...
+}
+
+unittest
+{
+    auto b = GCAllocator.instance.allocate(10_000);
+    assert(GCAllocator.instance.expand(b, 1));
+}
+
+unittest
+{
+    import core.memory: GC;
+
+    // test allocation sizes
+    assert(GCAllocator.instance.goodAllocSize(1) == 16);
+    for(size_t s = 16; s <= 8192; s *= 2)
+    {
+        assert(GCAllocator.instance.goodAllocSize(s) == s);
+        assert(GCAllocator.instance.goodAllocSize(s - (s / 2) + 1) == s);
+
+        auto buffer = GCAllocator.instance.allocate(s);
+        scope(exit) GCAllocator.instance.deallocate(buffer);
+
+        assert(GC.sizeOf(buffer.ptr) == s);
+
+        auto buffer2 = GCAllocator.instance.allocate(s - (s / 2) + 1);
+        scope(exit) GCAllocator.instance.deallocate(buffer2);
+
+        assert(GC.sizeOf(buffer2.ptr) == s);
+    }
+
+    // anything above a page is simply rounded up to next page
+    assert(GCAllocator.instance.goodAllocSize(4096 * 4 + 1) == 4096 * 5);
+}
diff --git a/libphobos/src/std/experimental/allocator/mallocator.d b/libphobos/src/std/experimental/allocator/mallocator.d
new file mode 100644
index 00000000000..4d4d7ef8be7
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/mallocator.d
@@ -0,0 +1,384 @@
+module std.experimental.allocator.mallocator;
+import std.experimental.allocator.common;
+
+/**
+   The C heap allocator.
+ */
+struct Mallocator
+{
+    unittest { testAllocator!(() => Mallocator.instance); }
+
+    /**
+    The alignment is a static constant equal to $(D platformAlignment), which
+    ensures proper alignment for any D data type.
+    */
+    enum uint alignment = platformAlignment;
+
+    /**
+    Standard allocator methods per the semantics defined above. The
+    $(D deallocate) and $(D reallocate) methods are $(D @system) because they
+    may move memory around, leaving dangling pointers in user code. Somewhat
+    paradoxically, $(D malloc) is $(D @safe) but that's only useful to safe
+    programs that can afford to leak memory allocated.
+    */
+    @trusted @nogc nothrow
+    void[] allocate(size_t bytes) shared
+    {
+        import core.stdc.stdlib : malloc;
+        if (!bytes) return null;
+        auto p = malloc(bytes);
+        return p ? p[0 .. bytes] : null;
+    }
+
+    /// Ditto
+    @system @nogc nothrow
+    bool deallocate(void[] b) shared
+    {
+        import core.stdc.stdlib : free;
+        free(b.ptr);
+        return true;
+    }
+
+    /// Ditto
+    @system @nogc nothrow
+    bool reallocate(ref void[] b, size_t s) shared
+    {
+        import core.stdc.stdlib : realloc;
+        if (!s)
+        {
+            // fuzzy area in the C standard, see http://goo.gl/ZpWeSE
+            // so just deallocate and nullify the pointer
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        auto p = cast(ubyte*) realloc(b.ptr, s);
+        if (!p) return false;
+        b = p[0 .. s];
+        return true;
+    }
+
+    /**
+    Returns the global instance of this allocator type. The C heap allocator is
+    thread-safe, therefore all of its methods and `it` itself are
+    $(D shared).
+    */
+    static shared Mallocator instance;
+}
+
+///
+@nogc nothrow
+unittest
+{
+    auto buffer = Mallocator.instance.allocate(1024 * 1024 * 4);
+    scope(exit) Mallocator.instance.deallocate(buffer);
+    //...
+}
+
+@nogc nothrow
+unittest
+{
+    @nogc nothrow
+    static void test(A)()
+    {
+        int* p = null;
+        p = cast(int*) A.instance.allocate(int.sizeof);
+        scope(exit) A.instance.deallocate(p[0 .. int.sizeof]);
+        *p = 42;
+        assert(*p == 42);
+    }
+    test!Mallocator();
+}
+
+@nogc nothrow
+unittest
+{
+    static void test(A)()
+    {
+        import std.experimental.allocator : make;
+        Object p = null;
+        p = A.instance.make!Object();
+        assert(p !is null);
+    }
+
+    test!Mallocator();
+}
+
+version (Posix)
+@nogc nothrow
+private extern(C) int posix_memalign(void**, size_t, size_t);
+
+version (Windows)
+{
+    // DMD Win 32 bit, DigitalMars C standard library misses the _aligned_xxx
+    // functions family (snn.lib)
+    version(CRuntime_DigitalMars)
+    {
+        // Helper to cast the infos written before the aligned pointer
+        // this header keeps track of the size (required to realloc) and of
+        // the base ptr (required to free).
+        private struct AlignInfo
+        {
+            void* basePtr;
+            size_t size;
+
+            @nogc nothrow
+            static AlignInfo* opCall(void* ptr)
+            {
+                return cast(AlignInfo*) (ptr - AlignInfo.sizeof);
+            }
+        }
+
+        @nogc nothrow
+        private void* _aligned_malloc(size_t size, size_t alignment)
+        {
+            import std.c.stdlib: malloc;
+            size_t offset = alignment + size_t.sizeof * 2 - 1;
+
+            // unaligned chunk
+            void* basePtr = malloc(size + offset);
+            if (!basePtr) return null;
+
+            // get aligned location within the chunk
+            void* alignedPtr = cast(void**)((cast(size_t)(basePtr) + offset)
+                & ~(alignment - 1));
+
+            // write the header before the aligned pointer
+            AlignInfo* head = AlignInfo(alignedPtr);
+            head.basePtr = basePtr;
+            head.size = size;
+
+            return alignedPtr;
+        }
+
+        @nogc nothrow
+        private void* _aligned_realloc(void* ptr, size_t size, size_t alignment)
+        {
+            import std.c.stdlib: free;
+            import std.c.string: memcpy;
+
+            if(!ptr) return _aligned_malloc(size, alignment);
+
+            // gets the header from the exising pointer
+            AlignInfo* head = AlignInfo(ptr);
+
+            // gets a new aligned pointer
+            void* alignedPtr = _aligned_malloc(size, alignment);
+            if (!alignedPtr)
+            {
+                //to https://msdn.microsoft.com/en-us/library/ms235462.aspx
+                //see Return value: in this case the original block is unchanged
+                return null;
+            }
+
+            // copy exising data
+            memcpy(alignedPtr, ptr, head.size);
+            free(head.basePtr);
+
+            return alignedPtr;
+        }
+
+        @nogc nothrow
+        private void _aligned_free(void *ptr)
+        {
+            import std.c.stdlib: free;
+            if (!ptr) return;
+            AlignInfo* head = AlignInfo(ptr);
+            free(head.basePtr);
+        }
+
+    }
+    // DMD Win 64 bit, uses microsoft standard C library which implements them
+    else
+    {
+        @nogc nothrow private extern(C) void* _aligned_malloc(size_t, size_t);
+        @nogc nothrow private extern(C) void _aligned_free(void *memblock);
+        @nogc nothrow private extern(C) void* _aligned_realloc(void *, size_t, size_t);
+    }
+}
+
+/**
+   Aligned allocator using OS-specific primitives, under a uniform API.
+ */
+struct AlignedMallocator
+{
+    unittest { testAllocator!(() => typeof(this).instance); }
+
+    /**
+    The default alignment is $(D platformAlignment).
+    */
+    enum uint alignment = platformAlignment;
+
+    /**
+    Forwards to $(D alignedAllocate(bytes, platformAlignment)).
+    */
+    @trusted @nogc nothrow
+    void[] allocate(size_t bytes) shared
+    {
+        if (!bytes) return null;
+        return alignedAllocate(bytes, alignment);
+    }
+
+    /**
+    Uses $(WEB man7.org/linux/man-pages/man3/posix_memalign.3.html,
+    $(D posix_memalign)) on Posix and
+    $(WEB msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx,
+    $(D __aligned_malloc)) on Windows.
+    */
+    version(Posix)
+    @trusted @nogc nothrow
+    void[] alignedAllocate(size_t bytes, uint a) shared
+    {
+        import core.stdc.errno : ENOMEM, EINVAL;
+        assert(a.isGoodDynamicAlignment);
+        void* result;
+        auto code = posix_memalign(&result, a, bytes);
+        if (code == ENOMEM)
+            return null;
+
+        else if (code == EINVAL)
+            assert (0, "AlignedMallocator.alignment is not a power of two multiple of (void*).sizeof, according to posix_memalign!");
+
+        else if (code != 0)
+            assert (0, "posix_memalign returned an unknown code!");
+
+        else
+            return result[0 .. bytes];
+    }
+    else version(Windows)
+    @trusted @nogc nothrow
+    void[] alignedAllocate(size_t bytes, uint a) shared
+    {
+        auto result = _aligned_malloc(bytes, a);
+        return result ? result[0 .. bytes] : null;
+    }
+    else static assert(0);
+
+    /**
+    Calls $(D free(b.ptr)) on Posix and
+    $(WEB msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx,
+    $(D __aligned_free(b.ptr))) on Windows.
+    */
+    version (Posix)
+    @system @nogc nothrow
+    bool deallocate(void[] b) shared
+    {
+        import core.stdc.stdlib : free;
+        free(b.ptr);
+        return true;
+    }
+    else version (Windows)
+    @system @nogc nothrow
+    bool deallocate(void[] b) shared
+    {
+        _aligned_free(b.ptr);
+        return true;
+    }
+    else static assert(0);
+
+    /**
+    On Posix, forwards to $(D realloc). On Windows, forwards to
+    $(D alignedReallocate(b, newSize, platformAlignment)).
+    */
+    version (Posix)
+    @system @nogc nothrow
+    bool reallocate(ref void[] b, size_t newSize) shared
+    {
+        return Mallocator.instance.reallocate(b, newSize);
+    }
+    version (Windows)
+    @system @nogc nothrow
+    bool reallocate(ref void[] b, size_t newSize) shared
+    {
+        return alignedReallocate(b, newSize, alignment);
+    }
+
+    /**
+    On Posix, uses $(D alignedAllocate) and copies data around because there is
+    no realloc for aligned memory. On Windows, calls
+    $(WEB msdn.microsoft.com/en-US/library/y69db7sx(v=vs.80).aspx,
+    $(D __aligned_realloc(b.ptr, newSize, a))).
+    */
+    version (Windows)
+    @system @nogc nothrow
+    bool alignedReallocate(ref void[] b, size_t s, uint a) shared
+    {
+        if (!s)
+        {
+            deallocate(b);
+            b = null;
+            return true;
+        }
+        auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a);
+        if (!p) return false;
+        b = p[0 .. s];
+        return true;
+    }
+
+    /**
+    Returns the global instance of this allocator type. The C heap allocator is
+    thread-safe, therefore all of its methods and `instance` itself are
+    $(D shared).
+    */
+    static shared AlignedMallocator instance;
+}
+
+///
+@nogc nothrow
+unittest
+{
+    auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4,
+        128);
+    scope(exit) AlignedMallocator.instance.deallocate(buffer);
+    //...
+}
+
+version(unittest) version(CRuntime_DigitalMars)
+@nogc nothrow
+size_t addr(ref void* ptr) { return cast(size_t) ptr; }
+
+version(CRuntime_DigitalMars)
+@nogc nothrow
+unittest
+{
+    void* m;
+
+    m = _aligned_malloc(16, 0x10);
+    if (m)
+    {
+        assert((m.addr & 0xF) == 0);
+        _aligned_free(m);
+    }
+
+    m = _aligned_malloc(16, 0x100);
+    if (m)
+    {
+        assert((m.addr & 0xFF) == 0);
+        _aligned_free(m);
+    }
+
+    m = _aligned_malloc(16, 0x1000);
+    if (m)
+    {
+        assert((m.addr & 0xFFF) == 0);
+        _aligned_free(m);
+    }
+
+    m = _aligned_malloc(16, 0x10);
+    if (m)
+    {
+        assert((cast(size_t)m & 0xF) == 0);
+        m = _aligned_realloc(m, 32, 0x10000);
+        if (m) assert((m.addr & 0xFFFF) == 0);
+        _aligned_free(m);
+    }
+
+    m = _aligned_malloc(8, 0x10);
+    if (m)
+    {
+        *cast(ulong*) m = 0X01234567_89ABCDEF;
+        m = _aligned_realloc(m, 0x800, 0x1000);
+        if (m) assert(*cast(ulong*) m == 0X01234567_89ABCDEF);
+        _aligned_free(m);
+    }
+}
diff --git a/libphobos/src/std/experimental/allocator/mmap_allocator.d b/libphobos/src/std/experimental/allocator/mmap_allocator.d
new file mode 100644
index 00000000000..8d2af1af895
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/mmap_allocator.d
@@ -0,0 +1,74 @@
+module std.experimental.allocator.mmap_allocator;
+
+// MmapAllocator
+/**
+
+Allocator (currently defined only for Posix) using $(D $(LUCKY mmap)) and $(D
+$(LUCKY munmap)) directly. There is no additional structure: each call to $(D
+allocate(s)) issues a call to $(D mmap(null, s, PROT_READ | PROT_WRITE,
+MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)), and each call to $(D deallocate(b)) issues
+$(D munmap(b.ptr, b.length)). So $(D MmapAllocator) is usually intended for
+allocating large chunks to be managed by fine-granular allocators.
+
+*/
+struct MmapAllocator
+{
+    /// The one shared instance.
+    static shared MmapAllocator instance;
+
+    /**
+    Alignment is page-size and hardcoded to 4096 (even though on certain systems
+    it could be larger).
+    */
+    enum size_t alignment = 4096;
+
+    version(Posix)
+    {
+        /// Allocator API.
+        void[] allocate(size_t bytes) shared
+        {
+            import core.sys.posix.sys.mman;
+            if (!bytes) return null;
+            auto p = mmap(null, bytes, PROT_READ | PROT_WRITE,
+                MAP_PRIVATE | MAP_ANON, -1, 0);
+            if (p is MAP_FAILED) return null;
+            return p[0 .. bytes];
+        }
+
+        /// Ditto
+        bool deallocate(void[] b) shared
+        {
+            import core.sys.posix.sys.mman : munmap;
+            if (b.ptr) munmap(b.ptr, b.length) == 0 || assert(0);
+            return true;
+        }
+    }
+    else version(Windows)
+    {
+        import core.sys.windows.windows;
+
+        /// Allocator API.
+        void[] allocate(size_t bytes) shared
+        {
+            if (!bytes) return null;
+            auto p = VirtualAlloc(null, bytes, MEM_COMMIT, PAGE_READWRITE);
+            if (p == null)
+                return null;
+            return p[0 .. bytes];
+        }
+
+        /// Ditto
+        bool deallocate(void[] b) shared
+        {
+            return b.ptr is null || VirtualFree(b.ptr, 0, MEM_RELEASE) != 0;
+        }
+    }
+}
+
+unittest
+{
+    alias alloc = MmapAllocator.instance;
+    auto p = alloc.allocate(100);
+    assert(p.length == 100);
+    alloc.deallocate(p);
+}
diff --git a/libphobos/src/std/experimental/allocator/package.d b/libphobos/src/std/experimental/allocator/package.d
new file mode 100644
index 00000000000..56df975c3fd
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/package.d
@@ -0,0 +1,2009 @@
+// Written in the D programming language.
+/**
+
+High-level interface for allocators. Implements bundled allocation/creation
+and destruction/deallocation of data including `struct`s and `class`es,
+and also array primitives related to allocation. This module is the entry point
+for both making use of allocators and for their documentation.
+
+Synopsis:
+---
+// Allocate an int, initialize it with 42
+int* p = theAllocator.make!int(42);
+assert(*p == 42);
+// Destroy and deallocate it
+theAllocator.dispose(p);
+
+// Allocate using the global process allocator
+p = processAllocator.make!int(100);
+assert(*p == 100);
+// Destroy and deallocate
+processAllocator.dispose(p);
+
+// Create an array of 50 doubles initialized to -1.0
+double[] arr = theAllocator.makeArray!double(50, -1.0);
+// Append two zeros to it
+theAllocator.expandArray(arr, 2, 0.0);
+// On second thought, take that back
+theAllocator.shrinkArray(arr, 2);
+// Destroy and deallocate
+theAllocator.dispose(arr);
+---
+
+$(H2 Layered Structure)
+
+D's allocators have a layered structure in both implementation and documentation:
+
+$(OL
+$(LI A high-level, dynamically-typed layer (described further down in this
+module). It consists of an interface called $(LREF IAllocator), which concrete
+allocators need to implement. The interface primitives themselves are oblivious
+to the type of the objects being allocated; they only deal in `void[]`, by
+necessity of the interface being dynamic (as opposed to type-parameterized).
+Each thread has a current allocator it uses by default, which is a thread-local
+variable $(LREF theAllocator) of type $(LREF IAllocator). The process has a
+global _allocator called $(LREF processAllocator), also of type $(LREF
+IAllocator). When a new thread is created, $(LREF processAllocator) is copied
+into $(LREF theAllocator). An application can change the objects to which these
+references point. By default, at application startup, $(LREF processAllocator)
+refers to an object that uses D's garbage collected heap. This layer also
+include high-level functions such as $(LREF make) and $(LREF dispose) that
+comfortably allocate/create and respectively destroy/deallocate objects. This
+layer is all needed for most casual uses of allocation primitives.)
+
+$(LI A mid-level, statically-typed layer for assembling several allocators into
+one. It uses properties of the type of the objects being created to route
+allocation requests to possibly specialized allocators. This layer is relatively
+thin and implemented and documented in the $(XREF2
+std,experimental,_allocator,typed) module. It allows an interested user to e.g.
+use different allocators for arrays versus fixed-sized objects, to the end of
+better overall performance.)
+
+$(LI A low-level collection of highly generic $(I heap building blocks)$(MDASH)
+Lego-like pieces that can be used to assemble application-specific allocators.
+The real allocation smarts are occurring at this level. This layer is of
+interest to advanced applications that want to configure their own allocators.
+A good illustration of typical uses of these building blocks is module $(XREF2
+std,experimental,_allocator,showcase) which defines a collection of frequently-
+used preassembled allocator objects. The implementation and documentation entry
+point is $(XREF2 std,experimental,_allocator,building_blocks). By design, the
+primitives of the static interface have the same signatures as the $(LREF
+IAllocator) primitives but are for the most part optional and driven by static
+introspection. The parameterized class $(LREF CAllocatorImpl) offers an
+immediate and useful means to package a static low-level _allocator into an
+implementation of $(LREF IAllocator).)
+
+$(LI Core _allocator objects that interface with D's garbage collected heap
+($(XREF2 std,experimental,_allocator,gc_allocator)), the C `malloc` family
+($(XREF2 std,experimental,_allocator,mallocator)), and the OS ($(XREF2
+std,experimental,_allocator,mmap_allocator)). Most custom allocators would
+ultimately obtain memory from one of these core allocators.)
+)
+
+$(H2 Idiomatic Use of $(D std.experimental._allocator))
+
+As of this time, $(D std.experimental._allocator) is not integrated with D's
+built-in operators that allocate memory, such as `new`, array literals, or
+array concatenation operators. That means $(D std.experimental._allocator) is
+opt-in$(MDASH)applications need to make explicit use of it.
+
+For casual creation and disposal of dynamically-allocated objects, use $(LREF
+make), $(LREF dispose), and the array-specific functions $(LREF makeArray),
+$(LREF expandArray), and $(LREF shrinkArray). These use by default D's garbage
+collected heap, but open the application to better configuration options. These
+primitives work either with `theAllocator` but also with any allocator obtained
+by combining heap building blocks. For example:
+
+----
+void fun(size_t n)
+{
+    // Use the current allocator
+    int[] a1 = theAllocator.makeArray!int(n);
+    scope(exit) theAllocator.dispose(a1);
+    ...
+}
+----
+
+To experiment with alternative allocators, set $(LREF theAllocator) for the
+current thread. For example, consider an application that allocates many 8-byte
+objects. These are not well supported by the default _allocator, so a $(A
+$(MY_JOIN_LINE std,experimental,_allocator,building_blocks,free_list).html, free
+list _allocator) would be recommended. To install one in `main`, the
+application would use:
+
+----
+void main()
+{
+    import std.experimental.allocator.building_blocks.free_list
+        : FreeList;
+    theAllocator = allocatorObject(FreeList!8());
+    ...
+}
+----
+
+$(H3 Saving the `IAllocator` Reference For Later Use)
+
+As with any global resource, setting `theAllocator` and `processAllocator`
+should not be done often and casually. In particular, allocating memory with
+one allocator and deallocating with another causes undefined behavior.
+Typically, these variables are set during application initialization phase and
+last through the application.
+
+To avoid this, long-lived objects that need to perform allocations,
+reallocations, and deallocations relatively often may want to store a reference
+to the _allocator object they use throughout their lifetime. Then, instead of
+using `theAllocator` for internal allocation-related tasks, they'd use the
+internally held reference. For example, consider a user-defined hash table:
+
+----
+struct HashTable
+{
+    private IAllocator _allocator;
+    this(size_t buckets, IAllocator allocator = theAllocator) {
+        this._allocator = allocator;
+        ...
+    }
+    // Getter and setter
+    IAllocator allocator() { return _allocator; }
+    void allocator(IAllocator a) { assert(empty); _allocator = a; }
+}
+----
+
+Following initialization, the `HashTable` object would consistently use its
+$(D _allocator) object for acquiring memory. Furthermore, setting
+$(D HashTable._allocator) to point to a different _allocator should be legal but
+only if the object is empty; otherwise, the object wouldn't be able to
+deallocate its existing state.
+
+$(H3 Using Allocators without `IAllocator`)
+
+Allocators assembled from the heap building blocks don't need to go through
+`IAllocator` to be usable. They have the same primitives as `IAllocator` and
+they work with $(LREF make), $(LREF makeArray), $(LREF dispose) etc. So it
+suffice to create allocator objects wherever fit and use them appropriately:
+
+----
+void fun(size_t n)
+{
+    // Use a stack-installed allocator for up to 64KB
+    StackFront!65536 myAllocator;
+    int[] a2 = myAllocator.makeArray!int(n);
+    scope(exit) myAllocator.dispose(a2);
+    ...
+}
+----
+
+In this case, `myAllocator` does not obey the `IAllocator` interface, but
+implements its primitives so it can work with `makeArray` by means of duck
+typing.
+
+One important thing to note about this setup is that statically-typed assembled
+allocators are almost always faster than allocators that go through
+`IAllocator`. An important rule of thumb is: "assemble allocator first, adapt
+to `IAllocator` after". A good allocator implements intricate logic by means of
+template assembly, and gets wrapped with `IAllocator` (usually by means of
+$(LREF allocatorObject)) only once, at client level.
+
+Macros:
+MYREF = $(LINK2 std_experimental_allocator_$2.html, $1)&nbsp;
+MYREF2 = $(LINK2 std_experimental_allocator_$2.html#$1, $1)&nbsp;
+TDC = <td nowrap>$(D $1)$+</td>
+TDC2 = <td nowrap>$(D $(MYREF $1,$+))</td>
+TDC3 = <td nowrap>$(D $(MYREF2 $1,$+))</td>
+RES = $(I result)
+POST = $(BR)$(SMALL $(I Post:) $(BLUE $(D $0)))
+MY_JOIN_LINE = $1$(MY_JOIN_LINE_TAIL $+)
+MY_JOIN_LINE_TAIL = _$1$(MY_JOIN_LINE_TAIL $+)
+JOIN_DOT = $1$(JOIN_DOT_TAIL $+)
+JOIN_DOT_TAIL = .$1$(JOIN_DOT_TAIL $+)
+XREF2 = $(A $(MY_JOIN_LINE $1,$+).html,$(D $(JOIN_DOT $1,$+)))
+
+Copyright: Andrei Alexandrescu 2013-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/experimental/_allocator)
+
+*/
+
+module std.experimental.allocator;
+
+public import std.experimental.allocator.common,
+    std.experimental.allocator.typed;
+
+// Example in the synopsis above
+unittest
+{
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.building_blocks.segregator : Segregator;
+    import std.experimental.allocator.building_blocks.bucketizer : Bucketizer;
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    import std.experimental.allocator.building_blocks.bitmapped_block
+        : BitmappedBlock;
+
+    alias FList = FreeList!(GCAllocator, 0, unbounded);
+    alias A = Segregator!(
+        8, FreeList!(GCAllocator, 0, 8),
+        128, Bucketizer!(FList, 1, 128, 16),
+        256, Bucketizer!(FList, 129, 256, 32),
+        512, Bucketizer!(FList, 257, 512, 64),
+        1024, Bucketizer!(FList, 513, 1024, 128),
+        2048, Bucketizer!(FList, 1025, 2048, 256),
+        3584, Bucketizer!(FList, 2049, 3584, 512),
+        4072 * 1024, AllocatorList!(
+            (n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate(
+                max(n, 4072 * 1024)))),
+        GCAllocator
+    );
+    A tuMalloc;
+    auto b = tuMalloc.allocate(500);
+    assert(b.length == 500);
+    auto c = tuMalloc.allocate(113);
+    assert(c.length == 113);
+    assert(tuMalloc.expand(c, 14));
+    tuMalloc.deallocate(b);
+    tuMalloc.deallocate(c);
+}
+
+import std.algorithm, std.conv, std.exception, std.range, std.traits,
+    std.typecons;
+version(unittest) import std.random, std.stdio;
+
+/**
+Dynamic allocator interface. Code that defines allocators ultimately implements
+this interface. This should be used wherever a uniform type is required for
+encapsulating various allocator implementations.
+
+Composition of allocators is not recommended at this level due to
+inflexibility of dynamic interfaces and inefficiencies caused by cascaded
+multiple calls. Instead, compose allocators using the static interface defined
+in $(A std_experimental_allocator_building_blocks.html,
+`std.experimental.allocator.building_blocks`), then adapt the composed
+allocator to `IAllocator` (possibly by using $(LREF CAllocatorImpl) below).
+
+Methods returning $(D Ternary) return $(D Ternary.yes) upon success,
+$(D Ternary.no) upon failure, and $(D Ternary.unknown) if the primitive is not
+implemented by the allocator instance.
+*/
+interface IAllocator
+{
+    /**
+    Returns the alignment offered.
+    */
+    @property uint alignment();
+
+    /**
+    Returns the good allocation size that guarantees zero internal
+    fragmentation.
+    */
+    size_t goodAllocSize(size_t s);
+
+    /**
+    Allocates `n` bytes of memory.
+    */
+    void[] allocate(size_t, TypeInfo ti = null);
+
+    /**
+    Allocates `n` bytes of memory with specified alignment `a`. Implementations
+    that do not support this primitive should always return `null`.
+    */
+    void[] alignedAllocate(size_t n, uint a);
+
+    /**
+    Allocates and returns all memory available to this allocator.
+    Implementations that do not support this primitive should always return
+    `null`.
+    */
+    void[] allocateAll();
+
+    /**
+    Expands a memory block in place and returns `true` if successful.
+    Implementations that don't support this primitive should always return
+    `false`.
+    */
+    bool expand(ref void[], size_t);
+
+    /// Reallocates a memory block.
+    bool reallocate(ref void[], size_t);
+
+    /// Reallocates a memory block with specified alignment.
+    bool alignedReallocate(ref void[] b, size_t size, uint alignment);
+
+    /**
+    Returns $(D Ternary.yes) if the allocator owns $(D b), $(D Ternary.no) if
+    the allocator doesn't own $(D b), and $(D Ternary.unknown) if ownership
+    cannot be determined. Implementations that don't support this primitive
+    should always return `Ternary.unknown`.
+    */
+    Ternary owns(void[] b);
+
+    /**
+    Resolves an internal pointer to the full block allocated. Implementations
+    that don't support this primitive should always return `Ternary.unknown`.
+    */
+    Ternary resolveInternalPointer(void* p, ref void[] result);
+
+    /**
+    Deallocates a memory block. Implementations that don't support this
+    primitive should always return `false`. A simple way to check that an
+    allocator supports deallocation is to call $(D deallocate(null)).
+    */
+    bool deallocate(void[] b);
+
+    /**
+    Deallocates all memory. Implementations that don't support this primitive
+    should always return `false`.
+    */
+    bool deallocateAll();
+
+    /**
+    Returns $(D Ternary.yes) if no memory is currently allocated from this
+    allocator, $(D Ternary.no) if some allocations are currently active, or
+    $(D Ternary.unknown) if not supported.
+    */
+    Ternary empty();
+}
+
+__gshared IAllocator _processAllocator;
+IAllocator _threadAllocator;
+
+shared static this()
+{
+    assert(!_processAllocator);
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    _processAllocator = allocatorObject(GCAllocator.instance);
+}
+
+static this()
+{
+    assert(!_threadAllocator);
+    _threadAllocator = _processAllocator;
+}
+
+/**
+Gets/sets the allocator for the current thread. This is the default allocator
+that should be used for allocating thread-local memory. For allocating memory
+to be shared across threads, use $(D processAllocator) (below). By default,
+$(D theAllocator) ultimately fetches memory from $(D processAllocator), which
+in turn uses the garbage collected heap.
+*/
+@property IAllocator theAllocator()
+{
+    return _threadAllocator;
+}
+
+/// Ditto
+@property void theAllocator(IAllocator a)
+{
+    assert(a);
+    _threadAllocator = a;
+}
+
+///
+unittest
+{
+    // Install a new allocator that is faster for 128-byte allocations.
+    import std.experimental.allocator.building_blocks.free_list : FreeList;
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    auto oldAllocator = theAllocator;
+    scope(exit) theAllocator = oldAllocator;
+    theAllocator = allocatorObject(FreeList!(GCAllocator, 128)());
+    // Use the now changed allocator to allocate an array
+    const ubyte[] arr = theAllocator.makeArray!ubyte(128);
+    assert(arr.ptr);
+    //...
+}
+
+/**
+Gets/sets the allocator for the current process. This allocator must be used
+for allocating memory shared across threads. Objects created using this
+allocator can be cast to $(D shared).
+*/
+@property IAllocator processAllocator()
+{
+    return _processAllocator;
+}
+
+/// Ditto
+@property void processAllocator(IAllocator a)
+{
+    assert(a);
+    _processAllocator = a;
+}
+
+unittest
+{
+    assert(processAllocator);
+    assert(processAllocator is theAllocator);
+}
+
+/**
+Dynamically allocates (using $(D alloc)) and then creates in the memory
+allocated an object of type $(D T), using $(D args) (if any) for its
+initialization. Initialization occurs in the memory allocated and is otherwise
+semantically the same as $(D T(args)).
+(Note that using $(D alloc.make!(T[])) creates a pointer to an (empty) array
+of $(D T)s, not an array. To use an allocator to allocate and initialize an
+array, use $(D alloc.makeArray!T) described below.)
+
+Params:
+T = Type of the object being created.
+alloc = The allocator used for getting the needed memory. It may be an object
+implementing the static interface for allocators, or an $(D IAllocator)
+reference.
+args = Optional arguments used for initializing the created object. If not
+present, the object is default constructed.
+
+Returns: If $(D T) is a class type, returns a reference to the created $(D T)
+object. Otherwise, returns a $(D T*) pointing to the created object. In all
+cases, returns $(D null) if allocation failed.
+
+Throws: If $(D T)'s constructor throws, deallocates the allocated memory and
+propagates the exception.
+*/
+auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args)
+{
+    import std.algorithm : max;
+    import std.conv : emplace;
+    auto m = alloc.allocate(max(stateSize!T, 1));
+    if (!m.ptr) return null;
+    scope(failure) alloc.deallocate(m);
+    static if (is(T == class))
+        return emplace!T(m, args);
+    else return emplace(cast(T*) m.ptr, args);
+}
+
+///
+unittest
+{
+    // Dynamically allocate one integer
+    const int* p1 = theAllocator.make!int;
+    // It's implicitly initialized with its .init value
+    assert(*p1 == 0);
+    // Dynamically allocate one double, initialize to 42.5
+    const double* p2 = theAllocator.make!double(42.5);
+    assert(*p2 == 42.5);
+
+    // Dynamically allocate a struct
+    static struct Point
+    {
+        int x, y, z;
+    }
+    // Use the generated constructor taking field values in order
+    const Point* p = theAllocator.make!Point(1, 2);
+    assert(p.x == 1 && p.y == 2 && p.z == 0);
+
+    // Dynamically allocate a class object
+    static class Customer
+    {
+        uint id = uint.max;
+        this() {}
+        this(uint id) { this.id = id; }
+        // ...
+    }
+    Customer cust = theAllocator.make!Customer;
+    assert(cust.id == uint.max); // default initialized
+    cust = theAllocator.make!Customer(42);
+    assert(cust.id == 42);
+}
+
+unittest // bugzilla 15639 & 15772
+{
+    abstract class Foo {}
+    class Bar: Foo {}
+    static assert(!is(typeof(theAllocator.make!Foo)));
+    static assert( is(typeof(theAllocator.make!Bar)));
+}
+
+unittest
+{
+    void test(Allocator)(auto ref Allocator alloc)
+    {
+        const int* a = alloc.make!int(10);
+        assert(*a == 10);
+
+        struct A
+        {
+            int x;
+            string y;
+            double z;
+        }
+
+        A* b = alloc.make!A(42);
+        assert(b.x == 42);
+        assert(b.y is null);
+        import std.math : isNaN;
+        assert(b.z.isNaN);
+
+        b = alloc.make!A(43, "44", 45);
+        assert(b.x == 43);
+        assert(b.y == "44");
+        assert(b.z == 45);
+
+        static class B
+        {
+            int x;
+            string y;
+            double z;
+            this(int _x, string _y = null, double _z = double.init)
+            {
+                x = _x;
+                y = _y;
+                z = _z;
+            }
+        }
+
+        B c = alloc.make!B(42);
+        assert(c.x == 42);
+        assert(c.y is null);
+        assert(c.z.isNaN);
+
+        c = alloc.make!B(43, "44", 45);
+        assert(c.x == 43);
+        assert(c.y == "44");
+        assert(c.z == 45);
+
+        const parray = alloc.make!(int[]);
+        assert((*parray).empty);
+    }
+
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow
+{
+    import core.stdc.string : memcpy;
+    if (!array.length) return;
+    memcpy(array.ptr, &filler, T.sizeof);
+    // Fill the array from the initialized portion of itself exponentially.
+    for (size_t offset = T.sizeof; offset < array.length; )
+    {
+        size_t extent = min(offset, array.length - offset);
+        memcpy(array.ptr + offset, array.ptr, extent);
+        offset += extent;
+    }
+}
+
+unittest
+{
+    int[] a;
+    fillWithMemcpy(a, 42);
+    assert(a.length == 0);
+    a = [ 1, 2, 3, 4, 5 ];
+    fillWithMemcpy(a, 42);
+    assert(a == [ 42, 42, 42, 42, 42]);
+}
+
+private T[] uninitializedFillDefault(T)(T[] array) nothrow
+{
+    T t = T.init;
+    fillWithMemcpy(array, t);
+    return array;
+}
+
+pure nothrow @nogc
+unittest
+{
+    static struct S { int x = 42; @disable this(this); }
+
+    int[5] expected = [42, 42, 42, 42, 42];
+    S[5] arr = void;
+    uninitializedFillDefault(arr);
+    assert ((cast(int*)arr.ptr)[0 .. arr.length] == expected);
+}
+
+unittest
+{
+    int[] a = [1, 2, 4];
+    uninitializedFillDefault(a);
+    assert(a == [0, 0, 0]);
+}
+
+/**
+Create an array of $(D T) with $(D length) elements using $(D alloc). The array is either default-initialized, filled with copies of $(D init), or initialized with values fetched from `range`.
+
+Params:
+T = element type of the array being created
+alloc = the allocator used for getting memory
+length = length of the newly created array
+init = element used for filling the array
+range = range used for initializing the array elements
+
+Returns:
+The newly-created array, or $(D null) if either $(D length) was $(D 0) or
+allocation failed.
+
+Throws:
+The first two overloads throw only if `alloc`'s primitives do. The
+overloads that involve copy initialization deallocate memory and propagate the
+exception if the copy operation throws.
+*/
+T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length)
+{
+    if (!length) return null;
+    auto m = alloc.allocate(T.sizeof * length);
+    if (!m.ptr) return null;
+    return uninitializedFillDefault(cast(T[]) m);
+}
+
+unittest
+{
+    void test1(A)(auto ref A alloc)
+    {
+        int[] a = alloc.makeArray!int(0);
+        assert(a.length == 0 && a.ptr is null);
+        a = alloc.makeArray!int(5);
+        assert(a.length == 5);
+        assert(a == [ 0, 0, 0, 0, 0]);
+    }
+
+    void test2(A)(auto ref A alloc)
+    {
+        static struct S { int x = 42; @disable this(this); }
+        S[] arr = alloc.makeArray!S(5);
+        assert(arr.length == 5);
+        assert((cast(int*)arr.ptr)[0 .. 5] == [ 42, 42, 42, 42, 42]);
+    }
+
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test1(GCAllocator.instance);
+    test1(theAllocator);
+    test2(GCAllocator.instance);
+    test2(theAllocator);
+}
+
+/// Ditto
+T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length,
+    auto ref T init)
+{
+    if (!length) return null;
+    auto m = alloc.allocate(T.sizeof * length);
+    if (!m.ptr) return null;
+    auto result = cast(T[]) m;
+    import std.traits : hasElaborateCopyConstructor;
+    static if (hasElaborateCopyConstructor!T)
+    {
+        scope(failure) alloc.deallocate(m);
+        size_t i = 0;
+        static if (hasElaborateDestructor!T)
+        {
+            scope (failure)
+            {
+                foreach (j; 0 .. i)
+                {
+                    destroy(result[j]);
+                }
+            }
+        }
+        for (; i < length; ++i)
+        {
+            emplace!T(result.ptr + i, init);
+        }
+    }
+    else
+    {
+        fillWithMemcpy(result, init);
+    }
+    return result;
+}
+
+///
+unittest
+{
+    int[] a = theAllocator.makeArray!int(2);
+    assert(a == [0, 0]);
+    a = theAllocator.makeArray!int(3, 42);
+    assert(a == [42, 42, 42]);
+    import std.range : only;
+    a = theAllocator.makeArray!int(only(42, 43, 44));
+    assert(a == [42, 43, 44]);
+}
+
+unittest
+{
+    void test(A)(auto ref A alloc)
+    {
+        long[] a = alloc.makeArray!long(0, 42);
+        assert(a.length == 0 && a.ptr is null);
+        a = alloc.makeArray!long(5, 42);
+        assert(a.length == 5);
+        assert(a == [ 42, 42, 42, 42, 42 ]);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+/// Ditto
+T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range)
+if (isInputRange!R)
+{
+    static if (isForwardRange!R)
+    {
+        size_t length = walkLength(range.save);
+        if (!length) return null;
+        auto m = alloc.allocate(T.sizeof * length);
+        if (!m.ptr) return null;
+        auto result = cast(T[]) m;
+
+        size_t i = 0;
+        scope (failure)
+        {
+            foreach (j; 0 .. i)
+            {
+                destroy(result[j]);
+            }
+            alloc.deallocate(m);
+        }
+
+        for (; !range.empty; range.popFront, ++i)
+        {
+            import std.conv : emplace;
+            emplace!T(result.ptr + i, range.front);
+        }
+
+        return result;
+    }
+    else
+    {
+        // Estimated size
+        size_t estimated = 8;
+        auto m = alloc.allocate(T.sizeof * estimated);
+        if (!m.ptr) return null;
+        auto result = cast(T[]) m;
+
+        size_t initialized = 0;
+        void bailout()
+        {
+            foreach (i; 0 .. initialized)
+            {
+                destroy(result[i]);
+            }
+            alloc.deallocate(m);
+        }
+        scope (failure) bailout;
+
+        for (; !range.empty; range.popFront, ++initialized)
+        {
+            if (initialized == estimated)
+            {
+                // Need to reallocate
+                if (!alloc.reallocate(m, T.sizeof * (estimated *= 2)))
+                {
+                    bailout;
+                    return null;
+                }
+                result = cast(T[]) m;
+            }
+            import std.conv : emplace;
+            emplace!T(result.ptr + initialized, range.front);
+        }
+
+        // Try to shrink memory, no harm if not possible
+        if (initialized < estimated
+            && alloc.reallocate(m, T.sizeof * initialized))
+        {
+            result = cast(T[]) m;
+        }
+
+        return result[0 .. initialized];
+    }
+}
+
+unittest
+{
+    void test(A)(auto ref A alloc)
+    {
+        long[] a = alloc.makeArray!long((int[]).init);
+        assert(a.length == 0 && a.ptr is null);
+        a = alloc.makeArray!long([5, 42]);
+        assert(a.length == 2);
+        assert(a == [ 5, 42]);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+version(unittest)
+{
+    private struct ForcedInputRange
+    {
+        int[]* array;
+        bool empty() { return !array || (*array).empty; }
+        ref int front() { return (*array)[0]; }
+        void popFront() { *array = (*array)[1 .. $]; }
+    }
+}
+
+unittest
+{
+    import std.array : array;
+    import std.range : iota;
+    int[] arr = iota(10).array;
+
+    void test(A)(auto ref A alloc)
+    {
+        ForcedInputRange r;
+        long[] a = alloc.makeArray!long(r);
+        assert(a.length == 0 && a.ptr is null);
+        auto arr2 = arr;
+        r.array = &arr2;
+        a = alloc.makeArray!long(r);
+        assert(a.length == 10);
+        assert(a == iota(10).array);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+/**
+Grows $(D array) by appending $(D delta) more elements. The needed memory is
+allocated using $(D alloc). The extra elements added are either default-
+initialized, filled with copies of $(D init), or initialized with values
+fetched from `range`.
+
+Params:
+T = element type of the array being created
+alloc = the allocator used for getting memory
+array = a reference to the array being grown
+delta = number of elements to add (upon success the new length of $(D array) is
+$(D array.length + delta))
+init = element used for filling the array
+range = range used for initializing the array elements
+
+Returns:
+$(D true) upon success, $(D false) if memory could not be allocated. In the
+latter case $(D array) is left unaffected.
+
+Throws:
+The first two overloads throw only if `alloc`'s primitives do. The
+overloads that involve copy initialization deallocate memory and propagate the
+exception if the copy operation throws.
+*/
+bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array,
+        size_t delta)
+{
+    if (!delta) return true;
+    immutable oldLength = array.length;
+    void[] buf = array;
+    if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false;
+    array = cast(T[]) buf;
+    array[oldLength .. $].uninitializedFillDefault;
+    return true;
+}
+
+unittest
+{
+    void test(A)(auto ref A alloc)
+    {
+        auto arr = alloc.makeArray!int([1, 2, 3]);
+        assert(alloc.expandArray(arr, 3));
+        assert(arr == [1, 2, 3, 0, 0, 0]);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+/// Ditto
+bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array,
+    size_t delta, auto ref T init)
+{
+    if (!delta) return true;
+    void[] buf = array;
+    if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false;
+    immutable oldLength = array.length;
+    array = cast(T[]) buf;
+    scope(failure) array[oldLength .. $].uninitializedFillDefault;
+    import std.algorithm : uninitializedFill;
+    array[oldLength .. $].uninitializedFill(init);
+    return true;
+}
+
+unittest
+{
+    void test(A)(auto ref A alloc)
+    {
+        auto arr = alloc.makeArray!int([1, 2, 3]);
+        assert(alloc.expandArray(arr, 3, 1));
+        assert(arr == [1, 2, 3, 1, 1, 1]);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+/// Ditto
+bool expandArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array,
+        R range)
+if (isInputRange!R)
+{
+    static if (isForwardRange!R)
+    {
+        immutable delta = walkLength(range.save);
+        if (!delta) return true;
+        immutable oldLength = array.length;
+
+        // Reallocate support memory
+        void[] buf = array;
+        if (!alloc.reallocate(buf, buf.length + T.sizeof * delta))
+        {
+            return false;
+        }
+        array = cast(T[]) buf;
+        // At this point we're committed to the new length.
+
+        auto toFill = array[oldLength .. $];
+        scope (failure)
+        {
+            // Fill the remainder with default-constructed data
+            toFill.uninitializedFillDefault;
+        }
+
+        for (; !range.empty; range.popFront, toFill.popFront)
+        {
+            assert(!toFill.empty);
+            import std.conv : emplace;
+            emplace!T(&toFill.front, range.front);
+        }
+        assert(toFill.empty);
+    }
+    else
+    {
+        scope(failure)
+        {
+            // The last element didn't make it, fill with default
+            array[$ - 1 .. $].uninitializedFillDefault;
+        }
+        void[] buf = array;
+        for (; !range.empty; range.popFront)
+        {
+            if (!alloc.reallocate(buf, buf.length + T.sizeof))
+            {
+                array = cast(T[]) buf;
+                return false;
+            }
+            import std.conv : emplace;
+            emplace!T(buf[$ - T.sizeof .. $], range.front);
+        }
+
+        array = cast(T[]) buf;
+    }
+    return true;
+}
+
+///
+unittest
+{
+    auto arr = theAllocator.makeArray!int([1, 2, 3]);
+    assert(theAllocator.expandArray(arr, 2));
+    assert(arr == [1, 2, 3, 0, 0]);
+    import std.range : only;
+    assert(theAllocator.expandArray(arr, only(4, 5)));
+    assert(arr == [1, 2, 3, 0, 0, 4, 5]);
+
+    ForcedInputRange r;
+    int[] b = [ 1, 2, 3, 4 ];
+    auto temp = b;
+    r.array = &temp;
+    assert(theAllocator.expandArray(arr, r));
+    assert(arr == [1, 2, 3, 0, 0, 4, 5, 1, 2, 3, 4]);
+}
+
+/**
+Shrinks an array by $(D delta) elements.
+
+If $(D array.length < delta), does nothing and returns `false`. Otherwise,
+destroys the last $(D array.length - delta) elements in the array and then
+reallocates the array's buffer. If reallocation fails, fills the array with
+default-initialized data.
+
+Params:
+T = element type of the array being created
+alloc = the allocator used for getting memory
+array = a reference to the array being shrunk
+delta = number of elements to remove (upon success the new length of $(D array) is $(D array.length - delta))
+
+Returns:
+`true` upon success, `false` if memory could not be reallocated. In the latter
+case, the slice $(D array[$ - delta .. $]) is left with default-initialized
+elements.
+
+Throws:
+The first two overloads throw only if `alloc`'s primitives do. The
+overloads that involve copy initialization deallocate memory and propagate the
+exception if the copy operation throws.
+*/
+bool shrinkArray(T, Allocator)(auto ref Allocator alloc,
+        ref T[] array, size_t delta)
+{
+    if (delta > array.length) return false;
+
+    // Destroy elements. If a destructor throws, fill the already destroyed
+    // stuff with the default initializer.
+    {
+        size_t destroyed;
+        scope(failure)
+        {
+            array[$ - delta .. $][0 .. destroyed].uninitializedFillDefault;
+        }
+        foreach (ref e; array[$ - delta .. $])
+        {
+            e.destroy;
+            ++destroyed;
+        }
+    }
+
+    if (delta == array.length)
+    {
+        alloc.deallocate(array);
+        array = null;
+        return true;
+    }
+
+    void[] buf = array;
+    if (!alloc.reallocate(buf, buf.length - T.sizeof * delta))
+    {
+        // urgh, at least fill back with default
+        array[$ - delta .. $].uninitializedFillDefault;
+        return false;
+    }
+    array = cast(T[]) buf;
+    return true;
+}
+
+///
+unittest
+{
+    int[] a = theAllocator.makeArray!int(100, 42);
+    assert(a.length == 100);
+    assert(theAllocator.shrinkArray(a, 98));
+    assert(a.length == 2);
+    assert(a == [42, 42]);
+}
+
+unittest
+{
+    void test(A)(auto ref A alloc)
+    {
+        long[] a = alloc.makeArray!long((int[]).init);
+        assert(a.length == 0 && a.ptr is null);
+        a = alloc.makeArray!long(100, 42);
+        assert(alloc.shrinkArray(a, 98));
+        assert(a.length == 2);
+        assert(a == [ 42, 42]);
+    }
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    test(GCAllocator.instance);
+    test(theAllocator);
+}
+
+/**
+
+Destroys and then deallocates (using $(D alloc)) the object pointed to by a
+pointer, the class object referred to by a $(D class) or $(D interface)
+reference, or an entire array. It is assumed the respective entities had been
+allocated with the same allocator.
+
+*/
+void dispose(A, T)(auto ref A alloc, T* p)
+{
+    static if (hasElaborateDestructor!T)
+    {
+        destroy(*p);
+    }
+    alloc.deallocate((cast(void*)p)[0 .. T.sizeof]);
+}
+
+/// Ditto
+void dispose(A, T)(auto ref A alloc, T p)
+if (is(T == class) || is(T == interface))
+{
+    if (!p) return;
+    static if (is(T == interface))
+    {
+        version(Windows)
+        {
+            import core.sys.windows.unknwn;
+            static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
+                ~ __PRETTY_FUNCTION__);
+        }
+        auto ob = cast(Object) p;
+    }
+    else
+        alias ob = p;
+    auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length];
+    destroy(p);
+    alloc.deallocate(support);
+}
+
+/// Ditto
+void dispose(A, T)(auto ref A alloc, T[] array)
+{
+    static if (hasElaborateDestructor!(typeof(array[0])))
+    {
+        foreach (ref e; array)
+        {
+            destroy(e);
+        }
+    }
+    alloc.deallocate(array);
+}
+
+unittest
+{
+    static int x;
+    static interface I
+    {
+        void method();
+    }
+    static class A : I
+    {
+        int y;
+        override void method() { x = 21; }
+        ~this() { x = 42; }
+    }
+    static class B : A
+    {
+    }
+    auto a = theAllocator.make!A;
+    a.method();
+    assert(x == 21);
+    theAllocator.dispose(a);
+    assert(x == 42);
+
+    B b = theAllocator.make!B;
+    b.method();
+    assert(x == 21);
+    theAllocator.dispose(b);
+    assert(x == 42);
+
+    I i = theAllocator.make!B;
+    i.method();
+    assert(x == 21);
+    theAllocator.dispose(i);
+    assert(x == 42);
+
+    int[] arr = theAllocator.makeArray!int(43);
+    theAllocator.dispose(arr);
+}
+
+unittest //bugzilla 15721
+{
+    import std.experimental.allocator.mallocator: Mallocator;
+
+    interface Foo {}
+    class Bar: Foo {}
+
+    Bar bar;
+    Foo foo;
+    bar = Mallocator.instance.make!Bar;
+    foo = cast(Foo) bar;
+    Mallocator.instance.dispose(foo);
+}
+
+/**
+
+Returns a dynamically-typed $(D CAllocator) built around a given statically-
+typed allocator $(D a) of type $(D A). Passing a pointer to the allocator
+creates a dynamic allocator around the allocator pointed to by the pointer,
+without attempting to copy or move it. Passing the allocator by value or
+reference behaves as follows.
+
+$(UL
+$(LI If $(D A) has no state, the resulting object is allocated in static
+shared storage.)
+$(LI If $(D A) has state and is copyable, the result will store a copy of it
+within. The result itself is allocated in its own statically-typed allocator.)
+$(LI If $(D A) has state and is not copyable, the result will move the
+passed-in argument into the result. The result itself is allocated in its own
+statically-typed allocator.)
+)
+
+*/
+CAllocatorImpl!A allocatorObject(A)(auto ref A a)
+if (!isPointer!A)
+{
+    import std.conv : emplace;
+    static if (stateSize!A == 0)
+    {
+        enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof);
+        static __gshared ulong[s] state;
+        static __gshared CAllocatorImpl!A result;
+        if (!result)
+        {
+            // Don't care about a few races
+            result = emplace!(CAllocatorImpl!A)(state[]);
+        }
+        assert(result);
+        return result;
+    }
+    else static if (is(typeof({ A b = a; A c = b; }))) // copyable
+    {
+        auto state = a.allocate(stateSize!(CAllocatorImpl!A));
+        import std.traits : hasMember;
+        static if (hasMember!(A, "deallocate"))
+        {
+            scope(failure) a.deallocate(state);
+        }
+        return cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state);
+    }
+    else // the allocator object is not copyable
+    {
+        // This is sensitive... create on the stack and then move
+        enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof);
+        ulong[s] state;
+        import std.algorithm : move;
+        emplace!(CAllocatorImpl!A)(state[], move(a));
+        auto dynState = a.allocate(stateSize!(CAllocatorImpl!A));
+        // Bitblast the object in its final destination
+        dynState[] = state[];
+        return cast(CAllocatorImpl!A) dynState.ptr;
+    }
+}
+
+/// Ditto
+CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa)
+{
+    assert(pa);
+    import std.conv : emplace;
+    auto state = pa.allocate(stateSize!(CAllocatorImpl!(A, Yes.indirect)));
+    import std.traits : hasMember;
+    static if (hasMember!(A, "deallocate"))
+    {
+        scope(failure) pa.deallocate(state);
+    }
+    return emplace!(CAllocatorImpl!(A, Yes.indirect))
+        (state, pa);
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.mallocator : Mallocator;
+    IAllocator a = allocatorObject(Mallocator.instance);
+    auto b = a.allocate(100);
+    assert(b.length == 100);
+    assert(a.deallocate(b));
+
+    // The in-situ region must be used by pointer
+    import std.experimental.allocator.building_blocks.region : InSituRegion;
+    auto r = InSituRegion!1024();
+    a = allocatorObject(&r);
+    b = a.allocate(200);
+    assert(b.length == 200);
+    // In-situ regions can deallocate the last allocation
+    assert(a.deallocate(b));
+}
+
+/**
+
+Implementation of $(D IAllocator) using $(D Allocator). This adapts a
+statically-built allocator type to $(D IAllocator) that is directly usable by
+non-templated code.
+
+Usually $(D CAllocatorImpl) is used indirectly by calling
+$(LREF theAllocator).
+*/
+class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect)
+    : IAllocator
+{
+    import std.traits : hasMember;
+
+    /**
+    The implementation is available as a public member.
+    */
+    static if (indirect)
+    {
+        private Allocator* pimpl;
+        ref Allocator impl()
+        {
+            return *pimpl;
+        }
+        this(Allocator* pa)
+        {
+            pimpl = pa;
+        }
+    }
+    else
+    {
+        static if (stateSize!Allocator) Allocator impl;
+        else alias impl = Allocator.instance;
+    }
+
+    /// Returns $(D impl.alignment).
+    override @property uint alignment()
+    {
+        return impl.alignment;
+    }
+
+    /**
+    Returns $(D impl.goodAllocSize(s)).
+    */
+    override size_t goodAllocSize(size_t s)
+    {
+        return impl.goodAllocSize(s);
+    }
+
+    /**
+    Returns $(D impl.allocate(s)).
+    */
+    override void[] allocate(size_t s, TypeInfo ti = null)
+    {
+        return impl.allocate(s);
+    }
+
+    /**
+    If $(D impl.alignedAllocate) exists, calls it and returns the result.
+    Otherwise, always returns `null`.
+    */
+    override void[] alignedAllocate(size_t s, uint a)
+    {
+        static if (hasMember!(Allocator, "alignedAllocate"))
+            return impl.alignedAllocate(s, a);
+        else
+            return null;
+    }
+
+    /**
+    If `Allocator` implements `owns`, forwards to it. Otherwise, returns
+    `Ternary.unknown`.
+    */
+    override Ternary owns(void[] b)
+    {
+        static if (hasMember!(Allocator, "owns")) return impl.owns(b);
+        else return Ternary.unknown;
+    }
+
+    /// Returns $(D impl.expand(b, s)) if defined, $(D false) otherwise.
+    override bool expand(ref void[] b, size_t s)
+    {
+        static if (hasMember!(Allocator, "expand"))
+            return impl.expand(b, s);
+        else
+            return false;
+    }
+
+    /// Returns $(D impl.reallocate(b, s)).
+    override bool reallocate(ref void[] b, size_t s)
+    {
+        return impl.reallocate(b, s);
+    }
+
+    /// Forwards to $(D impl.alignedReallocate).
+    bool alignedReallocate(ref void[] b, size_t s, uint a)
+    {
+        static if (!hasMember!(Allocator, "alignedAllocate"))
+        {
+            return false;
+        }
+        else
+        {
+            return impl.alignedReallocate(b, s, a);
+        }
+    }
+
+    // Undocumented for now
+    Ternary resolveInternalPointer(void* p, ref void[] result)
+    {
+        static if (hasMember!(Allocator, "resolveInternalPointer"))
+        {
+            result = impl.resolveInternalPointer(p);
+            return Ternary(result.ptr !is null);
+        }
+        else
+        {
+            return Ternary.unknown;
+        }
+    }
+
+    /**
+    If $(D impl.deallocate) is not defined, returns $(D Ternary.unknown). If
+    $(D impl.deallocate) returns $(D void) (the common case), calls it and
+    returns $(D Ternary.yes). If $(D impl.deallocate) returns $(D bool), calls
+    it and returns $(D Ternary.yes) for $(D true), $(D Ternary.no) for $(D
+    false).
+    */
+    override bool deallocate(void[] b)
+    {
+        static if (hasMember!(Allocator, "deallocate"))
+        {
+            return impl.deallocate(b);
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+    Calls $(D impl.deallocateAll()) and returns $(D Ternary.yes) if defined,
+    otherwise returns $(D Ternary.unknown).
+    */
+    override bool deallocateAll()
+    {
+        static if (hasMember!(Allocator, "deallocateAll"))
+        {
+            return impl.deallocateAll();
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+    Forwards to $(D impl.empty()) if defined, otherwise returns
+    $(D Ternary.unknown).
+    */
+    override Ternary empty()
+    {
+        static if (hasMember!(Allocator, "empty"))
+        {
+            return Ternary(impl.empty);
+        }
+        else
+        {
+            return Ternary.unknown;
+        }
+    }
+
+    /**
+    Returns $(D impl.allocateAll()) if present, $(D null) otherwise.
+    */
+    override void[] allocateAll()
+    {
+        static if (hasMember!(Allocator, "allocateAll"))
+        {
+            return impl.allocateAll();
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
+
+// Example in intro above
+unittest
+{
+    // Allocate an int, initialize it with 42
+    int* p = theAllocator.make!int(42);
+    assert(*p == 42);
+    // Destroy and deallocate it
+    theAllocator.dispose(p);
+
+    // Allocate using the global process allocator
+    p = processAllocator.make!int(100);
+    assert(*p == 100);
+    // Destroy and deallocate
+    processAllocator.dispose(p);
+
+    // Create an array of 50 doubles initialized to -1.0
+    double[] arr = theAllocator.makeArray!double(50, -1.0);
+    // Append two zeros to it
+    theAllocator.expandArray(arr, 2, 0.0);
+    // On second thought, take that back
+    theAllocator.shrinkArray(arr, 2);
+    // Destroy and deallocate
+    theAllocator.dispose(arr);
+}
+
+__EOF__
+
+/**
+
+Stores an allocator object in thread-local storage (i.e. non-$(D shared) D
+global). $(D ThreadLocal!A) is a subtype of $(D A) so it appears to implement
+$(D A)'s allocator primitives.
+
+$(D A) must hold state, otherwise $(D ThreadLocal!A) refuses instantiation. This
+means e.g. $(D ThreadLocal!Mallocator) does not work because $(D Mallocator)'s
+state is not stored as members of $(D Mallocator), but instead is hidden in the
+C library implementation.
+
+*/
+struct ThreadLocal(A)
+{
+    static assert(stateSize!A,
+        typeof(A).stringof
+        ~ " does not have state so it cannot be used with ThreadLocal");
+
+    /**
+    The allocator instance.
+    */
+    static A instance;
+
+    /**
+    `ThreadLocal!A` is a subtype of `A` so it appears to implement `A`'s
+    allocator primitives.
+    */
+    alias instance this;
+
+    /**
+    `ThreadLocal` disables all constructors. The intended usage is
+    `ThreadLocal!A.instance`.
+    */
+    @disable this();
+    /// Ditto
+    @disable this(this);
+}
+
+///
+unittest
+{
+    static assert(!is(ThreadLocal!Mallocator));
+    static assert(!is(ThreadLocal!GCAllocator));
+    alias ThreadLocal!(FreeList!(GCAllocator, 0, 8)) Allocator;
+    auto b = Allocator.instance.allocate(5);
+    static assert(hasMember!(Allocator, "allocate"));
+}
+
+/*
+(Not public.)
+
+A binary search tree that uses no allocation of its own. Instead, it relies on
+user code to allocate nodes externally. Then $(D EmbeddedTree)'s primitives wire
+the nodes appropriately.
+
+Warning: currently $(D EmbeddedTree) is not using rebalancing, so it may
+degenerate. A red-black tree implementation storing the color with one of the
+pointers is planned for the future.
+*/
+private struct EmbeddedTree(T, alias less)
+{
+    static struct Node
+    {
+        T payload;
+        Node* left, right;
+    }
+
+    private Node* root;
+
+    private Node* insert(Node* n, ref Node* backref)
+    {
+        backref = n;
+        n.left = n.right = null;
+        return n;
+    }
+
+    Node* find(Node* data)
+    {
+        for (auto n = root; n; )
+        {
+            if (less(data, n))
+            {
+                n = n.left;
+            }
+            else if (less(n, data))
+            {
+                n = n.right;
+            }
+            else
+            {
+                return n;
+            }
+        }
+        return null;
+    }
+
+    Node* insert(Node* data)
+    {
+        if (!root)
+        {
+            root = data;
+            data.left = data.right = null;
+            return root;
+        }
+        auto n = root;
+        for (;;)
+        {
+            if (less(data, n))
+            {
+                if (!n.left)
+                {
+                    // Found insertion point
+                    return insert(data, n.left);
+                }
+                n = n.left;
+            }
+            else if (less(n, data))
+            {
+                if (!n.right)
+                {
+                    // Found insertion point
+                    return insert(data, n.right);
+                }
+                n = n.right;
+            }
+            else
+            {
+                // Found
+                return n;
+            }
+            if (!n) return null;
+        }
+    }
+
+    Node* remove(Node* data)
+    {
+        auto n = root;
+        Node* parent = null;
+        for (;;)
+        {
+            if (!n) return null;
+            if (less(data, n))
+            {
+                parent = n;
+                n = n.left;
+            }
+            else if (less(n, data))
+            {
+                parent = n;
+                n = n.right;
+            }
+            else
+            {
+                // Found
+                remove(n, parent);
+                return n;
+            }
+        }
+    }
+
+    private void remove(Node* n, Node* parent)
+    {
+        assert(n);
+        assert(!parent || parent.left == n || parent.right == n);
+        Node** referrer = parent
+            ? (parent.left == n ? &parent.left : &parent.right)
+            : &root;
+        if (!n.left)
+        {
+            *referrer = n.right;
+        }
+        else if (!n.right)
+        {
+            *referrer = n.left;
+        }
+        else
+        {
+            // Find the leftmost child in the right subtree
+            auto leftmost = n.right;
+            Node** leftmostReferrer = &n.right;
+            while (leftmost.left)
+            {
+                leftmostReferrer = &leftmost.left;
+                leftmost = leftmost.left;
+            }
+            // Unlink leftmost from there
+            *leftmostReferrer = leftmost.right;
+            // Link leftmost in lieu of n
+            leftmost.left = n.left;
+            leftmost.right = n.right;
+            *referrer = leftmost;
+        }
+    }
+
+    Ternary empty() const
+    {
+        return Ternary(!root);
+    }
+
+    void dump()
+    {
+        writeln(typeid(this), " @ ", cast(void*) &this);
+        dump(root, 3);
+    }
+
+    void dump(Node* r, uint indent)
+    {
+        write(repeat(' ', indent).array);
+        if (!r)
+        {
+            writeln("(null)");
+            return;
+        }
+        writeln(r.payload, " @ ", cast(void*) r);
+        dump(r.left, indent + 3);
+        dump(r.right, indent + 3);
+    }
+
+    void assertSane()
+    {
+        static bool isBST(Node* r, Node* lb, Node* ub)
+        {
+            if (!r) return true;
+            if (lb && !less(lb, r)) return false;
+            if (ub && !less(r, ub)) return false;
+            return isBST(r.left, lb, r) &&
+                isBST(r.right, r, ub);
+        }
+        if (isBST(root, null, null)) return;
+        dump;
+        assert(0);
+    }
+}
+
+unittest
+{
+    alias a = GCAllocator.instance;
+    alias Tree = EmbeddedTree!(int, (a, b) => a.payload < b.payload);
+    Tree t;
+    assert(t.empty);
+    int[] vals = [ 6, 3, 9, 1, 0, 2, 8, 11 ];
+    foreach (v; vals)
+    {
+        auto n = new Tree.Node(v, null, null);
+        assert(t.insert(n));
+        assert(n);
+        t.assertSane;
+    }
+    assert(!t.empty);
+    foreach (v; vals)
+    {
+        Tree.Node n = { v };
+        assert(t.remove(&n));
+        t.assertSane;
+    }
+    assert(t.empty);
+}
+
+/*
+
+$(D InternalPointersTree) adds a primitive on top of another allocator: calling
+$(D resolveInternalPointer(p)) returns the block within which the internal
+pointer $(D p) lies. Pointers right after the end of allocated blocks are also
+considered internal.
+
+The implementation stores three additional words with each allocation (one for
+the block size and two for search management).
+
+*/
+private struct InternalPointersTree(Allocator)
+{
+    alias Tree = EmbeddedTree!(size_t,
+        (a, b) => cast(void*) a + a.payload < cast(void*) b);
+    alias Parent = AffixAllocator!(Allocator, Tree.Node);
+
+    // Own state
+    private Tree blockMap;
+
+    alias alignment = Parent.alignment;
+
+    /**
+    The implementation is available as a public member.
+    */
+    static if (stateSize!Parent) Parent parent;
+    else alias parent = Parent.instance;
+
+    /// Allocator API.
+    void[] allocate(size_t bytes)
+    {
+        auto r = parent.allocate(bytes);
+        if (!r.ptr) return r;
+        Tree.Node* n = &parent.prefix(r);
+        n.payload = bytes;
+        blockMap.insert(n) || assert(0);
+        return r;
+    }
+
+    /// Ditto
+    bool deallocate(void[] b)
+    {
+        if (!b.ptr) return;
+        Tree.Node* n = &parent.prefix(b);
+        blockMap.remove(n) || assert(false);
+        parent.deallocate(b);
+        return true;
+    }
+
+    /// Ditto
+    static if (hasMember!(Allocator, "reallocate"))
+    bool reallocate(ref void[] b, size_t s)
+    {
+        auto n = &parent.prefix(b);
+        assert(n.payload == b.length);
+        blockMap.remove(n) || assert(0);
+        if (!parent.reallocate(b, s))
+        {
+            // Failed, must reinsert the same node in the tree
+            assert(n.payload == b.length);
+            blockMap.insert(n) || assert(0);
+            return false;
+        }
+        // Insert the new node
+        n = &parent.prefix(b);
+        n.payload = s;
+        blockMap.insert(n) || assert(0);
+        return true;
+    }
+
+    /// Ditto
+    Ternary owns(void[] b)
+    {
+        return Ternary(resolveInternalPointer(b.ptr) !is null);
+    }
+
+    /// Ditto
+    Ternary empty()
+    {
+        return Ternary(blockMap.empty);
+    }
+
+    /** Returns the block inside which $(D p) resides, or $(D null) if the
+    pointer does not belong.
+    */
+    void[] resolveInternalPointer(void* p)
+    {
+        // Must define a custom find
+        Tree.Node* find()
+        {
+            for (auto n = blockMap.root; n; )
+            {
+                if (p < n)
+                {
+                    n = n.left;
+                }
+                else if (p > (cast(void*) (n + 1)) + n.payload)
+                {
+                    n = n.right;
+                }
+                else
+                {
+                    return n;
+                }
+            }
+            return null;
+        }
+
+        auto n = find();
+        if (!n) return null;
+        return (cast(void*) (n + 1))[0 .. n.payload];
+    }
+}
+
+unittest
+{
+    InternalPointersTree!(Mallocator) a;
+    int[] vals = [ 6, 3, 9, 1, 2, 8, 11 ];
+    void[][] allox;
+    foreach (v; vals)
+    {
+        allox ~= a.allocate(v);
+    }
+    a.blockMap.assertSane;
+
+    foreach (b; allox)
+    {
+        auto p = a.resolveInternalPointer(b.ptr);
+        assert(p.ptr is b.ptr && p.length >= b.length);
+        p = a.resolveInternalPointer(b.ptr + b.length);
+        assert(p.ptr is b.ptr && p.length >= b.length);
+        p = a.resolveInternalPointer(b.ptr + b.length / 2);
+        assert(p.ptr is b.ptr && p.length >= b.length);
+        auto bogus = new void[b.length];
+        assert(a.resolveInternalPointer(bogus.ptr) is null);
+    }
+
+    foreach (b; allox.randomCover)
+    {
+        a.deallocate(b);
+    }
+
+    assert(a.empty);
+}
+
+//version (std_allocator_benchmark)
+unittest
+{
+    static void testSpeed(A)()
+    {
+        static if (stateSize!A) A a;
+        else alias a = A.instance;
+
+        void[][128] bufs;
+
+        import std.random;
+        foreach (i; 0 .. 100_000)
+        {
+            auto j = uniform(0, bufs.length);
+            switch (uniform(0, 2))
+            {
+            case 0:
+                a.deallocate(bufs[j]);
+                bufs[j] = a.allocate(uniform(0, 4096));
+                break;
+            case 1:
+                a.deallocate(bufs[j]);
+                bufs[j] = null;
+                break;
+            default:
+                assert(0);
+            }
+        }
+    }
+
+    alias FList = FreeList!(GCAllocator, 0, unbounded);
+    alias A = Segregator!(
+        8, FreeList!(GCAllocator, 0, 8),
+        128, Bucketizer!(FList, 1, 128, 16),
+        256, Bucketizer!(FList, 129, 256, 32),
+        512, Bucketizer!(FList, 257, 512, 64),
+        1024, Bucketizer!(FList, 513, 1024, 128),
+        2048, Bucketizer!(FList, 1025, 2048, 256),
+        3584, Bucketizer!(FList, 2049, 3584, 512),
+        4072 * 1024, AllocatorList!(
+            (size_t n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate(
+                max(n, 4072 * 1024)))),
+        GCAllocator
+    );
+
+    import std.datetime, std.experimental.allocator.null_allocator;
+    if (false) writeln(benchmark!(
+        testSpeed!NullAllocator,
+        testSpeed!Mallocator,
+        testSpeed!GCAllocator,
+        testSpeed!(ThreadLocal!A),
+        testSpeed!(A),
+    )(20)[].map!(t => t.to!("seconds", double)));
+}
+
+unittest
+{
+    auto a = allocatorObject(Mallocator.instance);
+    auto b = a.allocate(100);
+    assert(b.length == 100);
+
+    FreeList!(GCAllocator, 0, 8) fl;
+    auto sa = allocatorObject(fl);
+    b = a.allocate(101);
+    assert(b.length == 101);
+
+    FallbackAllocator!(InSituRegion!(10240, 64), GCAllocator) fb;
+    // Doesn't work yet...
+    //a = allocatorObject(fb);
+    //b = a.allocate(102);
+    //assert(b.length == 102);
+}
+
+///
+unittest
+{
+    /// Define an allocator bound to the built-in GC.
+    IAllocator alloc = allocatorObject(GCAllocator.instance);
+    auto b = alloc.allocate(42);
+    assert(b.length == 42);
+    assert(alloc.deallocate(b) == Ternary.yes);
+
+    // Define an elaborate allocator and bind it to the class API.
+    // Note that the same variable "alloc" is used.
+    alias FList = FreeList!(GCAllocator, 0, unbounded);
+    alias A = ThreadLocal!(
+        Segregator!(
+            8, FreeList!(GCAllocator, 0, 8),
+            128, Bucketizer!(FList, 1, 128, 16),
+            256, Bucketizer!(FList, 129, 256, 32),
+            512, Bucketizer!(FList, 257, 512, 64),
+            1024, Bucketizer!(FList, 513, 1024, 128),
+            2048, Bucketizer!(FList, 1025, 2048, 256),
+            3584, Bucketizer!(FList, 2049, 3584, 512),
+            4072 * 1024, AllocatorList!(
+                (n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate(
+                    max(n, 4072 * 1024)))),
+            GCAllocator
+        )
+    );
+
+    auto alloc2 = allocatorObject(A.instance);
+    b = alloc.allocate(101);
+    assert(alloc.deallocate(b) == Ternary.yes);
+}
diff --git a/libphobos/src/std/experimental/allocator/showcase.d b/libphobos/src/std/experimental/allocator/showcase.d
new file mode 100644
index 00000000000..f463f125e38
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/showcase.d
@@ -0,0 +1,92 @@
+/**
+
+Collection of typical and useful prebuilt allocators using the given
+components. User code would typically import this module and use its
+facilities, or import individual heap building blocks and assemble them.
+
+*/
+module std.experimental.allocator.showcase;
+
+import std.experimental.allocator.building_blocks.fallback_allocator,
+    std.experimental.allocator.gc_allocator,
+    std.experimental.allocator.building_blocks.region;
+import std.traits : hasMember;
+
+/**
+
+Allocator that uses stack allocation for up to $(D stackSize) bytes and
+then falls back to $(D Allocator). Defined as:
+
+----
+alias StackFront(size_t stackSize, Allocator) =
+    FallbackAllocator!(
+        InSituRegion!(stackSize, Allocator.alignment,
+            hasMember!(Allocator, "deallocate")
+                ? Yes.defineDeallocate
+                : No.defineDeallocate),
+        Allocator);
+----
+
+Choosing `stackSize` is as always a compromise. Too small a size exhausts the
+stack storage after a few allocations, after which there are no gains over the
+backup allocator. Too large a size increases the stack consumed by the thread
+and may end up worse off because it explores cold portions of the stack.
+
+*/
+alias StackFront(size_t stackSize, Allocator = GCAllocator) =
+    FallbackAllocator!(
+        InSituRegion!(stackSize, Allocator.alignment),
+        Allocator);
+
+///
+unittest
+{
+    StackFront!4096 a;
+    auto b = a.allocate(4000);
+    assert(b.length == 4000);
+    auto c = a.allocate(4000);
+    assert(c.length == 4000);
+    a.deallocate(b);
+    a.deallocate(c);
+}
+
+/**
+Creates a scalable `AllocatorList` of `Regions`, each having at least
+`bytesPerRegion` bytes. Allocation is very fast. This allocator does not offer
+`deallocate` but does free all regions in its destructor. It is recommended for
+short-lived batch applications that count on never running out of memory.
+*/
+auto mmapRegionList(size_t bytesPerRegion)
+{
+    static struct Factory
+    {
+        size_t bytesPerRegion;
+        private import std.algorithm : max;
+        private import std.experimental.allocator.building_blocks.region
+            : Region;
+        private import std.experimental.allocator.mmap_allocator
+            : MmapAllocator;
+        this(size_t n)
+        {
+            bytesPerRegion = n;
+        }
+        auto opCall(size_t n)
+        {
+            return Region!MmapAllocator(max(n, bytesPerRegion));
+        }
+    }
+    import std.experimental.allocator.building_blocks.allocator_list
+        : AllocatorList;
+    import std.experimental.allocator.building_blocks.null_allocator
+        : NullAllocator;
+    auto shop = Factory(bytesPerRegion);
+    return AllocatorList!(Factory, NullAllocator)(shop);
+}
+
+///
+unittest
+{
+    auto alloc = mmapRegionList(1024 * 1024);
+    const b = alloc.allocate(100);
+    assert(b.length == 100);
+}
diff --git a/libphobos/src/std/experimental/allocator/typed.d b/libphobos/src/std/experimental/allocator/typed.d
new file mode 100644
index 00000000000..22d8f67e53d
--- /dev/null
+++ b/libphobos/src/std/experimental/allocator/typed.d
@@ -0,0 +1,427 @@
+/**
+This module defines `TypedAllocator`, a statically-typed allocator that
+aggregates multiple untyped allocators and uses them depending on the static
+properties of the types allocated. For example, distinct allocators may be used
+for thread-local vs. thread-shared data, or for fixed-size data (`struct`,
+`class` objects) vs. resizable data (arrays).
+
+Macros:
+T2=$(TR <td style="text-align:left">$(D $1)</td> $(TD $(ARGS $+)))
+*/
+
+module std.experimental.allocator.typed;
+
+import std.experimental.allocator;
+import std.experimental.allocator.common;
+import std.traits : isPointer, hasElaborateDestructor;
+import std.typecons : Flag, Yes, No;
+import std.algorithm : min;
+import std.range : isInputRange, isForwardRange, walkLength, save, empty,
+    front, popFront;
+
+/**
+Allocation-related flags dictated by type characteristics. `TypedAllocator`
+deduces these flags from the type being allocated and uses the appropriate
+allocator accordingly.
+*/
+enum AllocFlag : uint
+{
+    init = 0,
+    /**
+    Fixed-size allocation (unlikely to get reallocated later). Examples: `int`,
+    `double`, any `struct` or `class` type. By default it is assumed that the
+    allocation is variable-size, i.e. susceptible to later reallocation
+    (for example all array types). This flag is advisory, i.e. in-place resizing
+    may be attempted for `fixedSize` allocations and may succeed. The flag is
+    just a hint to the compiler it may use allocation strategies that work well
+    with objects of fixed size.
+    */
+    fixedSize = 1,
+    /**
+    The type being allocated embeds no pointers. Examples: `int`, `int[]`, $(D
+    Tuple!(int, float)). The implicit conservative assumption is that the type
+    has members with indirections so it needs to be scanned if garbage
+    collected. Example of types with pointers: `int*[]`, $(D Tuple!(int,
+    string)).
+    */
+    hasNoIndirections = 4,
+    /**
+    By default it is conservatively assumed that allocated memory may be `cast`
+    to `shared`, passed across threads, and deallocated in a different thread
+    than the one that allocated it. If that's not the case, there are two
+    options. First, `immutableShared` means the memory is allocated for
+    `immutable` data and will be deallocated in the same thread it was
+    allocated in. Second, `threadLocal` means the memory is not to be shared
+    across threads at all. The two flags cannot be simultaneously present.
+    */
+    immutableShared = 8,
+    /// ditto
+    threadLocal = 16,
+}
+
+/**
+`TypedAllocator` acts like a chassis on which several specialized allocators
+can be assembled. To let the system make a choice about a particular kind of
+allocation, use `Default` for the respective parameters.
+
+There is a hierarchy of allocation kinds. When an allocator is implemented for
+a given combination of flags, it is used. Otherwise, the next down the list is
+chosen.
+
+$(BOOKTABLE ,
+
+$(TR $(TH `AllocFlag` combination) $(TH Description))
+
+$(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections
+|$(NBSP)AllocFlag.fixedSize,
+This is the most specific allocation policy: the memory being allocated is
+thread local, has no indirections at all, and will not be reallocated. Examples
+of types fitting this description: `int`, `double`, $(D Tuple!(int, long)), but
+not $(D Tuple!(int, string)), which contains an indirection.)
+
+$(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections,
+As above, but may be reallocated later. Examples of types fitting this
+description are $(D int[]), $(D double[]), $(D Tuple!(int, long)[]), but not
+$(D Tuple!(int, string)[]), which contains an indirection.)
+
+$(T2 AllocFlag.threadLocal,
+As above, but may embed indirections. Examples of types fitting this
+description are $(D int*[]), $(D Object[]), $(D Tuple!(int, string)[]).)
+
+$(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections
+|$(NBSP)AllocFlag.fixedSize,
+The type being allocated is `immutable` and has no pointers. The thread that
+allocated it must also deallocate it. Example: `immutable(int)`.)
+
+$(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections,
+As above, but the type may be appended to in the future. Example: `string`.)
+
+$(T2 AllocFlag.immutableShared,
+As above, but the type may embed references. Example: `immutable(Object)[]`.)
+
+$(T2 AllocFlag.hasNoIndirections |$(NBSP)AllocFlag.fixedSize,
+The type being allocated may be shared across threads, embeds no indirections,
+and has fixed size.)
+
+$(T2 AllocFlag.hasNoIndirections,
+The type being allocated may be shared across threads, may embed indirections,
+and has variable size.)
+
+$(T2 AllocFlag.fixedSize,
+The type being allocated may be shared across threads, may embed indirections,
+and has fixed size.)
+
+$(T2 0, The most conservative/general allocation: memory may be shared,
+deallocated in a different thread, may or may not be resized, and may embed
+references.)
+)
+
+Params:
+PrimaryAllocator = The default allocator.
+Policies = Zero or more pairs consisting of an `AllocFlag` and an allocator
+type.
+*/
+struct TypedAllocator(PrimaryAllocator, Policies...)
+{
+    import std.typecons : Tuple;
+    import std.meta : AliasSeq;
+    import std.algorithm.sorting : isSorted;
+
+    static assert(Policies.length == 0 || isSorted([Stride2!Policies]));
+
+    private template Stride2(T...)
+    {
+        static if (T.length >= 2)
+        {
+            alias Stride2 = AliasSeq!(T[0], Stride2!(T[2 .. $]));
+        }
+        else
+        {
+            alias Stride2 = AliasSeq!(T[0 .. $]);
+        }
+    }
+
+    // state {
+    static if (stateSize!PrimaryAllocator) private PrimaryAllocator primary;
+    else alias primary = PrimaryAllocator.instance;
+    static if (Policies.length > 0)
+        private Tuple!(Stride2!(Policies[1 .. $])) extras;
+    // }
+
+    //pragma(msg, "Allocators available: ", typeof(extras));
+
+    private static bool match(uint have, uint want)
+    {
+        enum uint maskAway =
+            ~(AllocFlag.immutableShared | AllocFlag.threadLocal);
+        // Do we offer thread local?
+        if (have & AllocFlag.threadLocal)
+        {
+            if (want & AllocFlag.threadLocal)
+                return match(have & maskAway, want & maskAway);
+            return false;
+        }
+        if (have & AllocFlag.immutableShared)
+        {
+            // Okay to ask for either thread local or immutable shared
+            if (want & (AllocFlag.threadLocal
+                    | AllocFlag.immutableShared))
+                return match(have & maskAway, want & maskAway);
+            return false;
+        }
+        // From here on we have full-blown thread sharing.
+        if (have & AllocFlag.hasNoIndirections)
+        {
+            if (want & AllocFlag.hasNoIndirections)
+                return match(have & ~AllocFlag.hasNoIndirections,
+                    want & ~AllocFlag.hasNoIndirections);
+            return false;
+        }
+        // Fixed size or variable size both match.
+        return true;
+    }
+
+    /**
+    Given `flags` as a combination of `AllocFlag` values, or a type `T`, returns
+    the allocator that's a closest fit in capabilities.
+    */
+    auto ref allocatorFor(uint flags)()
+    {
+        static if (Policies.length == 0 || !match(Policies[0], flags))
+        {
+            return primary;
+        }
+        else static if (Policies.length && match(Policies[$ - 2], flags))
+        {
+            return extras[$ - 1];
+        }
+        else
+        {
+            foreach (i, choice; Stride2!Policies)
+            {
+                static if (!match(choice, flags))
+                {
+                    return extras[i - 1];
+                }
+            }
+            assert(0);
+        }
+    }
+
+    /// ditto
+    auto ref allocatorFor(T)()
+    {
+        static if (is(T == void[]))
+        {
+            return primary;
+        }
+        else
+        {
+            return allocatorFor!(type2flags!T)();
+        }
+    }
+
+    /**
+    Given a type `T`, returns its allocation-related flags as a combination of
+    `AllocFlag` values.
+    */
+    static uint type2flags(T)()
+    {
+        uint result;
+        static if (is(T == immutable))
+            result |= AllocFlag.immutableShared;
+        else static if (is(T == shared))
+            result |= AllocFlag.forSharing;
+        static if (!is(T == U[], U))
+            result |= AllocFlag.fixedSize;
+        import std.traits : hasIndirections;
+        static if (!hasIndirections!T)
+            result |= AllocFlag.hasNoIndirections;
+        return result;
+    }
+
+    /**
+    Dynamically allocates (using the appropriate allocator chosen with
+    `allocatorFor!T`) and then creates in the memory allocated an object of
+    type `T`, using `args` (if any) for its initialization. Initialization
+    occurs in the memory allocated and is otherwise semantically the same as
+    `T(args)`. (Note that using `make!(T[])` creates a pointer to an
+    (empty) array of `T`s, not an array. To allocate and initialize an
+    array, use `makeArray!T` described below.)
+
+    Params:
+    T = Type of the object being created.
+    args = Optional arguments used for initializing the created object. If not
+    present, the object is default constructed.
+
+    Returns: If `T` is a class type, returns a reference to the created `T`
+    object. Otherwise, returns a `T*` pointing to the created object. In all
+    cases, returns `null` if allocation failed.
+
+    Throws: If `T`'s constructor throws, deallocates the allocated memory and
+    propagates the exception.
+    */
+    auto make(T, A...)(auto ref A args)
+    {
+        return .make!T(allocatorFor!T, args);
+    }
+
+    /**
+    Create an array of `T` with `length` elements. The array is either
+    default-initialized, filled with copies of `init`, or initialized with
+    values fetched from `range`.
+
+    Params:
+    T = element type of the array being created
+    length = length of the newly created array
+    init = element used for filling the array
+    range = range used for initializing the array elements
+
+    Returns:
+    The newly-created array, or `null` if either `length` was `0` or
+    allocation failed.
+
+    Throws:
+    The first two overloads throw only if the used allocator's primitives do.
+    The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws.
+    */
+    T[] makeArray(T)(size_t length)
+    {
+        return .makeArray!T(allocatorFor!(T[]), length);
+    }
+
+    /// Ditto
+    T[] makeArray(T)(size_t length, auto ref T init)
+    {
+        return .makeArray!T(allocatorFor!(T[]), init, length);
+    }
+
+    /// Ditto
+    T[] makeArray(T, R)(R range)
+    if (isInputRange!R)
+    {
+        return .makeArray!T(allocatorFor!(T[]), range);
+    }
+
+    /**
+    Grows `array` by appending `delta` more elements. The needed memory is
+    allocated using the same allocator that was used for the array type. The
+    extra elements added are either default-initialized, filled with copies of
+    `init`, or initialized with values fetched from `range`.
+
+    Params:
+    T = element type of the array being created
+    array = a reference to the array being grown
+    delta = number of elements to add (upon success the new length of `array`
+    is $(D array.length + delta))
+    init = element used for filling the array
+    range = range used for initializing the array elements
+
+    Returns:
+    `true` upon success, `false` if memory could not be allocated. In the
+    latter case `array` is left unaffected.
+
+    Throws:
+    The first two overloads throw only if the used allocator's primitives do.
+    The overloads that involve copy initialization deallocate memory and
+    propagate the exception if the copy operation throws.
+    */
+    bool expandArray(T)(ref T[] array, size_t delta)
+    {
+        return .expandArray(allocatorFor!(T[]), array, delta);
+    }
+    /// Ditto
+    bool expandArray(T)(T[] array, size_t delta, auto ref T init)
+    {
+        return .expandArray(allocatorFor!(T[]), array, delta, init);
+    }
+    /// Ditto
+    bool expandArray(T, R)(ref T[] array, R range)
+    if (isInputRange!R)
+    {
+        return .expandArray(allocatorFor!(T[]), array, range);
+    }
+
+    /**
+    Shrinks an array by `delta` elements using `allocatorFor!(T[])`.
+
+    If $(D arr.length < delta), does nothing and returns `false`. Otherwise,
+    destroys the last $(D arr.length - delta) elements in the array and then
+    reallocates the array's buffer. If reallocation fails, fills the array with
+    default-initialized data.
+
+    Params:
+    T = element type of the array being created
+    arr = a reference to the array being shrunk
+    delta = number of elements to remove (upon success the new length of
+    `arr` is $(D arr.length - delta))
+
+    Returns:
+    `true` upon success, `false` if memory could not be reallocated. In the
+    latter case $(D arr[$ - delta .. $]) is left with default-initialized
+    elements.
+
+    Throws:
+    The first two overloads throw only if the used allocator's primitives do.
+    The overloads that involve copy initialization deallocate memory and
+    propagate the exception if the copy operation throws.
+    */
+    bool shrinkArray(T)(ref T[] arr, size_t delta)
+    {
+        return .shrinkArray(allocatorFor!(T[]), arr, delta);
+    }
+
+    /**
+    Destroys and then deallocates (using `allocatorFor!T`) the object pointed
+    to by a pointer, the class object referred to by a `class` or `interface`
+    reference, or an entire array. It is assumed the respective entities had
+    been allocated with the same allocator.
+    */
+    void dispose(T)(T* p)
+    {
+        return .dispose(allocatorFor!T, p);
+    }
+    /// Ditto
+    void dispose(T)(T p)
+    if (is(T == class) || is(T == interface))
+    {
+        return .dispose(allocatorFor!T, p);
+    }
+    /// Ditto
+    void dispose(T)(T[] array)
+    {
+        return .dispose(allocatorFor!(T[]), array);
+    }
+}
+
+///
+unittest
+{
+    import std.experimental.allocator.gc_allocator : GCAllocator;
+    import std.experimental.allocator.mallocator : Mallocator;
+    import std.experimental.allocator.mmap_allocator : MmapAllocator;
+    alias MyAllocator = TypedAllocator!(GCAllocator,
+        AllocFlag.fixedSize | AllocFlag.threadLocal, Mallocator,
+        AllocFlag.fixedSize | AllocFlag.threadLocal
+                | AllocFlag.hasNoIndirections,
+            MmapAllocator,
+    );
+    MyAllocator a;
+    auto b = &a.allocatorFor!0();
+    static assert(is(typeof(*b) == shared GCAllocator));
+    enum f1 = AllocFlag.fixedSize | AllocFlag.threadLocal;
+    auto c = &a.allocatorFor!f1();
+    static assert(is(typeof(*c) == Mallocator));
+    enum f2 = AllocFlag.fixedSize | AllocFlag.threadLocal;
+    static assert(is(typeof(a.allocatorFor!f2()) == Mallocator));
+    // Partial match
+    enum f3 = AllocFlag.threadLocal;
+    static assert(is(typeof(a.allocatorFor!f3()) == Mallocator));
+
+    int* p = a.make!int;
+    scope(exit) a.dispose(p);
+    int[] arr = a.makeArray!int(42);
+    scope(exit) a.dispose(arr);
+    assert(a.expandArray(arr, 3));
+    assert(a.shrinkArray(arr, 4));
+}
diff --git a/libphobos/src/std/experimental/logger/core.d b/libphobos/src/std/experimental/logger/core.d
new file mode 100644
index 00000000000..ca7f57e1f65
--- /dev/null
+++ b/libphobos/src/std/experimental/logger/core.d
@@ -0,0 +1,3058 @@
+module std.experimental.logger.core;
+
+import std.array;
+import std.stdio;
+import std.conv;
+import std.datetime;
+import std.string;
+import std.range;
+import std.traits;
+import std.exception;
+import std.concurrency;
+import std.format;
+import core.atomic;
+import core.sync.mutex : Mutex;
+
+import std.experimental.logger.filelogger;
+
+shared static this()
+{
+    stdSharedLoggerMutex = new Mutex;
+}
+
+/** This template evaluates if the passed $(D LogLevel) is active.
+The previously described version statements are used to decide if the
+$(D LogLevel) is active. The version statements only influence the compile
+unit they are used with, therefore this function can only disable logging this
+specific compile unit.
+*/
+template isLoggingActiveAt(LogLevel ll)
+{
+    version (StdLoggerDisableLogging)
+    {
+        enum isLoggingActiveAt = false;
+    }
+    else
+    {
+        static if (ll == LogLevel.trace)
+        {
+            version (StdLoggerDisableTrace) enum isLoggingActiveAt = false;
+        }
+        else static if (ll == LogLevel.info)
+        {
+            version (StdLoggerDisableInfo) enum isLoggingActiveAt = false;
+        }
+        else static if (ll == LogLevel.warning)
+        {
+            version (StdLoggerDisableWarning) enum isLoggingActiveAt = false;
+        }
+        else static if (ll == LogLevel.error)
+        {
+            version (StdLoggerDisableError) enum isLoggingActiveAt = false;
+        }
+        else static if (ll == LogLevel.critical)
+        {
+            version (StdLoggerDisableCritical) enum isLoggingActiveAt = false;
+        }
+        else static if (ll == LogLevel.fatal)
+        {
+            version (StdLoggerDisableFatal) enum isLoggingActiveAt = false;
+        }
+        // If `isLoggingActiveAt` didn't get defined above to false,
+        // we default it to true.
+        static if (!is(typeof(isLoggingActiveAt) == bool))
+        {
+            enum isLoggingActiveAt = true;
+        }
+    }
+}
+
+/// This compile-time flag is $(D true) if logging is not statically disabled.
+enum isLoggingActive = isLoggingActiveAt!(LogLevel.all);
+
+/** This functions is used at runtime to determine if a $(D LogLevel) is
+active. The same previously defined version statements are used to disable
+certain levels. Again the version statements are associated with a compile
+unit and can therefore not disable logging in other compile units.
+pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc
+*/
+bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL,
+    LogLevel globalLL, lazy bool condition = true) @safe
+{
+    switch (ll)
+    {
+        case LogLevel.trace:
+            version (StdLoggerDisableTrace) return false;
+            else break;
+        case LogLevel.info:
+            version (StdLoggerDisableInfo) return false;
+            else break;
+        case LogLevel.warning:
+            version (StdLoggerDisableWarning) return false;
+            else break;
+        case LogLevel.critical:
+            version (StdLoggerDisableCritical) return false;
+            else break;
+        case LogLevel.fatal:
+            version (StdLoggerDisableFatal) return false;
+            else break;
+        default: break;
+    }
+
+    return ll >= globalLL
+        && ll >= loggerLL
+        && ll != LogLevel.off
+        && globalLL != LogLevel.off
+        && loggerLL != LogLevel.off
+        && condition;
+}
+
+/** This template returns the $(D LogLevel) named "logLevel" of type $(D
+LogLevel) defined in a user defined module where the filename has the
+suffix "_loggerconfig.d". This $(D LogLevel) sets the minimal $(D LogLevel)
+of the module.
+
+A minimal $(D LogLevel) can be defined on a per module basis.
+In order to define a module $(D LogLevel) a file with a modulename
+"MODULENAME_loggerconfig" must be found. If no such module exists and the
+module is a nested module, it is checked if there exists a
+"PARENT_MODULE_loggerconfig" module with such a symbol.
+If this module exists and it contains a $(D LogLevel) called logLevel this $(D
+LogLevel) will be used. This parent lookup is continued until there is no
+parent module. Then the moduleLogLevel is $(D LogLevel.all).
+*/
+template moduleLogLevel(string moduleName) if (!moduleName.length)
+{
+    // default
+    enum moduleLogLevel = LogLevel.all;
+}
+
+///
+unittest
+{
+    static assert(moduleLogLevel!"" == LogLevel.all);
+}
+
+/// ditto
+template moduleLogLevel(string moduleName) if (moduleName.length)
+{
+    import std.string : format;
+    mixin(q{
+        static if (__traits(compiles, {import %1$s : logLevel;}))
+        {
+            import %1$s : logLevel;
+            static assert(is(typeof(logLevel) : LogLevel),
+                          "Expect 'logLevel' to be of Type 'LogLevel'.");
+            // don't enforce enum here
+            alias moduleLogLevel = logLevel;
+        }
+        else
+            // use logLevel of package or default
+            alias moduleLogLevel = moduleLogLevel!(parentOf(moduleName));
+    }.format(moduleName ~ "_loggerconfig"));
+}
+
+///
+unittest
+{
+    static assert(moduleLogLevel!"not.amodule.path" == LogLevel.all);
+}
+
+private string parentOf(string mod)
+{
+    foreach_reverse (i, c; mod)
+        if (c == '.') return mod[0 .. i];
+    return null;
+}
+
+/* This function formates a $(D SysTime) into an $(D OutputRange).
+
+The $(D SysTime) is formatted similar to
+$(LREF std.datatime.DateTime.toISOExtString) except the fractional second part.
+The fractional second part is in milliseconds and is always 3 digits.
+*/
+void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time)
+    if (isOutputRange!(OutputRange,string))
+{
+    const auto dt = cast(DateTime)time;
+    const auto fsec = time.fracSecs.total!"msecs";
+
+    formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d",
+        dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+        fsec);
+}
+
+/** This function logs data.
+
+In order for the data to be processed, the $(D LogLevel) of the log call must
+be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the
+$(D defaultLogLevel); additionally the condition passed must be $(D true).
+
+Params:
+  ll = The $(D LogLevel) used by this log call.
+  condition = The condition must be $(D true) for the data to be logged.
+  args = The data that should be logged.
+
+Example:
+--------------------
+log(LogLevel.warning, true, "Hello World", 3.1415);
+--------------------
+*/
+void log(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(const LogLevel ll,
+    lazy bool condition, lazy A args)
+    if (args.length != 1)
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+               stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
+                (ll, condition, args);
+        }
+    }
+}
+
+/// Ditto
+void log(T, string moduleName = __MODULE__)(const LogLevel ll,
+    lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__)
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName,
+                prettyFuncName);
+        }
+    }
+}
+
+/** This function logs data.
+
+In order for the data to be processed the $(D LogLevel) of the log call must
+be greater or equal to the $(D LogLevel) of the $(D sharedLog).
+
+Params:
+  ll = The $(D LogLevel) used by this log call.
+  args = The data that should be logged.
+
+Example:
+--------------------
+log(LogLevel.warning, "Hello World", 3.1415);
+--------------------
+*/
+void log(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args)
+    if (args.length > 1 && !is(Unqual!(A[0]) : bool))
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
+                (ll, args);
+        }
+    }
+}
+
+/// Ditto
+void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg,
+    int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__,
+    string prettyFuncName = __PRETTY_FUNCTION__)
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName,
+                moduleName);
+        }
+    }
+}
+
+/** This function logs data.
+
+In order for the data to be processed the $(D LogLevel) of the
+$(D sharedLog) must be greater or equal to the $(D defaultLogLevel)
+add the condition passed must be $(D true).
+
+Params:
+  condition = The condition must be $(D true) for the data to be logged.
+  args = The data that should be logged.
+
+Example:
+--------------------
+log(true, "Hello World", 3.1415);
+--------------------
+*/
+void log(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
+    if (args.length != 1)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
+            (stdThreadLocalLog.logLevel, condition, args);
+    }
+}
+
+/// Ditto
+void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg,
+    int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__,
+    string prettyFuncName = __PRETTY_FUNCTION__)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel,
+            condition, arg, line, file, funcName, prettyFuncName);
+    }
+}
+
+/** This function logs data.
+
+In order for the data to be processed the $(D LogLevel) of the
+$(D sharedLog) must be greater or equal to the $(D defaultLogLevel).
+
+Params:
+  args = The data that should be logged.
+
+Example:
+--------------------
+log("Hello World", 3.1415);
+--------------------
+*/
+void log(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(lazy A args)
+    if ((args.length > 1 && !is(Unqual!(A[0]) : bool)
+            && !is(Unqual!(A[0]) == LogLevel))
+        || args.length == 0)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.log!(line, file, funcName,
+           prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args);
+    }
+}
+
+void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file,
+            funcName, prettyFuncName, moduleName);
+    }
+}
+
+/** This function logs data in a $(D printf)-style manner.
+
+In order for the data to be processed the $(D LogLevel) of the log call must
+be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the
+$(D defaultLogLevel) additionally the condition passed must be $(D true).
+
+Params:
+  ll = The $(D LogLevel) used by this log call.
+  condition = The condition must be $(D true) for the data to be logged.
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+logf(LogLevel.warning, true, "Hello World %f", 3.1415);
+--------------------
+*/
+void logf(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__,
+    string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(const LogLevel ll,
+    lazy bool condition, lazy string msg, lazy A args)
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
+                (ll, condition, msg, args);
+        }
+    }
+}
+
+/** This function logs data in a $(D printf)-style manner.
+
+In order for the data to be processed the $(D LogLevel) of the log call must
+be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the
+$(D defaultLogLevel).
+
+Params:
+  ll = The $(D LogLevel) used by this log call.
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+logf(LogLevel.warning, "Hello World %f", 3.1415);
+--------------------
+*/
+void logf(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg,
+        lazy A args)
+{
+    static if (isLoggingActive)
+    {
+        if (ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
+                (ll, msg, args);
+        }
+    }
+}
+
+/** This function logs data in a $(D printf)-style manner.
+
+In order for the data to be processed the $(D LogLevel) of the log call must
+be greater or equal to the $(D defaultLogLevel) additionally the condition
+passed must be $(D true).
+
+Params:
+  condition = The condition must be $(D true) for the data to be logged.
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+logf(true, "Hello World %f", 3.1415);
+--------------------
+*/
+void logf(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(lazy bool condition,
+        lazy string msg, lazy A args)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
+            (stdThreadLocalLog.logLevel, condition, msg, args);
+    }
+}
+
+/** This function logs data in a $(D printf)-style manner.
+
+In order for the data to be processed the $(D LogLevel) of the log call must
+be greater or equal to the $(D defaultLogLevel).
+
+Params:
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+logf("Hello World %f", 3.1415);
+--------------------
+*/
+void logf(int line = __LINE__, string file = __FILE__,
+    string funcName = __FUNCTION__,
+    string prettyFuncName = __PRETTY_FUNCTION__,
+    string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
+{
+    static if (isLoggingActive)
+    {
+        stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName)
+            (stdThreadLocalLog.logLevel, msg, args);
+    }
+}
+
+/** This template provides the global log functions with the $(D LogLevel)
+is encoded in the function name.
+
+The aliases following this template create the public names of these log
+functions.
+*/
+template defaultLogFunction(LogLevel ll)
+{
+    void defaultLogFunction(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy A args)
+        if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0)
+    {
+        static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName,
+                prettyFuncName, moduleName)(args);
+        }
+    }
+
+    void defaultLogFunction(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
+    {
+        static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName,
+                prettyFuncName, moduleName)(condition, args);
+        }
+    }
+}
+
+/** This function logs data to the $(D stdThreadLocalLog), optionally depending
+on a condition.
+
+In order for the resulting log message to be logged the $(D LogLevel) must
+be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and
+must be greater or equal than the global $(D LogLevel).
+Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel)
+of the $(D stdSharedLogger).
+If a condition is given, it must evaluate to $(D true).
+
+Params:
+  condition = The condition must be $(D true) for the data to be logged.
+  args = The data that should be logged.
+
+Example:
+--------------------
+trace(1337, "is number");
+info(1337, "is number");
+error(1337, "is number");
+critical(1337, "is number");
+fatal(1337, "is number");
+trace(true, 1337, "is number");
+info(false, 1337, "is number");
+error(true, 1337, "is number");
+critical(false, 1337, "is number");
+fatal(true, 1337, "is number");
+--------------------
+*/
+alias trace = defaultLogFunction!(LogLevel.trace);
+/// Ditto
+alias info = defaultLogFunction!(LogLevel.info);
+/// Ditto
+alias warning = defaultLogFunction!(LogLevel.warning);
+/// Ditto
+alias error = defaultLogFunction!(LogLevel.error);
+/// Ditto
+alias critical = defaultLogFunction!(LogLevel.critical);
+/// Ditto
+alias fatal = defaultLogFunction!(LogLevel.fatal);
+
+/** This template provides the global $(D printf)-style log functions with
+the $(D LogLevel) is encoded in the function name.
+
+The aliases following this template create the public names of the log
+functions.
+*/
+template defaultLogFunctionf(LogLevel ll)
+{
+    void defaultLogFunctionf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
+    {
+        static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName,
+                prettyFuncName, moduleName)(msg, args);
+        }
+    }
+
+    void defaultLogFunctionf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy bool condition,
+            lazy string msg, lazy A args)
+    {
+        static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+        {
+            stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName,
+                prettyFuncName, moduleName)(condition, msg, args);
+        }
+    }
+}
+
+/** This function logs data to the $(D sharedLog) in a $(D printf)-style
+manner.
+
+In order for the resulting log message to be logged the $(D LogLevel) must
+be greater or equal than the $(D LogLevel) of the $(D sharedLog) and
+must be greater or equal than the global $(D LogLevel).
+Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel)
+of the $(D stdSharedLogger).
+
+Params:
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+tracef("is number %d", 1);
+infof("is number %d", 2);
+errorf("is number %d", 3);
+criticalf("is number %d", 4);
+fatalf("is number %d", 5);
+--------------------
+
+The second version of the function logs data to the $(D sharedLog) in a $(D
+printf)-style manner.
+
+In order for the resulting log message to be logged the $(D LogLevel) must
+be greater or equal than the $(D LogLevel) of the $(D sharedLog) and
+must be greater or equal than the global $(D LogLevel).
+Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel)
+of the $(D stdSharedLogger).
+
+Params:
+  condition = The condition must be $(D true) for the data to be logged.
+  msg = The $(D printf)-style string.
+  args = The data that should be logged.
+
+Example:
+--------------------
+tracef(false, "is number %d", 1);
+infof(false, "is number %d", 2);
+errorf(true, "is number %d", 3);
+criticalf(true, "is number %d", 4);
+fatalf(someFunct(), "is number %d", 5);
+--------------------
+*/
+alias tracef = defaultLogFunctionf!(LogLevel.trace);
+/// Ditto
+alias infof = defaultLogFunctionf!(LogLevel.info);
+/// Ditto
+alias warningf = defaultLogFunctionf!(LogLevel.warning);
+/// Ditto
+alias errorf = defaultLogFunctionf!(LogLevel.error);
+/// Ditto
+alias criticalf = defaultLogFunctionf!(LogLevel.critical);
+/// Ditto
+alias fatalf = defaultLogFunctionf!(LogLevel.fatal);
+
+private struct MsgRange
+{
+    import std.traits : isSomeString, isSomeChar;
+
+    private Logger log;
+
+    private char[] buffer;
+
+    this(Logger log) @safe
+    {
+        this.log = log;
+    }
+
+    void put(T)(T msg) @safe
+        if (isSomeString!T)
+    {
+        log.logMsgPart(msg);
+    }
+
+    void put(dchar elem) @safe
+    {
+        import std.utf : encode;
+        encode(this.buffer, elem);
+        log.logMsgPart(this.buffer);
+    }
+}
+
+private void formatString(A...)(MsgRange oRange, A args)
+{
+    import std.format : formattedWrite;
+
+    foreach (arg; args)
+    {
+        std.format.formattedWrite(oRange, "%s", arg);
+    }
+}
+
+unittest
+{
+    void dummy() @safe
+    {
+        auto tl = new TestLogger();
+        auto dst = MsgRange(tl);
+        formatString(dst, "aaa", "bbb");
+    }
+
+    dummy();
+}
+
+/**
+There are eight usable logging level. These level are $(I all), $(I trace),
+$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off).
+If a log function with $(D LogLevel.fatal) is called the shutdown handler of
+that logger is called.
+*/
+enum LogLevel : ubyte
+{
+    all = 1, /** Lowest possible assignable $(D LogLevel). */
+    trace = 32, /** $(D LogLevel) for tracing the execution of the program. */
+    info = 64, /** This level is used to display information about the
+                program. */
+    warning = 96, /** warnings about the program should be displayed with this
+                   level. */
+    error = 128, /** Information about errors should be logged with this
+                   level.*/
+    critical = 160, /** Messages that inform about critical errors should be
+                    logged with this level. */
+    fatal = 192,   /** Log messages that describe fatal errors should use this
+                  level. */
+    off = ubyte.max /** Highest possible $(D LogLevel). */
+}
+
+/** This class is the base of every logger. In order to create a new kind of
+logger a deriving class needs to implement the $(D writeLogMsg) method. By
+default this is not thread-safe.
+
+It is also possible to $(D override) the three methods $(D beginLogMsg),
+$(D logMsgPart) and $(D finishLogMsg) together, this option gives more
+flexibility.
+*/
+abstract class Logger
+{
+    /** LogEntry is a aggregation combining all information associated
+    with a log message. This aggregation will be passed to the method
+    writeLogMsg.
+    */
+    protected struct LogEntry
+    {
+        /// the filename the log function was called from
+        string file;
+        /// the line number the log function was called from
+        int line;
+        /// the name of the function the log function was called from
+        string funcName;
+        /// the pretty formatted name of the function the log function was
+        /// called from
+        string prettyFuncName;
+        /// the name of the module the log message is coming from
+        string moduleName;
+        /// the $(D LogLevel) associated with the log message
+        LogLevel logLevel;
+        /// thread id of the log message
+        Tid threadId;
+        /// the time the message was logged
+        SysTime timestamp;
+        /// the message of the log message
+        string msg;
+        /// A refernce to the $(D Logger) used to create this $(D LogEntry)
+        Logger logger;
+    }
+
+    /** This constructor takes a name of type $(D string), and a $(D LogLevel).
+
+    Every subclass of $(D Logger) has to call this constructor from their
+    constructor. It sets the $(D LogLevel), the name of the $(D Logger), and
+    creates a fatal handler. The fatal handler will throw an $(D Error) if a
+    log call is made with a $(D LogLevel) $(D LogLevel.fatal).
+    */
+    this(LogLevel lv) @safe
+    {
+        this.logLevel_ = lv;
+        this.fatalHandler_ = delegate() {
+            throw new Error("A fatal log message was logged");
+        };
+
+        this.mutex = new Mutex();
+    }
+
+    /** A custom logger must implement this method in order to work in a
+    $(D MultiLogger) and $(D ArrayLogger).
+
+    Params:
+      payload = All information associated with call to log function.
+
+    See_Also: beginLogMsg, logMsgPart, finishLogMsg
+    */
+    abstract protected void writeLogMsg(ref LogEntry payload) @safe;
+
+    /* The default implementation will use an $(D std.array.appender)
+    internally to construct the message string. This means dynamic,
+    GC memory allocation. A logger can avoid this allocation by
+    reimplementing $(D beginLogMsg), $(D logMsgPart) and $(D finishLogMsg).
+    $(D beginLogMsg) is always called first, followed by any number of calls
+    to $(D logMsgPart) and one call to $(D finishLogMsg).
+
+    As an example for such a custom $(D Logger) compare this:
+    ----------------
+    class CLogger : Logger
+    {
+        override void beginLogMsg(string file, int line, string funcName,
+            string prettyFuncName, string moduleName, LogLevel logLevel,
+            Tid threadId, SysTime timestamp)
+        {
+            ... logic here
+        }
+
+        override void logMsgPart(const(char)[] msg)
+        {
+            ... logic here
+        }
+
+        override void finishLogMsg()
+        {
+            ... logic here
+        }
+
+        void writeLogMsg(ref LogEntry payload)
+        {
+            this.beginLogMsg(payload.file, payload.line, payload.funcName,
+                payload.prettyFuncName, payload.moduleName, payload.logLevel,
+                payload.threadId, payload.timestamp, payload.logger);
+
+            this.logMsgPart(payload.msg);
+            this.finishLogMsg();
+        }
+    }
+    ----------------
+    */
+    protected void beginLogMsg(string file, int line, string funcName,
+        string prettyFuncName, string moduleName, LogLevel logLevel,
+        Tid threadId, SysTime timestamp, Logger logger)
+        @safe
+    {
+        static if (isLoggingActive)
+        {
+            msgAppender = appender!string();
+            header = LogEntry(file, line, funcName, prettyFuncName,
+                moduleName, logLevel, threadId, timestamp, null, logger);
+        }
+    }
+
+    /** Logs a part of the log message. */
+    protected void logMsgPart(const(char)[] msg) @safe
+    {
+        static if (isLoggingActive)
+        {
+               msgAppender.put(msg);
+        }
+    }
+
+    /** Signals that the message has been written and no more calls to
+    $(D logMsgPart) follow. */
+    protected void finishLogMsg() @safe
+    {
+        static if (isLoggingActive)
+        {
+            header.msg = msgAppender.data;
+            this.writeLogMsg(header);
+        }
+    }
+
+    /** The $(D LogLevel) determines if the log call are processed or dropped
+    by the $(D Logger). In order for the log call to be processed the
+    $(D LogLevel) of the log call must be greater or equal to the $(D LogLevel)
+    of the $(D logger).
+
+    These two methods set and get the $(D LogLevel) of the used $(D Logger).
+
+    Example:
+    -----------
+    auto f = new FileLogger(stdout);
+    f.logLevel = LogLevel.info;
+    assert(f.logLevel == LogLevel.info);
+    -----------
+    */
+    @property final LogLevel logLevel() const pure @safe @nogc
+    {
+        return trustedLoad(this.logLevel_);
+    }
+
+    /// Ditto
+    @property final void logLevel(const LogLevel lv) @safe @nogc
+    {
+        synchronized (mutex) this.logLevel_ = lv;
+    }
+
+    /** This $(D delegate) is called in case a log message with
+    $(D LogLevel.fatal) gets logged.
+
+    By default an $(D Error) will be thrown.
+    */
+    @property final void delegate() fatalHandler() @safe @nogc
+    {
+        synchronized (mutex) return this.fatalHandler_;
+    }
+
+    /// Ditto
+    @property final void fatalHandler(void delegate() @safe fh) @safe @nogc
+    {
+        synchronized (mutex) this.fatalHandler_ = fh;
+    }
+
+    /** This method allows forwarding log entries from one logger to another.
+
+    $(D forwardMsg) will ensure proper synchronization and then call
+    $(D writeLogMsg). This is an API for implementing your own loggers and
+    should not be called by normal user code. A notable difference from other
+    logging functions is that the $(D globalLogLevel) wont be evaluated again
+    since it is assumed that the caller already checked that.
+    */
+    void forwardMsg(ref LogEntry payload) @trusted
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(payload.logLevel, this.logLevel_,
+                globalLogLevel))
+            {
+                this.writeLogMsg(payload);
+
+                if (payload.logLevel == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This template provides the log functions for the $(D Logger) $(D class)
+    with the $(D LogLevel) encoded in the function name.
+
+    For further information see the the two functions defined inside of this
+    template.
+
+    The aliases following this template create the public names of these log
+    functions.
+    */
+    template memLogFunctions(LogLevel ll)
+    {
+        /** This function logs data to the used $(D Logger).
+
+        In order for the resulting log message to be logged the $(D LogLevel)
+        must be greater or equal than the $(D LogLevel) of the used $(D Logger)
+        and must be greater or equal than the global $(D LogLevel).
+
+        Params:
+          args = The data that should be logged.
+
+        Example:
+        --------------------
+        auto s = new FileLogger(stdout);
+        s.trace(1337, "is number");
+        s.info(1337, "is number");
+        s.error(1337, "is number");
+        s.critical(1337, "is number");
+        s.fatal(1337, "is number");
+        --------------------
+        */
+        void logImpl(int line = __LINE__, string file = __FILE__,
+            string funcName = __FUNCTION__,
+            string prettyFuncName = __PRETTY_FUNCTION__,
+            string moduleName = __MODULE__, A...)(lazy A args)
+            if (args.length == 0 || (args.length > 0 && !is(A[0] : bool)))
+        {
+            static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+                synchronized (mutex)
+            {
+                if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
+                {
+                    this.beginLogMsg(file, line, funcName, prettyFuncName,
+                        moduleName, ll, thisTid, Clock.currTime, this);
+
+                    auto writer = MsgRange(this);
+                    formatString(writer, args);
+
+                    this.finishLogMsg();
+
+                    static if (ll == LogLevel.fatal)
+                        this.fatalHandler_();
+                }
+            }
+        }
+
+        /** This function logs data to the used $(D Logger) depending on a
+        condition.
+
+        In order for the resulting log message to be logged the $(D LogLevel) must
+        be greater or equal than the $(D LogLevel) of the used $(D Logger) and
+        must be greater or equal than the global $(D LogLevel) additionally the
+        condition passed must be $(D true).
+
+        Params:
+          condition = The condition must be $(D true) for the data to be logged.
+          args = The data that should be logged.
+
+        Example:
+        --------------------
+        auto s = new FileLogger(stdout);
+        s.trace(true, 1337, "is number");
+        s.info(false, 1337, "is number");
+        s.error(true, 1337, "is number");
+        s.critical(false, 1337, "is number");
+        s.fatal(true, 1337, "is number");
+        --------------------
+        */
+        void logImpl(int line = __LINE__, string file = __FILE__,
+            string funcName = __FUNCTION__,
+            string prettyFuncName = __PRETTY_FUNCTION__,
+            string moduleName = __MODULE__, A...)(lazy bool condition,
+                lazy A args)
+        {
+            static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+                synchronized (mutex)
+            {
+                if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
+                                     condition))
+                {
+                    this.beginLogMsg(file, line, funcName, prettyFuncName,
+                        moduleName, ll, thisTid, Clock.currTime, this);
+
+                    auto writer = MsgRange(this);
+                    formatString(writer, args);
+
+                    this.finishLogMsg();
+
+                    static if (ll == LogLevel.fatal)
+                        this.fatalHandler_();
+                }
+            }
+        }
+
+        /** This function logs data to the used $(D Logger) in a
+        $(D printf)-style manner.
+
+        In order for the resulting log message to be logged the $(D LogLevel)
+        must be greater or equal than the $(D LogLevel) of the used $(D Logger)
+        and must be greater or equal than the global $(D LogLevel) additionally
+           the passed condition must be $(D true).
+
+        Params:
+          condition = The condition must be $(D true) for the data to be logged.
+          msg = The $(D printf)-style string.
+          args = The data that should be logged.
+
+        Example:
+        --------------------
+        auto s = new FileLogger(stderr);
+        s.tracef(true, "is number %d", 1);
+        s.infof(true, "is number %d", 2);
+        s.errorf(false, "is number %d", 3);
+        s.criticalf(someFunc(), "is number %d", 4);
+        s.fatalf(true, "is number %d", 5);
+        --------------------
+        */
+        void logImplf(int line = __LINE__, string file = __FILE__,
+            string funcName = __FUNCTION__,
+            string prettyFuncName = __PRETTY_FUNCTION__,
+            string moduleName = __MODULE__, A...)(lazy bool condition,
+                lazy string msg, lazy A args)
+        {
+            static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+                synchronized (mutex)
+            {
+                if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
+                                     condition))
+                {
+                    this.beginLogMsg(file, line, funcName, prettyFuncName,
+                        moduleName, ll, thisTid, Clock.currTime, this);
+
+                    auto writer = MsgRange(this);
+                    formattedWrite(writer, msg, args);
+
+                    this.finishLogMsg();
+
+                    static if (ll == LogLevel.fatal)
+                        this.fatalHandler_();
+                }
+            }
+        }
+
+        /** This function logs data to the used $(D Logger) in a
+        $(D printf)-style manner.
+
+        In order for the resulting log message to be logged the $(D LogLevel) must
+        be greater or equal than the $(D LogLevel) of the used $(D Logger) and
+        must be greater or equal than the global $(D LogLevel).
+
+        Params:
+          msg = The $(D printf)-style string.
+          args = The data that should be logged.
+
+        Example:
+        --------------------
+        auto s = new FileLogger(stderr);
+        s.tracef("is number %d", 1);
+        s.infof("is number %d", 2);
+        s.errorf("is number %d", 3);
+        s.criticalf("is number %d", 4);
+        s.fatalf("is number %d", 5);
+        --------------------
+        */
+        void logImplf(int line = __LINE__, string file = __FILE__,
+            string funcName = __FUNCTION__,
+            string prettyFuncName = __PRETTY_FUNCTION__,
+            string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
+        {
+            static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
+                synchronized (mutex)
+            {
+                if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
+                {
+                    this.beginLogMsg(file, line, funcName, prettyFuncName,
+                        moduleName, ll, thisTid, Clock.currTime, this);
+
+                    auto writer = MsgRange(this);
+                    formattedWrite(writer, msg, args);
+
+                    this.finishLogMsg();
+
+                    static if (ll == LogLevel.fatal)
+                        this.fatalHandler_();
+                }
+            }
+        }
+    }
+
+    /// Ditto
+    alias trace = memLogFunctions!(LogLevel.trace).logImpl;
+    /// Ditto
+    alias tracef = memLogFunctions!(LogLevel.trace).logImplf;
+    /// Ditto
+    alias info = memLogFunctions!(LogLevel.info).logImpl;
+    /// Ditto
+    alias infof = memLogFunctions!(LogLevel.info).logImplf;
+    /// Ditto
+    alias warning = memLogFunctions!(LogLevel.warning).logImpl;
+    /// Ditto
+    alias warningf = memLogFunctions!(LogLevel.warning).logImplf;
+    /// Ditto
+    alias error = memLogFunctions!(LogLevel.error).logImpl;
+    /// Ditto
+    alias errorf = memLogFunctions!(LogLevel.error).logImplf;
+    /// Ditto
+    alias critical = memLogFunctions!(LogLevel.critical).logImpl;
+    /// Ditto
+    alias criticalf = memLogFunctions!(LogLevel.critical).logImplf;
+    /// Ditto
+    alias fatal = memLogFunctions!(LogLevel.fatal).logImpl;
+    /// Ditto
+    alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf;
+
+    /** This method logs data with the $(D LogLevel) of the used $(D Logger).
+
+    This method takes a $(D bool) as first argument. In order for the
+    data to be processed the $(D bool) must be $(D true) and the $(D LogLevel)
+    of the Logger must be greater or equal to the global $(D LogLevel).
+
+    Params:
+      args = The data that should be logged.
+      condition = The condition must be $(D true) for the data to be logged.
+      args = The data that is to be logged.
+
+    Returns: The logger used by the logging function as reference.
+
+    Example:
+    --------------------
+    auto l = new StdioLogger();
+    l.log(1337);
+    --------------------
+    */
+    final void log(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(const LogLevel ll,
+        lazy bool condition, lazy A args)
+        if (args.length != 1)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /// Ditto
+    final void log(T, string moduleName = __MODULE__)(const LogLevel ll,
+        lazy bool condition, lazy T args, int line = __LINE__,
+        string file = __FILE__, string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
+                condition) && ll >= moduleLogLevel!moduleName)
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) with a specific
+    $(D LogLevel).
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    must be greater or equal than the $(D LogLevel) of the used $(D Logger)
+    and must be greater or equal than the global $(D LogLevel).
+
+    Params:
+      ll = The specific $(D LogLevel) used for logging the log message.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.log(LogLevel.trace, 1337, "is number");
+    s.log(LogLevel.info, 1337, "is number");
+    s.log(LogLevel.warning, 1337, "is number");
+    s.log(LogLevel.error, 1337, "is number");
+    s.log(LogLevel.fatal, 1337, "is number");
+    --------------------
+    */
+    final void log(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args)
+        if ((args.length > 1 && !is(Unqual!(A[0]) : bool)) || args.length == 0)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /// Ditto
+    final void log(T)(const LogLevel ll, lazy T args, int line = __LINE__,
+        string file = __FILE__, string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) depending on a
+    explicitly passed condition with the $(D LogLevel) of the used
+    $(D Logger).
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    of the used $(D Logger) must be greater or equal than the global
+    $(D LogLevel) and the condition must be $(D true).
+
+    Params:
+      condition = The condition must be $(D true) for the data to be logged.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.log(true, 1337, "is number");
+    s.log(true, 1337, "is number");
+    s.log(true, 1337, "is number");
+    s.log(false, 1337, "is number");
+    s.log(false, 1337, "is number");
+    --------------------
+    */
+    final void log(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
+        if (args.length != 1)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_,
+                globalLogLevel, condition))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /// Ditto
+    final void log(T)(lazy bool condition, lazy T args, int line = __LINE__,
+        string file = __FILE__, string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel,
+                condition))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) with the $(D LogLevel)
+    of the used $(D Logger).
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    of the used $(D Logger) must be greater or equal than the global
+    $(D LogLevel).
+
+    Params:
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.log(1337, "is number");
+    s.log(info, 1337, "is number");
+    s.log(1337, "is number");
+    s.log(1337, "is number");
+    s.log(1337, "is number");
+    --------------------
+    */
+    final void log(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy A args)
+        if ((args.length > 1
+                && !is(Unqual!(A[0]) : bool)
+                && !is(Unqual!(A[0]) == LogLevel))
+            || args.length == 0)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_,
+                globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+                auto writer = MsgRange(this);
+                formatString(writer, args);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /// Ditto
+    final void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+                auto writer = MsgRange(this);
+                formatString(writer, arg);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) with a specific
+    $(D LogLevel) and depending on a condition in a $(D printf)-style manner.
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    must be greater or equal than the $(D LogLevel) of the used $(D Logger)
+    and must be greater or equal than the global $(D LogLevel) and the
+    condition must be $(D true).
+
+    Params:
+      ll = The specific $(D LogLevel) used for logging the log message.
+      condition = The condition must be $(D true) for the data to be logged.
+      msg = The format string used for this log call.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number");
+    s.logf(LogLevel.info, true ,"%d %s", 1337, "is number");
+    s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number");
+    s.logf(LogLevel.error, false ,"%d %s", 1337, "is number");
+    s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number");
+    --------------------
+    */
+    final void logf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(const LogLevel ll,
+        lazy bool condition, lazy string msg, lazy A args)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formattedWrite(writer, msg, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) with a specific
+    $(D LogLevel) in a $(D printf)-style manner.
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    must be greater or equal than the $(D LogLevel) of the used $(D Logger)
+    and must be greater or equal than the global $(D LogLevel).
+
+    Params:
+      ll = The specific $(D LogLevel) used for logging the log message.
+      msg = The format string used for this log call.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.logf(LogLevel.trace, "%d %s", 1337, "is number");
+    s.logf(LogLevel.info, "%d %s", 1337, "is number");
+    s.logf(LogLevel.warning, "%d %s", 1337, "is number");
+    s.logf(LogLevel.error, "%d %s", 1337, "is number");
+    s.logf(LogLevel.fatal, "%d %s", 1337, "is number");
+    --------------------
+    */
+    final void logf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(const LogLevel ll,
+            lazy string msg, lazy A args)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, ll, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formattedWrite(writer, msg, args);
+
+                this.finishLogMsg();
+
+                if (ll == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This function logs data to the used $(D Logger) depending on a
+    condition with the $(D LogLevel) of the used $(D Logger) in a
+    $(D printf)-style manner.
+
+    In order for the resulting log message to be logged the $(D LogLevel)
+    of the used $(D Logger) must be greater or equal than the global
+    $(D LogLevel) and the condition must be $(D true).
+
+    Params:
+      condition = The condition must be $(D true) for the data to be logged.
+      msg = The format string used for this log call.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.logf(true ,"%d %s", 1337, "is number");
+    s.logf(true ,"%d %s", 1337, "is number");
+    s.logf(true ,"%d %s", 1337, "is number");
+    s.logf(false ,"%d %s", 1337, "is number");
+    s.logf(true ,"%d %s", 1337, "is number");
+    --------------------
+    */
+    final void logf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy bool condition,
+            lazy string msg, lazy A args)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel,
+                condition))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formattedWrite(writer, msg, args);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    /** This method logs data to the used $(D Logger) with the $(D LogLevel)
+    of the this $(D Logger) in a $(D printf)-style manner.
+
+    In order for the data to be processed the $(D LogLevel) of the $(D Logger)
+    must be greater or equal to the global $(D LogLevel).
+
+    Params:
+      msg = The format string used for this log call.
+      args = The data that should be logged.
+
+    Example:
+    --------------------
+    auto s = new FileLogger(stdout);
+    s.logf("%d %s", 1337, "is number");
+    s.logf("%d %s", 1337, "is number");
+    s.logf("%d %s", 1337, "is number");
+    s.logf("%d %s", 1337, "is number");
+    s.logf("%d %s", 1337, "is number");
+    --------------------
+    */
+    final void logf(int line = __LINE__, string file = __FILE__,
+        string funcName = __FUNCTION__,
+        string prettyFuncName = __PRETTY_FUNCTION__,
+        string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
+    {
+        static if (isLoggingActive) synchronized (mutex)
+        {
+            if (isLoggingEnabled(this.logLevel_, this.logLevel_,
+                globalLogLevel))
+            {
+                this.beginLogMsg(file, line, funcName, prettyFuncName,
+                    moduleName, this.logLevel_, thisTid, Clock.currTime, this);
+
+                auto writer = MsgRange(this);
+                formattedWrite(writer, msg, args);
+
+                this.finishLogMsg();
+
+                if (this.logLevel_ == LogLevel.fatal)
+                    this.fatalHandler_();
+            }
+        }
+    }
+
+    private void delegate() @safe fatalHandler_;
+    private shared LogLevel logLevel_ = LogLevel.info;
+    private Mutex mutex;
+
+    protected Appender!string msgAppender;
+    protected LogEntry header;
+}
+
+// Thread Global
+
+private __gshared Mutex stdSharedLoggerMutex;
+private __gshared Logger stdSharedDefaultLogger;
+private shared Logger stdSharedLogger;
+private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all;
+
+/* This method returns the global default Logger.
+ * Marked @trusted because of excessive reliance on __gshared data
+ */
+private @property Logger defaultSharedLoggerImpl() @trusted
+{
+    static __gshared ubyte[__traits(classInstanceSize, FileLogger)] _buffer;
+
+    synchronized (stdSharedLoggerMutex)
+    {
+        auto buffer = cast(ubyte[]) _buffer;
+
+        if (stdSharedDefaultLogger is null)
+        {
+            stdSharedDefaultLogger = emplace!FileLogger(buffer, stderr, LogLevel.all);
+        }
+    }
+    return stdSharedDefaultLogger;
+}
+
+/** This property sets and gets the default $(D Logger).
+
+Example:
+-------------
+sharedLog = new FileLogger(yourFile);
+-------------
+The example sets a new $(D FileLogger) as new $(D sharedLog).
+
+If at some point you want to use the original default logger again, you can
+use $(D sharedLog = null;). This will put back the original.
+
+Note:
+While getting and setting $(D sharedLog) is thread-safe, it has to be considered
+that the returned reference is only a current snapshot and in the following
+code, you must make sure no other thread reassigns to it between reading and
+writing $(D sharedLog).
+-------------
+if (sharedLog !is myLogger)
+    sharedLog = new myLogger;
+-------------
+*/
+@property Logger sharedLog() @safe
+{
+    static auto trustedLoad(ref shared Logger logger) @trusted
+    {
+        return atomicLoad!(MemoryOrder.acq)(logger);
+    }
+
+    // If we have set up our own logger use that
+    if (auto logger = trustedLoad(stdSharedLogger))
+    {
+        return logger;
+    }
+    else
+    {
+        // Otherwise resort to the default logger
+        return defaultSharedLoggerImpl;
+    }
+}
+
+/// Ditto
+@property void sharedLog(Logger logger) @trusted
+{
+    atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger);
+}
+
+/** This methods get and set the global $(D LogLevel).
+
+Every log message with a $(D LogLevel) lower as the global $(D LogLevel)
+will be discarded before it reaches $(D writeLogMessage) method of any
+$(D Logger).
+*/
+/* Implementation note:
+For any public logging call, the global log level shall only be queried once on
+entry. Otherwise when another threads changes the level, we would work with
+different levels at different spots in the code.
+*/
+@property LogLevel globalLogLevel() @safe @nogc
+{
+    return trustedLoad(stdLoggerGlobalLogLevel);
+}
+
+/// Ditto
+@property void globalLogLevel(LogLevel ll) @safe
+{
+    trustedStore(stdLoggerGlobalLogLevel, ll);
+}
+
+// Thread Local
+
+/** The $(D StdForwardLogger) will always forward anything to the sharedLog.
+
+The $(D StdForwardLogger) will not throw if data is logged with $(D
+LogLevel.fatal).
+*/
+class StdForwardLogger : Logger
+{
+    /** The default constructor for the $(D StdForwardLogger).
+
+    Params:
+      lv = The $(D LogLevel) for the $(D MultiLogger). By default the $(D
+          LogLevel) is $(D all).
+    */
+    this(const LogLevel lv = LogLevel.all) @safe
+    {
+        super(lv);
+        this.fatalHandler = delegate() {};
+    }
+
+    override protected void writeLogMsg(ref LogEntry payload)
+    {
+          sharedLog.forwardMsg(payload);
+    }
+}
+
+///
+@safe unittest
+{
+    auto nl1 = new StdForwardLogger(LogLevel.all);
+}
+
+/** This $(D LogLevel) is unqiue to every thread.
+
+The thread local $(D Logger) will use this $(D LogLevel) to filter log calls
+every same way as presented earlier.
+*/
+//public LogLevel threadLogLevel = LogLevel.all;
+private Logger stdLoggerThreadLogger;
+private Logger stdLoggerDefaultThreadLogger;
+
+/* This method returns the thread local default Logger.
+*/
+private @property Logger stdThreadLocalLogImpl() @trusted
+{
+    static ubyte[__traits(classInstanceSize, StdForwardLogger)] _buffer;
+
+    auto buffer = cast(ubyte[]) _buffer;
+
+    if (stdLoggerDefaultThreadLogger is null)
+    {
+        stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all);
+    }
+    return stdLoggerDefaultThreadLogger;
+}
+
+/** This function returns a thread unique $(D Logger), that by default
+propergates all data logged to it to the $(D sharedLog).
+
+These properties can be used to set and get this $(D Logger). Every
+modification to this $(D Logger) will only be visible in the thread the
+modification has been done from.
+
+This $(D Logger) is called by the free standing log functions. This allows to
+create thread local redirections and still use the free standing log
+functions.
+*/
+@property Logger stdThreadLocalLog() @safe
+{
+    // If we have set up our own logger use that
+    if (auto logger = stdLoggerThreadLogger)
+        return logger;
+    else
+        // Otherwise resort to the default logger
+        return stdThreadLocalLogImpl;
+}
+
+/// Ditto
+@property void stdThreadLocalLog(Logger logger) @safe
+{
+    stdLoggerThreadLogger = logger;
+}
+
+/// Ditto
+unittest
+{
+    import std.file : deleteme, remove;
+    Logger l = stdThreadLocalLog;
+    stdThreadLocalLog = new FileLogger(deleteme ~ "-someFile.log");
+    scope(exit) remove(deleteme ~ "-someFile.log");
+
+    auto tempLog = stdThreadLocalLog;
+    stdThreadLocalLog = l;
+    destroy(tempLog);
+}
+
+@safe unittest
+{
+    LogLevel ll = globalLogLevel;
+    globalLogLevel = LogLevel.fatal;
+    assert(globalLogLevel == LogLevel.fatal);
+    globalLogLevel = ll;
+}
+
+package class TestLogger : Logger
+{
+    int line = -1;
+    string file = null;
+    string func = null;
+    string prettyFunc = null;
+    string msg = null;
+    LogLevel lvl;
+
+    this(const LogLevel lv = LogLevel.all) @safe
+    {
+        super(lv);
+    }
+
+    override protected void writeLogMsg(ref LogEntry payload) @safe
+    {
+        this.line = payload.line;
+        this.file = payload.file;
+        this.func = payload.funcName;
+        this.prettyFunc = payload.prettyFuncName;
+        this.lvl = payload.logLevel;
+        this.msg = payload.msg;
+    }
+}
+
+version(unittest) private void testFuncNames(Logger logger) @safe
+{
+    string s = "I'm here";
+    logger.log(s);
+}
+
+@safe unittest
+{
+    auto tl1 = new TestLogger();
+    testFuncNames(tl1);
+    assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func);
+    assert(tl1.prettyFunc ==
+        "void std.experimental.logger.core.testFuncNames(Logger logger) @safe",
+        tl1.prettyFunc);
+    assert(tl1.msg == "I'm here", tl1.msg);
+}
+
+@safe unittest
+{
+    auto tl1 = new TestLogger(LogLevel.all);
+    tl1.log();
+    assert(tl1.line == __LINE__ - 1);
+    tl1.log(true);
+    assert(tl1.line == __LINE__ - 1);
+    tl1.log(false);
+    assert(tl1.line == __LINE__ - 3);
+    tl1.log(LogLevel.info);
+    assert(tl1.line == __LINE__ - 1);
+    tl1.log(LogLevel.off);
+    assert(tl1.line == __LINE__ - 3);
+    tl1.log(LogLevel.info, true);
+    assert(tl1.line == __LINE__ - 1);
+    tl1.log(LogLevel.info, false);
+    assert(tl1.line == __LINE__ - 3);
+
+    auto oldunspecificLogger = sharedLog;
+    scope(exit) {
+        sharedLog = oldunspecificLogger;
+    }
+
+    sharedLog = tl1;
+
+    log();
+    assert(tl1.line == __LINE__ - 1);
+
+    log(LogLevel.info);
+    assert(tl1.line == __LINE__ - 1);
+
+    log(true);
+    assert(tl1.line == __LINE__ - 1);
+
+    log(LogLevel.warning, true);
+    assert(tl1.line == __LINE__ - 1);
+
+    trace();
+    assert(tl1.line == __LINE__ - 1);
+}
+
+@safe unittest
+{
+    import std.experimental.logger.multilogger : MultiLogger;
+
+    auto tl1 = new TestLogger;
+    auto tl2 = new TestLogger;
+
+    auto ml = new MultiLogger();
+    ml.insertLogger("one", tl1);
+    ml.insertLogger("two", tl2);
+
+    string msg = "Hello Logger World";
+    ml.log(msg);
+    int lineNumber = __LINE__ - 1;
+    assert(tl1.msg == msg);
+    assert(tl1.line == lineNumber);
+    assert(tl2.msg == msg);
+    assert(tl2.line == lineNumber);
+
+    ml.removeLogger("one");
+    ml.removeLogger("two");
+    auto n = ml.removeLogger("one");
+    assert(n is null);
+}
+
+@safe unittest
+{
+    bool errorThrown = false;
+    auto tl = new TestLogger;
+    auto dele = delegate() {
+        errorThrown = true;
+    };
+    tl.fatalHandler = dele;
+    tl.fatal();
+    assert(errorThrown);
+}
+
+@safe unittest
+{
+    auto l = new TestLogger(LogLevel.all);
+    string msg = "Hello Logger World";
+    l.log(msg);
+    int lineNumber = __LINE__ - 1;
+    assert(l.msg == msg);
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    l.log(true, msg);
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg, l.msg);
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    l.log(false, msg);
+    assert(l.msg == msg);
+    assert(l.line == lineNumber, to!string(l.line));
+    assert(l.logLevel == LogLevel.all);
+
+    msg = "%s Another message";
+    l.logf(msg, "Yet");
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    l.logf(true, msg, "Yet");
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    l.logf(false, msg, "Yet");
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    () @trusted {
+        assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet"));
+    } ();
+    lineNumber = __LINE__ - 2;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    () @trusted {
+        assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet"));
+    } ();
+    lineNumber = __LINE__ - 2;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet"));
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    auto oldunspecificLogger = sharedLog;
+
+    assert(oldunspecificLogger.logLevel == LogLevel.all,
+         to!string(oldunspecificLogger.logLevel));
+
+    assert(l.logLevel == LogLevel.all);
+    sharedLog = l;
+    assert(globalLogLevel == LogLevel.all,
+            to!string(globalLogLevel));
+
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+    }
+
+    assert(sharedLog.logLevel == LogLevel.all);
+    assert(stdThreadLocalLog.logLevel == LogLevel.all);
+    assert(globalLogLevel == LogLevel.all);
+
+    msg = "Another message";
+    log(msg);
+    lineNumber = __LINE__ - 1;
+    assert(l.logLevel == LogLevel.all);
+    assert(l.line == lineNumber, to!string(l.line));
+    assert(l.msg == msg, l.msg);
+
+    log(true, msg);
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg);
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    log(false, msg);
+    assert(l.msg == msg);
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    msg = "%s Another message";
+    logf(msg, "Yet");
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    logf(true, msg, "Yet");
+    lineNumber = __LINE__ - 1;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    logf(false, msg, "Yet");
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    msg = "%s Another message";
+    () @trusted {
+        assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet"));
+    } ();
+    lineNumber = __LINE__ - 2;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    () @trusted {
+        assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet"));
+    } ();
+    lineNumber = __LINE__ - 2;
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+
+    assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet"));
+    assert(l.msg == msg.format("Yet"));
+    assert(l.line == lineNumber);
+    assert(l.logLevel == LogLevel.all);
+}
+
+unittest // default logger
+{
+    import std.file : deleteme, exists, remove;
+
+    string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
+    FileLogger l = new FileLogger(filename);
+    auto oldunspecificLogger = sharedLog;
+    sharedLog = l;
+
+    scope(exit)
+    {
+        remove(filename);
+        assert(!exists(filename));
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    string notWritten = "this should not be written to file";
+    string written = "this should be written to file";
+
+    globalLogLevel = LogLevel.critical;
+    assert(globalLogLevel == LogLevel.critical);
+
+    log(LogLevel.warning, notWritten);
+    log(LogLevel.critical, written);
+
+    l.file.flush();
+    l.file.close();
+
+    auto file = File(filename, "r");
+    assert(!file.eof);
+
+    string readLine = file.readln();
+    assert(readLine.indexOf(written) != -1, readLine);
+    assert(readLine.indexOf(notWritten) == -1, readLine);
+    file.close();
+}
+
+unittest
+{
+    import std.file : deleteme, remove;
+
+    string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
+    auto oldunspecificLogger = sharedLog;
+
+    scope(exit)
+    {
+        remove(filename);
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    string notWritten = "this should not be written to file";
+    string written = "this should be written to file";
+
+    auto l = new FileLogger(filename);
+    sharedLog = l;
+    sharedLog.logLevel = LogLevel.critical;
+
+    log(LogLevel.error, false, notWritten);
+    log(LogLevel.critical, true, written);
+    destroy(l);
+
+    auto file = File(filename, "r");
+    auto readLine = file.readln();
+    assert(!readLine.empty, readLine);
+    assert(readLine.indexOf(written) != -1);
+    assert(readLine.indexOf(notWritten) == -1);
+    file.close();
+}
+
+@safe unittest
+{
+    auto tl = new TestLogger(LogLevel.all);
+    int l = __LINE__;
+    tl.info("a");
+    assert(tl.line == l+1);
+    assert(tl.msg == "a");
+    assert(tl.logLevel == LogLevel.all);
+    assert(globalLogLevel == LogLevel.all);
+    l = __LINE__;
+    tl.trace("b");
+    assert(tl.msg == "b", tl.msg);
+    assert(tl.line == l+1, to!string(tl.line));
+}
+
+// testing possible log conditions
+@safe unittest
+{
+    auto oldunspecificLogger = sharedLog;
+
+    auto mem = new TestLogger;
+    mem.fatalHandler = delegate() {};
+    sharedLog = mem;
+
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    int value = 0;
+    foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+            LogLevel.info, LogLevel.warning, LogLevel.error,
+            LogLevel.critical, LogLevel.fatal, LogLevel.off])
+    {
+
+        globalLogLevel = gll;
+
+        foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                LogLevel.info, LogLevel.warning, LogLevel.error,
+                LogLevel.critical, LogLevel.fatal, LogLevel.off])
+        {
+
+            mem.logLevel = ll;
+
+            foreach (cond; [true, false])
+            {
+                foreach (condValue; [true, false])
+                {
+                    foreach (memOrG; [true, false])
+                    {
+                        foreach (prntf; [true, false])
+                        {
+                            foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                                    LogLevel.info, LogLevel.warning,
+                                    LogLevel.error, LogLevel.critical,
+                                    LogLevel.fatal, LogLevel.off])
+                            {
+                                foreach (singleMulti; 0 .. 2)
+                                {
+                                    int lineCall;
+                                    mem.msg = "-1";
+                                    if (memOrG)
+                                    {
+                                        if (prntf)
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.logf(ll2, condValue, "%s",
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.logf(ll2, condValue,
+                                                        "%d %d", value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.logf(ll2, "%s", value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.logf(ll2, "%d %d",
+                                                        value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                        else
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.log(ll2, condValue,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.log(ll2, condValue,
+                                                        to!string(value), value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.log(ll2, to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.log(ll2,
+                                                        to!string(value),
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                    }
+                                    else
+                                    {
+                                        if (prntf)
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    logf(ll2, condValue, "%s",
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    logf(ll2, condValue,
+                                                        "%s %d", value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    logf(ll2, "%s", value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    logf(ll2, "%s %s", value,
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                        else
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    log(ll2, condValue,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    log(ll2, condValue, value,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    log(ll2, to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    log(ll2, value,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                    }
+
+                                    string valueStr = to!string(value);
+                                    ++value;
+
+                                    bool ll2Off = (ll2 != LogLevel.off);
+                                    bool gllOff = (gll != LogLevel.off);
+                                    bool llOff = (ll != LogLevel.off);
+                                    bool condFalse = (cond ? condValue : true);
+                                    bool ll2VSgll = (ll2 >= gll);
+                                    bool ll2VSll = (ll2 >= ll);
+
+                                    bool shouldLog = ll2Off && gllOff && llOff
+                                        && condFalse && ll2VSgll && ll2VSll;
+
+                                    /*
+                                    writefln(
+                                        "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)"
+                                        , gll != LogLevel.off, ll2 != LogLevel.off,
+                                        cond ? condValue : true,
+                                        ll2 >= gll, ll2 >= ll, shouldLog);
+                                    */
+
+
+                                    if (shouldLog)
+                                    {
+                                        assert(mem.msg.indexOf(valueStr) != -1,
+                                            format(
+                                            "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
+                                            "cond(%b) condValue(%b)" ~
+                                            " memOrG(%b) shouldLog(%b) %s == %s" ~
+                                            " %b %b %b %b %b",
+                                            lineCall, ll2Off, gll, ll, ll2, cond,
+                                            condValue, memOrG, shouldLog, mem.msg,
+                                            valueStr, gllOff, llOff, condFalse,
+                                            ll2VSgll, ll2VSll
+                                        ));
+                                    }
+                                    else
+                                    {
+                                        assert(mem.msg.indexOf(valueStr),
+                                            format(
+                                            "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
+                                            "cond(%b) condValue(%b)" ~
+                                            " memOrG(%b) shouldLog(%b) %s == %s" ~
+                                            " %b %b %b %b %b",
+                                            lineCall, ll2Off, gll, ll, ll2, cond,
+                                            condValue, memOrG, shouldLog, mem.msg,
+                                            valueStr, gllOff, llOff, condFalse,
+                                            ll2VSgll, ll2VSll
+                                        ));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+// more testing
+@safe unittest
+{
+    auto oldunspecificLogger = sharedLog;
+
+    auto mem = new TestLogger;
+    mem.fatalHandler = delegate() {};
+    sharedLog = mem;
+
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    int value = 0;
+    foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+            LogLevel.info, LogLevel.warning, LogLevel.error,
+            LogLevel.critical, LogLevel.fatal, LogLevel.off])
+    {
+
+        globalLogLevel = gll;
+
+        foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                LogLevel.info, LogLevel.warning, LogLevel.error,
+                LogLevel.critical, LogLevel.fatal, LogLevel.off])
+        {
+            mem.logLevel = ll;
+
+            foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                    LogLevel.info, LogLevel.warning, LogLevel.error,
+                    LogLevel.critical, LogLevel.fatal, LogLevel.off])
+            {
+                stdThreadLocalLog.logLevel = tll;
+
+                foreach (cond; [true, false])
+                {
+                    foreach (condValue; [true, false])
+                    {
+                        foreach (memOrG; [true, false])
+                        {
+                            foreach (prntf; [true, false])
+                            {
+                                foreach (singleMulti; 0 .. 2)
+                                {
+                                    int lineCall;
+                                    mem.msg = "-1";
+                                    if (memOrG)
+                                    {
+                                        if (prntf)
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.logf(condValue, "%s",
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.logf(condValue,
+                                                        "%d %d", value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.logf("%s", value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.logf("%d %d",
+                                                        value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                        else
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.log(condValue,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.log(condValue,
+                                                        to!string(value), value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    mem.log(to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    mem.log(to!string(value),
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                    }
+                                    else
+                                    {
+                                        if (prntf)
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    logf(condValue, "%s", value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    logf(condValue, "%s %d", value,
+                                                        value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    logf("%s", value);
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    logf("%s %s", value, value);
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                        else
+                                        {
+                                            if (cond)
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    log(condValue,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    log(condValue, value,
+                                                        to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                            else
+                                            {
+                                                if (singleMulti == 0)
+                                                {
+                                                    log(to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                                else
+                                                {
+                                                    log(value, to!string(value));
+                                                    lineCall = __LINE__;
+                                                }
+                                            }
+                                        }
+                                    }
+
+                                    string valueStr = to!string(value);
+                                    ++value;
+
+                                    bool gllOff = (gll != LogLevel.off);
+                                    bool llOff = (ll != LogLevel.off);
+                                    bool tllOff = (tll != LogLevel.off);
+                                    bool llVSgll = (ll >= gll);
+                                    bool tllVSll =
+                                        (stdThreadLocalLog.logLevel >= ll);
+                                    bool condFalse = (cond ? condValue : true);
+
+                                    bool shouldLog = gllOff && llOff
+                                        && (memOrG ? true : tllOff)
+                                        && (memOrG ?
+                                            (ll >= gll) :
+                                            (tll >= gll && tll >= ll))
+                                        && condFalse;
+
+                                    if (shouldLog)
+                                    {
+                                        assert(mem.msg.indexOf(valueStr) != -1,
+                                            format("\ngll(%s) ll(%s) tll(%s) " ~
+                                                "cond(%s) condValue(%s) " ~
+                                                "memOrG(%s) prntf(%s) " ~
+                                                "singleMulti(%s)",
+                                                gll, ll, tll, cond, condValue,
+                                                memOrG, prntf, singleMulti)
+                                            ~ format(" gllOff(%s) llOff(%s) " ~
+                                                "llVSgll(%s) tllVSll(%s) " ~
+                                                "tllOff(%s) condFalse(%s) "
+                                                ~ "shoudlLog(%s)",
+                                                gll != LogLevel.off,
+                                                ll != LogLevel.off, llVSgll,
+                                                tllVSll, tllOff, condFalse,
+                                                shouldLog)
+                                            ~ format("msg(%s) line(%s) " ~
+                                                "lineCall(%s) valueStr(%s)",
+                                                mem.msg, mem.line, lineCall,
+                                                valueStr)
+                                        );
+                                    }
+                                    else
+                                    {
+                                        assert(mem.msg.indexOf(valueStr) == -1,
+                                            format("\ngll(%s) ll(%s) tll(%s) " ~
+                                                "cond(%s) condValue(%s) " ~
+                                                "memOrG(%s) prntf(%s) " ~
+                                                "singleMulti(%s)",
+                                                gll, ll, tll, cond, condValue,
+                                                memOrG, prntf, singleMulti)
+                                            ~ format(" gllOff(%s) llOff(%s) " ~
+                                                "llVSgll(%s) tllVSll(%s) " ~
+                                                "tllOff(%s) condFalse(%s) "
+                                                ~ "shoudlLog(%s)",
+                                                gll != LogLevel.off,
+                                                ll != LogLevel.off, llVSgll,
+                                                tllVSll, tllOff, condFalse,
+                                                shouldLog)
+                                            ~ format("msg(%s) line(%s) " ~
+                                                "lineCall(%s) valueStr(%s)",
+                                                mem.msg, mem.line, lineCall,
+                                                valueStr)
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+// testing more possible log conditions
+@safe unittest
+{
+    bool fatalLog;
+    auto mem = new TestLogger;
+    mem.fatalHandler = delegate() { fatalLog = true; };
+    auto oldunspecificLogger = sharedLog;
+
+    stdThreadLocalLog.logLevel = LogLevel.all;
+
+    sharedLog = mem;
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+            LogLevel.info, LogLevel.warning, LogLevel.error,
+            LogLevel.critical, LogLevel.fatal, LogLevel.off])
+    {
+
+        globalLogLevel = gll;
+
+        foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                LogLevel.info, LogLevel.warning, LogLevel.error,
+                LogLevel.critical, LogLevel.fatal, LogLevel.off])
+        {
+            mem.logLevel = ll;
+
+            foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
+                    LogLevel.info, LogLevel.warning, LogLevel.error,
+                    LogLevel.critical, LogLevel.fatal, LogLevel.off])
+            {
+                stdThreadLocalLog.logLevel = tll;
+
+                foreach (cond; [true, false])
+                {
+                    assert(globalLogLevel == gll);
+                    assert(mem.logLevel == ll);
+
+                    bool gllVSll = LogLevel.trace >= globalLogLevel;
+                    bool llVSgll = ll >= globalLogLevel;
+                    bool lVSll = LogLevel.trace >= ll;
+                    bool gllOff = globalLogLevel != LogLevel.off;
+                    bool llOff = mem.logLevel != LogLevel.off;
+                    bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off;
+                    bool tllVSll = tll >= ll;
+                    bool tllVSgll = tll >= gll;
+                    bool lVSgll = LogLevel.trace >= tll;
+
+                    bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
+                    bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond;
+
+                    mem.line = -1;
+                    /*
+                    writefln("gll(%3u) ll(%3u) cond(%b) test(%b)",
+                        gll, ll, cond, test);
+                    writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll,
+                        gllOff, llOff, cond, test2);
+                    */
+
+                    mem.trace(__LINE__); int line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    trace(__LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.trace(cond, __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    trace(cond, __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.tracef("%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    tracef("%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.tracef(cond, "%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    tracef(cond, "%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    llVSgll = ll >= globalLogLevel;
+                    lVSll = LogLevel.info >= ll;
+                    lVSgll = LogLevel.info >= tll;
+                    test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
+                    testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
+                        lVSgll && cond;
+
+                    mem.info(__LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    info(__LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.info(cond, __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    info(cond, __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.infof("%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    infof("%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.infof(cond, "%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    infof(cond, "%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    llVSgll = ll >= globalLogLevel;
+                    lVSll = LogLevel.warning >= ll;
+                    lVSgll = LogLevel.warning >= tll;
+                    test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
+                    testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
+                        lVSgll && cond;
+
+                    mem.warning(__LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    warning(__LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.warning(cond, __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    warning(cond, __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.warningf("%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    warningf("%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.warningf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    warningf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    llVSgll = ll >= globalLogLevel;
+                    lVSll = LogLevel.critical >= ll;
+                    lVSgll = LogLevel.critical >= tll;
+                    test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
+                    testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
+                        lVSgll && cond;
+
+                    mem.critical(__LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    critical(__LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.critical(cond, __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    critical(cond, __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.criticalf("%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    criticalf("%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    mem.criticalf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+
+                    criticalf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+
+                    llVSgll = ll >= globalLogLevel;
+                    lVSll = LogLevel.fatal >= ll;
+                    lVSgll = LogLevel.fatal >= tll;
+                    test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
+                    testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
+                        lVSgll && cond;
+
+                    mem.fatal(__LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+                    assert(test ? fatalLog : true);
+                    fatalLog = false;
+
+                    fatal(__LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+                    assert(testG ? fatalLog : true);
+                    fatalLog = false;
+
+                    mem.fatal(cond, __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+                    assert(test ? fatalLog : true);
+                    fatalLog = false;
+
+                    fatal(cond, __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+                    assert(testG ? fatalLog : true);
+                    fatalLog = false;
+
+                    mem.fatalf("%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+                    assert(test ? fatalLog : true);
+                    fatalLog = false;
+
+                    fatalf("%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+                    assert(testG ? fatalLog : true);
+                    fatalLog = false;
+
+                    mem.fatalf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(test ? mem.line == line : true); line = -1;
+                    assert(test ? fatalLog : true);
+                    fatalLog = false;
+
+                    fatalf(cond, "%d", __LINE__); line = __LINE__;
+                    assert(testG ? mem.line == line : true); line = -1;
+                    assert(testG ? fatalLog : true);
+                    fatalLog = false;
+                }
+            }
+        }
+    }
+}
+
+// Issue #5
+@safe unittest
+{
+    auto oldunspecificLogger = sharedLog;
+
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    auto tl = new TestLogger(LogLevel.info);
+    sharedLog = tl;
+
+    trace("trace");
+    assert(tl.msg.indexOf("trace") == -1);
+}
+
+// Issue #5
+@safe unittest
+{
+    import std.experimental.logger.multilogger : MultiLogger;
+    stdThreadLocalLog.logLevel = LogLevel.all;
+
+    auto oldunspecificLogger = sharedLog;
+
+    scope(exit)
+    {
+        sharedLog = oldunspecificLogger;
+        globalLogLevel = LogLevel.all;
+    }
+
+    auto logger = new MultiLogger(LogLevel.error);
+
+    auto tl = new TestLogger(LogLevel.info);
+    logger.insertLogger("required", tl);
+    sharedLog = logger;
+
+    trace("trace");
+    assert(tl.msg.indexOf("trace") == -1);
+    info("info");
+    assert(tl.msg.indexOf("info") == -1);
+    error("error");
+    assert(tl.msg.indexOf("error") == 0);
+}
+
+unittest
+{
+    import std.exception : assertThrown;
+    auto tl = new TestLogger();
+    assertThrown!Throwable(tl.fatal("fatal"));
+}
+
+// log objects with non-safe toString
+unittest
+{
+    struct Test
+    {
+        string toString() const @system
+        {
+            return "test";
+        }
+    }
+
+    auto tl = new TestLogger();
+    tl.info(Test.init);
+    assert(tl.msg == "test");
+}
+
+// Workaround for atomics not allowed in @safe code
+private auto trustedLoad(T)(ref shared T value) @trusted
+{
+    return atomicLoad!(MemoryOrder.acq)(value);
+}
+
+// ditto
+private void trustedStore(T)(ref shared T dst, ref T src) @trusted
+{
+    atomicStore!(MemoryOrder.rel)(dst, src);
+}
+
+// check that thread-local logging does not propagate
+// to shared logger
+unittest
+{
+    import std.concurrency, core.atomic, core.thread;
+
+    static shared logged_count = 0;
+
+    class TestLog : Logger
+    {
+        Tid tid;
+
+        this()
+        {
+            super (LogLevel.trace);
+            this.tid = thisTid;
+        }
+
+        override void writeLogMsg(ref LogEntry payload) @trusted
+        {
+            assert(thisTid == this.tid);
+            atomicOp!"+="(logged_count, 1);
+        }
+    }
+
+    class IgnoredLog : Logger
+    {
+        this()
+        {
+            super (LogLevel.trace);
+        }
+
+        override void writeLogMsg(ref LogEntry payload) @trusted
+        {
+            assert(false);
+        }
+    }
+
+    auto oldSharedLog = sharedLog;
+    scope(exit)
+    {
+        sharedLog = oldSharedLog;
+    }
+
+    sharedLog = new IgnoredLog;
+    Thread[] spawned;
+
+    foreach (i; 0..4)
+    {
+        spawned ~= new Thread({
+            stdThreadLocalLog = new TestLog;
+            trace("zzzzzzzzzz");
+        });
+        spawned[$-1].start();
+    }
+
+    foreach (t; spawned)
+        t.join();
+
+    assert (atomicOp!"=="(logged_count, 4));
+}
+
+@safe unittest
+{
+    auto dl = cast(FileLogger)sharedLog;
+    assert(dl !is null);
+    assert(dl.logLevel == LogLevel.all);
+    assert(globalLogLevel == LogLevel.all);
+
+    auto tl = cast(StdForwardLogger)stdThreadLocalLog;
+    assert(tl !is null);
+    stdThreadLocalLog.logLevel = LogLevel.all;
+}
+
+// Issue 14940
+@safe unittest
+{
+    import std.typecons : Nullable;
+
+    Nullable!int a = 1;
+    auto l = new TestLogger();
+    l.infof("log: %s", a);
+    assert(l.msg == "log: 1");
+}
+
+// Ensure @system toString methods work
+unittest
+{
+    enum SystemToStringMsg = "SystemToString";
+    static struct SystemToString
+    {
+        string toString() @system
+        {
+            return SystemToStringMsg;
+        }
+    }
+
+    auto tl = new TestLogger();
+
+    SystemToString sts;
+    tl.logf("%s", sts);
+    assert(tl.msg == SystemToStringMsg);
+}
diff --git a/libphobos/src/std/experimental/logger/filelogger.d b/libphobos/src/std/experimental/logger/filelogger.d
new file mode 100644
index 00000000000..da30ac11800
--- /dev/null
+++ b/libphobos/src/std/experimental/logger/filelogger.d
@@ -0,0 +1,202 @@
+module std.experimental.logger.filelogger;
+
+import std.stdio;
+import std.experimental.logger.core;
+
+/** This $(D Logger) implementation writes log messages to the associated
+file. The name of the file has to be passed on construction time. If the file
+is already present new log messages will be append at its end.
+*/
+class FileLogger : Logger
+{
+    import std.format : formattedWrite;
+    import std.datetime : SysTime;
+    import std.concurrency : Tid;
+
+    /** A constructor for the $(D FileLogger) Logger.
+
+    Params:
+      fn = The filename of the output file of the $(D FileLogger). If that
+      file can not be opened for writting an exception will be thrown.
+      lv = The $(D LogLevel) for the $(D FileLogger). By default the
+      $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all).
+
+    Example:
+    -------------
+    auto l1 = new FileLogger("logFile", "loggerName");
+    auto l2 = new FileLogger("logFile", "loggerName", LogLevel.fatal);
+    -------------
+    */
+    this(in string fn, const LogLevel lv = LogLevel.all) @safe
+    {
+        import std.exception : enforce;
+        super(lv);
+        this.filename = fn;
+        this.file_.open(this.filename, "a");
+    }
+
+    /** A constructor for the $(D FileLogger) Logger that takes a reference to
+    a $(D File).
+
+    The $(D File) passed must be open for all the log call to the
+    $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger)
+    for logging will result in undefined behaviour.
+
+    Params:
+      file = The file used for logging.
+      lv = The $(D LogLevel) for the $(D FileLogger). By default the
+      $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all).
+
+    Example:
+    -------------
+    auto file = File("logFile.log", "w");
+    auto l1 = new FileLogger(file, "LoggerName");
+    auto l2 = new FileLogger(file, "LoggerName", LogLevel.fatal);
+    -------------
+    */
+    this(File file, const LogLevel lv = LogLevel.all) @safe
+    {
+        super(lv);
+        this.file_ = file;
+    }
+
+    /** If the $(D FileLogger) is managing the $(D File) it logs to, this
+    method will return a reference to this File.
+    */
+    @property File file() @safe
+    {
+        return this.file_;
+    }
+
+    /* This method overrides the base class method in order to log to a file
+    without requiring heap allocated memory. Additionally, the $(D FileLogger)
+    local mutex is logged to serialize the log calls.
+    */
+    override protected void beginLogMsg(string file, int line, string funcName,
+        string prettyFuncName, string moduleName, LogLevel logLevel,
+        Tid threadId, SysTime timestamp, Logger logger)
+        @safe
+    {
+        import std.string : lastIndexOf;
+        ptrdiff_t fnIdx = file.lastIndexOf('/') + 1;
+        ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1;
+
+        auto lt = this.file_.lockingTextWriter();
+        systimeToISOString(lt, timestamp);
+        formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $],
+            funcName[funIdx .. $], line);
+    }
+
+    /* This methods overrides the base class method and writes the parts of
+    the log call directly to the file.
+    */
+    override protected void logMsgPart(const(char)[] msg)
+    {
+        formattedWrite(this.file_.lockingTextWriter(), "%s", msg);
+    }
+
+    /* This methods overrides the base class method and finalizes the active
+    log call. This requires flushing the $(D File) and releasing the
+    $(D FileLogger) local mutex.
+    */
+    override protected void finishLogMsg()
+    {
+        this.file_.lockingTextWriter().put("\n");
+        this.file_.flush();
+    }
+
+    /* This methods overrides the base class method and delegates the
+    $(D LogEntry) data to the actual implementation.
+    */
+    override protected void writeLogMsg(ref LogEntry payload)
+    {
+        this.beginLogMsg(payload.file, payload.line, payload.funcName,
+            payload.prettyFuncName, payload.moduleName, payload.logLevel,
+            payload.threadId, payload.timestamp, payload.logger);
+        this.logMsgPart(payload.msg);
+        this.finishLogMsg();
+    }
+
+    /** If the $(D FileLogger) was constructed with a filename, this method
+    returns this filename. Otherwise an empty $(D string) is returned.
+    */
+    string getFilename()
+    {
+        return this.filename;
+    }
+
+    private File file_;
+    private string filename;
+}
+
+unittest
+{
+    import std.file : deleteme, remove;
+    import std.array : empty;
+    import std.string : indexOf;
+
+    string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
+    auto l = new FileLogger(filename);
+
+    scope(exit)
+    {
+        remove(filename);
+    }
+
+    string notWritten = "this should not be written to file";
+    string written = "this should be written to file";
+
+    l.logLevel = LogLevel.critical;
+    l.log(LogLevel.warning, notWritten);
+    l.log(LogLevel.critical, written);
+    destroy(l);
+
+    auto file = File(filename, "r");
+    string readLine = file.readln();
+    assert(readLine.indexOf(written) != -1, readLine);
+    readLine = file.readln();
+    assert(readLine.indexOf(notWritten) == -1, readLine);
+}
+
+unittest
+{
+    import std.file : deleteme, remove;
+    import std.array : empty;
+    import std.string : indexOf;
+
+    string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
+    auto file = File(filename, "w");
+    auto l = new FileLogger(file);
+
+    scope(exit)
+    {
+        remove(filename);
+    }
+
+    string notWritten = "this should not be written to file";
+    string written = "this should be written to file";
+
+    l.logLevel = LogLevel.critical;
+    l.log(LogLevel.warning, notWritten);
+    l.log(LogLevel.critical, written);
+    file.close();
+
+    file = File(filename, "r");
+    string readLine = file.readln();
+    assert(readLine.indexOf(written) != -1, readLine);
+    readLine = file.readln();
+    assert(readLine.indexOf(notWritten) == -1, readLine);
+    file.close();
+}
+
+@safe unittest
+{
+    auto dl = cast(FileLogger)sharedLog;
+    assert(dl !is null);
+    assert(dl.logLevel == LogLevel.all);
+    assert(globalLogLevel == LogLevel.all);
+
+    auto tl = cast(StdForwardLogger)stdThreadLocalLog;
+    assert(tl !is null);
+    stdThreadLocalLog.logLevel = LogLevel.all;
+}
diff --git a/libphobos/src/std/experimental/logger/multilogger.d b/libphobos/src/std/experimental/logger/multilogger.d
new file mode 100644
index 00000000000..549a789842c
--- /dev/null
+++ b/libphobos/src/std/experimental/logger/multilogger.d
@@ -0,0 +1,196 @@
+module std.experimental.logger.multilogger;
+
+import std.experimental.logger.core;
+import std.experimental.logger.filelogger;
+
+/** This Element is stored inside the $(D MultiLogger) and associates a
+$(D Logger) to a $(D string).
+*/
+struct MultiLoggerEntry
+{
+    string name; /// The name if the $(D Logger)
+    Logger logger; /// The stored $(D Logger)
+}
+
+/** MultiLogger logs to multiple $(D Logger). The $(D Logger)s are stored in an
+$(D Logger[]) in their order of insertion.
+
+Every data logged to this $(D MultiLogger) will be distributed to all the $(D
+Logger)s inserted into it. This $(D MultiLogger) implementation can
+hold multiple $(D Logger)s with the same name. If the method $(D removeLogger)
+is used to remove a $(D Logger) only the first occurrence with that name will
+be removed.
+*/
+class MultiLogger : Logger
+{
+    /** A constructor for the $(D MultiLogger) Logger.
+
+    Params:
+      lv = The $(D LogLevel) for the $(D MultiLogger). By default the
+      $(D LogLevel) for $(D MultiLogger) is $(D LogLevel.all).
+
+    Example:
+    -------------
+    auto l1 = new MultiLogger(LogLevel.trace);
+    -------------
+    */
+    this(const LogLevel lv = LogLevel.all) @safe
+    {
+        super(lv);
+    }
+
+    /** This member holds all $(D Logger)s stored in the $(D MultiLogger).
+
+    When inheriting from $(D MultiLogger) this member can be used to gain
+    access to the stored $(D Logger).
+    */
+    protected MultiLoggerEntry[] logger;
+
+    /** This method inserts a new Logger into the $(D MultiLogger).
+
+    Params:
+      name = The name of the $(D Logger) to insert.
+      newLogger = The $(D Logger) to insert.
+    */
+    void insertLogger(string name, Logger newLogger) @safe
+    {
+        this.logger ~= MultiLoggerEntry(name, newLogger);
+    }
+
+    /** This method removes a Logger from the $(D MultiLogger).
+
+    Params:
+      toRemove = The name of the $(D Logger) to remove. If the $(D Logger)
+        is not found $(D null) will be returned. Only the first occurrence of
+        a $(D Logger) with the given name will be removed.
+
+    Returns: The removed $(D Logger).
+    */
+    Logger removeLogger(in char[] toRemove) @safe
+    {
+        import std.algorithm : copy;
+        import std.range.primitives : back, popBack;
+        for (size_t i = 0; i < this.logger.length; ++i)
+        {
+            if (this.logger[i].name == toRemove)
+            {
+                Logger ret = this.logger[i].logger;
+                this.logger[i] = this.logger.back;
+                this.logger.popBack();
+
+                return ret;
+            }
+        }
+
+        return null;
+    }
+
+    /* The override to pass the payload to all children of the
+    $(D MultiLoggerBase).
+    */
+    override protected void writeLogMsg(ref LogEntry payload) @safe
+    {
+        foreach (it; this.logger)
+        {
+            /* We don't perform any checks here to avoid race conditions.
+            Instead the child will check on its own if its log level matches
+            and assume LogLevel.all for the globalLogLevel (since we already
+            know the message passes this test).
+            */
+            it.logger.forwardMsg(payload);
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.experimental.logger.nulllogger;
+    import std.exception : assertThrown;
+    auto a = new MultiLogger;
+    auto n0 = new NullLogger();
+    auto n1 = new NullLogger();
+    a.insertLogger("zero", n0);
+    a.insertLogger("one", n1);
+
+    auto n0_1 = a.removeLogger("zero");
+    assert(n0_1 is n0);
+    auto n = a.removeLogger("zero");
+    assert(n is null);
+
+    auto n1_1 = a.removeLogger("one");
+    assert(n1_1 is n1);
+    n = a.removeLogger("one");
+    assert(n is null);
+}
+
+@safe unittest
+{
+    auto a = new MultiLogger;
+    auto n0 = new TestLogger;
+    auto n1 = new TestLogger;
+    a.insertLogger("zero", n0);
+    a.insertLogger("one", n1);
+
+    a.log("Hello TestLogger"); int line = __LINE__;
+    assert(n0.msg == "Hello TestLogger");
+    assert(n0.line == line);
+    assert(n1.msg == "Hello TestLogger");
+    assert(n0.line == line);
+}
+
+// Issue #16
+unittest
+{
+    import std.file : deleteme;
+    import std.stdio : File;
+    import std.string : indexOf;
+    string logName = deleteme ~ __FUNCTION__ ~ ".log";
+    auto logFileOutput = File(logName, "w");
+    scope(exit)
+    {
+        import std.file : remove;
+        logFileOutput.close();
+        remove(logName);
+    }
+    auto traceLog = new FileLogger(logFileOutput, LogLevel.all);
+    auto infoLog  = new TestLogger(LogLevel.info);
+
+    auto root = new MultiLogger(LogLevel.all);
+    root.insertLogger("fileLogger", traceLog);
+    root.insertLogger("stdoutLogger", infoLog);
+
+    string tMsg = "A trace message";
+    root.trace(tMsg); int line1 = __LINE__;
+
+    assert(infoLog.line != line1);
+    assert(infoLog.msg != tMsg);
+
+    string iMsg = "A info message";
+    root.info(iMsg); int line2 = __LINE__;
+
+    assert(infoLog.line == line2);
+    assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg);
+
+    logFileOutput.close();
+    logFileOutput = File(logName, "r");
+    assert(logFileOutput.isOpen);
+    assert(!logFileOutput.eof);
+
+    auto line = logFileOutput.readln();
+    assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg);
+    assert(!logFileOutput.eof);
+    line = logFileOutput.readln();
+    assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg);
+}
+
+@safe unittest
+{
+    auto dl = cast(FileLogger)sharedLog;
+    assert(dl !is null);
+    assert(dl.logLevel == LogLevel.all);
+    assert(globalLogLevel == LogLevel.all);
+
+    auto tl = cast(StdForwardLogger)stdThreadLocalLog;
+    assert(tl !is null);
+    stdThreadLocalLog.logLevel = LogLevel.all;
+}
diff --git a/libphobos/src/std/experimental/logger/nulllogger.d b/libphobos/src/std/experimental/logger/nulllogger.d
new file mode 100644
index 00000000000..3dde03be1ac
--- /dev/null
+++ b/libphobos/src/std/experimental/logger/nulllogger.d
@@ -0,0 +1,36 @@
+module std.experimental.logger.nulllogger;
+
+import std.experimental.logger.core;
+
+/** The $(D NullLogger) will not process any log messages.
+
+In case of a log message with $(D LogLevel.fatal) nothing will happen.
+*/
+class NullLogger : Logger
+{
+    /** The default constructor for the $(D NullLogger).
+
+    Independent of the parameter this Logger will never log a message.
+
+    Params:
+      lv = The $(D LogLevel) for the $(D NullLogger). By default the $(D LogLevel)
+      for $(D NullLogger) is $(D LogLevel.all).
+    */
+    this(const LogLevel lv = LogLevel.all) @safe
+    {
+        super(lv);
+        this.fatalHandler = delegate() {};
+    }
+
+    override protected void writeLogMsg(ref LogEntry payload) @safe @nogc
+    {
+    }
+}
+
+///
+@safe unittest
+{
+    auto nl1 = new NullLogger(LogLevel.all);
+    nl1.info("You will never read this.");
+    nl1.fatal("You will never read this, either and it will not throw");
+}
diff --git a/libphobos/src/std/experimental/logger/package.d b/libphobos/src/std/experimental/logger/package.d
new file mode 100644
index 00000000000..bd126f47ac3
--- /dev/null
+++ b/libphobos/src/std/experimental/logger/package.d
@@ -0,0 +1,183 @@
+/**
+Implements logging facilities.
+
+Copyright: Copyright Robert "burner" Schadek 2013 --
+License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+Authors: $(WEB http://www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek)
+
+$(H3 Basic Logging)
+
+Message logging is a common approach to expose runtime information of a
+program. Logging should be easy, but also flexible and powerful, therefore
+$(D D) provides a standard interface for logging.
+
+The easiest way to create a log message is to write:
+-------------
+import std.experimental.logger;
+
+void main() {
+    log("Hello World");
+}
+-------------
+This will print a message to the $(D stderr) device. The message will contain
+the filename, the linenumber, the name of the surrounding function, the time
+and the message.
+
+More complex log call can go along the lines like:
+-------------
+log("Logging to the sharedLog with its default LogLevel");
+logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging");
+info("Logging to the sharedLog with its info LogLevel");
+warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6");
+error("Logging to the sharedLog with its error LogLevel");
+errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with");
+critical("Logging to the"," sharedLog with its error LogLevel");
+fatal("Logging to the sharedLog with its fatal LogLevel");
+
+auto fLogger = new FileLogger("NameOfTheLogFile");
+fLogger.log("Logging to the fileLogger with its default LogLevel");
+fLogger.info("Logging to the fileLogger with its default LogLevel");
+fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6");
+fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less");
+fLogger.critical("Logging to the fileLogger with its info LogLevel");
+fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6");
+fLogger.fatal("Logging to the fileLogger with its warning LogLevel");
+-------------
+Additionally, this example shows how a new $(D FileLogger) is created.
+Individual $(D Logger) and the global log functions share commonly named
+functions to log data.
+
+The names of the functions are as follows:
+$(LI $(D log))
+$(LI $(D trace))
+$(LI $(D info))
+$(LI $(D warning))
+$(LI $(D critical))
+$(LI $(D fatal))
+The default $(D Logger) will by default log to $(D stderr) and has a default
+$(D LogLevel) of $(D LogLevel.all). The default Logger can be accessed by
+using the property called $(D sharedLog). This property a reference to the
+current default $(D Logger). This reference can be used to assign a new
+default $(D Logger).
+-------------
+sharedLog = new FileLogger("New_Default_Log_File.log");
+-------------
+
+Additional $(D Logger) can be created by creating a new instance of the
+required $(D Logger).
+
+$(H3 Logging Fundamentals)
+$(H4 LogLevel)
+The $(D LogLevel) of a log call can be defined in two ways. The first is by
+calling $(D log) and passing the $(D LogLevel) explicit as the first argument.
+The second way of setting the $(D LogLevel) of a
+log call, is by calling either $(D trace), $(D info), $(D warning),
+$(D critical), or $(D fatal). The log call will than have the respective
+$(D LogLevel). If no $(D LogLevel) is defined the log call will use the
+current $(D LogLevel) of the used $(D Logger). If data is logged with
+$(D LogLevel) $(D fatal) by default an $(D Error) will be thrown.
+This behaviour can be modified by using the member $(D fatalHandler) to
+assign a custom delegate to handle log call with $(D LogLevel) $(D fatal).
+
+$(H4 Conditional Logging)
+Conditional logging can be achieved be passing a $(D bool) as first
+argument to a log function. If conditional logging is used the condition must
+be $(D true) in order to have the log message logged.
+
+In order to combine an explicit $(D LogLevel) passing with conditional
+logging, the $(D LogLevel) has to be passed as first argument followed by the
+$(D bool).
+
+$(H4 Filtering Log Messages)
+Messages are logged if the $(D LogLevel) of the log message is greater than or
+equal to than the $(D LogLevel) of the used $(D Logger) and additionally if the
+$(D LogLevel) of the log message is greater equal to the global $(D LogLevel).
+If a condition is passed into the log call, this condition must be true.
+
+The global $(D LogLevel) is accessible by using $(D globalLogLevel).
+To assign the $(D LogLevel) of a $(D Logger) use the $(D logLevel) property of
+the logger.
+
+$(H4 Printf Style Logging)
+If $(D printf)-style logging is needed add a $(B f) to the logging call, such as
+$(D myLogger.infof("Hello %s", "world");) or $(D fatalf("errno %d", 1337)).
+The additional $(B f) appended to the function name enables $(D printf)-style
+logging for all combinations of explicit $(D LogLevel) and conditional
+logging functions and methods.
+
+$(H4 Thread Local Redirection)
+Calls to the free standing log functions are not directly forwarded to the
+global $(D Logger) $(D sharedLog). Actually, a thread local $(D Logger) of
+type $(D StdForwardLogger) process the log call and then, by default, forward
+the created $(D Logger.LogEntry) to the $(D sharedLog) $(D Logger).
+The thread local $(D Logger) is accessable by the $(D stdThreadLocalLog)
+property. This property allows to assign user defined $(D Logger). The default
+$(D LogLevel) of the $(D stdThreadLocalLog) $(D Logger) is $(D LogLevel.all)
+and it will therefore forward all messaged to the $(D sharedLog) $(D Logger).
+The $(D LogLevel) of the $(D stdThreadLocalLog) can be used to filter log
+calls before they reach the $(D sharedLog) $(D Logger).
+
+$(H3 User Defined Logger)
+To customize the $(D Logger) behavior, create a new $(D class) that inherits from
+the abstract $(D Logger) $(D class), and implements the $(D writeLogMsg)
+method.
+-------------
+class MyCustomLogger : Logger
+{
+    this(LogLevel lv) @safe
+    {
+        super(lv);
+    }
+
+    override void writeLogMsg(ref LogEntry payload)
+    {
+        // log message in my custom way
+    }
+}
+
+auto logger = new MyCustomLogger(LogLevel.info);
+logger.log("Awesome log message with LogLevel.info");
+-------------
+
+To gain more precise control over the logging process, additionally to
+overwriting the $(D writeLogMsg) method the methods $(D beginLogMsg),
+$(D logMsgPart) and $(D finishLogMsg) can be overwritten.
+
+$(H3 Compile Time Disabling of $(D Logger))
+In order to disable logging at compile time, pass $(D StdLoggerDisableLogging) as a
+version argument to the $(D D) compiler when compiling your program code.
+This will disable all logging functionality.
+Specific $(D LogLevel) can be disabled at compile time as well.
+In order to disable logging with the $(D trace) $(D LogLevel) pass
+$(D StdLoggerDisableTrace) as a version.
+The following table shows which version statement disables which
+$(D LogLevel).
+$(TABLE
+    $(TR $(TD $(D LogLevel.trace) ) $(TD StdLoggerDisableTrace))
+    $(TR $(TD $(D LogLevel.info) ) $(TD StdLoggerDisableInfo))
+    $(TR $(TD $(D LogLevel.warning) ) $(TD StdLoggerDisableWarning))
+    $(TR $(TD $(D LogLevel.error) ) $(TD StdLoggerDisableError))
+    $(TR $(TD $(D LogLevel.critical) ) $(TD StdLoggerDisableCritical))
+    $(TR $(TD $(D LogLevel.fatal) ) $(TD StdLoggerDisableFatal))
+)
+Such a version statement will only disable logging in the associated compile
+unit.
+
+$(H3 Provided Logger)
+By default four $(D Logger) implementations are given. The $(D FileLogger)
+logs data to files. It can also be used to log to $(D stdout) and $(D stderr)
+as these devices are files as well. A $(D Logger) that logs to $(D stdout) can
+therefore be created by $(D new FileLogger(stdout)).
+The $(D MultiLogger) is basically an associative array of $(D string)s to
+$(D Logger). It propagates log calls to its stored $(D Logger). The
+$(D ArrayLogger) contains an array of $(D Logger) and also propagates log
+calls to its stored $(D Logger). The $(D NullLogger) does not do anything. It
+will never log a message and will never throw on a log call with $(D LogLevel)
+$(D error).
+*/
+module std.experimental.logger;
+
+public import std.experimental.logger.core;
+public import std.experimental.logger.filelogger;
+public import std.experimental.logger.nulllogger;
+public import std.experimental.logger.multilogger;
diff --git a/libphobos/src/std/experimental/ndslice/internal.d b/libphobos/src/std/experimental/ndslice/internal.d
new file mode 100644
index 00000000000..78c186dc1ca
--- /dev/null
+++ b/libphobos/src/std/experimental/ndslice/internal.d
@@ -0,0 +1,194 @@
+module std.experimental.ndslice.internal;
+
+import std.traits;
+import std.meta; //: AliasSeq, anySatisfy, Filter, Reverse;
+
+package:
+
+enum string tailErrorMessage(
+    string fun = __FUNCTION__,
+    string pfun = __PRETTY_FUNCTION__) =
+"
+- - -
+Error in function
+" ~ fun ~ "
+- - -
+Function prototype
+" ~ pfun ~ "
+_____";
+
+mixin template _DefineRet()
+{
+    alias Ret = typeof(return);
+    static if (hasElaborateAssign!(Ret.PureRange))
+        Ret ret;
+    else
+        Ret ret = void;
+}
+
+mixin template DimensionsCountCTError()
+{
+    static assert(Dimensions.length <= N,
+        "Dimensions list length = " ~ Dimensions.length.stringof
+        ~ " should be less than or equal to N = " ~ N.stringof
+        ~ tailErrorMessage!());
+}
+
+enum DimensionsCountRTError = q{
+    assert(dimensions.length <= N,
+        "Dimensions list length should be less than or equal to N = " ~ N.stringof
+        ~ tailErrorMessage!());
+};
+
+mixin template DimensionCTError()
+{
+    static assert(dimension >= 0,
+        "dimension = " ~ dimension.stringof ~ " at position "
+        ~ i.stringof ~ " should be greater than or equal to 0"
+        ~ tailErrorMessage!());
+    static assert(dimension < N,
+        "dimension = " ~ dimension.stringof ~ " at position "
+        ~ i.stringof ~ " should be less than N = " ~ N.stringof
+        ~ tailErrorMessage!());
+}
+
+enum DimensionRTError = q{
+    static if (isSigned!(typeof(dimension)))
+    assert(dimension >= 0, "dimension should be greater than or equal to 0"
+        ~ tailErrorMessage!());
+    assert(dimension < N, "dimension should be less than N = " ~ N.stringof
+        ~ tailErrorMessage!());
+};
+
+private alias IncFront(Seq...) = AliasSeq!(Seq[0] + 1, Seq[1 .. $]);
+
+private alias DecFront(Seq...) = AliasSeq!(Seq[0] - 1, Seq[1 .. $]);
+
+private enum bool isNotZero(alias t) = t != 0;
+
+alias NSeqEvert(Seq...) = Filter!(isNotZero, DecFront!(Reverse!(IncFront!Seq)));
+
+alias Parts(Seq...) = DecAll!(IncFront!Seq);
+
+alias Snowball(Seq...) = AliasSeq!(size_t.init, SnowballImpl!(size_t.init, Seq));
+
+private template SnowballImpl(size_t val, Seq...)
+{
+    static if (Seq.length == 0)
+        alias SnowballImpl = AliasSeq!();
+    else
+        alias SnowballImpl = AliasSeq!(Seq[0] + val, SnowballImpl!(Seq[0] +  val, Seq[1 .. $]));
+}
+
+private template DecAll(Seq...)
+{
+    static if (Seq.length == 0)
+        alias DecAll = AliasSeq!();
+    else
+        alias DecAll = AliasSeq!(Seq[0] - 1, DecAll!(Seq[1 .. $]));
+}
+
+template SliceFromSeq(Range, Seq...)
+{
+    static if (Seq.length == 0)
+        alias SliceFromSeq = Range;
+    else
+    {
+        import std.experimental.ndslice.slice: Slice;
+        alias SliceFromSeq = SliceFromSeq!(Slice!(Seq[$ - 1], Range), Seq[0 .. $ - 1]);
+    }
+}
+
+template DynamicArrayDimensionsCount(T)
+{
+    static if(isDynamicArray!T)
+        enum size_t DynamicArrayDimensionsCount = 1 + DynamicArrayDimensionsCount!(typeof(T.init[0]));
+    else
+        enum size_t DynamicArrayDimensionsCount = 0;
+}
+
+bool isPermutation(size_t N)(auto ref in size_t[N] perm)
+{
+    int[N] mask;
+    if (isValidPartialPermutationImpl(perm, mask) == false)
+        return false;
+    foreach (e; mask)
+        if (e == false)
+            return false;
+    return true;
+}
+
+bool isValidPartialPermutation(size_t N)(in size_t[] perm)
+{
+    int[N] mask;
+    return isValidPartialPermutationImpl(perm, mask);
+}
+
+private bool isValidPartialPermutationImpl(size_t N)(in size_t[] perm, ref int[N] mask)
+{
+    if (perm.length == 0)
+        return false;
+    foreach (j; perm)
+    {
+        if (j >= N)
+            return false;
+        if (mask[j]) //duplicate
+            return false;
+        mask[j] = true;
+    }
+    return true;
+}
+
+enum isIndex(I) = is(I : size_t);
+
+private enum isReference(P) =
+    hasIndirections!P
+    || isFunctionPointer!P
+    || is(P == interface);
+
+enum hasReference(T) = anySatisfy!(isReference, RepresentationTypeTuple!T);
+
+alias ImplicitlyUnqual(T) = Select!(isImplicitlyConvertible!(T, Unqual!T), Unqual!T, T);
+
+//TODO: replace with `static foreach`
+template Iota(size_t i, size_t j)
+{
+    static assert(i <= j, "Iota: i should be less than or equal to j");
+    static if (i == j)
+        alias Iota = AliasSeq!();
+    else
+        alias Iota = AliasSeq!(i, Iota!(i + 1, j));
+}
+
+template Repeat(T, size_t N)
+{
+    static if (N)
+        alias Repeat = AliasSeq!(Repeat!(T, N - 1), T);
+    else
+        alias Repeat = AliasSeq!();
+}
+
+size_t lengthsProduct(size_t N)(auto ref in size_t[N] lengths)
+{
+    size_t length = lengths[0];
+    foreach (i; Iota!(1, N))
+            length *= lengths[i];
+    return length;
+}
+
+pure nothrow unittest
+{
+    const size_t[3] lengths = [3, 4, 5];
+    assert(lengthsProduct(lengths) == 60);
+    assert(lengthsProduct([3, 4, 5]) == 60);
+}
+
+enum canSave(T) = isPointer!T || isDynamicArray!T ||
+    __traits(compiles,
+    {
+        T r1 = T.init;
+        auto s1 = r1.save;
+        static assert (is(typeof(s1) == T));
+    });
+
+struct _Slice { size_t i, j; }
diff --git a/libphobos/src/std/experimental/ndslice/iteration.d b/libphobos/src/std/experimental/ndslice/iteration.d
new file mode 100644
index 00000000000..bbfd7d9e68d
--- /dev/null
+++ b/libphobos/src/std/experimental/ndslice/iteration.d
@@ -0,0 +1,1222 @@
+/**
+$(SCRIPT inhibitQuickIndex = 1;)
+
+This is a submodule of $(LINK2 std_experimental_ndslice.html, std.experimental.ndslice).
+
+Operators only change strides and lengths of a slice.
+The range of a slice remains unmodified.
+All operators return slice of the same type as the type of the argument.
+
+$(BOOKTABLE $(H2 Transpose operators),
+
+$(TR $(TH Function Name) $(TH Descriprottion))
+$(T2 transposed, `100000.iota.sliced(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.)
+$(T2 swapped, `1000.iota.sliced(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.)
+$(T2 everted, `1000.iota.sliced(3, 4, 5).everted.shape` returns `[5, 4, 3]`.)
+)
+See also $(SUBREF selection, evertPack).
+
+$(BOOKTABLE $(H2 Iteration operators),
+
+$(TR $(TH Function Name) $(TH Description))
+$(T2 strided, `1000.iota.sliced(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.)
+$(T2 reversed, `slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.)
+$(T2 allReversed, `20.iota.sliced(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.)
+)
+
+$(BOOKTABLE $(H2 Other operators),
+$(TR $(TH Function Name) $(TH Description))
+$(T2 rotated, `10.iota.sliced(2, 3).rotated` equals to `[[2, 5], [1, 4], [0, 3]]`.)
+)
+
+$(H4 Drop operators)
+
+$(LREF dropToHypercube)
+$(LREF drop) $(LREF dropBack)
+$(LREF dropOne) $(LREF dropBackOne)
+$(LREF dropExactly) $(LREF dropBackExactly)
+$(LREF allDrop) $(LREF allDropBack)
+$(LREF allDropOne) $(LREF allDropBackOne)
+$(LREF allDropExactly) $(LREF allDropBackExactly)
+
+$(GRAMMAR
+$(GNAME DropOperatorName):
+    $(D dropToHypercube)
+    $(GLINK DropRoot)
+    $(GLINK DropRoot) $(GLINK DropSuffix)
+    $(GLINK DropRoot) $(D Back)
+    $(GLINK DropRoot) $(D Back) $(GLINK DropSuffix)
+$(GNAME DropRoot):
+    $(D drop)
+    $(D allDrop)
+$(GNAME DropSuffix):
+    $(D One)
+    $(D Exactly)
+)
+
+$(H2 Bifacial operators)
+
+Some operators are bifacial,
+i.e. they have two versions: one with template parameters, and another one
+with function parameters. Versions with template parameters are preferable
+because they allow compile time checks and can be optimized better.
+
+$(BOOKTABLE ,
+
+$(TR $(TH Function Name) $(TH Variadic) $(TH Template) $(TH Function))
+$(T4 swapped, No, `slice.swapped!(2, 3)`, `slice.swapped(2, 3)`)
+$(T4 rotated, No, `slice.rotated!(2, 3)(-1)`, `slice.rotated(2, 3, -1)`)
+$(T4 strided, Yes/No, `slice.strided!(1, 2)(20, 40)`, `slice.strided(1, 20).strided(2, 40)`)
+$(T4 transposed, Yes, `slice.transposed!(1, 4, 3)`, `slice.transposed(1, 4, 3)`)
+$(T4 reversed, Yes, `slice.reversed!(0, 2)`, `slice.reversed(0, 2)`)
+)
+
+Bifacial interface of $(LREF drop), $(LREF dropBack)
+$(LREF dropExactly), and $(LREF dropBackExactly)
+is identical to that of $(LREF strided).
+
+Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne)
+is identical to that of $(LREF reversed).
+
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   Ilya Yaroshenko
+
+Source:    $(PHOBOSSRC std/_experimental/_ndslice/_iteration.d)
+
+Macros:
+SUBMODULE = $(LINK2 std_experimental_ndslice_$1.html, std.experimental.ndslice.$1)
+SUBREF = $(LINK2 std_experimental_ndslice_$1.html#.$2, $(TT $2))$(NBSP)
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
+*/
+module std.experimental.ndslice.iteration;
+
+import std.traits;
+
+import std.experimental.ndslice.internal;
+import std.experimental.ndslice.slice; //: Slice;
+
+private enum _swappedCode = q{
+    with (slice)
+    {
+        auto tl = _lengths[dimensionA];
+        auto ts = _strides[dimensionA];
+        _lengths[dimensionA] = _lengths[dimensionB];
+        _strides[dimensionA] = _strides[dimensionB];
+        _lengths[dimensionB] = tl;
+        _strides[dimensionB] = ts;
+    }
+    return slice;
+};
+
+/++
+Swaps two dimensions.
+
+Params:
+    slice = input slice
+    dimensionA = first dimension
+    dimensionB = second dimension
+Returns:
+    n-dimensional slice of the same type
+See_also: $(LREF everted), $(LREF transposed)
++/
+template swapped(size_t dimensionA, size_t dimensionB)
+{
+    auto swapped(size_t N, Range)(Slice!(N, Range) slice)
+    {
+        {
+            enum i = 0;
+            alias dimension = dimensionA;
+            mixin DimensionCTError;
+        }
+        {
+            enum i = 1;
+            alias dimension = dimensionB;
+            mixin DimensionCTError;
+        }
+        mixin (_swappedCode);
+    }
+}
+
+/// ditto
+Slice!(N, Range) swapped(size_t N, Range)(Slice!(N, Range) slice, size_t dimensionA, size_t dimensionB)
+in{
+    {
+        alias dimension = dimensionA;
+        mixin (DimensionRTError);
+    }
+    {
+        alias dimension = dimensionB;
+        mixin (DimensionRTError);
+    }
+}
+body
+{
+    mixin (_swappedCode);
+}
+
+/// ditto
+Slice!(2, Range) swapped(Range)(Slice!(2, Range) slice)
+body
+{
+    return slice.swapped!(0, 1);
+}
+
+/// Template
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert((3 * 4 * 5 * 6).iota
+        .sliced(3, 4, 5, 6)
+        .swapped!(3, 1)
+        .shape == cast(size_t[4])[3, 6, 5, 4]);
+}
+
+/// Function
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert((3 * 4 * 5 * 6).iota
+        .sliced(3, 4, 5, 6)
+        .swapped(1, 3)
+        .shape == cast(size_t[4])[3, 6, 5, 4]);
+}
+
+/// 2D
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert(12.iota
+        .sliced(3, 4)
+        .swapped
+        .shape == cast(size_t[2])[4, 3]);
+}
+
+private enum _rotatedCode = q{
+    k &= 0b11;
+    if (k == 0)
+        return slice;
+    if (k == 2)
+        return slice.allReversed;
+    static if (__traits(compiles, { enum _enum = dimensionA + dimensionB; }))
+    {
+        slice = slice.swapped!(dimensionA, dimensionB);
+        if (k == 1)
+            return slice.reversed!dimensionA;
+        else
+            return slice.reversed!dimensionB;
+    }
+    else
+    {
+        slice = slice.swapped (dimensionA, dimensionB);
+        if (k == 1)
+            return slice.reversed(dimensionA);
+        else
+            return slice.reversed(dimensionB);
+    }
+};
+
+/++
+Rotates two selected dimensions by `k*90` degrees.
+The order of dimensions is important.
+If the slice has two dimensions, the default direction is counterclockwise.
+
+Params:
+    slice = input slice
+    dimensionA = first dimension
+    dimensionB = second dimension
+    k = rotation counter, can be negative
+Returns:
+    n-dimensional slice of the same type
++/
+template rotated(size_t dimensionA, size_t dimensionB)
+{
+    auto rotated(size_t N, Range)(Slice!(N, Range) slice, sizediff_t k = 1)
+    {
+        {
+            enum i = 0;
+            alias dimension = dimensionA;
+            mixin DimensionCTError;
+        }
+        {
+            enum i = 1;
+            alias dimension = dimensionB;
+            mixin DimensionCTError;
+        }
+        mixin (_rotatedCode);
+    }
+}
+
+/// ditto
+Slice!(N, Range) rotated(size_t N, Range)(Slice!(N, Range) slice, size_t dimensionA, size_t dimensionB, sizediff_t k = 1)
+in{
+    {
+        alias dimension = dimensionA;
+        mixin (DimensionRTError);
+    }
+    {
+        alias dimension = dimensionB;
+        mixin (DimensionRTError);
+    }
+}
+body
+{
+    mixin (_rotatedCode);
+}
+
+/// ditto
+Slice!(2, Range) rotated(Range)(Slice!(2, Range) slice, sizediff_t k = 1)
+body
+{
+    return slice.rotated!(0, 1)(k);
+}
+
+/// Template
+@safe pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    auto slice = 6.iota.sliced(2, 3);
+
+    auto a = [[0, 1, 2],
+              [3, 4, 5]];
+
+    auto b = [[2, 5],
+              [1, 4],
+              [0, 3]];
+
+    auto c = [[5, 4, 3],
+              [2, 1, 0]];
+
+    auto d = [[3, 0],
+              [4, 1],
+              [5, 2]];
+
+    assert(slice.rotated       ( 4) == a);
+    assert(slice.rotated!(0, 1)(-4) == a);
+    assert(slice.rotated (1, 0,  8) == a);
+
+    assert(slice.rotated            == b);
+    assert(slice.rotated!(0, 1)(-3) == b);
+    assert(slice.rotated (1, 0,  3) == b);
+
+    assert(slice.rotated       ( 6) == c);
+    assert(slice.rotated!(0, 1)( 2) == c);
+    assert(slice.rotated (0, 1, -2) == c);
+
+    assert(slice.rotated       ( 7) == d);
+    assert(slice.rotated!(0, 1)( 3) == d);
+    assert(slice.rotated (1, 0,   ) == d);
+}
+
+/++
+Reverses the order of dimensions.
+
+Params:
+    slice = input slice
+Returns:
+    n-dimensional slice of the same type
+See_also: $(LREF swapped), $(LREF transposed)
++/
+Slice!(N, Range) everted(size_t N, Range)(auto ref Slice!(N, Range) slice)
+{
+    mixin _DefineRet;
+    with (slice)
+    {
+         foreach (i; Iota!(0, N))
+        {
+            ret._lengths[N - 1 - i] = _lengths[i];
+            ret._strides[N - 1 - i] = _strides[i];
+        }
+        foreach (i; Iota!(N, PureN))
+        {
+            ret._lengths[i] = _lengths[i];
+            ret._strides[i] = _strides[i];
+        }
+        ret._ptr = _ptr;
+        return ret;
+    }
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert(60.iota
+        .sliced(3, 4, 5)
+        .everted
+        .shape == cast(size_t[3])[5, 4, 3]);
+}
+
+private enum _transposedCode = q{
+    mixin _DefineRet;
+    with (slice)
+    {
+        foreach (i; Iota!(0, N))
+        {
+            ret._lengths[i] = _lengths[perm[i]];
+            ret._strides[i] = _strides[perm[i]];
+        }
+        foreach (i; Iota!(N, PureN))
+        {
+            ret._lengths[i] = _lengths[i];
+            ret._strides[i] = _strides[i];
+        }
+        ret._ptr = _ptr;
+        return ret;
+    }
+};
+
+private size_t[N] completeTranspose(size_t N)(in size_t[] dimensions)
+{
+    assert(dimensions.length <= N);
+    size_t[N] ctr;
+    uint[N] mask;
+    foreach (i, ref dimension; dimensions)
+    {
+        mask[dimension] = true;
+        ctr[i] = dimension;
+    }
+    size_t j = dimensions.length;
+    foreach (i, e; mask)
+        if (e == false)
+            ctr[j++] = i;
+    return ctr;
+}
+
+/++
+N-dimensional transpose operator.
+Brings selected dimensions to the first position.
+Params:
+    slice = input slice
+    Dimensions = indexes of dimensions to be brought to the first position
+    dimensions = indexes of dimensions to be brought to the first position
+    dimension = index of dimension to be brought to the first position
+Returns:
+    n-dimensional slice of the same type
+See_also: $(LREF swapped), $(LREF everted)
++/
+template transposed(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice)
+    {
+        mixin DimensionsCountCTError;
+        foreach (i, dimension; Dimensions)
+            mixin DimensionCTError;
+        static assert(isValidPartialPermutation!N([Dimensions]),
+            "Failed to complete permutation of dimensions " ~ Dimensions.stringof
+            ~ tailErrorMessage!());
+        enum perm = completeTranspose!N([Dimensions]);
+        static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
+        mixin (_transposedCode);
+    }
+}
+
+///ditto
+Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice, size_t dimension)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    size_t[1] permutation = void;
+    permutation[0] = dimension;
+    immutable perm = completeTranspose!N(permutation);
+    assert(perm.isPermutation, __PRETTY_FUNCTION__  ~ ": internal error.");
+    mixin (_transposedCode);
+}
+
+///ditto
+Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice, in size_t[] dimensions...)
+in
+{
+    mixin (DimensionsCountRTError);
+    foreach (dimension; dimensions)
+        mixin (DimensionRTError);
+}
+body
+{
+    assert(dimensions.isValidPartialPermutation!N,
+        "Failed to complete permutation of dimensions."
+        ~ tailErrorMessage!());
+    immutable perm = completeTranspose!N(dimensions);
+    assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
+    mixin (_transposedCode);
+}
+
+///ditto
+Slice!(2, Range) transposed(Range)(auto ref Slice!(2, Range) slice)
+{
+    return .transposed!(1, 0)(slice);
+}
+
+/// Template
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert((3 * 4 * 5 * 6 * 7).iota
+        .sliced(3, 4, 5, 6, 7)
+        .transposed!(4, 1, 0)
+        .shape == cast(size_t[5])[7, 4, 3, 5, 6]);
+}
+
+/// Function
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert((3 * 4 * 5 * 6 * 7).iota
+        .sliced(3, 4, 5, 6, 7)
+        .transposed(4, 1, 0)
+        .shape == cast(size_t[5])[7, 4, 3, 5, 6]);
+}
+
+/// Single-argument function
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert((3 * 4 * 5 * 6 * 7).iota
+        .sliced(3, 4, 5, 6, 7)
+        .transposed(4)
+        .shape == cast(size_t[5])[7, 3, 4, 5, 6]);
+}
+
+/// `2`-dimensional transpose
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    assert(12.iota
+        .sliced(3, 4)
+        .transposed
+        .shape == cast(size_t[2])[4, 3]);
+}
+
+private enum _reversedCode = q{
+    with (slice)
+    {
+        if (_lengths[dimension])
+            _ptr += _strides[dimension] * (_lengths[dimension] - 1);
+        _strides[dimension] = -_strides[dimension];
+    }
+};
+
+/++
+Reverses the direction of iteration for all dimensions.
+Params:
+    slice = input slice
+Returns:
+    n-dimensional slice of the same type
++/
+Slice!(N, Range) allReversed(size_t N, Range)(Slice!(N, Range) slice)
+{
+    foreach (dimension; Iota!(0, N))
+    {
+        mixin (_reversedCode);
+    }
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5).allReversed;
+    auto b = 20.iota.retro.sliced(4, 5);
+    assert(a == b);
+}
+
+/++
+Reverses the direction of iteration for selected dimensions.
+
+Params:
+    slice = input slice
+    Dimensions = indexes of dimensions to reverse order of iteration
+    dimensions = indexes of dimensions to reverse order of iteration
+    dimension = index of dimension to reverse order of iteration
+Returns:
+    n-dimensional slice of the same type
++/
+template reversed(Dimensions...)
+    if (Dimensions.length)
+{
+    auto reversed(size_t N, Range)(Slice!(N, Range) slice)
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            mixin (_reversedCode);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) reversed(size_t N, Range)(Slice!(N, Range) slice, size_t dimension)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    mixin (_reversedCode);
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) reversed(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...)
+in
+{
+    foreach (dimension; dimensions)
+        mixin (DimensionRTError);
+}
+body
+{
+    foreach (dimension; dimensions)
+        mixin (_reversedCode);
+    return slice;
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = [1, 2, 3, 4].sliced(2, 2);
+    assert(slice                    == [[1, 2], [3, 4]]);
+
+    // Template
+    assert(slice.reversed! 0        == [[3, 4], [1, 2]]);
+    assert(slice.reversed! 1        == [[2, 1], [4, 3]]);
+    assert(slice.reversed!(0, 1)    == [[4, 3], [2, 1]]);
+    assert(slice.reversed!(1, 0)    == [[4, 3], [2, 1]]);
+    assert(slice.reversed!(1, 1)    == [[1, 2], [3, 4]]);
+    assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]);
+
+    // Function
+    assert(slice.reversed (0)       == [[3, 4], [1, 2]]);
+    assert(slice.reversed (1)       == [[2, 1], [4, 3]]);
+    assert(slice.reversed (0, 1)    == [[4, 3], [2, 1]]);
+    assert(slice.reversed (1, 0)    == [[4, 3], [2, 1]]);
+    assert(slice.reversed (1, 1)    == [[1, 2], [3, 4]]);
+    assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.selection;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, retro, chain;
+    auto i0 = iota(0,  4); auto r0 = i0.retro;
+    auto i1 = iota(4,  8); auto r1 = i1.retro;
+    auto i2 = iota(8, 12); auto r2 = i2.retro;
+    auto slice = 12.iota.sliced(3, 4);
+    assert(slice                   .byElement.equal(chain(i0, i1, i2)));
+    // Template
+    assert(slice.reversed!(0)      .byElement.equal(chain(i2, i1, i0)));
+    assert(slice.reversed!(1)      .byElement.equal(chain(r0, r1, r2)));
+    assert(slice.reversed!(0, 1)   .byElement.equal(chain(r2, r1, r0)));
+    assert(slice.reversed!(1, 0)   .byElement.equal(chain(r2, r1, r0)));
+    assert(slice.reversed!(1, 1)   .byElement.equal(chain(i0, i1, i2)));
+    assert(slice.reversed!(0, 0, 0).byElement.equal(chain(i2, i1, i0)));
+    // Function
+    assert(slice.reversed (0)      .byElement.equal(chain(i2, i1, i0)));
+    assert(slice.reversed (1)      .byElement.equal(chain(r0, r1, r2)));
+    assert(slice.reversed (0, 1)   .byElement.equal(chain(r2, r1, r0)));
+    assert(slice.reversed (1, 0)   .byElement.equal(chain(r2, r1, r0)));
+    assert(slice.reversed (1, 1)   .byElement.equal(chain(i0, i1, i2)));
+    assert(slice.reversed (0, 0, 0).byElement.equal(chain(i2, i1, i0)));
+}
+
+private enum _stridedCode = q{
+    assert(factor > 0, "factor must be positive"
+        ~ tailErrorMessage!());
+    immutable rem = slice._lengths[dimension] % factor;
+    slice._lengths[dimension] /= factor;
+    if (slice._lengths[dimension]) //do not remove `if (...)`
+        slice._strides[dimension] *= factor;
+    if (rem)
+        slice._lengths[dimension]++;
+};
+
+/++
+Multiplies the stride of the selected dimension by the factor.
+
+Params:
+    slice = input slice
+    Dimensions = indexes of dimensions to be strided
+    dimensions = indexes of dimensions to be strided
+    factors = list of step extension factors
+    factor = step extension factors
+Returns:
+    n-dimensional slice of the same type
++/
+template strided(Dimensions...)
+    if (Dimensions.length)
+{
+    auto strided(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) factors)
+    body
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            immutable factor = factors[i];
+            mixin (_stridedCode);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) strided(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t factor)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    mixin (_stridedCode);
+    return slice;
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice
+         = [0,1,2,3,    4,5,6,7,   8,9,10,11].sliced(3, 4);
+
+    assert(slice
+        == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]);
+
+    // Template
+    assert(slice.strided!0(2)
+        == [[0,1,2,3],            [8,9,10,11]]);
+
+    assert(slice.strided!1(3)
+        == [[0,    3], [4,    7], [8,     11]]);
+
+    assert(slice.strided!(0, 1)(2, 3)
+        == [[0,    3],            [8,     11]]);
+
+    // Function
+    assert(slice.strided(0, 2)
+        == [[0,1,2,3],            [8,9,10,11]]);
+
+    assert(slice.strided(1, 3)
+        == [[0,    3], [4,    7], [8,     11]]);
+
+    assert(slice.strided(0, 2).strided(1, 3)
+        == [[0,    3],            [8,     11]]);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.range: iota;
+    static assert(iota(13 * 40).sliced(13, 40).strided!(0, 1)(2, 5).shape == [7, 8]);
+    static assert(93.iota.sliced(93).strided!(0, 0)(7, 3).shape == [5]);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.selection;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, stride, chain;
+    auto i0 = iota(0,  4); auto s0 = i0.stride(3);
+    auto i1 = iota(4,  8); auto s1 = i1.stride(3);
+    auto i2 = iota(8, 12); auto s2 = i2.stride(3);
+    auto slice = 12.iota.sliced(3, 4);
+    assert(slice              .byElement.equal(chain(i0, i1, i2)));
+    // Template
+    assert(slice.strided!0(2) .byElement.equal(chain(i0, i2)));
+    assert(slice.strided!1(3) .byElement.equal(chain(s0, s1, s2)));
+    assert(slice.strided!(0, 1)(2, 3).byElement.equal(chain(s0, s2)));
+    // Function
+    assert(slice.strided(0, 2).byElement.equal(chain(i0, i2)));
+    assert(slice.strided(1, 3).byElement.equal(chain(s0, s1, s2)));
+    assert(slice.strided(0, 2).strided(1, 3).byElement.equal(chain(s0, s2)));
+}
+
+/++
+Convenience function which calls `slice.popFront!dimension()` for each dimension and returns the slice.
+
+`allDropBackOne` provides the same functionality but calls `slice.popBack!dimension()` instead.
+
+Params:
+    slice = input slice
+Returns:
+    n-dimensional slice of the same type
++/
+Slice!(N, Range) allDropOne(size_t N, Range)(Slice!(N, Range) slice)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popFront!dimension;
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) allDropBackOne(size_t N, Range)(Slice!(N, Range) slice)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popBack!dimension;
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.allDropOne[0, 0] == 6);
+    assert(a.allDropOne.shape == cast(size_t[2])[3, 4]);
+    assert(a.allDropBackOne[$ - 1, $ - 1] == 13);
+    assert(a.allDropBackOne.shape == cast(size_t[2])[3, 4]);
+}
+
+/++
+These functions are similar to `allDrop` and `allDropBack` but they call
+`slice.popFrontExactly!dimension(n)` and `slice.popBackExactly!dimension(n)` instead.
+
+Note:
+Unlike `allDrop`, `allDropExactly(n)` assume that the slice holds
+a multi-dimensional cube with a size of at least n.
+This makes `allDropExactly` faster than `allDrop`.
+Only use `allDropExactly` when it is guaranteed that the slice holds
+a multi-dimensional cube with a size of at least n.
+
+Params:
+    slice = input slice
+    n = number of elements to drop
+Returns:
+    n-dimensional slice of the same type
++/
+Slice!(N, Range) allDropExactly(size_t N, Range)(Slice!(N, Range) slice, size_t n)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popFrontExactly!dimension(n);
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) allDropBackExactly(size_t N, Range)(Slice!(N, Range) slice, size_t n)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popBackExactly!dimension(n);
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.allDropExactly(2)[0, 0] == 12);
+    assert(a.allDropExactly(2).shape == cast(size_t[2])[2, 3]);
+    assert(a.allDropBackExactly(2)[$ - 1, $ - 1] == 7);
+    assert(a.allDropBackExactly(2).shape == cast(size_t[2])[2, 3]);
+}
+
+/++
+Convenience function which calls `slice.popFrontN!dimension(n)` for each dimension and returns the slice.
+
+`allDropBack` provides the same functionality but calls `slice.popBackN!dimension(n)` instead.
+
+Note:
+`allDrop` and `allDropBack` remove up to n elements and stop when the slice is empty.
+
+Params:
+    slice = input slice
+    n = number of elements to drop
+Returns:
+    n-dimensional slice of the same type
++/
+Slice!(N, Range) allDrop(size_t N, Range)(Slice!(N, Range) slice, size_t n)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popFrontN!dimension(n);
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) allDropBack(size_t N, Range)(Slice!(N, Range) slice, size_t n)
+{
+    foreach (dimension; Iota!(0, N))
+        slice.popBackN!dimension(n);
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.allDrop(2)[0, 0] == 12);
+    assert(a.allDrop(2).shape == cast(size_t[2])[2, 3]);
+    assert(a.allDropBack(2)[$ - 1, $ - 1] == 7);
+    assert(a.allDropBack(2).shape == cast(size_t[2])[2, 3]);
+
+    assert(a.allDrop    (5).shape == cast(size_t[2])[0, 0]);
+    assert(a.allDropBack(5).shape == cast(size_t[2])[0, 0]);
+}
+
+/++
+Convenience function which calls `slice.popFront!dimension()` for selected dimensions and returns the slice.
+
+`dropBackOne` provides the same functionality but calls `slice.popBack!dimension()` instead.
+
+Params:
+    slice = input slice
+Returns:
+    n-dimensional slice of the same type
++/
+template dropOne(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice)
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popFront!dimension;
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice, size_t dimension)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popFront(dimension);
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...)
+in
+{
+    foreach (dimension; dimensions)
+        mixin (DimensionRTError);
+}
+body
+{
+    foreach (dimension; dimensions)
+        slice.popFront(dimension);
+    return slice;
+}
+
+///ditto
+template dropBackOne(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice)
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popBack!dimension;
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice, size_t dimension)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popBack(dimension);
+    return slice;
+}
+
+///ditto
+Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...)
+in
+{
+    foreach (dimension; dimensions)
+        mixin (DimensionRTError);
+}
+body
+{
+    foreach (dimension; dimensions)
+        slice.popBack(dimension);
+    return slice;
+}
+
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.dropOne!(1, 0)[0, 0] == 6);
+    assert(a.dropOne (1, 0)[0, 0] == 6);
+    assert(a.dropOne!(1, 0).shape == cast(size_t[2])[3, 4]);
+    assert(a.dropOne (1, 0).shape == cast(size_t[2])[3, 4]);
+    assert(a.dropBackOne!(1, 0)[$ - 1, $ - 1] == 13);
+    assert(a.dropBackOne (1, 0)[$ - 1, $ - 1] == 13);
+    assert(a.dropBackOne!(1, 0).shape == cast(size_t[2])[3, 4]);
+    assert(a.dropBackOne (1, 0).shape == cast(size_t[2])[3, 4]);
+
+    assert(a.dropOne!(0, 0)[0, 0] == 10);
+    assert(a.dropOne (0, 0)[0, 0] == 10);
+    assert(a.dropOne!(0, 0).shape == cast(size_t[2])[2, 5]);
+    assert(a.dropOne (0, 0).shape == cast(size_t[2])[2, 5]);
+    assert(a.dropBackOne!(1, 1)[$ - 1, $ - 1] == 17);
+    assert(a.dropBackOne (1, 1)[$ - 1, $ - 1] == 17);
+    assert(a.dropBackOne!(1, 1).shape == cast(size_t[2])[4, 3]);
+    assert(a.dropBackOne (1, 1).shape == cast(size_t[2])[4, 3]);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.dropOne(0).dropOne(0)[0, 0] == 10);
+    assert(a.dropOne(0).dropOne(0).shape == cast(size_t[2])[2, 5]);
+    assert(a.dropBackOne(1).dropBackOne(1)[$ - 1, $ - 1] == 17);
+    assert(a.dropBackOne(1).dropBackOne(1).shape == cast(size_t[2])[4, 3]);
+}
+
+
+/++
+These functions are similar to `drop` and `dropBack` but they call
+`slice.popFrontExactly!dimension(n)` and `slice.popBackExactly!dimension(n)` instead.
+
+Note:
+Unlike `drop`, `dropExactly` assumes that the slice holds enough elements in
+the selected dimension.
+This makes `dropExactly` faster than `drop`.
+
+Params:
+    slice = input slice
+    ns = list of numbers of elements to drop
+    n = number of elements to drop
+Returns:
+    n-dimensional slice of the same type
++/
+template dropExactly(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) ns)
+    body
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popFrontExactly!dimension(ns[i]);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popFrontExactly(dimension, n);
+    return slice;
+}
+
+///ditto
+template dropBackExactly(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) ns)
+    body
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popBackExactly!dimension(ns[i]);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popBackExactly(dimension, n);
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.dropExactly    !(1, 0)(2, 3)[0, 0] == 17);
+    assert(a.dropExactly    !(1, 0)(2, 3).shape == cast(size_t[2])[1, 3]);
+    assert(a.dropBackExactly!(0, 1)(2, 3)[$ - 1, $ - 1] == 6);
+    assert(a.dropBackExactly!(0, 1)(2, 3).shape == cast(size_t[2])[2, 2]);
+
+    assert(a.dropExactly(1, 2).dropExactly(0, 3)[0, 0] == 17);
+    assert(a.dropExactly(1, 2).dropExactly(0, 3).shape == cast(size_t[2])[1, 3]);
+    assert(a.dropBackExactly(0, 2).dropBackExactly(1, 3)[$ - 1, $ - 1] == 6);
+    assert(a.dropBackExactly(0, 2).dropBackExactly(1, 3).shape == cast(size_t[2])[2, 2]);
+}
+
+/++
+Convenience function which calls `slice.popFrontN!dimension(n)` for the selected
+dimension and returns the slice.
+
+`dropBack` provides the same functionality but calls `slice.popBackN!dimension(n)` instead.
+
+Note:
+`drop` and `dropBack` remove up to n elements and stop when the slice is empty.
+
+Params:
+    slice = input slice
+    ns = list of numbers of elements to drop
+    n = number of elements to drop
+Returns:
+    n-dimensional slice of the same type
++/
+template drop(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) ns)
+    body
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popFrontN!dimension(ns[i]);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popFrontN(dimension, n);
+    return slice;
+}
+
+///ditto
+template dropBack(Dimensions...)
+    if (Dimensions.length)
+{
+    Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) ns)
+    body
+    {
+        foreach (i, dimension; Dimensions)
+        {
+            mixin DimensionCTError;
+            slice.popBackN!dimension(ns[i]);
+        }
+        return slice;
+    }
+}
+
+///ditto
+Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
+in
+{
+    mixin (DimensionRTError);
+}
+body
+{
+    slice.popBackN(dimension, n);
+    return slice;
+}
+
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    auto a = 20.iota.sliced(4, 5);
+
+    assert(a.drop    !(1, 0)(2, 3)[0, 0] == 17);
+    assert(a.drop    !(1, 0)(2, 3).shape == cast(size_t[2])[1, 3]);
+    assert(a.dropBack!(0, 1)(2, 3)[$ - 1, $ - 1] == 6);
+    assert(a.dropBack!(0, 1)(2, 3).shape == cast(size_t[2])[2, 2]);
+    assert(a.dropBack!(0, 1)(5, 5).shape == cast(size_t[2])[0, 0]);
+
+
+    assert(a.drop(1, 2).drop(0, 3)[0, 0] == 17);
+    assert(a.drop(1, 2).drop(0, 3).shape == cast(size_t[2])[1, 3]);
+    assert(a.dropBack(0, 2).dropBack(1, 3)[$ - 1, $ - 1] == 6);
+    assert(a.dropBack(0, 2).dropBack(1, 3).shape == cast(size_t[2])[2, 2]);
+    assert(a.dropBack(0, 5).dropBack(1, 5).shape == cast(size_t[2])[0, 0]);
+}
+
+/++
+Returns maximal multidimensional cube.
+
+Params:
+    slice = input slice
+Returns:
+    n-dimensional slice of the same type
++/
+Slice!(N, Range) dropToHypercube(size_t N, Range)(Slice!(N, Range) slice)
+body
+{
+    size_t length = slice._lengths[0];
+    foreach (i; Iota!(1, N))
+        if (length > slice._lengths[i])
+            length = slice._lengths[i];
+    foreach (i; Iota!(0, N))
+        slice._lengths[i] = length;
+    return slice;
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, retro;
+    assert((5 * 3 * 6 * 7).iota
+        .sliced(5, 3, 6, 7)
+        .dropToHypercube
+        .shape == cast(size_t[4])[3, 3, 3, 3]);
+}
diff --git a/libphobos/src/std/experimental/ndslice/package.d b/libphobos/src/std/experimental/ndslice/package.d
new file mode 100644
index 00000000000..a0ec713480b
--- /dev/null
+++ b/libphobos/src/std/experimental/ndslice/package.d
@@ -0,0 +1,562 @@
+/+
+## Guide for Slice/BLAS contributors
+
+1. Make sure functions are
+       a. inlined(!),
+       b. `@nogc`,
+       c. `nothrow`,
+       d. `pure`.
+    For this reason, it is preferable to use _simple_ `assert`s with messages
+    that can be computed at compile time.
+    The goals are:
+        1. to reduce executable size for _any_ compilation mode
+        2. to reduce template bloat in object files
+        3. to reduce compilation time
+        4. to allow users to write extern C bindings for code libraries on `Slice` type.
+
+2. `std.format`, `std.string`, and `std.conv` should not be used in error
+    message formatting.`"Use" ~ Concatenation.stringof`.
+
+3. `mixin template`s may be used for pretty error message formatting.
+
+4. `Exception`s/`enforce`s should no be used to check indexes and lengths.
+    Exceptions are only allowed for algorithms where validation of input data is
+    too complicated for the user. `reshape` function is a good example of a case
+    where Exceptions are required.
+    If a function might throw an exception, an example with exception handing should be added.
+
+5.  For simple checks like matrix transposition, compile time flags should not be used.
+    It is much better to opt for runtime matrix transposition.
+    Furthermore, Slice type provides runtime matrix transposition out of the box.
+
+6.  _Fortran_VS_C_ flags should not be used. They are about notation,
+    but not about the algorithm itself. For math world users,
+    a corresponding code example might be included in the documentation.
+    `transposed` / `everted` can be used in cache-friendly codes.
+
+7. Compile time evaluation should not be used to produce dummy types like `IdentityMatrix`.
+
+8. Memory allocation and algorithm logic should be separated whenever possible.
+
+9. CTFE unittests should be added to new functions.
++/
+
+/**
+$(H1 Multidimensional Random Access Ranges)
+
+The package provides a multidimensional array implementation.
+It would be well suited to creating machine learning and image
+processing algorithms, but should also be general enough for use anywhere with
+homogeneously-typed multidimensional data.
+In addition, it includes various functions for iteration, accessing, and manipulation.
+
+Quick_Start:
+$(SUBREF slice, sliced) is a function designed to create
+a multidimensional view over a range.
+Multidimensional view is presented by $(SUBREF slice, Slice) type.
+------
+auto matrix = new double[12].sliced(3, 4);
+matrix[] = 0;
+------
+
+Note:
+In many examples $(LINK2 std_range.html#iota, std.range.iota) is used
+instead of a regular array, which makes it
+possible to carry out tests without memory allocation.
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Submodule) $(TH Declarations)
+)
+$(TR $(TDNW Basic Level
+        $(BR) $(SMALL $(SUBREF slice, Slice), its properties, operator overloading))
+     $(TDNW $(SUBMODULE slice))
+     $(TD
+        $(SUBREF slice, sliced)
+        $(SUBREF slice, Slice)
+        $(SUBREF slice, assumeSameStructure)
+        $(SUBREF slice, ReplaceArrayWithPointer)
+        $(SUBREF slice, DeepElementType)
+    )
+)
+$(TR $(TDNW Middle Level
+        $(BR) $(SMALL Various iteration operators))
+     $(TDNW $(SUBMODULE iteration))
+     $(TD
+        $(SUBREF iteration, transposed)
+        $(SUBREF iteration, strided)
+        $(SUBREF iteration, reversed)
+        $(SUBREF iteration, rotated)
+        $(SUBREF iteration, everted)
+        $(SUBREF iteration, swapped)
+        $(SUBREF iteration, allReversed)
+        $(SUBREF iteration, dropToHypercube) and other `drop` primitives
+    )
+)
+$(TR $(TDNW Advanced Level $(BR)
+        $(SMALL Abstract operators for loop free programming
+            $(BR) Take `movingWindowByChannel` as an example))
+     $(TDNW $(SUBMODULE selection))
+     $(TD
+        $(SUBREF selection, blocks)
+        $(SUBREF selection, windows)
+        $(SUBREF selection, diagonal)
+        $(SUBREF selection, reshape)
+        $(SUBREF selection, byElement)
+        $(SUBREF selection, byElementInStandardSimplex)
+        $(SUBREF selection, indexSlice)
+        $(SUBREF selection, pack)
+        $(SUBREF selection, evertPack)
+        $(SUBREF selection, unpack)
+    )
+)
+))
+
+$(H2 Example: Image Processing)
+
+A median filter is implemented as an example. The function
+`movingWindowByChannel` can also be used with other filters that use a sliding
+window as the argument, in particular with convolution matrices such as the
+$(LINK2 https://en.wikipedia.org/wiki/Sobel_operator, Sobel operator).
+
+`movingWindowByChannel` iterates over an image in sliding window mode.
+Each window is transferred to a `filter`, which calculates the value of the
+pixel that corresponds to the given window.
+
+This function does not calculate border cases in which a window overlaps
+the image partially. However, the function can still be used to carry out such
+calculations. That can be done by creating an amplified image, with the edges
+reflected from the original image, and then applying the given function to the
+new file.
+
+Note: You can find the example at
+$(LINK2 https://github.com/DlangScience/examples/tree/master/image_processing/median-filter, GitHub).
+
+-------
+/++
+Params:
+    filter = unary function. Dimension window 2D is the argument.
+    image = image dimensions `(h, w, c)`,
+        where с is the number of channels in the image
+    nr = number of rows in the window
+    nс = number of columns in the window
+
+Returns:
+    image dimensions `(h - nr + 1, w - nc + 1, c)`,
+        where с is the number of channels in the image.
+        Dense data layout is guaranteed.
++/
+
+Slice!(3, C*) movingWindowByChannel(alias filter, C)
+(Slice!(3, C*) image, size_t nr, size_t nc)
+{
+    import std.algorithm.iteration: map;
+    import std.array: array;
+
+        // 0. 3D
+        // The last dimension represents the color channel.
+    auto wnds = image
+        // 1. 2D composed of 1D
+        // Packs the last dimension.
+        .pack!1
+        // 2. 2D composed of 2D composed of 1D
+        // Splits image into overlapping windows.
+        .windows(nr, nc)
+        // 3. 5D
+        // Unpacks the windows.
+        .unpack
+        // 4. 5D
+        // Brings the color channel dimension to the third position.
+        .transposed!(0, 1, 4)
+        // 5. 3D Composed of 2D
+        // Packs the last two dimensions.
+        .pack!2;
+
+    return wnds
+        // 6. Range composed of 2D
+        // Gathers all windows in the range.
+        .byElement
+        // 7. Range composed of pixels
+        // 2D to pixel lazy conversion.
+        .map!filter
+        // 8. `C[]`
+        // The only memory allocation in this function.
+        .array
+        // 9. 3D
+        // Returns slice with corresponding shape.
+        .sliced(wnds.shape);
+}
+-------
+
+A function that calculates the value of iterator median is also necessary.
+
+-------
+/++
+
+Params:
+    r = input range
+    buf = buffer with length no less than the number of elements in `r`
+Returns:
+    median value over the range `r`
++/
+T median(Range, T)(Range r, T[] buf)
+{
+    import std.algorithm.sorting: topN;
+    size_t n;
+    foreach (e; r)
+        buf[n++] = e;
+    auto m = n >> 1;
+    buf[0 .. n].topN(m);
+    return buf[m];
+}
+-------
+
+The `main` function:
+
+-------
+void main(string[] args)
+{
+    import std.conv: to;
+    import std.getopt: getopt, defaultGetoptPrinter;
+    import std.path: stripExtension;
+
+    uint nr, nc, def = 3;
+    auto helpInformation = args.getopt(
+        "nr", "number of rows in window, default value is " ~ def.to!string, &nr,
+        "nc", "number of columns in window, default value is equal to nr", &nc);
+    if (helpInformation.helpWanted)
+    {
+        defaultGetoptPrinter(
+            "Usage: median-filter [<options...>] [<file_names...>]\noptions:",
+            helpInformation.options);
+        return;
+    }
+    if (!nr) nr = def;
+    if (!nc) nc = nr;
+
+    auto buf = new ubyte[nr * nc];
+
+    foreach (name; args[1 .. $])
+    {
+        import imageformats; // can be found at code.dlang.org
+
+        IFImage image = read_image(name);
+
+        auto ret = image.pixels
+            .sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c)
+            .movingWindowByChannel
+                !(window => median(window.byElement, buf))
+                 (nr, nc);
+
+        write_image(
+            name.stripExtension ~ "_filtered.png",
+            ret.length!1,
+            ret.length!0,
+            (&ret[0, 0, 0])[0 .. ret.elementsCount]);
+    }
+}
+-------
+
+This program works both with color and grayscale images.
+
+-------
+$ median-filter --help
+Usage: median-filter [<options...>] [<file_names...>]
+options:
+     --nr number of rows in window, default value is 3
+     --nc number of columns in window default value equals to nr
+-h --help This help information.
+-------
+
+$(H2 Compared with `numpy.ndarray`)
+
+numpy is undoubtedly one of the most effective software packages that has
+facilitated the work of many engineers and scientists. However, due to the
+specifics of implementation of Python, a programmer who wishes to use the
+functions not represented in numpy may find that the built-in functions
+implemented specifically for numpy are not enough, and their Python
+implementations work at a very low speed. Extending numpy can be done, but
+is somewhat laborious as even the most basic numpy functions that refer
+directly to `ndarray` data must be implemented in C for reasonable performance.
+
+At the same time, while working with `ndslice`, an engineer has access to the
+whole set of standard D library, so the functions he creates will be as
+efficient as if they were written in C.
+
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   Ilya Yaroshenko
+
+Acknowledgements:   John Loughran Colvin
+
+Source:    $(PHOBOSSRC std/_experimental/_ndslice/_package.d)
+
+Macros:
+SUBMODULE = $(LINK2 std_experimental_ndslice_$1.html, std.experimental.ndslice.$1)
+SUBREF = $(LINK2 std_experimental_ndslice_$1.html#.$2, $(TT $2))$(NBSP)
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
+*/
+module std.experimental.ndslice;
+
+public import std.experimental.ndslice.slice;
+public import std.experimental.ndslice.iteration;
+public import std.experimental.ndslice.selection;
+
+// relaxed example
+unittest
+{
+    static Slice!(3, ubyte*) movingWindowByChannel
+    (Slice!(3, ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(2, ubyte*)) filter)
+    {
+        import std.algorithm.iteration: map;
+        import std.array: array;
+        auto wnds = image
+            .pack!1
+            .windows(nr, nc)
+            .unpack
+            .transposed!(0, 1, 4)
+            .pack!2;
+        return wnds
+            .byElement
+            .map!filter
+            .array
+            .sliced(wnds.shape);
+    }
+
+    static T median(Range, T)(Range r, T[] buf)
+    {
+        import std.algorithm.sorting: topN;
+        size_t n;
+        foreach (e; r)
+            buf[n++] = e;
+        auto m = n >> 1;
+        buf[0 .. n].topN(m);
+        return buf[m];
+    }
+
+    import std.conv: to;
+    import std.getopt: getopt, defaultGetoptPrinter;
+    import std.path: stripExtension;
+
+    auto args = ["std"];
+    uint nr, nc, def = 3;
+    auto helpInformation = args.getopt(
+        "nr", "number of rows in window, default value is " ~ def.to!string, &nr,
+        "nc", "number of columns in window default value equals to nr", &nc);
+    if (helpInformation.helpWanted)
+    {
+        defaultGetoptPrinter(
+            "Usage: median-filter [<options...>] [<file_names...>]\noptions:",
+            helpInformation.options);
+        return;
+    }
+    if (!nr) nr = def;
+    if (!nc) nc = nr;
+
+    auto buf = new ubyte[nr * nc];
+
+    foreach (name; args[1 .. $])
+    {
+        auto ret =
+            movingWindowByChannel
+                 (new ubyte[300].sliced(10, 10, 3), nr, nc, window => median(window.byElement, buf));
+    }
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.algorithm.comparison: equal;
+    import std.range: iota;
+    immutable r = 1000.iota;
+
+    auto t0 = r.sliced(1000);
+    assert(t0.front == 0);
+    assert(t0.back == 999);
+    assert(t0[9] == 9);
+
+    auto t1 = t0[10 .. 20];
+    assert(t1.front == 10);
+    assert(t1.back == 19);
+    assert(t1[9] == 19);
+
+    t1.popFront();
+    assert(t1.front == 11);
+    t1.popFront();
+    assert(t1.front == 12);
+
+    t1.popBack();
+    assert(t1.back == 18);
+    t1.popBack();
+    assert(t1.back == 17);
+
+    assert(t1.equal(iota(12, 18)));
+}
+
+pure nothrow unittest
+{
+    import std.algorithm.comparison: equal;
+    import std.array: array;
+    import std.range: iota;
+    auto r = 1000.iota.array;
+
+    auto t0 = r.sliced(1000);
+    assert(t0.length == 1000);
+    assert(t0.front == 0);
+    assert(t0.back == 999);
+    assert(t0[9] == 9);
+
+    auto t1 = t0[10 .. 20];
+    assert(t1.front == 10);
+    assert(t1.back == 19);
+    assert(t1[9] == 19);
+
+    t1.popFront();
+    assert(t1.front == 11);
+    t1.popFront();
+    assert(t1.front == 12);
+
+    t1.popBack();
+    assert(t1.back == 18);
+    t1.popBack();
+    assert(t1.back == 17);
+
+    assert(t1.equal(iota(12, 18)));
+
+    t1.front = 13;
+    assert(t1.front == 13);
+    t1.front++;
+    assert(t1.front == 14);
+    t1.front += 2;
+    assert(t1.front == 16);
+    t1.front = 12;
+    assert((t1.front = 12) == 12);
+
+    t1.back = 13;
+    assert(t1.back == 13);
+    t1.back++;
+    assert(t1.back == 14);
+    t1.back += 2;
+    assert(t1.back == 16);
+    t1.back = 12;
+    assert((t1.back = 12) == 12);
+
+    t1[3] = 13;
+    assert(t1[3] == 13);
+    t1[3]++;
+    assert(t1[3] == 14);
+    t1[3] += 2;
+    assert(t1[3] == 16);
+    t1[3] = 12;
+    assert((t1[3] = 12) == 12);
+
+    t1[3 .. 5] = 100;
+    assert(t1[2] != 100);
+    assert(t1[3] == 100);
+    assert(t1[4] == 100);
+    assert(t1[5] != 100);
+
+    t1[3 .. 5] += 100;
+    assert(t1[2] <  100);
+    assert(t1[3] == 200);
+    assert(t1[4] == 200);
+    assert(t1[5] <  100);
+
+    --t1[3 .. 5];
+
+    assert(t1[2] <  100);
+    assert(t1[3] == 199);
+    assert(t1[4] == 199);
+    assert(t1[5] <  100);
+
+    --t1[];
+    assert(t1[3] == 198);
+    assert(t1[4] == 198);
+
+    t1[] += 2;
+    assert(t1[3] == 200);
+    assert(t1[4] == 200);
+
+    t1[] *= t1[];
+    assert(t1[3] == 40000);
+    assert(t1[4] == 40000);
+
+
+    assert(&t1[$ - 1] is &(t1.back()));
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.range: iota;
+    auto r = (10_000L * 2 * 3 * 4).iota;
+
+    auto t0 = r.sliced(10, 20, 30, 40);
+    assert(t0.length == 10);
+    assert(t0.length!0 == 10);
+    assert(t0.length!1 == 20);
+    assert(t0.length!2 == 30);
+    assert(t0.length!3 == 40);
+}
+
+pure nothrow unittest
+{
+    import std.experimental.ndslice.internal: Iota;
+    import std.meta: AliasSeq;
+    import std.range;
+    import std.typecons: Tuple;
+    foreach (R; AliasSeq!(
+        int*, int[], typeof(1.iota),
+        const(int)*, const(int)[],
+        immutable(int)*, immutable(int)[],
+        double*, double[], typeof(10.0.iota),
+        Tuple!(double, int[string])*, Tuple!(double, int[string])[]))
+    foreach (n; Iota!(1, 4))
+    {
+        alias S = Slice!(n, R);
+        static assert(isRandomAccessRange!S);
+        static assert(hasSlicing!S);
+        static assert(hasLength!S);
+    }
+
+    immutable int[] im = [1,2,3,4,5,6];
+    auto slice = im.sliced(2, 3);
+}
+
+pure nothrow unittest
+{
+    auto tensor = new int[3 * 4 * 8].sliced(3, 4, 8);
+    assert(&(tensor.back.back.back()) is &tensor[2, 3, 7]);
+    assert(&(tensor.front.front.front()) is &tensor[0, 0, 0]);
+}
+
+pure nothrow unittest
+{
+    import std.experimental.ndslice.selection: pack;
+    auto slice = new int[24].sliced(2, 3, 4);
+    auto r0 = slice.pack!1[1, 2];
+    slice.pack!1[1, 2][] = 4;
+    auto r1 = slice[1, 2];
+    assert(slice[1, 2, 3] == 4);
+}
+
+pure nothrow unittest
+{
+    auto ar = new int[3 * 8 * 9];
+
+    auto tensor = ar.sliced(3, 8, 9);
+    tensor[0, 1, 2] = 4;
+    tensor[0, 1, 2]++;
+    assert(tensor[0, 1, 2] == 5);
+    tensor[0, 1, 2]--;
+    assert(tensor[0, 1, 2] == 4);
+    tensor[0, 1, 2] += 2;
+    assert(tensor[0, 1, 2] == 6);
+
+    auto matrix = tensor[0 .. $, 1, 0 .. $];
+    matrix[] = 10;
+    assert(tensor[0, 1, 2] == 10);
+    assert(matrix[0, 2] == tensor[0, 1, 2]);
+    assert(&matrix[0, 2] is &tensor[0, 1, 2]);
+}
diff --git a/libphobos/src/std/experimental/ndslice/selection.d b/libphobos/src/std/experimental/ndslice/selection.d
new file mode 100644
index 00000000000..42e4da3476d
--- /dev/null
+++ b/libphobos/src/std/experimental/ndslice/selection.d
@@ -0,0 +1,1615 @@
+/**
+$(SCRIPT inhibitQuickIndex = 1;)
+
+This is a submodule of $(LINK2 std_experimental_ndslice.html, std.experimental.ndslice).
+
+Selectors create new views and iteration patterns over the same data, without copying.
+
+$(H2 Subspace selectors)
+
+Subspace selectors serve to generalize and combine other selectors easily.
+For a slice of `Slice!(N, Range)` type `slice.pack!K` creates a slice of
+slices of `Slice!(N-K, Slice!(K+1, Range))` type by packing
+the last `K` dimensions of the top dimension pack,
+and the type of element of `slice.byElement` is `Slice!(K, Range)`.
+Another way to use $(LREF pack) is transposition of dimension packs using
+$(LREF evertPack). Examples of use of subspace selectors are available for selectors,
+$(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementsCount).
+
+$(BOOKTABLE ,
+
+$(TR $(TH Function Name) $(TH Description))
+$(T2 pack     , returns slice of slices)
+$(T2 unpack   , merges all dimension packs)
+$(T2 evertPack, reverses dimension packs)
+)
+
+$(BOOKTABLE $(H2 Selectors),
+
+$(TR $(TH Function Name) $(TH Description))
+$(T2 byElement, a random access range of all elements with `index` property)
+$(T2 byElementInStandardSimplex, an input range of all elements in standard simplex of hypercube with `index` property.
+    If the slice has two dimensions, it is a range of all elements of upper left triangular matrix.)
+$(T2 indexSlice, returns a slice with elements equal to the initial index)
+$(T2 reshape, returns a new slice for the same data)
+$(T2 diagonal, 1-dimensional slice composed of diagonal elements)
+$(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks.
+    If the slice has two dimensions, it is a block matrix.)
+$(T2 windows, n-dimensional slice of n-dimensional overlapping windows.
+    If the slice has two dimensions, it is a sliding window.)
+)
+
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   Ilya Yaroshenko
+
+Source:    $(PHOBOSSRC std/_experimental/_ndslice/_selection.d)
+
+Macros:
+SUBMODULE = $(LINK2 std_experimental_ndslice_$1.html, std.experimental.ndslice.$1)
+SUBREF = $(LINK2 std_experimental_ndslice_$1.html#.$2, $(TT $2))$(NBSP)
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
+*/
+module std.experimental.ndslice.selection;
+
+import std.traits;
+import std.meta; //: allSatisfy;
+
+import std.experimental.ndslice.internal;
+import std.experimental.ndslice.slice; //: Slice;
+
+/++
+Creates a packed slice, i.e. slice of slices.
+The function does not carry out any calculations, it simply returns the same
+binary data presented differently.
+
+Params:
+    K = sizes of dimension packs
+Returns:
+    `pack!K` returns `Slice!(N-K, Slice!(K+1, Range))`;
+    `slice.pack!(K1, K2, ..., Kn)` is the same as `slice.pacKed!K1.pacKed!K2. ... pacKed!Kn`.
++/
+template pack(K...)
+{
+    auto pack(size_t N, Range)(auto ref Slice!(N, Range) slice)
+    {
+        template Template(size_t NInner, Range, R...)
+        {
+            static if (R.length > 0)
+            {
+                static if (NInner > R[0])
+                    alias Template = Template!(NInner - R[0], Slice!(R[0] + 1, Range), R[1 .. $]);
+                else
+                static assert(0,
+                    "Sum of all lengths of packs " ~ K.stringof
+                    ~ " should be less than N = "~ N.stringof
+                    ~ tailErrorMessage!());
+            }
+            else
+            {
+                alias Template = Slice!(NInner, Range);
+            }
+        }
+        with (slice) return Template!(N, Range, K)(_lengths, _strides, _ptr);
+    }
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range.primitives: ElementType;
+    import std.range: iota;
+    import std.algorithm.comparison: equal;
+    auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
+    auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
+    auto b = a.pack!(2, 3); // same as `a.pack!2.pack!3`
+    auto c = b[1, 2, 3, 4];
+    auto d = c[5, 6, 7];
+    auto e = d[8, 9];
+    auto g = a[1, 2, 3, 4, 5, 6, 7, 8, 9];
+    assert(e == g);
+    assert(a == b);
+    assert(c == a[1, 2, 3, 4]);
+    alias R = typeof(r);
+    static assert(is(typeof(b) == typeof(a.pack!2.pack!3)));
+    static assert(is(typeof(b) == Slice!(4, Slice!(4, Slice!(3, R)))));
+    static assert(is(typeof(c) == Slice!(3, Slice!(3, R))));
+    static assert(is(typeof(d) == Slice!(2, R)));
+    static assert(is(typeof(e) == ElementType!R));
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.selection;
+    import std.range: iota;
+    auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
+    auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
+    auto b = a.pack!(2, 3);
+    static assert(b.shape.length == 4);
+    static assert(b.structure.lengths.length == 4);
+    static assert(b.structure.strides.length == 4);
+    static assert(b
+        .byElement.front
+        .shape.length == 3);
+    static assert(b
+        .byElement.front
+        .byElement.front
+        .shape.length == 2);
+}
+
+/++
+Unpacks a packed slice.
+
+The function does not carry out any calculations, it simply returns the same
+binary data presented differently.
+
+Params:
+    slice = packed slice
+Returns:
+    unpacked slice
+
+See_also: $(LREF pack), $(LREF evertPack)
++/
+Slice!(N, Range).PureThis unpack(size_t N, Range)(auto ref Slice!(N, Range) slice)
+{
+    with (slice) return PureThis(_lengths, _strides, _ptr);
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    import std.algorithm.comparison: equal;
+    auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
+    auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
+    auto b = a.pack!(2, 3).unpack();
+    static assert(is(typeof(a) == typeof(b)));
+    assert(a == b);
+}
+
+/++
+Reverses the order of dimension packs.
+This function is used in a functional pipeline with other selectors.
+
+Params:
+    slice = packed slice
+Returns:
+    packed slice
+
+See_also: $(LREF pack), $(LREF unpack)
++/
+SliceFromSeq!(Slice!(N, Range).PureRange, NSeqEvert!(Slice!(N, Range).NSeq))
+evertPack(size_t N, Range)(auto ref Slice!(N, Range) slice)
+{
+    mixin _DefineRet;
+    static assert(Ret.NSeq.length > 0);
+    with (slice)
+    {
+        alias C = Snowball!(Parts!NSeq);
+        alias D = Reverse!(Snowball!(Reverse!(Parts!NSeq)));
+        foreach (i, _; NSeq)
+        {
+            foreach (j; Iota!(0, C[i + 1] - C[i]))
+            {
+                ret._lengths[j + D[i + 1]] = _lengths[j + C[i]];
+                ret._strides[j + D[i + 1]] = _strides[j + C[i]];
+            }
+        }
+        ret._ptr = _ptr;
+        return ret;
+    }
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: transposed;
+    import std.range: iota;
+    auto slice = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota
+        .sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
+    assert(slice
+        .pack!2
+        .evertPack
+        .unpack
+             == slice.transposed!(
+                slice.shape.length-2,
+                slice.shape.length-1));
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: transposed;
+    import std.range.primitives: ElementType;
+    import std.range: iota;
+    import std.algorithm.comparison: equal;
+    auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
+    auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
+    auto b = a
+        .pack!(2, 3)
+        .evertPack;
+    auto c = b[8, 9];
+    auto d = c[5, 6, 7];
+    auto e = d[1, 2, 3, 4];
+    auto g = a[1, 2, 3, 4, 5, 6, 7, 8, 9];
+    assert(e == g);
+    assert(a == b.evertPack);
+    assert(c == a.transposed!(7, 8, 4, 5, 6)[8, 9]);
+    alias R = typeof(r);
+    static assert(is(typeof(b) == Slice!(2, Slice!(4, Slice!(5, R)))));
+    static assert(is(typeof(c) == Slice!(3, Slice!(5, R))));
+    static assert(is(typeof(d) == Slice!(4, R)));
+    static assert(is(typeof(e) == ElementType!R));
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    static assert(is(typeof(new int[20]
+        .sliced(20)
+        .evertPack)
+         == Slice!(1LU, int*)));
+    static assert(is(typeof(new int[20]
+        .sliced(20)
+        .sliced(3)
+        .evertPack)
+         == Slice!(2LU, int*)));
+    static assert(is(typeof(new int[20]
+        .sliced(20)
+        .sliced(1,2,3)
+        .sliced(3)
+        .evertPack)
+         == Slice!(3LU, Slice!(2LU, int*))));
+    static assert(is(typeof(new int[20]
+        .sliced(20)
+        .sliced(1,2,3)
+        .evertPack)
+         == Slice!(4LU, int*)));
+}
+
+/++
+Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice.
+`diagonal` can be generalized with other selectors such as
+$(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
+
+Params:
+    N = dimension count
+    slice = input slice
+Returns:
+    packed `1`-dimensional composed of `N`-dimensional slices
++/
+Slice!(1, Range) diagonal(size_t N, Range)(auto ref Slice!(N, Range) slice)
+{
+    auto NewN = slice.PureN - N + 1;
+    mixin _DefineRet;
+    ret._lengths[0] = slice._lengths[0];
+    ret._strides[0] = slice._strides[0];
+    foreach (i; Iota!(1, N))
+    {
+        if (ret._lengths[0] > slice._lengths[i])
+            ret._lengths[0] = slice._lengths[i];
+        ret._strides[0] += slice._strides[i];
+    }
+    foreach (i; Iota!(1, ret.PureN))
+    {
+        ret._lengths[i] = slice._lengths[i + N - 1];
+        ret._strides[i] = slice._strides[i + N - 1];
+    }
+    ret._ptr = slice._ptr;
+    return ret;
+}
+
+/// Matrix, main diagonal
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, only;
+
+    //  -------
+    // | 0 1 2 |
+    // | 3 4 5 |
+    //  -------
+    //->
+    // | 0 4 |
+    assert(6.iota
+        .sliced(2, 3)
+        .diagonal
+        .equal(only(0, 4)));
+}
+
+/// ditto
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+
+    auto slice = new int[9].sliced(3, 3);
+    int i;
+    foreach (ref e; slice.diagonal)
+        e = ++i;
+    assert(slice == [
+        [1, 0, 0],
+        [0, 2, 0],
+        [0, 0, 3]]);
+}
+
+/// Matrix, subdiagonal
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: dropOne;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, only;
+    //  -------
+    // | 0 1 2 |
+    // | 3 4 5 |
+    //  -------
+    //->
+    // | 1 5 |
+    assert(6.iota
+        .sliced(2, 3)
+        .dropOne!1
+        .diagonal
+        .equal(only(1, 5)));
+}
+
+/// Matrix, antidiagonal
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: dropToHypercube, reversed;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, only;
+    //  -------
+    // | 0 1 2 |
+    // | 3 4 5 |
+    //  -------
+    //->
+    // | 1 3 |
+    assert(6.iota
+        .sliced(2, 3)
+        .dropToHypercube
+        .reversed!1
+        .diagonal
+        .equal(only(1, 3)));
+}
+
+/// 3D, main diagonal
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, only;
+    //  -----------
+    // |  0   1  2 |
+    // |  3   4  5 |
+    //  - - - - - -
+    // |  6   7  8 |
+    // |  9  10 11 |
+    //  -----------
+    //->
+    // | 0 10 |
+    assert(12.iota
+        .sliced(2, 2, 3)
+        .diagonal
+        .equal(only(0, 10)));
+}
+
+/// 3D, subdiagonal
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: dropOne;
+    import std.algorithm.comparison: equal;
+    import std.range: iota, only;
+    //  -----------
+    // |  0   1  2 |
+    // |  3   4  5 |
+    //  - - - - - -
+    // |  6   7  8 |
+    // |  9  10 11 |
+    //  -----------
+    //->
+    // | 1 11 |
+    assert(12.iota
+        .sliced(2, 2, 3)
+        .dropOne!2
+        .diagonal
+        .equal(only(1, 11)));
+}
+
+/// 3D, diagonal plain
+@safe pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: dropOne;
+    import std.algorithm.comparison: equal;
+    import std.range: iota;
+    //  -----------
+    // |  0   1  2 |
+    // |  3   4  5 |
+    // |  6   7  8 |
+    //  - - - - - -
+    // |  9  10 11 |
+    // | 12  13 14 |
+    // | 15  16 17 |
+    //  - - - - - -
+    // | 18  20 21 |
+    // | 22  23 24 |
+    // | 24  25 26 |
+    //  -----------
+    //->
+    //  -----------
+    // |  0   4  8 |
+    // |  9  13 17 |
+    // | 18  23 26 |
+    //  -----------
+    auto slice = 27.iota
+        .sliced(3, 3, 3)
+        .pack!2
+        .evertPack
+        .diagonal
+        .evertPack;
+    assert(slice ==
+        [[ 0,  4,  8],
+         [ 9, 13, 17],
+         [18, 22, 26]]);
+}
+
+/++
+Returns an n-dimensional slice of n-dimensional non-overlapping blocks.
+`blocks` can be generalized with other selectors.
+For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks.
+
+Params:
+    N = dimension count
+    slice = slice to be split into blocks
+    lengths = dimensions of block, residual blocks are ignored
+Returns:
+    packed `N`-dimensional slice composed of `N`-dimensional slices
++/
+Slice!(N, Slice!(N+1, Range)) blocks(size_t N, Range, Lengths...)(auto ref Slice!(N, Range) slice, Lengths lengths)
+    if (allSatisfy!(isIndex, Lengths) && Lengths.length == N)
+in
+{
+    foreach (i, length; lengths)
+        assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
+            ~ tailErrorMessage!());
+}
+body
+{
+    mixin _DefineRet;
+    foreach (dimension; Iota!(0, N))
+    {
+        ret._lengths[dimension] = slice._lengths[dimension] / lengths[dimension];
+        ret._strides[dimension] = slice._strides[dimension];
+        if (ret._lengths[dimension]) //do not remove `if (...)`
+            ret._strides[dimension] *= lengths[dimension];
+        ret._lengths[dimension + N] = lengths[dimension];
+        ret._strides[dimension + N] = slice._strides[dimension];
+    }
+    foreach (dimension; Iota!(N, slice.PureN))
+    {
+        ret._lengths[dimension + N] = slice._lengths[dimension];
+        ret._strides[dimension + N] = slice._strides[dimension];
+    }
+    ret._ptr = slice._ptr;
+    return ret;
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[40].sliced(5, 8);
+    auto blocks = slice.blocks(2, 3);
+    int i;
+    foreach (block; blocks.byElement)
+        block[] = ++i;
+
+    assert(blocks ==
+        [[[[1, 1, 1], [1, 1, 1]],
+          [[2, 2, 2], [2, 2, 2]]],
+         [[[3, 3, 3], [3, 3, 3]],
+          [[4, 4, 4], [4, 4, 4]]]]);
+
+    assert(    slice ==
+        [[1, 1, 1,  2, 2, 2,  0, 0],
+         [1, 1, 1,  2, 2, 2,  0, 0],
+
+         [3, 3, 3,  4, 4, 4,  0, 0],
+         [3, 3, 3,  4, 4, 4,  0, 0],
+
+         [0, 0, 0,  0, 0, 0,  0, 0]]);
+}
+
+/// Diagonal blocks
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[40].sliced(5, 8);
+    auto blocks = slice.blocks(2, 3);
+    auto diagonalBlocks = blocks.diagonal.unpack;
+
+    diagonalBlocks[0][] = 1;
+    diagonalBlocks[1][] = 2;
+
+    assert(diagonalBlocks ==
+        [[[1, 1, 1], [1, 1, 1]],
+         [[2, 2, 2], [2, 2, 2]]]);
+
+    assert(blocks ==
+        [[[[1, 1, 1], [1, 1, 1]],
+          [[0, 0, 0], [0, 0, 0]]],
+         [[[0, 0, 0], [0, 0, 0]],
+          [[2, 2, 2], [2, 2, 2]]]]);
+
+    assert(slice ==
+        [[1, 1, 1,  0, 0, 0,  0, 0],
+         [1, 1, 1,  0, 0, 0,  0, 0],
+
+         [0, 0, 0,  2, 2, 2,  0, 0],
+         [0, 0, 0,  2, 2, 2,  0, 0],
+
+         [0, 0, 0, 0, 0, 0, 0, 0]]);
+}
+
+/// Matrix divided into vertical blocks
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[65].sliced(5, 13);
+    auto blocks = slice
+        .pack!1
+        .evertPack
+        .blocks(3)
+        .unpack
+        .pack!2;
+
+    int i;
+    foreach (block; blocks.byElement)
+        block[] = ++i;
+
+    assert(slice ==
+        [[1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
+         [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
+         [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
+         [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
+         [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0]]);
+}
+
+/++
+Returns an n-dimensional slice of n-dimensional overlapping windows.
+`windows` can be generalized with other selectors.
+For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice.
+
+Params:
+    N = dimension count
+    slice = slice to be iterated
+    lengths = dimensions of windows
+Returns:
+    packed `N`-dimensional slice composed of `N`-dimensional slices
++/
+Slice!(N, Slice!(N+1, Range)) windows(size_t N, Range, Lengths...)(auto ref Slice!(N, Range) slice, Lengths lengths)
+    if (allSatisfy!(isIndex, Lengths) && Lengths.length == N)
+in
+{
+    foreach (i, length; lengths)
+        assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
+            ~ tailErrorMessage!());
+}
+body
+{
+    mixin _DefineRet;
+    foreach (dimension; Iota!(0, N))
+    {
+        ret._lengths[dimension] = slice._lengths[dimension] >= lengths[dimension] ?
+                                  slice._lengths[dimension] - lengths[dimension] + 1: 0;
+        ret._strides[dimension] = slice._strides[dimension];
+        ret._lengths[dimension + N] = lengths[dimension];
+        ret._strides[dimension + N] = slice._strides[dimension];
+    }
+    foreach (dimension; Iota!(N, slice.PureN))
+    {
+        ret._lengths[dimension + N] = slice._lengths[dimension];
+        ret._strides[dimension +  N] = slice._strides[dimension];
+    }
+    ret._ptr = slice._ptr;
+    return ret;
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[40].sliced(5, 8);
+    auto windows = slice.windows(2, 3);
+    foreach (window; windows.byElement)
+        window[] += 1;
+
+    assert(slice ==
+        [[1,  2,  3, 3, 3, 3,  2,  1],
+
+         [2,  4,  6, 6, 6, 6,  4,  2],
+         [2,  4,  6, 6, 6, 6,  4,  2],
+         [2,  4,  6, 6, 6, 6,  4,  2],
+
+         [1,  2,  3, 3, 3, 3,  2,  1]]);
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[40].sliced(5, 8);
+    auto windows = slice.windows(2, 3);
+    windows[1, 2][] = 1;
+    windows[1, 2][0, 1] += 1;
+    windows.unpack[1, 2, 0, 1] += 1;
+
+    assert(slice ==
+        [[0, 0,  0, 0, 0,  0, 0, 0],
+
+         [0, 0,  1, 3, 1,  0, 0, 0],
+         [0, 0,  1, 1, 1,  0, 0, 0],
+
+         [0, 0,  0, 0, 0,  0, 0, 0],
+         [0, 0,  0, 0, 0,  0, 0, 0]]);
+}
+
+/// Multi-diagonal matrix
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[64].sliced(8, 8);
+    auto windows = slice.windows(3, 3);
+
+    auto multidiagonal = windows
+        .diagonal
+        .unpack;
+    foreach (window; multidiagonal)
+        window[] += 1;
+
+    assert(slice ==
+        [[ 1, 1, 1,  0, 0, 0, 0, 0],
+         [ 1, 2, 2, 1,  0, 0, 0, 0],
+         [ 1, 2, 3, 2, 1,  0, 0, 0],
+         [0,  1, 2, 3, 2, 1,  0, 0],
+         [0, 0,  1, 2, 3, 2, 1,  0],
+         [0, 0, 0,  1, 2, 3, 2, 1],
+         [0, 0, 0, 0,  1, 2, 2, 1],
+         [0, 0, 0, 0, 0,  1, 1, 1]]);
+}
+
+/// Sliding window over matrix columns
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[40].sliced(5, 8);
+    auto windows = slice
+        .pack!1
+        .evertPack
+        .windows(3)
+        .unpack
+        .pack!2;
+
+
+    foreach (window; windows.byElement)
+        window[] += 1;
+
+    assert(slice ==
+        [[1,  2,  3, 3, 3, 3,  2,  1],
+         [1,  2,  3, 3, 3, 3,  2,  1],
+         [1,  2,  3, 3, 3, 3,  2,  1],
+         [1,  2,  3, 3, 3, 3,  2,  1],
+         [1,  2,  3, 3, 3, 3,  2,  1]]);
+}
+
+/++
+Returns a new slice for the same data.
+
+Params:
+    slice = slice to be reshaped
+    lengths = list of new dimensions. One of the lengths can be set to `-1`.
+        In this case, the corresponding dimension is inferable.
+Returns:
+    reshaped slice
+Throws:
+    $(LREF ReshapeException) if the slice cannot be reshaped with the input lengths.
++/
+Slice!(Lengths.length, Range)
+    reshape
+        (         size_t N, Range       , Lengths...     )
+        (auto ref Slice!(N, Range) slice, Lengths lengths)
+    if ( allSatisfy!(isIndex, Lengths) && Lengths.length)
+{
+    mixin _DefineRet;
+    foreach (i; Iota!(0, ret.N))
+        ret._lengths[i] = lengths[i];
+
+    immutable size_t eco = slice.elementsCount;
+              size_t ecn = ret  .elementsCount;
+
+    if (eco == 0)
+        throw new ReshapeException(
+            slice._lengths.dup,
+            slice._strides.dup,
+            ret.  _lengths.dup,
+            "slice should be not empty");
+
+    foreach (i; Iota!(0, ret.N))
+        if (ret._lengths[i] == -1)
+        {
+            ecn = -ecn;
+            ret._lengths[i] = eco / ecn;
+            ecn *= ret._lengths[i];
+            break;
+        }
+
+    if (eco != ecn)
+        throw new ReshapeException(
+            slice._lengths.dup,
+            slice._strides.dup,
+            ret.  _lengths.dup,
+            "total element count should be the same");
+
+    for (size_t oi, ni, oj, nj; oi < slice.N && ni < ret.N; oi = oj, ni = nj)
+    {
+        size_t op = slice._lengths[oj++];
+        size_t np = ret  ._lengths[nj++];
+
+        for (;;)
+        {
+            if (op < np)
+                op *= slice._lengths[oj++];
+            if (op > np)
+                np *= ret  ._lengths[nj++];
+            if (op == np)
+                break;
+        }
+        while (oj < slice.N && slice._lengths[oj] == 1) oj++;
+        while (nj < ret  .N && ret  ._lengths[nj] == 1) nj++;
+
+        for (size_t l = oi, r = oi + 1; r < oj; r++)
+            if (slice._lengths[r] != 1)
+            {
+                if (slice._strides[l] != slice._lengths[r] * slice._strides[r])
+                    throw new ReshapeException(
+                        slice._lengths.dup,
+                        slice._strides.dup,
+                        ret.  _lengths.dup,
+                        "structure is incompatible with new shape");
+                l = r;
+            }
+
+        ret._strides[nj - 1] = slice._strides[oj - 1];
+        foreach_reverse(i; ni .. nj - 1)
+            ret._strides[i] = ret._lengths[i + 1] * ret._strides[i + 1];
+    assert((oi == slice.N) == (ni == ret.N));
+    }
+    foreach (i; Iota!(ret.N, ret.PureN))
+    {
+        ret._lengths[i] = slice._lengths[i + slcie.N - ret.N];
+        ret._strides[i] = slice._strides[i + slcie.N - ret.N];
+    }
+    ret._ptr = slice._ptr;
+    return ret;
+}
+
+///
+@safe pure unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    import std.experimental.ndslice.iteration: allReversed;
+    auto slice = 12.iota
+        .sliced(3, 4)
+        .allReversed
+        .reshape(-1, 3);
+    assert(slice ==
+        [[11, 10, 9],
+         [ 8,  7, 6],
+         [ 5,  4, 3],
+         [ 2,  1, 0]]);
+}
+
+/// Reshaping with memory allocation
+pure unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: reversed;
+    import std.array: array;
+
+    auto reshape2(S, L...)(S slice, L lengths)
+    {
+        // Tries to reshape without allocation
+        try return slice.reshape(lengths);
+        catch(ReshapeException e)
+            //allocates the elements and creates a slice
+            //Note: -1 length is not supported by reshape2
+            return slice.byElement.array.sliced(lengths);
+    }
+
+    auto slice =
+        [0, 1,  2,  3,
+         4, 5,  6,  7,
+         8, 9, 10, 11]
+        .sliced(3, 4)
+        .reversed!0;
+
+    assert(reshape2(slice, 4, 3) ==
+        [[ 8, 9, 10],
+         [11, 4,  5],
+         [ 6, 7,  0],
+         [ 1, 2,  3]]);
+}
+
+@safe pure unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration: allReversed;
+    import std.stdio;
+    import std.range: iota;
+    auto slice = 12.iota.sliced(1, 1, 3, 2, 1, 2, 1).allReversed;
+    assert(slice.reshape(1, -1, 1, 1, 3, 1) ==
+        [[[[[[11], [10], [9]]]],
+          [[[[ 8], [ 7], [6]]]],
+          [[[[ 5], [ 4], [3]]]],
+          [[[[ 2], [ 1], [0]]]]]]);
+}
+
+/// See_also: $(LREF reshape)
+class ReshapeException: Exception
+{
+    /// Old lengths
+    size_t[] lengths;
+    /// Old strides
+    sizediff_t[] strides;
+    /// New lengths
+    size_t[] newLengths;
+    ///
+    this(
+        size_t[] lengths,
+        sizediff_t[] strides,
+        size_t[] newLengths,
+        string msg,
+        string file = __FILE__,
+        uint line = cast(uint)__LINE__,
+        Throwable next = null
+        ) pure nothrow @nogc @safe
+    {
+        super(msg, file, line, next);
+        this.lengths = lengths;
+        this.strides = strides;
+        this.newLengths = newLengths;
+    }
+}
+
+/++
+Returns a random access range of all elements of a slice.
+The order of elements is preserved.
+`byElement` can be generalized with other selectors.
+
+Params:
+    N = dimension count
+    slice = slice to be iterated
+Returns:
+    random access range composed of elements of the `slice`
++/
+auto byElement(size_t N, Range)(auto ref Slice!(N, Range) slice)
+{
+    with (Slice!(N, Range))
+    {
+        /++
+        ByElement shifts the range's `_ptr` without modifying its strides and lengths.
+        +/
+        static struct ByElement
+        {
+            This _slice;
+            size_t _length;
+            size_t[N] _indexes;
+
+            static if (canSave!PureRange)
+            auto save() @property
+            {
+                return typeof(this)(_slice.save, _length, _indexes);
+            }
+
+            bool empty() const @property
+            {
+                pragma(inline, true);
+                return _length == 0;
+            }
+
+            size_t length() const @property
+            {
+                pragma(inline, true);
+                return _length;
+            }
+
+            auto ref front() @property
+            {
+                assert(!this.empty);
+                static if (N == PureN)
+                {
+                    return _slice._ptr[0];
+                }
+                else with (_slice)
+                {
+                    alias M = DeepElemType.PureN;
+                    return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr);
+                }
+            }
+
+            static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+            auto front(E)(E elem) @property
+            {
+                assert(!this.empty);
+                return _slice._ptr[0] = elem;
+            }
+
+            void popFront()
+            {
+                assert(_length != 0);
+                _length--;
+                popFrontImpl;
+            }
+
+            private void popFrontImpl()
+            {
+                foreach_reverse(i; Iota!(0, N)) with (_slice)
+                {
+                    _ptr += _strides[i];
+                    _indexes[i]++;
+                    if (_indexes[i] < _lengths[i])
+                        return;
+                    debug (ndslice) assert(_indexes[i] == _lengths[i]);
+                    _ptr -= _lengths[i] * _strides[i];
+                    _indexes[i] = 0;
+                }
+            }
+
+            auto ref back() @property
+            {
+                assert(!this.empty);
+                return opIndex(_length - 1);
+            }
+
+            static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+            auto back(E)(E elem) @property
+            {
+                assert(!this.empty);
+                return opIndexAssign(_length - 1, elem);
+            }
+
+            void popBack()
+            {
+                pragma(inline, true);
+                assert(_length != 0);
+                _length--;
+            }
+
+            void popFrontExactly(size_t n)
+            in
+            {
+                assert(n <= _length);
+            }
+            body
+            {
+                _length -= n;
+                //calculates shift and new indexes
+                sizediff_t _shift;
+                n += _indexes[N-1];
+                foreach_reverse(i; Iota!(1, N)) with (_slice)
+                {
+                    immutable v = n / _lengths[i];
+                    n %= _lengths[i];
+                    _shift += (n - _indexes[i]) * _strides[i];
+                    _indexes[i] = n;
+                    n = _indexes[i - 1] + v;
+                }
+                assert(n < _slice._lengths[0]);
+                with (_slice)
+                {
+                    _shift += (n - _indexes[0]) * _strides[0];
+                    _indexes[0] = n;
+                }
+                _slice._ptr += _shift;
+            }
+
+            void popBackExactly(size_t n)
+            in
+            {
+                assert(n <= _length);
+            }
+            body
+            {
+                pragma(inline, true);
+                _length -= n;
+            }
+
+            //calculates shift for index n
+            private sizediff_t getShift(size_t n)
+            in
+            {
+                assert(n < _length);
+            }
+            body
+            {
+                sizediff_t _shift;
+                n += _indexes[N-1];
+                foreach_reverse(i; Iota!(1, N)) with (_slice)
+                {
+                    immutable v = n / _lengths[i];
+                    n %= _lengths[i];
+                    _shift += (n - _indexes[i]) * _strides[i];
+                    n = _indexes[i - 1] + v;
+                }
+                debug (ndslice) assert(n < _slice._lengths[0]);
+                with (_slice)
+                    _shift += (n - _indexes[0]) * _strides[0];
+                return _shift;
+            }
+
+            auto ref opIndex(size_t index)
+            {
+                static if (N == PureN)
+                {
+                    return _slice._ptr[getShift(index)];
+                }
+                else with (_slice)
+                {
+                    alias M = DeepElemType.PureN;
+                    return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr);
+                }
+            }
+
+            static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+            auto opIndexAssign(E)(E elem, size_t index)
+            {
+                static if (N == PureN)
+                {
+                    return _slice._ptr[getShift(index)] = elem;
+                }
+                else
+                {
+                    static assert(0,
+                        "ByElement.opIndexAssign is not implemented for packed slices."
+                        ~ "Use additional empty slicing `elemsOfSlice[index][] = value`"
+                        ~ tailErrorMessage());
+                }
+            }
+
+            static if (isMutable!DeepElemType && N == PureN)
+            {
+                auto opIndexAssign(V)(V val, _Slice slice)
+                {
+                    return this[slice][] = val;
+                }
+
+                auto opIndexAssign(V)(V val)
+                {
+                    foreach (ref e; this)
+                        e = val;
+                    return this;
+                }
+
+                auto opIndexAssign(V : T[], T)(V val)
+                    if (__traits(compiles, front = val[0]))
+                {
+                    assert(_length == val.length, "lengths should be equal" ~ tailErrorMessage!());
+                    foreach (ref e; this)
+                    {
+                        e = val[0];
+                        val = val[1 .. $];
+                    }
+                    return this;
+                }
+
+                auto opIndexAssign(V : Slice!(1, _Range), _Range)(V val)
+                    if (__traits(compiles, front = val.front))
+                {
+                    assert(_length == val.length, "lengths should be equal" ~ tailErrorMessage!());
+                    foreach (ref e; this)
+                    {
+                        e = val.front;
+                        val.popFront;
+                    }
+                    return this;
+                }
+            }
+
+            auto opIndex(_Slice sl)
+            {
+                auto ret = this;
+                ret.popFrontExactly(sl.i);
+                ret.popBackExactly(_length - sl.j);
+                return ret;
+            }
+
+            alias opDollar = length;
+
+            _Slice opSlice(size_t pos : 0)(size_t i, size_t j)
+            in
+            {
+                assert(i <= j,
+                    "the left bound must be less than or equal to the right bound"
+                    ~ tailErrorMessage!());
+                assert(j - i <= _length,
+                    "the difference between the right and the left bound must be less than or equal to range length"
+                    ~ tailErrorMessage!());
+            }
+            body
+            {
+                pragma(inline, true);
+                return typeof(return)(i, j);
+            }
+
+            size_t[N] index() @property
+            {
+                pragma(inline, true);
+                return _indexes;
+            }
+        }
+        return ByElement(slice, slice.elementsCount);
+    }
+}
+
+/// Regular slice
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.algorithm.comparison: equal;
+    import std.range: iota;
+    assert(20.iota
+        .sliced(4, 5)
+        .byElement
+        .equal(20.iota));
+}
+
+/// Packed slice
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration;
+    import std.range: iota, drop;
+    import std.algorithm.comparison: equal;
+    assert((3 * 4 * 5 * 6 * 7).iota
+        .sliced(3, 4, 5, 6, 7)
+        .pack!2
+        .byElement()
+        .drop(1)
+        .front
+        .byElement
+        .equal(iota(6 * 7, 6 * 7 * 2)));
+}
+
+/// Properties
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    auto elems = 12.iota.sliced(3, 4).byElement;
+    elems.popFrontExactly(2);
+    assert(elems.front == 2);
+    assert(elems.index == [0, 2]);
+    elems.popBackExactly(2);
+    assert(elems.back == 9);
+    assert(elems.length == 8);
+}
+
+/// Index property
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new long[20].sliced(5, 4);
+
+    for(auto elems = slice.byElement; !elems.empty; elems.popFront)
+    {
+        size_t[2] index = elems.index;
+        elems.front = index[0] * 10 + index[1] * 3;
+    }
+    assert(slice ==
+        [[ 0,  3,  6,  9],
+         [10, 13, 16, 19],
+         [20, 23, 26, 29],
+         [30, 33, 36, 39],
+         [40, 43, 46, 49]]);
+}
+
+/++
+Random access and slicing
++/
+@nogc nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.algorithm.comparison: equal;
+    import std.array: array;
+    import std.range: iota, repeat;
+    static data = 20.iota.array;
+    auto elems = data.sliced(4, 5).byElement;
+
+    elems = elems[11 .. $ - 2];
+
+    assert(elems.length == 7);
+    assert(elems.front == 11);
+    assert(elems.back == 17);
+
+    foreach (i; 0 .. 7)
+        assert(elems[i] == i + 11);
+
+    // assign an element
+    elems[2 .. 6] = -1;
+    assert(elems[2 .. 6].equal(repeat(-1, 4)));
+
+    // assign an array
+    static ar = [-1, -2, -3, -4];
+    elems[2 .. 6] = ar;
+    assert(elems[2 .. 6].equal(ar));
+
+    // assign a slice
+    ar[] *= 2;
+    auto sl = ar.sliced(ar.length);
+    elems[2 .. 6] = sl;
+    assert(elems[2 .. 6].equal(sl));
+}
+
+/++
+Forward access works faster than random access or backward access.
+Use $(SUBREF iteration, allReversed) in pipeline before
+`byElement` to achieve fast backward access.
++/
+@safe @nogc pure nothrow unittest
+{
+    import std.range: retro, iota;
+    import std.experimental.ndslice.iteration: allReversed;
+
+    auto slice = 60.iota.sliced(3, 4, 5);
+
+    /// Slow backward iteration #1
+    foreach (ref e; slice.byElement.retro)
+    {
+        //...
+    }
+
+    /// Slow backward iteration #2
+    foreach_reverse (ref e; slice.byElement)
+    {
+        //...
+    }
+
+    /// Fast backward iteration
+    foreach (ref e; slice.allReversed.byElement)
+    {
+        //...
+    }
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota, isRandomAccessRange, hasSlicing;
+    auto elems = 20.iota.sliced(4, 5).byElement;
+    static assert(isRandomAccessRange!(typeof(elems)));
+    static assert(hasSlicing!(typeof(elems)));
+}
+
+// Checks strides
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration;
+    import std.range: iota, isRandomAccessRange;
+    auto elems = 20.iota.sliced(4, 5).everted.byElement;
+    static assert(isRandomAccessRange!(typeof(elems)));
+
+    elems = elems[11 .. $ - 2];
+    auto elems2 = elems;
+    foreach (i; 0 .. 7)
+    {
+        assert(elems[i] == elems2.front);
+        elems2.popFront;
+    }
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration;
+    import std.range: iota, isForwardRange, hasLength;
+    import std.algorithm.comparison: equal;
+
+    auto range = (3 * 4 * 5 * 6 * 7).iota;
+    auto slice0 = range.sliced(3, 4, 5, 6, 7);
+    auto slice1 = slice0.transposed!(2, 1).pack!2;
+    auto elems0 = slice0.byElement;
+    auto elems1 = slice1.byElement;
+
+    import std.meta;
+    foreach (S; AliasSeq!(typeof(elems0), typeof(elems1)))
+    {
+        static assert(isForwardRange!S);
+        static assert(hasLength!S);
+    }
+
+    assert(elems0.length == slice0.elementsCount);
+    assert(elems1.length == 5 * 4 * 3);
+
+    auto elems2 = elems1;
+    foreach (q; slice1)
+        foreach (w; q)
+            foreach (e; w)
+            {
+                assert(!elems2.empty);
+                assert(e == elems2.front);
+                elems2.popFront;
+            }
+    assert(elems2.empty);
+
+    elems0.popFront();
+    elems0.popFrontExactly(slice0.elementsCount - 14);
+    assert(elems0.length == 13);
+    assert(elems0.equal(range[slice0.elementsCount - 13 .. slice0.elementsCount]));
+
+    foreach (elem; elems0) {}
+}
+
+// Issue 15549
+unittest
+{
+    import std.range: iota;
+    import std.range.primitives;
+    alias A = typeof(10.iota.sliced(2, 5).sliced(1, 1, 1, 1));
+    static assert(isRandomAccessRange!A);
+    static assert(hasLength!A);
+    static assert(hasSlicing!A);
+    alias B = typeof(new double[10].sliced(2, 5).sliced(1, 1, 1, 1));
+    static assert(isRandomAccessRange!B);
+    static assert(hasLength!B);
+    static assert(hasSlicing!B);
+}
+
+/++
+Returns an forward range of all elements of standard simplex of a slice.
+In case the slice has two dimensions, it is composed of elements of upper left triangular matrix.
+The order of elements is preserved.
+`byElementInStandardSimplex` can be generalized with other selectors.
+
+Params:
+    N = dimension count
+    slice = slice to be iterated
+Returns:
+    forward range composed of all elements of standard simplex of the `slice`
++/
+auto byElementInStandardSimplex(size_t N, Range)(auto ref Slice!(N, Range) slice, size_t maxCobeLength = size_t.max)
+{
+    with (Slice!(N, Range))
+    {
+        /++
+        ByElementInTopSimplex shifts the range's `_ptr` without modifying its strides and lengths.
+        +/
+        static struct ByElementInTopSimplex
+        {
+            This _slice;
+            size_t _length;
+            size_t maxCobeLength;
+            size_t sum;
+            size_t[N] _indexes;
+
+            static if (canSave!PureRange)
+            auto save() @property
+            {
+                return typeof(this)(_slice.save, _length, maxCobeLength, sum, _indexes);
+            }
+
+            bool empty() const @property
+            {
+                pragma(inline, true);
+                return _length == 0;
+            }
+
+            size_t length() const @property
+            {
+                pragma(inline, true);
+                return _length;
+            }
+
+            auto ref front() @property
+            {
+                assert(!this.empty);
+                static if (N == PureN)
+                    return _slice._ptr[0];
+                else with (_slice)
+                {
+                    alias M = DeepElemType.PureN;
+                    return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr);
+                }
+            }
+
+            static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+            auto front(E)(E elem) @property
+            {
+                pragma(inline, true);
+                assert(!this.empty);
+                return _slice._ptr[0] = elem;
+            }
+
+            void popFront()
+            {
+                pragma(inline, true);
+                assert(_length != 0);
+                _length--;
+                popFrontImpl;
+            }
+
+            private void popFrontImpl()
+            {
+                foreach_reverse(i; Iota!(0, N)) with (_slice)
+                {
+                    _ptr += _strides[i];
+                    _indexes[i]++;
+                    debug (ndslice) assert(_indexes[i] <= _lengths[i]);
+                    sum++;
+                    if (sum < maxCobeLength)
+                        return;
+                    debug (ndslice) assert(sum == maxCobeLength);
+                    _ptr -= _indexes[i] * _strides[i];
+                    sum -= _indexes[i];
+                    _indexes[i] = 0;
+                }
+            }
+
+            size_t[N] index() @property
+            {
+                pragma(inline, true);
+                return _indexes;
+            }
+        }
+        foreach (i; Iota!(0, N))
+            if (maxCobeLength > slice._lengths[i])
+                maxCobeLength = slice._lengths[i];
+        immutable size_t elementsCount = ((maxCobeLength + 1) * maxCobeLength ^^ (N - 1)) / 2;
+        return ByElementInTopSimplex(slice, elementsCount, maxCobeLength);
+    }
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    auto slice = new int[20].sliced(4, 5);
+    auto elems = slice
+        .byElementInStandardSimplex;
+    int i;
+    foreach (ref e; elems)
+        e = ++i;
+    assert(slice ==
+        [[ 1, 2, 3, 4, 0],
+         [ 5, 6, 7, 0, 0],
+         [ 8, 9, 0, 0, 0],
+         [10, 0, 0, 0, 0]]);
+}
+
+///
+pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.experimental.ndslice.iteration;
+    auto slice = new int[20].sliced(4, 5);
+    auto elems = slice
+        .transposed
+        .allReversed
+        .byElementInStandardSimplex;
+    int i;
+    foreach (ref e; elems)
+        e = ++i;
+    assert(slice ==
+        [[0,  0, 0, 0, 4],
+         [0,  0, 0, 7, 3],
+         [0,  0, 9, 6, 2],
+         [0, 10, 8, 5, 1]]);
+}
+
+/// Properties
+@safe @nogc pure nothrow unittest
+{
+    import std.experimental.ndslice.slice;
+    import std.range: iota;
+    auto elems = 12.iota.sliced(3, 4).byElementInStandardSimplex;
+    elems.popFront;
+    assert(elems.front == 1);
+    assert(elems.index == cast(size_t[2])[0, 1]);
+    import std.range: popFrontN;
+    elems.popFrontN(3);
+    assert(elems.front == 5);
+    assert(elems.index == cast(size_t[2])[1, 1]);
+    assert(elems.length == 2);
+}
+
+/++
+Returns a slice, the elements of which are equal to the initial index value.
+
+Params:
+    N = dimension count
+    lengths = list of dimension lengths
+Returns:
+    `N`-dimensional slice composed of indexes
+See_also: $(LREF IndexSlice)
++/
+IndexSlice!(Lengths.length) indexSlice(Lengths...)(Lengths lengths)
+    if (allSatisfy!(isIndex, Lengths))
+{
+    return .indexSlice!(Lengths.length)([lengths]);
+}
+
+///ditto
+IndexSlice!N indexSlice(size_t N)(auto ref size_t[N] lengths)
+{
+    import std.experimental.ndslice.slice: sliced;
+    with (typeof(return)) return Range(lengths[1 .. $]).sliced(lengths);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    auto im = indexSlice(7, 9);
+
+    assert(im[2, 1] == cast(size_t[2])[2, 1]);
+
+    for (auto elems = im.byElement; !elems.empty; elems.popFront)
+        assert(elems.front == elems.index);
+
+    //slicing works correctly
+    auto cm = im[1 .. $ - 3, 4 .. $ - 1];
+    assert(cm[2, 1] == cast(size_t[2])[3, 5]);
+}
+
+/++
+Slice composed of indexes.
+See_also: $(LREF indexSlice)
++/
+template IndexSlice(size_t N)
+    if (N)
+{
+    struct IndexMap
+    {
+        private size_t[N-1] _lengths;
+
+        auto save() @property const {
+            pragma(inline, true);
+            return this;
+        }
+
+        size_t[N] opIndex(size_t index) const
+        {
+            pragma(inline, true);
+            size_t[N] indexes = void;
+            foreach_reverse(i; Iota!(0, N - 1))
+            {
+                indexes[i + 1] = index % _lengths[i];
+                index /= _lengths[i];
+            }
+            indexes[0] = index;
+            return indexes;
+        }
+    }
+    alias IndexSlice = Slice!(N, IndexMap);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    alias IS4 = IndexSlice!4;
+    static assert(is(IS4 == Slice!(4, Range), Range));
+}
+
+unittest
+{
+    auto r = indexSlice(1);
+}
diff --git a/libphobos/src/std/experimental/ndslice/slice.d b/libphobos/src/std/experimental/ndslice/slice.d
new file mode 100644
index 00000000000..bb6573ee900
--- /dev/null
+++ b/libphobos/src/std/experimental/ndslice/slice.d
@@ -0,0 +1,2733 @@
+/**
+This is a submodule of $(LINK2 std_experimental_ndslice.html, std.experimental.ndslice).
+
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors:   Ilya Yaroshenko
+
+Source:    $(PHOBOSSRC std/_experimental/_ndslice/_slice.d)
+
+Macros:
+SUBMODULE = $(LINK2 std_experimental_ndslice_$1.html, std.experimental.ndslice.$1)
+SUBREF = $(LINK2 std_experimental_ndslice_$1.html#.$2, $(TT $2))$(NBSP)
+T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
+T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
+STD = $(TD $(SMALL $0))
+*/
+module std.experimental.ndslice.slice;
+
+import std.traits;
+import std.meta;
+import std.typecons; //: Flag, Yes, No;
+
+import std.experimental.ndslice.internal;
+
+/++
+Creates an n-dimensional slice-shell over a `range`.
+Params:
+    range = a random access range or an array; only index operator
+        `auto opIndex(size_t index)` is required for ranges. The length of the
+        range should be equal to the sum of shift and the product of
+        lengths. If `allowDownsize`, the length of the
+        range should be greater than or equal to the sum of shift and the product of
+        lengths.
+    lengths = list of lengths for each dimension
+    shift = index of the first element of a `range`.
+        The first `shift` elements of range are ignored.
+    Names = names of elements in a slice tuple.
+        Slice tuple is a slice, which holds single set of lengths and strides
+        for a number of ranges.
+    mod = If `yes`, the array will be replaced with its pointer to improve performance.
+Use `no` for compile time function evaluation.
+
+Returns:
+    n-dimensional slice
++/
+auto sliced(
+    Flag!"replaceArrayWithPointer" replaceArrayWithPointer = Yes.replaceArrayWithPointer,
+    Flag!"allowDownsize" allowDownsize = No.allowDownsize,
+    Range, Lengths...)(Range range, Lengths lengths)
+    if (!isStaticArray!Range && !isNarrowString!Range
+        && allSatisfy!(isIndex, Lengths) && Lengths.length)
+{
+    return .sliced!(replaceArrayWithPointer, allowDownsize, Lengths.length, Range)(range, [lengths]);
+}
+
+///ditto
+auto sliced(
+    Flag!"replaceArrayWithPointer" replaceArrayWithPointer = Yes.replaceArrayWithPointer,
+    Flag!"allowDownsize" allowDownsize = No.allowDownsize,
+    size_t N, Range)(Range range, auto ref in size_t[N] lengths, size_t shift = 0)
+    if (!isStaticArray!Range && !isNarrowString!Range && N)
+in
+{
+    import std.range.primitives: hasLength;
+    foreach (len; lengths)
+        assert(len > 0,
+            "All lengths must be positive."
+            ~ tailErrorMessage!());
+    static if (hasLength!Range)
+    {
+        static if (allowDownsize)
+        {
+            assert(lengthsProduct!N(lengths) + shift <= range.length,
+                "Range length must be greater than or equal to the sum of shift and the product of lengths."
+                ~ tailErrorMessage!());
+        }
+        else
+        {
+            assert(lengthsProduct!N(lengths) + shift == range.length,
+                "Range length must be equal to the sum of shift and the product of lengths."
+                ~ tailErrorMessage!());
+        }
+    }
+}
+body
+{
+    static if (isDynamicArray!Range && replaceArrayWithPointer)
+    {
+        Slice!(N, typeof(range.ptr)) ret = void;
+        ret._ptr = range.ptr + shift;
+    }
+    else
+    {
+        alias S = Slice!(N, ImplicitlyUnqual!(typeof(range)));
+        static if (hasElaborateAssign!(S.PureRange))
+            S ret;
+        else
+            S ret = void;
+        static if (hasPtrBehavior!(S.PureRange))
+        {
+            static if (S.NSeq.length == 1)
+                ret._ptr = range;
+            else
+                ret._ptr = range._ptr;
+            ret._ptr += shift;
+        }
+        else
+        {
+            static if (S.NSeq.length == 1)
+            {
+                ret._ptr._range = range;
+                ret._ptr._shift = shift;
+            }
+            else
+            {
+                ret._ptr = range._ptr;
+                ret._ptr._shift += range._strides[0] * shift;
+            }
+        }
+    }
+    ret._lengths[N - 1] = lengths[N - 1];
+    static if (ret.NSeq.length == 1)
+        ret._strides[N - 1] = 1;
+    else
+        ret._strides[N - 1] = range._strides[0];
+    foreach_reverse(i; Iota!(0, N - 1))
+    {
+        ret._lengths[i] = lengths[i];
+        ret._strides[i] = ret._strides[i + 1] * ret._lengths[i + 1];
+    }
+    foreach (i; Iota!(N, ret.PureN))
+    {
+        ret._lengths[i] = range._lengths[i - N + 1];
+        ret._strides[i] = range._strides[i - N + 1];
+    }
+    return ret;
+}
+
+private enum bool _isSlice(T) = is(T : Slice!(N, Range), size_t N, Range);
+
+///ditto
+template sliced(Names...)
+ if (Names.length && !anySatisfy!(isType, Names) && allSatisfy!(isStringValue, Names))
+{
+    mixin (
+    "
+    auto sliced(
+            Flag!`replaceArrayWithPointer` replaceArrayWithPointer = Yes.replaceArrayWithPointer,
+            Flag!`allowDownsize` allowDownsize = No.allowDownsize,
+            " ~ _Range_Types!Names ~ "
+            Lengths...)
+            (" ~ _Range_DeclarationList!Names ~
+            "Lengths lengths)
+    if (allSatisfy!(isIndex, Lengths))
+    {
+        alias sliced = .sliced!Names;
+        return sliced!(replaceArrayWithPointer, allowDownsize)(" ~ _Range_Values!Names ~ "[lengths]);
+    }
+
+    auto sliced(
+            Flag!`replaceArrayWithPointer` replaceArrayWithPointer = Yes.replaceArrayWithPointer,
+            Flag!`allowDownsize` allowDownsize = No.allowDownsize,
+            size_t N, " ~ _Range_Types!Names ~ ")
+            (" ~ _Range_DeclarationList!Names ~"
+            auto ref in size_t[N] lengths,
+            size_t shift = 0)
+    {
+        alias RS = AliasSeq!(" ~ _Range_Types!Names ~ ");"
+        ~ q{
+            import std.range.primitives: hasLength;
+            import std.meta: staticMap;
+            static assert(!anySatisfy!(_isSlice, RS),
+                `Packed slices are not allowed in slice tuples`
+                ~ tailErrorMessage!());
+            alias PT = PtrTuple!Names;
+            alias SPT = PT!(staticMap!(PrepareRangeType, RS));
+            static if (hasElaborateAssign!SPT)
+                SPT range;
+            else
+                SPT range = void;
+            version(assert) immutable minLength = lengthsProduct!N(lengths) + shift;
+            foreach (i, name; Names)
+            {
+                alias T = typeof(range.ptrs[i]);
+                alias R = RS[i];
+                static assert(!isStaticArray!R);
+                static assert(!isNarrowString!R);
+                mixin (`alias r = range_` ~ name ~`;`);
+                static if (hasLength!R)
+                {
+                    static if (allowDownsize)
+                    {
+                        assert(minLength <= r.length,
+                            `length of range '` ~ name ~`' must be greater than or equal `
+                            ~ `to the sum of shift and the product of lengths.`
+                            ~ tailErrorMessage!());
+                    }
+                    else
+                    {
+                        assert(minLength == r.length,
+                            `length of range '` ~ name ~`' must be equal `
+                            ~ `to the sum of shift and the product of lengths.`
+                            ~ tailErrorMessage!());
+                    }
+                }
+                static if (isDynamicArray!T && replaceArrayWithPointer)
+                    range.ptrs[i] = r.ptr;
+                else
+                    range.ptrs[i] = T(0, r);
+            }
+            return .sliced!(replaceArrayWithPointer, allowDownsize, N, SPT)(range, lengths, shift);
+        }
+    ~ "}");
+}
+
+/// Creates a slice from an array.
+pure nothrow unittest
+{
+    auto slice = new int [5 * 6 * 7].sliced(5, 6, 7);
+    assert(slice.length == 5);
+    assert(slice.elementsCount == 5 * 6 * 7);
+    static assert(is(typeof(slice) == Slice!(3, int*)));
+}
+
+/// Creates a slice using shift parameter.
+@safe @nogc pure nothrow unittest
+{
+    import std.range: iota;
+    auto slice = (5 * 6 * 7 + 9).iota.sliced([5, 6, 7], 9);
+    assert(slice.length == 5);
+    assert(slice.elementsCount == 5 * 6 * 7);
+    assert(slice[0, 0, 0] == 9);
+}
+
+/// $(LINK2 https://en.wikipedia.org/wiki/Vandermonde_matrix, Vandermonde matrix)
+pure nothrow unittest
+{
+    auto vandermondeMatrix(Slice!(1, double*) x)
+    {
+        auto ret = new double[x.length ^^ 2]
+            .sliced(x.length, x.length);
+        foreach (i; 0 .. x.length)
+        foreach (j; 0 .. x.length)
+            ret[i, j] = x[i] ^^ j;
+        return ret;
+    }
+
+    auto x = [1.0, 2, 3, 4, 5].sliced(5);
+    auto v = vandermondeMatrix(x);
+    assert(v ==
+        [[  1.0,   1,   1,   1,   1],
+         [  1.0,   2,   4,   8,  16],
+         [  1.0,   3,   9,  27,  81],
+         [  1.0,   4,  16,  64, 256],
+         [  1.0,   5,  25, 125, 625]]);
+}
+
+/++
+Creates a slice composed of named elements, each one of which corresponds
+to a given argument. See also $(LREF assumeSameStructure).
++/
+pure nothrow unittest
+{
+    import std.algorithm.comparison: equal;
+    import std.experimental.ndslice.selection: byElement;
+    import std.range: iota;
+
+    auto alpha = 12.iota;
+    auto beta = new int[12];
+
+    auto m = sliced!("a", "b")(alpha, beta, 4, 3);
+    foreach (r; m)
+        foreach (e; r)
+            e.b = e.a;
+    assert(equal(alpha, beta));
+
+    beta[] = 0;
+    foreach (e; m.byElement)
+        e.b = e.a;
+    assert(equal(alpha, beta));
+}
+
+/++
+Creates an array and an n-dimensional slice over it.
++/
+pure nothrow unittest
+{
+    auto createSlice(T, Lengths...)(Lengths lengths)
+    {
+        return createSlice2!(T, Lengths.length)(cast(size_t[Lengths.length])[lengths]);
+    }
+
+    ///ditto
+    auto createSlice2(T, size_t N)(auto ref size_t[N] lengths)
+    {
+        size_t length = lengths[0];
+        foreach (len; lengths[1 .. N])
+                length *= len;
+        return new T[length].sliced(lengths);
+    }
+
+    auto slice = createSlice!int(5, 6, 7);
+    assert(slice.length == 5);
+    assert(slice.elementsCount == 5 * 6 * 7);
+    static assert(is(typeof(slice) == Slice!(3, int*)));
+
+    auto duplicate = createSlice2!int(slice.shape);
+    duplicate[] = slice;
+}
+
+/++
+Creates a common n-dimensional array.
++/
+pure nothrow unittest
+{
+    auto ndarray(size_t N, Range)(auto ref Slice!(N, Range) slice)
+    {
+        import std.array: array;
+        static if (N == 1)
+        {
+            return slice.array;
+        }
+        else
+        {
+            import std.algorithm.iteration: map;
+            return slice.map!(a => ndarray(a)).array;
+        }
+    }
+
+    import std.range: iota;
+    auto ar = ndarray(12.iota.sliced(3, 4));
+    static assert(is(typeof(ar) == int[][]));
+    assert(ar == [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]);
+}
+
+/++
+Allocates an array through a specified allocator and creates an n-dimensional slice over it.
+See also $(LINK2 std_experimental_allocator.html, std.experimental.allocator).
++/
+unittest
+{
+    import std.experimental.allocator;
+
+
+    // `theAllocator.makeSlice(3, 4)` allocates an array with length equal to `12`
+    // and returns this array and a `2`-dimensional slice-shell over it.
+    auto makeSlice(T, Allocator, Lengths...)(auto ref Allocator alloc, Lengths lengths)
+    {
+        enum N = Lengths.length;
+        struct Result { T[] array; Slice!(N, T*) slice; }
+        size_t length = lengths[0];
+        foreach (len; lengths[1 .. N])
+                length *= len;
+        T[] a = alloc.makeArray!T(length);
+        return Result(a, a.sliced(lengths));
+    }
+
+    auto tup = makeSlice!int(theAllocator, 2, 3, 4);
+
+    static assert(is(typeof(tup.array) == int[]));
+    static assert(is(typeof(tup.slice) == Slice!(3, int*)));
+
+    assert(tup.array.length           == 24);
+    assert(tup.slice.elementsCount    == 24);
+    assert(tup.array.ptr == &tup.slice[0, 0, 0]);
+
+    theAllocator.dispose(tup.array);
+}
+
+/// Input range primitives for slices over user defined types
+pure nothrow @nogc unittest
+{
+    struct MyIota
+    {
+        //`[index]` operator overloading
+        auto opIndex(size_t index)
+        {
+            return index;
+        }
+    }
+
+    alias S = Slice!(3, MyIota);
+    auto slice = MyIota().sliced(20, 10);
+
+    import std.range.primitives;
+    static assert(hasLength!S);
+    static assert(isInputRange!S);
+    static assert(isForwardRange!S == false);
+}
+
+/// Random access range primitives for slices over user defined types
+pure nothrow @nogc unittest
+{
+    struct MyIota
+    {
+        //`[index]` operator overloading
+        auto opIndex(size_t index)
+        {
+            return index;
+        }
+        // `save` property to allow a slice to be a forward range
+        auto save() @property
+        {
+            return this;
+        }
+    }
+
+    alias S = Slice!(3, MyIota);
+    auto slice = MyIota().sliced(20, 10);
+
+    import std.range.primitives;
+    static assert(hasLength!S);
+    static assert(hasSlicing!S);
+    static assert(isForwardRange!S);
+    static assert(isBidirectionalRange!S);
+    static assert(isRandomAccessRange!S);
+}
+
+/// Slice tuple and flags
+pure nothrow @nogc unittest
+{
+    import std.typecons: Yes, No;
+    static immutable a = [1, 2, 3, 4, 5, 6];
+    static immutable b = [1.0, 2, 3, 4, 5, 6];
+    alias namedSliced = sliced!("a", "b");
+    auto slice = namedSliced!(No.replaceArrayWithPointer, Yes.allowDownsize)
+        (a, b, 2, 3);
+    assert(slice[1, 2].a == slice[1, 2].b);
+}
+
+// sliced slice
+pure nothrow unittest
+{
+    import std.range: iota;
+    auto data = new int[24];
+    foreach (int i,ref e; data)
+        e = i;
+    auto a = data[0..10].sliced(10)[0..6].sliced(2, 3);
+    auto b = 10.iota.sliced(10)[0..6].sliced(2, 3);
+    assert(a == b);
+    a[] += b;
+    foreach (int i, e; data[0..6])
+        assert(e == 2*i);
+    foreach (int i, e; data[6..$])
+        assert(e == i+6);
+    auto c  = data.sliced(12, 2)[0..6].sliced(2, 3);
+    auto d  = 24.iota.sliced(12, 2)[0..6].sliced(2, 3);
+    auto cc = data[0..12].sliced(2, 3, 2);
+    auto dc = 12.iota.sliced(2, 3, 2);
+    assert(c._lengths == cc._lengths);
+    assert(c._strides == cc._strides);
+    assert(d._lengths == dc._lengths);
+    assert(d._strides == dc._strides);
+    assert(cc == c);
+    assert(dc == d);
+    auto e  =    data.sliced(8, 3)[0..5].sliced(5);
+    auto f  = 24.iota.sliced(8, 3)[0..5].sliced(5);
+    assert(e ==    data[0..15].sliced(5, 3));
+    assert(f == 15.iota.sliced(5, 3));
+}
+
+private template _Range_Types(Names...)
+{
+    static if (Names.length)
+        enum string _Range_Types = "Range_" ~ Names[0] ~ ", " ~ _Range_Types!(Names[1..$]);
+    else
+        enum string _Range_Types = "";
+}
+
+private template _Range_Values(Names...)
+{
+    static if (Names.length)
+        enum string _Range_Values = "range_" ~ Names[0] ~ ", " ~ _Range_Values!(Names[1..$]);
+    else
+        enum string _Range_Values = "";
+}
+
+private template _Range_DeclarationList(Names...)
+{
+    static if (Names.length)
+        enum string _Range_DeclarationList = "Range_" ~ Names[0] ~ " range_" ~ Names[0] ~ ", " ~ _Range_DeclarationList!(Names[1..$]);
+    else
+        enum string _Range_DeclarationList = "";
+}
+
+private template _Slice_DeclarationList(Names...)
+{
+    static if (Names.length)
+        enum string _Slice_DeclarationList = "Slice!(N, Range_" ~ Names[0] ~ ") slice_" ~ Names[0] ~ ", " ~ _Slice_DeclarationList!(Names[1..$]);
+    else
+        enum string _Slice_DeclarationList = "";
+}
+
+/++
+Groups slices into a slice tuple. The slices must have identical structure.
+Slice tuple is a slice, which holds single set of lengths and strides
+for a number of ranges.
+Params:
+    Names = names of elements in a slice tuple
+Returns:
+    n-dimensional slice
+See_also: $(LREF .Slice.structure).
++/
+template assumeSameStructure(Names...)
+ if (Names.length && !anySatisfy!(isType, Names) && allSatisfy!(isStringValue, Names))
+{
+    mixin (
+    "
+    auto assumeSameStructure(
+            size_t N, " ~ _Range_Types!Names ~ ")
+            (" ~ _Slice_DeclarationList!Names ~ ")
+    {
+        alias RS = AliasSeq!("  ~_Range_Types!Names ~ ");"
+        ~ q{
+            import std.meta: staticMap;
+            static assert(!anySatisfy!(_isSlice, RS),
+                `Packed slices not allowed in slice tuples`
+                ~ tailErrorMessage!());
+            alias PT = PtrTuple!Names;
+            alias SPT = PT!(staticMap!(PrepareRangeType, RS));
+            static if (hasElaborateAssign!SPT)
+                Slice!(N, SPT) ret;
+            else
+                Slice!(N, SPT) ret = void;
+            mixin (`alias slice0 = slice_` ~ Names[0] ~`;`);
+            ret._lengths = slice0._lengths;
+            ret._strides = slice0._strides;
+            ret._ptr.ptrs[0] = slice0._ptr;
+            foreach (i, name; Names[1..$])
+            {
+                mixin (`alias slice = slice_` ~ name ~`;`);
+                assert(ret._lengths == slice._lengths,
+                    `Shapes must be identical`
+                    ~ tailErrorMessage!());
+                assert(ret._strides == slice._strides,
+                    `Strides must be identical`
+                    ~ tailErrorMessage!());
+                ret._ptr.ptrs[i+1] = slice._ptr;
+            }
+            return ret;
+        }
+    ~ "}");
+}
+
+///
+pure nothrow unittest
+{
+    import std.algorithm.comparison: equal;
+    import std.experimental.ndslice.selection: byElement;
+    import std.range: iota;
+
+    auto alpha = 12.iota   .sliced(4, 3);
+    auto beta = new int[12].sliced(4, 3);
+
+    auto m = assumeSameStructure!("a", "b")(alpha, beta);
+    foreach (r; m)
+        foreach (e; r)
+            e.b = e.a;
+    assert(alpha == beta);
+
+    beta[] = 0;
+    foreach (e; m.byElement)
+        e.b = e.a;
+    assert(alpha == beta);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    import std.algorithm.iteration: map, sum, reduce;
+    import std.algorithm.comparison: max;
+    import std.experimental.ndslice.iteration: transposed;
+    /// Returns maximal column average.
+    auto maxAvg(S)(S matrix) {
+        return matrix.transposed.map!sum.reduce!max
+             / matrix.length;
+    }
+    enum matrix = [1, 2,
+                   3, 4].sliced!(No.replaceArrayWithPointer)(2, 2);
+    ///Сompile time function evaluation
+    static assert(maxAvg(matrix) == 3);
+}
+
+/++
+Returns the element type of the `Slice` type.
++/
+alias DeepElementType(S : Slice!(N, Range), size_t N, Range) = S.DeepElemType;
+
+///
+unittest
+{
+    import std.range: iota;
+    static assert(is(DeepElementType!(Slice!(4, const(int)[]))     == const(int)));
+    static assert(is(DeepElementType!(Slice!(4, immutable(int)*))  == immutable(int)));
+    static assert(is(DeepElementType!(Slice!(4, typeof(100.iota))) == int));
+    //packed slice
+    static assert(is(DeepElementType!(Slice!(2, Slice!(5, int*)))  == Slice!(4, int*)));
+}
+
+/++
+Presents $(LREF .Slice.structure).
++/
+struct Structure(size_t N)
+{
+    ///
+    size_t[N] lengths;
+    ///
+    sizediff_t[N] strides;
+}
+
+/++
+Presents an n-dimensional view over a range.
+
+$(H3 Definitions)
+
+In order to change data in a slice using
+overloaded operators such as `=`, `+=`, `++`,
+a syntactic structure of type
+`<slice to change>[<index and interval sequence...>]` must be used.
+It is worth noting that just like for regular arrays, operations `a = b`
+and `a[] = b` have different meanings.
+In the first case, after the operation is carried out, `a` simply points at the same data as `b`
+does, and the data which `a` previously pointed at remains unmodified.
+Here, `а` and `b` must be of the same type.
+In the second case, `a` points at the same data as before,
+but the data itself will be changed. In this instance, the number of dimensions of `b`
+may be less than the number of dimensions of `а`; and `b` can be a Slice,
+a regular multidimensional array, or simply a value (e.g. a number).
+
+In the following table you will find the definitions you might come across
+in comments on operator overloading.
+
+$(BOOKTABLE
+$(TR $(TH Definition) $(TH Examples at `N == 3`))
+$(TR $(TD An $(BLUE interval) is a part of a sequence of type `i .. j`.)
+    $(STD `2..$-3`, `0..4`))
+$(TR $(TD An $(BLUE index) is a part of a sequence of type `i`.)
+    $(STD `3`, `$-1`))
+$(TR $(TD A $(BLUE partially defined slice) is a sequence composed of
+    $(BLUE intervals) and $(BLUE indexes) with an overall length strictly less than `N`.)
+    $(STD `[3]`, `[0..$]`, `[3, 3]`, `[0..$,0..3]`, `[0..$,2]`))
+$(TR $(TD A $(BLUE fully defined index) is a sequence
+    composed only of $(BLUE indexes) with an overall length equal to `N`.)
+    $(STD `[2,3,1]`))
+$(TR $(TD A $(BLUE fully defined slice) is an empty sequence
+    or a sequence composed of $(BLUE indexes) and at least one
+    $(BLUE interval) with an overall length equal to `N`.)
+    $(STD `[]`, `[3..$,0..3,0..$-1]`, `[2,0..$,1]`))
+)
+
+$(H3 Internal Binary Representation)
+
+Multidimensional Slice is a structure that consists of lengths, strides, and a pointer.
+For ranges, a shell is used instead of a pointer.
+This shell contains a shift of the current initial element of a multidimensional slice
+and the range itself. With the exception of overloaded operators, no functions in this
+package change or copy data. The operations are only carried out on lengths, strides,
+and pointers. If a slice is defined over a range, only the shift of the initial element
+changes instead of the pointer.
+
+$(H4 Internal Representation for Pointers)
+
+Type definition
+
+-------
+Slice!(N, T*)
+-------
+
+Schema
+
+-------
+Slice!(N, T*)
+    size_t[N]     lengths
+    sizediff_t[N] strides
+    T*            ptr
+-------
+
+Example:
+
+Definitions
+
+-------
+import std.experimental.ndslice;
+auto a = new double[24];
+Slice!(3, double*) s = a.sliced(2, 3, 4);
+Slice!(3, double*) t = s.transposed!(1, 2, 0);
+Slice!(3, double*) r = r.reversed!1;
+-------
+
+Representation
+
+-------
+s________________________
+    lengths[0] ::=  2
+    lengths[1] ::=  3
+    lengths[2] ::=  4
+
+    strides[0] ::= 12
+    strides[1] ::=  4
+    strides[2] ::=  1
+
+    ptr        ::= &a[0]
+
+t____transposed!(1, 2, 0)
+    lengths[0] ::=  3
+    lengths[1] ::=  4
+    lengths[2] ::=  2
+
+    strides[0] ::=  4
+    strides[1] ::=  1
+    strides[2] ::= 12
+
+    ptr        ::= &a[0]
+
+r______________reversed!1
+    lengths[0] ::=  2
+    lengths[1] ::=  3
+    lengths[2] ::=  4
+
+    strides[0] ::= 12
+    strides[1] ::= -4
+    strides[2] ::=  1
+
+    ptr        ::= &a[8] // (old_strides[1] * (lengths[1] - 1)) = 8
+-------
+
+$(H4 Internal Representation for Ranges)
+
+Type definition
+
+-------
+Slice!(N, Range)
+-------
+
+Representation
+
+-------
+Slice!(N, Range)
+    size_t[N]     lengths
+    sizediff_t[N] strides
+    PtrShell!T    ptr
+        sizediff_t shift
+        Range      range
+-------
+
+
+Example:
+
+Definitions
+
+-------
+import std.experimental.ndslice;
+import std.range: iota;
+auto a = iota(24);
+alias A = typeof(a);
+Slice!(3, A) s = a.sliced(2, 3, 4);
+Slice!(3, A) t = s.transposed!(1, 2, 0);
+Slice!(3, A) r = r.reversed!1;
+-------
+
+Representation
+
+-------
+s________________________
+    lengths[0] ::=  2
+    lengths[1] ::=  3
+    lengths[2] ::=  4
+
+    strides[0] ::= 12
+    strides[1] ::=  4
+    strides[2] ::=  1
+
+        shift  ::=  0
+        range  ::=  a
+
+t____transposed!(1, 2, 0)
+    lengths[0] ::=  3
+    lengths[1] ::=  4
+    lengths[2] ::=  2
+
+    strides[0] ::=  4
+    strides[1] ::=  1
+    strides[2] ::= 12
+
+        shift  ::=  0
+        range  ::=  a
+
+r______________reversed!1
+    lengths[0] ::=  2
+    lengths[1] ::=  3
+    lengths[2] ::=  4
+
+    strides[0] ::= 12
+    strides[1] ::= -4
+    strides[2] ::=  1
+
+        shift  ::=  8 // (old_strides[1] * (lengths[1] - 1)) = 8
+        range  ::=  a
+-------
++/
+struct Slice(size_t _N, _Range)
+    if (_N && _N < 256LU && ((!is(Unqual!_Range : Slice!(N0, Range0), size_t N0, Range0)
+                     && (isPointer!_Range || is(typeof(_Range.init[size_t.init]))))
+                    || is(_Range == Slice!(N1, Range1), size_t N1, Range1)))
+{
+    package:
+
+    enum doUnittest = is(_Range == int*) && _N == 1;
+
+    alias N = _N;
+    alias Range = _Range;
+
+    alias This = Slice!(N, Range);
+    static if (is(Range == Slice!(N_, Range_), size_t N_, Range_))
+    {
+        enum size_t PureN = N + Range.PureN - 1;
+        alias PureRange = Range.PureRange;
+        alias NSeq = AliasSeq!(N, Range.NSeq);
+    }
+    else
+    {
+        alias PureN = N;
+        alias PureRange = Range;
+        alias NSeq = AliasSeq!(N);
+    }
+    alias PureThis = Slice!(PureN, PureRange);
+
+    static assert(PureN < 256, "Slice: Pure N should be less than 256");
+
+    static if (N == 1)
+        alias ElemType = typeof(Range.init[size_t.init]);
+    else
+        alias ElemType = Slice!(N-1, Range);
+
+    static if (NSeq.length == 1)
+        alias DeepElemType = typeof(Range.init[size_t.init]);
+    else
+    static if (Range.N == 1)
+        alias DeepElemType = Range.ElemType;
+    else
+        alias DeepElemType = Slice!(Range.N - 1, Range.Range);
+
+    enum hasAccessByRef = isPointer!PureRange ||
+        __traits(compiles, { auto a = &(_ptr[0]); } );
+
+    enum PureIndexLength(Slices...) = Filter!(isIndex, Slices).length;
+    template isFullPureIndex(Indexes...)
+    {
+        static if (allSatisfy!(isIndex, Indexes))
+            enum isFullPureIndex  = Indexes.length == N;
+        else
+        static if (Indexes.length == 1 && isStaticArray!(Indexes[0]))
+            enum isFullPureIndex = Indexes[0].length == N && isIndex!(ForeachType!(Indexes[0]));
+        else
+            enum isFullPureIndex = false;
+    }
+    enum isPureSlice(Slices...) =
+           Slices.length <= N
+        && PureIndexLength!Slices < N
+        && Filter!(isStaticArray, Slices).length == 0;
+
+    enum isFullPureSlice(Slices...) =
+           Slices.length == 0
+        || Slices.length == N
+        && PureIndexLength!Slices < N
+        && Filter!(isStaticArray, Slices).length == 0;
+
+    size_t[PureN] _lengths;
+    sizediff_t[PureN] _strides;
+    static if (hasPtrBehavior!PureRange)
+        PureRange _ptr;
+    else
+        PtrShell!PureRange _ptr;
+
+    sizediff_t backIndex(size_t dimension = 0)() @property const
+        if (dimension < N)
+    {
+        return _strides[dimension] * (_lengths[dimension] - 1);
+    }
+
+    size_t indexStride(Indexes...)(Indexes _indexes)
+        if (isFullPureIndex!Indexes)
+    {
+        static if (isStaticArray!(Indexes[0]))
+        {
+            size_t stride;
+            foreach (i; Iota!(0, N)) //static
+            {
+                assert(_indexes[0][i] < _lengths[i],
+                    "indexStride: index at position "
+                    ~ i.stringof ~ " (from range [0 .." ~ N.stringof ~ ")) "
+                    ~ " must be less than corresponding length");
+                stride += _strides[i] * _indexes[0][i];
+            }
+            return stride;
+        }
+        else
+        {
+            size_t stride;
+            foreach (i, index; _indexes) //static
+            {
+                assert(index < _lengths[i],
+                    "indexStride: index at position "
+                    ~ i.stringof ~ " (from range [0 .." ~ N.stringof ~ ")) "
+                    ~ " must be less than corresponding length");
+                stride += _strides[i] * index;
+            }
+            return stride;
+        }
+    }
+
+    this(ref in size_t[PureN] lengths, ref in sizediff_t[PureN] strides, PureRange range)
+    {
+        foreach (i; Iota!(0, PureN))
+            _lengths[i] = lengths[i];
+        foreach (i; Iota!(0, PureN))
+            _strides[i] = strides[i];
+        static if (hasPtrBehavior!PureRange)
+            _ptr = range;
+        else
+            _ptr._range = range;
+
+    }
+
+    static if (!hasPtrBehavior!PureRange)
+    this(ref in size_t[PureN] lengths, ref in sizediff_t[PureN] strides, PtrShell!PureRange shell)
+    {
+        foreach (i; Iota!(0, PureN))
+            _lengths[i] = lengths[i];
+        foreach (i; Iota!(0, PureN))
+            _strides[i] = strides[i];
+        _ptr = shell;
+    }
+
+    public:
+
+    /++
+    Returns: static array of lengths
+    See_also: $(LREF .Slice.structure)
+    +/
+    size_t[N] shape() @property const
+    {
+        pragma(inline, true);
+        return _lengths[0 .. N];
+    }
+
+    static if (doUnittest)
+    /// Regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        assert(60.iota
+            .sliced(3, 4, 5)
+            .shape == cast(size_t[3])[3, 4, 5]);
+    }
+
+    static if (doUnittest)
+    /// Packed slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.experimental.ndslice.selection: pack;
+        import std.range: iota;
+        assert((3 * 4 * 5 * 6 * 7).iota
+            .sliced(3, 4, 5, 6, 7)
+            .pack!2
+            .shape == cast(size_t[3])[3, 4, 5]);
+    }
+
+    /++
+    Returns: static array of lengths and static array of strides
+    See_also: $(LREF .Slice.shape)
+   +/
+    Structure!N structure() @property const
+    {
+        pragma(inline, true);
+        return typeof(return)(_lengths[0 .. N], _strides[0 .. N]);
+    }
+
+    static if (doUnittest)
+    /// Regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        assert(60.iota
+            .sliced(3, 4, 5)
+            .structure == Structure!3([3, 4, 5], [20, 5, 1]));
+    }
+
+    static if (doUnittest)
+    /// Modified regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.experimental.ndslice.selection: pack;
+        import std.experimental.ndslice.iteration: reversed, strided, transposed;
+        import std.range: iota;
+        assert(600.iota
+            .sliced(3, 4, 50)
+            .reversed!2      //makes stride negative
+            .strided!2(6)    //multiplies stride by 6 and changes corresponding length
+            .transposed!2    //brings dimension `2` to the first position
+            .structure == Structure!3([9, 3, 4], [-6, 200, 50]));
+    }
+
+    static if (doUnittest)
+    /// Packed slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.experimental.ndslice.selection: pack;
+        import std.range: iota;
+        assert((3 * 4 * 5 * 6 * 7).iota
+            .sliced(3, 4, 5, 6, 7)
+            .pack!2
+            .structure == Structure!3([3, 4, 5], [20 * 42, 5 * 42, 1 * 42]));
+    }
+
+    /++
+    Range primitive.
+    Defined only if `Range` is a forward range or a pointer type.
+    +/
+    static if (canSave!PureRange)
+    auto save() @property
+    {
+        static if (isPointer!PureRange)
+            return typeof(this)(_lengths, _strides, _ptr);
+        else
+            return typeof(this)(_lengths, _strides, _ptr.save);
+    }
+
+    static if (doUnittest)
+    /// Forward range
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        auto slice = 6.iota.sliced(2, 3).save;
+    }
+
+    static if (doUnittest)
+    /// Pointer type.
+    pure nothrow unittest
+    {
+         //slice type is `Slice!(2, int*)`
+         auto slice = new int[6].sliced(2, 3).save;
+    }
+
+
+    /++
+    Multidimensional `length` property.
+    Returns: length of the corresponding dimension
+    See_also: $(LREF .Slice.shape), $(LREF .Slice.structure)
+    +/
+    size_t length(size_t dimension = 0)() @property const
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        return _lengths[dimension];
+    }
+
+    static if (doUnittest)
+    ///
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        auto slice = 60.iota.sliced(3, 4, 5);
+        assert(slice.length   == 3);
+        assert(slice.length!0 == 3);
+        assert(slice.length!1 == 4);
+        assert(slice.length!2 == 5);
+    }
+
+    alias opDollar = length;
+
+    /++
+        Multidimensional `stride` property.
+        Returns: stride of the corresponding dimension
+        See_also: $(LREF .Slice.structure)
+    +/
+    size_t stride(size_t dimension = 0)() @property const
+        if (dimension < N)
+    {
+        return _strides[dimension];
+    }
+
+    static if (doUnittest)
+    /// Regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        auto slice = 60.iota.sliced(3, 4, 5);
+        assert(slice.stride   == 20);
+        assert(slice.stride!0 == 20);
+        assert(slice.stride!1 == 5);
+        assert(slice.stride!2 == 1);
+    }
+
+    static if (doUnittest)
+    /// Modified regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.experimental.ndslice.iteration: reversed, strided, swapped;
+        import std.range: iota;
+        assert(600.iota
+            .sliced(3, 4, 50)
+            .reversed!2      //makes stride negative
+            .strided!2(6)    //multiplies stride by 6 and changes the corresponding length
+            .swapped!(1, 2)  //swaps dimensions `1` and `2`
+            .stride!1 == -6);
+    }
+
+    /++
+    Multidimensional input range primitive.
+    +/
+    bool empty(size_t dimension = 0)()
+    @property const
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        return _lengths[dimension] == 0;
+    }
+
+    ///ditto
+    auto ref front(size_t dimension = 0)() @property
+        if (dimension < N)
+    {
+        assert(!empty!dimension);
+        static if (PureN == 1)
+        {
+            static if (__traits(compiles,{ auto _f = _ptr.front; }))
+                return _ptr.front;
+            else
+                return _ptr[0];
+        }
+        else
+        {
+            static if (hasElaborateAssign!PureRange)
+                ElemType ret;
+            else
+                ElemType ret = void;
+            foreach (i; Iota!(0, dimension))
+            {
+                ret._lengths[i] = _lengths[i];
+                ret._strides[i] = _strides[i];
+            }
+            foreach (i; Iota!(dimension, PureN-1))
+            {
+                ret._lengths[i] = _lengths[i + 1];
+                ret._strides[i] = _strides[i + 1];
+            }
+            ret._ptr = _ptr;
+            return ret;
+        }
+    }
+
+    static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+    {
+        ///ditto
+        auto front(size_t dimension = 0, T)(T value) @property
+            if (dimension == 0)
+        {
+            assert(!empty!dimension);
+            static if (__traits(compiles, { _ptr.front = value; }))
+                return _ptr.front = value;
+            else
+                return _ptr[0] = value;
+        }
+    }
+
+    ///ditto
+    auto ref back(size_t dimension = 0)() @property
+        if (dimension < N)
+    {
+        assert(!empty!dimension);
+        static if (PureN == 1)
+        {
+            return _ptr[backIndex];
+        }
+        else
+        {
+            static if (hasElaborateAssign!PureRange)
+                ElemType ret;
+            else
+                ElemType ret = void;
+            foreach (i; Iota!(0, dimension))
+            {
+                ret._lengths[i] = _lengths[i];
+                ret._strides[i] = _strides[i];
+            }
+            foreach (i; Iota!(dimension, PureN-1))
+            {
+                ret._lengths[i] = _lengths[i + 1];
+                ret._strides[i] = _strides[i + 1];
+            }
+            ret._ptr = _ptr + backIndex!dimension;
+            return ret;
+        }
+    }
+
+    static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
+    {
+        ///ditto
+        auto back(size_t dimension = 0, T)(T value) @property
+            if (dimension == 0)
+        {
+            assert(!empty!dimension);
+            return _ptr[backIndex] = value;
+        }
+    }
+
+    ///ditto
+    void popFront(size_t dimension = 0)()
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0.");
+        _lengths[dimension]--;
+        _ptr += _strides[dimension];
+    }
+
+    ///ditto
+    void popBack(size_t dimension = 0)()
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0.");
+        _lengths[dimension]--;
+    }
+
+    ///ditto
+    void popFrontExactly(size_t dimension = 0)(size_t n)
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof);
+        _lengths[dimension] -= n;
+        _ptr += _strides[dimension] * n;
+    }
+
+    ///ditto
+    void popBackExactly(size_t dimension = 0)(size_t n)
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof);
+        _lengths[dimension] -= n;
+    }
+
+    ///ditto
+    void popFrontN(size_t dimension = 0)(size_t n)
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        import std.algorithm.comparison: min;
+        popFrontExactly!dimension(min(n, _lengths[dimension]));
+    }
+
+    ///ditto
+    void popBackN(size_t dimension = 0)(size_t n)
+        if (dimension < N)
+    {
+        pragma(inline, true);
+        import std.algorithm.comparison: min;
+        popBackExactly!dimension(min(n, _lengths[dimension]));
+    }
+
+    static if (doUnittest)
+    ///
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        import std.range.primitives;
+        auto slice = 6000.iota.sliced(10, 20, 30);
+
+        static assert(isRandomAccessRange!(typeof(slice)));
+        static assert(hasSlicing!(typeof(slice)));
+        static assert(hasLength!(typeof(slice)));
+
+        assert(slice.shape == cast(size_t[3])[10, 20, 30]);
+        slice.popFront;
+        slice.popFront!1;
+        slice.popBackExactly!2(4);
+        assert(slice.shape == cast(size_t[3])[9, 19, 26]);
+
+        auto matrix = slice.front!1;
+        assert(matrix.shape == cast(size_t[2])[9, 26]);
+
+        auto column = matrix.back!1;
+        assert(column.shape == cast(size_t[1])[9]);
+
+        slice.popFrontExactly!1(slice.length!1);
+        assert(slice.empty   == false);
+        assert(slice.empty!1 == true);
+        assert(slice.empty!2 == false);
+        assert(slice.shape == cast(size_t[3])[9, 0, 26]);
+
+        assert(slice.back.front!1.empty);
+
+        slice.popFrontN!0(40);
+        slice.popFrontN!2(40);
+        assert(slice.shape == cast(size_t[3])[0, 0, 0]);
+    }
+
+    package void popFront(size_t dimension)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        assert(_lengths[dimension], ": length!dim should be greater than 0.");
+        _lengths[dimension]--;
+        _ptr += _strides[dimension];
+    }
+
+
+    package void popBack(size_t dimension)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        assert(_lengths[dimension], ": length!dim should be greater than 0.");
+        _lengths[dimension]--;
+    }
+
+    package void popFrontExactly(size_t dimension, size_t n)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!dim");
+        _lengths[dimension] -= n;
+        _ptr += _strides[dimension] * n;
+    }
+
+    package void popBackExactly(size_t dimension, size_t n)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!dim");
+        _lengths[dimension] -= n;
+    }
+
+    package void popFrontN(size_t dimension, size_t n)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        import std.algorithm.comparison: min;
+        popFrontExactly(dimension, min(n, _lengths[dimension]));
+    }
+
+    package void popBackN(size_t dimension, size_t n)
+    {
+        assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof);
+        import std.algorithm.comparison: min;
+        popBackExactly(dimension, min(n, _lengths[dimension]));
+    }
+
+    /++
+    Returns: total number of elements in a slice
+    +/
+    size_t elementsCount() const
+    {
+        size_t len = 1;
+        foreach (i; Iota!(0, N))
+            len *= _lengths[i];
+        return len;
+    }
+
+    static if (doUnittest)
+    /// Regular slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.range: iota;
+        assert(60.iota.sliced(3, 4, 5).elementsCount == 60);
+    }
+
+
+    static if (doUnittest)
+    /// Packed slice
+    @safe @nogc pure nothrow unittest
+    {
+        import std.experimental.ndslice.selection: pack, evertPack;
+        import std.range: iota;
+        auto slice = (3 * 4 * 5 * 6 * 7 * 8).iota
+            .sliced(3, 4, 5, 6, 7, 8);
+        auto p = slice.pack!2;
+        assert(p.elementsCount == 360);
+        assert(p[0, 0, 0, 0].elementsCount == 56);
+        assert(p.evertPack.elementsCount == 56);
+    }
+
+    /++
+    Overloading `==` and `!=`
+    +/
+    bool opEquals(size_t NR, RangeR)(auto ref Slice!(NR, RangeR) rslice)
+        if (Slice!(NR, RangeR).PureN == PureN)
+    {
+        if (this._lengths != rslice._lengths)
+            return false;
+        static if (
+               !hasReference!(typeof(this))
+            && !hasReference!(typeof(rslice))
+            && __traits(compiles, this._ptr == rslice._ptr)
+            )
+        {
+            if (this._strides == rslice._strides && this._ptr == rslice._ptr)
+                return true;
+        }
+        return opEqualsImpl(this, rslice);
+    }
+
+    ///ditto
+    bool opEquals(T)(T[] rarrary)
+    {
+        if (this.length != rarrary.length)
+            return false;
+        foreach(i, ref e; rarrary)
+            if (e != this[i])
+                return false;
+        return true;
+    }
+
+    static if (doUnittest)
+    ///
+    pure nothrow unittest
+    {
+        auto a = [1, 2, 3, 4].sliced(2, 2);
+
+        assert(a != [1, 2, 3, 4, 5, 6].sliced(2, 3));
+        assert(a != [[1, 2, 3], [4, 5, 6]]);
+
+        assert(a == [1, 2, 3, 4].sliced(2, 2));
+        assert(a == [[1, 2], [3, 4]]);
+
+        assert(a != [9, 2, 3, 4].sliced(2, 2));
+        assert(a != [[9, 2], [3, 4]]);
+    }
+
+    _Slice opSlice(size_t dimension)(size_t i, size_t j)
+        if (dimension < N)
+    in   {
+        assert(i <= j,
+            "Slice.opSlice!" ~ dimension.stringof ~ ": the left bound must be less than or equal to the right bound.");
+        assert(j - i <= _lengths[dimension],
+            "Slice.opSlice!" ~ dimension.stringof ~
+            ": difference between the right and the left bounds must be less than or equal to the length of the given dimension.");
+    }
+    body
+    {
+        pragma(inline, true);
+        return typeof(return)(i, j);
+    }
+
+    /++
+    $(BLUE Fully defined index).
+    +/
+    auto ref opIndex(Indexes...)(Indexes _indexes)
+        if (isFullPureIndex!Indexes)
+    {
+        static if (PureN == N)
+            return _ptr[indexStride(_indexes)];
+        else
+            return DeepElemType(_lengths[N .. $], _strides[N .. $], _ptr + indexStride(_indexes));
+    }
+
+    static if (doUnittest)
+    ///
+    pure nothrow unittest
+    {
+        auto slice = new int[10].sliced(5, 2);
+
+        auto p = &slice[1, 1];
+        *p = 3;
+        assert(slice[1, 1] == 3);
+
+        size_t[2] index = [1, 1];
+        assert(slice[index] == 3);
+    }
+
+    /++
+    $(BLUE Partially or fully defined slice).
+    +/
+    auto opIndex(Slices...)(Slices slices)
+        if (isPureSlice!Slices)
+    {
+        static if (Slices.length)
+        {
+
+            enum size_t j(size_t n) = n - Filter!(isIndex, Slices[0 .. n+1]).length;
+            enum size_t F = PureIndexLength!Slices;
+            enum size_t S = Slices.length;
+            static assert(N-F > 0);
+            size_t stride;
+            static if (hasElaborateAssign!PureRange)
+                Slice!(N-F, Range) ret;
+            else
+                Slice!(N-F, Range) ret = void;
+            foreach (i, slice; slices) //static
+            {
+                static if (isIndex!(Slices[i]))
+                {
+                    assert(slice < _lengths[i], "Slice.opIndex: index must be less than length");
+                    stride += _strides[i] * slice;
+                }
+                else
+                {
+                    stride += _strides[i] * slice.i;
+                    ret._lengths[j!i] = slice.j - slice.i;
+                    ret._strides[j!i] = _strides[i];
+                }
+            }
+            foreach (i; Iota!(S, PureN))
+            {
+                ret._lengths[i - F] = _lengths[i];
+                ret._strides[i - F] = _strides[i];
+            }
+            ret._ptr = _ptr + stride;
+            return ret;
+        }
+        else
+        {
+            return this;
+        }
+    }
+
+    static if (doUnittest)
+    ///
+    pure nothrow unittest
+    {
+        auto slice = new int[15].sliced(5, 3);
+
+        /// Fully defined slice
+        assert(slice[] == slice);
+        auto sublice = slice[0..$-2, 1..$];
+
+        /// Partially defined slice
+        auto row = slice[3];
+        auto col = slice[0..$, 1];
+    }
+
+    static if (doUnittest)
+    pure nothrow unittest
+    {
+        auto slice = new int[15].sliced!(No.replaceArrayWithPointer)(5, 3);
+
+        /// Fully defined slice
+        assert(slice[] == slice);
+        auto sublice = slice[0..$-2, 1..$];
+
+        /// Partially defined slice
+        auto row = slice[3];
+        auto col = slice[0..$, 1];
+    }
+
+    static if (isMutable!DeepElemType && PureN == N)
+    {
+        private void opIndexAssignImpl(string op, size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices)
+            if (isFullPureSlice!Slices
+                && RN <= ReturnType!(opIndex!Slices).N)
+        {
+            auto slice = this[slices];
+            assert(slice._lengths[$ - RN .. $] == value._lengths, __FUNCTION__ ~ ": argument must have the corresponding shape.");
+            version(none) //future optimization
+            static if ((isPointer!Range || isDynamicArray!Range) && (isPointer!RRange || isDynamicArray!RRange))
+            {
+                enum d = slice.N - value.N;
+                foreach_reverse (i; Iota!(0, value.N))
+                    if (slice._lengths[i + d] == 1)
+                    {
+                        if (value._lengths[i] == 1)
+                        {
+                            static if (i != value.N - 1)
+                            {
+                                import std.experimental.ndslice.iteration: swapped;
+                                slice = slice.swapped(i + d, slice.N - 1);
+                                value = value.swapped(i    , value.N - 1);
+                            }
+                            goto L1;
+                        }
+                        else
+                        {
+                            goto L2;
+                        }
+                    }
+                L1:
+                _indexAssign!(true, op)(slice, value);
+                return;
+            }
+            L2:
+            _indexAssign!(false, op)(slice, value);
+        }
+
+        private void opIndexAssignImpl(string op, T, Slices...)(T[] value, Slices slices)
+            if (isFullPureSlice!Slices
+                && !isDynamicArray!DeepElemType
+                && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N)
+        {
+            auto slice = this[slices];
+            version(none) //future optimization
+            static if (isPointer!Range || isDynamicArray!Range)
+            {
+                if (slice._lengths[$-1] == 1)
+                {
+                    _indexAssign!(true, op)(slice, value);
+                    return;
+                }
+            }
+            _indexAssign!(false, op)(slice, value);
+        }
+
+        private void opIndexAssignImpl(string op, T, Slices...)(T value, Slices slices)
+            if (isFullPureSlice!Slices
+                && (!isDynamicArray!T || isDynamicArray!DeepElemType)
+                && !is(T : Slice!(RN, RRange), size_t RN, RRange))
+        {
+            auto slice = this[slices];
+            version(none) //future optimization
+            static if (isPointer!Range || isDynamicArray!Range)
+            {
+                if (slice._lengths[$-1] == 1)
+                {
+                    _indexAssign!(true, op)(slice, value);
+                    return;
+                }
+            }
+            _indexAssign!(false, op)(slice, value);
+        }
+
+        /++
+        Assignment of a value of `Slice` type to a $(BLUE fully defined slice).
+        +/
+        void opIndexAssign(size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices)
+            if (isFullPureSlice!Slices
+                && RN <= ReturnType!(opIndex!Slices).N)
+        {
+            opIndexAssignImpl!""(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+            auto b = [1, 2, 3, 4].sliced(2, 2);
+
+            a[0..$, 0..$-1] = b;
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] = b[0];
+            assert(a == [[1, 2, 0], [1, 2, 0]]);
+
+            a[1, 0..$-1] = b[1];
+            assert(a[1] == [3, 4, 0]);
+
+            a[1, 0..$-1][] = b[0];
+            assert(a[1] == [1, 2, 0]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+            auto b = [1, 2, 3, 4].sliced(2, 2);
+
+            a[0..$, 0..$-1] = b;
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] = b[0];
+            assert(a == [[1, 2, 0], [1, 2, 0]]);
+
+            a[1, 0..$-1] = b[1];
+            assert(a[1] == [3, 4, 0]);
+
+            a[1, 0..$-1][] = b[0];
+            assert(a[1] == [1, 2, 0]);
+        }
+
+        /++
+        Assignment of a regular multidimensional array to a $(BLUE fully defined slice).
+        +/
+        void opIndexAssign(T, Slices...)(T[] value, Slices slices)
+            if (isFullPureSlice!Slices
+                && !isDynamicArray!DeepElemType
+                && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N)
+        {
+            opIndexAssignImpl!""(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+            auto b = [[1, 2], [3, 4]];
+
+            a[] = [[1, 2, 3], [4, 5, 6]];
+            assert(a == [[1, 2, 3], [4, 5, 6]]);
+
+            a[0..$, 0..$-1] = [[1, 2], [3, 4]];
+            assert(a == [[1, 2, 3], [3, 4, 6]]);
+
+            a[0..$, 0..$-1] = [1, 2];
+            assert(a == [[1, 2, 3], [1, 2, 6]]);
+
+            a[1, 0..$-1] = [3, 4];
+            assert(a[1] == [3, 4, 6]);
+
+            a[1, 0..$-1][] = [3, 4];
+            assert(a[1] == [3, 4, 6]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+            auto b = [[1, 2], [3, 4]];
+
+            a[] = [[1, 2, 3], [4, 5, 6]];
+            assert(a == [[1, 2, 3], [4, 5, 6]]);
+
+            a[0..$, 0..$-1] = [[1, 2], [3, 4]];
+            assert(a == [[1, 2, 3], [3, 4, 6]]);
+
+            a[0..$, 0..$-1] = [1, 2];
+            assert(a == [[1, 2, 3], [1, 2, 6]]);
+
+            a[1, 0..$-1] = [3, 4];
+            assert(a[1] == [3, 4, 6]);
+
+            a[1, 0..$-1][] = [3, 4];
+            assert(a[1] == [3, 4, 6]);
+        }
+
+        /++
+        Assignment of a value (e.g. a number) to a $(BLUE fully defined slice).
+        +/
+        void opIndexAssign(T, Slices...)(T value, Slices slices)
+            if (isFullPureSlice!Slices
+                && (!isDynamicArray!T || isDynamicArray!DeepElemType)
+                && !is(T : Slice!(RN, RRange), size_t RN, RRange))
+        {
+            opIndexAssignImpl!""(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            a[] = 9;
+            assert(a == [[9, 9, 9], [9, 9, 9]]);
+
+            a[0..$, 0..$-1] = 1;
+            assert(a == [[1, 1, 9], [1, 1, 9]]);
+
+            a[0..$, 0..$-1] = 2;
+            assert(a == [[2, 2, 9], [2, 2, 9]]);
+
+            a[1, 0..$-1] = 3;
+            assert(a[1] == [3, 3, 9]);
+
+            a[1, 0..$-1] = 4;
+            assert(a[1] == [4, 4, 9]);
+
+            a[1, 0..$-1][] = 5;
+            assert(a[1] == [5, 5, 9]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            a[] = 9;
+            assert(a == [[9, 9, 9], [9, 9, 9]]);
+
+            a[0..$, 0..$-1] = 1;
+            assert(a == [[1, 1, 9], [1, 1, 9]]);
+
+            a[0..$, 0..$-1] = 2;
+            assert(a == [[2, 2, 9], [2, 2, 9]]);
+
+            a[1, 0..$-1] = 3;
+            assert(a[1] == [3, 3, 9]);
+
+            a[1, 0..$-1] = 4;
+            assert(a[1] == [4, 4, 9]);
+
+            a[1, 0..$-1][] = 5;
+            assert(a[1] == [5, 5, 9]);
+        }
+
+        /++
+        Assignment of a value (e.g. a number) to a $(BLUE fully defined index).
+        +/
+        auto ref opIndexAssign(T, Indexes...)(T value, Indexes _indexes)
+            if (isFullPureIndex!Indexes)
+        {
+            return _ptr[indexStride(_indexes)] = value;
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            a[1, 2] = 3;
+            assert(a[1, 2] == 3);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            a[1, 2] = 3;
+            assert(a[1, 2] == 3);
+        }
+
+        /++
+        Op Assignment `op=` of a value (e.g. a number) to a $(BLUE fully defined index).
+        +/
+        auto ref opIndexOpAssign(string op, T, Indexes...)(T value, Indexes _indexes)
+            if (isFullPureIndex!Indexes)
+        {
+            mixin (`return _ptr[indexStride(_indexes)] ` ~ op ~ `= value;`);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            a[1, 2] += 3;
+            assert(a[1, 2] == 3);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            a[1, 2] += 3;
+            assert(a[1, 2] == 3);
+        }
+
+        /++
+        Op Assignment `op=` of a value of `Slice` type to a $(BLUE fully defined slice).
+        +/
+        void opIndexOpAssign(string op, size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices)
+            if (isFullPureSlice!Slices
+                && RN <= ReturnType!(opIndex!Slices).N)
+        {
+            opIndexAssignImpl!op(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+            auto b = [1, 2, 3, 4].sliced(2, 2);
+
+            a[0..$, 0..$-1] += b;
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] += b[0];
+            assert(a == [[2, 4, 0], [4, 6, 0]]);
+
+            a[1, 0..$-1] += b[1];
+            assert(a[1] == [7, 10, 0]);
+
+            a[1, 0..$-1][] += b[0];
+            assert(a[1] == [8, 12, 0]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+            auto b = [1, 2, 3, 4].sliced(2, 2);
+
+            a[0..$, 0..$-1] += b;
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] += b[0];
+            assert(a == [[2, 4, 0], [4, 6, 0]]);
+
+            a[1, 0..$-1] += b[1];
+            assert(a[1] == [7, 10, 0]);
+
+            a[1, 0..$-1][] += b[0];
+            assert(a[1] == [8, 12, 0]);
+        }
+
+        /++
+        Op Assignment `op=` of a regular multidimensional array to a $(BLUE fully defined slice).
+        +/
+        void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices)
+            if (isFullPureSlice!Slices
+                && !isDynamicArray!DeepElemType
+                && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N)
+        {
+            opIndexAssignImpl!op(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            a[0..$, 0..$-1] += [[1, 2], [3, 4]];
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] += [1, 2];
+            assert(a == [[2, 4, 0], [4, 6, 0]]);
+
+            a[1, 0..$-1] += [3, 4];
+            assert(a[1] == [7, 10, 0]);
+
+            a[1, 0..$-1][] += [1, 2];
+            assert(a[1] == [8, 12, 0]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            a[0..$, 0..$-1] += [[1, 2], [3, 4]];
+            assert(a == [[1, 2, 0], [3, 4, 0]]);
+
+            a[0..$, 0..$-1] += [1, 2];
+            assert(a == [[2, 4, 0], [4, 6, 0]]);
+
+            a[1, 0..$-1] += [3, 4];
+            assert(a[1] == [7, 10, 0]);
+
+            a[1, 0..$-1][] += [1, 2];
+            assert(a[1] == [8, 12, 0]);
+        }
+
+        /++
+        Op Assignment `op=` of a value (e.g. a number) to a $(BLUE fully defined slice).
+        +/
+        void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices)
+            if (isFullPureSlice!Slices
+                && (!isDynamicArray!T || isDynamicArray!DeepElemType)
+                && !is(T : Slice!(RN, RRange), size_t RN, RRange))
+        {
+            opIndexAssignImpl!op(value, slices);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            a[] += 1;
+            assert(a == [[1, 1, 1], [1, 1, 1]]);
+
+            a[0..$, 0..$-1] += 2;
+            assert(a == [[3, 3, 1], [3, 3, 1]]);
+
+            a[1, 0..$-1] += 3;
+            assert(a[1] == [6, 6, 1]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            a[] += 1;
+            assert(a == [[1, 1, 1], [1, 1, 1]]);
+
+            a[0..$, 0..$-1] += 2;
+            assert(a == [[3, 3, 1], [3, 3, 1]]);
+
+            a[1, 0..$-1] += 3;
+            assert(a[1] == [6, 6, 1]);
+        }
+
+        /++
+        Increment `++` and Decrement `--` operators for a $(BLUE fully defined index).
+        +/
+        auto ref opIndexUnary(string op, Indexes...)(Indexes _indexes)
+            if (isFullPureIndex!Indexes && (op == `++` || op == `--`))
+        {
+            mixin (`return ` ~ op ~ `_ptr[indexStride(_indexes)];`);
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            ++a[1, 2];
+            assert(a[1, 2] == 1);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            ++a[1, 2];
+            assert(a[1, 2] == 1);
+        }
+
+        /++
+        Increment `++` and Decrement `--` operators for a $(BLUE fully defined slice).
+        +/
+        void opIndexUnary(string op, Slices...)(Slices slices)
+            if (isFullPureSlice!Slices && (op == `++` || op == `--`))
+        {
+            auto sl = this[slices];
+            static if (sl.N == 1)
+            {
+                for (; sl.length; sl.popFront)
+                {
+                    mixin (op ~ `sl.front;`);
+                }
+            }
+            else
+            {
+                foreach (v; sl)
+                {
+                    mixin (op ~ `v[];`);
+                }
+            }
+        }
+
+        static if (doUnittest)
+        ///
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced(2, 3);
+
+            ++a[];
+            assert(a == [[1, 1, 1], [1, 1, 1]]);
+
+            --a[1, 0..$-1];
+            assert(a[1] == [0, 0, 1]);
+        }
+
+        static if (doUnittest)
+        pure nothrow unittest
+        {
+            auto a = new int[6].sliced!(No.replaceArrayWithPointer)(2, 3);
+
+            ++a[];
+            assert(a == [[1, 1, 1], [1, 1, 1]]);
+
+            --a[1, 0..$-1];
+            assert(a[1] == [0, 0, 1]);
+        }
+    }
+}
+
+
+/++
+Slicing, indexing, and arithmetic operations.
++/
+pure nothrow unittest
+{
+    import std.array: array;
+    import std.range: iota;
+    import std.experimental.ndslice.iteration: transposed;
+
+    auto tensor = 60.iota.array.sliced(3, 4, 5);
+
+    assert(tensor[1, 2] == tensor[1][2]);
+    assert(tensor[1, 2, 3] == tensor[1][2][3]);
+
+    assert( tensor[0..$, 0..$, 4] == tensor.transposed!2[4]);
+    assert(&tensor[0..$, 0..$, 4][1, 2] is &tensor[1, 2, 4]);
+
+    tensor[1, 2, 3]++; //`opIndex` returns value by reference.
+    --tensor[1, 2, 3]; //`opUnary`
+
+    ++tensor[];
+    tensor[] -= 1;
+
+    // `opIndexAssing` accepts only fully defined indexes and slices.
+    // Use an additional empty slice `[]`.
+    static assert(!__traits(compiles), tensor[0 .. 2] *= 2);
+
+    tensor[0 .. 2][] *= 2;          //OK, empty slice
+    tensor[0 .. 2, 3, 0..$] /= 2; //OK, 3 index or slice positions are defined.
+
+    //fully defined index may be replaced by a static array
+    size_t[3] index = [1, 2, 3];
+    assert(tensor[index] == tensor[1, 2, 3]);
+}
+
+/++
+Operations with rvalue slices.
++/
+pure nothrow unittest
+{
+    import std.experimental.ndslice.iteration: transposed, everted;
+
+    auto tensor = new int[60].sliced(3, 4, 5);
+    auto matrix = new int[12].sliced(3, 4);
+    auto vector = new int[ 3].sliced(3);
+
+    foreach (i; 0..3)
+        vector[i] = i;
+
+    // fills matrix columns
+    matrix.transposed[] = vector;
+
+    // fills tensor with vector
+    // transposed tensor shape is (4, 5, 3)
+    //            vector shape is (      3)
+    tensor.transposed!(1, 2)[] = vector;
+
+
+    // transposed tensor shape is (5, 3, 4)
+    //            matrix shape is (   3, 4)
+    tensor.transposed!2[] += matrix;
+
+    // transposed tensor shape is (5, 4, 3)
+    // transposed matrix shape is (   4, 3)
+    tensor.everted[] ^= matrix.transposed; // XOR
+}
+
+/++
+Creating a slice from text.
+See also $(LINK2 std_format.html, std.format).
++/
+unittest
+{
+    import std.algorithm,  std.conv, std.exception, std.format,
+        std.functional, std.string, std.range;
+
+    Slice!(2, int*) toMatrix(string str)
+    {
+        string[][] data = str.lineSplitter.filter!(not!empty).map!split.array;
+
+        size_t rows    = data   .length.enforce("empty input");
+        size_t columns = data[0].length.enforce("empty first row");
+
+        data.each!(a => enforce(a.length == columns, "rows have different lengths"));
+
+        auto slice = new int[rows * columns].sliced(rows, columns);
+        foreach (i, line; data)
+            foreach (j, num; line)
+                slice[i, j] = num.to!int;
+        return slice;
+    }
+
+    auto input = "\r1 2  3\r\n 4 5 6\n";
+
+    auto matrix = toMatrix(input);
+    assert(matrix == [[1, 2, 3], [4, 5, 6]]);
+
+    // back to text
+    auto text2 = format("%(%(%s %)\n%)\n", matrix);
+    assert(text2 == "1 2 3\n4 5 6\n");
+}
+
+// Slicing
+@safe @nogc pure nothrow unittest
+{
+    import std.range: iota;
+    auto a = 240000.iota.sliced(10, 20, 30, 40);
+    auto b = a[0..$, 10, 4 .. 27, 4];
+    auto c = b[2 .. 9, 5 .. 10];
+    auto d = b[3..$, $-2];
+    assert(b[4, 17] == a[4, 10, 21, 4]);
+    assert(c[1, 2] == a[3, 10, 11, 4]);
+    assert(d[3] == a[6, 10, 25, 4]);
+}
+
+// Operator overloading. # 1
+pure nothrow unittest
+{
+    import std.range: iota;
+    import std.array: array;
+    auto fun(ref int x) { x *= 3; }
+
+    auto tensor = 720
+        .iota
+        .array
+        .sliced(8, 9, 10);
+
+    ++tensor[];
+    fun(tensor[0, 0, 0]);
+
+    assert(tensor[0, 0, 0] == 3);
+
+    tensor[0, 0, 0] *= 4;
+    tensor[0, 0, 0]--;
+    assert(tensor[0, 0, 0] == 11);
+}
+
+// Operator overloading. # 2
+pure nothrow unittest
+{
+    import std.algorithm.iteration: map;
+    import std.array: array;
+    import std.bigint;
+    import std.range: iota;
+
+    auto matrix = 72
+        .iota
+        .map!(i => BigInt(i))
+        .array
+        .sliced(8, 9);
+
+    matrix[3 .. 6, 2] += 100;
+    foreach (i; 0 .. 8)
+        foreach (j; 0 .. 9)
+            if (i >= 3 && i < 6 && j == 2)
+                assert(matrix[i, j] >= 100);
+            else
+                assert(matrix[i, j] < 100);
+}
+
+// Operator overloading. # 3
+pure nothrow unittest
+{
+    import std.algorithm.iteration: map;
+    import std.array: array;
+    import std.range: iota;
+
+    auto matrix = 72
+        .iota
+        .array
+        .sliced(8, 9);
+
+    matrix[] = matrix;
+    matrix[] += matrix;
+    assert(matrix[2, 3] == (2 * 9 + 3) * 2);
+
+    auto vec = iota(100, 109).sliced(9);
+    matrix[] = vec;
+    foreach (v; matrix)
+        assert(v == vec);
+
+    matrix[] += vec;
+    foreach (vector; matrix)
+        foreach (elem; vector)
+            assert(elem >= 200);
+}
+
+// Type deduction
+unittest
+{
+    // Arrays
+    foreach (T; AliasSeq!(int, const int, immutable int))
+        static assert(is(typeof((T[]).init.sliced(3, 4)) == Slice!(2, T*)));
+
+    // Container Array
+    import std.container.array;
+    Array!int ar;
+    static assert(is(typeof(ar[].sliced(3, 4)) == Slice!(2, typeof(ar[]))));
+
+    // Implicit conversion of a range to its unqualified type.
+    import std.range: iota;
+    auto      i0 = 60.iota;
+    const     i1 = 60.iota;
+    immutable i2 = 60.iota;
+    alias S = Slice!(3, typeof(iota(0)));
+    foreach (i; AliasSeq!(i0, i1, i2))
+        static assert(is(typeof(i.sliced(3, 4, 5)) == S));
+}
+
+// Test for map #1
+unittest
+{
+    import std.algorithm.iteration: map;
+    import std.range.primitives;
+    auto slice = [1, 2, 3, 4].sliced(2, 2);
+
+    auto r = slice.map!(a => a.map!(a => a * 6));
+    assert(r.front.front == 6);
+    assert(r.front.back == 12);
+    assert(r.back.front == 18);
+    assert(r.back.back == 24);
+    assert(r[0][0] ==  6);
+    assert(r[0][1] == 12);
+    assert(r[1][0] == 18);
+    assert(r[1][1] == 24);
+    static assert(hasSlicing!(typeof(r)));
+    static assert(isForwardRange!(typeof(r)));
+    static assert(isRandomAccessRange!(typeof(r)));
+
+}
+
+// Test for map #2
+unittest
+{
+    import std.algorithm.iteration: map;
+    import std.range.primitives;
+    auto data = [1, 2, 3, 4].map!(a => a * 2);
+    static assert(hasSlicing!(typeof(data)));
+    static assert(isForwardRange!(typeof(data)));
+    static assert(isRandomAccessRange!(typeof(data)));
+    auto slice = data.sliced(2, 2);
+    static assert(hasSlicing!(typeof(slice)));
+    static assert(isForwardRange!(typeof(slice)));
+    static assert(isRandomAccessRange!(typeof(slice)));
+    auto r = slice.map!(a => a.map!(a => a * 3));
+    static assert(hasSlicing!(typeof(r)));
+    static assert(isForwardRange!(typeof(r)));
+    static assert(isRandomAccessRange!(typeof(r)));
+    assert(r.front.front == 6);
+    assert(r.front.back == 12);
+    assert(r.back.front == 18);
+    assert(r.back.back == 24);
+    assert(r[0][0] ==  6);
+    assert(r[0][1] == 12);
+    assert(r[1][0] == 18);
+    assert(r[1][1] == 24);
+}
+
+private bool opEqualsImpl
+    (size_t NL, RangeL, size_t NR, RangeR)(
+    auto ref Slice!(NL, RangeL) ls,
+    auto ref Slice!(NR, RangeR) rs)
+in
+{
+    assert(ls._lengths == rs._lengths);
+}
+body
+{
+    foreach (i; 0 .. ls.length)
+    {
+        static if (Slice!(NL, RangeL).PureN == 1)
+        {
+            if (ls[i] != rs[i])
+                return false;
+        }
+        else
+        {
+            if (!opEqualsImpl(ls[i], rs[i]))
+                return false;
+        }
+    }
+    return true;
+}
+
+private struct PtrShell(Range)
+{
+    sizediff_t _shift;
+    Range _range;
+
+    enum hasAccessByRef = isPointer!Range ||
+        __traits(compiles, { auto a = &(_range[0]); } );
+
+    void opOpAssign(string op)(sizediff_t shift)
+        if (op == `+` || op == `-`)
+    {
+        pragma(inline, true);
+        mixin (`_shift ` ~ op ~ `= shift;`);
+    }
+
+    auto opBinary(string op)(sizediff_t shift)
+        if (op == `+` || op == `-`)
+    {
+        mixin (`return typeof(this)(_shift ` ~ op ~ ` shift, _range);`);
+    }
+
+    auto ref opIndex(sizediff_t index)
+    in
+    {
+        import std.range.primitives: hasLength;
+        assert(_shift + index >= 0);
+        static if (hasLength!Range)
+            assert(_shift + index <= _range.length);
+    }
+    body
+    {
+        return _range[_shift + index];
+    }
+
+    static if (!hasAccessByRef)
+    {
+        auto ref opIndexAssign(T)(T value, sizediff_t index)
+        in
+        {
+            import std.range.primitives: hasLength;
+            assert(_shift + index >= 0);
+            static if (hasLength!Range)
+                assert(_shift + index <= _range.length);
+        }
+        body
+        {
+            return _range[_shift + index] = value;
+        }
+
+        auto ref opIndexOpAssign(string op, T)(T value, sizediff_t index)
+        in
+        {
+            import std.range.primitives: hasLength;
+            assert(_shift + index >= 0);
+            static if (hasLength!Range)
+                assert(_shift + index <= _range.length);
+        }
+        body
+        {
+            mixin (`return _range[_shift + index] ` ~ op ~ `= value;`);
+        }
+
+        auto ref opIndexUnary(string op)(sizediff_t index)
+        in
+        {
+            import std.range.primitives: hasLength;
+            assert(_shift + index >= 0);
+            static if (hasLength!Range)
+                assert(_shift + index <= _range.length);
+        }
+        body
+        {
+            mixin (`return ` ~ op ~ `_range[_shift + index];`);
+        }
+    }
+
+    static if (canSave!Range)
+    auto save() @property
+    {
+        static if (isDynamicArray!Range)
+            return typeof(this)(_shift, _range);
+        else
+            return typeof(this)(_shift, _range.save);
+    }
+}
+
+private auto ptrShell(Range)(Range range, sizediff_t shift = 0)
+{
+    return PtrShell!Range(shift, range);
+}
+
+@safe pure nothrow unittest
+{
+    import std.internal.test.dummyrange;
+    foreach (RB; AliasSeq!(ReturnBy.Reference, ReturnBy.Value))
+    {
+        DummyRange!(RB, Length.Yes, RangeType.Random) range;
+        range.reinit;
+        assert(range.length >= 10);
+        auto ptr = range.ptrShell;
+        assert(ptr[0] == range[0]);
+        auto save0 = range[0];
+        ptr[0] += 10;
+        ++ptr[0];
+        assert(ptr[0] == save0 + 11);
+        (ptr + 5)[2] = 333;
+        assert(range[7] == 333);
+    }
+}
+
+pure nothrow unittest
+{
+    import std.internal.test.dummyrange;
+    foreach (RB; AliasSeq!(ReturnBy.Reference, ReturnBy.Value))
+    {
+        DummyRange!(RB, Length.Yes, RangeType.Random) range;
+        range.reinit;
+        assert(range.length >= 10);
+        auto slice = range.sliced(10);
+        assert(slice[0] == range[0]);
+        auto save0 = range[0];
+        slice[0] += 10;
+        ++slice[0];
+        assert(slice[0] == save0 + 11);
+        slice[5 .. $][2] = 333;
+        assert(range[7] == 333);
+    }
+}
+
+private enum isSlicePointer(T) = isPointer!T || is(T : PtrShell!R, R);
+
+private struct LikePtr {}
+
+package template hasPtrBehavior(T)
+{
+    static if (isPointer!T)
+        enum hasPtrBehavior = true;
+    else
+    static if (!isAggregateType!T)
+        enum hasPtrBehavior = false;
+    else
+        enum hasPtrBehavior = hasUDA!(T, LikePtr);
+}
+
+private template PtrTuple(Names...)
+{
+    @LikePtr struct PtrTuple(Ptrs...)
+        if (allSatisfy!(isSlicePointer, Ptrs) && Ptrs.length == Names.length)
+    {
+        Ptrs ptrs;
+
+        static if (allSatisfy!(canSave, Ptrs))
+        auto save() @property
+        {
+            static if (anySatisfy!(hasElaborateAssign, Ptrs))
+                PtrTuple p;
+            else
+                PtrTuple p = void;
+            foreach (i, ref ptr; ptrs)
+                static if (isPointer!(Ptrs[i]))
+                    p.ptrs[i] = ptr;
+                else
+                    p.ptrs[i] = ptr.save;
+
+            return p;
+        }
+
+        void opOpAssign(string op)(sizediff_t shift)
+            if (op == `+` || op == `-`)
+        {
+            foreach (ref ptr; ptrs)
+                mixin (`ptr ` ~ op ~ `= shift;`);
+        }
+
+        auto opBinary(string op)(sizediff_t shift)
+            if (op == `+` || op == `-`)
+        {
+            auto ret = this.ptrs;
+            ret.opOpAssign!op(shift);
+            return ret;
+        }
+
+        public struct Index
+        {
+            Ptrs _ptrs__;
+            mixin (PtrTupleFrontMembers!Names);
+        }
+
+        auto opIndex(sizediff_t index)
+        {
+            auto p = ptrs;
+            foreach (ref ptr; p)
+                ptr += index;
+            return Index(p);
+        }
+
+        auto front() @property
+        {
+            return Index(ptrs);
+        }
+    }
+}
+
+pure nothrow unittest
+{
+    auto a = new int[20], b = new int[20];
+    import std.stdio;
+    alias T = PtrTuple!("a", "b");
+    alias S = T!(int*, int*);
+    auto t = S(a.ptr, b.ptr);
+    t[4].a++;
+    auto r = t[4];
+    r.b = r.a * 2;
+    assert(b[4] == 2);
+    t.front.a++;
+    r = t.front;
+    r.b = r.a * 2;
+    assert(b[0] == 2);
+}
+
+private template PtrTupleFrontMembers(Names...)
+    if (Names.length <= 32)
+{
+    static if (Names.length)
+    {
+        alias Top = Names[0..$-1];
+        enum int m = Top.length;
+        enum PtrTupleFrontMembers = PtrTupleFrontMembers!Top
+        ~ "
+        @property auto ref " ~ Names[$-1] ~ "() {
+            static if (__traits(compiles,{ auto _f = _ptrs__[" ~ m.stringof ~ "].front; }))
+                return _ptrs__[" ~ m.stringof ~ "].front;
+            else
+                return _ptrs__[" ~ m.stringof ~ "][0];
+        }
+        ";
+    }
+    else
+    {
+        enum PtrTupleFrontMembers = "";
+    }
+}
+
+
+private template PrepareRangeType(Range)
+{
+    static if (isPointer!Range)
+        alias PrepareRangeType = Range;
+    else
+        alias PrepareRangeType = PtrShell!Range;
+}
+
+private enum bool isType(alias T) = false;
+
+private enum bool isType(T) = true;
+
+private enum isStringValue(alias T) = is(typeof(T) : string);
+
+private void _indexAssign(bool lastStrideEquals1, string op, size_t N, size_t RN, Range, RRange)(Slice!(N, Range) slice, Slice!(RN, RRange) value)
+    if (N >= RN)
+{
+    static if (N == 1)
+    {
+        static if (lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range) && (isPointer!RRange || isDynamicArray!RRange))
+        {
+            static if (isPointer!Range)
+                auto l = slice._ptr;
+            else
+                auto l = slice._ptr._range[slice._ptr._shift .. slice._ptr._shift + slice._lengths[0]];
+            static if (isPointer!RRange)
+                auto r = value._ptr;
+            else
+                auto r = value._ptr._range[value._ptr._shift .. value._ptr._shift + value._lengths[0]];
+            auto len = slice._lengths[0];
+            for (size_t i; i < len; i++)
+            {
+                mixin("l[i]" ~ op ~ "= r[i];");
+            }
+        }
+        else
+        {
+            while (slice._lengths[0])
+            {
+                mixin("slice.front " ~ op ~ "= value.front;");
+                slice.popFront;
+                value.popFront;
+            }
+        }
+    }
+    else
+    static if (N == RN)
+    {
+        while (slice._lengths[0])
+        {
+            _indexAssign!(lastStrideEquals1, op)(slice.front, value.front);
+            slice.popFront;
+            value.popFront;
+        }
+    }
+    else
+    {
+        while (slice._lengths[0])
+        {
+            _indexAssign!(lastStrideEquals1, op)(slice.front, value);
+            slice.popFront;
+        }
+    }
+}
+
+private void _indexAssign(bool lastStrideEquals1, string op, size_t N, Range, T)(Slice!(N, Range) slice, T[] value)
+    if (DynamicArrayDimensionsCount!(T[]) <= N)
+{
+    assert(slice.length == value.length, __FUNCTION__ ~ ": argument must have the same length.");
+    static if (N == 1)
+    {
+        static if (lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range))
+        {
+            static if (isPointer!Range)
+                auto l = slice._ptr;
+            else
+                auto l = slice._ptr._range[slice._ptr._shift .. slice._ptr._shift + slice._lengths[0]];
+            auto r = value;
+            auto len = slice._lengths[0];
+            for (size_t i; i < len; i++)
+            {
+                mixin("l[i]" ~ op ~ "= r[i];");
+            }
+        }
+        else
+        {
+            while (slice._lengths[0])
+            {
+                mixin("slice.front " ~ op ~ "= value[0];");
+                slice.popFront;
+                value = value[1..$];
+            }
+        }
+    }
+    else
+    static if (N == DynamicArrayDimensionsCount!(T[]))
+    {
+        while (slice._lengths[0])
+        {
+            _indexAssign!(lastStrideEquals1, op)(slice.front, value[0]);
+            slice.popFront;
+            value = value[1 .. $];
+        }
+    }
+    else
+    {
+        while (slice._lengths[0])
+        {
+            _indexAssign!(lastStrideEquals1, op)(slice.front, value);
+            slice.popFront;
+        }
+    }
+}
+
+private void _indexAssign(bool lastStrideEquals1, string op, size_t N, Range, T)(Slice!(N, Range) slice, T value)
+    if ((!isDynamicArray!T || isDynamicArray!(Slice!(N, Range).DeepElemType))
+                && !is(T : Slice!(RN, RRange), size_t RN, RRange))
+{
+    static if (N == 1)
+    {
+        static if (lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range))
+        {
+            static if (isPointer!Range)
+                auto l = slice._ptr;
+            else
+                auto l = slice._ptr._range[slice._ptr._shift .. $];
+            auto len = slice._lengths[0];
+            for (size_t i; i < len; i++)
+            {
+                mixin("l[i]" ~ op ~ "= value;");
+            }
+        }
+        else
+        {
+            while (slice._lengths[0])
+            {
+                mixin("slice.front " ~ op ~ "= value;");
+                slice.popFront;
+            }
+        }
+    }
+    else
+    {
+        while (slice._lengths[0])
+        {
+            _indexAssign!(lastStrideEquals1, op)(slice.front, value);
+            slice.popFront;
+        }
+    }
+}
diff --git a/libphobos/src/std/experimental/note.md b/libphobos/src/std/experimental/note.md
new file mode 100644
index 00000000000..377327030ba
--- /dev/null
+++ b/libphobos/src/std/experimental/note.md
@@ -0,0 +1 @@
+This is intended for experimental modules.
diff --git a/libphobos/src/std/file.d b/libphobos/src/std/file.d
new file mode 100644
index 00000000000..5b68aff34b8
--- /dev/null
+++ b/libphobos/src/std/file.d
@@ -0,0 +1,3943 @@
+// Written in the D programming language.
+
+/**
+Utilities for manipulating files and scanning directories. Functions
+in this module handle files as a unit, e.g., read or write one _file
+at a time. For opening files and manipulating them via handles refer
+to module $(LINK2 std_stdio.html,$(D std.stdio)).
+
+Macros:
+WIKI = Phobos/StdFile
+
+Copyright: Copyright Digital Mars 2007 - 2011.
+See_Also:  The $(WEB ddili.org/ders/d.en/files.html, official tutorial) for an
+introduction to working with files in D, module
+$(LINK2 std_stdio.html,$(D std.stdio)) for opening files and manipulating them
+via handles, and module $(LINK2 std_path.html,$(D std.path)) for manipulating
+path strings.
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB digitalmars.com, Walter Bright),
+           $(WEB erdani.org, Andrei Alexandrescu),
+           Jonathan M Davis
+Source:    $(PHOBOSSRC std/_file.d)
+ */
+module std.file;
+
+import core.stdc.stdlib, core.stdc.string, core.stdc.errno;
+
+import std.conv;
+import std.datetime;
+import std.exception;
+import std.meta;
+import std.path;
+import std.range.primitives;
+import std.traits;
+import std.typecons;
+import std.internal.cstring;
+
+version (Windows)
+{
+    import core.sys.windows.windows, std.windows.syserror;
+}
+else version (Posix)
+{
+    import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
+        core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
+}
+else
+    static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
+
+// Character type used for operating system filesystem APIs
+version (Windows)
+{
+    private alias FSChar = wchar;
+}
+else version (Posix)
+{
+    private alias FSChar = char;
+}
+else
+    static assert(0);
+
+package @property string deleteme() @safe
+{
+    import std.process : thisProcessID;
+    static _deleteme = "deleteme.dmd.unittest.pid";
+    static _first = true;
+
+    if(_first)
+    {
+        _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID);
+        _first = false;
+    }
+
+    return _deleteme;
+}
+
+version (unittest) private struct TestAliasedString
+{
+    string get() @safe @nogc pure nothrow { return _s; }
+    alias get this;
+    @disable this(this);
+    string _s;
+}
+
+version(Android)
+{
+    package enum system_directory = "/system/etc";
+    package enum system_file      = "/system/etc/hosts";
+}
+else version(Posix)
+{
+    package enum system_directory = "/usr/include";
+    package enum system_file      = "/usr/include/assert.h";
+}
+
+
+/++
+    Exception thrown for file I/O errors.
+ +/
+class FileException : Exception
+{
+    /++
+        OS error code.
+     +/
+    immutable uint errno;
+
+    /++
+        Constructor which takes an error message.
+
+        Params:
+            name = Name of file for which the error occurred.
+            msg  = Message describing the error.
+            file = The file where the error occurred.
+            line = The line where the error occurred.
+     +/
+    this(in char[] name, in char[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
+    {
+        if(msg.empty)
+            super(name.idup, file, line);
+        else
+            super(text(name, ": ", msg), file, line);
+
+        errno = 0;
+    }
+
+    /++
+        Constructor which takes the error number ($(LUCKY GetLastError)
+        in Windows, $(D_PARAM errno) in Posix).
+
+        Params:
+            name  = Name of file for which the error occurred.
+            errno = The error number.
+            file  = The file where the error occurred.
+                    Defaults to $(D __FILE__).
+            line  = The line where the error occurred.
+                    Defaults to $(D __LINE__).
+     +/
+    version(Windows) this(in char[] name,
+                          uint errno = .GetLastError(),
+                          string file = __FILE__,
+                          size_t line = __LINE__) @safe
+    {
+        this(name, sysErrorString(errno), file, line);
+        this.errno = errno;
+    }
+    else version(Posix) this(in char[] name,
+                             uint errno = .errno,
+                             string file = __FILE__,
+                             size_t line = __LINE__) @trusted
+    {
+        auto s = strerror(errno);
+        this(name, to!string(s), file, line);
+        this.errno = errno;
+    }
+}
+
+private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__)
+{
+    if (condition)
+        return condition;
+    version (Windows)
+    {
+        throw new FileException(name, .GetLastError(), file, line);
+    }
+    else version (Posix)
+    {
+        throw new FileException(name, .errno, file, line);
+    }
+}
+
+version (Windows)
+@trusted
+private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, string file = __FILE__, size_t line = __LINE__)
+{
+    if (condition)
+        return condition;
+    if (!name)
+    {
+        import core.stdc.wchar_ : wcslen;
+        import std.conv : to;
+
+        auto len = wcslen(namez);
+        name = to!string(namez[0 .. len]);
+    }
+    throw new FileException(name, .GetLastError(), file, line);
+}
+
+version (Posix)
+@trusted
+private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, string file = __FILE__, size_t line = __LINE__)
+{
+    if (condition)
+        return condition;
+    if (!name)
+    {
+        import core.stdc.string : strlen;
+
+        auto len = strlen(namez);
+        name = namez[0 .. len].idup;
+    }
+    throw new FileException(name, .errno, file, line);
+}
+
+/* **********************************
+ * Basic File operations.
+ */
+
+/********************************************
+Read entire contents of file $(D name) and returns it as an untyped
+array. If the file size is larger than $(D upTo), only $(D upTo)
+bytes are read.
+
+Params:
+    name = string or range of characters representing the file _name
+    upTo = if present, the maximum number of bytes to read
+
+Returns: Untyped array of bytes _read.
+
+Throws: $(LREF FileException) on error.
+ */
+
+void[] read(R)(R name, size_t upTo = size_t.max)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        return readImpl(name, name.tempCString!FSChar(), upTo);
+    else
+        return readImpl(null, name.tempCString!FSChar(), upTo);
+}
+
+///
+@safe unittest
+{
+    import std.utf : byChar;
+    scope(exit)
+    {
+        assert(exists("someUniqueFilename"));
+        remove("someUniqueFilename");
+    }
+
+    write("someUniqueFilename", "1234");
+    assert(read("someUniqueFilename", 2) == "12");
+    assert(read("someUniqueFilename".byChar) == "1234");
+    assert((cast(ubyte[])read("someUniqueFilename")).length == 4);
+}
+
+void[] read(R)(auto ref R name, size_t upTo = size_t.max)
+    if (isConvertibleToString!R)
+{
+    return read!(StringTypeOf!R)(name, upTo);
+}
+
+unittest
+{
+    static assert(__traits(compiles, read(TestAliasedString(null))));
+}
+
+version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @trusted
+{
+    import std.algorithm : min;
+    import std.array : uninitializedArray;
+    import core.memory : GC;
+
+    // A few internal configuration parameters {
+    enum size_t
+        minInitialAlloc = 1024 * 4,
+        maxInitialAlloc = size_t.max / 2,
+        sizeIncrement = 1024 * 16,
+        maxSlackMemoryAllowed = 1024;
+    // }
+
+    immutable fd = core.sys.posix.fcntl.open(namez,
+            core.sys.posix.fcntl.O_RDONLY);
+    cenforce(fd != -1, name);
+    scope(exit) core.sys.posix.unistd.close(fd);
+
+    stat_t statbuf = void;
+    cenforce(fstat(fd, &statbuf) == 0, name, namez);
+
+    immutable initialAlloc = to!size_t(statbuf.st_size
+        ? min(statbuf.st_size + 1, maxInitialAlloc)
+        : minInitialAlloc);
+    void[] result = uninitializedArray!(ubyte[])(initialAlloc);
+    scope(failure) delete result;
+    size_t size = 0;
+
+    for (;;)
+    {
+        immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size,
+                min(result.length, upTo) - size);
+        cenforce(actual != -1, name, namez);
+        if (actual == 0) break;
+        size += actual;
+        if (size < result.length) continue;
+        immutable newAlloc = size + sizeIncrement;
+        result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc];
+    }
+
+    return result.length - size >= maxSlackMemoryAllowed
+        ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size]
+        : result[0 .. size];
+}
+
+
+version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe
+{
+    import std.algorithm : min;
+    import std.array : uninitializedArray;
+    static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
+                              SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
+                              DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
+    {
+        return CreateFileW(namez, dwDesiredAccess, dwShareMode,
+                           lpSecurityAttributes, dwCreationDisposition,
+                           dwFlagsAndAttributes, hTemplateFile);
+
+    }
+    static trustedCloseHandle(HANDLE hObject) @trusted
+    {
+        return CloseHandle(hObject);
+    }
+    static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted
+    {
+        DWORD sizeHigh;
+        DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
+        bool result = sizeLow != INVALID_FILE_SIZE;
+        if (result)
+            fileSize = makeUlong(sizeLow, sizeHigh);
+        return result;
+    }
+    static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted
+    {
+        // Read by chunks of size < 4GB (Windows API limit)
+        ulong totalNumRead = 0;
+        while (totalNumRead != nNumberOfBytesToRead)
+        {
+            uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000);
+            DWORD numRead = void;
+            auto result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null);
+            if (result == 0 || numRead != chunkSize)
+                return false;
+            totalNumRead += chunkSize;
+        }
+        return true;
+    }
+
+    alias defaults =
+        AliasSeq!(GENERIC_READ,
+            FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init,
+            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+            HANDLE.init);
+    auto h = trustedCreateFileW(namez, defaults);
+
+    cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+    scope(exit) cenforce(trustedCloseHandle(h), name, namez);
+    ulong fileSize = void;
+    cenforce(trustedGetFileSize(h, fileSize), name, namez);
+    size_t size = min(upTo, fileSize);
+    auto buf = uninitializedArray!(ubyte[])(size);
+    scope(failure) delete buf;
+
+    cenforce(trustedReadFile(h, buf.ptr, size), name, namez);
+    return buf[0 .. size];
+}
+
+version (linux) @safe unittest
+{
+    // A file with "zero" length that doesn't have 0 length at all
+    auto s = std.file.readText("/proc/sys/kernel/osrelease");
+    assert(s.length > 0);
+    //writefln("'%s'", s);
+}
+
+@safe unittest
+{
+    scope(exit) if (exists(deleteme)) remove(deleteme);
+    import std.stdio;
+    auto f = File(deleteme, "w");
+    f.write("abcd"); f.flush();
+    assert(read(deleteme) == "abcd");
+}
+
+/********************************************
+Read and validates (using $(XREF utf, validate)) a text file. $(D S)
+can be a type of array of characters of any width and constancy. No
+width conversion is performed; if the width of the characters in file
+$(D name) is different from the width of elements of $(D S),
+validation will fail.
+
+Params:
+    name = string or range of characters representing the file _name
+
+Returns: Array of characters read.
+
+Throws: $(D FileException) on file error, $(D UTFException) on UTF
+decoding error.
+ */
+
+S readText(S = string, R)(R name)
+    if (isSomeString!S &&
+        (isInputRange!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+        !isConvertibleToString!R)
+{
+    import std.utf : validate;
+    static auto trustedCast(void[] buf) @trusted { return cast(S)buf; }
+    auto result = trustedCast(read(name));
+    validate(result);
+    return result;
+}
+
+///
+@safe unittest
+{
+    import std.string;
+    write("someUniqueFilename", "abc\n");
+    scope(exit)
+    {
+        assert(exists("someUniqueFilename"));
+        remove("someUniqueFilename");
+    }
+    enforce(chomp(readText("someUniqueFilename")) == "abc");
+}
+
+S readText(S = string, R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return readText!(S, StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, readText(TestAliasedString(null))));
+}
+
+/*********************************************
+Write $(D buffer) to file $(D name).
+
+Params:
+    name = string or range of characters representing the file _name
+    buffer = data to be written to file
+
+Throws: $(D FileException) on error.
+ */
+void write(R)(R name, const void[] buffer)
+    if ((isInputRange!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+        !isConvertibleToString!R)
+{
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        writeImpl(name, name.tempCString!FSChar(), buffer, false);
+    else
+        writeImpl(null, name.tempCString!FSChar(), buffer, false);
+}
+
+///
+unittest
+{
+   scope(exit)
+   {
+       assert(exists("someUniqueFilename"));
+       remove("someUniqueFilename");
+   }
+
+   int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
+   write("someUniqueFilename", a);
+   assert(cast(int[]) read("someUniqueFilename") == a);
+}
+
+void write(R)(auto ref R name, const void[] buffer)
+    if (isConvertibleToString!R)
+{
+    write!(StringTypeOf!R)(name, buffer);
+}
+
+unittest
+{
+    static assert(__traits(compiles, write(TestAliasedString(null), null)));
+}
+
+/*********************************************
+Appends $(D buffer) to file $(D name).
+
+Params:
+    name = string or range of characters representing the file _name
+    buffer = data to be appended to file
+
+Throws: $(D FileException) on error.
+ */
+void append(R)(R name, const void[] buffer)
+    if ((isInputRange!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+        !isConvertibleToString!R)
+{
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        writeImpl(name, name.tempCString!FSChar(), buffer, true);
+    else
+        writeImpl(null, name.tempCString!FSChar(), buffer, true);
+}
+
+///
+unittest
+{
+   scope(exit)
+   {
+       assert(exists("someUniqueFilename"));
+       remove("someUniqueFilename");
+   }
+
+   int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
+   write("someUniqueFilename", a);
+   int[] b = [ 13, 21 ];
+   append("someUniqueFilename", b);
+   assert(cast(int[]) read("someUniqueFilename") == a ~ b);
+}
+
+void append(R)(auto ref R name, const void[] buffer)
+    if (isConvertibleToString!R)
+{
+    append!(StringTypeOf!R)(name, buffer);
+}
+
+unittest
+{
+    static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
+}
+
+// Posix implementation helper for write and append
+
+version(Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez,
+        in void[] buffer, bool append) @trusted
+{
+    // append or write
+    auto mode = append ? O_CREAT | O_WRONLY | O_APPEND
+                       : O_CREAT | O_WRONLY | O_TRUNC;
+
+    immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666);
+    cenforce(fd != -1, name, namez);
+    {
+        scope(failure) core.sys.posix.unistd.close(fd);
+        immutable size = buffer.length;
+        cenforce(
+            core.sys.posix.unistd.write(fd, buffer.ptr, size) == size,
+            name, namez);
+    }
+    cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez);
+}
+
+// Windows implementation helper for write and append
+
+version(Windows) private void writeImpl(const(char)[] name, const(FSChar)* namez,
+        in void[] buffer, bool append) @trusted
+{
+    if (append)
+    {
+        alias defaults =
+            AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS,
+                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+                HANDLE.init);
+
+        auto h = CreateFileW(namez, defaults);
+
+        cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+        scope(exit) cenforce(CloseHandle(h), name, namez);
+        DWORD numwritten;
+        cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER
+                && WriteFile(h,buffer.ptr,to!DWORD(buffer.length),&numwritten,null) != 0
+                && buffer.length == numwritten,
+                name, namez);
+    }
+    else // write
+    {
+        alias defaults =
+            AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
+                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+                HANDLE.init);
+
+        auto h = CreateFileW(namez, defaults);
+
+        cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+        scope(exit) cenforce(CloseHandle(h), name, namez);
+        DWORD numwritten;
+        cenforce(WriteFile(h, buffer.ptr, to!DWORD(buffer.length), &numwritten, null) != 0
+                && buffer.length == numwritten,
+                name, namez);
+    }
+}
+
+/***************************************************
+ * Rename file $(D from) to $(D to).
+ * If the target file exists, it is overwritten.
+ * Params:
+ *    from = string or range of characters representing the existing file name
+ *    to = string or range of characters representing the target file name
+ * Throws: $(D FileException) on error.
+ */
+void rename(RF, RT)(RF from, RT to)
+    if ((isInputRange!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF) && !isConvertibleToString!RF &&
+        (isInputRange!RT && isSomeChar!(ElementEncodingType!RT) || isSomeString!RT) && !isConvertibleToString!RT)
+{
+    // Place outside of @trusted block
+    auto fromz = from.tempCString!FSChar();
+    auto toz = to.tempCString!FSChar();
+
+    static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+        alias f = from;
+    else
+        enum string f = null;
+
+    static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+        alias t = to;
+    else
+        enum string t = null;
+
+    renameImpl(f, t, fromz, toz);
+}
+
+void rename(RF, RT)(auto ref RF from, auto ref RT to)
+    if (isConvertibleToString!RF || isConvertibleToString!RT)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, RF, RT);
+    rename!Types(from, to);
+}
+
+unittest
+{
+    static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null))));
+    static assert(__traits(compiles, rename("", TestAliasedString(null))));
+    static assert(__traits(compiles, rename(TestAliasedString(null), "")));
+    import std.utf : byChar;
+    static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
+}
+
+private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted
+{
+    version(Windows)
+    {
+        auto result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING);
+        if (!result)
+        {
+            import core.stdc.wchar_ : wcslen;
+            import std.conv : to;
+
+            if (!f)
+                f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
+
+            if (!t)
+                t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
+
+            enforce(false,
+                new FileException(
+                    text("Attempting to rename file ", f, " to ", t)));
+        }
+    }
+    else version(Posix)
+    {
+        import core.stdc.stdio;
+
+        cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz);
+    }
+}
+
+@safe unittest
+{
+    import std.utf : byWchar;
+
+    auto t1 = deleteme, t2 = deleteme~"2";
+    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+    write(t1, "1");
+    rename(t1, t2);
+    assert(readText(t2) == "1");
+    write(t1, "2");
+    rename(t1, t2.byWchar);
+    assert(readText(t2) == "2");
+}
+
+
+/***************************************************
+Delete file $(D name).
+
+Params:
+    name = string or range of characters representing the file name
+
+Throws: $(D FileException) on error.
+ */
+void remove(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        removeImpl(name, name.tempCString!FSChar());
+    else
+        removeImpl(null, name.tempCString!FSChar());
+}
+
+void remove(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    remove!(StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, remove(TestAliasedString("foo"))));
+}
+
+private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted
+{
+    version(Windows)
+    {
+        cenforce(DeleteFileW(namez), name, namez);
+    }
+    else version(Posix)
+    {
+        import core.stdc.stdio;
+
+        if (!name)
+        {
+            import core.stdc.string : strlen;
+            auto len = strlen(namez);
+            name = namez[0 .. len];
+        }
+        cenforce(core.stdc.stdio.remove(namez) == 0,
+            "Failed to remove file " ~ name);
+    }
+}
+
+version(Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
+{
+    auto namez = name.tempCString!FSChar();
+
+    WIN32_FILE_ATTRIBUTE_DATA fad = void;
+
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+    {
+        static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+        {
+            enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
+                new FileException(name.idup));
+        }
+        getFA(name, namez, fad);
+    }
+    else
+    {
+        static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+        {
+            import core.stdc.wchar_ : wcslen;
+            import std.conv : to;
+
+            enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
+                new FileException(namez[0 .. wcslen(namez)].to!string));
+        }
+        getFA(namez, fad);
+    }
+    return fad;
+}
+
+version(Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc
+{
+    ULARGE_INTEGER li;
+    li.LowPart  = dwLow;
+    li.HighPart = dwHigh;
+    return li.QuadPart;
+}
+
+/***************************************************
+Get size of file $(D name) in bytes.
+
+Params:
+    name = string or range of characters representing the file name
+
+Throws: $(D FileException) on error (e.g., file not found).
+ */
+ulong getSize(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        with (getFileAttributesWin(name))
+            return makeUlong(nFileSizeLow, nFileSizeHigh);
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString();
+
+        static trustedStat(const(FSChar)* namez, out stat_t buf) @trusted
+        {
+            return stat(namez, &buf);
+        }
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        stat_t statbuf = void;
+        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+        return statbuf.st_size;
+    }
+}
+
+ulong getSize(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return getSize!(StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
+}
+
+@safe unittest
+{
+    // create a file of size 1
+    write(deleteme, "a");
+    scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+    assert(getSize(deleteme) == 1);
+    // create a file of size 3
+    write(deleteme, "abc");
+    import std.utf : byChar;
+    assert(getSize(deleteme.byChar) == 3);
+}
+
+
+/++
+    Get the access and modified times of file or folder $(D name).
+
+    Params:
+        name             = File/Folder name to get times for.
+        accessTime       = Time the file/folder was last accessed.
+        modificationTime = Time the file/folder was last modified.
+
+    Throws:
+        $(D FileException) on error.
+ +/
+void getTimes(R)(R name,
+              out SysTime accessTime,
+              out SysTime modificationTime)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        with (getFileAttributesWin(name))
+        {
+            accessTime = FILETIMEToSysTime(&ftLastAccessTime);
+            modificationTime = FILETIMEToSysTime(&ftLastWriteTime);
+        }
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString();
+
+        static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+        {
+            return stat(namez, &buf);
+        }
+        stat_t statbuf = void;
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+        accessTime = SysTime(unixTimeToStdTime(statbuf.st_atime));
+        modificationTime = SysTime(unixTimeToStdTime(statbuf.st_mtime));
+    }
+}
+
+void getTimes(R)(auto ref R name,
+              out SysTime accessTime,
+              out SysTime modificationTime)
+    if (isConvertibleToString!R)
+{
+    return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
+}
+
+unittest
+{
+    SysTime atime, mtime;
+    static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
+}
+
+unittest
+{
+    import std.stdio : writefln;
+
+    auto currTime = Clock.currTime();
+
+    write(deleteme, "a");
+    scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+
+    SysTime accessTime1 = void;
+    SysTime modificationTime1 = void;
+
+    getTimes(deleteme, accessTime1, modificationTime1);
+
+    enum leeway = dur!"seconds"(5);
+
+    {
+        auto diffa = accessTime1 - currTime;
+        auto diffm = modificationTime1 - currTime;
+        scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm);
+
+        assert(abs(diffa) <= leeway);
+        assert(abs(diffm) <= leeway);
+    }
+
+    version(fullFileTests)
+    {
+        import core.thread;
+        enum sleepTime = dur!"seconds"(2);
+        Thread.sleep(sleepTime);
+
+        currTime = Clock.currTime();
+        write(deleteme, "b");
+
+        SysTime accessTime2 = void;
+        SysTime modificationTime2 = void;
+
+        getTimes(deleteme, accessTime2, modificationTime2);
+
+        {
+            auto diffa = accessTime2 - currTime;
+            auto diffm = modificationTime2 - currTime;
+            scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm);
+
+            //There is no guarantee that the access time will be updated.
+            assert(abs(diffa) <= leeway + sleepTime);
+            assert(abs(diffm) <= leeway);
+        }
+
+        assert(accessTime1 <= accessTime2);
+        assert(modificationTime1 <= modificationTime2);
+    }
+}
+
+
+version(StdDdoc)
+{
+    /++
+     $(BLUE This function is Windows-Only.)
+
+     Get creation/access/modified times of file $(D name).
+
+     This is the same as $(D getTimes) except that it also gives you the file
+     creation time - which isn't possible on Posix systems.
+
+     Params:
+     name                 = File name to get times for.
+     fileCreationTime     = Time the file was created.
+     fileAccessTime       = Time the file was last accessed.
+     fileModificationTime = Time the file was last modified.
+
+     Throws:
+     $(D FileException) on error.
+     +/
+    void getTimesWin(R)(R name,
+                        out SysTime fileCreationTime,
+                        out SysTime fileAccessTime,
+                        out SysTime fileModificationTime)
+        if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+            !isConvertibleToString!R);
+}
+else version(Windows)
+{
+    void getTimesWin(R)(R name,
+                        out SysTime fileCreationTime,
+                        out SysTime fileAccessTime,
+                        out SysTime fileModificationTime)
+        if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+            !isConvertibleToString!R)
+    {
+        with (getFileAttributesWin(name))
+        {
+            fileCreationTime = std.datetime.FILETIMEToSysTime(&ftCreationTime);
+            fileAccessTime = std.datetime.FILETIMEToSysTime(&ftLastAccessTime);
+            fileModificationTime = std.datetime.FILETIMEToSysTime(&ftLastWriteTime);
+        }
+    }
+
+    void getTimesWin(R)(auto ref R name,
+                        out SysTime fileCreationTime,
+                        out SysTime fileAccessTime,
+                        out SysTime fileModificationTime)
+        if (isConvertibleToString!R)
+    {
+        getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime);
+    }
+}
+
+version(Windows) unittest
+{
+    import std.stdio : writefln;
+    auto currTime = Clock.currTime();
+
+    write(deleteme, "a");
+    scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+
+    SysTime creationTime1 = void;
+    SysTime accessTime1 = void;
+    SysTime modificationTime1 = void;
+
+    getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1);
+
+    enum leeway = dur!"seconds"(5);
+
+    {
+        auto diffc = creationTime1 - currTime;
+        auto diffa = accessTime1 - currTime;
+        auto diffm = modificationTime1 - currTime;
+        scope(failure)
+        {
+            writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]",
+                     creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm);
+        }
+
+        // Deleting and recreating a file doesn't seem to always reset the "file creation time"
+        //assert(abs(diffc) <= leeway);
+        assert(abs(diffa) <= leeway);
+        assert(abs(diffm) <= leeway);
+    }
+
+    version(fullFileTests)
+    {
+        import core.thread;
+        Thread.sleep(dur!"seconds"(2));
+
+        currTime = Clock.currTime();
+        write(deleteme, "b");
+
+        SysTime creationTime2 = void;
+        SysTime accessTime2 = void;
+        SysTime modificationTime2 = void;
+
+        getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2);
+
+        {
+            auto diffa = accessTime2 - currTime;
+            auto diffm = modificationTime2 - currTime;
+            scope(failure)
+            {
+                writefln("[%s] [%s] [%s] [%s] [%s]",
+                         accessTime2, modificationTime2, currTime, diffa, diffm);
+            }
+
+            assert(abs(diffa) <= leeway);
+            assert(abs(diffm) <= leeway);
+        }
+
+        assert(creationTime1 == creationTime2);
+        assert(accessTime1 <= accessTime2);
+        assert(modificationTime1 <= modificationTime2);
+    }
+
+    {
+        SysTime ctime, atime, mtime;
+        static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime)));
+    }
+}
+
+
+/++
+    Set access/modified times of file or folder $(D name).
+
+    Params:
+        name             = File/Folder name to get times for.
+        accessTime       = Time the file/folder was last accessed.
+        modificationTime = Time the file/folder was last modified.
+
+    Throws:
+        $(D FileException) on error.
+ +/
+void setTimes(R)(R name,
+              SysTime accessTime,
+              SysTime modificationTime)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedCreateFileW(const(FSChar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
+                                       SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
+                                       DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
+        {
+            return CreateFileW(namez, dwDesiredAccess, dwShareMode,
+                               lpSecurityAttributes, dwCreationDisposition,
+                               dwFlagsAndAttributes, hTemplateFile);
+
+        }
+        static auto trustedCloseHandle(HANDLE hObject) @trusted
+        {
+            return CloseHandle(hObject);
+        }
+        static auto trustedSetFileTime(HANDLE hFile, in FILETIME *lpCreationTime,
+                                       in ref FILETIME lpLastAccessTime, in ref FILETIME lpLastWriteTime) @trusted
+        {
+            return SetFileTime(hFile, lpCreationTime, &lpLastAccessTime, &lpLastWriteTime);
+        }
+
+        const ta = SysTimeToFILETIME(accessTime);
+        const tm = SysTimeToFILETIME(modificationTime);
+        alias defaults =
+            AliasSeq!(GENERIC_WRITE,
+                      0,
+                      null,
+                      OPEN_EXISTING,
+                      FILE_ATTRIBUTE_NORMAL |
+                      FILE_ATTRIBUTE_DIRECTORY |
+                      FILE_FLAG_BACKUP_SEMANTICS,
+                      HANDLE.init);
+        auto h = trustedCreateFileW(namez, defaults);
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(h != INVALID_HANDLE_VALUE, names, namez);
+
+        scope(exit)
+            cenforce(trustedCloseHandle(h), names, namez);
+
+        cenforce(trustedSetFileTime(h, null, ta, tm), names, namez);
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
+        {
+            return utimes(namez, times);
+        }
+        timeval[2] t = void;
+
+        t[0] = accessTime.toTimeVal();
+        t[1] = modificationTime.toTimeVal();
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedUtimes(namez, t) == 0, names, namez);
+    }
+}
+
+void setTimes(R)(auto ref R name,
+              SysTime accessTime,
+              SysTime modificationTime)
+    if (isConvertibleToString!R)
+{
+    setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
+}
+
+@safe unittest
+{
+    static assert(__traits(compiles, setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init)));
+}
+
+unittest
+{
+    import std.stdio : File;
+    string newdir = deleteme ~ r".dir";
+    string dir = newdir ~ r"/a/b/c";
+    string file = dir ~ "/file";
+
+    if (!exists(dir)) mkdirRecurse(dir);
+    { auto f = File(file, "w"); }
+
+    foreach (path; [file, dir])  // test file and dir
+    {
+        SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30));
+        SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30));
+        setTimes(path, atime, mtime);
+
+        SysTime atime_res;
+        SysTime mtime_res;
+        getTimes(path, atime_res, mtime_res);
+        assert(atime == atime_res);
+        assert(mtime == mtime_res);
+    }
+
+    rmdirRecurse(newdir);
+}
+
+/++
+    Returns the time that the given file was last modified.
+
+    Throws:
+        $(D FileException) if the given file does not exist.
++/
+SysTime timeLastModified(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        SysTime dummy;
+        SysTime ftm;
+
+        getTimesWin(name, dummy, dummy, ftm);
+
+        return ftm;
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+        {
+            return stat(namez, &buf);
+        }
+        stat_t statbuf = void;
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+        return SysTime(unixTimeToStdTime(statbuf.st_mtime));
+    }
+}
+
+SysTime timeLastModified(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return timeLastModified!(StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
+}
+
+/++
+    Returns the time that the given file was last modified. If the
+    file does not exist, returns $(D returnIfMissing).
+
+    A frequent usage pattern occurs in build automation tools such as
+    $(WEB gnu.org/software/make, make) or $(WEB
+    en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
+    target) must be rebuilt from file $(D source) (i.e., $(D target) is
+    older than $(D source) or does not exist), use the comparison
+    below. The code throws a $(D FileException) if $(D source) does not
+    exist (as it should). On the other hand, the $(D SysTime.min) default
+    makes a non-existing $(D target) seem infinitely old so the test
+    correctly prompts building it.
+
+    Params:
+        name            = The name of the file to get the modification time for.
+        returnIfMissing = The time to return if the given file does not exist.
+
+Example:
+--------------------
+if(timeLastModified(source) >= timeLastModified(target, SysTime.min))
+{
+    // must (re)build
+}
+else
+{
+    // target is up-to-date
+}
+--------------------
++/
+SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
+{
+    version(Windows)
+    {
+        if (!exists(name))
+            return returnIfMissing;
+
+        SysTime dummy;
+        SysTime ftm;
+
+        getTimesWin(name, dummy, dummy, ftm);
+
+        return ftm;
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+        {
+            return stat(namez, &buf);
+        }
+        stat_t statbuf = void;
+
+        return trustedStat(namez, statbuf) != 0 ?
+               returnIfMissing :
+               SysTime(unixTimeToStdTime(statbuf.st_mtime));
+    }
+}
+
+unittest
+{
+    //std.process.system("echo a > deleteme") == 0 || assert(false);
+    if(exists(deleteme))
+        remove(deleteme);
+
+    write(deleteme, "a\n");
+
+    scope(exit)
+    {
+        assert(exists(deleteme));
+        remove(deleteme);
+    }
+
+    // assert(lastModified("deleteme") >
+    //         lastModified("this file does not exist", SysTime.min));
+    //assert(lastModified("deleteme") > lastModified(__FILE__));
+}
+
+
+/**
+ * Determine whether the given file (or directory) exists.
+ * Params:
+ *    name = string or range of characters representing the file name
+ * Returns:
+ *    true if the filename specified as input exists
+ */
+bool exists(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    return existsImpl(name.tempCString!FSChar());
+}
+
+bool exists(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return exists!(StringTypeOf!R)(name);
+}
+
+private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
+{
+    version(Windows)
+    {
+        // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
+        // fileio/base/getfileattributes.asp
+        return GetFileAttributesW(namez) != 0xFFFFFFFF;
+    }
+    else version(Posix)
+    {
+        /*
+            The reason why we use stat (and not access) here is
+            the quirky behavior of access for SUID programs: if
+            we used access, a file may not appear to "exist",
+            despite that the program would be able to open it
+            just fine. The behavior in question is described as
+            follows in the access man page:
+
+            > The check is done using the calling process's real
+            > UID and GID, rather than the effective IDs as is
+            > done when actually attempting an operation (e.g.,
+            > open(2)) on the file. This allows set-user-ID
+            > programs to easily determine the invoking user's
+            > authority.
+
+            While various operating systems provide eaccess or
+            euidaccess functions, these are not part of POSIX -
+            so it's safer to use stat instead.
+        */
+
+        stat_t statbuf = void;
+        return lstat(namez, &statbuf) == 0;
+    }
+    else
+        static assert(0);
+}
+
+@safe unittest
+{
+    assert(exists("."));
+    assert(!exists("this file does not exist"));
+    write(deleteme, "a\n");
+    scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+    assert(exists(deleteme));
+}
+
+
+/++
+ Returns the attributes of the given file.
+
+ Note that the file attributes on Windows and Posix systems are
+ completely different. On Windows, they're what is returned by
+ $(WEB msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
+ GetFileAttributes), whereas on Posix systems, they're the $(LUCKY
+ st_mode) value which is part of the $(D stat struct) gotten by
+ calling the $(WEB en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat))
+ function.
+
+ On Posix systems, if the given file is a symbolic link, then
+ attributes are the attributes of the file pointed to by the symbolic
+ link.
+
+ Params:
+ name = The file to get the attributes of.
+
+ Throws: $(D FileException) on error.
+  +/
+uint getAttributes(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedGetFileAttributesW(const(FSChar)* namez) @trusted
+        {
+            return GetFileAttributesW(namez);
+        }
+        immutable result = trustedGetFileAttributesW(namez);
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
+
+        return result;
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+        {
+            return stat(namez, &buf);
+        }
+        stat_t statbuf = void;
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+        return statbuf.st_mode;
+    }
+}
+
+uint getAttributes(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return getAttributes!(StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
+}
+
+/++
+    If the given file is a symbolic link, then this returns the attributes of the
+    symbolic link itself rather than file that it points to. If the given file
+    is $(I not) a symbolic link, then this function returns the same result
+    as getAttributes.
+
+    On Windows, getLinkAttributes is identical to getAttributes. It exists on
+    Windows so that you don't have to special-case code for Windows when dealing
+    with symbolic links.
+
+    Params:
+        name = The file to get the symbolic link attributes of.
+
+    Returns:
+        the attributes
+
+    Throws:
+        $(D FileException) on error.
+ +/
+uint getLinkAttributes(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        return getAttributes(name);
+    }
+    else version(Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
+        {
+            return lstat(namez, &buf);
+        }
+        stat_t lstatbuf = void;
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
+        return lstatbuf.st_mode;
+    }
+}
+
+uint getLinkAttributes(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return getLinkAttributes!(StringTypeOf!R)(name);
+}
+
+unittest
+{
+    static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
+}
+
+/++
+    Set the attributes of the given file.
+
+    Params:
+        name = the file name
+        attributes = the attributes to set the file to
+
+    Throws:
+        $(D FileException) if the given file does not exist.
+ +/
+void setAttributes(R)(R name, uint attributes)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version (Windows)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedSetFileAttributesW(const(FSChar)* namez, uint dwFileAttributes) @trusted
+        {
+            return SetFileAttributesW(namez, dwFileAttributes);
+        }
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
+    }
+    else version (Posix)
+    {
+        auto namez = name.tempCString!FSChar();
+        static auto trustedChmod(const(FSChar)* namez, mode_t mode) @trusted
+        {
+            return chmod(namez, mode);
+        }
+        assert(attributes <= mode_t.max);
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias names = name;
+        else
+            string names = null;
+        cenforce(!trustedChmod(namez, cast(mode_t)attributes), names, namez);
+    }
+}
+
+void setAttributes(R)(auto ref R name, uint attributes)
+    if (isConvertibleToString!R)
+{
+    return setAttributes!(StringTypeOf!R)(name, attributes);
+}
+
+unittest
+{
+    static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
+}
+
+/++
+    Returns whether the given file is a directory.
+
+    Params:
+        name = The path to the file.
+
+    Returns:
+        true if the name specifies a directory
+
+    Throws:
+        $(D FileException) if the given file does not exist.
+
+Example:
+--------------------
+assert(!"/etc/fonts/fonts.conf".isDir);
+assert("/usr/share/include".isDir);
+--------------------
+  +/
+@property bool isDir(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
+    }
+    else version(Posix)
+    {
+        return (getAttributes(name) & S_IFMT) == S_IFDIR;
+    }
+}
+
+@property bool isDir(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return name.isDir!(StringTypeOf!R);
+}
+
+unittest
+{
+    static assert(__traits(compiles, TestAliasedString(null).isDir));
+}
+
+@safe unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+            assert("C:\\Program Files\\".isDir);
+
+        if("C:\\Windows\\system.ini".exists)
+            assert(!"C:\\Windows\\system.ini".isDir);
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+            assert(system_directory.isDir);
+
+        if(system_file.exists)
+            assert(!system_file.isDir);
+    }
+}
+
+unittest
+{
+    version(Windows)
+        enum dir = "C:\\Program Files\\";
+    else version(Posix)
+        enum dir = system_directory;
+
+    if (dir.exists)
+    {
+        DirEntry de = DirEntry(dir);
+        assert(de.isDir);
+        assert(DirEntry(dir).isDir);
+    }
+}
+
+/++
+    Returns whether the given file attributes are for a directory.
+
+    Params:
+        attributes = The file attributes.
+
+    Returns:
+        true if attibutes specifies a directory
+
+Example:
+--------------------
+assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf")));
+assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf")));
+--------------------
+  +/
+bool attrIsDir(uint attributes) @safe pure nothrow @nogc
+{
+    version(Windows)
+    {
+        return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+    }
+    else version(Posix)
+    {
+        return (attributes & S_IFMT) == S_IFDIR;
+    }
+}
+
+@safe unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+        {
+            assert(attrIsDir(getAttributes("C:\\Program Files\\")));
+            assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
+        }
+
+        if("C:\\Windows\\system.ini".exists)
+        {
+            assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
+            assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
+        }
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+        {
+            assert(attrIsDir(getAttributes(system_directory)));
+            assert(attrIsDir(getLinkAttributes(system_directory)));
+        }
+
+        if(system_file.exists)
+        {
+            assert(!attrIsDir(getAttributes(system_file)));
+            assert(!attrIsDir(getLinkAttributes(system_file)));
+        }
+    }
+}
+
+
+/++
+    Returns whether the given file (or directory) is a file.
+
+    On Windows, if a file is not a directory, then it's a file. So,
+    either $(D isFile) or $(D isDir) will return true for any given file.
+
+    On Posix systems, if $(D isFile) is $(D true), that indicates that the file
+    is a regular file (e.g. not a block not device). So, on Posix systems, it's
+    possible for both $(D isFile) and $(D isDir) to be $(D false) for a
+    particular file (in which case, it's a special file). You can use
+    $(D getAttributes) to get the attributes to figure out what type of special
+    it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the
+    result from $(D stat). In either case, see the man page for $(D stat) for
+    more information.
+
+    Params:
+        name = The path to the file.
+
+    Returns:
+        true if name specifies a file
+
+    Throws:
+        $(D FileException) if the given file does not exist.
+
+Example:
+--------------------
+assert("/etc/fonts/fonts.conf".isFile);
+assert(!"/usr/share/include".isFile);
+--------------------
+  +/
+@property bool isFile(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+        return !name.isDir;
+    else version(Posix)
+        return (getAttributes(name) & S_IFMT) == S_IFREG;
+}
+
+@property bool isFile(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return name.isFile!(StringTypeOf!R);
+}
+
+unittest
+{
+    static assert(__traits(compiles, TestAliasedString(null).isFile));
+}
+
+@safe unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+            assert(!"C:\\Program Files\\".isFile);
+
+        if("C:\\Windows\\system.ini".exists)
+            assert("C:\\Windows\\system.ini".isFile);
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+            assert(!system_directory.isFile);
+
+        if(system_file.exists)
+            assert(system_file.isFile);
+    }
+}
+
+
+/++
+    Returns whether the given file attributes are for a file.
+
+    On Windows, if a file is not a directory, it's a file. So, either
+    $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the
+    attributes of any given file.
+
+    On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the
+    file is a regular file (e.g. not a block not device). So, on Posix systems,
+    it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false)
+    for a particular file (in which case, it's a special file). If a file is a
+    special file, you can use the attributes to check what type of special file
+    it is (see the man page for $(D stat) for more information).
+
+    Params:
+        attributes = The file attributes.
+
+    Returns:
+        true if the given file attributes are for a file
+
+Example:
+--------------------
+assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
+assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
+--------------------
+  +/
+bool attrIsFile(uint attributes) @safe pure nothrow @nogc
+{
+    version(Windows)
+    {
+        return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+    }
+    else version(Posix)
+    {
+        return (attributes & S_IFMT) == S_IFREG;
+    }
+}
+
+@safe unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+        {
+            assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
+            assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
+        }
+
+        if("C:\\Windows\\system.ini".exists)
+        {
+            assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
+            assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
+        }
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+        {
+            assert(!attrIsFile(getAttributes(system_directory)));
+            assert(!attrIsFile(getLinkAttributes(system_directory)));
+        }
+
+        if(system_file.exists)
+        {
+            assert(attrIsFile(getAttributes(system_file)));
+            assert(attrIsFile(getLinkAttributes(system_file)));
+        }
+    }
+}
+
+
+/++
+    Returns whether the given file is a symbolic link.
+
+    On Windows, returns $(D true) when the file is either a symbolic link or a
+    junction point.
+
+    Params:
+        name = The path to the file.
+
+    Returns:
+        true if name is a symbolic link
+
+    Throws:
+        $(D FileException) if the given file does not exist.
+  +/
+@property bool isSymlink(R)(R name)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+        return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+    else version(Posix)
+        return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
+}
+
+@property bool isSymlink(R)(auto ref R name)
+    if (isConvertibleToString!R)
+{
+    return name.isSymlink!(StringTypeOf!R);
+}
+
+unittest
+{
+    static assert(__traits(compiles, TestAliasedString(null).isSymlink));
+}
+
+unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+            assert(!"C:\\Program Files\\".isSymlink);
+
+        if("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
+            assert("C:\\Documents and Settings\\".isSymlink);
+
+        enum fakeSymFile = "C:\\Windows\\system.ini";
+        if(fakeSymFile.exists)
+        {
+            assert(!fakeSymFile.isSymlink);
+
+            assert(!fakeSymFile.isSymlink);
+            assert(!attrIsSymlink(getAttributes(fakeSymFile)));
+            assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
+
+            assert(attrIsFile(getAttributes(fakeSymFile)));
+            assert(attrIsFile(getLinkAttributes(fakeSymFile)));
+            assert(!attrIsDir(getAttributes(fakeSymFile)));
+            assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
+
+            assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
+        }
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+        {
+            assert(!system_directory.isSymlink);
+
+            immutable symfile = deleteme ~ "_slink\0";
+            scope(exit) if(symfile.exists) symfile.remove();
+
+            core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
+
+            assert(symfile.isSymlink);
+            assert(!attrIsSymlink(getAttributes(symfile)));
+            assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+            assert(attrIsDir(getAttributes(symfile)));
+            assert(!attrIsDir(getLinkAttributes(symfile)));
+
+            assert(!attrIsFile(getAttributes(symfile)));
+            assert(!attrIsFile(getLinkAttributes(symfile)));
+        }
+
+        if(system_file.exists)
+        {
+            assert(!system_file.isSymlink);
+
+            immutable symfile = deleteme ~ "_slink\0";
+            scope(exit) if(symfile.exists) symfile.remove();
+
+            core.sys.posix.unistd.symlink(system_file, symfile.ptr);
+
+            assert(symfile.isSymlink);
+            assert(!attrIsSymlink(getAttributes(symfile)));
+            assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+            assert(!attrIsDir(getAttributes(symfile)));
+            assert(!attrIsDir(getLinkAttributes(symfile)));
+
+            assert(attrIsFile(getAttributes(symfile)));
+            assert(!attrIsFile(getLinkAttributes(symfile)));
+        }
+    }
+
+    static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
+}
+
+
+/++
+    Returns whether the given file attributes are for a symbolic link.
+
+    On Windows, return $(D true) when the file is either a symbolic link or a
+    junction point.
+
+    Params:
+        attributes = The file attributes.
+
+    Returns:
+        true if attributes are for a symbolic link
+
+Example:
+--------------------
+core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
+
+assert(!getAttributes("/tmp/alink").isSymlink);
+assert(getLinkAttributes("/tmp/alink").isSymlink);
+--------------------
+  +/
+bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
+{
+    version(Windows)
+        return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+    else version(Posix)
+        return (attributes & S_IFMT) == S_IFLNK;
+}
+
+
+/****************************************************
+ * Change directory to $(D pathname).
+ * Throws: $(D FileException) on error.
+ */
+void chdir(R)(R pathname)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    // Place outside of @trusted block
+    auto pathz = pathname.tempCString!FSChar();
+
+    version(Windows)
+    {
+        static auto trustedChdir(const(FSChar)* pathz) @trusted
+        {
+            return SetCurrentDirectoryW(pathz);
+        }
+    }
+    else version(Posix)
+    {
+        static auto trustedChdir(const(FSChar)* pathz) @trusted
+        {
+            return core.sys.posix.unistd.chdir(pathz) == 0;
+        }
+    }
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        alias pathStr = pathname;
+    else
+        string pathStr = null;
+    cenforce(trustedChdir(pathz), pathStr, pathz);
+}
+
+void chdir(R)(auto ref R pathname)
+    if (isConvertibleToString!R)
+{
+    return chdir!(StringTypeOf!R)(pathname);
+}
+
+unittest
+{
+    static assert(__traits(compiles, chdir(TestAliasedString(null))));
+}
+
+/****************************************************
+Make directory $(D pathname).
+
+Throws: $(D FileException) on Posix or $(D WindowsException) on Windows
+        if an error occured.
+ */
+void mkdir(R)(R pathname)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    // Place outside of @trusted block
+    auto pathz = pathname.tempCString!FSChar();
+
+    version(Windows)
+    {
+        static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted
+        {
+            return CreateDirectoryW(pathz, null);
+        }
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias pathStr = pathname;
+        else
+            string pathStr = null;
+        wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
+    }
+    else version(Posix)
+    {
+        static auto trustedMkdir(const(FSChar)* pathz, mode_t mode) @trusted
+        {
+            return core.sys.posix.sys.stat.mkdir(pathz, mode);
+        }
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias pathStr = pathname;
+        else
+            string pathStr = null;
+        cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
+    }
+}
+
+void mkdir(R)(auto ref R pathname)
+    if (isConvertibleToString!R)
+{
+    return mkdir!(StringTypeOf!R)(pathname);
+}
+
+unittest
+{
+    static assert(__traits(compiles, mkdir(TestAliasedString(null))));
+}
+
+// Same as mkdir but ignores "already exists" errors.
+// Returns: "true" if the directory was created,
+//   "false" if it already existed.
+private bool ensureDirExists(in char[] pathname)
+{
+    version(Windows)
+    {
+        if (CreateDirectoryW(pathname.tempCStringW(), null))
+            return true;
+        cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
+    }
+    else version(Posix)
+    {
+        if (core.sys.posix.sys.stat.mkdir(pathname.tempCString(), octal!777) == 0)
+            return true;
+        cenforce(errno == EEXIST || errno == EISDIR, pathname);
+    }
+    enforce(pathname.isDir, new FileException(pathname.idup));
+    return false;
+}
+
+/****************************************************
+ * Make directory and all parent directories as needed.
+ *
+ * Throws: $(D FileException) on error.
+ */
+
+void mkdirRecurse(in char[] pathname)
+{
+    const left = dirName(pathname);
+    if (left.length != pathname.length && !exists(left))
+    {
+        mkdirRecurse(left);
+    }
+    if (!baseName(pathname).empty)
+    {
+        ensureDirExists(pathname);
+    }
+}
+
+unittest
+{
+    {
+        immutable basepath = deleteme ~ "_dir";
+        scope(exit) rmdirRecurse(basepath);
+
+        auto path = buildPath(basepath, "a", "..", "b");
+        mkdirRecurse(path);
+        path = path.buildNormalizedPath;
+        assert(path.isDir);
+
+        path = buildPath(basepath, "c");
+        write(path, "");
+        assertThrown!FileException(mkdirRecurse(path));
+
+        path = buildPath(basepath, "d");
+        mkdirRecurse(path);
+        mkdirRecurse(path); // should not throw
+    }
+
+    version(Windows)
+    {
+        assertThrown!FileException(mkdirRecurse(`1:\foobar`));
+    }
+
+    // bug3570
+    {
+        immutable basepath = deleteme ~ "_dir";
+        version (Windows)
+        {
+            immutable path = basepath ~ "\\fake\\here\\";
+        }
+        else version (Posix)
+        {
+            immutable path = basepath ~ `/fake/here/`;
+        }
+
+        mkdirRecurse(path);
+        assert(basepath.exists && basepath.isDir);
+        scope(exit) rmdirRecurse(basepath);
+        assert(path.exists && path.isDir);
+    }
+}
+
+/****************************************************
+Remove directory $(D pathname).
+
+Params:
+    pathname = Range or string specifying the directory name
+
+Throws: $(D FileException) on error.
+ */
+void rmdir(R)(R pathname)
+    if (isInputRange!R && isSomeChar!(ElementEncodingType!R) &&
+        !isConvertibleToString!R)
+{
+    // Place outside of @trusted block
+    auto pathz = pathname.tempCString!FSChar();
+
+    version(Windows)
+    {
+        static auto trustedRmdir(const(FSChar)* pathz) @trusted
+        {
+            return RemoveDirectoryW(pathz);
+        }
+    }
+    else version(Posix)
+    {
+        static auto trustedRmdir(const(FSChar)* pathz) @trusted
+        {
+            return core.sys.posix.unistd.rmdir(pathz) == 0;
+        }
+    }
+    static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+        alias pathStr = pathname;
+    else
+        string pathStr = null;
+    cenforce(trustedRmdir(pathz), pathStr, pathz);
+}
+
+void rmdir(R)(auto ref R pathname)
+    if (isConvertibleToString!R)
+{
+    rmdir!(StringTypeOf!R)(pathname);
+}
+
+unittest
+{
+    static assert(__traits(compiles, rmdir(TestAliasedString(null))));
+}
+
+/++
+    $(BLUE This function is Posix-Only.)
+
+    Creates a symbolic _link (_symlink).
+
+    Params:
+        original = The file that is being linked. This is the target path that's
+            stored in the _symlink. A relative path is relative to the created
+            _symlink.
+        link = The _symlink to create. A relative path is relative to the
+            current working directory.
+
+    Throws:
+        $(D FileException) on error (which includes if the _symlink already
+        exists).
+  +/
+version(StdDdoc) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) @safe;
+else version(Posix) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) @safe
+{
+    static auto trustedSymlink(const(C1)[] path1, const(C2)[] path2) @trusted
+    {
+        return core.sys.posix.unistd.symlink(path1.tempCString(),
+                                             path2.tempCString());
+    }
+    cenforce(trustedSymlink(original, link) == 0, link);
+}
+
+version(Posix) @safe unittest
+{
+    if(system_directory.exists)
+    {
+        immutable symfile = deleteme ~ "_slink\0";
+        scope(exit) if(symfile.exists) symfile.remove();
+
+        symlink(system_directory, symfile);
+
+        assert(symfile.exists);
+        assert(symfile.isSymlink);
+        assert(!attrIsSymlink(getAttributes(symfile)));
+        assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+        assert(attrIsDir(getAttributes(symfile)));
+        assert(!attrIsDir(getLinkAttributes(symfile)));
+
+        assert(!attrIsFile(getAttributes(symfile)));
+        assert(!attrIsFile(getLinkAttributes(symfile)));
+    }
+
+    if(system_file.exists)
+    {
+        assert(!system_file.isSymlink);
+
+        immutable symfile = deleteme ~ "_slink\0";
+        scope(exit) if(symfile.exists) symfile.remove();
+
+        symlink(system_file, symfile);
+
+        assert(symfile.exists);
+        assert(symfile.isSymlink);
+        assert(!attrIsSymlink(getAttributes(symfile)));
+        assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+        assert(!attrIsDir(getAttributes(symfile)));
+        assert(!attrIsDir(getLinkAttributes(symfile)));
+
+        assert(attrIsFile(getAttributes(symfile)));
+        assert(!attrIsFile(getLinkAttributes(symfile)));
+    }
+}
+
+
+/++
+    $(BLUE This function is Posix-Only.)
+
+    Returns the path to the file pointed to by a symlink. Note that the
+    path could be either relative or absolute depending on the symlink.
+    If the path is relative, it's relative to the symlink, not the current
+    working directory.
+
+    Throws:
+        $(D FileException) on error.
+  +/
+version(StdDdoc) string readLink(C)(const(C)[] link) @safe;
+else version(Posix) string readLink(C)(const(C)[] link) @safe
+{
+    static auto trustedReadlink(const(C)[] path, char[] buf) @trusted
+    {
+        return core.sys.posix.unistd.readlink(path.tempCString(), buf.ptr, buf.length);
+    }
+    static auto trustedAssumeUnique(ref C[] array) @trusted
+    {
+        return assumeUnique(array);
+    }
+
+    enum bufferLen = 2048;
+    enum maxCodeUnits = 6;
+    char[bufferLen] buffer;
+    auto size = trustedReadlink(link, buffer);
+    cenforce(size != -1, link);
+
+    if(size <= bufferLen - maxCodeUnits)
+        return to!string(buffer[0 .. size]);
+
+    auto dynamicBuffer = new char[](bufferLen * 3 / 2);
+
+    foreach(i; 0 .. 10)
+    {
+        size = trustedReadlink(link, dynamicBuffer);
+        cenforce(size != -1, link);
+
+        if(size <= dynamicBuffer.length - maxCodeUnits)
+        {
+            dynamicBuffer.length = size;
+            return trustedAssumeUnique(dynamicBuffer);
+        }
+
+        dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
+    }
+
+    throw new FileException(to!string(link), "Path is too long to read.");
+}
+
+version(Posix) @safe unittest
+{
+    import std.string;
+    foreach(file; [system_directory, system_file])
+    {
+        if(file.exists)
+        {
+            immutable symfile = deleteme ~ "_slink\0";
+            scope(exit) if(symfile.exists) symfile.remove();
+
+            symlink(file, symfile);
+            assert(readLink(symfile) == file, format("Failed file: %s", file));
+        }
+    }
+
+    assertThrown!FileException(readLink("/doesnotexist"));
+}
+
+
+/****************************************************
+ * Get the current working directory.
+ * Throws: $(D FileException) on error.
+ */
+version(Windows) string getcwd()
+{
+    import std.utf : toUTF8;
+    /* GetCurrentDirectory's return value:
+        1. function succeeds: the number of characters that are written to
+    the buffer, not including the terminating null character.
+        2. function fails: zero
+        3. the buffer (lpBuffer) is not large enough: the required size of
+    the buffer, in characters, including the null-terminating character.
+    */
+    wchar[4096] buffW = void; //enough for most common case
+    immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
+            "getcwd");
+    // we can do it because toUTFX always produces a fresh string
+    if(n < buffW.length)
+    {
+        return toUTF8(buffW[0 .. n]);
+    }
+    else //staticBuff isn't enough
+    {
+        auto ptr = cast(wchar*) malloc(wchar.sizeof * n);
+        scope(exit) free(ptr);
+        immutable n2 = GetCurrentDirectoryW(n, ptr);
+        cenforce(n2 && n2 < n, "getcwd");
+        return toUTF8(ptr[0 .. n2]);
+    }
+}
+else version (Posix) string getcwd()
+{
+    auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
+            "cannot get cwd");
+    scope(exit) core.stdc.stdlib.free(p);
+    return p[0 .. core.stdc.string.strlen(p)].idup;
+}
+
+unittest
+{
+    auto s = getcwd();
+    assert(s.length);
+}
+
+version (OSX)
+    private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize);
+else version (FreeBSD)
+    private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
+        size_t* oldlenp, const void* newp, size_t newlen);
+else version (NetBSD)
+    private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
+        size_t* oldlenp, const void* newp, size_t newlen);
+
+/**
+ * Returns the full path of the current executable.
+ *
+ * Throws:
+ * $(XREF object, Exception)
+ */
+@trusted string thisExePath ()
+{
+    version (OSX)
+    {
+        import core.sys.posix.stdlib : realpath;
+
+        uint size;
+
+        _NSGetExecutablePath(null, &size); // get the length of the path
+        auto buffer = new char[size];
+        _NSGetExecutablePath(buffer.ptr, &size);
+
+        auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
+
+        scope (exit)
+        {
+            if (absolutePath)
+                free(absolutePath);
+        }
+
+        errnoEnforce(absolutePath);
+        return to!(string)(absolutePath);
+    }
+    else version (linux)
+    {
+        return readLink("/proc/self/exe");
+    }
+    else version (Windows)
+    {
+        wchar[MAX_PATH] buf;
+        wchar[] buffer = buf[];
+
+        while (true)
+        {
+            auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
+            enforce(len, sysErrorString(GetLastError()));
+            if (len != buffer.length)
+                return to!(string)(buffer[0 .. len]);
+            buffer.length *= 2;
+        }
+    }
+    else version (FreeBSD)
+    {
+        enum
+        {
+            CTL_KERN = 1,
+            KERN_PROC = 14,
+            KERN_PROC_PATHNAME = 12
+        }
+
+        int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
+        size_t len;
+
+        auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
+        errnoEnforce(result == 0);
+
+        auto buffer = new char[len - 1];
+        result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
+        errnoEnforce(result == 0);
+
+        return buffer.assumeUnique;
+    }
+    else version (NetBSD)
+    {
+        return readLink("/proc/self/exe");
+    }
+    else version (Solaris)
+    {
+        import core.sys.posix.unistd : getpid;
+        import std.string : format;
+
+        // Only Solaris 10 and later
+        return readLink(format("/proc/%d/path/a.out", getpid()));
+    }
+    else
+        static assert(0, "thisExePath is not supported on this platform");
+}
+
+@safe unittest
+{
+    auto path = thisExePath();
+
+    assert(path.exists);
+    assert(path.isAbsolute);
+    assert(path.isFile);
+}
+
+version(StdDdoc)
+{
+    /++
+        Info on a file, similar to what you'd get from stat on a Posix system.
+      +/
+    struct DirEntry
+    {
+        /++
+            Constructs a DirEntry for the given file (or directory).
+
+            Params:
+                path = The file (or directory) to get a DirEntry for.
+
+            Throws:
+                $(D FileException) if the file does not exist.
+        +/
+        this(string path);
+
+        version (Windows)
+        {
+            private this(string path, in WIN32_FIND_DATAW *fd);
+        }
+        else version (Posix)
+        {
+            private this(string path, core.sys.posix.dirent.dirent* fd);
+        }
+
+        /++
+            Returns the path to the file represented by this $(D DirEntry).
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(de1.name == "/etc/fonts/fonts.conf");
+
+auto de2 = DirEntry("/usr/share/include");
+assert(de2.name == "/usr/share/include");
+--------------------
+          +/
+        @property string name() const;
+
+
+        /++
+            Returns whether the file represented by this $(D DirEntry) is a
+            directory.
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(!de1.isDir);
+
+auto de2 = DirEntry("/usr/share/include");
+assert(de2.isDir);
+--------------------
+          +/
+        @property bool isDir();
+
+
+        /++
+            Returns whether the file represented by this $(D DirEntry) is a file.
+
+            On Windows, if a file is not a directory, then it's a file. So,
+            either $(D isFile) or $(D isDir) will return $(D true).
+
+            On Posix systems, if $(D isFile) is $(D true), that indicates that
+            the file is a regular file (e.g. not a block not device). So, on
+            Posix systems, it's possible for both $(D isFile) and $(D isDir) to
+            be $(D false) for a particular file (in which case, it's a special
+            file). You can use $(D attributes) or $(D statBuf) to get more
+            information about a special file (see the stat man page for more
+            details).
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(de1.isFile);
+
+auto de2 = DirEntry("/usr/share/include");
+assert(!de2.isFile);
+--------------------
+          +/
+        @property bool isFile();
+
+        /++
+            Returns whether the file represented by this $(D DirEntry) is a
+            symbolic link.
+
+            On Windows, return $(D true) when the file is either a symbolic
+            link or a junction point.
+          +/
+        @property bool isSymlink();
+
+        /++
+            Returns the size of the the file represented by this $(D DirEntry)
+            in bytes.
+          +/
+        @property ulong size();
+
+        /++
+            $(BLUE This function is Windows-Only.)
+
+            Returns the creation time of the file represented by this
+            $(D DirEntry).
+          +/
+        @property SysTime timeCreated() const;
+
+        /++
+            Returns the time that the file represented by this $(D DirEntry) was
+            last accessed.
+
+            Note that many file systems do not update the access time for files
+            (generally for performance reasons), so there's a good chance that
+            $(D timeLastAccessed) will return the same value as
+            $(D timeLastModified).
+          +/
+        @property SysTime timeLastAccessed();
+
+        /++
+            Returns the time that the file represented by this $(D DirEntry) was
+            last modified.
+          +/
+        @property SysTime timeLastModified();
+
+        /++
+            Returns the attributes of the file represented by this $(D DirEntry).
+
+            Note that the file attributes on Windows and Posix systems are
+            completely different. On, Windows, they're what is returned by
+            $(D GetFileAttributes)
+            $(WEB msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
+            Whereas, an Posix systems, they're the $(D st_mode) value which is
+            part of the $(D stat) struct gotten by calling $(D stat).
+
+            On Posix systems, if the file represented by this $(D DirEntry) is a
+            symbolic link, then attributes are the attributes of the file
+            pointed to by the symbolic link.
+          +/
+        @property uint attributes();
+
+        /++
+            On Posix systems, if the file represented by this $(D DirEntry) is a
+            symbolic link, then $(D linkAttributes) are the attributes of the
+            symbolic link itself. Otherwise, $(D linkAttributes) is identical to
+            $(D attributes).
+
+            On Windows, $(D linkAttributes) is identical to $(D attributes). It
+            exists on Windows so that you don't have to special-case code for
+            Windows when dealing with symbolic links.
+          +/
+        @property uint linkAttributes();
+
+        version(Windows)
+            alias stat_t = void*;
+
+        /++
+            $(BLUE This function is Posix-Only.)
+
+            The $(D stat) struct gotten from calling $(D stat).
+          +/
+        @property stat_t statBuf();
+    }
+}
+else version(Windows)
+{
+    struct DirEntry
+    {
+        import std.utf : toUTF8;
+    public:
+        alias name this;
+
+        this(string path)
+        {
+            if(!path.exists())
+                throw new FileException(path, "File does not exist");
+
+            _name = path;
+
+            with (getFileAttributesWin(path))
+            {
+                _size = makeUlong(nFileSizeLow, nFileSizeHigh);
+                _timeCreated = std.datetime.FILETIMEToSysTime(&ftCreationTime);
+                _timeLastAccessed = std.datetime.FILETIMEToSysTime(&ftLastAccessTime);
+                _timeLastModified = std.datetime.FILETIMEToSysTime(&ftLastWriteTime);
+                _attributes = dwFileAttributes;
+            }
+        }
+
+        private this(string path, in WIN32_FIND_DATAW *fd)
+        {
+            import core.stdc.wchar_ : wcslen;
+
+            size_t clength = wcslen(fd.cFileName.ptr);
+            _name = toUTF8(fd.cFileName[0 .. clength]);
+            _name = buildPath(path, toUTF8(fd.cFileName[0 .. clength]));
+            _size = (cast(ulong)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
+            _timeCreated = std.datetime.FILETIMEToSysTime(&fd.ftCreationTime);
+            _timeLastAccessed = std.datetime.FILETIMEToSysTime(&fd.ftLastAccessTime);
+            _timeLastModified = std.datetime.FILETIMEToSysTime(&fd.ftLastWriteTime);
+            _attributes = fd.dwFileAttributes;
+        }
+
+        @property string name() const pure nothrow
+        {
+            return _name;
+        }
+
+        @property bool isDir() const pure nothrow
+        {
+            return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+        }
+
+        @property bool isFile() const pure nothrow
+        {
+            //Are there no options in Windows other than directory and file?
+            //If there are, then this probably isn't the best way to determine
+            //whether this DirEntry is a file or not.
+            return !isDir;
+        }
+
+        @property bool isSymlink() const pure nothrow
+        {
+            return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+        }
+
+        @property ulong size() const pure nothrow
+        {
+            return _size;
+        }
+
+        @property SysTime timeCreated() const pure nothrow
+        {
+            return cast(SysTime)_timeCreated;
+        }
+
+        @property SysTime timeLastAccessed() const pure nothrow
+        {
+            return cast(SysTime)_timeLastAccessed;
+        }
+
+        @property SysTime timeLastModified() const pure nothrow
+        {
+            return cast(SysTime)_timeLastModified;
+        }
+
+        @property uint attributes() const pure nothrow
+        {
+            return _attributes;
+        }
+
+        @property uint linkAttributes() const pure nothrow
+        {
+            return _attributes;
+        }
+
+    private:
+        string _name; /// The file or directory represented by this DirEntry.
+
+        SysTime _timeCreated;      /// The time when the file was created.
+        SysTime _timeLastAccessed; /// The time when the file was last accessed.
+        SysTime _timeLastModified; /// The time when the file was last modified.
+
+        ulong _size;       /// The size of the file in bytes.
+        uint  _attributes; /// The file attributes from WIN32_FIND_DATAW.
+    }
+}
+else version(Posix)
+{
+    struct DirEntry
+    {
+    public:
+        alias name this;
+
+        this(string path)
+        {
+            if(!path.exists)
+                throw new FileException(path, "File does not exist");
+
+            _name = path;
+
+            _didLStat = false;
+            _didStat = false;
+            _dTypeSet = false;
+        }
+
+        private this(string path, core.sys.posix.dirent.dirent* fd)
+        {
+            immutable len = core.stdc.string.strlen(fd.d_name.ptr);
+            _name = buildPath(path, fd.d_name[0 .. len]);
+
+            _didLStat = false;
+            _didStat = false;
+
+            //fd_d_type doesn't work for all file systems,
+            //in which case the result is DT_UNKOWN. But we
+            //can determine the correct type from lstat, so
+            //we'll only set the dtype here if we could
+            //correctly determine it (not lstat in the case
+            //of DT_UNKNOWN in case we don't ever actually
+            //need the dtype, thus potentially avoiding the
+            //cost of calling lstat).
+            static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
+            {
+                if(fd.d_type != DT_UNKNOWN)
+                {
+                    _dType = fd.d_type;
+                    _dTypeSet = true;
+                }
+                else
+                    _dTypeSet = false;
+            }
+            else
+            {
+                // e.g. Solaris does not have the d_type member
+                _dTypeSet = false;
+            }
+        }
+
+        @property string name() const pure nothrow
+        {
+            return _name;
+        }
+
+        @property bool isDir()
+        {
+            _ensureStatOrLStatDone();
+
+            return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
+        }
+
+        @property bool isFile()
+        {
+            _ensureStatOrLStatDone();
+
+            return (_statBuf.st_mode & S_IFMT) == S_IFREG;
+        }
+
+        @property bool isSymlink()
+        {
+            _ensureLStatDone();
+
+            return (_lstatMode & S_IFMT) == S_IFLNK;
+        }
+
+        @property ulong size()
+        {
+            _ensureStatDone();
+            return _statBuf.st_size;
+        }
+
+        @property SysTime timeStatusChanged()
+        {
+            _ensureStatDone();
+
+            return SysTime(unixTimeToStdTime(_statBuf.st_ctime));
+        }
+
+        @property SysTime timeLastAccessed()
+        {
+            _ensureStatDone();
+
+            return SysTime(unixTimeToStdTime(_statBuf.st_ctime));
+        }
+
+        @property SysTime timeLastModified()
+        {
+            _ensureStatDone();
+
+            return SysTime(unixTimeToStdTime(_statBuf.st_mtime));
+        }
+
+        @property uint attributes()
+        {
+            _ensureStatDone();
+
+            return _statBuf.st_mode;
+        }
+
+        @property uint linkAttributes()
+        {
+            _ensureLStatDone();
+
+            return _lstatMode;
+        }
+
+        @property stat_t statBuf()
+        {
+            _ensureStatDone();
+
+            return _statBuf;
+        }
+
+    private:
+        /++
+            This is to support lazy evaluation, because doing stat's is
+            expensive and not always needed.
+         +/
+        void _ensureStatDone() @safe
+        {
+            static auto trustedStat(in char[] path, stat_t* buf) @trusted
+            {
+                return stat(path.tempCString(), buf);
+            }
+            if(_didStat)
+                return;
+
+            enforce(trustedStat(_name, &_statBuf) == 0,
+                    "Failed to stat file `" ~ _name ~ "'");
+
+            _didStat = true;
+        }
+
+        /++
+            This is to support lazy evaluation, because doing stat's is
+            expensive and not always needed.
+
+            Try both stat and lstat for isFile and isDir
+            to detect broken symlinks.
+         +/
+        void _ensureStatOrLStatDone()
+        {
+            if(_didStat)
+                return;
+
+            if( stat(_name.tempCString(), &_statBuf) != 0 )
+            {
+                _ensureLStatDone();
+
+                _statBuf = stat_t.init;
+                _statBuf.st_mode = S_IFLNK;
+            }
+            else
+            {
+                _didStat = true;
+            }
+        }
+
+        /++
+            This is to support lazy evaluation, because doing stat's is
+            expensive and not always needed.
+         +/
+        void _ensureLStatDone()
+        {
+            if(_didLStat)
+                return;
+
+            stat_t statbuf = void;
+
+            enforce(lstat(_name.tempCString(), &statbuf) == 0,
+                "Failed to stat file `" ~ _name ~ "'");
+
+            _lstatMode = statbuf.st_mode;
+
+            _dTypeSet = true;
+            _didLStat = true;
+        }
+
+        string _name; /// The file or directory represented by this DirEntry.
+
+        stat_t _statBuf = void;  /// The result of stat().
+        uint  _lstatMode;               /// The stat mode from lstat().
+        ubyte _dType;                   /// The type of the file.
+
+        bool _didLStat = false;   /// Whether lstat() has been called for this DirEntry.
+        bool _didStat = false;    /// Whether stat() has been called for this DirEntry.
+        bool _dTypeSet = false;   /// Whether the dType of the file has been set.
+    }
+}
+
+unittest
+{
+    version(Windows)
+    {
+        if("C:\\Program Files\\".exists)
+        {
+            auto de = DirEntry("C:\\Program Files\\");
+            assert(!de.isFile);
+            assert(de.isDir);
+            assert(!de.isSymlink);
+        }
+
+        if("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
+        {
+            auto de = DirEntry("C:\\Documents and Settings\\");
+            assert(de.isSymlink);
+        }
+
+        if("C:\\Windows\\system.ini".exists)
+        {
+            auto de = DirEntry("C:\\Windows\\system.ini");
+            assert(de.isFile);
+            assert(!de.isDir);
+            assert(!de.isSymlink);
+        }
+    }
+    else version(Posix)
+    {
+        if(system_directory.exists)
+        {
+            {
+                auto de = DirEntry(system_directory);
+                assert(!de.isFile);
+                assert(de.isDir);
+                assert(!de.isSymlink);
+            }
+
+            immutable symfile = deleteme ~ "_slink\0";
+            scope(exit) if(symfile.exists) symfile.remove();
+
+            core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
+
+            {
+                auto de = DirEntry(symfile);
+                assert(!de.isFile);
+                assert(de.isDir);
+                assert(de.isSymlink);
+            }
+
+            symfile.remove();
+            core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
+
+            {
+                //Issue 8298
+                DirEntry de = DirEntry(symfile);
+
+                assert(!de.isFile);
+                assert(!de.isDir);
+                assert(de.isSymlink);
+                assertThrown(de.size);
+                assertThrown(de.timeStatusChanged);
+                assertThrown(de.timeLastAccessed);
+                assertThrown(de.timeLastModified);
+                assertThrown(de.attributes);
+                assertThrown(de.statBuf);
+                assert(symfile.exists);
+                symfile.remove();
+            }
+        }
+
+        if(system_file.exists)
+        {
+            auto de = DirEntry(system_file);
+            assert(de.isFile);
+            assert(!de.isDir);
+            assert(!de.isSymlink);
+        }
+    }
+}
+
+alias PreserveAttributes = Flag!"preserveAttributes";
+
+version (StdDdoc)
+{
+    /// Defaults to PreserveAttributes.yes on Windows, and the opposite on all other platforms.
+    PreserveAttributes preserveAttributesDefault;
+}
+else version(Windows)
+{
+    enum preserveAttributesDefault = PreserveAttributes.yes;
+}
+else
+{
+    enum preserveAttributesDefault = PreserveAttributes.no;
+}
+
+/***************************************************
+Copy file $(D from) to file $(D to). File timestamps are preserved.
+File attributes are preserved, if $(D preserve) equals $(D PreserveAttributes.yes).
+On Windows only $(D PreserveAttributes.yes) (the default on Windows) is supported.
+If the target file exists, it is overwritten.
+
+Params:
+    from = string or range of characters representing the existing file name
+    to = string or range of characters representing the target file name
+    preserve = whether to preserve the file attributes
+
+Throws: $(D FileException) on error.
+ */
+void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
+    if (isInputRange!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF &&
+        isInputRange!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT)
+{
+    // Place outside of @trusted block
+    auto fromz = from.tempCString!FSChar();
+    auto toz = to.tempCString!FSChar();
+
+    static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+        alias f = from;
+    else
+        enum string f = null;
+
+    static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+        alias t = to;
+    else
+        enum string t = null;
+
+    copyImpl(f, t, fromz, toz, preserve);
+}
+
+void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
+    if (isConvertibleToString!RF || isConvertibleToString!RT)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, RF, RT);
+    copy!Types(from, to, preserve);
+}
+
+unittest // issue 15319
+{
+    import std.file : dirEntries;
+    auto fs = dirEntries(getcwd, SpanMode.depth);
+    assert(__traits(compiles, copy(fs.front, fs.front)));
+}
+
+private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz,
+        PreserveAttributes preserve) @trusted
+{
+    version(Windows)
+    {
+        assert(preserve == Yes.preserve);
+        immutable result = CopyFileW(fromz, toz, false);
+        if (!result)
+        {
+            import core.stdc.wchar_ : wcslen;
+            import std.conv : to;
+
+            if (!t)
+                t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
+
+            throw new FileException(t);
+        }
+    }
+    else version(Posix)
+    {
+        import core.stdc.stdio;
+        static import std.conv;
+
+        immutable fd = core.sys.posix.fcntl.open(fromz, O_RDONLY);
+        cenforce(fd != -1, f, fromz);
+        scope(exit) core.sys.posix.unistd.close(fd);
+
+        stat_t statbuf = void;
+        cenforce(fstat(fd, &statbuf) == 0, f, fromz);
+        //cenforce(core.sys.posix.sys.stat.fstat(fd, &statbuf) == 0, f, fromz);
+
+        immutable fdw = core.sys.posix.fcntl.open(toz,
+                O_CREAT | O_WRONLY | O_TRUNC, octal!666);
+        cenforce(fdw != -1, t, toz);
+        scope(failure) core.stdc.stdio.remove(toz);
+        {
+            scope(failure) core.sys.posix.unistd.close(fdw);
+            auto BUFSIZ = 4096u * 16;
+            auto buf = core.stdc.stdlib.malloc(BUFSIZ);
+            if (!buf)
+            {
+                BUFSIZ = 4096;
+                buf = core.stdc.stdlib.malloc(BUFSIZ);
+                if (!buf)
+                {
+                    import core.exception : onOutOfMemoryError;
+                    onOutOfMemoryError();
+                }
+            }
+            scope(exit) core.stdc.stdlib.free(buf);
+
+            for (auto size = statbuf.st_size; size; )
+            {
+                immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
+                cenforce(
+                    core.sys.posix.unistd.read(fd, buf, toxfer) == toxfer
+                    && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
+                    f, fromz);
+                assert(size >= toxfer);
+                size -= toxfer;
+            }
+            if (preserve)
+                cenforce(fchmod(fdw, std.conv.to!mode_t(statbuf.st_mode)) == 0, f, fromz);
+        }
+
+        cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
+
+        utimbuf utim = void;
+        utim.actime = cast(time_t)statbuf.st_atime;
+        utim.modtime = cast(time_t)statbuf.st_mtime;
+
+        cenforce(utime(toz, &utim) != -1, f, fromz);
+    }
+}
+
+unittest
+{
+    import std.algorithm, std.file; // issue 14817
+    auto t1 = deleteme, t2 = deleteme~"2";
+    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+    write(t1, "1");
+    copy(t1, t2);
+    assert(readText(t2) == "1");
+    write(t1, "2");
+    copy(t1, t2);
+    assert(readText(t2) == "2");
+
+    import std.utf : byChar;
+    copy(t1.byChar, t2.byChar);
+    assert(readText(t2.byChar) == "2");
+}
+
+version(Posix) unittest //issue 11434
+{
+    auto t1 = deleteme, t2 = deleteme~"2";
+    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+    write(t1, "1");
+    setAttributes(t1, octal!767);
+    copy(t1, t2, Yes.preserveAttributes);
+    assert(readText(t2) == "1");
+    assert(getAttributes(t2) == octal!100767);
+}
+
+/++
+    Remove directory and all of its content and subdirectories,
+    recursively.
+
+    Throws:
+        $(D FileException) if there is an error (including if the given
+        file is not a directory).
+ +/
+void rmdirRecurse(in char[] pathname)
+{
+    //No references to pathname will be kept after rmdirRecurse,
+    //so the cast is safe
+    rmdirRecurse(DirEntry(cast(string)pathname));
+}
+
+/++
+    Remove directory and all of its content and subdirectories,
+    recursively.
+
+    Throws:
+        $(D FileException) if there is an error (including if the given
+        file is not a directory).
+ +/
+void rmdirRecurse(ref DirEntry de)
+{
+    if(!de.isDir)
+        throw new FileException(de.name, "Not a directory");
+
+    if (de.isSymlink)
+    {
+        version (Windows)
+            rmdir(de.name);
+        else
+            remove(de.name);
+    }
+    else
+    {
+        // all children, recursively depth-first
+        foreach(DirEntry e; dirEntries(de.name, SpanMode.depth, false))
+        {
+            attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
+        }
+
+        // the dir itself
+        rmdir(de.name);
+    }
+}
+///ditto
+//Note, without this overload, passing an RValue DirEntry still works, but
+//actually fully reconstructs a DirEntry inside the
+//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
+//expensive.
+//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
+void rmdirRecurse(DirEntry de)
+{
+    rmdirRecurse(de);
+}
+
+version(Windows) unittest
+{
+    auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
+    mkdirRecurse(d);
+    rmdirRecurse(deleteme ~ ".dir");
+    enforce(!exists(deleteme ~ ".dir"));
+}
+
+version(Posix) unittest
+{
+    import std.process: executeShell;
+    collectException(rmdirRecurse(deleteme));
+    auto d = deleteme~"/a/b/c/d/e/f/g";
+    enforce(collectException(mkdir(d)));
+    mkdirRecurse(d);
+    core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
+            (deleteme~"/link\0").ptr);
+    rmdirRecurse(deleteme~"/link");
+    enforce(exists(d));
+    rmdirRecurse(deleteme);
+    enforce(!exists(deleteme));
+
+    d = deleteme~"/a/b/c/d/e/f/g";
+    mkdirRecurse(d);
+    version(Android) string link_cmd = "ln -s ";
+    else string link_cmd = "ln -sf ";
+    executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link");
+    rmdirRecurse(deleteme);
+    enforce(!exists(deleteme));
+}
+
+unittest
+{
+    void[] buf;
+
+    buf = new void[10];
+    (cast(byte[])buf)[] = 3;
+    string unit_file = deleteme ~ "-unittest_write.tmp";
+    if (exists(unit_file)) remove(unit_file);
+    write(unit_file, buf);
+    void[] buf2 = read(unit_file);
+    assert(buf == buf2);
+
+    string unit2_file = deleteme ~ "-unittest_write2.tmp";
+    copy(unit_file, unit2_file);
+    buf2 = read(unit2_file);
+    assert(buf == buf2);
+
+    remove(unit_file);
+    assert(!exists(unit_file));
+    remove(unit2_file);
+    assert(!exists(unit2_file));
+}
+
+/**
+ * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
+ */
+enum SpanMode
+{
+    /** Only spans one directory. */
+    shallow,
+    /** Spans the directory depth-first, i.e. the content of any
+     subdirectory is spanned before that subdirectory itself. Useful
+     e.g. when recursively deleting files.  */
+    depth,
+    /** Spans the directory breadth-first, i.e. the content of any
+     subdirectory is spanned right after that subdirectory itself. */
+    breadth,
+}
+
+private struct DirIteratorImpl
+{
+    import std.array : Appender, appender;
+    SpanMode _mode;
+    // Whether we should follow symlinked directories while iterating.
+    // It also indicates whether we should avoid functions which call
+    // stat (since we should only need lstat in this case and it would
+    // be more efficient to not call stat in addition to lstat).
+    bool _followSymlink;
+    DirEntry _cur;
+    Appender!(DirHandle[]) _stack;
+    Appender!(DirEntry[]) _stashed; //used in depth first mode
+    //stack helpers
+    void pushExtra(DirEntry de){ _stashed.put(de); }
+    //ditto
+    bool hasExtra(){ return !_stashed.data.empty; }
+    //ditto
+    DirEntry popExtra()
+    {
+        DirEntry de;
+        de = _stashed.data[$-1];
+        _stashed.shrinkTo(_stashed.data.length - 1);
+        return de;
+
+    }
+    version(Windows)
+    {
+        struct DirHandle
+        {
+            string dirpath;
+            HANDLE h;
+        }
+
+        bool stepIn(string directory)
+        {
+            auto search_pattern = chainPath(directory, "*.*");
+            WIN32_FIND_DATAW findinfo;
+            HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo);
+            cenforce(h != INVALID_HANDLE_VALUE, directory);
+            _stack.put(DirHandle(directory, h));
+            return toNext(false, &findinfo);
+        }
+
+        bool next()
+        {
+            if(_stack.data.empty)
+                return false;
+            WIN32_FIND_DATAW findinfo;
+            return toNext(true, &findinfo);
+        }
+
+        bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo)
+        {
+            import core.stdc.wchar_ : wcscmp;
+
+            if(fetch)
+            {
+                if(FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+                {
+                    popDirStack();
+                    return false;
+                }
+            }
+            while( wcscmp(findinfo.cFileName.ptr, ".") == 0
+                    || wcscmp(findinfo.cFileName.ptr, "..") == 0)
+                if(FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+                {
+                    popDirStack();
+                    return false;
+                }
+            _cur = DirEntry(_stack.data[$-1].dirpath, findinfo);
+            return true;
+        }
+
+        void popDirStack()
+        {
+            assert(!_stack.data.empty);
+            FindClose(_stack.data[$-1].h);
+            _stack.shrinkTo(_stack.data.length-1);
+        }
+
+        void releaseDirStack()
+        {
+            foreach( d;  _stack.data)
+                FindClose(d.h);
+        }
+
+        bool mayStepIn()
+        {
+            return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
+        }
+    }
+    else version(Posix)
+    {
+        struct DirHandle
+        {
+            string dirpath;
+            DIR*   h;
+        }
+
+        bool stepIn(string directory)
+        {
+            auto h = directory.length ? opendir(directory.tempCString()) : opendir(".");
+            cenforce(h, directory);
+            _stack.put(DirHandle(directory, h));
+            return next();
+        }
+
+        bool next()
+        {
+            if(_stack.data.empty)
+                return false;
+            for(dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; )
+            {
+                // Skip "." and ".."
+                if(core.stdc.string.strcmp(fdata.d_name.ptr, ".")  &&
+                   core.stdc.string.strcmp(fdata.d_name.ptr, "..") )
+                {
+                    _cur = DirEntry(_stack.data[$-1].dirpath, fdata);
+                    return true;
+                }
+            }
+            popDirStack();
+            return false;
+        }
+
+        void popDirStack()
+        {
+            assert(!_stack.data.empty);
+            closedir(_stack.data[$-1].h);
+            _stack.shrinkTo(_stack.data.length-1);
+        }
+
+        void releaseDirStack()
+        {
+            foreach( d;  _stack.data)
+                closedir(d.h);
+        }
+
+        bool mayStepIn()
+        {
+            return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
+        }
+    }
+
+    this(R)(R pathname, SpanMode mode, bool followSymlink)
+        if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
+    {
+        _mode = mode;
+        _followSymlink = followSymlink;
+        _stack = appender(cast(DirHandle[])[]);
+        if (_mode == SpanMode.depth)
+            _stashed = appender(cast(DirEntry[])[]);
+
+        static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+            alias pathnameStr = pathname;
+        else
+        {
+            import std.array;
+            string pathnameStr = pathname.array;
+        }
+        if (stepIn(pathnameStr))
+        {
+            if(_mode == SpanMode.depth)
+                while(mayStepIn())
+                {
+                    auto thisDir = _cur;
+                    if(stepIn(_cur.name))
+                    {
+                        pushExtra(thisDir);
+                    }
+                    else
+                        break;
+                }
+        }
+    }
+    @property bool empty(){ return _stashed.data.empty && _stack.data.empty; }
+    @property DirEntry front(){ return _cur; }
+    void popFront()
+    {
+        switch(_mode)
+        {
+        case SpanMode.depth:
+            if(next())
+            {
+                while(mayStepIn())
+                {
+                    auto thisDir = _cur;
+                    if(stepIn(_cur.name))
+                    {
+                        pushExtra(thisDir);
+                    }
+                    else
+                        break;
+                }
+            }
+            else if(hasExtra())
+                _cur = popExtra();
+            break;
+        case SpanMode.breadth:
+            if(mayStepIn())
+            {
+                if(!stepIn(_cur.name))
+                    while(!empty && !next()){}
+            }
+            else
+                while(!empty && !next()){}
+            break;
+        default:
+            next();
+        }
+    }
+
+    ~this()
+    {
+        releaseDirStack();
+    }
+}
+
+struct DirIterator
+{
+private:
+    RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
+    this(string pathname, SpanMode mode, bool followSymlink)
+    {
+        impl = typeof(impl)(pathname, mode, followSymlink);
+    }
+public:
+    @property bool empty(){ return impl.empty; }
+    @property DirEntry front(){ return impl.front; }
+    void popFront(){ impl.popFront(); }
+
+}
+/++
+    Returns an input range of DirEntry that lazily iterates a given directory,
+    also provides two ways of foreach iteration. The iteration variable can be of
+    type $(D_PARAM string) if only the name is needed, or $(D_PARAM DirEntry)
+    if additional details are needed. The span mode dictates the how the
+    directory is traversed. The name of the each directory entry iterated
+    contains the absolute path.
+
+    Params:
+        path = The directory to iterate over.
+               If empty, the current directory will be iterated.
+        mode = Whether the directory's sub-directories should be iterated
+               over depth-first ($(D_PARAM depth)), breadth-first
+               ($(D_PARAM breadth)), or not at all ($(D_PARAM shallow)).
+        followSymlink = Whether symbolic links which point to directories
+                         should be treated as directories and their contents
+                         iterated over.
+
+    Throws:
+        $(D FileException) if the directory does not exist.
+
+Example:
+--------------------
+// Iterate a directory in depth
+foreach (string name; dirEntries("destroy/me", SpanMode.depth))
+{
+ remove(name);
+}
+// Iterate the current directory in breadth
+foreach (string name; dirEntries("", SpanMode.breadth))
+{
+ writeln(name);
+}
+// Iterate a directory and get detailed info about it
+foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
+{
+ writeln(e.name, "\t", e.size);
+}
+// Iterate over all *.d files in current directory and all its subdirectories
+auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
+foreach(d; dFiles)
+    writeln(d.name);
+// Hook it up with std.parallelism to compile them all in parallel:
+foreach(d; parallel(dFiles, 1)) //passes by 1 file to each thread
+{
+    string cmd = "dmd -c "  ~ d.name;
+    writeln(cmd);
+    std.process.system(cmd);
+}
+--------------------
+ +/
+auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
+{
+    return DirIterator(path, mode, followSymlink);
+}
+
+/// Duplicate functionality of D1's $(D std.file.listdir()):
+unittest
+{
+    string[] listdir(string pathname)
+    {
+        import std.file;
+        import std.path;
+        import std.algorithm;
+        import std.array;
+
+        return std.file.dirEntries(pathname, SpanMode.shallow)
+            .filter!(a => a.isFile)
+            .map!(a => std.path.baseName(a.name))
+            .array;
+    }
+
+    void main(string[] args)
+    {
+        import std.stdio;
+
+        string[] files = listdir(args[1]);
+        writefln("%s", files);
+     }
+}
+
+unittest
+{
+    import std.algorithm;
+    import std.range;
+    import std.process;
+    version(Android)
+        string testdir = deleteme; // This has to be an absolute path when
+                                   // called from a shared library on Android,
+                                   // ie an apk
+    else
+        string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative
+    mkdirRecurse(buildPath(testdir, "somedir"));
+    scope(exit) rmdirRecurse(testdir);
+    write(buildPath(testdir, "somefile"), null);
+    write(buildPath(testdir, "somedir", "somedeepfile"), null);
+
+    // testing range interface
+    size_t equalEntries(string relpath, SpanMode mode)
+    {
+        auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
+        assert(walkLength(dirEntries(relpath, mode)) == len);
+        assert(equal(
+                   map!(a => std.path.absolutePath(a.name))(dirEntries(relpath, mode)),
+                   map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
+        return len;
+    }
+
+    assert(equalEntries(testdir, SpanMode.shallow) == 2);
+    assert(equalEntries(testdir, SpanMode.depth) == 3);
+    assert(equalEntries(testdir, SpanMode.breadth) == 3);
+
+    // testing opApply
+    foreach (string name; dirEntries(testdir, SpanMode.breadth))
+    {
+        //writeln(name);
+        assert(name.startsWith(testdir));
+    }
+    foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
+    {
+        //writeln(name);
+        assert(e.isFile || e.isDir, e.name);
+    }
+
+    //issue 7264
+    foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
+    {
+
+    }
+    foreach (entry; dirEntries(testdir, SpanMode.breadth))
+    {
+        static assert(is(typeof(entry) == DirEntry));
+    }
+    //issue 7138
+    auto a = array(dirEntries(testdir, SpanMode.shallow));
+
+    // issue 11392
+    auto dFiles = dirEntries(testdir, SpanMode.shallow);
+    foreach(d; dFiles){}
+
+    // issue 15146
+    dirEntries("", SpanMode.shallow).walkLength();
+}
+
+/++
+    Convenience wrapper for filtering file names with a glob pattern.
+
+    Params:
+        path = The directory to iterate over.
+        pattern  = String with wildcards, such as $(RED "*.d"). The supported
+                   wildcard strings are described under
+                   $(XREF _path, globMatch).
+        mode = Whether the directory's sub-directories should be iterated
+               over depth-first ($(D_PARAM depth)), breadth-first
+               ($(D_PARAM breadth)), or not at all ($(D_PARAM shallow)).
+        followSymlink = Whether symbolic links which point to directories
+                         should be treated as directories and their contents
+                         iterated over.
+
+    Throws:
+        $(D FileException) if the directory does not exist.
+
+Example:
+--------------------
+// Iterate over all D source files in current directory and all its
+// subdirectories
+auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
+foreach(d; dFiles)
+    writeln(d.name);
+--------------------
+ +/
+auto dirEntries(string path, string pattern, SpanMode mode,
+    bool followSymlink = true)
+{
+    import std.algorithm : filter;
+    bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
+    return filter!f(DirIterator(path, mode, followSymlink));
+}
+
+unittest
+{
+    import std.stdio : writefln;
+    immutable dpath = deleteme ~ "_dir";
+    immutable fpath = deleteme ~ "_file";
+    immutable sdpath = deleteme ~ "_sdir";
+    immutable sfpath = deleteme ~ "_sfile";
+    scope(exit)
+    {
+        if (dpath.exists) rmdirRecurse(dpath);
+        if (fpath.exists) remove(fpath);
+        if (sdpath.exists) remove(sdpath);
+        if (sfpath.exists) remove(sfpath);
+    }
+
+    mkdir(dpath);
+    write(fpath, "hello world");
+    version (Posix)
+    {
+        core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
+        core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
+    }
+
+    static struct Flags { bool dir, file, link; }
+    auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
+    version (Posix)
+    {
+        tests[sdpath] = Flags(true, false, true);
+        tests[sfpath] = Flags(false, true, true);
+    }
+
+    auto past = Clock.currTime() - 2.seconds;
+    auto future = past + 4.seconds;
+
+    foreach (path, flags; tests)
+    {
+        auto de = DirEntry(path);
+        assert(de.name == path);
+        assert(de.isDir == flags.dir);
+        assert(de.isFile == flags.file);
+        assert(de.isSymlink == flags.link);
+
+        assert(de.isDir == path.isDir);
+        assert(de.isFile == path.isFile);
+        assert(de.isSymlink == path.isSymlink);
+        assert(de.size == path.getSize());
+        assert(de.attributes == getAttributes(path));
+        assert(de.linkAttributes == getLinkAttributes(path));
+
+        scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
+        assert(de.timeLastAccessed > past);
+        assert(de.timeLastAccessed < future);
+        assert(de.timeLastModified > past);
+        assert(de.timeLastModified < future);
+
+        assert(attrIsDir(de.attributes) == flags.dir);
+        assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
+        assert(attrIsFile(de.attributes) == flags.file);
+        assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
+        assert(!attrIsSymlink(de.attributes));
+        assert(attrIsSymlink(de.linkAttributes) == flags.link);
+
+        version(Windows)
+        {
+            assert(de.timeCreated > past);
+            assert(de.timeCreated < future);
+        }
+        else version(Posix)
+        {
+            assert(de.timeStatusChanged > past);
+            assert(de.timeStatusChanged < future);
+            assert(de.attributes == de.statBuf.st_mode);
+        }
+    }
+}
+
+
+/**
+Reads an entire file into an array.
+ */
+Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
+slurp(Types...)(string filename, in char[] format)
+{
+    import std.stdio : File;
+    import std.format : formattedRead;
+    import std.array : appender;
+    typeof(return) result;
+    auto app = appender!(typeof(return))();
+    ElementType!(typeof(return)) toAdd;
+    auto f = File(filename);
+    scope(exit) f.close();
+    foreach (line; f.byLine())
+    {
+        formattedRead(line, format, &toAdd);
+        enforce(line.empty,
+                text("Trailing characters at the end of line: `", line,
+                        "'"));
+        app.put(toAdd);
+    }
+    return app.data;
+}
+
+///
+unittest
+{
+    scope(exit)
+    {
+        assert(exists("someUniqueFilename"));
+        remove("someUniqueFilename");
+    }
+
+    write("someUniqueFilename", "12 12.25\n345 1.125");
+
+    // Load file; each line is an int followed by comma, whitespace and a
+    // double.
+    auto a = slurp!(int, double)("someUniqueFilename", "%s %s");
+    assert(a.length == 2);
+    assert(a[0] == tuple(12, 12.25));
+    assert(a[1] == tuple(345, 1.125));
+}
+
+
+/**
+Returns the path to a directory for temporary files.
+
+On Windows, this function returns the result of calling the Windows API function
+$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
+
+On POSIX platforms, it searches through the following list of directories
+and returns the first one which is found to exist:
+$(OL
+    $(LI The directory given by the $(D TMPDIR) environment variable.)
+    $(LI The directory given by the $(D TEMP) environment variable.)
+    $(LI The directory given by the $(D TMP) environment variable.)
+    $(LI $(D /tmp))
+    $(LI $(D /var/tmp))
+    $(LI $(D /usr/tmp))
+)
+
+On all platforms, $(D tempDir) returns $(D ".") on failure, representing
+the current working directory.
+
+The return value of the function is cached, so the procedures described
+above will only be performed the first time the function is called.  All
+subsequent runs will return the same string, regardless of whether
+environment variables and directory structures have changed in the
+meantime.
+
+The POSIX $(D tempDir) algorithm is inspired by Python's
+$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
+*/
+string tempDir() @trusted
+{
+    static string cache;
+    if (cache is null)
+    {
+        version(Windows)
+        {
+            import std.utf : toUTF8;
+            // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
+            wchar[MAX_PATH + 2] buf;
+            DWORD len = GetTempPathW(buf.length, buf.ptr);
+            if (len) cache = toUTF8(buf[0 .. len]);
+        }
+        else version(Android)
+        {
+            // Don't check for a global temporary directory as
+            // Android doesn't have one.
+        }
+        else version(Posix)
+        {
+            import std.process : environment;
+            // This function looks through the list of alternative directories
+            // and returns the first one which exists and is a directory.
+            static string findExistingDir(T...)(lazy T alternatives)
+            {
+                foreach (dir; alternatives)
+                    if (!dir.empty && exists(dir)) return dir;
+                return null;
+            }
+
+            cache = findExistingDir(environment.get("TMPDIR"),
+                                    environment.get("TEMP"),
+                                    environment.get("TMP"),
+                                    "/tmp",
+                                    "/var/tmp",
+                                    "/usr/tmp");
+        }
+        else static assert (false, "Unsupported platform");
+
+        if (cache is null) cache = getcwd();
+    }
+    return cache;
+}
diff --git a/libphobos/src/std/format.d b/libphobos/src/std/format.d
new file mode 100644
index 00000000000..d145498b2cd
--- /dev/null
+++ b/libphobos/src/std/format.d
@@ -0,0 +1,6547 @@
+// Written in the D programming language.
+
+/**
+   This module implements the formatting functionality for strings and
+   I/O. It's comparable to C99's $(D vsprintf()) and uses a similar
+   _format encoding scheme.
+
+   For an introductory look at $(B std._format)'s capabilities and how to use
+   this module see the dedicated
+   $(LINK2 http://wiki.dlang.org/Defining_custom_print_format_specifiers, DWiki article).
+
+   This module centers around two functions:
+
+$(BOOKTABLE ,
+$(TR $(TH Function Name) $(TH Description)
+)
+    $(TR $(TD $(D $(LREF formattedRead)))
+        $(TD Reads values according to the _format string from an InputRange.
+    ))
+    $(TR $(TD $(D $(LREF formattedWrite)))
+        $(TD Formats its arguments according to the _format string and puts them
+        to an OutputRange.
+    ))
+)
+
+   Please see the documentation of function $(D $(LREF formattedWrite)) for a
+   description of the _format string.
+
+   Two functions have been added for convenience:
+
+$(BOOKTABLE ,
+$(TR $(TH Function Name) $(TH Description)
+)
+    $(TR $(TD $(D $(LREF _format)))
+        $(TD Returns a GC-allocated string with the formatting result.
+    ))
+    $(TR $(TD $(D $(LREF sformat)))
+        $(TD Puts the formatting result into a preallocated array.
+    ))
+)
+
+   These two functions are publicly imported by $(LINK2 std_string.html,
+   std.string) to be easily available.
+
+   The functions $(D $(LREF formatValue)) and $(D $(LREF unformatValue)) are
+   used for the plumbing.
+
+   Macros: WIKI = Phobos/StdFormat
+
+   Copyright: Copyright Digital Mars 2000-2013.
+
+   License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+   Authors: $(WEB walterbright.com, Walter Bright), $(WEB erdani.com,
+   Andrei Alexandrescu), and Kenji Hara
+
+   Source: $(PHOBOSSRC std/_format.d)
+ */
+module std.format;
+
+//debug=format;                // uncomment to turn on debugging printf's
+
+import core.vararg;
+import std.exception;
+import std.meta;
+import std.range.primitives;
+import std.traits;
+
+version(CRuntime_DigitalMars)
+{
+    version = DigitalMarsC;
+}
+
+version (DigitalMarsC)
+{
+    // This is DMC's internal floating point formatting function
+    extern (C)
+    {
+        extern shared char* function(int c, int flags, int precision,
+                in real* pdval,
+                char* buf, size_t* psl, int width) __pfloatfmt;
+    }
+}
+
+/**********************************************************************
+ * Signals a mismatch between a format and its corresponding argument.
+ */
+class FormatException : Exception
+{
+    @safe pure nothrow
+    this()
+    {
+        super("format error");
+    }
+
+    @safe pure nothrow
+    this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+    {
+        super(msg, fn, ln, next);
+    }
+}
+
+private alias enforceFmt = enforceEx!FormatException;
+
+
+/**********************************************************************
+   Interprets variadic argument list $(D args), formats them according
+   to $(D fmt), and sends the resulting characters to $(D w). The
+   encoding of the output is the same as $(D Char). The type $(D Writer)
+   must satisfy $(D $(XREF_PACK range,primitives,isOutputRange)!(Writer, Char)).
+
+   The variadic arguments are normally consumed in order. POSIX-style
+   $(WEB opengroup.org/onlinepubs/009695399/functions/printf.html,
+   positional parameter syntax) is also supported. Each argument is
+   formatted into a sequence of chars according to the format
+   specification, and the characters are passed to $(D w). As many
+   arguments as specified in the format string are consumed and
+   formatted. If there are fewer arguments than format specifiers, a
+   $(D FormatException) is thrown. If there are more remaining arguments
+   than needed by the format specification, they are ignored but only
+   if at least one argument was formatted.
+
+   The format string supports the formatting of array and nested array elements
+   via the grouping format specifiers $(B %&#40;) and $(B %&#41;). Each
+   matching pair of $(B %&#40;) and $(B %&#41;) corresponds with a single array
+   argument. The enclosed sub-format string is applied to individual array
+   elements.  The trailing portion of the sub-format string following the
+   conversion specifier for the array element is interpreted as the array
+   delimiter, and is therefore omitted following the last array element. The
+   $(B %|) specifier may be used to explicitly indicate the start of the
+   delimiter, so that the preceding portion of the string will be included
+   following the last array element.  (See below for explicit examples.)
+
+   Params:
+
+   w = Output is sent to this writer. Typical output writers include
+   $(XREF array,Appender!string) and $(XREF stdio,LockingTextWriter).
+
+   fmt = Format string.
+
+   args = Variadic argument list.
+
+   Returns: Formatted number of arguments.
+
+   Throws: Mismatched arguments and formats result in a $(D
+   FormatException) being thrown.
+
+   Format_String: <a name="format-string">$(I Format strings)</a>
+   consist of characters interspersed with $(I format
+   specifications). Characters are simply copied to the output (such
+   as putc) after any necessary conversion to the corresponding UTF-8
+   sequence.
+
+   The format string has the following grammar:
+
+$(PRE
+$(I FormatString):
+    $(I FormatStringItem)*
+$(I FormatStringItem):
+    $(B '%%')
+    $(B '%') $(I Position) $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
+    $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)')
+    $(I OtherCharacterExceptPercent)
+$(I Position):
+    $(I empty)
+    $(I Integer) $(B '$')
+$(I Flags):
+    $(I empty)
+    $(B '-') $(I Flags)
+    $(B '+') $(I Flags)
+    $(B '#') $(I Flags)
+    $(B '0') $(I Flags)
+    $(B ' ') $(I Flags)
+$(I Width):
+    $(I empty)
+    $(I Integer)
+    $(B '*')
+$(I Precision):
+    $(I empty)
+    $(B '.')
+    $(B '.') $(I Integer)
+    $(B '.*')
+$(I Integer):
+    $(I Digit)
+    $(I Digit) $(I Integer)
+$(I Digit):
+    $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9')
+$(I FormatChar):
+    $(B 's')|$(B 'c')|$(B 'b')|$(B 'd')|$(B 'o')|$(B 'x')|$(B 'X')|$(B 'e')|$(B 'E')|$(B 'f')|$(B 'F')|$(B 'g')|$(B 'G')|$(B 'a')|$(B 'A')
+)
+
+    $(BOOKTABLE Flags affect formatting depending on the specifier as
+    follows., $(TR $(TH Flag) $(TH Types&nbsp;affected) $(TH Semantics))
+
+    $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in
+        the field.  It overrides any $(B 0) flag.))
+
+    $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in
+    a signed conversion with a $(B +).  It overrides any $(I space)
+    flag.))
+
+    $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to
+    precision as necessary so that the first digit of the octal
+    formatting is a '0', even if both the argument and the $(I
+    Precision) are zero.))
+
+    $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If
+       non-zero, prefix result with $(B 0x) ($(B 0X)).))
+
+    $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal
+       point and print trailing zeros.))
+
+    $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading
+    zeros to pad rather than spaces (except for the floating point
+    values $(D nan) and $(D infinity)).  Ignore if there's a $(I
+    Precision).))
+
+    $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive
+    numbers in a signed conversion with a space.)))
+
+    $(DL
+        $(DT $(I Width))
+        $(DD
+        Specifies the minimum field width.
+        If the width is a $(B *), an additional argument of type $(B int),
+        preceding the actual argument, is taken as the width.
+        If the width is negative, it is as if the $(B -) was given
+        as a $(I Flags) character.)
+
+        $(DT $(I Precision))
+        $(DD Gives the precision for numeric conversions.
+        If the precision is a $(B *), an additional argument of type $(B int),
+        preceding the actual argument, is taken as the precision.
+        If it is negative, it is as if there was no $(I Precision) specifier.)
+
+        $(DT $(I FormatChar))
+        $(DD
+        $(DL
+            $(DT $(B 's'))
+            $(DD The corresponding argument is formatted in a manner consistent
+            with its type:
+            $(DL
+                $(DT $(B bool))
+                $(DD The result is $(D "true") or $(D "false").)
+                $(DT integral types)
+                $(DD The $(B %d) format is used.)
+                $(DT floating point types)
+                $(DD The $(B %g) format is used.)
+                $(DT string types)
+                $(DD The result is the string converted to UTF-8.
+                A $(I Precision) specifies the maximum number of characters
+                to use in the result.)
+                $(DT structs)
+                $(DD If the struct defines a $(B toString()) method the result is
+                the string returned from this function. Otherwise the result is
+                StructName(field<sub>0</sub>, field<sub>1</sub>, ...) where
+                field<sub>n</sub> is the nth element formatted with the default
+                format.)
+                $(DT classes derived from $(B Object))
+                $(DD The result is the string returned from the class instance's
+                $(B .toString()) method.
+                A $(I Precision) specifies the maximum number of characters
+                to use in the result.)
+                $(DT unions)
+                $(DD If the union defines a $(B toString()) method the result is
+                the string returned from this function. Otherwise the result is
+                the name of the union, without its contents.)
+                $(DT non-string static and dynamic arrays)
+                $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
+                where s<sub>n</sub> is the nth element
+                formatted with the default format.)
+                $(DT associative arrays)
+                $(DD The result is the equivalent of what the initializer
+                would look like for the contents of the associative array,
+                e.g.: ["red" : 10, "blue" : 20].)
+            ))
+
+            $(DT $(B 'c'))
+            $(DD The corresponding argument must be a character type.)
+
+            $(DT $(B 'b','d','o','x','X'))
+            $(DD The corresponding argument must be an integral type
+            and is formatted as an integer. If the argument is a signed type
+            and the $(I FormatChar) is $(B d) it is converted to
+            a signed string of characters, otherwise it is treated as
+            unsigned. An argument of type $(B bool) is formatted as '1'
+            or '0'. The base used is binary for $(B b), octal for $(B o),
+            decimal
+            for $(B d), and hexadecimal for $(B x) or $(B X).
+            $(B x) formats using lower case letters, $(B X) uppercase.
+            If there are fewer resulting digits than the $(I Precision),
+            leading zeros are used as necessary.
+            If the $(I Precision) is 0 and the number is 0, no digits
+            result.)
+
+            $(DT $(B 'e','E'))
+            $(DD A floating point number is formatted as one digit before
+            the decimal point, $(I Precision) digits after, the $(I FormatChar),
+            &plusmn;, followed by at least a two digit exponent:
+            $(I d.dddddd)e$(I &plusmn;dd).
+            If there is no $(I Precision), six
+            digits are generated after the decimal point.
+            If the $(I Precision) is 0, no decimal point is generated.)
+
+            $(DT $(B 'f','F'))
+            $(DD A floating point number is formatted in decimal notation.
+            The $(I Precision) specifies the number of digits generated
+            after the decimal point. It defaults to six. At least one digit
+            is generated before the decimal point. If the $(I Precision)
+            is zero, no decimal point is generated.)
+
+            $(DT $(B 'g','G'))
+            $(DD A floating point number is formatted in either $(B e) or
+            $(B f) format for $(B g); $(B E) or $(B F) format for
+            $(B G).
+            The $(B f) format is used if the exponent for an $(B e) format
+            is greater than -5 and less than the $(I Precision).
+            The $(I Precision) specifies the number of significant
+            digits, and defaults to six.
+            Trailing zeros are elided after the decimal point, if the fractional
+            part is zero then no decimal point is generated.)
+
+            $(DT $(B 'a','A'))
+            $(DD A floating point number is formatted in hexadecimal
+            exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
+            There is one hexadecimal digit before the decimal point, and as
+            many after as specified by the $(I Precision).
+            If the $(I Precision) is zero, no decimal point is generated.
+            If there is no $(I Precision), as many hexadecimal digits as
+            necessary to exactly represent the mantissa are generated.
+            The exponent is written in as few digits as possible,
+            but at least one, is in decimal, and represents a power of 2 as in
+            $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
+            The exponent for zero is zero.
+            The hexadecimal digits, x and p are in upper case if the
+            $(I FormatChar) is upper case.)
+        ))
+    )
+
+    Floating point NaN's are formatted as $(B nan) if the
+    $(I FormatChar) is lower case, or $(B NAN) if upper.
+    Floating point infinities are formatted as $(B inf) or
+    $(B infinity) if the
+    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
+
+    Example:
+    -----------------
+    import std.array : appender;
+    import std.format : formattedWrite;
+
+    auto writer = appender!string();
+    formattedWrite(writer, "%s is the ultimate %s.", 42, "answer");
+    assert(writer.data == "42 is the ultimate answer.");
+    // Clear the writer
+    writer = appender!string();
+    formattedWrite(writer, "Date: %2$s %1$s", "October", 5);
+    assert(writer.data == "Date: 5 October");
+    -----------------
+
+    The positional and non-positional styles can be mixed in the same
+    format string. (POSIX leaves this behavior undefined.) The internal
+    counter for non-positional parameters tracks the next parameter after
+    the largest positional parameter already used.
+
+    Example using array and nested array formatting:
+    -------------------------
+    import std.stdio;
+
+    void main()
+    {
+        writefln("My items are %(%s %).", [1,2,3]);
+        writefln("My items are %(%s, %).", [1,2,3]);
+    }
+    -------------------------
+    The output is:
+$(CONSOLE
+My items are 1 2 3.
+My items are 1, 2, 3.
+)
+
+    The trailing end of the sub-format string following the specifier for each
+    item is interpreted as the array delimiter, and is therefore omitted
+    following the last array item. The $(B %|) delimiter specifier may be used
+    to indicate where the delimiter begins, so that the portion of the format
+    string prior to it will be retained in the last array element:
+    -------------------------
+    import std.stdio;
+
+    void main()
+    {
+        writefln("My items are %(-%s-%|, %).", [1,2,3]);
+    }
+    -------------------------
+    which gives the output:
+$(CONSOLE
+My items are -1-, -2-, -3-.
+)
+
+    These compound format specifiers may be nested in the case of a nested
+    array argument:
+    -------------------------
+    import std.stdio;
+    void main() {
+         auto mat = [[1, 2, 3],
+                     [4, 5, 6],
+                     [7, 8, 9]];
+
+         writefln("%(%(%d %)\n%)", mat);
+         writeln();
+
+         writefln("[%(%(%d %)\n %)]", mat);
+         writeln();
+
+         writefln("[%([%(%d %)]%|\n %)]", mat);
+         writeln();
+    }
+    -------------------------
+    The output is:
+$(CONSOLE
+1 2 3
+4 5 6
+7 8 9
+
+[1 2 3
+ 4 5 6
+ 7 8 9]
+
+[[1 2 3]
+ [4 5 6]
+ [7 8 9]]
+)
+
+    Inside a compound format specifier, strings and characters are escaped
+    automatically. To avoid this behavior, add $(B '-') flag to
+    $(D "%$(LPAREN)").
+    -------------------------
+    import std.stdio;
+
+    void main()
+    {
+        writefln("My friends are %s.", ["John", "Nancy"]);
+        writefln("My friends are %(%s, %).", ["John", "Nancy"]);
+        writefln("My friends are %-(%s, %).", ["John", "Nancy"]);
+    }
+    -------------------------
+   which gives the output:
+$(CONSOLE
+My friends are ["John", "Nancy"].
+My friends are "John", "Nancy".
+My friends are John, Nancy.
+)
+ */
+uint formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args)
+{
+    import std.conv : text, to;
+
+    alias FPfmt = void function(Writer, const(void)*, ref FormatSpec!Char) @safe pure nothrow;
+
+    auto spec = FormatSpec!Char(fmt);
+
+    FPfmt[A.length] funs;
+    const(void)*[A.length] argsAddresses;
+    if (!__ctfe)
+    {
+        foreach (i, Arg; A)
+        {
+            funs[i] = ()@trusted{ return cast(FPfmt)&formatGeneric!(Writer, Arg, Char); }();
+            // We can safely cast away shared because all data is either
+            // immutable or completely owned by this function.
+            argsAddresses[i] = (ref arg)@trusted{ return cast(const void*) &arg; }(args[i]);
+
+            // Reflect formatting @safe/pure ability of each arguments to this function
+            if (0) formatValue(w, args[i], spec);
+        }
+    }
+
+    // Are we already done with formats? Then just dump each parameter in turn
+    uint currentArg = 0;
+    while (spec.writeUpToNextSpec(w))
+    {
+        if (currentArg == funs.length && !spec.indexStart)
+        {
+            // leftover spec?
+            enforceFmt(fmt.length == 0,
+                text("Orphan format specifier: %", spec.spec));
+            break;
+        }
+        if (spec.width == spec.DYNAMIC)
+        {
+            auto width = to!(typeof(spec.width))(getNthInt(currentArg, args));
+            if (width < 0)
+            {
+                spec.flDash = true;
+                width = -width;
+            }
+            spec.width = width;
+            ++currentArg;
+        }
+        else if (spec.width < 0)
+        {
+            // means: get width as a positional parameter
+            auto index = cast(uint) -spec.width;
+            assert(index > 0);
+            auto width = to!(typeof(spec.width))(getNthInt(index - 1, args));
+            if (currentArg < index) currentArg = index;
+            if (width < 0)
+            {
+                spec.flDash = true;
+                width = -width;
+            }
+            spec.width = width;
+        }
+        if (spec.precision == spec.DYNAMIC)
+        {
+            auto precision = to!(typeof(spec.precision))(
+                getNthInt(currentArg, args));
+            if (precision >= 0) spec.precision = precision;
+            // else negative precision is same as no precision
+            else spec.precision = spec.UNSPECIFIED;
+            ++currentArg;
+        }
+        else if (spec.precision < 0)
+        {
+            // means: get precision as a positional parameter
+            auto index = cast(uint) -spec.precision;
+            assert(index > 0);
+            auto precision = to!(typeof(spec.precision))(
+                getNthInt(index- 1, args));
+            if (currentArg < index) currentArg = index;
+            if (precision >= 0) spec.precision = precision;
+            // else negative precision is same as no precision
+            else spec.precision = spec.UNSPECIFIED;
+        }
+        // Format!
+        if (spec.indexStart > 0)
+        {
+            // using positional parameters!
+
+            // Make the conditional compilation of this loop explicit, to avoid "statement not reachable" warnings.
+            static if(A.length > 0)
+            {
+                foreach (i; spec.indexStart - 1 .. spec.indexEnd)
+                {
+                    if (funs.length <= i) break;
+                    if (__ctfe)
+                        formatNth(w, spec, i, args);
+                    else
+                        funs[i](w, argsAddresses[i], spec);
+                }
+            }
+            if (currentArg < spec.indexEnd) currentArg = spec.indexEnd;
+        }
+        else
+        {
+            if (__ctfe)
+                formatNth(w, spec, currentArg, args);
+            else
+                funs[currentArg](w, argsAddresses[currentArg], spec);
+            ++currentArg;
+        }
+    }
+    return currentArg;
+}
+
+@safe pure unittest
+{
+    import std.array;
+    auto w = appender!string();
+    formattedWrite(w, "%s %d", "@safe/pure", 42);
+    assert(w.data == "@safe/pure 42");
+}
+
+/**
+Reads characters from input range $(D r), converts them according
+to $(D fmt), and writes them to $(D args).
+
+Params:
+    r = The range to read from.
+    fmt = The format of the data to read.
+    args = The drain of the data read.
+
+Returns:
+
+On success, the function returns the number of variables filled. This count
+can match the expected number of readings or fewer, even zero, if a
+matching failure happens.
+ */
+uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, S args)
+{
+    import std.typecons : isTuple;
+
+    auto spec = FormatSpec!Char(fmt);
+    static if (!S.length)
+    {
+        spec.readUpToNextSpec(r);
+        enforce(spec.trailing.empty, "Trailing characters in formattedRead format string");
+        return 0;
+    }
+    else
+    {
+        // The function below accounts for '*' == fields meant to be
+        // read and skipped
+        void skipUnstoredFields()
+        {
+            for (;;)
+            {
+                spec.readUpToNextSpec(r);
+                if (spec.width != spec.DYNAMIC) break;
+                // must skip this field
+                skipData(r, spec);
+            }
+        }
+
+        skipUnstoredFields();
+        if (r.empty)
+        {
+            // Input is empty, nothing to read
+            return 0;
+        }
+        alias A = typeof(*args[0]);
+        static if (isTuple!A)
+        {
+            foreach (i, T; A.Types)
+            {
+                (*args[0])[i] = unformatValue!(T)(r, spec);
+                skipUnstoredFields();
+            }
+        }
+        else
+        {
+            *args[0] = unformatValue!(A)(r, spec);
+        }
+        return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
+    }
+}
+
+///
+unittest
+{
+    string s = "hello!124:34.5";
+    string a;
+    int b;
+    double c;
+    formattedRead(s, "%s!%s:%s", &a, &b, &c);
+    assert(a == "hello" && b == 124 && c == 34.5);
+}
+
+unittest
+{
+    import std.math;
+    string s = " 1.2 3.4 ";
+    double x, y, z;
+    assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
+    assert(s.empty);
+    assert(approxEqual(x, 1.2));
+    assert(approxEqual(y, 3.4));
+    assert(isNaN(z));
+}
+
+template FormatSpec(Char)
+    if (!is(Unqual!Char == Char))
+{
+    alias FormatSpec = FormatSpec!(Unqual!Char);
+}
+
+/**
+ * A General handler for $(D printf) style format specifiers. Used for building more
+ * specific formatting functions.
+ */
+struct FormatSpec(Char)
+    if (is(Unqual!Char == Char))
+{
+    import std.ascii : isDigit;
+    import std.algorithm : startsWith;
+    import std.conv : parse, text, to;
+
+    /**
+       Minimum _width, default $(D 0).
+     */
+    int width = 0;
+
+    /**
+       Precision. Its semantics depends on the argument type. For
+       floating point numbers, _precision dictates the number of
+       decimals printed.
+     */
+    int precision = UNSPECIFIED;
+
+    /**
+       Special value for width and precision. $(D DYNAMIC) width or
+       precision means that they were specified with $(D '*') in the
+       format string and are passed at runtime through the varargs.
+     */
+    enum int DYNAMIC = int.max;
+
+    /**
+       Special value for precision, meaning the format specifier
+       contained no explicit precision.
+     */
+    enum int UNSPECIFIED = DYNAMIC - 1;
+
+    /**
+       The actual format specifier, $(D 's') by default.
+    */
+    char spec = 's';
+
+    /**
+       Index of the argument for positional parameters, from $(D 1) to
+       $(D ubyte.max). ($(D 0) means not used).
+    */
+    ubyte indexStart;
+
+    /**
+       Index of the last argument for positional parameter range, from
+       $(D 1) to $(D ubyte.max). ($(D 0) means not used).
+    */
+    ubyte indexEnd;
+
+    version(StdDdoc)
+    {
+        /**
+         The format specifier contained a $(D '-') ($(D printf)
+         compatibility).
+         */
+        bool flDash;
+
+        /**
+         The format specifier contained a $(D '0') ($(D printf)
+         compatibility).
+         */
+        bool flZero;
+
+        /**
+         The format specifier contained a $(D ' ') ($(D printf)
+         compatibility).
+         */
+        bool flSpace;
+
+        /**
+         The format specifier contained a $(D '+') ($(D printf)
+         compatibility).
+         */
+        bool flPlus;
+
+        /**
+         The format specifier contained a $(D '#') ($(D printf)
+         compatibility).
+         */
+        bool flHash;
+
+        // Fake field to allow compilation
+        ubyte allFlags;
+    }
+    else
+    {
+        union
+        {
+            import std.bitmanip : bitfields;
+            mixin(bitfields!(
+                        bool, "flDash", 1,
+                        bool, "flZero", 1,
+                        bool, "flSpace", 1,
+                        bool, "flPlus", 1,
+                        bool, "flHash", 1,
+                        ubyte, "", 3));
+            ubyte allFlags;
+        }
+    }
+
+    /**
+       In case of a compound format specifier starting with $(D
+       "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested)
+       contains the string contained within the two separators.
+     */
+    const(Char)[] nested;
+
+    /**
+       In case of a compound format specifier, $(D _sep) contains the
+       string positioning after $(D "%|").
+     */
+    const(Char)[] sep;
+
+    /**
+       $(D _trailing) contains the rest of the format string.
+     */
+    const(Char)[] trailing;
+
+    /*
+       This string is inserted before each sequence (e.g. array)
+       formatted (by default $(D "[")).
+     */
+    enum immutable(Char)[] seqBefore = "[";
+
+    /*
+       This string is inserted after each sequence formatted (by
+       default $(D "]")).
+     */
+    enum immutable(Char)[] seqAfter = "]";
+
+    /*
+       This string is inserted after each element keys of a sequence (by
+       default $(D ":")).
+     */
+    enum immutable(Char)[] keySeparator = ":";
+
+    /*
+       This string is inserted in between elements of a sequence (by
+       default $(D ", ")).
+     */
+    enum immutable(Char)[] seqSeparator = ", ";
+
+    /**
+       Construct a new $(D FormatSpec) using the format string $(D fmt), no
+       processing is done until needed.
+     */
+    this(in Char[] fmt) @safe pure
+    {
+        trailing = fmt;
+    }
+
+    bool writeUpToNextSpec(OutputRange)(OutputRange writer)
+    {
+        if (trailing.empty)
+            return false;
+        for (size_t i = 0; i < trailing.length; ++i)
+        {
+            if (trailing[i] != '%') continue;
+            put(writer, trailing[0 .. i]);
+            trailing = trailing[i .. $];
+            enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`);
+            trailing = trailing[1 .. $];
+
+            if (trailing[0] != '%')
+            {
+                // Spec found. Fill up the spec, and bailout
+                fillUp();
+                return true;
+            }
+            // Doubled! Reset and Keep going
+            i = 0;
+        }
+        // no format spec found
+        put(writer, trailing);
+        trailing = null;
+        return false;
+    }
+
+    unittest
+    {
+        import std.array;
+        auto w = appender!(char[])();
+        auto f = FormatSpec("abc%sdef%sghi");
+        f.writeUpToNextSpec(w);
+        assert(w.data == "abc", w.data);
+        assert(f.trailing == "def%sghi", text(f.trailing));
+        f.writeUpToNextSpec(w);
+        assert(w.data == "abcdef", w.data);
+        assert(f.trailing == "ghi");
+        // test with embedded %%s
+        f = FormatSpec("ab%%cd%%ef%sg%%h%sij");
+        w.clear();
+        f.writeUpToNextSpec(w);
+        assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data);
+        f.writeUpToNextSpec(w);
+        assert(w.data == "ab%cd%efg%h" && f.trailing == "ij");
+        // bug4775
+        f = FormatSpec("%%%s");
+        w.clear();
+        f.writeUpToNextSpec(w);
+        assert(w.data == "%" && f.trailing == "");
+        f = FormatSpec("%%%%%s%%");
+        w.clear();
+        while (f.writeUpToNextSpec(w)) continue;
+        assert(w.data == "%%%");
+
+        f = FormatSpec("a%%b%%c%");
+        w.clear();
+        assertThrown!FormatException(f.writeUpToNextSpec(w));
+        assert(w.data == "a%b%c" && f.trailing == "%");
+    }
+
+    private void fillUp()
+    {
+        // Reset content
+        if (__ctfe)
+        {
+            flDash = false;
+            flZero = false;
+            flSpace = false;
+            flPlus = false;
+            flHash = false;
+        }
+        else
+        {
+            allFlags = 0;
+        }
+
+        width = 0;
+        precision = UNSPECIFIED;
+        nested = null;
+        // Parse the spec (we assume we're past '%' already)
+        for (size_t i = 0; i < trailing.length; )
+        {
+            switch (trailing[i])
+            {
+            case '(':
+                // Embedded format specifier.
+                auto j = i + 1;
+                // Get the matching balanced paren
+                for (uint innerParens;;)
+                {
+                    enforceFmt(j + 1 < trailing.length,
+                        text("Incorrect format specifier: %", trailing[i .. $]));
+                    if (trailing[j++] != '%')
+                    {
+                        // skip, we're waiting for %( and %)
+                        continue;
+                    }
+                    if (trailing[j] == '-') // for %-(
+                    {
+                        ++j;    // skip
+                        enforceFmt(j < trailing.length,
+                            text("Incorrect format specifier: %", trailing[i .. $]));
+                    }
+                    if (trailing[j] == ')')
+                    {
+                        if (innerParens-- == 0) break;
+                    }
+                    else if (trailing[j] == '|')
+                    {
+                        if (innerParens == 0) break;
+                    }
+                    else if (trailing[j] == '(')
+                    {
+                        ++innerParens;
+                    }
+                }
+                if (trailing[j] == '|')
+                {
+                    auto k = j;
+                    for (++j;;)
+                    {
+                        if (trailing[j++] != '%')
+                            continue;
+                        if (trailing[j] == '%')
+                            ++j;
+                        else if (trailing[j] == ')')
+                            break;
+                        else
+                            throw new Exception(
+                                text("Incorrect format specifier: %",
+                                        trailing[j .. $]));
+                    }
+                    nested = trailing[i + 1 .. k - 1];
+                    sep = trailing[k + 1 .. j - 1];
+                }
+                else
+                {
+                    nested = trailing[i + 1 .. j - 1];
+                    sep = null; // use null (issue 12135)
+                }
+                //this = FormatSpec(innerTrailingSpec);
+                spec = '(';
+                // We practically found the format specifier
+                trailing = trailing[j + 1 .. $];
+                return;
+            case '-': flDash = true; ++i; break;
+            case '+': flPlus = true; ++i; break;
+            case '#': flHash = true; ++i; break;
+            case '0': flZero = true; ++i; break;
+            case ' ': flSpace = true; ++i; break;
+            case '*':
+                if (isDigit(trailing[++i]))
+                {
+                    // a '*' followed by digits and '$' is a
+                    // positional format
+                    trailing = trailing[1 .. $];
+                    width = -parse!(typeof(width))(trailing);
+                    i = 0;
+                    enforceFmt(trailing[i++] == '$',
+                        "$ expected");
+                }
+                else
+                {
+                    // read result
+                    width = DYNAMIC;
+                }
+                break;
+            case '1': .. case '9':
+                auto tmp = trailing[i .. $];
+                const widthOrArgIndex = parse!uint(tmp);
+                enforceFmt(tmp.length,
+                    text("Incorrect format specifier %", trailing[i .. $]));
+                i = tmp.ptr - trailing.ptr;
+                if (tmp.startsWith('$'))
+                {
+                    // index of the form %n$
+                    indexEnd = indexStart = to!ubyte(widthOrArgIndex);
+                    ++i;
+                }
+                else if (tmp.startsWith(':'))
+                {
+                    // two indexes of the form %m:n$, or one index of the form %m:$
+                    indexStart = to!ubyte(widthOrArgIndex);
+                    tmp = tmp[1 .. $];
+                    if (tmp.startsWith('$'))
+                    {
+                        indexEnd = indexEnd.max;
+                    }
+                    else
+                    {
+                        indexEnd = parse!(typeof(indexEnd))(tmp);
+                    }
+                    i = tmp.ptr - trailing.ptr;
+                    enforceFmt(trailing[i++] == '$',
+                        "$ expected");
+                }
+                else
+                {
+                    // width
+                    width = to!int(widthOrArgIndex);
+                }
+                break;
+            case '.':
+                // Precision
+                if (trailing[++i] == '*')
+                {
+                    if (isDigit(trailing[++i]))
+                    {
+                        // a '.*' followed by digits and '$' is a
+                        // positional precision
+                        trailing = trailing[i .. $];
+                        i = 0;
+                        precision = -parse!int(trailing);
+                        enforceFmt(trailing[i++] == '$',
+                            "$ expected");
+                    }
+                    else
+                    {
+                        // read result
+                        precision = DYNAMIC;
+                    }
+                }
+                else if (trailing[i] == '-')
+                {
+                    // negative precision, as good as 0
+                    precision = 0;
+                    auto tmp = trailing[i .. $];
+                    parse!int(tmp); // skip digits
+                    i = tmp.ptr - trailing.ptr;
+                }
+                else if (isDigit(trailing[i]))
+                {
+                    auto tmp = trailing[i .. $];
+                    precision = parse!int(tmp);
+                    i = tmp.ptr - trailing.ptr;
+                }
+                else
+                {
+                    // "." was specified, but nothing after it
+                    precision = 0;
+                }
+                break;
+            default:
+                // this is the format char
+                spec = cast(char) trailing[i++];
+                trailing = trailing[i .. $];
+                return;
+            } // end switch
+        } // end for
+        throw new Exception(text("Incorrect format specifier: ", trailing));
+    }
+
+    //--------------------------------------------------------------------------
+    private bool readUpToNextSpec(R)(ref R r)
+    {
+        import std.ascii : isLower, isWhite;
+        import std.utf : stride;
+
+        // Reset content
+        if (__ctfe)
+        {
+            flDash = false;
+            flZero = false;
+            flSpace = false;
+            flPlus = false;
+            flHash = false;
+        }
+        else
+        {
+            allFlags = 0;
+        }
+        width = 0;
+        precision = UNSPECIFIED;
+        nested = null;
+        // Parse the spec
+        while (trailing.length)
+        {
+            if (*trailing.ptr == '%')
+            {
+                if (trailing.length > 1 && trailing.ptr[1] == '%')
+                {
+                    assert(!r.empty);
+                    // Require a '%'
+                    if (r.front != '%') break;
+                    trailing = trailing[2 .. $];
+                    r.popFront();
+                }
+                else
+                {
+                    enforce(isLower(trailing[1]) || trailing[1] == '*' ||
+                            trailing[1] == '(',
+                            text("'%", trailing[1],
+                                    "' not supported with formatted read"));
+                    trailing = trailing[1 .. $];
+                    fillUp();
+                    return true;
+                }
+            }
+            else
+            {
+                if (trailing.ptr[0] == ' ')
+                {
+                    while (!r.empty && isWhite(r.front)) r.popFront();
+                    //r = std.algorithm.find!(not!(isWhite))(r);
+                }
+                else
+                {
+                    enforce(!r.empty,
+                            text("parseToFormatSpec: Cannot find character `",
+                                    trailing.ptr[0], "' in the input string."));
+                    if (r.front != trailing.front) break;
+                    r.popFront();
+                }
+                trailing = trailing[stride(trailing, 0) .. $];
+            }
+        }
+        return false;
+    }
+
+    private string getCurFmtStr() const
+    {
+        import std.array : appender;
+        auto w = appender!string();
+        auto f = FormatSpec!Char("%s"); // for stringnize
+
+        put(w, '%');
+        if (indexStart != 0)
+        {
+            formatValue(w, indexStart, f);
+            put(w, '$');
+        }
+        if (flDash)  put(w, '-');
+        if (flZero)  put(w, '0');
+        if (flSpace) put(w, ' ');
+        if (flPlus)  put(w, '+');
+        if (flHash)  put(w, '#');
+        if (width != 0)
+            formatValue(w, width, f);
+        if (precision != FormatSpec!Char.UNSPECIFIED)
+        {
+            put(w, '.');
+            formatValue(w, precision, f);
+        }
+        put(w, spec);
+        return w.data;
+    }
+
+    unittest
+    {
+        // issue 5237
+        import std.array;
+        auto w = appender!string();
+        auto f = FormatSpec!char("%.16f");
+        f.writeUpToNextSpec(w); // dummy eating
+        assert(f.spec == 'f');
+        auto fmt = f.getCurFmtStr();
+        assert(fmt == "%.16f");
+    }
+
+    private const(Char)[] headUpToNextSpec()
+    {
+        import std.array : appender;
+        auto w = appender!(typeof(return))();
+        auto tr = trailing;
+
+        while (tr.length)
+        {
+            if (*tr.ptr == '%')
+            {
+                if (tr.length > 1 && tr.ptr[1] == '%')
+                {
+                    tr = tr[2 .. $];
+                    w.put('%');
+                }
+                else
+                    break;
+            }
+            else
+            {
+                w.put(tr.front);
+                tr.popFront();
+            }
+        }
+        return w.data;
+    }
+
+    string toString()
+    {
+        return text("address = ", cast(void*) &this,
+                "\nwidth = ", width,
+                "\nprecision = ", precision,
+                "\nspec = ", spec,
+                "\nindexStart = ", indexStart,
+                "\nindexEnd = ", indexEnd,
+                "\nflDash = ", flDash,
+                "\nflZero = ", flZero,
+                "\nflSpace = ", flSpace,
+                "\nflPlus = ", flPlus,
+                "\nflHash = ", flHash,
+                "\nnested = ", nested,
+                "\ntrailing = ", trailing, "\n");
+    }
+}
+
+///
+@safe pure unittest
+{
+    import std.array;
+    auto a = appender!(string)();
+    auto fmt = "Number: %2.4e\nString: %s";
+    auto f = FormatSpec!char(fmt);
+
+    f.writeUpToNextSpec(a);
+
+    assert(a.data == "Number: ");
+    assert(f.trailing == "\nString: %s");
+    assert(f.spec == 'e');
+    assert(f.width == 2);
+    assert(f.precision == 4);
+
+    f.writeUpToNextSpec(a);
+
+    assert(a.data == "Number: \nString: ");
+    assert(f.trailing == "");
+    assert(f.spec == 's');
+}
+
+// Issue 14059
+unittest
+{
+    import std.array : appender;
+    auto a = appender!(string)();
+
+    auto f = FormatSpec!char("%-(%s%"); // %)")
+    assertThrown(f.writeUpToNextSpec(a));
+
+    f = FormatSpec!char("%(%-"); // %)")
+    assertThrown(f.writeUpToNextSpec(a));
+}
+
+/**
+Helper function that returns a $(D FormatSpec) for a single specifier given
+in $(D fmt)
+
+Params:
+    fmt = A format specifier
+
+Returns:
+    A $(D FormatSpec) with the specifier parsed.
+
+Enforces giving only one specifier to the function.
+  */
+FormatSpec!Char singleSpec(Char)(Char[] fmt)
+{
+    import std.conv : text;
+    enforce(fmt.length >= 2, new Exception("fmt must be at least 2 characters long"));
+    enforce(fmt.front == '%', new Exception("fmt must start with a '%' character"));
+
+    static struct DummyOutputRange {
+        void put(C)(C[] buf) {} // eat elements
+    }
+    auto a = DummyOutputRange();
+    auto spec = FormatSpec!Char(fmt);
+    //dummy write
+    spec.writeUpToNextSpec(a);
+
+    enforce(spec.trailing.empty,
+            new Exception(text("Trailing characters in fmt string: '", spec.trailing)));
+
+    return spec;
+}
+
+///
+unittest
+{
+    auto spec = singleSpec("%2.3e");
+
+    assert(spec.trailing == "");
+    assert(spec.spec == 'e');
+    assert(spec.width == 2);
+    assert(spec.precision == 3);
+
+    assertThrown(singleSpec(""));
+    assertThrown(singleSpec("2.3e"));
+    assertThrown(singleSpec("%2.3eTest"));
+}
+
+/**
+$(D bool)s are formatted as "true" or "false" with %s and as "1" or
+"0" with integral-specific format specs.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    BooleanTypeOf!T val = obj;
+
+    if (f.spec == 's')
+    {
+        string s = val ? "true" : "false";
+        if (!f.flDash)
+        {
+            // right align
+            if (f.width > s.length)
+                foreach (i ; 0 .. f.width - s.length) put(w, ' ');
+            put(w, s);
+        }
+        else
+        {
+            // left align
+            put(w, s);
+            if (f.width > s.length)
+                foreach (i ; 0 .. f.width - s.length) put(w, ' ');
+        }
+    }
+    else
+        formatValue(w, cast(int) val, f);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    formatValue(w, true, spec);
+
+    assert(w.data == "true");
+}
+
+@safe pure unittest
+{
+    assertCTFEable!(
+    {
+        formatTest( false, "false" );
+        formatTest( true,  "true"  );
+    });
+}
+unittest
+{
+    class C1 { bool val; alias val this; this(bool v){ val = v; } }
+    class C2 { bool val; alias val this; this(bool v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(false), "false" );
+    formatTest( new C1(true),  "true" );
+    formatTest( new C2(false), "C" );
+    formatTest( new C2(true),  "C" );
+
+    struct S1 { bool val; alias val this; }
+    struct S2 { bool val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(false), "false" );
+    formatTest( S1(true),  "true"  );
+    formatTest( S2(false), "S" );
+    formatTest( S2(true),  "S" );
+}
+
+unittest
+{
+    string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
+    assert(t1 == "[  true] [ false] [true  ]");
+
+    string t2 = format("[%3s] [%-2s]", true, false);
+    assert(t2 == "[true] [false]");
+}
+
+/**
+$(D null) literal is formatted as $(D "null").
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char))
+{
+    enforceFmt(f.spec == 's',
+        "null");
+
+    put(w, "null");
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    formatValue(w, null, spec);
+
+    assert(w.data == "null");
+}
+
+@safe pure unittest
+{
+    assertCTFEable!(
+    {
+        formatTest( null, "null" );
+    });
+}
+
+/**
+Integrals are formatted like $(D printf) does.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    alias U = IntegralTypeOf!T;
+    U val = obj;    // Extracting alias this may be impure/system/may-throw
+
+    if (f.spec == 'r')
+    {
+        // raw write, skip all else and write the thing
+        auto raw = (ref val)@trusted{
+            return (cast(const char*) &val)[0 .. val.sizeof];
+        }(val);
+        if (needToSwapEndianess(f))
+        {
+            foreach_reverse (c; raw)
+                put(w, c);
+        }
+        else
+        {
+            foreach (c; raw)
+                put(w, c);
+        }
+        return;
+    }
+
+    uint base =
+        f.spec == 'x' || f.spec == 'X' ? 16 :
+        f.spec == 'o' ? 8 :
+        f.spec == 'b' ? 2 :
+        f.spec == 's' || f.spec == 'd' || f.spec == 'u' ? 10 :
+        0;
+    enforceFmt(base > 0,
+        "integral");
+
+    // Forward on to formatIntegral to handle both U and const(U)
+    // Saves duplication of code for both versions.
+    static if (is(ucent) && (is(U == cent) || is(U == ucent)))
+        alias C = U;
+    else static if (isSigned!U)
+        alias C = long;
+    else
+        alias C = ulong;
+    formatIntegral(w, cast(C) val, f, base, Unsigned!U.max);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%d");
+    formatValue(w, 1337, spec);
+
+    assert(w.data == "1337");
+}
+
+private void formatIntegral(Writer, T, Char)(Writer w, const(T) val, const ref FormatSpec!Char fs, uint base, ulong mask)
+{
+    T arg = val;
+
+    bool negative = (base == 10 && arg < 0);
+    if (negative)
+    {
+        arg = -arg;
+    }
+
+    // All unsigned integral types should fit in ulong.
+    static if (is(ucent) && is(typeof(arg) == ucent))
+        formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative);
+    else
+        formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative);
+}
+
+private void formatUnsigned(Writer, T, Char)(Writer w, T arg, const ref FormatSpec!Char fs, uint base, bool negative)
+{
+    /* Write string:
+     *    leftpad prefix1 prefix2 zerofill digits rightpad
+     */
+
+    /* Convert arg to digits[].
+     * Note that 0 becomes an empty digits[]
+     */
+    char[64] buffer = void; // 64 bits in base 2 at most
+    char[] digits;
+    {
+        size_t i = buffer.length;
+        while (arg)
+        {
+            --i;
+            char c = cast(char) (arg % base);
+            arg /= base;
+            if (c < 10)
+                buffer[i] = cast(char)(c + '0');
+            else
+                buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10));
+        }
+        digits = buffer[i .. $]; // got the digits without the sign
+    }
+
+
+    int precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision;
+
+    char padChar = 0;
+    if (!fs.flDash)
+    {
+        padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' ';
+    }
+
+    // Compute prefix1 and prefix2
+    char prefix1 = 0;
+    char prefix2 = 0;
+    if (base == 10)
+    {
+        if (negative)
+            prefix1 = '-';
+        else if (fs.flPlus)
+            prefix1 = '+';
+        else if (fs.flSpace)
+            prefix1 = ' ';
+    }
+    else if (base == 16 && fs.flHash && digits.length)
+    {
+        prefix1 = '0';
+        prefix2 = fs.spec == 'x' ? 'x' : 'X';
+    }
+    // adjust precision to print a '0' for octal if alternate format is on
+    else if (base == 8 && fs.flHash &&
+             (precision <= 1 || precision <= digits.length) && // too low precision
+             digits.length > 0)
+        prefix1 = '0';
+
+    size_t zerofill = precision > digits.length ? precision - digits.length : 0;
+    size_t leftpad = 0;
+    size_t rightpad = 0;
+
+    ptrdiff_t spacesToPrint = fs.width - ((prefix1 != 0) + (prefix2 != 0) + zerofill + digits.length);
+    if (spacesToPrint > 0) // need to do some padding
+    {
+        if (padChar == '0')
+            zerofill += spacesToPrint;
+        else if (padChar)
+            leftpad = spacesToPrint;
+        else
+            rightpad = spacesToPrint;
+    }
+
+    /**** Print ****/
+
+    foreach (i ; 0 .. leftpad)
+        put(w, ' ');
+
+    if (prefix1) put(w, prefix1);
+    if (prefix2) put(w, prefix2);
+
+    foreach (i ; 0 .. zerofill)
+        put(w, '0');
+
+    put(w, digits);
+
+    foreach (i ; 0 .. rightpad)
+        put(w, ' ');
+}
+
+@safe pure unittest
+{
+    assertCTFEable!(
+    {
+        formatTest( 10, "10" );
+    });
+}
+unittest
+{
+    class C1 { long val; alias val this; this(long v){ val = v; } }
+    class C2 { long val; alias val this; this(long v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(10), "10" );
+    formatTest( new C2(10), "C" );
+
+    struct S1 { long val; alias val this; }
+    struct S2 { long val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(10), "10" );
+    formatTest( S2(10), "S" );
+}
+
+// bugzilla 9117
+unittest
+{
+    static struct Frop {}
+
+    static struct Foo
+    {
+        int n = 0;
+        alias n this;
+        T opCast(T) () if (is(T == Frop))
+        {
+            return Frop();
+        }
+        string toString()
+        {
+            return "Foo";
+        }
+    }
+
+    static struct Bar
+    {
+        Foo foo;
+        alias foo this;
+        string toString()
+        {
+            return "Bar";
+        }
+    }
+
+    const(char)[] result;
+    void put(const char[] s){ result ~= s; }
+
+    Foo foo;
+    formattedWrite(&put, "%s", foo);    // OK
+    assert(result == "Foo");
+
+    result = null;
+
+    Bar bar;
+    formattedWrite(&put, "%s", bar);    // NG
+    assert(result == "Bar");
+}
+
+/**
+Floating-point values are formatted like $(D printf) does.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    import core.stdc.stdio : snprintf;
+    import std.algorithm : find, min;
+    FormatSpec!Char fs = f; // fs is copy for change its values.
+    FloatingPointTypeOf!T val = obj;
+
+    if (fs.spec == 'r')
+    {
+        // raw write, skip all else and write the thing
+        auto raw = (ref val)@trusted{
+            return (cast(const char*) &val)[0 .. val.sizeof];
+        }(val);
+        if (needToSwapEndianess(f))
+        {
+            foreach_reverse (c; raw)
+                put(w, c);
+        }
+        else
+        {
+            foreach (c; raw)
+                put(w, c);
+        }
+        return;
+    }
+    enforceFmt(find("fgFGaAeEs", fs.spec).length,
+        "incompatible format character for floating point type");
+
+    version (CRuntime_Microsoft)
+    {
+        import std.math : isNaN, isInfinity;
+        double tval = val; // convert early to get "inf" in case of overflow
+        string s;
+        if (isNaN(tval))
+            s = "nan"; // snprintf writes 1.#QNAN
+        else if (isInfinity(tval))
+            s = val >= 0 ? "inf" : "-inf"; // snprintf writes 1.#INF
+
+        if (s.length > 0)
+        {
+          version(none)
+          {
+            return formatValue(w, s, f);
+          }
+          else  // FIXME:workaroun
+          {
+            s = s[0 .. f.precision < $ ? f.precision : $];
+            if (!f.flDash)
+            {
+                // right align
+                if (f.width > s.length)
+                    foreach (j ; 0 .. f.width - s.length) put(w, ' ');
+                put(w, s);
+            }
+            else
+            {
+                // left align
+                put(w, s);
+                if (f.width > s.length)
+                    foreach (j ; 0 .. f.width - s.length) put(w, ' ');
+            }
+            return;
+          }
+        }
+    }
+    else
+        alias tval = val;
+    if (fs.spec == 's') fs.spec = 'g';
+    char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/
+                     + 1 /*\0*/] sprintfSpec = void;
+    sprintfSpec[0] = '%';
+    uint i = 1;
+    if (fs.flDash) sprintfSpec[i++] = '-';
+    if (fs.flPlus) sprintfSpec[i++] = '+';
+    if (fs.flZero) sprintfSpec[i++] = '0';
+    if (fs.flSpace) sprintfSpec[i++] = ' ';
+    if (fs.flHash) sprintfSpec[i++] = '#';
+    sprintfSpec[i .. i + 3] = "*.*";
+    i += 3;
+    if (is(Unqual!(typeof(val)) == real)) sprintfSpec[i++] = 'L';
+    sprintfSpec[i++] = fs.spec;
+    sprintfSpec[i] = 0;
+    //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val);
+    char[512] buf = void;
+
+    immutable n = ()@trusted{
+        return snprintf(buf.ptr, buf.length,
+                        sprintfSpec.ptr,
+                        fs.width,
+                        // negative precision is same as no precision specified
+                        fs.precision == fs.UNSPECIFIED ? -1 : fs.precision,
+                        tval);
+    }();
+
+    enforceFmt(n >= 0,
+        "floating point formatting failure");
+    put(w, buf[0 .. min(n, buf.length-1)]);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%.1f");
+    formatValue(w, 1337.7, spec);
+
+    assert(w.data == "1337.7");
+}
+
+@safe /*pure*/ unittest     // formatting floating point values is now impure
+{
+    import std.conv : to;
+    foreach (T; AliasSeq!(float, double, real))
+    {
+        formatTest( to!(          T)(5.5), "5.5" );
+        formatTest( to!(    const T)(5.5), "5.5" );
+        formatTest( to!(immutable T)(5.5), "5.5" );
+
+        formatTest( T.nan, "nan" );
+    }
+}
+
+unittest
+{
+    formatTest( 2.25, "2.25" );
+
+    class C1 { double val; alias val this; this(double v){ val = v; } }
+    class C2 { double val; alias val this; this(double v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(2.25), "2.25" );
+    formatTest( new C2(2.25), "C" );
+
+    struct S1 { double val; alias val this; }
+    struct S2 { double val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(2.25), "2.25" );
+    formatTest( S2(2.25), "S" );
+}
+
+/*
+Formatting a $(D creal) is deprecated but still kept around for a while.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char))
+{
+    creal val = obj;
+
+    formatValue(w, val.re, f);
+    if (val.im >= 0)
+    {
+        put(w, '+');
+    }
+    formatValue(w, val.im, f);
+    put(w, 'i');
+}
+
+@safe /*pure*/ unittest     // formatting floating point values is now impure
+{
+    import std.conv : to;
+    foreach (T; AliasSeq!(cfloat, cdouble, creal))
+    {
+        formatTest( to!(          T)(1 + 1i), "1+1i" );
+        formatTest( to!(    const T)(1 + 1i), "1+1i" );
+        formatTest( to!(immutable T)(1 + 1i), "1+1i" );
+    }
+    foreach (T; AliasSeq!(cfloat, cdouble, creal))
+    {
+        formatTest( to!(          T)(0 - 3i), "0-3i" );
+        formatTest( to!(    const T)(0 - 3i), "0-3i" );
+        formatTest( to!(immutable T)(0 - 3i), "0-3i" );
+    }
+}
+
+unittest
+{
+    formatTest( 3+2.25i, "3+2.25i" );
+
+    class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } }
+    class C2 { cdouble val; alias val this; this(cdouble v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(3+2.25i), "3+2.25i" );
+    formatTest( new C2(3+2.25i), "C" );
+
+    struct S1 { cdouble val; alias val this; }
+    struct S2 { cdouble val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(3+2.25i), "3+2.25i" );
+    formatTest( S2(3+2.25i), "S" );
+}
+
+/*
+   Formatting an $(D ireal) is deprecated but still kept around for a while.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char))
+{
+    ireal val = obj;
+
+    formatValue(w, val.im, f);
+    put(w, 'i');
+}
+
+@safe /*pure*/ unittest     // formatting floating point values is now impure
+{
+    import std.conv : to;
+    foreach (T; AliasSeq!(ifloat, idouble, ireal))
+    {
+        formatTest( to!(          T)(1i), "1i" );
+        formatTest( to!(    const T)(1i), "1i" );
+        formatTest( to!(immutable T)(1i), "1i" );
+    }
+}
+
+unittest
+{
+    formatTest( 2.25i, "2.25i" );
+
+    class C1 { idouble val; alias val this; this(idouble v){ val = v; } }
+    class C2 { idouble val; alias val this; this(idouble v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(2.25i), "2.25i" );
+    formatTest( new C2(2.25i), "C" );
+
+    struct S1 { idouble val; alias val this; }
+    struct S2 { idouble val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(2.25i), "2.25i" );
+    formatTest( S2(2.25i), "S" );
+}
+
+/**
+Individual characters ($(D char), $(D wchar), or $(D dchar)) are formatted as
+Unicode characters with %s and as integers with integral-specific format
+specs.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    CharTypeOf!T val = obj;
+
+    if (f.spec == 's' || f.spec == 'c')
+    {
+        put(w, val);
+    }
+    else
+    {
+        alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
+        formatValue(w, cast(U) val, f);
+    }
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%c");
+    formatValue(w, 'a', spec);
+
+    assert(w.data == "a");
+}
+
+@safe pure unittest
+{
+    assertCTFEable!(
+    {
+        formatTest( 'c', "c" );
+    });
+}
+
+unittest
+{
+    class C1 { char val; alias val this; this(char v){ val = v; } }
+    class C2 { char val; alias val this; this(char v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1('c'), "c" );
+    formatTest( new C2('c'), "C" );
+
+    struct S1 { char val; alias val this; }
+    struct S2 { char val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1('c'), "c" );
+    formatTest( S2('c'), "S" );
+}
+
+@safe pure unittest
+{
+    //Little Endian
+    formatTest( "%-r", cast( char)'c', ['c'         ] );
+    formatTest( "%-r", cast(wchar)'c', ['c', 0      ] );
+    formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] );
+    formatTest( "%-r", '本', ['\x2c', '\x67'] );
+
+    //Big Endian
+    formatTest( "%+r", cast( char)'c', [         'c'] );
+    formatTest( "%+r", cast(wchar)'c', [0,       'c'] );
+    formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] );
+    formatTest( "%+r", '本', ['\x67', '\x2c'] );
+}
+
+/**
+Strings are formatted like $(D printf) does.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    Unqual!(StringTypeOf!T) val = obj;  // for `alias this`, see bug5371
+    formatRange(w, val, f);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    formatValue(w, "hello", spec);
+
+    assert(w.data == "hello");
+}
+
+unittest
+{
+    formatTest( "abc", "abc" );
+}
+
+unittest
+{
+    // Test for bug 5371 for classes
+    class C1 { const string var; alias var this; this(string s){ var = s; } }
+    class C2 {       string var; alias var this; this(string s){ var = s; } }
+    formatTest( new C1("c1"), "c1" );
+    formatTest( new C2("c2"), "c2" );
+
+    // Test for bug 5371 for structs
+    struct S1 { const string var; alias var this; }
+    struct S2 {       string var; alias var this; }
+    formatTest( S1("s1"), "s1" );
+    formatTest( S2("s2"), "s2" );
+}
+
+unittest
+{
+    class  C3 { string val; alias val this; this(string s){ val = s; }
+                override string toString() const { return "C"; } }
+    formatTest( new C3("c3"), "C" );
+
+    struct S3 { string val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S3("s3"), "S" );
+}
+
+@safe pure unittest
+{
+    //Little Endian
+    formatTest( "%-r", "ab"c, ['a'         , 'b'         ] );
+    formatTest( "%-r", "ab"w, ['a', 0      , 'b', 0      ] );
+    formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] );
+    formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
+    formatTest( "%-r", "日本語"w, ['\xe5', '\x65',                 '\x2c', '\x67',                 '\x9e', '\x8a'                ] );
+    formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67', '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] );
+
+    //Big Endian
+    formatTest( "%+r", "ab"c, [         'a',          'b'] );
+    formatTest( "%+r", "ab"w, [      0, 'a',       0, 'b'] );
+    formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] );
+    formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
+    formatTest( "%+r", "日本語"w, [                '\x65', '\xe5',                 '\x67', '\x2c',                 '\x8a', '\x9e'] );
+    formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00', '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] );
+}
+
+/**
+Static-size arrays are formatted as dynamic arrays.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, auto ref T obj, ref FormatSpec!Char f)
+if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    formatValue(w, obj[], f);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    char[2] two = ['a', 'b'];
+    formatValue(w, two, spec);
+
+    assert(w.data == "ab");
+}
+
+unittest    // Test for issue 8310
+{
+    import std.array : appender;
+    FormatSpec!char f;
+    auto w = appender!string();
+
+    char[2] two = ['a', 'b'];
+    formatValue(w, two, f);
+
+    char[2] getTwo(){ return two; }
+    formatValue(w, getTwo(), f);
+}
+
+/**
+Dynamic arrays are formatted as input ranges.
+
+Specializations:
+    $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).)
+        $(LI Const array is converted to input range by removing its qualifier.))
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    static if (is(const(ArrayTypeOf!T) == const(void[])))
+    {
+        formatValue(w, cast(const ubyte[])obj, f);
+    }
+    else static if (!isInputRange!T)
+    {
+        alias U = Unqual!(ArrayTypeOf!T);
+        static assert(isInputRange!U);
+        U val = obj;
+        formatValue(w, val, f);
+    }
+    else
+    {
+        formatRange(w, obj, f);
+    }
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    auto two = [1, 2];
+    formatValue(w, two, spec);
+
+    assert(w.data == "[1, 2]");
+}
+
+// alias this, input range I/F, and toString()
+unittest
+{
+    struct S(int flags)
+    {
+        int[] arr;
+      static if (flags & 1)
+        alias arr this;
+
+      static if (flags & 2)
+      {
+        @property bool empty() const { return arr.length == 0; }
+        @property int front() const { return arr[0] * 2; }
+        void popFront() { arr = arr[1..$]; }
+      }
+
+      static if (flags & 4)
+        string toString() const { return "S"; }
+    }
+    formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
+    formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]");        // Test for bug 7628
+    formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
+    formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
+    formatTest(S!0b100([0, 1, 2]), "S");
+    formatTest(S!0b101([0, 1, 2]), "S");                // Test for bug 7628
+    formatTest(S!0b110([0, 1, 2]), "S");
+    formatTest(S!0b111([0, 1, 2]), "S");
+
+    class C(uint flags)
+    {
+        int[] arr;
+      static if (flags & 1)
+        alias arr this;
+
+        this(int[] a) { arr = a; }
+
+      static if (flags & 2)
+      {
+        @property bool empty() const { return arr.length == 0; }
+        @property int front() const { return arr[0] * 2; }
+        void popFront() { arr = arr[1..$]; }
+      }
+
+      static if (flags & 4)
+        override string toString() const { return "C"; }
+    }
+    formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString());
+    formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]");    // Test for bug 7628
+    formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]");
+    formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]");
+    formatTest(new C!0b100([0, 1, 2]), "C");
+    formatTest(new C!0b101([0, 1, 2]), "C");            // Test for bug 7628
+    formatTest(new C!0b110([0, 1, 2]), "C");
+    formatTest(new C!0b111([0, 1, 2]), "C");
+}
+
+unittest
+{
+    // void[]
+    void[] val0;
+    formatTest( val0, "[]" );
+
+    void[] val = cast(void[])cast(ubyte[])[1, 2, 3];
+    formatTest( val, "[1, 2, 3]" );
+
+    void[0] sval0 = [];
+    formatTest( sval0, "[]");
+
+    void[3] sval = cast(void[3])cast(ubyte[3])[1, 2, 3];
+    formatTest( sval, "[1, 2, 3]" );
+}
+
+unittest
+{
+    // const(T[]) -> const(T)[]
+    const short[] a = [1, 2, 3];
+    formatTest( a, "[1, 2, 3]" );
+
+    struct S { const(int[]) arr; alias arr this; }
+    auto s = S([1,2,3]);
+    formatTest( s, "[1, 2, 3]" );
+}
+
+unittest
+{
+    // 6640
+    struct Range
+    {
+        string value;
+        @property bool empty() const { return !value.length; }
+        @property dchar front() const { return value.front; }
+        void popFront() { value.popFront(); }
+
+        @property size_t length() const { return value.length; }
+    }
+    immutable table =
+    [
+        ["[%s]", "[string]"],
+        ["[%10s]", "[    string]"],
+        ["[%-10s]", "[string    ]"],
+        ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
+        ["[%(%c %)]", "[s t r i n g]"],
+    ];
+    foreach (e; table)
+    {
+        formatTest(e[0], "string", e[1]);
+        formatTest(e[0], Range("string"), e[1]);
+    }
+}
+
+unittest
+{
+    // string literal from valid UTF sequence is encoding free.
+    foreach (StrType; AliasSeq!(string, wstring, dstring))
+    {
+        // Valid and printable (ASCII)
+        formatTest( [cast(StrType)"hello"],
+                    `["hello"]` );
+
+        // 1 character escape sequences (' is not escaped in strings)
+        formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
+                    `["\"'\0\\\a\b\f\n\r\t\v"]` );
+
+        // 1 character optional escape sequences
+        formatTest( [cast(StrType)"\'\?"],
+                    `["'?"]` );
+
+        // Valid and non-printable code point (<= U+FF)
+        formatTest( [cast(StrType)"\x10\x1F\x20test"],
+                    `["\x10\x1F test"]` );
+
+        // Valid and non-printable code point (<= U+FFFF)
+        formatTest( [cast(StrType)"\u200B..\u200F"],
+                    `["\u200B..\u200F"]` );
+
+        // Valid and non-printable code point (<= U+10FFFF)
+        formatTest( [cast(StrType)"\U000E0020..\U000E007F"],
+                    `["\U000E0020..\U000E007F"]` );
+    }
+
+    // invalid UTF sequence needs hex-string literal postfix (c/w/d)
+    {
+        // U+FFFF with UTF-8 (Invalid code point for interchange)
+        formatTest( [cast(string)[0xEF, 0xBF, 0xBF]],
+                    `[x"EF BF BF"c]` );
+
+        // U+FFFF with UTF-16 (Invalid code point for interchange)
+        formatTest( [cast(wstring)[0xFFFF]],
+                    `[x"FFFF"w]` );
+
+        // U+FFFF with UTF-32 (Invalid code point for interchange)
+        formatTest( [cast(dstring)[0xFFFF]],
+                    `[x"FFFF"d]` );
+    }
+}
+
+unittest
+{
+    // nested range formatting with array of string
+    formatTest( "%({%(%02x %)}%| %)", ["test", "msg"],
+                `{74 65 73 74} {6d 73 67}` );
+}
+
+unittest
+{
+    // stop auto escaping inside range formatting
+    auto arr = ["hello", "world"];
+    formatTest( "%(%s, %)",  arr, `"hello", "world"` );
+    formatTest( "%-(%s, %)", arr, `hello, world` );
+
+    auto aa1 = [1:"hello", 2:"world"];
+    formatTest( "%(%s:%s, %)",  aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] );
+    formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] );
+
+    auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
+    formatTest( "%-(%s:%s, %)",        aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] );
+    formatTest( "%-(%s:%(%s%), %)",    aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] );
+    formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] );
+}
+
+// input range formatting
+private void formatRange(Writer, T, Char)(ref Writer w, ref T val, ref FormatSpec!Char f)
+if (isInputRange!T)
+{
+    import std.conv : text;
+
+    // Formatting character ranges like string
+    if (f.spec == 's')
+    {
+        alias E = ElementType!T;
+
+        static if (!is(E == enum) && is(CharTypeOf!E))
+        {
+            static if (is(StringTypeOf!T))
+            {
+                auto s = val[0 .. f.precision < $ ? f.precision : $];
+                if (!f.flDash)
+                {
+                    // right align
+                    if (f.width > s.length)
+                        foreach (i ; 0 .. f.width - s.length) put(w, ' ');
+                    put(w, s);
+                }
+                else
+                {
+                    // left align
+                    put(w, s);
+                    if (f.width > s.length)
+                        foreach (i ; 0 .. f.width - s.length) put(w, ' ');
+                }
+            }
+            else
+            {
+                if (!f.flDash)
+                {
+                    static if (hasLength!T)
+                    {
+                        // right align
+                        auto len = val.length;
+                    }
+                    else static if (isForwardRange!T && !isInfinite!T)
+                    {
+                        auto len = walkLength(val.save);
+                    }
+                    else
+                    {
+                        enforce(f.width == 0, "Cannot right-align a range without length");
+                        size_t len = 0;
+                    }
+                    if (f.precision != f.UNSPECIFIED && len > f.precision)
+                        len = f.precision;
+
+                    if (f.width > len)
+                        foreach (i ; 0 .. f.width - len)
+                            put(w, ' ');
+                    if (f.precision == f.UNSPECIFIED)
+                        put(w, val);
+                    else
+                    {
+                        size_t printed = 0;
+                        for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
+                            put(w, val.front);
+                    }
+                }
+                else
+                {
+                    size_t printed = void;
+
+                    // left align
+                    if (f.precision == f.UNSPECIFIED)
+                    {
+                        static if (hasLength!T)
+                        {
+                            printed = val.length;
+                            put(w, val);
+                        }
+                        else
+                        {
+                            printed = 0;
+                            for (; !val.empty; val.popFront(), ++printed)
+                                put(w, val.front);
+                        }
+                    }
+                    else
+                    {
+                        printed = 0;
+                        for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
+                            put(w, val.front);
+                    }
+
+                    if (f.width > printed)
+                        foreach (i ; 0 .. f.width - printed)
+                            put(w, ' ');
+                }
+            }
+        }
+        else
+        {
+            put(w, f.seqBefore);
+            if (!val.empty)
+            {
+                formatElement(w, val.front, f);
+                val.popFront();
+                for (size_t i; !val.empty; val.popFront(), ++i)
+                {
+                    put(w, f.seqSeparator);
+                    formatElement(w, val.front, f);
+                }
+            }
+            static if (!isInfinite!T) put(w, f.seqAfter);
+        }
+    }
+    else if (f.spec == 'r')
+    {
+        static if (is(DynamicArrayTypeOf!T))
+        {
+            alias ARR = DynamicArrayTypeOf!T;
+            foreach (e ; cast(ARR)val)
+            {
+                formatValue(w, e, f);
+            }
+        }
+        else
+        {
+            for (size_t i; !val.empty; val.popFront(), ++i)
+            {
+                formatValue(w, val.front, f);
+            }
+        }
+    }
+    else if (f.spec == '(')
+    {
+        if (val.empty)
+            return;
+        // Nested specifier is to be used
+        for (;;)
+        {
+            auto fmt = FormatSpec!Char(f.nested);
+            fmt.writeUpToNextSpec(w);
+            if (f.flDash)
+                formatValue(w, val.front, fmt);
+            else
+                formatElement(w, val.front, fmt);
+            if (f.sep.ptr)
+            {
+                put(w, fmt.trailing);
+                val.popFront();
+                if (val.empty)
+                    break;
+                put(w, f.sep);
+            }
+            else
+            {
+                val.popFront();
+                if (val.empty)
+                    break;
+                put(w, fmt.trailing);
+            }
+        }
+    }
+    else
+        throw new Exception(text("Incorrect format specifier for range: %", f.spec));
+}
+
+// character formatting with ecaping
+private void formatChar(Writer)(Writer w, in dchar c, in char quote)
+{
+    import std.uni : isGraphical;
+
+    string fmt;
+    if (isGraphical(c))
+    {
+        if (c == quote || c == '\\')
+            put(w, '\\');
+        put(w, c);
+        return;
+    }
+    else if (c <= 0xFF)
+    {
+        if (c < 0x20)
+        {
+            foreach (i, k; "\n\r\t\a\b\f\v\0")
+            {
+                if (c == k)
+                {
+                    put(w, '\\');
+                    put(w, "nrtabfv0"[i]);
+                    return;
+                }
+            }
+        }
+        fmt = "\\x%02X";
+    }
+    else if (c <= 0xFFFF)
+        fmt = "\\u%04X";
+    else
+        fmt = "\\U%08X";
+
+    formattedWrite(w, fmt, cast(uint)c);
+}
+
+// undocumented because of deprecation
+// string elements are formatted like UTF-8 string literals.
+void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (is(StringTypeOf!T) && !is(T == enum))
+{
+    import std.utf : UTFException;
+    import std.array : appender;
+
+    StringTypeOf!T str = val;   // bug 8015
+
+    if (f.spec == 's')
+    {
+        try
+        {
+            // ignore other specifications and quote
+            auto app = appender!(typeof(val[0])[])();
+            put(app, '\"');
+            for (size_t i = 0; i < str.length; )
+            {
+                import std.utf : decode;
+
+                auto c = decode(str, i);
+                // \uFFFE and \uFFFF are considered valid by isValidDchar,
+                // so need checking for interchange.
+                if (c == 0xFFFE || c == 0xFFFF)
+                    goto LinvalidSeq;
+                formatChar(app, c, '"');
+            }
+            put(app, '\"');
+            put(w, app.data);
+            return;
+        }
+        catch (UTFException)
+        {
+        }
+
+        // If val contains invalid UTF sequence, formatted like HexString literal
+    LinvalidSeq:
+        static if (is(typeof(str[0]) : const(char)))
+        {
+            enum postfix = 'c';
+            alias IntArr = const(ubyte)[];
+        }
+        else static if (is(typeof(str[0]) : const(wchar)))
+        {
+            enum postfix = 'w';
+            alias IntArr = const(ushort)[];
+        }
+        else static if (is(typeof(str[0]) : const(dchar)))
+        {
+            enum postfix = 'd';
+            alias IntArr = const(uint)[];
+        }
+        formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr)str, postfix);
+    }
+    else
+        formatValue(w, str, f);
+}
+
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    formatElement(w, "Hello World", spec);
+
+    assert(w.data == "\"Hello World\"");
+}
+
+unittest
+{
+    // Test for bug 8015
+    import std.typecons;
+
+    struct MyStruct {
+        string str;
+        @property string toStr() {
+            return str;
+        }
+        alias toStr this;
+    }
+
+    Tuple!(MyStruct) t;
+}
+
+// undocumented because of deprecation
+// Character elements are formatted like UTF-8 character literals.
+void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (is(CharTypeOf!T) && !is(T == enum))
+{
+    if (f.spec == 's')
+    {
+        put(w, '\'');
+        formatChar(w, val, '\'');
+        put(w, '\'');
+    }
+    else
+        formatValue(w, val, f);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    formatElement(w, "H", spec);
+
+    assert(w.data == "\"H\"", w.data);
+}
+
+// undocumented
+// Maybe T is noncopyable struct, so receive it by 'auto ref'.
+void formatElement(Writer, T, Char)(Writer w, auto ref T val, ref FormatSpec!Char f)
+if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum))
+{
+    formatValue(w, val, f);
+}
+
+/**
+   Associative arrays are formatted by using $(D ':') and $(D ", ") as
+   separators, and enclosed by $(D '[') and $(D ']').
+
+Params:
+    w = The $(D OutputRange) to write to.
+    obj = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f)
+if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
+{
+    AssocArrayTypeOf!T val = obj;
+
+    enforceFmt(f.spec == 's' || f.spec == '(',
+        "associative");
+
+    enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
+    auto fmtSpec = f.spec == '(' ? f.nested : defSpec;
+
+    size_t i = 0, end = val.length;
+
+    if (f.spec == 's')
+        put(w, f.seqBefore);
+    foreach (k, ref v; val)
+    {
+        auto fmt = FormatSpec!Char(fmtSpec);
+        fmt.writeUpToNextSpec(w);
+        if (f.flDash)
+        {
+            formatValue(w, k, fmt);
+            fmt.writeUpToNextSpec(w);
+            formatValue(w, v, fmt);
+        }
+        else
+        {
+            formatElement(w, k, fmt);
+            fmt.writeUpToNextSpec(w);
+            formatElement(w, v, fmt);
+        }
+        if (f.sep !is null)
+        {
+            fmt.writeUpToNextSpec(w);
+            if (++i != end)
+                put(w, f.sep);
+        }
+        else
+        {
+            if (++i != end)
+                fmt.writeUpToNextSpec(w);
+        }
+    }
+    if (f.spec == 's')
+        put(w, f.seqAfter);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+    auto aa = ["H":"W"];
+    formatElement(w, aa, spec);
+
+    assert(w.data == "[\"H\":\"W\"]", w.data);
+}
+
+unittest
+{
+    int[string] aa0;
+    formatTest( aa0, `[]` );
+
+    // elements escaping
+    formatTest(  ["aaa":1, "bbb":2],
+               [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] );
+    formatTest(  ['c':"str"],
+                `['c':"str"]` );
+    formatTest(  ['"':"\"", '\'':"'"],
+               [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] );
+
+    // range formatting for AA
+    auto aa3 = [1:"hello", 2:"world"];
+    // escape
+    formatTest( "{%(%s:%s $ %)}", aa3,
+               [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
+    // use range formatting for key and value, and use %|
+    formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3,
+               [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] );
+
+    // issue 12135
+    formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
+    formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
+}
+
+unittest
+{
+    class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } }
+    class C2 { int[char] val; alias val this; this(int[char] v){ val = v; }
+               override string toString() const { return "C"; } }
+    formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
+    formatTest( new C2(['c':1, 'd':2]), "C" );
+
+    struct S1 { int[char] val; alias val this; }
+    struct S2 { int[char] val; alias val this;
+                string toString() const { return "S"; } }
+    formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
+    formatTest( S2(['c':1, 'd':2]), "S" );
+}
+
+unittest  // Issue 8921
+{
+    enum E : char { A = 'a', B = 'b', C = 'c' }
+    E[3] e = [E.A, E.B, E.C];
+    formatTest(e, "[A, B, C]");
+
+    E[] e2 = [E.A, E.B, E.C];
+    formatTest(e2, "[A, B, C]");
+}
+
+template hasToString(T, Char)
+{
+    static if(isPointer!T && !isAggregateType!T)
+    {
+        // X* does not have toString, even if X is aggregate type has toString.
+        enum hasToString = 0;
+    }
+    else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((const(char)[] s){}, f); })))
+    {
+        enum hasToString = 4;
+    }
+    else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}, "%s"); })))
+    {
+        enum hasToString = 3;
+    }
+    else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}); })))
+    {
+        enum hasToString = 2;
+    }
+    else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S)
+    {
+        enum hasToString = 1;
+    }
+    else
+    {
+        enum hasToString = 0;
+    }
+}
+
+// object formatting with toString
+private void formatObject(Writer, T, Char)(ref Writer w, ref T val, ref FormatSpec!Char f)
+if (hasToString!(T, Char))
+{
+    static if (is(typeof(val.toString((const(char)[] s){}, f))))
+    {
+        val.toString((const(char)[] s) { put(w, s); }, f);
+    }
+    else static if (is(typeof(val.toString((const(char)[] s){}, "%s"))))
+    {
+        val.toString((const(char)[] s) { put(w, s); }, f.getCurFmtStr());
+    }
+    else static if (is(typeof(val.toString((const(char)[] s){}))))
+    {
+        val.toString((const(char)[] s) { put(w, s); });
+    }
+    else static if (is(typeof(val.toString()) S) && isSomeString!S)
+    {
+        put(w, val.toString());
+    }
+    else
+        static assert(0);
+}
+
+void enforceValidFormatSpec(T, Char)(ref FormatSpec!Char f)
+{
+    static if (!isInputRange!T && hasToString!(T, Char) != 4)
+    {
+        enforceFmt(f.spec == 's',
+            "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
+    }
+}
+
+unittest
+{
+    static interface IF1 { }
+    class CIF1 : IF1 { }
+    static struct SF1 { }
+    static union UF1 { }
+    static class CF1 { }
+
+    static interface IF2 { string toString(); }
+    static class CIF2 : IF2 { override string toString() { return ""; } }
+    static struct SF2 { string toString() { return ""; } }
+    static union UF2 { string toString() { return ""; } }
+    static class CF2 { override string toString() { return ""; } }
+
+    static interface IK1 { void toString(scope void delegate(const(char)[]) sink,
+                           FormatSpec!char) const; }
+    static class CIK1 : IK1 { override void toString(scope void delegate(const(char)[]) sink,
+                              FormatSpec!char) const { sink("CIK1"); } }
+    static struct KS1 { void toString(scope void delegate(const(char)[]) sink,
+                        FormatSpec!char) const { sink("KS1"); } }
+
+    static union KU1 { void toString(scope void delegate(const(char)[]) sink,
+                       FormatSpec!char) const { sink("KU1"); } }
+
+    static class KC1 { void toString(scope void delegate(const(char)[]) sink,
+                       FormatSpec!char) const { sink("KC1"); } }
+
+    IF1 cif1 = new CIF1;
+    assertThrown!FormatException(format("%f", cif1));
+    assertThrown!FormatException(format("%f", SF1()));
+    assertThrown!FormatException(format("%f", UF1()));
+    assertThrown!FormatException(format("%f", new CF1()));
+
+    IF2 cif2 = new CIF2;
+    assertThrown!FormatException(format("%f", cif2));
+    assertThrown!FormatException(format("%f", SF2()));
+    assertThrown!FormatException(format("%f", UF2()));
+    assertThrown!FormatException(format("%f", new CF2()));
+
+    IK1 cik1 = new CIK1;
+    assert(format("%f", cik1) == "CIK1");
+    assert(format("%f", KS1()) == "KS1");
+    assert(format("%f", KU1()) == "KU1");
+    assert(format("%f", new KC1()) == "KC1");
+}
+
+/**
+   Aggregates ($(D struct), $(D union), $(D class), and $(D interface)) are
+   basically formatted by calling $(D toString).
+   $(D toString) should have one of the following signatures:
+
+---
+const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt);
+const void toString(scope void delegate(const(char)[]) sink, string fmt);
+const void toString(scope void delegate(const(char)[]) sink);
+const string toString();
+---
+
+   For the class objects which have input range interface,
+   $(UL $(LI If the instance $(D toString) has overridden
+             $(D Object.toString), it is used.)
+        $(LI Otherwise, the objects are formatted as input range.))
+
+   For the struct and union objects which does not have $(D toString),
+   $(UL $(LI If they have range interface, formatted as input range.)
+        $(LI Otherwise, they are formatted like $(D Type(field1, filed2, ...)).))
+
+   Otherwise, are formatted just as their type name.
+ */
+void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (is(T == class) && !is(T == enum))
+{
+    enforceValidFormatSpec!(T, Char)(f);
+    // TODO: Change this once toString() works for shared objects.
+    static assert(!is(T == shared), "unable to format shared objects");
+
+    if (val is null)
+        put(w, "null");
+    else
+    {
+        static if (hasToString!(T, Char) > 1 || (!isInputRange!T && !is(BuiltinTypeOf!T)))
+        {
+            formatObject!(Writer, T, Char)(w, val, f);
+        }
+        else
+        {
+          //string delegate() dg = &val.toString;
+            Object o = val;     // workaround
+            string delegate() dg = &o.toString;
+            if (dg.funcptr != &Object.toString) // toString is overridden
+            {
+                formatObject(w, val, f);
+            }
+            else static if (isInputRange!T)
+            {
+                formatRange(w, val, f);
+            }
+            else static if (is(BuiltinTypeOf!T X))
+            {
+                X x = val;
+                formatValue(w, x, f);
+            }
+            else
+            {
+                formatObject(w, val, f);
+            }
+        }
+    }
+}
+
+/++
+   $(D formatValue) allows to reuse existing format specifiers:
+ +/
+unittest
+{
+   import std.format;
+
+   struct Point
+   {
+       int x, y;
+
+       void toString(scope void delegate(const(char)[]) sink,
+                     FormatSpec!char fmt) const
+       {
+           sink("(");
+           sink.formatValue(x, fmt);
+           sink(",");
+           sink.formatValue(y, fmt);
+           sink(")");
+       }
+   }
+
+   auto p = Point(16,11);
+   assert(format("%03d", p) == "(016,011)");
+   assert(format("%02x", p) == "(10,0b)");
+}
+
+/++
+   The following code compares the use of $(D formatValue) and $(D formattedWrite).
+ +/
+unittest
+{
+   import std.format;
+   import std.array : appender;
+
+   auto writer1 = appender!string();
+   writer1.formattedWrite("%08b", 42);
+
+   auto writer2 = appender!string();
+   auto f = singleSpec("%08b");
+   writer2.formatValue(42, f);
+
+   assert(writer1.data == writer2.data && writer1.data == "00101010");
+}
+
+unittest
+{
+    import std.array : appender;
+    import std.range.interfaces;
+    // class range (issue 5154)
+    auto c = inputRangeObject([1,2,3,4]);
+    formatTest( c, "[1, 2, 3, 4]" );
+    assert(c.empty);
+    c = null;
+    formatTest( c, "null" );
+}
+
+unittest
+{
+    // 5354
+    // If the class has both range I/F and custom toString, the use of custom
+    // toString routine is prioritized.
+
+    // Enable the use of custom toString that gets a sink delegate
+    // for class formatting.
+
+    enum inputRangeCode =
+    q{
+        int[] arr;
+        this(int[] a){ arr = a; }
+        @property int front() const { return arr[0]; }
+        @property bool empty() const { return arr.length == 0; }
+        void popFront(){ arr = arr[1..$]; }
+    };
+
+    class C1
+    {
+        mixin(inputRangeCode);
+        void toString(scope void delegate(const(char)[]) dg, ref FormatSpec!char f) const { dg("[012]"); }
+    }
+    class C2
+    {
+        mixin(inputRangeCode);
+        void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
+    }
+    class C3
+    {
+        mixin(inputRangeCode);
+        void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
+    }
+    class C4
+    {
+        mixin(inputRangeCode);
+        override string toString() const { return "[012]"; }
+    }
+    class C5
+    {
+        mixin(inputRangeCode);
+    }
+
+    formatTest( new C1([0, 1, 2]), "[012]" );
+    formatTest( new C2([0, 1, 2]), "[012]" );
+    formatTest( new C3([0, 1, 2]), "[012]" );
+    formatTest( new C4([0, 1, 2]), "[012]" );
+    formatTest( new C5([0, 1, 2]), "[0, 1, 2]" );
+}
+
+/// ditto
+void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
+{
+    enforceValidFormatSpec!(T, Char)(f);
+    if (val is null)
+        put(w, "null");
+    else
+    {
+        static if (hasToString!(T, Char))
+        {
+            formatObject(w, val, f);
+        }
+        else static if (isInputRange!T)
+        {
+            formatRange(w, val, f);
+        }
+        else
+        {
+            version (Windows)
+            {
+                import core.sys.windows.com : IUnknown;
+                static if (is(T : IUnknown))
+                {
+                    formatValue(w, *cast(void**)&val, f);
+                }
+                else
+                {
+                    formatValue(w, cast(Object)val, f);
+                }
+            }
+            else
+            {
+                formatValue(w, cast(Object)val, f);
+            }
+        }
+    }
+}
+
+unittest
+{
+    // interface
+    import std.range.interfaces;
+    InputRange!int i = inputRangeObject([1,2,3,4]);
+    formatTest( i, "[1, 2, 3, 4]" );
+    assert(i.empty);
+    i = null;
+    formatTest( i, "null" );
+
+    // interface (downcast to Object)
+    interface Whatever {}
+    class C : Whatever
+    {
+        override @property string toString() const { return "ab"; }
+    }
+    Whatever val = new C;
+    formatTest( val, "ab" );
+
+    // Issue 11175
+    version (Windows)
+    {
+        import core.sys.windows.windows : HRESULT;
+        import core.sys.windows.com : IUnknown, IID;
+
+        interface IUnknown2 : IUnknown { }
+
+        class D : IUnknown2
+        {
+            extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
+            extern(Windows) uint AddRef() { return 0; }
+            extern(Windows) uint Release() { return 0; }
+        }
+
+        IUnknown2 d = new D;
+        string expected = format("%X", cast(void*)d);
+        formatTest(d, expected);
+    }
+}
+
+/// ditto
+// Maybe T is noncopyable struct, so receive it by 'auto ref'.
+void formatValue(Writer, T, Char)(Writer w, auto ref T val, ref FormatSpec!Char f)
+if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
+{
+    enforceValidFormatSpec!(T, Char)(f);
+    static if (hasToString!(T, Char))
+    {
+        formatObject(w, val, f);
+    }
+    else static if (isInputRange!T)
+    {
+        formatRange(w, val, f);
+    }
+    else static if (is(T == struct))
+    {
+        enum left = T.stringof~"(";
+        enum separator = ", ";
+        enum right = ")";
+
+        put(w, left);
+        foreach (i, e; val.tupleof)
+        {
+            static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
+            {
+                static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof)
+                    put(w, separator~val.tupleof[i].stringof[4..$]~"}");
+                else
+                    put(w, separator~val.tupleof[i].stringof[4..$]);
+            }
+            else
+            {
+                static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof)
+                    put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]);
+                else
+                {
+                    static if (i > 0)
+                        put(w, separator);
+                    formatElement(w, e, f);
+                }
+            }
+        }
+        put(w, right);
+    }
+    else
+    {
+        put(w, T.stringof);
+    }
+}
+
+unittest
+{
+    // bug 4638
+    struct U8  {  string toString() const { return "blah"; } }
+    struct U16 { wstring toString() const { return "blah"; } }
+    struct U32 { dstring toString() const { return "blah"; } }
+    formatTest( U8(), "blah" );
+    formatTest( U16(), "blah" );
+    formatTest( U32(), "blah" );
+}
+
+unittest
+{
+    // 3890
+    struct Int{ int n; }
+    struct Pair{ string s; Int i; }
+    formatTest( Pair("hello", Int(5)),
+                `Pair("hello", Int(5))` );
+}
+
+unittest
+{
+    // union formatting without toString
+    union U1
+    {
+        int n;
+        string s;
+    }
+    U1 u1;
+    formatTest( u1, "U1" );
+
+    // union formatting with toString
+    union U2
+    {
+        int n;
+        string s;
+        string toString() const { return s; }
+    }
+    U2 u2;
+    u2.s = "hello";
+    formatTest( u2, "hello" );
+}
+
+unittest
+{
+    import std.array;
+    // 7230
+    static struct Bug7230
+    {
+        string s = "hello";
+        union {
+            string a;
+            int b;
+            double c;
+        }
+        long x = 10;
+    }
+
+    Bug7230 bug;
+    bug.b = 123;
+
+    FormatSpec!char f;
+    auto w = appender!(char[])();
+    formatValue(w, bug, f);
+    assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
+}
+
+unittest
+{
+    import std.array;
+    static struct S{ @disable this(this); }
+    S s;
+
+    FormatSpec!char f;
+    auto w = appender!string();
+    formatValue(w, s, f);
+    assert(w.data == "S()");
+}
+
+/**
+$(D enum) is formatted like its base value.
+
+Params:
+    w = The $(D OutputRange) to write to.
+    val = The value to write.
+    f = The $(D FormatSpec) defining how to write the value.
+ */
+void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (is(T == enum))
+{
+    if (f.spec == 's')
+    {
+        foreach (i, e; EnumMembers!T)
+        {
+            if (val == e)
+            {
+                formatValue(w, __traits(allMembers, T)[i], f);
+                return;
+            }
+        }
+
+        // val is not a member of T, output cast(T)rawValue instead.
+        put(w, "cast(" ~ T.stringof ~ ")");
+        static assert(!is(OriginalType!T == T));
+    }
+    formatValue(w, cast(OriginalType!T)val, f);
+}
+
+///
+unittest
+{
+    import std.array : appender;
+    auto w = appender!string();
+    auto spec = singleSpec("%s");
+
+    enum A { first, second, third }
+
+    formatElement(w, A.second, spec);
+
+    assert(w.data == "second");
+}
+
+unittest
+{
+    enum A { first, second, third }
+    formatTest( A.second, "second" );
+    formatTest( cast(A)72, "cast(A)72" );
+}
+unittest
+{
+    enum A : string { one = "uno", two = "dos", three = "tres" }
+    formatTest( A.three, "three" );
+    formatTest( cast(A)"mill\&oacute;n", "cast(A)mill\&oacute;n" );
+}
+unittest
+{
+    enum A : bool { no, yes }
+    formatTest( A.yes, "yes" );
+    formatTest( A.no, "no" );
+}
+unittest
+{
+    // Test for bug 6892
+    enum Foo { A = 10 }
+    formatTest("%s",    Foo.A, "A");
+    formatTest(">%4s<", Foo.A, ">   A<");
+    formatTest("%04d",  Foo.A, "0010");
+    formatTest("%+2u",  Foo.A, "+10");
+    formatTest("%02x",  Foo.A, "0a");
+    formatTest("%3o",   Foo.A, " 12");
+    formatTest("%b",    Foo.A, "1010");
+}
+
+/**
+   Pointers are formatted as hex integers.
+ */
+void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
+if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
+{
+    static if (isInputRange!T)
+    {
+        if (val !is null)
+        {
+            formatRange(w, *val, f);
+            return;
+        }
+    }
+
+    static if (is(typeof({ shared const void* p = val; })))
+        alias SharedOf(T) = shared(T);
+    else
+        alias SharedOf(T) = T;
+
+    const SharedOf!(void*) p = val;
+    const pnum = ()@trusted{ return cast(ulong) p; }();
+
+    if (f.spec == 's')
+    {
+        if (p is null)
+        {
+            put(w, "null");
+            return;
+        }
+        FormatSpec!Char fs = f; // fs is copy for change its values.
+        fs.spec = 'X';
+        formatValue(w, pnum, fs);
+    }
+    else
+    {
+        enforceFmt(f.spec == 'X' || f.spec == 'x',
+           "Expected one of %s, %x or %X for pointer type.");
+        formatValue(w, pnum, f);
+    }
+}
+
+@safe pure unittest
+{
+    // pointer
+    import std.range;
+    auto r = retro([1,2,3,4]);
+    auto p = ()@trusted{ auto p = &r; return p; }();
+    formatTest( p, "[4, 3, 2, 1]" );
+    assert(p.empty);
+    p = null;
+    formatTest( p, "null" );
+
+    auto q = ()@trusted{ return cast(void*)0xFFEECCAA; }();
+    formatTest( q, "FFEECCAA" );
+}
+
+pure unittest
+{
+    // Test for issue 7869
+    struct S
+    {
+        string toString() const { return ""; }
+    }
+    S* p = null;
+    formatTest( p, "null" );
+
+    S* q = cast(S*)0xFFEECCAA;
+    formatTest( q, "FFEECCAA" );
+}
+
+unittest
+{
+    // Test for issue 8186
+    class B
+    {
+        int*a;
+        this(){ a = new int; }
+        alias a this;
+    }
+    formatTest( B.init, "null" );
+}
+
+pure unittest
+{
+    // Test for issue 9336
+    shared int i;
+    format("%s", &i);
+}
+
+pure unittest
+{
+    // Test for issue 11778
+    int* p = null;
+    assertThrown(format("%d", p));
+    assertThrown(format("%04d", p + 2));
+}
+
+pure unittest
+{
+    // Test for issue 12505
+    void* p = null;
+    formatTest( "%08X", p, "00000000" );
+}
+
+/**
+   Delegates are formatted by 'ReturnType delegate(Parameters) FunctionAttributes'
+ */
+void formatValue(Writer, T, Char)(Writer w, scope T, ref FormatSpec!Char f)
+    if (isDelegate!T)
+{
+    formatValue(w, T.stringof, f);
+}
+
+///
+unittest
+{
+    import std.conv : to;
+
+    int i;
+
+    int foo(short k) @nogc
+    {
+        return i + k;
+    }
+
+    int delegate(short) @nogc bar() nothrow
+    {
+        return &foo;
+    }
+
+    assert(to!string(&bar) == "int delegate(short) @nogc delegate() nothrow");
+}
+
+unittest
+{
+    void func() {}
+    formatTest( &func, "void delegate()" );
+}
+
+/*
+  Formats an object of type 'D' according to 'f' and writes it to
+  'w'. The pointer 'arg' is assumed to point to an object of type
+  'D'. The untyped signature is for the sake of taking this function's
+  address.
+ */
+private void formatGeneric(Writer, D, Char)(Writer w, const(void)* arg, ref FormatSpec!Char f)
+{
+    formatValue(w, *cast(D*) arg, f);
+}
+
+private void formatNth(Writer, Char, A...)(Writer w, ref FormatSpec!Char f, size_t index, A args)
+{
+    import std.conv : to;
+    static string gencode(size_t count)()
+    {
+        string result;
+        foreach (n; 0 .. count)
+        {
+            auto num = to!string(n);
+            result ~=
+                "case "~num~":"~
+                "    formatValue(w, args["~num~"], f);"~
+                "    break;";
+        }
+        return result;
+    }
+
+    switch (index)
+    {
+        mixin(gencode!(A.length)());
+
+        default:
+            assert(0, "n = "~cast(char)(index + '0'));
+    }
+}
+
+pure unittest
+{
+    int[] a = [ 1, 3, 2 ];
+    formatTest( "testing %(%s & %) embedded", a,
+                "testing 1 & 3 & 2 embedded");
+    formatTest( "testing %((%s) %)) wyda3", a,
+                "testing (1) (3) (2) wyda3" );
+
+    int[0] empt = [];
+    formatTest( "(%s)", empt,
+                "([])" );
+}
+
+//------------------------------------------------------------------------------
+// Fix for issue 1591
+private int getNthInt(A...)(uint index, A args)
+{
+    import std.conv : to;
+    static if (A.length)
+    {
+        if (index)
+        {
+            return getNthInt(index - 1, args[1 .. $]);
+        }
+        static if (isIntegral!(typeof(args[0])))
+        {
+            return to!int(args[0]);
+        }
+        else
+        {
+            throw new FormatException("int expected");
+        }
+    }
+    else
+    {
+        throw new FormatException("int expected");
+    }
+}
+
+/* ======================== Unit Tests ====================================== */
+
+version(unittest)
+void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
+{
+    import core.exception;
+    import std.array : appender;
+    import std.conv : text;
+    FormatSpec!char f;
+    auto w = appender!string();
+    formatValue(w, val, f);
+    enforce!AssertError(
+            w.data == expected,
+            text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
+}
+
+version(unittest)
+void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
+{
+    import core.exception;
+    import std.array : appender;
+    import std.conv : text;
+    auto w = appender!string();
+    formattedWrite(w, fmt, val);
+    enforce!AssertError(
+            w.data == expected,
+            text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
+}
+
+version(unittest)
+void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
+{
+    import core.exception;
+    import std.conv : text;
+    import std.array : appender;
+    FormatSpec!char f;
+    auto w = appender!string();
+    formatValue(w, val, f);
+    foreach(cur; expected)
+    {
+        if(w.data == cur) return;
+    }
+    enforce!AssertError(
+            false,
+            text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
+}
+
+version(unittest)
+void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
+{
+    import core.exception;
+    import std.conv : text;
+    import std.array : appender;
+    auto w = appender!string();
+    formattedWrite(w, fmt, val);
+    foreach(cur; expected)
+    {
+        if(w.data == cur) return;
+    }
+    enforce!AssertError(
+            false,
+            text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
+}
+
+@safe /*pure*/ unittest     // formatting floating point values is now impure
+{
+    import std.array;
+
+    auto stream = appender!string();
+    formattedWrite(stream, "%s", 1.1);
+    assert(stream.data == "1.1", stream.data);
+}
+
+pure unittest
+{
+    import std.algorithm;
+    import std.array;
+
+    auto stream = appender!string();
+    formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
+    assert(stream.data == "[4, 9, 25]", stream.data);
+
+    // Test shared data.
+    stream = appender!string();
+    shared int s = 6;
+    formattedWrite(stream, "%s", s);
+    assert(stream.data == "6");
+}
+
+pure unittest
+{
+    import std.array;
+    auto stream = appender!string();
+    formattedWrite(stream, "%u", 42);
+    assert(stream.data == "42", stream.data);
+}
+
+pure unittest
+{
+    // testing raw writes
+    import std.array;
+    auto w = appender!(char[])();
+    uint a = 0x02030405;
+    formattedWrite(w, "%+r", a);
+    assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
+        && w.data[2] == 4 && w.data[3] == 5);
+    w.clear();
+    formattedWrite(w, "%-r", a);
+    assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
+        && w.data[2] == 3 && w.data[3] == 2);
+}
+
+pure unittest
+{
+    // testing positional parameters
+    import std.array;
+    auto w = appender!(char[])();
+    formattedWrite(w,
+            "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
+            42, 0);
+    assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
+            w.data);
+    w.clear();
+    formattedWrite(w, "asd%s", 23);
+    assert(w.data == "asd23", w.data);
+    w.clear();
+    formattedWrite(w, "%s%s", 23, 45);
+    assert(w.data == "2345", w.data);
+}
+
+unittest
+{
+    import std.conv : text, octal;
+    import std.array : appender;
+    import std.c.stdio : snprintf;
+    import core.stdc.string : strlen;
+
+    debug(format) printf("std.format.format.unittest\n");
+
+    auto stream = appender!(char[])();
+    //goto here;
+
+    formattedWrite(stream,
+            "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
+    assert(stream.data == "hello world! true 57 ",
+        stream.data);
+
+    stream.clear();
+    formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
+    // core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
+
+    /* The host C library is used to format floats.  C99 doesn't
+    * specify what the hex digit before the decimal point is for
+    * %A.  */
+
+    version (CRuntime_Glibc)
+    {
+        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
+                stream.data);
+    }
+    else version (OSX)
+    {
+        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
+                stream.data);
+    }
+    else version (MinGW)
+    {
+        assert(stream.data == "1.67 -0XA.3D70A3D70A3D8P-3 nan",
+                stream.data);
+    }
+    else version (CRuntime_Microsoft)
+    {
+        assert(stream.data == "1.67 -0X1.47AE14P+0 nan"
+            || stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", // MSVCRT 14+ (VS 2015)
+                stream.data);
+    }
+    else
+    {
+        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
+                stream.data);
+    }
+    stream.clear();
+
+    formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
+    assert(stream.data == "1234af AFAFAFAF");
+    stream.clear();
+
+    formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
+    assert(stream.data == "100100011010010101111 25753727657");
+    stream.clear();
+
+    formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
+    assert(stream.data == "1193135 2947526575");
+    stream.clear();
+
+    // formattedWrite(stream, "%s", 1.2 + 3.4i);
+    // assert(stream.data == "1.2+3.4i");
+    // stream.clear();
+
+    formattedWrite(stream, "%a %A", 1.32, 6.78f);
+    //formattedWrite(stream, "%x %X", 1.32);
+    version (CRuntime_Microsoft)
+        assert(stream.data == "0x1.51eb85p+0 0X1.B1EB86P+2"
+            || stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB860000000P+2"); // MSVCRT 14+ (VS 2015)
+    else
+        assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
+    stream.clear();
+
+    formattedWrite(stream, "%#06.*f",2,12.345);
+    assert(stream.data == "012.35");
+    stream.clear();
+
+    formattedWrite(stream, "%#0*.*f",6,2,12.345);
+    assert(stream.data == "012.35");
+    stream.clear();
+
+    const real constreal = 1;
+    formattedWrite(stream, "%g",constreal);
+    assert(stream.data == "1");
+    stream.clear();
+
+    formattedWrite(stream, "%7.4g:", 12.678);
+    assert(stream.data == "  12.68:");
+    stream.clear();
+
+    formattedWrite(stream, "%7.4g:", 12.678L);
+    assert(stream.data == "  12.68:");
+    stream.clear();
+
+    formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
+    assert(stream.data == "-4.000000|-0010|0x001|  0x1",
+            stream.data);
+    stream.clear();
+
+    int i;
+    string s;
+
+    i = -10;
+    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(stream.data == "-10|-10|-10|-10|-10.0000");
+    stream.clear();
+
+    i = -5;
+    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(stream.data == "-5| -5|-05|-5|-5.0000");
+    stream.clear();
+
+    i = 0;
+    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(stream.data == "0|  0|000|0|0.0000");
+    stream.clear();
+
+    i = 5;
+    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(stream.data == "5|  5|005|5|5.0000");
+    stream.clear();
+
+    i = 10;
+    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(stream.data == "10| 10|010|10|10.0000");
+    stream.clear();
+
+    formattedWrite(stream, "%.0d", 0);
+    assert(stream.data == "");
+    stream.clear();
+
+    formattedWrite(stream, "%.g", .34);
+    assert(stream.data == "0.3");
+    stream.clear();
+
+    stream.clear(); formattedWrite(stream, "%.0g", .34);
+    assert(stream.data == "0.3");
+
+    stream.clear(); formattedWrite(stream, "%.2g", .34);
+    assert(stream.data == "0.34");
+
+    stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08);
+    assert(stream.data == "0.00000001");
+
+    stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05);
+    assert(stream.data == "0.00001000");
+
+    //return;
+    //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
+
+    s = "helloworld";
+    string r;
+    stream.clear(); formattedWrite(stream, "%.2s", s[0..5]);
+    assert(stream.data == "he");
+    stream.clear(); formattedWrite(stream, "%.20s", s[0..5]);
+    assert(stream.data == "hello");
+    stream.clear(); formattedWrite(stream, "%8s", s[0..5]);
+    assert(stream.data == "   hello");
+
+    byte[] arrbyte = new byte[4];
+    arrbyte[0] = 100;
+    arrbyte[1] = -99;
+    arrbyte[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrbyte);
+    assert(stream.data == "[100, -99, 0, 0]", stream.data);
+
+    ubyte[] arrubyte = new ubyte[4];
+    arrubyte[0] = 100;
+    arrubyte[1] = 200;
+    arrubyte[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrubyte);
+    assert(stream.data == "[100, 200, 0, 0]", stream.data);
+
+    short[] arrshort = new short[4];
+    arrshort[0] = 100;
+    arrshort[1] = -999;
+    arrshort[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrshort);
+    assert(stream.data == "[100, -999, 0, 0]");
+    stream.clear(); formattedWrite(stream, "%s",arrshort);
+    assert(stream.data == "[100, -999, 0, 0]");
+
+    ushort[] arrushort = new ushort[4];
+    arrushort[0] = 100;
+    arrushort[1] = 20_000;
+    arrushort[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrushort);
+    assert(stream.data == "[100, 20000, 0, 0]");
+
+    int[] arrint = new int[4];
+    arrint[0] = 100;
+    arrint[1] = -999;
+    arrint[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrint);
+    assert(stream.data == "[100, -999, 0, 0]");
+    stream.clear(); formattedWrite(stream, "%s",arrint);
+    assert(stream.data == "[100, -999, 0, 0]");
+
+    long[] arrlong = new long[4];
+    arrlong[0] = 100;
+    arrlong[1] = -999;
+    arrlong[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrlong);
+    assert(stream.data == "[100, -999, 0, 0]");
+    stream.clear(); formattedWrite(stream, "%s",arrlong);
+    assert(stream.data == "[100, -999, 0, 0]");
+
+    ulong[] arrulong = new ulong[4];
+    arrulong[0] = 100;
+    arrulong[1] = 999;
+    arrulong[3] = 0;
+    stream.clear(); formattedWrite(stream, "%s", arrulong);
+    assert(stream.data == "[100, 999, 0, 0]");
+
+    string[] arr2 = new string[4];
+    arr2[0] = "hello";
+    arr2[1] = "world";
+    arr2[3] = "foo";
+    stream.clear(); formattedWrite(stream, "%s", arr2);
+    assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);
+
+    stream.clear(); formattedWrite(stream, "%.8d", 7);
+    assert(stream.data == "00000007");
+
+    stream.clear(); formattedWrite(stream, "%.8x", 10);
+    assert(stream.data == "0000000a");
+
+    stream.clear(); formattedWrite(stream, "%-3d", 7);
+    assert(stream.data == "7  ");
+
+    stream.clear(); formattedWrite(stream, "%*d", -3, 7);
+    assert(stream.data == "7  ");
+
+    stream.clear(); formattedWrite(stream, "%.*d", -3, 7);
+    //writeln(stream.data);
+    assert(stream.data == "7");
+
+    stream.clear(); formattedWrite(stream, "%s", "abc"c);
+    assert(stream.data == "abc");
+    stream.clear(); formattedWrite(stream, "%s", "def"w);
+    assert(stream.data == "def", text(stream.data.length));
+    stream.clear(); formattedWrite(stream, "%s", "ghi"d);
+    assert(stream.data == "ghi");
+
+here:
+    void* p = cast(void*)0xDEADBEEF;
+    stream.clear(); formattedWrite(stream, "%s", p);
+    assert(stream.data == "DEADBEEF", stream.data);
+
+    stream.clear(); formattedWrite(stream, "%#x", 0xabcd);
+    assert(stream.data == "0xabcd");
+    stream.clear(); formattedWrite(stream, "%#X", 0xABCD);
+    assert(stream.data == "0XABCD");
+
+    stream.clear(); formattedWrite(stream, "%#o", octal!12345);
+    assert(stream.data == "012345");
+    stream.clear(); formattedWrite(stream, "%o", 9);
+    assert(stream.data == "11");
+
+    stream.clear(); formattedWrite(stream, "%+d", 123);
+    assert(stream.data == "+123");
+    stream.clear(); formattedWrite(stream, "%+d", -123);
+    assert(stream.data == "-123");
+    stream.clear(); formattedWrite(stream, "% d", 123);
+    assert(stream.data == " 123");
+    stream.clear(); formattedWrite(stream, "% d", -123);
+    assert(stream.data == "-123");
+
+    stream.clear(); formattedWrite(stream, "%%");
+    assert(stream.data == "%");
+
+    stream.clear(); formattedWrite(stream, "%d", true);
+    assert(stream.data == "1");
+    stream.clear(); formattedWrite(stream, "%d", false);
+    assert(stream.data == "0");
+
+    stream.clear(); formattedWrite(stream, "%d", 'a');
+    assert(stream.data == "97", stream.data);
+    wchar wc = 'a';
+    stream.clear(); formattedWrite(stream, "%d", wc);
+    assert(stream.data == "97");
+    dchar dc = 'a';
+    stream.clear(); formattedWrite(stream, "%d", dc);
+    assert(stream.data == "97");
+
+    byte b = byte.max;
+    stream.clear(); formattedWrite(stream, "%x", b);
+    assert(stream.data == "7f");
+    stream.clear(); formattedWrite(stream, "%x", ++b);
+    assert(stream.data == "80");
+    stream.clear(); formattedWrite(stream, "%x", ++b);
+    assert(stream.data == "81");
+
+    short sh = short.max;
+    stream.clear(); formattedWrite(stream, "%x", sh);
+    assert(stream.data == "7fff");
+    stream.clear(); formattedWrite(stream, "%x", ++sh);
+    assert(stream.data == "8000");
+    stream.clear(); formattedWrite(stream, "%x", ++sh);
+    assert(stream.data == "8001");
+
+    i = int.max;
+    stream.clear(); formattedWrite(stream, "%x", i);
+    assert(stream.data == "7fffffff");
+    stream.clear(); formattedWrite(stream, "%x", ++i);
+    assert(stream.data == "80000000");
+    stream.clear(); formattedWrite(stream, "%x", ++i);
+    assert(stream.data == "80000001");
+
+    stream.clear(); formattedWrite(stream, "%x", 10);
+    assert(stream.data == "a");
+    stream.clear(); formattedWrite(stream, "%X", 10);
+    assert(stream.data == "A");
+    stream.clear(); formattedWrite(stream, "%x", 15);
+    assert(stream.data == "f");
+    stream.clear(); formattedWrite(stream, "%X", 15);
+    assert(stream.data == "F");
+
+    Object c = null;
+    stream.clear(); formattedWrite(stream, "%s", c);
+    assert(stream.data == "null");
+
+    enum TestEnum
+    {
+        Value1, Value2
+    }
+    stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2);
+    assert(stream.data == "Value2", stream.data);
+    stream.clear(); formattedWrite(stream, "%s", cast(TestEnum)5);
+    assert(stream.data == "cast(TestEnum)5", stream.data);
+
+    //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
+    //stream.clear(); formattedWrite(stream, "%s", aa.values);
+    //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
+    //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
+    //stream.clear(); formattedWrite(stream, "%s", aa);
+    //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
+
+    static const dchar[] ds = ['a','b'];
+    for (int j = 0; j < ds.length; ++j)
+    {
+        stream.clear(); formattedWrite(stream, " %d", ds[j]);
+        if (j == 0)
+            assert(stream.data == " 97");
+        else
+            assert(stream.data == " 98");
+    }
+
+    stream.clear(); formattedWrite(stream, "%.-3d", 7);
+    assert(stream.data == "7", ">" ~ stream.data ~ "<");
+}
+
+unittest
+{
+    import std.array;
+    import std.stdio;
+
+    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
+    assert(aa[3] == "hello");
+    assert(aa[4] == "betty");
+
+    auto stream = appender!(char[])();
+    alias AllNumerics =
+        AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong,
+                  float, double, real);
+    foreach (T; AllNumerics)
+    {
+        T value = 1;
+        stream.clear();
+        formattedWrite(stream, "%s", value);
+        assert(stream.data == "1");
+    }
+
+    stream.clear();
+    formattedWrite(stream, "%s", aa);
+}
+
+unittest
+{
+    string s = "hello!124:34.5";
+    string a;
+    int b;
+    double c;
+    formattedRead(s, "%s!%s:%s", &a, &b, &c);
+    assert(a == "hello" && b == 124 && c == 34.5);
+}
+
+version(unittest)
+void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__)
+{
+    import core.exception;
+    import std.array : appender;
+    auto w = appender!string();
+    formattedWrite(w, fmt, val);
+
+    auto input = w.data;
+    enforce!AssertError(
+            input == formatted,
+            input, fn, ln);
+
+    T val2;
+    formattedRead(input, fmt, &val2);
+    static if (isAssociativeArray!T)
+    if (__ctfe)
+    {
+        alias aa1 = val;
+        alias aa2 = val2;
+        assert(aa1 == aa2);
+
+        assert(aa1.length == aa2.length);
+
+        assert(aa1.keys == aa2.keys);
+
+        assert(aa1.values == aa2.values);
+        assert(aa1.values.length == aa2.values.length);
+        foreach (i; 0 .. aa1.values.length)
+            assert(aa1.values[i] == aa2.values[i]);
+
+        foreach (i, key; aa1.keys)
+            assert(aa1.values[i] == aa1[key]);
+        foreach (i, key; aa2.keys)
+            assert(aa2.values[i] == aa2[key]);
+        return;
+    }
+    enforce!AssertError(
+            val == val2,
+            input, fn, ln);
+}
+
+version(unittest)
+void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__)
+{
+    import core.exception;
+    import std.array : appender;
+    auto w = appender!string();
+    formattedWrite(w, fmt, val);
+
+    auto input = w.data;
+
+    foreach(cur; formatted)
+    {
+        if(input == cur) return;
+    }
+    enforce!AssertError(
+            false,
+            input,
+            fn,
+            ln);
+
+    T val2;
+    formattedRead(input, fmt, &val2);
+    static if (isAssociativeArray!T)
+    if (__ctfe)
+    {
+        alias aa1 = val;
+        alias aa2 = val2;
+        assert(aa1 == aa2);
+
+        assert(aa1.length == aa2.length);
+
+        assert(aa1.keys == aa2.keys);
+
+        assert(aa1.values == aa2.values);
+        assert(aa1.values.length == aa2.values.length);
+        foreach (i; 0 .. aa1.values.length)
+            assert(aa1.values[i] == aa2.values[i]);
+
+        foreach (i, key; aa1.keys)
+            assert(aa1.values[i] == aa1[key]);
+        foreach (i, key; aa2.keys)
+            assert(aa2.values[i] == aa2[key]);
+        return;
+    }
+    enforce!AssertError(
+            val == val2,
+            input, fn, ln);
+}
+
+unittest
+{
+    void booleanTest()
+    {
+        auto b = true;
+        formatReflectTest(b, "%s",  `true`);
+        formatReflectTest(b, "%b",  `1`);
+        formatReflectTest(b, "%o",  `1`);
+        formatReflectTest(b, "%d",  `1`);
+        formatReflectTest(b, "%u",  `1`);
+        formatReflectTest(b, "%x",  `1`);
+    }
+
+    void integerTest()
+    {
+        auto n = 127;
+        formatReflectTest(n, "%s",  `127`);
+        formatReflectTest(n, "%b",  `1111111`);
+        formatReflectTest(n, "%o",  `177`);
+        formatReflectTest(n, "%d",  `127`);
+        formatReflectTest(n, "%u",  `127`);
+        formatReflectTest(n, "%x",  `7f`);
+    }
+
+    void floatingTest()
+    {
+        auto f = 3.14;
+        formatReflectTest(f, "%s",  `3.14`);
+        version (MinGW)
+            formatReflectTest(f, "%e",  `3.140000e+000`);
+        else
+            formatReflectTest(f, "%e",  `3.140000e+00`);
+        formatReflectTest(f, "%f",  `3.140000`);
+        formatReflectTest(f, "%g",  `3.14`);
+    }
+
+    void charTest()
+    {
+        auto c = 'a';
+        formatReflectTest(c, "%s",  `a`);
+        formatReflectTest(c, "%c",  `a`);
+        formatReflectTest(c, "%b",  `1100001`);
+        formatReflectTest(c, "%o",  `141`);
+        formatReflectTest(c, "%d",  `97`);
+        formatReflectTest(c, "%u",  `97`);
+        formatReflectTest(c, "%x",  `61`);
+    }
+
+    void strTest()
+    {
+        auto s = "hello";
+        formatReflectTest(s, "%s",                      `hello`);
+        formatReflectTest(s, "%(%c,%)",                 `h,e,l,l,o`);
+        formatReflectTest(s, "%(%s,%)",                 `'h','e','l','l','o'`);
+        formatReflectTest(s, "[%(<%c>%| $ %)]",         `[<h> $ <e> $ <l> $ <l> $ <o>]`);
+    }
+
+    void daTest()
+    {
+        auto a = [1,2,3,4];
+        formatReflectTest(a, "%s",                      `[1, 2, 3, 4]`);
+        formatReflectTest(a, "[%(%s; %)]",              `[1; 2; 3; 4]`);
+        formatReflectTest(a, "[%(<%s>%| $ %)]",         `[<1> $ <2> $ <3> $ <4>]`);
+    }
+
+    void saTest()
+    {
+        int[4] sa = [1,2,3,4];
+        formatReflectTest(sa, "%s",                     `[1, 2, 3, 4]`);
+        formatReflectTest(sa, "[%(%s; %)]",             `[1; 2; 3; 4]`);
+        formatReflectTest(sa, "[%(<%s>%| $ %)]",        `[<1> $ <2> $ <3> $ <4>]`);
+    }
+
+    void aaTest()
+    {
+        auto aa = [1:"hello", 2:"world"];
+        formatReflectTest(aa, "%s",                     [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]);
+        formatReflectTest(aa, "[%(%s->%s, %)]",         [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]);
+        formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}",  [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]);
+    }
+
+    import std.exception;
+    assertCTFEable!(
+    {
+        booleanTest();
+        integerTest();
+        if (!__ctfe) floatingTest();    // snprintf
+        charTest();
+        strTest();
+        daTest();
+        saTest();
+        aaTest();
+        return true;
+    });
+}
+
+//------------------------------------------------------------------------------
+private void skipData(Range, Char)(ref Range input, ref FormatSpec!Char spec)
+{
+    import std.ascii : isDigit;
+    import std.conv : text;
+
+    switch (spec.spec)
+    {
+        case 'c': input.popFront(); break;
+        case 'd':
+            if (input.front == '+' || input.front == '-') input.popFront();
+            goto case 'u';
+        case 'u':
+            while (!input.empty && isDigit(input.front)) input.popFront();
+            break;
+        default:
+            assert(false,
+                    text("Format specifier not understood: %", spec.spec));
+    }
+}
+
+private template acceptedSpecs(T)
+{
+         static if (isIntegral!T)       enum acceptedSpecs = "bdosuxX";
+    else static if (isFloatingPoint!T)  enum acceptedSpecs = "seEfgG";
+    else static if (isSomeChar!T)       enum acceptedSpecs = "bcdosuxX";    // integral + 'c'
+    else                                enum acceptedSpecs = "";
+}
+
+/**
+ * Reads a boolean value and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && is(Unqual!T == bool))
+{
+    import std.algorithm : find;
+    import std.conv : parse, text;
+    if (spec.spec == 's')
+    {
+        return parse!T(input);
+    }
+    enforce(find(acceptedSpecs!long, spec.spec).length,
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    return unformatValue!long(input, spec) != 0;
+}
+
+pure unittest
+{
+    string line;
+
+    bool f1;
+
+    line = "true";
+    formattedRead(line, "%s", &f1);
+    assert(f1);
+
+    line = "TrUE";
+    formattedRead(line, "%s", &f1);
+    assert(f1);
+
+    line = "false";
+    formattedRead(line, "%s", &f1);
+    assert(!f1);
+
+    line = "fALsE";
+    formattedRead(line, "%s", &f1);
+    assert(!f1);
+
+    line = "1";
+    formattedRead(line, "%d", &f1);
+    assert(f1);
+
+    line = "-1";
+    formattedRead(line, "%d", &f1);
+    assert(f1);
+
+    line = "0";
+    formattedRead(line, "%d", &f1);
+    assert(!f1);
+
+    line = "-0";
+    formattedRead(line, "%d", &f1);
+    assert(!f1);
+}
+
+/**
+ * Reads null literal and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && is(T == typeof(null)))
+{
+    import std.conv : parse, text;
+    enforce(spec.spec == 's',
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    return parse!T(input);
+}
+
+/**
+   Reads an integral value and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && isIntegral!T && !is(T == enum))
+{
+    import std.algorithm : find;
+    import std.conv : parse, text;
+    enforce(find(acceptedSpecs!T, spec.spec).length,
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    enforce(spec.width == 0, "Parsing integers with a width specification is not implemented");   // TODO
+
+    uint base =
+        spec.spec == 'x' || spec.spec == 'X' ? 16 :
+        spec.spec == 'o' ? 8 :
+        spec.spec == 'b' ? 2 :
+        spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0;
+    assert(base != 0);
+    return parse!T(input, base);
+}
+
+/**
+   Reads a floating-point value and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isFloatingPoint!T && !is(T == enum))
+{
+    import std.algorithm : find;
+    import std.conv : parse, text;
+    if (spec.spec == 'r')
+    {
+        // raw read
+        //enforce(input.length >= T.sizeof);
+        enforce(isSomeString!Range || ElementType!(Range).sizeof == 1, "Cannot parse input of type %s".format(Range.stringof));
+        union X
+        {
+            ubyte[T.sizeof] raw;
+            T typed;
+        }
+        X x;
+        foreach (i; 0 .. T.sizeof)
+        {
+            static if (isSomeString!Range)
+            {
+                x.raw[i] = input[0];
+                input = input[1 .. $];
+            }
+            else
+            {
+                // TODO: recheck this
+                x.raw[i] = cast(ubyte) input.front;
+                input.popFront();
+            }
+        }
+        return x.typed;
+    }
+    enforce(find(acceptedSpecs!T, spec.spec).length,
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    return parse!T(input);
+}
+
+version(none)unittest
+{
+    union A
+    {
+        char[float.sizeof] untyped;
+        float typed;
+    }
+    A a;
+    a.typed = 5.5;
+    char[] input = a.untyped[];
+    float witness;
+    formattedRead(input, "%r", &witness);
+    assert(witness == a.typed);
+}
+
+pure unittest
+{
+    import std.typecons;
+    char[] line = "1 2".dup;
+    int a, b;
+    formattedRead(line, "%s %s", &a, &b);
+    assert(a == 1 && b == 2);
+
+    line = "10 2 3".dup;
+    formattedRead(line, "%d ", &a);
+    assert(a == 10);
+    assert(line == "2 3");
+
+    Tuple!(int, float) t;
+    line = "1 2.125".dup;
+    formattedRead(line, "%d %g", &t);
+    assert(t[0] == 1 && t[1] == 2.125);
+
+    line = "1 7643 2.125".dup;
+    formattedRead(line, "%s %*u %s", &t);
+    assert(t[0] == 1 && t[1] == 2.125);
+}
+
+/**
+ * Reads one character and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && isSomeChar!T && !is(T == enum))
+{
+    import std.algorithm : find;
+    import std.conv : to, text;
+    if (spec.spec == 's' || spec.spec == 'c')
+    {
+        auto result = to!T(input.front);
+        input.popFront();
+        return result;
+    }
+    enforce(find(acceptedSpecs!T, spec.spec).length,
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    static if (T.sizeof == 1)
+        return unformatValue!ubyte(input, spec);
+    else static if (T.sizeof == 2)
+        return unformatValue!ushort(input, spec);
+    else static if (T.sizeof == 4)
+        return unformatValue!uint(input, spec);
+    else
+        static assert(0);
+}
+
+pure unittest
+{
+    string line;
+
+    char c1, c2;
+
+    line = "abc";
+    formattedRead(line, "%s%c", &c1, &c2);
+    assert(c1 == 'a' && c2 == 'b');
+    assert(line == "c");
+}
+
+/**
+   Reads a string and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
+{
+    import std.conv : text;
+
+    if (spec.spec == '(')
+    {
+        return unformatRange!T(input, spec);
+    }
+    enforce(spec.spec == 's',
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    static if (isStaticArray!T)
+    {
+        T result;
+        auto app = result[];
+    }
+    else
+    {
+        import std.array : appender;
+        auto app = appender!T();
+    }
+    if (spec.trailing.empty)
+    {
+        for (; !input.empty; input.popFront())
+        {
+            static if (isStaticArray!T)
+                if (app.empty)
+                    break;
+            app.put(input.front);
+        }
+    }
+    else
+    {
+        auto end = spec.trailing.front;
+        for (; !input.empty && input.front != end; input.popFront())
+        {
+            static if (isStaticArray!T)
+                if (app.empty)
+                    break;
+            app.put(input.front);
+        }
+    }
+    static if (isStaticArray!T)
+    {
+        enforce(app.empty, "need more input");
+        return result;
+    }
+    else
+        return app.data;
+}
+
+pure unittest
+{
+    string line;
+
+    string s1, s2;
+
+    line = "hello, world";
+    formattedRead(line, "%s", &s1);
+    assert(s1 == "hello, world", s1);
+
+    line = "hello, world;yah";
+    formattedRead(line, "%s;%s", &s1, &s2);
+    assert(s1 == "hello, world", s1);
+    assert(s2 == "yah", s2);
+
+    line = `['h','e','l','l','o']`;
+    string s3;
+    formattedRead(line, "[%(%s,%)]", &s3);
+    assert(s3 == "hello");
+
+    line = `"hello"`;
+    string s4;
+    formattedRead(line, "\"%(%c%)\"", &s4);
+    assert(s4 == "hello");
+}
+
+/**
+   Reads an array (except for string types) and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
+{
+    import std.conv : parse, text;
+    if (spec.spec == '(')
+    {
+        return unformatRange!T(input, spec);
+    }
+    enforce(spec.spec == 's',
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    return parse!T(input);
+}
+
+pure unittest
+{
+    string line;
+
+    line = "[1,2,3]";
+    int[] s1;
+    formattedRead(line, "%s", &s1);
+    assert(s1 == [1,2,3]);
+}
+
+pure unittest
+{
+    string line;
+
+    line = "[1,2,3]";
+    int[] s1;
+    formattedRead(line, "[%(%s,%)]", &s1);
+    assert(s1 == [1,2,3]);
+
+    line = `["hello", "world"]`;
+    string[] s2;
+    formattedRead(line, "[%(%s, %)]", &s2);
+    assert(s2 == ["hello", "world"]);
+
+    line = "123 456";
+    int[] s3;
+    formattedRead(line, "%(%s %)", &s3);
+    assert(s3 == [123, 456]);
+
+    line = "h,e,l,l,o; w,o,r,l,d";
+    string[] s4;
+    formattedRead(line, "%(%(%c,%); %)", &s4);
+    assert(s4 == ["hello", "world"]);
+}
+
+pure unittest
+{
+    string line;
+
+    int[4] sa1;
+    line = `[1,2,3,4]`;
+    formattedRead(line, "%s", &sa1);
+    assert(sa1 == [1,2,3,4]);
+
+    int[4] sa2;
+    line = `[1,2,3]`;
+    assertThrown(formattedRead(line, "%s", &sa2));
+
+    int[4] sa3;
+    line = `[1,2,3,4,5]`;
+    assertThrown(formattedRead(line, "%s", &sa3));
+}
+
+pure unittest
+{
+    string input;
+
+    int[4] sa1;
+    input = `[1,2,3,4]`;
+    formattedRead(input, "[%(%s,%)]", &sa1);
+    assert(sa1 == [1,2,3,4]);
+
+    int[4] sa2;
+    input = `[1,2,3]`;
+    assertThrown(formattedRead(input, "[%(%s,%)]", &sa2));
+}
+
+pure unittest
+{
+    // 7241
+    string input = "a";
+    auto spec = FormatSpec!char("%s");
+    spec.readUpToNextSpec(input);
+    auto result = unformatValue!(dchar[1])(input, spec);
+    assert(result[0] == 'a');
+}
+
+/**
+ * Reads an associative array and returns it.
+ */
+T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range && isAssociativeArray!T && !is(T == enum))
+{
+    import std.conv : parse, text;
+    if (spec.spec == '(')
+    {
+        return unformatRange!T(input, spec);
+    }
+    enforce(spec.spec == 's',
+            text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
+
+    return parse!T(input);
+}
+
+pure unittest
+{
+    string line;
+
+    string[int] aa1;
+    line = `[1:"hello", 2:"world"]`;
+    formattedRead(line, "%s", &aa1);
+    assert(aa1 == [1:"hello", 2:"world"]);
+
+    int[string] aa2;
+    line = `{"hello"=1; "world"=2}`;
+    formattedRead(line, "{%(%s=%s; %)}", &aa2);
+    assert(aa2 == ["hello":1, "world":2]);
+
+    int[string] aa3;
+    line = `{[hello=1]; [world=2]}`;
+    formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3);
+    assert(aa3 == ["hello":1, "world":2]);
+}
+
+//debug = unformatRange;
+
+private T unformatRange(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+in
+{
+    assert(spec.spec == '(');
+}
+body
+{
+    debug (unformatRange) printf("unformatRange:\n");
+
+    T result;
+    static if (isStaticArray!T)
+    {
+        size_t i;
+    }
+
+    const(Char)[] cont = spec.trailing;
+    for (size_t j = 0; j < spec.trailing.length; ++j)
+    {
+        if (spec.trailing[j] == '%')
+        {
+            cont = spec.trailing[0 .. j];
+            break;
+        }
+    }
+    debug (unformatRange) printf("\t");
+    debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front);
+    debug (unformatRange) printf("cont = %.*s\n", cont);
+
+    bool checkEnd()
+    {
+        return input.empty || !cont.empty && input.front == cont.front;
+    }
+
+    if (!checkEnd())
+    {
+        for (;;)
+        {
+            auto fmt = FormatSpec!Char(spec.nested);
+            fmt.readUpToNextSpec(input);
+            enforce(!input.empty, "Unexpected end of input when parsing range");
+
+            debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front);
+            static if (isStaticArray!T)
+            {
+                result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt);
+            }
+            else static if (isDynamicArray!T)
+            {
+                result ~= unformatElement!(ElementType!T)(input, fmt);
+            }
+            else static if (isAssociativeArray!T)
+            {
+                auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt);
+                fmt.readUpToNextSpec(input);        // eat key separator
+
+                result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt);
+            }
+            debug (unformatRange) {
+            if (input.empty) printf("-> front = [empty] ");
+            else             printf("-> front = %c ", input.front);
+            }
+
+            static if (isStaticArray!T)
+            {
+                debug (unformatRange) printf("i = %u < %u\n", i, T.length);
+                enforce(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length));
+            }
+
+            if (spec.sep != null)
+                fmt.readUpToNextSpec(input);
+            auto sep = spec.sep != null ? spec.sep
+                         : fmt.trailing;
+            debug (unformatRange) {
+            if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, sep);
+            else                            printf("\n");
+            }
+
+            if (checkEnd())
+                break;
+
+            if (!sep.empty && input.front == sep.front)
+            {
+                while (!sep.empty)
+                {
+                    enforce(!input.empty, "Unexpected end of input when parsing range separator");
+                    enforce(input.front == sep.front, "Unexpected character when parsing range separator");
+                    input.popFront();
+                    sep.popFront();
+                }
+                debug (unformatRange) printf("input.front = %c\n", input.front);
+            }
+        }
+    }
+    static if (isStaticArray!T)
+    {
+        enforce(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length));
+    }
+    return result;
+}
+
+// Undocumented
+T unformatElement(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
+    if (isInputRange!Range)
+{
+    import std.conv : parseElement;
+    static if (isSomeString!T)
+    {
+        if (spec.spec == 's')
+        {
+            return parseElement!T(input);
+        }
+    }
+    else static if (isSomeChar!T)
+    {
+        if (spec.spec == 's')
+        {
+            return parseElement!T(input);
+        }
+    }
+
+    return unformatValue!T(input, spec);
+}
+
+
+// Legacy implementation
+
+enum Mangle : char
+{
+    Tvoid     = 'v',
+    Tbool     = 'b',
+    Tbyte     = 'g',
+    Tubyte    = 'h',
+    Tshort    = 's',
+    Tushort   = 't',
+    Tint      = 'i',
+    Tuint     = 'k',
+    Tlong     = 'l',
+    Tulong    = 'm',
+    Tfloat    = 'f',
+    Tdouble   = 'd',
+    Treal     = 'e',
+
+    Tifloat   = 'o',
+    Tidouble  = 'p',
+    Tireal    = 'j',
+    Tcfloat   = 'q',
+    Tcdouble  = 'r',
+    Tcreal    = 'c',
+
+    Tchar     = 'a',
+    Twchar    = 'u',
+    Tdchar    = 'w',
+
+    Tarray    = 'A',
+    Tsarray   = 'G',
+    Taarray   = 'H',
+    Tpointer  = 'P',
+    Tfunction = 'F',
+    Tident    = 'I',
+    Tclass    = 'C',
+    Tstruct   = 'S',
+    Tenum     = 'E',
+    Ttypedef  = 'T',
+    Tdelegate = 'D',
+
+    Tconst    = 'x',
+    Timmutable = 'y',
+}
+
+// return the TypeInfo for a primitive type and null otherwise.  This
+// is required since for arrays of ints we only have the mangled char
+// to work from. If arrays always subclassed TypeInfo_Array this
+// routine could go away.
+private TypeInfo primitiveTypeInfo(Mangle m)
+{
+    // BUG: should fix this in static this() to avoid double checked locking bug
+    __gshared TypeInfo[Mangle] dic;
+    if (!dic.length) {
+        dic = [
+            Mangle.Tvoid : typeid(void),
+            Mangle.Tbool : typeid(bool),
+            Mangle.Tbyte : typeid(byte),
+            Mangle.Tubyte : typeid(ubyte),
+            Mangle.Tshort : typeid(short),
+            Mangle.Tushort : typeid(ushort),
+            Mangle.Tint : typeid(int),
+            Mangle.Tuint : typeid(uint),
+            Mangle.Tlong : typeid(long),
+            Mangle.Tulong : typeid(ulong),
+            Mangle.Tfloat : typeid(float),
+            Mangle.Tdouble : typeid(double),
+            Mangle.Treal : typeid(real),
+            Mangle.Tifloat : typeid(ifloat),
+            Mangle.Tidouble : typeid(idouble),
+            Mangle.Tireal : typeid(ireal),
+            Mangle.Tcfloat : typeid(cfloat),
+            Mangle.Tcdouble : typeid(cdouble),
+            Mangle.Tcreal : typeid(creal),
+            Mangle.Tchar : typeid(char),
+            Mangle.Twchar : typeid(wchar),
+            Mangle.Tdchar : typeid(dchar)
+            ];
+    }
+    auto p = m in dic;
+    return p ? *p : null;
+}
+
+// This stuff has been removed from the docs and is planned for deprecation.
+/*
+ * Interprets variadic argument list pointed to by argptr whose types
+ * are given by arguments[], formats them according to embedded format
+ * strings in the variadic argument list, and sends the resulting
+ * characters to putc.
+ *
+ * The variadic arguments are consumed in order.  Each is formatted
+ * into a sequence of chars, using the default format specification
+ * for its type, and the characters are sequentially passed to putc.
+ * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is
+ * encountered, it is interpreted as a format string. As many
+ * arguments as specified in the format string are consumed and
+ * formatted according to the format specifications in that string and
+ * passed to putc. If there are too few remaining arguments, a
+ * $(D FormatException) is thrown. If there are more remaining arguments than
+ * needed by the format specification, the default processing of
+ * arguments resumes until they are all consumed.
+ *
+ * Params:
+ *        putc =        Output is sent do this delegate, character by character.
+ *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted.
+ *        argptr = Points to variadic argument list.
+ *
+ * Throws:
+ *        Mismatched arguments and formats result in a $(D FormatException) being thrown.
+ *
+ * Format_String:
+ *        <a name="format-string">$(I Format strings)</a>
+ *        consist of characters interspersed with
+ *        $(I format specifications). Characters are simply copied
+ *        to the output (such as putc) after any necessary conversion
+ *        to the corresponding UTF-8 sequence.
+ *
+ *        A $(I format specification) starts with a '%' character,
+ *        and has the following grammar:
+
+$(CONSOLE
+$(I FormatSpecification):
+    $(B '%%')
+    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
+
+$(I Flags):
+    $(I empty)
+    $(B '-') $(I Flags)
+    $(B '+') $(I Flags)
+    $(B '#') $(I Flags)
+    $(B '0') $(I Flags)
+    $(B ' ') $(I Flags)
+
+$(I Width):
+    $(I empty)
+    $(I Integer)
+    $(B '*')
+
+$(I Precision):
+    $(I empty)
+    $(B '.')
+    $(B '.') $(I Integer)
+    $(B '.*')
+
+$(I Integer):
+    $(I Digit)
+    $(I Digit) $(I Integer)
+
+$(I Digit):
+    $(B '0')
+    $(B '1')
+    $(B '2')
+    $(B '3')
+    $(B '4')
+    $(B '5')
+    $(B '6')
+    $(B '7')
+    $(B '8')
+    $(B '9')
+
+$(I FormatChar):
+    $(B 's')
+    $(B 'b')
+    $(B 'd')
+    $(B 'o')
+    $(B 'x')
+    $(B 'X')
+    $(B 'e')
+    $(B 'E')
+    $(B 'f')
+    $(B 'F')
+    $(B 'g')
+    $(B 'G')
+    $(B 'a')
+    $(B 'A')
+)
+    $(DL
+    $(DT $(I Flags))
+    $(DL
+        $(DT $(B '-'))
+        $(DD
+        Left justify the result in the field.
+        It overrides any $(B 0) flag.)
+
+        $(DT $(B '+'))
+        $(DD Prefix positive numbers in a signed conversion with a $(B +).
+        It overrides any $(I space) flag.)
+
+        $(DT $(B '#'))
+        $(DD Use alternative formatting:
+        $(DL
+            $(DT For $(B 'o'):)
+            $(DD Add to precision as necessary so that the first digit
+            of the octal formatting is a '0', even if both the argument
+            and the $(I Precision) are zero.)
+            $(DT For $(B 'x') ($(B 'X')):)
+            $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).)
+            $(DT For floating point formatting:)
+            $(DD Always insert the decimal point.)
+            $(DT For $(B 'g') ($(B 'G')):)
+            $(DD Do not elide trailing zeros.)
+        ))
+
+        $(DT $(B '0'))
+        $(DD For integer and floating point formatting when not nan or
+        infinity, use leading zeros
+        to pad rather than spaces.
+        Ignore if there's a $(I Precision).)
+
+        $(DT $(B ' '))
+        $(DD Prefix positive numbers in a signed conversion with a space.)
+    )
+
+    $(DT $(I Width))
+    $(DD
+    Specifies the minimum field width.
+    If the width is a $(B *), the next argument, which must be
+    of type $(B int), is taken as the width.
+    If the width is negative, it is as if the $(B -) was given
+    as a $(I Flags) character.)
+
+    $(DT $(I Precision))
+    $(DD Gives the precision for numeric conversions.
+    If the precision is a $(B *), the next argument, which must be
+    of type $(B int), is taken as the precision. If it is negative,
+    it is as if there was no $(I Precision).)
+
+    $(DT $(I FormatChar))
+    $(DD
+    $(DL
+        $(DT $(B 's'))
+        $(DD The corresponding argument is formatted in a manner consistent
+        with its type:
+        $(DL
+            $(DT $(B bool))
+            $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.)
+            $(DT integral types)
+            $(DD The $(B %d) format is used.)
+            $(DT floating point types)
+            $(DD The $(B %g) format is used.)
+            $(DT string types)
+            $(DD The result is the string converted to UTF-8.)
+            A $(I Precision) specifies the maximum number of characters
+            to use in the result.
+            $(DT classes derived from $(B Object))
+            $(DD The result is the string returned from the class instance's
+            $(B .toString()) method.
+            A $(I Precision) specifies the maximum number of characters
+            to use in the result.)
+            $(DT non-string static and dynamic arrays)
+            $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
+            where s<sub>k</sub> is the kth element
+            formatted with the default format.)
+        ))
+
+        $(DT $(B 'b','d','o','x','X'))
+        $(DD The corresponding argument must be an integral type
+        and is formatted as an integer. If the argument is a signed type
+        and the $(I FormatChar) is $(B d) it is converted to
+        a signed string of characters, otherwise it is treated as
+        unsigned. An argument of type $(B bool) is formatted as '1'
+        or '0'. The base used is binary for $(B b), octal for $(B o),
+        decimal
+        for $(B d), and hexadecimal for $(B x) or $(B X).
+        $(B x) formats using lower case letters, $(B X) uppercase.
+        If there are fewer resulting digits than the $(I Precision),
+        leading zeros are used as necessary.
+        If the $(I Precision) is 0 and the number is 0, no digits
+        result.)
+
+        $(DT $(B 'e','E'))
+        $(DD A floating point number is formatted as one digit before
+        the decimal point, $(I Precision) digits after, the $(I FormatChar),
+        &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
+        If there is no $(I Precision), six
+        digits are generated after the decimal point.
+        If the $(I Precision) is 0, no decimal point is generated.)
+
+        $(DT $(B 'f','F'))
+        $(DD A floating point number is formatted in decimal notation.
+        The $(I Precision) specifies the number of digits generated
+        after the decimal point. It defaults to six. At least one digit
+        is generated before the decimal point. If the $(I Precision)
+        is zero, no decimal point is generated.)
+
+        $(DT $(B 'g','G'))
+        $(DD A floating point number is formatted in either $(B e) or
+        $(B f) format for $(B g); $(B E) or $(B F) format for
+        $(B G).
+        The $(B f) format is used if the exponent for an $(B e) format
+        is greater than -5 and less than the $(I Precision).
+        The $(I Precision) specifies the number of significant
+        digits, and defaults to six.
+        Trailing zeros are elided after the decimal point, if the fractional
+        part is zero then no decimal point is generated.)
+
+        $(DT $(B 'a','A'))
+        $(DD A floating point number is formatted in hexadecimal
+        exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
+        There is one hexadecimal digit before the decimal point, and as
+        many after as specified by the $(I Precision).
+        If the $(I Precision) is zero, no decimal point is generated.
+        If there is no $(I Precision), as many hexadecimal digits as
+        necessary to exactly represent the mantissa are generated.
+        The exponent is written in as few digits as possible,
+        but at least one, is in decimal, and represents a power of 2 as in
+        $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
+        The exponent for zero is zero.
+        The hexadecimal digits, x and p are in upper case if the
+        $(I FormatChar) is upper case.)
+    )
+
+    Floating point NaN's are formatted as $(B nan) if the
+    $(I FormatChar) is lower case, or $(B NAN) if upper.
+    Floating point infinities are formatted as $(B inf) or
+    $(B infinity) if the
+    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
+    ))
+
+Example:
+
+-------------------------
+import core.stdc.stdio;
+import std.format;
+
+void myPrint(...)
+{
+    void putc(dchar c)
+    {
+        fputc(c, stdout);
+    }
+
+    std.format.doFormat(&putc, _arguments, _argptr);
+}
+
+void main()
+{
+    int x = 27;
+
+    // prints 'The answer is 27:6'
+    myPrint("The answer is %s:", x, 6);
+}
+------------------------
+ */
+void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap)
+{
+    import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8;
+    import core.stdc.string : strlen;
+    import core.stdc.stdlib : alloca, malloc, realloc, free;
+    import core.stdc.stdio : snprintf;
+
+    size_t bufLength = 1024;
+    void* argBuffer = malloc(bufLength);
+    scope(exit) free(argBuffer);
+
+    size_t bufUsed = 0;
+    foreach(ti; arguments)
+    {
+        // Ensure the required alignment
+        bufUsed += ti.talign - 1;
+        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1);
+        auto pos = bufUsed;
+        // Align to next word boundary
+        bufUsed += ti.tsize + size_t.sizeof - 1;
+        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1);
+        // Resize buffer if necessary
+        while (bufUsed > bufLength)
+        {
+            bufLength *= 2;
+            argBuffer = realloc(argBuffer, bufLength);
+        }
+        // Copy argument into buffer
+        va_arg(ap, ti, argBuffer + pos);
+    }
+
+    auto argptr = argBuffer;
+    void* skipArg(TypeInfo ti)
+    {
+        // Ensure the required alignment
+        argptr += ti.talign - 1;
+        argptr -= cast(size_t)argptr & (ti.talign - 1);
+        auto p = argptr;
+        // Align to next word boundary
+        argptr += ti.tsize + size_t.sizeof - 1;
+        argptr -= cast(size_t)argptr & (size_t.sizeof - 1);
+        return p;
+    }
+    auto getArg(T)()
+    {
+        return *cast(T*)skipArg(typeid(T));
+    }
+
+    TypeInfo ti;
+    Mangle m;
+    uint flags;
+    int field_width;
+    int precision;
+
+    enum : uint
+    {
+        FLdash = 1,
+        FLplus = 2,
+        FLspace = 4,
+        FLhash = 8,
+        FLlngdbl = 0x20,
+        FL0pad = 0x40,
+        FLprecision = 0x80,
+    }
+
+    static TypeInfo skipCI(TypeInfo valti)
+    {
+        for (;;)
+        {
+            if (typeid(valti).name.length == 18 &&
+                    typeid(valti).name[9..18] == "Invariant")
+                valti = (cast(TypeInfo_Invariant)valti).base;
+            else if (typeid(valti).name.length == 14 &&
+                    typeid(valti).name[9..14] == "Const")
+                valti = (cast(TypeInfo_Const)valti).base;
+            else
+                break;
+        }
+
+        return valti;
+    }
+
+    void formatArg(char fc)
+    {
+        bool vbit;
+        ulong vnumber;
+        char vchar;
+        dchar vdchar;
+        Object vobject;
+        real vreal;
+        creal vcreal;
+        Mangle m2;
+        int signed = 0;
+        uint base = 10;
+        int uc;
+        char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
+        const(char)* prefix = "";
+        string s;
+
+        void putstr(const char[] s)
+        {
+            //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
+            ptrdiff_t padding = field_width -
+                (strlen(prefix) + toUCSindex(s, s.length));
+            ptrdiff_t prepad = 0;
+            ptrdiff_t postpad = 0;
+            if (padding > 0)
+            {
+                if (flags & FLdash)
+                    postpad = padding;
+                else
+                    prepad = padding;
+            }
+
+            if (flags & FL0pad)
+            {
+                while (*prefix)
+                    putc(*prefix++);
+                while (prepad--)
+                    putc('0');
+            }
+            else
+            {
+                while (prepad--)
+                    putc(' ');
+                while (*prefix)
+                    putc(*prefix++);
+            }
+
+            foreach (dchar c; s)
+                putc(c);
+
+            while (postpad--)
+                putc(' ');
+        }
+
+        void putreal(real v)
+        {
+            //printf("putreal %Lg\n", vreal);
+
+            switch (fc)
+            {
+                case 's':
+                    fc = 'g';
+                    break;
+
+                case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
+                    break;
+
+                default:
+                    //printf("fc = '%c'\n", fc);
+                Lerror:
+                    throw new FormatException("incompatible format character for floating point type");
+            }
+            version (DigitalMarsC)
+            {
+                uint sl;
+                char[] fbuf = tmpbuf;
+                if (!(flags & FLprecision))
+                    precision = 6;
+                while (1)
+                {
+                    sl = fbuf.length;
+                    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
+                            precision, &v, cast(char*)fbuf, &sl, field_width);
+                    if (sl != -1)
+                        break;
+                    sl = fbuf.length * 2;
+                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+                }
+                putstr(fbuf[0 .. sl]);
+            }
+            else
+            {
+                ptrdiff_t sl;
+                char[] fbuf = tmpbuf;
+                char[12] format;
+                format[0] = '%';
+                int i = 1;
+                if (flags & FLdash)
+                    format[i++] = '-';
+                if (flags & FLplus)
+                    format[i++] = '+';
+                if (flags & FLspace)
+                    format[i++] = ' ';
+                if (flags & FLhash)
+                    format[i++] = '#';
+                if (flags & FL0pad)
+                    format[i++] = '0';
+                format[i + 0] = '*';
+                format[i + 1] = '.';
+                format[i + 2] = '*';
+                format[i + 3] = 'L';
+                format[i + 4] = fc;
+                format[i + 5] = 0;
+                if (!(flags & FLprecision))
+                    precision = -1;
+                while (1)
+                {
+                    sl = fbuf.length;
+                    int n;
+                    version (CRuntime_Microsoft)
+                    {
+                        import std.math : isNaN, isInfinity;
+                        if (isNaN(v)) // snprintf writes 1.#QNAN
+                            n = snprintf(fbuf.ptr, sl, "nan");
+                        else if(isInfinity(v)) // snprintf writes 1.#INF
+                            n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf");
+                        else
+                            n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
+                                         precision, cast(double)v);
+                    }
+                    else
+                        n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
+                                precision, v);
+                    //printf("format = '%s', n = %d\n", cast(char*)format, n);
+                    if (n >= 0 && n < sl)
+                    {        sl = n;
+                        break;
+                    }
+                    if (n < 0)
+                        sl = sl * 2;
+                    else
+                        sl = n + 1;
+                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+                }
+                putstr(fbuf[0 .. sl]);
+            }
+            return;
+        }
+
+        static Mangle getMan(TypeInfo ti)
+        {
+          auto m = cast(Mangle)typeid(ti).name[9];
+          if (typeid(ti).name.length == 20 &&
+              typeid(ti).name[9..20] == "StaticArray")
+                m = cast(Mangle)'G';
+          return m;
+        }
+
+        /* p = pointer to the first element in the array
+         * len = number of elements in the array
+         * valti = type of the elements
+         */
+        void putArray(void* p, size_t len, TypeInfo valti)
+        {
+          //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize);
+          putc('[');
+          valti = skipCI(valti);
+          size_t tsize = valti.tsize;
+          auto argptrSave = argptr;
+          auto tiSave = ti;
+          auto mSave = m;
+          ti = valti;
+          //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr);
+          m = getMan(valti);
+          while (len--)
+          {
+            //doFormat(putc, (&valti)[0 .. 1], p);
+            argptr = p;
+            formatArg('s');
+            p += tsize;
+            if (len > 0) putc(',');
+          }
+          m = mSave;
+          ti = tiSave;
+          argptr = argptrSave;
+          putc(']');
+        }
+
+        void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
+        {
+            putc('[');
+            bool comma=false;
+            auto argptrSave = argptr;
+            auto tiSave = ti;
+            auto mSave = m;
+            valti = skipCI(valti);
+            keyti = skipCI(keyti);
+            foreach(ref fakevalue; vaa)
+            {
+                if (comma) putc(',');
+                comma = true;
+                void *pkey = &fakevalue;
+                version (D_LP64)
+                    pkey -= (long.sizeof + 15) & ~(15);
+                else
+                    pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
+
+                // the key comes before the value
+                auto keysize = keyti.tsize;
+                version (D_LP64)
+                    auto keysizet = (keysize + 15) & ~(15);
+                else
+                    auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
+
+                void* pvalue = pkey + keysizet;
+
+                //doFormat(putc, (&keyti)[0..1], pkey);
+                m = getMan(keyti);
+                argptr = pkey;
+
+                ti = keyti;
+                formatArg('s');
+
+                putc(':');
+                //doFormat(putc, (&valti)[0..1], pvalue);
+                m = getMan(valti);
+                argptr = pvalue;
+
+                ti = valti;
+                formatArg('s');
+            }
+            m = mSave;
+            ti = tiSave;
+            argptr = argptrSave;
+            putc(']');
+        }
+
+        //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
+        int mi;
+        switch (m)
+        {
+            case Mangle.Tbool:
+                vbit = getArg!(bool)();
+                if (fc != 's')
+                {   vnumber = vbit;
+                    goto Lnumber;
+                }
+                putstr(vbit ? "true" : "false");
+                return;
+
+            case Mangle.Tchar:
+                vchar = getArg!(char)();
+                if (fc != 's')
+                {   vnumber = vchar;
+                    goto Lnumber;
+                }
+            L2:
+                putstr((&vchar)[0 .. 1]);
+                return;
+
+            case Mangle.Twchar:
+                vdchar = getArg!(wchar)();
+                goto L1;
+
+            case Mangle.Tdchar:
+                vdchar = getArg!(dchar)();
+            L1:
+                if (fc != 's')
+                {   vnumber = vdchar;
+                    goto Lnumber;
+                }
+                if (vdchar <= 0x7F)
+                {   vchar = cast(char)vdchar;
+                    goto L2;
+                }
+                else
+                {   if (!isValidDchar(vdchar))
+                        throw new UTFException("invalid dchar in format");
+                    char[4] vbuf;
+                    putstr(toUTF8(vbuf, vdchar));
+                }
+                return;
+
+            case Mangle.Tbyte:
+                signed = 1;
+                vnumber = getArg!(byte)();
+                goto Lnumber;
+
+            case Mangle.Tubyte:
+                vnumber = getArg!(ubyte)();
+                goto Lnumber;
+
+            case Mangle.Tshort:
+                signed = 1;
+                vnumber = getArg!(short)();
+                goto Lnumber;
+
+            case Mangle.Tushort:
+                vnumber = getArg!(ushort)();
+                goto Lnumber;
+
+            case Mangle.Tint:
+                signed = 1;
+                vnumber = getArg!(int)();
+                goto Lnumber;
+
+            case Mangle.Tuint:
+            Luint:
+                vnumber = getArg!(uint)();
+                goto Lnumber;
+
+            case Mangle.Tlong:
+                signed = 1;
+                vnumber = cast(ulong)getArg!(long)();
+                goto Lnumber;
+
+            case Mangle.Tulong:
+            Lulong:
+                vnumber = getArg!(ulong)();
+                goto Lnumber;
+
+            case Mangle.Tclass:
+                vobject = getArg!(Object)();
+                if (vobject is null)
+                    s = "null";
+                else
+                    s = vobject.toString();
+                goto Lputstr;
+
+            case Mangle.Tpointer:
+                vnumber = cast(ulong)getArg!(void*)();
+                if (fc != 'x')  uc = 1;
+                flags |= FL0pad;
+                if (!(flags & FLprecision))
+                {   flags |= FLprecision;
+                    precision = (void*).sizeof;
+                }
+                base = 16;
+                goto Lnumber;
+
+            case Mangle.Tfloat:
+            case Mangle.Tifloat:
+                if (fc == 'x' || fc == 'X')
+                    goto Luint;
+                vreal = getArg!(float)();
+                goto Lreal;
+
+            case Mangle.Tdouble:
+            case Mangle.Tidouble:
+                if (fc == 'x' || fc == 'X')
+                    goto Lulong;
+                vreal = getArg!(double)();
+                goto Lreal;
+
+            case Mangle.Treal:
+            case Mangle.Tireal:
+                vreal = getArg!(real)();
+                goto Lreal;
+
+            case Mangle.Tcfloat:
+                vcreal = getArg!(cfloat)();
+                goto Lcomplex;
+
+            case Mangle.Tcdouble:
+                vcreal = getArg!(cdouble)();
+                goto Lcomplex;
+
+            case Mangle.Tcreal:
+                vcreal = getArg!(creal)();
+                goto Lcomplex;
+
+            case Mangle.Tsarray:
+                putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
+                return;
+
+            case Mangle.Tarray:
+                mi = 10;
+                if (typeid(ti).name.length == 14 &&
+                    typeid(ti).name[9..14] == "Array")
+                { // array of non-primitive types
+                  TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+                  tn = skipCI(tn);
+                  switch (cast(Mangle)typeid(tn).name[9])
+                  {
+                    case Mangle.Tchar:  goto LarrayChar;
+                    case Mangle.Twchar: goto LarrayWchar;
+                    case Mangle.Tdchar: goto LarrayDchar;
+                    default:
+                        break;
+                  }
+                  void[] va = getArg!(void[])();
+                  putArray(va.ptr, va.length, tn);
+                  return;
+                }
+                if (typeid(ti).name.length == 25 &&
+                    typeid(ti).name[9..25] == "AssociativeArray")
+                { // associative array
+                  ubyte[long] vaa = getArg!(ubyte[long])();
+                  putAArray(vaa,
+                        (cast(TypeInfo_AssociativeArray)ti).next,
+                        (cast(TypeInfo_AssociativeArray)ti).key);
+                  return;
+                }
+
+                while (1)
+                {
+                    m2 = cast(Mangle)typeid(ti).name[mi];
+                    switch (m2)
+                    {
+                        case Mangle.Tchar:
+                        LarrayChar:
+                            s = getArg!(string)();
+                            goto Lputstr;
+
+                        case Mangle.Twchar:
+                        LarrayWchar:
+                            wchar[] sw = getArg!(wchar[])();
+                            s = toUTF8(sw);
+                            goto Lputstr;
+
+                        case Mangle.Tdchar:
+                        LarrayDchar:
+                            s = toUTF8(getArg!(dstring)());
+                        Lputstr:
+                            if (fc != 's')
+                                throw new FormatException("string");
+                            if (flags & FLprecision && precision < s.length)
+                                s = s[0 .. precision];
+                            putstr(s);
+                            break;
+
+                        case Mangle.Tconst:
+                        case Mangle.Timmutable:
+                            mi++;
+                            continue;
+
+                        default:
+                            TypeInfo ti2 = primitiveTypeInfo(m2);
+                            if (!ti2)
+                              goto Lerror;
+                            void[] va = getArg!(void[])();
+                            putArray(va.ptr, va.length, ti2);
+                    }
+                    return;
+                }
+                assert(0);
+
+            case Mangle.Ttypedef:
+                ti = (cast(TypeInfo_Typedef)ti).base;
+                m = cast(Mangle)typeid(ti).name[9];
+                formatArg(fc);
+                return;
+
+            case Mangle.Tenum:
+                ti = (cast(TypeInfo_Enum)ti).base;
+                m = cast(Mangle)typeid(ti).name[9];
+                formatArg(fc);
+                return;
+
+            case Mangle.Tstruct:
+            {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
+                if (tis.xtoString is null)
+                    throw new FormatException("Can't convert " ~ tis.toString()
+                            ~ " to string: \"string toString()\" not defined");
+                s = tis.xtoString(skipArg(tis));
+                goto Lputstr;
+            }
+
+            default:
+                goto Lerror;
+        }
+
+    Lnumber:
+        switch (fc)
+        {
+            case 's':
+            case 'd':
+                if (signed)
+                {   if (cast(long)vnumber < 0)
+                    {        prefix = "-";
+                        vnumber = -vnumber;
+                    }
+                    else if (flags & FLplus)
+                        prefix = "+";
+                    else if (flags & FLspace)
+                        prefix = " ";
+                }
+                break;
+
+            case 'b':
+                signed = 0;
+                base = 2;
+                break;
+
+            case 'o':
+                signed = 0;
+                base = 8;
+                break;
+
+            case 'X':
+                uc = 1;
+                if (flags & FLhash && vnumber)
+                    prefix = "0X";
+                signed = 0;
+                base = 16;
+                break;
+
+            case 'x':
+                if (flags & FLhash && vnumber)
+                    prefix = "0x";
+                signed = 0;
+                base = 16;
+                break;
+
+            default:
+                goto Lerror;
+        }
+
+        if (!signed)
+        {
+            switch (m)
+            {
+                case Mangle.Tbyte:
+                    vnumber &= 0xFF;
+                    break;
+
+                case Mangle.Tshort:
+                    vnumber &= 0xFFFF;
+                    break;
+
+                case Mangle.Tint:
+                    vnumber &= 0xFFFFFFFF;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        if (flags & FLprecision && fc != 'p')
+            flags &= ~FL0pad;
+
+        if (vnumber < base)
+        {
+            if (vnumber == 0 && precision == 0 && flags & FLprecision &&
+                !(fc == 'o' && flags & FLhash))
+            {
+                putstr(null);
+                return;
+            }
+            if (precision == 0 || !(flags & FLprecision))
+            {        vchar = cast(char)('0' + vnumber);
+                if (vnumber < 10)
+                    vchar = cast(char)('0' + vnumber);
+                else
+                    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
+                goto L2;
+            }
+        }
+
+        {
+            ptrdiff_t n = tmpbuf.length;
+            char c;
+            int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
+
+            while (vnumber)
+            {
+                c = cast(char)((vnumber % base) + '0');
+                if (c > '9')
+                    c += hexoffset;
+                vnumber /= base;
+                tmpbuf[--n] = c;
+            }
+            if (tmpbuf.length - n < precision && precision < tmpbuf.length)
+            {
+                ptrdiff_t m = tmpbuf.length - precision;
+                tmpbuf[m .. n] = '0';
+                n = m;
+            }
+            else if (flags & FLhash && fc == 'o')
+                prefix = "0";
+            putstr(tmpbuf[n .. tmpbuf.length]);
+            return;
+        }
+
+    Lreal:
+        putreal(vreal);
+        return;
+
+    Lcomplex:
+        putreal(vcreal.re);
+        if (vcreal.im >= 0)
+        {
+            putc('+');
+        }
+        putreal(vcreal.im);
+        putc('i');
+        return;
+
+    Lerror:
+        throw new FormatException("formatArg");
+    }
+
+    for (int j = 0; j < arguments.length; )
+    {
+        ti = arguments[j++];
+        //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length);
+        //ti.print();
+
+        flags = 0;
+        precision = 0;
+        field_width = 0;
+
+        ti = skipCI(ti);
+        int mi = 9;
+        do
+        {
+            if (typeid(ti).name.length <= mi)
+                goto Lerror;
+            m = cast(Mangle)typeid(ti).name[mi++];
+        } while (m == Mangle.Tconst || m == Mangle.Timmutable);
+
+        if (m == Mangle.Tarray)
+        {
+            if (typeid(ti).name.length == 14 &&
+                    typeid(ti).name[9..14] == "Array")
+            {
+                TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+                tn = skipCI(tn);
+                switch (cast(Mangle)typeid(tn).name[9])
+                {
+                case Mangle.Tchar:
+                case Mangle.Twchar:
+                case Mangle.Tdchar:
+                    ti = tn;
+                    mi = 9;
+                    break;
+                default:
+                    break;
+                }
+            }
+          L1:
+            Mangle m2 = cast(Mangle)typeid(ti).name[mi];
+            string  fmt;                        // format string
+            wstring wfmt;
+            dstring dfmt;
+
+            /* For performance reasons, this code takes advantage of the
+             * fact that most format strings will be ASCII, and that the
+             * format specifiers are always ASCII. This means we only need
+             * to deal with UTF in a couple of isolated spots.
+             */
+
+            switch (m2)
+            {
+            case Mangle.Tchar:
+                fmt = getArg!(string)();
+                break;
+
+            case Mangle.Twchar:
+                wfmt = getArg!(wstring)();
+                fmt = toUTF8(wfmt);
+                break;
+
+            case Mangle.Tdchar:
+                dfmt = getArg!(dstring)();
+                fmt = toUTF8(dfmt);
+                break;
+
+            case Mangle.Tconst:
+            case Mangle.Timmutable:
+                mi++;
+                goto L1;
+
+            default:
+                formatArg('s');
+                continue;
+            }
+
+            for (size_t i = 0; i < fmt.length; )
+            {        dchar c = fmt[i++];
+
+                dchar getFmtChar()
+                {   // Valid format specifier characters will never be UTF
+                    if (i == fmt.length)
+                        throw new FormatException("invalid specifier");
+                    return fmt[i++];
+                }
+
+                int getFmtInt()
+                {   int n;
+
+                    while (1)
+                    {
+                        n = n * 10 + (c - '0');
+                        if (n < 0)        // overflow
+                            throw new FormatException("int overflow");
+                        c = getFmtChar();
+                        if (c < '0' || c > '9')
+                            break;
+                    }
+                    return n;
+                }
+
+                int getFmtStar()
+                {   Mangle m;
+                    TypeInfo ti;
+
+                    if (j == arguments.length)
+                        throw new FormatException("too few arguments");
+                    ti = arguments[j++];
+                    m = cast(Mangle)typeid(ti).name[9];
+                    if (m != Mangle.Tint)
+                        throw new FormatException("int argument expected");
+                    return getArg!(int)();
+                }
+
+                if (c != '%')
+                {
+                    if (c > 0x7F)        // if UTF sequence
+                    {
+                        i--;                // back up and decode UTF sequence
+                        import std.utf : decode;
+                        c = decode(fmt, i);
+                    }
+                  Lputc:
+                    putc(c);
+                    continue;
+                }
+
+                // Get flags {-+ #}
+                flags = 0;
+                while (1)
+                {
+                    c = getFmtChar();
+                    switch (c)
+                    {
+                    case '-':        flags |= FLdash;        continue;
+                    case '+':        flags |= FLplus;        continue;
+                    case ' ':        flags |= FLspace;        continue;
+                    case '#':        flags |= FLhash;        continue;
+                    case '0':        flags |= FL0pad;        continue;
+
+                    case '%':        if (flags == 0)
+                                          goto Lputc;
+                                     break;
+
+                    default:         break;
+                    }
+                    break;
+                }
+
+                // Get field width
+                field_width = 0;
+                if (c == '*')
+                {
+                    field_width = getFmtStar();
+                    if (field_width < 0)
+                    {   flags |= FLdash;
+                        field_width = -field_width;
+                    }
+
+                    c = getFmtChar();
+                }
+                else if (c >= '0' && c <= '9')
+                    field_width = getFmtInt();
+
+                if (flags & FLplus)
+                    flags &= ~FLspace;
+                if (flags & FLdash)
+                    flags &= ~FL0pad;
+
+                // Get precision
+                precision = 0;
+                if (c == '.')
+                {   flags |= FLprecision;
+                    //flags &= ~FL0pad;
+
+                    c = getFmtChar();
+                    if (c == '*')
+                    {
+                        precision = getFmtStar();
+                        if (precision < 0)
+                        {   precision = 0;
+                            flags &= ~FLprecision;
+                        }
+
+                        c = getFmtChar();
+                    }
+                    else if (c >= '0' && c <= '9')
+                        precision = getFmtInt();
+                }
+
+                if (j == arguments.length)
+                    goto Lerror;
+                ti = arguments[j++];
+                ti = skipCI(ti);
+                mi = 9;
+                do
+                {
+                    m = cast(Mangle)typeid(ti).name[mi++];
+                } while (m == Mangle.Tconst || m == Mangle.Timmutable);
+
+                if (c > 0x7F)                // if UTF sequence
+                    goto Lerror;        // format specifiers can't be UTF
+                formatArg(cast(char)c);
+            }
+        }
+        else
+        {
+            formatArg('s');
+        }
+    }
+    return;
+
+  Lerror:
+    throw new FormatException();
+}
+
+
+private bool needToSwapEndianess(Char)(ref FormatSpec!Char f)
+{
+    import std.system : endian, Endian;
+
+    return endian == Endian.littleEndian && f.flPlus
+        || endian == Endian.bigEndian && f.flDash;
+}
+
+/* ======================== Unit Tests ====================================== */
+
+unittest
+{
+    import std.conv : octal;
+
+    int i;
+    string s;
+
+    debug(format) printf("std.format.format.unittest\n");
+
+    s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo");
+    assert(s == "hello world! true 57 1000000000x foo");
+
+    s = format("%s %A %s", 1.67, -1.28, float.nan);
+    /* The host C library is used to format floats.
+     * C99 doesn't specify what the hex digit before the decimal point
+     * is for %A.
+     */
+    //version (linux)
+    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
+    //else version (OSX)
+    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
+    //else
+    version (MinGW)
+        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
+    else version (CRuntime_Microsoft)
+        assert(s == "1.67 -0X1.47AE14P+0 nan"
+            || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015)
+    else
+        assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s);
+
+    s = format("%x %X", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "1234af AFAFAFAF");
+
+    s = format("%b %o", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "100100011010010101111 25753727657");
+
+    s = format("%d %s", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "1193135 2947526575");
+
+    //version(X86_64)
+    //{
+    //    pragma(msg, "several format tests disabled on x86_64 due to bug 5625");
+    //}
+    //else
+    //{
+        s = format("%s", 1.2 + 3.4i);
+        assert(s == "1.2+3.4i", s);
+
+        //s = format("%x %X", 1.32, 6.78f);
+        //assert(s == "3ff51eb851eb851f 40D8F5C3");
+
+    //}
+
+    s = format("%#06.*f",2,12.345);
+    assert(s == "012.35");
+
+    s = format("%#0*.*f",6,2,12.345);
+    assert(s == "012.35");
+
+    s = format("%7.4g:", 12.678);
+    assert(s == "  12.68:");
+
+    s = format("%7.4g:", 12.678L);
+    assert(s == "  12.68:");
+
+    s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
+    assert(s == "-4.000000|-0010|0x001|  0x1");
+
+    i = -10;
+    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "-10|-10|-10|-10|-10.0000");
+
+    i = -5;
+    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "-5| -5|-05|-5|-5.0000");
+
+    i = 0;
+    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "0|  0|000|0|0.0000");
+
+    i = 5;
+    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "5|  5|005|5|5.0000");
+
+    i = 10;
+    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "10| 10|010|10|10.0000");
+
+    s = format("%.0d", 0);
+    assert(s == "");
+
+    s = format("%.g", .34);
+    assert(s == "0.3");
+
+    s = format("%.0g", .34);
+    assert(s == "0.3");
+
+    s = format("%.2g", .34);
+    assert(s == "0.34");
+
+    s = format("%0.0008f", 1e-08);
+    assert(s == "0.00000001");
+
+    s = format("%0.0008f", 1e-05);
+    assert(s == "0.00001000");
+
+    s = "helloworld";
+    string r;
+    r = format("%.2s", s[0..5]);
+    assert(r == "he");
+    r = format("%.20s", s[0..5]);
+    assert(r == "hello");
+    r = format("%8s", s[0..5]);
+    assert(r == "   hello");
+
+    byte[] arrbyte = new byte[4];
+    arrbyte[0] = 100;
+    arrbyte[1] = -99;
+    arrbyte[3] = 0;
+    r = format("%s", arrbyte);
+    assert(r == "[100, -99, 0, 0]");
+
+    ubyte[] arrubyte = new ubyte[4];
+    arrubyte[0] = 100;
+    arrubyte[1] = 200;
+    arrubyte[3] = 0;
+    r = format("%s", arrubyte);
+    assert(r == "[100, 200, 0, 0]");
+
+    short[] arrshort = new short[4];
+    arrshort[0] = 100;
+    arrshort[1] = -999;
+    arrshort[3] = 0;
+    r = format("%s", arrshort);
+    assert(r == "[100, -999, 0, 0]");
+
+    ushort[] arrushort = new ushort[4];
+    arrushort[0] = 100;
+    arrushort[1] = 20_000;
+    arrushort[3] = 0;
+    r = format("%s", arrushort);
+    assert(r == "[100, 20000, 0, 0]");
+
+    int[] arrint = new int[4];
+    arrint[0] = 100;
+    arrint[1] = -999;
+    arrint[3] = 0;
+    r = format("%s", arrint);
+    assert(r == "[100, -999, 0, 0]");
+
+    long[] arrlong = new long[4];
+    arrlong[0] = 100;
+    arrlong[1] = -999;
+    arrlong[3] = 0;
+    r = format("%s", arrlong);
+    assert(r == "[100, -999, 0, 0]");
+
+    ulong[] arrulong = new ulong[4];
+    arrulong[0] = 100;
+    arrulong[1] = 999;
+    arrulong[3] = 0;
+    r = format("%s", arrulong);
+    assert(r == "[100, 999, 0, 0]");
+
+    string[] arr2 = new string[4];
+    arr2[0] = "hello";
+    arr2[1] = "world";
+    arr2[3] = "foo";
+    r = format("%s", arr2);
+    assert(r == `["hello", "world", "", "foo"]`);
+
+    r = format("%.8d", 7);
+    assert(r == "00000007");
+    r = format("%.8x", 10);
+    assert(r == "0000000a");
+
+    r = format("%-3d", 7);
+    assert(r == "7  ");
+
+    r = format("%*d", -3, 7);
+    assert(r == "7  ");
+
+    r = format("%.*d", -3, 7);
+    assert(r == "7");
+
+    r = format("abc"c);
+    assert(r == "abc");
+
+    //format() returns the same type as inputted.
+    wstring wr;
+    wr = format("def"w);
+    assert(wr == "def"w);
+
+    dstring dr;
+    dr = format("ghi"d);
+    assert(dr == "ghi"d);
+
+    void* p = cast(void*)0xDEADBEEF;
+    r = format("%s", p);
+    assert(r == "DEADBEEF");
+
+    r = format("%#x", 0xabcd);
+    assert(r == "0xabcd");
+    r = format("%#X", 0xABCD);
+    assert(r == "0XABCD");
+
+    r = format("%#o", octal!12345);
+    assert(r == "012345");
+    r = format("%o", 9);
+    assert(r == "11");
+    r = format("%#o", 0);   // issue 15663
+    assert(r == "0");
+
+    r = format("%+d", 123);
+    assert(r == "+123");
+    r = format("%+d", -123);
+    assert(r == "-123");
+    r = format("% d", 123);
+    assert(r == " 123");
+    r = format("% d", -123);
+    assert(r == "-123");
+
+    r = format("%%");
+    assert(r == "%");
+
+    r = format("%d", true);
+    assert(r == "1");
+    r = format("%d", false);
+    assert(r == "0");
+
+    r = format("%d", 'a');
+    assert(r == "97");
+    wchar wc = 'a';
+    r = format("%d", wc);
+    assert(r == "97");
+    dchar dc = 'a';
+    r = format("%d", dc);
+    assert(r == "97");
+
+    byte b = byte.max;
+    r = format("%x", b);
+    assert(r == "7f");
+    r = format("%x", ++b);
+    assert(r == "80");
+    r = format("%x", ++b);
+    assert(r == "81");
+
+    short sh = short.max;
+    r = format("%x", sh);
+    assert(r == "7fff");
+    r = format("%x", ++sh);
+    assert(r == "8000");
+    r = format("%x", ++sh);
+    assert(r == "8001");
+
+    i = int.max;
+    r = format("%x", i);
+    assert(r == "7fffffff");
+    r = format("%x", ++i);
+    assert(r == "80000000");
+    r = format("%x", ++i);
+    assert(r == "80000001");
+
+    r = format("%x", 10);
+    assert(r == "a");
+    r = format("%X", 10);
+    assert(r == "A");
+    r = format("%x", 15);
+    assert(r == "f");
+    r = format("%X", 15);
+    assert(r == "F");
+
+    Object c = null;
+    r = format("%s", c);
+    assert(r == "null");
+
+    enum TestEnum
+    {
+        Value1, Value2
+    }
+    r = format("%s", TestEnum.Value2);
+    assert(r == "Value2");
+
+    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
+    r = format("%s", aa.values);
+    assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`);
+    r = format("%s", aa);
+    assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`);
+
+    static const dchar[] ds = ['a','b'];
+    for (int j = 0; j < ds.length; ++j)
+    {
+        r = format(" %d", ds[j]);
+        if (j == 0)
+            assert(r == " 97");
+        else
+            assert(r == " 98");
+    }
+
+    r = format(">%14d<, %s", 15, [1,2,3]);
+    assert(r == ">            15<, [1, 2, 3]");
+
+    assert(format("%8s", "bar") == "     bar");
+    assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4");
+}
+
+unittest
+{
+    // bugzilla 3479
+    import std.array;
+    auto stream = appender!(char[])();
+    formattedWrite(stream, "%2$.*1$d", 12, 10);
+    assert(stream.data == "000000000010", stream.data);
+}
+
+unittest
+{
+    // bug 6893
+    import std.array;
+    enum E : ulong { A, B, C }
+    auto stream = appender!(char[])();
+    formattedWrite(stream, "%s", E.C);
+    assert(stream.data == "C");
+}
+
+/*****************************************************
+ * Format arguments into a string.
+ *
+ * Params: fmt  = Format string. For detailed specification, see $(XREF _format,formattedWrite).
+ *         args = Variadic list of arguments to format into returned string.
+ */
+immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) if (isSomeChar!Char)
+{
+    import std.format : formattedWrite, FormatException;
+    import std.array : appender;
+    auto w = appender!(immutable(Char)[]);
+    auto n = formattedWrite(w, fmt, args);
+    version (all)
+    {
+        // In the future, this check will be removed to increase consistency
+        // with formattedWrite
+        import std.conv : text;
+        import std.exception : enforce;
+        enforce(n == args.length, new FormatException(
+            text("Orphan format arguments: args[", n, "..", args.length, "]")));
+    }
+    return w.data;
+}
+
+unittest
+{
+    import std.format;
+    import core.exception;
+    import std.exception;
+    assertCTFEable!(
+    {
+//  assert(format(null) == "");
+    assert(format("foo") == "foo");
+    assert(format("foo%%") == "foo%");
+    assert(format("foo%s", 'C') == "fooC");
+    assert(format("%s foo", "bar") == "bar foo");
+    assert(format("%s foo %s", "bar", "abc") == "bar foo abc");
+    assert(format("foo %d", -123) == "foo -123");
+    assert(format("foo %d", 123) == "foo 123");
+
+    assertThrown!FormatException(format("foo %s"));
+    assertThrown!FormatException(format("foo %s", 123, 456));
+
+    assert(format("hel%slo%s%s%s", "world", -138, 'c', true) ==
+                  "helworldlo-138ctrue");
+    });
+
+    assert(is(typeof(format("happy")) == string));
+    assert(is(typeof(format("happy"w)) == wstring));
+    assert(is(typeof(format("happy"d)) == dstring));
+}
+
+/*****************************************************
+ * Format arguments into buffer $(I buf) which must be large
+ * enough to hold the result. Throws RangeError if it is not.
+ * Returns: The slice of $(D buf) containing the formatted string.
+ */
+char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args)
+{
+    import core.exception : onRangeError;
+    import std.utf : encode;
+    import std.format : formattedWrite, FormatException;
+
+    size_t i;
+
+    struct Sink
+    {
+        void put(dchar c)
+        {
+            char[4] enc;
+            auto n = encode(enc, c);
+
+            if (buf.length < i + n)
+                onRangeError("std.string.sformat", 0);
+
+            buf[i .. i + n] = enc[0 .. n];
+            i += n;
+        }
+        void put(const(char)[] s)
+        {
+            if (buf.length < i + s.length)
+                onRangeError("std.string.sformat", 0);
+
+            buf[i .. i + s.length] = s[];
+            i += s.length;
+        }
+        void put(const(wchar)[] s)
+        {
+            for (; !s.empty; s.popFront())
+                put(s.front);
+        }
+        void put(const(dchar)[] s)
+        {
+            for (; !s.empty; s.popFront())
+                put(s.front);
+        }
+    }
+    auto n = formattedWrite(Sink(), fmt, args);
+    version (all)
+    {
+        // In the future, this check will be removed to increase consistency
+        // with formattedWrite
+        import std.conv : text;
+        import std.exception : enforce;
+        enforce(n == args.length, new FormatException(
+            text("Orphan format arguments: args[", n, "..", args.length, "]")));
+    }
+    return buf[0 .. i];
+}
+
+unittest
+{
+    import core.exception;
+    import std.format;
+
+    debug(string) trustedPrintf("std.string.sformat.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    char[10] buf;
+
+    assert(sformat(buf[], "foo") == "foo");
+    assert(sformat(buf[], "foo%%") == "foo%");
+    assert(sformat(buf[], "foo%s", 'C') == "fooC");
+    assert(sformat(buf[], "%s foo", "bar") == "bar foo");
+    assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc"));
+    assert(sformat(buf[], "foo %d", -123) == "foo -123");
+    assert(sformat(buf[], "foo %d", 123) == "foo 123");
+
+    assertThrown!FormatException(sformat(buf[], "foo %s"));
+    assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456));
+
+    assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d");
+    });
+}
diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d
new file mode 100644
index 00000000000..19ed00931c8
--- /dev/null
+++ b/libphobos/src/std/functional.d
@@ -0,0 +1,1473 @@
+// Written in the D programming language.
+
+/**
+Functions that manipulate other functions.
+
+This module provides functions for compile time function composition. These
+functions are helpful when constructing predicates for the algorithms in
+$(LINK2 std_algorithm.html, std.algorithm) or $(LINK2 std_range.html,
+std.range).
+
+$(BOOKTABLE ,
+$(TR $(TH Function Name) $(TH Description)
+)
+    $(TR $(TD $(D $(LREF adjoin)))
+        $(TD Joins a couple of functions into one that executes the original
+        functions independently and returns a tuple with all the results.
+    ))
+    $(TR $(TD $(D $(LREF compose)), $(D $(LREF pipe)))
+        $(TD Join a couple of functions into one that executes the original
+        functions one after the other, using one function's result for the next
+        function's argument.
+    ))
+    $(TR $(TD $(D $(LREF forward)))
+        $(TD Forwards function arguments while saving ref-ness.
+    ))
+    $(TR $(TD $(D $(LREF lessThan)), $(D $(LREF greaterThan)), $(D $(LREF equalTo)))
+        $(TD Ready-made predicate functions to compare two values.
+    ))
+    $(TR $(TD $(D $(LREF memoize)))
+        $(TD Creates a function that caches its result for fast re-evalation.
+    ))
+    $(TR $(TD $(D $(LREF not)))
+        $(TD Creates a function that negates another.
+    ))
+    $(TR $(TD $(D $(LREF partial)))
+        $(TD Creates a function that binds the first argument of a given function
+        to a given value.
+    ))
+    $(TR $(TD $(D $(LREF reverseArgs)), $(D $(LREF binaryReverseArgs)))
+        $(TD Predicate that reverses the order of its arguments.
+    ))
+    $(TR $(TD $(D $(LREF toDelegate)))
+        $(TD Converts a callable to a delegate.
+    ))
+    $(TR $(TD $(D $(LREF unaryFun)), $(D $(LREF binaryFun)))
+        $(TD Create a unary or binary function from a string. Most often
+        used when defining algorithms on ranges.
+    ))
+)
+
+Macros:
+
+WIKI = Phobos/StdFunctional
+
+Copyright: Copyright Andrei Alexandrescu 2008 - 2009.
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu)
+Source:    $(PHOBOSSRC std/_functional.d)
+*/
+/*
+         Copyright Andrei Alexandrescu 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.functional;
+
+import std.meta, std.traits;
+
+
+private template needOpCallAlias(alias fun)
+{
+    /* Determine whether or not unaryFun and binaryFun need to alias to fun or
+     * fun.opCall. Basically, fun is a function object if fun(...) compiles. We
+     * want is(unaryFun!fun) (resp., is(binaryFun!fun)) to be true if fun is
+     * any function object. There are 4 possible cases:
+     *
+     *  1) fun is the type of a function object with static opCall;
+     *  2) fun is an instance of a function object with static opCall;
+     *  3) fun is the type of a function object with non-static opCall;
+     *  4) fun is an instance of a function object with non-static opCall.
+     *
+     * In case (1), is(unaryFun!fun) should compile, but does not if unaryFun
+     * aliases itself to fun, because typeof(fun) is an error when fun itself
+     * is a type. So it must be aliased to fun.opCall instead. All other cases
+     * should be aliased to fun directly.
+     */
+    static if (is(typeof(fun.opCall) == function))
+    {
+        import std.traits : Parameters;
+
+        enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () {
+            return fun(Parameters!fun.init);
+        });
+    }
+    else
+        enum needOpCallAlias = false;
+}
+
+/**
+Transforms a string representing an expression into a unary
+function. The string must either use symbol name $(D a) as
+the parameter or provide the symbol via the $(D parmName) argument.
+If $(D fun) is not a string, $(D unaryFun) aliases itself away to $(D fun).
+*/
+
+template unaryFun(alias fun, string parmName = "a")
+{
+    static if (is(typeof(fun) : string))
+    {
+        static if (!fun._ctfeMatchUnary(parmName))
+        {
+            import std.traits, std.typecons, std.meta;
+            import std.algorithm, std.conv, std.exception, std.math, std.range, std.string;
+        }
+        auto unaryFun(ElementType)(auto ref ElementType __a)
+        {
+            mixin("alias " ~ parmName ~ " = __a ;");
+            return mixin(fun);
+        }
+    }
+    else static if (needOpCallAlias!fun)
+    {
+        // Issue 9906
+        alias unaryFun = fun.opCall;
+    }
+    else
+    {
+        alias unaryFun = fun;
+    }
+}
+
+///
+unittest
+{
+    // Strings are compiled into functions:
+    alias isEven = unaryFun!("(a & 1) == 0");
+    assert(isEven(2) && !isEven(1));
+}
+
+unittest
+{
+    static int f1(int a) { return a + 1; }
+    static assert(is(typeof(unaryFun!(f1)(1)) == int));
+    assert(unaryFun!(f1)(41) == 42);
+    int f2(int a) { return a + 1; }
+    static assert(is(typeof(unaryFun!(f2)(1)) == int));
+    assert(unaryFun!(f2)(41) == 42);
+    assert(unaryFun!("a + 1")(41) == 42);
+    //assert(unaryFun!("return a + 1;")(41) == 42);
+
+    int num = 41;
+    assert(unaryFun!"a + 1"(num) == 42);
+
+    // Issue 9906
+    struct Seen
+    {
+        static bool opCall(int n) { return true; }
+    }
+    static assert(needOpCallAlias!Seen);
+    static assert(is(typeof(unaryFun!Seen(1))));
+    assert(unaryFun!Seen(1));
+
+    Seen s;
+    static assert(!needOpCallAlias!s);
+    static assert(is(typeof(unaryFun!s(1))));
+    assert(unaryFun!s(1));
+
+    struct FuncObj
+    {
+        bool opCall(int n) { return true; }
+    }
+    FuncObj fo;
+    static assert(!needOpCallAlias!fo);
+    static assert(is(typeof(unaryFun!fo)));
+    assert(unaryFun!fo(1));
+
+    // Function object with non-static opCall can only be called with an
+    // instance, not with merely the type.
+    static assert(!is(typeof(unaryFun!FuncObj)));
+}
+
+/**
+Transforms a string representing an expression into a binary function. The
+string must either use symbol names $(D a) and $(D b) as the parameters or
+provide the symbols via the $(D parm1Name) and $(D parm2Name) arguments.
+If $(D fun) is not a string, $(D binaryFun) aliases itself away to
+$(D fun).
+*/
+
+template binaryFun(alias fun, string parm1Name = "a",
+        string parm2Name = "b")
+{
+    static if (is(typeof(fun) : string))
+    {
+        static if (!fun._ctfeMatchBinary(parm1Name, parm2Name))
+        {
+            import std.traits, std.typecons, std.meta;
+            import std.algorithm, std.conv, std.exception, std.math, std.range, std.string;
+        }
+        auto binaryFun(ElementType1, ElementType2)
+            (auto ref ElementType1 __a, auto ref ElementType2 __b)
+        {
+            mixin("alias "~parm1Name~" = __a ;");
+            mixin("alias "~parm2Name~" = __b ;");
+            return mixin(fun);
+        }
+    }
+    else static if (needOpCallAlias!fun)
+    {
+        // Issue 9906
+        alias binaryFun = fun.opCall;
+    }
+    else
+    {
+        alias binaryFun = fun;
+    }
+}
+
+///
+unittest
+{
+    alias less = binaryFun!("a < b");
+    assert(less(1, 2) && !less(2, 1));
+    alias greater = binaryFun!("a > b");
+    assert(!greater("1", "2") && greater("2", "1"));
+}
+
+unittest
+{
+    static int f1(int a, string b) { return a + 1; }
+    static assert(is(typeof(binaryFun!(f1)(1, "2")) == int));
+    assert(binaryFun!(f1)(41, "a") == 42);
+    string f2(int a, string b) { return b ~ "2"; }
+    static assert(is(typeof(binaryFun!(f2)(1, "1")) == string));
+    assert(binaryFun!(f2)(1, "4") == "42");
+    assert(binaryFun!("a + b")(41, 1) == 42);
+    //@@BUG
+    //assert(binaryFun!("return a + b;")(41, 1) == 42);
+
+    // Issue 9906
+    struct Seen
+    {
+        static bool opCall(int x, int y) { return true; }
+    }
+    static assert(is(typeof(binaryFun!Seen)));
+    assert(binaryFun!Seen(1,1));
+
+    struct FuncObj
+    {
+        bool opCall(int x, int y) { return true; }
+    }
+    FuncObj fo;
+    static assert(!needOpCallAlias!fo);
+    static assert(is(typeof(binaryFun!fo)));
+    assert(unaryFun!fo(1,1));
+
+    // Function object with non-static opCall can only be called with an
+    // instance, not with merely the type.
+    static assert(!is(typeof(binaryFun!FuncObj)));
+}
+
+// skip all ASCII chars except a..z, A..Z, 0..9, '_' and '.'.
+private uint _ctfeSkipOp(ref string op)
+{
+    if (!__ctfe) assert(false);
+    import std.ascii : isASCII, isAlphaNum;
+    immutable oldLength = op.length;
+    while (op.length)
+    {
+        immutable front = op[0];
+        if(front.isASCII() && !(front.isAlphaNum() || front == '_' || front == '.'))
+            op = op[1..$];
+        else
+            break;
+    }
+    return oldLength != op.length;
+}
+
+// skip all digits
+private uint _ctfeSkipInteger(ref string op)
+{
+    if (!__ctfe) assert(false);
+    import std.ascii : isDigit;
+    immutable oldLength = op.length;
+    while (op.length)
+    {
+        immutable front = op[0];
+        if(front.isDigit())
+            op = op[1..$];
+        else
+            break;
+    }
+    return oldLength != op.length;
+}
+
+// skip name
+private uint _ctfeSkipName(ref string op, string name)
+{
+    if (!__ctfe) assert(false);
+    if (op.length >= name.length && op[0..name.length] == name)
+    {
+        op = op[name.length..$];
+        return 1;
+    }
+    return 0;
+}
+
+// returns 1 if $(D fun) is trivial unary function
+private uint _ctfeMatchUnary(string fun, string name)
+{
+    if (!__ctfe) assert(false);
+    import std.stdio;
+    fun._ctfeSkipOp();
+    for (;;)
+    {
+        immutable h = fun._ctfeSkipName(name) + fun._ctfeSkipInteger();
+        if (h == 0)
+        {
+            fun._ctfeSkipOp();
+            break;
+        }
+        else if (h == 1)
+        {
+            if(!fun._ctfeSkipOp())
+                break;
+        }
+        else
+            return 0;
+    }
+    return fun.length == 0;
+}
+
+unittest
+{
+    static assert(!_ctfeMatchUnary("sqrt(ё)", "ё"));
+    static assert(!_ctfeMatchUnary("ё.sqrt", "ё"));
+    static assert(!_ctfeMatchUnary(".ё+ё", "ё"));
+    static assert(!_ctfeMatchUnary("_ё+ё", "ё"));
+    static assert(!_ctfeMatchUnary("ёё", "ё"));
+    static assert(_ctfeMatchUnary("a+a", "a"));
+    static assert(_ctfeMatchUnary("a + 10", "a"));
+    static assert(_ctfeMatchUnary("4 == a", "a"));
+    static assert(_ctfeMatchUnary("2==a", "a"));
+    static assert(_ctfeMatchUnary("1 != a", "a"));
+    static assert(_ctfeMatchUnary("a!=4", "a"));
+    static assert(_ctfeMatchUnary("a< 1", "a"));
+    static assert(_ctfeMatchUnary("434 < a", "a"));
+    static assert(_ctfeMatchUnary("132 > a", "a"));
+    static assert(_ctfeMatchUnary("123 >a", "a"));
+    static assert(_ctfeMatchUnary("a>82", "a"));
+    static assert(_ctfeMatchUnary("ё>82", "ё"));
+    static assert(_ctfeMatchUnary("ё[ё(ё)]", "ё"));
+    static assert(_ctfeMatchUnary("ё[21]", "ё"));
+}
+
+// returns 1 if $(D fun) is trivial binary function
+private uint _ctfeMatchBinary(string fun, string name1, string name2)
+{
+    if (!__ctfe) assert(false);
+    fun._ctfeSkipOp();
+    for (;;)
+    {
+        immutable h = fun._ctfeSkipName(name1) + fun._ctfeSkipName(name2) + fun._ctfeSkipInteger();
+        if (h == 0)
+        {
+            fun._ctfeSkipOp();
+            break;
+        }
+        else if (h == 1)
+        {
+            if(!fun._ctfeSkipOp())
+                break;
+        }
+        else
+            return 0;
+    }
+    return fun.length == 0;
+}
+
+unittest {
+
+    static assert(!_ctfeMatchBinary("sqrt(ё)", "ё", "b"));
+    static assert(!_ctfeMatchBinary("ё.sqrt", "ё", "b"));
+    static assert(!_ctfeMatchBinary(".ё+ё", "ё", "b"));
+    static assert(!_ctfeMatchBinary("_ё+ё", "ё", "b"));
+    static assert(!_ctfeMatchBinary("ёё", "ё", "b"));
+    static assert(_ctfeMatchBinary("a+a", "a", "b"));
+    static assert(_ctfeMatchBinary("a + 10", "a", "b"));
+    static assert(_ctfeMatchBinary("4 == a", "a", "b"));
+    static assert(_ctfeMatchBinary("2==a", "a", "b"));
+    static assert(_ctfeMatchBinary("1 != a", "a", "b"));
+    static assert(_ctfeMatchBinary("a!=4", "a", "b"));
+    static assert(_ctfeMatchBinary("a< 1", "a", "b"));
+    static assert(_ctfeMatchBinary("434 < a", "a", "b"));
+    static assert(_ctfeMatchBinary("132 > a", "a", "b"));
+    static assert(_ctfeMatchBinary("123 >a", "a", "b"));
+    static assert(_ctfeMatchBinary("a>82", "a", "b"));
+    static assert(_ctfeMatchBinary("ё>82", "ё", "q"));
+    static assert(_ctfeMatchBinary("ё[ё(10)]", "ё", "q"));
+    static assert(_ctfeMatchBinary("ё[21]", "ё", "q"));
+
+    static assert(!_ctfeMatchBinary("sqrt(ё)+b", "b", "ё"));
+    static assert(!_ctfeMatchBinary("ё.sqrt-b", "b", "ё"));
+    static assert(!_ctfeMatchBinary(".ё+b", "b", "ё"));
+    static assert(!_ctfeMatchBinary("_b+ё", "b", "ё"));
+    static assert(!_ctfeMatchBinary("ba", "b", "a"));
+    static assert(_ctfeMatchBinary("a+b", "b", "a"));
+    static assert(_ctfeMatchBinary("a + b", "b", "a"));
+    static assert(_ctfeMatchBinary("b == a", "b", "a"));
+    static assert(_ctfeMatchBinary("b==a", "b", "a"));
+    static assert(_ctfeMatchBinary("b != a", "b", "a"));
+    static assert(_ctfeMatchBinary("a!=b", "b", "a"));
+    static assert(_ctfeMatchBinary("a< b", "b", "a"));
+    static assert(_ctfeMatchBinary("b < a", "b", "a"));
+    static assert(_ctfeMatchBinary("b > a", "b", "a"));
+    static assert(_ctfeMatchBinary("b >a", "b", "a"));
+    static assert(_ctfeMatchBinary("a>b", "b", "a"));
+    static assert(_ctfeMatchBinary("ё>b", "b", "ё"));
+    static assert(_ctfeMatchBinary("b[ё(-1)]", "b", "ё"));
+    static assert(_ctfeMatchBinary("ё[-21]", "b", "ё"));
+}
+
+//undocumented
+template safeOp(string S)
+    if (S=="<"||S==">"||S=="<="||S==">="||S=="=="||S=="!=")
+{
+    private bool unsafeOp(ElementType1, ElementType2)(ElementType1 a, ElementType2 b) pure
+        if (isIntegral!ElementType1 && isIntegral!ElementType2)
+    {
+        alias T = CommonType!(ElementType1, ElementType2);
+        return mixin("cast(T)a "~S~" cast(T)b");
+    }
+
+    bool safeOp(T0, T1)(auto ref T0 a, auto ref T1 b)
+    {
+        static if (isIntegral!T0 && isIntegral!T1 &&
+                   (mostNegative!T0 < 0) != (mostNegative!T1 < 0))
+        {
+            static if (S == "<=" || S == "<")
+            {
+                static if (mostNegative!T0 < 0)
+                    immutable result = a < 0 || unsafeOp(a, b);
+                else
+                    immutable result = b >= 0 && unsafeOp(a, b);
+            }
+            else
+            {
+                static if (mostNegative!T0 < 0)
+                    immutable result = a >= 0 && unsafeOp(a, b);
+                else
+                    immutable result = b < 0 || unsafeOp(a, b);
+            }
+        }
+        else
+        {
+            static assert (is(typeof(mixin("a "~S~" b"))),
+                "Invalid arguments: Cannot compare types " ~ T0.stringof ~ " and " ~ T1.stringof ~ ".");
+
+            immutable result = mixin("a "~S~" b");
+        }
+        return result;
+    }
+}
+
+unittest //check user defined types
+{
+    import std.algorithm : equal;
+    struct Foo
+    {
+        int a;
+        auto opEquals(Foo foo)
+        {
+            return a == foo.a;
+        }
+    }
+    assert(safeOp!"!="(Foo(1), Foo(2)));
+}
+
+/**
+   Predicate that returns $(D_PARAM a < b).
+   Correctly compares signed and unsigned integers, ie. -1 < 2U.
+*/
+alias lessThan = safeOp!"<";
+
+///
+pure @safe @nogc nothrow unittest
+{
+    assert(lessThan(2, 3));
+    assert(lessThan(2U, 3U));
+    assert(lessThan(2, 3.0));
+    assert(lessThan(-2, 3U));
+    assert(lessThan(2, 3U));
+    assert(!lessThan(3U, -2));
+    assert(!lessThan(3U, 2));
+    assert(!lessThan(0, 0));
+    assert(!lessThan(0U, 0));
+    assert(!lessThan(0, 0U));
+}
+
+/**
+   Predicate that returns $(D_PARAM a > b).
+   Correctly compares signed and unsigned integers, ie. 2U > -1.
+*/
+alias greaterThan = safeOp!">";
+
+///
+unittest
+{
+    assert(!greaterThan(2, 3));
+    assert(!greaterThan(2U, 3U));
+    assert(!greaterThan(2, 3.0));
+    assert(!greaterThan(-2, 3U));
+    assert(!greaterThan(2, 3U));
+    assert(greaterThan(3U, -2));
+    assert(greaterThan(3U, 2));
+    assert(!greaterThan(0, 0));
+    assert(!greaterThan(0U, 0));
+    assert(!greaterThan(0, 0U));
+}
+
+/**
+   Predicate that returns $(D_PARAM a == b).
+   Correctly compares signed and unsigned integers, ie. !(-1 == ~0U).
+*/
+alias equalTo = safeOp!"==";
+
+///
+unittest
+{
+    assert(equalTo(0U, 0));
+    assert(equalTo(0, 0U));
+    assert(!equalTo(-1, ~0U));
+}
+/**
+   N-ary predicate that reverses the order of arguments, e.g., given
+   $(D pred(a, b, c)), returns $(D pred(c, b, a)).
+*/
+template reverseArgs(alias pred)
+{
+    auto reverseArgs(Args...)(auto ref Args args)
+        if (is(typeof(pred(Reverse!args))))
+    {
+        return pred(Reverse!args);
+    }
+}
+
+///
+unittest
+{
+    alias gt = reverseArgs!(binaryFun!("a < b"));
+    assert(gt(2, 1) && !gt(1, 1));
+    int x = 42;
+    bool xyz(int a, int b) { return a * x < b / x; }
+    auto foo = &xyz;
+    foo(4, 5);
+    alias zyx = reverseArgs!(foo);
+    assert(zyx(5, 4) == foo(4, 5));
+}
+
+///
+unittest
+{
+    int abc(int a, int b, int c) { return a * b + c; }
+    alias cba = reverseArgs!abc;
+    assert(abc(91, 17, 32) == cba(32, 17, 91));
+}
+
+///
+unittest
+{
+    int a(int a) { return a * 2; }
+    alias _a = reverseArgs!a;
+    assert(a(2) == _a(2));
+}
+
+///
+unittest
+{
+    int b() { return 4; }
+    alias _b = reverseArgs!b;
+    assert(b() == _b());
+}
+
+/**
+   Binary predicate that reverses the order of arguments, e.g., given
+   $(D pred(a, b)), returns $(D pred(b, a)).
+*/
+template binaryReverseArgs(alias pred)
+{
+    auto binaryReverseArgs(ElementType1, ElementType2)
+            (auto ref ElementType1 a, auto ref ElementType2 b)
+    {
+        return pred(b, a);
+    }
+}
+
+///
+unittest
+{
+    alias gt = binaryReverseArgs!(binaryFun!("a < b"));
+    assert(gt(2, 1) && !gt(1, 1));
+}
+
+///
+unittest
+{
+    int x = 42;
+    bool xyz(int a, int b) { return a * x < b / x; }
+    auto foo = &xyz;
+    foo(4, 5);
+    alias zyx = binaryReverseArgs!(foo);
+    assert(zyx(5, 4) == foo(4, 5));
+}
+
+/**
+Negates predicate $(D pred).
+ */
+template not(alias pred)
+{
+    auto not(T...)(auto ref T args)
+    {
+        static if (is(typeof(!pred(args))))
+            return !pred(args);
+        else static if (T.length == 1)
+            return !unaryFun!pred(args);
+        else static if (T.length == 2)
+            return !binaryFun!pred(args);
+        else
+            static assert(0);
+    }
+}
+
+///
+unittest
+{
+    import std.functional;
+    import std.algorithm : find;
+    import std.uni : isWhite;
+    string a = "   Hello, world!";
+    assert(find!(not!isWhite)(a) == "Hello, world!");
+}
+
+unittest
+{
+    assert(not!"a != 5"(5));
+    assert(not!"a != b"(5, 5));
+
+    assert(not!(() => false)());
+    assert(not!(a => a != 5)(5));
+    assert(not!((a, b) => a != b)(5, 5));
+    assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5));
+}
+
+/**
+$(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially
+applies) $(D_PARAM fun) by tying its first argument to $(D_PARAM arg).
+ */
+template partial(alias fun, alias arg)
+{
+    static if (is(typeof(fun) == delegate) || is(typeof(fun) == function))
+    {
+        ReturnType!fun partial(Parameters!fun[1..$] args2)
+        {
+            return fun(arg, args2);
+        }
+    }
+    else
+    {
+        auto partial(Ts...)(Ts args2)
+        {
+            static if (is(typeof(fun(arg, args2))))
+            {
+                return fun(arg, args2);
+            }
+            else
+            {
+                static string errormsg()
+                {
+                    string msg = "Cannot call '" ~ fun.stringof ~ "' with arguments " ~
+                        "(" ~ arg.stringof;
+                    foreach(T; Ts)
+                        msg ~= ", " ~ T.stringof;
+                    msg ~= ").";
+                    return msg;
+                }
+                static assert(0, errormsg());
+            }
+        }
+    }
+}
+
+///
+unittest
+{
+    int fun(int a, int b) { return a + b; }
+    alias fun5 = partial!(fun, 5);
+    assert(fun5(6) == 11);
+    // Note that in most cases you'd use an alias instead of a value
+    // assignment. Using an alias allows you to partially evaluate template
+    // functions without committing to a particular type of the function.
+}
+
+// Explicitly undocumented. It will be removed in March 2016. @@@DEPRECATED_2016-03@@@
+deprecated("Please use std.functional.partial instead")
+alias curry = partial;
+
+// tests for partially evaluating callables
+unittest
+{
+    static int f1(int a, int b) { return a + b; }
+    assert(partial!(f1, 5)(6) == 11);
+
+    int f2(int a, int b) { return a + b; }
+    int x = 5;
+    assert(partial!(f2, x)(6) == 11);
+    x = 7;
+    assert(partial!(f2, x)(6) == 13);
+    static assert(partial!(f2, 5)(6) == 11);
+
+    auto dg = &f2;
+    auto f3 = &partial!(dg, x);
+    assert(f3(6) == 13);
+
+    static int funOneArg(int a) { return a; }
+    assert(partial!(funOneArg, 1)() == 1);
+
+    static int funThreeArgs(int a, int b, int c) { return a + b + c; }
+    alias funThreeArgs1 = partial!(funThreeArgs, 1);
+    assert(funThreeArgs1(2, 3) == 6);
+    static assert(!is(typeof(funThreeArgs1(2))));
+
+    enum xe = 5;
+    alias fe = partial!(f2, xe);
+    static assert(fe(6) == 11);
+}
+
+// tests for partially evaluating templated/overloaded callables
+unittest
+{
+    static auto add(A, B)(A x, B y)
+    {
+        return x + y;
+    }
+
+    alias add5 = partial!(add, 5);
+    assert(add5(6) == 11);
+    static assert(!is(typeof(add5())));
+    static assert(!is(typeof(add5(6, 7))));
+
+    // taking address of templated partial evaluation needs explicit type
+    auto dg = &add5!(int);
+    assert(dg(6) == 11);
+
+    int x = 5;
+    alias addX = partial!(add, x);
+    assert(addX(6) == 11);
+
+    static struct Callable
+    {
+        static string opCall(string a, string b) { return a ~ b; }
+        int opCall(int a, int b) { return a * b; }
+        double opCall(double a, double b) { return a + b; }
+    }
+    Callable callable;
+    assert(partial!(Callable, "5")("6") == "56");
+    assert(partial!(callable, 5)(6) == 30);
+    assert(partial!(callable, 7.0)(3.0) == 7.0 + 3.0);
+
+    static struct TCallable
+    {
+        auto opCall(A, B)(A a, B b)
+        {
+            return a + b;
+        }
+    }
+    TCallable tcallable;
+    assert(partial!(tcallable, 5)(6) == 11);
+    static assert(!is(typeof(partial!(tcallable, "5")(6))));
+
+    static A funOneArg(A)(A a) { return a; }
+    alias funOneArg1 = partial!(funOneArg, 1);
+    assert(funOneArg1() == 1);
+
+    static auto funThreeArgs(A, B, C)(A a, B b, C c) { return a + b + c; }
+    alias funThreeArgs1 = partial!(funThreeArgs, 1);
+    assert(funThreeArgs1(2, 3) == 6);
+    static assert(!is(typeof(funThreeArgs1(1))));
+
+    auto dg2 = &funOneArg1!();
+    assert(dg2() == 1);
+}
+
+/**
+Takes multiple functions and adjoins them together. The result is a
+$(XREF typecons, Tuple) with one element per passed-in function. Upon
+invocation, the returned tuple is the adjoined results of all
+functions.
+
+Note: In the special case where only a single function is provided
+($(D F.length == 1)), adjoin simply aliases to the single passed function
+($(D F[0])).
+*/
+template adjoin(F...) if (F.length == 1)
+{
+    alias adjoin = F[0];
+}
+/// ditto
+template adjoin(F...) if (F.length > 1)
+{
+    auto adjoin(V...)(auto ref V a)
+    {
+        import std.typecons : tuple;
+        static if (F.length == 2)
+        {
+            return tuple(F[0](a), F[1](a));
+        }
+        else static if (F.length == 3)
+        {
+            return tuple(F[0](a), F[1](a), F[2](a));
+        }
+        else
+        {
+            import std.format : format;
+            import std.range : iota;
+            return mixin (q{tuple(%(F[%s](a)%|, %))}.format(iota(0, F.length)));
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.functional, std.typecons;
+    static bool f1(int a) { return a != 0; }
+    static int f2(int a) { return a / 2; }
+    auto x = adjoin!(f1, f2)(5);
+    assert(is(typeof(x) == Tuple!(bool, int)));
+    assert(x[0] == true && x[1] == 2);
+}
+
+unittest
+{
+    import std.typecons;
+    static bool F1(int a) { return a != 0; }
+    auto x1 = adjoin!(F1)(5);
+    static int F2(int a) { return a / 2; }
+    auto x2 = adjoin!(F1, F2)(5);
+    assert(is(typeof(x2) == Tuple!(bool, int)));
+    assert(x2[0] && x2[1] == 2);
+    auto x3 = adjoin!(F1, F2, F2)(5);
+    assert(is(typeof(x3) == Tuple!(bool, int, int)));
+    assert(x3[0] && x3[1] == 2 && x3[2] == 2);
+
+    bool F4(int a) { return a != x1; }
+    alias eff4 = adjoin!(F4);
+    static struct S
+    {
+        bool delegate(int) store;
+        int fun() { return 42 + store(5); }
+    }
+    S s;
+    s.store = (int a) { return eff4(a); };
+    auto x4 = s.fun();
+    assert(x4 == 43);
+}
+
+unittest
+{
+    import std.meta : staticMap;
+    import std.typecons : Tuple, tuple;
+    alias funs = staticMap!(unaryFun, "a", "a * 2", "a * 3", "a * a", "-a");
+    alias afun = adjoin!funs;
+    assert(afun(5) == tuple(5, 10, 15, 25, -5));
+
+    static class C{}
+    alias IC = immutable(C);
+    IC foo(){return typeof(return).init;}
+    Tuple!(IC, IC, IC, IC) ret1 = adjoin!(foo, foo, foo, foo)();
+
+    static struct S{int* p;}
+    alias IS = immutable(S);
+    IS bar(){return typeof(return).init;}
+    enum Tuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)();
+}
+
+/**
+   Composes passed-in functions $(D fun[0], fun[1], ...) returning a
+   function $(D f(x)) that in turn returns $(D
+   fun[0](fun[1](...(x)))...). Each function can be a regular
+   functions, a delegate, or a string.
+*/
+template compose(fun...)
+{
+    static if (fun.length == 1)
+    {
+        alias compose = unaryFun!(fun[0]);
+    }
+    else static if (fun.length == 2)
+    {
+        // starch
+        alias fun0 = unaryFun!(fun[0]);
+        alias fun1 = unaryFun!(fun[1]);
+
+        // protein: the core composition operation
+        typeof({ E a; return fun0(fun1(a)); }()) compose(E)(E a)
+        {
+            return fun0(fun1(a));
+        }
+    }
+    else
+    {
+        // protein: assembling operations
+        alias compose = compose!(fun[0], compose!(fun[1 .. $]));
+    }
+}
+
+///
+unittest
+{
+    import std.algorithm: equal, map;
+    import std.array: split;
+    import std.conv: to;
+
+    // First split a string in whitespace-separated tokens and then
+    // convert each token into an integer
+    assert(compose!(map!(to!(int)), split)("1 2 3").equal([1, 2, 3]));
+}
+
+/**
+   Pipes functions in sequence. Offers the same functionality as $(D
+   compose), but with functions specified in reverse order. This may
+   lead to more readable code in some situation because the order of
+   execution is the same as lexical order.
+
+   Example:
+
+----
+// Read an entire text file, split the resulting string in
+// whitespace-separated tokens, and then convert each token into an
+// integer
+int[] a = pipe!(readText, split, map!(to!(int)))("file.txt");
+----
+ */
+alias pipe(fun...) = compose!(Reverse!(fun));
+
+unittest
+{
+    import std.conv : to;
+    string foo(int a) { return to!(string)(a); }
+    int bar(string a) { return to!(int)(a) + 1; }
+    double baz(int a) { return a + 0.5; }
+    assert(compose!(baz, bar, foo)(1) == 2.5);
+    assert(pipe!(foo, bar, baz)(1) == 2.5);
+
+    assert(compose!(baz, `to!(int)(a) + 1`, foo)(1) == 2.5);
+    assert(compose!(baz, bar)("1"[]) == 2.5);
+
+    assert(compose!(baz, bar)("1") == 2.5);
+
+    assert(compose!(`a + 0.5`, `to!(int)(a) + 1`, foo)(1) == 2.5);
+}
+
+/**
+ * $(LUCKY Memoizes) a function so as to avoid repeated
+ * computation. The memoization structure is a hash table keyed by a
+ * tuple of the function's arguments. There is a speed gain if the
+ * function is repeatedly called with the same arguments and is more
+ * expensive than a hash table lookup. For more information on memoization, refer to $(WEB docs.google.com/viewer?url=http%3A%2F%2Fhop.perl.plover.com%2Fbook%2Fpdf%2F03CachingAndMemoization.pdf, this book chapter).
+
+Example:
+----
+double transmogrify(int a, string b)
+{
+   ... expensive computation ...
+}
+alias fastTransmogrify = memoize!transmogrify;
+unittest
+{
+    auto slow = transmogrify(2, "hello");
+    auto fast = fastTransmogrify(2, "hello");
+    assert(slow == fast);
+}
+----
+
+Technically the memoized function should be pure because $(D memoize) assumes it will
+always return the same result for a given tuple of arguments. However, $(D memoize) does not
+enforce that because sometimes it
+is useful to memoize an impure function, too.
+*/
+template memoize(alias fun)
+{
+    // alias Args = Parameters!fun; // Bugzilla 13580
+
+    ReturnType!fun memoize(Parameters!fun args)
+    {
+        alias Args = Parameters!fun;
+        import std.typecons : Tuple;
+
+        static ReturnType!fun[Tuple!Args] memo;
+        auto t = Tuple!Args(args);
+        if (auto p = t in memo)
+            return *p;
+        return memo[t] = fun(args);
+    }
+}
+
+/// ditto
+template memoize(alias fun, uint maxSize)
+{
+    // alias Args = Parameters!fun; // Bugzilla 13580
+    ReturnType!fun memoize(Parameters!fun args)
+    {
+        import std.typecons : tuple;
+        static struct Value { Parameters!fun args; ReturnType!fun res; }
+        static Value[] memo;
+        static size_t[] initialized;
+
+        if (!memo.length)
+        {
+            import core.memory;
+
+            enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN);
+            memo = (cast(Value*)GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize];
+            enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof);
+            initialized = (cast(size_t*)GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords];
+        }
+
+        import core.bitop : bt, bts;
+        import std.conv : emplace;
+
+        size_t hash;
+        foreach (ref arg; args)
+            hash = hashOf(arg, hash);
+        // cuckoo hashing
+        immutable idx1 = hash % maxSize;
+        if (!bt(initialized.ptr, idx1))
+        {
+            emplace(&memo[idx1], args, fun(args));
+            bts(initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025)
+            return memo[idx1].res;
+        }
+        else if (memo[idx1].args == args)
+            return memo[idx1].res;
+        // FNV prime
+        immutable idx2 = (hash * 16777619) % maxSize;
+        if (!bt(initialized.ptr, idx2))
+        {
+            emplace(&memo[idx2], memo[idx1]);
+            bts(initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025)
+        }
+        else if (memo[idx2].args == args)
+            return memo[idx2].res;
+        else if (idx1 != idx2)
+            memo[idx2] = memo[idx1];
+
+        memo[idx1] = Value(args, fun(args));
+        return memo[idx1].res;
+    }
+}
+
+/**
+ * To _memoize a recursive function, simply insert the memoized call in lieu of the plain recursive call.
+ * For example, to transform the exponential-time Fibonacci implementation into a linear-time computation:
+ */
+unittest
+{
+    ulong fib(ulong n)
+    {
+        return n < 2 ? 1 : memoize!fib(n - 2) + memoize!fib(n - 1);
+    }
+    assert(fib(10) == 89);
+}
+
+/**
+ * To improve the speed of the factorial function,
+ */
+unittest
+{
+    ulong fact(ulong n)
+    {
+        return n < 2 ? 1 : n * memoize!fact(n - 1);
+    }
+    assert(fact(10) == 3628800);
+}
+
+/**
+ * This memoizes all values of $(D fact) up to the largest argument. To only cache the final
+ * result, move $(D memoize) outside the function as shown below.
+ */
+unittest
+{
+    ulong factImpl(ulong n)
+    {
+        return n < 2 ? 1 : n * factImpl(n - 1);
+    }
+    alias fact = memoize!factImpl;
+    assert(fact(10) == 3628800);
+}
+
+/**
+ * When the $(D maxSize) parameter is specified, memoize will used
+ * a fixed size hash table to limit the number of cached entries.
+ */
+unittest
+{
+    ulong fact(ulong n)
+    {
+        // Memoize no more than 8 values
+        return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1);
+    }
+    assert(fact(8) == 40320);
+    // using more entries than maxSize will overwrite existing entries
+    assert(fact(10) == 3628800);
+}
+
+unittest
+{
+    import core.math;
+    alias msqrt = memoize!(function double(double x) { return sqrt(x); });
+    auto y = msqrt(2.0);
+    assert(y == msqrt(2.0));
+    y = msqrt(4.0);
+    assert(y == sqrt(4.0));
+
+    // alias mrgb2cmyk = memoize!rgb2cmyk;
+    // auto z = mrgb2cmyk([43, 56, 76]);
+    // assert(z == mrgb2cmyk([43, 56, 76]));
+
+    //alias mfib = memoize!fib;
+
+    static ulong fib(ulong n)
+    {
+        alias mfib = memoize!fib;
+        return n < 2 ? 1 : mfib(n - 2) + mfib(n - 1);
+    }
+
+    auto z = fib(10);
+    assert(z == 89);
+
+    static ulong fact(ulong n)
+    {
+        alias mfact = memoize!fact;
+        return n < 2 ? 1 : n * mfact(n - 1);
+    }
+    assert(fact(10) == 3628800);
+
+    // Issue 12568
+    static uint len2(const string s) { // Error
+    alias mLen2 = memoize!len2;
+    if (s.length == 0)
+        return 0;
+    else
+        return 1 + mLen2(s[1 .. $]);
+    }
+
+    int _func(int x) { return 1; }
+    alias func = memoize!(_func, 10);
+    assert(func(int.init) == 1);
+    assert(func(int.init) == 1);
+}
+
+private struct DelegateFaker(F)
+{
+    import std.typecons;
+
+    // for @safe
+    static F castToF(THIS)(THIS x) @trusted
+    {
+        return cast(F) x;
+    }
+
+    /*
+     * What all the stuff below does is this:
+     *--------------------
+     * struct DelegateFaker(F) {
+     *     extern(linkage)
+     *     [ref] ReturnType!F doIt(Parameters!F args) [@attributes]
+     *     {
+     *         auto fp = cast(F) &this;
+     *         return fp(args);
+     *     }
+     * }
+     *--------------------
+     */
+
+    // We will use MemberFunctionGenerator in std.typecons.  This is a policy
+    // configuration for generating the doIt().
+    template GeneratingPolicy()
+    {
+        // Inform the genereator that we only have type information.
+        enum WITHOUT_SYMBOL = true;
+
+        // Generate the function body of doIt().
+        template generateFunctionBody(unused...)
+        {
+            enum generateFunctionBody =
+            // [ref] ReturnType doIt(Parameters args) @attributes
+            q{
+                // When this function gets called, the this pointer isn't
+                // really a this pointer (no instance even really exists), but
+                // a function pointer that points to the function to be called.
+                // Cast it to the correct type and call it.
+
+                auto fp = castToF(&this);
+                return fp(args);
+            };
+        }
+    }
+    // Type information used by the generated code.
+    alias FuncInfo_doIt = FuncInfo!(F);
+
+    // Generate the member function doIt().
+    mixin( std.typecons.MemberFunctionGenerator!(GeneratingPolicy!())
+            .generateFunction!("FuncInfo_doIt", "doIt", F) );
+}
+
+/**
+ * Convert a callable to a delegate with the same parameter list and
+ * return type, avoiding heap allocations and use of auxiliary storage.
+ *
+ * Example:
+ * ----
+ * void doStuff() {
+ *     writeln("Hello, world.");
+ * }
+ *
+ * void runDelegate(void delegate() myDelegate) {
+ *     myDelegate();
+ * }
+ *
+ * auto delegateToPass = toDelegate(&doStuff);
+ * runDelegate(delegateToPass);  // Calls doStuff, prints "Hello, world."
+ * ----
+ *
+ * BUGS:
+ * $(UL
+ *   $(LI Does not work with $(D @safe) functions.)
+ *   $(LI Ignores C-style / D-style variadic arguments.)
+ * )
+ */
+auto toDelegate(F)(auto ref F fp) if (isCallable!(F))
+{
+    static if (is(F == delegate))
+    {
+        return fp;
+    }
+    else static if (is(typeof(&F.opCall) == delegate)
+                || (is(typeof(&F.opCall) V : V*) && is(V == function)))
+    {
+        return toDelegate(&fp.opCall);
+    }
+    else
+    {
+        alias DelType = typeof(&(new DelegateFaker!(F)).doIt);
+
+        static struct DelegateFields {
+            union {
+                DelType del;
+                //pragma(msg, typeof(del));
+
+                struct {
+                    void* contextPtr;
+                    void* funcPtr;
+                }
+            }
+        }
+
+        // fp is stored in the returned delegate's context pointer.
+        // The returned delegate's function pointer points to
+        // DelegateFaker.doIt.
+        DelegateFields df;
+
+        df.contextPtr = cast(void*) fp;
+
+        DelegateFaker!(F) dummy;
+        auto dummyDel = &dummy.doIt;
+        df.funcPtr = dummyDel.funcptr;
+
+        return df.del;
+    }
+}
+
+unittest {
+    static int inc(ref uint num) {
+        num++;
+        return 8675309;
+    }
+
+    uint myNum = 0;
+    auto incMyNumDel = toDelegate(&inc);
+    static assert(is(typeof(incMyNumDel) == int delegate(ref uint)));
+    auto returnVal = incMyNumDel(myNum);
+    assert(myNum == 1);
+
+    interface I { int opCall(); }
+    class C: I { int opCall() { inc(myNum); return myNum;} }
+    auto c = new C;
+    auto i = cast(I) c;
+
+    auto getvalc = toDelegate(c);
+    assert(getvalc() == 2);
+
+    auto getvali = toDelegate(i);
+    assert(getvali() == 3);
+
+    struct S1 { int opCall() { inc(myNum); return myNum; } }
+    static assert(!is(typeof(&s1.opCall) == delegate));
+    S1 s1;
+    auto getvals1 = toDelegate(s1);
+    assert(getvals1() == 4);
+
+    struct S2 { static int opCall() { return 123456; } }
+    static assert(!is(typeof(&S2.opCall) == delegate));
+    S2 s2;
+    auto getvals2 =&S2.opCall;
+    assert(getvals2() == 123456);
+
+    /* test for attributes */
+    {
+        static int refvar = 0xDeadFace;
+
+        static ref int func_ref() { return refvar; }
+        static int func_pure() pure { return 1; }
+        static int func_nothrow() nothrow { return 2; }
+        static int func_property() @property { return 3; }
+        static int func_safe() @safe { return 4; }
+        static int func_trusted() @trusted { return 5; }
+        static int func_system() @system { return 6; }
+        static int func_pure_nothrow() pure nothrow { return 7; }
+        static int func_pure_nothrow_safe() pure @safe { return 8; }
+
+        auto dg_ref = toDelegate(&func_ref);
+        auto dg_pure = toDelegate(&func_pure);
+        auto dg_nothrow = toDelegate(&func_nothrow);
+        auto dg_property = toDelegate(&func_property);
+        auto dg_safe = toDelegate(&func_safe);
+        auto dg_trusted = toDelegate(&func_trusted);
+        auto dg_system = toDelegate(&func_system);
+        auto dg_pure_nothrow = toDelegate(&func_pure_nothrow);
+        auto dg_pure_nothrow_safe = toDelegate(&func_pure_nothrow_safe);
+
+        //static assert(is(typeof(dg_ref) == ref int delegate())); // [BUG@DMD]
+        static assert(is(typeof(dg_pure) == int delegate() pure));
+        static assert(is(typeof(dg_nothrow) == int delegate() nothrow));
+        static assert(is(typeof(dg_property) == int delegate() @property));
+        //static assert(is(typeof(dg_safe) == int delegate() @safe));
+        static assert(is(typeof(dg_trusted) == int delegate() @trusted));
+        static assert(is(typeof(dg_system) == int delegate() @system));
+        static assert(is(typeof(dg_pure_nothrow) == int delegate() pure nothrow));
+        //static assert(is(typeof(dg_pure_nothrow_safe) == int delegate() @safe pure nothrow));
+
+        assert(dg_ref() == refvar);
+        assert(dg_pure() == 1);
+        assert(dg_nothrow() == 2);
+        assert(dg_property() == 3);
+        //assert(dg_safe() == 4);
+        assert(dg_trusted() == 5);
+        assert(dg_system() == 6);
+        assert(dg_pure_nothrow() == 7);
+        //assert(dg_pure_nothrow_safe() == 8);
+    }
+    /* test for linkage */
+    {
+        struct S
+        {
+            extern(C) static void xtrnC() {}
+            extern(D) static void xtrnD() {}
+        }
+        auto dg_xtrnC = toDelegate(&S.xtrnC);
+        auto dg_xtrnD = toDelegate(&S.xtrnD);
+        static assert(! is(typeof(dg_xtrnC) == typeof(dg_xtrnD)));
+    }
+}
+
+/**
+Forwards function arguments with saving ref-ness.
+*/
+template forward(args...)
+{
+    import std.meta;
+
+    static if (args.length)
+    {
+        import std.algorithm.mutation : move;
+
+        alias arg = args[0];
+        static if (__traits(isRef, arg))
+            alias fwd = arg;
+        else
+            @property fwd()(){ return move(arg); }
+        alias forward = AliasSeq!(fwd, forward!(args[1..$]));
+    }
+    else
+        alias forward = AliasSeq!();
+}
+
+///
+@safe unittest
+{
+    class C
+    {
+        static int foo(int n) { return 1; }
+        static int foo(ref int n) { return 2; }
+    }
+    int bar()(auto ref int x) { return C.foo(forward!x); }
+
+    assert(bar(1) == 1);
+    int i;
+    assert(bar(i) == 2);
+}
+
+///
+@safe unittest
+{
+    void foo(int n, ref string s) { s = null; foreach (i; 0..n) s ~= "Hello"; }
+
+    // forwards all arguments which are bound to parameter tuple
+    void bar(Args...)(auto ref Args args) { return foo(forward!args); }
+
+    // forwards all arguments with swapping order
+    void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); }
+
+    string s;
+    bar(1, s);
+    assert(s == "Hello");
+    baz(s, 2);
+    assert(s == "HelloHello");
+}
+
+@safe unittest
+{
+    auto foo(TL...)(auto ref TL args)
+    {
+        string result = "";
+        foreach (i, _; args)
+        {
+            //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R");
+            result ~= __traits(isRef, args[i]) ? "L" : "R";
+        }
+        return result;
+    }
+
+    string bar(TL...)(auto ref TL args)
+    {
+        return foo(forward!args);
+    }
+    string baz(TL...)(auto ref TL args)
+    {
+        int x;
+        return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x);
+    }
+
+    struct S {}
+    S makeS(){ return S(); }
+    int n;
+    string s;
+    assert(bar(S(), makeS(), n, s) == "RRLL");
+    assert(baz(S(), makeS(), n, s) == "LLRRRL");
+}
+
+@safe unittest
+{
+    ref int foo(return ref int a) { return a; }
+    ref int bar(Args)(auto ref Args args)
+    {
+        return foo(forward!args);
+    }
+    static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG
+    int value = 3;
+    auto x2 = bar(value); // case of OK
+}
+
diff --git a/libphobos/src/std/getopt.d b/libphobos/src/std/getopt.d
new file mode 100644
index 00000000000..c508243e44b
--- /dev/null
+++ b/libphobos/src/std/getopt.d
@@ -0,0 +1,1664 @@
+// Written in the D programming language.
+
+/**
+Processing of command line options.
+
+The getopt module implements a $(D getopt) function, which adheres to
+the POSIX syntax for command line options. GNU extensions are
+supported in the form of long options introduced by a double dash
+("--"). Support for bundling of command line options, as was the case
+with the more traditional single-letter approach, is provided but not
+enabled by default.
+
+Macros:
+
+WIKI = Phobos/StdGetopt
+
+Copyright: Copyright Andrei Alexandrescu 2008 - 2015.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu)
+Credits:   This module and its documentation are inspired by Perl's $(WEB
+           perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of
+           D's $(D getopt) is simpler than its Perl counterpart because $(D
+           getopt) infers the expected parameter types from the static types of
+           the passed-in pointers.
+Source:    $(PHOBOSSRC std/_getopt.d)
+*/
+/*
+         Copyright Andrei Alexandrescu 2008 - 2015.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.getopt;
+
+import std.traits;
+import std.exception;  // basicExceptionCtors
+
+/**
+Thrown on one of the following conditions:
+$(UL
+  $(LI An unrecognized command-line argument is passed, and
+       $(D std.getopt.config.passThrough) was not present.)
+  $(LI A command-line option was not found, and
+       $(D std.getopt.config.required) was present.)
+)
+*/
+class GetOptException : Exception
+{
+    mixin basicExceptionCtors;
+}
+
+static assert(is(typeof(new GetOptException("message"))));
+static assert(is(typeof(new GetOptException("message", Exception.init))));
+
+/**
+   Parse and remove command line options from a string array.
+
+   Synopsis:
+
+---------
+import std.getopt;
+
+string data = "file.dat";
+int length = 24;
+bool verbose;
+enum Color { no, yes };
+Color color;
+
+void main(string[] args)
+{
+  auto helpInformation = getopt(
+    args,
+    "length",  &length,    // numeric
+    "file",    &data,      // string
+    "verbose", &verbose,   // flag
+    "color", "Information about this color", &color);    // enum
+  ...
+
+  if (helpInformation.helpWanted)
+  {
+    defaultGetoptPrinter("Some information about the program.",
+      helpInformation.options);
+  }
+}
+---------
+
+ The $(D getopt) function takes a reference to the command line
+ (as received by $(D main)) as its first argument, and an
+ unbounded number of pairs of strings and pointers. Each string is an
+ option meant to "fill" the value referenced by the pointer to its
+ right (the "bound" pointer). The option string in the call to
+ $(D getopt) should not start with a dash.
+
+ In all cases, the command-line options that were parsed and used by
+ $(D getopt) are removed from $(D args). Whatever in the
+ arguments did not look like an option is left in $(D args) for
+ further processing by the program. Values that were unaffected by the
+ options are not touched, so a common idiom is to initialize options
+ to their defaults and then invoke $(D getopt). If a
+ command-line argument is recognized as an option with a parameter and
+ the parameter cannot be parsed properly (e.g., a number is expected
+ but not present), a $(D ConvException) exception is thrown.
+ If $(D std.getopt.config.passThrough) was not passed to $(D getopt)
+ and an unrecognized command-line argument is found, a $(D GetOptException)
+ is thrown.
+
+ Depending on the type of the pointer being bound, $(D getopt)
+ recognizes the following kinds of options:
+
+ $(OL
+    $(LI $(I Boolean options). A lone argument sets the option to $(D true).
+    Additionally $(B true) or $(B false) can be set within the option separated
+    with an "=" sign:
+
+---------
+  bool verbose = false, debugging = true;
+  getopt(args, "verbose", &verbose, "debug", &debugging);
+---------
+
+    To set $(D verbose) to $(D true), invoke the program with either
+    $(D --verbose) or $(D --verbose=true).
+
+    To set $(D debugging) to $(D false), invoke the program with
+    $(D --debugging=false).
+    )
+
+    $(LI $(I Numeric options.) If an option is bound to a numeric type, a
+    number is expected as the next option, or right within the option separated
+    with an "=" sign:
+
+---------
+  uint timeout;
+  getopt(args, "timeout", &timeout);
+---------
+
+    To set $(D timeout) to $(D 5), invoke the program with either
+    $(D --timeout=5) or $(D --timeout 5).
+    )
+
+    $(LI $(I Incremental options.) If an option name has a "+" suffix and is
+    bound to a numeric type, then the option's value tracks the number of times
+    the option occurred on the command line:
+
+---------
+  uint paranoid;
+  getopt(args, "paranoid+", &paranoid);
+---------
+
+    Invoking the program with "--paranoid --paranoid --paranoid" will set $(D
+    paranoid) to 3. Note that an incremental option never expects a parameter,
+    e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set
+    $(D paranoid) to 42; instead, $(D paranoid) is set to 2 and "42" is not
+    considered as part of the normal program arguments.
+    )
+
+    $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as
+    a string is expected as the next option, or right within the option
+    separated with an "=" sign:
+
+---------
+  enum Color { no, yes };
+  Color color; // default initialized to Color.no
+  getopt(args, "color", &color);
+---------
+
+    To set $(D color) to $(D Color.yes), invoke the program with either
+    $(D --color=yes) or $(D --color yes).
+    )
+
+    $(LI $(I String options.) If an option is bound to a string, a string is
+    expected as the next option, or right within the option separated with an
+    "=" sign:
+
+---------
+string outputFile;
+getopt(args, "output", &outputFile);
+---------
+
+    Invoking the program with "--output=myfile.txt" or "--output myfile.txt"
+    will set $(D outputFile) to "myfile.txt". If you want to pass a string
+    containing spaces, you need to use the quoting that is appropriate to your
+    shell, e.g. --output='my file.txt'.
+    )
+
+    $(LI $(I Array options.) If an option is bound to an array, a new element
+    is appended to the array each time the option occurs:
+
+---------
+string[] outputFiles;
+getopt(args, "output", &outputFiles);
+---------
+
+    Invoking the program with "--output=myfile.txt --output=yourfile.txt" or
+    "--output myfile.txt --output yourfile.txt" will set $(D outputFiles) to
+    $(D [ "myfile.txt", "yourfile.txt" ]).
+
+    Alternatively you can set $(LREF arraySep) as the element separator:
+
+---------
+string[] outputFiles;
+arraySep = ",";  // defaults to "", separation by whitespace
+getopt(args, "output", &outputFiles);
+---------
+
+    With the above code you can invoke the program with
+    "--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".)
+
+    $(LI $(I Hash options.) If an option is bound to an associative array, a
+    string of the form "name=value" is expected as the next option, or right
+    within the option separated with an "=" sign:
+
+---------
+double[string] tuningParms;
+getopt(args, "tune", &tuningParms);
+---------
+
+    Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set
+    $(D tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ].
+
+    Alternatively you can set $(LREF arraySep) as the element separator:
+
+---------
+double[string] tuningParms;
+arraySep = ",";  // defaults to "", separation by whitespace
+getopt(args, "tune", &tuningParms);
+---------
+
+    With the above code you can invoke the program with
+    "--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6".
+
+    In general, the keys and values can be of any parsable types.
+    )
+
+    $(LI $(I Callback options.) An option can be bound to a function or
+    delegate with the signature $(D void function()), $(D void function(string
+    option)), $(D void function(string option, string value)), or their
+    delegate equivalents.
+
+    $(UL
+        $(LI If the callback doesn't take any arguments, the callback is
+        invoked whenever the option is seen.
+        )
+
+        $(LI If the callback takes one string argument, the option string
+        (without the leading dash(es)) is passed to the callback.  After that,
+        the option string is considered handled and removed from the options
+        array.
+
+---------
+void main(string[] args)
+{
+  uint verbosityLevel = 1;
+  void myHandler(string option)
+  {
+    if (option == "quiet")
+    {
+      verbosityLevel = 0;
+    }
+    else
+    {
+      assert(option == "verbose");
+      verbosityLevel = 2;
+    }
+  }
+  getopt(args, "verbose", &myHandler, "quiet", &myHandler);
+}
+---------
+
+        )
+
+        $(LI If the callback takes two string arguments, the option string is
+        handled as an option with one argument, and parsed accordingly. The
+        option and its value are passed to the callback. After that, whatever
+        was passed to the callback is considered handled and removed from the
+        list.
+
+---------
+int main(string[] args)
+{
+  uint verbosityLevel = 1;
+  bool handlerFailed = false;
+  void myHandler(string option, string value)
+  {
+    switch (value)
+    {
+      case "quiet": verbosityLevel = 0; break;
+      case "verbose": verbosityLevel = 2; break;
+      case "shouting": verbosityLevel = verbosityLevel.max; break;
+      default :
+        stderr.writeln("Dunno how verbose you want me to be by saying ",
+          value);
+        handlerFailed = true;
+        break;
+    }
+  }
+  getopt(args, "verbosity", &myHandler);
+  return handlerFailed ? 1 : 0;
+}
+---------
+        )
+    ))
+)
+
+Options_with_multiple_names:
+Sometimes option synonyms are desirable, e.g. "--verbose",
+"--loquacious", and "--garrulous" should have the same effect. Such
+alternate option names can be included in the option specification,
+using "|" as a separator:
+
+---------
+bool verbose;
+getopt(args, "verbose|loquacious|garrulous", &verbose);
+---------
+
+Case:
+By default options are case-insensitive. You can change that behavior
+by passing $(D getopt) the $(D caseSensitive) directive like this:
+
+---------
+bool foo, bar;
+getopt(args,
+    std.getopt.config.caseSensitive,
+    "foo", &foo,
+    "bar", &bar);
+---------
+
+In the example above, "--foo", "--bar", "--FOo", "--bAr" etc. are recognized.
+The directive is active til the end of $(D getopt), or until the
+converse directive $(D caseInsensitive) is encountered:
+
+---------
+bool foo, bar;
+getopt(args,
+    std.getopt.config.caseSensitive,
+    "foo", &foo,
+    std.getopt.config.caseInsensitive,
+    "bar", &bar);
+---------
+
+The option "--Foo" is rejected due to $(D
+std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
+etc. because the directive $(D
+std.getopt.config.caseInsensitive) turned sensitivity off before
+option "bar" was parsed.
+
+Short_versus_long_options:
+Traditionally, programs accepted single-letter options preceded by
+only one dash (e.g. $(D -t)). $(D getopt) accepts such parameters
+seamlessly. When used with a double-dash (e.g. $(D --t)), a
+single-letter option behaves the same as a multi-letter option. When
+used with a single dash, a single-letter option is accepted. If the
+option has a parameter, that must be "stuck" to the option without
+any intervening space or "=":
+
+---------
+uint timeout;
+getopt(args, "timeout|t", &timeout);
+---------
+
+To set $(D timeout) to $(D 5), use either of the following: $(D --timeout=5),
+$(D --timeout 5), $(D --t=5), $(D --t 5), or $(D -t5). Forms such as $(D -t 5)
+and $(D -timeout=5) will be not accepted.
+
+For more details about short options, refer also to the next section.
+
+Bundling:
+Single-letter options can be bundled together, i.e. "-abc" is the same as
+$(D "-a -b -c"). By default, this option is turned off. You can turn it on
+with the $(D std.getopt.config.bundling) directive:
+
+---------
+bool foo, bar;
+getopt(args,
+    std.getopt.config.bundling,
+    "foo|f", &foo,
+    "bar|b", &bar);
+---------
+
+In case you want to only enable bundling for some of the parameters,
+bundling can be turned off with $(D std.getopt.config.noBundling).
+
+Required:
+An option can be marked as required. If that option is not present in the
+arguments an exception will be thrown.
+
+---------
+bool foo, bar;
+getopt(args,
+    std.getopt.config.required,
+    "foo|f", &foo,
+    "bar|b", &bar);
+---------
+
+Only the option directly following $(D std.getopt.config.required) is
+required.
+
+Passing_unrecognized_options_through:
+If an application needs to do its own processing of whichever arguments
+$(D getopt) did not understand, it can pass the
+$(D std.getopt.config.passThrough) directive to $(D getopt):
+
+---------
+bool foo, bar;
+getopt(args,
+    std.getopt.config.passThrough,
+    "foo", &foo,
+    "bar", &bar);
+---------
+
+An unrecognized option such as "--baz" will be found untouched in
+$(D args) after $(D getopt) returns.
+
+Help_Information_Generation:
+If an option string is followed by another string, this string serves as a
+description for this option. The $(D getopt) function returns a struct of type
+$(D GetoptResult). This return value contains information about all passed options
+as well a $(D bool GetoptResult.helpWanted) flag indicating whether information
+about these options was requested. The $(getopt) function always adds an option for
+--help|-h` to set the flag if the option is seen on the command line.
+
+Options_Terminator:
+A lone double-dash terminates $(D getopt) gathering. It is used to
+separate program options from other parameters (e.g., options to be passed
+to another program). Invoking the example above with $(D "--foo -- --bar")
+parses foo but leaves "--bar" in $(D args). The double-dash itself is
+removed from the argument array unless the $(D std.getopt.config.keepEndOfOptions)
+directive is given.
+*/
+GetoptResult getopt(T...)(ref string[] args, T opts)
+{
+    import std.exception : enforce;
+    enforce(args.length,
+            "Invalid arguments string passed: program name missing");
+    configuration cfg;
+    GetoptResult rslt;
+
+    GetOptException excep;
+    getoptImpl(args, cfg, rslt, excep, opts);
+
+    if (!rslt.helpWanted && excep !is null)
+    {
+        throw excep;
+    }
+
+    return rslt;
+}
+
+///
+unittest
+{
+    auto args = ["prog", "--foo", "-b"];
+
+    bool foo;
+    bool bar;
+    auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b",
+        "Some help message about bar.", &bar);
+
+    if (rslt.helpWanted)
+    {
+        defaultGetoptPrinter("Some information about the program.",
+            rslt.options);
+    }
+}
+
+/**
+   Configuration options for $(D getopt).
+
+   You can pass them to $(D getopt) in any position, except in between an option
+   string and its bound pointer.
+*/
+enum config {
+    /// Turn case sensitivity on
+    caseSensitive,
+    /// Turn case sensitivity off
+    caseInsensitive,
+    /// Turn bundling on
+    bundling,
+    /// Turn bundling off
+    noBundling,
+    /// Pass unrecognized arguments through
+    passThrough,
+    /// Signal unrecognized arguments as errors
+    noPassThrough,
+    /// Stop at first argument that does not look like an option
+    stopOnFirstNonOption,
+    /// Do not erase the endOfOptions separator from args
+    keepEndOfOptions,
+    /// Make the next option a required option
+    required
+}
+
+/** The result of the $(D getopt) function.
+
+$(D helpWanted) is set if the option `--help` or `-h` was passed to the option parser.
+*/
+struct GetoptResult {
+    bool helpWanted; /// Flag indicating if help was requested
+    Option[] options; /// All possible options
+}
+
+/** Information about an option.
+*/
+struct Option {
+    string optShort; /// The short symbol for this option
+    string optLong; /// The long symbol for this option
+    string help; /// The description of this option
+    bool required; /// If a option is required, not passing it will result in an error
+}
+
+private pure Option splitAndGet(string opt) @trusted nothrow
+{
+    import std.array : split;
+    auto sp = split(opt, "|");
+    Option ret;
+    if (sp.length > 1)
+    {
+        ret.optShort = "-" ~ (sp[0].length < sp[1].length ?
+            sp[0] : sp[1]);
+        ret.optLong = "--" ~ (sp[0].length > sp[1].length ?
+            sp[0] : sp[1]);
+    }
+    else
+    {
+        ret.optLong = "--" ~ sp[0];
+    }
+
+    return ret;
+}
+
+/*
+This function verifies that the variadic parameters passed in getOpt
+follow this pattern:
+
+  [config override], option, [description], receiver,
+
+ - config override: a config value, optional
+ - option:          a string or a char
+ - description:     a string, optional
+ - receiver:        a pointer or a callable
+*/
+private template optionValidator(A...)
+{
+    import std.typecons: staticIota;
+    import std.format: format;
+
+    enum fmt = "getopt validator: %s (at position %d)";
+    enum isReceiver(T) = isPointer!T || (is(T==function)) || (is(T==delegate));
+    enum isOptionStr(T) = isSomeString!T || isSomeChar!T;
+
+    auto validator()
+    {
+        string msg;
+        static if (A.length > 0)
+        {
+            static if (isReceiver!(A[0]))
+            {
+                msg = format(fmt, "first argument must be a string or a config", 0);
+            }
+            else static if (!isOptionStr!(A[0]) && !is(A[0] == config))
+            {
+                msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0);
+            }
+            else foreach(i; staticIota!(1, A.length))
+            {
+                static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) &&
+                    !(is(A[i] == config)))
+                {
+                    msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i);
+                    break;
+                }
+                else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1]))
+                {
+                    msg = format(fmt, "a receiver can not be preceeded by a receiver", i);
+                    break;
+                }
+                else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1])
+                    && isSomeString!(A[i-2]))
+                {
+                    msg = format(fmt, "a string can not be preceeded by two strings", i);
+                    break;
+                }
+            }
+            static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config))
+            {
+                msg = format(fmt, "last argument must be a receiver or a config",
+                    A.length -1);
+            }
+        }
+        return msg;
+    }
+    enum message = validator;
+    alias optionValidator = message;
+}
+
+@safe pure unittest
+{
+    alias P = void*;
+    alias S = string;
+    alias A = char;
+    alias C = config;
+    alias F = void function();
+
+    static assert(optionValidator!(S,P) == "");
+    static assert(optionValidator!(S,F) == "");
+    static assert(optionValidator!(A,P) == "");
+    static assert(optionValidator!(A,F) == "");
+
+    static assert(optionValidator!(C,S,P) == "");
+    static assert(optionValidator!(C,S,F) == "");
+    static assert(optionValidator!(C,A,P) == "");
+    static assert(optionValidator!(C,A,F) == "");
+
+    static assert(optionValidator!(C,S,S,P) == "");
+    static assert(optionValidator!(C,S,S,F) == "");
+    static assert(optionValidator!(C,A,S,P) == "");
+    static assert(optionValidator!(C,A,S,F) == "");
+
+    static assert(optionValidator!(C,S,S,P) == "");
+    static assert(optionValidator!(C,S,S,P,C,S,F) == "");
+    static assert(optionValidator!(C,S,P,C,S,S,F) == "");
+
+    static assert(optionValidator!(C,A,P,A,S,F) == "");
+    static assert(optionValidator!(C,A,P,C,A,S,F) == "");
+
+    static assert(optionValidator!(P,S,S) != "");
+    static assert(optionValidator!(P,P,S) != "");
+    static assert(optionValidator!(P,F,S,P) != "");
+    static assert(optionValidator!(C,C,S) != "");
+    static assert(optionValidator!(S,S,P,S,S,P,S) != "");
+    static assert(optionValidator!(S,S,P,P) != "");
+    static assert(optionValidator!(S,S,S,P) != "");
+
+    static assert(optionValidator!(C,A,S,P,C,A,F) == "");
+    static assert(optionValidator!(C,A,P,C,A,S,F) == "");
+}
+
+unittest // bugzilla 15914
+{
+    bool opt;
+    string[] args = ["program", "-a"];
+    getopt(args, config.passThrough, 'a', &opt);
+    assert(opt);
+    opt = false;
+    args = ["program", "-a"];
+    getopt(args, 'a', &opt);
+    assert(opt);
+    opt = false;
+    args = ["program", "-a"];
+    getopt(args, 'a', "help string", &opt);
+    assert(opt);
+    opt = false;
+    args = ["program", "-a"];
+    getopt(args, config.caseSensitive, 'a', "help string", &opt);
+    assert(opt);
+}
+
+private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
+    ref GetoptResult rslt, ref GetOptException excep, T opts)
+{
+    enum validationMessage = optionValidator!T;
+    static assert(validationMessage == "", validationMessage);
+
+    import std.algorithm : remove;
+    import std.conv : to;
+    static if (opts.length)
+    {
+        static if (is(typeof(opts[0]) : config))
+        {
+            // it's a configuration flag, act on it
+            setConfig(cfg, opts[0]);
+            return getoptImpl(args, cfg, rslt, excep, opts[1 .. $]);
+        }
+        else
+        {
+            // it's an option string
+            auto option = to!string(opts[0]);
+            Option optionHelp = splitAndGet(option);
+            optionHelp.required = cfg.required;
+
+            static if (is(typeof(opts[1]) : string))
+            {
+                auto receiver = opts[2];
+                optionHelp.help = opts[1];
+                immutable lowSliceIdx = 3;
+            }
+            else
+            {
+                auto receiver = opts[1];
+                immutable lowSliceIdx = 2;
+            }
+
+            rslt.options ~= optionHelp;
+
+            bool incremental;
+            // Handle options of the form --blah+
+            if (option.length && option[$ - 1] == autoIncrementChar)
+            {
+                option = option[0 .. $ - 1];
+                incremental = true;
+            }
+
+            bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
+
+            if (cfg.required && !optWasHandled)
+            {
+                excep = new GetOptException("Required option "
+                    ~ option ~ " was not supplied", excep);
+            }
+            cfg.required = false;
+
+            getoptImpl(args, cfg, rslt, excep, opts[lowSliceIdx .. $]);
+        }
+    }
+    else
+    {
+        // no more options to look for, potentially some arguments left
+        for (size_t i = 1; i < args.length;)
+        {
+            auto a = args[i];
+            if (endOfOptions.length && a == endOfOptions)
+            {
+                // Consume the "--" if keepEndOfOptions is not specified
+                if (!cfg.keepEndOfOptions)
+                    args = args.remove(i);
+                break;
+            }
+            if (!a.length || a[0] != optionChar)
+            {
+                // not an option
+                if (cfg.stopOnFirstNonOption) break;
+                ++i;
+                continue;
+            }
+            if (a == "--help" || a == "-h")
+            {
+                rslt.helpWanted = true;
+                args = args.remove(i);
+                continue;
+            }
+            if (!cfg.passThrough)
+            {
+                throw new GetOptException("Unrecognized option "~a, excep);
+            }
+            ++i;
+        }
+
+        Option helpOpt;
+        helpOpt.optShort = "-h";
+        helpOpt.optLong = "--help";
+        helpOpt.help = "This help information.";
+        rslt.options ~= helpOpt;
+    }
+}
+
+private bool handleOption(R)(string option, R receiver, ref string[] args,
+    ref configuration cfg, bool incremental)
+{
+    import std.algorithm : map, splitter;
+    import std.ascii : isAlpha;
+    import std.conv : text, to;
+    // Scan arguments looking for a match for this option
+    bool ret = false;
+    for (size_t i = 1; i < args.length; )
+    {
+        auto a = args[i];
+        if (endOfOptions.length && a == endOfOptions) break;
+        if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
+        {
+            // first non-option is end of options
+            break;
+        }
+        // Unbundle bundled arguments if necessary
+        if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
+                a[1] != optionChar)
+        {
+            string[] expanded;
+            foreach (j, dchar c; a[1 .. $])
+            {
+                // If the character is not alpha, stop right there. This allows
+                // e.g. -j100 to work as "pass argument 100 to option -j".
+                if (!isAlpha(c))
+                {
+                    if (c == '=')
+                        j++;
+                    expanded ~= a[j + 1 .. $];
+                    break;
+                }
+                expanded ~= text(optionChar, c);
+            }
+            args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
+            continue;
+        }
+
+        string val;
+        if (!optMatch(a, option, val, cfg))
+        {
+            ++i;
+            continue;
+        }
+
+        ret = true;
+
+        // found it
+        // from here on, commit to eat args[i]
+        // (and potentially args[i + 1] too, but that comes later)
+        args = args[0 .. i] ~ args[i + 1 .. $];
+
+        static if (is(typeof(*receiver) == bool))
+        {
+            // parse '--b=true/false'
+            if (val.length)
+            {
+                *receiver = to!(typeof(*receiver))(val);
+                break;
+            }
+
+            // no argument means set it to true
+            *receiver = true;
+            break;
+        }
+        else
+        {
+            import std.exception : enforce;
+            // non-boolean option, which might include an argument
+            //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void);
+            enum isCallbackWithLessThanTwoParameters =
+                (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
+                !is(typeof(receiver("", "")));
+            if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental)
+            {
+                // Eat the next argument too.  Check to make sure there's one
+                // to be eaten first, though.
+                enforce(i < args.length,
+                    "Missing value for argument " ~ a ~ ".");
+                val = args[i];
+                args = args[0 .. i] ~ args[i + 1 .. $];
+            }
+            static if (is(typeof(*receiver) == enum))
+            {
+                *receiver = to!(typeof(*receiver))(val);
+            }
+            else static if (is(typeof(*receiver) : real))
+            {
+                // numeric receiver
+                if (incremental) ++*receiver;
+                else *receiver = to!(typeof(*receiver))(val);
+            }
+            else static if (is(typeof(*receiver) == string))
+            {
+                // string receiver
+                *receiver = to!(typeof(*receiver))(val);
+            }
+            else static if (is(typeof(receiver) == delegate) ||
+                            is(typeof(*receiver) == function))
+            {
+                static if (is(typeof(receiver("", "")) : void))
+                {
+                    // option with argument
+                    receiver(option, val);
+                }
+                else static if (is(typeof(receiver("")) : void))
+                {
+                    static assert(is(typeof(receiver("")) : void));
+                    // boolean-style receiver
+                    receiver(option);
+                }
+                else
+                {
+                    static assert(is(typeof(receiver()) : void));
+                    // boolean-style receiver without argument
+                    receiver();
+                }
+            }
+            else static if (isArray!(typeof(*receiver)))
+            {
+                // array receiver
+                import std.range : ElementEncodingType;
+                alias E = ElementEncodingType!(typeof(*receiver));
+
+                if (arraySep == "")
+                {
+                    *receiver ~= to!E(val);
+                }
+                else
+                {
+                    foreach (elem; val.splitter(arraySep).map!(a => to!E(a))())
+                        *receiver ~= elem;
+                }
+            }
+            else static if (isAssociativeArray!(typeof(*receiver)))
+            {
+                // hash receiver
+                alias K = typeof(receiver.keys[0]);
+                alias V = typeof(receiver.values[0]);
+
+                import std.range : only;
+                import std.typecons : Tuple, tuple;
+                import std.string : indexOf;
+
+                static Tuple!(K, V) getter(string input)
+                {
+                    auto j = indexOf(input, assignChar);
+                    auto key = input[0 .. j];
+                    auto value = input[j + 1 .. $];
+                    return tuple(to!K(key), to!V(value));
+                }
+
+                static void setHash(Range)(R receiver, Range range)
+                {
+                    foreach (k, v; range.map!getter)
+                        (*receiver)[k] = v;
+                }
+
+                if (arraySep == "")
+                    setHash(receiver, val.only);
+                else
+                    setHash(receiver, val.splitter(arraySep));
+            }
+            else
+            {
+                static assert(false, "Dunno how to deal with type " ~
+                        typeof(receiver).stringof);
+            }
+        }
+    }
+
+    return ret;
+}
+
+// 5316 - arrays with arraySep
+unittest
+{
+    import std.conv;
+
+    arraySep = ",";
+    scope (exit) arraySep = "";
+
+    string[] names;
+    auto args = ["program.name", "-nfoo,bar,baz"];
+    getopt(args, "name|n", &names);
+    assert(names == ["foo", "bar", "baz"], to!string(names));
+
+    names = names.init;
+    args = ["program.name", "-n", "foo,bar,baz"];
+    getopt(args, "name|n", &names);
+    assert(names == ["foo", "bar", "baz"], to!string(names));
+
+    names = names.init;
+    args = ["program.name", "--name=foo,bar,baz"];
+    getopt(args, "name|n", &names);
+    assert(names == ["foo", "bar", "baz"], to!string(names));
+
+    names = names.init;
+    args = ["program.name", "--name", "foo,bar,baz"];
+    getopt(args, "name|n", &names);
+    assert(names == ["foo", "bar", "baz"], to!string(names));
+}
+
+// 5316 - associative arrays with arraySep
+unittest
+{
+    import std.conv;
+
+    arraySep = ",";
+    scope (exit) arraySep = "";
+
+    int[string] values;
+    values = values.init;
+    auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
+    getopt(args, "values|v", &values);
+    assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
+
+    values = values.init;
+    args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
+    getopt(args, "values|v", &values);
+    assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
+
+    values = values.init;
+    args = ["program.name", "--values=foo=0,bar=1,baz=2"];
+    getopt(args, "values|t", &values);
+    assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
+
+    values = values.init;
+    args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
+    getopt(args, "values|v", &values);
+    assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
+}
+
+/**
+   The option character (default '-').
+
+   Defaults to '-' but it can be assigned to prior to calling $(D getopt).
+ */
+dchar optionChar = '-';
+
+/**
+   The string that conventionally marks the end of all options (default '--').
+
+   Defaults to "--" but can be assigned to prior to calling $(D getopt). Assigning an
+   empty string to $(D endOfOptions) effectively disables it.
+ */
+string endOfOptions = "--";
+
+/**
+   The assignment character used in options with parameters (default '=').
+
+   Defaults to '=' but can be assigned to prior to calling $(D getopt).
+ */
+dchar assignChar = '=';
+
+/**
+   The string used to separate the elements of an array or associative array
+   (default is "" which means the elements are separated by whitespace).
+
+   Defaults to "" but can be assigned to prior to calling $(D getopt).
+ */
+string arraySep = "";
+
+enum autoIncrementChar = '+';
+
+private struct configuration
+{
+    import std.bitmanip : bitfields;
+    mixin(bitfields!(
+                bool, "caseSensitive",  1,
+                bool, "bundling", 1,
+                bool, "passThrough", 1,
+                bool, "stopOnFirstNonOption", 1,
+                bool, "keepEndOfOptions", 1,
+                bool, "required", 1,
+                ubyte, "", 2));
+}
+
+private bool optMatch(string arg, string optPattern, ref string value,
+    configuration cfg)
+{
+    import std.uni : toUpper;
+    import std.string : indexOf;
+    import std.array : split;
+    //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
+    //scope(success) writeln("optMatch result: ", value);
+    if (!arg.length || arg[0] != optionChar) return false;
+    // yank the leading '-'
+    arg = arg[1 .. $];
+    immutable isLong = arg.length > 1 && arg[0] == optionChar;
+    //writeln("isLong: ", isLong);
+    // yank the second '-' if present
+    if (isLong) arg = arg[1 .. $];
+    immutable eqPos = indexOf(arg, assignChar);
+    if (isLong && eqPos >= 0)
+    {
+        // argument looks like --opt=value
+        value = arg[eqPos + 1 .. $];
+        arg = arg[0 .. eqPos];
+    }
+    else
+    {
+        if (!isLong && eqPos==1)
+        {
+            // argument looks like -o=value
+            value = arg[2 .. $];
+            arg = arg[0 .. 1];
+        }
+        else
+        if (!isLong && !cfg.bundling)
+        {
+            // argument looks like -ovalue and there's no bundling
+            value = arg[1 .. $];
+            arg = arg[0 .. 1];
+        }
+        else
+        {
+            // argument looks like --opt, or -oxyz with bundling
+            value = null;
+        }
+    }
+    //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
+    // Split the option
+    const variants = split(optPattern, "|");
+    foreach (v ; variants)
+    {
+        //writeln("Trying variant: ", v, " against ", arg);
+        if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v))
+            return true;
+        if (cfg.bundling && !isLong && v.length == 1
+                && indexOf(arg, v) >= 0)
+        {
+            //writeln("success");
+            return true;
+        }
+    }
+    return false;
+}
+
+private void setConfig(ref configuration cfg, config option)
+{
+    switch (option)
+    {
+    case config.caseSensitive: cfg.caseSensitive = true; break;
+    case config.caseInsensitive: cfg.caseSensitive = false; break;
+    case config.bundling: cfg.bundling = true; break;
+    case config.noBundling: cfg.bundling = false; break;
+    case config.passThrough: cfg.passThrough = true; break;
+    case config.noPassThrough: cfg.passThrough = false; break;
+    case config.required: cfg.required = true; break;
+    case config.stopOnFirstNonOption:
+        cfg.stopOnFirstNonOption = true; break;
+    case config.keepEndOfOptions:
+        cfg.keepEndOfOptions = true; break;
+    default: assert(false);
+    }
+}
+
+unittest
+{
+    import std.conv;
+    import std.math;
+
+    uint paranoid = 2;
+    string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
+    getopt(args, "paranoid+", &paranoid);
+    assert(paranoid == 5, to!(string)(paranoid));
+
+    enum Color { no, yes }
+    Color color;
+    args = ["program.name", "--color=yes",];
+    getopt(args, "color", &color);
+    assert(color, to!(string)(color));
+
+    color = Color.no;
+    args = ["program.name", "--color", "yes",];
+    getopt(args, "color", &color);
+    assert(color, to!(string)(color));
+
+    string data = "file.dat";
+    int length = 24;
+    bool verbose = false;
+    args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
+    getopt(
+        args,
+        "length",  &length,
+        "file",    &data,
+        "verbose", &verbose);
+    assert(args.length == 1);
+    assert(data == "dat.file");
+    assert(length == 5);
+    assert(verbose);
+
+    //
+    string[] outputFiles;
+    args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
+    getopt(args, "output", &outputFiles);
+    assert(outputFiles.length == 2
+           && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
+
+    outputFiles = [];
+    arraySep = ",";
+    args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
+    getopt(args, "output", &outputFiles);
+    assert(outputFiles.length == 2
+           && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
+    arraySep = "";
+
+    foreach (testArgs;
+        [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
+         ["program.name", "--tune=alpha=0.5,beta=0.6"],
+         ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
+    {
+        arraySep = ",";
+        double[string] tuningParms;
+        getopt(testArgs, "tune", &tuningParms);
+        assert(testArgs.length == 1);
+        assert(tuningParms.length == 2);
+        assert(approxEqual(tuningParms["alpha"], 0.5));
+        assert(approxEqual(tuningParms["beta"], 0.6));
+        arraySep = "";
+    }
+
+    uint verbosityLevel = 1;
+    void myHandler(string option)
+    {
+        if (option == "quiet")
+        {
+            verbosityLevel = 0;
+        }
+        else
+        {
+            assert(option == "verbose");
+            verbosityLevel = 2;
+        }
+    }
+    args = ["program.name", "--quiet"];
+    getopt(args, "verbose", &myHandler, "quiet", &myHandler);
+    assert(verbosityLevel == 0);
+    args = ["program.name", "--verbose"];
+    getopt(args, "verbose", &myHandler, "quiet", &myHandler);
+    assert(verbosityLevel == 2);
+
+    verbosityLevel = 1;
+    void myHandler2(string option, string value)
+    {
+        assert(option == "verbose");
+        verbosityLevel = 2;
+    }
+    args = ["program.name", "--verbose", "2"];
+    getopt(args, "verbose", &myHandler2);
+    assert(verbosityLevel == 2);
+
+    verbosityLevel = 1;
+    void myHandler3()
+    {
+        verbosityLevel = 2;
+    }
+    args = ["program.name", "--verbose"];
+    getopt(args, "verbose", &myHandler3);
+    assert(verbosityLevel == 2);
+
+    bool foo, bar;
+    args = ["program.name", "--foo", "--bAr"];
+    getopt(args,
+        std.getopt.config.caseSensitive,
+        std.getopt.config.passThrough,
+        "foo", &foo,
+        "bar", &bar);
+    assert(args[1] == "--bAr");
+
+    // test stopOnFirstNonOption
+
+    args = ["program.name", "--foo", "nonoption", "--bar"];
+    foo = bar = false;
+    getopt(args,
+        std.getopt.config.stopOnFirstNonOption,
+        "foo", &foo,
+        "bar", &bar);
+    assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
+
+    args = ["program.name", "--foo", "nonoption", "--zab"];
+    foo = bar = false;
+    getopt(args,
+        std.getopt.config.stopOnFirstNonOption,
+        "foo", &foo,
+        "bar", &bar);
+    assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
+
+    args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
+    bool fb1, fb2;
+    bool tb1 = true;
+    getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
+    assert(fb1 && fb2 && !tb1);
+
+    // test keepEndOfOptions
+
+    args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
+    getopt(args,
+        std.getopt.config.keepEndOfOptions,
+        "foo", &foo,
+        "bar", &bar);
+    assert(args == ["program.name", "nonoption", "--", "--baz"]);
+
+    // Ensure old behavior without the keepEndOfOptions
+
+    args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
+    getopt(args,
+        "foo", &foo,
+        "bar", &bar);
+    assert(args == ["program.name", "nonoption", "--baz"]);
+
+    // test function callbacks
+
+    static class MyEx : Exception
+    {
+        this() { super(""); }
+        this(string option) { this(); this.option = option; }
+        this(string option, string value) { this(option); this.value = value; }
+
+        string option;
+        string value;
+    }
+
+    static void myStaticHandler1() { throw new MyEx(); }
+    args = ["program.name", "--verbose"];
+    try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
+    catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
+
+    static void myStaticHandler2(string option) { throw new MyEx(option); }
+    args = ["program.name", "--verbose"];
+    try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
+    catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
+
+    static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
+    args = ["program.name", "--verbose", "2"];
+    try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
+    catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
+}
+
+unittest
+{
+    // From bugzilla 2142
+    bool f_linenum, f_filename;
+    string[] args = [ "", "-nl" ];
+    getopt
+        (
+            args,
+            std.getopt.config.bundling,
+            //std.getopt.config.caseSensitive,
+            "linenum|l", &f_linenum,
+            "filename|n", &f_filename
+        );
+    assert(f_linenum);
+    assert(f_filename);
+}
+
+unittest
+{
+    // From bugzilla 6887
+    string[] p;
+    string[] args = ["", "-pa"];
+    getopt(args, "p", &p);
+    assert(p.length == 1);
+    assert(p[0] == "a");
+}
+
+unittest
+{
+    // From bugzilla 6888
+    int[string] foo;
+    auto args = ["", "-t", "a=1"];
+    getopt(args, "t", &foo);
+    assert(foo == ["a":1]);
+}
+
+unittest
+{
+    // From bugzilla 9583
+    int opt;
+    auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
+    getopt(args, "opt", &opt);
+    assert(args == ["prog", "--a", "--b", "--c"]);
+}
+
+unittest
+{
+    string foo, bar;
+    auto args = ["prog", "-thello", "-dbar=baz"];
+    getopt(args, "t", &foo, "d", &bar);
+    assert(foo == "hello");
+    assert(bar == "bar=baz");
+
+    // From bugzilla 5762
+    string a;
+    args = ["prog", "-a-0x12"];
+    getopt(args, config.bundling, "a|addr", &a);
+    assert(a == "-0x12", a);
+    args = ["prog", "--addr=-0x12"];
+    getopt(args, config.bundling, "a|addr", &a);
+    assert(a == "-0x12");
+
+    // From https://d.puremagic.com/issues/show_bug.cgi?id=11764
+    args = ["main", "-test"];
+    bool opt;
+    args.getopt(config.passThrough, "opt", &opt);
+    assert(args == ["main", "-test"]);
+
+    // From https://issues.dlang.org/show_bug.cgi?id=15220
+    args = ["main", "-o=str"];
+    string o;
+    args.getopt("o", &o);
+    assert(o == "str");
+
+    args = ["main", "-o=str"];
+    o = null;
+    args.getopt(config.bundling, "o", &o);
+    assert(o == "str");
+}
+
+unittest // 5228
+{
+    import std.exception;
+    import std.conv;
+
+    auto args = ["prog", "--foo=bar"];
+    int abc;
+    assertThrown!GetOptException(getopt(args, "abc", &abc));
+
+    args = ["prog", "--abc=string"];
+    assertThrown!ConvException(getopt(args, "abc", &abc));
+}
+
+unittest // From bugzilla 7693
+{
+    import std.exception;
+
+    enum Foo {
+        bar,
+        baz
+    }
+
+    auto args = ["prog", "--foo=barZZZ"];
+    Foo foo;
+    assertThrown(getopt(args, "foo", &foo));
+    args = ["prog", "--foo=bar"];
+    assertNotThrown(getopt(args, "foo", &foo));
+    args = ["prog", "--foo", "barZZZ"];
+    assertThrown(getopt(args, "foo", &foo));
+    args = ["prog", "--foo", "baz"];
+    assertNotThrown(getopt(args, "foo", &foo));
+}
+
+unittest // same bug as 7693 only for bool
+{
+    import std.exception;
+
+    auto args = ["prog", "--foo=truefoobar"];
+    bool foo;
+    assertThrown(getopt(args, "foo", &foo));
+    args = ["prog", "--foo"];
+    getopt(args, "foo", &foo);
+    assert(foo);
+}
+
+unittest
+{
+    bool foo;
+    auto args = ["prog", "--foo"];
+    getopt(args, "foo", &foo);
+    assert(foo);
+}
+
+unittest
+{
+    bool foo;
+    bool bar;
+    auto args = ["prog", "--foo", "-b"];
+    getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
+        config.caseSensitive, "bar|b", "Some bar", &bar);
+    assert(foo);
+    assert(bar);
+}
+
+unittest
+{
+    bool foo;
+    bool bar;
+    auto args = ["prog", "-b", "--foo", "-z"];
+    getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
+        &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
+        config.passThrough);
+    assert(foo);
+    assert(bar);
+}
+
+unittest
+{
+    import std.exception;
+
+    bool foo;
+    bool bar;
+    auto args = ["prog", "-b", "-z"];
+    assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
+        "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
+        config.passThrough));
+}
+
+unittest
+{
+    import std.exception;
+
+    bool foo;
+    bool bar;
+    auto args = ["prog", "--foo", "-z"];
+    assertNotThrown(getopt(args, config.caseInsensitive, config.required,
+        "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
+        &bar, config.passThrough));
+    assert(foo);
+    assert(!bar);
+}
+
+unittest
+{
+    bool foo;
+    auto args = ["prog", "-f"];
+    auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
+    assert(foo);
+    assert(!r.helpWanted);
+}
+
+unittest // implicit help option without config.passThrough
+{
+    string[] args = ["program", "--help"];
+    auto r = getopt(args);
+    assert(r.helpWanted);
+}
+
+// Issue 13316 - std.getopt: implicit help option breaks the next argument
+unittest
+{
+    string[] args = ["program", "--help", "--", "something"];
+    getopt(args);
+    assert(args == ["program", "something"]);
+
+    args = ["program", "--help", "--"];
+    getopt(args);
+    assert(args == ["program"]);
+
+    bool b;
+    args = ["program", "--help", "nonoption", "--option"];
+    getopt(args, config.stopOnFirstNonOption, "option", &b);
+    assert(args == ["program", "nonoption", "--option"]);
+}
+
+// Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option
+unittest
+{
+    auto endOfOptionsBackup = endOfOptions;
+    scope(exit) endOfOptions = endOfOptionsBackup;
+    endOfOptions = "endofoptions";
+    string[] args = ["program", "endofoptions", "--option"];
+    bool b = false;
+    getopt(args, "option", &b);
+    assert(!b);
+    assert(args == ["program", "--option"]);
+}
+
+/** This function prints the passed $(D Option)s and text in an aligned manner on $(D stdout).
+
+The passed text will be printed first, followed by a newline, then the short
+and long version of every option will be printed. The short and long version
+will be aligned to the longest option of every $(D Option) passed. If the option
+is required, then "Required:" will be printed after the long version of the
+$(D Option). If a help message is present it will be printed next. The format is
+illustrated by this code:
+
+------------
+foreach(it; opt)
+{
+    writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
+        lengthOfLongestLongOption, it.optLong,
+        it.required ? " Required: " : " ", it.help);
+}
+------------
+
+Params:
+    text = The text to printed at the beginning of the help output.
+    opt = The $(D Option) extracted from the $(D getopt) parameter.
+*/
+void defaultGetoptPrinter(string text, Option[] opt)
+{
+    import std.stdio : stdout;
+
+    defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt);
+}
+
+/** This function writes the passed text and $(D Option) into an output range
+in the manner described in the documentation of function
+$(D defaultGetoptPrinter).
+
+Params:
+    output = The output range used to write the help information.
+    text = The text to print at the beginning of the help output.
+    opt = The $(D Option) extracted from the $(D getopt) parameter.
+*/
+void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt)
+{
+    import std.format : formattedWrite;
+    import std.algorithm : min, max;
+
+    output.formattedWrite("%s\n", text);
+
+    size_t ls, ll;
+    bool hasRequired = false;
+    foreach (it; opt)
+    {
+        ls = max(ls, it.optShort.length);
+        ll = max(ll, it.optLong.length);
+
+        hasRequired = hasRequired || it.required;
+    }
+
+    size_t argLength = ls + ll + 2;
+
+    string re = " Required: ";
+
+    foreach (it; opt)
+    {
+        output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong,
+            hasRequired ? re.length : 1, it.required ? re : " ", it.help);
+    }
+}
+
+unittest
+{
+    import std.conv;
+
+    import std.array;
+    import std.string;
+    bool a;
+    auto args = ["prog", "--foo"];
+    auto t = getopt(args, "foo|f", "Help", &a);
+    string s;
+    auto app = appender!string();
+    defaultGetoptFormatter(app, "Some Text", t.options);
+
+    string helpMsg = app.data;
+    //writeln(helpMsg);
+    assert(helpMsg.length);
+    assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
+        ~ helpMsg);
+    assert(helpMsg.indexOf("--foo") != -1);
+    assert(helpMsg.indexOf("-f") != -1);
+    assert(helpMsg.indexOf("-h") != -1);
+    assert(helpMsg.indexOf("--help") != -1);
+    assert(helpMsg.indexOf("Help") != -1);
+
+    string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
+        ~ "information.\n";
+    assert(wanted == helpMsg);
+}
+
+unittest
+{
+    import std.conv;
+    import std.string;
+    import std.array ;
+    bool a;
+    auto args = ["prog", "--foo"];
+    auto t = getopt(args, config.required, "foo|f", "Help", &a);
+    string s;
+    auto app = appender!string();
+    defaultGetoptFormatter(app, "Some Text", t.options);
+
+    string helpMsg = app.data;
+    //writeln(helpMsg);
+    assert(helpMsg.length);
+    assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
+        ~ helpMsg);
+    assert(helpMsg.indexOf("Required:") != -1);
+    assert(helpMsg.indexOf("--foo") != -1);
+    assert(helpMsg.indexOf("-f") != -1);
+    assert(helpMsg.indexOf("-h") != -1);
+    assert(helpMsg.indexOf("--help") != -1);
+    assert(helpMsg.indexOf("Help") != -1);
+
+    string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
+        ~ "          This help information.\n";
+    assert(wanted == helpMsg, helpMsg ~ wanted);
+}
+
+unittest // Issue 14724
+{
+    bool a;
+    auto args = ["prog", "--help"];
+    GetoptResult rslt;
+    try
+    {
+        rslt = getopt(args, config.required, "foo|f", "bool a", &a);
+    }
+    catch(Exception e)
+    {
+        assert(false, "If the request for help was passed required options"
+                "must not be set.");
+    }
+
+    assert(rslt.helpWanted);
+}
diff --git a/libphobos/src/std/internal/cstring.d b/libphobos/src/std/internal/cstring.d
new file mode 100644
index 00000000000..839b1da9147
--- /dev/null
+++ b/libphobos/src/std/internal/cstring.d
@@ -0,0 +1,257 @@
+/**
+Helper functions for working with $(I C strings).
+
+This module is intended to provide fast, safe and garbage free
+way to work with $(I C strings).
+
+Copyright: Denis Shelomovskij 2013-2014
+
+License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: Denis Shelomovskij
+
+Macros:
+COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, $(D core.$1.$2))
+*/
+module std.internal.cstring;
+
+///
+unittest
+{
+    version(Posix)
+    {
+        import core.stdc.stdlib: free;
+        import core.sys.posix.stdlib: setenv;
+        import std.exception: enforce;
+
+        void setEnvironment(in char[] name, in char[] value)
+        { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); }
+    }
+
+    version(Windows)
+    {
+        import core.sys.windows.windows: SetEnvironmentVariableW;
+        import std.exception: enforce;
+
+        void setEnvironment(in char[] name, in char[] value)
+        { enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); }
+    }
+}
+
+import std.traits;
+import std.range;
+
+version(unittest)
+@property inout(C)[] asArray(C)(inout C* cstr) pure nothrow @nogc @trusted
+if(isSomeChar!C)
+in { assert(cstr); }
+body
+{
+    size_t length = 0;
+    while(cstr[length])
+        ++length;
+    return cstr[0 .. length];
+}
+
+/**
+Creates temporary 0-terminated $(I C string) with copy of passed text.
+
+Params:
+    To = character type of returned C string
+    str = string or input range to be converted
+
+Returns:
+
+The value returned is implicitly convertible to $(D const To*) and
+has two properties: $(D ptr) to access $(I C string) as $(D const To*)
+and $(D buffPtr) to access it as $(D To*).
+
+The temporary $(I C string) is valid unless returned object is destroyed.
+Thus if returned object is assigned to a variable the temporary is
+valid unless the variable goes out of scope. If returned object isn't
+assigned to a variable it will be destroyed at the end of creating
+primary expression.
+
+Implementation_note:
+For small strings tempCString will use stack allocated buffer,
+for large strings (approximately 250 characters and more) it will
+allocate temporary one using C's $(D malloc).
+
+Note:
+This function is intended to be used in function call expression (like
+$(D strlen(str.tempCString()))). Incorrect usage of this function may
+lead to memory corruption.
+See $(RED WARNING) in $(B Examples) section.
+*/
+
+auto tempCString(To = char, From)(From str)
+    if (isSomeChar!To && (isInputRange!From || isSomeString!From) &&
+        isSomeChar!(ElementEncodingType!From))
+{
+
+    alias CF = Unqual!(ElementEncodingType!From);
+
+    enum To* useStack = () @trusted { return cast(To*)size_t.max; }();
+
+    static struct Res
+    {
+    @trusted:
+    nothrow @nogc:
+
+        @disable this();
+        @disable this(this);
+        alias ptr this;
+
+        @property inout(To)* buffPtr() inout pure
+        {
+            return _ptr == useStack ? _buff.ptr : _ptr;
+        }
+
+        @property const(To)* ptr() const pure
+        {
+            return buffPtr;
+        }
+
+        ~this()
+        {
+            if (_ptr != useStack)
+            {
+                import core.stdc.stdlib : free;
+                free(_ptr);
+            }
+        }
+
+    private:
+        To* _ptr;
+        version (unittest)
+        {
+            enum buffLength = 16 / To.sizeof;   // smaller size to trigger reallocations
+        }
+        else
+        {
+            enum buffLength = 256 / To.sizeof;   // production size
+        }
+
+        To[buffLength] _buff;  // the 'small string optimization'
+
+        static Res trustedVoidInit() { Res res = void; return res; }
+    }
+
+    Res res = Res.trustedVoidInit();     // expensive to fill _buff[]
+
+    // Note: res._ptr can't point to res._buff as structs are movable.
+
+    To[] p;
+    bool p_is_onstack = true;
+    size_t i;
+
+    static To[] trustedRealloc(To[] buf, size_t i, To[] res, size_t strLength, bool res_is_onstack)
+        @trusted @nogc nothrow
+    {
+        pragma(inline, false);  // because it's rarely called
+
+        import core.exception   : onOutOfMemoryError;
+        import core.stdc.string : memcpy;
+        import core.stdc.stdlib : malloc, realloc;
+
+        if (res_is_onstack)
+        {
+            size_t newlen = res.length * 3 / 2;
+            if (newlen <= strLength)
+                newlen = strLength + 1; // +1 for terminating 0
+            auto ptr = cast(To*)malloc(newlen * To.sizeof);
+            if (!ptr)
+                onOutOfMemoryError();
+            memcpy(ptr, res.ptr, i * To.sizeof);
+            return ptr[0 .. newlen];
+        }
+        else
+        {
+            if (buf.length >= size_t.max / (2 * To.sizeof))
+                onOutOfMemoryError();
+            const newlen = buf.length * 3 / 2;
+            auto ptr = cast(To*)realloc(buf.ptr, newlen * To.sizeof);
+            if (!ptr)
+                onOutOfMemoryError();
+            return ptr[0 .. newlen];
+        }
+    }
+
+    size_t strLength;
+    static if (hasLength!From)
+    {
+        strLength = str.length;
+    }
+    import std.utf : byUTF;
+    static if (isSomeString!From)
+    {
+        auto r = cast(const(CF)[])str;  // because inout(CF) causes problems with byUTF
+        if (r is null)  // Bugzilla 14980
+        {
+            res._ptr = null;
+            return res;
+        }
+    }
+    else
+        alias r = str;
+    To[] q = res._buff;
+    foreach (const c; byUTF!(Unqual!To)(r))
+    {
+        if (i + 1 == q.length)
+        {
+            p = trustedRealloc(p, i, res._buff, strLength, p_is_onstack);
+            p_is_onstack = false;
+            q = p;
+        }
+        q[i++] = c;
+    }
+    q[i] = 0;
+    res._ptr = p_is_onstack ? useStack : &p[0];
+    return res;
+}
+
+///
+nothrow @nogc unittest
+{
+    import core.stdc.string;
+
+    string str = "abc";
+
+    // Intended usage
+    assert(strlen(str.tempCString()) == 3);
+
+    // Correct usage
+    auto tmp = str.tempCString();
+    assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr`
+
+    // $(RED WARNING): $(RED Incorrect usage)
+    auto pInvalid1 = str.tempCString().ptr;
+    const char* pInvalid2 = str.tempCString();
+    // Both pointers refer to invalid memory here as
+    // returned values aren't assigned to a variable and
+    // both primary expressions are ended.
+}
+
+@safe nothrow @nogc unittest
+{
+    assert("abc".tempCString().asArray == "abc");
+    assert("abc"d.tempCString().ptr.asArray == "abc");
+    assert("abc".tempCString!wchar().buffPtr.asArray == "abc"w);
+
+    import std.utf : byChar, byWchar;
+    char[300] abc = 'a';
+    assert(tempCString(abc[].byChar).buffPtr.asArray == abc);
+    assert(tempCString(abc[].byWchar).buffPtr.asArray == abc);
+}
+
+// Bugzilla 14980
+nothrow @nogc unittest
+{
+    const(char[]) str = null;
+    auto res = tempCString(str);
+    const char* ptr = res;
+    assert(ptr is null);
+}
+
+version(Windows)
+    alias tempCStringW = tempCString!(wchar, const(char)[]);
diff --git a/libphobos/src/std/internal/digest/sha_SSSE3.d b/libphobos/src/std/internal/digest/sha_SSSE3.d
new file mode 100644
index 00000000000..168061084a3
--- /dev/null
+++ b/libphobos/src/std/internal/digest/sha_SSSE3.d
@@ -0,0 +1,711 @@
+// Written in the D programming language.
+
+/**
+ * Computes SHA1 digests of arbitrary data, using an optimized algorithm with SSSE3 instructions.
+ *
+ * Authors:
+ * The general idea is described by Dean Gaudet.
+ * Another important observation is published by Max Locktyukhin.
+ * (Both implementations are public domain.)
+ * Translation to X86 and D by Kai Nacke <kai@redstar.de>
+ *
+ * References:
+ *      $(LINK2 http://arctic.org/~dean/crypto/sha1.html)
+ *      $(LINK2 http://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1/, Fast implementation of SHA1)
+ */
+module std.internal.digest.sha_SSSE3;
+
+version(D_PIC)
+{
+    // Do not use (Bug9378).
+}
+else version(D_InlineAsm_X86)
+{
+    private version = USE_SSSE3;
+    private version = _32Bit;
+}
+else version(D_InlineAsm_X86_64)
+{
+    private version = USE_SSSE3;
+    private version = _64Bit;
+}
+
+/*
+ * The idea is quite simple. The SHA-1 specification defines the following message schedule:
+ *     W[i] = (W[i-3] ^ W[i-8]  ^ W[i-14] ^ W[i-16]) rol 1
+ *
+ * To employ SSE, simply write down the formula four times:
+ *     W[i  ] = (W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]) rol 1
+ *     W[i+1] = (W[i-2] ^ W[i-7] ^ W[i-13] ^ W[i-15]) rol 1
+ *     W[i+2] = (W[i-1] ^ W[i-6] ^ W[i-12] ^ W[i-14]) rol 1
+ *     W[i+3] = (W[i  ] ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1
+ * The last formula requires value W[i] computed with the first formula.
+ * Because the xor operation and the rotate operation are commutative, we can replace the
+ * last formula with
+ *     W[i+3] = (     0 ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1
+ * and then calculate
+ *     W[i+3] ^= W[i] rol 1
+ * which unfortunately requires many additional operations. This approach was described by
+ * Dean Gaudet.
+ *
+ * Max Locktyukhin observed that
+ *     W[i] = W[i-A] ^ W[i-B]
+ * is equivalent to
+ *     W[i] = W[i-2*A] ^ W[i-2*B]
+ * (if the indices are still in valid ranges). Using this observation, the formula is
+ * translated to
+ *     W[i] = (W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32]) rol 2
+ * Again, to employ SSE the formula is used four times.
+ *
+ * Later on, the expression W[i] + K(i) is used. (K(i) is the constant used in round i.)
+ * Once the 4 W[i] are calculated, we can also add the four K(i) values with one SSE instruction.
+ *
+ * The 32bit and 64bit implementations are almost identical. The main difference is that there
+ * are only 8 XMM registers in 32bit mode. Therefore, space on the stack is needed to save
+ * computed values.
+ */
+
+version(USE_SSSE3)
+{
+    /*
+     * The general idea is to use the XMM registers as a sliding window over
+     * message schedule. XMM0 to XMM7 are used to store the last 64 byte of
+     * the message schedule. In 64 bit mode this is fine because of the number of
+     * registers. The main difference of the 32 bit code is that a part of the
+     * calculated message schedule is saved on the stack because 2 temporary
+     * registers are needed.
+     */
+
+    /* Number of message words we are precalculating. */
+    private immutable int PRECALC_AHEAD = 16;
+
+    /* T1 and T2 are used for intermediate results of computations. */
+    private immutable string T1 = "EAX";
+    private immutable string T2 = "EBX";
+
+    /* The registers used for the SHA-1 variables. */
+    private immutable string A = "ECX";
+    private immutable string B = "ESI";
+    private immutable string C = "EDI";
+    private immutable string D = "EBP";
+    private immutable string E = "EDX";
+
+    /* */
+    version(_32Bit)
+    {
+        private immutable string SP = "ESP";
+        private immutable string BUFFER_PTR = "EAX";
+        private immutable string STATE_PTR = "EBX";
+
+        // Control byte for shuffle instruction (only used in round 0-15)
+        private immutable string X_SHUFFLECTL = "XMM6";
+
+        // Round constant (only used in round 0-15)
+        private immutable string X_CONSTANT = "XMM7";
+    }
+    version(_64Bit)
+    {
+        private immutable string SP = "RSP";
+        private immutable string BUFFER_PTR = "R9";
+        private immutable string STATE_PTR = "R8";
+
+        // Registers for temporary results (XMM10 and XMM11 are also used temporary)
+        private immutable string W_TMP = "XMM8";
+        private immutable string W_TMP2 = "XMM9";
+
+        // Control byte for shuffle instruction (only used in round 0-15)
+        private immutable string X_SHUFFLECTL = "XMM12";
+
+        // Round constant
+        private immutable string X_CONSTANT = "XMM13";
+    }
+
+    /* The control words for the byte shuffle instruction. */
+    align(16) private immutable uint[4] bswap_shufb_ctl =
+    [
+        0x0001_0203, 0x0405_0607, 0x0809_0a0b, 0x0c0d_0e0f
+    ];
+
+    /* The round constants. */
+    align(16) private immutable uint[16] constants =
+    [
+        // Constants for round 0-19
+        0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
+        // Constants for round 20-39
+        0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
+        // Constants for round 40-59
+        0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
+        // Constants for round 60-79
+        0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6
+    ];
+
+    /** Simple version to produce numbers < 100 as string. */
+    private nothrow pure string to_string(uint i)
+    {
+        if (i < 10)
+            return "0123456789"[i .. i + 1];
+
+        assert(i < 100);
+        char[2] s;
+        s[0] = cast(char)(i / 10 + '0');
+        s[1] = cast(char)(i % 10 + '0');
+        return s.idup;
+    }
+
+    /** Returns the reference to constant used in round i. */
+    private nothrow pure string constant(uint i)
+    {
+        return "[constants + 16*"~to_string(i/20)~"]";
+    }
+
+    /** Returns the XMM register number used in round i */
+    private nothrow pure uint regno(uint i)
+    {
+        return (i/4)&7;
+    }
+
+    /** Returns reference to storage of vector W[i..i+4]. */
+    private nothrow pure string WiV(uint i)
+    {
+        return "["~SP~" + WI_PTR + "~to_string((i/4)&7)~"*16]";
+    }
+
+    /** Returns reference to storage of vector (W + K)[i..i+4]. */
+    private nothrow pure string WiKiV(uint i)
+    {
+        return "["~SP~" + WI_PLUS_KI_PTR + "~to_string((i/4)&3)~"*16]";
+    }
+
+    /** Returns reference to storage of value W[i] + K[i]. */
+    private nothrow pure string WiKi(uint i)
+    {
+        return "["~SP~" + WI_PLUS_KI_PTR + 4*"~to_string(i&15)~"]";
+    }
+
+    /**
+     * Chooses the instruction sequence based on the 32bit or 64bit model.
+     */
+    private nothrow pure string[] swt3264(string[] insn32, string[] insn64)
+    {
+        version(_32Bit)
+        {
+            return insn32;
+        }
+        version(_64Bit)
+        {
+            return insn64;
+        }
+    }
+
+    /**
+     * Flattens the instruction sequence and wraps it in an asm block.
+     */
+    private nothrow pure string wrap(string[] insn)
+    {
+        string s = "asm pure nothrow @nogc {";
+        foreach (t; insn) s ~= (t ~ "; \n");
+        s ~= "}";
+        return s;
+        // Is not CTFE:
+        // return "asm pure nothrow @nogc { " ~ join(insn, "; \n") ~ "}";
+    }
+
+    /**
+     * Weaves the 2 instruction sequences together.
+     */
+    private nothrow pure string[] weave(string[] seq1, string[] seq2, uint dist = 1)
+    {
+        string[] res = [];
+        auto i1 = 0, i2 = 0;
+        while (i1 < seq1.length || i2 < seq2.length)
+        {
+            if (i2 < seq2.length)
+            {
+                res ~= seq2[i2..i2+1];
+                i2 += 1;
+            }
+            if (i1 < seq1.length)
+            {
+                import std.algorithm.comparison : min;
+
+                res ~= seq1[i1 .. min(i1+dist, $)];
+                i1 += dist;
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Generates instructions to load state from memory into registers.
+     */
+    private nothrow pure string[] loadstate(string base, string a, string b, string c, string d, string e)
+    {
+        return ["mov "~a~",["~base~" + 0*4]",
+                "mov "~b~",["~base~" + 1*4]",
+                "mov "~c~",["~base~" + 2*4]",
+                "mov "~d~",["~base~" + 3*4]",
+                "mov "~e~",["~base~" + 4*4]" ];
+    }
+
+    /**
+     * Generates instructions to update state from registers, saving result in memory.
+     */
+    private nothrow pure string[] savestate(string base, string a, string b, string c, string d, string e)
+    {
+        return ["add ["~base~" + 0*4],"~a,
+                "add ["~base~" + 1*4],"~b,
+                "add ["~base~" + 2*4],"~c,
+                "add ["~base~" + 3*4],"~d,
+                "add ["~base~" + 4*4],"~e ];
+    }
+
+    /** Calculates Ch(x, y, z) = z ^ (x & (y ^ z)) */
+    private nothrow pure string[] Ch(string x, string y, string z)
+    {
+        return ["mov "~T1~","~y,
+                "xor "~T1~","~z,
+                "and "~T1~","~x,
+                "xor "~T1~","~z ];
+    }
+
+    /** Calculates Parity(x, y, z) = x ^ y ^ z */
+    private nothrow pure string[] Parity(string x, string y, string z)
+    {
+        return ["mov "~T1~","~z,
+                "xor "~T1~","~y,
+                "xor "~T1~","~x ];
+    }
+
+    /** Calculates Maj(x, y, z) = (x & y) | (z & (x ^ y)) */
+    private nothrow pure string[] Maj(string x, string y, string z)
+    {
+        return ["mov "~T1~","~y,
+                "mov "~T2~","~x,
+                "or  "~T1~","~x,
+                "and "~T2~","~y,
+                "and "~T1~","~z,
+                "or  "~T1~","~T2 ];
+    }
+
+    /** Returns function for round i. Function returns result in T1 and may destroy T2. */
+    private nothrow pure string[] F(int i, string b, string c, string d)
+    {
+        string[] insn;
+        if (i >=  0 && i <= 19) insn = Ch(b, c, d);
+        else if (i >= 20 && i <= 39) insn = Parity(b, c, d);
+        else if (i >= 40 && i <= 59) insn = Maj(b, c, d);
+        else if (i >= 60 && i <= 79) insn = Parity(b, c, d);
+        else assert(false, "Coding error");
+        return insn;
+    }
+
+    /** Returns instruction used to setup a round. */
+    private nothrow pure string[] xsetup(int i)
+    {
+        if (i == 0)
+        {
+            return swt3264(["movdqa "~X_SHUFFLECTL~",[bswap_shufb_ctl]",
+                             "movdqa "~X_CONSTANT~","~constant(i)],
+                            ["movdqa "~X_SHUFFLECTL~",[bswap_shufb_ctl]",
+                             "movdqa "~X_CONSTANT~","~constant(i)]);
+        }
+        version(_64Bit)
+        {
+            if (i%20 == 0)
+            {
+                return ["movdqa "~X_CONSTANT~","~constant(i)];
+            }
+        }
+        return [];
+    }
+
+    /**
+     * Loads the message words and performs the little to big endian conversion.
+     * Requires that the shuffle control word and the round constant is loaded
+     * into required XMM register. The BUFFER_PTR register must point to the
+     * buffer.
+     */
+    private nothrow pure string[] precalc_00_15(int i)
+    {
+        int regno = regno(i);
+
+        string W = "XMM" ~ to_string(regno);
+        version(_32Bit)
+        {
+            string W_TMP = "XMM" ~ to_string(regno+2);
+        }
+        version(_64Bit)
+        {
+            string W_TMP = "XMM" ~ to_string(regno+8);
+        }
+
+        if ((i & 3) == 0)
+        {
+            return ["movdqu "~W~",["~BUFFER_PTR~" + "~to_string(regno)~"*16]"];
+        }
+        else if ((i & 3) == 1)
+        {
+            return ["pshufb "~W~","~X_SHUFFLECTL] ~
+                    swt3264(["movdqa "~WiV(i)~","~W], []);
+        }
+        else if ((i & 3) == 2)
+        {
+            return ["movdqa "~W_TMP~","~W,
+                    "paddd "~W_TMP~","~X_CONSTANT,
+                   ];
+        }
+        else
+        {
+            return ["movdqa "~WiKiV(i)~","~W_TMP,
+                   ];
+        }
+    }
+
+    /**
+     * Done on 4 consequtive W[i] values in a single XMM register
+     *  W[i  ] = (W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]) rol 1
+     *  W[i+1] = (W[i-2] ^ W[i-7] ^ W[i-13] ^ W[i-15]) rol 1
+     *  W[i+2] = (W[i-1] ^ W[i-6] ^ W[i-12] ^ W[i-14]) rol 1
+     *  W[i+3] = (   0   ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1
+     *
+     * This additional calculation unfortunately requires many additional operations
+     *  W[i+3] ^= W[i] rol 1
+     *
+     * Once we have 4 W[i] values in XMM we can also add four K values with one instruction
+     *   W[i:i+3] += {K,K,K,K}
+     */
+    private nothrow pure string[] precalc_16_31(int i)
+    {
+        int regno = regno(i);
+
+        string W = "XMM" ~ to_string(regno);
+        string W_minus_4 = "XMM" ~ to_string((regno-1)&7);
+        string W_minus_8 = "XMM" ~ to_string((regno-2)&7);
+        string W_minus_12 = "XMM" ~ to_string((regno-3)&7);
+        string W_minus_16 = "XMM" ~ to_string((regno-4)&7);
+        version(_32Bit)
+        {
+            string W_TMP = "XMM" ~ to_string((regno+1)&7);
+            string W_TMP2 = "XMM" ~ to_string((regno+2)&7);
+        }
+
+        if ((i & 3) == 0)
+        {
+            return ["movdqa "~W~","~W_minus_12,
+                    "palignr "~W~","~W_minus_16~",8",   // W[i] = W[i-14]
+                    "pxor "~W~","~W_minus_16,           // W[i] ^= W[i-16]
+                    "pxor "~W~","~W_minus_8,            // W[i] ^= W[i-8]
+                    "movdqa "~W_TMP~","~W_minus_4,
+            ];
+        }
+        else if ((i & 3) == 1)
+        {
+            return ["psrldq "~W_TMP~",4",               // W[i-3]
+                    "pxor "~W~","~W_TMP,                // W[i] ^= W[i-3]
+                    "movdqa "~W_TMP~","~W,
+                    "psrld "~W~",31",
+                    "pslld "~W_TMP~",1",
+            ];
+        }
+        else if ((i & 3) == 2)
+        {
+            return ["por "~W~","~W_TMP,
+                    "movdqa "~W_TMP~","~W,
+                    "pslldq "~W_TMP~",12",
+                    "movdqa "~W_TMP2~","~W_TMP,
+                    "pslld "~W_TMP~",1",
+            ];
+        }
+        else
+        {
+            return ["psrld "~W_TMP2~",31",
+                    "por "~W_TMP~","~W_TMP2,
+                    "pxor "~W~","~W_TMP,
+                    "movdqa "~W_TMP~","~W ] ~
+                   swt3264(["movdqa "~WiV(i)~","~W,
+                            "paddd "~W_TMP~","~constant(i) ],
+                           ["paddd "~W_TMP~","~X_CONSTANT ]) ~
+                   ["movdqa "~WiKiV(i)~","~W_TMP];
+        }
+    }
+
+    /** Performs the main calculation as decribed above. */
+    private nothrow pure string[] precalc_32_79(int i)
+    {
+        int regno = regno(i);
+
+        string W = "XMM" ~ to_string(regno);
+        string W_minus_4 = "XMM" ~ to_string((regno-1)&7);
+        string W_minus_8 = "XMM" ~ to_string((regno-2)&7);
+        string W_minus_16 = "XMM" ~ to_string((regno-4)&7);
+        version(_32Bit)
+        {
+            string W_minus_28 = "[ESP + WI_PTR + "~ to_string((regno-7)&7)~"*16]";
+            string W_minus_32 = "[ESP + WI_PTR + "~ to_string((regno-8)&7)~"*16]";
+            string W_TMP = "XMM" ~ to_string((regno+1)&7);
+            string W_TMP2 = "XMM" ~ to_string((regno+2)&7);
+        }
+        version(_64Bit)
+        {
+            string W_minus_28 = "XMM" ~ to_string((regno-7)&7);
+            string W_minus_32 = "XMM" ~ to_string((regno-8)&7);
+        }
+
+        if ((i & 3) == 0)
+        {
+            return swt3264(["movdqa "~W~","~W_minus_32], []) ~
+                   ["movdqa "~W_TMP~","~W_minus_4,
+                    "pxor "~W~","~W_minus_28,         // W is W_minus_32 before xor
+                    "palignr "~W_TMP~","~W_minus_8~",8",
+            ];
+        }
+        else if ((i & 3) == 1)
+        {
+            return ["pxor "~W~","~W_minus_16,
+                    "pxor "~W~","~W_TMP,
+                    "movdqa "~W_TMP~","~W,
+            ];
+        }
+        else if ((i & 3) == 2)
+        {
+            return ["psrld "~W~",30",
+                    "pslld "~W_TMP~",2",
+                    "por "~W_TMP~","~W,
+            ];
+        }
+        else
+        {
+            if (i < 76)
+                return ["movdqa "~W~","~W_TMP] ~
+                       swt3264(["movdqa "~WiV(i)~","~W,
+                                "paddd "~W_TMP~","~constant(i)],
+                               ["paddd "~W_TMP~","~X_CONSTANT]) ~
+                       ["movdqa "~WiKiV(i)~","~W_TMP];
+            else
+                return swt3264(["paddd "~W_TMP~","~constant(i)],
+                               ["paddd "~W_TMP~","~X_CONSTANT]) ~
+                       ["movdqa "~WiKiV(i)~","~W_TMP];
+        }
+    }
+
+    /** Choose right precalc method. */
+    private nothrow pure string[] precalc(int i)
+    {
+        if (i >= 0 && i < 16) return precalc_00_15(i);
+        if (i >= 16 && i < 32) return precalc_16_31(i);
+        if (i >= 32 && i < 80) return precalc_32_79(i);
+        return [];
+    }
+
+    /**
+     * Return code for round i and i+1.
+     * Performs the following rotation:
+     * in=>out: A=>D, B=>E, C=>A, D=>B, E=>C
+     */
+    private nothrow pure string[] round(int i, string a, string b, string c, string d, string e)
+    {
+        return xsetup(PRECALC_AHEAD + i) ~
+               weave(F(i, b, c, d) ~ // Returns result in T1; may destroy T2
+               ["add "~e~","~WiKi(i),
+                "ror "~b~",2",
+                "mov "~T2~","~a,
+                "add "~d~","~WiKi(i+1),
+                "rol "~T2~",5",
+                "add "~e~","~T1 ],
+                precalc(PRECALC_AHEAD + i), 2) ~
+               weave(
+               ["add "~T2~","~e,  // T2 = (A <<< 5) + F(B, C, D) + Wi + Ki + E
+                "mov "~e~","~T2,
+                "rol "~T2~",5",
+                "add "~d~","~T2 ] ~
+               F(i+1, a, b, c) ~ // Returns result in T1; may destroy T2
+               ["add "~d~","~T1,
+                "ror "~a~",2"],
+                precalc(PRECALC_AHEAD + i+1), 2);
+    }
+
+    // Offset into stack (see below)
+    version(_32Bit)
+    {
+        private enum { STATE_OFS = 4, WI_PLUS_KI_PTR = 8, WI_PTR = 72 };
+    }
+    version(_64Bit)
+    {
+        private enum { WI_PLUS_KI_PTR = 0 };
+    }
+
+    /** The prologue sequence. */
+    private nothrow pure string[] prologue()
+    {
+        version(_32Bit)
+        {
+            /*
+             * Parameters:
+             *   EAX contains pointer to input buffer
+             *
+             * Stack layout as follows:
+             * +----------------+
+             * | ptr to state   |
+             * +----------------+
+             * | return address |
+             * +----------------+
+             * | EBP            |
+             * +----------------+
+             * | ESI            |
+             * +----------------+
+             * | EDI            |
+             * +----------------+
+             * | EBX            |
+             * +----------------+
+             * | Space for      |
+             * | Wi             | <- ESP+72
+             * +----------------+
+             * | Space for      |
+             * | Wi+Ki          | <- ESP+8
+             * +----------------+ <- 16byte aligned
+             * | ptr to state   | <- ESP+4
+             * +----------------+
+             * | old ESP        | <- ESP
+             * +----------------+
+             */
+            static assert(BUFFER_PTR == "EAX");
+            static assert(STATE_PTR == "EBX");
+            return [// Save registers according to calling convention
+                    "push EBP",
+                    "push ESI",
+                    "push EDI",
+                    "push EBX",
+                    // Load parameters
+                    "mov EBX, [ESP + 5*4]", //pointer to state
+                    // Align stack
+                    "mov EBP, ESP",
+                    "sub ESP, 4*16 + 8*16",
+                    "and ESP, 0xffff_fff0",
+                    "push EBX",
+                    "push EBP",
+            ];
+        }
+        version(_64Bit)
+        {
+            /*
+             * Parameters:
+             *   RSI contains pointer to state
+             *   RDI contains pointer to input buffer
+             *
+             * Stack layout as follows:
+             * +----------------+
+             * | return address |
+             * +----------------+
+             * | RBP            |
+             * +----------------+
+             * | RBX            |
+             * +----------------+
+             * | Unused         |
+             * +----------------+
+             * | Space for      |
+             * | Wi+Ki          | <- RSP
+             * +----------------+ <- 16byte aligned
+             */
+            return [// Save registers according to calling convention
+                    "push RBP",
+                    "push RBX",
+                    // Save parameters
+                    "mov "~STATE_PTR~", RSI", //pointer to state
+                    "mov "~BUFFER_PTR~", RDI", //pointer to buffer
+                    // Align stack
+                    "sub RSP, 4*16+8",
+            ];
+        }
+    }
+
+    /**
+      * The epilogue sequence. Just pop the saved registers from stack and return to caller.
+      */
+    private nothrow pure string[] epilogue()
+    {
+        version(_32Bit)
+        {
+            return ["pop ESP",
+                    "pop EBX",
+                    "pop EDI",
+                    "pop ESI",
+                    "pop EBP",
+                    "ret 4",
+                   ];
+        }
+        version(_64Bit)
+        {
+            return ["add RSP,4*16+8",
+                    "pop RBX",
+                    "pop RBP",
+                    "ret 0",
+                   ];
+        }
+    }
+
+    /**
+     *
+     */
+    public void transformSSSE3(uint[5]* state, const(ubyte[64])* buffer) pure nothrow @nogc
+    {
+        mixin(wrap(["naked;"] ~ prologue()));
+        // Precalc first 4*16=64 bytes
+        mixin(wrap(xsetup(0)));
+        mixin(wrap(weave(precalc(0)~precalc(1)~precalc(2)~precalc(3),
+                         precalc(4)~precalc(5)~precalc(6)~precalc(7))));
+        mixin(wrap(weave(loadstate(STATE_PTR, A, B, C, D, E),
+                   weave(precalc(8)~precalc(9)~precalc(10)~precalc(11),
+                         precalc(12)~precalc(13)~precalc(14)~precalc(15)))));
+        // Round 1
+        mixin(wrap(round( 0, A, B, C, D, E)));
+        mixin(wrap(round( 2, D, E, A, B, C)));
+        mixin(wrap(round( 4, B, C, D, E, A)));
+        mixin(wrap(round( 6, E, A, B, C, D)));
+        mixin(wrap(round( 8, C, D, E, A, B)));
+        mixin(wrap(round(10, A, B, C, D, E)));
+        mixin(wrap(round(12, D, E, A, B, C)));
+        mixin(wrap(round(14, B, C, D, E, A)));
+        mixin(wrap(round(16, E, A, B, C, D)));
+        mixin(wrap(round(18, C, D, E, A, B)));
+        // Round 2
+        mixin(wrap(round(20, A, B, C, D, E)));
+        mixin(wrap(round(22, D, E, A, B, C)));
+        mixin(wrap(round(24, B, C, D, E, A)));
+        mixin(wrap(round(26, E, A, B, C, D)));
+        mixin(wrap(round(28, C, D, E, A, B)));
+        mixin(wrap(round(30, A, B, C, D, E)));
+        mixin(wrap(round(32, D, E, A, B, C)));
+        mixin(wrap(round(34, B, C, D, E, A)));
+        mixin(wrap(round(36, E, A, B, C, D)));
+        mixin(wrap(round(38, C, D, E, A, B)));
+        // Round 3
+        mixin(wrap(round(40, A, B, C, D, E)));
+        mixin(wrap(round(42, D, E, A, B, C)));
+        mixin(wrap(round(44, B, C, D, E, A)));
+        mixin(wrap(round(46, E, A, B, C, D)));
+        mixin(wrap(round(48, C, D, E, A, B)));
+        mixin(wrap(round(50, A, B, C, D, E)));
+        mixin(wrap(round(52, D, E, A, B, C)));
+        mixin(wrap(round(54, B, C, D, E, A)));
+        mixin(wrap(round(56, E, A, B, C, D)));
+        mixin(wrap(round(58, C, D, E, A, B)));
+        // Round 4
+        mixin(wrap(round(60, A, B, C, D, E)));
+        mixin(wrap(round(62, D, E, A, B, C)));
+        mixin(wrap(round(64, B, C, D, E, A)));
+        mixin(wrap(round(66, E, A, B, C, D)));
+        mixin(wrap(round(68, C, D, E, A, B)));
+        mixin(wrap(round(70, A, B, C, D, E)));
+        mixin(wrap(round(72, D, E, A, B, C)));
+        mixin(wrap(round(74, B, C, D, E, A)));
+        mixin(wrap(round(76, E, A, B, C, D)));
+        mixin(wrap(round(78, C, D, E, A, B)));
+        version(_32Bit)
+        {
+            // Load pointer to state
+            mixin(wrap(["mov "~STATE_PTR~",[ESP + STATE_OFS]"]));
+        }
+        mixin(wrap(savestate(STATE_PTR, A, B, C, D, E)));
+        mixin(wrap(epilogue()));
+    }
+}
diff --git a/libphobos/src/std/internal/math/biguintcore.d b/libphobos/src/std/internal/math/biguintcore.d
new file mode 100644
index 00000000000..10f63f56ea5
--- /dev/null
+++ b/libphobos/src/std/internal/math/biguintcore.d
@@ -0,0 +1,2431 @@
+/** Fundamental operations for arbitrary-precision arithmetic
+ *
+ * These functions are for internal use only.
+ */
+/*          Copyright Don Clugston 2008 - 2010.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+/* References:
+   "Modern Computer Arithmetic" (MCA) is the primary reference for all
+    algorithms used in this library.
+  - R.P. Brent and P. Zimmermann, "Modern Computer Arithmetic",
+    Version 0.5.9, (Oct 2010).
+  - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022,
+    Max-Planck Institute fuer Informatik, (Oct 1998).
+  - G. Hanrot, M. Quercia, and P. Zimmermann, "The Middle Product Algorithm, I.",
+    INRIA 4664, (Dec 2002).
+  - M. Bodrato and A. Zanoni, "What about Toom-Cook Matrices Optimality?",
+    http://bodrato.it/papers (2006).
+  - A. Fog, "Optimizing subroutines in assembly language",
+    www.agner.org/optimize (2008).
+  - A. Fog, "The microarchitecture of Intel and AMD CPU's",
+    www.agner.org/optimize (2008).
+  - A. Fog, "Instruction tables: Lists of instruction latencies, throughputs
+    and micro-operation breakdowns for Intel and AMD CPU's.", www.agner.org/optimize (2008).
+
+Idioms:
+  Many functions in this module use
+  'func(Tulong)(Tulong x) if (is(Tulong == ulong))' rather than 'func(ulong x)'
+  in order to disable implicit conversion.
+
+*/
+module std.internal.math.biguintcore;
+
+version(D_InlineAsm_X86)
+{
+    import std.internal.math.biguintx86;
+}
+else
+{
+    import std.internal.math.biguintnoasm;
+}
+
+alias multibyteAdd = multibyteAddSub!('+');
+alias multibyteSub = multibyteAddSub!('-');
+
+
+private import core.cpuid;
+private import std.traits : Unqual;
+public import std.ascii : LetterCase;
+
+shared static this()
+{
+    CACHELIMIT = core.cpuid.datacache[0].size*1024/2;
+}
+
+private:
+// Limits for when to switch between algorithms.
+immutable size_t CACHELIMIT;   // Half the size of the data cache.
+enum size_t FASTDIVLIMIT = 100; // crossover to recursive division
+
+
+// These constants are used by shift operations
+static if (BigDigit.sizeof == int.sizeof)
+{
+    enum { LG2BIGDIGITBITS = 5, BIGDIGITSHIFTMASK = 31 };
+    alias BIGHALFDIGIT = ushort;
+}
+else static if (BigDigit.sizeof == long.sizeof)
+{
+    alias BIGHALFDIGIT = uint;
+    enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 };
+}
+else static assert(0, "Unsupported BigDigit size");
+
+private import std.exception : assumeUnique;
+private import std.traits:isIntegral;
+enum BigDigitBits = BigDigit.sizeof*8;
+template maxBigDigits(T) if (isIntegral!T)
+{
+    enum maxBigDigits = (T.sizeof+BigDigit.sizeof-1)/BigDigit.sizeof;
+}
+
+static immutable BigDigit[] ZERO = [0];
+static immutable BigDigit[] ONE = [1];
+static immutable BigDigit[] TWO = [2];
+static immutable BigDigit[] TEN = [10];
+
+
+public:
+
+/// BigUint performs memory management and wraps the low-level calls.
+struct BigUint
+{
+private:
+    pure invariant()
+    {
+        assert( data.length >= 1 && (data.length == 1 || data[$-1] != 0 ));
+    }
+
+    immutable(BigDigit) [] data = ZERO;
+
+    this(immutable(BigDigit) [] x) pure nothrow @nogc @safe
+    {
+       data = x;
+    }
+  package(std)  // used from: std.bigint
+    this(T)(T x) pure nothrow @safe if (isIntegral!T)
+    {
+        opAssign(x);
+    }
+
+    enum trustedAssumeUnique = function(BigDigit[] input) pure @trusted @nogc {
+        return assumeUnique(input);
+    };
+public:
+    // Length in uints
+    @property size_t uintLength() pure nothrow const @safe @nogc
+    {
+        static if (BigDigit.sizeof == uint.sizeof)
+        {
+            return data.length;
+        }
+        else static if (BigDigit.sizeof == ulong.sizeof)
+        {
+            return data.length * 2 -
+            ((data[$-1] & 0xFFFF_FFFF_0000_0000L) ? 1 : 0);
+        }
+    }
+    @property size_t ulongLength() pure nothrow const @safe @nogc
+    {
+        static if (BigDigit.sizeof == uint.sizeof)
+        {
+            return (data.length + 1) >> 1;
+        }
+        else static if (BigDigit.sizeof == ulong.sizeof)
+        {
+            return data.length;
+        }
+    }
+
+    // The value at (cast(ulong[])data)[n]
+    ulong peekUlong(int n) pure nothrow const @safe @nogc
+    {
+        static if (BigDigit.sizeof == int.sizeof)
+        {
+            if (data.length == n*2 + 1) return data[n*2];
+            return data[n*2] + ((cast(ulong)data[n*2 + 1]) << 32 );
+        }
+        else static if (BigDigit.sizeof == long.sizeof)
+        {
+            return data[n];
+        }
+    }
+    uint peekUint(int n) pure nothrow const @safe @nogc
+    {
+        static if (BigDigit.sizeof == int.sizeof)
+        {
+            return data[n];
+        }
+        else
+        {
+            ulong x = data[n >> 1];
+            return (n & 1) ? cast(uint)(x >> 32) : cast(uint)x;
+        }
+    }
+public:
+    ///
+    void opAssign(Tulong)(Tulong u) pure nothrow @safe if (is (Tulong == ulong))
+    {
+        if (u == 0) data = ZERO;
+        else if (u == 1) data = ONE;
+        else if (u == 2) data = TWO;
+        else if (u == 10) data = TEN;
+        else
+        {
+            static if (BigDigit.sizeof == int.sizeof)
+            {
+                uint ulo = cast(uint)(u & 0xFFFF_FFFF);
+                uint uhi = cast(uint)(u >> 32);
+                if (uhi == 0)
+                {
+                    data = [ulo];
+                }
+                else
+                {
+                    data = [ulo, uhi];
+                }
+            }
+            else static if (BigDigit.sizeof == long.sizeof)
+            {
+                data = [u];
+            }
+        }
+    }
+    void opAssign(Tdummy = void)(BigUint y) pure nothrow @nogc @safe
+    {
+        this.data = y.data;
+    }
+
+    ///
+    int opCmp(Tdummy = void)(const BigUint y) pure nothrow @nogc const @safe
+    {
+        if (data.length != y.data.length)
+            return (data.length > y.data.length) ?  1 : -1;
+        size_t k = highestDifferentDigit(data, y.data);
+        if (data[k] == y.data[k])
+            return 0;
+        return data[k] > y.data[k] ? 1 : -1;
+    }
+
+    ///
+    int opCmp(Tulong)(Tulong y) pure nothrow @nogc const @safe if(is (Tulong == ulong))
+    {
+        if (data.length > maxBigDigits!Tulong)
+            return 1;
+
+        foreach_reverse (i; 0 .. maxBigDigits!Tulong)
+        {
+            BigDigit tmp = cast(BigDigit)(y>>(i*BigDigitBits));
+            if (tmp == 0)
+                if (data.length >= i+1)
+                {
+                    // Since ZERO is [0], so we cannot simply return 1 here, as
+                    // data[i] would be 0 for i==0 in that case.
+                    return (data[i] > 0) ? 1 : 0;
+                }
+                else
+                    continue;
+            else
+                if (i+1 > data.length)
+                    return -1;
+                else if (tmp != data[i])
+                    return data[i] > tmp ? 1 : -1;
+        }
+        return 0;
+    }
+
+    bool opEquals(Tdummy = void)(ref const BigUint y) pure nothrow @nogc const @safe
+    {
+           return y.data[] == data[];
+    }
+
+    bool opEquals(Tdummy = void)(ulong y) pure nothrow @nogc const @safe
+    {
+        if (data.length > 2)
+            return false;
+        uint ylo = cast(uint)(y & 0xFFFF_FFFF);
+        uint yhi = cast(uint)(y >> 32);
+        if (data.length==2 && data[1]!=yhi)
+            return false;
+        if (data.length==1 && yhi!=0)
+            return false;
+        return (data[0] == ylo);
+    }
+
+    bool isZero() pure const nothrow @safe @nogc
+    {
+        return data.length == 1 && data[0] == 0;
+    }
+
+    size_t numBytes() pure nothrow const @safe @nogc
+    {
+        return data.length * BigDigit.sizeof;
+    }
+
+    // the extra bytes are added to the start of the string
+    char [] toDecimalString(int frontExtraBytes) const pure nothrow
+    {
+        auto predictlength = 20+20*(data.length/2); // just over 19
+        char [] buff = new char[frontExtraBytes + predictlength];
+        ptrdiff_t sofar = biguintToDecimal(buff, data.dup);
+        return buff[sofar-frontExtraBytes..$];
+    }
+
+    /** Convert to a hex string, printing a minimum number of digits 'minPadding',
+     *  allocating an additional 'frontExtraBytes' at the start of the string.
+     *  Padding is done with padChar, which may be '0' or ' '.
+     *  'separator' is a digit separation character. If non-zero, it is inserted
+     *  between every 8 digits.
+     *  Separator characters do not contribute to the minPadding.
+     */
+    char [] toHexString(int frontExtraBytes, char separator = 0,
+            int minPadding=0, char padChar = '0',
+            LetterCase letterCase = LetterCase.upper) const pure nothrow @safe
+    {
+        // Calculate number of extra padding bytes
+        size_t extraPad = (minPadding > data.length * 2 * BigDigit.sizeof)
+            ? minPadding - data.length * 2 * BigDigit.sizeof : 0;
+
+        // Length not including separator bytes
+        size_t lenBytes = data.length * 2 * BigDigit.sizeof;
+
+        // Calculate number of separator bytes
+        size_t mainSeparatorBytes = separator ? (lenBytes  / 8) - 1 : 0;
+        size_t totalSeparatorBytes = separator ? ((extraPad + lenBytes + 7) / 8) - 1: 0;
+
+        char [] buff = new char[lenBytes + extraPad + totalSeparatorBytes + frontExtraBytes];
+        biguintToHex(buff[$ - lenBytes - mainSeparatorBytes .. $], data, separator, letterCase);
+        if (extraPad > 0)
+        {
+            if (separator)
+            {
+                size_t start = frontExtraBytes; // first index to pad
+                if (extraPad &7)
+                {
+                    // Do 1 to 7 extra zeros.
+                    buff[frontExtraBytes .. frontExtraBytes + (extraPad & 7)] = padChar;
+                    buff[frontExtraBytes + (extraPad & 7)] = (padChar == ' ' ? ' ' : separator);
+                    start += (extraPad & 7) + 1;
+                }
+                for (int i=0; i< (extraPad >> 3); ++i)
+                {
+                    buff[start .. start + 8] = padChar;
+                    buff[start + 8] = (padChar == ' ' ? ' ' : separator);
+                    start += 9;
+                }
+            }
+            else
+            {
+                buff[frontExtraBytes .. frontExtraBytes + extraPad]=padChar;
+            }
+        }
+        int z = frontExtraBytes;
+        if (lenBytes > minPadding)
+        {
+            // Strip leading zeros.
+            ptrdiff_t maxStrip = lenBytes - minPadding;
+            while (z< buff.length-1 && (buff[z]=='0' || buff[z]==padChar) && maxStrip>0)
+            {
+                ++z;
+                --maxStrip;
+            }
+        }
+        if (padChar!='0')
+        {
+            // Convert leading zeros into padChars.
+            for (size_t k= z; k< buff.length-1 && (buff[k]=='0' || buff[k]==padChar); ++k)
+            {
+                if (buff[k]=='0') buff[k]=padChar;
+            }
+        }
+        return buff[z-frontExtraBytes..$];
+    }
+
+    // return false if invalid character found
+    bool fromHexString(const(char)[] s) pure nothrow @safe
+    {
+        //Strip leading zeros
+        int firstNonZero = 0;
+        while ((firstNonZero < s.length - 1) &&
+            (s[firstNonZero]=='0' || s[firstNonZero]=='_'))
+        {
+                ++firstNonZero;
+        }
+        auto len = (s.length - firstNonZero + 15)/4;
+        auto tmp = new BigDigit[len+1];
+        uint part = 0;
+        uint sofar = 0;
+        uint partcount = 0;
+        assert(s.length>0);
+        for (ptrdiff_t i = s.length - 1; i>=firstNonZero; --i)
+        {
+            assert(i>=0);
+            char c = s[i];
+            if (s[i]=='_') continue;
+            uint x = (c>='0' && c<='9') ? c - '0'
+                   : (c>='A' && c<='F') ? c - 'A' + 10
+                   : (c>='a' && c<='f') ? c - 'a' + 10
+                   : 100;
+            if (x==100) return false;
+            part >>= 4;
+            part |= (x<<(32-4));
+            ++partcount;
+            if (partcount==8)
+            {
+                tmp[sofar] = part;
+                ++sofar;
+                partcount = 0;
+                part = 0;
+            }
+        }
+        if (part)
+        {
+            for ( ; partcount != 8; ++partcount) part >>= 4;
+            tmp[sofar] = part;
+            ++sofar;
+        }
+        if (sofar == 0)
+            data = ZERO;
+        else
+            data = trustedAssumeUnique(tmp[0 .. sofar]);
+
+        return true;
+    }
+
+    // return true if OK; false if erroneous characters found
+    // FIXME: actually throws `ConvException` on error.
+    bool fromDecimalString(const(char)[] s) pure @trusted
+    {
+        //Strip leading zeros
+        int firstNonZero = 0;
+        while ((firstNonZero < s.length) &&
+            (s[firstNonZero]=='0' || s[firstNonZero]=='_'))
+        {
+                ++firstNonZero;
+        }
+        if (firstNonZero == s.length && s.length >= 1)
+        {
+            data = ZERO;
+            return true;
+        }
+        auto predictlength = (18*2 + 2*(s.length-firstNonZero)) / 19;
+        auto tmp = new BigDigit[predictlength];
+
+        uint hi = biguintFromDecimal(tmp, s[firstNonZero..$]);
+        tmp.length = hi;
+
+        data = trustedAssumeUnique(tmp);
+        return true;
+    }
+
+    ////////////////////////
+    //
+    // All of these member functions create a new BigUint.
+
+    // return x >> y
+    BigUint opShr(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong))
+    {
+        assert(y>0);
+        uint bits = cast(uint)y & BIGDIGITSHIFTMASK;
+        if ((y>>LG2BIGDIGITBITS) >= data.length) return BigUint(ZERO);
+        uint words = cast(uint)(y >> LG2BIGDIGITBITS);
+        if (bits==0)
+        {
+            return BigUint(data[words..$]);
+        }
+        else
+        {
+            uint [] result = new BigDigit[data.length - words];
+            multibyteShr(result, data[words..$], bits);
+
+            if (result.length > 1 && result[$-1] == 0)
+                return BigUint(trustedAssumeUnique(result[0 .. $-1]));
+            else
+                return BigUint(trustedAssumeUnique(result));
+        }
+    }
+
+    // return x << y
+    BigUint opShl(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong))
+    {
+        assert(y>0);
+        if (isZero()) return this;
+        uint bits = cast(uint)y & BIGDIGITSHIFTMASK;
+        assert ((y>>LG2BIGDIGITBITS) < cast(ulong)(uint.max));
+        uint words = cast(uint)(y >> LG2BIGDIGITBITS);
+        BigDigit [] result = new BigDigit[data.length + words+1];
+        result[0..words] = 0;
+        if (bits==0)
+        {
+            result[words..words+data.length] = data[];
+            return BigUint(trustedAssumeUnique(result[0..words+data.length]));
+        }
+        else
+        {
+            uint c = multibyteShl(result[words..words+data.length], data, bits);
+            if (c==0) return BigUint(trustedAssumeUnique(result[0..words+data.length]));
+            result[$-1] = c;
+            return BigUint(trustedAssumeUnique(result));
+        }
+    }
+
+    // If wantSub is false, return x + y, leaving sign unchanged
+    // If wantSub is true, return abs(x - y), negating sign if x < y
+    static BigUint addOrSubInt(Tulong)(const BigUint x, Tulong y,
+            bool wantSub, ref bool sign) pure nothrow if (is(Tulong == ulong))
+    {
+        BigUint r;
+        if (wantSub)
+        {   // perform a subtraction
+            if (x.data.length > 2)
+            {
+                r.data = subInt(x.data, y);
+            }
+            else
+            {   // could change sign!
+                ulong xx = x.data[0];
+                if (x.data.length > 1)
+                    xx += (cast(ulong)x.data[1]) << 32;
+                ulong d;
+                if (xx <= y)
+                {
+                    d = y - xx;
+                    sign = !sign;
+                }
+                else
+                {
+                    d = xx - y;
+                }
+                if (d == 0)
+                {
+                    r = 0UL;
+                    sign = false;
+                    return r;
+                }
+                if (d > uint.max)
+                {
+                    r.data = [cast(uint)(d & 0xFFFF_FFFF), cast(uint)(d>>32)];
+                }
+                else
+                {
+                    r.data = [cast(uint)(d & 0xFFFF_FFFF)];
+                }
+            }
+        }
+        else
+        {
+            r.data = addInt(x.data, y);
+        }
+        return r;
+    }
+
+    // If wantSub is false, return x + y, leaving sign unchanged.
+    // If wantSub is true, return abs(x - y), negating sign if x<y
+    static BigUint addOrSub(BigUint x, BigUint y, bool wantSub, bool *sign)
+        pure nothrow
+    {
+        BigUint r;
+        if (wantSub)
+        {   // perform a subtraction
+            bool negative;
+            r.data = sub(x.data, y.data, &negative);
+            *sign ^= negative;
+            if (r.isZero())
+            {
+                *sign = false;
+            }
+        }
+        else
+        {
+            r.data = add(x.data, y.data);
+        }
+        return r;
+    }
+
+
+    //  return x*y.
+    //  y must not be zero.
+    static BigUint mulInt(T = ulong)(BigUint x, T y) pure nothrow
+    {
+        if (y==0 || x == 0) return BigUint(ZERO);
+        uint hi = cast(uint)(y >>> 32);
+        uint lo = cast(uint)(y & 0xFFFF_FFFF);
+        uint [] result = new BigDigit[x.data.length+1+(hi!=0)];
+        result[x.data.length] = multibyteMul(result[0..x.data.length], x.data, lo, 0);
+        if (hi!=0)
+        {
+            result[x.data.length+1] = multibyteMulAdd!('+')(result[1..x.data.length+1],
+                x.data, hi, 0);
+        }
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(result)));
+    }
+
+    /*  return x * y.
+     */
+    static BigUint mul(BigUint x, BigUint y) pure nothrow
+    {
+        if (y==0 || x == 0)
+            return BigUint(ZERO);
+        auto len = x.data.length + y.data.length;
+        BigDigit [] result = new BigDigit[len];
+        if (y.data.length > x.data.length)
+        {
+            mulInternal(result, y.data, x.data);
+        }
+        else
+        {
+            if (x.data[]==y.data[]) squareInternal(result, x.data);
+            else mulInternal(result, x.data, y.data);
+        }
+        // the highest element could be zero,
+        // in which case we need to reduce the length
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(result)));
+    }
+
+    // return x / y
+    static BigUint divInt(T)(BigUint x, T y_) pure nothrow
+    if ( is(Unqual!T == uint) )
+    {
+        uint y = y_;
+        if (y == 1)
+            return x;
+        uint [] result = new BigDigit[x.data.length];
+        if ((y&(-y))==y)
+        {
+            assert(y!=0, "BigUint division by zero");
+            // perfect power of 2
+            uint b = 0;
+            for (;y!=1; y>>=1)
+            {
+                ++b;
+            }
+            multibyteShr(result, x.data, b);
+        }
+        else
+        {
+            result[] = x.data[];
+            uint rem = multibyteDivAssign(result, y, 0);
+        }
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(result)));
+    }
+
+    static BigUint divInt(T)(BigUint x, T y) pure nothrow
+    if ( is(Unqual!T == ulong) )
+    {
+        if (y <= uint.max)
+            return divInt!uint(x, cast(uint)y);
+        if (x.data.length < 2)
+            return BigUint(ZERO);
+        uint hi = cast(uint)(y >>> 32);
+        uint lo = cast(uint)(y & 0xFFFF_FFFF);
+        immutable uint[2] z = [lo, hi];
+        BigDigit[] result = new BigDigit[x.data.length - z.length + 1];
+        divModInternal(result, null, x.data, z[]);
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(result)));
+    }
+
+    // return x % y
+    static uint modInt(T)(BigUint x, T y_) pure if ( is(Unqual!T == uint) )
+    {
+        uint y = y_;
+        assert(y!=0);
+        if ((y&(-y)) == y)
+        {   // perfect power of 2
+            return x.data[0] & (y-1);
+        }
+        else
+        {
+            // horribly inefficient - malloc, copy, & store are unnecessary.
+            uint [] wasteful = new BigDigit[x.data.length];
+            wasteful[] = x.data[];
+            uint rem = multibyteDivAssign(wasteful, y, 0);
+            delete wasteful;
+            return rem;
+        }
+    }
+
+    // return x / y
+    static BigUint div(BigUint x, BigUint y) pure nothrow
+    {
+        if (y.data.length > x.data.length)
+            return BigUint(ZERO);
+        if (y.data.length == 1)
+            return divInt(x, y.data[0]);
+        BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1];
+           divModInternal(result, null, x.data, y.data);
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(result)));
+    }
+
+    // return x % y
+    static BigUint mod(BigUint x, BigUint y) pure nothrow
+    {
+        if (y.data.length > x.data.length) return x;
+        if (y.data.length == 1)
+        {
+            return BigUint([modInt(x, y.data[0])]);
+        }
+        BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1];
+        BigDigit [] rem = new BigDigit[y.data.length];
+        divModInternal(result, rem, x.data, y.data);
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(rem)));
+    }
+
+    // return x op y
+    static BigUint bitwiseOp(string op)(BigUint x, BigUint y, bool xSign, bool ySign, ref bool resultSign)
+    pure nothrow @safe if (op == "|" || op == "^" || op == "&")
+    {
+        auto d1 = includeSign(x.data, y.uintLength, xSign);
+        auto d2 = includeSign(y.data, x.uintLength, ySign);
+
+        foreach (i; 0..d1.length)
+        {
+            mixin("d1[i] " ~ op ~ "= d2[i];");
+        }
+
+        mixin("resultSign = xSign " ~ op ~ " ySign;");
+
+        if (resultSign)
+        {
+            twosComplement(d1, d1);
+        }
+
+        return BigUint(removeLeadingZeros(trustedAssumeUnique(d1)));
+    }
+
+    /**
+     * Return a BigUint which is x raised to the power of y.
+     * Method: Powers of 2 are removed from x, then left-to-right binary
+     * exponentiation is used.
+     * Memory allocation is minimized: at most one temporary BigUint is used.
+     */
+    static BigUint pow(BigUint x, ulong y) pure nothrow
+    {
+        // Deal with the degenerate cases first.
+        if (y==0) return BigUint(ONE);
+        if (y==1) return x;
+        if (x==0 || x==1) return x;
+
+        BigUint result;
+
+        // Simplify, step 1: Remove all powers of 2.
+        uint firstnonzero = firstNonZeroDigit(x.data);
+        // Now we know x = x[firstnonzero..$] * (2^^(firstnonzero*BigDigitBits))
+        // where BigDigitBits = BigDigit.sizeof * 8
+
+        // See if x[firstnonzero..$] can now fit into a single digit.
+        bool singledigit = ((x.data.length - firstnonzero) == 1);
+        // If true, then x0 is that digit
+        // and the result will be (x0 ^^ y) * (2^^(firstnonzero*y*BigDigitBits))
+        BigDigit x0 = x.data[firstnonzero];
+        assert(x0 !=0);
+        // Length of the non-zero portion
+        size_t nonzerolength = x.data.length - firstnonzero;
+        ulong y0;
+        uint evenbits = 0; // number of even bits in the bottom of x
+        while (!(x0 & 1))
+        {
+            x0 >>= 1;
+            ++evenbits;
+        }
+
+        if ((x.data.length- firstnonzero == 2))
+        {
+            // Check for a single digit straddling a digit boundary
+            BigDigit x1 = x.data[firstnonzero+1];
+            if ((x1 >> evenbits) == 0)
+            {
+                x0 |= (x1 << (BigDigit.sizeof * 8 - evenbits));
+                singledigit = true;
+            }
+        }
+        // Now if (singledigit), x^^y  = (x0 ^^ y) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits))
+
+        uint evenshiftbits = 0; // Total powers of 2 to shift by, at the end
+
+        // Simplify, step 2: For singledigits, see if we can trivially reduce y
+
+        BigDigit finalMultiplier = 1UL;
+
+        if (singledigit)
+        {
+            // x fits into a single digit. Raise it to the highest power we can
+            // that still fits into a single digit, then reduce the exponent accordingly.
+            // We're quite likely to have a residual multiply at the end.
+            // For example, 10^^100 = (((5^^13)^^7) * 5^^9) * 2^^100.
+            // and 5^^13 still fits into a uint.
+            evenshiftbits  = cast(uint)( (evenbits * y) & BIGDIGITSHIFTMASK);
+            if (x0 == 1)
+            {   // Perfect power of 2
+                result = 1UL;
+                return result << (evenbits + firstnonzero * 8 * BigDigit.sizeof) * y;
+            }
+            int p = highestPowerBelowUintMax(x0);
+            if (y <= p)
+            {   // Just do it with pow
+                result = cast(ulong)intpow(x0, y);
+                if (evenbits + firstnonzero == 0)
+                    return result;
+                return result << (evenbits + firstnonzero * 8 * BigDigit.sizeof) * y;
+            }
+            y0 = y / p;
+            finalMultiplier = intpow(x0, y - y0*p);
+            x0 = intpow(x0, p);
+            // Result is x0
+            nonzerolength = 1;
+        }
+        // Now if (singledigit), x^^y  = finalMultiplier * (x0 ^^ y0) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits))
+
+        // Perform a crude check for overflow and allocate result buffer.
+        // The length required is y * lg2(x) bits.
+        // which will always fit into y*x.length digits. But this is
+        // a gross overestimate if x is small (length 1 or 2) and the highest
+        // digit is nearly empty.
+        // A better estimate is:
+        //   y * lg2(x[$-1]/BigDigit.max) + y * (x.length - 1) digits,
+        //  and the first term is always between
+        //  y * (bsr(x.data[$-1]) + 1) / BIGDIGITBITS and
+        //  y * (bsr(x.data[$-1]) + 2) / BIGDIGITBITS
+        // For single digit payloads, we already have
+        //   x^^y  = finalMultiplier * (x0 ^^ y0) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits))
+        // and x0 is almost a full digit, so it's a tight estimate.
+        // Number of digits is therefore 1 + x0.length*y0 + (evenbits*y)/BIGDIGIT + firstnonzero*y
+        // Note that the divisions must be rounded up.
+
+        // Estimated length in BigDigits
+        ulong estimatelength = singledigit
+            ? 1 + y0 + ((evenbits*y  + BigDigit.sizeof * 8 - 1) / (BigDigit.sizeof *8)) + firstnonzero*y
+            :  x.data.length * y;
+        // Imprecise check for overflow. Makes the extreme cases easier to debug
+        // (less extreme overflow will result in an out of memory error).
+        if (estimatelength > uint.max/(4*BigDigit.sizeof))
+            assert(0, "Overflow in BigInt.pow");
+
+        // The result buffer includes space for all the trailing zeros
+        BigDigit [] resultBuffer = new BigDigit[cast(size_t)estimatelength];
+
+        // Do all the powers of 2!
+        size_t result_start = cast(size_t)( firstnonzero * y
+            + (singledigit ? ((evenbits * y) >> LG2BIGDIGITBITS) : 0));
+
+        resultBuffer[0..result_start] = 0;
+        BigDigit [] t1 = resultBuffer[result_start..$];
+        BigDigit [] r1;
+
+        if (singledigit)
+        {
+            r1 = t1[0..1];
+            r1[0] = x0;
+            y = y0;
+        }
+        else
+        {
+            // It's not worth right shifting by evenbits unless we also shrink the length after each
+            // multiply or squaring operation. That might still be worthwhile for large y.
+            r1 = t1[0..x.data.length - firstnonzero];
+            r1[0..$] = x.data[firstnonzero..$];
+        }
+
+        if (y>1)
+        {   // Set r1 = r1 ^^ y.
+            // The secondary buffer only needs space for the multiplication results
+            BigDigit [] secondaryBuffer = new BigDigit[resultBuffer.length - result_start];
+            BigDigit [] t2 = secondaryBuffer;
+            BigDigit [] r2;
+
+            int shifts = 63; // num bits in a long
+            while(!(y & 0x8000_0000_0000_0000L))
+            {
+                y <<= 1;
+                --shifts;
+            }
+            y <<=1;
+
+            while(y!=0)
+            {
+                // For each bit of y: Set r1 =  r1 * r1
+                // If the bit is 1, set r1 = r1 * x
+                // Eg, if y is 0b101, result = ((x^^2)^^2)*x == x^^5.
+                // Optimization opportunity: if more than 2 bits in y are set,
+                // it's usually possible to reduce the number of multiplies
+                // by caching odd powers of x. eg for y = 54,
+                // (0b110110), set u = x^^3, and result is ((u^^8)*u)^^2
+                r2 = t2[0 .. r1.length*2];
+                squareInternal(r2, r1);
+                if (y & 0x8000_0000_0000_0000L)
+                {
+                    r1 = t1[0 .. r2.length + nonzerolength];
+                    if (singledigit)
+                    {
+                        r1[$-1] = multibyteMul(r1[0 .. $-1], r2, x0, 0);
+                    }
+                    else
+                    {
+                        mulInternal(r1, r2, x.data[firstnonzero..$]);
+                    }
+                }
+                else
+                {
+                    r1 = t1[0 .. r2.length];
+                    r1[] = r2[];
+                }
+                y <<=1;
+                shifts--;
+            }
+            while (shifts>0)
+            {
+                r2 = t2[0 .. r1.length * 2];
+                squareInternal(r2, r1);
+                r1 = t1[0 .. r2.length];
+                r1[] = r2[];
+                --shifts;
+            }
+        }
+
+        if (finalMultiplier!=1)
+        {
+            BigDigit carry = multibyteMul(r1, r1, finalMultiplier, 0);
+            if (carry)
+            {
+                r1 = t1[0 .. r1.length + 1];
+                r1[$-1] = carry;
+            }
+        }
+        if (evenshiftbits)
+        {
+            BigDigit carry = multibyteShl(r1, r1, evenshiftbits);
+            if (carry!=0)
+            {
+                r1 = t1[0 .. r1.length + 1];
+                r1[$ - 1] = carry;
+            }
+        }
+        while(r1[$ - 1]==0)
+        {
+            r1=r1[0 .. $ - 1];
+        }
+        return BigUint(trustedAssumeUnique(resultBuffer[0 .. result_start + r1.length]));
+    }
+
+    // Implement toHash so that BigUint works properly as an AA key.
+    size_t toHash() const @trusted nothrow
+    {
+        return typeid(data).getHash(&data);
+    }
+
+} // end BigUint
+
+@safe pure nothrow unittest
+{
+    // ulong comparison test
+    BigUint a = [1];
+    assert(a == 1);
+    assert(a < 0x8000_0000_0000_0000UL); // bug 9548
+
+    // bug 12234
+    BigUint z = [0];
+    assert(z == 0UL);
+    assert(!(z > 0UL));
+    assert(!(z < 0UL));
+}
+
+// Remove leading zeros from x, to restore the BigUint invariant
+inout(BigDigit) [] removeLeadingZeros(inout(BigDigit) [] x) pure nothrow @safe
+{
+    size_t k = x.length;
+    while(k>1 && x[k - 1]==0) --k;
+    return x[0 .. k];
+}
+
+pure unittest
+{
+   BigUint r = BigUint([5]);
+   BigUint t = BigUint([7]);
+   BigUint s = BigUint.mod(r, t);
+   assert(s==5);
+}
+
+
+@safe pure unittest
+{
+    BigUint r;
+    r = 5UL;
+    assert(r.peekUlong(0) == 5UL);
+    assert(r.peekUint(0) == 5U);
+    r = 0x1234_5678_9ABC_DEF0UL;
+    assert(r.peekUlong(0) == 0x1234_5678_9ABC_DEF0UL);
+    assert(r.peekUint(0) == 0x9ABC_DEF0U);
+}
+
+
+// Pow tests
+pure unittest
+{
+    BigUint r, s;
+    r.fromHexString("80000000_00000001");
+    s = BigUint.pow(r, 5);
+    r.fromHexString("08000000_00000000_50000000_00000001_40000000_00000002_80000000"
+      ~ "_00000002_80000000_00000001");
+    assert(s == r);
+    s = 10UL;
+    s = BigUint.pow(s, 39);
+    r.fromDecimalString("1000000000000000000000000000000000000000");
+    assert(s == r);
+    r.fromHexString("1_E1178E81_00000000");
+    s = BigUint.pow(r, 15); // Regression test: this used to overflow array bounds
+
+    r.fromDecimalString("000_000_00");
+    assert(r == 0);
+    r.fromDecimalString("0007");
+    assert(r == 7);
+    r.fromDecimalString("0");
+    assert(r == 0);
+}
+
+// Radix conversion tests
+@safe pure unittest
+{
+    BigUint r;
+    r.fromHexString("1_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 0) == "1_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 20) == "0001_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 16+8) == "00000001_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 16+9) == "0_00000001_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8) ==   "00000000_00000001_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8+1) ==      "0_00000000_00000001_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8+1, ' ') == "                  1_E1178E81_00000000");
+    assert(r.toHexString(0, 0, 16+8+8+1) == "00000000000000001E1178E8100000000");
+    r = 0UL;
+    assert(r.toHexString(0, '_', 0) == "0");
+    assert(r.toHexString(0, '_', 7) == "0000000");
+    assert(r.toHexString(0, '_', 7, ' ') == "      0");
+    assert(r.toHexString(0, '#', 9) == "0#00000000");
+    assert(r.toHexString(0, 0, 9) == "000000000");
+}
+
+//
+@safe pure unittest
+{
+    BigUint r;
+    r.fromHexString("1_E1178E81_00000000");
+    assert(r.toHexString(0, '_', 0, '0', LetterCase.lower) == "1_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 20, '0', LetterCase.lower) == "0001_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 16+8, '0', LetterCase.lower) == "00000001_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 16+9, '0', LetterCase.lower) == "0_00000001_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8, '0', LetterCase.lower) ==   "00000000_00000001_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8+1, '0', LetterCase.lower) == "0_00000000_00000001_e1178e81_00000000");
+    assert(r.toHexString(0, '_', 16+8+8+1, ' ', LetterCase.lower) == "                  1_e1178e81_00000000");
+    assert(r.toHexString(0, 0, 16+8+8+1, '0', LetterCase.lower) == "00000000000000001e1178e8100000000");
+    r = 0UL;
+    assert(r.toHexString(0, '_', 0, '0', LetterCase.lower) == "0");
+    assert(r.toHexString(0, '_', 7, '0', LetterCase.lower) == "0000000");
+    assert(r.toHexString(0, '_', 7, ' ', LetterCase.lower) == "      0");
+    assert(r.toHexString(0, '#', 9, '0', LetterCase.lower) == "0#00000000");
+    assert(r.toHexString(0, 'Z', 9, '0', LetterCase.lower) == "0Z00000000");
+    assert(r.toHexString(0, 0, 9, '0', LetterCase.lower) == "000000000");
+}
+
+
+private:
+void twosComplement(const(BigDigit) [] x, BigDigit[] result)
+pure nothrow @safe
+{
+    foreach (i; 0..x.length)
+    {
+        result[i] = ~x[i];
+    }
+    result[x.length..$] = BigDigit.max;
+
+    bool sgn = false;
+
+    foreach (i; 0..result.length)
+    {
+        if (result[i] == BigDigit.max)
+        {
+            result[i] = 0;
+        }
+        else
+        {
+            result[i] += 1;
+            break;
+        }
+    }
+}
+
+// Encode BigInt as BigDigit array (sign and 2's complement)
+BigDigit[] includeSign(const(BigDigit) [] x, size_t minSize, bool sign)
+pure nothrow @safe
+{
+    size_t length = (x.length > minSize) ? x.length : minSize;
+    BigDigit [] result = new BigDigit[length];
+    if (sign)
+    {
+        twosComplement(x, result);
+    }
+    else
+    {
+        result[0..x.length] = x;
+    }
+    return result;
+}
+
+// works for any type
+T intpow(T)(T x, ulong n) pure nothrow @safe
+{
+    T p;
+
+    switch (n)
+    {
+    case 0:
+        p = 1;
+        break;
+
+    case 1:
+        p = x;
+        break;
+
+    case 2:
+        p = x * x;
+        break;
+
+    default:
+        p = 1;
+        while (1){
+            if (n & 1)
+                p *= x;
+            n >>= 1;
+            if (!n)
+                break;
+            x *= x;
+        }
+        break;
+    }
+    return p;
+}
+
+
+//  returns the maximum power of x that will fit in a uint.
+int highestPowerBelowUintMax(uint x) pure nothrow @safe
+{
+     assert(x>1);
+     static immutable ubyte [22] maxpwr = [ 31, 20, 15, 13, 12, 11, 10, 10, 9, 9,
+                                          8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7];
+     if (x<24) return maxpwr[x-2];
+     if (x<41) return 6;
+     if (x<85) return 5;
+     if (x<256) return 4;
+     if (x<1626) return 3;
+     if (x<65536) return 2;
+     return 1;
+}
+
+//  returns the maximum power of x that will fit in a ulong.
+int highestPowerBelowUlongMax(uint x) pure nothrow @safe
+{
+     assert(x>1);
+     static immutable ubyte [39] maxpwr = [ 63, 40, 31, 27, 24, 22, 21, 20, 19, 18,
+                                         17, 17, 16, 16, 15, 15, 15, 15, 14, 14,
+                                         14, 14, 13, 13, 13, 13, 13, 13, 13, 12,
+                                         12, 12, 12, 12, 12, 12, 12, 12, 12];
+     if (x<41) return maxpwr[x-2];
+     if (x<57) return 11;
+     if (x<85) return 10;
+     if (x<139) return 9;
+     if (x<256) return 8;
+     if (x<566) return 7;
+     if (x<1626) return 6;
+     if (x<7132) return 5;
+     if (x<65536) return 4;
+     if (x<2642246) return 3;
+     return 2;
+}
+
+version(unittest)
+{
+
+int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe
+{
+     int pwr = 1;
+     for (ulong q = x;x*q < cast(ulong)uint.max; )
+     {
+         q*=x; ++pwr;
+     }
+     return pwr;
+}
+
+@safe pure unittest
+{
+    assert(highestPowerBelowUintMax(10)==9);
+    for (int k=82; k<88; ++k)
+    {
+        assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k));
+    }
+}
+
+}
+
+
+/*  General unsigned subtraction routine for bigints.
+ *  Sets result = x - y. If the result is negative, negative will be true.
+ */
+BigDigit [] sub(const BigDigit [] x, const BigDigit [] y, bool *negative)
+pure nothrow
+{
+    if (x.length == y.length)
+    {
+        // There's a possibility of cancellation, if x and y are almost equal.
+        ptrdiff_t last = highestDifferentDigit(x, y);
+        BigDigit [] result = new BigDigit[last+1];
+        if (x[last] < y[last])
+        {   // we know result is negative
+            multibyteSub(result[0..last+1], y[0..last+1], x[0..last+1], 0);
+            *negative = true;
+        }
+        else
+        {   // positive or zero result
+            multibyteSub(result[0..last+1], x[0..last+1], y[0..last+1], 0);
+            *negative = false;
+        }
+        while (result.length > 1 && result[$-1] == 0)
+        {
+            result = result[0..$-1];
+        }
+//        if (result.length >1 && result[$-1]==0) return result[0..$-1];
+        return result;
+    }
+    // Lengths are different
+    const(BigDigit) [] large, small;
+    if (x.length < y.length)
+    {
+        *negative = true;
+        large = y; small = x;
+    }
+    else
+    {
+        *negative = false;
+        large = x; small = y;
+    }
+    // result.length will be equal to larger length, or could decrease by 1.
+
+
+    BigDigit [] result = new BigDigit[large.length];
+    BigDigit carry = multibyteSub(result[0..small.length], large[0..small.length], small, 0);
+    result[small.length..$] = large[small.length..$];
+    if (carry)
+    {
+        multibyteIncrementAssign!('-')(result[small.length..$], carry);
+    }
+    while (result.length > 1 && result[$-1] == 0)
+    {
+        result = result[0..$-1];
+    }
+    return result;
+}
+
+
+// return a + b
+BigDigit [] add(const BigDigit [] a, const BigDigit [] b) pure nothrow
+{
+    const(BigDigit) [] x, y;
+    if (a.length < b.length)
+    {
+        x = b; y = a;
+    }
+    else
+    {
+        x = a; y = b;
+    }
+    // now we know x.length > y.length
+    // create result. add 1 in case it overflows
+    BigDigit [] result = new BigDigit[x.length + 1];
+
+    BigDigit carry = multibyteAdd(result[0..y.length], x[0..y.length], y, 0);
+    if (x.length != y.length)
+    {
+        result[y.length..$-1]= x[y.length..$];
+        carry  = multibyteIncrementAssign!('+')(result[y.length..$-1], carry);
+    }
+    if (carry)
+    {
+        result[$-1] = carry;
+        return result;
+    }
+    else
+        return result[0..$-1];
+}
+
+/**  return x + y
+ */
+BigDigit [] addInt(const BigDigit[] x, ulong y) pure nothrow
+{
+    uint hi = cast(uint)(y >>> 32);
+    uint lo = cast(uint)(y& 0xFFFF_FFFF);
+    auto len = x.length;
+    if (x.length < 2 && hi!=0) ++len;
+    BigDigit [] result = new BigDigit[len+1];
+    result[0..x.length] = x[];
+    if (x.length < 2 && hi!=0)
+    {
+        result[1]=hi;
+        hi=0;
+    }
+    uint carry = multibyteIncrementAssign!('+')(result[0..$-1], lo);
+    if (hi!=0) carry += multibyteIncrementAssign!('+')(result[1..$-1], hi);
+    if (carry)
+    {
+        result[$-1] = carry;
+        return result;
+    }
+    else
+        return result[0..$-1];
+}
+
+/** Return x - y.
+ *  x must be greater than y.
+ */
+BigDigit [] subInt(const BigDigit[] x, ulong y) pure nothrow
+{
+    uint hi = cast(uint)(y >>> 32);
+    uint lo = cast(uint)(y & 0xFFFF_FFFF);
+    BigDigit [] result = new BigDigit[x.length];
+    result[] = x[];
+    multibyteIncrementAssign!('-')(result[], lo);
+    if (hi)
+        multibyteIncrementAssign!('-')(result[1..$], hi);
+    if (result[$-1] == 0)
+        return result[0..$-1];
+    else
+        return result;
+}
+
+/**  General unsigned multiply routine for bigints.
+ *  Sets result = x * y.
+ *
+ *  The length of y must not be larger than the length of x.
+ *  Different algorithms are used, depending on the lengths of x and y.
+ *  TODO: "Modern Computer Arithmetic" suggests the OddEvenKaratsuba algorithm for the
+ *  unbalanced case. (But I doubt it would be faster in practice).
+ *
+ */
+void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y)
+    pure nothrow
+{
+    assert( result.length == x.length + y.length );
+    assert( y.length > 0 );
+    assert( x.length >= y.length);
+    if (y.length <= KARATSUBALIMIT)
+    {
+        // Small multiplier, we'll just use the asm classic multiply.
+        if (y.length == 1)
+        {   // Trivial case, no cache effects to worry about
+            result[x.length] = multibyteMul(result[0..x.length], x, y[0], 0);
+            return;
+        }
+
+        if (x.length + y.length < CACHELIMIT)
+            return mulSimple(result, x, y);
+
+        // If x is so big that it won't fit into the cache, we divide it into chunks
+        // Every chunk must be greater than y.length.
+        // We make the first chunk shorter, if necessary, to ensure this.
+
+        auto chunksize = CACHELIMIT / y.length;
+        auto residual  =  x.length % chunksize;
+        if (residual < y.length)
+        {
+            chunksize -= y.length;
+        }
+
+        // Use schoolbook multiply.
+        mulSimple(result[0 .. chunksize + y.length], x[0..chunksize], y);
+        auto done = chunksize;
+
+        while (done < x.length)
+        {
+            // result[done .. done+ylength] already has a value.
+            chunksize = (done + (CACHELIMIT / y.length) < x.length) ? (CACHELIMIT / y.length) :  x.length - done;
+            BigDigit [KARATSUBALIMIT] partial;
+            partial[0..y.length] = result[done..done+y.length];
+            mulSimple(result[done..done+chunksize+y.length], x[done..done+chunksize], y);
+            addAssignSimple(result[done..done+chunksize + y.length], partial[0..y.length]);
+            done += chunksize;
+        }
+        return;
+    }
+
+    auto half = (x.length >> 1) + (x.length & 1);
+    if (2*y.length*y.length <= x.length*x.length)
+    {
+        // UNBALANCED MULTIPLY
+        // Use school multiply to cut into quasi-squares of Karatsuba-size
+        // or larger. The ratio of the two sides of the 'square' must be
+        // between 1.414:1 and 1:1. Use Karatsuba on each chunk.
+        //
+        // For maximum performance, we want the ratio to be as close to
+        // 1:1 as possible. To achieve this, we can either pad x or y.
+        // The best choice depends on the modulus x%y.
+        auto numchunks = x.length / y.length;
+        auto chunksize = y.length;
+        auto extra =  x.length % y.length;
+        auto maxchunk = chunksize + extra;
+        bool paddingY; // true = we're padding Y, false = we're padding X.
+        if (extra * extra * 2 < y.length*y.length)
+        {
+            // The leftover bit is small enough that it should be incorporated
+            // in the existing chunks.
+            // Make all the chunks a tiny bit bigger
+            // (We're padding y with zeros)
+            chunksize += extra / numchunks;
+            extra = x.length - chunksize*numchunks;
+            // there will probably be a few left over.
+            // Every chunk will either have size chunksize, or chunksize+1.
+            maxchunk = chunksize + 1;
+            paddingY = true;
+            assert(chunksize + extra + chunksize *(numchunks-1) == x.length );
+        }
+        else
+        {
+            // the extra bit is large enough that it's worth making a new chunk.
+            // (This means we're padding x with zeros, when doing the first one).
+            maxchunk = chunksize;
+            ++numchunks;
+            paddingY = false;
+            assert(extra + chunksize *(numchunks-1) == x.length );
+        }
+        // We make the buffer a bit bigger so we have space for the partial sums.
+        BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(maxchunk) + y.length];
+        BigDigit [] partial = scratchbuff[$ - y.length .. $];
+        size_t done; // how much of X have we done so far?
+        double residual = 0;
+        if (paddingY)
+        {
+            // If the first chunk is bigger, do it first. We're padding y.
+            mulKaratsuba(result[0 .. y.length + chunksize + (extra > 0 ? 1 : 0 )],
+                x[0 .. chunksize + (extra>0?1:0)], y, scratchbuff);
+            done = chunksize + (extra > 0 ? 1 : 0);
+            if (extra) --extra;
+        }
+        else
+        {   // We're padding X. Begin with the extra bit.
+            mulKaratsuba(result[0 .. y.length + extra], y, x[0..extra], scratchbuff);
+            done = extra;
+            extra = 0;
+        }
+        auto basechunksize = chunksize;
+        while (done < x.length)
+        {
+            chunksize = basechunksize + (extra > 0 ? 1 : 0);
+            if (extra) --extra;
+            partial[] = result[done .. done+y.length];
+            mulKaratsuba(result[done .. done + y.length + chunksize],
+                       x[done .. done+chunksize], y, scratchbuff);
+            addAssignSimple(result[done .. done + y.length + chunksize], partial);
+            done += chunksize;
+        }
+        delete scratchbuff;
+    }
+    else
+    {
+        // Balanced. Use Karatsuba directly.
+        BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(x.length)];
+        mulKaratsuba(result, x, y, scratchbuff);
+        delete scratchbuff;
+    }
+}
+
+/**  General unsigned squaring routine for BigInts.
+ *   Sets result = x*x.
+ *   NOTE: If the highest half-digit of x is zero, the highest digit of result will
+ *   also be zero.
+ */
+void squareInternal(BigDigit[] result, const BigDigit[] x) pure nothrow
+{
+  // Squaring is potentially half a multiply, plus add the squares of
+  // the diagonal elements.
+  assert(result.length == 2*x.length);
+  if (x.length <= KARATSUBASQUARELIMIT)
+  {
+      if (x.length==1)
+      {
+         result[1] = multibyteMul(result[0..1], x, x[0], 0);
+         return;
+      }
+      return squareSimple(result, x);
+  }
+  // The nice thing about squaring is that it always stays balanced
+  BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(x.length)];
+  squareKaratsuba(result, x, scratchbuff);
+  delete scratchbuff;
+}
+
+
+import core.bitop : bsr;
+
+/// if remainder is null, only calculate quotient.
+void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [] u,
+        const BigDigit [] v) pure nothrow
+{
+    assert(quotient.length == u.length - v.length + 1);
+    assert(remainder == null || remainder.length == v.length);
+    assert(v.length > 1);
+    assert(u.length >= v.length);
+
+    // Normalize by shifting v left just enough so that
+    // its high-order bit is on, and shift u left the
+    // same amount. The highest bit of u will never be set.
+
+    BigDigit [] vn = new BigDigit[v.length];
+    BigDigit [] un = new BigDigit[u.length + 1];
+    // How much to left shift v, so that its MSB is set.
+    uint s = BIGDIGITSHIFTMASK - bsr(v[$-1]);
+    if (s!=0)
+    {
+        multibyteShl(vn, v, s);
+        un[$-1] = multibyteShl(un[0..$-1], u, s);
+    }
+    else
+    {
+        vn[] = v[];
+        un[0..$-1] = u[];
+        un[$-1] = 0;
+    }
+    if (quotient.length<FASTDIVLIMIT)
+    {
+        schoolbookDivMod(quotient, un, vn);
+    }
+    else
+    {
+        blockDivMod(quotient, un, vn);
+    }
+
+    // Unnormalize remainder, if required.
+    if (remainder != null)
+    {
+        if (s == 0) remainder[] = un[0..vn.length];
+        else multibyteShr(remainder, un[0..vn.length+1], s);
+    }
+    delete un;
+    delete vn;
+}
+
+pure unittest
+{
+    immutable(uint) [] u = [0, 0xFFFF_FFFE, 0x8000_0000];
+    immutable(uint) [] v = [0xFFFF_FFFF, 0x8000_0000];
+    uint [] q = new uint[u.length - v.length + 1];
+    uint [] r = new uint[2];
+    divModInternal(q, r, u, v);
+    assert(q[]==[0xFFFF_FFFFu, 0]);
+    assert(r[]==[0xFFFF_FFFFu, 0x7FFF_FFFF]);
+    u = [0, 0xFFFF_FFFE, 0x8000_0001];
+    v = [0xFFFF_FFFF, 0x8000_0000];
+    divModInternal(q, r, u, v);
+}
+
+
+private:
+// Converts a big uint to a hexadecimal string.
+//
+// Optionally, a separator character (eg, an underscore) may be added between
+// every 8 digits.
+// buff.length must be data.length*8 if separator is zero,
+// or data.length*9 if separator is non-zero. It will be completely filled.
+char [] biguintToHex(char [] buff, const BigDigit [] data, char separator=0,
+        LetterCase letterCase = LetterCase.upper) pure nothrow @safe
+{
+    int x=0;
+    for (ptrdiff_t i=data.length - 1; i>=0; --i)
+    {
+        toHexZeroPadded(buff[x..x+8], data[i], letterCase);
+        x+=8;
+        if (separator)
+        {
+            if (i>0) buff[x] = separator;
+            ++x;
+        }
+    }
+    return buff;
+}
+
+/** Convert a big uint into a decimal string.
+ *
+ * Params:
+ *  data    The biguint to be converted. Will be destroyed.
+ *  buff    The destination buffer for the decimal string. Must be
+ *          large enough to store the result, including leading zeros.
+ *          Will be filled backwards, starting from buff[$-1].
+ *
+ * buff.length must be >= (data.length*32)/log2(10) = 9.63296 * data.length.
+ * Returns:
+ *    the lowest index of buff which was used.
+ */
+size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow
+{
+    ptrdiff_t sofar = buff.length;
+    // Might be better to divide by (10^38/2^32) since that gives 38 digits for
+    // the price of 3 divisions and a shr; this version only gives 27 digits
+    // for 3 divisions.
+    while(data.length>1)
+    {
+        uint rem = multibyteDivAssign(data, 10_0000_0000, 0);
+        itoaZeroPadded(buff[sofar-9 .. sofar], rem);
+        sofar -= 9;
+        if (data[$-1] == 0 && data.length > 1)
+        {
+            data.length = data.length - 1;
+        }
+    }
+    itoaZeroPadded(buff[sofar-10 .. sofar], data[0]);
+    sofar -= 10;
+    // and strip off the leading zeros
+    while(sofar!= buff.length-1 && buff[sofar] == '0')
+        sofar++;
+    return sofar;
+}
+
+/** Convert a decimal string into a big uint.
+ *
+ * Params:
+ *  data    The biguint to be receive the result. Must be large enough to
+ *          store the result.
+ *  s       The decimal string. May contain _ or 0..9
+ *
+ * The required length for the destination buffer is slightly less than
+ *  1 + s.length/log2(10) = 1 + s.length/3.3219.
+ *
+ * Returns:
+ *    the highest index of data which was used.
+ */
+int biguintFromDecimal(BigDigit [] data, const(char)[] s) pure
+in
+{
+    assert((data.length >= 2) || (data.length == 1 && s.length == 1));
+}
+body
+{
+    import std.conv : ConvException;
+
+    // Convert to base 1e19 = 10_000_000_000_000_000_000.
+    // (this is the largest power of 10 that will fit into a long).
+    // The length will be less than 1 + s.length/log2(10) = 1 + s.length/3.3219.
+    // 485 bits will only just fit into 146 decimal digits.
+    // As we convert the string, we record the number of digits we've seen in base 19:
+    // hi is the number of digits/19, lo is the extra digits (0 to 18).
+    // TODO: This is inefficient for very large strings (it is O(n^^2)).
+    // We should take advantage of fast multiplication once the numbers exceed
+    // Karatsuba size.
+    uint lo = 0; // number of powers of digits, 0..18
+    uint x = 0;
+    ulong y = 0;
+    uint hi = 0; // number of base 1e19 digits
+    data[0] = 0; // initially number is 0.
+    if (data.length > 1)
+        data[1] = 0;
+
+    for (int i= (s[0]=='-' || s[0]=='+')? 1 : 0; i<s.length; ++i)
+    {
+        if (s[i] == '_')
+            continue;
+        if (s[i] < '0' || s[i] > '9')
+            throw new ConvException("invalid digit");
+        x *= 10;
+        x += s[i] - '0';
+        ++lo;
+        if (lo == 9)
+        {
+            y = x;
+            x = 0;
+        }
+        if (lo == 18)
+        {
+            y *= 10_0000_0000;
+            y += x;
+            x = 0;
+        }
+        if (lo == 19)
+        {
+            y *= 10;
+            y += x;
+            x = 0;
+            // Multiply existing number by 10^19, then add y1.
+            if (hi>0)
+            {
+                data[hi] = multibyteMul(data[0..hi], data[0..hi], 1220703125*2u, 0); // 5^13*2 = 0x9184_E72A
+                ++hi;
+                data[hi] = multibyteMul(data[0..hi], data[0..hi], 15625*262144u, 0); // 5^6*2^18 = 0xF424_0000
+                ++hi;
+            }
+            else
+                hi = 2;
+            uint c = multibyteIncrementAssign!('+')(data[0..hi], cast(uint)(y&0xFFFF_FFFF));
+            c += multibyteIncrementAssign!('+')(data[1..hi], cast(uint)(y>>32));
+            if (c!=0)
+            {
+                data[hi]=c;
+                ++hi;
+            }
+            y = 0;
+            lo = 0;
+        }
+    }
+    // Now set y = all remaining digits.
+    if (lo>=18)
+    {
+    }
+    else if (lo>=9)
+    {
+        for (int k=9; k<lo; ++k) y*=10;
+        y+=x;
+    }
+    else
+    {
+        for (int k=0; k<lo; ++k) y*=10;
+        y+=x;
+    }
+    if (lo != 0)
+    {
+        if (hi == 0)
+        {
+            data[0] = cast(uint)y;
+            if (data.length == 1)
+            {
+                hi = 1;
+            }
+            else
+            {
+                data[1] = cast(uint)(y >>> 32);
+                hi=2;
+            }
+        }
+        else
+        {
+            while (lo>0)
+            {
+                uint c = multibyteMul(data[0..hi], data[0..hi], 10, 0);
+                if (c!=0)
+                {
+                    data[hi]=c;
+                    ++hi;
+                }
+                --lo;
+            }
+            uint c = multibyteIncrementAssign!('+')(data[0..hi], cast(uint)(y&0xFFFF_FFFF));
+            if (y > 0xFFFF_FFFFL)
+            {
+                c += multibyteIncrementAssign!('+')(data[1..hi], cast(uint)(y>>32));
+            }
+            if (c!=0)
+            {
+                data[hi]=c;
+                ++hi;
+            }
+        }
+    }
+    while (hi>1 && data[hi-1]==0)
+        --hi;
+    return hi;
+}
+
+
+private:
+// ------------------------
+// These in-place functions are only for internal use; they are incompatible
+// with COW.
+
+// Classic 'schoolbook' multiplication.
+void mulSimple(BigDigit[] result, const(BigDigit) [] left,
+        const(BigDigit)[] right) pure nothrow
+in
+{
+    assert(result.length == left.length + right.length);
+    assert(right.length>1);
+}
+body
+{
+    result[left.length] = multibyteMul(result[0..left.length], left, right[0], 0);
+    multibyteMultiplyAccumulate(result[1..$], left, right[1..$]);
+}
+
+// Classic 'schoolbook' squaring
+void squareSimple(BigDigit[] result, const(BigDigit) [] x) pure nothrow
+in
+{
+    assert(result.length == 2*x.length);
+    assert(x.length>1);
+}
+body
+{
+    multibyteSquare(result, x);
+}
+
+
+// add two uints of possibly different lengths. Result must be as long
+// as the larger length.
+// Returns carry (0 or 1).
+uint addSimple(BigDigit[] result, const BigDigit [] left, const BigDigit [] right)
+pure nothrow
+in
+{
+    assert(result.length == left.length);
+    assert(left.length >= right.length);
+    assert(right.length>0);
+}
+body
+{
+    uint carry = multibyteAdd(result[0..right.length],
+            left[0..right.length], right, 0);
+    if (right.length < left.length)
+    {
+        result[right.length..left.length] = left[right.length .. $];
+        carry = multibyteIncrementAssign!('+')(result[right.length..$], carry);
+    }
+    return carry;
+}
+
+//  result = left - right
+// returns carry (0 or 1)
+BigDigit subSimple(BigDigit [] result,const(BigDigit) [] left,
+        const(BigDigit) [] right) pure nothrow
+in
+{
+    assert(result.length == left.length);
+    assert(left.length >= right.length);
+    assert(right.length>0);
+}
+body
+{
+    BigDigit carry = multibyteSub(result[0..right.length],
+            left[0..right.length], right, 0);
+    if (right.length < left.length)
+    {
+        result[right.length..left.length] = left[right.length .. $];
+        carry = multibyteIncrementAssign!('-')(result[right.length..$], carry);
+    } //else if (result.length==left.length+1) { result[$-1] = carry; carry=0; }
+    return carry;
+}
+
+
+/* result = result - right
+ * Returns carry = 1 if result was less than right.
+*/
+BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right)
+pure nothrow
+{
+    assert(result.length >= right.length);
+    uint c = multibyteSub(result[0..right.length], result[0..right.length], right, 0);
+    if (c && result.length > right.length)
+        c = multibyteIncrementAssign!('-')(result[right.length .. $], c);
+    return c;
+}
+
+/* result = result + right
+*/
+BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right)
+pure nothrow
+{
+    assert(result.length >= right.length);
+    uint c = multibyteAdd(result[0..right.length], result[0..right.length], right, 0);
+    if (c && result.length > right.length)
+       c = multibyteIncrementAssign!('+')(result[right.length .. $], c);
+    return c;
+}
+
+/* performs result += wantSub? - right : right;
+*/
+BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right,
+        bool wantSub) pure nothrow
+{
+    if (wantSub)
+        return subAssignSimple(result, right);
+    else
+        return addAssignSimple(result, right);
+}
+
+
+// return true if x<y, considering leading zeros
+bool less(const(BigDigit)[] x, const(BigDigit)[] y) pure nothrow
+{
+    assert(x.length >= y.length);
+    auto k = x.length-1;
+    while(x[k]==0 && k>=y.length)
+        --k;
+    if (k>=y.length)
+        return false;
+    while (k>0 && x[k]==y[k])
+        --k;
+    return x[k] < y[k];
+}
+
+// Set result = abs(x-y), return true if result is negative(x<y), false if x<=y.
+bool inplaceSub(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y)
+    pure nothrow
+{
+    assert(result.length == (x.length >= y.length) ? x.length : y.length);
+
+    size_t minlen;
+    bool negative;
+    if (x.length >= y.length)
+    {
+        minlen = y.length;
+        negative = less(x, y);
+    }
+    else
+    {
+       minlen = x.length;
+       negative = !less(y, x);
+    }
+    const (BigDigit)[] large, small;
+    if (negative)
+    {
+        large = y; small = x;
+    }
+    else
+    {
+        large = x; small = y;
+    }
+
+    BigDigit carry = multibyteSub(result[0..minlen], large[0..minlen], small[0..minlen], 0);
+    if (x.length != y.length)
+    {
+        result[minlen..large.length]= large[minlen..$];
+        result[large.length..$] = 0;
+        if (carry)
+            multibyteIncrementAssign!('-')(result[minlen..$], carry);
+    }
+    return negative;
+}
+
+/* Determine how much space is required for the temporaries
+ * when performing a Karatsuba multiplication.
+ */
+size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow @safe
+{
+    return xlen <= KARATSUBALIMIT ? 0 : 2*xlen; // - KARATSUBALIMIT+2;
+}
+
+/* Sets result = x*y, using Karatsuba multiplication.
+* x must be longer or equal to y.
+* Valid only for balanced multiplies, where x is not shorter than y.
+* It is superior to schoolbook multiplication if and only if
+*    sqrt(2)*y.length > x.length > y.length.
+* Karatsuba multiplication is O(n^1.59), whereas schoolbook is O(n^2)
+* The maximum allowable length of x and y is uint.max; but better algorithms
+* should be used far before that length is reached.
+* Params:
+* scratchbuff      An array long enough to store all the temporaries. Will be destroyed.
+*/
+void mulKaratsuba(BigDigit [] result, const(BigDigit) [] x,
+        const(BigDigit)[] y, BigDigit [] scratchbuff) pure nothrow
+{
+    assert(x.length >= y.length);
+          assert(result.length < uint.max, "Operands too large");
+    assert(result.length == x.length + y.length);
+    if (x.length <= KARATSUBALIMIT)
+    {
+        return mulSimple(result, x, y);
+    }
+    // Must be almost square (otherwise, a schoolbook iteration is better)
+    assert(2L * y.length * y.length > (x.length-1) * (x.length-1),
+        "Bigint Internal Error: Asymmetric Karatsuba");
+
+    // The subtractive version of Karatsuba multiply uses the following result:
+    // (Nx1 + x0)*(Ny1 + y0) = (N*N)*x1y1 + x0y0 + N * (x0y0 + x1y1 - mid)
+    // where mid = (x0-x1)*(y0-y1)
+    // requiring 3 multiplies of length N, instead of 4.
+    // The advantage of the subtractive over the additive version is that
+    // the mid multiply cannot exceed length N. But there are subtleties:
+    // (x0-x1),(y0-y1) may be negative or zero. To keep it simple, we
+    // retain all of the leading zeros in the subtractions
+
+    // half length, round up.
+    auto half = (x.length >> 1) + (x.length & 1);
+
+    const(BigDigit) [] x0 = x[0 .. half];
+    const(BigDigit) [] x1 = x[half .. $];
+    const(BigDigit) [] y0 = y[0 .. half];
+    const(BigDigit) [] y1 = y[half .. $];
+    BigDigit [] mid = scratchbuff[0 .. half*2];
+    BigDigit [] newscratchbuff = scratchbuff[half*2 .. $];
+    BigDigit [] resultLow = result[0 .. 2*half];
+    BigDigit [] resultHigh = result[2*half .. $];
+     // initially use result to store temporaries
+    BigDigit [] xdiff= result[0 .. half];
+    BigDigit [] ydiff = result[half .. half*2];
+
+    // First, we calculate mid, and sign of mid
+    bool midNegative = inplaceSub(xdiff, x0, x1)
+                      ^ inplaceSub(ydiff, y0, y1);
+    mulKaratsuba(mid, xdiff, ydiff, newscratchbuff);
+
+    // Low half of result gets x0 * y0. High half gets x1 * y1
+
+    mulKaratsuba(resultLow, x0, y0, newscratchbuff);
+
+    if (2L * y1.length * y1.length < x1.length * x1.length)
+    {
+        // an asymmetric situation has been created.
+        // Worst case is if x:y = 1.414 : 1, then x1:y1 = 2.41 : 1.
+        // Applying one schoolbook multiply gives us two pieces each 1.2:1
+        if (y1.length <= KARATSUBALIMIT)
+            mulSimple(resultHigh, x1, y1);
+        else
+        {
+            // divide x1 in two, then use schoolbook multiply on the two pieces.
+            auto quarter = (x1.length >> 1) + (x1.length & 1);
+            bool ysmaller = (quarter >= y1.length);
+            mulKaratsuba(resultHigh[0..quarter+y1.length], ysmaller ? x1[0..quarter] : y1,
+                ysmaller ? y1 : x1[0..quarter], newscratchbuff);
+            // Save the part which will be overwritten.
+            bool ysmaller2 = ((x1.length - quarter) >= y1.length);
+            newscratchbuff[0..y1.length] = resultHigh[quarter..quarter + y1.length];
+            mulKaratsuba(resultHigh[quarter..$], ysmaller2 ? x1[quarter..$] : y1,
+                ysmaller2 ? y1 : x1[quarter..$], newscratchbuff[y1.length..$]);
+
+            resultHigh[quarter..$].addAssignSimple(newscratchbuff[0..y1.length]);
+        }
+    }
+    else
+        mulKaratsuba(resultHigh, x1, y1, newscratchbuff);
+
+    /* We now have result = x0y0 + (N*N)*x1y1
+       Before adding or subtracting mid, we must calculate
+       result += N * (x0y0 + x1y1)
+       We can do this with three half-length additions. With a = x0y0, b = x1y1:
+                      aHI aLO
+        +       aHI   aLO
+        +       bHI   bLO
+        +  bHI  bLO
+        =  R3   R2    R1   R0
+        R1 = aHI + bLO + aLO
+        R2 = aHI + bLO + aHI + carry_from_R1
+        R3 = bHi + carry_from_R2
+
+     It might actually be quicker to do it in two full-length additions:
+     newscratchbuff[2*half] = addSimple(newscratchbuff[0..2*half], result[0..2*half], result[2*half..$]);
+     addAssignSimple(result[half..$], newscratchbuff[0..2*half+1]);
+   */
+    BigDigit[] R1 = result[half..half*2];
+    BigDigit[] R2 = result[half*2..half*3];
+    BigDigit[] R3 = result[half*3..$];
+    BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1
+    BigDigit c2 = multibyteAdd(R1, R2, result[0..half], 0); // c2:R1 = R2 + R1 + R0
+    BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3
+    if (c1+c2)
+        multibyteIncrementAssign!('+')(result[half*2..$], c1+c2);
+    if (c1+c3)
+        multibyteIncrementAssign!('+')(R3, c1+c3);
+
+    // And finally we subtract mid
+    addOrSubAssignSimple(result[half..$], mid, !midNegative);
+}
+
+void squareKaratsuba(BigDigit [] result, const BigDigit [] x,
+        BigDigit [] scratchbuff) pure nothrow
+{
+    // See mulKaratsuba for implementation comments.
+    // Squaring is simpler, since it never gets asymmetric.
+    assert(result.length < uint.max, "Operands too large");
+    assert(result.length == 2*x.length);
+    if (x.length <= KARATSUBASQUARELIMIT)
+    {
+        return squareSimple(result, x);
+    }
+    // half length, round up.
+    auto half = (x.length >> 1) + (x.length & 1);
+
+    const(BigDigit)[] x0 = x[0 .. half];
+    const(BigDigit)[] x1 = x[half .. $];
+    BigDigit [] mid = scratchbuff[0 .. half*2];
+    BigDigit [] newscratchbuff = scratchbuff[half*2 .. $];
+     // initially use result to store temporaries
+    BigDigit [] xdiff= result[0 .. half];
+    BigDigit [] ydiff = result[half .. half*2];
+
+    // First, we calculate mid. We don't need its sign
+    inplaceSub(xdiff, x0, x1);
+    squareKaratsuba(mid, xdiff, newscratchbuff);
+
+    // Set result = x0x0 + (N*N)*x1x1
+    squareKaratsuba(result[0 .. 2*half], x0, newscratchbuff);
+    squareKaratsuba(result[2*half .. $], x1, newscratchbuff);
+
+    /* result += N * (x0x0 + x1x1)
+       Do this with three half-length additions. With a = x0x0, b = x1x1:
+        R1 = aHI + bLO + aLO
+        R2 = aHI + bLO + aHI + carry_from_R1
+        R3 = bHi + carry_from_R2
+    */
+    BigDigit[] R1 = result[half..half*2];
+    BigDigit[] R2 = result[half*2..half*3];
+    BigDigit[] R3 = result[half*3..$];
+    BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1
+    BigDigit c2 = multibyteAdd(R1, R2, result[0..half], 0); // c2:R1 = R2 + R1 + R0
+    BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3
+    if (c1+c2) multibyteIncrementAssign!('+')(result[half*2..$], c1+c2);
+    if (c1+c3) multibyteIncrementAssign!('+')(R3, c1+c3);
+
+    // And finally we subtract mid, which is always positive
+    subAssignSimple(result[half..$], mid);
+}
+
+/* Knuth's Algorithm D, as presented in
+ * H.S. Warren, "Hacker's Delight", Addison-Wesley Professional (2002).
+ * Also described in "Modern Computer Arithmetic" 0.2, Exercise 1.8.18.
+ * Given u and v, calculates  quotient  = u / v, u = u % v.
+ * v must be normalized (ie, the MSB of v must be 1).
+ * The most significant words of quotient and u may be zero.
+ * u[0..v.length] holds the remainder.
+ */
+void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v)
+    pure nothrow
+{
+    assert(quotient.length == u.length - v.length);
+    assert(v.length > 1);
+    assert(u.length >= v.length);
+    assert((v[$-1]&0x8000_0000)!=0);
+    assert(u[$-1] < v[$-1]);
+    // BUG: This code only works if BigDigit is uint.
+    uint vhi = v[$-1];
+    uint vlo = v[$-2];
+
+    for (ptrdiff_t j = u.length - v.length - 1; j >= 0; j--)
+    {
+        // Compute estimate of quotient[j],
+        // qhat = (three most significant words of u)/(two most sig words of v).
+        uint qhat;
+        if (u[j + v.length] == vhi)
+        {
+            // uu/vhi could exceed uint.max (it will be 0x8000_0000 or 0x8000_0001)
+            qhat = uint.max;
+        }
+        else
+        {
+            uint ulo = u[j + v.length - 2];
+            version(D_InlineAsm_X86)
+            {
+                // Note: On DMD, this is only ~10% faster than the non-asm code.
+                uint *p = &u[j + v.length - 1];
+                asm pure nothrow
+                {
+                    mov EAX, p;
+                    mov EDX, [EAX+4];
+                    mov EAX, [EAX];
+                    div dword ptr [vhi];
+                    mov qhat, EAX;
+                    mov ECX, EDX;
+div3by2correction:
+                    mul dword ptr [vlo]; // EDX:EAX = qhat * vlo
+                    sub EAX, ulo;
+                    sbb EDX, ECX;
+                    jbe div3by2done;
+                    mov EAX, qhat;
+                    dec EAX;
+                    mov qhat, EAX;
+                    add ECX, dword ptr [vhi];
+                    jnc div3by2correction;
+div3by2done:    ;
+                }
+            }
+            else
+            { // version(InlineAsm)
+                ulong uu = (cast(ulong)(u[j + v.length]) << 32) | u[j + v.length - 1];
+                ulong bigqhat = uu / vhi;
+                ulong rhat =  uu - bigqhat * vhi;
+                qhat = cast(uint)bigqhat;
+again:
+                if (cast(ulong)qhat * vlo > ((rhat << 32) + ulo))
+                {
+                    --qhat;
+                    rhat += vhi;
+                    if (!(rhat & 0xFFFF_FFFF_0000_0000L))
+                        goto again;
+                }
+            } // version(InlineAsm)
+        }
+        // Multiply and subtract.
+        uint carry = multibyteMulAdd!('-')(u[j..j + v.length], v, qhat, 0);
+
+        if (u[j+v.length] < carry)
+        {
+            // If we subtracted too much, add back
+            --qhat;
+            carry -= multibyteAdd(u[j..j + v.length],u[j..j + v.length], v, 0);
+        }
+        quotient[j] = qhat;
+        u[j + v.length] = u[j + v.length] - carry;
+    }
+}
+
+private:
+
+// TODO: Replace with a library call
+void itoaZeroPadded(char[] output, uint value)
+    pure nothrow @safe @nogc
+{
+    for (auto i = output.length; i--;)
+    {
+        if (value < 10)
+        {
+            output[i] = cast(char)(value + '0');
+            value = 0;
+        }
+        else
+        {
+            output[i] = cast(char)(value % 10 + '0');
+            value /= 10;
+        }
+    }
+}
+
+void toHexZeroPadded(char[] output, uint value,
+        LetterCase letterCase = LetterCase.upper) pure nothrow @safe
+{
+    ptrdiff_t x = output.length - 1;
+    static immutable string upperHexDigits = "0123456789ABCDEF";
+    static immutable string lowerHexDigits = "0123456789abcdef";
+    for( ; x>=0; --x)
+    {
+        if (letterCase == LetterCase.upper)
+        {
+            output[x] = upperHexDigits[value & 0xF];
+        }
+        else
+        {
+            output[x] = lowerHexDigits[value & 0xF];
+        }
+        value >>= 4;
+    }
+}
+
+private:
+
+// Returns the highest value of i for which left[i]!=right[i],
+// or 0 if left[] == right[]
+size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right)
+pure nothrow @nogc @safe
+{
+    assert(left.length == right.length);
+    for (ptrdiff_t i = left.length - 1; i>0; --i)
+    {
+        if (left[i] != right[i])
+            return i;
+    }
+    return 0;
+}
+
+// Returns the lowest value of i for which x[i]!=0.
+int firstNonZeroDigit(const BigDigit [] x) pure nothrow @nogc @safe
+{
+    int k = 0;
+    while (x[k]==0)
+    {
+        ++k;
+        assert(k<x.length);
+    }
+    return k;
+}
+
+/*
+    Calculate quotient and remainder of u / v using fast recursive division.
+    v must be normalised, and must be at least half as long as u.
+    Given u and v, v normalised, calculates  quotient  = u/v, u = u%v.
+    scratch is temporary storage space, length must be >= quotient + 1.
+
+Returns:
+    u[0..v.length] is the remainder. u[v.length..$] is corrupted.
+
+    Implements algorithm 1.8 from MCA.
+    This algorithm has an annoying special case. After the first recursion, the
+    highest bit of the quotient may be set. This means that in the second
+    recursive call, the 'in' contract would be violated. (This happens only
+    when the top quarter of u is equal to the top half of v. A base 10
+    equivalent example of this situation is 5517/56; the first step gives
+    55/5 = 11). To maintain the in contract, we pad a zero to the top of both
+    u and the quotient. 'mayOverflow' indicates that that the special case
+    has occurred.
+    (In MCA, a different strategy is used: the in contract is weakened, and
+    schoolbookDivMod is more general: it allows the high bit of u to be set).
+    See also:
+    - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022,
+      Max-Planck Institute fuer Informatik, (Oct 1998).
+*/
+void recursiveDivMod(BigDigit[] quotient, BigDigit[] u, const(BigDigit)[] v,
+                     BigDigit[] scratch, bool mayOverflow = false)
+                     pure nothrow
+in
+{
+    // v must be normalized
+    assert(v.length > 1);
+    assert((v[$ - 1] & 0x8000_0000) != 0);
+    assert(!(u[$ - 1] & 0x8000_0000));
+    assert(quotient.length == u.length - v.length);
+    if (mayOverflow)
+    {
+        assert(u[$-1] == 0);
+        assert(u[$-2] & 0x8000_0000);
+    }
+
+    // Must be symmetric. Use block schoolbook division if not.
+    assert((mayOverflow ? u.length-1 : u.length) <= 2 * v.length);
+    assert((mayOverflow ? u.length-1 : u.length) >= v.length);
+    assert(scratch.length >= quotient.length + (mayOverflow ? 0 : 1));
+}
+body
+{
+    if (quotient.length < FASTDIVLIMIT)
+    {
+        return schoolbookDivMod(quotient, u, v);
+    }
+
+    // Split quotient into two halves, but keep padding in the top half
+    auto k = (mayOverflow ?  quotient.length - 1 : quotient.length) >> 1;
+
+    // RECURSION 1: Calculate the high half of the quotient
+
+    // Note that if u and quotient were padded, they remain padded during
+    // this call, so in contract is satisfied.
+    recursiveDivMod(quotient[k .. $], u[2 * k .. $], v[k .. $],
+        scratch, mayOverflow);
+
+    // quotient[k..$] is our guess at the high quotient.
+    // u[2*k.. 2.*k + v.length - k = k + v.length] is the high part of the
+    // first remainder. u[0..2*k] is the low part.
+
+    // Calculate the full first remainder to be
+    //    remainder - highQuotient * lowDivisor
+    // reducing highQuotient until the remainder is positive.
+    // The low part of the remainder, u[0..k], cannot be altered by this.
+
+    adjustRemainder(quotient[k .. $], u[k .. k + v.length], v, k,
+            scratch[0 .. quotient.length], mayOverflow);
+
+    // RECURSION 2: Calculate the low half of the quotient
+    // The full first remainder is now in u[0..k + v.length].
+
+    if (u[k + v.length - 1] & 0x8000_0000)
+    {
+        // Special case. The high quotient is 0x1_00...000 or 0x1_00...001.
+        // This means we need an extra quotient word for the next recursion.
+        // We need to restore the invariant for the recursive calls.
+        // We do this by padding both u and quotient. Extending u is trivial,
+        // because the higher words will not be used again. But for the
+        // quotient, we're clobbering the low word of the high quotient,
+        // so we need save it, and add it back in after the recursive call.
+
+        auto clobberedQuotient = quotient[k];
+        u[k+v.length] = 0;
+
+        recursiveDivMod(quotient[0 .. k+1], u[k .. k + v.length+1],
+            v[k .. $], scratch, true);
+        adjustRemainder(quotient[0 .. k+1], u[0 .. v.length], v, k,
+            scratch[0 .. 2 * k+1], true);
+
+        // Now add the quotient word that got clobbered earlier.
+        multibyteIncrementAssign!('+')(quotient[k..$], clobberedQuotient);
+    }
+    else
+    {
+        // The special case has NOT happened.
+        recursiveDivMod(quotient[0 .. k], u[k .. k + v.length], v[k .. $],
+            scratch, false);
+
+        // high remainder is in u[k..k+(v.length-k)] == u[k .. v.length]
+
+        adjustRemainder(quotient[0 .. k], u[0 .. v.length], v, k,
+            scratch[0 .. 2 * k]);
+    }
+}
+
+// rem -= quot * v[0..k].
+// If would make rem negative, decrease quot until rem is >=0.
+// Needs (quot.length * k) scratch space to store the result of the multiply.
+void adjustRemainder(BigDigit[] quot, BigDigit[] rem, const(BigDigit)[] v,
+        ptrdiff_t k,
+        BigDigit[] scratch, bool mayOverflow = false) pure nothrow
+{
+    assert(rem.length == v.length);
+    mulInternal(scratch, quot, v[0 .. k]);
+    uint carry = 0;
+    if (mayOverflow)
+        carry = scratch[$-1] + subAssignSimple(rem, scratch[0..$-1]);
+    else
+        carry = subAssignSimple(rem, scratch);
+    while(carry)
+    {
+        multibyteIncrementAssign!('-')(quot, 1); // quot--
+        carry -= multibyteAdd(rem, rem, v, 0);
+    }
+}
+
+// Cope with unbalanced division by performing block schoolbook division.
+void blockDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v)
+pure nothrow
+{
+    assert(quotient.length == u.length - v.length);
+    assert(v.length > 1);
+    assert(u.length >= v.length);
+    assert((v[$-1] & 0x8000_0000)!=0);
+    assert((u[$-1] & 0x8000_0000)==0);
+    BigDigit [] scratch = new BigDigit[v.length + 1];
+
+    // Perform block schoolbook division, with 'v.length' blocks.
+    auto m = u.length - v.length;
+    while (m > v.length)
+    {
+        bool mayOverflow = (u[m + v.length -1 ] & 0x8000_0000)!=0;
+        BigDigit saveq;
+        if (mayOverflow)
+        {
+            u[m + v.length] = 0;
+            saveq = quotient[m];
+        }
+        recursiveDivMod(quotient[m-v.length..m + (mayOverflow? 1: 0)],
+            u[m - v.length..m + v.length + (mayOverflow? 1: 0)], v, scratch, mayOverflow);
+        if (mayOverflow)
+        {
+            assert(quotient[m] == 0);
+            quotient[m] = saveq;
+        }
+        m -= v.length;
+    }
+    recursiveDivMod(quotient[0..m], u[0..m + v.length], v, scratch);
+    delete scratch;
+}
+
+unittest
+{
+    import core.stdc.stdio;
+
+    void printBiguint(const uint [] data)
+    {
+        char [] buff = biguintToHex(new char[data.length*9], data, '_');
+        printf("%.*s\n", buff.length, buff.ptr);
+    }
+
+    void printDecimalBigUint(BigUint data)
+    {
+        auto str = data.toDecimalString(0);
+        printf("%.*s\n", str.length, str.ptr);
+    }
+
+    uint [] a, b;
+    a = new uint[43];
+    b = new uint[179];
+    for (int i=0; i<a.length; ++i) a[i] = 0x1234_B6E9 + i;
+    for (int i=0; i<b.length; ++i) b[i] = 0x1BCD_8763 - i*546;
+
+    a[$-1] |= 0x8000_0000;
+    uint [] r = new uint[a.length];
+    uint [] q = new uint[b.length-a.length+1];
+
+    divModInternal(q, r, b, a);
+    q = q[0..$-1];
+    uint [] r1 = r.dup;
+    uint [] q1 = q.dup;
+    blockDivMod(q, b, a);
+    r = b[0..a.length];
+    assert(r[] == r1[]);
+    assert(q[] == q1[]);
+}
diff --git a/libphobos/src/std/internal/math/biguintnoasm.d b/libphobos/src/std/internal/math/biguintnoasm.d
new file mode 100644
index 00000000000..035aad4b998
--- /dev/null
+++ b/libphobos/src/std/internal/math/biguintnoasm.d
@@ -0,0 +1,369 @@
+/** Arbitrary precision arithmetic ('bignum') for processors with no asm support
+ *
+ * All functions operate on arrays of uints, stored LSB first.
+ * If there is a destination array, it will be the first parameter.
+ * Currently, all of these functions are subject to change, and are
+ * intended for internal use only.
+ * This module is intended only to assist development of high-speed routines
+ * on currently unsupported processors.
+ * The X86 asm version is about 30 times faster than the D version(DMD).
+ */
+
+/*          Copyright Don Clugston 2008 - 2010.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+module std.internal.math.biguintnoasm;
+
+nothrow:
+@safe:
+
+public:
+alias BigDigit = uint; // A Bignum is an array of BigDigits.
+
+    // Limits for when to switch between multiplication algorithms.
+enum : int { KARATSUBALIMIT = 10 }; // Minimum value for which Karatsuba is worthwhile.
+enum : int { KARATSUBASQUARELIMIT=12 }; // Minimum value for which square Karatsuba is worthwhile
+
+
+/** Multi-byte addition or subtraction
+ *    dest[] = src1[] + src2[] + carry (0 or 1).
+ * or dest[] = src1[] - src2[] - carry (0 or 1).
+ * Returns carry or borrow (0 or 1).
+ * Set op == '+' for addition, '-' for subtraction.
+ */
+uint multibyteAddSub(char op)(uint[] dest, const(uint) [] src1,
+    const (uint) [] src2, uint carry) pure @nogc @safe
+{
+    ulong c = carry;
+    for (size_t i = 0; i < src2.length; ++i)
+    {
+        static if (op=='+') c = c  + src1[i] + src2[i];
+             else           c = cast(ulong)src1[i] - src2[i] - c;
+        dest[i] = cast(uint)c;
+        c = (c > 0xFFFF_FFFF);
+    }
+    return cast(uint)c;
+}
+
+unittest
+{
+    uint [] a = new uint[40];
+    uint [] b = new uint[40];
+    uint [] c = new uint[40];
+    for (size_t i = 0; i < a.length; ++i)
+    {
+        if (i&1) a[i]=cast(uint)(0x8000_0000 + i);
+        else a[i]=cast(uint)i;
+        b[i]= 0x8000_0003;
+    }
+    c[19]=0x3333_3333;
+    uint carry = multibyteAddSub!('+')(c[0..18], b[0..18], a[0..18], 0);
+    assert(c[0]==0x8000_0003);
+    assert(c[1]==4);
+    assert(c[19]==0x3333_3333); // check for overrun
+    assert(carry==1);
+    for (size_t i = 0; i < a.length; ++i)
+    {
+        a[i] = b[i] = c[i] = 0;
+    }
+    a[8]=0x048D159E;
+    b[8]=0x048D159E;
+    a[10]=0x1D950C84;
+    b[10]=0x1D950C84;
+    a[5] =0x44444444;
+    carry = multibyteAddSub!('-')(a[0..12], a[0..12], b[0..12], 0);
+    assert(a[11] == 0);
+    for (size_t i = 0; i < 10; ++i)
+        if (i != 5)
+            assert(a[i] == 0);
+
+    for (size_t q = 3; q < 36; ++q)
+    {
+        for (size_t i = 0; i< a.length; ++i)
+        {
+            a[i] = b[i] = c[i] = 0;
+        }
+        a[q-2]=0x040000;
+        b[q-2]=0x040000;
+       carry = multibyteAddSub!('-')(a[0..q], a[0..q], b[0..q], 0);
+       assert(a[q-2]==0);
+    }
+}
+
+
+
+/** dest[] += carry, or dest[] -= carry.
+ *  op must be '+' or '-'
+ *  Returns final carry or borrow (0 or 1)
+ */
+uint multibyteIncrementAssign(char op)(uint[] dest, uint carry)
+    pure @nogc @safe
+{
+    static if (op=='+')
+    {
+        ulong c = carry;
+        c += dest[0];
+        dest[0] = cast(uint)c;
+        if (c<=0xFFFF_FFFF)
+            return 0;
+
+        for (size_t i = 1; i < dest.length; ++i)
+        {
+            ++dest[i];
+            if (dest[i] != 0)
+                return 0;
+        }
+        return 1;
+    }
+    else
+    {
+        ulong c = carry;
+        c = dest[0] - c;
+        dest[0] = cast(uint)c;
+        if (c<=0xFFFF_FFFF)
+            return 0;
+        for (size_t i = 1; i < dest.length; ++i)
+        {
+            --dest[i];
+            if (dest[i] != 0xFFFF_FFFF)
+                return 0;
+        }
+        return 1;
+    }
+}
+
+/** dest[] = src[] << numbits
+ *  numbits must be in the range 1..31
+ */
+uint multibyteShl(uint [] dest, const(uint) [] src, uint numbits)
+    pure @nogc @safe
+{
+    ulong c = 0;
+    for (size_t i = 0; i < dest.length; ++i)
+    {
+        c += (cast(ulong)(src[i]) << numbits);
+        dest[i] = cast(uint)c;
+        c >>>= 32;
+   }
+   return cast(uint)c;
+}
+
+
+/** dest[] = src[] >> numbits
+ *  numbits must be in the range 1..31
+ */
+void multibyteShr(uint [] dest, const(uint) [] src, uint numbits)
+    pure @nogc @safe
+{
+    ulong c = 0;
+    for(ptrdiff_t i = dest.length; i!=0; --i)
+    {
+        c += (src[i-1] >>numbits) + (cast(ulong)(src[i-1]) << (64 - numbits));
+        dest[i-1] = cast(uint)c;
+        c >>>= 32;
+   }
+}
+
+unittest
+{
+
+    uint [] aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteShr(aa[0..$-2], aa, 4);
+    assert(aa[0] == 0x6122_2222 && aa[1] == 0xA455_5555 && aa[2] == 0x0899_9999);
+    assert(aa[3] == 0xBCCC_CCCD);
+
+    aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteShr(aa[0..$-1], aa, 4);
+    assert(aa[0] == 0x6122_2222 && aa[1] == 0xA455_5555
+        && aa[2] == 0xD899_9999 && aa[3] == 0x0BCC_CCCC);
+
+    aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD,
+        0xEEEE_EEEE];
+    multibyteShl(aa[1..4], aa[1..$], 4);
+    assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230
+        && aa[2]==0x5555_5561 && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD);
+}
+
+/** dest[] = src[] * multiplier + carry.
+ * Returns carry.
+ */
+uint multibyteMul(uint[] dest, const(uint)[] src, uint multiplier, uint carry)
+    pure @nogc @safe
+{
+    assert(dest.length == src.length);
+    ulong c = carry;
+    for(size_t i = 0; i < src.length; ++i)
+    {
+        c += cast(ulong)(src[i]) * multiplier;
+        dest[i] = cast(uint)c;
+        c>>=32;
+    }
+    return cast(uint)c;
+}
+
+unittest
+{
+    uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A,
+        0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteMul(aa[1..4], aa[1..4], 16, 0);
+    assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230 && aa[2]==0x5555_5561
+        && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD);
+}
+
+/**
+ * dest[] += src[] * multiplier + carry(0..FFFF_FFFF).
+ * Returns carry out of MSB (0..FFFF_FFFF).
+ */
+uint multibyteMulAdd(char op)(uint [] dest, const(uint)[] src,
+    uint multiplier, uint carry) pure @nogc @safe
+{
+    assert(dest.length == src.length);
+    ulong c = carry;
+    for(size_t i = 0; i < src.length; ++i)
+    {
+        static if(op=='+')
+        {
+            c += cast(ulong)(multiplier) * src[i]  + dest[i];
+            dest[i] = cast(uint)c;
+            c >>= 32;
+        }
+        else
+        {
+            c += cast(ulong)multiplier * src[i];
+            ulong t = cast(ulong)dest[i] - cast(uint)c;
+            dest[i] = cast(uint)t;
+            c = cast(uint)((c>>32) - (t>>32));
+        }
+    }
+    return cast(uint)c;
+}
+
+unittest
+{
+
+    uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A,
+        0xBCCC_CCCD, 0xEEEE_EEEE];
+    uint [] bb = [0x1234_1234, 0xF0F0_F0F0, 0x00C0_C0C0, 0xF0F0_F0F0,
+        0xC0C0_C0C0];
+    multibyteMulAdd!('+')(bb[1..$-1], aa[1..$-2], 16, 5);
+    assert(bb[0] == 0x1234_1234 && bb[4] == 0xC0C0_C0C0);
+    assert(bb[1] == 0x2222_2230 + 0xF0F0_F0F0 + 5
+        && bb[2] == 0x5555_5561 + 0x00C0_C0C0 + 1
+        && bb[3] == 0x9999_99A4 + 0xF0F0_F0F0 );
+}
+
+
+/**
+   Sets result = result[0..left.length] + left * right
+
+   It is defined in this way to allow cache-efficient multiplication.
+   This function is equivalent to:
+    ----
+    for (size_t i = 0; i< right.length; ++i) {
+        dest[left.length + i] = multibyteMulAdd(dest[i..left.length+i],
+                left, right[i], 0);
+    }
+    ----
+ */
+void multibyteMultiplyAccumulate(uint [] dest, const(uint)[] left, const(uint)
+        [] right) pure @nogc @safe
+{
+    for (size_t i = 0; i < right.length; ++i)
+    {
+        dest[left.length + i] = multibyteMulAdd!('+')(dest[i..left.length+i],
+                left, right[i], 0);
+    }
+}
+
+/**  dest[] /= divisor.
+ * overflow is the initial remainder, and must be in the range 0..divisor-1.
+ */
+uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow)
+    pure @nogc @safe
+{
+    ulong c = cast(ulong)overflow;
+    for(ptrdiff_t i = dest.length-1; i>= 0; --i)
+    {
+        c = (c<<32) + cast(ulong)(dest[i]);
+        uint q = cast(uint)(c/divisor);
+        c -= divisor * q;
+        dest[i] = q;
+    }
+    return cast(uint)c;
+}
+
+unittest
+{
+    uint [] aa = new uint[101];
+    for (uint i = 0; i < aa.length; ++i)
+        aa[i] = 0x8765_4321 * (i+3);
+    uint overflow = multibyteMul(aa, aa, 0x8EFD_FCFB, 0x33FF_7461);
+    uint r = multibyteDivAssign(aa, 0x8EFD_FCFB, overflow);
+    for (uint i=0; i<aa.length; ++i)
+    {
+        assert(aa[i] == 0x8765_4321 * (i+3));
+    }
+    assert(r == 0x33FF_7461);
+
+}
+// Set dest[2*i..2*i+1]+=src[i]*src[i]
+void multibyteAddDiagonalSquares(uint[] dest, const(uint)[] src)
+    pure @nogc @safe
+{
+    ulong c = 0;
+    for(size_t i = 0; i < src.length; ++i)
+    {
+        // At this point, c is 0 or 1, since FFFF*FFFF+FFFF_FFFF = 1_0000_0000.
+        c += cast(ulong)(src[i]) * src[i] + dest[2*i];
+        dest[2*i] = cast(uint)c;
+        c = (c>>=32) + dest[2*i+1];
+        dest[2*i+1] = cast(uint)c;
+        c >>= 32;
+    }
+}
+
+// Does half a square multiply. (square = diagonal + 2*triangle)
+void multibyteTriangleAccumulate(uint[] dest, const(uint)[] x)
+    pure @nogc @safe
+{
+    // x[0]*x[1...$] + x[1]*x[2..$] + ... + x[$-2]x[$-1..$]
+    dest[x.length] = multibyteMul(dest[1 .. x.length], x[1..$], x[0], 0);
+    if (x.length < 4)
+    {
+        if (x.length == 3)
+        {
+            ulong c = cast(ulong)(x[$-1]) * x[$-2]  + dest[2*x.length-3];
+            dest[2*x.length - 3] = cast(uint)c;
+            c >>= 32;
+            dest[2*x.length - 2] = cast(uint)c;
+        }
+        return;
+    }
+    for (size_t i = 2; i < x.length - 2; ++i)
+    {
+        dest[i-1+ x.length] = multibyteMulAdd!('+')(
+             dest[i+i-1 .. i+x.length-1], x[i..$], x[i-1], 0);
+    }
+        // Unroll the last two entries, to reduce loop overhead:
+    ulong  c = cast(ulong)(x[$-3]) * x[$-2] + dest[2*x.length-5];
+    dest[2*x.length-5] = cast(uint)c;
+    c >>= 32;
+    c += cast(ulong)(x[$-3]) * x[$-1] + dest[2*x.length-4];
+    dest[2*x.length-4] = cast(uint)c;
+    c >>= 32;
+    c += cast(ulong)(x[$-1]) * x[$-2];
+    dest[2*x.length-3] = cast(uint)c;
+    c >>= 32;
+    dest[2*x.length-2] = cast(uint)c;
+}
+
+void multibyteSquare(BigDigit[] result, const(BigDigit) [] x) pure @nogc @safe
+{
+    multibyteTriangleAccumulate(result, x);
+    result[$-1] = multibyteShl(result[1..$-1], result[1..$-1], 1); // mul by 2
+    result[0] = 0;
+    multibyteAddDiagonalSquares(result, x);
+}
diff --git a/libphobos/src/std/internal/math/biguintx86.d b/libphobos/src/std/internal/math/biguintx86.d
new file mode 100644
index 00000000000..31f2b90c6b8
--- /dev/null
+++ b/libphobos/src/std/internal/math/biguintx86.d
@@ -0,0 +1,1334 @@
+/** Optimised asm arbitrary precision arithmetic ('bignum')
+ * routines for X86 processors.
+ *
+ * All functions operate on arrays of uints, stored LSB first.
+ * If there is a destination array, it will be the first parameter.
+ * Currently, all of these functions are subject to change, and are
+ * intended for internal use only.
+ * The symbol [#] indicates an array of machine words which is to be
+ * interpreted as a multi-byte number.
+ */
+
+/*          Copyright Don Clugston 2008 - 2010.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+/**
+ * In simple terms, there are 3 modern x86 microarchitectures:
+ * (a) the P6 family (Pentium Pro, PII, PIII, PM, Core), produced by Intel;
+ * (b) the K6, Athlon, and AMD64 families, produced by AMD; and
+ * (c) the Pentium 4, produced by Marketing.
+ *
+ * This code has been optimised for the Intel P6 family.
+ * Generally the code remains near-optimal for Intel Core2/Corei7, after
+ * translating EAX-> RAX, etc, since all these CPUs use essentially the same
+ * pipeline, and are typically limited by memory access.
+ * The code uses techniques described in Agner Fog's superb Pentium manuals
+ * available at www.agner.org.
+ * Not optimised for AMD, which can do two memory loads per cycle (Intel
+ * CPUs can only do one). Despite this, performance is superior on AMD.
+ * Performance is dreadful on P4.
+ *
+ *  Timing results (cycles per int)
+ *              --Intel Pentium--  --AMD--
+ *              PM     P4   Core2   K7
+ *  +,-         2.25  15.6   2.25   1.5
+ *  <<,>>       2.0    6.6   2.0    5.0
+ *    (<< MMX)  1.7    5.3   1.5    1.2
+ *  *           5.0   15.0   4.0    4.3
+ *  mulAdd      5.7   19.0   4.9    4.0
+ *  div        30.0   32.0  32.0   22.4
+ *  mulAcc(32)  6.5   20.0   5.4    4.9
+ *
+ * mulAcc(32) is multiplyAccumulate() for a 32*32 multiply. Thus it includes
+ * function call overhead.
+ * The timing for Div is quite unpredictable, but it's probably too slow
+ * to be useful. On 64-bit processors, these times should
+ * halve if run in 64-bit mode, except for the MMX functions.
+ */
+
+module std.internal.math.biguintx86;
+
+@system:
+pure:
+nothrow:
+
+/*
+  Naked asm is used throughout, because:
+  (a) it frees up the EBP register
+  (b) compiler bugs prevent the use of .ptr when a frame pointer is used.
+*/
+
+version(D_InlineAsm_X86) {
+
+private:
+
+/* Duplicate string s, with n times, substituting index for '@'.
+ *
+ * Each instance of '@' in s is replaced by 0,1,...n-1. This is a helper
+ * function for some of the asm routines.
+ */
+string indexedLoopUnroll(int n, string s) pure @safe
+{
+    string u;
+    for (int i = 0; i<n; ++i) {
+        string nstr= (i>9 ? ""~ cast(char)('0'+i/10) : "") ~ cast(char)('0' + i%10);
+
+        int last = 0;
+        for (int j = 0; j<s.length; ++j) {
+            if (s[j]=='@') {
+                u ~= s[last..j] ~ nstr;
+                last = j+1;
+            }
+        }
+        if (last<s.length) u = u ~ s[last..$];
+
+    }
+    return u;
+}
+unittest
+{
+    assert(indexedLoopUnroll(3, "@*23;")=="0*23;1*23;2*23;");
+}
+
+public:
+
+alias BigDigit = uint; // A Bignum is an array of BigDigits. Usually the machine word size.
+
+// Limits for when to switch between multiplication algorithms.
+enum : int { KARATSUBALIMIT = 18 }; // Minimum value for which Karatsuba is worthwhile.
+enum : int { KARATSUBASQUARELIMIT=26 }; // Minimum value for which square Karatsuba is worthwhile
+
+/** Multi-byte addition or subtraction
+ *    dest[#] = src1[#] + src2[#] + carry (0 or 1).
+ * or dest[#] = src1[#] - src2[#] - carry (0 or 1).
+ * Returns carry or borrow (0 or 1).
+ * Set op == '+' for addition, '-' for subtraction.
+ */
+uint multibyteAddSub(char op)(uint[] dest, const uint [] src1, const uint []
+        src2, uint carry) pure
+{
+    // Timing:
+    // Pentium M: 2.25/int
+    // P6 family, Core2 have a partial flags stall when reading the carry flag in
+    // an ADC, SBB operation after an operation such as INC or DEC which
+    // modifies some, but not all, flags. We avoid this by storing carry into
+    // a resister (AL), and restoring it after the branch.
+
+    enum { LASTPARAM = 4*4 } // 3* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push EDI;
+        push EBX;
+        push ESI;
+        mov ECX, [ESP + LASTPARAM + 4*4]; // dest.length;
+        mov EDX, [ESP + LASTPARAM + 3*4]; // src1.ptr
+        mov ESI, [ESP + LASTPARAM + 1*4]; // src2.ptr
+        mov EDI, [ESP + LASTPARAM + 5*4]; // dest.ptr
+             // Carry is in EAX
+        // Count UP to zero (from -len) to minimize loop overhead.
+        lea EDX, [EDX + 4*ECX]; // EDX = end of src1.
+        lea ESI, [ESI + 4*ECX]; // EBP = end of src2.
+        lea EDI, [EDI + 4*ECX]; // EDI = end of dest.
+
+        neg ECX;
+        add ECX, 8;
+        jb L2;  // if length < 8 , bypass the unrolled loop.
+L_unrolled:
+        shr AL, 1; // get carry from EAX
+    }
+    mixin(" asm pure nothrow {"
+        ~ indexedLoopUnroll( 8,
+        "mov EAX, [@*4-8*4+EDX+ECX*4];"
+        ~ ( op == '+' ? "adc" : "sbb" ) ~ " EAX, [@*4-8*4+ESI+ECX*4];"
+        ~ "mov [@*4-8*4+EDI+ECX*4], EAX;")
+        ~ "}");
+    asm pure nothrow {
+        setc AL; // save carry
+        add ECX, 8;
+        ja L_unrolled;
+L2:     // Do the residual 1..7 ints.
+
+        sub ECX, 8;
+        jz done;
+L_residual:
+        shr AL, 1; // get carry from EAX
+    }
+    mixin(" asm pure nothrow {"
+        ~ indexedLoopUnroll( 1,
+        "mov EAX, [@*4+EDX+ECX*4];"
+        ~ ( op == '+' ? "adc" : "sbb" ) ~ " EAX, [@*4+ESI+ECX*4];"
+        ~ "mov [@*4+EDI+ECX*4], EAX;") ~ "}");
+    asm pure nothrow {
+        setc AL; // save carry
+        add ECX, 1;
+        jnz L_residual;
+done:
+        and EAX, 1; // make it O or 1.
+        pop ESI;
+        pop EBX;
+        pop EDI;
+        ret 6*4;
+    }
+}
+
+unittest
+{
+    uint [] a = new uint[40];
+    uint [] b = new uint[40];
+    uint [] c = new uint[40];
+    for (int i=0; i<a.length; ++i)
+    {
+        if (i&1) a[i]=0x8000_0000 + i;
+        else a[i]=i;
+        b[i]= 0x8000_0003;
+    }
+    c[19]=0x3333_3333;
+    uint carry = multibyteAddSub!('+')(c[0..18], a[0..18], b[0..18], 0);
+    assert(carry==1);
+    assert(c[0]==0x8000_0003);
+    assert(c[1]==4);
+    assert(c[19]==0x3333_3333); // check for overrun
+    for (int i=0; i<a.length; ++i)
+    {
+        a[i]=b[i]=c[i]=0;
+    }
+    a[8]=0x048D159E;
+    b[8]=0x048D159E;
+    a[10]=0x1D950C84;
+    b[10]=0x1D950C84;
+    a[5] =0x44444444;
+    carry = multibyteAddSub!('-')(a[0..12], a[0..12], b[0..12], 0);
+    assert(a[11]==0);
+    for (int i=0; i<10; ++i) if (i!=5) assert(a[i]==0);
+
+    for (int q=3; q<36;++q) {
+        for (int i=0; i<a.length; ++i)
+        {
+            a[i]=b[i]=c[i]=0;
+        }
+        a[q-2]=0x040000;
+        b[q-2]=0x040000;
+       carry = multibyteAddSub!('-')(a[0..q], a[0..q], b[0..q], 0);
+       assert(a[q-2]==0);
+    }
+}
+
+/** dest[#] += carry, or dest[#] -= carry.
+ *  op must be '+' or '-'
+ *  Returns final carry or borrow (0 or 1)
+ */
+uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) pure
+{
+    enum { LASTPARAM = 1*4 } // 0* pushes + return address.
+    asm pure nothrow {
+        naked;
+        mov ECX, [ESP + LASTPARAM + 0*4]; // dest.length;
+        mov EDX, [ESP + LASTPARAM + 1*4]; // dest.ptr
+        // EAX  = carry
+L1: ;
+    }
+    static if (op=='+')
+        asm pure nothrow { add [EDX], EAX; }
+    else
+        asm pure nothrow { sub [EDX], EAX; }
+    asm pure nothrow {
+        mov EAX, 1;
+        jnc L2;
+        add EDX, 4;
+        dec ECX;
+        jnz L1;
+        mov EAX, 2;
+L2:     dec EAX;
+        ret 2*4;
+    }
+}
+
+/** dest[#] = src[#] << numbits
+ *  numbits must be in the range 1..31
+ *  Returns the overflow
+ */
+uint multibyteShlNoMMX(uint [] dest, const uint [] src, uint numbits) pure
+{
+    // Timing: Optimal for P6 family.
+    // 2.0 cycles/int on PPro..PM (limited by execution port p0)
+    // 5.0 cycles/int on Athlon, which has 7 cycles for SHLD!!
+    enum { LASTPARAM = 4*4 } // 3* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+        mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr;
+        mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length;
+        mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr;
+        mov ECX, EAX; // numbits;
+
+        mov EAX, [-4+ESI + 4*EBX];
+        mov EDX, 0;
+        shld EDX, EAX, CL;
+        push EDX; // Save return value
+        cmp EBX, 1;
+        jz L_last;
+        mov EDX, [-4+ESI + 4*EBX];
+        test EBX, 1;
+        jz L_odd;
+        sub EBX, 1;
+L_even:
+        mov EDX, [-4+ ESI + 4*EBX];
+        shld EAX, EDX, CL;
+        mov [EDI+4*EBX], EAX;
+L_odd:
+        mov EAX, [-8+ESI + 4*EBX];
+        shld EDX, EAX, CL;
+        mov [-4+EDI + 4*EBX], EDX;
+        sub EBX, 2;
+        jg L_even;
+L_last:
+        shl EAX, CL;
+        mov [EDI], EAX;
+        pop EAX; // pop return value
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+    }
+}
+
+/** dest[#] = src[#] >> numbits
+ *  numbits must be in the range 1..31
+ * This version uses MMX.
+ */
+uint multibyteShl(uint [] dest, const uint [] src, uint numbits) pure
+{
+    // Timing:
+    // K7 1.2/int. PM 1.7/int P4 5.3/int
+    enum { LASTPARAM = 4*4 } // 3* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+        mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr;
+        mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length;
+        mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr;
+
+        movd MM3, EAX; // numbits = bits to shift left
+        xor EAX, 63;
+        align   16;
+        inc EAX;
+        movd MM4, EAX ; // 64-numbits = bits to shift right
+
+        // Get the return value into EAX
+        and EAX, 31; // EAX = 32-numbits
+        movd MM2, EAX; // 32-numbits
+        movd MM1, [ESI+4*EBX-4];
+        psrlq MM1, MM2;
+        movd EAX, MM1;  // EAX = return value
+        test EBX, 1;
+        jz L_even;
+L_odd:
+        cmp EBX, 1;
+        jz L_length1;
+
+         // deal with odd lengths
+        movq MM1, [ESI+4*EBX-8];
+        psrlq MM1, MM2;
+        movd    [EDI +4*EBX-4], MM1;
+        sub EBX, 1;
+L_even: // It's either singly or doubly even
+        movq    MM2, [ESI + 4*EBX - 8];
+        psllq   MM2, MM3;
+        sub EBX, 2;
+        jle L_last;
+        movq MM1, MM2;
+        add EBX, 2;
+        test EBX, 2;
+        jz L_onceeven;
+        sub EBX, 2;
+
+        // MAIN LOOP -- 128 bytes per iteration
+ L_twiceeven:      // here MM2 is the carry
+        movq    MM0, [ESI + 4*EBX-8];
+        psrlq   MM0, MM4;
+        movq    MM1, [ESI + 4*EBX-8];
+        psllq   MM1, MM3;
+        por     MM2, MM0;
+        movq    [EDI +4*EBX], MM2;
+L_onceeven:        // here MM1 is the carry
+        movq    MM0, [ESI + 4*EBX-16];
+        psrlq   MM0, MM4;
+        movq    MM2, [ESI + 4*EBX-16];
+        por     MM1, MM0;
+        movq    [EDI +4*EBX-8], MM1;
+        psllq   MM2, MM3;
+        sub EBX, 4;
+        jg L_twiceeven;
+L_last:
+        movq    [EDI +4*EBX], MM2;
+L_alldone:
+        emms;  // NOTE: costs 6 cycles on Intel CPUs
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+
+L_length1:
+        // length 1 is a special case
+        movd MM1, [ESI];
+        psllq MM1, MM3;
+        movd [EDI], MM1;
+        jmp L_alldone;
+    }
+}
+
+void multibyteShr(uint [] dest, const uint [] src, uint numbits) pure
+{
+    enum { LASTPARAM = 4*4 } // 3* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+        mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr;
+        mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length;
+align 16;
+        mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr;
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest
+        lea ESI, [ESI + 4*EBX]; // ESI = end of src
+        neg EBX;                // count UP to zero.
+
+        movd MM3, EAX; // numbits = bits to shift right
+        xor EAX, 63;
+        inc EAX;
+        movd MM4, EAX ; // 64-numbits = bits to shift left
+
+        test EBX, 1;
+        jz L_even;
+L_odd:
+         // deal with odd lengths
+        and EAX, 31; // EAX = 32-numbits
+        movd MM2, EAX; // 32-numbits
+        cmp EBX, -1;
+        jz L_length1;
+
+        movq MM0, [ESI+4*EBX];
+        psrlq MM0, MM3;
+        movd    [EDI +4*EBX], MM0;
+        add EBX, 1;
+L_even:
+        movq    MM2, [ESI + 4*EBX];
+        psrlq   MM2, MM3;
+
+        movq MM1, MM2;
+        add EBX, 4;
+        cmp EBX, -2+4;
+        jz L_last;
+        // It's either singly or doubly even
+        sub EBX, 2;
+        test EBX, 2;
+        jnz L_onceeven;
+        add EBX, 2;
+
+        // MAIN LOOP -- 128 bytes per iteration
+ L_twiceeven:      // here MM2 is the carry
+        movq    MM0, [ESI + 4*EBX-8];
+        psllq   MM0, MM4;
+        movq    MM1, [ESI + 4*EBX-8];
+        psrlq   MM1, MM3;
+        por     MM2, MM0;
+        movq    [EDI +4*EBX-16], MM2;
+L_onceeven:        // here MM1 is the carry
+        movq    MM0, [ESI + 4*EBX];
+        psllq   MM0, MM4;
+        movq    MM2, [ESI + 4*EBX];
+        por     MM1, MM0;
+        movq    [EDI +4*EBX-8], MM1;
+        psrlq   MM2, MM3;
+        add EBX, 4;
+        jl L_twiceeven;
+L_last:
+        movq    [EDI +4*EBX-16], MM2;
+L_alldone:
+        emms;  // NOTE: costs 6 cycles on Intel CPUs
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+
+L_length1:
+        // length 1 is a special case
+        movd MM1, [ESI+4*EBX];
+        psrlq MM1, MM3;
+        movd    [EDI +4*EBX], MM1;
+        jmp L_alldone;
+
+    }
+}
+
+/** dest[#] = src[#] >> numbits
+ *  numbits must be in the range 1..31
+ */
+void multibyteShrNoMMX(uint [] dest, const uint [] src, uint numbits) pure
+{
+    // Timing: Optimal for P6 family.
+    // 2.0 cycles/int on PPro..PM (limited by execution port p0)
+    // Terrible performance on AMD64, which has 7 cycles for SHRD!!
+    enum { LASTPARAM = 4*4 } // 3* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+        mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr;
+        mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length;
+        mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr;
+        mov ECX, EAX; // numbits;
+
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest
+        lea ESI, [ESI + 4*EBX]; // ESI = end of src
+        neg EBX;                // count UP to zero.
+        mov EAX, [ESI + 4*EBX];
+        cmp EBX, -1;
+        jz L_last;
+        mov EDX, [ESI + 4*EBX];
+        test EBX, 1;
+        jz L_odd;
+        add EBX, 1;
+L_even:
+        mov EDX, [ ESI + 4*EBX];
+        shrd EAX, EDX, CL;
+        mov [-4 + EDI+4*EBX], EAX;
+L_odd:
+        mov EAX, [4 + ESI + 4*EBX];
+        shrd EDX, EAX, CL;
+        mov [EDI + 4*EBX], EDX;
+        add EBX, 2;
+        jl L_even;
+L_last:
+        shr EAX, CL;
+        mov [-4 + EDI], EAX;
+
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+    }
+}
+
+unittest
+{
+
+    uint [] aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteShr(aa[0..$-1], aa, 4);
+    assert(aa[0] == 0x6122_2222 && aa[1]==0xA455_5555
+        && aa[2]==0xD899_9999 && aa[3]==0x0BCC_CCCC);
+
+    aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteShr(aa[2..$-1], aa[2..$-1], 4);
+    assert(aa[0] == 0x1222_2223 && aa[1]==0x4555_5556
+        && aa[2]==0xD899_9999 && aa[3]==0x0BCC_CCCC);
+
+    aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteShr(aa[0..$-2], aa, 4);
+    assert(aa[1]==0xA455_5555 && aa[2]==0x0899_9999);
+    assert(aa[0]==0x6122_2222);
+    assert(aa[3]==0xBCCC_CCCD);
+
+
+    aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    uint r = multibyteShl(aa[2..4], aa[2..4], 4);
+    assert(aa[0] == 0xF0FF_FFFF && aa[1]==0x1222_2223
+        && aa[2]==0x5555_5560 && aa[3]==0x9999_99A4 && aa[4]==0xBCCC_CCCD);
+    assert(r==8);
+
+    aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    r = multibyteShl(aa[1..4], aa[1..4], 4);
+    assert(aa[0] == 0xF0FF_FFFF
+        && aa[2]==0x5555_5561);
+        assert(aa[3]==0x9999_99A4 && aa[4]==0xBCCC_CCCD);
+    assert(r==8);
+        assert(aa[1]==0x2222_2230);
+
+    aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    r = multibyteShl(aa[0..4], aa[1..5], 31);
+}
+
+/** dest[#] = src[#] * multiplier + carry.
+ * Returns carry.
+ */
+uint multibyteMul(uint[] dest, const uint[] src, uint multiplier, uint carry)
+    pure
+{
+    // Timing: definitely not optimal.
+    // Pentium M: 5.0 cycles/operation, has 3 resource stalls/iteration
+    // Fastest implementation found was 4.6 cycles/op, but not worth the complexity.
+
+    enum { LASTPARAM = 4*4 } // 4* pushes + return address.
+    // We'll use p2 (load unit) instead of the overworked p0 or p1 (ALU units)
+    // when initializing variables to zero.
+    version(D_PIC)
+    {
+        enum { zero = 0 }
+    }
+    else
+    {
+        __gshared int zero = 0;
+    }
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+
+        mov EDI, [ESP + LASTPARAM + 4*4]; // dest.ptr
+        mov EBX, [ESP + LASTPARAM + 4*3]; // dest.length
+        mov ESI, [ESP + LASTPARAM + 4*2];  // src.ptr
+        align 16;
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest
+        lea ESI, [ESI + 4*EBX]; // ESI = end of src
+        mov ECX, EAX; // [carry]; -- last param is in EAX.
+        neg EBX;                // count UP to zero.
+        test EBX, 1;
+        jnz L_odd;
+        add EBX, 1;
+ L1:
+        mov EAX, [-4 + ESI + 4*EBX];
+        mul int ptr [ESP+LASTPARAM]; //[multiplier];
+        add EAX, ECX;
+        mov ECX, zero;
+        mov [-4+EDI + 4*EBX], EAX;
+        adc ECX, EDX;
+L_odd:
+        mov EAX, [ESI + 4*EBX];  // p2
+        mul int ptr [ESP+LASTPARAM]; //[multiplier]; // p0*3,
+        add EAX, ECX;
+        mov ECX, zero;
+        adc ECX, EDX;
+        mov [EDI + 4*EBX], EAX;
+        add EBX, 2;
+        jl L1;
+
+        mov EAX, ECX; // get final carry
+
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 5*4;
+    }
+}
+
+unittest
+{
+    uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    multibyteMul(aa[1..4], aa[1..4], 16, 0);
+    assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230 && aa[2]==0x5555_5561 && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD);
+}
+
+// The inner multiply-and-add loop, together with the Even entry point.
+// Multiples by M_ADDRESS which should be "ESP+LASTPARAM" or "ESP". OP must be "add" or "sub"
+// This is the most time-critical code in the BigInt library.
+// It is used by both MulAdd, multiplyAccumulate, and triangleAccumulate
+string asmMulAdd_innerloop(string OP, string M_ADDRESS) pure {
+    // The bottlenecks in this code are extremely complicated. The MUL, ADD, and ADC
+    // need 4 cycles on each of the ALUs units p0 and p1. So we use memory load
+    // (unit p2) for initializing registers to zero.
+    // There are also dependencies between the instructions, and we run up against the
+    // ROB-read limit (can only read 2 registers per cycle).
+    // We also need the number of uops in the loop to be a multiple of 3.
+    // The only available execution unit for this is p3 (memory write). Unfortunately we can't do that
+        // if Position-Independent Code is required.
+
+        // Register usage
+    // ESI = end of src
+    // EDI = end of dest
+    // EBX = index. Counts up to zero (in steps of 2).
+    // EDX:EAX = scratch, used in multiply.
+    // ECX = carry1.
+    // EBP = carry2.
+        // ESP = points to the multiplier.
+
+        // The first member of 'dest' which will be modified is [EDI+4*EBX].
+        // EAX must already contain the first member of 'src', [ESI+4*EBX].
+
+    version(D_PIC) { bool using_PIC = true; } else { bool using_PIC = false; }
+    return "
+        // Entry point for even length
+        add EBX, 1;
+        mov EBP, ECX; // carry
+
+        mul int ptr [" ~ M_ADDRESS ~ "]; // M
+        mov ECX, 0;
+
+        add EBP, EAX;
+        mov EAX, [ESI+4*EBX];
+        adc ECX, EDX;
+
+        mul int ptr [" ~ M_ADDRESS ~ "]; // M
+        " ~ OP ~ " [-4+EDI+4*EBX], EBP;
+        mov EBP, zero;
+
+        adc ECX, EAX;
+        mov EAX, [4+ESI+4*EBX];
+
+        adc EBP, EDX;
+        add EBX, 2;
+        jnl L_done;
+L1:
+        mul int ptr [" ~ M_ADDRESS ~ "];
+        " ~ OP ~ " [-8+EDI+4*EBX], ECX;
+        adc EBP, EAX;
+        mov ECX, zero;
+        mov EAX, [ESI+4*EBX];
+        adc ECX, EDX;
+" ~
+        (using_PIC ? "" : "   mov storagenop, EDX; ") // make #uops in loop a multiple of 3, can't do this in PIC mode.
+~ "
+        mul int ptr [" ~ M_ADDRESS ~ "];
+        " ~ OP ~ " [-4+EDI+4*EBX], EBP;
+        mov EBP, zero;
+
+        adc ECX, EAX;
+        mov EAX, [4+ESI+4*EBX];
+
+        adc EBP, EDX;
+        add EBX, 2;
+        jl L1;
+L_done: " ~ OP ~ " [-8+EDI+4*EBX], ECX;
+        adc EBP, 0;
+";
+                // final carry is now in EBP
+}
+
+string asmMulAdd_enter_odd(string OP, string M_ADDRESS) pure
+{
+    return "
+        mul int ptr [" ~M_ADDRESS ~"];
+        mov EBP, zero;
+        add ECX, EAX;
+        mov EAX, [4+ESI+4*EBX];
+
+        adc EBP, EDX;
+        add EBX, 2;
+        jl L1;
+        jmp L_done;
+";
+}
+
+
+
+/**
+ * dest[#] += src[#] * multiplier OP carry(0..FFFF_FFFF).
+ * where op == '+' or '-'
+ * Returns carry out of MSB (0..FFFF_FFFF).
+ */
+uint multibyteMulAdd(char op)(uint [] dest, const uint [] src, uint
+        multiplier, uint carry) pure {
+    // Timing: This is the most time-critical bignum function.
+    // Pentium M: 5.4 cycles/operation, still has 2 resource stalls + 1load block/iteration
+
+    // The main loop is pipelined and unrolled by 2,
+    //   so entry to the loop is also complicated.
+
+    // Register usage
+    // EDX:EAX = multiply
+    // EBX = counter
+    // ECX = carry1
+    // EBP = carry2
+    // EDI = dest
+    // ESI = src
+
+    enum string OP = (op=='+')? "add" : "sub";
+    version(D_PIC) {
+        enum { zero = 0 }
+    } else {
+        // use p2 (load unit) instead of the overworked p0 or p1 (ALU units)
+        // when initializing registers to zero.
+        __gshared int zero = 0;
+        // use p3/p4 units
+        __gshared int storagenop; // write-only
+    }
+
+    enum { LASTPARAM = 5*4 } // 4* pushes + return address.
+    asm pure nothrow {
+        naked;
+
+        push ESI;
+        push EDI;
+        push EBX;
+        push EBP;
+        mov EDI, [ESP + LASTPARAM + 4*4]; // dest.ptr
+        mov EBX, [ESP + LASTPARAM + 4*3]; // dest.length
+        align 16;
+        nop;
+        mov ESI, [ESP + LASTPARAM + 4*2];  // src.ptr
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest
+        lea ESI, [ESI + 4*EBX]; // ESI = end of src
+        mov EBP, 0;
+        mov ECX, EAX; // ECX = input carry.
+        neg EBX;                // count UP to zero.
+        mov EAX, [ESI+4*EBX];
+        test EBX, 1;
+        jnz L_enter_odd;
+    }
+    // Main loop, with entry point for even length
+    mixin("asm pure nothrow {" ~ asmMulAdd_innerloop(OP, "ESP+LASTPARAM") ~ "}");
+    asm pure nothrow {
+        mov EAX, EBP; // get final carry
+        pop EBP;
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 5*4;
+    }
+L_enter_odd:
+    mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd(OP, "ESP+LASTPARAM") ~ "}");
+}
+
+unittest
+{
+
+    uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE];
+    uint [] bb = [0x1234_1234, 0xF0F0_F0F0, 0x00C0_C0C0, 0xF0F0_F0F0, 0xC0C0_C0C0];
+    multibyteMulAdd!('+')(bb[1..$-1], aa[1..$-2], 16, 5);
+    assert(bb[0] == 0x1234_1234 && bb[4] == 0xC0C0_C0C0);
+    assert(bb[1] == 0x2222_2230 + 0xF0F0_F0F0+5 && bb[2] == 0x5555_5561+0x00C0_C0C0+1
+         && bb[3] == 0x9999_99A4+0xF0F0_F0F0 );
+}
+
+/**
+   Sets result[#] = result[0..left.length] + left[#] * right[#]
+
+   It is defined in this way to allow cache-efficient multiplication.
+   This function is equivalent to:
+    ----
+    for (int i = 0; i< right.length; ++i) {
+        dest[left.length + i] = multibyteMulAdd(dest[i..left.length+i],
+                left, right[i], 0);
+    }
+    ----
+ */
+void multibyteMultiplyAccumulate(uint [] dest, const uint[] left,
+        const uint [] right) pure {
+    // Register usage
+    // EDX:EAX = used in multiply
+    // EBX = index
+    // ECX = carry1
+    // EBP = carry2
+    // EDI = end of dest for this pass through the loop. Index for outer loop.
+    // ESI = end of left. never changes
+    // [ESP] = M = right[i] = multiplier for this pass through the loop.
+    // right.length is changed into dest.ptr+dest.length
+    version(D_PIC) {
+        enum { zero = 0 }
+    } else {
+        // use p2 (load unit) instead of the overworked p0 or p1 (ALU units)
+        // when initializing registers to zero.
+        __gshared int zero = 0;
+        // use p3/p4 units
+        __gshared int storagenop; // write-only
+    }
+
+    enum { LASTPARAM = 6*4 } // 4* pushes + local + return address.
+    asm pure nothrow {
+        naked;
+
+        push ESI;
+        push EDI;
+        align 16;
+        push EBX;
+        push EBP;
+        push EAX;    // local variable M
+        mov EDI, [ESP + LASTPARAM + 4*5]; // dest.ptr
+        mov EBX, [ESP + LASTPARAM + 4*2]; // left.length
+        mov ESI, [ESP + LASTPARAM + 4*3];  // left.ptr
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest for first pass
+
+        mov EAX, [ESP + LASTPARAM + 4*0]; // right.length
+        lea EAX, [EDI + 4*EAX];
+        mov [ESP + LASTPARAM + 4*0], EAX; // last value for EDI
+
+        lea ESI, [ESI + 4*EBX]; // ESI = end of left
+        mov EAX, [ESP + LASTPARAM + 4*1]; // right.ptr
+        mov EAX, [EAX];
+        mov [ESP], EAX; // M
+outer_loop:
+        mov EBP, 0;
+        mov ECX, 0; // ECX = input carry.
+        neg EBX;                // count UP to zero.
+        mov EAX, [ESI+4*EBX];
+        test EBX, 1;
+        jnz L_enter_odd;
+    }
+    // -- Inner loop, with even entry point
+    mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}");
+    asm pure nothrow {
+        mov [-4+EDI+4*EBX], EBP;
+        add EDI, 4;
+        cmp EDI, [ESP + LASTPARAM + 4*0]; // is EDI = &dest[$]?
+        jz outer_done;
+        mov EAX, [ESP + LASTPARAM + 4*1]; // right.ptr
+        mov EAX, [EAX+4];                 // get new M
+        mov [ESP], EAX;                   // save new M
+        add int ptr [ESP + LASTPARAM + 4*1], 4; // right.ptr
+        mov EBX, [ESP + LASTPARAM + 4*2]; // left.length
+        jmp outer_loop;
+outer_done:
+        pop EAX;
+        pop EBP;
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 6*4;
+    }
+L_enter_odd:
+    mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}");
+}
+
+/**  dest[#] /= divisor.
+ * overflow is the initial remainder, and must be in the range 0..divisor-1.
+ * divisor must not be a power of 2 (use right shift for that case;
+ * A division by zero will occur if divisor is a power of 2).
+ * Returns the final remainder
+ *
+ * Based on public domain code by Eric Bainville.
+ * (http://www.bealto.com/) Used with permission.
+ */
+uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) pure
+{
+    // Timing: limited by a horrible dependency chain.
+    // Pentium M: 18 cycles/op, 8 resource stalls/op.
+    // EAX, EDX = scratch, used by MUL
+    // EDI = dest
+    // CL = shift
+    // ESI = quotient
+    // EBX = remainderhi
+    // EBP = remainderlo
+    // [ESP-4] = mask
+    // [ESP] = kinv (2^64 /divisor)
+    enum { LASTPARAM = 5*4 } // 4* pushes + return address.
+    enum { LOCALS = 2*4} // MASK, KINV
+    asm pure nothrow {
+        naked;
+
+        push ESI;
+        push EDI;
+        push EBX;
+        push EBP;
+
+        mov EDI, [ESP + LASTPARAM + 4*2]; // dest.ptr
+        mov EBX, [ESP + LASTPARAM + 4*1]; // dest.length
+
+        // Loop from msb to lsb
+        lea     EDI, [EDI + 4*EBX];
+        mov EBP, EAX; // rem is the input remainder, in 0..divisor-1
+        // Build the pseudo-inverse of divisor k: 2^64/k
+        // First determine the shift in ecx to get the max number of bits in kinv
+        xor     ECX, ECX;
+        mov     EAX, [ESP + LASTPARAM]; //divisor;
+        mov     EDX, 1;
+kinv1:
+        inc     ECX;
+        ror     EDX, 1;
+        shl     EAX, 1;
+        jnc     kinv1;
+        dec     ECX;
+        // Here, ecx is a left shift moving the msb of k to bit 32
+
+        mov     EAX, 1;
+        shl     EAX, CL;
+        dec     EAX;
+        ror     EAX, CL ; //ecx bits at msb
+        push    EAX; // MASK
+
+        // Then divide 2^(32+cx) by divisor (edx already ok)
+        xor     EAX, EAX;
+        div     int ptr [ESP + LASTPARAM +  LOCALS-4*1]; //divisor;
+        push    EAX; // kinv
+        align   16;
+L2:
+        // Get 32 bits of quotient approx, multiplying
+        // most significant word of (rem*2^32+input)
+        mov     EAX, [ESP+4]; //MASK;
+        and     EAX, [EDI - 4];
+        or      EAX, EBP;
+        rol     EAX, CL;
+        mov     EBX, EBP;
+        mov     EBP, [EDI - 4];
+        mul     int ptr [ESP]; //KINV;
+
+        shl     EAX, 1;
+        rcl     EDX, 1;
+
+        // Multiply by k and subtract to get remainder
+        // Subtraction must be done on two words
+        mov     EAX, EDX;
+        mov     ESI, EDX; // quot = high word
+        mul     int ptr [ESP + LASTPARAM+LOCALS]; //divisor;
+        sub     EBP, EAX;
+        sbb     EBX, EDX;
+        jz      Lb;  // high word is 0, goto adjust on single word
+
+        // Adjust quotient and remainder on two words
+Ld:     inc     ESI;
+        sub     EBP, [ESP + LASTPARAM+LOCALS]; //divisor;
+        sbb     EBX, 0;
+        jnz     Ld;
+
+        // Adjust quotient and remainder on single word
+Lb:     cmp     EBP, [ESP + LASTPARAM+LOCALS]; //divisor;
+        jc      Lc; // rem in 0..divisor-1, OK
+        sub     EBP, [ESP + LASTPARAM+LOCALS]; //divisor;
+        inc     ESI;
+        jmp     Lb;
+
+        // Store result
+Lc:
+        mov     [EDI - 4], ESI;
+        lea     EDI, [EDI - 4];
+        dec     int ptr [ESP + LASTPARAM + 4*1+LOCALS]; // len
+        jnz    L2;
+
+        pop EAX; // discard kinv
+        pop EAX; // discard mask
+
+        mov     EAX, EBP; // return final remainder
+        pop     EBP;
+        pop     EBX;
+        pop     EDI;
+        pop     ESI;
+        ret     3*4;
+    }
+}
+
+unittest
+{
+    uint [] aa = new uint[101];
+    for (int i=0; i<aa.length; ++i) aa[i] = 0x8765_4321 * (i+3);
+    uint overflow = multibyteMul(aa, aa, 0x8EFD_FCFB, 0x33FF_7461);
+    uint r = multibyteDivAssign(aa, 0x8EFD_FCFB, overflow);
+    for (int i=0; i<aa.length-1; ++i) assert(aa[i] == 0x8765_4321 * (i+3));
+    assert(r==0x33FF_7461);
+}
+
+// Set dest[2*i..2*i+1]+=src[i]*src[i]
+void multibyteAddDiagonalSquares(uint [] dest, const uint [] src) pure
+{
+    /* Unlike mulAdd, the carry is only 1 bit,
+           since FFFF*FFFF+FFFF_FFFF = 1_0000_0000.
+           Note also that on the last iteration, no carry can occur.
+           As for multibyteAdd, we save & restore carry flag through the loop.
+
+           The timing is entirely dictated by the dependency chain. We could
+           improve it by moving the mov EAX after the adc [EDI], EAX. Probably not worthwhile.
+    */
+    enum { LASTPARAM = 4*5 } // 4* pushes + return address.
+    asm pure nothrow {
+        naked;
+        push ESI;
+        push EDI;
+        push EBX;
+            push ECX;
+        mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr;
+        mov EBX, [ESP + LASTPARAM + 4*0]; //src.length;
+        mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr;
+        lea EDI, [EDI + 8*EBX];      // EDI = end of dest
+        lea ESI, [ESI + 4*EBX];      // ESI = end of src
+        neg EBX;                     // count UP to zero.
+        xor ECX, ECX;             // initial carry = 0.
+L1:
+        mov EAX, [ESI + 4*EBX];
+        mul EAX, EAX;
+        shr CL, 1;                 // get carry
+        adc [EDI + 8*EBX], EAX;
+        adc [EDI + 8*EBX + 4], EDX;
+        setc CL;                   // save carry
+        inc EBX;
+        jnz L1;
+
+        pop ECX;
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+    }
+}
+
+unittest
+{
+    uint [] aa = new uint[13];
+        uint [] bb = new uint[6];
+    for (int i=0; i<aa.length; ++i) aa[i] = 0x8000_0000;
+    for (int i=0; i<bb.length; ++i) bb[i] = i;
+        aa[$-1]= 7;
+    multibyteAddDiagonalSquares(aa[0..$-1], bb);
+        assert(aa[$-1]==7);
+        for (int i=0; i<bb.length; ++i) { assert(aa[2*i]==0x8000_0000+i*i); assert(aa[2*i+1]==0x8000_0000); }
+}
+
+void multibyteTriangleAccumulateD(uint[] dest, uint[] x) pure
+{
+    for (int i = 0; i < x.length-3; ++i) {
+        dest[i+x.length] = multibyteMulAdd!('+')(
+             dest[i+i+1 .. i+x.length], x[i+1..$], x[i], 0);
+    }
+    ulong c = cast(ulong)(x[$-3]) * x[$-2] + dest[$-5];
+    dest[$-5] = cast(uint)c;
+    c >>= 32;
+    c += cast(ulong)(x[$-3]) * x[$-1] + dest[$-4];
+    dest[$-4] = cast(uint)c;
+    c >>= 32;
+length2:
+    c += cast(ulong)(x[$-2]) * x[$-1];
+        dest[$-3] = cast(uint)c;
+        c >>= 32;
+        dest[$-2] = cast(uint)c;
+}
+
+//dest += src[0]*src[1...$] + src[1]*src[2..$] + ... + src[$-3]*src[$-2..$]+ src[$-2]*src[$-1]
+// assert(dest.length = src.length*2);
+// assert(src.length >= 3);
+void multibyteTriangleAccumulateAsm(uint[] dest, const uint[] src) pure
+{
+    // Register usage
+    // EDX:EAX = used in multiply
+    // EBX = index
+    // ECX = carry1
+    // EBP = carry2
+    // EDI = end of dest for this pass through the loop. Index for outer loop.
+    // ESI = end of src. never changes
+    // [ESP] = M = src[i] = multiplier for this pass through the loop.
+    // dest.length is changed into dest.ptr+dest.length
+    version(D_PIC) {
+        enum { zero = 0 }
+    } else {
+        // use p2 (load unit) instead of the overworked p0 or p1 (ALU units)
+        // when initializing registers to zero.
+        __gshared int zero = 0;
+        // use p3/p4 units
+        __gshared int storagenop; // write-only
+    }
+
+    enum { LASTPARAM = 6*4 } // 4* pushes + local + return address.
+    asm pure nothrow {
+        naked;
+
+        push ESI;
+        push EDI;
+        align 16;
+        push EBX;
+        push EBP;
+        push EAX;    // local variable M= src[i]
+        mov EDI, [ESP + LASTPARAM + 4*3]; // dest.ptr
+        mov EBX, [ESP + LASTPARAM + 4*0]; // src.length
+        mov ESI, [ESP + LASTPARAM + 4*1];  // src.ptr
+
+        lea ESI, [ESI + 4*EBX]; // ESI = end of left
+        add int ptr [ESP + LASTPARAM + 4*1], 4; // src.ptr, used for getting M
+
+        // local variable [ESP + LASTPARAM + 4*2] = last value for EDI
+        lea EDI, [EDI + 4*EBX]; // EDI = end of dest for first pass
+
+        lea EAX, [EDI + 4*EBX-3*4]; // up to src.length - 3
+        mov [ESP + LASTPARAM + 4*2], EAX; // last value for EDI  = &dest[src.length*2 -3]
+
+        cmp EBX, 3;
+        jz length_is_3;
+
+        // We start at src[1], not src[0].
+        dec EBX;
+        mov [ESP + LASTPARAM + 4*0], EBX;
+
+outer_loop:
+        mov EBX, [ESP + LASTPARAM + 4*0]; // src.length
+        mov EBP, 0;
+        mov ECX, 0; // ECX = input carry.
+        dec [ESP + LASTPARAM + 4*0]; // Next time, the length will be shorter by 1.
+        neg EBX;                // count UP to zero.
+
+        mov EAX, [ESI + 4*EBX - 4*1]; // get new M
+        mov [ESP], EAX;                   // save new M
+
+        mov EAX, [ESI+4*EBX];
+        test EBX, 1;
+        jnz L_enter_odd;
+    }
+    // -- Inner loop, with even entry point
+    mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}");
+    asm pure nothrow {
+        mov [-4+EDI+4*EBX], EBP;
+        add EDI, 4;
+        cmp EDI, [ESP + LASTPARAM + 4*2]; // is EDI = &dest[$-3]?
+        jnz outer_loop;
+length_is_3:
+        mov EAX, [ESI - 4*3];
+        mul EAX, [ESI - 4*2];
+        mov ECX, 0;
+        add [EDI-2*4], EAX;  // ECX:dest[$-5] += x[$-3] * x[$-2]
+        adc ECX, EDX;
+
+        mov EAX, [ESI - 4*3];
+        mul EAX, [ESI - 4*1]; // x[$-3] * x[$-1]
+        add EAX, ECX;
+        mov ECX, 0;
+        adc EDX, 0;
+        // now EDX: EAX = c + x[$-3] * x[$-1]
+        add [EDI-1*4], EAX; // ECX:dest[$-4] += (EDX:EAX)
+        adc ECX, EDX;  //  ECX holds dest[$-3], it acts as carry for the last row
+// do length==2
+        mov EAX, [ESI - 4*2];
+        mul EAX, [ESI - 4*1];
+        add ECX, EAX;
+        adc EDX, 0;
+        mov [EDI - 0*4], ECX; // dest[$-2:$-3] = c + x[$-2] * x[$-1];
+        mov [EDI + 1*4], EDX;
+
+        pop EAX;
+        pop EBP;
+        pop EBX;
+        pop EDI;
+        pop ESI;
+        ret 4*4;
+    }
+L_enter_odd:
+    mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}");
+}
+
+unittest
+{
+   uint [] aa = new uint[200];
+   uint [] a  = aa[0..100];
+   uint [] b  = new uint [100];
+   aa[] = 761;
+   a[] = 0;
+   b[] = 0;
+   a[3] = 6;
+   b[0]=1;
+   b[1] = 17;
+   b[50..100]=78;
+   multibyteTriangleAccumulateAsm(a, b[0..50]);
+   uint [] c = new uint[100];
+   c[] = 0;
+   c[1] = 17;
+   c[3] = 6;
+   assert(a[]==c[]);
+   assert(a[0]==0);
+   aa[] = 0xFFFF_FFFF;
+   a[] = 0;
+   b[] = 0;
+   b[0]= 0xbf6a1f01;
+   b[1]=  0x6e38ed64;
+   b[2]=  0xdaa797ed;
+   b[3] = 0;
+
+   multibyteTriangleAccumulateAsm(a[0..8], b[0..4]);
+   assert(a[1]==0x3a600964);
+   assert(a[2]==0x339974f6);
+   assert(a[3]==0x46736fce);
+   assert(a[4]==0x5e24a2b4);
+
+   b[3] = 0xe93ff9f4;
+   b[4] = 0x184f03;
+   a[]=0;
+   multibyteTriangleAccumulateAsm(a[0..14], b[0..7]);
+   assert(a[3]==0x79fff5c2);
+   assert(a[4]==0xcf384241);
+   assert(a[5]== 0x4a17fc8);
+   assert(a[6]==0x4d549025);
+}
+
+
+void multibyteSquare(BigDigit[] result, const BigDigit [] x) pure
+{
+    if (x.length < 4) {
+        // Special cases, not worth doing triangular.
+        result[x.length] = multibyteMul(result[0..x.length], x, x[0], 0);
+        multibyteMultiplyAccumulate(result[1..$], x, x[1..$]);
+        return;
+    }
+    //  Do half a square multiply.
+    //  dest += src[0]*src[1...$] + src[1]*src[2..$] + ... + src[$-3]*src[$-2..$]+ src[$-2]*src[$-1]
+    result[x.length] = multibyteMul(result[1 .. x.length], x[1..$], x[0], 0);
+    multibyteTriangleAccumulateAsm(result[2..$], x[1..$]);
+    // Multiply by 2
+    result[$-1] = multibyteShlNoMMX(result[1..$-1], result[1..$-1], 1);
+    // And add the diagonal elements
+    result[0] = 0;
+    multibyteAddDiagonalSquares(result, x);
+}
+
+version(BignumPerformanceTest) {
+import core.stdc.stdio;
+int clock() { asm { push EBX; xor EAX, EAX; cpuid; pop EBX; rdtsc; } }
+
+__gshared uint [2200] X1;
+__gshared uint [2200] Y1;
+__gshared uint [4000] Z1;
+
+void testPerformance() pure
+{
+    // The performance results at the top of this file were obtained using
+    // a Windows device driver to access the CPU performance counters.
+    // The code below is less accurate but more widely usable.
+    // The value for division is quite inconsistent.
+    for (int i=0; i<X1.length; ++i) { X1[i]=i; Y1[i]=i; Z1[i]=i; }
+    int t, t0;
+    multibyteShl(Z1[0..2000], X1[0..2000], 7);
+    t0 = clock();
+    multibyteShl(Z1[0..1000], X1[0..1000], 7);
+    t = clock();
+    multibyteShl(Z1[0..2000], X1[0..2000], 7);
+    auto shltime = (clock() - t) - (t - t0);
+    t0 = clock();
+    multibyteShr(Z1[2..1002], X1[4..1004], 13);
+    t = clock();
+    multibyteShr(Z1[2..2002], X1[4..2004], 13);
+    auto shrtime = (clock() - t) - (t - t0);
+    t0 = clock();
+    multibyteAddSub!('+')(Z1[0..1000], X1[0..1000], Y1[0..1000], 0);
+    t = clock();
+    multibyteAddSub!('+')(Z1[0..2000], X1[0..2000], Y1[0..2000], 0);
+    auto addtime = (clock() - t) - (t-t0);
+    t0 = clock();
+    multibyteMul(Z1[0..1000], X1[0..1000], 7, 0);
+    t = clock();
+    multibyteMul(Z1[0..2000], X1[0..2000], 7, 0);
+    auto multime = (clock() - t) - (t - t0);
+    multibyteMulAdd!('+')(Z1[0..2000], X1[0..2000], 217, 0);
+    t0 = clock();
+    multibyteMulAdd!('+')(Z1[0..1000], X1[0..1000], 217, 0);
+    t = clock();
+    multibyteMulAdd!('+')(Z1[0..2000], X1[0..2000], 217, 0);
+    auto muladdtime = (clock() - t) - (t - t0);
+    multibyteMultiplyAccumulate(Z1[0..64], X1[0..32], Y1[0..32]);
+    t = clock();
+    multibyteMultiplyAccumulate(Z1[0..64], X1[0..32], Y1[0..32]);
+    auto accumtime = clock() - t;
+    t0 = clock();
+    multibyteDivAssign(Z1[0..2000], 217, 0);
+    t = clock();
+    multibyteDivAssign(Z1[0..1000], 37, 0);
+    auto divtime = (t - t0) - (clock() - t);
+        t= clock();
+    multibyteSquare(Z1[0..64], X1[0..32]);
+    auto squaretime = clock() - t;
+
+    printf("-- BigInt asm performance (cycles/int) --\n");
+    printf("Add:        %.2f\n", addtime/1000.0);
+    printf("Shl:        %.2f\n", shltime/1000.0);
+    printf("Shr:        %.2f\n", shrtime/1000.0);
+    printf("Mul:        %.2f\n", multime/1000.0);
+    printf("MulAdd:     %.2f\n", muladdtime/1000.0);
+    printf("Div:        %.2f\n", divtime/1000.0);
+    printf("MulAccum32: %.2f*n*n (total %d)\n", accumtime/(32.0*32.0), accumtime);
+    printf("Square32: %.2f*n*n (total %d)\n\n", squaretime/(32.0*32.0), squaretime);
+}
+
+static this()
+{
+    testPerformance();
+}
+}
+
+} // version(D_InlineAsm_X86)
diff --git a/libphobos/src/std/internal/math/errorfunction.d b/libphobos/src/std/internal/math/errorfunction.d
new file mode 100644
index 00000000000..efa53a18420
--- /dev/null
+++ b/libphobos/src/std/internal/math/errorfunction.d
@@ -0,0 +1,466 @@
+/**
+ * Error Functions and Normal Distribution.
+ *
+ * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Copyright: Based on the CEPHES math library, which is
+ *            Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com).
+ * Authors:   Stephen L. Moshier, ported to D by Don Clugston
+ */
+/**
+ * Macros:
+ *  NAN = $(RED NAN)
+ *  SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
+ *  GAMMA =  &#915;
+ *  INTEGRAL = &#8747;
+ *  INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
+ *  POWER = $1<sup>$2</sup>
+ *  BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
+ *  CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
+ *  TABLE_SV = <table border="1" cellpadding="4" cellspacing="0">
+ *      <caption>Special Values</caption>
+ *      $0</table>
+ *  SVH = $(TR $(TH $1) $(TH $2))
+ *  SV  = $(TR $(TD $1) $(TD $2))
+ */
+module std.internal.math.errorfunction;
+import std.math;
+
+pure:
+nothrow:
+@safe:
+@nogc:
+
+private {
+immutable real EXP_2  = 0.13533528323661269189L; /* exp(-2) */
+enum real SQRT2PI = 2.50662827463100050242E0L; // sqrt(2pi)
+
+
+enum real MAXLOG = 0x1.62e42fefa39ef358p+13L;  // log(real.max)
+enum real MINLOG = -0x1.6436716d5406e6d8p+13L; // log(real.min*real.epsilon) = log(smallest denormal)
+}
+
+T rationalPoly(T)(T x, const(T) [] numerator, const(T) [] denominator) pure nothrow
+{
+    return poly(x, numerator)/poly(x, denominator);
+}
+
+
+private {
+
+/* erfc(x) = exp(-x^2) P(1/x)/Q(1/x)
+   1/8 <= 1/x <= 1
+   Peak relative error 5.8e-21  */
+immutable real [10] P = [ -0x1.30dfa809b3cc6676p-17, 0x1.38637cd0913c0288p+18,
+   0x1.2f015e047b4476bp+22, 0x1.24726f46aa9ab08p+25, 0x1.64b13c6395dc9c26p+27,
+   0x1.294c93046ad55b5p+29, 0x1.5962a82f92576dap+30, 0x1.11a709299faba04ap+31,
+   0x1.11028065b087be46p+31, 0x1.0d8ef40735b097ep+30
+];
+
+immutable real [11] Q = [ 0x1.14d8e2a72dec49f4p+19, 0x1.0c880ff467626e1p+23,
+   0x1.04417ef060b58996p+26, 0x1.404e61ba86df4ebap+28, 0x1.0f81887bc82b873ap+30,
+   0x1.4552a5e39fb49322p+31, 0x1.11779a0ceb2a01cep+32, 0x1.3544dd691b5b1d5cp+32,
+   0x1.a91781f12251f02ep+31, 0x1.0d8ef3da605a1c86p+30, 1.0
+];
+
+
+/* erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2)
+   1/128 <= 1/x < 1/8
+   Peak relative error 1.9e-21  */
+immutable real [5] R = [ 0x1.b9f6d8b78e22459ep-6, 0x1.1b84686b0a4ea43ap-1,
+   0x1.b8f6aebe96000c2ap+1, 0x1.cb1dbedac27c8ec2p+2, 0x1.cf885f8f572a4c14p+1
+];
+
+immutable real [6] S = [
+   0x1.87ae3cae5f65eb5ep-5, 0x1.01616f266f306d08p+0, 0x1.a4abe0411eed6c22p+2,
+   0x1.eac9ce3da600abaap+3, 0x1.5752a9ac2faebbccp+3, 1.0
+];
+
+/* erf(x)  = x P(x^2)/Q(x^2)
+   0 <= x <= 1
+   Peak relative error 7.6e-23  */
+immutable real [7] T = [ 0x1.0da01654d757888cp+20, 0x1.2eb7497bc8b4f4acp+17,
+   0x1.79078c19530f72a8p+15, 0x1.4eaf2126c0b2c23p+11, 0x1.1f2ea81c9d272a2ep+8,
+   0x1.59ca6e2d866e625p+2, 0x1.c188e0b67435faf4p-4
+];
+
+immutable real [7] U = [ 0x1.dde6025c395ae34ep+19, 0x1.c4bc8b6235df35aap+18,
+   0x1.8465900e88b6903ap+16, 0x1.855877093959ffdp+13, 0x1.e5c44395625ee358p+9,
+   0x1.6a0fed103f1c68a6p+5, 1.0
+];
+
+}
+
+/**
+ *  Complementary error function
+ *
+ * erfc(x) = 1 - erf(x), and has high relative accuracy for
+ * values of x far from zero. (For values near zero, use erf(x)).
+ *
+ *  1 - erf(x) =  2/ $(SQRT)(&pi;)
+ *     $(INTEGRAL x, $(INFINITY)) exp( - $(POWER t, 2)) dt
+ *
+ *
+ * For small x, erfc(x) = 1 - erf(x); otherwise rational
+ * approximations are computed.
+ *
+ * A special function expx2(x) is used to suppress error amplification
+ * in computing exp(-x^2).
+ */
+real erfc(real a)
+{
+    if (a == real.infinity)
+        return 0.0;
+    if (a == -real.infinity)
+        return 2.0;
+
+    real x;
+
+    if (a < 0.0L )
+        x = -a;
+    else
+        x = a;
+    if (x < 1.0)
+        return 1.0 - erf(a);
+
+    real z = -a * a;
+
+    if (z < -MAXLOG){
+//    mtherr( "erfcl", UNDERFLOW );
+        if (a < 0) return 2.0;
+        else return 0.0;
+    }
+
+    /* Compute z = exp(z).  */
+    z = expx2(a, -1);
+    real y = 1.0/x;
+
+
+    if( x < 8.0 ) y = z * rationalPoly(y, P, Q);
+    else          y = z * y * rationalPoly(y * y, R, S);
+
+    if (a < 0.0L)
+        y = 2.0L - y;
+
+    if (y == 0.0) {
+//    mtherr( "erfcl", UNDERFLOW );
+        if (a < 0) return 2.0;
+        else return 0.0;
+    }
+
+    return y;
+}
+
+
+private {
+/* Exponentially scaled erfc function
+   exp(x^2) erfc(x)
+   valid for x > 1.
+   Use with normalDistribution and expx2.  */
+
+real erfce(real x)
+{
+    real y = 1.0/x;
+
+    if (x < 8.0) {
+        return rationalPoly( y, P, Q);
+    } else {
+        return y * rationalPoly(y*y, R, S);
+    }
+}
+
+}
+
+/**
+ *  Error function
+ *
+ * The integral is
+ *
+ *  erf(x) =  2/ $(SQRT)(&pi;)
+ *     $(INTEGRAL 0, x) exp( - $(POWER t, 2)) dt
+ *
+ * The magnitude of x is limited to about 106.56 for IEEE 80-bit
+ * arithmetic; 1 or -1 is returned outside this range.
+ *
+ * For 0 <= |x| < 1, a rational polynomials are used; otherwise
+ * erf(x) = 1 - erfc(x).
+ *
+ * ACCURACY:
+ *                      Relative error:
+ * arithmetic   domain     # trials      peak         rms
+ *    IEEE      0,1         50000       2.0e-19     5.7e-20
+ */
+real erf(real x)
+{
+    if (x == 0.0)
+        return x; // deal with negative zero
+    if (x == -real.infinity)
+        return -1.0;
+    if (x == real.infinity)
+        return 1.0;
+    if (abs(x) > 1.0L)
+        return 1.0L - erfc(x);
+
+    real z = x * x;
+    return x * rationalPoly(z, T, U);
+}
+
+unittest {
+   // High resolution test points.
+    enum real erfc0_250 = 0.723663330078125 + 1.0279753638067014931732235184287934646022E-5;
+    enum real erfc0_375 = 0.5958709716796875 + 1.2118885490201676174914080878232469565953E-5;
+    enum real erfc0_500 = 0.4794921875 + 7.9346869534623172533461080354712635484242E-6;
+    enum real erfc0_625 = 0.3767547607421875 + 4.3570693945275513594941232097252997287766E-6;
+    enum real erfc0_750 = 0.2888336181640625 + 1.0748182422368401062165408589222625794046E-5;
+    enum real erfc0_875 = 0.215911865234375 + 1.3073705765341685464282101150637224028267E-5;
+    enum real erfc1_000 = 0.15728759765625 + 1.1609394035130658779364917390740703933002E-5;
+    enum real erfc1_125 = 0.111602783203125 + 8.9850951672359304215530728365232161564636E-6;
+
+    enum real erf0_875  = (1-0.215911865234375) - 1.3073705765341685464282101150637224028267E-5;
+
+
+    assert(feqrel(erfc(0.250L), erfc0_250 )>=real.mant_dig-1);
+    assert(feqrel(erfc(0.375L), erfc0_375 )>=real.mant_dig-0);
+    assert(feqrel(erfc(0.500L), erfc0_500 )>=real.mant_dig-2);
+    assert(feqrel(erfc(0.625L), erfc0_625 )>=real.mant_dig-1);
+    assert(feqrel(erfc(0.750L), erfc0_750 )>=real.mant_dig-1);
+    assert(feqrel(erfc(0.875L), erfc0_875 )>=real.mant_dig-4);
+    assert(feqrel(erfc(1.000L), erfc1_000 )>=real.mant_dig-2);
+    assert(feqrel(erfc(1.125L), erfc1_125 )>=real.mant_dig-2);
+    assert(feqrel(erf(0.875L), erf0_875 )>=real.mant_dig-1);
+    // The DMC implementation of erfc() fails this next test (just)
+    assert(feqrel(erfc(4.1L),0.67000276540848983727e-8L)>=real.mant_dig-5);
+
+    assert(isIdentical(erf(0.0),0.0));
+    assert(isIdentical(erf(-0.0),-0.0));
+    assert(erf(real.infinity) == 1.0);
+    assert(erf(-real.infinity) == -1.0);
+    assert(isIdentical(erf(NaN(0xDEF)),NaN(0xDEF)));
+    assert(isIdentical(erfc(NaN(0xDEF)),NaN(0xDEF)));
+    assert(isIdentical(erfc(real.infinity),0.0));
+    assert(erfc(-real.infinity) == 2.0);
+    assert(erfc(0) == 1.0);
+}
+
+/*
+ *  Exponential of squared argument
+ *
+ * Computes y = exp(x*x) while suppressing error amplification
+ * that would ordinarily arise from the inexactness of the
+ * exponential argument x*x.
+ *
+ * If sign < 0, the result is inverted; i.e., y = exp(-x*x) .
+ *
+ * ACCURACY:
+ *                      Relative error:
+ * arithmetic      domain        # trials      peak         rms
+ *   IEEE     -106.566, 106.566    10^5       1.6e-19     4.4e-20
+ */
+
+real expx2(real x, int sign)
+{
+    /*
+    Cephes Math Library Release 2.9:  June, 2000
+    Copyright 2000 by Stephen L. Moshier
+    */
+    const real M = 32768.0;
+    const real MINV = 3.0517578125e-5L;
+
+    x = abs(x);
+    if (sign < 0)
+        x = -x;
+
+  /* Represent x as an exact multiple of M plus a residual.
+     M is a power of 2 chosen so that exp(m * m) does not overflow
+     or underflow and so that |x - m| is small.  */
+    real m = MINV * floor(M * x + 0.5L);
+    real f = x - m;
+
+    /* x^2 = m^2 + 2mf + f^2 */
+    real u = m * m;
+    real u1 = 2 * m * f  +  f * f;
+
+    if (sign < 0) {
+        u = -u;
+        u1 = -u1;
+    }
+
+    if ((u+u1) > MAXLOG)
+        return real.infinity;
+
+    /* u is exact, u1 is small.  */
+    return exp(u) * exp(u1);
+}
+
+
+/*
+Computes the normal distribution function.
+
+The normal (or Gaussian, or bell-shaped) distribution is
+defined as:
+
+normalDist(x) = 1/$(SQRT) &pi; $(INTEGRAL -$(INFINITY), x) exp( - $(POWER t, 2)/2) dt
+    = 0.5 + 0.5 * erf(x/sqrt(2))
+    = 0.5 * erfc(- x/sqrt(2))
+
+To maintain accuracy at high values of x, use
+normalDistribution(x) = 1 - normalDistribution(-x).
+
+Accuracy:
+Within a few bits of machine resolution over the entire
+range.
+
+References:
+$(LINK http://www.netlib.org/cephes/ldoubdoc.html),
+G. Marsaglia, "Evaluating the Normal Distribution",
+Journal of Statistical Software <b>11</b>, (July 2004).
+*/
+real normalDistributionImpl(real a)
+{
+    real x = a * SQRT1_2;
+    real z = abs(x);
+
+    if( z < 1.0 )
+        return 0.5L + 0.5L * erf(x);
+    else {
+        real y = 0.5L * erfce(z);
+        /* Multiply by exp(-x^2 / 2)  */
+        z = expx2(a, -1);
+        y = y * sqrt(z);
+        if( x > 0.0L )
+            y = 1.0L - y;
+        return y;
+    }
+}
+
+unittest {
+assert(fabs(normalDistributionImpl(1L) - (0.841344746068543))< 0.0000000000000005);
+assert(isIdentical(normalDistributionImpl(NaN(0x325)), NaN(0x325)));
+}
+
+/*
+ * Inverse of Normal distribution function
+ *
+ * Returns the argument, x, for which the area under the
+ * Normal probability density function (integrated from
+ * minus infinity to x) is equal to p.
+ *
+ * For small arguments 0 < p < exp(-2), the program computes
+ * z = sqrt( -2 log(p) );  then the approximation is
+ * x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z) .
+ * For larger arguments,  x/sqrt(2 pi) = w + w^3 R(w^2)/S(w^2)) ,
+ * where w = p - 0.5 .
+ */
+real normalDistributionInvImpl(real p)
+in {
+  assert(p>=0.0L && p<=1.0L, "Domain error");
+}
+body
+{
+static immutable real[8] P0 =
+[ -0x1.758f4d969484bfdcp-7, 0x1.53cee17a59259dd2p-3,
+   -0x1.ea01e4400a9427a2p-1,  0x1.61f7504a0105341ap+1, -0x1.09475a594d0399f6p+2,
+    0x1.7c59e7a0df99e3e2p+1, -0x1.87a81da52edcdf14p-1,  0x1.1fb149fd3f83600cp-7
+];
+
+static immutable real[8] Q0 =
+[ -0x1.64b92ae791e64bb2p-7, 0x1.7585c7d597298286p-3,
+   -0x1.40011be4f7591ce6p+0, 0x1.1fc067d8430a425ep+2, -0x1.21008ffb1e7ccdf2p+3,
+   0x1.3d1581cf9bc12fccp+3, -0x1.53723a89fd8f083cp+2, 1.0
+];
+
+static immutable real[10] P1 =
+[ 0x1.20ceea49ea142f12p-13, 0x1.cbe8a7267aea80bp-7,
+   0x1.79fea765aa787c48p-2, 0x1.d1f59faa1f4c4864p+1, 0x1.1c22e426a013bb96p+4,
+   0x1.a8675a0c51ef3202p+5, 0x1.75782c4f83614164p+6, 0x1.7a2f3d90948f1666p+6,
+   0x1.5cd116ee4c088c3ap+5, 0x1.1361e3eb6e3cc20ap+2
+];
+
+static immutable real[10] Q1 =
+[ 0x1.3a4ce1406cea98fap-13, 0x1.f45332623335cda2p-7,
+   0x1.98f28bbd4b98db1p-2, 0x1.ec3b24f9c698091cp+1, 0x1.1cc56ecda7cf58e4p+4,
+   0x1.92c6f7376bf8c058p+5, 0x1.4154c25aa47519b4p+6, 0x1.1b321d3b927849eap+6,
+   0x1.403a5f5a4ce7b202p+4, 1.0
+];
+
+static immutable real[8] P2 =
+[ 0x1.8c124a850116a6d8p-21, 0x1.534abda3c2fb90bap-13,
+   0x1.29a055ec93a4718cp-7, 0x1.6468e98aad6dd474p-3, 0x1.3dab2ef4c67a601cp+0,
+   0x1.e1fb3a1e70c67464p+1, 0x1.b6cce8035ff57b02p+2, 0x1.9f4c9e749ff35f62p+1
+];
+
+static immutable real[8] Q2 =
+[ 0x1.af03f4fc0655e006p-21, 0x1.713192048d11fb2p-13,
+   0x1.4357e5bbf5fef536p-7, 0x1.7fdac8749985d43cp-3, 0x1.4a080c813a2d8e84p+0,
+   0x1.c3a4b423cdb41bdap+1, 0x1.8160694e24b5557ap+2, 1.0
+];
+
+static immutable real[8] P3 =
+[ -0x1.55da447ae3806168p-34, -0x1.145635641f8778a6p-24,
+ -0x1.abf46d6b48040128p-17, -0x1.7da550945da790fcp-11, -0x1.aa0b2a31157775fap-8,
+   0x1.b11d97522eed26bcp-3, 0x1.1106d22f9ae89238p+1, 0x1.029a358e1e630f64p+1
+];
+
+static immutable real[8] Q3 =
+[ -0x1.74022dd5523e6f84p-34, -0x1.2cb60d61e29ee836p-24,
+   -0x1.d19e6ec03a85e556p-17, -0x1.9ea2a7b4422f6502p-11, -0x1.c54b1e852f107162p-8,
+   0x1.e05268dd3c07989ep-3, 0x1.239c6aff14afbf82p+1, 1.0
+];
+
+    if(p<=0.0L || p>=1.0L)
+    {
+        if (p == 0.0L)
+            return -real.infinity;
+        if( p == 1.0L )
+            return real.infinity;
+        return real.nan; // domain error
+    }
+    int code = 1;
+    real y = p;
+    if( y > (1.0L - EXP_2) ) {
+        y = 1.0L - y;
+        code = 0;
+    }
+
+    real x, z, y2, x0, x1;
+
+    if ( y > EXP_2 ) {
+        y = y - 0.5L;
+        y2 = y * y;
+        x = y + y * (y2 * rationalPoly( y2, P0, Q0));
+        return x * SQRT2PI;
+    }
+
+    x = sqrt( -2.0L * log(y) );
+    x0 = x - log(x)/x;
+    z = 1.0L/x;
+    if ( x < 8.0L ) {
+        x1 = z * rationalPoly( z, P1, Q1);
+    } else if( x < 32.0L ) {
+        x1 = z * rationalPoly( z, P2, Q2);
+    } else {
+        x1 = z * rationalPoly( z, P3, Q3);
+    }
+    x = x0 - x1;
+    if ( code != 0 ) {
+        x = -x;
+    }
+    return x;
+}
+
+
+unittest {
+    // TODO: Use verified test points.
+    // The values below are from Excel 2003.
+    assert(fabs(normalDistributionInvImpl(0.001) - (-3.09023230616779))< 0.00000000000005);
+    assert(fabs(normalDistributionInvImpl(1e-50) - (-14.9333375347885))< 0.00000000000005);
+    assert(feqrel(normalDistributionInvImpl(0.999), -normalDistributionInvImpl(0.001)) > real.mant_dig-6);
+
+    // Excel 2003 gets all the following values wrong!
+    assert(normalDistributionInvImpl(0.0) == -real.infinity);
+    assert(normalDistributionInvImpl(1.0) == real.infinity);
+    assert(normalDistributionInvImpl(0.5) == 0);
+    // (Excel 2003 returns norminv(p) = -30 for all p < 1e-200).
+    // The value tested here is the one the function returned in Jan 2006.
+    real unknown1 = normalDistributionInvImpl(1e-250L);
+    assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005);
+}
diff --git a/libphobos/src/std/internal/math/gammafunction.d b/libphobos/src/std/internal/math/gammafunction.d
new file mode 100644
index 00000000000..b409e777b6f
--- /dev/null
+++ b/libphobos/src/std/internal/math/gammafunction.d
@@ -0,0 +1,1673 @@
+/**
+ * Implementation of the gamma and beta functions, and their integrals.
+ *
+ * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Copyright: Based on the CEPHES math library, which is
+ *            Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com).
+ * Authors:   Stephen L. Moshier (original C code). Conversion to D by Don Clugston
+ *
+ *
+Macros:
+ *  TABLE_SV = <table border="1" cellpadding="4" cellspacing="0">
+ *      <caption>Special Values</caption>
+ *      $0</table>
+ *  SVH = $(TR $(TH $1) $(TH $2))
+ *  SV  = $(TR $(TD $1) $(TD $2))
+ *  GAMMA =  &#915;
+ *  INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
+ *  POWER = $1<sup>$2</sup>
+ *  NAN = $(RED NAN)
+ */
+module std.internal.math.gammafunction;
+import std.internal.math.errorfunction;
+import std.math;
+
+pure:
+nothrow:
+@safe:
+@nogc:
+
+private {
+
+enum real SQRT2PI = 2.50662827463100050242E0L; // sqrt(2pi)
+immutable real EULERGAMMA = 0.57721_56649_01532_86060_65120_90082_40243_10421_59335_93992L; /** Euler-Mascheroni constant 0.57721566.. */
+
+// Polynomial approximations for gamma and loggamma.
+
+immutable real[8] GammaNumeratorCoeffs = [ 1.0,
+    0x1.acf42d903366539ep-1, 0x1.73a991c8475f1aeap-2, 0x1.c7e918751d6b2a92p-4,
+    0x1.86d162cca32cfe86p-6, 0x1.0c378e2e6eaf7cd8p-8, 0x1.dc5c66b7d05feb54p-12,
+    0x1.616457b47e448694p-15
+];
+
+immutable real[9] GammaDenominatorCoeffs = [ 1.0,
+  0x1.a8f9faae5d8fc8bp-2,  -0x1.cb7895a6756eebdep-3,  -0x1.7b9bab006d30652ap-5,
+  0x1.c671af78f312082ep-6, -0x1.a11ebbfaf96252dcp-11, -0x1.447b4d2230a77ddap-10,
+  0x1.ec1d45bb85e06696p-13,-0x1.d4ce24d05bd0a8e6p-17
+];
+
+immutable real[9] GammaSmallCoeffs = [ 1.0,
+    0x1.2788cfc6fb618f52p-1, -0x1.4fcf4026afa2f7ecp-1, -0x1.5815e8fa24d7e306p-5,
+    0x1.5512320aea2ad71ap-3, -0x1.59af0fb9d82e216p-5,  -0x1.3b4b61d3bfdf244ap-7,
+    0x1.d9358e9d9d69fd34p-8, -0x1.38fc4bcbada775d6p-10
+];
+
+immutable real[9] GammaSmallNegCoeffs = [ -1.0,
+    0x1.2788cfc6fb618f54p-1, 0x1.4fcf4026afa2bc4cp-1, -0x1.5815e8fa2468fec8p-5,
+    -0x1.5512320baedaf4b6p-3, -0x1.59af0fa283baf07ep-5, 0x1.3b4a70de31e05942p-7,
+    0x1.d9398be3bad13136p-8, 0x1.291b73ee05bcbba2p-10
+];
+
+immutable real[7] logGammaStirlingCoeffs = [
+    0x1.5555555555553f98p-4, -0x1.6c16c16c07509b1p-9, 0x1.a01a012461cbf1e4p-11,
+    -0x1.3813089d3f9d164p-11, 0x1.b911a92555a277b8p-11, -0x1.ed0a7b4206087b22p-10,
+    0x1.402523859811b308p-8
+];
+
+immutable real[7] logGammaNumerator = [
+    -0x1.0edd25913aaa40a2p+23, -0x1.31c6ce2e58842d1ep+24, -0x1.f015814039477c3p+23,
+    -0x1.74ffe40c4b184b34p+22, -0x1.0d9c6d08f9eab55p+20,  -0x1.54c6b71935f1fc88p+16,
+    -0x1.0e761b42932b2aaep+11
+];
+
+immutable real[8] logGammaDenominator = [
+    -0x1.4055572d75d08c56p+24, -0x1.deeb6013998e4d76p+24, -0x1.106f7cded5dcc79ep+24,
+    -0x1.25e17184848c66d2p+22, -0x1.301303b99a614a0ap+19, -0x1.09e76ab41ae965p+15,
+    -0x1.00f95ced9e5f54eep+9, 1.0
+];
+
+/*
+ * Helper function: Gamma function computed by Stirling's formula.
+ *
+ * Stirling's formula for the gamma function is:
+ *
+ * $(GAMMA)(x) = sqrt(2 &pi;) x<sup>x-0.5</sup> exp(-x) (1 + 1/x P(1/x))
+ *
+ */
+real gammaStirling(real x)
+{
+    // CEPHES code Copyright 1994 by Stephen L. Moshier
+
+    static immutable real[9] SmallStirlingCoeffs = [
+        0x1.55555555555543aap-4, 0x1.c71c71c720dd8792p-9, -0x1.5f7268f0b5907438p-9,
+        -0x1.e13cd410e0477de6p-13, 0x1.9b0f31643442616ep-11, 0x1.2527623a3472ae08p-14,
+        -0x1.37f6bc8ef8b374dep-11,-0x1.8c968886052b872ap-16, 0x1.76baa9c6d3eeddbcp-11
+    ];
+
+    static immutable real[7] LargeStirlingCoeffs = [ 1.0L,
+        8.33333333333333333333E-2L, 3.47222222222222222222E-3L,
+        -2.68132716049382716049E-3L, -2.29472093621399176955E-4L,
+        7.84039221720066627474E-4L, 6.97281375836585777429E-5L
+    ];
+
+    real w = 1.0L/x;
+    real y = exp(x);
+    if ( x > 1024.0L ) {
+        // For large x, use rational coefficients from the analytical expansion.
+        w = poly(w, LargeStirlingCoeffs);
+        // Avoid overflow in pow()
+        real v = pow( x, 0.5L * x - 0.25L );
+        y = v * (v / y);
+    }
+    else {
+        w = 1.0L + w * poly( w, SmallStirlingCoeffs);
+        static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) {
+            // Avoid overflow in pow() for 64-bit reals
+            if (x > 143.0) {
+                real v = pow( x, 0.5 * x - 0.25 );
+                y = v * (v / y);
+            }
+            else {
+                y = pow( x, x - 0.5 ) / y;
+            }
+        }
+        else {
+            y = pow( x, x - 0.5L ) / y;
+        }
+    }
+    y = SQRT2PI * y * w;
+    return  y;
+}
+
+/*
+ * Helper function: Incomplete gamma function computed by Temme's expansion.
+ *
+ * This is a port of igamma_temme_large from Boost.
+ *
+ */
+real igammaTemmeLarge(real a, real x)
+{
+    static immutable real[][13] coef = [
+        [ -0.333333333333333333333, 0.0833333333333333333333,
+          -0.0148148148148148148148, 0.00115740740740740740741,
+          0.000352733686067019400353, -0.0001787551440329218107,
+          0.39192631785224377817e-4, -0.218544851067999216147e-5,
+          -0.18540622107151599607e-5, 0.829671134095308600502e-6,
+          -0.176659527368260793044e-6, 0.670785354340149858037e-8,
+          0.102618097842403080426e-7, -0.438203601845335318655e-8,
+          0.914769958223679023418e-9, -0.255141939949462497669e-10,
+          -0.583077213255042506746e-10, 0.243619480206674162437e-10,
+          -0.502766928011417558909e-11 ],
+        [ -0.00185185185185185185185, -0.00347222222222222222222,
+          0.00264550264550264550265, -0.000990226337448559670782,
+          0.000205761316872427983539, -0.40187757201646090535e-6,
+          -0.18098550334489977837e-4, 0.764916091608111008464e-5,
+          -0.161209008945634460038e-5, 0.464712780280743434226e-8,
+          0.137863344691572095931e-6, -0.575254560351770496402e-7,
+          0.119516285997781473243e-7, -0.175432417197476476238e-10,
+          -0.100915437106004126275e-8, 0.416279299184258263623e-9,
+          -0.856390702649298063807e-10 ],
+        [ 0.00413359788359788359788, -0.00268132716049382716049,
+          0.000771604938271604938272, 0.200938786008230452675e-5,
+          -0.000107366532263651605215, 0.529234488291201254164e-4,
+          -0.127606351886187277134e-4, 0.342357873409613807419e-7,
+          0.137219573090629332056e-5, -0.629899213838005502291e-6,
+          0.142806142060642417916e-6, -0.204770984219908660149e-9,
+          -0.140925299108675210533e-7, 0.622897408492202203356e-8,
+          -0.136704883966171134993e-8 ],
+        [ 0.000649434156378600823045, 0.000229472093621399176955,
+          -0.000469189494395255712128, 0.000267720632062838852962,
+          -0.756180167188397641073e-4, -0.239650511386729665193e-6,
+          0.110826541153473023615e-4, -0.56749528269915965675e-5,
+          0.142309007324358839146e-5, -0.278610802915281422406e-10,
+          -0.169584040919302772899e-6, 0.809946490538808236335e-7,
+          -0.191111684859736540607e-7 ],
+        [ -0.000861888290916711698605, 0.000784039221720066627474,
+          -0.000299072480303190179733, -0.146384525788434181781e-5,
+          0.664149821546512218666e-4, -0.396836504717943466443e-4,
+          0.113757269706784190981e-4, 0.250749722623753280165e-9,
+          -0.169541495365583060147e-5, 0.890750753220530968883e-6,
+          -0.229293483400080487057e-6],
+        [ -0.000336798553366358150309, -0.697281375836585777429e-4,
+          0.000277275324495939207873, -0.000199325705161888477003,
+          0.679778047793720783882e-4, 0.141906292064396701483e-6,
+          -0.135940481897686932785e-4, 0.801847025633420153972e-5,
+          -0.229148117650809517038e-5 ],
+        [ 0.000531307936463992223166, -0.000592166437353693882865,
+          0.000270878209671804482771, 0.790235323266032787212e-6,
+          -0.815396936756196875093e-4, 0.561168275310624965004e-4,
+          -0.183291165828433755673e-4, -0.307961345060330478256e-8,
+          0.346515536880360908674e-5, -0.20291327396058603727e-5,
+          0.57887928631490037089e-6 ],
+        [ 0.000344367606892377671254, 0.517179090826059219337e-4,
+          -0.000334931610811422363117, 0.000281269515476323702274,
+          -0.000109765822446847310235, -0.127410090954844853795e-6,
+          0.277444515115636441571e-4, -0.182634888057113326614e-4,
+          0.578769494973505239894e-5 ],
+        [ -0.000652623918595309418922, 0.000839498720672087279993,
+          -0.000438297098541721005061, -0.696909145842055197137e-6,
+          0.000166448466420675478374, -0.000127835176797692185853,
+          0.462995326369130429061e-4 ],
+        [ -0.000596761290192746250124, -0.720489541602001055909e-4,
+          0.000678230883766732836162, -0.0006401475260262758451,
+          0.000277501076343287044992 ],
+        [ 0.00133244544948006563713, -0.0019144384985654775265,
+          0.00110893691345966373396 ],
+        [ 0.00157972766073083495909, 0.000162516262783915816899,
+          -0.00206334210355432762645, 0.00213896861856890981541,
+          -0.00101085593912630031708 ],
+        [ -0.00407251211951401664727, 0.00640336283380806979482,
+          -0.00404101610816766177474 ]
+    ];
+
+    // avoid nans when one of the arguments is inf:
+    if(x == real.infinity && a != real.infinity)
+        return 0;
+
+    if(x != real.infinity && a == real.infinity)
+        return 1;
+
+    real sigma = (x - a) / a;
+    real phi = sigma - log(sigma + 1);
+
+    real y = a * phi;
+    real z = sqrt(2 * phi);
+    if(x < a)
+        z = -z;
+
+    real[13] workspace;
+    foreach(i; 0 .. coef.length)
+        workspace[i] = poly(z, coef[i]);
+
+    real result = poly(1 / a, workspace);
+    result *= exp(-y) / sqrt(2 * PI * a);
+    if(x < a)
+        result = -result;
+
+    result += erfc(sqrt(y)) / 2;
+
+    return result;
+}
+
+} // private
+
+public:
+/// The maximum value of x for which gamma(x) < real.infinity.
+static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended)
+    enum real MAXGAMMA = 1755.5483429L;
+else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble)
+    enum real MAXGAMMA = 171.6243769L;
+else
+    static assert(0, "missing MAXGAMMA for other real types");
+
+
+/*****************************************************
+ *  The Gamma function, $(GAMMA)(x)
+ *
+ *  $(GAMMA)(x) is a generalisation of the factorial function
+ *  to real and complex numbers.
+ *  Like x!, $(GAMMA)(x+1) = x*$(GAMMA)(x).
+ *
+ *  Mathematically, if z.re > 0 then
+ *   $(GAMMA)(z) = $(INTEGRATE 0, &infin;) $(POWER t, z-1)$(POWER e, -t) dt
+ *
+ *  $(TABLE_SV
+ *    $(SVH  x,          $(GAMMA)(x) )
+ *    $(SV  $(NAN),      $(NAN)      )
+ *    $(SV  &plusmn;0.0, &plusmn;&infin;)
+ *    $(SV integer > 0,  (x-1)!      )
+ *    $(SV integer < 0,  $(NAN)      )
+ *    $(SV +&infin;,     +&infin;    )
+ *    $(SV -&infin;,     $(NAN)      )
+ *  )
+ */
+real gamma(real x)
+{
+/* Based on code from the CEPHES library.
+ * CEPHES code Copyright 1994 by Stephen L. Moshier
+ *
+ * Arguments |x| <= 13 are reduced by recurrence and the function
+ * approximated by a rational function of degree 7/8 in the
+ * interval (2,3).  Large arguments are handled by Stirling's
+ * formula. Large negative arguments are made positive using
+ * a reflection formula.
+ */
+
+    real q, z;
+    if (isNaN(x)) return x;
+    if (x == -x.infinity) return real.nan;
+    if ( fabs(x) > MAXGAMMA ) return real.infinity;
+    if (x==0) return 1.0 / x; // +- infinity depending on sign of x, create an exception.
+
+    q = fabs(x);
+
+    if ( q > 13.0L )    {
+        // Large arguments are handled by Stirling's
+        // formula. Large negative arguments are made positive using
+        // the reflection formula.
+
+        if ( x < 0.0L ) {
+            if (x < -1/real.epsilon)
+            {
+                // Large negatives lose all precision
+                return real.nan;
+            }
+            int sgngam = 1; // sign of gamma.
+            long intpart = cast(long)(q);
+            if (q == intpart)
+                  return real.nan; // poles for all integers <0.
+            real p = intpart;
+            if ( (intpart & 1) == 0 )
+                sgngam = -1;
+            z = q - p;
+            if ( z > 0.5L ) {
+                p += 1.0L;
+                z = q - p;
+            }
+            z = q * sin( PI * z );
+            z = fabs(z) * gammaStirling(q);
+            if ( z <= PI/real.max ) return sgngam * real.infinity;
+            return sgngam * PI/z;
+        } else {
+            return gammaStirling(x);
+        }
+    }
+
+    // Arguments |x| <= 13 are reduced by recurrence and the function
+    // approximated by a rational function of degree 7/8 in the
+    // interval (2,3).
+
+    z = 1.0L;
+    while ( x >= 3.0L ) {
+        x -= 1.0L;
+        z *= x;
+    }
+
+    while ( x < -0.03125L ) {
+        z /= x;
+        x += 1.0L;
+    }
+
+    if ( x <= 0.03125L ) {
+        if ( x == 0.0L )
+            return real.nan;
+        else {
+            if ( x < 0.0L ) {
+                x = -x;
+                return z / (x * poly( x, GammaSmallNegCoeffs ));
+            } else {
+                return z / (x * poly( x, GammaSmallCoeffs ));
+            }
+        }
+    }
+
+    while ( x < 2.0L ) {
+        z /= x;
+        x += 1.0L;
+    }
+    if ( x == 2.0L ) return z;
+
+    x -= 2.0L;
+    return z * poly( x, GammaNumeratorCoeffs ) / poly( x, GammaDenominatorCoeffs );
+}
+
+unittest {
+    // gamma(n) = factorial(n-1) if n is an integer.
+    real fact = 1.0L;
+    for (int i=1; fact<real.max; ++i) {
+        // Require exact equality for small factorials
+        if (i<14) assert(gamma(i*1.0L) == fact);
+        assert(feqrel(gamma(i*1.0L), fact) >= real.mant_dig-15);
+        fact *= (i*1.0L);
+    }
+    assert(gamma(0.0) == real.infinity);
+    assert(gamma(-0.0) == -real.infinity);
+    assert(isNaN(gamma(-1.0)));
+    assert(isNaN(gamma(-15.0)));
+    assert(isIdentical(gamma(NaN(0xABC)), NaN(0xABC)));
+    assert(gamma(real.infinity) == real.infinity);
+    assert(gamma(real.max) == real.infinity);
+    assert(isNaN(gamma(-real.infinity)));
+    assert(gamma(real.min_normal*real.epsilon) == real.infinity);
+    assert(gamma(MAXGAMMA)< real.infinity);
+    assert(gamma(MAXGAMMA*2) == real.infinity);
+
+    // Test some high-precision values (50 decimal digits)
+    real SQRT_PI = 1.77245385090551602729816748334114518279754945612238L;
+
+
+    assert(feqrel(gamma(0.5L), SQRT_PI) >= real.mant_dig-1);
+    assert(feqrel(gamma(17.25L), 4.224986665692703551570937158682064589938e13L) >= real.mant_dig-4);
+
+    assert(feqrel(gamma(1.0 / 3.0L),  2.67893853470774763365569294097467764412868937795730L) >= real.mant_dig-2);
+    assert(feqrel(gamma(0.25L),
+        3.62560990822190831193068515586767200299516768288006L) >= real.mant_dig-1);
+    assert(feqrel(gamma(1.0 / 5.0L),
+        4.59084371199880305320475827592915200343410999829340L) >= real.mant_dig-1);
+}
+
+/*****************************************************
+ * Natural logarithm of gamma function.
+ *
+ * Returns the base e (2.718...) logarithm of the absolute
+ * value of the gamma function of the argument.
+ *
+ * For reals, logGamma is equivalent to log(fabs(gamma(x))).
+ *
+ *  $(TABLE_SV
+ *    $(SVH  x,             logGamma(x)   )
+ *    $(SV  $(NAN),         $(NAN)      )
+ *    $(SV integer <= 0,    +&infin;    )
+ *    $(SV &plusmn;&infin;, +&infin;    )
+ *  )
+ */
+real logGamma(real x)
+{
+    /* Based on code from the CEPHES library.
+     * CEPHES code Copyright 1994 by Stephen L. Moshier
+     *
+     * For arguments greater than 33, the logarithm of the gamma
+     * function is approximated by the logarithmic version of
+     * Stirling's formula using a polynomial approximation of
+     * degree 4. Arguments between -33 and +33 are reduced by
+     * recurrence to the interval [2,3] of a rational approximation.
+     * The cosecant reflection formula is employed for arguments
+     * less than -33.
+     */
+    real q, w, z, f, nx;
+
+    if (isNaN(x)) return x;
+    if (fabs(x) == x.infinity) return x.infinity;
+
+    if( x < -34.0L )
+    {
+        q = -x;
+        w = logGamma(q);
+        real p = floor(q);
+        if ( p == q )
+            return real.infinity;
+        int intpart = cast(int)(p);
+        real sgngam = 1;
+        if ( (intpart & 1) == 0 )
+            sgngam = -1;
+        z = q - p;
+        if ( z > 0.5L ) {
+            p += 1.0L;
+            z = p - q;
+        }
+        z = q * sin( PI * z );
+        if ( z == 0.0L )
+            return sgngam * real.infinity;
+    /*  z = LOGPI - logl( z ) - w; */
+        z = log( PI/z ) - w;
+        return z;
+    }
+
+    if( x < 13.0L )
+    {
+        z = 1.0L;
+        nx = floor( x +  0.5L );
+        f = x - nx;
+        while ( x >= 3.0L ) {
+            nx -= 1.0L;
+            x = nx + f;
+            z *= x;
+        }
+        while ( x < 2.0L ) {
+            if( fabs(x) <= 0.03125 )
+            {
+                if ( x == 0.0L )
+                    return real.infinity;
+                if ( x < 0.0L )
+                {
+                    x = -x;
+                    q = z / (x * poly( x, GammaSmallNegCoeffs));
+                } else
+                    q = z / (x * poly( x, GammaSmallCoeffs));
+                return log( fabs(q) );
+            }
+            z /= nx +  f;
+            nx += 1.0L;
+            x = nx + f;
+        }
+        z = fabs(z);
+        if ( x == 2.0L )
+            return log(z);
+        x = (nx - 2.0L) + f;
+        real p = x * rationalPoly( x, logGammaNumerator, logGammaDenominator);
+        return log(z) + p;
+    }
+
+    // const real MAXLGM = 1.04848146839019521116e+4928L;
+    //  if( x > MAXLGM ) return sgngaml * real.infinity;
+
+    const real LOGSQRT2PI  =  0.91893853320467274178L; // log( sqrt( 2*pi ) )
+
+    q = ( x - 0.5L ) * log(x) - x + LOGSQRT2PI;
+    if (x > 1.0e10L) return q;
+    real p = 1.0L / (x*x);
+    q += poly( p, logGammaStirlingCoeffs ) / x;
+    return q ;
+}
+
+unittest {
+    assert(isIdentical(logGamma(NaN(0xDEF)), NaN(0xDEF)));
+    assert(logGamma(real.infinity) == real.infinity);
+    assert(logGamma(-1.0) == real.infinity);
+    assert(logGamma(0.0) == real.infinity);
+    assert(logGamma(-50.0) == real.infinity);
+    assert(isIdentical(0.0L, logGamma(1.0L)));
+    assert(isIdentical(0.0L, logGamma(2.0L)));
+    assert(logGamma(real.min_normal*real.epsilon) == real.infinity);
+    assert(logGamma(-real.min_normal*real.epsilon) == real.infinity);
+
+    // x, correct loggamma(x), correct d/dx loggamma(x).
+    immutable static real[] testpoints = [
+    8.0L,                    8.525146484375L      + 1.48766904143001655310E-5,   2.01564147795560999654E0L,
+    8.99993896484375e-1L,    6.6375732421875e-2L  + 5.11505711292524166220E-6L, -7.54938684259372234258E-1,
+    7.31597900390625e-1L,    2.2369384765625e-1   + 5.21506341809849792422E-6L, -1.13355566660398608343E0L,
+    2.31639862060546875e-1L, 1.3686676025390625L  + 1.12609441752996145670E-5L, -4.56670961813812679012E0,
+    1.73162841796875L,      -8.88214111328125e-2L + 3.36207740803753034508E-6L, 2.33339034686200586920E-1L,
+    1.23162841796875L,      -9.3902587890625e-2L  + 1.28765089229009648104E-5L, -2.49677345775751390414E-1L,
+    7.3786976294838206464e19L,   3.301798506038663053312e21L - 1.656137564136932662487046269677E5L,
+                          4.57477139169563904215E1L,
+    1.08420217248550443401E-19L, 4.36682586669921875e1L + 1.37082843669932230418E-5L,
+                         -9.22337203685477580858E18L,
+    1.0L, 0.0L, -5.77215664901532860607E-1L,
+    2.0L, 0.0L, 4.22784335098467139393E-1L,
+    -0.5L,  1.2655029296875L    + 9.19379714539648894580E-6L, 3.64899739785765205590E-2L,
+    -1.5L,  8.6004638671875e-1L + 6.28657731014510932682E-7L, 7.03156640645243187226E-1L,
+    -2.5L, -5.6243896484375E-2L + 1.79986700949327405470E-7,  1.10315664064524318723E0L,
+    -3.5L,  -1.30902099609375L  + 1.43111007079536392848E-5L, 1.38887092635952890151E0L
+    ];
+   // TODO: test derivatives as well.
+    for (int i=0; i<testpoints.length; i+=3) {
+        assert( feqrel(logGamma(testpoints[i]), testpoints[i+1]) > real.mant_dig-5);
+        if (testpoints[i]<MAXGAMMA) {
+            assert( feqrel(log(fabs(gamma(testpoints[i]))), testpoints[i+1]) > real.mant_dig-5);
+        }
+    }
+    assert(logGamma(-50.2) == log(fabs(gamma(-50.2))));
+    assert(logGamma(-0.008) == log(fabs(gamma(-0.008))));
+    assert(feqrel(logGamma(-38.8),log(fabs(gamma(-38.8)))) > real.mant_dig-4);
+    static if (real.mant_dig >= 64) // incl. 80-bit reals
+        assert(feqrel(logGamma(1500.0L),log(gamma(1500.0L))) > real.mant_dig-2);
+    else static if (real.mant_dig >= 53) // incl. 64-bit reals
+        assert(feqrel(logGamma(150.0L),log(gamma(150.0L))) > real.mant_dig-2);
+}
+
+
+private {
+static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) {
+    enum real MAXLOG = 0x1.62e42fefa39ef358p+13L;  // log(real.max)
+    enum real MINLOG = -0x1.6436716d5406e6d8p+13L; // log(real.min_normal*real.epsilon) = log(smallest denormal)
+}
+else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) {
+    enum real MAXLOG = 0x1.62e42fefa39efp+9L;  // log(real.max)
+    enum real MINLOG = -0x1.74385446d71c3p+9L; // log(real.min_normal*real.epsilon) = log(smallest denormal)
+}
+else
+    static assert(0, "missing MAXLOG and MINLOG for other real types");
+
+enum real BETA_BIG = 9.223372036854775808e18L;
+enum real BETA_BIGINV = 1.084202172485504434007e-19L;
+}
+
+/** Incomplete beta integral
+ *
+ * Returns incomplete beta integral of the arguments, evaluated
+ * from zero to x. The regularized incomplete beta function is defined as
+ *
+ * betaIncomplete(a, b, x) = &Gamma;(a+b)/(&Gamma;(a) &Gamma;(b)) *
+ * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t),b-1) dt
+ *
+ * and is the same as the the cumulative distribution function.
+ *
+ * The domain of definition is 0 <= x <= 1.  In this
+ * implementation a and b are restricted to positive values.
+ * The integral from x to 1 may be obtained by the symmetry
+ * relation
+ *
+ *    betaIncompleteCompl(a, b, x )  =  betaIncomplete( b, a, 1-x )
+ *
+ * The integral is evaluated by a continued fraction expansion
+ * or, when b*x is small, by a power series.
+ */
+real betaIncomplete(real aa, real bb, real xx )
+{
+    if ( !(aa>0 && bb>0) )
+    {
+         if ( isNaN(aa) ) return aa;
+         if ( isNaN(bb) ) return bb;
+         return real.nan; // domain error
+    }
+    if (!(xx>0 && xx<1.0)) {
+        if (isNaN(xx)) return xx;
+        if ( xx == 0.0L ) return 0.0;
+        if ( xx == 1.0L )  return 1.0;
+        return real.nan; // domain error
+    }
+    if ( (bb * xx) <= 1.0L && xx <= 0.95L)   {
+        return betaDistPowerSeries(aa, bb, xx);
+    }
+    real x;
+    real xc; // = 1 - x
+
+    real a, b;
+    int flag = 0;
+
+    /* Reverse a and b if x is greater than the mean. */
+    if( xx > (aa/(aa+bb)) ) {
+        // here x > aa/(aa+bb) and (bb*x>1 or x>0.95)
+        flag = 1;
+        a = bb;
+        b = aa;
+        xc = xx;
+        x = 1.0L - xx;
+    } else {
+        a = aa;
+        b = bb;
+        xc = 1.0L - xx;
+        x = xx;
+    }
+
+    if( flag == 1 && (b * x) <= 1.0L && x <= 0.95L) {
+        // here xx > aa/(aa+bb) and  ((bb*xx>1) or xx>0.95) and (aa*(1-xx)<=1) and xx > 0.05
+        return 1.0 - betaDistPowerSeries(a, b, x); // note loss of precision
+    }
+
+    real w;
+    // Choose expansion for optimal convergence
+    // One is for x * (a+b+2) < (a+1),
+    // the other is for x * (a+b+2) > (a+1).
+    real y = x * (a+b-2.0L) - (a-1.0L);
+    if( y < 0.0L ) {
+        w = betaDistExpansion1( a, b, x );
+    } else {
+        w = betaDistExpansion2( a, b, x ) / xc;
+    }
+
+    /* Multiply w by the factor
+         a      b
+        x  (1-x)   Gamma(a+b) / ( a Gamma(a) Gamma(b) ) .   */
+
+    y = a * log(x);
+    real t = b * log(xc);
+    if ( (a+b) < MAXGAMMA && fabs(y) < MAXLOG && fabs(t) < MAXLOG ) {
+        t = pow(xc,b);
+        t *= pow(x,a);
+        t /= a;
+        t *= w;
+        t *= gamma(a+b) / (gamma(a) * gamma(b));
+    } else {
+        /* Resort to logarithms.  */
+        y += t + logGamma(a+b) - logGamma(a) - logGamma(b);
+        y += log(w/a);
+
+        t = exp(y);
+/+
+        // There seems to be a bug in Cephes at this point.
+        // Problems occur for y > MAXLOG, not y < MINLOG.
+        if( y < MINLOG ) {
+            t = 0.0L;
+        } else {
+            t = exp(y);
+        }
++/
+    }
+    if( flag == 1 ) {
+/+   // CEPHES includes this code, but I think it is erroneous.
+        if( t <= real.epsilon ) {
+            t = 1.0L - real.epsilon;
+        } else
++/
+        t = 1.0L - t;
+    }
+    return t;
+}
+
+/** Inverse of incomplete beta integral
+ *
+ * Given y, the function finds x such that
+ *
+ *  betaIncomplete(a, b, x) == y
+ *
+ *  Newton iterations or interval halving is used.
+ */
+real betaIncompleteInv(real aa, real bb, real yy0 )
+{
+    real a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt;
+    int i, rflg, dir, nflg;
+
+    if (isNaN(yy0)) return yy0;
+    if (isNaN(aa)) return aa;
+    if (isNaN(bb)) return bb;
+    if( yy0 <= 0.0L )
+        return 0.0L;
+    if( yy0 >= 1.0L )
+        return 1.0L;
+    x0 = 0.0L;
+    yl = 0.0L;
+    x1 = 1.0L;
+    yh = 1.0L;
+    if( aa <= 1.0L || bb <= 1.0L ) {
+        dithresh = 1.0e-7L;
+        rflg = 0;
+        a = aa;
+        b = bb;
+        y0 = yy0;
+        x = a/(a+b);
+        y = betaIncomplete( a, b, x );
+        nflg = 0;
+        goto ihalve;
+    } else {
+        nflg = 0;
+        dithresh = 1.0e-4L;
+    }
+
+    // approximation to inverse function
+
+    yp = -normalDistributionInvImpl( yy0 );
+
+    if( yy0 > 0.5L ) {
+        rflg = 1;
+        a = bb;
+        b = aa;
+        y0 = 1.0L - yy0;
+        yp = -yp;
+    } else {
+        rflg = 0;
+        a = aa;
+        b = bb;
+        y0 = yy0;
+    }
+
+    lgm = (yp * yp - 3.0L)/6.0L;
+    x = 2.0L/( 1.0L/(2.0L * a-1.0L)  +  1.0L/(2.0L * b - 1.0L) );
+    d = yp * sqrt( x + lgm ) / x
+        - ( 1.0L/(2.0L * b - 1.0L) - 1.0L/(2.0L * a - 1.0L) )
+        * (lgm + (5.0L/6.0L) - 2.0L/(3.0L * x));
+    d = 2.0L * d;
+    if( d < MINLOG ) {
+        x = 1.0L;
+        goto under;
+    }
+    x = a/( a + b * exp(d) );
+    y = betaIncomplete( a, b, x );
+    yp = (y - y0)/y0;
+    if( fabs(yp) < 0.2 )
+        goto newt;
+
+    /* Resort to interval halving if not close enough. */
+ihalve:
+
+    dir = 0;
+    di = 0.5L;
+    for( i=0; i<400; i++ ) {
+        if( i != 0 ) {
+            x = x0  +  di * (x1 - x0);
+            if( x == 1.0L ) {
+                x = 1.0L - real.epsilon;
+            }
+            if( x == 0.0L ) {
+                di = 0.5;
+                x = x0  +  di * (x1 - x0);
+                if( x == 0.0 )
+                    goto under;
+            }
+            y = betaIncomplete( a, b, x );
+            yp = (x1 - x0)/(x1 + x0);
+            if( fabs(yp) < dithresh )
+                goto newt;
+            yp = (y-y0)/y0;
+            if( fabs(yp) < dithresh )
+                goto newt;
+        }
+        if( y < y0 ) {
+            x0 = x;
+            yl = y;
+            if( dir < 0 ) {
+                dir = 0;
+                di = 0.5L;
+            } else if( dir > 3 )
+                di = 1.0L - (1.0L - di) * (1.0L - di);
+            else if( dir > 1 )
+                di = 0.5L * di + 0.5L;
+            else
+                di = (y0 - y)/(yh - yl);
+            dir += 1;
+            if( x0 > 0.95L ) {
+                if( rflg == 1 ) {
+                    rflg = 0;
+                    a = aa;
+                    b = bb;
+                    y0 = yy0;
+                } else {
+                    rflg = 1;
+                    a = bb;
+                    b = aa;
+                    y0 = 1.0 - yy0;
+                }
+                x = 1.0L - x;
+                y = betaIncomplete( a, b, x );
+                x0 = 0.0;
+                yl = 0.0;
+                x1 = 1.0;
+                yh = 1.0;
+                goto ihalve;
+            }
+        } else {
+            x1 = x;
+            if( rflg == 1 && x1 < real.epsilon ) {
+                x = 0.0L;
+                goto done;
+            }
+            yh = y;
+            if( dir > 0 ) {
+                dir = 0;
+                di = 0.5L;
+            }
+            else if( dir < -3 )
+                di = di * di;
+            else if( dir < -1 )
+                di = 0.5L * di;
+            else
+                di = (y - y0)/(yh - yl);
+            dir -= 1;
+            }
+        }
+    if( x0 >= 1.0L ) {
+        // partial loss of precision
+        x = 1.0L - real.epsilon;
+        goto done;
+    }
+    if( x <= 0.0L ) {
+under:
+        // underflow has occurred
+        x = real.min_normal * real.min_normal;
+        goto done;
+    }
+
+newt:
+
+    if ( nflg ) {
+        goto done;
+    }
+    nflg = 1;
+    lgm = logGamma(a+b) - logGamma(a) - logGamma(b);
+
+    for( i=0; i<15; i++ ) {
+        /* Compute the function at this point. */
+        if ( i != 0 )
+            y = betaIncomplete(a,b,x);
+        if ( y < yl ) {
+            x = x0;
+            y = yl;
+        } else if( y > yh ) {
+            x = x1;
+            y = yh;
+        } else if( y < y0 ) {
+            x0 = x;
+            yl = y;
+        } else {
+            x1 = x;
+            yh = y;
+        }
+        if( x == 1.0L || x == 0.0L )
+            break;
+        /* Compute the derivative of the function at this point. */
+        d = (a - 1.0L) * log(x) + (b - 1.0L) * log(1.0L - x) + lgm;
+        if ( d < MINLOG ) {
+            goto done;
+        }
+        if ( d > MAXLOG ) {
+            break;
+        }
+        d = exp(d);
+        /* Compute the step to the next approximation of x. */
+        d = (y - y0)/d;
+        xt = x - d;
+        if ( xt <= x0 ) {
+            y = (x - x0) / (x1 - x0);
+            xt = x0 + 0.5L * y * (x - x0);
+            if( xt <= 0.0L )
+                break;
+        }
+        if ( xt >= x1 ) {
+            y = (x1 - x) / (x1 - x0);
+            xt = x1 - 0.5L * y * (x1 - x);
+            if ( xt >= 1.0L )
+                break;
+        }
+        x = xt;
+        if ( fabs(d/x) < (128.0L * real.epsilon) )
+            goto done;
+    }
+    /* Did not converge.  */
+    dithresh = 256.0L * real.epsilon;
+    goto ihalve;
+
+done:
+    if ( rflg ) {
+        if( x <= real.epsilon )
+            x = 1.0L - real.epsilon;
+        else
+            x = 1.0L - x;
+    }
+    return x;
+}
+
+unittest { // also tested by the normal distribution
+    // check NaN propagation
+    assert(isIdentical(betaIncomplete(NaN(0xABC),2,3), NaN(0xABC)));
+    assert(isIdentical(betaIncomplete(7,NaN(0xABC),3), NaN(0xABC)));
+    assert(isIdentical(betaIncomplete(7,15,NaN(0xABC)), NaN(0xABC)));
+    assert(isIdentical(betaIncompleteInv(NaN(0xABC),1,17), NaN(0xABC)));
+    assert(isIdentical(betaIncompleteInv(2,NaN(0xABC),8), NaN(0xABC)));
+    assert(isIdentical(betaIncompleteInv(2,3, NaN(0xABC)), NaN(0xABC)));
+
+    assert(isNaN(betaIncomplete(-1, 2, 3)));
+
+    assert(betaIncomplete(1, 2, 0)==0);
+    assert(betaIncomplete(1, 2, 1)==1);
+    assert(isNaN(betaIncomplete(1, 2, 3)));
+    assert(betaIncompleteInv(1, 1, 0)==0);
+    assert(betaIncompleteInv(1, 1, 1)==1);
+
+    // Test against Mathematica   betaRegularized[z,a,b]
+    // These arbitrary points are chosen to give good code coverage.
+    assert(feqrel(betaIncomplete(8, 10, 0.2), 0.010_934_315_234_099_2L) >=  real.mant_dig - 5);
+    assert(feqrel(betaIncomplete(2, 2.5, 0.9), 0.989_722_597_604_452_767_171_003_59L) >= real.mant_dig - 1);
+    static if (real.mant_dig >= 64) // incl. 80-bit reals
+        assert(feqrel(betaIncomplete(1000, 800, 0.5), 1.179140859734704555102808541457164E-06L) >= real.mant_dig - 13);
+    else
+        assert(feqrel(betaIncomplete(1000, 800, 0.5), 1.179140859734704555102808541457164E-06L) >= real.mant_dig - 14);
+    assert(feqrel(betaIncomplete(0.0001, 10000, 0.0001), 0.999978059362107134278786L) >= real.mant_dig - 18);
+    assert(betaIncomplete(0.01, 327726.7, 0.545113) == 1.0);
+    assert(feqrel(betaIncompleteInv(8, 10, 0.010_934_315_234_099_2L), 0.2L) >= real.mant_dig - 2);
+    assert(feqrel(betaIncomplete(0.01, 498.437, 0.0121433), 0.99999664562033077636065L) >= real.mant_dig - 1);
+    assert(feqrel(betaIncompleteInv(5, 10, 0.2000002972865658842), 0.229121208190918L) >= real.mant_dig - 3);
+    assert(feqrel(betaIncompleteInv(4, 7, 0.8000002209179505L), 0.483657360076904L) >= real.mant_dig - 3);
+
+    // Coverage tests. I don't have correct values for these tests, but
+    // these values cover most of the code, so they are useful for
+    // regression testing.
+    // Extensive testing failed to increase the coverage. It seems likely that about
+    // half the code in this function is unnecessary; there is potential for
+    // significant improvement over the original CEPHES code.
+    static if (real.mant_dig == 64) // 80-bit reals
+    {
+        assert(betaIncompleteInv(0.01, 8e-48, 5.45464e-20) == 1-real.epsilon);
+        assert(betaIncompleteInv(0.01, 8e-48, 9e-26) == 1-real.epsilon);
+
+        // Beware: a one-bit change in pow() changes almost all digits in the result!
+        assert(feqrel(betaIncompleteInv(0x1.b3d151fbba0eb18p+1, 1.2265e-19, 2.44859e-18), 0x1.c0110c8531d0952cp-1L) > 10);
+        // This next case uncovered a one-bit difference in the FYL2X instruction
+        // between Intel and AMD processors. This difference gets magnified by 2^^38.
+        // WolframAlpha crashes attempting to calculate this.
+        assert(feqrel(betaIncompleteInv(0x1.ff1275ae5b939bcap-41, 4.6713e18, 0.0813601),
+            0x1.f97749d90c7adba8p-63L) >= real.mant_dig - 39);
+        real a1 = 3.40483;
+        assert(betaIncompleteInv(a1, 4.0640301659679627772e19L, 0.545113) == 0x1.ba8c08108aaf5d14p-109);
+        real b1 = 2.82847e-25;
+        assert(feqrel(betaIncompleteInv(0.01, b1, 9e-26), 0x1.549696104490aa9p-830L) >= real.mant_dig-10);
+
+        // --- Problematic cases ---
+        // This is a situation where the series expansion fails to converge
+        assert( isNaN(betaIncompleteInv(0.12167, 4.0640301659679627772e19L, 0.0813601)));
+        // This next result is almost certainly erroneous.
+        // Mathematica states: "(cannot be determined by current methods)"
+        assert(betaIncomplete(1.16251e20, 2.18e39, 5.45e-20) == -real.infinity);
+        // WolframAlpha gives no result for this, though indicates that it approximately 1.0 - 1.3e-9
+        assert(1 - betaIncomplete(0.01, 328222, 4.0375e-5) == 0x1.5f62926b4p-30);
+    }
+}
+
+
+private {
+// Implementation functions
+
+// Continued fraction expansion #1 for incomplete beta integral
+// Use when x < (a+1)/(a+b+2)
+real betaDistExpansion1(real a, real b, real x )
+{
+    real xk, pk, pkm1, pkm2, qk, qkm1, qkm2;
+    real k1, k2, k3, k4, k5, k6, k7, k8;
+    real r, t, ans;
+    int n;
+
+    k1 = a;
+    k2 = a + b;
+    k3 = a;
+    k4 = a + 1.0L;
+    k5 = 1.0L;
+    k6 = b - 1.0L;
+    k7 = k4;
+    k8 = a + 2.0L;
+
+    pkm2 = 0.0L;
+    qkm2 = 1.0L;
+    pkm1 = 1.0L;
+    qkm1 = 1.0L;
+    ans = 1.0L;
+    r = 1.0L;
+    n = 0;
+    const real thresh = 3.0L * real.epsilon;
+    do  {
+        xk = -( x * k1 * k2 )/( k3 * k4 );
+        pk = pkm1 +  pkm2 * xk;
+        qk = qkm1 +  qkm2 * xk;
+        pkm2 = pkm1;
+        pkm1 = pk;
+        qkm2 = qkm1;
+        qkm1 = qk;
+
+        xk = ( x * k5 * k6 )/( k7 * k8 );
+        pk = pkm1 +  pkm2 * xk;
+        qk = qkm1 +  qkm2 * xk;
+        pkm2 = pkm1;
+        pkm1 = pk;
+        qkm2 = qkm1;
+        qkm1 = qk;
+
+        if( qk != 0.0L )
+            r = pk/qk;
+        if( r != 0.0L ) {
+            t = fabs( (ans - r)/r );
+            ans = r;
+        } else {
+           t = 1.0L;
+        }
+
+        if( t < thresh )
+            return ans;
+
+        k1 += 1.0L;
+        k2 += 1.0L;
+        k3 += 2.0L;
+        k4 += 2.0L;
+        k5 += 1.0L;
+        k6 -= 1.0L;
+        k7 += 2.0L;
+        k8 += 2.0L;
+
+        if( (fabs(qk) + fabs(pk)) > BETA_BIG ) {
+            pkm2 *= BETA_BIGINV;
+            pkm1 *= BETA_BIGINV;
+            qkm2 *= BETA_BIGINV;
+            qkm1 *= BETA_BIGINV;
+            }
+        if( (fabs(qk) < BETA_BIGINV) || (fabs(pk) < BETA_BIGINV) ) {
+            pkm2 *= BETA_BIG;
+            pkm1 *= BETA_BIG;
+            qkm2 *= BETA_BIG;
+            qkm1 *= BETA_BIG;
+            }
+        }
+    while( ++n < 400 );
+// loss of precision has occurred
+// mtherr( "incbetl", PLOSS );
+    return ans;
+}
+
+// Continued fraction expansion #2 for incomplete beta integral
+// Use when x > (a+1)/(a+b+2)
+real betaDistExpansion2(real a, real b, real x )
+{
+    real  xk, pk, pkm1, pkm2, qk, qkm1, qkm2;
+    real k1, k2, k3, k4, k5, k6, k7, k8;
+    real r, t, ans, z;
+
+    k1 = a;
+    k2 = b - 1.0L;
+    k3 = a;
+    k4 = a + 1.0L;
+    k5 = 1.0L;
+    k6 = a + b;
+    k7 = a + 1.0L;
+    k8 = a + 2.0L;
+
+    pkm2 = 0.0L;
+    qkm2 = 1.0L;
+    pkm1 = 1.0L;
+    qkm1 = 1.0L;
+    z = x / (1.0L-x);
+    ans = 1.0L;
+    r = 1.0L;
+    int n = 0;
+    const real thresh = 3.0L * real.epsilon;
+    do {
+
+        xk = -( z * k1 * k2 )/( k3 * k4 );
+        pk = pkm1 +  pkm2 * xk;
+        qk = qkm1 +  qkm2 * xk;
+        pkm2 = pkm1;
+        pkm1 = pk;
+        qkm2 = qkm1;
+        qkm1 = qk;
+
+        xk = ( z * k5 * k6 )/( k7 * k8 );
+        pk = pkm1 +  pkm2 * xk;
+        qk = qkm1 +  qkm2 * xk;
+        pkm2 = pkm1;
+        pkm1 = pk;
+        qkm2 = qkm1;
+        qkm1 = qk;
+
+        if( qk != 0.0L )
+            r = pk/qk;
+        if( r != 0.0L ) {
+            t = fabs( (ans - r)/r );
+            ans = r;
+        } else
+            t = 1.0L;
+
+        if( t < thresh )
+            return ans;
+        k1 += 1.0L;
+        k2 -= 1.0L;
+        k3 += 2.0L;
+        k4 += 2.0L;
+        k5 += 1.0L;
+        k6 += 1.0L;
+        k7 += 2.0L;
+        k8 += 2.0L;
+
+        if( (fabs(qk) + fabs(pk)) > BETA_BIG ) {
+            pkm2 *= BETA_BIGINV;
+            pkm1 *= BETA_BIGINV;
+            qkm2 *= BETA_BIGINV;
+            qkm1 *= BETA_BIGINV;
+        }
+        if( (fabs(qk) < BETA_BIGINV) || (fabs(pk) < BETA_BIGINV) ) {
+            pkm2 *= BETA_BIG;
+            pkm1 *= BETA_BIG;
+            qkm2 *= BETA_BIG;
+            qkm1 *= BETA_BIG;
+        }
+    } while( ++n < 400 );
+// loss of precision has occurred
+//mtherr( "incbetl", PLOSS );
+    return ans;
+}
+
+/* Power series for incomplete gamma integral.
+   Use when b*x is small.  */
+real betaDistPowerSeries(real a, real b, real x )
+{
+    real ai = 1.0L / a;
+    real u = (1.0L - b) * x;
+    real v = u / (a + 1.0L);
+    real t1 = v;
+    real t = u;
+    real n = 2.0L;
+    real s = 0.0L;
+    real z = real.epsilon * ai;
+    while( fabs(v) > z ) {
+        u = (n - b) * x / n;
+        t *= u;
+        v = t / (a + n);
+        s += v;
+        n += 1.0L;
+    }
+    s += t1;
+    s += ai;
+
+    u = a * log(x);
+    if ( (a+b) < MAXGAMMA && fabs(u) < MAXLOG ) {
+        t = gamma(a+b)/(gamma(a)*gamma(b));
+        s = s * t * pow(x,a);
+    } else {
+        t = logGamma(a+b) - logGamma(a) - logGamma(b) + u + log(s);
+
+        if( t < MINLOG ) {
+            s = 0.0L;
+        } else
+            s = exp(t);
+    }
+    return s;
+}
+
+}
+
+/***************************************
+ *  Incomplete gamma integral and its complement
+ *
+ * These functions are defined by
+ *
+ *   gammaIncomplete = ( $(INTEGRATE 0, x) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
+ *
+ *  gammaIncompleteCompl(a,x)   =   1 - gammaIncomplete(a,x)
+ * = ($(INTEGRATE x, &infin;) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
+ *
+ * In this implementation both arguments must be positive.
+ * The integral is evaluated by either a power series or
+ * continued fraction expansion, depending on the relative
+ * values of a and x.
+ */
+real gammaIncomplete(real a, real x )
+in {
+   assert(x >= 0);
+   assert(a > 0);
+}
+body {
+    /* left tail of incomplete gamma function:
+     *
+     *          inf.      k
+     *   a  -x   -       x
+     *  x  e     >   ----------
+     *           -     -
+     *          k=0   | (a+k+1)
+     *
+     */
+    if (x==0)
+       return 0.0L;
+
+    if ( (x > 1.0L) && (x > a ) )
+        return 1.0L - gammaIncompleteCompl(a,x);
+
+    real ax = a * log(x) - x - logGamma(a);
+/+
+    if( ax < MINLOGL ) return 0; // underflow
+    //  { mtherr( "igaml", UNDERFLOW ); return( 0.0L ); }
++/
+    ax = exp(ax);
+
+    /* power series */
+    real r = a;
+    real c = 1.0L;
+    real ans = 1.0L;
+
+    do  {
+        r += 1.0L;
+        c *= x/r;
+        ans += c;
+    } while( c/ans > real.epsilon );
+
+    return ans * ax/a;
+}
+
+/** ditto */
+real gammaIncompleteCompl(real a, real x )
+in {
+   assert(x >= 0);
+   assert(a > 0);
+}
+body {
+    if (x==0)
+        return 1.0L;
+    if ( (x < 1.0L) || (x < a) )
+        return 1.0L - gammaIncomplete(a,x);
+
+    // DAC (Cephes bug fix): This is necessary to avoid
+    // spurious nans, eg
+    // log(x)-x = NaN when x = real.infinity
+    const real MAXLOGL =  1.1356523406294143949492E4L;
+    if (x > MAXLOGL)
+        return igammaTemmeLarge(a, x);
+
+    real ax = a * log(x) - x - logGamma(a);
+//const real MINLOGL = -1.1355137111933024058873E4L;
+//  if ( ax < MINLOGL ) return 0; // underflow;
+    ax = exp(ax);
+
+
+    /* continued fraction */
+    real y = 1.0L - a;
+    real z = x + y + 1.0L;
+    real c = 0.0L;
+
+    real pk, qk, t;
+
+    real pkm2 = 1.0L;
+    real qkm2 = x;
+    real pkm1 = x + 1.0L;
+    real qkm1 = z * x;
+    real ans = pkm1/qkm1;
+
+    do  {
+        c += 1.0L;
+        y += 1.0L;
+        z += 2.0L;
+        real yc = y * c;
+        pk = pkm1 * z  -  pkm2 * yc;
+        qk = qkm1 * z  -  qkm2 * yc;
+        if( qk != 0.0L ) {
+            real r = pk/qk;
+            t = fabs( (ans - r)/r );
+            ans = r;
+        } else {
+            t = 1.0L;
+        }
+    pkm2 = pkm1;
+        pkm1 = pk;
+        qkm2 = qkm1;
+        qkm1 = qk;
+
+        const real BIG = 9.223372036854775808e18L;
+
+        if ( fabs(pk) > BIG ) {
+            pkm2 /= BIG;
+            pkm1 /= BIG;
+            qkm2 /= BIG;
+            qkm1 /= BIG;
+        }
+    } while ( t > real.epsilon );
+
+    return ans * ax;
+}
+
+/** Inverse of complemented incomplete gamma integral
+ *
+ * Given a and p, the function finds x such that
+ *
+ *  gammaIncompleteCompl( a, x ) = p.
+ *
+ * Starting with the approximate value x = a $(POWER t, 3), where
+ * t = 1 - d - normalDistributionInv(p) sqrt(d),
+ * and d = 1/9a,
+ * the routine performs up to 10 Newton iterations to find the
+ * root of incompleteGammaCompl(a,x) - p = 0.
+ */
+real gammaIncompleteComplInv(real a, real p)
+in {
+  assert(p>=0 && p<= 1);
+  assert(a>0);
+}
+body {
+    if (p==0) return real.infinity;
+
+    real y0 = p;
+    const real MAXLOGL =  1.1356523406294143949492E4L;
+    real x0, x1, x, yl, yh, y, d, lgm, dithresh;
+    int i, dir;
+
+    /* bound the solution */
+    x0 = real.max;
+    yl = 0.0L;
+    x1 = 0.0L;
+    yh = 1.0L;
+    dithresh = 4.0 * real.epsilon;
+
+    /* approximation to inverse function */
+    d = 1.0L/(9.0L*a);
+    y = 1.0L - d - normalDistributionInvImpl(y0) * sqrt(d);
+    x = a * y * y * y;
+
+    lgm = logGamma(a);
+
+    for( i=0; i<10; i++ ) {
+        if( x > x0 || x < x1 )
+            goto ihalve;
+        y = gammaIncompleteCompl(a,x);
+        if ( y < yl || y > yh )
+            goto ihalve;
+        if ( y < y0 ) {
+            x0 = x;
+            yl = y;
+        } else {
+            x1 = x;
+            yh = y;
+        }
+    /* compute the derivative of the function at this point */
+        d = (a - 1.0L) * log(x0) - x0 - lgm;
+        if ( d < -MAXLOGL )
+            goto ihalve;
+        d = -exp(d);
+    /* compute the step to the next approximation of x */
+        d = (y - y0)/d;
+        x = x - d;
+        if ( i < 3 ) continue;
+        if ( fabs(d/x) < dithresh ) return x;
+    }
+
+    /* Resort to interval halving if Newton iteration did not converge. */
+ihalve:
+    d = 0.0625L;
+    if ( x0 == real.max ) {
+        if( x <= 0.0L )
+            x = 1.0L;
+        while( x0 == real.max ) {
+            x = (1.0L + d) * x;
+            y = gammaIncompleteCompl( a, x );
+            if ( y < y0 ) {
+                x0 = x;
+                yl = y;
+                break;
+            }
+            d = d + d;
+        }
+    }
+    d = 0.5L;
+    dir = 0;
+
+    for( i=0; i<400; i++ ) {
+        x = x1  +  d * (x0 - x1);
+        y = gammaIncompleteCompl( a, x );
+        lgm = (x0 - x1)/(x1 + x0);
+        if ( fabs(lgm) < dithresh )
+            break;
+        lgm = (y - y0)/y0;
+        if ( fabs(lgm) < dithresh )
+            break;
+        if ( x <= 0.0L )
+            break;
+        if ( y > y0 ) {
+            x1 = x;
+            yh = y;
+            if ( dir < 0 ) {
+                dir = 0;
+                d = 0.5L;
+            } else if ( dir > 1 )
+                d = 0.5L * d + 0.5L;
+            else
+                d = (y0 - yl)/(yh - yl);
+            dir += 1;
+        } else {
+            x0 = x;
+            yl = y;
+            if ( dir > 0 ) {
+                dir = 0;
+                d = 0.5L;
+            } else if ( dir < -1 )
+                d = 0.5L * d;
+            else
+                d = (y0 - yl)/(yh - yl);
+            dir -= 1;
+        }
+    }
+    /+
+    if( x == 0.0L )
+        mtherr( "igamil", UNDERFLOW );
+    +/
+    return x;
+}
+
+unittest {
+//Values from Excel's GammaInv(1-p, x, 1)
+assert(fabs(gammaIncompleteComplInv(1, 0.5) - 0.693147188044814) < 0.00000005);
+assert(fabs(gammaIncompleteComplInv(12, 0.99) - 5.42818075054289) < 0.00000005);
+assert(fabs(gammaIncompleteComplInv(100, 0.8) - 91.5013985848288L) < 0.000005);
+assert(gammaIncomplete(1, 0)==0);
+assert(gammaIncompleteCompl(1, 0)==1);
+assert(gammaIncomplete(4545, real.infinity)==1);
+
+// Values from Excel's (1-GammaDist(x, alpha, 1, TRUE))
+
+assert(fabs(1.0L-gammaIncompleteCompl(0.5, 2) - 0.954499729507309L) < 0.00000005);
+assert(fabs(gammaIncomplete(0.5, 2) - 0.954499729507309L) < 0.00000005);
+// Fixed Cephes bug:
+assert(gammaIncompleteCompl(384, real.infinity)==0);
+assert(gammaIncompleteComplInv(3, 0)==real.infinity);
+// Fixed a bug that caused gammaIncompleteCompl to return a wrong value when
+// x was larger than a, but not by much, and both were large:
+// The value is from WolframAlpha (Gamma[100000, 100001, inf] / Gamma[100000])
+static if (real.mant_dig >= 64) // incl. 80-bit reals
+    assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.000000000005);
+else
+    assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.00000005);
+}
+
+
+// DAC: These values are Bn / n for n=2,4,6,8,10,12,14.
+immutable real [7] Bn_n  = [
+    1.0L/(6*2), -1.0L/(30*4), 1.0L/(42*6), -1.0L/(30*8),
+    5.0L/(66*10), -691.0L/(2730*12), 7.0L/(6*14) ];
+
+/** Digamma function
+*
+*  The digamma function is the logarithmic derivative of the gamma function.
+*
+*  digamma(x) = d/dx logGamma(x)
+*
+* References:
+*   1. Abramowitz, M., and Stegun, I. A. (1970).
+*      Handbook of mathematical functions. Dover, New York,
+*      pages 258-259, equations 6.3.6 and 6.3.18.
+*/
+real digamma(real x)
+{
+   // Based on CEPHES, Stephen L. Moshier.
+
+    real p, q, nz, s, w, y, z;
+    long i, n;
+    int negative;
+
+    negative = 0;
+    nz = 0.0;
+
+    if ( x <= 0.0 ) {
+        negative = 1;
+        q = x;
+        p = floor(q);
+        if( p == q ) {
+            return real.nan; // singularity.
+        }
+    /* Remove the zeros of tan(PI x)
+     * by subtracting the nearest integer from x
+     */
+        nz = q - p;
+        if ( nz != 0.5 ) {
+            if ( nz > 0.5 ) {
+                p += 1.0;
+                nz = q - p;
+            }
+            nz = PI/tan(PI*nz);
+        } else {
+            nz = 0.0;
+        }
+        x = 1.0 - x;
+    }
+
+    // check for small positive integer
+    if ((x <= 13.0) && (x == floor(x)) ) {
+        y = 0.0;
+        n = lrint(x);
+        // DAC: CEPHES bugfix. Cephes did this in reverse order, which
+        // created a larger roundoff error.
+        for (i=n-1; i>0; --i) {
+            y+=1.0L/i;
+        }
+        y -= EULERGAMMA;
+        goto done;
+    }
+
+    s = x;
+    w = 0.0;
+    while ( s < 10.0 ) {
+        w += 1.0/s;
+        s += 1.0;
+    }
+
+    if ( s < 1.0e17 ) {
+        z = 1.0/(s * s);
+        y = z * poly(z, Bn_n);
+    } else
+        y = 0.0;
+
+    y = log(s)  -  0.5L/s  -  y  -  w;
+
+done:
+    if ( negative ) {
+        y -= nz;
+    }
+    return y;
+}
+
+unittest {
+    // Exact values
+    assert(digamma(1.0)== -EULERGAMMA);
+    assert(feqrel(digamma(0.25), -PI/2 - 3* LN2 - EULERGAMMA) >= real.mant_dig-7);
+    assert(feqrel(digamma(1.0L/6), -PI/2 *sqrt(3.0L) - 2* LN2 -1.5*log(3.0L) - EULERGAMMA) >= real.mant_dig-7);
+    assert(digamma(-5.0).isNaN());
+    assert(feqrel(digamma(2.5), -EULERGAMMA - 2*LN2 + 2.0 + 2.0L/3) >= real.mant_dig-9);
+    assert(isIdentical(digamma(NaN(0xABC)), NaN(0xABC)));
+
+    for (int k=1; k<40; ++k) {
+        real y=0;
+        for (int u=k; u>=1; --u) {
+            y += 1.0L/u;
+        }
+        assert(feqrel(digamma(k+1.0), -EULERGAMMA + y) >= real.mant_dig-2);
+    }
+}
+
+/** Log Minus Digamma function
+*
+*  logmdigamma(x) = log(x) - digamma(x)
+*
+* References:
+*   1. Abramowitz, M., and Stegun, I. A. (1970).
+*      Handbook of mathematical functions. Dover, New York,
+*      pages 258-259, equations 6.3.6 and 6.3.18.
+*/
+real logmdigamma(real x)
+{
+    if (x <= 0.0)
+    {
+        if (x == 0.0)
+        {
+            return real.infinity;
+        }
+        return real.nan;
+    }
+
+    real s = x;
+    real w = 0.0;
+    while ( s < 10.0 ) {
+        w += 1.0/s;
+        s += 1.0;
+    }
+
+    real y;
+    if ( s < 1.0e17 ) {
+        immutable real z = 1.0/(s * s);
+        y = z * poly(z, Bn_n);
+    } else
+        y = 0.0;
+
+    return x == s ? y + 0.5L/s : (log(x/s) + 0.5L/s + y + w);
+}
+
+unittest {
+    assert(logmdigamma(-5.0).isNaN());
+    assert(isIdentical(logmdigamma(NaN(0xABC)), NaN(0xABC)));
+    assert(logmdigamma(0.0) == real.infinity);
+    for(auto x = 0.01; x < 1.0; x += 0.1)
+        assert(approxEqual(digamma(x), log(x) - logmdigamma(x)));
+    for(auto x = 1.0; x < 15.0; x += 1.0)
+        assert(approxEqual(digamma(x), log(x) - logmdigamma(x)));
+}
+
+/** Inverse of the Log Minus Digamma function
+ *
+ *   Returns x such $(D log(x) - digamma(x) == y).
+ *
+ * References:
+ *   1. Abramowitz, M., and Stegun, I. A. (1970).
+ *      Handbook of mathematical functions. Dover, New York,
+ *      pages 258-259, equation 6.3.18.
+ *
+ * Authors: Ilya Yaroshenko
+ */
+real logmdigammaInverse(real y)
+{
+    import std.numeric: findRoot;
+    // FIXME: should be returned back to enum.
+    // Fix requires CTFEable `log` on non-x86 targets (check both LDC and GDC).
+    immutable maxY = logmdigamma(real.min_normal);
+    assert(maxY > 0 && maxY <= real.max);
+
+    if (y >= maxY)
+    {
+        //lim x->0 (log(x)-digamma(x))*x == 1
+        return 1 / y;
+    }
+    if (y < 0)
+    {
+        return real.nan;
+    }
+    if (y < real.min_normal)
+    {
+        //6.3.18
+        return 0.5 / y;
+    }
+    if (y > 0)
+    {
+        // x/2 <= logmdigamma(1 / x) <= x, x > 0
+        // calls logmdigamma ~6 times
+        return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y,  2*y);
+    }
+    return y; //NaN
+}
+
+unittest {
+    import std.typecons;
+    //WolframAlpha, 22.02.2015
+    immutable Tuple!(real, real)[5] testData = [
+        tuple(1.0L, 0.615556766479594378978099158335549201923L),
+        tuple(1.0L/8, 4.15937801516894947161054974029150730555L),
+        tuple(1.0L/1024, 512.166612384991507850643277924243523243L),
+        tuple(0.000500083333325000003968249801594877323784632117L, 1000.0L),
+        tuple(1017.644138623741168814449776695062817947092468536L, 1.0L/1024),
+    ];
+    foreach (test; testData)
+        assert(approxEqual(logmdigammaInverse(test[0]), test[1], 1e-15, 0));
+
+    assert(approxEqual(logmdigamma(logmdigammaInverse(1)), 1, 1e-15, 0));
+    assert(approxEqual(logmdigamma(logmdigammaInverse(real.min_normal)), real.min_normal, 1e-15, 0));
+    assert(approxEqual(logmdigamma(logmdigammaInverse(real.max/2)), real.max/2, 1e-15, 0));
+    assert(approxEqual(logmdigammaInverse(logmdigamma(1)), 1, 1e-15, 0));
+    assert(approxEqual(logmdigammaInverse(logmdigamma(real.min_normal)), real.min_normal, 1e-15, 0));
+    assert(approxEqual(logmdigammaInverse(logmdigamma(real.max/2)), real.max/2, 1e-15, 0));
+}
diff --git a/libphobos/src/std/internal/processinit.d b/libphobos/src/std/internal/processinit.d
new file mode 100644
index 00000000000..a343c0d4d2e
--- /dev/null
+++ b/libphobos/src/std/internal/processinit.d
@@ -0,0 +1,22 @@
+// Written in the D programming language.
+
+/++
+    The only purpose of this module is to do the static construction for
+    std.process in order to eliminate cyclic construction errors.
+
+    Copyright: Copyright 2011 -
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+    Authors:   Jonathan M Davis and Kato Shoichi
+    Source:    $(PHOBOSSRC std/internal/_processinit.d)
+  +/
+module std.internal.processinit;
+
+version(OSX)
+{
+    extern(C) void std_process_shared_static_this();
+
+    shared static this()
+    {
+        std_process_shared_static_this();
+    }
+}
diff --git a/libphobos/src/std/internal/scopebuffer.d b/libphobos/src/std/internal/scopebuffer.d
new file mode 100644
index 00000000000..f8fd2d8f96e
--- /dev/null
+++ b/libphobos/src/std/internal/scopebuffer.d
@@ -0,0 +1,407 @@
+/*
+ * Copyright: 2014 by Digital Mars
+ * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors: Walter Bright
+ * Source: $(PHOBOSSRC std/internal/_scopebuffer.d)
+ */
+
+module std.internal.scopebuffer;
+
+
+//debug=ScopeBuffer;
+
+private import core.stdc.stdlib : realloc;
+private import std.traits;
+
+/**************************************
+ * ScopeBuffer encapsulates using a local array as a temporary buffer.
+ * It is initialized with the local array that should be large enough for
+ * most uses. If the need exceeds the size, ScopeBuffer will resize it
+ * using malloc() and friends.
+ *
+ * ScopeBuffer cannot contain more than (uint.max-16)/2 elements.
+ *
+ * ScopeBuffer is an OutputRange.
+ *
+ * Since ScopeBuffer potentially stores elements of type T in malloc'd memory,
+ * those elements are not scanned when the GC collects. This can cause
+ * memory corruption. Do not use ScopeBuffer when elements of type T point
+ * to the GC heap.
+ *
+ * Example:
+---
+import core.stdc.stdio;
+import std.internal.scopebuffer;
+void main()
+{
+    char[2] buf = void;
+    auto textbuf = ScopeBuffer!char(buf);
+    scope(exit) textbuf.free(); // necessary for cleanup
+
+    // Put characters and strings into textbuf, verify they got there
+    textbuf.put('a');
+    textbuf.put('x');
+    textbuf.put("abc");
+    assert(textbuf.length == 5);
+    assert(textbuf[1..3] == "xa");
+    assert(textbuf[3] == 'b');
+
+    // Can shrink it
+    textbuf.length = 3;
+    assert(textbuf[0..textbuf.length] == "axa");
+    assert(textbuf[textbuf.length - 1] == 'a');
+    assert(textbuf[1..3] == "xa");
+
+    textbuf.put('z');
+    assert(textbuf[] == "axaz");
+
+    // Can shrink it to 0 size, and reuse same memory
+    textbuf.length = 0;
+}
+---
+ * It is invalid to access ScopeBuffer's contents when ScopeBuffer goes out of scope.
+ * Hence, copying the contents are necessary to keep them around:
+---
+import std.internal.scopebuffer;
+string cat(string s1, string s2)
+{
+    char[10] tmpbuf = void;
+    auto textbuf = ScopeBuffer!char(tmpbuf);
+    scope(exit) textbuf.free();
+    textbuf.put(s1);
+    textbuf.put(s2);
+    textbuf.put("even more");
+    return textbuf[].idup;
+}
+---
+ * ScopeBuffer is intended for high performance usages in $(D @system) and $(D @trusted) code.
+ * It is designed to fit into two 64 bit registers, again for high performance use.
+ * If used incorrectly, memory leaks and corruption can result. Be sure to use
+ * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a ScopeBuffer
+ * instance's contents after $(D ScopeBuffer.free()) has been called.
+ *
+ * The realloc parameter defaults to C's realloc(). Another can be supplied to override it.
+ *
+ * ScopeBuffer instances may be copied, as in:
+---
+textbuf = doSomething(textbuf, args);
+---
+ * which can be very efficent, but these must be regarded as a move rather than a copy.
+ * Additionally, the code between passing and returning the instance must not throw
+ * exceptions, otherwise when ScopeBuffer.free() is called, memory may get corrupted.
+ */
+
+@system
+struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc)
+          if (isAssignable!T &&
+              !hasElaborateDestructor!T &&
+              !hasElaborateCopyConstructor!T &&
+              !hasElaborateAssign!T)
+{
+    import core.stdc.string : memcpy;
+    import core.exception : onOutOfMemoryError;
+
+
+    /**************************
+     * Initialize with buf to use as scratch buffer space.
+     * Params:
+     *  buf = Scratch buffer space, must have length that is even
+     * Example:
+     * ---
+     * ubyte[10] tmpbuf = void;
+     * auto sbuf = ScopeBuffer!ubyte(tmpbuf);
+     * ---
+     * If buf was created by the same realloc passed as a parameter
+     * to ScopeBuffer, then the contents of ScopeBuffer can be extracted without needing
+     * to copy them, and ScopeBuffer.free() will not need to be called.
+     */
+    this(T[] buf)
+        in
+        {
+            assert(!(buf.length & wasResized));    // assure even length of scratch buffer space
+            assert(buf.length <= uint.max);     // because we cast to uint later
+        }
+    body
+    {
+        this.buf = buf.ptr;
+        this.bufLen = cast(uint)buf.length;
+    }
+
+    unittest
+    {
+        ubyte[10] tmpbuf = void;
+        auto sbuf = ScopeBuffer!ubyte(tmpbuf);
+    }
+
+    /**************************
+     * Releases any memory used.
+     * This will invalidate any references returned by the [] operator.
+     * A destructor is not used, because that would make it not POD
+     * (Plain Old Data) and it could not be placed in registers.
+     */
+    void free()
+    {
+        debug(ScopeBuffer) buf[0 .. bufLen] = 0;
+        if (bufLen & wasResized)
+            realloc(buf, 0);
+        buf = null;
+        bufLen = 0;
+        used = 0;
+    }
+
+    /****************************
+     * Copying of ScopeBuffer is not allowed.
+     */
+    //@disable this(this);
+
+    /************************
+     * Append element c to the buffer.
+     * This member function makes ScopeBuffer an OutputRange.
+     */
+    void put(T c)
+    {
+        /* j will get enregistered, while used will not because resize() may change used
+         */
+        const j = used;
+        if (j == bufLen)
+        {
+            assert(j <= (uint.max - 16) / 2);
+            resize(j * 2 + 16);
+        }
+        buf[j] = c;
+        used = j + 1;
+    }
+
+    /************************
+     * Append array s to the buffer.
+     *
+     * If $(D const(T)) can be converted to $(D T), then put will accept
+     * $(D const(T)[]) as input. It will accept a $(D T[]) otherwise.
+     */
+    private alias CT = Select!(is(const(T) : T), const(T), T);
+    /// ditto
+    void put(CT[] s)
+    {
+        const newlen = used + s.length;
+        assert((cast(ulong)used + s.length) <= uint.max);
+        const len = bufLen;
+        if (newlen > len)
+        {
+            assert(len <= uint.max / 2);
+            resize(newlen <= len * 2 ? len * 2 : newlen);
+        }
+        buf[used .. newlen] = s[];
+        used = cast(uint)newlen;
+    }
+
+    /******
+     * Retrieve a slice into the result.
+     * Returns:
+     *  A slice into the temporary buffer that is only
+     *  valid until the next put() or ScopeBuffer goes out of scope.
+     */
+    @system inout(T)[] opSlice(size_t lower, size_t upper) inout
+        in
+        {
+            assert(lower <= bufLen);
+            assert(upper <= bufLen);
+            assert(lower <= upper);
+        }
+    body
+    {
+        return buf[lower .. upper];
+    }
+
+    /// ditto
+    @system inout(T)[] opSlice() inout
+    {
+        assert(used <= bufLen);
+        return buf[0 .. used];
+    }
+
+    /*******
+     * Returns:
+     *  the element at index i.
+     */
+    ref inout(T) opIndex(size_t i) inout
+    {
+        assert(i < bufLen);
+        return buf[i];
+    }
+
+    /***
+     * Returns:
+     *  the number of elements in the ScopeBuffer
+     */
+    @property size_t length() const
+    {
+        return used;
+    }
+
+    /***
+     * Used to shrink the length of the buffer,
+     * typically to 0 so the buffer can be reused.
+     * Cannot be used to extend the length of the buffer.
+     */
+    @property void length(size_t i)
+        in
+        {
+            assert(i <= this.used);
+        }
+    body
+    {
+        this.used = cast(uint)i;
+    }
+
+    alias opDollar = length;
+
+  private:
+    T* buf;
+    // Using uint instead of size_t so the struct fits in 2 registers in 64 bit code
+    uint bufLen;
+    enum wasResized = 1;         // this bit is set in bufLen if we control the memory
+    uint used;
+
+    void resize(size_t newsize)
+        in
+        {
+            assert(newsize <= uint.max);
+        }
+    body
+    {
+        //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize);
+        newsize |= wasResized;
+        void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof);
+        if (!newBuf)
+            onOutOfMemoryError();
+        if (!(bufLen & wasResized))
+        {
+            memcpy(newBuf, buf, used * T.sizeof);
+            debug(ScopeBuffer) buf[0 .. bufLen] = 0;
+        }
+        buf = cast(T*)newBuf;
+        bufLen = cast(uint)newsize;
+
+        /* This function is called only rarely,
+         * inlining results in poorer register allocation.
+         */
+        version (DigitalMars)
+            /* With dmd, a fake loop will prevent inlining.
+             * Using a hack until a language enhancement is implemented.
+             */
+            while (1) { break; }
+    }
+}
+
+unittest
+{
+    import core.stdc.stdio;
+    import std.range;
+
+    char[2] tmpbuf = void;
+    {
+    // Exercise all the lines of code except for assert(0)'s
+    auto textbuf = ScopeBuffer!char(tmpbuf);
+    scope(exit) textbuf.free();
+
+    static assert(isOutputRange!(ScopeBuffer!char, char));
+
+    textbuf.put('a');
+    textbuf.put('x');
+    textbuf.put("abc");         // tickle put([])'s resize
+    assert(textbuf.length == 5);
+    assert(textbuf[1..3] == "xa");
+    assert(textbuf[3] == 'b');
+
+    textbuf.length = textbuf.length - 1;
+    assert(textbuf[0..textbuf.length] == "axab");
+
+    textbuf.length = 3;
+    assert(textbuf[0..textbuf.length] == "axa");
+    assert(textbuf[textbuf.length - 1] == 'a');
+    assert(textbuf[1..3] == "xa");
+
+    textbuf.put(cast(dchar)'z');
+    assert(textbuf[] == "axaz");
+
+    textbuf.length = 0;                 // reset for reuse
+    assert(textbuf.length == 0);
+
+    foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj")
+    {
+        textbuf.put(c); // tickle put(c)'s resize
+    }
+    assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj");
+    } // run destructor on textbuf here
+
+}
+
+unittest
+{
+    string cat(string s1, string s2)
+    {
+        char[10] tmpbuf = void;
+        auto textbuf = ScopeBuffer!char(tmpbuf);
+        scope(exit) textbuf.free();
+        textbuf.put(s1);
+        textbuf.put(s2);
+        textbuf.put("even more");
+        return textbuf[].idup;
+    }
+
+    auto s = cat("hello", "betty");
+    assert(s == "hellobettyeven more");
+}
+
+// const
+unittest
+{
+    char[10] tmpbuf = void;
+    auto textbuf = ScopeBuffer!char(tmpbuf);
+    scope(exit) textbuf.free();
+    foreach(i; 0..10) textbuf.put('w');
+    const csb = textbuf;
+    const elem = csb[3];
+    const slice0 = csb[0..5];
+    const slice1 = csb[];
+}
+
+/*********************************
+ * This is a slightly simpler way to create a ScopeBuffer instance
+ * that uses type deduction.
+ * Params:
+ *      tmpbuf = the initial buffer to use
+ * Returns:
+ *      an instance of ScopeBuffer
+ * Example:
+---
+ubyte[10] tmpbuf = void;
+auto sb = scopeBuffer(tmpbuf);
+scope(exit) sp.free();
+---
+ */
+
+auto scopeBuffer(T)(T[] tmpbuf)
+{
+    return ScopeBuffer!T(tmpbuf);
+}
+
+unittest
+{
+    ubyte[10] tmpbuf = void;
+    auto sb = scopeBuffer(tmpbuf);
+    scope(exit) sb.free();
+}
+
+unittest
+{
+    ScopeBuffer!(int*) b;
+    int*[] s;
+    b.put(s);
+
+    ScopeBuffer!char c;
+    string s1;
+    char[] s2;
+    c.put(s1);
+    c.put(s2);
+}
diff --git a/libphobos/src/std/internal/test/dummyrange.d b/libphobos/src/std/internal/test/dummyrange.d
new file mode 100644
index 00000000000..68ebf16968f
--- /dev/null
+++ b/libphobos/src/std/internal/test/dummyrange.d
@@ -0,0 +1,557 @@
+/**
+For testing only.
+Used with the dummy ranges for testing higher order ranges.
+*/
+module std.internal.test.dummyrange;
+
+import std.meta;
+import std.typecons;
+import std.range.primitives;
+
+enum RangeType
+{
+    Input,
+    Forward,
+    Bidirectional,
+    Random
+}
+
+enum Length
+{
+    Yes,
+    No
+}
+
+enum ReturnBy
+{
+    Reference,
+    Value
+}
+
+import std.traits : isArray;
+
+// Range that's useful for testing other higher order ranges,
+// can be parametrized with attributes.  It just dumbs down an array of
+// numbers 1..10.
+struct DummyRange(ReturnBy _r, Length _l, RangeType _rt, T = uint[])
+        if (isArray!T)
+{
+    private enum uinttestData =
+        [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U];
+    // These enums are so that the template params are visible outside
+    // this instantiation.
+    enum r = _r;
+    enum l = _l;
+    enum rt = _rt;
+
+    static if (is(T == uint[]))
+    {
+        T arr = uinttestData;
+    }
+    else
+    {
+        T arr;
+    }
+
+    alias RetType = ElementType!(T);
+    alias RetTypeNoAutoDecoding = ElementEncodingType!(T);
+
+    void reinit()
+    {
+        // Workaround for DMD bug 4378
+        static if (is(T == uint[]))
+        {
+            arr = uinttestData;
+        }
+    }
+
+    void popFront()
+    {
+        arr = arr[1..$];
+    }
+
+    @property bool empty() const
+    {
+        return arr.length == 0;
+    }
+
+    static if (r == ReturnBy.Reference)
+    {
+        @property ref inout(RetType) front() inout
+        {
+            return arr[0];
+        }
+    }
+    else
+    {
+        @property RetType front() const
+        {
+            return arr[0];
+        }
+
+        @property void front(RetTypeNoAutoDecoding val)
+        {
+            arr[0] = val;
+        }
+    }
+
+    static if (rt >= RangeType.Forward)
+    {
+        @property typeof(this) save()
+        {
+            return this;
+        }
+    }
+
+    static if (rt >= RangeType.Bidirectional)
+    {
+        void popBack()
+        {
+            arr = arr[0..$ - 1];
+        }
+
+        static if (r == ReturnBy.Reference)
+        {
+            @property ref inout(RetType) back() inout
+            {
+                return arr[$ - 1];
+            }
+        }
+        else
+        {
+            @property RetType back() const
+            {
+                return arr[$ - 1];
+            }
+
+            @property void back(RetTypeNoAutoDecoding val)
+            {
+                arr[$ - 1] = val;
+            }
+        }
+    }
+
+    static if (rt >= RangeType.Random)
+    {
+        static if (r == ReturnBy.Reference)
+        {
+            ref inout(RetType) opIndex(size_t index) inout
+            {
+                return arr[index];
+            }
+        }
+        else
+        {
+            RetType opIndex(size_t index) const
+            {
+                return arr[index];
+            }
+
+            RetType opIndexAssign(RetTypeNoAutoDecoding val, size_t index)
+            {
+                return arr[index] = val;
+            }
+
+            RetType opIndexOpAssign(string op)(RetTypeNoAutoDecoding value, size_t index)
+            {
+                mixin("return arr[index] " ~ op ~ "= value;");
+            }
+
+            RetType opIndexUnary(string op)(size_t index)
+            {
+                mixin("return " ~ op ~ "arr[index];");
+            }
+        }
+
+        typeof(this) opSlice(size_t lower, size_t upper)
+        {
+            auto ret = this;
+            ret.arr = arr[lower..upper];
+            return ret;
+        }
+    }
+
+    static if (l == Length.Yes)
+    {
+        @property size_t length() const
+        {
+            return arr.length;
+        }
+
+        alias opDollar = length;
+    }
+}
+
+enum dummyLength = 10;
+
+alias AllDummyRanges = AliasSeq!(
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward),
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional),
+    DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random),
+    DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward),
+    DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional),
+    DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input),
+    DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward),
+    DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional),
+    DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random),
+    DummyRange!(ReturnBy.Value, Length.No, RangeType.Input),
+    DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward),
+    DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional)
+);
+
+template AllDummyRangesType(T)
+{
+    alias AllDummyRangesType = AliasSeq!(
+        DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward, T),
+        DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional, T),
+        DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T),
+        DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward, T),
+        DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional, T),
+        DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input, T),
+        DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward, T),
+        DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional, T),
+        DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T),
+        DummyRange!(ReturnBy.Value, Length.No, RangeType.Input, T),
+        DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward, T),
+        DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional, T)
+    );
+}
+
+/**
+Tests whether forward, bidirectional and random access properties are
+propagated properly from the base range(s) R to the higher order range
+H.  Useful in combination with DummyRange for testing several higher
+order ranges.
+*/
+template propagatesRangeType(H, R...)
+{
+    static if (allSatisfy!(isRandomAccessRange, R))
+        enum bool propagatesRangeType = isRandomAccessRange!H;
+    else static if (allSatisfy!(isBidirectionalRange, R))
+        enum bool propagatesRangeType = isBidirectionalRange!H;
+    else static if (allSatisfy!(isForwardRange, R))
+        enum bool propagatesRangeType = isForwardRange!H;
+    else
+        enum bool propagatesRangeType = isInputRange!H;
+}
+
+template propagatesLength(H, R...)
+{
+    static if (allSatisfy!(hasLength, R))
+        enum bool propagatesLength = hasLength!H;
+    else
+        enum bool propagatesLength = !hasLength!H;
+}
+
+/**
+Reference type input range
+*/
+class ReferenceInputRange(T)
+{
+    import std.array : array;
+
+    this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);}
+    final @property ref T front(){return _payload.front;}
+    final void popFront(){_payload.popFront();}
+    final @property bool empty(){return _payload.empty;}
+    protected T[] _payload;
+}
+
+/**
+Infinite input range
+*/
+class ReferenceInfiniteInputRange(T)
+{
+    this(T first = T.init) {_val = first;}
+    final @property T front(){return _val;}
+    final void popFront(){++_val;}
+    enum bool empty = false;
+    protected T _val;
+}
+
+/**
+Reference forward range
+*/
+class ReferenceForwardRange(T) : ReferenceInputRange!T
+{
+    this(Range)(Range r) if (isInputRange!Range) {super(r);}
+    final @property auto save(this This)() {return new This( _payload);}
+}
+
+/**
+Infinite forward range
+*/
+class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T
+{
+    this(T first = T.init) {super(first);}
+    final @property ReferenceInfiniteForwardRange save()
+    {return new ReferenceInfiniteForwardRange!T(_val);}
+}
+
+/**
+Reference bidirectional range
+*/
+class ReferenceBidirectionalRange(T) : ReferenceForwardRange!T
+{
+    this(Range)(Range r) if (isInputRange!Range) {super(r);}
+    final @property ref T back(){return _payload.back;}
+    final void popBack(){_payload.popBack();}
+}
+
+unittest
+{
+    static assert(isInputRange!(ReferenceInputRange!int));
+    static assert(isInputRange!(ReferenceInfiniteInputRange!int));
+
+    static assert(isForwardRange!(ReferenceForwardRange!int));
+    static assert(isForwardRange!(ReferenceInfiniteForwardRange!int));
+
+    static assert(isBidirectionalRange!(ReferenceBidirectionalRange!int));
+}
+
+private:
+
+pure struct Cmp(T) if (is(T == uint))
+{
+    static auto iota(size_t low = 1, size_t high = 11)
+    {
+        import std.range : iota;
+        return iota(cast(uint)low, cast(uint)high);
+    }
+
+    static void init(ref uint[] arr)
+    {
+        import std.array : array;
+        arr = iota().array;
+    }
+
+    static bool function(uint,uint) cmp = function(uint a, uint b) { return a == b; };
+
+    enum dummyValue = 1337U;
+    enum dummyValueRslt = 1337U * 2;
+}
+
+pure struct Cmp(T) if (is(T == double))
+{
+    import std.math : approxEqual;
+
+    static auto iota(size_t low = 1, size_t high = 11)
+    {
+        import std.range : iota;
+        return iota(cast(double)low, cast(double)high, 1.0);
+    }
+
+    static void init(ref double[] arr)
+    {
+        import std.array : array;
+        arr = iota().array;
+    }
+
+    alias cmp = approxEqual!(double,double);
+
+    enum dummyValue = 1337.0;
+    enum dummyValueRslt = 1337.0 * 2.0;
+}
+
+struct TestFoo
+{
+    int a;
+
+    bool opEquals(const ref TestFoo other) const
+    {
+        return this.a == other.a;
+    }
+
+    TestFoo opBinary(string op)(TestFoo other)
+    {
+        TestFoo ret = this;
+        mixin("ret.a " ~ op ~ "= other.a;");
+        return ret;
+    }
+
+    TestFoo opOpAssign(string op)(TestFoo other)
+    {
+        mixin("this.a " ~ op ~ "= other.a;");
+        return this;
+    }
+}
+
+pure struct Cmp(T) if (is(T == TestFoo))
+{
+    import std.math : approxEqual;
+
+    static auto iota(size_t low = 1, size_t high = 11)
+    {
+        import std.range : iota;
+        import std.algorithm.iteration : map;
+        return iota(cast(int)low, cast(int)high).map!(a => TestFoo(a));
+    }
+
+    static void init(ref TestFoo[] arr)
+    {
+        import std.array : array;
+        arr = iota().array;
+    }
+
+    static bool function(TestFoo,TestFoo) cmp = function(TestFoo a, TestFoo b)
+    {
+        return a.a == b.a;
+    };
+
+    @property static TestFoo dummyValue()
+    {
+        return TestFoo(1337);
+    }
+
+    @property static TestFoo dummyValueRslt()
+    {
+        return TestFoo(1337 * 2);
+    }
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.range : iota, retro, repeat;
+    import std.traits : Unqual;
+
+    static void testInputRange(T,Cmp)()
+    {
+        T it;
+        Cmp.init(it.arr);
+        for (size_t numRuns = 0; numRuns < 2; ++numRuns)
+        {
+            if (numRuns == 1)
+            {
+                static if (is(Unqual!(ElementType!(T)) == uint))
+                {
+                    it.reinit();
+                }
+
+                Cmp.init(it.arr);
+            }
+
+            assert(equal!(Cmp.cmp)(it, Cmp.iota(1, 11)));
+
+            static if (hasLength!T)
+            {
+                assert(it.length == 10);
+            }
+
+            assert(!Cmp.cmp(it.front, Cmp.dummyValue));
+            auto s = it.front;
+            it.front = Cmp.dummyValue;
+            assert(Cmp.cmp(it.front, Cmp.dummyValue));
+            it.front = s;
+
+            auto cmp = Cmp.iota(1,11);
+
+            size_t jdx = 0;
+            while (!it.empty && !cmp.empty)
+            {
+                static if (hasLength!T)
+                {
+                    assert(it.length == 10 - jdx);
+                }
+
+                assert(Cmp.cmp(it.front, cmp.front));
+                it.popFront();
+                cmp.popFront();
+
+                ++jdx;
+            }
+
+            assert(it.empty);
+            assert(cmp.empty);
+        }
+
+    }
+
+    static void testForwardRange(T,Cmp)()
+    {
+        T it;
+        Cmp.init(it.arr);
+        auto s = it.save();
+        s.popFront();
+        assert(!Cmp.cmp(s.front, it.front));
+    }
+
+    static void testBidirectionalRange(T,Cmp)()
+    {
+        T it;
+        Cmp.init(it.arr);
+        assert(equal!(Cmp.cmp)(it.retro, Cmp.iota().retro));
+
+        auto s = it.back;
+        assert(!Cmp.cmp(s, Cmp.dummyValue));
+        it.back = Cmp.dummyValue;
+        assert( Cmp.cmp(it.back, Cmp.dummyValue));
+        it.back = s;
+    }
+
+    static void testRandomAccessRange(T,Cmp)()
+    {
+        T it;
+        Cmp.init(it.arr);
+        size_t idx = 0;
+        foreach (jt; it)
+        {
+            assert(it[idx] == jt);
+
+            T copy = it[idx .. $];
+            auto cmp = Cmp.iota(idx + 1, it.length + 1);
+            assert(equal!(Cmp.cmp)(copy, cmp));
+
+            ++idx;
+        }
+
+        {
+            auto copy = it;
+            copy.arr = it.arr.dup;
+            for (size_t i = 0; i < copy.length; ++i)
+            {
+                copy[i] = Cmp.dummyValue;
+                copy[i] += Cmp.dummyValue;
+            }
+            assert(equal!(Cmp.cmp)(copy, Cmp.dummyValueRslt.repeat(copy.length)));
+        }
+
+        static if (it.r == ReturnBy.Reference)
+        {
+            T copy;
+            copy.arr = it.arr.dup;
+            for (size_t i = 0; i < copy.length; ++i)
+            {
+                copy[i] = Cmp.dummyValue;
+                copy[i] += Cmp.dummyValue;
+            }
+
+            assert(equal!(Cmp.cmp)(copy, Cmp.dummyValueRslt.repeat(copy.length)));
+        }
+    }
+
+    import std.meta : AliasSeq;
+
+    foreach (S; AliasSeq!(uint, double, TestFoo))
+    {
+        foreach (T; AllDummyRangesType!(S[]))
+        {
+            testInputRange!(T,Cmp!S)();
+
+            static if (isForwardRange!T)
+            {
+                testForwardRange!(T,Cmp!S)();
+            }
+
+            static if (isBidirectionalRange!T)
+            {
+                testBidirectionalRange!(T,Cmp!S)();
+            }
+
+            static if (isRandomAccessRange!T)
+            {
+                testRandomAccessRange!(T,Cmp!S)();
+            }
+        }
+    }
+}
diff --git a/libphobos/src/std/internal/test/uda.d b/libphobos/src/std/internal/test/uda.d
new file mode 100644
index 00000000000..88e3a1b5ae9
--- /dev/null
+++ b/libphobos/src/std/internal/test/uda.d
@@ -0,0 +1,16 @@
+/**
+For testing only.
+Provides a struct with UDA's defined in an external module.
+Useful for validating behavior with member privacy.
+*/
+module std.internal.test.uda;
+
+enum Attr;
+
+struct HasPrivateMembers
+{
+  @Attr int a;
+  int b;
+  @Attr private int c;
+  private int d;
+}
diff --git a/libphobos/src/std/internal/unicode_comp.d b/libphobos/src/std/internal/unicode_comp.d
new file mode 100644
index 00000000000..966a83e0355
--- /dev/null
+++ b/libphobos/src/std/internal/unicode_comp.d
@@ -0,0 +1,34 @@
+module std.internal.unicode_comp;
+import std.internal.unicode_tables;
+
+@safe pure nothrow @nogc:
+
+static if(size_t.sizeof == 8) {
+//6976 bytes
+enum combiningClassTrieEntries = TrieEntry!(ubyte, 8, 7, 6)([ 0x0,  0x20,  0x120], [ 0x100,  0x400,  0x1240], [ 0x402030202020100,  0x206020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x20001,  0x300000000,  0x5000400000000,  0x8000000070006,  0xb0000000a0009,  0xe0000000d000c,  0x11000f0010000f,  0x11000f0011000f,  0x1100000011000f,  0x11000f00120000,  0x13000000110000,  0x17001600150014,  0x1b001a00190018,  0x1d0000001c,  0x0,  0x0,  0x1e0000,  0x0,  0x0,  0x0,  0x2000000000001f,  0x2100000000,  0x22,  0x240023,  0x28002700260025,  0x2a000000000029,  0x2b000000000000,  0x0,  0x0,  0x2c000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2d000000000000,  0x2f0000002e0000,  0x0,  0x0,  0x3100000030,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x34003300320000,  0x0,  0x36000000000035,  0x3a003900380037,  0x3c003b00000000,  0x3d000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3e,  0x0,  0x0,  0x3f,  0x0,  0x0,  0x40000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x41,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4200350000,  0x3a000000000043,  0x0,  0x0,  0x0,  0x0,  0x4400000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4600450000,  0x470000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6e6,  0xdcdce8e6e6e6e6e6,  0xdcdcdcdcd8e8dcdc,  0xcadcdcdcdccacadc,  0xdcdcdcdcdcdcdcca,  0x1010101dcdcdcdc,  0xe6e6e6dcdcdcdc01,  0xdce6f0e6e6e6e6e6,  0xdcdce6e6e6dcdc,  0xe6dcdcdcdce6e6e6,  0xe9eaeae9e6dcdce8,  0xe6e6e6e6e6e9eaea,  0xe6e6e6e6e6e6e6e6,  0x0,  0x0,  0xe6e6e6e6e6000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6dce6e6e6e6dc00,  0xe6e6e6e6dcdee6e6,  0xdcdcdcdcdcdce6e6,  0xe6e4dee6e6dce6e6,  0x11100f0e0d0c0b0a,  0x1700161514131312,  0x1200dce600191800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6e6e6e6e6,  0x201f1e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1f1e1d1c1b000000,  0xe6dcdce6e6222120,  0xdce6e6dce6e6e6e6,  0x0,  0x0,  0x23,  0x0,  0x0,  0x0,  0xe6e6000000000000,  0xe60000e6e6e6e6e6,  0xe60000e6dce6e6e6,  0xdce6e6dc00e6,  0x0,  0x0,  0x0,  0x0,  0x2400,  0x0,  0x0,  0x0,  0xdce6e6dce6e6dce6,  0xe6dce6dcdce6dcdc,  0xe6dce6dce6dce6e6,  0xe6e6dc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6e6000000,  0xe6dce6e6,  0x0,  0x0,  0x0,  0xe6e6000000000000,  0xe6e6e6e6e600e6e6,  0xe6e6e600e6e6e6e6,  0xe6e6e6e6e600,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdcdcdc00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6dce6e600000000,  0xdcdcdce6e6e6dce6,  0xe6dce6e6e61d1c1b,  0xe6e6e6e6dcdce6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x700000000,  0x0,  0x90000000000,  0xe6e6dce600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000000000,  0x5b540000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x96767,  0x0,  0x6b6b6b6b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7676,  0x0,  0x7a7a7a7a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdcdc,  0x0,  0x0,  0xdc00dc0000000000,  0xd800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8400828100,  0x828282820000,  0xe6e60009e6e60082,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x700000000000000,  0x90900,  0x0,  0xdc0000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e60000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900000000,  0x0,  0x0,  0x0,  0x900000000,  0x0,  0x0,  0x0,  0x90000,  0xe60000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdce6de00,  0x0,  0x0,  0xe600000000000000,  0xdc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,  0x0,  0xe6e6e60000000000,  0xdc0000e6e6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x700000000,  0x0,  0x900000000,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6dce6000000,  0xe6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9090000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7000000000000,  0x0,  0x9090000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x700000000000000,  0x0,  0x0,  0x0,  0xdcdcdc0100e6e6e6,  0xdcdcdcdce6e6dcdc,  0x1010101010100e6,  0xdc0000000001,  0xe600000000,  0x0,  0xe6e6e6e6e6dce6e6,  0xdcd6eae6e6dce6e6,  0xe6e6e6e6e6e6e6ca,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6,  0x0,  0x0,  0xdce6dce900000000,  0x0,  0x0,  0xe6e6e6e60101e6e6,  0xe6e6010101,  0xe60101000000e600,  0xdcdcdcdc0101e6dc,  0xe6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe600000000000000,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900000000000000,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe0e0dee8e4da0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe600000000000000,  0xe6e6e6e600000000,  0xe6e6e6e6e6e6,  0x0,  0x0,  0x0,  0xe600000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6,  0x0,  0x9000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900000000,  0x0,  0x0,  0x0,  0xe6e6e6e6e6e6e6e6,  0xe6e6e6e6e6e6e6e6,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdcdcdc000000,  0x0,  0x0,  0x0,  0x0,  0x9000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7000000,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe60000dce6e600e6,  0xe6e60000000000e6,  0xe600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc0000000000,  0x0,  0xe600dc0000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900000000dc01e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x70900,  0xe6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x909000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x709000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1d8d80000000000,  0xd8d8e20000000101,  0xd8d8d8,  0xdcdcdcdcdc000000,  0xe6e6e60000dcdcdc,  0xdcdce6e6,  0x0,  0x0,  0x0,  0xe6e6e6e60000,  0x0,  0x0,  0xe6e6e60000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+enum composeIdxMask = (1<<11)-1, composeCntShift = 11;
+enum compositionJumpTrieEntries = TrieEntry!(ushort, 12, 9)([ 0x0,  0x400], [ 0x1000,  0x2000], [ 0x3000200010000,  0x7000600050004,  0x7000700070008,  0xa000700090007,  0x70007000c000b,  0x7000700070007,  0x700070007000d,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x700070007000e,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff080208010800,  0x281618138003ffff,  0x383308328821301b,  0x285108507841383a,  0x8068485f185c3056,  0x3882407affff1078,  0x30a510a398903889,  0xffff30b648ad10ab,  0xffffffffffffffff,  0x28cf18cc80bcffff,  0x38ec08eb88da30d4,  0x290b110970fb40f3,  0x8122491919163110,  0x393c4134ffff1132,  0x3960115e994b4143,  0xffff317351691167,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff1979,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff217cffffffff,  0x984118209810980,  0xffff2185ffffffff,  0x989ffffffffffff,  0xffffffffffffffff,  0xffff0991198e218a,  0xffffffffffff0992,  0xffffffffffff2193,  0xffff2197ffffffff,  0x99f119d099c099b,  0xffff21a0ffffffff,  0x9a4ffffffffffff,  0xffffffffffffffff,  0xffff09ac19a921a5,  0xffffffffffff09ad,  0xffffffffffff21ae,  0x21b621b2ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x11bc11baffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff11c011be,  0xffffffffffffffff,  0xffffffffffffffff,  0x9c309c2ffffffff,  0xffffffffffffffff,  0xffffffff09c509c4,  0xffffffffffffffff,  0x9c909c809c709c6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x9caffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff29d029cb,  0xffffffffffffffff,  0xffffffffffffffff,  0x29d5ffffffffffff,  0xffffffffffff29da,  0x9dfffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x9e109e0ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x9e309e2ffffffff,  0xffffffff09e509e4,  0x9e709e6ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff09e8ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff39e9ffff,  0x29f4ffff21f0ffff,  0xffffffff39f9ffff,  0x2200ffffffffffff,  0xffffffff0a04ffff,  0xffffffff3205ffff,  0xffffffff2a0bffff,  0xffff0a11ffff0a10,  0xffffffff4212ffff,  0x321effff221affff,  0xffffffff4224ffff,  0x222cffffffffffff,  0xffffffff1230ffff,  0xffffffff4232ffff,  0x1a431a40323affff,  0xffff0a46ffffffff,  0xffff1247ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0a49ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa4cffffffff124a,  0xa5212501a4dffff,  0xffff0a57ffff2253,  0xffff0a58ffffffff,  0x2259ffffffffffff,  0xa5dffffffffffff,  0xa5effffffffffff,  0xffffffff0a5fffff,  0xa62ffffffff1260,  0xa6812661a63ffff,  0xffff0a6dffff2269,  0xffff0a6effffffff,  0x226fffffffffffff,  0xa73ffffffffffff,  0xa74ffffffffffff,  0xffffffff0a75ffff,  0xffffffffffffffff,  0xffff0a76ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0a780a77,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0a7a0a79,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0a7c0a7b,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1a7dffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0a81ffff0a80,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0a82ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0a83ffffffff,  0xffffffff0a84ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff0a85,  0xffffffffffffffff,  0xa87ffffffff0a86,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1288ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1a8affffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0a8dffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa90128effffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0a91ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa92ffffffffffff,  0xffffffffffffffff,  0xffff1a93ffffffff,  0xffff0a96ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa991297ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff1a9affff,  0xffffffffffff0a9d,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0a9effff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xaa0ffff0a9fffff,  0xaa2ffff0aa1ffff,  0xffffffff0aa3ffff,  0xffffffff0aa4ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0aa5ffffffff,  0xaa80aa7ffff0aa6,  0xffff0aa9ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xaab0aaaffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xaad0aacffffffff,  0xffffffffffffffff,  0xaaf0aaeffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff12b212b0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0ab50ab4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0ab70ab6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xac10ac022bc22b8,  0xac50ac40ac30ac2,  0xacf0ace22ca22c6,  0xad30ad20ad10ad0,  0xffffffff12d612d4,  0xffffffffffffffff,  0xffffffff12da12d8,  0xffffffffffffffff,  0xae50ae422e022dc,  0xae90ae80ae70ae6,  0xaf30af222ee22ea,  0xaf70af60af50af4,  0xffffffff1afb1af8,  0xffffffffffffffff,  0xffffffff1b011afe,  0xffffffffffffffff,  0xffffffff13061304,  0xffffffffffffffff,  0xffffffff130a1308,  0xffffffffffffffff,  0xffffffff1b0f1b0c,  0xffffffffffffffff,  0xffffffff1b12ffff,  0xffffffffffffffff,  0xb1e0b1d23192315,  0xb220b210b200b1f,  0xb2c0b2b23272323,  0xb300b2f0b2e0b2d,  0xffffffffffff0b31,  0xffffffffffff0b32,  0xffffffffffffffff,  0xffffffffffff0b33,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b34ffffffff,  0xffffffffffffffff,  0x1b35ffffffffffff,  0xffffffffffffffff,  0xffff0b38ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b39ffffffff,  0xffffffffffffffff,  0xffff1b3affffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b3effff0b3d,  0xffffffffffff0b3f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b41ffff0b40,  0xffffffffffff0b42,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xb43ffffffffffff,  0xffffffffffffffff,  0xb45ffffffff0b44,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xb46ffffffffffff,  0xffffffff0b47ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff0b48,  0xb49ffffffffffff,  0xffffffff0b4affff,  0xffffffffffff0b4b,  0xffffffff0b4cffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0b4dffff,  0xffffffff0b4f0b4e,  0xffffffffffffffff,  0xffffffffffffffff,  0xb510b50ffffffff,  0xb530b52ffffffff,  0xb550b54ffffffff,  0xffffffff0b570b56,  0xb590b58ffffffff,  0xb5b0b5affffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b5d0b5cffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b5effffffff,  0xffffffffffffffff,  0xb61ffff0b600b5f,  0xffffffffffffffff,  0xb630b62ffffffff,  0xffffffff0b650b64,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0b66ffffffff,  0xb67ffffffffffff,  0xb69ffff0b68ffff,  0xb6bffff0b6affff,  0xb6dffff0b6cffff,  0xb6fffff0b6effff,  0xb71ffff0b70ffff,  0xffffffff0b72ffff,  0xffff0b74ffff0b73,  0xffffffffffff0b75,  0x1376ffffffffffff,  0xffff1378ffffffff,  0xffffffff137affff,  0x137effffffff137c,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0b80ffff,  0xffffffffffffffff,  0xffff0b81ffffffff,  0xb82ffffffffffff,  0xb84ffff0b83ffff,  0xb86ffff0b85ffff,  0xb88ffff0b87ffff,  0xb8affff0b89ffff,  0xb8cffff0b8bffff,  0xffffffff0b8dffff,  0xffff0b8fffff0b8e,  0xffffffffffff0b90,  0x1391ffffffffffff,  0xffff1393ffffffff,  0xffffffff1395ffff,  0x1399ffffffff1397,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xb9bffffffffffff,  0xffff0b9e0b9d0b9c,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0b9fffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xba1ffff0ba0ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0ba2ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0ba40ba3ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+@property immutable(CompEntry[]) compositionTable()
+{
+alias CE = CompEntry;
+static immutable CE[] t = [CE(0x00338, 0x0226e),CE(0x00338, 0x02260),CE(0x00338, 0x0226f),CE(0x00300, 0x000c0),CE(0x00301, 0x000c1),CE(0x00302, 0x000c2),CE(0x00303, 0x000c3),CE(0x00304, 0x00100),CE(0x00306, 0x00102),CE(0x00307, 0x00226),CE(0x00308, 0x000c4),CE(0x00309, 0x01ea2),CE(0x0030a, 0x000c5),CE(0x0030c, 0x001cd),CE(0x0030f, 0x00200),CE(0x00311, 0x00202),CE(0x00323, 0x01ea0),CE(0x00325, 0x01e00),CE(0x00328, 0x00104),CE(0x00307, 0x01e02),CE(0x00323, 0x01e04),CE(0x00331, 0x01e06),CE(0x00301, 0x00106),CE(0x00302, 0x00108),CE(0x00307, 0x0010a),CE(0x0030c, 0x0010c),CE(0x00327, 0x000c7),CE(0x00307, 0x01e0a),CE(0x0030c, 0x0010e),CE(0x00323, 0x01e0c),CE(0x00327, 0x01e10),CE(0x0032d, 0x01e12),CE(0x00331, 0x01e0e),CE(0x00300, 0x000c8),CE(0x00301, 0x000c9),CE(0x00302, 0x000ca),CE(0x00303, 0x01ebc),CE(0x00304, 0x00112),CE(0x00306, 0x00114),CE(0x00307, 0x00116),CE(0x00308, 0x000cb),CE(0x00309, 0x01eba),CE(0x0030c, 0x0011a),CE(0x0030f, 0x00204),CE(0x00311, 0x00206),CE(0x00323, 0x01eb8),CE(0x00327, 0x00228),CE(0x00328, 0x00118),CE(0x0032d, 0x01e18),CE(0x00330, 0x01e1a),CE(0x00307, 0x01e1e),CE(0x00301, 0x001f4),CE(0x00302, 0x0011c),CE(0x00304, 0x01e20),CE(0x00306, 0x0011e),CE(0x00307, 0x00120),CE(0x0030c, 0x001e6),CE(0x00327, 0x00122),CE(0x00302, 0x00124),CE(0x00307, 0x01e22),CE(0x00308, 0x01e26),CE(0x0030c, 0x0021e),CE(0x00323, 0x01e24),CE(0x00327, 0x01e28),CE(0x0032e, 0x01e2a),CE(0x00300, 0x000cc),CE(0x00301, 0x000cd),CE(0x00302, 0x000ce),CE(0x00303, 0x00128),CE(0x00304, 0x0012a),CE(0x00306, 0x0012c),CE(0x00307, 0x00130),CE(0x00308, 0x000cf),CE(0x00309, 0x01ec8),CE(0x0030c, 0x001cf),CE(0x0030f, 0x00208),CE(0x00311, 0x0020a),CE(0x00323, 0x01eca),CE(0x00328, 0x0012e),CE(0x00330, 0x01e2c),CE(0x00302, 0x00134),CE(0x00301, 0x01e30),CE(0x0030c, 0x001e8),CE(0x00323, 0x01e32),CE(0x00327, 0x00136),CE(0x00331, 0x01e34),CE(0x00301, 0x00139),CE(0x0030c, 0x0013d),CE(0x00323, 0x01e36),CE(0x00327, 0x0013b),CE(0x0032d, 0x01e3c),CE(0x00331, 0x01e3a),CE(0x00301, 0x01e3e),CE(0x00307, 0x01e40),CE(0x00323, 0x01e42),CE(0x00300, 0x001f8),CE(0x00301, 0x00143),CE(0x00303, 0x000d1),CE(0x00307, 0x01e44),CE(0x0030c, 0x00147),CE(0x00323, 0x01e46),CE(0x00327, 0x00145),CE(0x0032d, 0x01e4a),CE(0x00331, 0x01e48),CE(0x00300, 0x000d2),CE(0x00301, 0x000d3),CE(0x00302, 0x000d4),CE(0x00303, 0x000d5),CE(0x00304, 0x0014c),CE(0x00306, 0x0014e),CE(0x00307, 0x0022e),CE(0x00308, 0x000d6),CE(0x00309, 0x01ece),CE(0x0030b, 0x00150),CE(0x0030c, 0x001d1),CE(0x0030f, 0x0020c),CE(0x00311, 0x0020e),CE(0x0031b, 0x001a0),CE(0x00323, 0x01ecc),CE(0x00328, 0x001ea),CE(0x00301, 0x01e54),CE(0x00307, 0x01e56),CE(0x00301, 0x00154),CE(0x00307, 0x01e58),CE(0x0030c, 0x00158),CE(0x0030f, 0x00210),CE(0x00311, 0x00212),CE(0x00323, 0x01e5a),CE(0x00327, 0x00156),CE(0x00331, 0x01e5e),CE(0x00301, 0x0015a),CE(0x00302, 0x0015c),CE(0x00307, 0x01e60),CE(0x0030c, 0x00160),CE(0x00323, 0x01e62),CE(0x00326, 0x00218),CE(0x00327, 0x0015e),CE(0x00307, 0x01e6a),CE(0x0030c, 0x00164),CE(0x00323, 0x01e6c),CE(0x00326, 0x0021a),CE(0x00327, 0x00162),CE(0x0032d, 0x01e70),CE(0x00331, 0x01e6e),CE(0x00300, 0x000d9),CE(0x00301, 0x000da),CE(0x00302, 0x000db),CE(0x00303, 0x00168),CE(0x00304, 0x0016a),CE(0x00306, 0x0016c),CE(0x00308, 0x000dc),CE(0x00309, 0x01ee6),CE(0x0030a, 0x0016e),CE(0x0030b, 0x00170),CE(0x0030c, 0x001d3),CE(0x0030f, 0x00214),CE(0x00311, 0x00216),CE(0x0031b, 0x001af),CE(0x00323, 0x01ee4),CE(0x00324, 0x01e72),CE(0x00328, 0x00172),CE(0x0032d, 0x01e76),CE(0x00330, 0x01e74),CE(0x00303, 0x01e7c),CE(0x00323, 0x01e7e),CE(0x00300, 0x01e80),CE(0x00301, 0x01e82),CE(0x00302, 0x00174),CE(0x00307, 0x01e86),CE(0x00308, 0x01e84),CE(0x00323, 0x01e88),CE(0x00307, 0x01e8a),CE(0x00308, 0x01e8c),CE(0x00300, 0x01ef2),CE(0x00301, 0x000dd),CE(0x00302, 0x00176),CE(0x00303, 0x01ef8),CE(0x00304, 0x00232),CE(0x00307, 0x01e8e),CE(0x00308, 0x00178),CE(0x00309, 0x01ef6),CE(0x00323, 0x01ef4),CE(0x00301, 0x00179),CE(0x00302, 0x01e90),CE(0x00307, 0x0017b),CE(0x0030c, 0x0017d),CE(0x00323, 0x01e92),CE(0x00331, 0x01e94),CE(0x00300, 0x000e0),CE(0x00301, 0x000e1),CE(0x00302, 0x000e2),CE(0x00303, 0x000e3),CE(0x00304, 0x00101),CE(0x00306, 0x00103),CE(0x00307, 0x00227),CE(0x00308, 0x000e4),CE(0x00309, 0x01ea3),CE(0x0030a, 0x000e5),CE(0x0030c, 0x001ce),CE(0x0030f, 0x00201),CE(0x00311, 0x00203),CE(0x00323, 0x01ea1),CE(0x00325, 0x01e01),CE(0x00328, 0x00105),CE(0x00307, 0x01e03),CE(0x00323, 0x01e05),CE(0x00331, 0x01e07),CE(0x00301, 0x00107),CE(0x00302, 0x00109),CE(0x00307, 0x0010b),CE(0x0030c, 0x0010d),CE(0x00327, 0x000e7),CE(0x00307, 0x01e0b),CE(0x0030c, 0x0010f),CE(0x00323, 0x01e0d),CE(0x00327, 0x01e11),CE(0x0032d, 0x01e13),CE(0x00331, 0x01e0f),CE(0x00300, 0x000e8),CE(0x00301, 0x000e9),CE(0x00302, 0x000ea),CE(0x00303, 0x01ebd),CE(0x00304, 0x00113),CE(0x00306, 0x00115),CE(0x00307, 0x00117),CE(0x00308, 0x000eb),CE(0x00309, 0x01ebb),CE(0x0030c, 0x0011b),CE(0x0030f, 0x00205),CE(0x00311, 0x00207),CE(0x00323, 0x01eb9),CE(0x00327, 0x00229),CE(0x00328, 0x00119),CE(0x0032d, 0x01e19),CE(0x00330, 0x01e1b),CE(0x00307, 0x01e1f),CE(0x00301, 0x001f5),CE(0x00302, 0x0011d),CE(0x00304, 0x01e21),CE(0x00306, 0x0011f),CE(0x00307, 0x00121),CE(0x0030c, 0x001e7),CE(0x00327, 0x00123),CE(0x00302, 0x00125),CE(0x00307, 0x01e23),CE(0x00308, 0x01e27),CE(0x0030c, 0x0021f),CE(0x00323, 0x01e25),CE(0x00327, 0x01e29),CE(0x0032e, 0x01e2b),CE(0x00331, 0x01e96),CE(0x00300, 0x000ec),CE(0x00301, 0x000ed),CE(0x00302, 0x000ee),CE(0x00303, 0x00129),CE(0x00304, 0x0012b),CE(0x00306, 0x0012d),CE(0x00308, 0x000ef),CE(0x00309, 0x01ec9),CE(0x0030c, 0x001d0),CE(0x0030f, 0x00209),CE(0x00311, 0x0020b),CE(0x00323, 0x01ecb),CE(0x00328, 0x0012f),CE(0x00330, 0x01e2d),CE(0x00302, 0x00135),CE(0x0030c, 0x001f0),CE(0x00301, 0x01e31),CE(0x0030c, 0x001e9),CE(0x00323, 0x01e33),CE(0x00327, 0x00137),CE(0x00331, 0x01e35),CE(0x00301, 0x0013a),CE(0x0030c, 0x0013e),CE(0x00323, 0x01e37),CE(0x00327, 0x0013c),CE(0x0032d, 0x01e3d),CE(0x00331, 0x01e3b),CE(0x00301, 0x01e3f),CE(0x00307, 0x01e41),CE(0x00323, 0x01e43),CE(0x00300, 0x001f9),CE(0x00301, 0x00144),CE(0x00303, 0x000f1),CE(0x00307, 0x01e45),CE(0x0030c, 0x00148),CE(0x00323, 0x01e47),CE(0x00327, 0x00146),CE(0x0032d, 0x01e4b),CE(0x00331, 0x01e49),CE(0x00300, 0x000f2),CE(0x00301, 0x000f3),CE(0x00302, 0x000f4),CE(0x00303, 0x000f5),CE(0x00304, 0x0014d),CE(0x00306, 0x0014f),CE(0x00307, 0x0022f),CE(0x00308, 0x000f6),CE(0x00309, 0x01ecf),CE(0x0030b, 0x00151),CE(0x0030c, 0x001d2),CE(0x0030f, 0x0020d),CE(0x00311, 0x0020f),CE(0x0031b, 0x001a1),CE(0x00323, 0x01ecd),CE(0x00328, 0x001eb),CE(0x00301, 0x01e55),CE(0x00307, 0x01e57),CE(0x00301, 0x00155),CE(0x00307, 0x01e59),CE(0x0030c, 0x00159),CE(0x0030f, 0x00211),CE(0x00311, 0x00213),CE(0x00323, 0x01e5b),CE(0x00327, 0x00157),CE(0x00331, 0x01e5f),CE(0x00301, 0x0015b),CE(0x00302, 0x0015d),CE(0x00307, 0x01e61),CE(0x0030c, 0x00161),CE(0x00323, 0x01e63),CE(0x00326, 0x00219),CE(0x00327, 0x0015f),CE(0x00307, 0x01e6b),CE(0x00308, 0x01e97),CE(0x0030c, 0x00165),CE(0x00323, 0x01e6d),CE(0x00326, 0x0021b),CE(0x00327, 0x00163),CE(0x0032d, 0x01e71),CE(0x00331, 0x01e6f),CE(0x00300, 0x000f9),CE(0x00301, 0x000fa),CE(0x00302, 0x000fb),CE(0x00303, 0x00169),CE(0x00304, 0x0016b),CE(0x00306, 0x0016d),CE(0x00308, 0x000fc),CE(0x00309, 0x01ee7),CE(0x0030a, 0x0016f),CE(0x0030b, 0x00171),CE(0x0030c, 0x001d4),CE(0x0030f, 0x00215),CE(0x00311, 0x00217),CE(0x0031b, 0x001b0),CE(0x00323, 0x01ee5),CE(0x00324, 0x01e73),CE(0x00328, 0x00173),CE(0x0032d, 0x01e77),CE(0x00330, 0x01e75),CE(0x00303, 0x01e7d),CE(0x00323, 0x01e7f),CE(0x00300, 0x01e81),CE(0x00301, 0x01e83),CE(0x00302, 0x00175),CE(0x00307, 0x01e87),CE(0x00308, 0x01e85),CE(0x0030a, 0x01e98),CE(0x00323, 0x01e89),CE(0x00307, 0x01e8b),CE(0x00308, 0x01e8d),CE(0x00300, 0x01ef3),CE(0x00301, 0x000fd),CE(0x00302, 0x00177),CE(0x00303, 0x01ef9),CE(0x00304, 0x00233),CE(0x00307, 0x01e8f),CE(0x00308, 0x000ff),CE(0x00309, 0x01ef7),CE(0x0030a, 0x01e99),CE(0x00323, 0x01ef5),CE(0x00301, 0x0017a),CE(0x00302, 0x01e91),CE(0x00307, 0x0017c),CE(0x0030c, 0x0017e),CE(0x00323, 0x01e93),CE(0x00331, 0x01e95),CE(0x00300, 0x01fed),CE(0x00301, 0x00385),CE(0x00342, 0x01fc1),CE(0x00300, 0x01ea6),CE(0x00301, 0x01ea4),CE(0x00303, 0x01eaa),CE(0x00309, 0x01ea8),CE(0x00304, 0x001de),CE(0x00301, 0x001fa),CE(0x00301, 0x001fc),CE(0x00304, 0x001e2),CE(0x00301, 0x01e08),CE(0x00300, 0x01ec0),CE(0x00301, 0x01ebe),CE(0x00303, 0x01ec4),CE(0x00309, 0x01ec2),CE(0x00301, 0x01e2e),CE(0x00300, 0x01ed2),CE(0x00301, 0x01ed0),CE(0x00303, 0x01ed6),CE(0x00309, 0x01ed4),CE(0x00301, 0x01e4c),CE(0x00304, 0x0022c),CE(0x00308, 0x01e4e),CE(0x00304, 0x0022a),CE(0x00301, 0x001fe),CE(0x00300, 0x001db),CE(0x00301, 0x001d7),CE(0x00304, 0x001d5),CE(0x0030c, 0x001d9),CE(0x00300, 0x01ea7),CE(0x00301, 0x01ea5),CE(0x00303, 0x01eab),CE(0x00309, 0x01ea9),CE(0x00304, 0x001df),CE(0x00301, 0x001fb),CE(0x00301, 0x001fd),CE(0x00304, 0x001e3),CE(0x00301, 0x01e09),CE(0x00300, 0x01ec1),CE(0x00301, 0x01ebf),CE(0x00303, 0x01ec5),CE(0x00309, 0x01ec3),CE(0x00301, 0x01e2f),CE(0x00300, 0x01ed3),CE(0x00301, 0x01ed1),CE(0x00303, 0x01ed7),CE(0x00309, 0x01ed5),CE(0x00301, 0x01e4d),CE(0x00304, 0x0022d),CE(0x00308, 0x01e4f),CE(0x00304, 0x0022b),CE(0x00301, 0x001ff),CE(0x00300, 0x001dc),CE(0x00301, 0x001d8),CE(0x00304, 0x001d6),CE(0x0030c, 0x001da),CE(0x00300, 0x01eb0),CE(0x00301, 0x01eae),CE(0x00303, 0x01eb4),CE(0x00309, 0x01eb2),CE(0x00300, 0x01eb1),CE(0x00301, 0x01eaf),CE(0x00303, 0x01eb5),CE(0x00309, 0x01eb3),CE(0x00300, 0x01e14),CE(0x00301, 0x01e16),CE(0x00300, 0x01e15),CE(0x00301, 0x01e17),CE(0x00300, 0x01e50),CE(0x00301, 0x01e52),CE(0x00300, 0x01e51),CE(0x00301, 0x01e53),CE(0x00307, 0x01e64),CE(0x00307, 0x01e65),CE(0x00307, 0x01e66),CE(0x00307, 0x01e67),CE(0x00301, 0x01e78),CE(0x00301, 0x01e79),CE(0x00308, 0x01e7a),CE(0x00308, 0x01e7b),CE(0x00307, 0x01e9b),CE(0x00300, 0x01edc),CE(0x00301, 0x01eda),CE(0x00303, 0x01ee0),CE(0x00309, 0x01ede),CE(0x00323, 0x01ee2),CE(0x00300, 0x01edd),CE(0x00301, 0x01edb),CE(0x00303, 0x01ee1),CE(0x00309, 0x01edf),CE(0x00323, 0x01ee3),CE(0x00300, 0x01eea),CE(0x00301, 0x01ee8),CE(0x00303, 0x01eee),CE(0x00309, 0x01eec),CE(0x00323, 0x01ef0),CE(0x00300, 0x01eeb),CE(0x00301, 0x01ee9),CE(0x00303, 0x01eef),CE(0x00309, 0x01eed),CE(0x00323, 0x01ef1),CE(0x0030c, 0x001ee),CE(0x00304, 0x001ec),CE(0x00304, 0x001ed),CE(0x00304, 0x001e0),CE(0x00304, 0x001e1),CE(0x00306, 0x01e1c),CE(0x00306, 0x01e1d),CE(0x00304, 0x00230),CE(0x00304, 0x00231),CE(0x0030c, 0x001ef),CE(0x00300, 0x01fba),CE(0x00301, 0x00386),CE(0x00304, 0x01fb9),CE(0x00306, 0x01fb8),CE(0x00313, 0x01f08),CE(0x00314, 0x01f09),CE(0x00345, 0x01fbc),CE(0x00300, 0x01fc8),CE(0x00301, 0x00388),CE(0x00313, 0x01f18),CE(0x00314, 0x01f19),CE(0x00300, 0x01fca),CE(0x00301, 0x00389),CE(0x00313, 0x01f28),CE(0x00314, 0x01f29),CE(0x00345, 0x01fcc),CE(0x00300, 0x01fda),CE(0x00301, 0x0038a),CE(0x00304, 0x01fd9),CE(0x00306, 0x01fd8),CE(0x00308, 0x003aa),CE(0x00313, 0x01f38),CE(0x00314, 0x01f39),CE(0x00300, 0x01ff8),CE(0x00301, 0x0038c),CE(0x00313, 0x01f48),CE(0x00314, 0x01f49),CE(0x00314, 0x01fec),CE(0x00300, 0x01fea),CE(0x00301, 0x0038e),CE(0x00304, 0x01fe9),CE(0x00306, 0x01fe8),CE(0x00308, 0x003ab),CE(0x00314, 0x01f59),CE(0x00300, 0x01ffa),CE(0x00301, 0x0038f),CE(0x00313, 0x01f68),CE(0x00314, 0x01f69),CE(0x00345, 0x01ffc),CE(0x00345, 0x01fb4),CE(0x00345, 0x01fc4),CE(0x00300, 0x01f70),CE(0x00301, 0x003ac),CE(0x00304, 0x01fb1),CE(0x00306, 0x01fb0),CE(0x00313, 0x01f00),CE(0x00314, 0x01f01),CE(0x00342, 0x01fb6),CE(0x00345, 0x01fb3),CE(0x00300, 0x01f72),CE(0x00301, 0x003ad),CE(0x00313, 0x01f10),CE(0x00314, 0x01f11),CE(0x00300, 0x01f74),CE(0x00301, 0x003ae),CE(0x00313, 0x01f20),CE(0x00314, 0x01f21),CE(0x00342, 0x01fc6),CE(0x00345, 0x01fc3),CE(0x00300, 0x01f76),CE(0x00301, 0x003af),CE(0x00304, 0x01fd1),CE(0x00306, 0x01fd0),CE(0x00308, 0x003ca),CE(0x00313, 0x01f30),CE(0x00314, 0x01f31),CE(0x00342, 0x01fd6),CE(0x00300, 0x01f78),CE(0x00301, 0x003cc),CE(0x00313, 0x01f40),CE(0x00314, 0x01f41),CE(0x00313, 0x01fe4),CE(0x00314, 0x01fe5),CE(0x00300, 0x01f7a),CE(0x00301, 0x003cd),CE(0x00304, 0x01fe1),CE(0x00306, 0x01fe0),CE(0x00308, 0x003cb),CE(0x00313, 0x01f50),CE(0x00314, 0x01f51),CE(0x00342, 0x01fe6),CE(0x00300, 0x01f7c),CE(0x00301, 0x003ce),CE(0x00313, 0x01f60),CE(0x00314, 0x01f61),CE(0x00342, 0x01ff6),CE(0x00345, 0x01ff3),CE(0x00300, 0x01fd2),CE(0x00301, 0x00390),CE(0x00342, 0x01fd7),CE(0x00300, 0x01fe2),CE(0x00301, 0x003b0),CE(0x00342, 0x01fe7),CE(0x00345, 0x01ff4),CE(0x00301, 0x003d3),CE(0x00308, 0x003d4),CE(0x00308, 0x00407),CE(0x00306, 0x004d0),CE(0x00308, 0x004d2),CE(0x00301, 0x00403),CE(0x00300, 0x00400),CE(0x00306, 0x004d6),CE(0x00308, 0x00401),CE(0x00306, 0x004c1),CE(0x00308, 0x004dc),CE(0x00308, 0x004de),CE(0x00300, 0x0040d),CE(0x00304, 0x004e2),CE(0x00306, 0x00419),CE(0x00308, 0x004e4),CE(0x00301, 0x0040c),CE(0x00308, 0x004e6),CE(0x00304, 0x004ee),CE(0x00306, 0x0040e),CE(0x00308, 0x004f0),CE(0x0030b, 0x004f2),CE(0x00308, 0x004f4),CE(0x00308, 0x004f8),CE(0x00308, 0x004ec),CE(0x00306, 0x004d1),CE(0x00308, 0x004d3),CE(0x00301, 0x00453),CE(0x00300, 0x00450),CE(0x00306, 0x004d7),CE(0x00308, 0x00451),CE(0x00306, 0x004c2),CE(0x00308, 0x004dd),CE(0x00308, 0x004df),CE(0x00300, 0x0045d),CE(0x00304, 0x004e3),CE(0x00306, 0x00439),CE(0x00308, 0x004e5),CE(0x00301, 0x0045c),CE(0x00308, 0x004e7),CE(0x00304, 0x004ef),CE(0x00306, 0x0045e),CE(0x00308, 0x004f1),CE(0x0030b, 0x004f3),CE(0x00308, 0x004f5),CE(0x00308, 0x004f9),CE(0x00308, 0x004ed),CE(0x00308, 0x00457),CE(0x0030f, 0x00476),CE(0x0030f, 0x00477),CE(0x00308, 0x004da),CE(0x00308, 0x004db),CE(0x00308, 0x004ea),CE(0x00308, 0x004eb),CE(0x00653, 0x00622),CE(0x00654, 0x00623),CE(0x00655, 0x00625),CE(0x00654, 0x00624),CE(0x00654, 0x00626),CE(0x00654, 0x006c2),CE(0x00654, 0x006d3),CE(0x00654, 0x006c0),CE(0x0093c, 0x00929),CE(0x0093c, 0x00931),CE(0x0093c, 0x00934),CE(0x009be, 0x009cb),CE(0x009d7, 0x009cc),CE(0x00b3e, 0x00b4b),CE(0x00b56, 0x00b48),CE(0x00b57, 0x00b4c),CE(0x00bd7, 0x00b94),CE(0x00bbe, 0x00bca),CE(0x00bd7, 0x00bcc),CE(0x00bbe, 0x00bcb),CE(0x00c56, 0x00c48),CE(0x00cd5, 0x00cc0),CE(0x00cc2, 0x00cca),CE(0x00cd5, 0x00cc7),CE(0x00cd6, 0x00cc8),CE(0x00cd5, 0x00ccb),CE(0x00d3e, 0x00d4a),CE(0x00d57, 0x00d4c),CE(0x00d3e, 0x00d4b),CE(0x00dca, 0x00dda),CE(0x00dcf, 0x00ddc),CE(0x00ddf, 0x00dde),CE(0x00dca, 0x00ddd),CE(0x0102e, 0x01026),CE(0x01b35, 0x01b06),CE(0x01b35, 0x01b08),CE(0x01b35, 0x01b0a),CE(0x01b35, 0x01b0c),CE(0x01b35, 0x01b0e),CE(0x01b35, 0x01b12),CE(0x01b35, 0x01b3b),CE(0x01b35, 0x01b3d),CE(0x01b35, 0x01b40),CE(0x01b35, 0x01b41),CE(0x01b35, 0x01b43),CE(0x00304, 0x01e38),CE(0x00304, 0x01e39),CE(0x00304, 0x01e5c),CE(0x00304, 0x01e5d),CE(0x00307, 0x01e68),CE(0x00307, 0x01e69),CE(0x00302, 0x01eac),CE(0x00306, 0x01eb6),CE(0x00302, 0x01ead),CE(0x00306, 0x01eb7),CE(0x00302, 0x01ec6),CE(0x00302, 0x01ec7),CE(0x00302, 0x01ed8),CE(0x00302, 0x01ed9),CE(0x00300, 0x01f02),CE(0x00301, 0x01f04),CE(0x00342, 0x01f06),CE(0x00345, 0x01f80),CE(0x00300, 0x01f03),CE(0x00301, 0x01f05),CE(0x00342, 0x01f07),CE(0x00345, 0x01f81),CE(0x00345, 0x01f82),CE(0x00345, 0x01f83),CE(0x00345, 0x01f84),CE(0x00345, 0x01f85),CE(0x00345, 0x01f86),CE(0x00345, 0x01f87),CE(0x00300, 0x01f0a),CE(0x00301, 0x01f0c),CE(0x00342, 0x01f0e),CE(0x00345, 0x01f88),CE(0x00300, 0x01f0b),CE(0x00301, 0x01f0d),CE(0x00342, 0x01f0f),CE(0x00345, 0x01f89),CE(0x00345, 0x01f8a),CE(0x00345, 0x01f8b),CE(0x00345, 0x01f8c),CE(0x00345, 0x01f8d),CE(0x00345, 0x01f8e),CE(0x00345, 0x01f8f),CE(0x00300, 0x01f12),CE(0x00301, 0x01f14),CE(0x00300, 0x01f13),CE(0x00301, 0x01f15),CE(0x00300, 0x01f1a),CE(0x00301, 0x01f1c),CE(0x00300, 0x01f1b),CE(0x00301, 0x01f1d),CE(0x00300, 0x01f22),CE(0x00301, 0x01f24),CE(0x00342, 0x01f26),CE(0x00345, 0x01f90),CE(0x00300, 0x01f23),CE(0x00301, 0x01f25),CE(0x00342, 0x01f27),CE(0x00345, 0x01f91),CE(0x00345, 0x01f92),CE(0x00345, 0x01f93),CE(0x00345, 0x01f94),CE(0x00345, 0x01f95),CE(0x00345, 0x01f96),CE(0x00345, 0x01f97),CE(0x00300, 0x01f2a),CE(0x00301, 0x01f2c),CE(0x00342, 0x01f2e),CE(0x00345, 0x01f98),CE(0x00300, 0x01f2b),CE(0x00301, 0x01f2d),CE(0x00342, 0x01f2f),CE(0x00345, 0x01f99),CE(0x00345, 0x01f9a),CE(0x00345, 0x01f9b),CE(0x00345, 0x01f9c),CE(0x00345, 0x01f9d),CE(0x00345, 0x01f9e),CE(0x00345, 0x01f9f),CE(0x00300, 0x01f32),CE(0x00301, 0x01f34),CE(0x00342, 0x01f36),CE(0x00300, 0x01f33),CE(0x00301, 0x01f35),CE(0x00342, 0x01f37),CE(0x00300, 0x01f3a),CE(0x00301, 0x01f3c),CE(0x00342, 0x01f3e),CE(0x00300, 0x01f3b),CE(0x00301, 0x01f3d),CE(0x00342, 0x01f3f),CE(0x00300, 0x01f42),CE(0x00301, 0x01f44),CE(0x00300, 0x01f43),CE(0x00301, 0x01f45),CE(0x00300, 0x01f4a),CE(0x00301, 0x01f4c),CE(0x00300, 0x01f4b),CE(0x00301, 0x01f4d),CE(0x00300, 0x01f52),CE(0x00301, 0x01f54),CE(0x00342, 0x01f56),CE(0x00300, 0x01f53),CE(0x00301, 0x01f55),CE(0x00342, 0x01f57),CE(0x00300, 0x01f5b),CE(0x00301, 0x01f5d),CE(0x00342, 0x01f5f),CE(0x00300, 0x01f62),CE(0x00301, 0x01f64),CE(0x00342, 0x01f66),CE(0x00345, 0x01fa0),CE(0x00300, 0x01f63),CE(0x00301, 0x01f65),CE(0x00342, 0x01f67),CE(0x00345, 0x01fa1),CE(0x00345, 0x01fa2),CE(0x00345, 0x01fa3),CE(0x00345, 0x01fa4),CE(0x00345, 0x01fa5),CE(0x00345, 0x01fa6),CE(0x00345, 0x01fa7),CE(0x00300, 0x01f6a),CE(0x00301, 0x01f6c),CE(0x00342, 0x01f6e),CE(0x00345, 0x01fa8),CE(0x00300, 0x01f6b),CE(0x00301, 0x01f6d),CE(0x00342, 0x01f6f),CE(0x00345, 0x01fa9),CE(0x00345, 0x01faa),CE(0x00345, 0x01fab),CE(0x00345, 0x01fac),CE(0x00345, 0x01fad),CE(0x00345, 0x01fae),CE(0x00345, 0x01faf),CE(0x00345, 0x01fb2),CE(0x00345, 0x01fc2),CE(0x00345, 0x01ff2),CE(0x00345, 0x01fb7),CE(0x00300, 0x01fcd),CE(0x00301, 0x01fce),CE(0x00342, 0x01fcf),CE(0x00345, 0x01fc7),CE(0x00345, 0x01ff7),CE(0x00300, 0x01fdd),CE(0x00301, 0x01fde),CE(0x00342, 0x01fdf),CE(0x00338, 0x0219a),CE(0x00338, 0x0219b),CE(0x00338, 0x021ae),CE(0x00338, 0x021cd),CE(0x00338, 0x021cf),CE(0x00338, 0x021ce),CE(0x00338, 0x02204),CE(0x00338, 0x02209),CE(0x00338, 0x0220c),CE(0x00338, 0x02224),CE(0x00338, 0x02226),CE(0x00338, 0x02241),CE(0x00338, 0x02244),CE(0x00338, 0x02247),CE(0x00338, 0x02249),CE(0x00338, 0x0226d),CE(0x00338, 0x02262),CE(0x00338, 0x02270),CE(0x00338, 0x02271),CE(0x00338, 0x02274),CE(0x00338, 0x02275),CE(0x00338, 0x02278),CE(0x00338, 0x02279),CE(0x00338, 0x02280),CE(0x00338, 0x02281),CE(0x00338, 0x022e0),CE(0x00338, 0x022e1),CE(0x00338, 0x02284),CE(0x00338, 0x02285),CE(0x00338, 0x02288),CE(0x00338, 0x02289),CE(0x00338, 0x022e2),CE(0x00338, 0x022e3),CE(0x00338, 0x022ac),CE(0x00338, 0x022ad),CE(0x00338, 0x022ae),CE(0x00338, 0x022af),CE(0x00338, 0x022ea),CE(0x00338, 0x022eb),CE(0x00338, 0x022ec),CE(0x00338, 0x022ed),CE(0x03099, 0x03094),CE(0x03099, 0x0304c),CE(0x03099, 0x0304e),CE(0x03099, 0x03050),CE(0x03099, 0x03052),CE(0x03099, 0x03054),CE(0x03099, 0x03056),CE(0x03099, 0x03058),CE(0x03099, 0x0305a),CE(0x03099, 0x0305c),CE(0x03099, 0x0305e),CE(0x03099, 0x03060),CE(0x03099, 0x03062),CE(0x03099, 0x03065),CE(0x03099, 0x03067),CE(0x03099, 0x03069),CE(0x03099, 0x03070),CE(0x0309a, 0x03071),CE(0x03099, 0x03073),CE(0x0309a, 0x03074),CE(0x03099, 0x03076),CE(0x0309a, 0x03077),CE(0x03099, 0x03079),CE(0x0309a, 0x0307a),CE(0x03099, 0x0307c),CE(0x0309a, 0x0307d),CE(0x03099, 0x0309e),CE(0x03099, 0x030f4),CE(0x03099, 0x030ac),CE(0x03099, 0x030ae),CE(0x03099, 0x030b0),CE(0x03099, 0x030b2),CE(0x03099, 0x030b4),CE(0x03099, 0x030b6),CE(0x03099, 0x030b8),CE(0x03099, 0x030ba),CE(0x03099, 0x030bc),CE(0x03099, 0x030be),CE(0x03099, 0x030c0),CE(0x03099, 0x030c2),CE(0x03099, 0x030c5),CE(0x03099, 0x030c7),CE(0x03099, 0x030c9),CE(0x03099, 0x030d0),CE(0x0309a, 0x030d1),CE(0x03099, 0x030d3),CE(0x0309a, 0x030d4),CE(0x03099, 0x030d6),CE(0x0309a, 0x030d7),CE(0x03099, 0x030d9),CE(0x0309a, 0x030da),CE(0x03099, 0x030dc),CE(0x0309a, 0x030dd),CE(0x03099, 0x030f7),CE(0x03099, 0x030f8),CE(0x03099, 0x030f9),CE(0x03099, 0x030fa),CE(0x03099, 0x030fe),CE(0x110ba, 0x1109a),CE(0x110ba, 0x1109c),CE(0x110ba, 0x110ab),CE(0x11127, 0x1112e),CE(0x11127, 0x1112f),];
+return t;
+}
+
+}
+
+
+static if(size_t.sizeof == 4) {
+//6976 bytes
+enum combiningClassTrieEntries = TrieEntry!(ubyte, 8, 7, 6)([ 0x0,  0x40,  0x240], [ 0x100,  0x400,  0x1240], [ 0x2020100,  0x4020302,  0x2020205,  0x2060202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20001,  0x0,  0x0,  0x3,  0x0,  0x50004,  0x70006,  0x80000,  0xa0009,  0xb0000,  0xd000c,  0xe0000,  0x10000f,  0x11000f,  0x11000f,  0x11000f,  0x11000f,  0x110000,  0x120000,  0x11000f,  0x110000,  0x130000,  0x150014,  0x170016,  0x190018,  0x1b001a,  0x1c,  0x1d,  0x0,  0x0,  0x0,  0x0,  0x1e0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1f,  0x200000,  0x0,  0x21,  0x22,  0x0,  0x240023,  0x0,  0x260025,  0x280027,  0x29,  0x2a0000,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2d0000,  0x2e0000,  0x2f0000,  0x0,  0x0,  0x0,  0x0,  0x30,  0x31,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x320000,  0x340033,  0x0,  0x0,  0x35,  0x360000,  0x380037,  0x3a0039,  0x0,  0x3c003b,  0x0,  0x3d0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x400000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x41,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x350000,  0x42,  0x43,  0x3a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x44,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x450000,  0x46,  0x470000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xdcdce8e6,  0xd8e8dcdc,  0xdcdcdcdc,  0xdccacadc,  0xcadcdcdc,  0xdcdcdcca,  0xdcdcdcdc,  0xdcdcdcdc,  0x1010101,  0xdcdcdc01,  0xe6e6e6dc,  0xe6e6e6e6,  0xdce6f0e6,  0xe6e6dcdc,  0xdcdce6,  0xdce6e6e6,  0xe6dcdcdc,  0xe6dcdce8,  0xe9eaeae9,  0xe6e9eaea,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0xe6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6dc00,  0xe6dce6e6,  0xdcdee6e6,  0xe6e6e6e6,  0xdcdce6e6,  0xdcdcdcdc,  0xe6dce6e6,  0xe6e4dee6,  0xd0c0b0a,  0x11100f0e,  0x14131312,  0x17001615,  0x191800,  0x1200dce6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6,  0xe6e6e6e6,  0x201f1e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b000000,  0x1f1e1d1c,  0xe6222120,  0xe6dcdce6,  0xe6e6e6e6,  0xdce6e6dc,  0x0,  0x0,  0x0,  0x0,  0x23,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e60000,  0xe6e6e6e6,  0xe60000e6,  0xdce6e6e6,  0xe60000e6,  0xe6dc00e6,  0xdce6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6dce6,  0xdce6e6dc,  0xdce6dcdc,  0xe6dce6dc,  0xe6dce6e6,  0xe6dce6dc,  0xe6e6dc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0xe6e6e6e6,  0xe6dce6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e60000,  0xe600e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e600,  0xe6e6e600,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdcdcdc00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6dce6e6,  0xe6e6dce6,  0xdcdcdce6,  0xe61d1c1b,  0xe6dce6e6,  0xe6dcdce6,  0xe6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0x0,  0x0,  0x0,  0x900,  0xe6dce600,  0xe6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900,  0x0,  0x5b5400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x96767,  0x0,  0x0,  0x0,  0x6b6b6b6b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7676,  0x0,  0x0,  0x0,  0x7a7a7a7a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdcdc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc00dc00,  0xd800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x828100,  0x84,  0x82820000,  0x8282,  0xe6e60082,  0xe6e60009,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7000000,  0x90900,  0x0,  0x0,  0x0,  0x0,  0xdc00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000,  0x0,  0x0,  0xe600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdce6de00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0xdc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0xe6e6e600,  0xe6e6e6e6,  0xdc0000e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0xe6e6e6dc,  0xe6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9090000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x70000,  0x0,  0x0,  0x9090000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6,  0xdcdcdc01,  0xe6e6dcdc,  0xdcdcdcdc,  0x10100e6,  0x1010101,  0x1,  0xdc00,  0x0,  0xe6,  0x0,  0x0,  0xe6dce6e6,  0xe6e6e6e6,  0xe6dce6e6,  0xdcd6eae6,  0xe6e6e6ca,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdce6dce9,  0x0,  0x0,  0x0,  0x0,  0x101e6e6,  0xe6e6e6e6,  0xe6010101,  0xe6,  0xe600,  0xe6010100,  0x101e6dc,  0xdcdcdcdc,  0xe6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe4da0000,  0xe0e0dee8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0x0,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x90000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6e6e6,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc000000,  0xdcdc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7000000,  0x0,  0x0,  0x0,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e600e6,  0xe60000dc,  0xe6,  0xe6e60000,  0xe600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x90000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x900,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e6e6e6,  0xe6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc00,  0x0,  0x0,  0x0,  0xe600dc00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xdc01e6,  0x9000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x70900,  0x0,  0xe6e6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9000000,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7090000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1d8d800,  0x101,  0xd8d8e200,  0xd8d8d8,  0x0,  0xdc000000,  0xdcdcdcdc,  0xdcdcdc,  0xe6e6e600,  0xdcdce6e6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe6e60000,  0xe6e6,  0x0,  0x0,  0x0,  0x0,  0xe6e60000,  0xe6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+enum composeIdxMask = (1<<11)-1, composeCntShift = 11;
+enum compositionJumpTrieEntries = TrieEntry!(ushort, 12, 9)([ 0x0,  0x800], [ 0x1000,  0x2000], [ 0x10000,  0x30002,  0x50004,  0x70006,  0x70008,  0x70007,  0x90007,  0xa0007,  0xc000b,  0x70007,  0x70007,  0x70007,  0x7000d,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x7000e,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x8010800,  0xffff0802,  0x8003ffff,  0x28161813,  0x8821301b,  0x38330832,  0x7841383a,  0x28510850,  0x185c3056,  0x8068485f,  0xffff1078,  0x3882407a,  0x98903889,  0x30a510a3,  0x48ad10ab,  0xffff30b6,  0xffffffff,  0xffffffff,  0x80bcffff,  0x28cf18cc,  0x88da30d4,  0x38ec08eb,  0x70fb40f3,  0x290b1109,  0x19163110,  0x81224919,  0xffff1132,  0x393c4134,  0x994b4143,  0x3960115e,  0x51691167,  0xffff3173,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff1979,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff217c,  0x9810980,  0x9841182,  0xffffffff,  0xffff2185,  0xffffffff,  0x989ffff,  0xffffffff,  0xffffffff,  0x198e218a,  0xffff0991,  0xffff0992,  0xffffffff,  0xffff2193,  0xffffffff,  0xffffffff,  0xffff2197,  0x99c099b,  0x99f119d,  0xffffffff,  0xffff21a0,  0xffffffff,  0x9a4ffff,  0xffffffff,  0xffffffff,  0x19a921a5,  0xffff09ac,  0xffff09ad,  0xffffffff,  0xffff21ae,  0xffffffff,  0xffffffff,  0x21b621b2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x11bc11ba,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x11c011be,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x9c309c2,  0xffffffff,  0xffffffff,  0x9c509c4,  0xffffffff,  0xffffffff,  0xffffffff,  0x9c709c6,  0x9c909c8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x9caffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x29d029cb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x29d5ffff,  0xffff29da,  0xffffffff,  0xffffffff,  0x9dfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x9e109e0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x9e309e2,  0x9e509e4,  0xffffffff,  0xffffffff,  0x9e709e6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff09e8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x39e9ffff,  0xffffffff,  0x21f0ffff,  0x29f4ffff,  0x39f9ffff,  0xffffffff,  0xffffffff,  0x2200ffff,  0xa04ffff,  0xffffffff,  0x3205ffff,  0xffffffff,  0x2a0bffff,  0xffffffff,  0xffff0a10,  0xffff0a11,  0x4212ffff,  0xffffffff,  0x221affff,  0x321effff,  0x4224ffff,  0xffffffff,  0xffffffff,  0x222cffff,  0x1230ffff,  0xffffffff,  0x4232ffff,  0xffffffff,  0x323affff,  0x1a431a40,  0xffffffff,  0xffff0a46,  0xffffffff,  0xffff1247,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a49,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff124a,  0xa4cffff,  0x1a4dffff,  0xa521250,  0xffff2253,  0xffff0a57,  0xffffffff,  0xffff0a58,  0xffffffff,  0x2259ffff,  0xffffffff,  0xa5dffff,  0xffffffff,  0xa5effff,  0xa5fffff,  0xffffffff,  0xffff1260,  0xa62ffff,  0x1a63ffff,  0xa681266,  0xffff2269,  0xffff0a6d,  0xffffffff,  0xffff0a6e,  0xffffffff,  0x226fffff,  0xffffffff,  0xa73ffff,  0xffffffff,  0xa74ffff,  0xa75ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a76,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa780a77,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa7a0a79,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa7c0a7b,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1a7dffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a80,  0xffff0a81,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa82ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a83,  0xa84ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a85,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a86,  0xa87ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1288ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1a8affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a8d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa90128e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0a91,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa92ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff1a93,  0xffffffff,  0xffff0a96,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa991297,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1a9affff,  0xffffffff,  0xffff0a9d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa9effff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xa9fffff,  0xaa0ffff,  0xaa1ffff,  0xaa2ffff,  0xaa3ffff,  0xffffffff,  0xaa4ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0aa5,  0xffff0aa6,  0xaa80aa7,  0xffffffff,  0xffff0aa9,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xaab0aaa,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xaad0aac,  0xffffffff,  0xffffffff,  0xffffffff,  0xaaf0aae,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x12b212b0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xab50ab4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xab70ab6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x22bc22b8,  0xac10ac0,  0xac30ac2,  0xac50ac4,  0x22ca22c6,  0xacf0ace,  0xad10ad0,  0xad30ad2,  0x12d612d4,  0xffffffff,  0xffffffff,  0xffffffff,  0x12da12d8,  0xffffffff,  0xffffffff,  0xffffffff,  0x22e022dc,  0xae50ae4,  0xae70ae6,  0xae90ae8,  0x22ee22ea,  0xaf30af2,  0xaf50af4,  0xaf70af6,  0x1afb1af8,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b011afe,  0xffffffff,  0xffffffff,  0xffffffff,  0x13061304,  0xffffffff,  0xffffffff,  0xffffffff,  0x130a1308,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b0f1b0c,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b12ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x23192315,  0xb1e0b1d,  0xb200b1f,  0xb220b21,  0x23272323,  0xb2c0b2b,  0xb2e0b2d,  0xb300b2f,  0xffff0b31,  0xffffffff,  0xffff0b32,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b33,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b34,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b35ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b38,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b39,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff1b3a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b3d,  0xffff0b3e,  0xffff0b3f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b40,  0xffff0b41,  0xffff0b42,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb43ffff,  0xffffffff,  0xffffffff,  0xffff0b44,  0xb45ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb46ffff,  0xb47ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b48,  0xffffffff,  0xffffffff,  0xb49ffff,  0xb4affff,  0xffffffff,  0xffff0b4b,  0xffffffff,  0xb4cffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb4dffff,  0xffffffff,  0xb4f0b4e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb510b50,  0xffffffff,  0xb530b52,  0xffffffff,  0xb550b54,  0xb570b56,  0xffffffff,  0xffffffff,  0xb590b58,  0xffffffff,  0xb5b0b5a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb5cffff,  0xffff0b5d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b5e,  0xffffffff,  0xffffffff,  0xb600b5f,  0xb61ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb630b62,  0xb650b64,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b66,  0xffffffff,  0xb67ffff,  0xb68ffff,  0xb69ffff,  0xb6affff,  0xb6bffff,  0xb6cffff,  0xb6dffff,  0xb6effff,  0xb6fffff,  0xb70ffff,  0xb71ffff,  0xb72ffff,  0xffffffff,  0xffff0b73,  0xffff0b74,  0xffff0b75,  0xffffffff,  0xffffffff,  0x1376ffff,  0xffffffff,  0xffff1378,  0x137affff,  0xffffffff,  0xffff137c,  0x137effff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb80ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0b81,  0xffffffff,  0xb82ffff,  0xb83ffff,  0xb84ffff,  0xb85ffff,  0xb86ffff,  0xb87ffff,  0xb88ffff,  0xb89ffff,  0xb8affff,  0xb8bffff,  0xb8cffff,  0xb8dffff,  0xffffffff,  0xffff0b8e,  0xffff0b8f,  0xffff0b90,  0xffffffff,  0xffffffff,  0x1391ffff,  0xffffffff,  0xffff1393,  0x1395ffff,  0xffffffff,  0xffff1397,  0x1399ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb9bffff,  0xb9d0b9c,  0xffff0b9e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xb9fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xba0ffff,  0xba1ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xba2ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xba3ffff,  0xffff0ba4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+@property immutable(CompEntry[]) compositionTable()
+{
+alias CE = CompEntry;
+static immutable CE[] t = [CE(0x00338, 0x0226e),CE(0x00338, 0x02260),CE(0x00338, 0x0226f),CE(0x00300, 0x000c0),CE(0x00301, 0x000c1),CE(0x00302, 0x000c2),CE(0x00303, 0x000c3),CE(0x00304, 0x00100),CE(0x00306, 0x00102),CE(0x00307, 0x00226),CE(0x00308, 0x000c4),CE(0x00309, 0x01ea2),CE(0x0030a, 0x000c5),CE(0x0030c, 0x001cd),CE(0x0030f, 0x00200),CE(0x00311, 0x00202),CE(0x00323, 0x01ea0),CE(0x00325, 0x01e00),CE(0x00328, 0x00104),CE(0x00307, 0x01e02),CE(0x00323, 0x01e04),CE(0x00331, 0x01e06),CE(0x00301, 0x00106),CE(0x00302, 0x00108),CE(0x00307, 0x0010a),CE(0x0030c, 0x0010c),CE(0x00327, 0x000c7),CE(0x00307, 0x01e0a),CE(0x0030c, 0x0010e),CE(0x00323, 0x01e0c),CE(0x00327, 0x01e10),CE(0x0032d, 0x01e12),CE(0x00331, 0x01e0e),CE(0x00300, 0x000c8),CE(0x00301, 0x000c9),CE(0x00302, 0x000ca),CE(0x00303, 0x01ebc),CE(0x00304, 0x00112),CE(0x00306, 0x00114),CE(0x00307, 0x00116),CE(0x00308, 0x000cb),CE(0x00309, 0x01eba),CE(0x0030c, 0x0011a),CE(0x0030f, 0x00204),CE(0x00311, 0x00206),CE(0x00323, 0x01eb8),CE(0x00327, 0x00228),CE(0x00328, 0x00118),CE(0x0032d, 0x01e18),CE(0x00330, 0x01e1a),CE(0x00307, 0x01e1e),CE(0x00301, 0x001f4),CE(0x00302, 0x0011c),CE(0x00304, 0x01e20),CE(0x00306, 0x0011e),CE(0x00307, 0x00120),CE(0x0030c, 0x001e6),CE(0x00327, 0x00122),CE(0x00302, 0x00124),CE(0x00307, 0x01e22),CE(0x00308, 0x01e26),CE(0x0030c, 0x0021e),CE(0x00323, 0x01e24),CE(0x00327, 0x01e28),CE(0x0032e, 0x01e2a),CE(0x00300, 0x000cc),CE(0x00301, 0x000cd),CE(0x00302, 0x000ce),CE(0x00303, 0x00128),CE(0x00304, 0x0012a),CE(0x00306, 0x0012c),CE(0x00307, 0x00130),CE(0x00308, 0x000cf),CE(0x00309, 0x01ec8),CE(0x0030c, 0x001cf),CE(0x0030f, 0x00208),CE(0x00311, 0x0020a),CE(0x00323, 0x01eca),CE(0x00328, 0x0012e),CE(0x00330, 0x01e2c),CE(0x00302, 0x00134),CE(0x00301, 0x01e30),CE(0x0030c, 0x001e8),CE(0x00323, 0x01e32),CE(0x00327, 0x00136),CE(0x00331, 0x01e34),CE(0x00301, 0x00139),CE(0x0030c, 0x0013d),CE(0x00323, 0x01e36),CE(0x00327, 0x0013b),CE(0x0032d, 0x01e3c),CE(0x00331, 0x01e3a),CE(0x00301, 0x01e3e),CE(0x00307, 0x01e40),CE(0x00323, 0x01e42),CE(0x00300, 0x001f8),CE(0x00301, 0x00143),CE(0x00303, 0x000d1),CE(0x00307, 0x01e44),CE(0x0030c, 0x00147),CE(0x00323, 0x01e46),CE(0x00327, 0x00145),CE(0x0032d, 0x01e4a),CE(0x00331, 0x01e48),CE(0x00300, 0x000d2),CE(0x00301, 0x000d3),CE(0x00302, 0x000d4),CE(0x00303, 0x000d5),CE(0x00304, 0x0014c),CE(0x00306, 0x0014e),CE(0x00307, 0x0022e),CE(0x00308, 0x000d6),CE(0x00309, 0x01ece),CE(0x0030b, 0x00150),CE(0x0030c, 0x001d1),CE(0x0030f, 0x0020c),CE(0x00311, 0x0020e),CE(0x0031b, 0x001a0),CE(0x00323, 0x01ecc),CE(0x00328, 0x001ea),CE(0x00301, 0x01e54),CE(0x00307, 0x01e56),CE(0x00301, 0x00154),CE(0x00307, 0x01e58),CE(0x0030c, 0x00158),CE(0x0030f, 0x00210),CE(0x00311, 0x00212),CE(0x00323, 0x01e5a),CE(0x00327, 0x00156),CE(0x00331, 0x01e5e),CE(0x00301, 0x0015a),CE(0x00302, 0x0015c),CE(0x00307, 0x01e60),CE(0x0030c, 0x00160),CE(0x00323, 0x01e62),CE(0x00326, 0x00218),CE(0x00327, 0x0015e),CE(0x00307, 0x01e6a),CE(0x0030c, 0x00164),CE(0x00323, 0x01e6c),CE(0x00326, 0x0021a),CE(0x00327, 0x00162),CE(0x0032d, 0x01e70),CE(0x00331, 0x01e6e),CE(0x00300, 0x000d9),CE(0x00301, 0x000da),CE(0x00302, 0x000db),CE(0x00303, 0x00168),CE(0x00304, 0x0016a),CE(0x00306, 0x0016c),CE(0x00308, 0x000dc),CE(0x00309, 0x01ee6),CE(0x0030a, 0x0016e),CE(0x0030b, 0x00170),CE(0x0030c, 0x001d3),CE(0x0030f, 0x00214),CE(0x00311, 0x00216),CE(0x0031b, 0x001af),CE(0x00323, 0x01ee4),CE(0x00324, 0x01e72),CE(0x00328, 0x00172),CE(0x0032d, 0x01e76),CE(0x00330, 0x01e74),CE(0x00303, 0x01e7c),CE(0x00323, 0x01e7e),CE(0x00300, 0x01e80),CE(0x00301, 0x01e82),CE(0x00302, 0x00174),CE(0x00307, 0x01e86),CE(0x00308, 0x01e84),CE(0x00323, 0x01e88),CE(0x00307, 0x01e8a),CE(0x00308, 0x01e8c),CE(0x00300, 0x01ef2),CE(0x00301, 0x000dd),CE(0x00302, 0x00176),CE(0x00303, 0x01ef8),CE(0x00304, 0x00232),CE(0x00307, 0x01e8e),CE(0x00308, 0x00178),CE(0x00309, 0x01ef6),CE(0x00323, 0x01ef4),CE(0x00301, 0x00179),CE(0x00302, 0x01e90),CE(0x00307, 0x0017b),CE(0x0030c, 0x0017d),CE(0x00323, 0x01e92),CE(0x00331, 0x01e94),CE(0x00300, 0x000e0),CE(0x00301, 0x000e1),CE(0x00302, 0x000e2),CE(0x00303, 0x000e3),CE(0x00304, 0x00101),CE(0x00306, 0x00103),CE(0x00307, 0x00227),CE(0x00308, 0x000e4),CE(0x00309, 0x01ea3),CE(0x0030a, 0x000e5),CE(0x0030c, 0x001ce),CE(0x0030f, 0x00201),CE(0x00311, 0x00203),CE(0x00323, 0x01ea1),CE(0x00325, 0x01e01),CE(0x00328, 0x00105),CE(0x00307, 0x01e03),CE(0x00323, 0x01e05),CE(0x00331, 0x01e07),CE(0x00301, 0x00107),CE(0x00302, 0x00109),CE(0x00307, 0x0010b),CE(0x0030c, 0x0010d),CE(0x00327, 0x000e7),CE(0x00307, 0x01e0b),CE(0x0030c, 0x0010f),CE(0x00323, 0x01e0d),CE(0x00327, 0x01e11),CE(0x0032d, 0x01e13),CE(0x00331, 0x01e0f),CE(0x00300, 0x000e8),CE(0x00301, 0x000e9),CE(0x00302, 0x000ea),CE(0x00303, 0x01ebd),CE(0x00304, 0x00113),CE(0x00306, 0x00115),CE(0x00307, 0x00117),CE(0x00308, 0x000eb),CE(0x00309, 0x01ebb),CE(0x0030c, 0x0011b),CE(0x0030f, 0x00205),CE(0x00311, 0x00207),CE(0x00323, 0x01eb9),CE(0x00327, 0x00229),CE(0x00328, 0x00119),CE(0x0032d, 0x01e19),CE(0x00330, 0x01e1b),CE(0x00307, 0x01e1f),CE(0x00301, 0x001f5),CE(0x00302, 0x0011d),CE(0x00304, 0x01e21),CE(0x00306, 0x0011f),CE(0x00307, 0x00121),CE(0x0030c, 0x001e7),CE(0x00327, 0x00123),CE(0x00302, 0x00125),CE(0x00307, 0x01e23),CE(0x00308, 0x01e27),CE(0x0030c, 0x0021f),CE(0x00323, 0x01e25),CE(0x00327, 0x01e29),CE(0x0032e, 0x01e2b),CE(0x00331, 0x01e96),CE(0x00300, 0x000ec),CE(0x00301, 0x000ed),CE(0x00302, 0x000ee),CE(0x00303, 0x00129),CE(0x00304, 0x0012b),CE(0x00306, 0x0012d),CE(0x00308, 0x000ef),CE(0x00309, 0x01ec9),CE(0x0030c, 0x001d0),CE(0x0030f, 0x00209),CE(0x00311, 0x0020b),CE(0x00323, 0x01ecb),CE(0x00328, 0x0012f),CE(0x00330, 0x01e2d),CE(0x00302, 0x00135),CE(0x0030c, 0x001f0),CE(0x00301, 0x01e31),CE(0x0030c, 0x001e9),CE(0x00323, 0x01e33),CE(0x00327, 0x00137),CE(0x00331, 0x01e35),CE(0x00301, 0x0013a),CE(0x0030c, 0x0013e),CE(0x00323, 0x01e37),CE(0x00327, 0x0013c),CE(0x0032d, 0x01e3d),CE(0x00331, 0x01e3b),CE(0x00301, 0x01e3f),CE(0x00307, 0x01e41),CE(0x00323, 0x01e43),CE(0x00300, 0x001f9),CE(0x00301, 0x00144),CE(0x00303, 0x000f1),CE(0x00307, 0x01e45),CE(0x0030c, 0x00148),CE(0x00323, 0x01e47),CE(0x00327, 0x00146),CE(0x0032d, 0x01e4b),CE(0x00331, 0x01e49),CE(0x00300, 0x000f2),CE(0x00301, 0x000f3),CE(0x00302, 0x000f4),CE(0x00303, 0x000f5),CE(0x00304, 0x0014d),CE(0x00306, 0x0014f),CE(0x00307, 0x0022f),CE(0x00308, 0x000f6),CE(0x00309, 0x01ecf),CE(0x0030b, 0x00151),CE(0x0030c, 0x001d2),CE(0x0030f, 0x0020d),CE(0x00311, 0x0020f),CE(0x0031b, 0x001a1),CE(0x00323, 0x01ecd),CE(0x00328, 0x001eb),CE(0x00301, 0x01e55),CE(0x00307, 0x01e57),CE(0x00301, 0x00155),CE(0x00307, 0x01e59),CE(0x0030c, 0x00159),CE(0x0030f, 0x00211),CE(0x00311, 0x00213),CE(0x00323, 0x01e5b),CE(0x00327, 0x00157),CE(0x00331, 0x01e5f),CE(0x00301, 0x0015b),CE(0x00302, 0x0015d),CE(0x00307, 0x01e61),CE(0x0030c, 0x00161),CE(0x00323, 0x01e63),CE(0x00326, 0x00219),CE(0x00327, 0x0015f),CE(0x00307, 0x01e6b),CE(0x00308, 0x01e97),CE(0x0030c, 0x00165),CE(0x00323, 0x01e6d),CE(0x00326, 0x0021b),CE(0x00327, 0x00163),CE(0x0032d, 0x01e71),CE(0x00331, 0x01e6f),CE(0x00300, 0x000f9),CE(0x00301, 0x000fa),CE(0x00302, 0x000fb),CE(0x00303, 0x00169),CE(0x00304, 0x0016b),CE(0x00306, 0x0016d),CE(0x00308, 0x000fc),CE(0x00309, 0x01ee7),CE(0x0030a, 0x0016f),CE(0x0030b, 0x00171),CE(0x0030c, 0x001d4),CE(0x0030f, 0x00215),CE(0x00311, 0x00217),CE(0x0031b, 0x001b0),CE(0x00323, 0x01ee5),CE(0x00324, 0x01e73),CE(0x00328, 0x00173),CE(0x0032d, 0x01e77),CE(0x00330, 0x01e75),CE(0x00303, 0x01e7d),CE(0x00323, 0x01e7f),CE(0x00300, 0x01e81),CE(0x00301, 0x01e83),CE(0x00302, 0x00175),CE(0x00307, 0x01e87),CE(0x00308, 0x01e85),CE(0x0030a, 0x01e98),CE(0x00323, 0x01e89),CE(0x00307, 0x01e8b),CE(0x00308, 0x01e8d),CE(0x00300, 0x01ef3),CE(0x00301, 0x000fd),CE(0x00302, 0x00177),CE(0x00303, 0x01ef9),CE(0x00304, 0x00233),CE(0x00307, 0x01e8f),CE(0x00308, 0x000ff),CE(0x00309, 0x01ef7),CE(0x0030a, 0x01e99),CE(0x00323, 0x01ef5),CE(0x00301, 0x0017a),CE(0x00302, 0x01e91),CE(0x00307, 0x0017c),CE(0x0030c, 0x0017e),CE(0x00323, 0x01e93),CE(0x00331, 0x01e95),CE(0x00300, 0x01fed),CE(0x00301, 0x00385),CE(0x00342, 0x01fc1),CE(0x00300, 0x01ea6),CE(0x00301, 0x01ea4),CE(0x00303, 0x01eaa),CE(0x00309, 0x01ea8),CE(0x00304, 0x001de),CE(0x00301, 0x001fa),CE(0x00301, 0x001fc),CE(0x00304, 0x001e2),CE(0x00301, 0x01e08),CE(0x00300, 0x01ec0),CE(0x00301, 0x01ebe),CE(0x00303, 0x01ec4),CE(0x00309, 0x01ec2),CE(0x00301, 0x01e2e),CE(0x00300, 0x01ed2),CE(0x00301, 0x01ed0),CE(0x00303, 0x01ed6),CE(0x00309, 0x01ed4),CE(0x00301, 0x01e4c),CE(0x00304, 0x0022c),CE(0x00308, 0x01e4e),CE(0x00304, 0x0022a),CE(0x00301, 0x001fe),CE(0x00300, 0x001db),CE(0x00301, 0x001d7),CE(0x00304, 0x001d5),CE(0x0030c, 0x001d9),CE(0x00300, 0x01ea7),CE(0x00301, 0x01ea5),CE(0x00303, 0x01eab),CE(0x00309, 0x01ea9),CE(0x00304, 0x001df),CE(0x00301, 0x001fb),CE(0x00301, 0x001fd),CE(0x00304, 0x001e3),CE(0x00301, 0x01e09),CE(0x00300, 0x01ec1),CE(0x00301, 0x01ebf),CE(0x00303, 0x01ec5),CE(0x00309, 0x01ec3),CE(0x00301, 0x01e2f),CE(0x00300, 0x01ed3),CE(0x00301, 0x01ed1),CE(0x00303, 0x01ed7),CE(0x00309, 0x01ed5),CE(0x00301, 0x01e4d),CE(0x00304, 0x0022d),CE(0x00308, 0x01e4f),CE(0x00304, 0x0022b),CE(0x00301, 0x001ff),CE(0x00300, 0x001dc),CE(0x00301, 0x001d8),CE(0x00304, 0x001d6),CE(0x0030c, 0x001da),CE(0x00300, 0x01eb0),CE(0x00301, 0x01eae),CE(0x00303, 0x01eb4),CE(0x00309, 0x01eb2),CE(0x00300, 0x01eb1),CE(0x00301, 0x01eaf),CE(0x00303, 0x01eb5),CE(0x00309, 0x01eb3),CE(0x00300, 0x01e14),CE(0x00301, 0x01e16),CE(0x00300, 0x01e15),CE(0x00301, 0x01e17),CE(0x00300, 0x01e50),CE(0x00301, 0x01e52),CE(0x00300, 0x01e51),CE(0x00301, 0x01e53),CE(0x00307, 0x01e64),CE(0x00307, 0x01e65),CE(0x00307, 0x01e66),CE(0x00307, 0x01e67),CE(0x00301, 0x01e78),CE(0x00301, 0x01e79),CE(0x00308, 0x01e7a),CE(0x00308, 0x01e7b),CE(0x00307, 0x01e9b),CE(0x00300, 0x01edc),CE(0x00301, 0x01eda),CE(0x00303, 0x01ee0),CE(0x00309, 0x01ede),CE(0x00323, 0x01ee2),CE(0x00300, 0x01edd),CE(0x00301, 0x01edb),CE(0x00303, 0x01ee1),CE(0x00309, 0x01edf),CE(0x00323, 0x01ee3),CE(0x00300, 0x01eea),CE(0x00301, 0x01ee8),CE(0x00303, 0x01eee),CE(0x00309, 0x01eec),CE(0x00323, 0x01ef0),CE(0x00300, 0x01eeb),CE(0x00301, 0x01ee9),CE(0x00303, 0x01eef),CE(0x00309, 0x01eed),CE(0x00323, 0x01ef1),CE(0x0030c, 0x001ee),CE(0x00304, 0x001ec),CE(0x00304, 0x001ed),CE(0x00304, 0x001e0),CE(0x00304, 0x001e1),CE(0x00306, 0x01e1c),CE(0x00306, 0x01e1d),CE(0x00304, 0x00230),CE(0x00304, 0x00231),CE(0x0030c, 0x001ef),CE(0x00300, 0x01fba),CE(0x00301, 0x00386),CE(0x00304, 0x01fb9),CE(0x00306, 0x01fb8),CE(0x00313, 0x01f08),CE(0x00314, 0x01f09),CE(0x00345, 0x01fbc),CE(0x00300, 0x01fc8),CE(0x00301, 0x00388),CE(0x00313, 0x01f18),CE(0x00314, 0x01f19),CE(0x00300, 0x01fca),CE(0x00301, 0x00389),CE(0x00313, 0x01f28),CE(0x00314, 0x01f29),CE(0x00345, 0x01fcc),CE(0x00300, 0x01fda),CE(0x00301, 0x0038a),CE(0x00304, 0x01fd9),CE(0x00306, 0x01fd8),CE(0x00308, 0x003aa),CE(0x00313, 0x01f38),CE(0x00314, 0x01f39),CE(0x00300, 0x01ff8),CE(0x00301, 0x0038c),CE(0x00313, 0x01f48),CE(0x00314, 0x01f49),CE(0x00314, 0x01fec),CE(0x00300, 0x01fea),CE(0x00301, 0x0038e),CE(0x00304, 0x01fe9),CE(0x00306, 0x01fe8),CE(0x00308, 0x003ab),CE(0x00314, 0x01f59),CE(0x00300, 0x01ffa),CE(0x00301, 0x0038f),CE(0x00313, 0x01f68),CE(0x00314, 0x01f69),CE(0x00345, 0x01ffc),CE(0x00345, 0x01fb4),CE(0x00345, 0x01fc4),CE(0x00300, 0x01f70),CE(0x00301, 0x003ac),CE(0x00304, 0x01fb1),CE(0x00306, 0x01fb0),CE(0x00313, 0x01f00),CE(0x00314, 0x01f01),CE(0x00342, 0x01fb6),CE(0x00345, 0x01fb3),CE(0x00300, 0x01f72),CE(0x00301, 0x003ad),CE(0x00313, 0x01f10),CE(0x00314, 0x01f11),CE(0x00300, 0x01f74),CE(0x00301, 0x003ae),CE(0x00313, 0x01f20),CE(0x00314, 0x01f21),CE(0x00342, 0x01fc6),CE(0x00345, 0x01fc3),CE(0x00300, 0x01f76),CE(0x00301, 0x003af),CE(0x00304, 0x01fd1),CE(0x00306, 0x01fd0),CE(0x00308, 0x003ca),CE(0x00313, 0x01f30),CE(0x00314, 0x01f31),CE(0x00342, 0x01fd6),CE(0x00300, 0x01f78),CE(0x00301, 0x003cc),CE(0x00313, 0x01f40),CE(0x00314, 0x01f41),CE(0x00313, 0x01fe4),CE(0x00314, 0x01fe5),CE(0x00300, 0x01f7a),CE(0x00301, 0x003cd),CE(0x00304, 0x01fe1),CE(0x00306, 0x01fe0),CE(0x00308, 0x003cb),CE(0x00313, 0x01f50),CE(0x00314, 0x01f51),CE(0x00342, 0x01fe6),CE(0x00300, 0x01f7c),CE(0x00301, 0x003ce),CE(0x00313, 0x01f60),CE(0x00314, 0x01f61),CE(0x00342, 0x01ff6),CE(0x00345, 0x01ff3),CE(0x00300, 0x01fd2),CE(0x00301, 0x00390),CE(0x00342, 0x01fd7),CE(0x00300, 0x01fe2),CE(0x00301, 0x003b0),CE(0x00342, 0x01fe7),CE(0x00345, 0x01ff4),CE(0x00301, 0x003d3),CE(0x00308, 0x003d4),CE(0x00308, 0x00407),CE(0x00306, 0x004d0),CE(0x00308, 0x004d2),CE(0x00301, 0x00403),CE(0x00300, 0x00400),CE(0x00306, 0x004d6),CE(0x00308, 0x00401),CE(0x00306, 0x004c1),CE(0x00308, 0x004dc),CE(0x00308, 0x004de),CE(0x00300, 0x0040d),CE(0x00304, 0x004e2),CE(0x00306, 0x00419),CE(0x00308, 0x004e4),CE(0x00301, 0x0040c),CE(0x00308, 0x004e6),CE(0x00304, 0x004ee),CE(0x00306, 0x0040e),CE(0x00308, 0x004f0),CE(0x0030b, 0x004f2),CE(0x00308, 0x004f4),CE(0x00308, 0x004f8),CE(0x00308, 0x004ec),CE(0x00306, 0x004d1),CE(0x00308, 0x004d3),CE(0x00301, 0x00453),CE(0x00300, 0x00450),CE(0x00306, 0x004d7),CE(0x00308, 0x00451),CE(0x00306, 0x004c2),CE(0x00308, 0x004dd),CE(0x00308, 0x004df),CE(0x00300, 0x0045d),CE(0x00304, 0x004e3),CE(0x00306, 0x00439),CE(0x00308, 0x004e5),CE(0x00301, 0x0045c),CE(0x00308, 0x004e7),CE(0x00304, 0x004ef),CE(0x00306, 0x0045e),CE(0x00308, 0x004f1),CE(0x0030b, 0x004f3),CE(0x00308, 0x004f5),CE(0x00308, 0x004f9),CE(0x00308, 0x004ed),CE(0x00308, 0x00457),CE(0x0030f, 0x00476),CE(0x0030f, 0x00477),CE(0x00308, 0x004da),CE(0x00308, 0x004db),CE(0x00308, 0x004ea),CE(0x00308, 0x004eb),CE(0x00653, 0x00622),CE(0x00654, 0x00623),CE(0x00655, 0x00625),CE(0x00654, 0x00624),CE(0x00654, 0x00626),CE(0x00654, 0x006c2),CE(0x00654, 0x006d3),CE(0x00654, 0x006c0),CE(0x0093c, 0x00929),CE(0x0093c, 0x00931),CE(0x0093c, 0x00934),CE(0x009be, 0x009cb),CE(0x009d7, 0x009cc),CE(0x00b3e, 0x00b4b),CE(0x00b56, 0x00b48),CE(0x00b57, 0x00b4c),CE(0x00bd7, 0x00b94),CE(0x00bbe, 0x00bca),CE(0x00bd7, 0x00bcc),CE(0x00bbe, 0x00bcb),CE(0x00c56, 0x00c48),CE(0x00cd5, 0x00cc0),CE(0x00cc2, 0x00cca),CE(0x00cd5, 0x00cc7),CE(0x00cd6, 0x00cc8),CE(0x00cd5, 0x00ccb),CE(0x00d3e, 0x00d4a),CE(0x00d57, 0x00d4c),CE(0x00d3e, 0x00d4b),CE(0x00dca, 0x00dda),CE(0x00dcf, 0x00ddc),CE(0x00ddf, 0x00dde),CE(0x00dca, 0x00ddd),CE(0x0102e, 0x01026),CE(0x01b35, 0x01b06),CE(0x01b35, 0x01b08),CE(0x01b35, 0x01b0a),CE(0x01b35, 0x01b0c),CE(0x01b35, 0x01b0e),CE(0x01b35, 0x01b12),CE(0x01b35, 0x01b3b),CE(0x01b35, 0x01b3d),CE(0x01b35, 0x01b40),CE(0x01b35, 0x01b41),CE(0x01b35, 0x01b43),CE(0x00304, 0x01e38),CE(0x00304, 0x01e39),CE(0x00304, 0x01e5c),CE(0x00304, 0x01e5d),CE(0x00307, 0x01e68),CE(0x00307, 0x01e69),CE(0x00302, 0x01eac),CE(0x00306, 0x01eb6),CE(0x00302, 0x01ead),CE(0x00306, 0x01eb7),CE(0x00302, 0x01ec6),CE(0x00302, 0x01ec7),CE(0x00302, 0x01ed8),CE(0x00302, 0x01ed9),CE(0x00300, 0x01f02),CE(0x00301, 0x01f04),CE(0x00342, 0x01f06),CE(0x00345, 0x01f80),CE(0x00300, 0x01f03),CE(0x00301, 0x01f05),CE(0x00342, 0x01f07),CE(0x00345, 0x01f81),CE(0x00345, 0x01f82),CE(0x00345, 0x01f83),CE(0x00345, 0x01f84),CE(0x00345, 0x01f85),CE(0x00345, 0x01f86),CE(0x00345, 0x01f87),CE(0x00300, 0x01f0a),CE(0x00301, 0x01f0c),CE(0x00342, 0x01f0e),CE(0x00345, 0x01f88),CE(0x00300, 0x01f0b),CE(0x00301, 0x01f0d),CE(0x00342, 0x01f0f),CE(0x00345, 0x01f89),CE(0x00345, 0x01f8a),CE(0x00345, 0x01f8b),CE(0x00345, 0x01f8c),CE(0x00345, 0x01f8d),CE(0x00345, 0x01f8e),CE(0x00345, 0x01f8f),CE(0x00300, 0x01f12),CE(0x00301, 0x01f14),CE(0x00300, 0x01f13),CE(0x00301, 0x01f15),CE(0x00300, 0x01f1a),CE(0x00301, 0x01f1c),CE(0x00300, 0x01f1b),CE(0x00301, 0x01f1d),CE(0x00300, 0x01f22),CE(0x00301, 0x01f24),CE(0x00342, 0x01f26),CE(0x00345, 0x01f90),CE(0x00300, 0x01f23),CE(0x00301, 0x01f25),CE(0x00342, 0x01f27),CE(0x00345, 0x01f91),CE(0x00345, 0x01f92),CE(0x00345, 0x01f93),CE(0x00345, 0x01f94),CE(0x00345, 0x01f95),CE(0x00345, 0x01f96),CE(0x00345, 0x01f97),CE(0x00300, 0x01f2a),CE(0x00301, 0x01f2c),CE(0x00342, 0x01f2e),CE(0x00345, 0x01f98),CE(0x00300, 0x01f2b),CE(0x00301, 0x01f2d),CE(0x00342, 0x01f2f),CE(0x00345, 0x01f99),CE(0x00345, 0x01f9a),CE(0x00345, 0x01f9b),CE(0x00345, 0x01f9c),CE(0x00345, 0x01f9d),CE(0x00345, 0x01f9e),CE(0x00345, 0x01f9f),CE(0x00300, 0x01f32),CE(0x00301, 0x01f34),CE(0x00342, 0x01f36),CE(0x00300, 0x01f33),CE(0x00301, 0x01f35),CE(0x00342, 0x01f37),CE(0x00300, 0x01f3a),CE(0x00301, 0x01f3c),CE(0x00342, 0x01f3e),CE(0x00300, 0x01f3b),CE(0x00301, 0x01f3d),CE(0x00342, 0x01f3f),CE(0x00300, 0x01f42),CE(0x00301, 0x01f44),CE(0x00300, 0x01f43),CE(0x00301, 0x01f45),CE(0x00300, 0x01f4a),CE(0x00301, 0x01f4c),CE(0x00300, 0x01f4b),CE(0x00301, 0x01f4d),CE(0x00300, 0x01f52),CE(0x00301, 0x01f54),CE(0x00342, 0x01f56),CE(0x00300, 0x01f53),CE(0x00301, 0x01f55),CE(0x00342, 0x01f57),CE(0x00300, 0x01f5b),CE(0x00301, 0x01f5d),CE(0x00342, 0x01f5f),CE(0x00300, 0x01f62),CE(0x00301, 0x01f64),CE(0x00342, 0x01f66),CE(0x00345, 0x01fa0),CE(0x00300, 0x01f63),CE(0x00301, 0x01f65),CE(0x00342, 0x01f67),CE(0x00345, 0x01fa1),CE(0x00345, 0x01fa2),CE(0x00345, 0x01fa3),CE(0x00345, 0x01fa4),CE(0x00345, 0x01fa5),CE(0x00345, 0x01fa6),CE(0x00345, 0x01fa7),CE(0x00300, 0x01f6a),CE(0x00301, 0x01f6c),CE(0x00342, 0x01f6e),CE(0x00345, 0x01fa8),CE(0x00300, 0x01f6b),CE(0x00301, 0x01f6d),CE(0x00342, 0x01f6f),CE(0x00345, 0x01fa9),CE(0x00345, 0x01faa),CE(0x00345, 0x01fab),CE(0x00345, 0x01fac),CE(0x00345, 0x01fad),CE(0x00345, 0x01fae),CE(0x00345, 0x01faf),CE(0x00345, 0x01fb2),CE(0x00345, 0x01fc2),CE(0x00345, 0x01ff2),CE(0x00345, 0x01fb7),CE(0x00300, 0x01fcd),CE(0x00301, 0x01fce),CE(0x00342, 0x01fcf),CE(0x00345, 0x01fc7),CE(0x00345, 0x01ff7),CE(0x00300, 0x01fdd),CE(0x00301, 0x01fde),CE(0x00342, 0x01fdf),CE(0x00338, 0x0219a),CE(0x00338, 0x0219b),CE(0x00338, 0x021ae),CE(0x00338, 0x021cd),CE(0x00338, 0x021cf),CE(0x00338, 0x021ce),CE(0x00338, 0x02204),CE(0x00338, 0x02209),CE(0x00338, 0x0220c),CE(0x00338, 0x02224),CE(0x00338, 0x02226),CE(0x00338, 0x02241),CE(0x00338, 0x02244),CE(0x00338, 0x02247),CE(0x00338, 0x02249),CE(0x00338, 0x0226d),CE(0x00338, 0x02262),CE(0x00338, 0x02270),CE(0x00338, 0x02271),CE(0x00338, 0x02274),CE(0x00338, 0x02275),CE(0x00338, 0x02278),CE(0x00338, 0x02279),CE(0x00338, 0x02280),CE(0x00338, 0x02281),CE(0x00338, 0x022e0),CE(0x00338, 0x022e1),CE(0x00338, 0x02284),CE(0x00338, 0x02285),CE(0x00338, 0x02288),CE(0x00338, 0x02289),CE(0x00338, 0x022e2),CE(0x00338, 0x022e3),CE(0x00338, 0x022ac),CE(0x00338, 0x022ad),CE(0x00338, 0x022ae),CE(0x00338, 0x022af),CE(0x00338, 0x022ea),CE(0x00338, 0x022eb),CE(0x00338, 0x022ec),CE(0x00338, 0x022ed),CE(0x03099, 0x03094),CE(0x03099, 0x0304c),CE(0x03099, 0x0304e),CE(0x03099, 0x03050),CE(0x03099, 0x03052),CE(0x03099, 0x03054),CE(0x03099, 0x03056),CE(0x03099, 0x03058),CE(0x03099, 0x0305a),CE(0x03099, 0x0305c),CE(0x03099, 0x0305e),CE(0x03099, 0x03060),CE(0x03099, 0x03062),CE(0x03099, 0x03065),CE(0x03099, 0x03067),CE(0x03099, 0x03069),CE(0x03099, 0x03070),CE(0x0309a, 0x03071),CE(0x03099, 0x03073),CE(0x0309a, 0x03074),CE(0x03099, 0x03076),CE(0x0309a, 0x03077),CE(0x03099, 0x03079),CE(0x0309a, 0x0307a),CE(0x03099, 0x0307c),CE(0x0309a, 0x0307d),CE(0x03099, 0x0309e),CE(0x03099, 0x030f4),CE(0x03099, 0x030ac),CE(0x03099, 0x030ae),CE(0x03099, 0x030b0),CE(0x03099, 0x030b2),CE(0x03099, 0x030b4),CE(0x03099, 0x030b6),CE(0x03099, 0x030b8),CE(0x03099, 0x030ba),CE(0x03099, 0x030bc),CE(0x03099, 0x030be),CE(0x03099, 0x030c0),CE(0x03099, 0x030c2),CE(0x03099, 0x030c5),CE(0x03099, 0x030c7),CE(0x03099, 0x030c9),CE(0x03099, 0x030d0),CE(0x0309a, 0x030d1),CE(0x03099, 0x030d3),CE(0x0309a, 0x030d4),CE(0x03099, 0x030d6),CE(0x0309a, 0x030d7),CE(0x03099, 0x030d9),CE(0x0309a, 0x030da),CE(0x03099, 0x030dc),CE(0x0309a, 0x030dd),CE(0x03099, 0x030f7),CE(0x03099, 0x030f8),CE(0x03099, 0x030f9),CE(0x03099, 0x030fa),CE(0x03099, 0x030fe),CE(0x110ba, 0x1109a),CE(0x110ba, 0x1109c),CE(0x110ba, 0x110ab),CE(0x11127, 0x1112e),CE(0x11127, 0x1112f),];
+return t;
+}
+
+}
+
diff --git a/libphobos/src/std/internal/unicode_decomp.d b/libphobos/src/std/internal/unicode_decomp.d
new file mode 100644
index 00000000000..bfe665b7bbb
--- /dev/null
+++ b/libphobos/src/std/internal/unicode_decomp.d
@@ -0,0 +1,34 @@
+module std.internal.unicode_decomp;
+import std.internal.unicode_tables;
+
+@safe pure nothrow @nogc:
+
+static if(size_t.sizeof == 8) {
+//22656 bytes
+enum compatMappingTrieEntries = TrieEntry!(ushort, 8, 8, 5)([ 0x0,  0x20,  0x2a0], [ 0x100,  0xa00,  0x21c0], [ 0x402030202020100,  0x706020202020205,  0x802020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x3000200010000,  0x7000600050004,  0xa000900080000,  0xc000b,  0xf000e000d0000,  0x11001000000000,  0x15001400130012,  0x19001800170016,  0x1b001a00000000,  0x0,  0x1c,  0x1e0000001d0000,  0x1f00000000,  0x0,  0x0,  0x0,  0x0,  0x2100200000,  0x2200000000,  0x2400230000,  0x0,  0x2500000000,  0x2700000026,  0x2800000000,  0x2900000000,  0x2a00000000,  0x2b00000000,  0x2c0000,  0x2e002d0000,  0x3100300000002f,  0x330032,  0x340000,  0x35000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3800370036,  0x0,  0x0,  0x0,  0x3b003a00390000,  0x3d003c,  0x410040003f003e,  0x45004400430042,  0x49004800470046,  0x4d004c004b004a,  0x510050004f004e,  0x530052,  0x57005600550054,  0x5a00590058,  0x5e005d005c005b,  0x6100000060005f,  0x620000,  0x0,  0x63000000000000,  0x67006600650064,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x69000000000068,  0x6a00000000,  0x0,  0x0,  0x6b000000000000,  0x0,  0x6c000000000000,  0x0,  0x0,  0x6e00000000006d,  0x7200710070006f,  0x7500740073,  0x79007800770076,  0x7d007c007b007a,  0x80007f007e0000,  0x81,  0x85008400830082,  0x89008800870086,  0x8d008c008b008a,  0x910090008f008e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x92000000000000,  0x93000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x97009600950094,  0x9b009a00990098,  0x9f009e009d009c,  0xa200a100a0,  0xa600a500a400a3,  0xaa00a900a800a7,  0xae00ad00ac00ab,  0xb200b100b000af,  0xb600b500b400b3,  0xba00b900b800b7,  0xbe00bd00bc00bb,  0xc200c100c000bf,  0xc600c500c400c3,  0xca00c900c800c7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xcc00cb,  0xcd0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xcf00ce00000000,  0xd100d00000,  0x0,  0x0,  0x0,  0x0,  0xd500d400d300d2,  0xd900d800d700d6,  0xdd00dc00db00da,  0xdf00d300d200de,  0xe200e100e000d5,  0xe500e400e300d9,  0xe900e800e700e6,  0xed00ec00eb00ea,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf100f000ef00ee,  0xf300f2,  0x0,  0x0,  0x0,  0x0,  0xf700f600f500f4,  0xf8,  0xfb00fa00f9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xff00fe00fd00fc,  0x103010201010100,  0x107010601050104,  0x10b010a01090108,  0x10c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x69200000015,  0x9000000000000,  0x30f034300000000,  0x11b20003,  0x78703140048,  0x49403c603ce,  0x58605730570056d,  0x5f8000005b005a6,  0x6580631062e062b,  0x6f906ea06e706e4,  0x7a907a6078f0000,  0x7e307bf07ac,  0x8b708b408b10000,  0x95f08cb,  0x9c209af09ac09a9,  0xa47000009ec09e2,  0xab30a8c0a890a86,  0xb550b490b460b43,  0xc5e0c5b0c410000,  0xc980c740c61,  0xd6e0d6b0d680000,  0xe1b00000e0c0d82,  0x9c8058c09c50589,  0xa3b05ec0a0a05ce,  0xa4105f20a3e05ef,  0xa6e061a0a4405f5,  0xaa2064700000000,  0xab006550aad0652,  0xab9065e0ad00675,  0xb0106a00afb069a,  0xb0a06a90b0406a3,  0xb1606ba,  0xb4f06f00b4c06ed,  0xb6b070f0b5206f3,  0xb3706d8000006f6,  0xbae072e0b730717,  0x7500bcc07430000,  0x7400bcf07460bd9,  0x78c000000000bc9,  0x7950c4d079b0c3e,  0xed70c47,  0xc8e07d90c8307ce,  0xca207ed,  0xd1d08580d070842,  0xd2b086c0d0d0848,  0xd49088a0d320873,  0xd5d08a60d380879,  0xd54089d,  0xd7808c10d7108ba,  0xd9808e10d7f08c8,  0xdc4090d0d9b08e4,  0xe0f09620de9093f,  0x97f0e290979096e,  0x8400614060d0e2f,  0xcae07f9,  0x0,  0x0,  0x8f0000000000000,  0xda7,  0x0,  0x0,  0x0,  0x0,  0x7360a670613060c,  0x78307800bb9073d,  0x70309f305b70c32,  0x8e70ca507f00b5f,  0x8d20d8d08d60d9e,  0x8ce0d9108da0d89,  0x9e505a900000d85,  0xe630e5a09de05a2,  0xb0706a600000000,  0xccc08170ba80728,  0xecc0e7b0ccf081a,  0xa64061006090b76,  0xaf80697,  0x9ef05b30c3b0789,  0xe680e5d0e600e57,  0x9f905bd09f605ba,  0xabf06640abc0661,  0xb6507090b620706,  0xcab07f60ca807f3,  0xd13084e0d10084b,  0xda408ed0da108ea,  0xd5a08a30d460887,  0xb1f06c300000000,  0x0,  0x9db059f00000000,  0xc9b07e60ac9066e,  0xc9107dc0c7b07c6,  0xe1509680c9407df,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa11073e0e9a0b0d,  0xde10eb80eb60eb4,  0x695,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4b00240012000f,  0x270006,  0xb4108400a280e96,  0xecf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b00000004001a,  0x1d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xed5,  0x5400000000,  0x54600000000,  0x0,  0x7410ee8001c0003,  0xfb40f630f43,  0x103c101600000fed,  0x1185,  0x0,  0x0,  0x0,  0x0,  0x0,  0x101f0fbd00000000,  0x1175111910f5108f,  0x1213,  0x0,  0x0,  0x0,  0x0,  0x0,  0x120c117e00000000,  0x124b120311d5,  0x10161011116e10ea,  0x11ee123c101f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x11f811f011ae,  0x10f00fad,  0x100d0000,  0x0,  0x12ad000012b612b0,  0x12a4000000000000,  0x0,  0x12d712c212ce,  0x0,  0x0,  0x12c80000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x130a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x12ef000012f812f2,  0x132d000000000000,  0x0,  0x131b13041310,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1333133000000000,  0x0,  0x0,  0x12fb12b90000,  0x0,  0x0,  0x0,  0x12ec12aa12e912a7,  0x12f512b300000000,  0x1339133600000000,  0x130112bf12fe12bc,  0x130712c500000000,  0x131512d1130d12cb,  0x133f133c00000000,  0x131812d4132a12e6,  0x132112dd131e12da,  0x132412e0,  0x132712e3,  0x0,  0x0,  0x1342000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13e913e600000000,  0x17ca13ec178f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x185b179213ef0000,  0x1811,  0x0,  0x18520000186d,  0x0,  0x0,  0x0,  0x186a000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18820000,  0x0,  0x188b0000,  0x188e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1879187618731870,  0x18881885187f187c,  0x0,  0x0,  0x189a000000000000,  0x189d,  0x0,  0x0,  0x0,  0x1897000018941891,  0x0,  0x0,  0x0,  0x0,  0x18ac000000000000,  0x18af00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18a618a318a00000,  0x18a900000000,  0x0,  0x0,  0x18b80000000018bb,  0x18be,  0x0,  0x0,  0x0,  0x18b518b2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18c1,  0x0,  0x0,  0x0,  0x0,  0x18ca18c400000000,  0x18c7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18cd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18d0,  0x18da000000000000,  0x18d618d3000018dd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18e618e000000000,  0x18e3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18e900000000,  0x18f318ef18ec,  0x0,  0x0,  0x0,  0x0,  0x18f6000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18ff000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18fc18f9,  0x0,  0x0,  0x0,  0x1902,  0x0,  0x0,  0x0,  0x0,  0x1907000000000000,  0x0,  0x0,  0x190a0000,  0x190d00000000,  0x1910000000000000,  0x0,  0x1913,  0x0,  0x0,  0x19040000,  0x0,  0x1916000000000000,  0x1931193519190000,  0x1938193c,  0x0,  0x191c0000,  0x0,  0x0,  0x0,  0x1922000000000000,  0x0,  0x0,  0x19250000,  0x192800000000,  0x192b000000000000,  0x0,  0x192e,  0x0,  0x0,  0x191f0000,  0x0,  0x0,  0x193f00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1942,  0x0,  0x1a3800000000,  0x1a3e00001a3b,  0x1a4400001a41,  0x1a4700000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a4a000000000000,  0x1a4d0000,  0x1a5600001a531a50,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5d50e550568,  0x6870e75062905e6,  0x71a060706cf06ac,  0x77e07230734,  0x82c06af0e7e07a4,  0x6920770056b088d,  0x9371a590e840e82,  0xe8e0e8c0a7d0a2e,  0xb79000006020e90,  0xe8807870e7105d3,  0xba30cd31a5d1a5b,  0x86a0ea41a610a24,  0x10ee10ec10ea1a63,  0xa110ae0123e123c,  0x10ec10ea086a0a24,  0x123e123c11f0,  0x0,  0x0,  0x0,  0x1313,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe86000000000000,  0xe900e660e8a09a0,  0xe980e940e920ad9,  0x1a650ea00e9e0e9c,  0xed31a670ea20ed1,  0xeac0eaa0ea60ea8,  0xeba0eb20eb00eae,  0xec00ebe0e790ebc,  0x6110ec40ec21a5f,  0x116e0eca0ec80ec6,  0xa1305da0a0705cb,  0xa1905e00a1605dd,  0xa6b06170a4a05fb,  0xa7a06260a71061d,  0xa7706230a740620,  0xaa9064e0aa5064a,  0xad6067b0ad30678,  0xaef06840acc0671,  0xb1906bd0afe069d,  0xb1c06c00b2206c6,  0xb2806cc0b2506c9,  0xb5806fc0b6e0712,  0xbab072b0ba50725,  0xbd207490bb10731,  0xbdf07560bd5074c,  0xc1207720bdc0753,  0xc1807780c150775,  0xc4a07980c440792,  0xc50079e0c5307a1,  0xc7f07ca0c7707c2,  0xc8a07d50c8607d1,  0xcef08380cec0835,  0xd1608510d0a0845,  0xd20085b0d190854,  0xd3f08800d350876,  0xd3b087c0d2e086f,  0xd4e089a0d420883,  0xd6308ac0d5708a0,  0xdc1090a0d6008a9,  0xdc709100dca0913,  0xd7b08c40d7408bd,  0xdde09270ddb0924,  0xde6093c0de30939,  0xdec09420def0945,  0xe0109540df50948,  0xe18096b0e040957,  0xe3509850e2c097c,  0xd510b2b0e380988,  0xd3509a60e210df2,  0x0,  0x9e905ad09fc05c0,  0x9b2057609b6057a,  0x9ba057e09be0582,  0x9cf059309ff05c3,  0x9d7059b09cb058f,  0xa0305c709d30597,  0xab6065b0ac20667,  0xa9306380a9f0644,  0xa9b06400a8f0634,  0xac5066a0a97063c,  0xb68070c0b5c0700,  0xc9f07ea0cc50810,  0xc6407af0c6807b3,  0xc6c07b70c7007bb,  0xcb508000cc80813,  0xcbd08080cb107fc,  0xcc1080c0cb90804,  0xd9508de0dbe0907,  0xdaa08f30dae08f7,  0xdb208fb0db608ff,  0xe09095c0dba0903,  0xe1e09710e240974,  0xe120965,  0x0,  0x10c1109f10be109c,  0x10d310b110ca10a8,  0xf160ef40f130ef1,  0xf280f060f1f0efd,  0x110610fb110310f8,  0x110a10ff,  0xf540f490f510f46,  0xf580f4d,  0x1145112311421120,  0x11571135114e112c,  0xf8b0f690f880f66,  0xf9d0f7b0f940f72,  0x119f1190119c118d,  0x11a7119811a31194,  0xfd20fc30fcf0fc0,  0xfda0fcb0fd60fc7,  0x11e611db11e311d8,  0x11ea11df,  0xffe0ff30ffb0ff0,  0x10020ff7,  0x122d121e122a121b,  0x1235122612311222,  0x1025000010220000,  0x102d000010290000,  0x1277125512741252,  0x128912671280125e,  0x106410421061103f,  0x10761054106d104b,  0x10f510f2108f1088,  0x1175117211191112,  0x1203120011d511d2,  0x124b1244,  0x10c510a310dc10ba,  0x10d710b510ce10ac,  0xf1a0ef80f310f0f,  0xf2c0f0a0f230f01,  0x114911271160113e,  0x115b113911521130,  0xf8f0f6d0fa60f84,  0xfa10f7f0f980f76,  0x127b125912921270,  0x128d126b12841262,  0x10681046107f105d,  0x107a10581071104f,  0x10e7108b10961099,  0x10e310e000001092,  0xee80ee50eeb0eee,  0x2a1170002a0f35,  0x116b111500200051,  0x116711640000111c,  0xf630f600f430f40,  0x350031002d0faa,  0x118511811178117b,  0x118911ab00000000,  0xfb40fb10fb70fba,  0x440040003c0000,  0x1213120f12061209,  0x1217123911f511f2,  0x101610131019101c,  0x995001c0018100a,  0x129d124700000000,  0x129912960000124e,  0x103c10390fed0fea,  0x3900031083,  0x1000100010001,  0x1000100010001,  0x100010001,  0x0,  0x1a690000,  0x4e000000000000,  0x0,  0x0,  0x0,  0x2ff02fc02fa,  0x0,  0x1000000000000,  0x1a6f000000000000,  0x1a7e1a7b00001a72,  0x0,  0xc0000008f,  0x0,  0x563000000000000,  0x920560,  0x0,  0x0,  0x1a76000000000000,  0x0,  0x1000000000000,  0x0,  0x0,  0x0,  0x0,  0xae00305,  0x392038303740365,  0x1aad02f403b003a1,  0xb3b00a500a10544,  0x30f034303140305,  0x392038303740365,  0x1aad02f403b003a1,  0xa500a10544,  0xb4107870a7d0692,  0xa280b790b0d0e8c,  0x8400cd30b3b05d3,  0xba3,  0x0,  0x0,  0x83f,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe4d05e309a2099e,  0xe770a220a1e0000,  0x6ac06020e500000,  0xe6d0b0d06ac06ac,  0xa28073406cf06cf,  0x786077e0000,  0x82c083b06af0000,  0x82c082c,  0x897088f0863,  0x77c0000060a,  0x5b0071a0000060a,  0xa7d000005e305d5,  0x7230000067e0629,  0x136a136213540787,  0x68000000ae0136f,  0x10060f3a10ec11ee,  0x1aab,  0xa7d0a2e05e60000,  0x73e0ae0,  0x0,  0x3ca03c103e203da,  0x498045903d20455,  0x3de04e703d604cf,  0x3be051104eb049c,  0x6de06d406d106cf,  0x91f091b091806b2,  0x950094d068206e1,  0x72305e605e30734,  0xb3d0b330b300ae0,  0xdd60dd20dcf086a,  0xdfd0dfa0b410b40,  0x5d30a2e09a00a28,  0x0,  0x0,  0x30d0000,  0x0,  0x0,  0x0,  0x1a8d1a8600000000,  0x0,  0x0,  0x0,  0x0,  0x1a9200000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a981a9b1a950000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1aa0,  0x1aa50000,  0x1aa8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ab200001aaf,  0x0,  0x1ac100001ab81ab5,  0x1ac4,  0x0,  0x0,  0x0,  0x1ac80000,  0x1ace000000001acb,  0x1ad10000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ad700000556,  0x0,  0x0,  0x55b054a1ad40000,  0x1add1ada,  0x1ae31ae0,  0x1ae91ae6,  0x0,  0x1aef1aec,  0x1afb1af8,  0x1b011afe,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b131b101b0d1b0a,  0x0,  0x0,  0x0,  0x0,  0x1b071b041af51af2,  0x0,  0x1b191b1600000000,  0x1b1f1b1c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b371b350000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x365030f03430314,  0x3a1039203830374,  0x342032f031c03b0,  0x382037303640355,  0x3f703af03a00391,  0xe600e200d900a3,  0xf600f200ee00ea,  0xb100ac00a700fa,  0xc500c000bb00b6,  0xdd00d400cf00ca,  0x368035903460319,  0x3a4039503860377,  0x3450332031f03b3,  0x385037603670358,  0x3fa03b203a30394,  0x172016e016a0166,  0x182017e017a0176,  0x192018e018a0186,  0x1a2019e019a0196,  0x1b201ae01aa01a6,  0x1c201be01ba01b6,  0x5d5056801ca01c6,  0x67e062905e605e3,  0x60706cf06ac0687,  0x77e07230734071a,  0x82c083b06af07a4,  0x6b2056b088d085e,  0x60a095a06820770,  0xa2e09a009370692,  0xb0d06020ad90a7d,  0xa280b79073e0ae0,  0xcd307870b3b05d3,  0xba308400a1105d8,  0xb410de1086a0a24,  0x30506110695,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1abc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x552054f0542,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b2c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6b2073e,  0x0,  0x0,  0x0,  0x1b2f000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x227c000000000000,  0x0,  0x0,  0x0,  0x0,  0x26b0000000000000,  0x0,  0x0,  0x0,  0x1f091f011efb1ee9,  0x1f1b1f171f131f0d,  0x1f5f1f571f4b1f21,  0x1f871f771f6f1f67,  0x1fb91fa51f8b1f89,  0x1fcd1fc71fc51fc1,  0x1feb1fe91fdd1fdb,  0x204f20451ff71fef,  0x207f207d2079206f,  0x20b420ae20982087,  0x20ce20cc20ca20c4,  0x20f820f220dc20da,  0x210f2108210020fc,  0x212f212b21292113,  0x2141213921352131,  0x21972195218b214f,  0x21e521e321d921d7,  0x32521f121ed21e9,  0x2260222303292211,  0x227a2274226e2266,  0x228422822280227e,  0x230c230622e22286,  0x231623122310230e,  0x2334233023222318,  0x235c235a23562354,  0x2376237423622360,  0x238a238823862384,  0x23aa23a823a62394,  0x23ee23de23dc23bc,  0x241c240a23fa23f6,  0x2452244c2442243e,  0x245e245c245a2456,  0x247e247a246c246a,  0x248e248a24842482,  0x2498249624922490,  0x250e250c24f224e8,  0x2530252c25282512,  0x2558255425522534,  0x25742572255c255a,  0x2592258425822578,  0x25ba25aa25982596,  0x25de25c825c425c2,  0x260025fa25e825e0,  0x261a261826142608,  0x26262624261e261c,  0x263c263a2638262a,  0x2658264e264a2648,  0x26622660265c265a,  0x2670266826662664,  0x26862684267e267c,  0x2690268e268a2688,  0x26a0269c26982692,  0x26aa26a826a626a2,  0x26b226ae,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b4900000000,  0x1fd11fcf1fcd,  0x0,  0x0,  0x0,  0x0,  0x1b8100001b7e,  0x1b8700001b84,  0x1b8d00001b8a,  0x1b9300001b90,  0x1b9900001b96,  0x1b9f00001b9c,  0x1ba500001ba20000,  0x1ba80000,  0x0,  0x1bb100001bae1bab,  0x1bba1bb700001bb4,  0x1bc01bbd0000,  0x1bc91bc6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b7b,  0x87000000000000,  0x1bcc1bd30000008a,  0x0,  0x0,  0x0,  0x1c4300001c26,  0x1c9200001bf6,  0x1caf00001c9b,  0x1cca00001cbf,  0x1cdc00001ccf,  0x1ceb00001ce1,  0x1cf700001cf20000,  0x1c100000,  0x0,  0x1d3b00001d261d1d,  0x1d611d5700001d42,  0x1d7e1d760000,  0x1caa1da1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1e44000000001c01,  0x1e571e521e4d,  0x1ca11e6000000000,  0x0,  0x0,  0x0,  0x0,  0x1a10194919440000,  0x19501a141a12194b,  0x1a181a1619571955,  0x1a201a1e1a1c1a1a,  0x19661961195c19a6,  0x196f196d196819b0,  0x198e198319811977,  0x1947199d19981993,  0x19de19dc19da19d8,  0x198c19e419e219e0,  0x19ee19ec19ea19e8,  0x19f619f419f21975,  0x19fe197f19fa19f8,  0x1a2219a419a219d4,  0x1a2a1a281a261a24,  0x1a3019a81a2e1a2c,  0x19ae19ac19aa1a32,  0x19b819b619b419b2,  0x19c019be19bc19ba,  0x19c819c619c419c2,  0x1a361a3419cc19ca,  0x1a0019d219d019ce,  0x1a081a061a041a02,  0x1a0e1a0c1a0a,  0x1f171ee900000000,  0x1efd1ef120471eef,  0x1ef71f0d23641ef3,  0x1f212051208c1eeb,  0x1e901e001d701ce,  0x20d020401fb01f2,  0x245023c02330225,  0x1db01d20257024e,  0x1ff01f601ed01e4,  0x237022902110208,  0x25b025202490240,  0x21e0216022e,  0x2a0026802700260,  0x284026402880274,  0x2c402b00290026c,  0x2a402ec02b802c0,  0x2d002b402bc02ac,  0x2d402e402c80298,  0x2a8029c0278028c,  0x29402e8027c02cc,  0x2e002dc028002d8,  0x23fe21e321112021,  0x0,  0x0,  0x41c04110406082e,  0x440043904320427,  0x475046e044e0447,  0x4850482047f047c,  0x19571950194b1944,  0x196f19681961195c,  0x1993198e19831977,  0x194d1946199d1998,  0x1963195e19591952,  0x198519791971196a,  0x199f199a19951990,  0x1974197c1988,  0x20471eef1f171ee9,  0x1f5f1eed1f611f19,  0x22e203291fcd1f0f,  0x204f25c822232286,  0x2240221b223b0325,  0x23ca255e231c2007,  0x2098236823e21fab,  0x22961fdf1f4925a2,  0x208a1f731f2b262c,  0x20fa1ef31efd1ef1,  0x20b220b81fc92001,  0x1fd725681f292390,  0x48e048b04882083,  0x4b704b404b10491,  0x4c304c004bd04ba,  0x4e404cc04c904c6,  0x4d604a3034e033b,  0x5290518050304f2,  0x34d033a0327053a,  0x7390a7f0a8206b4,  0x1c0a1bff1bf11bd8,  0x1c731c411c241c1a,  0x1cbd1cad1c991c90,  0x1cdf1cda1ccd1c1e,  0x1bde1cf51cf01bfb,  0x1c8e1d111d0f1ca6,  0x1d551d391d1b1d0d,  0x1ddc1c311d9f1d74,  0x1e041e001def1c22,  0x1c351e1b1e191e11,  0x1e421c5d1e341bed,  0x1e551e501e4b,  0x1beb1be51be01bda,  0x1c0c1c041bf91bf3,  0x1c331c201c1c1c13,  0x1c2e1c291c3c1c37,  0x1c501c571c4b1c46,  0x1c6d1c661c5f1c5c,  0x1c8b1c841c7d1c61,  0x1cb21ca81ca41c95,  0x1cd61cd21cc21cb7,  0x1c811d031cfa1ce4,  0x1d291d351d171d0c,  0x1d4c1d451d201d30,  0x1d6b1d641d3e1d51,  0x1d811d951d701d5a,  0x1d8f1d8a1d9b1d85,  0x1db81da41dac1d79,  0x1dc51dbf1dbb1db2,  0x1dd61dd21dce1dca,  0x1df11de61de31dde,  0x1e0b1e061c681df5,  0x1e291e241e1f1e13,  0x1c6f1e391e361e2e,  0x3610352033f0311,  0x39d038e037f0370,  0x33e032b03bb03ac,  0x37e036f03600351,  0x3ba03ab039c038d,  0x4230418040d0402,  0x56a0a530b0f042e,  0xa590ce60c580a0f,  0x210a06db0a600a5c,  0x223d21f920892200,  0xbea11b40c260cda,  0x689075b071c0b7b,  0xc290cdd0b8c0a26,  0x6010bf611c011b7,  0x68c07640b7e068d,  0xa560bfd11c30893,  0x11c60c350aec0b94,  0xc030b970a300c00,  0xc070b9a0a340a33,  0xc1b0b9e0a380a37,  0x7680b8206910c1f,  0xd000cfa0cf60690,  0xc0f11c90c380ce9,  0xbed11ba0c2c0ce0,  0xc2f0ce3076c0b86,  0x76f0b890bf011bd,  0x5d70999077b0bb4,  0x5e805ff0a2d0a2a,  0x6ae0b1306940a50,  0xba20722071f0b3a,  0xbc60bc20bbf0bbc,  0x8200c0b0bf90bf3,  0xd25082b08230cd5,  0x5d1092a09360869,  0x36c035d034a0337,  0x3a80399038a037b,  0x3490336032303b7,  0x389037a036b035c,  0x3fe03b603a70398,  0x42a041f04140409,  0x44a0443043c0435,  0xaf4047804710451,  0x0,  0x0,  0x0,  0x0,  0x26b4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe730e6b,  0x0,  0x256a258422132556,  0x26ae1ff91eff22c6,  0x202b25c8209226ae,  0x244a238221872090,  0x25a8251e250424e6,  0x233c22f0229a2254,  0x1f11265225bc24ca,  0x24e42302225e1fe3,  0x24dc22d620e6267a,  0x250a247821a12526,  0x232822a6221d211f,  0x1fb31f7d1f3125ae,  0x23922300225a21d5,  0x257e24ec24e02456,  0x23b02678266a2610,  0x25d624bc242c23d0,  0x212d206d2540267e,  0x23b4231a24682408,  0x20d4206b260c2566,  0x242422ca22b02256,  0x246e1fb125ec2438,  0x242e23e61f811f83,  0x21a3254e25f024c8,  0x20be1f0525462254,  0x1fc3237223322159,  0x1ef5214b1f3923b8,  0x1fed242221e12290,  0x253824cc23982063,  0x21ab228c25962276,  0x1f1f237021bb24a8,  0x241822441f7f1f5d,  0x1fb725c6253e2494,  0x21ef212720982011,  0x265625e423ba22d8,  0x220f1fa5268c2680,  0x217b210d2590226c,  0x22f622d021d12189,  0x2464243223e0234e,  0x25d8259c24d02588,  0x22ee201b1fa71f91,  0x2155211d25382514,  0x232c2406227221b7,  0x20f020be20491f27,  0x24502348233a215b,  0x2612260a25ca2460,  0x25c023da1f332630,  0x1f451f15216725fe,  0x225421e720d020c0,  0x25a624d6238022fc,  0x1fa325ea220726aa,  0x22be22a22233222d,  0x242023ae236e2340,  0x25f221911f612636,  0x258a22b220e21f3d,  0x23322237216b2143,  0x20d820091f9525f6,  0x2294224a222521fc,  0x251624462378233e,  0x1fcb260425c4251c,  0x235022fe200b22c0,  0x2682266e25f824de,  0x23f6247c22ae2231,  0x22ea2326240e23fc,  0x1f9724b01f23254c,  0x241421a521151f8f,  0x258e220d229c20b6,  0x2123252c25ee250e,  0x20371f4d,  0x220500002061,  0x238c232a1f850000,  0x23d823ce23cc23be,  0x245224102616,  0x2544000024e2,  0x25b4259e0000,  0x2642264000000000,  0x25fc25b026762644,  0x1faf1f511f471f35,  0x203d202f1fd51fb5,  0x20d62065205f2041,  0x21792175216120da,  0x220921f321db2185,  0x22ce22b622a82246,  0x23b22342230822f8,  0x23c623c223c42240,  0x23d623d423ca23c8,  0x2432240023f223e8,  0x24582444243a2436,  0x24ce249a249a2480,  0x254a2548252e2522,  0x259e259a256e256c,  0x215d263426282606,  0x248c274b,  0x1f2f1f5b1f7b1ef9,  0x1fbb1fad1f651f4f,  0x203b202d2025202f,  0x2094208e20692061,  0x2125212120aa20a2,  0x21712165214d213d,  0x2185217321792169,  0x21c921c521bf2193,  0x221f221d220521dd,  0x22a22276226e2229,  0x22dc22ce22c422c8,  0x2324230a23a422f8,  0x236a2358234a232a,  0x238e238c237e237c,  0x23b6239e23a02396,  0x2428240c240023f4,  0x24b2245824402432,  0x252a2524250024c6,  0x253c2544253a252e,  0x254a254225462548,  0x25a4258c256e2550,  0x260625f425ce25be,  0x262e262826202616,  0x272126ae265e2634,  0x1ea11e8d2733271f,  0x27a9277927671ea3,  0x26ac26a4,  0x0,  0xade0ae30adf0adb,  0xd280d280ae2,  0x0,  0x0,  0x134e000000000000,  0x134b135113481345,  0x0,  0x13d2000013850000,  0x1374136f135413a6,  0x13b7139b1360138e,  0x13ca13c702f413cd,  0x1359135613c313bf,  0x1371136c1364135c,  0x137f137c1376,  0x1390138b13881382,  0x139d00001398,  0x13a8000013a313a0,  0x13b413b1000013ab,  0x137913cf13bc13b9,  0x135f13ae13931367,  0x181e181e18181818,  0x18201820181e181e,  0x1824182418201820,  0x181c181c18241824,  0x18221822181c181c,  0x181a181a18221822,  0x183c183c181a181a,  0x183e183e183c183c,  0x18281828183e183e,  0x1826182618281828,  0x182a182a18261826,  0x182c182c182a182a,  0x18321832182c182c,  0x1834183418301830,  0x18381838182e182e,  0x1840184018361836,  0x1844184418401840,  0x1848184818441844,  0x1846184618481848,  0x184a184a18461846,  0x184c184c184c184c,  0x18501850186d186d,  0x184e184e18501850,  0x15911591184e184e,  0x186a186a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1842000000000000,  0x1803184218421842,  0x180717ff17ff1803,  0x18621862185b1807,  0x1860186018551855,  0x180b180b180b180b,  0x17cd17cd14151415,  0x17f117f1180d180d,  0x17fd17fd18011801,  0x1809180918051805,  0x17f517f517f51809,  0x1864186418641864,  0x17f517e517d517d1,  0x13fe13f713f417f9,  0x141e14171414140b,  0x146a144d1438142d,  0x1484147b1472146d,  0x14311422148c1487,  0x143c14d414d11435,  0x151a150c150514fa,  0x15a515a215931562,  0x15c815c515ba15b0,  0x1607157515e415df,  0x16451642163f160a,  0x165b16561653164c,  0x1679167416711662,  0x16851682167f167c,  0x16aa169616931688,  0x1579158c16c816b9,  0x14591455145116e0,  0x172d1461145d1526,  0x17691758174f1740,  0x177f17741771176c,  0x17aa17a3179c1782,  0x14e417c717c417b3,  0x64005d179714ee,  0x8000790072006b,  0x17e917e517e117dd,  0x140813db17f917f5,  0x14171414140e140b,  0x1464144d144a1447,  0x14781475146d146a,  0x14871484147e147b,  0x1674167116561653,  0x1693168816851679,  0x16e01579158c1696,  0x17551752152616e5,  0x176c176917631758,  0x17b317b017ad1797,  0x17d117c717c417be,  0x17ed17e517d917d5,  0x140b13fe13f713f4,  0x1438142d141e1411,  0x148c147b1467144d,  0x14d1143514311422,  0x150c150514fa143c,  0x1593156d1562151a,  0x15ba15b015a515a2,  0x157515e415df15c5,  0x1642163f160a1607,  0x1662165b164c1645,  0x16851682167f167c,  0x16c816b916aa1688,  0x1455145113e0158c,  0x1740172d15261459,  0x177117661758174f,  0x17a3179c17851774,  0x17e515ed17b317aa,  0x144d1411140b17ed,  0x151a1481147b1467,  0x16851557154c1529,  0x17661758158c1688,  0x162c162515ed17b3,  0x15ff15da15d71633,  0x152c161c16191602,  0x1490155d155a152f,  0x1440142a142613fb,  0x15bd159d159a1402,  0x1546153b153415c0,  0x157015171549154c,  0x15ff15da15d715b7,  0x152c161c16191602,  0x1490155d155a152f,  0x1440142a142613fb,  0x15bd159d159a1402,  0x1546153b153415c0,  0x157015171549154c,  0x1546153b153415b7,  0x15c815571529154c,  0x1534150c150514fa,  0x15df15c81546153b,  0x13e313e3,  0x0,  0x0,  0x0,  0x0,  0x1434143014301421,  0x145814541450143b,  0x14c114c514a314a3,  0x1521150114fd1508,  0x15251525151d1521,  0x153e159615651565,  0x154f154f1537153e,  0x15b315a815531553,  0x15cf15cb15cb15b3,  0x15f315f315e715d3,  0x16111615160d15f7,  0x1669166516481648,  0x16ad16c016c416bc,  0x16d216cb16cb16ad,  0x170b170216fe16d2,  0x1716171216f316eb,  0x177716ef00000000,  0x173417471743177b,  0x175b175f17381734,  0x1429140117b617b6,  0x1460143f14431425,  0x14a7148f14ab145c,  0x15ac15421569150f,  0x179f17a616d616b5,  0x174b166d172117ba,  0x168f15fb16bc1665,  0x168b16b1171a1730,  0x14ba1493173016b1,  0x168b13fa164f16f7,  0x173c1513159615e7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13d913de165e158f,  0x15eb14e915731706,  0x1497157c1578158a,  0x14f1,  0x0,  0x0,  0x0,  0x0,  0x5401b331b3102f6,  0x1b770093008d0546,  0x2ff1b79,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9931a6b1a6d02fc,  0xe3b00a500a10993,  0x1b451b4f1b4b0e3f,  0x1b351b3b1b391b47,  0x1b411b3f1b3d1b37,  0x98b000000001b43,  0xc000c000c098f,  0x99309930993000c,  0x2fa1b3102f6,  0x8d009305400546,  0xe3b00a500a11a6d,  0x971b4f1b4b0e3f,  0x2f802f402f2009d,  0x54405590548,  0x566009b0099098d,  0x0,  0x5a161f0057,  0x1622006800000061,  0x163000761629006f,  0x163a00841637007d,  0x13e913e613e613d5,  0x13ec178f178f13e9,  0x17ca17ca17ca13ec,  0x13f213d713d717ca,  0x141a13f213f213f2,  0x141c141c141c141a,  0x147014701470141c,  0x13f513f513f51470,  0x13f813f813f813f5,  0x13ff13ff13ff13f8,  0x14e214e014e013ff,  0x140913dc13dc14e2,  0x14f814f814f81409,  0x15321532153214f8,  0x1560156015601532,  0x15a015a015a01560,  0x15c315c315c315a0,  0x15dd15dd15dd15c3,  0x15e215e215e215dd,  0x16051605160515e2,  0x163d163d163d1605,  0x165916591659163d,  0x1677167716771659,  0x14ec14ec14ec1677,  0x140c140c140c14ec,  0x140f140f140f140c,  0x13e113e113e1140f,  0x14151788178813e1,  0x13fc13fc13fc1415,  0x16a2169e169e13fc,  0x169b16a616a616a2,  0x169b,  0x970095008d0000,  0x9f009d009b0099,  0x2f402f200a500a1,  0x30302fa02f802f6,  0x30f034303140305,  0x392038303740365,  0x546054003b003a1,  0x93055905440548,  0x5e305d505680566,  0x687067e062905e6,  0x71a060706cf06ac,  0x7a4077e07230734,  0x85e082c083b06af,  0x77006b2056b088d,  0x98b060a095a0682,  0x9930991098f098d,  0x9a0093706920995,  0x6020ad90a7d0a2e,  0xb79073e0ae00b0d,  0x7870b3b05d30a28,  0x8400a1105d80cd3,  0xde1086a0a240ba3,  0xe3b061106950b41,  0x1b280e410e3f0e3d,  0x1b3f1b3d1b331b2a,  0x1bd61e551e5c1b31,  0x1c181c081bfd1bef,  0x1cee1e171e0f1e02,  0x1bff1bf11bd81c16,  0x1c411c241c1a1c0a,  0x1cad1c991c901c73,  0x1cda1ccd1c1e1cbd,  0x1cf51cf01bfb1cdf,  0x1d111d0f1ca61bde,  0x1d391d1b1d0d1c8e,  0x1c311d9f1d741d55,  0x1e001def1c221ddc,  0x1e1b1e191e111e04,  0x1c5d1e341bed1c35,  0x8b00881c061e42,  0x1a101949194419d4,  0x19501a141a12194b,  0x1a181a1619571955,  0x1a201a1e1a1c1a1a,  0x19661961195c19a6,  0x196f196d196819b0,  0x198e198319811977,  0x199d19981993,  0x19d8194700000000,  0x19e019de19dc19da,  0x19e419e200000000,  0x19ec19ea19e8198c,  0x197519ee00000000,  0x19f819f619f419f2,  0x197f19fa00000000,  0x19fe,  0x90e4b0e450e43,  0x1a820e470e49,  0x1a8b1a891a841b22,  0x1b261b241a90,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26b600000000,  0x26b9,  0x0,  0x0,  0x26bc000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26c226bf00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26c826c500000000,  0x26d726d326cf26cb,  0x26db,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26df000000000000,  0x26e626ed26e226ea,  0x26f1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae000000602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e300000568,  0x68700000000,  0x71a06070000,  0x6af07a4077e0000,  0x88d085e0000083b,  0x682077006b2056b,  0x9370692060a095a,  0xad900000a2e09a0,  0x73e0ae00b0d0000,  0xb3b05d30a280b79,  0xa1105d80cd30000,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e6000005d50568,  0x687067e0629,  0x734071a06070000,  0x6af07a4077e0723,  0x88d085e0000083b,  0x682077006b2056b,  0x93706920000095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e6000005d50568,  0x687067e0629,  0x734071a060706cf,  0x7a400000723,  0x88d085e00000000,  0x682077006b2056b,  0x93706920000095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x9370692060a095a,  0xad90a7d0a2e09a0,  0x73e0ae00b0d0602,  0xb3b05d30a280b79,  0xa1105d80cd30787,  0x86a0a240ba30840,  0x61106950b410de1,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x61106950b410de1,  0xe800e6f,  0xf3c0f3a0f380ee3,  0xfad0f5e0f5c0f3e,  0xfe20fe00fde0faf,  0x10060fe80fe60fe4,  0x100f100d0fad1008,  0x1035103310311011,  0x10ea10861aa3077c,  0x110e10f010ee10ec,  0x11ae1170116e1110,  0x11ce11cc11b211b0,  0x11f811f011ee11d0,  0x123c11fe11fc11fa,  0x1a9e12421240123e,  0x123c11ae116e10f0,  0xf380ee311ee11f0,  0xf5c0f3e0f3c0f3a,  0xfde0faf0fad0f5e,  0xfe60fe40fe20fe0,  0xfad100810060fe8,  0x10311011100f100d,  0x1aa3077c10351033,  0x10ee10ec10ea1086,  0x116e1110110e10f0,  0x11b211b011ae1170,  0x11ee11d011ce11cc,  0x11fc11fa11f811f0,  0x1240123e123c11fe,  0x116e10f01a9e1242,  0x11ee11f0123c11ae,  0xf3c0f3a0f380ee3,  0xfad0f5e0f5c0f3e,  0xfe20fe00fde0faf,  0x10060fe80fe60fe4,  0x100f100d0fad1008,  0x1035103310311011,  0x10ea10861aa3077c,  0x110e10f010ee10ec,  0x11ae1170116e1110,  0x11ce11cc11b211b0,  0x11f811f011ee11d0,  0x123c11fe11fc11fa,  0x1a9e12421240123e,  0x123c11ae116e10f0,  0xf380ee311ee11f0,  0xf5c0f3e0f3c0f3a,  0xfde0faf0fad0f5e,  0xfe60fe40fe20fe0,  0xfad100810060fe8,  0x10311011100f100d,  0x1aa3077c10351033,  0x10ee10ec10ea1086,  0x116e1110110e10f0,  0x11b211b011ae1170,  0x11ee11d011ce11cc,  0x11fc11fa11f811f0,  0x1240123e123c11fe,  0x116e10f01a9e1242,  0x11ee11f0123c11ae,  0xf3c0f3a0f380ee3,  0xfad0f5e0f5c0f3e,  0xfe20fe00fde0faf,  0x10060fe80fe60fe4,  0x100f100d0fad1008,  0x1035103310311011,  0x10ea10861aa3077c,  0x110e10f010ee10ec,  0x11ae1170116e1110,  0x11ce11cc11b211b0,  0x11f811f011ee11d0,  0x123c11fe11fc11fa,  0x1a9e12421240123e,  0x123c11ae116e10f0,  0x12a212a011ee11f0,  0x314030500000000,  0x3740365030f0343,  0x3b003a103920383,  0x30f034303140305,  0x392038303740365,  0x314030503b003a1,  0x3740365030f0343,  0x3b003a103920383,  0x30f034303140305,  0x392038303740365,  0x314030503b003a1,  0x3740365030f0343,  0x3b003a103920383,  0x14e013f513f213d7,  0x13f8140917880000,  0x14ec167713fc15c3,  0x15e214f8140f140c,  0x13dc16591560163d,  0x13ff1470141c1532,  0x160515dd15a014e2,  0x1816183a184a1814,  0x13f513f20000,  0x13f80000000013e1,  0x14ec167713fc0000,  0x15e214f8140f140c,  0x16591560163d,  0x13ff1470141c1532,  0x1605000015a00000,  0x0,  0x13f500000000,  0x13f8000000000000,  0x14ec000013fc0000,  0x15e214f8140f0000,  0x165915600000,  0x13ff000000001532,  0x1605000015a00000,  0x18160000184a0000,  0x13f513f20000,  0x13f80000000013e1,  0x167713fc15c3,  0x15e214f8140f140c,  0x16591560163d,  0x13ff1470141c1532,  0x160515dd15a00000,  0x183a00001814,  0x14e013f513f213d7,  0x13f81409178813e1,  0x14ec000013fc15c3,  0x15e214f8140f140c,  0x13dc16591560163d,  0x13ff1470141c1532,  0x160515dd15a014e2,  0x0,  0x14e013f513f20000,  0x13f8140917880000,  0x14ec000013fc15c3,  0x15e214f8140f140c,  0x13dc16591560163d,  0x13ff1470141c1532,  0x160515dd15a014e2,  0x0,  0x3f103160307030a,  0x4fa04de04ab0468,  0x5310520050b,  0x0,  0x10a0106010200fe,  0x11a01160112010e,  0x12a01260122011e,  0x13a01360132012e,  0x14a01460142013e,  0x15a01560152014e,  0x5e31b4d0162015e,  0x93305e5082c,  0x5e605e305d50568,  0x6ac0687067e0629,  0x734071a060706cf,  0x6af07a4077e0723,  0x88d085e082c083b,  0x682077006b2056b,  0x76c06b1060a095a,  0x930082708660860,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x761075e00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x606,  0x0,  0x0,  0x0,  0x1cad1c9e1bc3,  0x0,  0x0,  0x0,  0x1cf71ff320b02197,  0x208c253220811f17,  0x21e722f221fe1f1d,  0x21eb1f6921451f9d,  0x2560235c24261f93,  0x219d22cc200f2073,  0x25a01eef1ee921b3,  0x21ad20011efd20fa,  0x23f023d221992574,  0x329221b22bc2005,  0x20351f9f2366,  0x0,  0x1b5d1b551b511b69,  0x1b591b711b611b6d,  0x1b65,  0x0,  0x1ffd2147,  0x0,  0x0,  0x0,  0x26f51f0b1f031f07,  0x1f3b1f371f351f2d,  0x1f431f471f411f3f,  0x1f531f5126fd1e63,  0x1e6526f71f631f55,  0x1f7126fb1f691f59,  0x1f7b1f791f251f75,  0x1e691f8d1f8927b9,  0x1fa11f9f1f9b1f99,  0x1fb51faf1fad1e6b,  0x1fc31fbf1fbd1fbb,  0x1fe11fd91fd51fd3,  0x1fe71fe71fe71fe5,  0x1ff51ff122e42703,  0x20031fff1ffb2705,  0x20152013200d2017,  0x2023201f201d2019,  0x202d202920292027,  0x204b203920332031,  0x2043203f204d203d,  0x2057205520711f8f,  0x205b205d20532059,  0x2077207527072067,  0x209620852081207b,  0x209e209c270b2709,  0x1e6d20a4209a20a0,  0x20ac20ac20a81e6f,  0x20be20bc20ba270d,  0x20c820c6270f20c2,  0x20d21e7120cc2137,  0x271320de20e020da,  0x20e820ea271520e4,  0x1e7320f620f420ec,  0x21062104210220fe,  0x21171e7727171e75,  0x27cd211f211b2119,  0x2486271b271b212b,  0x27291e7921332133,  0x1e7b213f213b277d,  0x2157215321512149,  0x21611e7d1e7f215f,  0x216f216d2163271d,  0x21792177216f2171,  0x2183217f217d2181,  0x218f210b21872185,  0x21b121a7219f219b,  0x21b521a921af2723,  0x21c7272521c321b9,  0x21cb1e8121bd21c1,  0x1e8321cd21d321cf,  0x21f5272721df21db,  0x22091e8922032215,  0x1f6d1f6b1e851e87,  0x1ebb2470220b2217,  0x222b2221221f221d,  0x22351e8b27312227,  0x273522462242222f,  0x1e8d224c22392248,  0x225822522250224e,  0x22621e8f225c2737,  0x226a1e9122642739,  0x273b227822762270,  0x273f2288273d2711,  0x2298228a2292228e,  0x22a422a222a822a0,  0x229e274122ac22aa,  0x22c41e9322ba22b8,  0x22d222b4274322c2,  0x22de22d427472745,  0x22e01e9522da22dc,  0x26f922ec22e622e8,  0x274d22fa274922f4,  0x274f2314230a2304,  0x275327512320231e,  0x23381e972336232e,  0x234623441e991e99,  0x1e9b2352234c234a,  0x2757236c2755235e,  0x2759237a27192372,  0x1e9f1e9d275d275b,  0x2763275f27612396,  0x239c239c239a2765,  0x1ea523a21ea323a0,  0x23b023ac27691ea7,  0x23c8276b1ea923b6,  0x23e423d8276f276d,  0x23ec23ea23e81eab,  0x23f8277327732771,  0x2404240227751ead,  0x1eb1241227771eaf,  0x277b241e2416241a,  0x243424301eb3242a,  0x2781277f1eb5243c,  0x2785244827831eb7,  0x278724582454244e,  0x2466278b24622789,  0x247424721eb9272b,  0x278d20a624761ebd,  0x2486272f272d278f,  0x249e1ebf25942488,  0x24a21fa924a0249c,  0x279124aa24a624a4,  0x24b824b624ac24a8,  0x24ce24c424ba24ae,  0x24c224c024be24b4,  0x1ec1279527972793,  0x279f24d824d424d2,  0x1ec51ec3279924da,  0x24ea1ec7279d279b,  0x24f624f024ee24ec,  0x250024f824fa24f4,  0x1ec9250224fe24fc,  0x25101ecb25082506,  0x251a251827a12512,  0x27a31e6725201ecd,  0x25361ed11ecf27a5,  0x27a7255825502542,  0x2576257025642562,  0x257a257c26ff27ab,  0x258c258627012580,  0x25b225ac27af27ad,  0x25cc25b827b125b6,  0x25da25d025d425d2,  0x1ed325e227b325dc,  0x26021ed527b525e6,  0x27bb27b7260e20ee,  0x27bd26221ed91ed7,  0x262e262e27bf1edb,  0x1edd263e27c12632,  0x26542650264c2646,  0x266c265e27c31edf,  0x26741ee31ee12672,  0x27c927c71ee527c5,  0x26901ee7268627cb,  0x269e269a26962694,  0x27cf26a2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//12288 bytes
+enum canonMappingTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x120], [ 0x100,  0x400,  0x1380], [ 0x302020202020100,  0x205020202020204,  0x602020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x1000000000000,  0x5000400030002,  0x6,  0x9000800070000,  0xc0000000b000a,  0x0,  0xe00000000000d,  0x0,  0x0,  0x1100000010000f,  0x130012,  0x16001500140000,  0x18000000170000,  0x1a000000190000,  0x0,  0x1c001b0000,  0x1d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1f001e,  0x0,  0x0,  0x23002200210020,  0x27002600250024,  0x28,  0x2b002a00000029,  0x2f002e002d002c,  0x30,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x31000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x34003300320000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x38003700360035,  0x3c003b003a0039,  0x3e003d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f00000000,  0x40,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x43004200410000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x47004600450044,  0x4b004a00490048,  0x4c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x250012000f000c,  0x850000004f0045,  0xcb00a400a1009e,  0x13301240121011e,  0x1a0019d01880000,  0x1da01b601a3,  0x2730270026d0000,  0x2f30287,  0x33803250322031f,  0x398000003620358,  0x3de03b703b403b1,  0x446043a04370434,  0x4b404b1049c0000,  0x4ee04ca04b7,  0x58a058705840000,  0x61c0000060d059e,  0x33e002b033b0028,  0x38c00790380006d,  0x392007f038f007c,  0x3a2008f03950082,  0x3cd00ba00000000,  0x3db00c803d800c5,  0x3e400d103fb00e8,  0x41000fd040a00f7,  0x419010604130100,  0x41c0109,  0x440012a043d0127,  0x45c01490443012d,  0x130,  0x471015d0462014f,  0x170047701630000,  0x47a01660484,  0x185000000000000,  0x18e04a801940499,  0x4a2,  0x4e401d004d901c5,  0x4f801e4,  0x5450231052f021b,  0x54b023705350221,  0x56902550552023e,  0x57b026405580244,  0x572025b,  0x594027d058d0276,  0x5b4029d059b0284,  0x5e002c905b702a0,  0x61002f605f502de,  0x3110628030b0302,  0x6310314062e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50401f0,  0x0,  0x0,  0x2ac000000000000,  0x5c3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13d036900560000,  0x2a304fb01e70450,  0x28e05a9029205ba,  0x28a05ad029605a5,  0x35b0048000005a1,  0x653064a03540041,  0x416010300000000,  0x522020e046b0157,  0x65f065c05250211,  0x465,  0x40700f4,  0x365005204960182,  0x656064d06500647,  0x36f005c036c0059,  0x3ea00d703e700d4,  0x456014304530140,  0x50101ed04fe01ea,  0x53b022705380224,  0x5c002a905bd02a6,  0x578026105660252,  0x425011200000000,  0x0,  0x351003e00000000,  0x4f101dd03f400e1,  0x4e701d304d101bd,  0x61602fc04ea01d6,  0x0,  0x0,  0x0,  0x66b00000010000d,  0x137,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x662,  0x0,  0x100000000,  0x0,  0x6450670063d0000,  0x72c06df06c3,  0x798077800000759,  0x8d1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x781073500000000,  0x8c10867084707e9,  0x92f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x92808ca00000000,  0x95f091f08fd,  0x9b4000000000000,  0x9b7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9c3000009cc09c6,  0x9ba000000000000,  0x0,  0x9ed09d809e4,  0x0,  0x0,  0x9de0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa0500000a0e0a08,  0xa41000000000000,  0x0,  0xa2f0a1a0a26,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa470a4400000000,  0x0,  0x0,  0xa1109cf0000,  0x0,  0x0,  0x0,  0xa0209c009ff09bd,  0xa0b09c900000000,  0xa4d0a4a00000000,  0xa1709d50a1409d2,  0xa1d09db00000000,  0xa2909e70a2309e1,  0xa530a5000000000,  0xa2c09ea0a3e09fc,  0xa3509f30a3209f0,  0xa3809f6,  0xa3b09f9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xac10abe00000000,  0xaca0ac40ac7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xacd00000ad3,  0x0,  0x0,  0x0,  0xad0000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xae80000,  0x0,  0xaf10000,  0xaf4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xadf0adc0ad90ad6,  0xaee0aeb0ae50ae2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb00000000000000,  0xb03,  0x0,  0x0,  0x0,  0xafd00000afa0af7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb12000000000000,  0xb1500000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb0c0b090b060000,  0xb0f00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb1e000000000b21,  0xb24,  0x0,  0x0,  0x0,  0xb1b0b18,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb27,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb300b2a00000000,  0xb2d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb33,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb36,  0xb40000000000000,  0xb3c0b3900000b43,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb4c0b4600000000,  0xb49,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb4f00000000,  0xb590b550b52,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb5f000000000000,  0x0,  0x0,  0xb620000,  0xb6500000000,  0xb68000000000000,  0x0,  0xb6b,  0x0,  0x0,  0xb5c0000,  0x0,  0xb6e000000000000,  0xb890b710000,  0xb8c,  0x0,  0xb740000,  0x0,  0x0,  0x0,  0xb7a000000000000,  0x0,  0x0,  0xb7d0000,  0xb8000000000,  0xb83000000000000,  0x0,  0xb86,  0x0,  0x0,  0xb770000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb8f00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb9200000000,  0xb9800000b95,  0xb9e00000b9b,  0xba100000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xba4000000000000,  0xba70000,  0xbb000000bad0baa,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3830070037d006a,  0x389007603860073,  0x39f008c039b0088,  0x3ae009b03a50092,  0x3ab009803a80095,  0x3d400c103d000bd,  0x40100ee03fe00eb,  0x40400f103f700e4,  0x41f010c040d00fa,  0x422010f04280115,  0x42e011b042b0118,  0x4490136045f014c,  0x46e015a04680154,  0x47d016904740160,  0x48a01760480016c,  0x48d017904870173,  0x493017f0490017c,  0x4a50191049f018b,  0x4ab019704ae019a,  0x4d501c104cd01b9,  0x4e001cc04dc01c8,  0x52c021805290215,  0x53e022a0532021e,  0x54802340541022d,  0x55f024b05550241,  0x55b0247054e023a,  0x56c02580562024e,  0x581026a0575025e,  0x5dd02c6057e0267,  0x5e302cc05e602cf,  0x597028005900279,  0x5ec02d505e902d2,  0x5f202db05ef02d8,  0x5f802e105fb02e4,  0x60402ea060102e7,  0x61902ff060702ed,  0x6340317062b030e,  0x56f04310637031a,  0x6590000062205fe,  0x0,  0x35f004c0372005f,  0x3280015032c0019,  0x330001d03340021,  0x345003203750062,  0x34d003a0341002e,  0x379006603490036,  0x3e100ce03ed00da,  0x3be00ab03ca00b7,  0x3c600b303ba00a7,  0x3f000dd03c200af,  0x4590146044d013a,  0x4f501e1051b0207,  0x4ba01a604be01aa,  0x4c201ae04c601b2,  0x50b01f7051e020a,  0x51301ff050701f3,  0x5170203050f01fb,  0x5b1029a05da02c3,  0x5c602af05ca02b3,  0x5ce02b705d202bb,  0x60a02f005d602bf,  0x61f030506250308,  0x61302f9,  0x0,  0x81b07f9081807f6,  0x82d080b08240802,  0x69e067c069b0679,  0x6b0068e06a70685,  0x858084d0855084a,  0x85c0851,  0x6d406c906d106c6,  0x6d806cd,  0x89308710890086e,  0x8a50883089c087a,  0x70706e5070406e2,  0x71906f7071006ee,  0x8eb08dc08e808d9,  0x8f308e408ef08e0,  0x74a073b07470738,  0x7520743074e073f,  0x90e0903090b0900,  0x9120907,  0x76a075f0767075c,  0x76e0763,  0x949093a09460937,  0x9510942094d093e,  0x787000007840000,  0x78f0000078b0000,  0x98b096909880966,  0x99d097b09940972,  0x7c0079e07bd079b,  0x7d207b007c907a7,  0x847084407e907e2,  0x8c108be08670860,  0x91f091c08fd08fa,  0x95f0958,  0x81f07fd08360814,  0x831080f08280806,  0x6a2068006b90697,  0x6b4069206ab0689,  0x897087508ae088c,  0x8a9088708a0087e,  0x70b06e907220700,  0x71d06fb071406f2,  0x98f096d09a60984,  0x9a1097f09980976,  0x7c407a207db07b9,  0x7d607b407cd07ab,  0x84107e507f007f3,  0x83d083a000007ec,  0x670066d06730676,  0x8bc000006bd,  0x8b9086306400000,  0x8b508b20000086a,  0x6df06dc06c306c0,  0xbb90bb60bb30726,  0x8d108cd08c408c7,  0x8d508f700000000,  0x72c0729072f0732,  0xbc20bbf0bbc0000,  0x92f092b09220925,  0x933095509190916,  0x7780775077b077e,  0x31d063d063a0772,  0x9b1095b00000000,  0x9ad09aa00000962,  0x798079507590756,  0x64307df,  0xbc70bc5,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x79300000000,  0x4f015200000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbcc0bc900000000,  0x0,  0x0,  0x0,  0x0,  0xbcf00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbd50bd80bd20000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbdb,  0xbde0000,  0xbe1,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbe700000be4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbea0000,  0xbf0000000000bed,  0xbf30000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbf900000006,  0x0,  0x0,  0x900030bf60000,  0xbff0bfc,  0xc050c02,  0xc0b0c08,  0x0,  0xc110c0e,  0xc1d0c1a,  0xc230c20,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc350c320c2f0c2c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc290c260c170c14,  0x0,  0xc3b0c3800000000,  0xc410c3e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc490c470000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc44,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc5100000c4e,  0xc5700000c54,  0xc5d00000c5a,  0xc6300000c60,  0xc6900000c66,  0xc6f00000c6c,  0xc7500000c720000,  0xc780000,  0x0,  0xc8100000c7e0c7b,  0xc8a0c8700000c84,  0xc900c8d0000,  0xc960c93,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc4b,  0x0,  0xc9900000000,  0x0,  0x0,  0x0,  0xca200000c9f,  0xca800000ca5,  0xcae00000cab,  0xcb400000cb1,  0xcba00000cb7,  0xcc000000cbd,  0xcc600000cc30000,  0xcc90000,  0x0,  0xcd200000ccf0ccc,  0xcdb0cd800000cd5,  0xce10cde0000,  0xce70ce4,  0x0,  0x0,  0x0,  0x0,  0x0,  0xcea000000000c9c,  0xcf30cf00ced,  0xcf600000000,  0x124b125d0fb71241,  0x13270e290d831043,  0xe4f12930e991327,  0x116710cd0f550e97,  0x1279121511fd11e3,  0x109d106910190feb,  0xd8d12f3128911c7,  0x11e110790ff50e1d,  0x11d910510edb1309,  0x120311890f65121d,  0x108d10250fbd0eff,  0xe050dd90d9d127d,  0x10d310770ff10f95,  0x125911e711dd1171,  0x10e9130712fb12cf,  0x12a111b9114d1107,  0xf0b0e87122f130b,  0x10ed1083117d112f,  0xecb0e8512cb1249,  0x11471047102f0fed,  0x117f0e0312b11159,  0x114f11150ddd0ddf,  0xf67123d12b511c5,  0xebb0d8712350feb,  0xe1110c110950f27,  0xd7f0f1b0da510f1,  0xe2311450f9d1011,  0x122711c910d70e7d,  0xf6f100d126d1005,  0xd9110bf0f7b11a5,  0x113d0fdb0ddb0dc3,  0xe091291122d1195,  0xfa10f070e9f0e37,  0x12f712ab10f31053,  0xfb50df91313130d,  0xf490ef312690ffd,  0x106d104b0f910f57,  0x11791153111110af,  0x12a3127111cd1261,  0x10670e410dfb0de9,  0xf230efd1227120b,  0x1091112d10030f77,  0xee50ebb0e670d97,  0x116b10a9109b0f29,  0x12d112c912951175,  0x128d110f0d9f12dd,  0xdb10d8f0f3512c1,  0xfeb0f9f0ec70ebd,  0x127711d310cb1073,  0xdf712af0fad1323,  0x103b10210fd10fcb,  0x114310e710bd10a1,  0x12b70f5d0dc512e3,  0x126310310ed70da9,  0x10950fd50f390f17,  0xecf0e310deb12bb,  0x10150fe10fc30fa7,  0x120d116310c3109f,  0xe1312c5128f1213,  0x10b110750e33103d,  0x130f12ff12bd11db,  0x1121118b102d0fcf,  0x1063108b11331125,  0xded11ad0d93123b,  0x11390f690ef50de7,  0x12670fb3101b0eb5,  0xf03122112b31205,  0xe590db5,  0xfab00000e7b,  0x10cf108f0de10000,  0x110d1105110310f5,  0x116d113512d3,  0x1233000011df,  0x128312730000,  0x12e912e700000000,  0x12bf127f130512eb,  0xe010db90db30da1,  0xe5f0e530e170e07,  0xecd0e7f0e790e63,  0xf470f430f2f0ed1,  0xfaf0fa30f970f53,  0x1049103510270fdd,  0x10eb10a3107d106f,  0x10fd10f910fb10f7,  0x110b1109110110ff,  0x11531127111d1117,  0x11731161115b1157,  0x11cb11971197118d,  0x1239123712231219,  0x1273126f124f124d,  0xf2b12e112d912c7,  0x119313be,  0xd9b0dc10dd70d81,  0xe0b0dff0dc90db7,  0xe5d0e510e490e53,  0xe9b0e950e830e7b,  0xf050f010eb10ea9,  0xf3f0f330f1d0f13,  0xf530f410f470f37,  0xf890f850f7f0f5f,  0xfbf0fbd0fab0f99,  0x102110050fff0fc7,  0x1057104910411045,  0x1089107f10e3106f,  0x10b910b510ab108f,  0x10d110cf10c910c7,  0x10ef10dd10df10d5,  0x114911311127111f,  0x11af1173115f1153,  0x121f121b11f911c3,  0x122b123312291223,  0x1239123112351237,  0x12751265124f123f,  0x12c712b91299128b,  0x12db12d912d512d3,  0x1394132712f912e1,  0xd370d2313a61392,  0x141c13ec13da0d39,  0x13251321,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xabb00000a7a0000,  0x0,  0x0,  0xab50ab200000000,  0xa590a560aae0aaa,  0xa680a650a5f0a5c,  0xa740a710a6b,  0xa830a800a7d0a77,  0xa8c00000a89,  0xa9500000a920a8f,  0xaa10a9e00000a98,  0xa6e0ab80aa70aa4,  0xa9b0a860a62,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x132900000000,  0x132c,  0x0,  0x0,  0x132f000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1335133200000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x133b133800000000,  0x134a13461342133e,  0x134e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1352000000000000,  0x135913601355135d,  0x1364,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13680d8b0d850d89,  0xda70da30da10d99,  0xdaf0db30dad0dab,  0xdbb0db913700cf9,  0xcfb136a0dc70dbd,  0xdd1136e0dcb0dbf,  0xdd70dd50d950dd3,  0xcff0de50de3142c,  0xdf50df30df10def,  0xe070e010dff0d01,  0xe110e0f0e0d0e0b,  0xe1b0e190e170e15,  0xe210e210e210e1f,  0xe270e25105d1376,  0xe2f0e2d0e2b1378,  0xe3b0e390e350e3d,  0xe470e450e430e3f,  0xe510e4d0e4d0e4b,  0xe690e5b0e570e55,  0xe650e610e6b0e5f,  0xe710e6f0e890de7,  0xe750e770e6d0e73,  0xe8d0e8b137a0e81,  0xe9d0e930e910e8f,  0xea50ea3137e137c,  0xd030eab0ea10ea7,  0xeb30eb30eaf0d05,  0xebb0eb90eb71380,  0xec30ec113820ebf,  0xec90d070ec50f0f,  0x13860ed30ed50ed1,  0xedd0edf13880ed9,  0xd090ee90ee70ee1,  0xef10eef0eed0eeb,  0xef70d0d138a0d0b,  0x14400eff0efb0ef9,  0x118f138e138e0f09,  0x139c0d0f0f0d0f0d,  0xd110f150f1113f0,  0xf250f210f1f0f19,  0xf2f0d130d150f2d,  0xf3d0f3b0f311390,  0xf470f450f3d0f3f,  0xf510f4d0f4b0f4f,  0xf5b0f590f550f53,  0xf730f6b0f630f61,  0xf750f6d0f711396,  0xf8713980f830f79,  0xf8b0d170f7d0f81,  0xd190f8d0f930f8f,  0xfa5139a0f9b0f97,  0xfaf0d1f0fa90fb9,  0xdcf0dcd0d1b0d1d,  0xd5111810fb10fbb,  0xfc90fc10fbf0fbd,  0xfd30d2113a40fc5,  0x13a80fdd0fd90fcd,  0xd230fe30fd70fdf,  0xfef0fe90fe70fe5,  0xff70d250ff313aa,  0xffb0d270ff913ac,  0x13ae100710051001,  0x13b2100913b01384,  0x1017100b1013100f,  0x102310211027101f,  0x101d13b4102b1029,  0x10410d2910391037,  0x104d103313b6103f,  0x1059104f13ba13b8,  0x105b0d2b10551057,  0x136c1065105f1061,  0x13c0107113bc106b,  0x13c21081107f107b,  0x13c613c410871085,  0x10990d2d10971093,  0x10a710a50d2f0d2f,  0xd3110b310ad10ab,  0x13ca10bb13c810b7,  0x13cc10c5138c10c1,  0xd350d3313d013ce,  0x13d613d213d410d5,  0x10db10db10d913d8,  0xd3b10e10d3910df,  0x10e910e513dc0d3d,  0x10ff13de0d3f10ef,  0x1113110d13e213e0,  0x111b111911170d41,  0x112313e613e613e4,  0x112b112913e80d43,  0xd47113713ea0d45,  0x13ee1141113b113f,  0x115511510d49114b,  0x13f413f20d4b115d,  0x13f8116513f60d4d,  0x13fa1173116f1169,  0x117b13fe117713fc,  0x118511830d4f139e,  0x14000ead11870d53,  0x118f13a213a01402,  0x119b0d55126b1191,  0x119f0dfd119d1199,  0x140411a711a311a1,  0x11b511b311a911a5,  0x11cb11c111b711ab,  0x11bf11bd11bb11b1,  0xd571408140a1406,  0x141211d511d111cf,  0xd5b0d59140c11d7,  0x11e50d5d1410140e,  0x11ef11eb11e911e7,  0x11f911f111f311ed,  0xd5f11fb11f711f5,  0x12070d61120111ff,  0x1211120f14141209,  0x14160cfd12170d63,  0x12250d670d651418,  0x141a1243123f1231,  0x1253125112471245,  0x125512571372141e,  0x1265125f1374125b,  0x1281127b14221420,  0x1297128714241285,  0x12a5129b129f129d,  0xd6912a9142612a7,  0x12c30d6b142812ad,  0x142e142a12cd0ee3,  0x143012d70d6f0d6d,  0x12db12db14320d71,  0xd7312e5143412df,  0x12f512f112ef12ed,  0x12fd12f914360d75,  0x13030d790d771301,  0x143c143a0d7b1438,  0x13150d7d1311143e,  0x131d131b13191317,  0x1442131f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+@property
+{
+private alias _IDCA = immutable(dchar[]);
+_IDCA decompCanonTable() { static _IDCA t = [ 0x0,  0x3b,  0x0,  0x3c,  0x338,  0x0,  0x3d,  0x338,  0x0,  0x3e,  0x338,  0x0,  0x41,  0x300,  0x0,  0x41,  0x301,  0x0,  0x41,  0x302,  0x0,  0x41,  0x302,  0x300,  0x0,  0x41,  0x302,  0x301,  0x0,  0x41,  0x302,  0x303,  0x0,  0x41,  0x302,  0x309,  0x0,  0x41,  0x303,  0x0,  0x41,  0x304,  0x0,  0x41,  0x306,  0x0,  0x41,  0x306,  0x300,  0x0,  0x41,  0x306,  0x301,  0x0,  0x41,  0x306,  0x303,  0x0,  0x41,  0x306,  0x309,  0x0,  0x41,  0x307,  0x0,  0x41,  0x307,  0x304,  0x0,  0x41,  0x308,  0x0,  0x41,  0x308,  0x304,  0x0,  0x41,  0x309,  0x0,  0x41,  0x30a,  0x0,  0x41,  0x30a,  0x301,  0x0,  0x41,  0x30c,  0x0,  0x41,  0x30f,  0x0,  0x41,  0x311,  0x0,  0x41,  0x323,  0x0,  0x41,  0x323,  0x302,  0x0,  0x41,  0x323,  0x306,  0x0,  0x41,  0x325,  0x0,  0x41,  0x328,  0x0,  0x42,  0x307,  0x0,  0x42,  0x323,  0x0,  0x42,  0x331,  0x0,  0x43,  0x301,  0x0,  0x43,  0x302,  0x0,  0x43,  0x307,  0x0,  0x43,  0x30c,  0x0,  0x43,  0x327,  0x0,  0x43,  0x327,  0x301,  0x0,  0x44,  0x307,  0x0,  0x44,  0x30c,  0x0,  0x44,  0x323,  0x0,  0x44,  0x327,  0x0,  0x44,  0x32d,  0x0,  0x44,  0x331,  0x0,  0x45,  0x300,  0x0,  0x45,  0x301,  0x0,  0x45,  0x302,  0x0,  0x45,  0x302,  0x300,  0x0,  0x45,  0x302,  0x301,  0x0,  0x45,  0x302,  0x303,  0x0,  0x45,  0x302,  0x309,  0x0,  0x45,  0x303,  0x0,  0x45,  0x304,  0x0,  0x45,  0x304,  0x300,  0x0,  0x45,  0x304,  0x301,  0x0,  0x45,  0x306,  0x0,  0x45,  0x307,  0x0,  0x45,  0x308,  0x0,  0x45,  0x309,  0x0,  0x45,  0x30c,  0x0,  0x45,  0x30f,  0x0,  0x45,  0x311,  0x0,  0x45,  0x323,  0x0,  0x45,  0x323,  0x302,  0x0,  0x45,  0x327,  0x0,  0x45,  0x327,  0x306,  0x0,  0x45,  0x328,  0x0,  0x45,  0x32d,  0x0,  0x45,  0x330,  0x0,  0x46,  0x307,  0x0,  0x47,  0x301,  0x0,  0x47,  0x302,  0x0,  0x47,  0x304,  0x0,  0x47,  0x306,  0x0,  0x47,  0x307,  0x0,  0x47,  0x30c,  0x0,  0x47,  0x327,  0x0,  0x48,  0x302,  0x0,  0x48,  0x307,  0x0,  0x48,  0x308,  0x0,  0x48,  0x30c,  0x0,  0x48,  0x323,  0x0,  0x48,  0x327,  0x0,  0x48,  0x32e,  0x0,  0x49,  0x300,  0x0,  0x49,  0x301,  0x0,  0x49,  0x302,  0x0,  0x49,  0x303,  0x0,  0x49,  0x304,  0x0,  0x49,  0x306,  0x0,  0x49,  0x307,  0x0,  0x49,  0x308,  0x0,  0x49,  0x308,  0x301,  0x0,  0x49,  0x309,  0x0,  0x49,  0x30c,  0x0,  0x49,  0x30f,  0x0,  0x49,  0x311,  0x0,  0x49,  0x323,  0x0,  0x49,  0x328,  0x0,  0x49,  0x330,  0x0,  0x4a,  0x302,  0x0,  0x4b,  0x0,  0x4b,  0x301,  0x0,  0x4b,  0x30c,  0x0,  0x4b,  0x323,  0x0,  0x4b,  0x327,  0x0,  0x4b,  0x331,  0x0,  0x4c,  0x301,  0x0,  0x4c,  0x30c,  0x0,  0x4c,  0x323,  0x0,  0x4c,  0x323,  0x304,  0x0,  0x4c,  0x327,  0x0,  0x4c,  0x32d,  0x0,  0x4c,  0x331,  0x0,  0x4d,  0x301,  0x0,  0x4d,  0x307,  0x0,  0x4d,  0x323,  0x0,  0x4e,  0x300,  0x0,  0x4e,  0x301,  0x0,  0x4e,  0x303,  0x0,  0x4e,  0x307,  0x0,  0x4e,  0x30c,  0x0,  0x4e,  0x323,  0x0,  0x4e,  0x327,  0x0,  0x4e,  0x32d,  0x0,  0x4e,  0x331,  0x0,  0x4f,  0x300,  0x0,  0x4f,  0x301,  0x0,  0x4f,  0x302,  0x0,  0x4f,  0x302,  0x300,  0x0,  0x4f,  0x302,  0x301,  0x0,  0x4f,  0x302,  0x303,  0x0,  0x4f,  0x302,  0x309,  0x0,  0x4f,  0x303,  0x0,  0x4f,  0x303,  0x301,  0x0,  0x4f,  0x303,  0x304,  0x0,  0x4f,  0x303,  0x308,  0x0,  0x4f,  0x304,  0x0,  0x4f,  0x304,  0x300,  0x0,  0x4f,  0x304,  0x301,  0x0,  0x4f,  0x306,  0x0,  0x4f,  0x307,  0x0,  0x4f,  0x307,  0x304,  0x0,  0x4f,  0x308,  0x0,  0x4f,  0x308,  0x304,  0x0,  0x4f,  0x309,  0x0,  0x4f,  0x30b,  0x0,  0x4f,  0x30c,  0x0,  0x4f,  0x30f,  0x0,  0x4f,  0x311,  0x0,  0x4f,  0x31b,  0x0,  0x4f,  0x31b,  0x300,  0x0,  0x4f,  0x31b,  0x301,  0x0,  0x4f,  0x31b,  0x303,  0x0,  0x4f,  0x31b,  0x309,  0x0,  0x4f,  0x31b,  0x323,  0x0,  0x4f,  0x323,  0x0,  0x4f,  0x323,  0x302,  0x0,  0x4f,  0x328,  0x0,  0x4f,  0x328,  0x304,  0x0,  0x50,  0x301,  0x0,  0x50,  0x307,  0x0,  0x52,  0x301,  0x0,  0x52,  0x307,  0x0,  0x52,  0x30c,  0x0,  0x52,  0x30f,  0x0,  0x52,  0x311,  0x0,  0x52,  0x323,  0x0,  0x52,  0x323,  0x304,  0x0,  0x52,  0x327,  0x0,  0x52,  0x331,  0x0,  0x53,  0x301,  0x0,  0x53,  0x301,  0x307,  0x0,  0x53,  0x302,  0x0,  0x53,  0x307,  0x0,  0x53,  0x30c,  0x0,  0x53,  0x30c,  0x307,  0x0,  0x53,  0x323,  0x0,  0x53,  0x323,  0x307,  0x0,  0x53,  0x326,  0x0,  0x53,  0x327,  0x0,  0x54,  0x307,  0x0,  0x54,  0x30c,  0x0,  0x54,  0x323,  0x0,  0x54,  0x326,  0x0,  0x54,  0x327,  0x0,  0x54,  0x32d,  0x0,  0x54,  0x331,  0x0,  0x55,  0x300,  0x0,  0x55,  0x301,  0x0,  0x55,  0x302,  0x0,  0x55,  0x303,  0x0,  0x55,  0x303,  0x301,  0x0,  0x55,  0x304,  0x0,  0x55,  0x304,  0x308,  0x0,  0x55,  0x306,  0x0,  0x55,  0x308,  0x0,  0x55,  0x308,  0x300,  0x0,  0x55,  0x308,  0x301,  0x0,  0x55,  0x308,  0x304,  0x0,  0x55,  0x308,  0x30c,  0x0,  0x55,  0x309,  0x0,  0x55,  0x30a,  0x0,  0x55,  0x30b,  0x0,  0x55,  0x30c,  0x0,  0x55,  0x30f,  0x0,  0x55,  0x311,  0x0,  0x55,  0x31b,  0x0,  0x55,  0x31b,  0x300,  0x0,  0x55,  0x31b,  0x301,  0x0,  0x55,  0x31b,  0x303,  0x0,  0x55,  0x31b,  0x309,  0x0,  0x55,  0x31b,  0x323,  0x0,  0x55,  0x323,  0x0,  0x55,  0x324,  0x0,  0x55,  0x328,  0x0,  0x55,  0x32d,  0x0,  0x55,  0x330,  0x0,  0x56,  0x303,  0x0,  0x56,  0x323,  0x0,  0x57,  0x300,  0x0,  0x57,  0x301,  0x0,  0x57,  0x302,  0x0,  0x57,  0x307,  0x0,  0x57,  0x308,  0x0,  0x57,  0x323,  0x0,  0x58,  0x307,  0x0,  0x58,  0x308,  0x0,  0x59,  0x300,  0x0,  0x59,  0x301,  0x0,  0x59,  0x302,  0x0,  0x59,  0x303,  0x0,  0x59,  0x304,  0x0,  0x59,  0x307,  0x0,  0x59,  0x308,  0x0,  0x59,  0x309,  0x0,  0x59,  0x323,  0x0,  0x5a,  0x301,  0x0,  0x5a,  0x302,  0x0,  0x5a,  0x307,  0x0,  0x5a,  0x30c,  0x0,  0x5a,  0x323,  0x0,  0x5a,  0x331,  0x0,  0x60,  0x0,  0x61,  0x300,  0x0,  0x61,  0x301,  0x0,  0x61,  0x302,  0x0,  0x61,  0x302,  0x300,  0x0,  0x61,  0x302,  0x301,  0x0,  0x61,  0x302,  0x303,  0x0,  0x61,  0x302,  0x309,  0x0,  0x61,  0x303,  0x0,  0x61,  0x304,  0x0,  0x61,  0x306,  0x0,  0x61,  0x306,  0x300,  0x0,  0x61,  0x306,  0x301,  0x0,  0x61,  0x306,  0x303,  0x0,  0x61,  0x306,  0x309,  0x0,  0x61,  0x307,  0x0,  0x61,  0x307,  0x304,  0x0,  0x61,  0x308,  0x0,  0x61,  0x308,  0x304,  0x0,  0x61,  0x309,  0x0,  0x61,  0x30a,  0x0,  0x61,  0x30a,  0x301,  0x0,  0x61,  0x30c,  0x0,  0x61,  0x30f,  0x0,  0x61,  0x311,  0x0,  0x61,  0x323,  0x0,  0x61,  0x323,  0x302,  0x0,  0x61,  0x323,  0x306,  0x0,  0x61,  0x325,  0x0,  0x61,  0x328,  0x0,  0x62,  0x307,  0x0,  0x62,  0x323,  0x0,  0x62,  0x331,  0x0,  0x63,  0x301,  0x0,  0x63,  0x302,  0x0,  0x63,  0x307,  0x0,  0x63,  0x30c,  0x0,  0x63,  0x327,  0x0,  0x63,  0x327,  0x301,  0x0,  0x64,  0x307,  0x0,  0x64,  0x30c,  0x0,  0x64,  0x323,  0x0,  0x64,  0x327,  0x0,  0x64,  0x32d,  0x0,  0x64,  0x331,  0x0,  0x65,  0x300,  0x0,  0x65,  0x301,  0x0,  0x65,  0x302,  0x0,  0x65,  0x302,  0x300,  0x0,  0x65,  0x302,  0x301,  0x0,  0x65,  0x302,  0x303,  0x0,  0x65,  0x302,  0x309,  0x0,  0x65,  0x303,  0x0,  0x65,  0x304,  0x0,  0x65,  0x304,  0x300,  0x0,  0x65,  0x304,  0x301,  0x0,  0x65,  0x306,  0x0,  0x65,  0x307,  0x0,  0x65,  0x308,  0x0,  0x65,  0x309,  0x0,  0x65,  0x30c,  0x0,  0x65,  0x30f,  0x0,  0x65,  0x311,  0x0,  0x65,  0x323,  0x0,  0x65,  0x323,  0x302,  0x0,  0x65,  0x327,  0x0,  0x65,  0x327,  0x306,  0x0,  0x65,  0x328,  0x0,  0x65,  0x32d,  0x0,  0x65,  0x330,  0x0,  0x66,  0x307,  0x0,  0x67,  0x301,  0x0,  0x67,  0x302,  0x0,  0x67,  0x304,  0x0,  0x67,  0x306,  0x0,  0x67,  0x307,  0x0,  0x67,  0x30c,  0x0,  0x67,  0x327,  0x0,  0x68,  0x302,  0x0,  0x68,  0x307,  0x0,  0x68,  0x308,  0x0,  0x68,  0x30c,  0x0,  0x68,  0x323,  0x0,  0x68,  0x327,  0x0,  0x68,  0x32e,  0x0,  0x68,  0x331,  0x0,  0x69,  0x300,  0x0,  0x69,  0x301,  0x0,  0x69,  0x302,  0x0,  0x69,  0x303,  0x0,  0x69,  0x304,  0x0,  0x69,  0x306,  0x0,  0x69,  0x308,  0x0,  0x69,  0x308,  0x301,  0x0,  0x69,  0x309,  0x0,  0x69,  0x30c,  0x0,  0x69,  0x30f,  0x0,  0x69,  0x311,  0x0,  0x69,  0x323,  0x0,  0x69,  0x328,  0x0,  0x69,  0x330,  0x0,  0x6a,  0x302,  0x0,  0x6a,  0x30c,  0x0,  0x6b,  0x301,  0x0,  0x6b,  0x30c,  0x0,  0x6b,  0x323,  0x0,  0x6b,  0x327,  0x0,  0x6b,  0x331,  0x0,  0x6c,  0x301,  0x0,  0x6c,  0x30c,  0x0,  0x6c,  0x323,  0x0,  0x6c,  0x323,  0x304,  0x0,  0x6c,  0x327,  0x0,  0x6c,  0x32d,  0x0,  0x6c,  0x331,  0x0,  0x6d,  0x301,  0x0,  0x6d,  0x307,  0x0,  0x6d,  0x323,  0x0,  0x6e,  0x300,  0x0,  0x6e,  0x301,  0x0,  0x6e,  0x303,  0x0,  0x6e,  0x307,  0x0,  0x6e,  0x30c,  0x0,  0x6e,  0x323,  0x0,  0x6e,  0x327,  0x0,  0x6e,  0x32d,  0x0,  0x6e,  0x331,  0x0,  0x6f,  0x300,  0x0,  0x6f,  0x301,  0x0,  0x6f,  0x302,  0x0,  0x6f,  0x302,  0x300,  0x0,  0x6f,  0x302,  0x301,  0x0,  0x6f,  0x302,  0x303,  0x0,  0x6f,  0x302,  0x309,  0x0,  0x6f,  0x303,  0x0,  0x6f,  0x303,  0x301,  0x0,  0x6f,  0x303,  0x304,  0x0,  0x6f,  0x303,  0x308,  0x0,  0x6f,  0x304,  0x0,  0x6f,  0x304,  0x300,  0x0,  0x6f,  0x304,  0x301,  0x0,  0x6f,  0x306,  0x0,  0x6f,  0x307,  0x0,  0x6f,  0x307,  0x304,  0x0,  0x6f,  0x308,  0x0,  0x6f,  0x308,  0x304,  0x0,  0x6f,  0x309,  0x0,  0x6f,  0x30b,  0x0,  0x6f,  0x30c,  0x0,  0x6f,  0x30f,  0x0,  0x6f,  0x311,  0x0,  0x6f,  0x31b,  0x0,  0x6f,  0x31b,  0x300,  0x0,  0x6f,  0x31b,  0x301,  0x0,  0x6f,  0x31b,  0x303,  0x0,  0x6f,  0x31b,  0x309,  0x0,  0x6f,  0x31b,  0x323,  0x0,  0x6f,  0x323,  0x0,  0x6f,  0x323,  0x302,  0x0,  0x6f,  0x328,  0x0,  0x6f,  0x328,  0x304,  0x0,  0x70,  0x301,  0x0,  0x70,  0x307,  0x0,  0x72,  0x301,  0x0,  0x72,  0x307,  0x0,  0x72,  0x30c,  0x0,  0x72,  0x30f,  0x0,  0x72,  0x311,  0x0,  0x72,  0x323,  0x0,  0x72,  0x323,  0x304,  0x0,  0x72,  0x327,  0x0,  0x72,  0x331,  0x0,  0x73,  0x301,  0x0,  0x73,  0x301,  0x307,  0x0,  0x73,  0x302,  0x0,  0x73,  0x307,  0x0,  0x73,  0x30c,  0x0,  0x73,  0x30c,  0x307,  0x0,  0x73,  0x323,  0x0,  0x73,  0x323,  0x307,  0x0,  0x73,  0x326,  0x0,  0x73,  0x327,  0x0,  0x74,  0x307,  0x0,  0x74,  0x308,  0x0,  0x74,  0x30c,  0x0,  0x74,  0x323,  0x0,  0x74,  0x326,  0x0,  0x74,  0x327,  0x0,  0x74,  0x32d,  0x0,  0x74,  0x331,  0x0,  0x75,  0x300,  0x0,  0x75,  0x301,  0x0,  0x75,  0x302,  0x0,  0x75,  0x303,  0x0,  0x75,  0x303,  0x301,  0x0,  0x75,  0x304,  0x0,  0x75,  0x304,  0x308,  0x0,  0x75,  0x306,  0x0,  0x75,  0x308,  0x0,  0x75,  0x308,  0x300,  0x0,  0x75,  0x308,  0x301,  0x0,  0x75,  0x308,  0x304,  0x0,  0x75,  0x308,  0x30c,  0x0,  0x75,  0x309,  0x0,  0x75,  0x30a,  0x0,  0x75,  0x30b,  0x0,  0x75,  0x30c,  0x0,  0x75,  0x30f,  0x0,  0x75,  0x311,  0x0,  0x75,  0x31b,  0x0,  0x75,  0x31b,  0x300,  0x0,  0x75,  0x31b,  0x301,  0x0,  0x75,  0x31b,  0x303,  0x0,  0x75,  0x31b,  0x309,  0x0,  0x75,  0x31b,  0x323,  0x0,  0x75,  0x323,  0x0,  0x75,  0x324,  0x0,  0x75,  0x328,  0x0,  0x75,  0x32d,  0x0,  0x75,  0x330,  0x0,  0x76,  0x303,  0x0,  0x76,  0x323,  0x0,  0x77,  0x300,  0x0,  0x77,  0x301,  0x0,  0x77,  0x302,  0x0,  0x77,  0x307,  0x0,  0x77,  0x308,  0x0,  0x77,  0x30a,  0x0,  0x77,  0x323,  0x0,  0x78,  0x307,  0x0,  0x78,  0x308,  0x0,  0x79,  0x300,  0x0,  0x79,  0x301,  0x0,  0x79,  0x302,  0x0,  0x79,  0x303,  0x0,  0x79,  0x304,  0x0,  0x79,  0x307,  0x0,  0x79,  0x308,  0x0,  0x79,  0x309,  0x0,  0x79,  0x30a,  0x0,  0x79,  0x323,  0x0,  0x7a,  0x301,  0x0,  0x7a,  0x302,  0x0,  0x7a,  0x307,  0x0,  0x7a,  0x30c,  0x0,  0x7a,  0x323,  0x0,  0x7a,  0x331,  0x0,  0xa8,  0x300,  0x0,  0xa8,  0x301,  0x0,  0xa8,  0x342,  0x0,  0xb4,  0x0,  0xb7,  0x0,  0xc6,  0x301,  0x0,  0xc6,  0x304,  0x0,  0xd8,  0x301,  0x0,  0xe6,  0x301,  0x0,  0xe6,  0x304,  0x0,  0xf8,  0x301,  0x0,  0x17f,  0x307,  0x0,  0x1b7,  0x30c,  0x0,  0x292,  0x30c,  0x0,  0x2b9,  0x0,  0x300,  0x0,  0x301,  0x0,  0x308,  0x301,  0x0,  0x313,  0x0,  0x391,  0x300,  0x0,  0x391,  0x301,  0x0,  0x391,  0x304,  0x0,  0x391,  0x306,  0x0,  0x391,  0x313,  0x0,  0x391,  0x313,  0x300,  0x0,  0x391,  0x313,  0x300,  0x345,  0x0,  0x391,  0x313,  0x301,  0x0,  0x391,  0x313,  0x301,  0x345,  0x0,  0x391,  0x313,  0x342,  0x0,  0x391,  0x313,  0x342,  0x345,  0x0,  0x391,  0x313,  0x345,  0x0,  0x391,  0x314,  0x0,  0x391,  0x314,  0x300,  0x0,  0x391,  0x314,  0x300,  0x345,  0x0,  0x391,  0x314,  0x301,  0x0,  0x391,  0x314,  0x301,  0x345,  0x0,  0x391,  0x314,  0x342,  0x0,  0x391,  0x314,  0x342,  0x345,  0x0,  0x391,  0x314,  0x345,  0x0,  0x391,  0x345,  0x0,  0x395,  0x300,  0x0,  0x395,  0x301,  0x0,  0x395,  0x313,  0x0,  0x395,  0x313,  0x300,  0x0,  0x395,  0x313,  0x301,  0x0,  0x395,  0x314,  0x0,  0x395,  0x314,  0x300,  0x0,  0x395,  0x314,  0x301,  0x0,  0x397,  0x300,  0x0,  0x397,  0x301,  0x0,  0x397,  0x313,  0x0,  0x397,  0x313,  0x300,  0x0,  0x397,  0x313,  0x300,  0x345,  0x0,  0x397,  0x313,  0x301,  0x0,  0x397,  0x313,  0x301,  0x345,  0x0,  0x397,  0x313,  0x342,  0x0,  0x397,  0x313,  0x342,  0x345,  0x0,  0x397,  0x313,  0x345,  0x0,  0x397,  0x314,  0x0,  0x397,  0x314,  0x300,  0x0,  0x397,  0x314,  0x300,  0x345,  0x0,  0x397,  0x314,  0x301,  0x0,  0x397,  0x314,  0x301,  0x345,  0x0,  0x397,  0x314,  0x342,  0x0,  0x397,  0x314,  0x342,  0x345,  0x0,  0x397,  0x314,  0x345,  0x0,  0x397,  0x345,  0x0,  0x399,  0x300,  0x0,  0x399,  0x301,  0x0,  0x399,  0x304,  0x0,  0x399,  0x306,  0x0,  0x399,  0x308,  0x0,  0x399,  0x313,  0x0,  0x399,  0x313,  0x300,  0x0,  0x399,  0x313,  0x301,  0x0,  0x399,  0x313,  0x342,  0x0,  0x399,  0x314,  0x0,  0x399,  0x314,  0x300,  0x0,  0x399,  0x314,  0x301,  0x0,  0x399,  0x314,  0x342,  0x0,  0x39f,  0x300,  0x0,  0x39f,  0x301,  0x0,  0x39f,  0x313,  0x0,  0x39f,  0x313,  0x300,  0x0,  0x39f,  0x313,  0x301,  0x0,  0x39f,  0x314,  0x0,  0x39f,  0x314,  0x300,  0x0,  0x39f,  0x314,  0x301,  0x0,  0x3a1,  0x314,  0x0,  0x3a5,  0x300,  0x0,  0x3a5,  0x301,  0x0,  0x3a5,  0x304,  0x0,  0x3a5,  0x306,  0x0,  0x3a5,  0x308,  0x0,  0x3a5,  0x314,  0x0,  0x3a5,  0x314,  0x300,  0x0,  0x3a5,  0x314,  0x301,  0x0,  0x3a5,  0x314,  0x342,  0x0,  0x3a9,  0x0,  0x3a9,  0x300,  0x0,  0x3a9,  0x301,  0x0,  0x3a9,  0x313,  0x0,  0x3a9,  0x313,  0x300,  0x0,  0x3a9,  0x313,  0x300,  0x345,  0x0,  0x3a9,  0x313,  0x301,  0x0,  0x3a9,  0x313,  0x301,  0x345,  0x0,  0x3a9,  0x313,  0x342,  0x0,  0x3a9,  0x313,  0x342,  0x345,  0x0,  0x3a9,  0x313,  0x345,  0x0,  0x3a9,  0x314,  0x0,  0x3a9,  0x314,  0x300,  0x0,  0x3a9,  0x314,  0x300,  0x345,  0x0,  0x3a9,  0x314,  0x301,  0x0,  0x3a9,  0x314,  0x301,  0x345,  0x0,  0x3a9,  0x314,  0x342,  0x0,  0x3a9,  0x314,  0x342,  0x345,  0x0,  0x3a9,  0x314,  0x345,  0x0,  0x3a9,  0x345,  0x0,  0x3b1,  0x300,  0x0,  0x3b1,  0x300,  0x345,  0x0,  0x3b1,  0x301,  0x0,  0x3b1,  0x301,  0x345,  0x0,  0x3b1,  0x304,  0x0,  0x3b1,  0x306,  0x0,  0x3b1,  0x313,  0x0,  0x3b1,  0x313,  0x300,  0x0,  0x3b1,  0x313,  0x300,  0x345,  0x0,  0x3b1,  0x313,  0x301,  0x0,  0x3b1,  0x313,  0x301,  0x345,  0x0,  0x3b1,  0x313,  0x342,  0x0,  0x3b1,  0x313,  0x342,  0x345,  0x0,  0x3b1,  0x313,  0x345,  0x0,  0x3b1,  0x314,  0x0,  0x3b1,  0x314,  0x300,  0x0,  0x3b1,  0x314,  0x300,  0x345,  0x0,  0x3b1,  0x314,  0x301,  0x0,  0x3b1,  0x314,  0x301,  0x345,  0x0,  0x3b1,  0x314,  0x342,  0x0,  0x3b1,  0x314,  0x342,  0x345,  0x0,  0x3b1,  0x314,  0x345,  0x0,  0x3b1,  0x342,  0x0,  0x3b1,  0x342,  0x345,  0x0,  0x3b1,  0x345,  0x0,  0x3b5,  0x300,  0x0,  0x3b5,  0x301,  0x0,  0x3b5,  0x313,  0x0,  0x3b5,  0x313,  0x300,  0x0,  0x3b5,  0x313,  0x301,  0x0,  0x3b5,  0x314,  0x0,  0x3b5,  0x314,  0x300,  0x0,  0x3b5,  0x314,  0x301,  0x0,  0x3b7,  0x300,  0x0,  0x3b7,  0x300,  0x345,  0x0,  0x3b7,  0x301,  0x0,  0x3b7,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x0,  0x3b7,  0x313,  0x300,  0x0,  0x3b7,  0x313,  0x300,  0x345,  0x0,  0x3b7,  0x313,  0x301,  0x0,  0x3b7,  0x313,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x342,  0x0,  0x3b7,  0x313,  0x342,  0x345,  0x0,  0x3b7,  0x313,  0x345,  0x0,  0x3b7,  0x314,  0x0,  0x3b7,  0x314,  0x300,  0x0,  0x3b7,  0x314,  0x300,  0x345,  0x0,  0x3b7,  0x314,  0x301,  0x0,  0x3b7,  0x314,  0x301,  0x345,  0x0,  0x3b7,  0x314,  0x342,  0x0,  0x3b7,  0x314,  0x342,  0x345,  0x0,  0x3b7,  0x314,  0x345,  0x0,  0x3b7,  0x342,  0x0,  0x3b7,  0x342,  0x345,  0x0,  0x3b7,  0x345,  0x0,  0x3b9,  0x0,  0x3b9,  0x300,  0x0,  0x3b9,  0x301,  0x0,  0x3b9,  0x304,  0x0,  0x3b9,  0x306,  0x0,  0x3b9,  0x308,  0x0,  0x3b9,  0x308,  0x300,  0x0,  0x3b9,  0x308,  0x301,  0x0,  0x3b9,  0x308,  0x342,  0x0,  0x3b9,  0x313,  0x0,  0x3b9,  0x313,  0x300,  0x0,  0x3b9,  0x313,  0x301,  0x0,  0x3b9,  0x313,  0x342,  0x0,  0x3b9,  0x314,  0x0,  0x3b9,  0x314,  0x300,  0x0,  0x3b9,  0x314,  0x301,  0x0,  0x3b9,  0x314,  0x342,  0x0,  0x3b9,  0x342,  0x0,  0x3bf,  0x300,  0x0,  0x3bf,  0x301,  0x0,  0x3bf,  0x313,  0x0,  0x3bf,  0x313,  0x300,  0x0,  0x3bf,  0x313,  0x301,  0x0,  0x3bf,  0x314,  0x0,  0x3bf,  0x314,  0x300,  0x0,  0x3bf,  0x314,  0x301,  0x0,  0x3c1,  0x313,  0x0,  0x3c1,  0x314,  0x0,  0x3c5,  0x300,  0x0,  0x3c5,  0x301,  0x0,  0x3c5,  0x304,  0x0,  0x3c5,  0x306,  0x0,  0x3c5,  0x308,  0x0,  0x3c5,  0x308,  0x300,  0x0,  0x3c5,  0x308,  0x301,  0x0,  0x3c5,  0x308,  0x342,  0x0,  0x3c5,  0x313,  0x0,  0x3c5,  0x313,  0x300,  0x0,  0x3c5,  0x313,  0x301,  0x0,  0x3c5,  0x313,  0x342,  0x0,  0x3c5,  0x314,  0x0,  0x3c5,  0x314,  0x300,  0x0,  0x3c5,  0x314,  0x301,  0x0,  0x3c5,  0x314,  0x342,  0x0,  0x3c5,  0x342,  0x0,  0x3c9,  0x300,  0x0,  0x3c9,  0x300,  0x345,  0x0,  0x3c9,  0x301,  0x0,  0x3c9,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x0,  0x3c9,  0x313,  0x300,  0x0,  0x3c9,  0x313,  0x300,  0x345,  0x0,  0x3c9,  0x313,  0x301,  0x0,  0x3c9,  0x313,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x342,  0x0,  0x3c9,  0x313,  0x342,  0x345,  0x0,  0x3c9,  0x313,  0x345,  0x0,  0x3c9,  0x314,  0x0,  0x3c9,  0x314,  0x300,  0x0,  0x3c9,  0x314,  0x300,  0x345,  0x0,  0x3c9,  0x314,  0x301,  0x0,  0x3c9,  0x314,  0x301,  0x345,  0x0,  0x3c9,  0x314,  0x342,  0x0,  0x3c9,  0x314,  0x342,  0x345,  0x0,  0x3c9,  0x314,  0x345,  0x0,  0x3c9,  0x342,  0x0,  0x3c9,  0x342,  0x345,  0x0,  0x3c9,  0x345,  0x0,  0x3d2,  0x301,  0x0,  0x3d2,  0x308,  0x0,  0x406,  0x308,  0x0,  0x410,  0x306,  0x0,  0x410,  0x308,  0x0,  0x413,  0x301,  0x0,  0x415,  0x300,  0x0,  0x415,  0x306,  0x0,  0x415,  0x308,  0x0,  0x416,  0x306,  0x0,  0x416,  0x308,  0x0,  0x417,  0x308,  0x0,  0x418,  0x300,  0x0,  0x418,  0x304,  0x0,  0x418,  0x306,  0x0,  0x418,  0x308,  0x0,  0x41a,  0x301,  0x0,  0x41e,  0x308,  0x0,  0x423,  0x304,  0x0,  0x423,  0x306,  0x0,  0x423,  0x308,  0x0,  0x423,  0x30b,  0x0,  0x427,  0x308,  0x0,  0x42b,  0x308,  0x0,  0x42d,  0x308,  0x0,  0x430,  0x306,  0x0,  0x430,  0x308,  0x0,  0x433,  0x301,  0x0,  0x435,  0x300,  0x0,  0x435,  0x306,  0x0,  0x435,  0x308,  0x0,  0x436,  0x306,  0x0,  0x436,  0x308,  0x0,  0x437,  0x308,  0x0,  0x438,  0x300,  0x0,  0x438,  0x304,  0x0,  0x438,  0x306,  0x0,  0x438,  0x308,  0x0,  0x43a,  0x301,  0x0,  0x43e,  0x308,  0x0,  0x443,  0x304,  0x0,  0x443,  0x306,  0x0,  0x443,  0x308,  0x0,  0x443,  0x30b,  0x0,  0x447,  0x308,  0x0,  0x44b,  0x308,  0x0,  0x44d,  0x308,  0x0,  0x456,  0x308,  0x0,  0x474,  0x30f,  0x0,  0x475,  0x30f,  0x0,  0x4d8,  0x308,  0x0,  0x4d9,  0x308,  0x0,  0x4e8,  0x308,  0x0,  0x4e9,  0x308,  0x0,  0x5d0,  0x5b7,  0x0,  0x5d0,  0x5b8,  0x0,  0x5d0,  0x5bc,  0x0,  0x5d1,  0x5bc,  0x0,  0x5d1,  0x5bf,  0x0,  0x5d2,  0x5bc,  0x0,  0x5d3,  0x5bc,  0x0,  0x5d4,  0x5bc,  0x0,  0x5d5,  0x5b9,  0x0,  0x5d5,  0x5bc,  0x0,  0x5d6,  0x5bc,  0x0,  0x5d8,  0x5bc,  0x0,  0x5d9,  0x5b4,  0x0,  0x5d9,  0x5bc,  0x0,  0x5da,  0x5bc,  0x0,  0x5db,  0x5bc,  0x0,  0x5db,  0x5bf,  0x0,  0x5dc,  0x5bc,  0x0,  0x5de,  0x5bc,  0x0,  0x5e0,  0x5bc,  0x0,  0x5e1,  0x5bc,  0x0,  0x5e3,  0x5bc,  0x0,  0x5e4,  0x5bc,  0x0,  0x5e4,  0x5bf,  0x0,  0x5e6,  0x5bc,  0x0,  0x5e7,  0x5bc,  0x0,  0x5e8,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x5c1,  0x0,  0x5e9,  0x5bc,  0x5c2,  0x0,  0x5e9,  0x5c1,  0x0,  0x5e9,  0x5c2,  0x0,  0x5ea,  0x5bc,  0x0,  0x5f2,  0x5b7,  0x0,  0x627,  0x653,  0x0,  0x627,  0x654,  0x0,  0x627,  0x655,  0x0,  0x648,  0x654,  0x0,  0x64a,  0x654,  0x0,  0x6c1,  0x654,  0x0,  0x6d2,  0x654,  0x0,  0x6d5,  0x654,  0x0,  0x915,  0x93c,  0x0,  0x916,  0x93c,  0x0,  0x917,  0x93c,  0x0,  0x91c,  0x93c,  0x0,  0x921,  0x93c,  0x0,  0x922,  0x93c,  0x0,  0x928,  0x93c,  0x0,  0x92b,  0x93c,  0x0,  0x92f,  0x93c,  0x0,  0x930,  0x93c,  0x0,  0x933,  0x93c,  0x0,  0x9a1,  0x9bc,  0x0,  0x9a2,  0x9bc,  0x0,  0x9af,  0x9bc,  0x0,  0x9c7,  0x9be,  0x0,  0x9c7,  0x9d7,  0x0,  0xa16,  0xa3c,  0x0,  0xa17,  0xa3c,  0x0,  0xa1c,  0xa3c,  0x0,  0xa2b,  0xa3c,  0x0,  0xa32,  0xa3c,  0x0,  0xa38,  0xa3c,  0x0,  0xb21,  0xb3c,  0x0,  0xb22,  0xb3c,  0x0,  0xb47,  0xb3e,  0x0,  0xb47,  0xb56,  0x0,  0xb47,  0xb57,  0x0,  0xb92,  0xbd7,  0x0,  0xbc6,  0xbbe,  0x0,  0xbc6,  0xbd7,  0x0,  0xbc7,  0xbbe,  0x0,  0xc46,  0xc56,  0x0,  0xcbf,  0xcd5,  0x0,  0xcc6,  0xcc2,  0x0,  0xcc6,  0xcc2,  0xcd5,  0x0,  0xcc6,  0xcd5,  0x0,  0xcc6,  0xcd6,  0x0,  0xd46,  0xd3e,  0x0,  0xd46,  0xd57,  0x0,  0xd47,  0xd3e,  0x0,  0xdd9,  0xdca,  0x0,  0xdd9,  0xdcf,  0x0,  0xdd9,  0xdcf,  0xdca,  0x0,  0xdd9,  0xddf,  0x0,  0xf40,  0xfb5,  0x0,  0xf42,  0xfb7,  0x0,  0xf4c,  0xfb7,  0x0,  0xf51,  0xfb7,  0x0,  0xf56,  0xfb7,  0x0,  0xf5b,  0xfb7,  0x0,  0xf71,  0xf72,  0x0,  0xf71,  0xf74,  0x0,  0xf71,  0xf80,  0x0,  0xf90,  0xfb5,  0x0,  0xf92,  0xfb7,  0x0,  0xf9c,  0xfb7,  0x0,  0xfa1,  0xfb7,  0x0,  0xfa6,  0xfb7,  0x0,  0xfab,  0xfb7,  0x0,  0xfb2,  0xf80,  0x0,  0xfb3,  0xf80,  0x0,  0x1025,  0x102e,  0x0,  0x1b05,  0x1b35,  0x0,  0x1b07,  0x1b35,  0x0,  0x1b09,  0x1b35,  0x0,  0x1b0b,  0x1b35,  0x0,  0x1b0d,  0x1b35,  0x0,  0x1b11,  0x1b35,  0x0,  0x1b3a,  0x1b35,  0x0,  0x1b3c,  0x1b35,  0x0,  0x1b3e,  0x1b35,  0x0,  0x1b3f,  0x1b35,  0x0,  0x1b42,  0x1b35,  0x0,  0x1fbf,  0x300,  0x0,  0x1fbf,  0x301,  0x0,  0x1fbf,  0x342,  0x0,  0x1ffe,  0x300,  0x0,  0x1ffe,  0x301,  0x0,  0x1ffe,  0x342,  0x0,  0x2002,  0x0,  0x2003,  0x0,  0x2190,  0x338,  0x0,  0x2192,  0x338,  0x0,  0x2194,  0x338,  0x0,  0x21d0,  0x338,  0x0,  0x21d2,  0x338,  0x0,  0x21d4,  0x338,  0x0,  0x2203,  0x338,  0x0,  0x2208,  0x338,  0x0,  0x220b,  0x338,  0x0,  0x2223,  0x338,  0x0,  0x2225,  0x338,  0x0,  0x223c,  0x338,  0x0,  0x2243,  0x338,  0x0,  0x2245,  0x338,  0x0,  0x2248,  0x338,  0x0,  0x224d,  0x338,  0x0,  0x2261,  0x338,  0x0,  0x2264,  0x338,  0x0,  0x2265,  0x338,  0x0,  0x2272,  0x338,  0x0,  0x2273,  0x338,  0x0,  0x2276,  0x338,  0x0,  0x2277,  0x338,  0x0,  0x227a,  0x338,  0x0,  0x227b,  0x338,  0x0,  0x227c,  0x338,  0x0,  0x227d,  0x338,  0x0,  0x2282,  0x338,  0x0,  0x2283,  0x338,  0x0,  0x2286,  0x338,  0x0,  0x2287,  0x338,  0x0,  0x2291,  0x338,  0x0,  0x2292,  0x338,  0x0,  0x22a2,  0x338,  0x0,  0x22a8,  0x338,  0x0,  0x22a9,  0x338,  0x0,  0x22ab,  0x338,  0x0,  0x22b2,  0x338,  0x0,  0x22b3,  0x338,  0x0,  0x22b4,  0x338,  0x0,  0x22b5,  0x338,  0x0,  0x2add,  0x338,  0x0,  0x3008,  0x0,  0x3009,  0x0,  0x3046,  0x3099,  0x0,  0x304b,  0x3099,  0x0,  0x304d,  0x3099,  0x0,  0x304f,  0x3099,  0x0,  0x3051,  0x3099,  0x0,  0x3053,  0x3099,  0x0,  0x3055,  0x3099,  0x0,  0x3057,  0x3099,  0x0,  0x3059,  0x3099,  0x0,  0x305b,  0x3099,  0x0,  0x305d,  0x3099,  0x0,  0x305f,  0x3099,  0x0,  0x3061,  0x3099,  0x0,  0x3064,  0x3099,  0x0,  0x3066,  0x3099,  0x0,  0x3068,  0x3099,  0x0,  0x306f,  0x3099,  0x0,  0x306f,  0x309a,  0x0,  0x3072,  0x3099,  0x0,  0x3072,  0x309a,  0x0,  0x3075,  0x3099,  0x0,  0x3075,  0x309a,  0x0,  0x3078,  0x3099,  0x0,  0x3078,  0x309a,  0x0,  0x307b,  0x3099,  0x0,  0x307b,  0x309a,  0x0,  0x309d,  0x3099,  0x0,  0x30a6,  0x3099,  0x0,  0x30ab,  0x3099,  0x0,  0x30ad,  0x3099,  0x0,  0x30af,  0x3099,  0x0,  0x30b1,  0x3099,  0x0,  0x30b3,  0x3099,  0x0,  0x30b5,  0x3099,  0x0,  0x30b7,  0x3099,  0x0,  0x30b9,  0x3099,  0x0,  0x30bb,  0x3099,  0x0,  0x30bd,  0x3099,  0x0,  0x30bf,  0x3099,  0x0,  0x30c1,  0x3099,  0x0,  0x30c4,  0x3099,  0x0,  0x30c6,  0x3099,  0x0,  0x30c8,  0x3099,  0x0,  0x30cf,  0x3099,  0x0,  0x30cf,  0x309a,  0x0,  0x30d2,  0x3099,  0x0,  0x30d2,  0x309a,  0x0,  0x30d5,  0x3099,  0x0,  0x30d5,  0x309a,  0x0,  0x30d8,  0x3099,  0x0,  0x30d8,  0x309a,  0x0,  0x30db,  0x3099,  0x0,  0x30db,  0x309a,  0x0,  0x30ef,  0x3099,  0x0,  0x30f0,  0x3099,  0x0,  0x30f1,  0x3099,  0x0,  0x30f2,  0x3099,  0x0,  0x30fd,  0x3099,  0x0,  0x349e,  0x0,  0x34b9,  0x0,  0x34bb,  0x0,  0x34df,  0x0,  0x3515,  0x0,  0x36ee,  0x0,  0x36fc,  0x0,  0x3781,  0x0,  0x382f,  0x0,  0x3862,  0x0,  0x387c,  0x0,  0x38c7,  0x0,  0x38e3,  0x0,  0x391c,  0x0,  0x393a,  0x0,  0x3a2e,  0x0,  0x3a6c,  0x0,  0x3ae4,  0x0,  0x3b08,  0x0,  0x3b19,  0x0,  0x3b49,  0x0,  0x3b9d,  0x0,  0x3c18,  0x0,  0x3c4e,  0x0,  0x3d33,  0x0,  0x3d96,  0x0,  0x3eac,  0x0,  0x3eb8,  0x0,  0x3f1b,  0x0,  0x3ffc,  0x0,  0x4008,  0x0,  0x4018,  0x0,  0x4039,  0x0,  0x4046,  0x0,  0x4096,  0x0,  0x40e3,  0x0,  0x412f,  0x0,  0x4202,  0x0,  0x4227,  0x0,  0x42a0,  0x0,  0x4301,  0x0,  0x4334,  0x0,  0x4359,  0x0,  0x43d5,  0x0,  0x43d9,  0x0,  0x440b,  0x0,  0x446b,  0x0,  0x452b,  0x0,  0x455d,  0x0,  0x4561,  0x0,  0x456b,  0x0,  0x45d7,  0x0,  0x45f9,  0x0,  0x4635,  0x0,  0x46be,  0x0,  0x46c7,  0x0,  0x4995,  0x0,  0x49e6,  0x0,  0x4a6e,  0x0,  0x4a76,  0x0,  0x4ab2,  0x0,  0x4b33,  0x0,  0x4bce,  0x0,  0x4cce,  0x0,  0x4ced,  0x0,  0x4cf8,  0x0,  0x4d56,  0x0,  0x4e0d,  0x0,  0x4e26,  0x0,  0x4e32,  0x0,  0x4e38,  0x0,  0x4e39,  0x0,  0x4e3d,  0x0,  0x4e41,  0x0,  0x4e82,  0x0,  0x4e86,  0x0,  0x4eae,  0x0,  0x4ec0,  0x0,  0x4ecc,  0x0,  0x4ee4,  0x0,  0x4f60,  0x0,  0x4f80,  0x0,  0x4f86,  0x0,  0x4f8b,  0x0,  0x4fae,  0x0,  0x4fbb,  0x0,  0x4fbf,  0x0,  0x5002,  0x0,  0x502b,  0x0,  0x507a,  0x0,  0x5099,  0x0,  0x50cf,  0x0,  0x50da,  0x0,  0x50e7,  0x0,  0x5140,  0x0,  0x5145,  0x0,  0x514d,  0x0,  0x5154,  0x0,  0x5164,  0x0,  0x5167,  0x0,  0x5168,  0x0,  0x5169,  0x0,  0x516d,  0x0,  0x5177,  0x0,  0x5180,  0x0,  0x518d,  0x0,  0x5192,  0x0,  0x5195,  0x0,  0x5197,  0x0,  0x51a4,  0x0,  0x51ac,  0x0,  0x51b5,  0x0,  0x51b7,  0x0,  0x51c9,  0x0,  0x51cc,  0x0,  0x51dc,  0x0,  0x51de,  0x0,  0x51f5,  0x0,  0x5203,  0x0,  0x5207,  0x0,  0x5217,  0x0,  0x5229,  0x0,  0x523a,  0x0,  0x523b,  0x0,  0x5246,  0x0,  0x5272,  0x0,  0x5277,  0x0,  0x5289,  0x0,  0x529b,  0x0,  0x52a3,  0x0,  0x52b3,  0x0,  0x52c7,  0x0,  0x52c9,  0x0,  0x52d2,  0x0,  0x52de,  0x0,  0x52e4,  0x0,  0x52f5,  0x0,  0x52fa,  0x0,  0x5305,  0x0,  0x5306,  0x0,  0x5317,  0x0,  0x533f,  0x0,  0x5349,  0x0,  0x5351,  0x0,  0x535a,  0x0,  0x5373,  0x0,  0x5375,  0x0,  0x537d,  0x0,  0x537f,  0x0,  0x53c3,  0x0,  0x53ca,  0x0,  0x53df,  0x0,  0x53e5,  0x0,  0x53eb,  0x0,  0x53f1,  0x0,  0x5406,  0x0,  0x540f,  0x0,  0x541d,  0x0,  0x5438,  0x0,  0x5442,  0x0,  0x5448,  0x0,  0x5468,  0x0,  0x549e,  0x0,  0x54a2,  0x0,  0x54bd,  0x0,  0x54f6,  0x0,  0x5510,  0x0,  0x5553,  0x0,  0x5555,  0x0,  0x5563,  0x0,  0x5584,  0x0,  0x5587,  0x0,  0x5599,  0x0,  0x559d,  0x0,  0x55ab,  0x0,  0x55b3,  0x0,  0x55c0,  0x0,  0x55c2,  0x0,  0x55e2,  0x0,  0x5606,  0x0,  0x5651,  0x0,  0x5668,  0x0,  0x5674,  0x0,  0x56f9,  0x0,  0x5716,  0x0,  0x5717,  0x0,  0x578b,  0x0,  0x57ce,  0x0,  0x57f4,  0x0,  0x580d,  0x0,  0x5831,  0x0,  0x5832,  0x0,  0x5840,  0x0,  0x585a,  0x0,  0x585e,  0x0,  0x58a8,  0x0,  0x58ac,  0x0,  0x58b3,  0x0,  0x58d8,  0x0,  0x58df,  0x0,  0x58ee,  0x0,  0x58f2,  0x0,  0x58f7,  0x0,  0x5906,  0x0,  0x591a,  0x0,  0x5922,  0x0,  0x5944,  0x0,  0x5948,  0x0,  0x5951,  0x0,  0x5954,  0x0,  0x5962,  0x0,  0x5973,  0x0,  0x59d8,  0x0,  0x59ec,  0x0,  0x5a1b,  0x0,  0x5a27,  0x0,  0x5a62,  0x0,  0x5a66,  0x0,  0x5ab5,  0x0,  0x5b08,  0x0,  0x5b28,  0x0,  0x5b3e,  0x0,  0x5b85,  0x0,  0x5bc3,  0x0,  0x5bd8,  0x0,  0x5be7,  0x0,  0x5bee,  0x0,  0x5bf3,  0x0,  0x5bff,  0x0,  0x5c06,  0x0,  0x5c22,  0x0,  0x5c3f,  0x0,  0x5c60,  0x0,  0x5c62,  0x0,  0x5c64,  0x0,  0x5c65,  0x0,  0x5c6e,  0x0,  0x5c8d,  0x0,  0x5cc0,  0x0,  0x5d19,  0x0,  0x5d43,  0x0,  0x5d50,  0x0,  0x5d6b,  0x0,  0x5d6e,  0x0,  0x5d7c,  0x0,  0x5db2,  0x0,  0x5dba,  0x0,  0x5de1,  0x0,  0x5de2,  0x0,  0x5dfd,  0x0,  0x5e28,  0x0,  0x5e3d,  0x0,  0x5e69,  0x0,  0x5e74,  0x0,  0x5ea6,  0x0,  0x5eb0,  0x0,  0x5eb3,  0x0,  0x5eb6,  0x0,  0x5ec9,  0x0,  0x5eca,  0x0,  0x5ed2,  0x0,  0x5ed3,  0x0,  0x5ed9,  0x0,  0x5eec,  0x0,  0x5efe,  0x0,  0x5f04,  0x0,  0x5f22,  0x0,  0x5f53,  0x0,  0x5f62,  0x0,  0x5f69,  0x0,  0x5f6b,  0x0,  0x5f8b,  0x0,  0x5f9a,  0x0,  0x5fa9,  0x0,  0x5fad,  0x0,  0x5fcd,  0x0,  0x5fd7,  0x0,  0x5ff5,  0x0,  0x5ff9,  0x0,  0x6012,  0x0,  0x601c,  0x0,  0x6075,  0x0,  0x6081,  0x0,  0x6094,  0x0,  0x60c7,  0x0,  0x60d8,  0x0,  0x60e1,  0x0,  0x6108,  0x0,  0x6144,  0x0,  0x6148,  0x0,  0x614c,  0x0,  0x614e,  0x0,  0x6160,  0x0,  0x6168,  0x0,  0x617a,  0x0,  0x618e,  0x0,  0x6190,  0x0,  0x61a4,  0x0,  0x61af,  0x0,  0x61b2,  0x0,  0x61de,  0x0,  0x61f2,  0x0,  0x61f6,  0x0,  0x6200,  0x0,  0x6210,  0x0,  0x621b,  0x0,  0x622e,  0x0,  0x6234,  0x0,  0x625d,  0x0,  0x62b1,  0x0,  0x62c9,  0x0,  0x62cf,  0x0,  0x62d3,  0x0,  0x62d4,  0x0,  0x62fc,  0x0,  0x62fe,  0x0,  0x633d,  0x0,  0x6350,  0x0,  0x6368,  0x0,  0x637b,  0x0,  0x6383,  0x0,  0x63a0,  0x0,  0x63a9,  0x0,  0x63c4,  0x0,  0x63c5,  0x0,  0x63e4,  0x0,  0x641c,  0x0,  0x6422,  0x0,  0x6452,  0x0,  0x6469,  0x0,  0x6477,  0x0,  0x647e,  0x0,  0x649a,  0x0,  0x649d,  0x0,  0x64c4,  0x0,  0x654f,  0x0,  0x6556,  0x0,  0x656c,  0x0,  0x6578,  0x0,  0x6599,  0x0,  0x65c5,  0x0,  0x65e2,  0x0,  0x65e3,  0x0,  0x6613,  0x0,  0x6649,  0x0,  0x6674,  0x0,  0x6688,  0x0,  0x6691,  0x0,  0x669c,  0x0,  0x66b4,  0x0,  0x66c6,  0x0,  0x66f4,  0x0,  0x66f8,  0x0,  0x6700,  0x0,  0x6717,  0x0,  0x671b,  0x0,  0x6721,  0x0,  0x674e,  0x0,  0x6753,  0x0,  0x6756,  0x0,  0x675e,  0x0,  0x677b,  0x0,  0x6785,  0x0,  0x6797,  0x0,  0x67f3,  0x0,  0x67fa,  0x0,  0x6817,  0x0,  0x681f,  0x0,  0x6852,  0x0,  0x6881,  0x0,  0x6885,  0x0,  0x688e,  0x0,  0x68a8,  0x0,  0x6914,  0x0,  0x6942,  0x0,  0x69a3,  0x0,  0x69ea,  0x0,  0x6a02,  0x0,  0x6a13,  0x0,  0x6aa8,  0x0,  0x6ad3,  0x0,  0x6adb,  0x0,  0x6b04,  0x0,  0x6b21,  0x0,  0x6b54,  0x0,  0x6b72,  0x0,  0x6b77,  0x0,  0x6b79,  0x0,  0x6b9f,  0x0,  0x6bae,  0x0,  0x6bba,  0x0,  0x6bbb,  0x0,  0x6c4e,  0x0,  0x6c67,  0x0,  0x6c88,  0x0,  0x6cbf,  0x0,  0x6ccc,  0x0,  0x6ccd,  0x0,  0x6ce5,  0x0,  0x6d16,  0x0,  0x6d1b,  0x0,  0x6d1e,  0x0,  0x6d34,  0x0,  0x6d3e,  0x0,  0x6d41,  0x0,  0x6d69,  0x0,  0x6d6a,  0x0,  0x6d77,  0x0,  0x6d78,  0x0,  0x6d85,  0x0,  0x6dcb,  0x0,  0x6dda,  0x0,  0x6dea,  0x0,  0x6df9,  0x0,  0x6e1a,  0x0,  0x6e2f,  0x0,  0x6e6e,  0x0,  0x6e9c,  0x0,  0x6eba,  0x0,  0x6ec7,  0x0,  0x6ecb,  0x0,  0x6ed1,  0x0,  0x6edb,  0x0,  0x6f0f,  0x0,  0x6f22,  0x0,  0x6f23,  0x0,  0x6f6e,  0x0,  0x6fc6,  0x0,  0x6feb,  0x0,  0x6ffe,  0x0,  0x701b,  0x0,  0x701e,  0x0,  0x7039,  0x0,  0x704a,  0x0,  0x7070,  0x0,  0x7077,  0x0,  0x707d,  0x0,  0x7099,  0x0,  0x70ad,  0x0,  0x70c8,  0x0,  0x70d9,  0x0,  0x7145,  0x0,  0x7149,  0x0,  0x716e,  0x0,  0x719c,  0x0,  0x71ce,  0x0,  0x71d0,  0x0,  0x7210,  0x0,  0x721b,  0x0,  0x7228,  0x0,  0x722b,  0x0,  0x7235,  0x0,  0x7250,  0x0,  0x7262,  0x0,  0x7280,  0x0,  0x7295,  0x0,  0x72af,  0x0,  0x72c0,  0x0,  0x72fc,  0x0,  0x732a,  0x0,  0x7375,  0x0,  0x737a,  0x0,  0x7387,  0x0,  0x738b,  0x0,  0x73a5,  0x0,  0x73b2,  0x0,  0x73de,  0x0,  0x7406,  0x0,  0x7409,  0x0,  0x7422,  0x0,  0x7447,  0x0,  0x745c,  0x0,  0x7469,  0x0,  0x7471,  0x0,  0x7485,  0x0,  0x7489,  0x0,  0x7498,  0x0,  0x74ca,  0x0,  0x7506,  0x0,  0x7524,  0x0,  0x753b,  0x0,  0x753e,  0x0,  0x7559,  0x0,  0x7565,  0x0,  0x7570,  0x0,  0x75e2,  0x0,  0x7610,  0x0,  0x761d,  0x0,  0x761f,  0x0,  0x7642,  0x0,  0x7669,  0x0,  0x76ca,  0x0,  0x76db,  0x0,  0x76e7,  0x0,  0x76f4,  0x0,  0x7701,  0x0,  0x771e,  0x0,  0x771f,  0x0,  0x7740,  0x0,  0x774a,  0x0,  0x778b,  0x0,  0x77a7,  0x0,  0x784e,  0x0,  0x786b,  0x0,  0x788c,  0x0,  0x7891,  0x0,  0x78ca,  0x0,  0x78cc,  0x0,  0x78fb,  0x0,  0x792a,  0x0,  0x793c,  0x0,  0x793e,  0x0,  0x7948,  0x0,  0x7949,  0x0,  0x7950,  0x0,  0x7956,  0x0,  0x795d,  0x0,  0x795e,  0x0,  0x7965,  0x0,  0x797f,  0x0,  0x798d,  0x0,  0x798e,  0x0,  0x798f,  0x0,  0x79ae,  0x0,  0x79ca,  0x0,  0x79eb,  0x0,  0x7a1c,  0x0,  0x7a40,  0x0,  0x7a4a,  0x0,  0x7a4f,  0x0,  0x7a81,  0x0,  0x7ab1,  0x0,  0x7acb,  0x0,  0x7aee,  0x0,  0x7b20,  0x0,  0x7bc0,  0x0,  0x7bc6,  0x0,  0x7bc9,  0x0,  0x7c3e,  0x0,  0x7c60,  0x0,  0x7c7b,  0x0,  0x7c92,  0x0,  0x7cbe,  0x0,  0x7cd2,  0x0,  0x7cd6,  0x0,  0x7ce3,  0x0,  0x7ce7,  0x0,  0x7ce8,  0x0,  0x7d00,  0x0,  0x7d10,  0x0,  0x7d22,  0x0,  0x7d2f,  0x0,  0x7d5b,  0x0,  0x7d63,  0x0,  0x7da0,  0x0,  0x7dbe,  0x0,  0x7dc7,  0x0,  0x7df4,  0x0,  0x7e02,  0x0,  0x7e09,  0x0,  0x7e37,  0x0,  0x7e41,  0x0,  0x7e45,  0x0,  0x7f3e,  0x0,  0x7f72,  0x0,  0x7f79,  0x0,  0x7f7a,  0x0,  0x7f85,  0x0,  0x7f95,  0x0,  0x7f9a,  0x0,  0x7fbd,  0x0,  0x7ffa,  0x0,  0x8001,  0x0,  0x8005,  0x0,  0x8046,  0x0,  0x8060,  0x0,  0x806f,  0x0,  0x8070,  0x0,  0x807e,  0x0,  0x808b,  0x0,  0x80ad,  0x0,  0x80b2,  0x0,  0x8103,  0x0,  0x813e,  0x0,  0x81d8,  0x0,  0x81e8,  0x0,  0x81ed,  0x0,  0x8201,  0x0,  0x8204,  0x0,  0x8218,  0x0,  0x826f,  0x0,  0x8279,  0x0,  0x828b,  0x0,  0x8291,  0x0,  0x829d,  0x0,  0x82b1,  0x0,  0x82b3,  0x0,  0x82bd,  0x0,  0x82e5,  0x0,  0x82e6,  0x0,  0x831d,  0x0,  0x8323,  0x0,  0x8336,  0x0,  0x8352,  0x0,  0x8353,  0x0,  0x8363,  0x0,  0x83ad,  0x0,  0x83bd,  0x0,  0x83c9,  0x0,  0x83ca,  0x0,  0x83cc,  0x0,  0x83dc,  0x0,  0x83e7,  0x0,  0x83ef,  0x0,  0x83f1,  0x0,  0x843d,  0x0,  0x8449,  0x0,  0x8457,  0x0,  0x84ee,  0x0,  0x84f1,  0x0,  0x84f3,  0x0,  0x84fc,  0x0,  0x8516,  0x0,  0x8564,  0x0,  0x85cd,  0x0,  0x85fa,  0x0,  0x8606,  0x0,  0x8612,  0x0,  0x862d,  0x0,  0x863f,  0x0,  0x8650,  0x0,  0x865c,  0x0,  0x8667,  0x0,  0x8669,  0x0,  0x8688,  0x0,  0x86a9,  0x0,  0x86e2,  0x0,  0x870e,  0x0,  0x8728,  0x0,  0x876b,  0x0,  0x8779,  0x0,  0x8786,  0x0,  0x87ba,  0x0,  0x87e1,  0x0,  0x8801,  0x0,  0x881f,  0x0,  0x884c,  0x0,  0x8860,  0x0,  0x8863,  0x0,  0x88c2,  0x0,  0x88cf,  0x0,  0x88d7,  0x0,  0x88de,  0x0,  0x88e1,  0x0,  0x88f8,  0x0,  0x88fa,  0x0,  0x8910,  0x0,  0x8941,  0x0,  0x8964,  0x0,  0x8986,  0x0,  0x898b,  0x0,  0x8996,  0x0,  0x8aa0,  0x0,  0x8aaa,  0x0,  0x8abf,  0x0,  0x8acb,  0x0,  0x8ad2,  0x0,  0x8ad6,  0x0,  0x8aed,  0x0,  0x8af8,  0x0,  0x8afe,  0x0,  0x8b01,  0x0,  0x8b39,  0x0,  0x8b58,  0x0,  0x8b80,  0x0,  0x8b8a,  0x0,  0x8c48,  0x0,  0x8c55,  0x0,  0x8cab,  0x0,  0x8cc1,  0x0,  0x8cc2,  0x0,  0x8cc8,  0x0,  0x8cd3,  0x0,  0x8d08,  0x0,  0x8d1b,  0x0,  0x8d77,  0x0,  0x8dbc,  0x0,  0x8dcb,  0x0,  0x8def,  0x0,  0x8df0,  0x0,  0x8eca,  0x0,  0x8ed4,  0x0,  0x8f26,  0x0,  0x8f2a,  0x0,  0x8f38,  0x0,  0x8f3b,  0x0,  0x8f62,  0x0,  0x8f9e,  0x0,  0x8fb0,  0x0,  0x8fb6,  0x0,  0x9023,  0x0,  0x9038,  0x0,  0x9072,  0x0,  0x907c,  0x0,  0x908f,  0x0,  0x9094,  0x0,  0x90ce,  0x0,  0x90de,  0x0,  0x90f1,  0x0,  0x90fd,  0x0,  0x9111,  0x0,  0x911b,  0x0,  0x916a,  0x0,  0x9199,  0x0,  0x91b4,  0x0,  0x91cc,  0x0,  0x91cf,  0x0,  0x91d1,  0x0,  0x9234,  0x0,  0x9238,  0x0,  0x9276,  0x0,  0x927c,  0x0,  0x92d7,  0x0,  0x92d8,  0x0,  0x9304,  0x0,  0x934a,  0x0,  0x93f9,  0x0,  0x9415,  0x0,  0x958b,  0x0,  0x95ad,  0x0,  0x95b7,  0x0,  0x962e,  0x0,  0x964b,  0x0,  0x964d,  0x0,  0x9675,  0x0,  0x9678,  0x0,  0x967c,  0x0,  0x9686,  0x0,  0x96a3,  0x0,  0x96b7,  0x0,  0x96b8,  0x0,  0x96c3,  0x0,  0x96e2,  0x0,  0x96e3,  0x0,  0x96f6,  0x0,  0x96f7,  0x0,  0x9723,  0x0,  0x9732,  0x0,  0x9748,  0x0,  0x9756,  0x0,  0x97db,  0x0,  0x97e0,  0x0,  0x97ff,  0x0,  0x980b,  0x0,  0x9818,  0x0,  0x9829,  0x0,  0x983b,  0x0,  0x985e,  0x0,  0x98e2,  0x0,  0x98ef,  0x0,  0x98fc,  0x0,  0x9928,  0x0,  0x9929,  0x0,  0x99a7,  0x0,  0x99c2,  0x0,  0x99f1,  0x0,  0x99fe,  0x0,  0x9a6a,  0x0,  0x9b12,  0x0,  0x9b6f,  0x0,  0x9c40,  0x0,  0x9c57,  0x0,  0x9cfd,  0x0,  0x9d67,  0x0,  0x9db4,  0x0,  0x9dfa,  0x0,  0x9e1e,  0x0,  0x9e7f,  0x0,  0x9e97,  0x0,  0x9e9f,  0x0,  0x9ebb,  0x0,  0x9ece,  0x0,  0x9ef9,  0x0,  0x9efe,  0x0,  0x9f05,  0x0,  0x9f0f,  0x0,  0x9f16,  0x0,  0x9f3b,  0x0,  0x9f43,  0x0,  0x9f8d,  0x0,  0x9f8e,  0x0,  0x9f9c,  0x0,  0x11099,  0x110ba,  0x0,  0x1109b,  0x110ba,  0x0,  0x110a5,  0x110ba,  0x0,  0x11131,  0x11127,  0x0,  0x11132,  0x11127,  0x0,  0x1d157,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x1d16e,  0x0,  0x1d158,  0x1d165,  0x1d16f,  0x0,  0x1d158,  0x1d165,  0x1d170,  0x0,  0x1d158,  0x1d165,  0x1d171,  0x0,  0x1d158,  0x1d165,  0x1d172,  0x0,  0x1d1b9,  0x1d165,  0x0,  0x1d1b9,  0x1d165,  0x1d16e,  0x0,  0x1d1b9,  0x1d165,  0x1d16f,  0x0,  0x1d1ba,  0x1d165,  0x0,  0x1d1ba,  0x1d165,  0x1d16e,  0x0,  0x1d1ba,  0x1d165,  0x1d16f,  0x0,  0x20122,  0x0,  0x2051c,  0x0,  0x20525,  0x0,  0x2054b,  0x0,  0x2063a,  0x0,  0x20804,  0x0,  0x208de,  0x0,  0x20a2c,  0x0,  0x20b63,  0x0,  0x214e4,  0x0,  0x216a8,  0x0,  0x216ea,  0x0,  0x219c8,  0x0,  0x21b18,  0x0,  0x21d0b,  0x0,  0x21de4,  0x0,  0x21de6,  0x0,  0x22183,  0x0,  0x2219f,  0x0,  0x22331,  0x0,  0x226d4,  0x0,  0x22844,  0x0,  0x2284a,  0x0,  0x22b0c,  0x0,  0x22bf1,  0x0,  0x2300a,  0x0,  0x232b8,  0x0,  0x2335f,  0x0,  0x23393,  0x0,  0x2339c,  0x0,  0x233c3,  0x0,  0x233d5,  0x0,  0x2346d,  0x0,  0x236a3,  0x0,  0x238a7,  0x0,  0x23a8d,  0x0,  0x23afa,  0x0,  0x23cbc,  0x0,  0x23d1e,  0x0,  0x23ed1,  0x0,  0x23f5e,  0x0,  0x23f8e,  0x0,  0x24263,  0x0,  0x242ee,  0x0,  0x243ab,  0x0,  0x24608,  0x0,  0x24735,  0x0,  0x24814,  0x0,  0x24c36,  0x0,  0x24c92,  0x0,  0x24fa1,  0x0,  0x24fb8,  0x0,  0x25044,  0x0,  0x250f2,  0x0,  0x250f3,  0x0,  0x25119,  0x0,  0x25133,  0x0,  0x25249,  0x0,  0x2541d,  0x0,  0x25626,  0x0,  0x2569a,  0x0,  0x256c5,  0x0,  0x2597c,  0x0,  0x25aa7,  0x0,  0x25bab,  0x0,  0x25c80,  0x0,  0x25cd0,  0x0,  0x25f86,  0x0,  0x261da,  0x0,  0x26228,  0x0,  0x26247,  0x0,  0x262d9,  0x0,  0x2633e,  0x0,  0x264da,  0x0,  0x26523,  0x0,  0x265a8,  0x0,  0x267a7,  0x0,  0x267b5,  0x0,  0x26b3c,  0x0,  0x26c36,  0x0,  0x26cd5,  0x0,  0x26d6b,  0x0,  0x26f2c,  0x0,  0x26fb1,  0x0,  0x270d2,  0x0,  0x273ca,  0x0,  0x27667,  0x0,  0x278ae,  0x0,  0x27966,  0x0,  0x27ca8,  0x0,  0x27ed3,  0x0,  0x27f2f,  0x0,  0x285d2,  0x0,  0x285ed,  0x0,  0x2872e,  0x0,  0x28bfa,  0x0,  0x28d77,  0x0,  0x29145,  0x0,  0x291df,  0x0,  0x2921a,  0x0,  0x2940a,  0x0,  0x29496,  0x0,  0x295b6,  0x0,  0x29b30,  0x0,  0x2a0ce,  0x0,  0x2a105,  0x0,  0x2a20e,  0x0,  0x2a291,  0x0,  0x2a392,  0x0,  0x2a600,  0x0]; return t; }
+_IDCA decompCompatTable() { static _IDCA t = [ 0x0,  0x20,  0x0,  0x20,  0x301,  0x0,  0x20,  0x303,  0x0,  0x20,  0x304,  0x0,  0x20,  0x305,  0x0,  0x20,  0x306,  0x0,  0x20,  0x307,  0x0,  0x20,  0x308,  0x0,  0x20,  0x308,  0x300,  0x0,  0x20,  0x308,  0x301,  0x0,  0x20,  0x308,  0x342,  0x0,  0x20,  0x30a,  0x0,  0x20,  0x30b,  0x0,  0x20,  0x313,  0x0,  0x20,  0x313,  0x300,  0x0,  0x20,  0x313,  0x301,  0x0,  0x20,  0x313,  0x342,  0x0,  0x20,  0x314,  0x0,  0x20,  0x314,  0x300,  0x0,  0x20,  0x314,  0x301,  0x0,  0x20,  0x314,  0x342,  0x0,  0x20,  0x327,  0x0,  0x20,  0x328,  0x0,  0x20,  0x333,  0x0,  0x20,  0x342,  0x0,  0x20,  0x345,  0x0,  0x20,  0x64b,  0x0,  0x20,  0x64c,  0x0,  0x20,  0x64c,  0x651,  0x0,  0x20,  0x64d,  0x0,  0x20,  0x64d,  0x651,  0x0,  0x20,  0x64e,  0x0,  0x20,  0x64e,  0x651,  0x0,  0x20,  0x64f,  0x0,  0x20,  0x64f,  0x651,  0x0,  0x20,  0x650,  0x0,  0x20,  0x650,  0x651,  0x0,  0x20,  0x651,  0x0,  0x20,  0x651,  0x670,  0x0,  0x20,  0x652,  0x0,  0x20,  0x3099,  0x0,  0x20,  0x309a,  0x0,  0x21,  0x0,  0x21,  0x21,  0x0,  0x21,  0x3f,  0x0,  0x22,  0x0,  0x23,  0x0,  0x24,  0x0,  0x25,  0x0,  0x26,  0x0,  0x27,  0x0,  0x28,  0x0,  0x28,  0x31,  0x29,  0x0,  0x28,  0x31,  0x30,  0x29,  0x0,  0x28,  0x31,  0x31,  0x29,  0x0,  0x28,  0x31,  0x32,  0x29,  0x0,  0x28,  0x31,  0x33,  0x29,  0x0,  0x28,  0x31,  0x34,  0x29,  0x0,  0x28,  0x31,  0x35,  0x29,  0x0,  0x28,  0x31,  0x36,  0x29,  0x0,  0x28,  0x31,  0x37,  0x29,  0x0,  0x28,  0x31,  0x38,  0x29,  0x0,  0x28,  0x31,  0x39,  0x29,  0x0,  0x28,  0x32,  0x29,  0x0,  0x28,  0x32,  0x30,  0x29,  0x0,  0x28,  0x33,  0x29,  0x0,  0x28,  0x34,  0x29,  0x0,  0x28,  0x35,  0x29,  0x0,  0x28,  0x36,  0x29,  0x0,  0x28,  0x37,  0x29,  0x0,  0x28,  0x38,  0x29,  0x0,  0x28,  0x39,  0x29,  0x0,  0x28,  0x41,  0x29,  0x0,  0x28,  0x42,  0x29,  0x0,  0x28,  0x43,  0x29,  0x0,  0x28,  0x44,  0x29,  0x0,  0x28,  0x45,  0x29,  0x0,  0x28,  0x46,  0x29,  0x0,  0x28,  0x47,  0x29,  0x0,  0x28,  0x48,  0x29,  0x0,  0x28,  0x49,  0x29,  0x0,  0x28,  0x4a,  0x29,  0x0,  0x28,  0x4b,  0x29,  0x0,  0x28,  0x4c,  0x29,  0x0,  0x28,  0x4d,  0x29,  0x0,  0x28,  0x4e,  0x29,  0x0,  0x28,  0x4f,  0x29,  0x0,  0x28,  0x50,  0x29,  0x0,  0x28,  0x51,  0x29,  0x0,  0x28,  0x52,  0x29,  0x0,  0x28,  0x53,  0x29,  0x0,  0x28,  0x54,  0x29,  0x0,  0x28,  0x55,  0x29,  0x0,  0x28,  0x56,  0x29,  0x0,  0x28,  0x57,  0x29,  0x0,  0x28,  0x58,  0x29,  0x0,  0x28,  0x59,  0x29,  0x0,  0x28,  0x5a,  0x29,  0x0,  0x28,  0x61,  0x29,  0x0,  0x28,  0x62,  0x29,  0x0,  0x28,  0x63,  0x29,  0x0,  0x28,  0x64,  0x29,  0x0,  0x28,  0x65,  0x29,  0x0,  0x28,  0x66,  0x29,  0x0,  0x28,  0x67,  0x29,  0x0,  0x28,  0x68,  0x29,  0x0,  0x28,  0x69,  0x29,  0x0,  0x28,  0x6a,  0x29,  0x0,  0x28,  0x6b,  0x29,  0x0,  0x28,  0x6c,  0x29,  0x0,  0x28,  0x6d,  0x29,  0x0,  0x28,  0x6e,  0x29,  0x0,  0x28,  0x6f,  0x29,  0x0,  0x28,  0x70,  0x29,  0x0,  0x28,  0x71,  0x29,  0x0,  0x28,  0x72,  0x29,  0x0,  0x28,  0x73,  0x29,  0x0,  0x28,  0x74,  0x29,  0x0,  0x28,  0x75,  0x29,  0x0,  0x28,  0x76,  0x29,  0x0,  0x28,  0x77,  0x29,  0x0,  0x28,  0x78,  0x29,  0x0,  0x28,  0x79,  0x29,  0x0,  0x28,  0x7a,  0x29,  0x0,  0x28,  0x1100,  0x29,  0x0,  0x28,  0x1100,  0x1161,  0x29,  0x0,  0x28,  0x1102,  0x29,  0x0,  0x28,  0x1102,  0x1161,  0x29,  0x0,  0x28,  0x1103,  0x29,  0x0,  0x28,  0x1103,  0x1161,  0x29,  0x0,  0x28,  0x1105,  0x29,  0x0,  0x28,  0x1105,  0x1161,  0x29,  0x0,  0x28,  0x1106,  0x29,  0x0,  0x28,  0x1106,  0x1161,  0x29,  0x0,  0x28,  0x1107,  0x29,  0x0,  0x28,  0x1107,  0x1161,  0x29,  0x0,  0x28,  0x1109,  0x29,  0x0,  0x28,  0x1109,  0x1161,  0x29,  0x0,  0x28,  0x110b,  0x29,  0x0,  0x28,  0x110b,  0x1161,  0x29,  0x0,  0x28,  0x110b,  0x1169,  0x110c,  0x1165,  0x11ab,  0x29,  0x0,  0x28,  0x110b,  0x1169,  0x1112,  0x116e,  0x29,  0x0,  0x28,  0x110c,  0x29,  0x0,  0x28,  0x110c,  0x1161,  0x29,  0x0,  0x28,  0x110c,  0x116e,  0x29,  0x0,  0x28,  0x110e,  0x29,  0x0,  0x28,  0x110e,  0x1161,  0x29,  0x0,  0x28,  0x110f,  0x29,  0x0,  0x28,  0x110f,  0x1161,  0x29,  0x0,  0x28,  0x1110,  0x29,  0x0,  0x28,  0x1110,  0x1161,  0x29,  0x0,  0x28,  0x1111,  0x29,  0x0,  0x28,  0x1111,  0x1161,  0x29,  0x0,  0x28,  0x1112,  0x29,  0x0,  0x28,  0x1112,  0x1161,  0x29,  0x0,  0x28,  0x4e00,  0x29,  0x0,  0x28,  0x4e03,  0x29,  0x0,  0x28,  0x4e09,  0x29,  0x0,  0x28,  0x4e5d,  0x29,  0x0,  0x28,  0x4e8c,  0x29,  0x0,  0x28,  0x4e94,  0x29,  0x0,  0x28,  0x4ee3,  0x29,  0x0,  0x28,  0x4f01,  0x29,  0x0,  0x28,  0x4f11,  0x29,  0x0,  0x28,  0x516b,  0x29,  0x0,  0x28,  0x516d,  0x29,  0x0,  0x28,  0x52b4,  0x29,  0x0,  0x28,  0x5341,  0x29,  0x0,  0x28,  0x5354,  0x29,  0x0,  0x28,  0x540d,  0x29,  0x0,  0x28,  0x547c,  0x29,  0x0,  0x28,  0x56db,  0x29,  0x0,  0x28,  0x571f,  0x29,  0x0,  0x28,  0x5b66,  0x29,  0x0,  0x28,  0x65e5,  0x29,  0x0,  0x28,  0x6708,  0x29,  0x0,  0x28,  0x6709,  0x29,  0x0,  0x28,  0x6728,  0x29,  0x0,  0x28,  0x682a,  0x29,  0x0,  0x28,  0x6c34,  0x29,  0x0,  0x28,  0x706b,  0x29,  0x0,  0x28,  0x7279,  0x29,  0x0,  0x28,  0x76e3,  0x29,  0x0,  0x28,  0x793e,  0x29,  0x0,  0x28,  0x795d,  0x29,  0x0,  0x28,  0x796d,  0x29,  0x0,  0x28,  0x81ea,  0x29,  0x0,  0x28,  0x81f3,  0x29,  0x0,  0x28,  0x8ca1,  0x29,  0x0,  0x28,  0x8cc7,  0x29,  0x0,  0x28,  0x91d1,  0x29,  0x0,  0x29,  0x0,  0x2a,  0x0,  0x2b,  0x0,  0x2c,  0x0,  0x2d,  0x0,  0x2e,  0x0,  0x2e,  0x2e,  0x0,  0x2e,  0x2e,  0x2e,  0x0,  0x2f,  0x0,  0x30,  0x0,  0x30,  0x2c,  0x0,  0x30,  0x2e,  0x0,  0x30,  0x2044,  0x33,  0x0,  0x30,  0x70b9,  0x0,  0x31,  0x0,  0x31,  0x2c,  0x0,  0x31,  0x2e,  0x0,  0x31,  0x30,  0x0,  0x31,  0x30,  0x2e,  0x0,  0x31,  0x30,  0x65e5,  0x0,  0x31,  0x30,  0x6708,  0x0,  0x31,  0x30,  0x70b9,  0x0,  0x31,  0x31,  0x0,  0x31,  0x31,  0x2e,  0x0,  0x31,  0x31,  0x65e5,  0x0,  0x31,  0x31,  0x6708,  0x0,  0x31,  0x31,  0x70b9,  0x0,  0x31,  0x32,  0x0,  0x31,  0x32,  0x2e,  0x0,  0x31,  0x32,  0x65e5,  0x0,  0x31,  0x32,  0x6708,  0x0,  0x31,  0x32,  0x70b9,  0x0,  0x31,  0x33,  0x0,  0x31,  0x33,  0x2e,  0x0,  0x31,  0x33,  0x65e5,  0x0,  0x31,  0x33,  0x70b9,  0x0,  0x31,  0x34,  0x0,  0x31,  0x34,  0x2e,  0x0,  0x31,  0x34,  0x65e5,  0x0,  0x31,  0x34,  0x70b9,  0x0,  0x31,  0x35,  0x0,  0x31,  0x35,  0x2e,  0x0,  0x31,  0x35,  0x65e5,  0x0,  0x31,  0x35,  0x70b9,  0x0,  0x31,  0x36,  0x0,  0x31,  0x36,  0x2e,  0x0,  0x31,  0x36,  0x65e5,  0x0,  0x31,  0x36,  0x70b9,  0x0,  0x31,  0x37,  0x0,  0x31,  0x37,  0x2e,  0x0,  0x31,  0x37,  0x65e5,  0x0,  0x31,  0x37,  0x70b9,  0x0,  0x31,  0x38,  0x0,  0x31,  0x38,  0x2e,  0x0,  0x31,  0x38,  0x65e5,  0x0,  0x31,  0x38,  0x70b9,  0x0,  0x31,  0x39,  0x0,  0x31,  0x39,  0x2e,  0x0,  0x31,  0x39,  0x65e5,  0x0,  0x31,  0x39,  0x70b9,  0x0,  0x31,  0x2044,  0x0,  0x31,  0x2044,  0x31,  0x30,  0x0,  0x31,  0x2044,  0x32,  0x0,  0x31,  0x2044,  0x33,  0x0,  0x31,  0x2044,  0x34,  0x0,  0x31,  0x2044,  0x35,  0x0,  0x31,  0x2044,  0x36,  0x0,  0x31,  0x2044,  0x37,  0x0,  0x31,  0x2044,  0x38,  0x0,  0x31,  0x2044,  0x39,  0x0,  0x31,  0x65e5,  0x0,  0x31,  0x6708,  0x0,  0x31,  0x70b9,  0x0,  0x32,  0x0,  0x32,  0x2c,  0x0,  0x32,  0x2e,  0x0,  0x32,  0x30,  0x0,  0x32,  0x30,  0x2e,  0x0,  0x32,  0x30,  0x65e5,  0x0,  0x32,  0x30,  0x70b9,  0x0,  0x32,  0x31,  0x0,  0x32,  0x31,  0x65e5,  0x0,  0x32,  0x31,  0x70b9,  0x0,  0x32,  0x32,  0x0,  0x32,  0x32,  0x65e5,  0x0,  0x32,  0x32,  0x70b9,  0x0,  0x32,  0x33,  0x0,  0x32,  0x33,  0x65e5,  0x0,  0x32,  0x33,  0x70b9,  0x0,  0x32,  0x34,  0x0,  0x32,  0x34,  0x65e5,  0x0,  0x32,  0x34,  0x70b9,  0x0,  0x32,  0x35,  0x0,  0x32,  0x35,  0x65e5,  0x0,  0x32,  0x36,  0x0,  0x32,  0x36,  0x65e5,  0x0,  0x32,  0x37,  0x0,  0x32,  0x37,  0x65e5,  0x0,  0x32,  0x38,  0x0,  0x32,  0x38,  0x65e5,  0x0,  0x32,  0x39,  0x0,  0x32,  0x39,  0x65e5,  0x0,  0x32,  0x2044,  0x33,  0x0,  0x32,  0x2044,  0x35,  0x0,  0x32,  0x65e5,  0x0,  0x32,  0x6708,  0x0,  0x32,  0x70b9,  0x0,  0x33,  0x0,  0x33,  0x2c,  0x0,  0x33,  0x2e,  0x0,  0x33,  0x30,  0x0,  0x33,  0x30,  0x65e5,  0x0,  0x33,  0x31,  0x0,  0x33,  0x31,  0x65e5,  0x0,  0x33,  0x32,  0x0,  0x33,  0x33,  0x0,  0x33,  0x34,  0x0,  0x33,  0x35,  0x0,  0x33,  0x36,  0x0,  0x33,  0x37,  0x0,  0x33,  0x38,  0x0,  0x33,  0x39,  0x0,  0x33,  0x2044,  0x34,  0x0,  0x33,  0x2044,  0x35,  0x0,  0x33,  0x2044,  0x38,  0x0,  0x33,  0x65e5,  0x0,  0x33,  0x6708,  0x0,  0x33,  0x70b9,  0x0,  0x34,  0x0,  0x34,  0x2c,  0x0,  0x34,  0x2e,  0x0,  0x34,  0x30,  0x0,  0x34,  0x31,  0x0,  0x34,  0x32,  0x0,  0x34,  0x33,  0x0,  0x34,  0x34,  0x0,  0x34,  0x35,  0x0,  0x34,  0x36,  0x0,  0x34,  0x37,  0x0,  0x34,  0x38,  0x0,  0x34,  0x39,  0x0,  0x34,  0x2044,  0x35,  0x0,  0x34,  0x65e5,  0x0,  0x34,  0x6708,  0x0,  0x34,  0x70b9,  0x0,  0x35,  0x0,  0x35,  0x2c,  0x0,  0x35,  0x2e,  0x0,  0x35,  0x30,  0x0,  0x35,  0x2044,  0x36,  0x0,  0x35,  0x2044,  0x38,  0x0,  0x35,  0x65e5,  0x0,  0x35,  0x6708,  0x0,  0x35,  0x70b9,  0x0,  0x36,  0x0,  0x36,  0x2c,  0x0,  0x36,  0x2e,  0x0,  0x36,  0x65e5,  0x0,  0x36,  0x6708,  0x0,  0x36,  0x70b9,  0x0,  0x37,  0x0,  0x37,  0x2c,  0x0,  0x37,  0x2e,  0x0,  0x37,  0x2044,  0x38,  0x0,  0x37,  0x65e5,  0x0,  0x37,  0x6708,  0x0,  0x37,  0x70b9,  0x0,  0x38,  0x0,  0x38,  0x2c,  0x0,  0x38,  0x2e,  0x0,  0x38,  0x65e5,  0x0,  0x38,  0x6708,  0x0,  0x38,  0x70b9,  0x0,  0x39,  0x0,  0x39,  0x2c,  0x0,  0x39,  0x2e,  0x0,  0x39,  0x65e5,  0x0,  0x39,  0x6708,  0x0,  0x39,  0x70b9,  0x0,  0x3a,  0x0,  0x3a,  0x3a,  0x3d,  0x0,  0x3b,  0x0,  0x3c,  0x0,  0x3c,  0x338,  0x0,  0x3d,  0x0,  0x3d,  0x3d,  0x0,  0x3d,  0x3d,  0x3d,  0x0,  0x3d,  0x338,  0x0,  0x3e,  0x0,  0x3e,  0x338,  0x0,  0x3f,  0x0,  0x3f,  0x21,  0x0,  0x3f,  0x3f,  0x0,  0x40,  0x0,  0x41,  0x0,  0x41,  0x55,  0x0,  0x41,  0x300,  0x0,  0x41,  0x301,  0x0,  0x41,  0x302,  0x0,  0x41,  0x302,  0x300,  0x0,  0x41,  0x302,  0x301,  0x0,  0x41,  0x302,  0x303,  0x0,  0x41,  0x302,  0x309,  0x0,  0x41,  0x303,  0x0,  0x41,  0x304,  0x0,  0x41,  0x306,  0x0,  0x41,  0x306,  0x300,  0x0,  0x41,  0x306,  0x301,  0x0,  0x41,  0x306,  0x303,  0x0,  0x41,  0x306,  0x309,  0x0,  0x41,  0x307,  0x0,  0x41,  0x307,  0x304,  0x0,  0x41,  0x308,  0x0,  0x41,  0x308,  0x304,  0x0,  0x41,  0x309,  0x0,  0x41,  0x30a,  0x0,  0x41,  0x30a,  0x301,  0x0,  0x41,  0x30c,  0x0,  0x41,  0x30f,  0x0,  0x41,  0x311,  0x0,  0x41,  0x323,  0x0,  0x41,  0x323,  0x302,  0x0,  0x41,  0x323,  0x306,  0x0,  0x41,  0x325,  0x0,  0x41,  0x328,  0x0,  0x41,  0x2215,  0x6d,  0x0,  0x42,  0x0,  0x42,  0x71,  0x0,  0x42,  0x307,  0x0,  0x42,  0x323,  0x0,  0x42,  0x331,  0x0,  0x43,  0x0,  0x43,  0x44,  0x0,  0x43,  0x6f,  0x2e,  0x0,  0x43,  0x301,  0x0,  0x43,  0x302,  0x0,  0x43,  0x307,  0x0,  0x43,  0x30c,  0x0,  0x43,  0x327,  0x0,  0x43,  0x327,  0x301,  0x0,  0x43,  0x2215,  0x6b,  0x67,  0x0,  0x44,  0x0,  0x44,  0x4a,  0x0,  0x44,  0x5a,  0x0,  0x44,  0x5a,  0x30c,  0x0,  0x44,  0x7a,  0x0,  0x44,  0x7a,  0x30c,  0x0,  0x44,  0x307,  0x0,  0x44,  0x30c,  0x0,  0x44,  0x323,  0x0,  0x44,  0x327,  0x0,  0x44,  0x32d,  0x0,  0x44,  0x331,  0x0,  0x45,  0x0,  0x45,  0x300,  0x0,  0x45,  0x301,  0x0,  0x45,  0x302,  0x0,  0x45,  0x302,  0x300,  0x0,  0x45,  0x302,  0x301,  0x0,  0x45,  0x302,  0x303,  0x0,  0x45,  0x302,  0x309,  0x0,  0x45,  0x303,  0x0,  0x45,  0x304,  0x0,  0x45,  0x304,  0x300,  0x0,  0x45,  0x304,  0x301,  0x0,  0x45,  0x306,  0x0,  0x45,  0x307,  0x0,  0x45,  0x308,  0x0,  0x45,  0x309,  0x0,  0x45,  0x30c,  0x0,  0x45,  0x30f,  0x0,  0x45,  0x311,  0x0,  0x45,  0x323,  0x0,  0x45,  0x323,  0x302,  0x0,  0x45,  0x327,  0x0,  0x45,  0x327,  0x306,  0x0,  0x45,  0x328,  0x0,  0x45,  0x32d,  0x0,  0x45,  0x330,  0x0,  0x46,  0x0,  0x46,  0x41,  0x58,  0x0,  0x46,  0x307,  0x0,  0x47,  0x0,  0x47,  0x42,  0x0,  0x47,  0x48,  0x7a,  0x0,  0x47,  0x50,  0x61,  0x0,  0x47,  0x79,  0x0,  0x47,  0x301,  0x0,  0x47,  0x302,  0x0,  0x47,  0x304,  0x0,  0x47,  0x306,  0x0,  0x47,  0x307,  0x0,  0x47,  0x30c,  0x0,  0x47,  0x327,  0x0,  0x48,  0x0,  0x48,  0x50,  0x0,  0x48,  0x56,  0x0,  0x48,  0x67,  0x0,  0x48,  0x7a,  0x0,  0x48,  0x302,  0x0,  0x48,  0x307,  0x0,  0x48,  0x308,  0x0,  0x48,  0x30c,  0x0,  0x48,  0x323,  0x0,  0x48,  0x327,  0x0,  0x48,  0x32e,  0x0,  0x49,  0x0,  0x49,  0x49,  0x0,  0x49,  0x49,  0x49,  0x0,  0x49,  0x4a,  0x0,  0x49,  0x55,  0x0,  0x49,  0x56,  0x0,  0x49,  0x58,  0x0,  0x49,  0x300,  0x0,  0x49,  0x301,  0x0,  0x49,  0x302,  0x0,  0x49,  0x303,  0x0,  0x49,  0x304,  0x0,  0x49,  0x306,  0x0,  0x49,  0x307,  0x0,  0x49,  0x308,  0x0,  0x49,  0x308,  0x301,  0x0,  0x49,  0x309,  0x0,  0x49,  0x30c,  0x0,  0x49,  0x30f,  0x0,  0x49,  0x311,  0x0,  0x49,  0x323,  0x0,  0x49,  0x328,  0x0,  0x49,  0x330,  0x0,  0x4a,  0x0,  0x4a,  0x302,  0x0,  0x4b,  0x0,  0x4b,  0x42,  0x0,  0x4b,  0x4b,  0x0,  0x4b,  0x4d,  0x0,  0x4b,  0x301,  0x0,  0x4b,  0x30c,  0x0,  0x4b,  0x323,  0x0,  0x4b,  0x327,  0x0,  0x4b,  0x331,  0x0,  0x4c,  0x0,  0x4c,  0x4a,  0x0,  0x4c,  0x54,  0x44,  0x0,  0x4c,  0x6a,  0x0,  0x4c,  0xb7,  0x0,  0x4c,  0x301,  0x0,  0x4c,  0x30c,  0x0,  0x4c,  0x323,  0x0,  0x4c,  0x323,  0x304,  0x0,  0x4c,  0x327,  0x0,  0x4c,  0x32d,  0x0,  0x4c,  0x331,  0x0,  0x4d,  0x0,  0x4d,  0x42,  0x0,  0x4d,  0x43,  0x0,  0x4d,  0x44,  0x0,  0x4d,  0x48,  0x7a,  0x0,  0x4d,  0x50,  0x61,  0x0,  0x4d,  0x56,  0x0,  0x4d,  0x57,  0x0,  0x4d,  0x301,  0x0,  0x4d,  0x307,  0x0,  0x4d,  0x323,  0x0,  0x4d,  0x3a9,  0x0,  0x4e,  0x0,  0x4e,  0x4a,  0x0,  0x4e,  0x6a,  0x0,  0x4e,  0x6f,  0x0,  0x4e,  0x300,  0x0,  0x4e,  0x301,  0x0,  0x4e,  0x303,  0x0,  0x4e,  0x307,  0x0,  0x4e,  0x30c,  0x0,  0x4e,  0x323,  0x0,  0x4e,  0x327,  0x0,  0x4e,  0x32d,  0x0,  0x4e,  0x331,  0x0,  0x4f,  0x0,  0x4f,  0x300,  0x0,  0x4f,  0x301,  0x0,  0x4f,  0x302,  0x0,  0x4f,  0x302,  0x300,  0x0,  0x4f,  0x302,  0x301,  0x0,  0x4f,  0x302,  0x303,  0x0,  0x4f,  0x302,  0x309,  0x0,  0x4f,  0x303,  0x0,  0x4f,  0x303,  0x301,  0x0,  0x4f,  0x303,  0x304,  0x0,  0x4f,  0x303,  0x308,  0x0,  0x4f,  0x304,  0x0,  0x4f,  0x304,  0x300,  0x0,  0x4f,  0x304,  0x301,  0x0,  0x4f,  0x306,  0x0,  0x4f,  0x307,  0x0,  0x4f,  0x307,  0x304,  0x0,  0x4f,  0x308,  0x0,  0x4f,  0x308,  0x304,  0x0,  0x4f,  0x309,  0x0,  0x4f,  0x30b,  0x0,  0x4f,  0x30c,  0x0,  0x4f,  0x30f,  0x0,  0x4f,  0x311,  0x0,  0x4f,  0x31b,  0x0,  0x4f,  0x31b,  0x300,  0x0,  0x4f,  0x31b,  0x301,  0x0,  0x4f,  0x31b,  0x303,  0x0,  0x4f,  0x31b,  0x309,  0x0,  0x4f,  0x31b,  0x323,  0x0,  0x4f,  0x323,  0x0,  0x4f,  0x323,  0x302,  0x0,  0x4f,  0x328,  0x0,  0x4f,  0x328,  0x304,  0x0,  0x50,  0x0,  0x50,  0x48,  0x0,  0x50,  0x50,  0x4d,  0x0,  0x50,  0x50,  0x56,  0x0,  0x50,  0x52,  0x0,  0x50,  0x54,  0x45,  0x0,  0x50,  0x61,  0x0,  0x50,  0x301,  0x0,  0x50,  0x307,  0x0,  0x51,  0x0,  0x52,  0x0,  0x52,  0x73,  0x0,  0x52,  0x301,  0x0,  0x52,  0x307,  0x0,  0x52,  0x30c,  0x0,  0x52,  0x30f,  0x0,  0x52,  0x311,  0x0,  0x52,  0x323,  0x0,  0x52,  0x323,  0x304,  0x0,  0x52,  0x327,  0x0,  0x52,  0x331,  0x0,  0x53,  0x0,  0x53,  0x44,  0x0,  0x53,  0x4d,  0x0,  0x53,  0x53,  0x0,  0x53,  0x76,  0x0,  0x53,  0x301,  0x0,  0x53,  0x301,  0x307,  0x0,  0x53,  0x302,  0x0,  0x53,  0x307,  0x0,  0x53,  0x30c,  0x0,  0x53,  0x30c,  0x307,  0x0,  0x53,  0x323,  0x0,  0x53,  0x323,  0x307,  0x0,  0x53,  0x326,  0x0,  0x53,  0x327,  0x0,  0x54,  0x0,  0x54,  0x45,  0x4c,  0x0,  0x54,  0x48,  0x7a,  0x0,  0x54,  0x4d,  0x0,  0x54,  0x307,  0x0,  0x54,  0x30c,  0x0,  0x54,  0x323,  0x0,  0x54,  0x326,  0x0,  0x54,  0x327,  0x0,  0x54,  0x32d,  0x0,  0x54,  0x331,  0x0,  0x55,  0x0,  0x55,  0x300,  0x0,  0x55,  0x301,  0x0,  0x55,  0x302,  0x0,  0x55,  0x303,  0x0,  0x55,  0x303,  0x301,  0x0,  0x55,  0x304,  0x0,  0x55,  0x304,  0x308,  0x0,  0x55,  0x306,  0x0,  0x55,  0x308,  0x0,  0x55,  0x308,  0x300,  0x0,  0x55,  0x308,  0x301,  0x0,  0x55,  0x308,  0x304,  0x0,  0x55,  0x308,  0x30c,  0x0,  0x55,  0x309,  0x0,  0x55,  0x30a,  0x0,  0x55,  0x30b,  0x0,  0x55,  0x30c,  0x0,  0x55,  0x30f,  0x0,  0x55,  0x311,  0x0,  0x55,  0x31b,  0x0,  0x55,  0x31b,  0x300,  0x0,  0x55,  0x31b,  0x301,  0x0,  0x55,  0x31b,  0x303,  0x0,  0x55,  0x31b,  0x309,  0x0,  0x55,  0x31b,  0x323,  0x0,  0x55,  0x323,  0x0,  0x55,  0x324,  0x0,  0x55,  0x328,  0x0,  0x55,  0x32d,  0x0,  0x55,  0x330,  0x0,  0x56,  0x0,  0x56,  0x49,  0x0,  0x56,  0x49,  0x49,  0x0,  0x56,  0x49,  0x49,  0x49,  0x0,  0x56,  0x303,  0x0,  0x56,  0x323,  0x0,  0x56,  0x2215,  0x6d,  0x0,  0x57,  0x0,  0x57,  0x43,  0x0,  0x57,  0x5a,  0x0,  0x57,  0x62,  0x0,  0x57,  0x300,  0x0,  0x57,  0x301,  0x0,  0x57,  0x302,  0x0,  0x57,  0x307,  0x0,  0x57,  0x308,  0x0,  0x57,  0x323,  0x0,  0x58,  0x0,  0x58,  0x49,  0x0,  0x58,  0x49,  0x49,  0x0,  0x58,  0x307,  0x0,  0x58,  0x308,  0x0,  0x59,  0x0,  0x59,  0x300,  0x0,  0x59,  0x301,  0x0,  0x59,  0x302,  0x0,  0x59,  0x303,  0x0,  0x59,  0x304,  0x0,  0x59,  0x307,  0x0,  0x59,  0x308,  0x0,  0x59,  0x309,  0x0,  0x59,  0x323,  0x0,  0x5a,  0x0,  0x5a,  0x301,  0x0,  0x5a,  0x302,  0x0,  0x5a,  0x307,  0x0,  0x5a,  0x30c,  0x0,  0x5a,  0x323,  0x0,  0x5a,  0x331,  0x0,  0x5b,  0x0,  0x5c,  0x0,  0x5d,  0x0,  0x5e,  0x0,  0x5f,  0x0,  0x60,  0x0,  0x61,  0x0,  0x61,  0x2e,  0x6d,  0x2e,  0x0,  0x61,  0x2f,  0x63,  0x0,  0x61,  0x2f,  0x73,  0x0,  0x61,  0x2be,  0x0,  0x61,  0x300,  0x0,  0x61,  0x301,  0x0,  0x61,  0x302,  0x0,  0x61,  0x302,  0x300,  0x0,  0x61,  0x302,  0x301,  0x0,  0x61,  0x302,  0x303,  0x0,  0x61,  0x302,  0x309,  0x0,  0x61,  0x303,  0x0,  0x61,  0x304,  0x0,  0x61,  0x306,  0x0,  0x61,  0x306,  0x300,  0x0,  0x61,  0x306,  0x301,  0x0,  0x61,  0x306,  0x303,  0x0,  0x61,  0x306,  0x309,  0x0,  0x61,  0x307,  0x0,  0x61,  0x307,  0x304,  0x0,  0x61,  0x308,  0x0,  0x61,  0x308,  0x304,  0x0,  0x61,  0x309,  0x0,  0x61,  0x30a,  0x0,  0x61,  0x30a,  0x301,  0x0,  0x61,  0x30c,  0x0,  0x61,  0x30f,  0x0,  0x61,  0x311,  0x0,  0x61,  0x323,  0x0,  0x61,  0x323,  0x302,  0x0,  0x61,  0x323,  0x306,  0x0,  0x61,  0x325,  0x0,  0x61,  0x328,  0x0,  0x62,  0x0,  0x62,  0x61,  0x72,  0x0,  0x62,  0x307,  0x0,  0x62,  0x323,  0x0,  0x62,  0x331,  0x0,  0x63,  0x0,  0x63,  0x2f,  0x6f,  0x0,  0x63,  0x2f,  0x75,  0x0,  0x63,  0x61,  0x6c,  0x0,  0x63,  0x63,  0x0,  0x63,  0x64,  0x0,  0x63,  0x6d,  0x0,  0x63,  0x6d,  0x32,  0x0,  0x63,  0x6d,  0x33,  0x0,  0x63,  0x301,  0x0,  0x63,  0x302,  0x0,  0x63,  0x307,  0x0,  0x63,  0x30c,  0x0,  0x63,  0x327,  0x0,  0x63,  0x327,  0x301,  0x0,  0x64,  0x0,  0x64,  0x42,  0x0,  0x64,  0x61,  0x0,  0x64,  0x6c,  0x0,  0x64,  0x6d,  0x0,  0x64,  0x6d,  0x32,  0x0,  0x64,  0x6d,  0x33,  0x0,  0x64,  0x7a,  0x0,  0x64,  0x7a,  0x30c,  0x0,  0x64,  0x307,  0x0,  0x64,  0x30c,  0x0,  0x64,  0x323,  0x0,  0x64,  0x327,  0x0,  0x64,  0x32d,  0x0,  0x64,  0x331,  0x0,  0x65,  0x0,  0x65,  0x56,  0x0,  0x65,  0x72,  0x67,  0x0,  0x65,  0x300,  0x0,  0x65,  0x301,  0x0,  0x65,  0x302,  0x0,  0x65,  0x302,  0x300,  0x0,  0x65,  0x302,  0x301,  0x0,  0x65,  0x302,  0x303,  0x0,  0x65,  0x302,  0x309,  0x0,  0x65,  0x303,  0x0,  0x65,  0x304,  0x0,  0x65,  0x304,  0x300,  0x0,  0x65,  0x304,  0x301,  0x0,  0x65,  0x306,  0x0,  0x65,  0x307,  0x0,  0x65,  0x308,  0x0,  0x65,  0x309,  0x0,  0x65,  0x30c,  0x0,  0x65,  0x30f,  0x0,  0x65,  0x311,  0x0,  0x65,  0x323,  0x0,  0x65,  0x323,  0x302,  0x0,  0x65,  0x327,  0x0,  0x65,  0x327,  0x306,  0x0,  0x65,  0x328,  0x0,  0x65,  0x32d,  0x0,  0x65,  0x330,  0x0,  0x66,  0x0,  0x66,  0x66,  0x0,  0x66,  0x66,  0x69,  0x0,  0x66,  0x66,  0x6c,  0x0,  0x66,  0x69,  0x0,  0x66,  0x6c,  0x0,  0x66,  0x6d,  0x0,  0x66,  0x307,  0x0,  0x67,  0x0,  0x67,  0x61,  0x6c,  0x0,  0x67,  0x301,  0x0,  0x67,  0x302,  0x0,  0x67,  0x304,  0x0,  0x67,  0x306,  0x0,  0x67,  0x307,  0x0,  0x67,  0x30c,  0x0,  0x67,  0x327,  0x0,  0x68,  0x0,  0x68,  0x50,  0x61,  0x0,  0x68,  0x61,  0x0,  0x68,  0x302,  0x0,  0x68,  0x307,  0x0,  0x68,  0x308,  0x0,  0x68,  0x30c,  0x0,  0x68,  0x323,  0x0,  0x68,  0x327,  0x0,  0x68,  0x32e,  0x0,  0x68,  0x331,  0x0,  0x69,  0x0,  0x69,  0x69,  0x0,  0x69,  0x69,  0x69,  0x0,  0x69,  0x6a,  0x0,  0x69,  0x6e,  0x0,  0x69,  0x76,  0x0,  0x69,  0x78,  0x0,  0x69,  0x300,  0x0,  0x69,  0x301,  0x0,  0x69,  0x302,  0x0,  0x69,  0x303,  0x0,  0x69,  0x304,  0x0,  0x69,  0x306,  0x0,  0x69,  0x308,  0x0,  0x69,  0x308,  0x301,  0x0,  0x69,  0x309,  0x0,  0x69,  0x30c,  0x0,  0x69,  0x30f,  0x0,  0x69,  0x311,  0x0,  0x69,  0x323,  0x0,  0x69,  0x328,  0x0,  0x69,  0x330,  0x0,  0x6a,  0x0,  0x6a,  0x302,  0x0,  0x6a,  0x30c,  0x0,  0x6b,  0x0,  0x6b,  0x41,  0x0,  0x6b,  0x48,  0x7a,  0x0,  0x6b,  0x50,  0x61,  0x0,  0x6b,  0x56,  0x0,  0x6b,  0x57,  0x0,  0x6b,  0x63,  0x61,  0x6c,  0x0,  0x6b,  0x67,  0x0,  0x6b,  0x6c,  0x0,  0x6b,  0x6d,  0x0,  0x6b,  0x6d,  0x32,  0x0,  0x6b,  0x6d,  0x33,  0x0,  0x6b,  0x74,  0x0,  0x6b,  0x301,  0x0,  0x6b,  0x30c,  0x0,  0x6b,  0x323,  0x0,  0x6b,  0x327,  0x0,  0x6b,  0x331,  0x0,  0x6b,  0x3a9,  0x0,  0x6c,  0x0,  0x6c,  0x6a,  0x0,  0x6c,  0x6d,  0x0,  0x6c,  0x6e,  0x0,  0x6c,  0x6f,  0x67,  0x0,  0x6c,  0x78,  0x0,  0x6c,  0xb7,  0x0,  0x6c,  0x301,  0x0,  0x6c,  0x30c,  0x0,  0x6c,  0x323,  0x0,  0x6c,  0x323,  0x304,  0x0,  0x6c,  0x327,  0x0,  0x6c,  0x32d,  0x0,  0x6c,  0x331,  0x0,  0x6d,  0x0,  0x6d,  0x32,  0x0,  0x6d,  0x33,  0x0,  0x6d,  0x41,  0x0,  0x6d,  0x56,  0x0,  0x6d,  0x57,  0x0,  0x6d,  0x62,  0x0,  0x6d,  0x67,  0x0,  0x6d,  0x69,  0x6c,  0x0,  0x6d,  0x6c,  0x0,  0x6d,  0x6d,  0x0,  0x6d,  0x6d,  0x32,  0x0,  0x6d,  0x6d,  0x33,  0x0,  0x6d,  0x6f,  0x6c,  0x0,  0x6d,  0x73,  0x0,  0x6d,  0x301,  0x0,  0x6d,  0x307,  0x0,  0x6d,  0x323,  0x0,  0x6d,  0x2215,  0x73,  0x0,  0x6d,  0x2215,  0x73,  0x32,  0x0,  0x6e,  0x0,  0x6e,  0x41,  0x0,  0x6e,  0x46,  0x0,  0x6e,  0x56,  0x0,  0x6e,  0x57,  0x0,  0x6e,  0x6a,  0x0,  0x6e,  0x6d,  0x0,  0x6e,  0x73,  0x0,  0x6e,  0x300,  0x0,  0x6e,  0x301,  0x0,  0x6e,  0x303,  0x0,  0x6e,  0x307,  0x0,  0x6e,  0x30c,  0x0,  0x6e,  0x323,  0x0,  0x6e,  0x327,  0x0,  0x6e,  0x32d,  0x0,  0x6e,  0x331,  0x0,  0x6f,  0x0,  0x6f,  0x56,  0x0,  0x6f,  0x300,  0x0,  0x6f,  0x301,  0x0,  0x6f,  0x302,  0x0,  0x6f,  0x302,  0x300,  0x0,  0x6f,  0x302,  0x301,  0x0,  0x6f,  0x302,  0x303,  0x0,  0x6f,  0x302,  0x309,  0x0,  0x6f,  0x303,  0x0,  0x6f,  0x303,  0x301,  0x0,  0x6f,  0x303,  0x304,  0x0,  0x6f,  0x303,  0x308,  0x0,  0x6f,  0x304,  0x0,  0x6f,  0x304,  0x300,  0x0,  0x6f,  0x304,  0x301,  0x0,  0x6f,  0x306,  0x0,  0x6f,  0x307,  0x0,  0x6f,  0x307,  0x304,  0x0,  0x6f,  0x308,  0x0,  0x6f,  0x308,  0x304,  0x0,  0x6f,  0x309,  0x0,  0x6f,  0x30b,  0x0,  0x6f,  0x30c,  0x0,  0x6f,  0x30f,  0x0,  0x6f,  0x311,  0x0,  0x6f,  0x31b,  0x0,  0x6f,  0x31b,  0x300,  0x0,  0x6f,  0x31b,  0x301,  0x0,  0x6f,  0x31b,  0x303,  0x0,  0x6f,  0x31b,  0x309,  0x0,  0x6f,  0x31b,  0x323,  0x0,  0x6f,  0x323,  0x0,  0x6f,  0x323,  0x302,  0x0,  0x6f,  0x328,  0x0,  0x6f,  0x328,  0x304,  0x0,  0x70,  0x0,  0x70,  0x2e,  0x6d,  0x2e,  0x0,  0x70,  0x41,  0x0,  0x70,  0x46,  0x0,  0x70,  0x56,  0x0,  0x70,  0x57,  0x0,  0x70,  0x63,  0x0,  0x70,  0x73,  0x0,  0x70,  0x301,  0x0,  0x70,  0x307,  0x0,  0x71,  0x0,  0x72,  0x0,  0x72,  0x61,  0x64,  0x0,  0x72,  0x61,  0x64,  0x2215,  0x73,  0x0,  0x72,  0x61,  0x64,  0x2215,  0x73,  0x32,  0x0,  0x72,  0x301,  0x0,  0x72,  0x307,  0x0,  0x72,  0x30c,  0x0,  0x72,  0x30f,  0x0,  0x72,  0x311,  0x0,  0x72,  0x323,  0x0,  0x72,  0x323,  0x304,  0x0,  0x72,  0x327,  0x0,  0x72,  0x331,  0x0,  0x73,  0x0,  0x73,  0x72,  0x0,  0x73,  0x74,  0x0,  0x73,  0x301,  0x0,  0x73,  0x301,  0x307,  0x0,  0x73,  0x302,  0x0,  0x73,  0x307,  0x0,  0x73,  0x30c,  0x0,  0x73,  0x30c,  0x307,  0x0,  0x73,  0x323,  0x0,  0x73,  0x323,  0x307,  0x0,  0x73,  0x326,  0x0,  0x73,  0x327,  0x0,  0x74,  0x0,  0x74,  0x307,  0x0,  0x74,  0x308,  0x0,  0x74,  0x30c,  0x0,  0x74,  0x323,  0x0,  0x74,  0x326,  0x0,  0x74,  0x327,  0x0,  0x74,  0x32d,  0x0,  0x74,  0x331,  0x0,  0x75,  0x0,  0x75,  0x300,  0x0,  0x75,  0x301,  0x0,  0x75,  0x302,  0x0,  0x75,  0x303,  0x0,  0x75,  0x303,  0x301,  0x0,  0x75,  0x304,  0x0,  0x75,  0x304,  0x308,  0x0,  0x75,  0x306,  0x0,  0x75,  0x308,  0x0,  0x75,  0x308,  0x300,  0x0,  0x75,  0x308,  0x301,  0x0,  0x75,  0x308,  0x304,  0x0,  0x75,  0x308,  0x30c,  0x0,  0x75,  0x309,  0x0,  0x75,  0x30a,  0x0,  0x75,  0x30b,  0x0,  0x75,  0x30c,  0x0,  0x75,  0x30f,  0x0,  0x75,  0x311,  0x0,  0x75,  0x31b,  0x0,  0x75,  0x31b,  0x300,  0x0,  0x75,  0x31b,  0x301,  0x0,  0x75,  0x31b,  0x303,  0x0,  0x75,  0x31b,  0x309,  0x0,  0x75,  0x31b,  0x323,  0x0,  0x75,  0x323,  0x0,  0x75,  0x324,  0x0,  0x75,  0x328,  0x0,  0x75,  0x32d,  0x0,  0x75,  0x330,  0x0,  0x76,  0x0,  0x76,  0x69,  0x0,  0x76,  0x69,  0x69,  0x0,  0x76,  0x69,  0x69,  0x69,  0x0,  0x76,  0x303,  0x0,  0x76,  0x323,  0x0,  0x77,  0x0,  0x77,  0x300,  0x0,  0x77,  0x301,  0x0,  0x77,  0x302,  0x0,  0x77,  0x307,  0x0,  0x77,  0x308,  0x0,  0x77,  0x30a,  0x0,  0x77,  0x323,  0x0,  0x78,  0x0,  0x78,  0x69,  0x0,  0x78,  0x69,  0x69,  0x0,  0x78,  0x307,  0x0,  0x78,  0x308,  0x0,  0x79,  0x0,  0x79,  0x300,  0x0,  0x79,  0x301,  0x0,  0x79,  0x302,  0x0,  0x79,  0x303,  0x0,  0x79,  0x304,  0x0,  0x79,  0x307,  0x0,  0x79,  0x308,  0x0,  0x79,  0x309,  0x0,  0x79,  0x30a,  0x0,  0x79,  0x323,  0x0,  0x7a,  0x0,  0x7a,  0x301,  0x0,  0x7a,  0x302,  0x0,  0x7a,  0x307,  0x0,  0x7a,  0x30c,  0x0,  0x7a,  0x323,  0x0,  0x7a,  0x331,  0x0,  0x7b,  0x0,  0x7c,  0x0,  0x7d,  0x0,  0x7e,  0x0,  0xa2,  0x0,  0xa3,  0x0,  0xa5,  0x0,  0xa6,  0x0,  0xac,  0x0,  0xb0,  0x43,  0x0,  0xb0,  0x46,  0x0,  0xb7,  0x0,  0xc6,  0x0,  0xc6,  0x301,  0x0,  0xc6,  0x304,  0x0,  0xd8,  0x301,  0x0,  0xe6,  0x301,  0x0,  0xe6,  0x304,  0x0,  0xf0,  0x0,  0xf8,  0x301,  0x0,  0x126,  0x0,  0x127,  0x0,  0x131,  0x0,  0x14b,  0x0,  0x153,  0x0,  0x18e,  0x0,  0x190,  0x0,  0x1ab,  0x0,  0x1b7,  0x30c,  0x0,  0x222,  0x0,  0x237,  0x0,  0x250,  0x0,  0x251,  0x0,  0x252,  0x0,  0x254,  0x0,  0x255,  0x0,  0x259,  0x0,  0x25b,  0x0,  0x25c,  0x0,  0x25f,  0x0,  0x261,  0x0,  0x263,  0x0,  0x265,  0x0,  0x266,  0x0,  0x268,  0x0,  0x269,  0x0,  0x26a,  0x0,  0x26d,  0x0,  0x26f,  0x0,  0x270,  0x0,  0x271,  0x0,  0x272,  0x0,  0x273,  0x0,  0x274,  0x0,  0x275,  0x0,  0x278,  0x0,  0x279,  0x0,  0x27b,  0x0,  0x281,  0x0,  0x282,  0x0,  0x283,  0x0,  0x289,  0x0,  0x28a,  0x0,  0x28b,  0x0,  0x28c,  0x0,  0x290,  0x0,  0x291,  0x0,  0x292,  0x0,  0x292,  0x30c,  0x0,  0x295,  0x0,  0x29d,  0x0,  0x29f,  0x0,  0x2b9,  0x0,  0x2bc,  0x6e,  0x0,  0x300,  0x0,  0x301,  0x0,  0x308,  0x301,  0x0,  0x313,  0x0,  0x391,  0x0,  0x391,  0x300,  0x0,  0x391,  0x301,  0x0,  0x391,  0x304,  0x0,  0x391,  0x306,  0x0,  0x391,  0x313,  0x0,  0x391,  0x313,  0x300,  0x0,  0x391,  0x313,  0x300,  0x345,  0x0,  0x391,  0x313,  0x301,  0x0,  0x391,  0x313,  0x301,  0x345,  0x0,  0x391,  0x313,  0x342,  0x0,  0x391,  0x313,  0x342,  0x345,  0x0,  0x391,  0x313,  0x345,  0x0,  0x391,  0x314,  0x0,  0x391,  0x314,  0x300,  0x0,  0x391,  0x314,  0x300,  0x345,  0x0,  0x391,  0x314,  0x301,  0x0,  0x391,  0x314,  0x301,  0x345,  0x0,  0x391,  0x314,  0x342,  0x0,  0x391,  0x314,  0x342,  0x345,  0x0,  0x391,  0x314,  0x345,  0x0,  0x391,  0x345,  0x0,  0x392,  0x0,  0x393,  0x0,  0x394,  0x0,  0x395,  0x0,  0x395,  0x300,  0x0,  0x395,  0x301,  0x0,  0x395,  0x313,  0x0,  0x395,  0x313,  0x300,  0x0,  0x395,  0x313,  0x301,  0x0,  0x395,  0x314,  0x0,  0x395,  0x314,  0x300,  0x0,  0x395,  0x314,  0x301,  0x0,  0x396,  0x0,  0x397,  0x0,  0x397,  0x300,  0x0,  0x397,  0x301,  0x0,  0x397,  0x313,  0x0,  0x397,  0x313,  0x300,  0x0,  0x397,  0x313,  0x300,  0x345,  0x0,  0x397,  0x313,  0x301,  0x0,  0x397,  0x313,  0x301,  0x345,  0x0,  0x397,  0x313,  0x342,  0x0,  0x397,  0x313,  0x342,  0x345,  0x0,  0x397,  0x313,  0x345,  0x0,  0x397,  0x314,  0x0,  0x397,  0x314,  0x300,  0x0,  0x397,  0x314,  0x300,  0x345,  0x0,  0x397,  0x314,  0x301,  0x0,  0x397,  0x314,  0x301,  0x345,  0x0,  0x397,  0x314,  0x342,  0x0,  0x397,  0x314,  0x342,  0x345,  0x0,  0x397,  0x314,  0x345,  0x0,  0x397,  0x345,  0x0,  0x398,  0x0,  0x399,  0x0,  0x399,  0x300,  0x0,  0x399,  0x301,  0x0,  0x399,  0x304,  0x0,  0x399,  0x306,  0x0,  0x399,  0x308,  0x0,  0x399,  0x313,  0x0,  0x399,  0x313,  0x300,  0x0,  0x399,  0x313,  0x301,  0x0,  0x399,  0x313,  0x342,  0x0,  0x399,  0x314,  0x0,  0x399,  0x314,  0x300,  0x0,  0x399,  0x314,  0x301,  0x0,  0x399,  0x314,  0x342,  0x0,  0x39a,  0x0,  0x39b,  0x0,  0x39c,  0x0,  0x39d,  0x0,  0x39e,  0x0,  0x39f,  0x0,  0x39f,  0x300,  0x0,  0x39f,  0x301,  0x0,  0x39f,  0x313,  0x0,  0x39f,  0x313,  0x300,  0x0,  0x39f,  0x313,  0x301,  0x0,  0x39f,  0x314,  0x0,  0x39f,  0x314,  0x300,  0x0,  0x39f,  0x314,  0x301,  0x0,  0x3a0,  0x0,  0x3a1,  0x0,  0x3a1,  0x314,  0x0,  0x3a3,  0x0,  0x3a4,  0x0,  0x3a5,  0x0,  0x3a5,  0x300,  0x0,  0x3a5,  0x301,  0x0,  0x3a5,  0x304,  0x0,  0x3a5,  0x306,  0x0,  0x3a5,  0x308,  0x0,  0x3a5,  0x314,  0x0,  0x3a5,  0x314,  0x300,  0x0,  0x3a5,  0x314,  0x301,  0x0,  0x3a5,  0x314,  0x342,  0x0,  0x3a6,  0x0,  0x3a7,  0x0,  0x3a8,  0x0,  0x3a9,  0x0,  0x3a9,  0x300,  0x0,  0x3a9,  0x301,  0x0,  0x3a9,  0x313,  0x0,  0x3a9,  0x313,  0x300,  0x0,  0x3a9,  0x313,  0x300,  0x345,  0x0,  0x3a9,  0x313,  0x301,  0x0,  0x3a9,  0x313,  0x301,  0x345,  0x0,  0x3a9,  0x313,  0x342,  0x0,  0x3a9,  0x313,  0x342,  0x345,  0x0,  0x3a9,  0x313,  0x345,  0x0,  0x3a9,  0x314,  0x0,  0x3a9,  0x314,  0x300,  0x0,  0x3a9,  0x314,  0x300,  0x345,  0x0,  0x3a9,  0x314,  0x301,  0x0,  0x3a9,  0x314,  0x301,  0x345,  0x0,  0x3a9,  0x314,  0x342,  0x0,  0x3a9,  0x314,  0x342,  0x345,  0x0,  0x3a9,  0x314,  0x345,  0x0,  0x3a9,  0x345,  0x0,  0x3b1,  0x0,  0x3b1,  0x300,  0x0,  0x3b1,  0x300,  0x345,  0x0,  0x3b1,  0x301,  0x0,  0x3b1,  0x301,  0x345,  0x0,  0x3b1,  0x304,  0x0,  0x3b1,  0x306,  0x0,  0x3b1,  0x313,  0x0,  0x3b1,  0x313,  0x300,  0x0,  0x3b1,  0x313,  0x300,  0x345,  0x0,  0x3b1,  0x313,  0x301,  0x0,  0x3b1,  0x313,  0x301,  0x345,  0x0,  0x3b1,  0x313,  0x342,  0x0,  0x3b1,  0x313,  0x342,  0x345,  0x0,  0x3b1,  0x313,  0x345,  0x0,  0x3b1,  0x314,  0x0,  0x3b1,  0x314,  0x300,  0x0,  0x3b1,  0x314,  0x300,  0x345,  0x0,  0x3b1,  0x314,  0x301,  0x0,  0x3b1,  0x314,  0x301,  0x345,  0x0,  0x3b1,  0x314,  0x342,  0x0,  0x3b1,  0x314,  0x342,  0x345,  0x0,  0x3b1,  0x314,  0x345,  0x0,  0x3b1,  0x342,  0x0,  0x3b1,  0x342,  0x345,  0x0,  0x3b1,  0x345,  0x0,  0x3b2,  0x0,  0x3b3,  0x0,  0x3b4,  0x0,  0x3b5,  0x0,  0x3b5,  0x300,  0x0,  0x3b5,  0x301,  0x0,  0x3b5,  0x313,  0x0,  0x3b5,  0x313,  0x300,  0x0,  0x3b5,  0x313,  0x301,  0x0,  0x3b5,  0x314,  0x0,  0x3b5,  0x314,  0x300,  0x0,  0x3b5,  0x314,  0x301,  0x0,  0x3b6,  0x0,  0x3b7,  0x0,  0x3b7,  0x300,  0x0,  0x3b7,  0x300,  0x345,  0x0,  0x3b7,  0x301,  0x0,  0x3b7,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x0,  0x3b7,  0x313,  0x300,  0x0,  0x3b7,  0x313,  0x300,  0x345,  0x0,  0x3b7,  0x313,  0x301,  0x0,  0x3b7,  0x313,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x342,  0x0,  0x3b7,  0x313,  0x342,  0x345,  0x0,  0x3b7,  0x313,  0x345,  0x0,  0x3b7,  0x314,  0x0,  0x3b7,  0x314,  0x300,  0x0,  0x3b7,  0x314,  0x300,  0x345,  0x0,  0x3b7,  0x314,  0x301,  0x0,  0x3b7,  0x314,  0x301,  0x345,  0x0,  0x3b7,  0x314,  0x342,  0x0,  0x3b7,  0x314,  0x342,  0x345,  0x0,  0x3b7,  0x314,  0x345,  0x0,  0x3b7,  0x342,  0x0,  0x3b7,  0x342,  0x345,  0x0,  0x3b7,  0x345,  0x0,  0x3b8,  0x0,  0x3b9,  0x0,  0x3b9,  0x300,  0x0,  0x3b9,  0x301,  0x0,  0x3b9,  0x304,  0x0,  0x3b9,  0x306,  0x0,  0x3b9,  0x308,  0x0,  0x3b9,  0x308,  0x300,  0x0,  0x3b9,  0x308,  0x301,  0x0,  0x3b9,  0x308,  0x342,  0x0,  0x3b9,  0x313,  0x0,  0x3b9,  0x313,  0x300,  0x0,  0x3b9,  0x313,  0x301,  0x0,  0x3b9,  0x313,  0x342,  0x0,  0x3b9,  0x314,  0x0,  0x3b9,  0x314,  0x300,  0x0,  0x3b9,  0x314,  0x301,  0x0,  0x3b9,  0x314,  0x342,  0x0,  0x3b9,  0x342,  0x0,  0x3ba,  0x0,  0x3bb,  0x0,  0x3bc,  0x0,  0x3bc,  0x41,  0x0,  0x3bc,  0x46,  0x0,  0x3bc,  0x56,  0x0,  0x3bc,  0x57,  0x0,  0x3bc,  0x67,  0x0,  0x3bc,  0x6c,  0x0,  0x3bc,  0x6d,  0x0,  0x3bc,  0x73,  0x0,  0x3bd,  0x0,  0x3be,  0x0,  0x3bf,  0x0,  0x3bf,  0x300,  0x0,  0x3bf,  0x301,  0x0,  0x3bf,  0x313,  0x0,  0x3bf,  0x313,  0x300,  0x0,  0x3bf,  0x313,  0x301,  0x0,  0x3bf,  0x314,  0x0,  0x3bf,  0x314,  0x300,  0x0,  0x3bf,  0x314,  0x301,  0x0,  0x3c0,  0x0,  0x3c1,  0x0,  0x3c1,  0x313,  0x0,  0x3c1,  0x314,  0x0,  0x3c2,  0x0,  0x3c3,  0x0,  0x3c4,  0x0,  0x3c5,  0x0,  0x3c5,  0x300,  0x0,  0x3c5,  0x301,  0x0,  0x3c5,  0x304,  0x0,  0x3c5,  0x306,  0x0,  0x3c5,  0x308,  0x0,  0x3c5,  0x308,  0x300,  0x0,  0x3c5,  0x308,  0x301,  0x0,  0x3c5,  0x308,  0x342,  0x0,  0x3c5,  0x313,  0x0,  0x3c5,  0x313,  0x300,  0x0,  0x3c5,  0x313,  0x301,  0x0,  0x3c5,  0x313,  0x342,  0x0,  0x3c5,  0x314,  0x0,  0x3c5,  0x314,  0x300,  0x0,  0x3c5,  0x314,  0x301,  0x0,  0x3c5,  0x314,  0x342,  0x0,  0x3c5,  0x342,  0x0,  0x3c6,  0x0,  0x3c7,  0x0,  0x3c8,  0x0,  0x3c9,  0x0,  0x3c9,  0x300,  0x0,  0x3c9,  0x300,  0x345,  0x0,  0x3c9,  0x301,  0x0,  0x3c9,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x0,  0x3c9,  0x313,  0x300,  0x0,  0x3c9,  0x313,  0x300,  0x345,  0x0,  0x3c9,  0x313,  0x301,  0x0,  0x3c9,  0x313,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x342,  0x0,  0x3c9,  0x313,  0x342,  0x345,  0x0,  0x3c9,  0x313,  0x345,  0x0,  0x3c9,  0x314,  0x0,  0x3c9,  0x314,  0x300,  0x0,  0x3c9,  0x314,  0x300,  0x345,  0x0,  0x3c9,  0x314,  0x301,  0x0,  0x3c9,  0x314,  0x301,  0x345,  0x0,  0x3c9,  0x314,  0x342,  0x0,  0x3c9,  0x314,  0x342,  0x345,  0x0,  0x3c9,  0x314,  0x345,  0x0,  0x3c9,  0x342,  0x0,  0x3c9,  0x342,  0x345,  0x0,  0x3c9,  0x345,  0x0,  0x3dc,  0x0,  0x3dd,  0x0,  0x406,  0x308,  0x0,  0x410,  0x306,  0x0,  0x410,  0x308,  0x0,  0x413,  0x301,  0x0,  0x415,  0x300,  0x0,  0x415,  0x306,  0x0,  0x415,  0x308,  0x0,  0x416,  0x306,  0x0,  0x416,  0x308,  0x0,  0x417,  0x308,  0x0,  0x418,  0x300,  0x0,  0x418,  0x304,  0x0,  0x418,  0x306,  0x0,  0x418,  0x308,  0x0,  0x41a,  0x301,  0x0,  0x41e,  0x308,  0x0,  0x423,  0x304,  0x0,  0x423,  0x306,  0x0,  0x423,  0x308,  0x0,  0x423,  0x30b,  0x0,  0x427,  0x308,  0x0,  0x42b,  0x308,  0x0,  0x42d,  0x308,  0x0,  0x430,  0x306,  0x0,  0x430,  0x308,  0x0,  0x433,  0x301,  0x0,  0x435,  0x300,  0x0,  0x435,  0x306,  0x0,  0x435,  0x308,  0x0,  0x436,  0x306,  0x0,  0x436,  0x308,  0x0,  0x437,  0x308,  0x0,  0x438,  0x300,  0x0,  0x438,  0x304,  0x0,  0x438,  0x306,  0x0,  0x438,  0x308,  0x0,  0x43a,  0x301,  0x0,  0x43d,  0x0,  0x43e,  0x308,  0x0,  0x443,  0x304,  0x0,  0x443,  0x306,  0x0,  0x443,  0x308,  0x0,  0x443,  0x30b,  0x0,  0x447,  0x308,  0x0,  0x44b,  0x308,  0x0,  0x44d,  0x308,  0x0,  0x456,  0x308,  0x0,  0x474,  0x30f,  0x0,  0x475,  0x30f,  0x0,  0x4d8,  0x308,  0x0,  0x4d9,  0x308,  0x0,  0x4e8,  0x308,  0x0,  0x4e9,  0x308,  0x0,  0x565,  0x582,  0x0,  0x574,  0x565,  0x0,  0x574,  0x56b,  0x0,  0x574,  0x56d,  0x0,  0x574,  0x576,  0x0,  0x57e,  0x576,  0x0,  0x5d0,  0x0,  0x5d0,  0x5b7,  0x0,  0x5d0,  0x5b8,  0x0,  0x5d0,  0x5bc,  0x0,  0x5d0,  0x5dc,  0x0,  0x5d1,  0x0,  0x5d1,  0x5bc,  0x0,  0x5d1,  0x5bf,  0x0,  0x5d2,  0x0,  0x5d2,  0x5bc,  0x0,  0x5d3,  0x0,  0x5d3,  0x5bc,  0x0,  0x5d4,  0x0,  0x5d4,  0x5bc,  0x0,  0x5d5,  0x5b9,  0x0,  0x5d5,  0x5bc,  0x0,  0x5d6,  0x5bc,  0x0,  0x5d8,  0x5bc,  0x0,  0x5d9,  0x5b4,  0x0,  0x5d9,  0x5bc,  0x0,  0x5da,  0x5bc,  0x0,  0x5db,  0x0,  0x5db,  0x5bc,  0x0,  0x5db,  0x5bf,  0x0,  0x5dc,  0x0,  0x5dc,  0x5bc,  0x0,  0x5dd,  0x0,  0x5de,  0x5bc,  0x0,  0x5e0,  0x5bc,  0x0,  0x5e1,  0x5bc,  0x0,  0x5e2,  0x0,  0x5e3,  0x5bc,  0x0,  0x5e4,  0x5bc,  0x0,  0x5e4,  0x5bf,  0x0,  0x5e6,  0x5bc,  0x0,  0x5e7,  0x5bc,  0x0,  0x5e8,  0x0,  0x5e8,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x5c1,  0x0,  0x5e9,  0x5bc,  0x5c2,  0x0,  0x5e9,  0x5c1,  0x0,  0x5e9,  0x5c2,  0x0,  0x5ea,  0x0,  0x5ea,  0x5bc,  0x0,  0x5f2,  0x5b7,  0x0,  0x621,  0x0,  0x627,  0x0,  0x627,  0x643,  0x628,  0x631,  0x0,  0x627,  0x644,  0x644,  0x647,  0x0,  0x627,  0x64b,  0x0,  0x627,  0x653,  0x0,  0x627,  0x654,  0x0,  0x627,  0x655,  0x0,  0x627,  0x674,  0x0,  0x628,  0x0,  0x628,  0x62c,  0x0,  0x628,  0x62d,  0x0,  0x628,  0x62d,  0x64a,  0x0,  0x628,  0x62e,  0x0,  0x628,  0x62e,  0x64a,  0x0,  0x628,  0x631,  0x0,  0x628,  0x632,  0x0,  0x628,  0x645,  0x0,  0x628,  0x646,  0x0,  0x628,  0x647,  0x0,  0x628,  0x649,  0x0,  0x628,  0x64a,  0x0,  0x629,  0x0,  0x62a,  0x0,  0x62a,  0x62c,  0x0,  0x62a,  0x62c,  0x645,  0x0,  0x62a,  0x62c,  0x649,  0x0,  0x62a,  0x62c,  0x64a,  0x0,  0x62a,  0x62d,  0x0,  0x62a,  0x62d,  0x62c,  0x0,  0x62a,  0x62d,  0x645,  0x0,  0x62a,  0x62e,  0x0,  0x62a,  0x62e,  0x645,  0x0,  0x62a,  0x62e,  0x649,  0x0,  0x62a,  0x62e,  0x64a,  0x0,  0x62a,  0x631,  0x0,  0x62a,  0x632,  0x0,  0x62a,  0x645,  0x0,  0x62a,  0x645,  0x62c,  0x0,  0x62a,  0x645,  0x62d,  0x0,  0x62a,  0x645,  0x62e,  0x0,  0x62a,  0x645,  0x649,  0x0,  0x62a,  0x645,  0x64a,  0x0,  0x62a,  0x646,  0x0,  0x62a,  0x647,  0x0,  0x62a,  0x649,  0x0,  0x62a,  0x64a,  0x0,  0x62b,  0x0,  0x62b,  0x62c,  0x0,  0x62b,  0x631,  0x0,  0x62b,  0x632,  0x0,  0x62b,  0x645,  0x0,  0x62b,  0x646,  0x0,  0x62b,  0x647,  0x0,  0x62b,  0x649,  0x0,  0x62b,  0x64a,  0x0,  0x62c,  0x0,  0x62c,  0x62d,  0x0,  0x62c,  0x62d,  0x649,  0x0,  0x62c,  0x62d,  0x64a,  0x0,  0x62c,  0x644,  0x20,  0x62c,  0x644,  0x627,  0x644,  0x647,  0x0,  0x62c,  0x645,  0x0,  0x62c,  0x645,  0x62d,  0x0,  0x62c,  0x645,  0x649,  0x0,  0x62c,  0x645,  0x64a,  0x0,  0x62c,  0x649,  0x0,  0x62c,  0x64a,  0x0,  0x62d,  0x0,  0x62d,  0x62c,  0x0,  0x62d,  0x62c,  0x64a,  0x0,  0x62d,  0x645,  0x0,  0x62d,  0x645,  0x649,  0x0,  0x62d,  0x645,  0x64a,  0x0,  0x62d,  0x649,  0x0,  0x62d,  0x64a,  0x0,  0x62e,  0x0,  0x62e,  0x62c,  0x0,  0x62e,  0x62d,  0x0,  0x62e,  0x645,  0x0,  0x62e,  0x649,  0x0,  0x62e,  0x64a,  0x0,  0x62f,  0x0,  0x630,  0x0,  0x630,  0x670,  0x0,  0x631,  0x0,  0x631,  0x633,  0x648,  0x644,  0x0,  0x631,  0x670,  0x0,  0x631,  0x6cc,  0x627,  0x644,  0x0,  0x632,  0x0,  0x633,  0x0,  0x633,  0x62c,  0x0,  0x633,  0x62c,  0x62d,  0x0,  0x633,  0x62c,  0x649,  0x0,  0x633,  0x62d,  0x0,  0x633,  0x62d,  0x62c,  0x0,  0x633,  0x62e,  0x0,  0x633,  0x62e,  0x649,  0x0,  0x633,  0x62e,  0x64a,  0x0,  0x633,  0x631,  0x0,  0x633,  0x645,  0x0,  0x633,  0x645,  0x62c,  0x0,  0x633,  0x645,  0x62d,  0x0,  0x633,  0x645,  0x645,  0x0,  0x633,  0x647,  0x0,  0x633,  0x649,  0x0,  0x633,  0x64a,  0x0,  0x634,  0x0,  0x634,  0x62c,  0x0,  0x634,  0x62c,  0x64a,  0x0,  0x634,  0x62d,  0x0,  0x634,  0x62d,  0x645,  0x0,  0x634,  0x62d,  0x64a,  0x0,  0x634,  0x62e,  0x0,  0x634,  0x631,  0x0,  0x634,  0x645,  0x0,  0x634,  0x645,  0x62e,  0x0,  0x634,  0x645,  0x645,  0x0,  0x634,  0x647,  0x0,  0x634,  0x649,  0x0,  0x634,  0x64a,  0x0,  0x635,  0x0,  0x635,  0x62d,  0x0,  0x635,  0x62d,  0x62d,  0x0,  0x635,  0x62d,  0x64a,  0x0,  0x635,  0x62e,  0x0,  0x635,  0x631,  0x0,  0x635,  0x644,  0x639,  0x645,  0x0,  0x635,  0x644,  0x649,  0x0,  0x635,  0x644,  0x649,  0x20,  0x627,  0x644,  0x644,  0x647,  0x20,  0x639,  0x644,  0x64a,  0x647,  0x20,  0x648,  0x633,  0x644,  0x645,  0x0,  0x635,  0x644,  0x6d2,  0x0,  0x635,  0x645,  0x0,  0x635,  0x645,  0x645,  0x0,  0x635,  0x649,  0x0,  0x635,  0x64a,  0x0,  0x636,  0x0,  0x636,  0x62c,  0x0,  0x636,  0x62d,  0x0,  0x636,  0x62d,  0x649,  0x0,  0x636,  0x62d,  0x64a,  0x0,  0x636,  0x62e,  0x0,  0x636,  0x62e,  0x645,  0x0,  0x636,  0x631,  0x0,  0x636,  0x645,  0x0,  0x636,  0x649,  0x0,  0x636,  0x64a,  0x0,  0x637,  0x0,  0x637,  0x62d,  0x0,  0x637,  0x645,  0x0,  0x637,  0x645,  0x62d,  0x0,  0x637,  0x645,  0x645,  0x0,  0x637,  0x645,  0x64a,  0x0,  0x637,  0x649,  0x0,  0x637,  0x64a,  0x0,  0x638,  0x0,  0x638,  0x645,  0x0,  0x639,  0x0,  0x639,  0x62c,  0x0,  0x639,  0x62c,  0x645,  0x0,  0x639,  0x644,  0x64a,  0x647,  0x0,  0x639,  0x645,  0x0,  0x639,  0x645,  0x645,  0x0,  0x639,  0x645,  0x649,  0x0,  0x639,  0x645,  0x64a,  0x0,  0x639,  0x649,  0x0,  0x639,  0x64a,  0x0,  0x63a,  0x0,  0x63a,  0x62c,  0x0,  0x63a,  0x645,  0x0,  0x63a,  0x645,  0x645,  0x0,  0x63a,  0x645,  0x649,  0x0,  0x63a,  0x645,  0x64a,  0x0,  0x63a,  0x649,  0x0,  0x63a,  0x64a,  0x0,  0x640,  0x64b,  0x0,  0x640,  0x64e,  0x0,  0x640,  0x64e,  0x651,  0x0,  0x640,  0x64f,  0x0,  0x640,  0x64f,  0x651,  0x0,  0x640,  0x650,  0x0,  0x640,  0x650,  0x651,  0x0,  0x640,  0x651,  0x0,  0x640,  0x652,  0x0,  0x641,  0x0,  0x641,  0x62c,  0x0,  0x641,  0x62d,  0x0,  0x641,  0x62e,  0x0,  0x641,  0x62e,  0x645,  0x0,  0x641,  0x645,  0x0,  0x641,  0x645,  0x64a,  0x0,  0x641,  0x649,  0x0,  0x641,  0x64a,  0x0,  0x642,  0x0,  0x642,  0x62d,  0x0,  0x642,  0x644,  0x6d2,  0x0,  0x642,  0x645,  0x0,  0x642,  0x645,  0x62d,  0x0,  0x642,  0x645,  0x645,  0x0,  0x642,  0x645,  0x64a,  0x0,  0x642,  0x649,  0x0,  0x642,  0x64a,  0x0,  0x643,  0x0,  0x643,  0x627,  0x0,  0x643,  0x62c,  0x0,  0x643,  0x62d,  0x0,  0x643,  0x62e,  0x0,  0x643,  0x644,  0x0,  0x643,  0x645,  0x0,  0x643,  0x645,  0x645,  0x0,  0x643,  0x645,  0x64a,  0x0,  0x643,  0x649,  0x0,  0x643,  0x64a,  0x0,  0x644,  0x0,  0x644,  0x627,  0x0,  0x644,  0x627,  0x653,  0x0,  0x644,  0x627,  0x654,  0x0,  0x644,  0x627,  0x655,  0x0,  0x644,  0x62c,  0x0,  0x644,  0x62c,  0x62c,  0x0,  0x644,  0x62c,  0x645,  0x0,  0x644,  0x62c,  0x64a,  0x0,  0x644,  0x62d,  0x0,  0x644,  0x62d,  0x645,  0x0,  0x644,  0x62d,  0x649,  0x0,  0x644,  0x62d,  0x64a,  0x0,  0x644,  0x62e,  0x0,  0x644,  0x62e,  0x645,  0x0,  0x644,  0x645,  0x0,  0x644,  0x645,  0x62d,  0x0,  0x644,  0x645,  0x64a,  0x0,  0x644,  0x647,  0x0,  0x644,  0x649,  0x0,  0x644,  0x64a,  0x0,  0x645,  0x0,  0x645,  0x627,  0x0,  0x645,  0x62c,  0x0,  0x645,  0x62c,  0x62d,  0x0,  0x645,  0x62c,  0x62e,  0x0,  0x645,  0x62c,  0x645,  0x0,  0x645,  0x62c,  0x64a,  0x0,  0x645,  0x62d,  0x0,  0x645,  0x62d,  0x62c,  0x0,  0x645,  0x62d,  0x645,  0x0,  0x645,  0x62d,  0x645,  0x62f,  0x0,  0x645,  0x62d,  0x64a,  0x0,  0x645,  0x62e,  0x0,  0x645,  0x62e,  0x62c,  0x0,  0x645,  0x62e,  0x645,  0x0,  0x645,  0x62e,  0x64a,  0x0,  0x645,  0x645,  0x0,  0x645,  0x645,  0x64a,  0x0,  0x645,  0x649,  0x0,  0x645,  0x64a,  0x0,  0x646,  0x0,  0x646,  0x62c,  0x0,  0x646,  0x62c,  0x62d,  0x0,  0x646,  0x62c,  0x645,  0x0,  0x646,  0x62c,  0x649,  0x0,  0x646,  0x62c,  0x64a,  0x0,  0x646,  0x62d,  0x0,  0x646,  0x62d,  0x645,  0x0,  0x646,  0x62d,  0x649,  0x0,  0x646,  0x62d,  0x64a,  0x0,  0x646,  0x62e,  0x0,  0x646,  0x631,  0x0,  0x646,  0x632,  0x0,  0x646,  0x645,  0x0,  0x646,  0x645,  0x649,  0x0,  0x646,  0x645,  0x64a,  0x0,  0x646,  0x646,  0x0,  0x646,  0x647,  0x0,  0x646,  0x649,  0x0,  0x646,  0x64a,  0x0,  0x647,  0x0,  0x647,  0x62c,  0x0,  0x647,  0x645,  0x0,  0x647,  0x645,  0x62c,  0x0,  0x647,  0x645,  0x645,  0x0,  0x647,  0x649,  0x0,  0x647,  0x64a,  0x0,  0x647,  0x670,  0x0,  0x648,  0x0,  0x648,  0x633,  0x644,  0x645,  0x0,  0x648,  0x654,  0x0,  0x648,  0x674,  0x0,  0x649,  0x0,  0x649,  0x670,  0x0,  0x64a,  0x0,  0x64a,  0x62c,  0x0,  0x64a,  0x62c,  0x64a,  0x0,  0x64a,  0x62d,  0x0,  0x64a,  0x62d,  0x64a,  0x0,  0x64a,  0x62e,  0x0,  0x64a,  0x631,  0x0,  0x64a,  0x632,  0x0,  0x64a,  0x645,  0x0,  0x64a,  0x645,  0x645,  0x0,  0x64a,  0x645,  0x64a,  0x0,  0x64a,  0x646,  0x0,  0x64a,  0x647,  0x0,  0x64a,  0x649,  0x0,  0x64a,  0x64a,  0x0,  0x64a,  0x654,  0x0,  0x64a,  0x654,  0x627,  0x0,  0x64a,  0x654,  0x62c,  0x0,  0x64a,  0x654,  0x62d,  0x0,  0x64a,  0x654,  0x62e,  0x0,  0x64a,  0x654,  0x631,  0x0,  0x64a,  0x654,  0x632,  0x0,  0x64a,  0x654,  0x645,  0x0,  0x64a,  0x654,  0x646,  0x0,  0x64a,  0x654,  0x647,  0x0,  0x64a,  0x654,  0x648,  0x0,  0x64a,  0x654,  0x649,  0x0,  0x64a,  0x654,  0x64a,  0x0,  0x64a,  0x654,  0x6c6,  0x0,  0x64a,  0x654,  0x6c7,  0x0,  0x64a,  0x654,  0x6c8,  0x0,  0x64a,  0x654,  0x6d0,  0x0,  0x64a,  0x654,  0x6d5,  0x0,  0x64a,  0x674,  0x0,  0x66e,  0x0,  0x66f,  0x0,  0x671,  0x0,  0x679,  0x0,  0x67a,  0x0,  0x67b,  0x0,  0x67e,  0x0,  0x67f,  0x0,  0x680,  0x0,  0x683,  0x0,  0x684,  0x0,  0x686,  0x0,  0x687,  0x0,  0x688,  0x0,  0x68c,  0x0,  0x68d,  0x0,  0x68e,  0x0,  0x691,  0x0,  0x698,  0x0,  0x6a1,  0x0,  0x6a4,  0x0,  0x6a6,  0x0,  0x6a9,  0x0,  0x6ad,  0x0,  0x6af,  0x0,  0x6b1,  0x0,  0x6b3,  0x0,  0x6ba,  0x0,  0x6bb,  0x0,  0x6be,  0x0,  0x6c1,  0x0,  0x6c1,  0x654,  0x0,  0x6c5,  0x0,  0x6c6,  0x0,  0x6c7,  0x0,  0x6c7,  0x674,  0x0,  0x6c8,  0x0,  0x6c9,  0x0,  0x6cb,  0x0,  0x6cc,  0x0,  0x6d0,  0x0,  0x6d2,  0x0,  0x6d2,  0x654,  0x0,  0x6d5,  0x654,  0x0,  0x915,  0x93c,  0x0,  0x916,  0x93c,  0x0,  0x917,  0x93c,  0x0,  0x91c,  0x93c,  0x0,  0x921,  0x93c,  0x0,  0x922,  0x93c,  0x0,  0x928,  0x93c,  0x0,  0x92b,  0x93c,  0x0,  0x92f,  0x93c,  0x0,  0x930,  0x93c,  0x0,  0x933,  0x93c,  0x0,  0x9a1,  0x9bc,  0x0,  0x9a2,  0x9bc,  0x0,  0x9af,  0x9bc,  0x0,  0x9c7,  0x9be,  0x0,  0x9c7,  0x9d7,  0x0,  0xa16,  0xa3c,  0x0,  0xa17,  0xa3c,  0x0,  0xa1c,  0xa3c,  0x0,  0xa2b,  0xa3c,  0x0,  0xa32,  0xa3c,  0x0,  0xa38,  0xa3c,  0x0,  0xb21,  0xb3c,  0x0,  0xb22,  0xb3c,  0x0,  0xb47,  0xb3e,  0x0,  0xb47,  0xb56,  0x0,  0xb47,  0xb57,  0x0,  0xb92,  0xbd7,  0x0,  0xbc6,  0xbbe,  0x0,  0xbc6,  0xbd7,  0x0,  0xbc7,  0xbbe,  0x0,  0xc46,  0xc56,  0x0,  0xcbf,  0xcd5,  0x0,  0xcc6,  0xcc2,  0x0,  0xcc6,  0xcc2,  0xcd5,  0x0,  0xcc6,  0xcd5,  0x0,  0xcc6,  0xcd6,  0x0,  0xd46,  0xd3e,  0x0,  0xd46,  0xd57,  0x0,  0xd47,  0xd3e,  0x0,  0xdd9,  0xdca,  0x0,  0xdd9,  0xdcf,  0x0,  0xdd9,  0xdcf,  0xdca,  0x0,  0xdd9,  0xddf,  0x0,  0xe4d,  0xe32,  0x0,  0xeab,  0xe99,  0x0,  0xeab,  0xea1,  0x0,  0xecd,  0xeb2,  0x0,  0xf0b,  0x0,  0xf40,  0xfb5,  0x0,  0xf42,  0xfb7,  0x0,  0xf4c,  0xfb7,  0x0,  0xf51,  0xfb7,  0x0,  0xf56,  0xfb7,  0x0,  0xf5b,  0xfb7,  0x0,  0xf71,  0xf72,  0x0,  0xf71,  0xf74,  0x0,  0xf71,  0xf80,  0x0,  0xf90,  0xfb5,  0x0,  0xf92,  0xfb7,  0x0,  0xf9c,  0xfb7,  0x0,  0xfa1,  0xfb7,  0x0,  0xfa6,  0xfb7,  0x0,  0xfab,  0xfb7,  0x0,  0xfb2,  0xf71,  0xf80,  0x0,  0xfb2,  0xf80,  0x0,  0xfb3,  0xf71,  0xf80,  0x0,  0xfb3,  0xf80,  0x0,  0x1025,  0x102e,  0x0,  0x10dc,  0x0,  0x1100,  0x0,  0x1100,  0x1161,  0x0,  0x1101,  0x0,  0x1102,  0x0,  0x1102,  0x1161,  0x0,  0x1103,  0x0,  0x1103,  0x1161,  0x0,  0x1104,  0x0,  0x1105,  0x0,  0x1105,  0x1161,  0x0,  0x1106,  0x0,  0x1106,  0x1161,  0x0,  0x1107,  0x0,  0x1107,  0x1161,  0x0,  0x1108,  0x0,  0x1109,  0x0,  0x1109,  0x1161,  0x0,  0x110a,  0x0,  0x110b,  0x0,  0x110b,  0x1161,  0x0,  0x110b,  0x116e,  0x0,  0x110c,  0x0,  0x110c,  0x1161,  0x0,  0x110c,  0x116e,  0x110b,  0x1174,  0x0,  0x110d,  0x0,  0x110e,  0x0,  0x110e,  0x1161,  0x0,  0x110e,  0x1161,  0x11b7,  0x1100,  0x1169,  0x0,  0x110f,  0x0,  0x110f,  0x1161,  0x0,  0x1110,  0x0,  0x1110,  0x1161,  0x0,  0x1111,  0x0,  0x1111,  0x1161,  0x0,  0x1112,  0x0,  0x1112,  0x1161,  0x0,  0x1114,  0x0,  0x1115,  0x0,  0x111a,  0x0,  0x111c,  0x0,  0x111d,  0x0,  0x111e,  0x0,  0x1120,  0x0,  0x1121,  0x0,  0x1122,  0x0,  0x1123,  0x0,  0x1127,  0x0,  0x1129,  0x0,  0x112b,  0x0,  0x112c,  0x0,  0x112d,  0x0,  0x112e,  0x0,  0x112f,  0x0,  0x1132,  0x0,  0x1136,  0x0,  0x1140,  0x0,  0x1147,  0x0,  0x114c,  0x0,  0x1157,  0x0,  0x1158,  0x0,  0x1159,  0x0,  0x1160,  0x0,  0x1161,  0x0,  0x1162,  0x0,  0x1163,  0x0,  0x1164,  0x0,  0x1165,  0x0,  0x1166,  0x0,  0x1167,  0x0,  0x1168,  0x0,  0x1169,  0x0,  0x116a,  0x0,  0x116b,  0x0,  0x116c,  0x0,  0x116d,  0x0,  0x116e,  0x0,  0x116f,  0x0,  0x1170,  0x0,  0x1171,  0x0,  0x1172,  0x0,  0x1173,  0x0,  0x1174,  0x0,  0x1175,  0x0,  0x1184,  0x0,  0x1185,  0x0,  0x1188,  0x0,  0x1191,  0x0,  0x1192,  0x0,  0x1194,  0x0,  0x119e,  0x0,  0x11a1,  0x0,  0x11aa,  0x0,  0x11ac,  0x0,  0x11ad,  0x0,  0x11b0,  0x0,  0x11b1,  0x0,  0x11b2,  0x0,  0x11b3,  0x0,  0x11b4,  0x0,  0x11b5,  0x0,  0x11c7,  0x0,  0x11c8,  0x0,  0x11cc,  0x0,  0x11ce,  0x0,  0x11d3,  0x0,  0x11d7,  0x0,  0x11d9,  0x0,  0x11dd,  0x0,  0x11df,  0x0,  0x11f1,  0x0,  0x11f2,  0x0,  0x1b05,  0x1b35,  0x0,  0x1b07,  0x1b35,  0x0,  0x1b09,  0x1b35,  0x0,  0x1b0b,  0x1b35,  0x0,  0x1b0d,  0x1b35,  0x0,  0x1b11,  0x1b35,  0x0,  0x1b3a,  0x1b35,  0x0,  0x1b3c,  0x1b35,  0x0,  0x1b3e,  0x1b35,  0x0,  0x1b3f,  0x1b35,  0x0,  0x1b42,  0x1b35,  0x0,  0x1d02,  0x0,  0x1d16,  0x0,  0x1d17,  0x0,  0x1d1c,  0x0,  0x1d1d,  0x0,  0x1d25,  0x0,  0x1d7b,  0x0,  0x1d85,  0x0,  0x2010,  0x0,  0x2013,  0x0,  0x2014,  0x0,  0x2032,  0x2032,  0x0,  0x2032,  0x2032,  0x2032,  0x0,  0x2032,  0x2032,  0x2032,  0x2032,  0x0,  0x2035,  0x2035,  0x0,  0x2035,  0x2035,  0x2035,  0x0,  0x20a9,  0x0,  0x2190,  0x0,  0x2190,  0x338,  0x0,  0x2191,  0x0,  0x2192,  0x0,  0x2192,  0x338,  0x0,  0x2193,  0x0,  0x2194,  0x338,  0x0,  0x21d0,  0x338,  0x0,  0x21d2,  0x338,  0x0,  0x21d4,  0x338,  0x0,  0x2202,  0x0,  0x2203,  0x338,  0x0,  0x2207,  0x0,  0x2208,  0x338,  0x0,  0x220b,  0x338,  0x0,  0x2211,  0x0,  0x2212,  0x0,  0x2223,  0x338,  0x0,  0x2225,  0x338,  0x0,  0x222b,  0x222b,  0x0,  0x222b,  0x222b,  0x222b,  0x0,  0x222b,  0x222b,  0x222b,  0x222b,  0x0,  0x222e,  0x222e,  0x0,  0x222e,  0x222e,  0x222e,  0x0,  0x223c,  0x338,  0x0,  0x2243,  0x338,  0x0,  0x2245,  0x338,  0x0,  0x2248,  0x338,  0x0,  0x224d,  0x338,  0x0,  0x2261,  0x338,  0x0,  0x2264,  0x338,  0x0,  0x2265,  0x338,  0x0,  0x2272,  0x338,  0x0,  0x2273,  0x338,  0x0,  0x2276,  0x338,  0x0,  0x2277,  0x338,  0x0,  0x227a,  0x338,  0x0,  0x227b,  0x338,  0x0,  0x227c,  0x338,  0x0,  0x227d,  0x338,  0x0,  0x2282,  0x338,  0x0,  0x2283,  0x338,  0x0,  0x2286,  0x338,  0x0,  0x2287,  0x338,  0x0,  0x2291,  0x338,  0x0,  0x2292,  0x338,  0x0,  0x22a2,  0x338,  0x0,  0x22a8,  0x338,  0x0,  0x22a9,  0x338,  0x0,  0x22ab,  0x338,  0x0,  0x22b2,  0x338,  0x0,  0x22b3,  0x338,  0x0,  0x22b4,  0x338,  0x0,  0x22b5,  0x338,  0x0,  0x2502,  0x0,  0x25a0,  0x0,  0x25cb,  0x0,  0x2985,  0x0,  0x2986,  0x0,  0x2add,  0x338,  0x0,  0x2d61,  0x0,  0x3001,  0x0,  0x3002,  0x0,  0x3008,  0x0,  0x3009,  0x0,  0x300a,  0x0,  0x300b,  0x0,  0x300c,  0x0,  0x300d,  0x0,  0x300e,  0x0,  0x300f,  0x0,  0x3010,  0x0,  0x3011,  0x0,  0x3012,  0x0,  0x3014,  0x0,  0x3014,  0x53,  0x3015,  0x0,  0x3014,  0x4e09,  0x3015,  0x0,  0x3014,  0x4e8c,  0x3015,  0x0,  0x3014,  0x52dd,  0x3015,  0x0,  0x3014,  0x5b89,  0x3015,  0x0,  0x3014,  0x6253,  0x3015,  0x0,  0x3014,  0x6557,  0x3015,  0x0,  0x3014,  0x672c,  0x3015,  0x0,  0x3014,  0x70b9,  0x3015,  0x0,  0x3014,  0x76d7,  0x3015,  0x0,  0x3015,  0x0,  0x3016,  0x0,  0x3017,  0x0,  0x3046,  0x3099,  0x0,  0x304b,  0x3099,  0x0,  0x304d,  0x3099,  0x0,  0x304f,  0x3099,  0x0,  0x3051,  0x3099,  0x0,  0x3053,  0x3099,  0x0,  0x3055,  0x3099,  0x0,  0x3057,  0x3099,  0x0,  0x3059,  0x3099,  0x0,  0x305b,  0x3099,  0x0,  0x305d,  0x3099,  0x0,  0x305f,  0x3099,  0x0,  0x3061,  0x3099,  0x0,  0x3064,  0x3099,  0x0,  0x3066,  0x3099,  0x0,  0x3068,  0x3099,  0x0,  0x306f,  0x3099,  0x0,  0x306f,  0x309a,  0x0,  0x3072,  0x3099,  0x0,  0x3072,  0x309a,  0x0,  0x3075,  0x3099,  0x0,  0x3075,  0x309a,  0x0,  0x3078,  0x3099,  0x0,  0x3078,  0x309a,  0x0,  0x307b,  0x304b,  0x0,  0x307b,  0x3099,  0x0,  0x307b,  0x309a,  0x0,  0x3088,  0x308a,  0x0,  0x3099,  0x0,  0x309a,  0x0,  0x309d,  0x3099,  0x0,  0x30a1,  0x0,  0x30a2,  0x0,  0x30a2,  0x30cf,  0x309a,  0x30fc,  0x30c8,  0x0,  0x30a2,  0x30eb,  0x30d5,  0x30a1,  0x0,  0x30a2,  0x30f3,  0x30d8,  0x309a,  0x30a2,  0x0,  0x30a2,  0x30fc,  0x30eb,  0x0,  0x30a3,  0x0,  0x30a4,  0x0,  0x30a4,  0x30cb,  0x30f3,  0x30af,  0x3099,  0x0,  0x30a4,  0x30f3,  0x30c1,  0x0,  0x30a5,  0x0,  0x30a6,  0x0,  0x30a6,  0x3099,  0x0,  0x30a6,  0x30a9,  0x30f3,  0x0,  0x30a7,  0x0,  0x30a8,  0x0,  0x30a8,  0x30b9,  0x30af,  0x30fc,  0x30c8,  0x3099,  0x0,  0x30a8,  0x30fc,  0x30ab,  0x30fc,  0x0,  0x30a9,  0x0,  0x30aa,  0x0,  0x30aa,  0x30f3,  0x30b9,  0x0,  0x30aa,  0x30fc,  0x30e0,  0x0,  0x30ab,  0x0,  0x30ab,  0x3099,  0x0,  0x30ab,  0x3099,  0x30ed,  0x30f3,  0x0,  0x30ab,  0x3099,  0x30f3,  0x30de,  0x0,  0x30ab,  0x30a4,  0x30ea,  0x0,  0x30ab,  0x30e9,  0x30c3,  0x30c8,  0x0,  0x30ab,  0x30ed,  0x30ea,  0x30fc,  0x0,  0x30ad,  0x0,  0x30ad,  0x3099,  0x0,  0x30ad,  0x3099,  0x30ab,  0x3099,  0x0,  0x30ad,  0x3099,  0x30cb,  0x30fc,  0x0,  0x30ad,  0x3099,  0x30eb,  0x30bf,  0x3099,  0x30fc,  0x0,  0x30ad,  0x30e5,  0x30ea,  0x30fc,  0x0,  0x30ad,  0x30ed,  0x0,  0x30ad,  0x30ed,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x0,  0x30ad,  0x30ed,  0x30e1,  0x30fc,  0x30c8,  0x30eb,  0x0,  0x30ad,  0x30ed,  0x30ef,  0x30c3,  0x30c8,  0x0,  0x30af,  0x0,  0x30af,  0x3099,  0x0,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x0,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x30c8,  0x30f3,  0x0,  0x30af,  0x30eb,  0x30bb,  0x3099,  0x30a4,  0x30ed,  0x0,  0x30af,  0x30ed,  0x30fc,  0x30cd,  0x0,  0x30b1,  0x0,  0x30b1,  0x3099,  0x0,  0x30b1,  0x30fc,  0x30b9,  0x0,  0x30b3,  0x0,  0x30b3,  0x3099,  0x0,  0x30b3,  0x30b3,  0x0,  0x30b3,  0x30c8,  0x0,  0x30b3,  0x30eb,  0x30ca,  0x0,  0x30b3,  0x30fc,  0x30db,  0x309a,  0x0,  0x30b5,  0x0,  0x30b5,  0x3099,  0x0,  0x30b5,  0x30a4,  0x30af,  0x30eb,  0x0,  0x30b5,  0x30f3,  0x30c1,  0x30fc,  0x30e0,  0x0,  0x30b7,  0x0,  0x30b7,  0x3099,  0x0,  0x30b7,  0x30ea,  0x30f3,  0x30af,  0x3099,  0x0,  0x30b9,  0x0,  0x30b9,  0x3099,  0x0,  0x30bb,  0x0,  0x30bb,  0x3099,  0x0,  0x30bb,  0x30f3,  0x30c1,  0x0,  0x30bb,  0x30f3,  0x30c8,  0x0,  0x30bd,  0x0,  0x30bd,  0x3099,  0x0,  0x30bf,  0x0,  0x30bf,  0x3099,  0x0,  0x30bf,  0x3099,  0x30fc,  0x30b9,  0x0,  0x30c1,  0x0,  0x30c1,  0x3099,  0x0,  0x30c3,  0x0,  0x30c4,  0x0,  0x30c4,  0x3099,  0x0,  0x30c6,  0x0,  0x30c6,  0x3099,  0x0,  0x30c6,  0x3099,  0x30b7,  0x0,  0x30c8,  0x0,  0x30c8,  0x3099,  0x0,  0x30c8,  0x3099,  0x30eb,  0x0,  0x30c8,  0x30f3,  0x0,  0x30ca,  0x0,  0x30ca,  0x30ce,  0x0,  0x30cb,  0x0,  0x30cc,  0x0,  0x30cd,  0x0,  0x30ce,  0x0,  0x30ce,  0x30c3,  0x30c8,  0x0,  0x30cf,  0x0,  0x30cf,  0x3099,  0x0,  0x30cf,  0x3099,  0x30fc,  0x30ec,  0x30eb,  0x0,  0x30cf,  0x309a,  0x0,  0x30cf,  0x309a,  0x30fc,  0x30bb,  0x30f3,  0x30c8,  0x0,  0x30cf,  0x309a,  0x30fc,  0x30c4,  0x0,  0x30cf,  0x30a4,  0x30c4,  0x0,  0x30d2,  0x0,  0x30d2,  0x3099,  0x0,  0x30d2,  0x3099,  0x30eb,  0x0,  0x30d2,  0x309a,  0x0,  0x30d2,  0x309a,  0x30a2,  0x30b9,  0x30c8,  0x30eb,  0x0,  0x30d2,  0x309a,  0x30af,  0x30eb,  0x0,  0x30d2,  0x309a,  0x30b3,  0x0,  0x30d5,  0x0,  0x30d5,  0x3099,  0x0,  0x30d5,  0x3099,  0x30c3,  0x30b7,  0x30a7,  0x30eb,  0x0,  0x30d5,  0x309a,  0x0,  0x30d5,  0x30a1,  0x30e9,  0x30c3,  0x30c8,  0x3099,  0x0,  0x30d5,  0x30a3,  0x30fc,  0x30c8,  0x0,  0x30d5,  0x30e9,  0x30f3,  0x0,  0x30d8,  0x0,  0x30d8,  0x3099,  0x0,  0x30d8,  0x3099,  0x30fc,  0x30bf,  0x0,  0x30d8,  0x309a,  0x0,  0x30d8,  0x309a,  0x30bd,  0x0,  0x30d8,  0x309a,  0x30cb,  0x30d2,  0x0,  0x30d8,  0x309a,  0x30f3,  0x30b9,  0x0,  0x30d8,  0x309a,  0x30fc,  0x30b7,  0x3099,  0x0,  0x30d8,  0x30af,  0x30bf,  0x30fc,  0x30eb,  0x0,  0x30d8,  0x30eb,  0x30c4,  0x0,  0x30db,  0x0,  0x30db,  0x3099,  0x0,  0x30db,  0x3099,  0x30eb,  0x30c8,  0x0,  0x30db,  0x309a,  0x0,  0x30db,  0x309a,  0x30a4,  0x30f3,  0x30c8,  0x0,  0x30db,  0x309a,  0x30f3,  0x30c8,  0x3099,  0x0,  0x30db,  0x30f3,  0x0,  0x30db,  0x30fc,  0x30eb,  0x0,  0x30db,  0x30fc,  0x30f3,  0x0,  0x30de,  0x0,  0x30de,  0x30a4,  0x30af,  0x30ed,  0x0,  0x30de,  0x30a4,  0x30eb,  0x0,  0x30de,  0x30c3,  0x30cf,  0x0,  0x30de,  0x30eb,  0x30af,  0x0,  0x30de,  0x30f3,  0x30b7,  0x30e7,  0x30f3,  0x0,  0x30df,  0x0,  0x30df,  0x30af,  0x30ed,  0x30f3,  0x0,  0x30df,  0x30ea,  0x0,  0x30df,  0x30ea,  0x30cf,  0x3099,  0x30fc,  0x30eb,  0x0,  0x30e0,  0x0,  0x30e1,  0x0,  0x30e1,  0x30ab,  0x3099,  0x0,  0x30e1,  0x30ab,  0x3099,  0x30c8,  0x30f3,  0x0,  0x30e1,  0x30fc,  0x30c8,  0x30eb,  0x0,  0x30e2,  0x0,  0x30e3,  0x0,  0x30e4,  0x0,  0x30e4,  0x30fc,  0x30c8,  0x3099,  0x0,  0x30e4,  0x30fc,  0x30eb,  0x0,  0x30e5,  0x0,  0x30e6,  0x0,  0x30e6,  0x30a2,  0x30f3,  0x0,  0x30e7,  0x0,  0x30e8,  0x0,  0x30e9,  0x0,  0x30ea,  0x0,  0x30ea,  0x30c3,  0x30c8,  0x30eb,  0x0,  0x30ea,  0x30e9,  0x0,  0x30eb,  0x0,  0x30eb,  0x30d2,  0x309a,  0x30fc,  0x0,  0x30eb,  0x30fc,  0x30d5,  0x3099,  0x30eb,  0x0,  0x30ec,  0x0,  0x30ec,  0x30e0,  0x0,  0x30ec,  0x30f3,  0x30c8,  0x30b1,  0x3099,  0x30f3,  0x0,  0x30ed,  0x0,  0x30ef,  0x0,  0x30ef,  0x3099,  0x0,  0x30ef,  0x30c3,  0x30c8,  0x0,  0x30f0,  0x0,  0x30f0,  0x3099,  0x0,  0x30f1,  0x0,  0x30f1,  0x3099,  0x0,  0x30f2,  0x0,  0x30f2,  0x3099,  0x0,  0x30f3,  0x0,  0x30fb,  0x0,  0x30fc,  0x0,  0x30fd,  0x3099,  0x0,  0x349e,  0x0,  0x34b9,  0x0,  0x34bb,  0x0,  0x34df,  0x0,  0x3515,  0x0,  0x36ee,  0x0,  0x36fc,  0x0,  0x3781,  0x0,  0x382f,  0x0,  0x3862,  0x0,  0x387c,  0x0,  0x38c7,  0x0,  0x38e3,  0x0,  0x391c,  0x0,  0x393a,  0x0,  0x3a2e,  0x0,  0x3a6c,  0x0,  0x3ae4,  0x0,  0x3b08,  0x0,  0x3b19,  0x0,  0x3b49,  0x0,  0x3b9d,  0x0,  0x3c18,  0x0,  0x3c4e,  0x0,  0x3d33,  0x0,  0x3d96,  0x0,  0x3eac,  0x0,  0x3eb8,  0x0,  0x3f1b,  0x0,  0x3ffc,  0x0,  0x4008,  0x0,  0x4018,  0x0,  0x4039,  0x0,  0x4046,  0x0,  0x4096,  0x0,  0x40e3,  0x0,  0x412f,  0x0,  0x4202,  0x0,  0x4227,  0x0,  0x42a0,  0x0,  0x4301,  0x0,  0x4334,  0x0,  0x4359,  0x0,  0x43d5,  0x0,  0x43d9,  0x0,  0x440b,  0x0,  0x446b,  0x0,  0x452b,  0x0,  0x455d,  0x0,  0x4561,  0x0,  0x456b,  0x0,  0x45d7,  0x0,  0x45f9,  0x0,  0x4635,  0x0,  0x46be,  0x0,  0x46c7,  0x0,  0x4995,  0x0,  0x49e6,  0x0,  0x4a6e,  0x0,  0x4a76,  0x0,  0x4ab2,  0x0,  0x4b33,  0x0,  0x4bce,  0x0,  0x4cce,  0x0,  0x4ced,  0x0,  0x4cf8,  0x0,  0x4d56,  0x0,  0x4e00,  0x0,  0x4e01,  0x0,  0x4e03,  0x0,  0x4e09,  0x0,  0x4e0a,  0x0,  0x4e0b,  0x0,  0x4e0d,  0x0,  0x4e19,  0x0,  0x4e26,  0x0,  0x4e28,  0x0,  0x4e2d,  0x0,  0x4e32,  0x0,  0x4e36,  0x0,  0x4e38,  0x0,  0x4e39,  0x0,  0x4e3d,  0x0,  0x4e3f,  0x0,  0x4e41,  0x0,  0x4e59,  0x0,  0x4e5d,  0x0,  0x4e82,  0x0,  0x4e85,  0x0,  0x4e86,  0x0,  0x4e8c,  0x0,  0x4e94,  0x0,  0x4ea0,  0x0,  0x4ea4,  0x0,  0x4eae,  0x0,  0x4eba,  0x0,  0x4ec0,  0x0,  0x4ecc,  0x0,  0x4ee4,  0x0,  0x4f01,  0x0,  0x4f11,  0x0,  0x4f60,  0x0,  0x4f80,  0x0,  0x4f86,  0x0,  0x4f8b,  0x0,  0x4fae,  0x0,  0x4fbb,  0x0,  0x4fbf,  0x0,  0x5002,  0x0,  0x502b,  0x0,  0x507a,  0x0,  0x5099,  0x0,  0x50cf,  0x0,  0x50da,  0x0,  0x50e7,  0x0,  0x512a,  0x0,  0x513f,  0x0,  0x5140,  0x0,  0x5145,  0x0,  0x514d,  0x0,  0x5154,  0x0,  0x5164,  0x0,  0x5165,  0x0,  0x5167,  0x0,  0x5168,  0x0,  0x5169,  0x0,  0x516b,  0x0,  0x516d,  0x0,  0x5177,  0x0,  0x5180,  0x0,  0x5182,  0x0,  0x518d,  0x0,  0x5192,  0x0,  0x5195,  0x0,  0x5196,  0x0,  0x5197,  0x0,  0x5199,  0x0,  0x51a4,  0x0,  0x51ab,  0x0,  0x51ac,  0x0,  0x51b5,  0x0,  0x51b7,  0x0,  0x51c9,  0x0,  0x51cc,  0x0,  0x51dc,  0x0,  0x51de,  0x0,  0x51e0,  0x0,  0x51f5,  0x0,  0x5200,  0x0,  0x5203,  0x0,  0x5207,  0x0,  0x5217,  0x0,  0x521d,  0x0,  0x5229,  0x0,  0x523a,  0x0,  0x523b,  0x0,  0x5246,  0x0,  0x524d,  0x0,  0x5272,  0x0,  0x5277,  0x0,  0x5289,  0x0,  0x529b,  0x0,  0x52a3,  0x0,  0x52b3,  0x0,  0x52b4,  0x0,  0x52c7,  0x0,  0x52c9,  0x0,  0x52d2,  0x0,  0x52de,  0x0,  0x52e4,  0x0,  0x52f5,  0x0,  0x52f9,  0x0,  0x52fa,  0x0,  0x5305,  0x0,  0x5306,  0x0,  0x5315,  0x0,  0x5317,  0x0,  0x531a,  0x0,  0x5338,  0x0,  0x533b,  0x0,  0x533f,  0x0,  0x5341,  0x0,  0x5344,  0x0,  0x5345,  0x0,  0x5349,  0x0,  0x5351,  0x0,  0x5354,  0x0,  0x535a,  0x0,  0x535c,  0x0,  0x5369,  0x0,  0x5370,  0x0,  0x5373,  0x0,  0x5375,  0x0,  0x537d,  0x0,  0x537f,  0x0,  0x5382,  0x0,  0x53b6,  0x0,  0x53c3,  0x0,  0x53c8,  0x0,  0x53ca,  0x0,  0x53cc,  0x0,  0x53df,  0x0,  0x53e3,  0x0,  0x53e5,  0x0,  0x53eb,  0x0,  0x53ef,  0x0,  0x53f1,  0x0,  0x53f3,  0x0,  0x5406,  0x0,  0x5408,  0x0,  0x540d,  0x0,  0x540f,  0x0,  0x541d,  0x0,  0x5438,  0x0,  0x5439,  0x0,  0x5442,  0x0,  0x5448,  0x0,  0x5468,  0x0,  0x549e,  0x0,  0x54a2,  0x0,  0x54bd,  0x0,  0x54f6,  0x0,  0x5510,  0x0,  0x554f,  0x0,  0x5553,  0x0,  0x5555,  0x0,  0x5563,  0x0,  0x5584,  0x0,  0x5587,  0x0,  0x5599,  0x0,  0x559d,  0x0,  0x55ab,  0x0,  0x55b3,  0x0,  0x55b6,  0x0,  0x55c0,  0x0,  0x55c2,  0x0,  0x55e2,  0x0,  0x5606,  0x0,  0x5651,  0x0,  0x5668,  0x0,  0x5674,  0x0,  0x56d7,  0x0,  0x56db,  0x0,  0x56f9,  0x0,  0x5716,  0x0,  0x5717,  0x0,  0x571f,  0x0,  0x5730,  0x0,  0x578b,  0x0,  0x57ce,  0x0,  0x57f4,  0x0,  0x580d,  0x0,  0x5831,  0x0,  0x5832,  0x0,  0x5840,  0x0,  0x585a,  0x0,  0x585e,  0x0,  0x58a8,  0x0,  0x58ac,  0x0,  0x58b3,  0x0,  0x58d8,  0x0,  0x58df,  0x0,  0x58eb,  0x0,  0x58ee,  0x0,  0x58f0,  0x0,  0x58f2,  0x0,  0x58f7,  0x0,  0x5902,  0x0,  0x5906,  0x0,  0x590a,  0x0,  0x5915,  0x0,  0x591a,  0x0,  0x591c,  0x0,  0x5922,  0x0,  0x5927,  0x0,  0x5927,  0x6b63,  0x0,  0x5929,  0x0,  0x5944,  0x0,  0x5948,  0x0,  0x5951,  0x0,  0x5954,  0x0,  0x5962,  0x0,  0x5973,  0x0,  0x59d8,  0x0,  0x59ec,  0x0,  0x5a1b,  0x0,  0x5a27,  0x0,  0x5a62,  0x0,  0x5a66,  0x0,  0x5ab5,  0x0,  0x5b08,  0x0,  0x5b28,  0x0,  0x5b3e,  0x0,  0x5b50,  0x0,  0x5b57,  0x0,  0x5b66,  0x0,  0x5b80,  0x0,  0x5b85,  0x0,  0x5b97,  0x0,  0x5bc3,  0x0,  0x5bd8,  0x0,  0x5be7,  0x0,  0x5bee,  0x0,  0x5bf3,  0x0,  0x5bf8,  0x0,  0x5bff,  0x0,  0x5c06,  0x0,  0x5c0f,  0x0,  0x5c22,  0x0,  0x5c38,  0x0,  0x5c3f,  0x0,  0x5c60,  0x0,  0x5c62,  0x0,  0x5c64,  0x0,  0x5c65,  0x0,  0x5c6e,  0x0,  0x5c71,  0x0,  0x5c8d,  0x0,  0x5cc0,  0x0,  0x5d19,  0x0,  0x5d43,  0x0,  0x5d50,  0x0,  0x5d6b,  0x0,  0x5d6e,  0x0,  0x5d7c,  0x0,  0x5db2,  0x0,  0x5dba,  0x0,  0x5ddb,  0x0,  0x5de1,  0x0,  0x5de2,  0x0,  0x5de5,  0x0,  0x5de6,  0x0,  0x5df1,  0x0,  0x5dfd,  0x0,  0x5dfe,  0x0,  0x5e28,  0x0,  0x5e3d,  0x0,  0x5e69,  0x0,  0x5e72,  0x0,  0x5e73,  0x6210,  0x0,  0x5e74,  0x0,  0x5e7a,  0x0,  0x5e7c,  0x0,  0x5e7f,  0x0,  0x5ea6,  0x0,  0x5eb0,  0x0,  0x5eb3,  0x0,  0x5eb6,  0x0,  0x5ec9,  0x0,  0x5eca,  0x0,  0x5ed2,  0x0,  0x5ed3,  0x0,  0x5ed9,  0x0,  0x5eec,  0x0,  0x5ef4,  0x0,  0x5efe,  0x0,  0x5f04,  0x0,  0x5f0b,  0x0,  0x5f13,  0x0,  0x5f22,  0x0,  0x5f50,  0x0,  0x5f53,  0x0,  0x5f61,  0x0,  0x5f62,  0x0,  0x5f69,  0x0,  0x5f6b,  0x0,  0x5f73,  0x0,  0x5f8b,  0x0,  0x5f8c,  0x0,  0x5f97,  0x0,  0x5f9a,  0x0,  0x5fa9,  0x0,  0x5fad,  0x0,  0x5fc3,  0x0,  0x5fcd,  0x0,  0x5fd7,  0x0,  0x5ff5,  0x0,  0x5ff9,  0x0,  0x6012,  0x0,  0x601c,  0x0,  0x6075,  0x0,  0x6081,  0x0,  0x6094,  0x0,  0x60c7,  0x0,  0x60d8,  0x0,  0x60e1,  0x0,  0x6108,  0x0,  0x6144,  0x0,  0x6148,  0x0,  0x614c,  0x0,  0x614e,  0x0,  0x6160,  0x0,  0x6168,  0x0,  0x617a,  0x0,  0x618e,  0x0,  0x6190,  0x0,  0x61a4,  0x0,  0x61af,  0x0,  0x61b2,  0x0,  0x61de,  0x0,  0x61f2,  0x0,  0x61f6,  0x0,  0x6200,  0x0,  0x6208,  0x0,  0x6210,  0x0,  0x621b,  0x0,  0x622e,  0x0,  0x6234,  0x0,  0x6236,  0x0,  0x624b,  0x0,  0x6253,  0x0,  0x625d,  0x0,  0x6295,  0x0,  0x62b1,  0x0,  0x62c9,  0x0,  0x62cf,  0x0,  0x62d3,  0x0,  0x62d4,  0x0,  0x62fc,  0x0,  0x62fe,  0x0,  0x6307,  0x0,  0x633d,  0x0,  0x6350,  0x0,  0x6355,  0x0,  0x6368,  0x0,  0x637b,  0x0,  0x6383,  0x0,  0x63a0,  0x0,  0x63a9,  0x0,  0x63c4,  0x0,  0x63c5,  0x0,  0x63e4,  0x0,  0x641c,  0x0,  0x6422,  0x0,  0x6452,  0x0,  0x6469,  0x0,  0x6477,  0x0,  0x647e,  0x0,  0x649a,  0x0,  0x649d,  0x0,  0x64c4,  0x0,  0x652f,  0x0,  0x6534,  0x0,  0x654f,  0x0,  0x6556,  0x0,  0x656c,  0x0,  0x6578,  0x0,  0x6587,  0x0,  0x6597,  0x0,  0x6599,  0x0,  0x65a4,  0x0,  0x65b0,  0x0,  0x65b9,  0x0,  0x65c5,  0x0,  0x65e0,  0x0,  0x65e2,  0x0,  0x65e3,  0x0,  0x65e5,  0x0,  0x660e,  0x6cbb,  0x0,  0x6613,  0x0,  0x6620,  0x0,  0x662d,  0x548c,  0x0,  0x6649,  0x0,  0x6674,  0x0,  0x6688,  0x0,  0x6691,  0x0,  0x669c,  0x0,  0x66b4,  0x0,  0x66c6,  0x0,  0x66f0,  0x0,  0x66f4,  0x0,  0x66f8,  0x0,  0x6700,  0x0,  0x6708,  0x0,  0x6709,  0x0,  0x6717,  0x0,  0x671b,  0x0,  0x6721,  0x0,  0x6728,  0x0,  0x674e,  0x0,  0x6753,  0x0,  0x6756,  0x0,  0x675e,  0x0,  0x677b,  0x0,  0x6785,  0x0,  0x6797,  0x0,  0x67f3,  0x0,  0x67fa,  0x0,  0x6817,  0x0,  0x681f,  0x0,  0x682a,  0x0,  0x682a,  0x5f0f,  0x4f1a,  0x793e,  0x0,  0x6852,  0x0,  0x6881,  0x0,  0x6885,  0x0,  0x688e,  0x0,  0x68a8,  0x0,  0x6914,  0x0,  0x6942,  0x0,  0x69a3,  0x0,  0x69ea,  0x0,  0x6a02,  0x0,  0x6a13,  0x0,  0x6aa8,  0x0,  0x6ad3,  0x0,  0x6adb,  0x0,  0x6b04,  0x0,  0x6b20,  0x0,  0x6b21,  0x0,  0x6b54,  0x0,  0x6b62,  0x0,  0x6b63,  0x0,  0x6b72,  0x0,  0x6b77,  0x0,  0x6b79,  0x0,  0x6b9f,  0x0,  0x6bae,  0x0,  0x6bb3,  0x0,  0x6bba,  0x0,  0x6bbb,  0x0,  0x6bcb,  0x0,  0x6bcd,  0x0,  0x6bd4,  0x0,  0x6bdb,  0x0,  0x6c0f,  0x0,  0x6c14,  0x0,  0x6c34,  0x0,  0x6c4e,  0x0,  0x6c67,  0x0,  0x6c88,  0x0,  0x6cbf,  0x0,  0x6ccc,  0x0,  0x6ccd,  0x0,  0x6ce5,  0x0,  0x6ce8,  0x0,  0x6d16,  0x0,  0x6d1b,  0x0,  0x6d1e,  0x0,  0x6d34,  0x0,  0x6d3e,  0x0,  0x6d41,  0x0,  0x6d69,  0x0,  0x6d6a,  0x0,  0x6d77,  0x0,  0x6d78,  0x0,  0x6d85,  0x0,  0x6dcb,  0x0,  0x6dda,  0x0,  0x6dea,  0x0,  0x6df9,  0x0,  0x6e1a,  0x0,  0x6e2f,  0x0,  0x6e6e,  0x0,  0x6e80,  0x0,  0x6e9c,  0x0,  0x6eba,  0x0,  0x6ec7,  0x0,  0x6ecb,  0x0,  0x6ed1,  0x0,  0x6edb,  0x0,  0x6f0f,  0x0,  0x6f14,  0x0,  0x6f22,  0x0,  0x6f23,  0x0,  0x6f6e,  0x0,  0x6fc6,  0x0,  0x6feb,  0x0,  0x6ffe,  0x0,  0x701b,  0x0,  0x701e,  0x0,  0x7039,  0x0,  0x704a,  0x0,  0x706b,  0x0,  0x7070,  0x0,  0x7077,  0x0,  0x707d,  0x0,  0x7099,  0x0,  0x70ad,  0x0,  0x70c8,  0x0,  0x70d9,  0x0,  0x7121,  0x0,  0x7145,  0x0,  0x7149,  0x0,  0x716e,  0x0,  0x719c,  0x0,  0x71ce,  0x0,  0x71d0,  0x0,  0x7210,  0x0,  0x721b,  0x0,  0x7228,  0x0,  0x722a,  0x0,  0x722b,  0x0,  0x7235,  0x0,  0x7236,  0x0,  0x723b,  0x0,  0x723f,  0x0,  0x7247,  0x0,  0x7250,  0x0,  0x7259,  0x0,  0x725b,  0x0,  0x7262,  0x0,  0x7279,  0x0,  0x7280,  0x0,  0x7295,  0x0,  0x72ac,  0x0,  0x72af,  0x0,  0x72c0,  0x0,  0x72fc,  0x0,  0x732a,  0x0,  0x7375,  0x0,  0x737a,  0x0,  0x7384,  0x0,  0x7387,  0x0,  0x7389,  0x0,  0x738b,  0x0,  0x73a5,  0x0,  0x73b2,  0x0,  0x73de,  0x0,  0x7406,  0x0,  0x7409,  0x0,  0x7422,  0x0,  0x7447,  0x0,  0x745c,  0x0,  0x7469,  0x0,  0x7471,  0x0,  0x7485,  0x0,  0x7489,  0x0,  0x7498,  0x0,  0x74ca,  0x0,  0x74dc,  0x0,  0x74e6,  0x0,  0x7506,  0x0,  0x7518,  0x0,  0x751f,  0x0,  0x7524,  0x0,  0x7528,  0x0,  0x7530,  0x0,  0x7532,  0x0,  0x7533,  0x0,  0x7537,  0x0,  0x753b,  0x0,  0x753e,  0x0,  0x7559,  0x0,  0x7565,  0x0,  0x7570,  0x0,  0x758b,  0x0,  0x7592,  0x0,  0x75e2,  0x0,  0x7610,  0x0,  0x761d,  0x0,  0x761f,  0x0,  0x7642,  0x0,  0x7669,  0x0,  0x7676,  0x0,  0x767d,  0x0,  0x76ae,  0x0,  0x76bf,  0x0,  0x76ca,  0x0,  0x76db,  0x0,  0x76e3,  0x0,  0x76e7,  0x0,  0x76ee,  0x0,  0x76f4,  0x0,  0x7701,  0x0,  0x771e,  0x0,  0x771f,  0x0,  0x7740,  0x0,  0x774a,  0x0,  0x778b,  0x0,  0x77a7,  0x0,  0x77db,  0x0,  0x77e2,  0x0,  0x77f3,  0x0,  0x784e,  0x0,  0x786b,  0x0,  0x788c,  0x0,  0x7891,  0x0,  0x78ca,  0x0,  0x78cc,  0x0,  0x78fb,  0x0,  0x792a,  0x0,  0x793a,  0x0,  0x793c,  0x0,  0x793e,  0x0,  0x7948,  0x0,  0x7949,  0x0,  0x7950,  0x0,  0x7956,  0x0,  0x795d,  0x0,  0x795e,  0x0,  0x7965,  0x0,  0x797f,  0x0,  0x7981,  0x0,  0x798d,  0x0,  0x798e,  0x0,  0x798f,  0x0,  0x79ae,  0x0,  0x79b8,  0x0,  0x79be,  0x0,  0x79ca,  0x0,  0x79d8,  0x0,  0x79eb,  0x0,  0x7a1c,  0x0,  0x7a40,  0x0,  0x7a4a,  0x0,  0x7a4f,  0x0,  0x7a74,  0x0,  0x7a7a,  0x0,  0x7a81,  0x0,  0x7ab1,  0x0,  0x7acb,  0x0,  0x7aee,  0x0,  0x7af9,  0x0,  0x7b20,  0x0,  0x7b8f,  0x0,  0x7bc0,  0x0,  0x7bc6,  0x0,  0x7bc9,  0x0,  0x7c3e,  0x0,  0x7c60,  0x0,  0x7c73,  0x0,  0x7c7b,  0x0,  0x7c92,  0x0,  0x7cbe,  0x0,  0x7cd2,  0x0,  0x7cd6,  0x0,  0x7ce3,  0x0,  0x7ce7,  0x0,  0x7ce8,  0x0,  0x7cf8,  0x0,  0x7d00,  0x0,  0x7d10,  0x0,  0x7d22,  0x0,  0x7d2f,  0x0,  0x7d42,  0x0,  0x7d5b,  0x0,  0x7d63,  0x0,  0x7da0,  0x0,  0x7dbe,  0x0,  0x7dc7,  0x0,  0x7df4,  0x0,  0x7e02,  0x0,  0x7e09,  0x0,  0x7e37,  0x0,  0x7e41,  0x0,  0x7e45,  0x0,  0x7f36,  0x0,  0x7f3e,  0x0,  0x7f51,  0x0,  0x7f72,  0x0,  0x7f79,  0x0,  0x7f7a,  0x0,  0x7f85,  0x0,  0x7f8a,  0x0,  0x7f95,  0x0,  0x7f9a,  0x0,  0x7fbd,  0x0,  0x7ffa,  0x0,  0x8001,  0x0,  0x8005,  0x0,  0x800c,  0x0,  0x8012,  0x0,  0x8033,  0x0,  0x8046,  0x0,  0x8060,  0x0,  0x806f,  0x0,  0x8070,  0x0,  0x807e,  0x0,  0x807f,  0x0,  0x8089,  0x0,  0x808b,  0x0,  0x80ad,  0x0,  0x80b2,  0x0,  0x8103,  0x0,  0x813e,  0x0,  0x81d8,  0x0,  0x81e3,  0x0,  0x81e8,  0x0,  0x81ea,  0x0,  0x81ed,  0x0,  0x81f3,  0x0,  0x81fc,  0x0,  0x8201,  0x0,  0x8204,  0x0,  0x820c,  0x0,  0x8218,  0x0,  0x821b,  0x0,  0x821f,  0x0,  0x826e,  0x0,  0x826f,  0x0,  0x8272,  0x0,  0x8278,  0x0,  0x8279,  0x0,  0x828b,  0x0,  0x8291,  0x0,  0x829d,  0x0,  0x82b1,  0x0,  0x82b3,  0x0,  0x82bd,  0x0,  0x82e5,  0x0,  0x82e6,  0x0,  0x831d,  0x0,  0x8323,  0x0,  0x8336,  0x0,  0x8352,  0x0,  0x8353,  0x0,  0x8363,  0x0,  0x83ad,  0x0,  0x83bd,  0x0,  0x83c9,  0x0,  0x83ca,  0x0,  0x83cc,  0x0,  0x83dc,  0x0,  0x83e7,  0x0,  0x83ef,  0x0,  0x83f1,  0x0,  0x843d,  0x0,  0x8449,  0x0,  0x8457,  0x0,  0x84ee,  0x0,  0x84f1,  0x0,  0x84f3,  0x0,  0x84fc,  0x0,  0x8516,  0x0,  0x8564,  0x0,  0x85cd,  0x0,  0x85fa,  0x0,  0x8606,  0x0,  0x8612,  0x0,  0x862d,  0x0,  0x863f,  0x0,  0x864d,  0x0,  0x8650,  0x0,  0x865c,  0x0,  0x8667,  0x0,  0x8669,  0x0,  0x866b,  0x0,  0x8688,  0x0,  0x86a9,  0x0,  0x86e2,  0x0,  0x870e,  0x0,  0x8728,  0x0,  0x876b,  0x0,  0x8779,  0x0,  0x8786,  0x0,  0x87ba,  0x0,  0x87e1,  0x0,  0x8801,  0x0,  0x881f,  0x0,  0x8840,  0x0,  0x884c,  0x0,  0x8860,  0x0,  0x8863,  0x0,  0x88c2,  0x0,  0x88cf,  0x0,  0x88d7,  0x0,  0x88de,  0x0,  0x88e1,  0x0,  0x88f8,  0x0,  0x88fa,  0x0,  0x8910,  0x0,  0x8941,  0x0,  0x8964,  0x0,  0x897e,  0x0,  0x8986,  0x0,  0x898b,  0x0,  0x8996,  0x0,  0x89d2,  0x0,  0x89e3,  0x0,  0x8a00,  0x0,  0x8aa0,  0x0,  0x8aaa,  0x0,  0x8abf,  0x0,  0x8acb,  0x0,  0x8ad2,  0x0,  0x8ad6,  0x0,  0x8aed,  0x0,  0x8af8,  0x0,  0x8afe,  0x0,  0x8b01,  0x0,  0x8b39,  0x0,  0x8b58,  0x0,  0x8b80,  0x0,  0x8b8a,  0x0,  0x8c37,  0x0,  0x8c46,  0x0,  0x8c48,  0x0,  0x8c55,  0x0,  0x8c78,  0x0,  0x8c9d,  0x0,  0x8ca1,  0x0,  0x8ca9,  0x0,  0x8cab,  0x0,  0x8cc1,  0x0,  0x8cc2,  0x0,  0x8cc7,  0x0,  0x8cc8,  0x0,  0x8cd3,  0x0,  0x8d08,  0x0,  0x8d1b,  0x0,  0x8d64,  0x0,  0x8d70,  0x0,  0x8d77,  0x0,  0x8db3,  0x0,  0x8dbc,  0x0,  0x8dcb,  0x0,  0x8def,  0x0,  0x8df0,  0x0,  0x8eab,  0x0,  0x8eca,  0x0,  0x8ed4,  0x0,  0x8f26,  0x0,  0x8f2a,  0x0,  0x8f38,  0x0,  0x8f3b,  0x0,  0x8f62,  0x0,  0x8f9b,  0x0,  0x8f9e,  0x0,  0x8fb0,  0x0,  0x8fb5,  0x0,  0x8fb6,  0x0,  0x9023,  0x0,  0x9038,  0x0,  0x904a,  0x0,  0x9069,  0x0,  0x9072,  0x0,  0x907c,  0x0,  0x908f,  0x0,  0x9091,  0x0,  0x9094,  0x0,  0x90ce,  0x0,  0x90de,  0x0,  0x90f1,  0x0,  0x90fd,  0x0,  0x9111,  0x0,  0x911b,  0x0,  0x9149,  0x0,  0x916a,  0x0,  0x9199,  0x0,  0x91b4,  0x0,  0x91c6,  0x0,  0x91cc,  0x0,  0x91cf,  0x0,  0x91d1,  0x0,  0x9234,  0x0,  0x9238,  0x0,  0x9276,  0x0,  0x927c,  0x0,  0x92d7,  0x0,  0x92d8,  0x0,  0x9304,  0x0,  0x934a,  0x0,  0x93f9,  0x0,  0x9415,  0x0,  0x9577,  0x0,  0x9580,  0x0,  0x958b,  0x0,  0x95ad,  0x0,  0x95b7,  0x0,  0x961c,  0x0,  0x962e,  0x0,  0x964b,  0x0,  0x964d,  0x0,  0x9675,  0x0,  0x9678,  0x0,  0x967c,  0x0,  0x9686,  0x0,  0x96a3,  0x0,  0x96b6,  0x0,  0x96b7,  0x0,  0x96b8,  0x0,  0x96b9,  0x0,  0x96c3,  0x0,  0x96e2,  0x0,  0x96e3,  0x0,  0x96e8,  0x0,  0x96f6,  0x0,  0x96f7,  0x0,  0x9723,  0x0,  0x9732,  0x0,  0x9748,  0x0,  0x9751,  0x0,  0x9756,  0x0,  0x975e,  0x0,  0x9762,  0x0,  0x9769,  0x0,  0x97cb,  0x0,  0x97db,  0x0,  0x97e0,  0x0,  0x97ed,  0x0,  0x97f3,  0x0,  0x97ff,  0x0,  0x9801,  0x0,  0x9805,  0x0,  0x980b,  0x0,  0x9818,  0x0,  0x9829,  0x0,  0x983b,  0x0,  0x985e,  0x0,  0x98a8,  0x0,  0x98db,  0x0,  0x98df,  0x0,  0x98e2,  0x0,  0x98ef,  0x0,  0x98fc,  0x0,  0x9928,  0x0,  0x9929,  0x0,  0x9996,  0x0,  0x9999,  0x0,  0x99a7,  0x0,  0x99ac,  0x0,  0x99c2,  0x0,  0x99f1,  0x0,  0x99fe,  0x0,  0x9a6a,  0x0,  0x9aa8,  0x0,  0x9ad8,  0x0,  0x9adf,  0x0,  0x9b12,  0x0,  0x9b25,  0x0,  0x9b2f,  0x0,  0x9b32,  0x0,  0x9b3c,  0x0,  0x9b5a,  0x0,  0x9b6f,  0x0,  0x9c40,  0x0,  0x9c57,  0x0,  0x9ce5,  0x0,  0x9cfd,  0x0,  0x9d67,  0x0,  0x9db4,  0x0,  0x9dfa,  0x0,  0x9e1e,  0x0,  0x9e75,  0x0,  0x9e7f,  0x0,  0x9e97,  0x0,  0x9e9f,  0x0,  0x9ea5,  0x0,  0x9ebb,  0x0,  0x9ec3,  0x0,  0x9ecd,  0x0,  0x9ece,  0x0,  0x9ed1,  0x0,  0x9ef9,  0x0,  0x9efd,  0x0,  0x9efe,  0x0,  0x9f05,  0x0,  0x9f0e,  0x0,  0x9f0f,  0x0,  0x9f13,  0x0,  0x9f16,  0x0,  0x9f20,  0x0,  0x9f3b,  0x0,  0x9f43,  0x0,  0x9f4a,  0x0,  0x9f52,  0x0,  0x9f8d,  0x0,  0x9f8e,  0x0,  0x9f9c,  0x0,  0x9f9f,  0x0,  0x9fa0,  0x0,  0xa76f,  0x0,  0x11099,  0x110ba,  0x0,  0x1109b,  0x110ba,  0x0,  0x110a5,  0x110ba,  0x0,  0x11131,  0x11127,  0x0,  0x11132,  0x11127,  0x0,  0x1d157,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x1d16e,  0x0,  0x1d158,  0x1d165,  0x1d16f,  0x0,  0x1d158,  0x1d165,  0x1d170,  0x0,  0x1d158,  0x1d165,  0x1d171,  0x0,  0x1d158,  0x1d165,  0x1d172,  0x0,  0x1d1b9,  0x1d165,  0x0,  0x1d1b9,  0x1d165,  0x1d16e,  0x0,  0x1d1b9,  0x1d165,  0x1d16f,  0x0,  0x1d1ba,  0x1d165,  0x0,  0x1d1ba,  0x1d165,  0x1d16e,  0x0,  0x1d1ba,  0x1d165,  0x1d16f,  0x0,  0x20122,  0x0,  0x2051c,  0x0,  0x20525,  0x0,  0x2054b,  0x0,  0x2063a,  0x0,  0x20804,  0x0,  0x208de,  0x0,  0x20a2c,  0x0,  0x20b63,  0x0,  0x214e4,  0x0,  0x216a8,  0x0,  0x216ea,  0x0,  0x219c8,  0x0,  0x21b18,  0x0,  0x21d0b,  0x0,  0x21de4,  0x0,  0x21de6,  0x0,  0x22183,  0x0,  0x2219f,  0x0,  0x22331,  0x0,  0x226d4,  0x0,  0x22844,  0x0,  0x2284a,  0x0,  0x22b0c,  0x0,  0x22bf1,  0x0,  0x2300a,  0x0,  0x232b8,  0x0,  0x2335f,  0x0,  0x23393,  0x0,  0x2339c,  0x0,  0x233c3,  0x0,  0x233d5,  0x0,  0x2346d,  0x0,  0x236a3,  0x0,  0x238a7,  0x0,  0x23a8d,  0x0,  0x23afa,  0x0,  0x23cbc,  0x0,  0x23d1e,  0x0,  0x23ed1,  0x0,  0x23f5e,  0x0,  0x23f8e,  0x0,  0x24263,  0x0,  0x242ee,  0x0,  0x243ab,  0x0,  0x24608,  0x0,  0x24735,  0x0,  0x24814,  0x0,  0x24c36,  0x0,  0x24c92,  0x0,  0x24fa1,  0x0,  0x24fb8,  0x0,  0x25044,  0x0,  0x250f2,  0x0,  0x250f3,  0x0,  0x25119,  0x0,  0x25133,  0x0,  0x25249,  0x0,  0x2541d,  0x0,  0x25626,  0x0,  0x2569a,  0x0,  0x256c5,  0x0,  0x2597c,  0x0,  0x25aa7,  0x0,  0x25bab,  0x0,  0x25c80,  0x0,  0x25cd0,  0x0,  0x25f86,  0x0,  0x261da,  0x0,  0x26228,  0x0,  0x26247,  0x0,  0x262d9,  0x0,  0x2633e,  0x0,  0x264da,  0x0,  0x26523,  0x0,  0x265a8,  0x0,  0x267a7,  0x0,  0x267b5,  0x0,  0x26b3c,  0x0,  0x26c36,  0x0,  0x26cd5,  0x0,  0x26d6b,  0x0,  0x26f2c,  0x0,  0x26fb1,  0x0,  0x270d2,  0x0,  0x273ca,  0x0,  0x27667,  0x0,  0x278ae,  0x0,  0x27966,  0x0,  0x27ca8,  0x0,  0x27ed3,  0x0,  0x27f2f,  0x0,  0x285d2,  0x0,  0x285ed,  0x0,  0x2872e,  0x0,  0x28bfa,  0x0,  0x28d77,  0x0,  0x29145,  0x0,  0x291df,  0x0,  0x2921a,  0x0,  0x2940a,  0x0,  0x29496,  0x0,  0x295b6,  0x0,  0x29b30,  0x0,  0x2a0ce,  0x0,  0x2a105,  0x0,  0x2a20e,  0x0,  0x2a291,  0x0,  0x2a392,  0x0,  0x2a600,  0x0]; return t; }
+}
+
+}
+
+
+static if(size_t.sizeof == 4) {
+//22656 bytes
+enum compatMappingTrieEntries = TrieEntry!(ushort, 8, 8, 5)([ 0x0,  0x40,  0x540], [ 0x100,  0xa00,  0x21c0], [ 0x2020100,  0x4020302,  0x2020205,  0x7060202,  0x2020202,  0x8020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x80000,  0xa0009,  0xc000b,  0x0,  0xd0000,  0xf000e,  0x0,  0x110010,  0x130012,  0x150014,  0x170016,  0x190018,  0x0,  0x1b001a,  0x0,  0x0,  0x1c,  0x0,  0x1d0000,  0x1e0000,  0x0,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x21,  0x0,  0x22,  0x230000,  0x24,  0x0,  0x0,  0x0,  0x25,  0x26,  0x27,  0x0,  0x28,  0x0,  0x29,  0x0,  0x2a,  0x0,  0x2b,  0x2c0000,  0x0,  0x2d0000,  0x2e,  0x2f,  0x310030,  0x330032,  0x0,  0x340000,  0x0,  0x0,  0x350000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x370036,  0x38,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x390000,  0x3b003a,  0x3d003c,  0x0,  0x3f003e,  0x410040,  0x430042,  0x450044,  0x470046,  0x490048,  0x4b004a,  0x4d004c,  0x4f004e,  0x510050,  0x530052,  0x0,  0x550054,  0x570056,  0x590058,  0x5a,  0x5c005b,  0x5e005d,  0x60005f,  0x610000,  0x620000,  0x0,  0x0,  0x0,  0x0,  0x630000,  0x650064,  0x670066,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x68,  0x690000,  0x0,  0x6a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6b0000,  0x0,  0x0,  0x0,  0x6c0000,  0x0,  0x0,  0x0,  0x0,  0x6d,  0x6e0000,  0x70006f,  0x720071,  0x740073,  0x75,  0x770076,  0x790078,  0x7b007a,  0x7d007c,  0x7e0000,  0x80007f,  0x81,  0x0,  0x830082,  0x850084,  0x870086,  0x890088,  0x8b008a,  0x8d008c,  0x8f008e,  0x910090,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x920000,  0x0,  0x930000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x950094,  0x970096,  0x990098,  0x9b009a,  0x9d009c,  0x9f009e,  0xa100a0,  0xa2,  0xa400a3,  0xa600a5,  0xa800a7,  0xaa00a9,  0xac00ab,  0xae00ad,  0xb000af,  0xb200b1,  0xb400b3,  0xb600b5,  0xb800b7,  0xba00b9,  0xbc00bb,  0xbe00bd,  0xc000bf,  0xc200c1,  0xc400c3,  0xc600c5,  0xc800c7,  0xca00c9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xcc00cb,  0x0,  0xcd0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xcf00ce,  0xd00000,  0xd1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xd300d2,  0xd500d4,  0xd700d6,  0xd900d8,  0xdb00da,  0xdd00dc,  0xd200de,  0xdf00d3,  0xe000d5,  0xe200e1,  0xe300d9,  0xe500e4,  0xe700e6,  0xe900e8,  0xeb00ea,  0xed00ec,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xef00ee,  0xf100f0,  0xf300f2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf500f4,  0xf700f6,  0xf8,  0x0,  0xfa00f9,  0xfb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfd00fc,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0x1070106,  0x1090108,  0x10b010a,  0x10c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x15,  0x692,  0x0,  0x90000,  0x0,  0x30f0343,  0x11b20003,  0x0,  0x3140048,  0x787,  0x3c603ce,  0x494,  0x570056d,  0x5860573,  0x5b005a6,  0x5f80000,  0x62e062b,  0x6580631,  0x6e706e4,  0x6f906ea,  0x78f0000,  0x7a907a6,  0x7bf07ac,  0x7e3,  0x8b10000,  0x8b708b4,  0x95f08cb,  0x0,  0x9ac09a9,  0x9c209af,  0x9ec09e2,  0xa470000,  0xa890a86,  0xab30a8c,  0xb460b43,  0xb550b49,  0xc410000,  0xc5e0c5b,  0xc740c61,  0xc98,  0xd680000,  0xd6e0d6b,  0xe0c0d82,  0xe1b0000,  0x9c50589,  0x9c8058c,  0xa0a05ce,  0xa3b05ec,  0xa3e05ef,  0xa4105f2,  0xa4405f5,  0xa6e061a,  0x0,  0xaa20647,  0xaad0652,  0xab00655,  0xad00675,  0xab9065e,  0xafb069a,  0xb0106a0,  0xb0406a3,  0xb0a06a9,  0xb1606ba,  0x0,  0xb4c06ed,  0xb4f06f0,  0xb5206f3,  0xb6b070f,  0x6f6,  0xb3706d8,  0xb730717,  0xbae072e,  0x7430000,  0x7500bcc,  0x7460bd9,  0x7400bcf,  0xbc9,  0x78c0000,  0x79b0c3e,  0x7950c4d,  0xed70c47,  0x0,  0xc8307ce,  0xc8e07d9,  0xca207ed,  0x0,  0xd070842,  0xd1d0858,  0xd0d0848,  0xd2b086c,  0xd320873,  0xd49088a,  0xd380879,  0xd5d08a6,  0xd54089d,  0x0,  0xd7108ba,  0xd7808c1,  0xd7f08c8,  0xd9808e1,  0xd9b08e4,  0xdc4090d,  0xde9093f,  0xe0f0962,  0x979096e,  0x97f0e29,  0x60d0e2f,  0x8400614,  0xcae07f9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8f00000,  0xda7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x613060c,  0x7360a67,  0xbb9073d,  0x7830780,  0x5b70c32,  0x70309f3,  0x7f00b5f,  0x8e70ca5,  0x8d60d9e,  0x8d20d8d,  0x8da0d89,  0x8ce0d91,  0xd85,  0x9e505a9,  0x9de05a2,  0xe630e5a,  0x0,  0xb0706a6,  0xba80728,  0xccc0817,  0xccf081a,  0xecc0e7b,  0x6090b76,  0xa640610,  0xaf80697,  0x0,  0xc3b0789,  0x9ef05b3,  0xe600e57,  0xe680e5d,  0x9f605ba,  0x9f905bd,  0xabc0661,  0xabf0664,  0xb620706,  0xb650709,  0xca807f3,  0xcab07f6,  0xd10084b,  0xd13084e,  0xda108ea,  0xda408ed,  0xd460887,  0xd5a08a3,  0x0,  0xb1f06c3,  0x0,  0x0,  0x0,  0x9db059f,  0xac9066e,  0xc9b07e6,  0xc7b07c6,  0xc9107dc,  0xc9407df,  0xe150968,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe9a0b0d,  0xa11073e,  0xeb60eb4,  0xde10eb8,  0x695,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x12000f,  0x4b0024,  0x270006,  0x0,  0xa280e96,  0xb410840,  0xecf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4001a,  0x2b0000,  0x1d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xed5,  0x0,  0x0,  0x54,  0x0,  0x546,  0x0,  0x0,  0x1c0003,  0x7410ee8,  0xf630f43,  0xfb4,  0xfed,  0x103c1016,  0x1185,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x101f0fbd,  0x10f5108f,  0x11751119,  0x1213,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x120c117e,  0x120311d5,  0x124b,  0x116e10ea,  0x10161011,  0x123c101f,  0x11ee,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x11f011ae,  0x11f8,  0x10f00fad,  0x0,  0x100d0000,  0x0,  0x0,  0x0,  0x12b612b0,  0x12ad0000,  0x0,  0x12a40000,  0x0,  0x0,  0x12c212ce,  0x12d7,  0x0,  0x0,  0x0,  0x0,  0x12c80000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x130a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x12f812f2,  0x12ef0000,  0x0,  0x132d0000,  0x0,  0x0,  0x13041310,  0x131b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13331330,  0x0,  0x0,  0x0,  0x0,  0x12b90000,  0x12fb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x12e912a7,  0x12ec12aa,  0x0,  0x12f512b3,  0x0,  0x13391336,  0x12fe12bc,  0x130112bf,  0x0,  0x130712c5,  0x130d12cb,  0x131512d1,  0x0,  0x133f133c,  0x132a12e6,  0x131812d4,  0x131e12da,  0x132112dd,  0x132412e0,  0x0,  0x132712e3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13420000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13e913e6,  0x13ec178f,  0x17ca,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13ef0000,  0x185b1792,  0x1811,  0x0,  0x0,  0x0,  0x186d,  0x1852,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x186a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18820000,  0x0,  0x0,  0x0,  0x188b0000,  0x0,  0x188e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18731870,  0x18791876,  0x187f187c,  0x18881885,  0x0,  0x0,  0x0,  0x0,  0x0,  0x189a0000,  0x189d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18941891,  0x18970000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18ac0000,  0x0,  0x18af,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18a00000,  0x18a618a3,  0x0,  0x18a9,  0x0,  0x0,  0x0,  0x0,  0x18bb,  0x18b80000,  0x18be,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18b518b2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18c1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18ca18c4,  0x18c7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18cd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18d0,  0x0,  0x0,  0x18da0000,  0x18dd,  0x18d618d3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18e618e0,  0x18e3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18e9,  0x18ef18ec,  0x18f3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18f60000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18ff0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18fc18f9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1902,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x19070000,  0x0,  0x0,  0x0,  0x0,  0x190a0000,  0x0,  0x0,  0x190d,  0x0,  0x19100000,  0x0,  0x0,  0x1913,  0x0,  0x0,  0x0,  0x0,  0x0,  0x19040000,  0x0,  0x0,  0x0,  0x0,  0x19160000,  0x19190000,  0x19311935,  0x1938193c,  0x0,  0x0,  0x0,  0x191c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x19220000,  0x0,  0x0,  0x0,  0x0,  0x19250000,  0x0,  0x0,  0x1928,  0x0,  0x192b0000,  0x0,  0x0,  0x192e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x191f0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x193f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1942,  0x0,  0x0,  0x0,  0x0,  0x1a38,  0x1a3b,  0x1a3e,  0x1a41,  0x1a44,  0x0,  0x1a47,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a4a0000,  0x1a4d0000,  0x0,  0x1a531a50,  0x1a560000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe550568,  0x5d5,  0x62905e6,  0x6870e75,  0x6cf06ac,  0x71a0607,  0x7230734,  0x77e,  0xe7e07a4,  0x82c06af,  0x56b088d,  0x6920770,  0xe840e82,  0x9371a59,  0xa7d0a2e,  0xe8e0e8c,  0x6020e90,  0xb790000,  0xe7105d3,  0xe880787,  0x1a5d1a5b,  0xba30cd3,  0x1a610a24,  0x86a0ea4,  0x10ea1a63,  0x10ee10ec,  0x123e123c,  0xa110ae0,  0x86a0a24,  0x10ec10ea,  0x123c11f0,  0x123e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1313,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe860000,  0xe8a09a0,  0xe900e66,  0xe920ad9,  0xe980e94,  0xe9e0e9c,  0x1a650ea0,  0xea20ed1,  0xed31a67,  0xea60ea8,  0xeac0eaa,  0xeb00eae,  0xeba0eb2,  0xe790ebc,  0xec00ebe,  0xec21a5f,  0x6110ec4,  0xec80ec6,  0x116e0eca,  0xa0705cb,  0xa1305da,  0xa1605dd,  0xa1905e0,  0xa4a05fb,  0xa6b0617,  0xa71061d,  0xa7a0626,  0xa740620,  0xa770623,  0xaa5064a,  0xaa9064e,  0xad30678,  0xad6067b,  0xacc0671,  0xaef0684,  0xafe069d,  0xb1906bd,  0xb2206c6,  0xb1c06c0,  0xb2506c9,  0xb2806cc,  0xb6e0712,  0xb5806fc,  0xba50725,  0xbab072b,  0xbb10731,  0xbd20749,  0xbd5074c,  0xbdf0756,  0xbdc0753,  0xc120772,  0xc150775,  0xc180778,  0xc440792,  0xc4a0798,  0xc5307a1,  0xc50079e,  0xc7707c2,  0xc7f07ca,  0xc8607d1,  0xc8a07d5,  0xcec0835,  0xcef0838,  0xd0a0845,  0xd160851,  0xd190854,  0xd20085b,  0xd350876,  0xd3f0880,  0xd2e086f,  0xd3b087c,  0xd420883,  0xd4e089a,  0xd5708a0,  0xd6308ac,  0xd6008a9,  0xdc1090a,  0xdca0913,  0xdc70910,  0xd7408bd,  0xd7b08c4,  0xddb0924,  0xdde0927,  0xde30939,  0xde6093c,  0xdef0945,  0xdec0942,  0xdf50948,  0xe010954,  0xe040957,  0xe18096b,  0xe2c097c,  0xe350985,  0xe380988,  0xd510b2b,  0xe210df2,  0xd3509a6,  0x0,  0x0,  0x9fc05c0,  0x9e905ad,  0x9b6057a,  0x9b20576,  0x9be0582,  0x9ba057e,  0x9ff05c3,  0x9cf0593,  0x9cb058f,  0x9d7059b,  0x9d30597,  0xa0305c7,  0xac20667,  0xab6065b,  0xa9f0644,  0xa930638,  0xa8f0634,  0xa9b0640,  0xa97063c,  0xac5066a,  0xb5c0700,  0xb68070c,  0xcc50810,  0xc9f07ea,  0xc6807b3,  0xc6407af,  0xc7007bb,  0xc6c07b7,  0xcc80813,  0xcb50800,  0xcb107fc,  0xcbd0808,  0xcb90804,  0xcc1080c,  0xdbe0907,  0xd9508de,  0xdae08f7,  0xdaa08f3,  0xdb608ff,  0xdb208fb,  0xdba0903,  0xe09095c,  0xe240974,  0xe1e0971,  0xe120965,  0x0,  0x0,  0x0,  0x10be109c,  0x10c1109f,  0x10ca10a8,  0x10d310b1,  0xf130ef1,  0xf160ef4,  0xf1f0efd,  0xf280f06,  0x110310f8,  0x110610fb,  0x110a10ff,  0x0,  0xf510f46,  0xf540f49,  0xf580f4d,  0x0,  0x11421120,  0x11451123,  0x114e112c,  0x11571135,  0xf880f66,  0xf8b0f69,  0xf940f72,  0xf9d0f7b,  0x119c118d,  0x119f1190,  0x11a31194,  0x11a71198,  0xfcf0fc0,  0xfd20fc3,  0xfd60fc7,  0xfda0fcb,  0x11e311d8,  0x11e611db,  0x11ea11df,  0x0,  0xffb0ff0,  0xffe0ff3,  0x10020ff7,  0x0,  0x122a121b,  0x122d121e,  0x12311222,  0x12351226,  0x10220000,  0x10250000,  0x10290000,  0x102d0000,  0x12741252,  0x12771255,  0x1280125e,  0x12891267,  0x1061103f,  0x10641042,  0x106d104b,  0x10761054,  0x108f1088,  0x10f510f2,  0x11191112,  0x11751172,  0x11d511d2,  0x12031200,  0x124b1244,  0x0,  0x10dc10ba,  0x10c510a3,  0x10ce10ac,  0x10d710b5,  0xf310f0f,  0xf1a0ef8,  0xf230f01,  0xf2c0f0a,  0x1160113e,  0x11491127,  0x11521130,  0x115b1139,  0xfa60f84,  0xf8f0f6d,  0xf980f76,  0xfa10f7f,  0x12921270,  0x127b1259,  0x12841262,  0x128d126b,  0x107f105d,  0x10681046,  0x1071104f,  0x107a1058,  0x10961099,  0x10e7108b,  0x1092,  0x10e310e0,  0xeeb0eee,  0xee80ee5,  0x2a0f35,  0x2a1170,  0x200051,  0x116b1115,  0x111c,  0x11671164,  0xf430f40,  0xf630f60,  0x2d0faa,  0x350031,  0x1178117b,  0x11851181,  0x0,  0x118911ab,  0xfb70fba,  0xfb40fb1,  0x3c0000,  0x440040,  0x12061209,  0x1213120f,  0x11f511f2,  0x12171239,  0x1019101c,  0x10161013,  0x18100a,  0x995001c,  0x0,  0x129d1247,  0x124e,  0x12991296,  0xfed0fea,  0x103c1039,  0x31083,  0x39,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x1,  0x0,  0x0,  0x1a690000,  0x0,  0x0,  0x4e0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2fc02fa,  0x2ff,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x1a6f0000,  0x1a72,  0x1a7e1a7b,  0x0,  0x0,  0x8f,  0xc,  0x0,  0x0,  0x0,  0x5630000,  0x920560,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a760000,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xae00305,  0x0,  0x3740365,  0x3920383,  0x3b003a1,  0x1aad02f4,  0xa10544,  0xb3b00a5,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x1aad02f4,  0xa10544,  0xa5,  0xa7d0692,  0xb410787,  0xb0d0e8c,  0xa280b79,  0xb3b05d3,  0x8400cd3,  0xba3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x83f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9a2099e,  0xe4d05e3,  0xa1e0000,  0xe770a22,  0xe500000,  0x6ac0602,  0x6ac06ac,  0xe6d0b0d,  0x6cf06cf,  0xa280734,  0x77e0000,  0x786,  0x6af0000,  0x82c083b,  0x82c082c,  0x0,  0x88f0863,  0x897,  0x60a,  0x77c,  0x60a,  0x5b0071a,  0x5e305d5,  0xa7d0000,  0x67e0629,  0x7230000,  0x13540787,  0x136a1362,  0xae0136f,  0x6800000,  0x10ec11ee,  0x10060f3a,  0x1aab,  0x0,  0x5e60000,  0xa7d0a2e,  0x73e0ae0,  0x0,  0x0,  0x0,  0x3e203da,  0x3ca03c1,  0x3d20455,  0x4980459,  0x3d604cf,  0x3de04e7,  0x4eb049c,  0x3be0511,  0x6d106cf,  0x6de06d4,  0x91806b2,  0x91f091b,  0x68206e1,  0x950094d,  0x5e30734,  0x72305e6,  0xb300ae0,  0xb3d0b33,  0xdcf086a,  0xdd60dd2,  0xb410b40,  0xdfd0dfa,  0x9a00a28,  0x5d30a2e,  0x0,  0x0,  0x0,  0x0,  0x30d0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a8d1a86,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a92,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a950000,  0x1a981a9b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1aa0,  0x0,  0x1aa50000,  0x0,  0x1aa8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1aaf,  0x1ab2,  0x0,  0x0,  0x1ab81ab5,  0x1ac10000,  0x1ac4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ac80000,  0x0,  0x1acb,  0x1ace0000,  0x1ad10000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x556,  0x1ad7,  0x0,  0x0,  0x0,  0x0,  0x1ad40000,  0x55b054a,  0x1add1ada,  0x0,  0x1ae31ae0,  0x0,  0x1ae91ae6,  0x0,  0x0,  0x0,  0x1aef1aec,  0x0,  0x1afb1af8,  0x0,  0x1b011afe,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b0d1b0a,  0x1b131b10,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1af51af2,  0x1b071b04,  0x0,  0x0,  0x0,  0x1b191b16,  0x1b1f1b1c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b350000,  0x1b37,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3430314,  0x365030f,  0x3830374,  0x3a10392,  0x31c03b0,  0x342032f,  0x3640355,  0x3820373,  0x3a00391,  0x3f703af,  0xd900a3,  0xe600e2,  0xee00ea,  0xf600f2,  0xa700fa,  0xb100ac,  0xbb00b6,  0xc500c0,  0xcf00ca,  0xdd00d4,  0x3460319,  0x3680359,  0x3860377,  0x3a40395,  0x31f03b3,  0x3450332,  0x3670358,  0x3850376,  0x3a30394,  0x3fa03b2,  0x16a0166,  0x172016e,  0x17a0176,  0x182017e,  0x18a0186,  0x192018e,  0x19a0196,  0x1a2019e,  0x1aa01a6,  0x1b201ae,  0x1ba01b6,  0x1c201be,  0x1ca01c6,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x305,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1abc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x54f0542,  0x552,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b2c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6b2073e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b2f0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x227c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26b00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1efb1ee9,  0x1f091f01,  0x1f131f0d,  0x1f1b1f17,  0x1f4b1f21,  0x1f5f1f57,  0x1f6f1f67,  0x1f871f77,  0x1f8b1f89,  0x1fb91fa5,  0x1fc51fc1,  0x1fcd1fc7,  0x1fdd1fdb,  0x1feb1fe9,  0x1ff71fef,  0x204f2045,  0x2079206f,  0x207f207d,  0x20982087,  0x20b420ae,  0x20ca20c4,  0x20ce20cc,  0x20dc20da,  0x20f820f2,  0x210020fc,  0x210f2108,  0x21292113,  0x212f212b,  0x21352131,  0x21412139,  0x218b214f,  0x21972195,  0x21d921d7,  0x21e521e3,  0x21ed21e9,  0x32521f1,  0x3292211,  0x22602223,  0x226e2266,  0x227a2274,  0x2280227e,  0x22842282,  0x22e22286,  0x230c2306,  0x2310230e,  0x23162312,  0x23222318,  0x23342330,  0x23562354,  0x235c235a,  0x23622360,  0x23762374,  0x23862384,  0x238a2388,  0x23a62394,  0x23aa23a8,  0x23dc23bc,  0x23ee23de,  0x23fa23f6,  0x241c240a,  0x2442243e,  0x2452244c,  0x245a2456,  0x245e245c,  0x246c246a,  0x247e247a,  0x24842482,  0x248e248a,  0x24922490,  0x24982496,  0x24f224e8,  0x250e250c,  0x25282512,  0x2530252c,  0x25522534,  0x25582554,  0x255c255a,  0x25742572,  0x25822578,  0x25922584,  0x25982596,  0x25ba25aa,  0x25c425c2,  0x25de25c8,  0x25e825e0,  0x260025fa,  0x26142608,  0x261a2618,  0x261e261c,  0x26262624,  0x2638262a,  0x263c263a,  0x264a2648,  0x2658264e,  0x265c265a,  0x26622660,  0x26662664,  0x26702668,  0x267e267c,  0x26862684,  0x268a2688,  0x2690268e,  0x26982692,  0x26a0269c,  0x26a626a2,  0x26aa26a8,  0x26b226ae,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b49,  0x1fcf1fcd,  0x1fd1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b7e,  0x1b81,  0x1b84,  0x1b87,  0x1b8a,  0x1b8d,  0x1b90,  0x1b93,  0x1b96,  0x1b99,  0x1b9c,  0x1b9f,  0x1ba20000,  0x1ba50000,  0x1ba80000,  0x0,  0x0,  0x0,  0x1bae1bab,  0x1bb10000,  0x1bb4,  0x1bba1bb7,  0x1bbd0000,  0x1bc0,  0x1bc91bc6,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b7b,  0x0,  0x0,  0x870000,  0x8a,  0x1bcc1bd3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1c26,  0x1c43,  0x1bf6,  0x1c92,  0x1c9b,  0x1caf,  0x1cbf,  0x1cca,  0x1ccf,  0x1cdc,  0x1ce1,  0x1ceb,  0x1cf20000,  0x1cf70000,  0x1c100000,  0x0,  0x0,  0x0,  0x1d261d1d,  0x1d3b0000,  0x1d42,  0x1d611d57,  0x1d760000,  0x1d7e,  0x1caa1da1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1c01,  0x1e440000,  0x1e521e4d,  0x1e57,  0x0,  0x1ca11e60,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x19440000,  0x1a101949,  0x1a12194b,  0x19501a14,  0x19571955,  0x1a181a16,  0x1a1c1a1a,  0x1a201a1e,  0x195c19a6,  0x19661961,  0x196819b0,  0x196f196d,  0x19811977,  0x198e1983,  0x19981993,  0x1947199d,  0x19da19d8,  0x19de19dc,  0x19e219e0,  0x198c19e4,  0x19ea19e8,  0x19ee19ec,  0x19f21975,  0x19f619f4,  0x19fa19f8,  0x19fe197f,  0x19a219d4,  0x1a2219a4,  0x1a261a24,  0x1a2a1a28,  0x1a2e1a2c,  0x1a3019a8,  0x19aa1a32,  0x19ae19ac,  0x19b419b2,  0x19b819b6,  0x19bc19ba,  0x19c019be,  0x19c419c2,  0x19c819c6,  0x19cc19ca,  0x1a361a34,  0x19d019ce,  0x1a0019d2,  0x1a041a02,  0x1a081a06,  0x1a0c1a0a,  0x1a0e,  0x0,  0x1f171ee9,  0x20471eef,  0x1efd1ef1,  0x23641ef3,  0x1ef71f0d,  0x208c1eeb,  0x1f212051,  0x1d701ce,  0x1e901e0,  0x1fb01f2,  0x20d0204,  0x2330225,  0x245023c,  0x257024e,  0x1db01d2,  0x1ed01e4,  0x1ff01f6,  0x2110208,  0x2370229,  0x2490240,  0x25b0252,  0x216022e,  0x21e,  0x2700260,  0x2a00268,  0x2880274,  0x2840264,  0x290026c,  0x2c402b0,  0x2b802c0,  0x2a402ec,  0x2bc02ac,  0x2d002b4,  0x2c80298,  0x2d402e4,  0x278028c,  0x2a8029c,  0x27c02cc,  0x29402e8,  0x28002d8,  0x2e002dc,  0x21112021,  0x23fe21e3,  0x0,  0x0,  0x0,  0x0,  0x406082e,  0x41c0411,  0x4320427,  0x4400439,  0x44e0447,  0x475046e,  0x47f047c,  0x4850482,  0x194b1944,  0x19571950,  0x1961195c,  0x196f1968,  0x19831977,  0x1993198e,  0x199d1998,  0x194d1946,  0x19591952,  0x1963195e,  0x1971196a,  0x19851979,  0x19951990,  0x199f199a,  0x197c1988,  0x1974,  0x1f171ee9,  0x20471eef,  0x1f611f19,  0x1f5f1eed,  0x1fcd1f0f,  0x22e20329,  0x22232286,  0x204f25c8,  0x223b0325,  0x2240221b,  0x231c2007,  0x23ca255e,  0x23e21fab,  0x20982368,  0x1f4925a2,  0x22961fdf,  0x1f2b262c,  0x208a1f73,  0x1efd1ef1,  0x20fa1ef3,  0x1fc92001,  0x20b220b8,  0x1f292390,  0x1fd72568,  0x4882083,  0x48e048b,  0x4b10491,  0x4b704b4,  0x4bd04ba,  0x4c304c0,  0x4c904c6,  0x4e404cc,  0x34e033b,  0x4d604a3,  0x50304f2,  0x5290518,  0x327053a,  0x34d033a,  0xa8206b4,  0x7390a7f,  0x1bf11bd8,  0x1c0a1bff,  0x1c241c1a,  0x1c731c41,  0x1c991c90,  0x1cbd1cad,  0x1ccd1c1e,  0x1cdf1cda,  0x1cf01bfb,  0x1bde1cf5,  0x1d0f1ca6,  0x1c8e1d11,  0x1d1b1d0d,  0x1d551d39,  0x1d9f1d74,  0x1ddc1c31,  0x1def1c22,  0x1e041e00,  0x1e191e11,  0x1c351e1b,  0x1e341bed,  0x1e421c5d,  0x1e501e4b,  0x1e55,  0x1be01bda,  0x1beb1be5,  0x1bf91bf3,  0x1c0c1c04,  0x1c1c1c13,  0x1c331c20,  0x1c3c1c37,  0x1c2e1c29,  0x1c4b1c46,  0x1c501c57,  0x1c5f1c5c,  0x1c6d1c66,  0x1c7d1c61,  0x1c8b1c84,  0x1ca41c95,  0x1cb21ca8,  0x1cc21cb7,  0x1cd61cd2,  0x1cfa1ce4,  0x1c811d03,  0x1d171d0c,  0x1d291d35,  0x1d201d30,  0x1d4c1d45,  0x1d3e1d51,  0x1d6b1d64,  0x1d701d5a,  0x1d811d95,  0x1d9b1d85,  0x1d8f1d8a,  0x1dac1d79,  0x1db81da4,  0x1dbb1db2,  0x1dc51dbf,  0x1dce1dca,  0x1dd61dd2,  0x1de31dde,  0x1df11de6,  0x1c681df5,  0x1e0b1e06,  0x1e1f1e13,  0x1e291e24,  0x1e361e2e,  0x1c6f1e39,  0x33f0311,  0x3610352,  0x37f0370,  0x39d038e,  0x3bb03ac,  0x33e032b,  0x3600351,  0x37e036f,  0x39c038d,  0x3ba03ab,  0x40d0402,  0x4230418,  0xb0f042e,  0x56a0a53,  0xc580a0f,  0xa590ce6,  0xa600a5c,  0x210a06db,  0x20892200,  0x223d21f9,  0xc260cda,  0xbea11b4,  0x71c0b7b,  0x689075b,  0xb8c0a26,  0xc290cdd,  0x11c011b7,  0x6010bf6,  0xb7e068d,  0x68c0764,  0x11c30893,  0xa560bfd,  0xaec0b94,  0x11c60c35,  0xa300c00,  0xc030b97,  0xa340a33,  0xc070b9a,  0xa380a37,  0xc1b0b9e,  0x6910c1f,  0x7680b82,  0xcf60690,  0xd000cfa,  0xc380ce9,  0xc0f11c9,  0xc2c0ce0,  0xbed11ba,  0x76c0b86,  0xc2f0ce3,  0xbf011bd,  0x76f0b89,  0x77b0bb4,  0x5d70999,  0xa2d0a2a,  0x5e805ff,  0x6940a50,  0x6ae0b13,  0x71f0b3a,  0xba20722,  0xbbf0bbc,  0xbc60bc2,  0xbf90bf3,  0x8200c0b,  0x8230cd5,  0xd25082b,  0x9360869,  0x5d1092a,  0x34a0337,  0x36c035d,  0x38a037b,  0x3a80399,  0x32303b7,  0x3490336,  0x36b035c,  0x389037a,  0x3a70398,  0x3fe03b6,  0x4140409,  0x42a041f,  0x43c0435,  0x44a0443,  0x4710451,  0xaf40478,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26b4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe730e6b,  0x0,  0x0,  0x0,  0x22132556,  0x256a2584,  0x1eff22c6,  0x26ae1ff9,  0x209226ae,  0x202b25c8,  0x21872090,  0x244a2382,  0x250424e6,  0x25a8251e,  0x229a2254,  0x233c22f0,  0x25bc24ca,  0x1f112652,  0x225e1fe3,  0x24e42302,  0x20e6267a,  0x24dc22d6,  0x21a12526,  0x250a2478,  0x221d211f,  0x232822a6,  0x1f3125ae,  0x1fb31f7d,  0x225a21d5,  0x23922300,  0x24e02456,  0x257e24ec,  0x266a2610,  0x23b02678,  0x242c23d0,  0x25d624bc,  0x2540267e,  0x212d206d,  0x24682408,  0x23b4231a,  0x260c2566,  0x20d4206b,  0x22b02256,  0x242422ca,  0x25ec2438,  0x246e1fb1,  0x1f811f83,  0x242e23e6,  0x25f024c8,  0x21a3254e,  0x25462254,  0x20be1f05,  0x23322159,  0x1fc32372,  0x1f3923b8,  0x1ef5214b,  0x21e12290,  0x1fed2422,  0x23982063,  0x253824cc,  0x25962276,  0x21ab228c,  0x21bb24a8,  0x1f1f2370,  0x1f7f1f5d,  0x24182244,  0x253e2494,  0x1fb725c6,  0x20982011,  0x21ef2127,  0x23ba22d8,  0x265625e4,  0x268c2680,  0x220f1fa5,  0x2590226c,  0x217b210d,  0x21d12189,  0x22f622d0,  0x23e0234e,  0x24642432,  0x24d02588,  0x25d8259c,  0x1fa71f91,  0x22ee201b,  0x25382514,  0x2155211d,  0x227221b7,  0x232c2406,  0x20491f27,  0x20f020be,  0x233a215b,  0x24502348,  0x25ca2460,  0x2612260a,  0x1f332630,  0x25c023da,  0x216725fe,  0x1f451f15,  0x20d020c0,  0x225421e7,  0x238022fc,  0x25a624d6,  0x220726aa,  0x1fa325ea,  0x2233222d,  0x22be22a2,  0x236e2340,  0x242023ae,  0x1f612636,  0x25f22191,  0x20e21f3d,  0x258a22b2,  0x216b2143,  0x23322237,  0x1f9525f6,  0x20d82009,  0x222521fc,  0x2294224a,  0x2378233e,  0x25162446,  0x25c4251c,  0x1fcb2604,  0x200b22c0,  0x235022fe,  0x25f824de,  0x2682266e,  0x22ae2231,  0x23f6247c,  0x240e23fc,  0x22ea2326,  0x1f23254c,  0x1f9724b0,  0x21151f8f,  0x241421a5,  0x229c20b6,  0x258e220d,  0x25ee250e,  0x2123252c,  0x20371f4d,  0x0,  0x2061,  0x2205,  0x1f850000,  0x238c232a,  0x23cc23be,  0x23d823ce,  0x24102616,  0x2452,  0x24e2,  0x2544,  0x259e0000,  0x25b4,  0x0,  0x26422640,  0x26762644,  0x25fc25b0,  0x1f471f35,  0x1faf1f51,  0x1fd51fb5,  0x203d202f,  0x205f2041,  0x20d62065,  0x216120da,  0x21792175,  0x21db2185,  0x220921f3,  0x22a82246,  0x22ce22b6,  0x230822f8,  0x23b22342,  0x23c42240,  0x23c623c2,  0x23ca23c8,  0x23d623d4,  0x23f223e8,  0x24322400,  0x243a2436,  0x24582444,  0x249a2480,  0x24ce249a,  0x252e2522,  0x254a2548,  0x256e256c,  0x259e259a,  0x26282606,  0x215d2634,  0x248c274b,  0x0,  0x1f7b1ef9,  0x1f2f1f5b,  0x1f651f4f,  0x1fbb1fad,  0x2025202f,  0x203b202d,  0x20692061,  0x2094208e,  0x20aa20a2,  0x21252121,  0x214d213d,  0x21712165,  0x21792169,  0x21852173,  0x21bf2193,  0x21c921c5,  0x220521dd,  0x221f221d,  0x226e2229,  0x22a22276,  0x22c422c8,  0x22dc22ce,  0x23a422f8,  0x2324230a,  0x234a232a,  0x236a2358,  0x237e237c,  0x238e238c,  0x23a02396,  0x23b6239e,  0x240023f4,  0x2428240c,  0x24402432,  0x24b22458,  0x250024c6,  0x252a2524,  0x253a252e,  0x253c2544,  0x25462548,  0x254a2542,  0x256e2550,  0x25a4258c,  0x25ce25be,  0x260625f4,  0x26202616,  0x262e2628,  0x265e2634,  0x272126ae,  0x2733271f,  0x1ea11e8d,  0x27671ea3,  0x27a92779,  0x26ac26a4,  0x0,  0x0,  0x0,  0xadf0adb,  0xade0ae3,  0xd280ae2,  0xd28,  0x0,  0x0,  0x0,  0x0,  0x0,  0x134e0000,  0x13481345,  0x134b1351,  0x0,  0x0,  0x13850000,  0x13d20000,  0x135413a6,  0x1374136f,  0x1360138e,  0x13b7139b,  0x2f413cd,  0x13ca13c7,  0x13c313bf,  0x13591356,  0x1364135c,  0x1371136c,  0x137c1376,  0x137f,  0x13881382,  0x1390138b,  0x1398,  0x139d,  0x13a313a0,  0x13a80000,  0x13ab,  0x13b413b1,  0x13bc13b9,  0x137913cf,  0x13931367,  0x135f13ae,  0x18181818,  0x181e181e,  0x181e181e,  0x18201820,  0x18201820,  0x18241824,  0x18241824,  0x181c181c,  0x181c181c,  0x18221822,  0x18221822,  0x181a181a,  0x181a181a,  0x183c183c,  0x183c183c,  0x183e183e,  0x183e183e,  0x18281828,  0x18281828,  0x18261826,  0x18261826,  0x182a182a,  0x182a182a,  0x182c182c,  0x182c182c,  0x18321832,  0x18301830,  0x18341834,  0x182e182e,  0x18381838,  0x18361836,  0x18401840,  0x18401840,  0x18441844,  0x18441844,  0x18481848,  0x18481848,  0x18461846,  0x18461846,  0x184a184a,  0x184c184c,  0x184c184c,  0x186d186d,  0x18501850,  0x18501850,  0x184e184e,  0x184e184e,  0x15911591,  0x186a186a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18420000,  0x18421842,  0x18031842,  0x17ff1803,  0x180717ff,  0x185b1807,  0x18621862,  0x18551855,  0x18601860,  0x180b180b,  0x180b180b,  0x14151415,  0x17cd17cd,  0x180d180d,  0x17f117f1,  0x18011801,  0x17fd17fd,  0x18051805,  0x18091809,  0x17f51809,  0x17f517f5,  0x18641864,  0x18641864,  0x17d517d1,  0x17f517e5,  0x13f417f9,  0x13fe13f7,  0x1414140b,  0x141e1417,  0x1438142d,  0x146a144d,  0x1472146d,  0x1484147b,  0x148c1487,  0x14311422,  0x14d11435,  0x143c14d4,  0x150514fa,  0x151a150c,  0x15931562,  0x15a515a2,  0x15ba15b0,  0x15c815c5,  0x15e415df,  0x16071575,  0x163f160a,  0x16451642,  0x1653164c,  0x165b1656,  0x16711662,  0x16791674,  0x167f167c,  0x16851682,  0x16931688,  0x16aa1696,  0x16c816b9,  0x1579158c,  0x145116e0,  0x14591455,  0x145d1526,  0x172d1461,  0x174f1740,  0x17691758,  0x1771176c,  0x177f1774,  0x179c1782,  0x17aa17a3,  0x17c417b3,  0x14e417c7,  0x179714ee,  0x64005d,  0x72006b,  0x800079,  0x17e117dd,  0x17e917e5,  0x17f917f5,  0x140813db,  0x140e140b,  0x14171414,  0x144a1447,  0x1464144d,  0x146d146a,  0x14781475,  0x147e147b,  0x14871484,  0x16561653,  0x16741671,  0x16851679,  0x16931688,  0x158c1696,  0x16e01579,  0x152616e5,  0x17551752,  0x17631758,  0x176c1769,  0x17ad1797,  0x17b317b0,  0x17c417be,  0x17d117c7,  0x17d917d5,  0x17ed17e5,  0x13f713f4,  0x140b13fe,  0x141e1411,  0x1438142d,  0x1467144d,  0x148c147b,  0x14311422,  0x14d11435,  0x14fa143c,  0x150c1505,  0x1562151a,  0x1593156d,  0x15a515a2,  0x15ba15b0,  0x15df15c5,  0x157515e4,  0x160a1607,  0x1642163f,  0x164c1645,  0x1662165b,  0x167f167c,  0x16851682,  0x16aa1688,  0x16c816b9,  0x13e0158c,  0x14551451,  0x15261459,  0x1740172d,  0x1758174f,  0x17711766,  0x17851774,  0x17a3179c,  0x17b317aa,  0x17e515ed,  0x140b17ed,  0x144d1411,  0x147b1467,  0x151a1481,  0x154c1529,  0x16851557,  0x158c1688,  0x17661758,  0x15ed17b3,  0x162c1625,  0x15d71633,  0x15ff15da,  0x16191602,  0x152c161c,  0x155a152f,  0x1490155d,  0x142613fb,  0x1440142a,  0x159a1402,  0x15bd159d,  0x153415c0,  0x1546153b,  0x1549154c,  0x15701517,  0x15d715b7,  0x15ff15da,  0x16191602,  0x152c161c,  0x155a152f,  0x1490155d,  0x142613fb,  0x1440142a,  0x159a1402,  0x15bd159d,  0x153415c0,  0x1546153b,  0x1549154c,  0x15701517,  0x153415b7,  0x1546153b,  0x1529154c,  0x15c81557,  0x150514fa,  0x1534150c,  0x1546153b,  0x15df15c8,  0x13e313e3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x14301421,  0x14341430,  0x1450143b,  0x14581454,  0x14a314a3,  0x14c114c5,  0x14fd1508,  0x15211501,  0x151d1521,  0x15251525,  0x15651565,  0x153e1596,  0x1537153e,  0x154f154f,  0x15531553,  0x15b315a8,  0x15cb15b3,  0x15cf15cb,  0x15e715d3,  0x15f315f3,  0x160d15f7,  0x16111615,  0x16481648,  0x16691665,  0x16c416bc,  0x16ad16c0,  0x16cb16ad,  0x16d216cb,  0x16fe16d2,  0x170b1702,  0x16f316eb,  0x17161712,  0x0,  0x177716ef,  0x1743177b,  0x17341747,  0x17381734,  0x175b175f,  0x17b617b6,  0x14291401,  0x14431425,  0x1460143f,  0x14ab145c,  0x14a7148f,  0x1569150f,  0x15ac1542,  0x16d616b5,  0x179f17a6,  0x172117ba,  0x174b166d,  0x16bc1665,  0x168f15fb,  0x171a1730,  0x168b16b1,  0x173016b1,  0x14ba1493,  0x164f16f7,  0x168b13fa,  0x159615e7,  0x173c1513,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x165e158f,  0x13d913de,  0x15731706,  0x15eb14e9,  0x1578158a,  0x1497157c,  0x14f1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b3102f6,  0x5401b33,  0x8d0546,  0x1b770093,  0x2ff1b79,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1a6d02fc,  0x9931a6b,  0xa10993,  0xe3b00a5,  0x1b4b0e3f,  0x1b451b4f,  0x1b391b47,  0x1b351b3b,  0x1b3d1b37,  0x1b411b3f,  0x1b43,  0x98b0000,  0xc098f,  0xc000c,  0x993000c,  0x9930993,  0x1b3102f6,  0x2fa,  0x5400546,  0x8d0093,  0xa11a6d,  0xe3b00a5,  0x1b4b0e3f,  0x971b4f,  0x2f2009d,  0x2f802f4,  0x5590548,  0x544,  0x99098d,  0x566009b,  0x0,  0x0,  0x161f0057,  0x5a,  0x61,  0x16220068,  0x1629006f,  0x16300076,  0x1637007d,  0x163a0084,  0x13e613d5,  0x13e913e6,  0x178f13e9,  0x13ec178f,  0x17ca13ec,  0x17ca17ca,  0x13d717ca,  0x13f213d7,  0x13f213f2,  0x141a13f2,  0x141c141a,  0x141c141c,  0x1470141c,  0x14701470,  0x13f51470,  0x13f513f5,  0x13f813f5,  0x13f813f8,  0x13ff13f8,  0x13ff13ff,  0x14e013ff,  0x14e214e0,  0x13dc14e2,  0x140913dc,  0x14f81409,  0x14f814f8,  0x153214f8,  0x15321532,  0x15601532,  0x15601560,  0x15a01560,  0x15a015a0,  0x15c315a0,  0x15c315c3,  0x15dd15c3,  0x15dd15dd,  0x15e215dd,  0x15e215e2,  0x160515e2,  0x16051605,  0x163d1605,  0x163d163d,  0x1659163d,  0x16591659,  0x16771659,  0x16771677,  0x14ec1677,  0x14ec14ec,  0x140c14ec,  0x140c140c,  0x140f140c,  0x140f140f,  0x13e1140f,  0x13e113e1,  0x178813e1,  0x14151788,  0x13fc1415,  0x13fc13fc,  0x169e13fc,  0x16a2169e,  0x16a616a2,  0x169b16a6,  0x169b,  0x0,  0x8d0000,  0x970095,  0x9b0099,  0x9f009d,  0xa500a1,  0x2f402f2,  0x2f802f6,  0x30302fa,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x5460540,  0x5440548,  0x930559,  0x5680566,  0x5e305d5,  0x62905e6,  0x687067e,  0x6cf06ac,  0x71a0607,  0x7230734,  0x7a4077e,  0x83b06af,  0x85e082c,  0x56b088d,  0x77006b2,  0x95a0682,  0x98b060a,  0x98f098d,  0x9930991,  0x6920995,  0x9a00937,  0xa7d0a2e,  0x6020ad9,  0xae00b0d,  0xb79073e,  0x5d30a28,  0x7870b3b,  0x5d80cd3,  0x8400a11,  0xa240ba3,  0xde1086a,  0x6950b41,  0xe3b0611,  0xe3f0e3d,  0x1b280e41,  0x1b331b2a,  0x1b3f1b3d,  0x1e5c1b31,  0x1bd61e55,  0x1bfd1bef,  0x1c181c08,  0x1e0f1e02,  0x1cee1e17,  0x1bd81c16,  0x1bff1bf1,  0x1c1a1c0a,  0x1c411c24,  0x1c901c73,  0x1cad1c99,  0x1c1e1cbd,  0x1cda1ccd,  0x1bfb1cdf,  0x1cf51cf0,  0x1ca61bde,  0x1d111d0f,  0x1d0d1c8e,  0x1d391d1b,  0x1d741d55,  0x1c311d9f,  0x1c221ddc,  0x1e001def,  0x1e111e04,  0x1e1b1e19,  0x1bed1c35,  0x1c5d1e34,  0x1c061e42,  0x8b0088,  0x194419d4,  0x1a101949,  0x1a12194b,  0x19501a14,  0x19571955,  0x1a181a16,  0x1a1c1a1a,  0x1a201a1e,  0x195c19a6,  0x19661961,  0x196819b0,  0x196f196d,  0x19811977,  0x198e1983,  0x19981993,  0x199d,  0x0,  0x19d81947,  0x19dc19da,  0x19e019de,  0x0,  0x19e419e2,  0x19e8198c,  0x19ec19ea,  0x0,  0x197519ee,  0x19f419f2,  0x19f819f6,  0x0,  0x197f19fa,  0x19fe,  0x0,  0xe450e43,  0x90e4b,  0xe470e49,  0x1a82,  0x1a841b22,  0x1a8b1a89,  0x1b241a90,  0x1b26,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26b6,  0x26b9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26bc0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26c226bf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26c826c5,  0x26cf26cb,  0x26d726d3,  0x26db,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26df0000,  0x26e226ea,  0x26e626ed,  0x26f1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0x602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x568,  0x5e605e3,  0x0,  0x687,  0x6070000,  0x71a,  0x77e0000,  0x6af07a4,  0x83b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90000,  0xb0d0000,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30000,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e60000,  0x67e0629,  0x687,  0x6070000,  0x734071a,  0x77e0723,  0x6af07a4,  0x83b,  0x88d085e,  0x6b2056b,  0x6820770,  0x95a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e60000,  0x67e0629,  0x687,  0x60706cf,  0x734071a,  0x723,  0x7a4,  0x0,  0x88d085e,  0x6b2056b,  0x6820770,  0x95a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x9370692,  0xa2e09a0,  0xad90a7d,  0xb0d0602,  0x73e0ae0,  0xa280b79,  0xb3b05d3,  0xcd30787,  0xa1105d8,  0xba30840,  0x86a0a24,  0xb410de1,  0x6110695,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0xb410de1,  0x6110695,  0xe800e6f,  0x0,  0xf380ee3,  0xf3c0f3a,  0xf5c0f3e,  0xfad0f5e,  0xfde0faf,  0xfe20fe0,  0xfe60fe4,  0x10060fe8,  0xfad1008,  0x100f100d,  0x10311011,  0x10351033,  0x1aa3077c,  0x10ea1086,  0x10ee10ec,  0x110e10f0,  0x116e1110,  0x11ae1170,  0x11b211b0,  0x11ce11cc,  0x11ee11d0,  0x11f811f0,  0x11fc11fa,  0x123c11fe,  0x1240123e,  0x1a9e1242,  0x116e10f0,  0x123c11ae,  0x11ee11f0,  0xf380ee3,  0xf3c0f3a,  0xf5c0f3e,  0xfad0f5e,  0xfde0faf,  0xfe20fe0,  0xfe60fe4,  0x10060fe8,  0xfad1008,  0x100f100d,  0x10311011,  0x10351033,  0x1aa3077c,  0x10ea1086,  0x10ee10ec,  0x110e10f0,  0x116e1110,  0x11ae1170,  0x11b211b0,  0x11ce11cc,  0x11ee11d0,  0x11f811f0,  0x11fc11fa,  0x123c11fe,  0x1240123e,  0x1a9e1242,  0x116e10f0,  0x123c11ae,  0x11ee11f0,  0xf380ee3,  0xf3c0f3a,  0xf5c0f3e,  0xfad0f5e,  0xfde0faf,  0xfe20fe0,  0xfe60fe4,  0x10060fe8,  0xfad1008,  0x100f100d,  0x10311011,  0x10351033,  0x1aa3077c,  0x10ea1086,  0x10ee10ec,  0x110e10f0,  0x116e1110,  0x11ae1170,  0x11b211b0,  0x11ce11cc,  0x11ee11d0,  0x11f811f0,  0x11fc11fa,  0x123c11fe,  0x1240123e,  0x1a9e1242,  0x116e10f0,  0x123c11ae,  0x11ee11f0,  0xf380ee3,  0xf3c0f3a,  0xf5c0f3e,  0xfad0f5e,  0xfde0faf,  0xfe20fe0,  0xfe60fe4,  0x10060fe8,  0xfad1008,  0x100f100d,  0x10311011,  0x10351033,  0x1aa3077c,  0x10ea1086,  0x10ee10ec,  0x110e10f0,  0x116e1110,  0x11ae1170,  0x11b211b0,  0x11ce11cc,  0x11ee11d0,  0x11f811f0,  0x11fc11fa,  0x123c11fe,  0x1240123e,  0x1a9e1242,  0x116e10f0,  0x123c11ae,  0x11ee11f0,  0xf380ee3,  0xf3c0f3a,  0xf5c0f3e,  0xfad0f5e,  0xfde0faf,  0xfe20fe0,  0xfe60fe4,  0x10060fe8,  0xfad1008,  0x100f100d,  0x10311011,  0x10351033,  0x1aa3077c,  0x10ea1086,  0x10ee10ec,  0x110e10f0,  0x116e1110,  0x11ae1170,  0x11b211b0,  0x11ce11cc,  0x11ee11d0,  0x11f811f0,  0x11fc11fa,  0x123c11fe,  0x1240123e,  0x1a9e1242,  0x116e10f0,  0x123c11ae,  0x11ee11f0,  0x12a212a0,  0x0,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x3140305,  0x30f0343,  0x3740365,  0x3920383,  0x3b003a1,  0x13f213d7,  0x14e013f5,  0x17880000,  0x13f81409,  0x13fc15c3,  0x14ec1677,  0x140f140c,  0x15e214f8,  0x1560163d,  0x13dc1659,  0x141c1532,  0x13ff1470,  0x15a014e2,  0x160515dd,  0x184a1814,  0x1816183a,  0x13f20000,  0x13f5,  0x13e1,  0x13f80000,  0x13fc0000,  0x14ec1677,  0x140f140c,  0x15e214f8,  0x1560163d,  0x1659,  0x141c1532,  0x13ff1470,  0x15a00000,  0x16050000,  0x0,  0x0,  0x0,  0x13f5,  0x0,  0x13f80000,  0x13fc0000,  0x14ec0000,  0x140f0000,  0x15e214f8,  0x15600000,  0x1659,  0x1532,  0x13ff0000,  0x15a00000,  0x16050000,  0x184a0000,  0x18160000,  0x13f20000,  0x13f5,  0x13e1,  0x13f80000,  0x13fc15c3,  0x1677,  0x140f140c,  0x15e214f8,  0x1560163d,  0x1659,  0x141c1532,  0x13ff1470,  0x15a00000,  0x160515dd,  0x1814,  0x183a,  0x13f213d7,  0x14e013f5,  0x178813e1,  0x13f81409,  0x13fc15c3,  0x14ec0000,  0x140f140c,  0x15e214f8,  0x1560163d,  0x13dc1659,  0x141c1532,  0x13ff1470,  0x15a014e2,  0x160515dd,  0x0,  0x0,  0x13f20000,  0x14e013f5,  0x17880000,  0x13f81409,  0x13fc15c3,  0x14ec0000,  0x140f140c,  0x15e214f8,  0x1560163d,  0x13dc1659,  0x141c1532,  0x13ff1470,  0x15a014e2,  0x160515dd,  0x0,  0x0,  0x307030a,  0x3f10316,  0x4ab0468,  0x4fa04de,  0x520050b,  0x531,  0x0,  0x0,  0x10200fe,  0x10a0106,  0x112010e,  0x11a0116,  0x122011e,  0x12a0126,  0x132012e,  0x13a0136,  0x142013e,  0x14a0146,  0x152014e,  0x15a0156,  0x162015e,  0x5e31b4d,  0x5e5082c,  0x933,  0x5d50568,  0x5e605e3,  0x67e0629,  0x6ac0687,  0x60706cf,  0x734071a,  0x77e0723,  0x6af07a4,  0x82c083b,  0x88d085e,  0x6b2056b,  0x6820770,  0x60a095a,  0x76c06b1,  0x8660860,  0x9300827,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x761075e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x606,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1c9e1bc3,  0x1cad,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20b02197,  0x1cf71ff3,  0x20811f17,  0x208c2532,  0x21fe1f1d,  0x21e722f2,  0x21451f9d,  0x21eb1f69,  0x24261f93,  0x2560235c,  0x200f2073,  0x219d22cc,  0x1ee921b3,  0x25a01eef,  0x1efd20fa,  0x21ad2001,  0x21992574,  0x23f023d2,  0x22bc2005,  0x329221b,  0x1f9f2366,  0x2035,  0x0,  0x0,  0x1b511b69,  0x1b5d1b55,  0x1b611b6d,  0x1b591b71,  0x1b65,  0x0,  0x0,  0x0,  0x1ffd2147,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1f031f07,  0x26f51f0b,  0x1f351f2d,  0x1f3b1f37,  0x1f411f3f,  0x1f431f47,  0x26fd1e63,  0x1f531f51,  0x1f631f55,  0x1e6526f7,  0x1f691f59,  0x1f7126fb,  0x1f251f75,  0x1f7b1f79,  0x1f8927b9,  0x1e691f8d,  0x1f9b1f99,  0x1fa11f9f,  0x1fad1e6b,  0x1fb51faf,  0x1fbd1fbb,  0x1fc31fbf,  0x1fd51fd3,  0x1fe11fd9,  0x1fe71fe5,  0x1fe71fe7,  0x22e42703,  0x1ff51ff1,  0x1ffb2705,  0x20031fff,  0x200d2017,  0x20152013,  0x201d2019,  0x2023201f,  0x20292027,  0x202d2029,  0x20332031,  0x204b2039,  0x204d203d,  0x2043203f,  0x20711f8f,  0x20572055,  0x20532059,  0x205b205d,  0x27072067,  0x20772075,  0x2081207b,  0x20962085,  0x270b2709,  0x209e209c,  0x209a20a0,  0x1e6d20a4,  0x20a81e6f,  0x20ac20ac,  0x20ba270d,  0x20be20bc,  0x270f20c2,  0x20c820c6,  0x20cc2137,  0x20d21e71,  0x20e020da,  0x271320de,  0x271520e4,  0x20e820ea,  0x20f420ec,  0x1e7320f6,  0x210220fe,  0x21062104,  0x27171e75,  0x21171e77,  0x211b2119,  0x27cd211f,  0x271b212b,  0x2486271b,  0x21332133,  0x27291e79,  0x213b277d,  0x1e7b213f,  0x21512149,  0x21572153,  0x1e7f215f,  0x21611e7d,  0x2163271d,  0x216f216d,  0x216f2171,  0x21792177,  0x217d2181,  0x2183217f,  0x21872185,  0x218f210b,  0x219f219b,  0x21b121a7,  0x21af2723,  0x21b521a9,  0x21c321b9,  0x21c72725,  0x21bd21c1,  0x21cb1e81,  0x21d321cf,  0x1e8321cd,  0x21df21db,  0x21f52727,  0x22032215,  0x22091e89,  0x1e851e87,  0x1f6d1f6b,  0x220b2217,  0x1ebb2470,  0x221f221d,  0x222b2221,  0x27312227,  0x22351e8b,  0x2242222f,  0x27352246,  0x22392248,  0x1e8d224c,  0x2250224e,  0x22582252,  0x225c2737,  0x22621e8f,  0x22642739,  0x226a1e91,  0x22762270,  0x273b2278,  0x273d2711,  0x273f2288,  0x2292228e,  0x2298228a,  0x22a822a0,  0x22a422a2,  0x22ac22aa,  0x229e2741,  0x22ba22b8,  0x22c41e93,  0x274322c2,  0x22d222b4,  0x27472745,  0x22de22d4,  0x22da22dc,  0x22e01e95,  0x22e622e8,  0x26f922ec,  0x274922f4,  0x274d22fa,  0x230a2304,  0x274f2314,  0x2320231e,  0x27532751,  0x2336232e,  0x23381e97,  0x1e991e99,  0x23462344,  0x234c234a,  0x1e9b2352,  0x2755235e,  0x2757236c,  0x27192372,  0x2759237a,  0x275d275b,  0x1e9f1e9d,  0x27612396,  0x2763275f,  0x239a2765,  0x239c239c,  0x1ea323a0,  0x1ea523a2,  0x27691ea7,  0x23b023ac,  0x1ea923b6,  0x23c8276b,  0x276f276d,  0x23e423d8,  0x23e81eab,  0x23ec23ea,  0x27732771,  0x23f82773,  0x27751ead,  0x24042402,  0x27771eaf,  0x1eb12412,  0x2416241a,  0x277b241e,  0x1eb3242a,  0x24342430,  0x1eb5243c,  0x2781277f,  0x27831eb7,  0x27852448,  0x2454244e,  0x27872458,  0x24622789,  0x2466278b,  0x1eb9272b,  0x24742472,  0x24761ebd,  0x278d20a6,  0x272d278f,  0x2486272f,  0x25942488,  0x249e1ebf,  0x24a0249c,  0x24a21fa9,  0x24a624a4,  0x279124aa,  0x24ac24a8,  0x24b824b6,  0x24ba24ae,  0x24ce24c4,  0x24be24b4,  0x24c224c0,  0x27972793,  0x1ec12795,  0x24d424d2,  0x279f24d8,  0x279924da,  0x1ec51ec3,  0x279d279b,  0x24ea1ec7,  0x24ee24ec,  0x24f624f0,  0x24fa24f4,  0x250024f8,  0x24fe24fc,  0x1ec92502,  0x25082506,  0x25101ecb,  0x27a12512,  0x251a2518,  0x25201ecd,  0x27a31e67,  0x1ecf27a5,  0x25361ed1,  0x25502542,  0x27a72558,  0x25642562,  0x25762570,  0x26ff27ab,  0x257a257c,  0x27012580,  0x258c2586,  0x27af27ad,  0x25b225ac,  0x27b125b6,  0x25cc25b8,  0x25d425d2,  0x25da25d0,  0x27b325dc,  0x1ed325e2,  0x27b525e6,  0x26021ed5,  0x260e20ee,  0x27bb27b7,  0x1ed91ed7,  0x27bd2622,  0x27bf1edb,  0x262e262e,  0x27c12632,  0x1edd263e,  0x264c2646,  0x26542650,  0x27c31edf,  0x266c265e,  0x1ee12672,  0x26741ee3,  0x1ee527c5,  0x27c927c7,  0x268627cb,  0x26901ee7,  0x26962694,  0x269e269a,  0x27cf26a2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//12288 bytes
+enum canonMappingTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x240], [ 0x100,  0x400,  0x1380], [ 0x2020100,  0x3020202,  0x2020204,  0x2050202,  0x2020202,  0x6020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x10000,  0x30002,  0x50004,  0x6,  0x0,  0x70000,  0x90008,  0xb000a,  0xc0000,  0x0,  0x0,  0xd,  0xe0000,  0x0,  0x0,  0x0,  0x0,  0x10000f,  0x110000,  0x130012,  0x0,  0x140000,  0x160015,  0x170000,  0x180000,  0x190000,  0x1a0000,  0x0,  0x0,  0x1b0000,  0x1c,  0x1d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1f001e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x210020,  0x230022,  0x250024,  0x270026,  0x28,  0x0,  0x29,  0x2b002a,  0x2d002c,  0x2f002e,  0x30,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x310000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x320000,  0x340033,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x360035,  0x380037,  0x3a0039,  0x3c003b,  0x3e003d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f,  0x40,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x410000,  0x430042,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x450044,  0x470046,  0x490048,  0x4b004a,  0x4c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf000c,  0x250012,  0x4f0045,  0x850000,  0xa1009e,  0xcb00a4,  0x121011e,  0x1330124,  0x1880000,  0x1a0019d,  0x1b601a3,  0x1da,  0x26d0000,  0x2730270,  0x2f30287,  0x0,  0x322031f,  0x3380325,  0x3620358,  0x3980000,  0x3b403b1,  0x3de03b7,  0x4370434,  0x446043a,  0x49c0000,  0x4b404b1,  0x4ca04b7,  0x4ee,  0x5840000,  0x58a0587,  0x60d059e,  0x61c0000,  0x33b0028,  0x33e002b,  0x380006d,  0x38c0079,  0x38f007c,  0x392007f,  0x3950082,  0x3a2008f,  0x0,  0x3cd00ba,  0x3d800c5,  0x3db00c8,  0x3fb00e8,  0x3e400d1,  0x40a00f7,  0x41000fd,  0x4130100,  0x4190106,  0x41c0109,  0x0,  0x43d0127,  0x440012a,  0x443012d,  0x45c0149,  0x130,  0x0,  0x462014f,  0x471015d,  0x1630000,  0x1700477,  0x1660484,  0x47a,  0x0,  0x1850000,  0x1940499,  0x18e04a8,  0x4a2,  0x0,  0x4d901c5,  0x4e401d0,  0x4f801e4,  0x0,  0x52f021b,  0x5450231,  0x5350221,  0x54b0237,  0x552023e,  0x5690255,  0x5580244,  0x57b0264,  0x572025b,  0x0,  0x58d0276,  0x594027d,  0x59b0284,  0x5b4029d,  0x5b702a0,  0x5e002c9,  0x5f502de,  0x61002f6,  0x30b0302,  0x3110628,  0x314062e,  0x631,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50401f0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2ac0000,  0x5c3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x560000,  0x13d0369,  0x1e70450,  0x2a304fb,  0x29205ba,  0x28e05a9,  0x29605a5,  0x28a05ad,  0x5a1,  0x35b0048,  0x3540041,  0x653064a,  0x0,  0x4160103,  0x46b0157,  0x522020e,  0x5250211,  0x65f065c,  0x465,  0x0,  0x40700f4,  0x0,  0x4960182,  0x3650052,  0x6500647,  0x656064d,  0x36c0059,  0x36f005c,  0x3e700d4,  0x3ea00d7,  0x4530140,  0x4560143,  0x4fe01ea,  0x50101ed,  0x5380224,  0x53b0227,  0x5bd02a6,  0x5c002a9,  0x5660252,  0x5780261,  0x0,  0x4250112,  0x0,  0x0,  0x0,  0x351003e,  0x3f400e1,  0x4f101dd,  0x4d101bd,  0x4e701d3,  0x4ea01d6,  0x61602fc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000d,  0x66b0000,  0x137,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x662,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x63d0000,  0x6450670,  0x6df06c3,  0x72c,  0x759,  0x7980778,  0x8d1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7810735,  0x84707e9,  0x8c10867,  0x92f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x92808ca,  0x91f08fd,  0x95f,  0x0,  0x9b40000,  0x9b7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9cc09c6,  0x9c30000,  0x0,  0x9ba0000,  0x0,  0x0,  0x9d809e4,  0x9ed,  0x0,  0x0,  0x0,  0x0,  0x9de0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa0e0a08,  0xa050000,  0x0,  0xa410000,  0x0,  0x0,  0xa1a0a26,  0xa2f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa470a44,  0x0,  0x0,  0x0,  0x0,  0x9cf0000,  0xa11,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9ff09bd,  0xa0209c0,  0x0,  0xa0b09c9,  0x0,  0xa4d0a4a,  0xa1409d2,  0xa1709d5,  0x0,  0xa1d09db,  0xa2309e1,  0xa2909e7,  0x0,  0xa530a50,  0xa3e09fc,  0xa2c09ea,  0xa3209f0,  0xa3509f3,  0xa3809f6,  0x0,  0xa3b09f9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xac10abe,  0xac40ac7,  0xaca,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xad3,  0xacd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xad00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xae80000,  0x0,  0x0,  0x0,  0xaf10000,  0x0,  0xaf4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xad90ad6,  0xadf0adc,  0xae50ae2,  0xaee0aeb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb000000,  0xb03,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xafa0af7,  0xafd0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb120000,  0x0,  0xb15,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb060000,  0xb0c0b09,  0x0,  0xb0f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb21,  0xb1e0000,  0xb24,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb1b0b18,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb27,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb300b2a,  0xb2d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb33,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb36,  0x0,  0x0,  0xb400000,  0xb43,  0xb3c0b39,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb4c0b46,  0xb49,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb4f,  0xb550b52,  0xb59,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb5f0000,  0x0,  0x0,  0x0,  0x0,  0xb620000,  0x0,  0x0,  0xb65,  0x0,  0xb680000,  0x0,  0x0,  0xb6b,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb5c0000,  0x0,  0x0,  0x0,  0x0,  0xb6e0000,  0xb710000,  0xb89,  0xb8c,  0x0,  0x0,  0x0,  0xb740000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb7a0000,  0x0,  0x0,  0x0,  0x0,  0xb7d0000,  0x0,  0x0,  0xb80,  0x0,  0xb830000,  0x0,  0x0,  0xb86,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb770000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb8f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xb92,  0xb95,  0xb98,  0xb9b,  0xb9e,  0x0,  0xba1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xba40000,  0xba70000,  0x0,  0xbad0baa,  0xbb00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x37d006a,  0x3830070,  0x3860073,  0x3890076,  0x39b0088,  0x39f008c,  0x3a50092,  0x3ae009b,  0x3a80095,  0x3ab0098,  0x3d000bd,  0x3d400c1,  0x3fe00eb,  0x40100ee,  0x3f700e4,  0x40400f1,  0x40d00fa,  0x41f010c,  0x4280115,  0x422010f,  0x42b0118,  0x42e011b,  0x45f014c,  0x4490136,  0x4680154,  0x46e015a,  0x4740160,  0x47d0169,  0x480016c,  0x48a0176,  0x4870173,  0x48d0179,  0x490017c,  0x493017f,  0x49f018b,  0x4a50191,  0x4ae019a,  0x4ab0197,  0x4cd01b9,  0x4d501c1,  0x4dc01c8,  0x4e001cc,  0x5290215,  0x52c0218,  0x532021e,  0x53e022a,  0x541022d,  0x5480234,  0x5550241,  0x55f024b,  0x54e023a,  0x55b0247,  0x562024e,  0x56c0258,  0x575025e,  0x581026a,  0x57e0267,  0x5dd02c6,  0x5e602cf,  0x5e302cc,  0x5900279,  0x5970280,  0x5e902d2,  0x5ec02d5,  0x5ef02d8,  0x5f202db,  0x5fb02e4,  0x5f802e1,  0x60102e7,  0x60402ea,  0x60702ed,  0x61902ff,  0x62b030e,  0x6340317,  0x637031a,  0x56f0431,  0x62205fe,  0x6590000,  0x0,  0x0,  0x372005f,  0x35f004c,  0x32c0019,  0x3280015,  0x3340021,  0x330001d,  0x3750062,  0x3450032,  0x341002e,  0x34d003a,  0x3490036,  0x3790066,  0x3ed00da,  0x3e100ce,  0x3ca00b7,  0x3be00ab,  0x3ba00a7,  0x3c600b3,  0x3c200af,  0x3f000dd,  0x44d013a,  0x4590146,  0x51b0207,  0x4f501e1,  0x4be01aa,  0x4ba01a6,  0x4c601b2,  0x4c201ae,  0x51e020a,  0x50b01f7,  0x50701f3,  0x51301ff,  0x50f01fb,  0x5170203,  0x5da02c3,  0x5b1029a,  0x5ca02b3,  0x5c602af,  0x5d202bb,  0x5ce02b7,  0x5d602bf,  0x60a02f0,  0x6250308,  0x61f0305,  0x61302f9,  0x0,  0x0,  0x0,  0x81807f6,  0x81b07f9,  0x8240802,  0x82d080b,  0x69b0679,  0x69e067c,  0x6a70685,  0x6b0068e,  0x855084a,  0x858084d,  0x85c0851,  0x0,  0x6d106c6,  0x6d406c9,  0x6d806cd,  0x0,  0x890086e,  0x8930871,  0x89c087a,  0x8a50883,  0x70406e2,  0x70706e5,  0x71006ee,  0x71906f7,  0x8e808d9,  0x8eb08dc,  0x8ef08e0,  0x8f308e4,  0x7470738,  0x74a073b,  0x74e073f,  0x7520743,  0x90b0900,  0x90e0903,  0x9120907,  0x0,  0x767075c,  0x76a075f,  0x76e0763,  0x0,  0x9460937,  0x949093a,  0x94d093e,  0x9510942,  0x7840000,  0x7870000,  0x78b0000,  0x78f0000,  0x9880966,  0x98b0969,  0x9940972,  0x99d097b,  0x7bd079b,  0x7c0079e,  0x7c907a7,  0x7d207b0,  0x7e907e2,  0x8470844,  0x8670860,  0x8c108be,  0x8fd08fa,  0x91f091c,  0x95f0958,  0x0,  0x8360814,  0x81f07fd,  0x8280806,  0x831080f,  0x6b90697,  0x6a20680,  0x6ab0689,  0x6b40692,  0x8ae088c,  0x8970875,  0x8a0087e,  0x8a90887,  0x7220700,  0x70b06e9,  0x71406f2,  0x71d06fb,  0x9a60984,  0x98f096d,  0x9980976,  0x9a1097f,  0x7db07b9,  0x7c407a2,  0x7cd07ab,  0x7d607b4,  0x7f007f3,  0x84107e5,  0x7ec,  0x83d083a,  0x6730676,  0x670066d,  0x6bd,  0x8bc,  0x6400000,  0x8b90863,  0x86a,  0x8b508b2,  0x6c306c0,  0x6df06dc,  0xbb30726,  0xbb90bb6,  0x8c408c7,  0x8d108cd,  0x0,  0x8d508f7,  0x72f0732,  0x72c0729,  0xbbc0000,  0xbc20bbf,  0x9220925,  0x92f092b,  0x9190916,  0x9330955,  0x77b077e,  0x7780775,  0x63a0772,  0x31d063d,  0x0,  0x9b1095b,  0x962,  0x9ad09aa,  0x7590756,  0x7980795,  0x64307df,  0x0,  0xbc70bc5,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x793,  0x0,  0x4f0152,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbcc0bc9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbcf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbd20000,  0xbd50bd8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbdb,  0x0,  0xbde0000,  0x0,  0xbe1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbe4,  0xbe7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbea0000,  0x0,  0xbed,  0xbf00000,  0xbf30000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6,  0xbf9,  0x0,  0x0,  0x0,  0x0,  0xbf60000,  0x90003,  0xbff0bfc,  0x0,  0xc050c02,  0x0,  0xc0b0c08,  0x0,  0x0,  0x0,  0xc110c0e,  0x0,  0xc1d0c1a,  0x0,  0xc230c20,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc2f0c2c,  0xc350c32,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc170c14,  0xc290c26,  0x0,  0x0,  0x0,  0xc3b0c38,  0xc410c3e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc470000,  0xc49,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc44,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc4e,  0xc51,  0xc54,  0xc57,  0xc5a,  0xc5d,  0xc60,  0xc63,  0xc66,  0xc69,  0xc6c,  0xc6f,  0xc720000,  0xc750000,  0xc780000,  0x0,  0x0,  0x0,  0xc7e0c7b,  0xc810000,  0xc84,  0xc8a0c87,  0xc8d0000,  0xc90,  0xc960c93,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc4b,  0x0,  0x0,  0x0,  0x0,  0xc99,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc9f,  0xca2,  0xca5,  0xca8,  0xcab,  0xcae,  0xcb1,  0xcb4,  0xcb7,  0xcba,  0xcbd,  0xcc0,  0xcc30000,  0xcc60000,  0xcc90000,  0x0,  0x0,  0x0,  0xccf0ccc,  0xcd20000,  0xcd5,  0xcdb0cd8,  0xcde0000,  0xce1,  0xce70ce4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc9c,  0xcea0000,  0xcf00ced,  0xcf3,  0x0,  0xcf6,  0xfb71241,  0x124b125d,  0xd831043,  0x13270e29,  0xe991327,  0xe4f1293,  0xf550e97,  0x116710cd,  0x11fd11e3,  0x12791215,  0x10190feb,  0x109d1069,  0x128911c7,  0xd8d12f3,  0xff50e1d,  0x11e11079,  0xedb1309,  0x11d91051,  0xf65121d,  0x12031189,  0xfbd0eff,  0x108d1025,  0xd9d127d,  0xe050dd9,  0xff10f95,  0x10d31077,  0x11dd1171,  0x125911e7,  0x12fb12cf,  0x10e91307,  0x114d1107,  0x12a111b9,  0x122f130b,  0xf0b0e87,  0x117d112f,  0x10ed1083,  0x12cb1249,  0xecb0e85,  0x102f0fed,  0x11471047,  0x12b11159,  0x117f0e03,  0xddd0ddf,  0x114f1115,  0x12b511c5,  0xf67123d,  0x12350feb,  0xebb0d87,  0x10950f27,  0xe1110c1,  0xda510f1,  0xd7f0f1b,  0xf9d1011,  0xe231145,  0x10d70e7d,  0x122711c9,  0x126d1005,  0xf6f100d,  0xf7b11a5,  0xd9110bf,  0xddb0dc3,  0x113d0fdb,  0x122d1195,  0xe091291,  0xe9f0e37,  0xfa10f07,  0x10f31053,  0x12f712ab,  0x1313130d,  0xfb50df9,  0x12690ffd,  0xf490ef3,  0xf910f57,  0x106d104b,  0x111110af,  0x11791153,  0x11cd1261,  0x12a31271,  0xdfb0de9,  0x10670e41,  0x1227120b,  0xf230efd,  0x10030f77,  0x1091112d,  0xe670d97,  0xee50ebb,  0x109b0f29,  0x116b10a9,  0x12951175,  0x12d112c9,  0xd9f12dd,  0x128d110f,  0xf3512c1,  0xdb10d8f,  0xec70ebd,  0xfeb0f9f,  0x10cb1073,  0x127711d3,  0xfad1323,  0xdf712af,  0xfd10fcb,  0x103b1021,  0x10bd10a1,  0x114310e7,  0xdc512e3,  0x12b70f5d,  0xed70da9,  0x12631031,  0xf390f17,  0x10950fd5,  0xdeb12bb,  0xecf0e31,  0xfc30fa7,  0x10150fe1,  0x10c3109f,  0x120d1163,  0x128f1213,  0xe1312c5,  0xe33103d,  0x10b11075,  0x12bd11db,  0x130f12ff,  0x102d0fcf,  0x1121118b,  0x11331125,  0x1063108b,  0xd93123b,  0xded11ad,  0xef50de7,  0x11390f69,  0x101b0eb5,  0x12670fb3,  0x12b31205,  0xf031221,  0xe590db5,  0x0,  0xe7b,  0xfab,  0xde10000,  0x10cf108f,  0x110310f5,  0x110d1105,  0x113512d3,  0x116d,  0x11df,  0x1233,  0x12730000,  0x1283,  0x0,  0x12e912e7,  0x130512eb,  0x12bf127f,  0xdb30da1,  0xe010db9,  0xe170e07,  0xe5f0e53,  0xe790e63,  0xecd0e7f,  0xf2f0ed1,  0xf470f43,  0xf970f53,  0xfaf0fa3,  0x10270fdd,  0x10491035,  0x107d106f,  0x10eb10a3,  0x10fb10f7,  0x10fd10f9,  0x110110ff,  0x110b1109,  0x111d1117,  0x11531127,  0x115b1157,  0x11731161,  0x1197118d,  0x11cb1197,  0x12231219,  0x12391237,  0x124f124d,  0x1273126f,  0x12d912c7,  0xf2b12e1,  0x119313be,  0x0,  0xdd70d81,  0xd9b0dc1,  0xdc90db7,  0xe0b0dff,  0xe490e53,  0xe5d0e51,  0xe830e7b,  0xe9b0e95,  0xeb10ea9,  0xf050f01,  0xf1d0f13,  0xf3f0f33,  0xf470f37,  0xf530f41,  0xf7f0f5f,  0xf890f85,  0xfab0f99,  0xfbf0fbd,  0xfff0fc7,  0x10211005,  0x10411045,  0x10571049,  0x10e3106f,  0x1089107f,  0x10ab108f,  0x10b910b5,  0x10c910c7,  0x10d110cf,  0x10df10d5,  0x10ef10dd,  0x1127111f,  0x11491131,  0x115f1153,  0x11af1173,  0x11f911c3,  0x121f121b,  0x12291223,  0x122b1233,  0x12351237,  0x12391231,  0x124f123f,  0x12751265,  0x1299128b,  0x12c712b9,  0x12d512d3,  0x12db12d9,  0x12f912e1,  0x13941327,  0x13a61392,  0xd370d23,  0x13da0d39,  0x141c13ec,  0x13251321,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa7a0000,  0xabb0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xab50ab2,  0xaae0aaa,  0xa590a56,  0xa5f0a5c,  0xa680a65,  0xa710a6b,  0xa74,  0xa7d0a77,  0xa830a80,  0xa89,  0xa8c,  0xa920a8f,  0xa950000,  0xa98,  0xaa10a9e,  0xaa70aa4,  0xa6e0ab8,  0xa860a62,  0xa9b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1329,  0x132c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x132f0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13351332,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x133b1338,  0x1342133e,  0x134a1346,  0x134e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13520000,  0x1355135d,  0x13591360,  0x1364,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xd850d89,  0x13680d8b,  0xda10d99,  0xda70da3,  0xdad0dab,  0xdaf0db3,  0x13700cf9,  0xdbb0db9,  0xdc70dbd,  0xcfb136a,  0xdcb0dbf,  0xdd1136e,  0xd950dd3,  0xdd70dd5,  0xde3142c,  0xcff0de5,  0xdf10def,  0xdf50df3,  0xdff0d01,  0xe070e01,  0xe0d0e0b,  0xe110e0f,  0xe170e15,  0xe1b0e19,  0xe210e1f,  0xe210e21,  0x105d1376,  0xe270e25,  0xe2b1378,  0xe2f0e2d,  0xe350e3d,  0xe3b0e39,  0xe430e3f,  0xe470e45,  0xe4d0e4b,  0xe510e4d,  0xe570e55,  0xe690e5b,  0xe6b0e5f,  0xe650e61,  0xe890de7,  0xe710e6f,  0xe6d0e73,  0xe750e77,  0x137a0e81,  0xe8d0e8b,  0xe910e8f,  0xe9d0e93,  0x137e137c,  0xea50ea3,  0xea10ea7,  0xd030eab,  0xeaf0d05,  0xeb30eb3,  0xeb71380,  0xebb0eb9,  0x13820ebf,  0xec30ec1,  0xec50f0f,  0xec90d07,  0xed50ed1,  0x13860ed3,  0x13880ed9,  0xedd0edf,  0xee70ee1,  0xd090ee9,  0xeed0eeb,  0xef10eef,  0x138a0d0b,  0xef70d0d,  0xefb0ef9,  0x14400eff,  0x138e0f09,  0x118f138e,  0xf0d0f0d,  0x139c0d0f,  0xf1113f0,  0xd110f15,  0xf1f0f19,  0xf250f21,  0xd150f2d,  0xf2f0d13,  0xf311390,  0xf3d0f3b,  0xf3d0f3f,  0xf470f45,  0xf4b0f4f,  0xf510f4d,  0xf550f53,  0xf5b0f59,  0xf630f61,  0xf730f6b,  0xf711396,  0xf750f6d,  0xf830f79,  0xf871398,  0xf7d0f81,  0xf8b0d17,  0xf930f8f,  0xd190f8d,  0xf9b0f97,  0xfa5139a,  0xfa90fb9,  0xfaf0d1f,  0xd1b0d1d,  0xdcf0dcd,  0xfb10fbb,  0xd511181,  0xfbf0fbd,  0xfc90fc1,  0x13a40fc5,  0xfd30d21,  0xfd90fcd,  0x13a80fdd,  0xfd70fdf,  0xd230fe3,  0xfe70fe5,  0xfef0fe9,  0xff313aa,  0xff70d25,  0xff913ac,  0xffb0d27,  0x10051001,  0x13ae1007,  0x13b01384,  0x13b21009,  0x1013100f,  0x1017100b,  0x1027101f,  0x10231021,  0x102b1029,  0x101d13b4,  0x10391037,  0x10410d29,  0x13b6103f,  0x104d1033,  0x13ba13b8,  0x1059104f,  0x10551057,  0x105b0d2b,  0x105f1061,  0x136c1065,  0x13bc106b,  0x13c01071,  0x107f107b,  0x13c21081,  0x10871085,  0x13c613c4,  0x10971093,  0x10990d2d,  0xd2f0d2f,  0x10a710a5,  0x10ad10ab,  0xd3110b3,  0x13c810b7,  0x13ca10bb,  0x138c10c1,  0x13cc10c5,  0x13d013ce,  0xd350d33,  0x13d410d5,  0x13d613d2,  0x10d913d8,  0x10db10db,  0xd3910df,  0xd3b10e1,  0x13dc0d3d,  0x10e910e5,  0xd3f10ef,  0x10ff13de,  0x13e213e0,  0x1113110d,  0x11170d41,  0x111b1119,  0x13e613e4,  0x112313e6,  0x13e80d43,  0x112b1129,  0x13ea0d45,  0xd471137,  0x113b113f,  0x13ee1141,  0xd49114b,  0x11551151,  0xd4b115d,  0x13f413f2,  0x13f60d4d,  0x13f81165,  0x116f1169,  0x13fa1173,  0x117713fc,  0x117b13fe,  0xd4f139e,  0x11851183,  0x11870d53,  0x14000ead,  0x13a01402,  0x118f13a2,  0x126b1191,  0x119b0d55,  0x119d1199,  0x119f0dfd,  0x11a311a1,  0x140411a7,  0x11a911a5,  0x11b511b3,  0x11b711ab,  0x11cb11c1,  0x11bb11b1,  0x11bf11bd,  0x140a1406,  0xd571408,  0x11d111cf,  0x141211d5,  0x140c11d7,  0xd5b0d59,  0x1410140e,  0x11e50d5d,  0x11e911e7,  0x11ef11eb,  0x11f311ed,  0x11f911f1,  0x11f711f5,  0xd5f11fb,  0x120111ff,  0x12070d61,  0x14141209,  0x1211120f,  0x12170d63,  0x14160cfd,  0xd651418,  0x12250d67,  0x123f1231,  0x141a1243,  0x12471245,  0x12531251,  0x1372141e,  0x12551257,  0x1374125b,  0x1265125f,  0x14221420,  0x1281127b,  0x14241285,  0x12971287,  0x129f129d,  0x12a5129b,  0x142612a7,  0xd6912a9,  0x142812ad,  0x12c30d6b,  0x12cd0ee3,  0x142e142a,  0xd6f0d6d,  0x143012d7,  0x14320d71,  0x12db12db,  0x143412df,  0xd7312e5,  0x12ef12ed,  0x12f512f1,  0x14360d75,  0x12fd12f9,  0xd771301,  0x13030d79,  0xd7b1438,  0x143c143a,  0x1311143e,  0x13150d7d,  0x13191317,  0x131d131b,  0x1442131f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+@property
+{
+private alias _IDCA = immutable(dchar[]);
+_IDCA decompCanonTable() { static _IDCA t = [ 0x0,  0x3b,  0x0,  0x3c,  0x338,  0x0,  0x3d,  0x338,  0x0,  0x3e,  0x338,  0x0,  0x41,  0x300,  0x0,  0x41,  0x301,  0x0,  0x41,  0x302,  0x0,  0x41,  0x302,  0x300,  0x0,  0x41,  0x302,  0x301,  0x0,  0x41,  0x302,  0x303,  0x0,  0x41,  0x302,  0x309,  0x0,  0x41,  0x303,  0x0,  0x41,  0x304,  0x0,  0x41,  0x306,  0x0,  0x41,  0x306,  0x300,  0x0,  0x41,  0x306,  0x301,  0x0,  0x41,  0x306,  0x303,  0x0,  0x41,  0x306,  0x309,  0x0,  0x41,  0x307,  0x0,  0x41,  0x307,  0x304,  0x0,  0x41,  0x308,  0x0,  0x41,  0x308,  0x304,  0x0,  0x41,  0x309,  0x0,  0x41,  0x30a,  0x0,  0x41,  0x30a,  0x301,  0x0,  0x41,  0x30c,  0x0,  0x41,  0x30f,  0x0,  0x41,  0x311,  0x0,  0x41,  0x323,  0x0,  0x41,  0x323,  0x302,  0x0,  0x41,  0x323,  0x306,  0x0,  0x41,  0x325,  0x0,  0x41,  0x328,  0x0,  0x42,  0x307,  0x0,  0x42,  0x323,  0x0,  0x42,  0x331,  0x0,  0x43,  0x301,  0x0,  0x43,  0x302,  0x0,  0x43,  0x307,  0x0,  0x43,  0x30c,  0x0,  0x43,  0x327,  0x0,  0x43,  0x327,  0x301,  0x0,  0x44,  0x307,  0x0,  0x44,  0x30c,  0x0,  0x44,  0x323,  0x0,  0x44,  0x327,  0x0,  0x44,  0x32d,  0x0,  0x44,  0x331,  0x0,  0x45,  0x300,  0x0,  0x45,  0x301,  0x0,  0x45,  0x302,  0x0,  0x45,  0x302,  0x300,  0x0,  0x45,  0x302,  0x301,  0x0,  0x45,  0x302,  0x303,  0x0,  0x45,  0x302,  0x309,  0x0,  0x45,  0x303,  0x0,  0x45,  0x304,  0x0,  0x45,  0x304,  0x300,  0x0,  0x45,  0x304,  0x301,  0x0,  0x45,  0x306,  0x0,  0x45,  0x307,  0x0,  0x45,  0x308,  0x0,  0x45,  0x309,  0x0,  0x45,  0x30c,  0x0,  0x45,  0x30f,  0x0,  0x45,  0x311,  0x0,  0x45,  0x323,  0x0,  0x45,  0x323,  0x302,  0x0,  0x45,  0x327,  0x0,  0x45,  0x327,  0x306,  0x0,  0x45,  0x328,  0x0,  0x45,  0x32d,  0x0,  0x45,  0x330,  0x0,  0x46,  0x307,  0x0,  0x47,  0x301,  0x0,  0x47,  0x302,  0x0,  0x47,  0x304,  0x0,  0x47,  0x306,  0x0,  0x47,  0x307,  0x0,  0x47,  0x30c,  0x0,  0x47,  0x327,  0x0,  0x48,  0x302,  0x0,  0x48,  0x307,  0x0,  0x48,  0x308,  0x0,  0x48,  0x30c,  0x0,  0x48,  0x323,  0x0,  0x48,  0x327,  0x0,  0x48,  0x32e,  0x0,  0x49,  0x300,  0x0,  0x49,  0x301,  0x0,  0x49,  0x302,  0x0,  0x49,  0x303,  0x0,  0x49,  0x304,  0x0,  0x49,  0x306,  0x0,  0x49,  0x307,  0x0,  0x49,  0x308,  0x0,  0x49,  0x308,  0x301,  0x0,  0x49,  0x309,  0x0,  0x49,  0x30c,  0x0,  0x49,  0x30f,  0x0,  0x49,  0x311,  0x0,  0x49,  0x323,  0x0,  0x49,  0x328,  0x0,  0x49,  0x330,  0x0,  0x4a,  0x302,  0x0,  0x4b,  0x0,  0x4b,  0x301,  0x0,  0x4b,  0x30c,  0x0,  0x4b,  0x323,  0x0,  0x4b,  0x327,  0x0,  0x4b,  0x331,  0x0,  0x4c,  0x301,  0x0,  0x4c,  0x30c,  0x0,  0x4c,  0x323,  0x0,  0x4c,  0x323,  0x304,  0x0,  0x4c,  0x327,  0x0,  0x4c,  0x32d,  0x0,  0x4c,  0x331,  0x0,  0x4d,  0x301,  0x0,  0x4d,  0x307,  0x0,  0x4d,  0x323,  0x0,  0x4e,  0x300,  0x0,  0x4e,  0x301,  0x0,  0x4e,  0x303,  0x0,  0x4e,  0x307,  0x0,  0x4e,  0x30c,  0x0,  0x4e,  0x323,  0x0,  0x4e,  0x327,  0x0,  0x4e,  0x32d,  0x0,  0x4e,  0x331,  0x0,  0x4f,  0x300,  0x0,  0x4f,  0x301,  0x0,  0x4f,  0x302,  0x0,  0x4f,  0x302,  0x300,  0x0,  0x4f,  0x302,  0x301,  0x0,  0x4f,  0x302,  0x303,  0x0,  0x4f,  0x302,  0x309,  0x0,  0x4f,  0x303,  0x0,  0x4f,  0x303,  0x301,  0x0,  0x4f,  0x303,  0x304,  0x0,  0x4f,  0x303,  0x308,  0x0,  0x4f,  0x304,  0x0,  0x4f,  0x304,  0x300,  0x0,  0x4f,  0x304,  0x301,  0x0,  0x4f,  0x306,  0x0,  0x4f,  0x307,  0x0,  0x4f,  0x307,  0x304,  0x0,  0x4f,  0x308,  0x0,  0x4f,  0x308,  0x304,  0x0,  0x4f,  0x309,  0x0,  0x4f,  0x30b,  0x0,  0x4f,  0x30c,  0x0,  0x4f,  0x30f,  0x0,  0x4f,  0x311,  0x0,  0x4f,  0x31b,  0x0,  0x4f,  0x31b,  0x300,  0x0,  0x4f,  0x31b,  0x301,  0x0,  0x4f,  0x31b,  0x303,  0x0,  0x4f,  0x31b,  0x309,  0x0,  0x4f,  0x31b,  0x323,  0x0,  0x4f,  0x323,  0x0,  0x4f,  0x323,  0x302,  0x0,  0x4f,  0x328,  0x0,  0x4f,  0x328,  0x304,  0x0,  0x50,  0x301,  0x0,  0x50,  0x307,  0x0,  0x52,  0x301,  0x0,  0x52,  0x307,  0x0,  0x52,  0x30c,  0x0,  0x52,  0x30f,  0x0,  0x52,  0x311,  0x0,  0x52,  0x323,  0x0,  0x52,  0x323,  0x304,  0x0,  0x52,  0x327,  0x0,  0x52,  0x331,  0x0,  0x53,  0x301,  0x0,  0x53,  0x301,  0x307,  0x0,  0x53,  0x302,  0x0,  0x53,  0x307,  0x0,  0x53,  0x30c,  0x0,  0x53,  0x30c,  0x307,  0x0,  0x53,  0x323,  0x0,  0x53,  0x323,  0x307,  0x0,  0x53,  0x326,  0x0,  0x53,  0x327,  0x0,  0x54,  0x307,  0x0,  0x54,  0x30c,  0x0,  0x54,  0x323,  0x0,  0x54,  0x326,  0x0,  0x54,  0x327,  0x0,  0x54,  0x32d,  0x0,  0x54,  0x331,  0x0,  0x55,  0x300,  0x0,  0x55,  0x301,  0x0,  0x55,  0x302,  0x0,  0x55,  0x303,  0x0,  0x55,  0x303,  0x301,  0x0,  0x55,  0x304,  0x0,  0x55,  0x304,  0x308,  0x0,  0x55,  0x306,  0x0,  0x55,  0x308,  0x0,  0x55,  0x308,  0x300,  0x0,  0x55,  0x308,  0x301,  0x0,  0x55,  0x308,  0x304,  0x0,  0x55,  0x308,  0x30c,  0x0,  0x55,  0x309,  0x0,  0x55,  0x30a,  0x0,  0x55,  0x30b,  0x0,  0x55,  0x30c,  0x0,  0x55,  0x30f,  0x0,  0x55,  0x311,  0x0,  0x55,  0x31b,  0x0,  0x55,  0x31b,  0x300,  0x0,  0x55,  0x31b,  0x301,  0x0,  0x55,  0x31b,  0x303,  0x0,  0x55,  0x31b,  0x309,  0x0,  0x55,  0x31b,  0x323,  0x0,  0x55,  0x323,  0x0,  0x55,  0x324,  0x0,  0x55,  0x328,  0x0,  0x55,  0x32d,  0x0,  0x55,  0x330,  0x0,  0x56,  0x303,  0x0,  0x56,  0x323,  0x0,  0x57,  0x300,  0x0,  0x57,  0x301,  0x0,  0x57,  0x302,  0x0,  0x57,  0x307,  0x0,  0x57,  0x308,  0x0,  0x57,  0x323,  0x0,  0x58,  0x307,  0x0,  0x58,  0x308,  0x0,  0x59,  0x300,  0x0,  0x59,  0x301,  0x0,  0x59,  0x302,  0x0,  0x59,  0x303,  0x0,  0x59,  0x304,  0x0,  0x59,  0x307,  0x0,  0x59,  0x308,  0x0,  0x59,  0x309,  0x0,  0x59,  0x323,  0x0,  0x5a,  0x301,  0x0,  0x5a,  0x302,  0x0,  0x5a,  0x307,  0x0,  0x5a,  0x30c,  0x0,  0x5a,  0x323,  0x0,  0x5a,  0x331,  0x0,  0x60,  0x0,  0x61,  0x300,  0x0,  0x61,  0x301,  0x0,  0x61,  0x302,  0x0,  0x61,  0x302,  0x300,  0x0,  0x61,  0x302,  0x301,  0x0,  0x61,  0x302,  0x303,  0x0,  0x61,  0x302,  0x309,  0x0,  0x61,  0x303,  0x0,  0x61,  0x304,  0x0,  0x61,  0x306,  0x0,  0x61,  0x306,  0x300,  0x0,  0x61,  0x306,  0x301,  0x0,  0x61,  0x306,  0x303,  0x0,  0x61,  0x306,  0x309,  0x0,  0x61,  0x307,  0x0,  0x61,  0x307,  0x304,  0x0,  0x61,  0x308,  0x0,  0x61,  0x308,  0x304,  0x0,  0x61,  0x309,  0x0,  0x61,  0x30a,  0x0,  0x61,  0x30a,  0x301,  0x0,  0x61,  0x30c,  0x0,  0x61,  0x30f,  0x0,  0x61,  0x311,  0x0,  0x61,  0x323,  0x0,  0x61,  0x323,  0x302,  0x0,  0x61,  0x323,  0x306,  0x0,  0x61,  0x325,  0x0,  0x61,  0x328,  0x0,  0x62,  0x307,  0x0,  0x62,  0x323,  0x0,  0x62,  0x331,  0x0,  0x63,  0x301,  0x0,  0x63,  0x302,  0x0,  0x63,  0x307,  0x0,  0x63,  0x30c,  0x0,  0x63,  0x327,  0x0,  0x63,  0x327,  0x301,  0x0,  0x64,  0x307,  0x0,  0x64,  0x30c,  0x0,  0x64,  0x323,  0x0,  0x64,  0x327,  0x0,  0x64,  0x32d,  0x0,  0x64,  0x331,  0x0,  0x65,  0x300,  0x0,  0x65,  0x301,  0x0,  0x65,  0x302,  0x0,  0x65,  0x302,  0x300,  0x0,  0x65,  0x302,  0x301,  0x0,  0x65,  0x302,  0x303,  0x0,  0x65,  0x302,  0x309,  0x0,  0x65,  0x303,  0x0,  0x65,  0x304,  0x0,  0x65,  0x304,  0x300,  0x0,  0x65,  0x304,  0x301,  0x0,  0x65,  0x306,  0x0,  0x65,  0x307,  0x0,  0x65,  0x308,  0x0,  0x65,  0x309,  0x0,  0x65,  0x30c,  0x0,  0x65,  0x30f,  0x0,  0x65,  0x311,  0x0,  0x65,  0x323,  0x0,  0x65,  0x323,  0x302,  0x0,  0x65,  0x327,  0x0,  0x65,  0x327,  0x306,  0x0,  0x65,  0x328,  0x0,  0x65,  0x32d,  0x0,  0x65,  0x330,  0x0,  0x66,  0x307,  0x0,  0x67,  0x301,  0x0,  0x67,  0x302,  0x0,  0x67,  0x304,  0x0,  0x67,  0x306,  0x0,  0x67,  0x307,  0x0,  0x67,  0x30c,  0x0,  0x67,  0x327,  0x0,  0x68,  0x302,  0x0,  0x68,  0x307,  0x0,  0x68,  0x308,  0x0,  0x68,  0x30c,  0x0,  0x68,  0x323,  0x0,  0x68,  0x327,  0x0,  0x68,  0x32e,  0x0,  0x68,  0x331,  0x0,  0x69,  0x300,  0x0,  0x69,  0x301,  0x0,  0x69,  0x302,  0x0,  0x69,  0x303,  0x0,  0x69,  0x304,  0x0,  0x69,  0x306,  0x0,  0x69,  0x308,  0x0,  0x69,  0x308,  0x301,  0x0,  0x69,  0x309,  0x0,  0x69,  0x30c,  0x0,  0x69,  0x30f,  0x0,  0x69,  0x311,  0x0,  0x69,  0x323,  0x0,  0x69,  0x328,  0x0,  0x69,  0x330,  0x0,  0x6a,  0x302,  0x0,  0x6a,  0x30c,  0x0,  0x6b,  0x301,  0x0,  0x6b,  0x30c,  0x0,  0x6b,  0x323,  0x0,  0x6b,  0x327,  0x0,  0x6b,  0x331,  0x0,  0x6c,  0x301,  0x0,  0x6c,  0x30c,  0x0,  0x6c,  0x323,  0x0,  0x6c,  0x323,  0x304,  0x0,  0x6c,  0x327,  0x0,  0x6c,  0x32d,  0x0,  0x6c,  0x331,  0x0,  0x6d,  0x301,  0x0,  0x6d,  0x307,  0x0,  0x6d,  0x323,  0x0,  0x6e,  0x300,  0x0,  0x6e,  0x301,  0x0,  0x6e,  0x303,  0x0,  0x6e,  0x307,  0x0,  0x6e,  0x30c,  0x0,  0x6e,  0x323,  0x0,  0x6e,  0x327,  0x0,  0x6e,  0x32d,  0x0,  0x6e,  0x331,  0x0,  0x6f,  0x300,  0x0,  0x6f,  0x301,  0x0,  0x6f,  0x302,  0x0,  0x6f,  0x302,  0x300,  0x0,  0x6f,  0x302,  0x301,  0x0,  0x6f,  0x302,  0x303,  0x0,  0x6f,  0x302,  0x309,  0x0,  0x6f,  0x303,  0x0,  0x6f,  0x303,  0x301,  0x0,  0x6f,  0x303,  0x304,  0x0,  0x6f,  0x303,  0x308,  0x0,  0x6f,  0x304,  0x0,  0x6f,  0x304,  0x300,  0x0,  0x6f,  0x304,  0x301,  0x0,  0x6f,  0x306,  0x0,  0x6f,  0x307,  0x0,  0x6f,  0x307,  0x304,  0x0,  0x6f,  0x308,  0x0,  0x6f,  0x308,  0x304,  0x0,  0x6f,  0x309,  0x0,  0x6f,  0x30b,  0x0,  0x6f,  0x30c,  0x0,  0x6f,  0x30f,  0x0,  0x6f,  0x311,  0x0,  0x6f,  0x31b,  0x0,  0x6f,  0x31b,  0x300,  0x0,  0x6f,  0x31b,  0x301,  0x0,  0x6f,  0x31b,  0x303,  0x0,  0x6f,  0x31b,  0x309,  0x0,  0x6f,  0x31b,  0x323,  0x0,  0x6f,  0x323,  0x0,  0x6f,  0x323,  0x302,  0x0,  0x6f,  0x328,  0x0,  0x6f,  0x328,  0x304,  0x0,  0x70,  0x301,  0x0,  0x70,  0x307,  0x0,  0x72,  0x301,  0x0,  0x72,  0x307,  0x0,  0x72,  0x30c,  0x0,  0x72,  0x30f,  0x0,  0x72,  0x311,  0x0,  0x72,  0x323,  0x0,  0x72,  0x323,  0x304,  0x0,  0x72,  0x327,  0x0,  0x72,  0x331,  0x0,  0x73,  0x301,  0x0,  0x73,  0x301,  0x307,  0x0,  0x73,  0x302,  0x0,  0x73,  0x307,  0x0,  0x73,  0x30c,  0x0,  0x73,  0x30c,  0x307,  0x0,  0x73,  0x323,  0x0,  0x73,  0x323,  0x307,  0x0,  0x73,  0x326,  0x0,  0x73,  0x327,  0x0,  0x74,  0x307,  0x0,  0x74,  0x308,  0x0,  0x74,  0x30c,  0x0,  0x74,  0x323,  0x0,  0x74,  0x326,  0x0,  0x74,  0x327,  0x0,  0x74,  0x32d,  0x0,  0x74,  0x331,  0x0,  0x75,  0x300,  0x0,  0x75,  0x301,  0x0,  0x75,  0x302,  0x0,  0x75,  0x303,  0x0,  0x75,  0x303,  0x301,  0x0,  0x75,  0x304,  0x0,  0x75,  0x304,  0x308,  0x0,  0x75,  0x306,  0x0,  0x75,  0x308,  0x0,  0x75,  0x308,  0x300,  0x0,  0x75,  0x308,  0x301,  0x0,  0x75,  0x308,  0x304,  0x0,  0x75,  0x308,  0x30c,  0x0,  0x75,  0x309,  0x0,  0x75,  0x30a,  0x0,  0x75,  0x30b,  0x0,  0x75,  0x30c,  0x0,  0x75,  0x30f,  0x0,  0x75,  0x311,  0x0,  0x75,  0x31b,  0x0,  0x75,  0x31b,  0x300,  0x0,  0x75,  0x31b,  0x301,  0x0,  0x75,  0x31b,  0x303,  0x0,  0x75,  0x31b,  0x309,  0x0,  0x75,  0x31b,  0x323,  0x0,  0x75,  0x323,  0x0,  0x75,  0x324,  0x0,  0x75,  0x328,  0x0,  0x75,  0x32d,  0x0,  0x75,  0x330,  0x0,  0x76,  0x303,  0x0,  0x76,  0x323,  0x0,  0x77,  0x300,  0x0,  0x77,  0x301,  0x0,  0x77,  0x302,  0x0,  0x77,  0x307,  0x0,  0x77,  0x308,  0x0,  0x77,  0x30a,  0x0,  0x77,  0x323,  0x0,  0x78,  0x307,  0x0,  0x78,  0x308,  0x0,  0x79,  0x300,  0x0,  0x79,  0x301,  0x0,  0x79,  0x302,  0x0,  0x79,  0x303,  0x0,  0x79,  0x304,  0x0,  0x79,  0x307,  0x0,  0x79,  0x308,  0x0,  0x79,  0x309,  0x0,  0x79,  0x30a,  0x0,  0x79,  0x323,  0x0,  0x7a,  0x301,  0x0,  0x7a,  0x302,  0x0,  0x7a,  0x307,  0x0,  0x7a,  0x30c,  0x0,  0x7a,  0x323,  0x0,  0x7a,  0x331,  0x0,  0xa8,  0x300,  0x0,  0xa8,  0x301,  0x0,  0xa8,  0x342,  0x0,  0xb4,  0x0,  0xb7,  0x0,  0xc6,  0x301,  0x0,  0xc6,  0x304,  0x0,  0xd8,  0x301,  0x0,  0xe6,  0x301,  0x0,  0xe6,  0x304,  0x0,  0xf8,  0x301,  0x0,  0x17f,  0x307,  0x0,  0x1b7,  0x30c,  0x0,  0x292,  0x30c,  0x0,  0x2b9,  0x0,  0x300,  0x0,  0x301,  0x0,  0x308,  0x301,  0x0,  0x313,  0x0,  0x391,  0x300,  0x0,  0x391,  0x301,  0x0,  0x391,  0x304,  0x0,  0x391,  0x306,  0x0,  0x391,  0x313,  0x0,  0x391,  0x313,  0x300,  0x0,  0x391,  0x313,  0x300,  0x345,  0x0,  0x391,  0x313,  0x301,  0x0,  0x391,  0x313,  0x301,  0x345,  0x0,  0x391,  0x313,  0x342,  0x0,  0x391,  0x313,  0x342,  0x345,  0x0,  0x391,  0x313,  0x345,  0x0,  0x391,  0x314,  0x0,  0x391,  0x314,  0x300,  0x0,  0x391,  0x314,  0x300,  0x345,  0x0,  0x391,  0x314,  0x301,  0x0,  0x391,  0x314,  0x301,  0x345,  0x0,  0x391,  0x314,  0x342,  0x0,  0x391,  0x314,  0x342,  0x345,  0x0,  0x391,  0x314,  0x345,  0x0,  0x391,  0x345,  0x0,  0x395,  0x300,  0x0,  0x395,  0x301,  0x0,  0x395,  0x313,  0x0,  0x395,  0x313,  0x300,  0x0,  0x395,  0x313,  0x301,  0x0,  0x395,  0x314,  0x0,  0x395,  0x314,  0x300,  0x0,  0x395,  0x314,  0x301,  0x0,  0x397,  0x300,  0x0,  0x397,  0x301,  0x0,  0x397,  0x313,  0x0,  0x397,  0x313,  0x300,  0x0,  0x397,  0x313,  0x300,  0x345,  0x0,  0x397,  0x313,  0x301,  0x0,  0x397,  0x313,  0x301,  0x345,  0x0,  0x397,  0x313,  0x342,  0x0,  0x397,  0x313,  0x342,  0x345,  0x0,  0x397,  0x313,  0x345,  0x0,  0x397,  0x314,  0x0,  0x397,  0x314,  0x300,  0x0,  0x397,  0x314,  0x300,  0x345,  0x0,  0x397,  0x314,  0x301,  0x0,  0x397,  0x314,  0x301,  0x345,  0x0,  0x397,  0x314,  0x342,  0x0,  0x397,  0x314,  0x342,  0x345,  0x0,  0x397,  0x314,  0x345,  0x0,  0x397,  0x345,  0x0,  0x399,  0x300,  0x0,  0x399,  0x301,  0x0,  0x399,  0x304,  0x0,  0x399,  0x306,  0x0,  0x399,  0x308,  0x0,  0x399,  0x313,  0x0,  0x399,  0x313,  0x300,  0x0,  0x399,  0x313,  0x301,  0x0,  0x399,  0x313,  0x342,  0x0,  0x399,  0x314,  0x0,  0x399,  0x314,  0x300,  0x0,  0x399,  0x314,  0x301,  0x0,  0x399,  0x314,  0x342,  0x0,  0x39f,  0x300,  0x0,  0x39f,  0x301,  0x0,  0x39f,  0x313,  0x0,  0x39f,  0x313,  0x300,  0x0,  0x39f,  0x313,  0x301,  0x0,  0x39f,  0x314,  0x0,  0x39f,  0x314,  0x300,  0x0,  0x39f,  0x314,  0x301,  0x0,  0x3a1,  0x314,  0x0,  0x3a5,  0x300,  0x0,  0x3a5,  0x301,  0x0,  0x3a5,  0x304,  0x0,  0x3a5,  0x306,  0x0,  0x3a5,  0x308,  0x0,  0x3a5,  0x314,  0x0,  0x3a5,  0x314,  0x300,  0x0,  0x3a5,  0x314,  0x301,  0x0,  0x3a5,  0x314,  0x342,  0x0,  0x3a9,  0x0,  0x3a9,  0x300,  0x0,  0x3a9,  0x301,  0x0,  0x3a9,  0x313,  0x0,  0x3a9,  0x313,  0x300,  0x0,  0x3a9,  0x313,  0x300,  0x345,  0x0,  0x3a9,  0x313,  0x301,  0x0,  0x3a9,  0x313,  0x301,  0x345,  0x0,  0x3a9,  0x313,  0x342,  0x0,  0x3a9,  0x313,  0x342,  0x345,  0x0,  0x3a9,  0x313,  0x345,  0x0,  0x3a9,  0x314,  0x0,  0x3a9,  0x314,  0x300,  0x0,  0x3a9,  0x314,  0x300,  0x345,  0x0,  0x3a9,  0x314,  0x301,  0x0,  0x3a9,  0x314,  0x301,  0x345,  0x0,  0x3a9,  0x314,  0x342,  0x0,  0x3a9,  0x314,  0x342,  0x345,  0x0,  0x3a9,  0x314,  0x345,  0x0,  0x3a9,  0x345,  0x0,  0x3b1,  0x300,  0x0,  0x3b1,  0x300,  0x345,  0x0,  0x3b1,  0x301,  0x0,  0x3b1,  0x301,  0x345,  0x0,  0x3b1,  0x304,  0x0,  0x3b1,  0x306,  0x0,  0x3b1,  0x313,  0x0,  0x3b1,  0x313,  0x300,  0x0,  0x3b1,  0x313,  0x300,  0x345,  0x0,  0x3b1,  0x313,  0x301,  0x0,  0x3b1,  0x313,  0x301,  0x345,  0x0,  0x3b1,  0x313,  0x342,  0x0,  0x3b1,  0x313,  0x342,  0x345,  0x0,  0x3b1,  0x313,  0x345,  0x0,  0x3b1,  0x314,  0x0,  0x3b1,  0x314,  0x300,  0x0,  0x3b1,  0x314,  0x300,  0x345,  0x0,  0x3b1,  0x314,  0x301,  0x0,  0x3b1,  0x314,  0x301,  0x345,  0x0,  0x3b1,  0x314,  0x342,  0x0,  0x3b1,  0x314,  0x342,  0x345,  0x0,  0x3b1,  0x314,  0x345,  0x0,  0x3b1,  0x342,  0x0,  0x3b1,  0x342,  0x345,  0x0,  0x3b1,  0x345,  0x0,  0x3b5,  0x300,  0x0,  0x3b5,  0x301,  0x0,  0x3b5,  0x313,  0x0,  0x3b5,  0x313,  0x300,  0x0,  0x3b5,  0x313,  0x301,  0x0,  0x3b5,  0x314,  0x0,  0x3b5,  0x314,  0x300,  0x0,  0x3b5,  0x314,  0x301,  0x0,  0x3b7,  0x300,  0x0,  0x3b7,  0x300,  0x345,  0x0,  0x3b7,  0x301,  0x0,  0x3b7,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x0,  0x3b7,  0x313,  0x300,  0x0,  0x3b7,  0x313,  0x300,  0x345,  0x0,  0x3b7,  0x313,  0x301,  0x0,  0x3b7,  0x313,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x342,  0x0,  0x3b7,  0x313,  0x342,  0x345,  0x0,  0x3b7,  0x313,  0x345,  0x0,  0x3b7,  0x314,  0x0,  0x3b7,  0x314,  0x300,  0x0,  0x3b7,  0x314,  0x300,  0x345,  0x0,  0x3b7,  0x314,  0x301,  0x0,  0x3b7,  0x314,  0x301,  0x345,  0x0,  0x3b7,  0x314,  0x342,  0x0,  0x3b7,  0x314,  0x342,  0x345,  0x0,  0x3b7,  0x314,  0x345,  0x0,  0x3b7,  0x342,  0x0,  0x3b7,  0x342,  0x345,  0x0,  0x3b7,  0x345,  0x0,  0x3b9,  0x0,  0x3b9,  0x300,  0x0,  0x3b9,  0x301,  0x0,  0x3b9,  0x304,  0x0,  0x3b9,  0x306,  0x0,  0x3b9,  0x308,  0x0,  0x3b9,  0x308,  0x300,  0x0,  0x3b9,  0x308,  0x301,  0x0,  0x3b9,  0x308,  0x342,  0x0,  0x3b9,  0x313,  0x0,  0x3b9,  0x313,  0x300,  0x0,  0x3b9,  0x313,  0x301,  0x0,  0x3b9,  0x313,  0x342,  0x0,  0x3b9,  0x314,  0x0,  0x3b9,  0x314,  0x300,  0x0,  0x3b9,  0x314,  0x301,  0x0,  0x3b9,  0x314,  0x342,  0x0,  0x3b9,  0x342,  0x0,  0x3bf,  0x300,  0x0,  0x3bf,  0x301,  0x0,  0x3bf,  0x313,  0x0,  0x3bf,  0x313,  0x300,  0x0,  0x3bf,  0x313,  0x301,  0x0,  0x3bf,  0x314,  0x0,  0x3bf,  0x314,  0x300,  0x0,  0x3bf,  0x314,  0x301,  0x0,  0x3c1,  0x313,  0x0,  0x3c1,  0x314,  0x0,  0x3c5,  0x300,  0x0,  0x3c5,  0x301,  0x0,  0x3c5,  0x304,  0x0,  0x3c5,  0x306,  0x0,  0x3c5,  0x308,  0x0,  0x3c5,  0x308,  0x300,  0x0,  0x3c5,  0x308,  0x301,  0x0,  0x3c5,  0x308,  0x342,  0x0,  0x3c5,  0x313,  0x0,  0x3c5,  0x313,  0x300,  0x0,  0x3c5,  0x313,  0x301,  0x0,  0x3c5,  0x313,  0x342,  0x0,  0x3c5,  0x314,  0x0,  0x3c5,  0x314,  0x300,  0x0,  0x3c5,  0x314,  0x301,  0x0,  0x3c5,  0x314,  0x342,  0x0,  0x3c5,  0x342,  0x0,  0x3c9,  0x300,  0x0,  0x3c9,  0x300,  0x345,  0x0,  0x3c9,  0x301,  0x0,  0x3c9,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x0,  0x3c9,  0x313,  0x300,  0x0,  0x3c9,  0x313,  0x300,  0x345,  0x0,  0x3c9,  0x313,  0x301,  0x0,  0x3c9,  0x313,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x342,  0x0,  0x3c9,  0x313,  0x342,  0x345,  0x0,  0x3c9,  0x313,  0x345,  0x0,  0x3c9,  0x314,  0x0,  0x3c9,  0x314,  0x300,  0x0,  0x3c9,  0x314,  0x300,  0x345,  0x0,  0x3c9,  0x314,  0x301,  0x0,  0x3c9,  0x314,  0x301,  0x345,  0x0,  0x3c9,  0x314,  0x342,  0x0,  0x3c9,  0x314,  0x342,  0x345,  0x0,  0x3c9,  0x314,  0x345,  0x0,  0x3c9,  0x342,  0x0,  0x3c9,  0x342,  0x345,  0x0,  0x3c9,  0x345,  0x0,  0x3d2,  0x301,  0x0,  0x3d2,  0x308,  0x0,  0x406,  0x308,  0x0,  0x410,  0x306,  0x0,  0x410,  0x308,  0x0,  0x413,  0x301,  0x0,  0x415,  0x300,  0x0,  0x415,  0x306,  0x0,  0x415,  0x308,  0x0,  0x416,  0x306,  0x0,  0x416,  0x308,  0x0,  0x417,  0x308,  0x0,  0x418,  0x300,  0x0,  0x418,  0x304,  0x0,  0x418,  0x306,  0x0,  0x418,  0x308,  0x0,  0x41a,  0x301,  0x0,  0x41e,  0x308,  0x0,  0x423,  0x304,  0x0,  0x423,  0x306,  0x0,  0x423,  0x308,  0x0,  0x423,  0x30b,  0x0,  0x427,  0x308,  0x0,  0x42b,  0x308,  0x0,  0x42d,  0x308,  0x0,  0x430,  0x306,  0x0,  0x430,  0x308,  0x0,  0x433,  0x301,  0x0,  0x435,  0x300,  0x0,  0x435,  0x306,  0x0,  0x435,  0x308,  0x0,  0x436,  0x306,  0x0,  0x436,  0x308,  0x0,  0x437,  0x308,  0x0,  0x438,  0x300,  0x0,  0x438,  0x304,  0x0,  0x438,  0x306,  0x0,  0x438,  0x308,  0x0,  0x43a,  0x301,  0x0,  0x43e,  0x308,  0x0,  0x443,  0x304,  0x0,  0x443,  0x306,  0x0,  0x443,  0x308,  0x0,  0x443,  0x30b,  0x0,  0x447,  0x308,  0x0,  0x44b,  0x308,  0x0,  0x44d,  0x308,  0x0,  0x456,  0x308,  0x0,  0x474,  0x30f,  0x0,  0x475,  0x30f,  0x0,  0x4d8,  0x308,  0x0,  0x4d9,  0x308,  0x0,  0x4e8,  0x308,  0x0,  0x4e9,  0x308,  0x0,  0x5d0,  0x5b7,  0x0,  0x5d0,  0x5b8,  0x0,  0x5d0,  0x5bc,  0x0,  0x5d1,  0x5bc,  0x0,  0x5d1,  0x5bf,  0x0,  0x5d2,  0x5bc,  0x0,  0x5d3,  0x5bc,  0x0,  0x5d4,  0x5bc,  0x0,  0x5d5,  0x5b9,  0x0,  0x5d5,  0x5bc,  0x0,  0x5d6,  0x5bc,  0x0,  0x5d8,  0x5bc,  0x0,  0x5d9,  0x5b4,  0x0,  0x5d9,  0x5bc,  0x0,  0x5da,  0x5bc,  0x0,  0x5db,  0x5bc,  0x0,  0x5db,  0x5bf,  0x0,  0x5dc,  0x5bc,  0x0,  0x5de,  0x5bc,  0x0,  0x5e0,  0x5bc,  0x0,  0x5e1,  0x5bc,  0x0,  0x5e3,  0x5bc,  0x0,  0x5e4,  0x5bc,  0x0,  0x5e4,  0x5bf,  0x0,  0x5e6,  0x5bc,  0x0,  0x5e7,  0x5bc,  0x0,  0x5e8,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x5c1,  0x0,  0x5e9,  0x5bc,  0x5c2,  0x0,  0x5e9,  0x5c1,  0x0,  0x5e9,  0x5c2,  0x0,  0x5ea,  0x5bc,  0x0,  0x5f2,  0x5b7,  0x0,  0x627,  0x653,  0x0,  0x627,  0x654,  0x0,  0x627,  0x655,  0x0,  0x648,  0x654,  0x0,  0x64a,  0x654,  0x0,  0x6c1,  0x654,  0x0,  0x6d2,  0x654,  0x0,  0x6d5,  0x654,  0x0,  0x915,  0x93c,  0x0,  0x916,  0x93c,  0x0,  0x917,  0x93c,  0x0,  0x91c,  0x93c,  0x0,  0x921,  0x93c,  0x0,  0x922,  0x93c,  0x0,  0x928,  0x93c,  0x0,  0x92b,  0x93c,  0x0,  0x92f,  0x93c,  0x0,  0x930,  0x93c,  0x0,  0x933,  0x93c,  0x0,  0x9a1,  0x9bc,  0x0,  0x9a2,  0x9bc,  0x0,  0x9af,  0x9bc,  0x0,  0x9c7,  0x9be,  0x0,  0x9c7,  0x9d7,  0x0,  0xa16,  0xa3c,  0x0,  0xa17,  0xa3c,  0x0,  0xa1c,  0xa3c,  0x0,  0xa2b,  0xa3c,  0x0,  0xa32,  0xa3c,  0x0,  0xa38,  0xa3c,  0x0,  0xb21,  0xb3c,  0x0,  0xb22,  0xb3c,  0x0,  0xb47,  0xb3e,  0x0,  0xb47,  0xb56,  0x0,  0xb47,  0xb57,  0x0,  0xb92,  0xbd7,  0x0,  0xbc6,  0xbbe,  0x0,  0xbc6,  0xbd7,  0x0,  0xbc7,  0xbbe,  0x0,  0xc46,  0xc56,  0x0,  0xcbf,  0xcd5,  0x0,  0xcc6,  0xcc2,  0x0,  0xcc6,  0xcc2,  0xcd5,  0x0,  0xcc6,  0xcd5,  0x0,  0xcc6,  0xcd6,  0x0,  0xd46,  0xd3e,  0x0,  0xd46,  0xd57,  0x0,  0xd47,  0xd3e,  0x0,  0xdd9,  0xdca,  0x0,  0xdd9,  0xdcf,  0x0,  0xdd9,  0xdcf,  0xdca,  0x0,  0xdd9,  0xddf,  0x0,  0xf40,  0xfb5,  0x0,  0xf42,  0xfb7,  0x0,  0xf4c,  0xfb7,  0x0,  0xf51,  0xfb7,  0x0,  0xf56,  0xfb7,  0x0,  0xf5b,  0xfb7,  0x0,  0xf71,  0xf72,  0x0,  0xf71,  0xf74,  0x0,  0xf71,  0xf80,  0x0,  0xf90,  0xfb5,  0x0,  0xf92,  0xfb7,  0x0,  0xf9c,  0xfb7,  0x0,  0xfa1,  0xfb7,  0x0,  0xfa6,  0xfb7,  0x0,  0xfab,  0xfb7,  0x0,  0xfb2,  0xf80,  0x0,  0xfb3,  0xf80,  0x0,  0x1025,  0x102e,  0x0,  0x1b05,  0x1b35,  0x0,  0x1b07,  0x1b35,  0x0,  0x1b09,  0x1b35,  0x0,  0x1b0b,  0x1b35,  0x0,  0x1b0d,  0x1b35,  0x0,  0x1b11,  0x1b35,  0x0,  0x1b3a,  0x1b35,  0x0,  0x1b3c,  0x1b35,  0x0,  0x1b3e,  0x1b35,  0x0,  0x1b3f,  0x1b35,  0x0,  0x1b42,  0x1b35,  0x0,  0x1fbf,  0x300,  0x0,  0x1fbf,  0x301,  0x0,  0x1fbf,  0x342,  0x0,  0x1ffe,  0x300,  0x0,  0x1ffe,  0x301,  0x0,  0x1ffe,  0x342,  0x0,  0x2002,  0x0,  0x2003,  0x0,  0x2190,  0x338,  0x0,  0x2192,  0x338,  0x0,  0x2194,  0x338,  0x0,  0x21d0,  0x338,  0x0,  0x21d2,  0x338,  0x0,  0x21d4,  0x338,  0x0,  0x2203,  0x338,  0x0,  0x2208,  0x338,  0x0,  0x220b,  0x338,  0x0,  0x2223,  0x338,  0x0,  0x2225,  0x338,  0x0,  0x223c,  0x338,  0x0,  0x2243,  0x338,  0x0,  0x2245,  0x338,  0x0,  0x2248,  0x338,  0x0,  0x224d,  0x338,  0x0,  0x2261,  0x338,  0x0,  0x2264,  0x338,  0x0,  0x2265,  0x338,  0x0,  0x2272,  0x338,  0x0,  0x2273,  0x338,  0x0,  0x2276,  0x338,  0x0,  0x2277,  0x338,  0x0,  0x227a,  0x338,  0x0,  0x227b,  0x338,  0x0,  0x227c,  0x338,  0x0,  0x227d,  0x338,  0x0,  0x2282,  0x338,  0x0,  0x2283,  0x338,  0x0,  0x2286,  0x338,  0x0,  0x2287,  0x338,  0x0,  0x2291,  0x338,  0x0,  0x2292,  0x338,  0x0,  0x22a2,  0x338,  0x0,  0x22a8,  0x338,  0x0,  0x22a9,  0x338,  0x0,  0x22ab,  0x338,  0x0,  0x22b2,  0x338,  0x0,  0x22b3,  0x338,  0x0,  0x22b4,  0x338,  0x0,  0x22b5,  0x338,  0x0,  0x2add,  0x338,  0x0,  0x3008,  0x0,  0x3009,  0x0,  0x3046,  0x3099,  0x0,  0x304b,  0x3099,  0x0,  0x304d,  0x3099,  0x0,  0x304f,  0x3099,  0x0,  0x3051,  0x3099,  0x0,  0x3053,  0x3099,  0x0,  0x3055,  0x3099,  0x0,  0x3057,  0x3099,  0x0,  0x3059,  0x3099,  0x0,  0x305b,  0x3099,  0x0,  0x305d,  0x3099,  0x0,  0x305f,  0x3099,  0x0,  0x3061,  0x3099,  0x0,  0x3064,  0x3099,  0x0,  0x3066,  0x3099,  0x0,  0x3068,  0x3099,  0x0,  0x306f,  0x3099,  0x0,  0x306f,  0x309a,  0x0,  0x3072,  0x3099,  0x0,  0x3072,  0x309a,  0x0,  0x3075,  0x3099,  0x0,  0x3075,  0x309a,  0x0,  0x3078,  0x3099,  0x0,  0x3078,  0x309a,  0x0,  0x307b,  0x3099,  0x0,  0x307b,  0x309a,  0x0,  0x309d,  0x3099,  0x0,  0x30a6,  0x3099,  0x0,  0x30ab,  0x3099,  0x0,  0x30ad,  0x3099,  0x0,  0x30af,  0x3099,  0x0,  0x30b1,  0x3099,  0x0,  0x30b3,  0x3099,  0x0,  0x30b5,  0x3099,  0x0,  0x30b7,  0x3099,  0x0,  0x30b9,  0x3099,  0x0,  0x30bb,  0x3099,  0x0,  0x30bd,  0x3099,  0x0,  0x30bf,  0x3099,  0x0,  0x30c1,  0x3099,  0x0,  0x30c4,  0x3099,  0x0,  0x30c6,  0x3099,  0x0,  0x30c8,  0x3099,  0x0,  0x30cf,  0x3099,  0x0,  0x30cf,  0x309a,  0x0,  0x30d2,  0x3099,  0x0,  0x30d2,  0x309a,  0x0,  0x30d5,  0x3099,  0x0,  0x30d5,  0x309a,  0x0,  0x30d8,  0x3099,  0x0,  0x30d8,  0x309a,  0x0,  0x30db,  0x3099,  0x0,  0x30db,  0x309a,  0x0,  0x30ef,  0x3099,  0x0,  0x30f0,  0x3099,  0x0,  0x30f1,  0x3099,  0x0,  0x30f2,  0x3099,  0x0,  0x30fd,  0x3099,  0x0,  0x349e,  0x0,  0x34b9,  0x0,  0x34bb,  0x0,  0x34df,  0x0,  0x3515,  0x0,  0x36ee,  0x0,  0x36fc,  0x0,  0x3781,  0x0,  0x382f,  0x0,  0x3862,  0x0,  0x387c,  0x0,  0x38c7,  0x0,  0x38e3,  0x0,  0x391c,  0x0,  0x393a,  0x0,  0x3a2e,  0x0,  0x3a6c,  0x0,  0x3ae4,  0x0,  0x3b08,  0x0,  0x3b19,  0x0,  0x3b49,  0x0,  0x3b9d,  0x0,  0x3c18,  0x0,  0x3c4e,  0x0,  0x3d33,  0x0,  0x3d96,  0x0,  0x3eac,  0x0,  0x3eb8,  0x0,  0x3f1b,  0x0,  0x3ffc,  0x0,  0x4008,  0x0,  0x4018,  0x0,  0x4039,  0x0,  0x4046,  0x0,  0x4096,  0x0,  0x40e3,  0x0,  0x412f,  0x0,  0x4202,  0x0,  0x4227,  0x0,  0x42a0,  0x0,  0x4301,  0x0,  0x4334,  0x0,  0x4359,  0x0,  0x43d5,  0x0,  0x43d9,  0x0,  0x440b,  0x0,  0x446b,  0x0,  0x452b,  0x0,  0x455d,  0x0,  0x4561,  0x0,  0x456b,  0x0,  0x45d7,  0x0,  0x45f9,  0x0,  0x4635,  0x0,  0x46be,  0x0,  0x46c7,  0x0,  0x4995,  0x0,  0x49e6,  0x0,  0x4a6e,  0x0,  0x4a76,  0x0,  0x4ab2,  0x0,  0x4b33,  0x0,  0x4bce,  0x0,  0x4cce,  0x0,  0x4ced,  0x0,  0x4cf8,  0x0,  0x4d56,  0x0,  0x4e0d,  0x0,  0x4e26,  0x0,  0x4e32,  0x0,  0x4e38,  0x0,  0x4e39,  0x0,  0x4e3d,  0x0,  0x4e41,  0x0,  0x4e82,  0x0,  0x4e86,  0x0,  0x4eae,  0x0,  0x4ec0,  0x0,  0x4ecc,  0x0,  0x4ee4,  0x0,  0x4f60,  0x0,  0x4f80,  0x0,  0x4f86,  0x0,  0x4f8b,  0x0,  0x4fae,  0x0,  0x4fbb,  0x0,  0x4fbf,  0x0,  0x5002,  0x0,  0x502b,  0x0,  0x507a,  0x0,  0x5099,  0x0,  0x50cf,  0x0,  0x50da,  0x0,  0x50e7,  0x0,  0x5140,  0x0,  0x5145,  0x0,  0x514d,  0x0,  0x5154,  0x0,  0x5164,  0x0,  0x5167,  0x0,  0x5168,  0x0,  0x5169,  0x0,  0x516d,  0x0,  0x5177,  0x0,  0x5180,  0x0,  0x518d,  0x0,  0x5192,  0x0,  0x5195,  0x0,  0x5197,  0x0,  0x51a4,  0x0,  0x51ac,  0x0,  0x51b5,  0x0,  0x51b7,  0x0,  0x51c9,  0x0,  0x51cc,  0x0,  0x51dc,  0x0,  0x51de,  0x0,  0x51f5,  0x0,  0x5203,  0x0,  0x5207,  0x0,  0x5217,  0x0,  0x5229,  0x0,  0x523a,  0x0,  0x523b,  0x0,  0x5246,  0x0,  0x5272,  0x0,  0x5277,  0x0,  0x5289,  0x0,  0x529b,  0x0,  0x52a3,  0x0,  0x52b3,  0x0,  0x52c7,  0x0,  0x52c9,  0x0,  0x52d2,  0x0,  0x52de,  0x0,  0x52e4,  0x0,  0x52f5,  0x0,  0x52fa,  0x0,  0x5305,  0x0,  0x5306,  0x0,  0x5317,  0x0,  0x533f,  0x0,  0x5349,  0x0,  0x5351,  0x0,  0x535a,  0x0,  0x5373,  0x0,  0x5375,  0x0,  0x537d,  0x0,  0x537f,  0x0,  0x53c3,  0x0,  0x53ca,  0x0,  0x53df,  0x0,  0x53e5,  0x0,  0x53eb,  0x0,  0x53f1,  0x0,  0x5406,  0x0,  0x540f,  0x0,  0x541d,  0x0,  0x5438,  0x0,  0x5442,  0x0,  0x5448,  0x0,  0x5468,  0x0,  0x549e,  0x0,  0x54a2,  0x0,  0x54bd,  0x0,  0x54f6,  0x0,  0x5510,  0x0,  0x5553,  0x0,  0x5555,  0x0,  0x5563,  0x0,  0x5584,  0x0,  0x5587,  0x0,  0x5599,  0x0,  0x559d,  0x0,  0x55ab,  0x0,  0x55b3,  0x0,  0x55c0,  0x0,  0x55c2,  0x0,  0x55e2,  0x0,  0x5606,  0x0,  0x5651,  0x0,  0x5668,  0x0,  0x5674,  0x0,  0x56f9,  0x0,  0x5716,  0x0,  0x5717,  0x0,  0x578b,  0x0,  0x57ce,  0x0,  0x57f4,  0x0,  0x580d,  0x0,  0x5831,  0x0,  0x5832,  0x0,  0x5840,  0x0,  0x585a,  0x0,  0x585e,  0x0,  0x58a8,  0x0,  0x58ac,  0x0,  0x58b3,  0x0,  0x58d8,  0x0,  0x58df,  0x0,  0x58ee,  0x0,  0x58f2,  0x0,  0x58f7,  0x0,  0x5906,  0x0,  0x591a,  0x0,  0x5922,  0x0,  0x5944,  0x0,  0x5948,  0x0,  0x5951,  0x0,  0x5954,  0x0,  0x5962,  0x0,  0x5973,  0x0,  0x59d8,  0x0,  0x59ec,  0x0,  0x5a1b,  0x0,  0x5a27,  0x0,  0x5a62,  0x0,  0x5a66,  0x0,  0x5ab5,  0x0,  0x5b08,  0x0,  0x5b28,  0x0,  0x5b3e,  0x0,  0x5b85,  0x0,  0x5bc3,  0x0,  0x5bd8,  0x0,  0x5be7,  0x0,  0x5bee,  0x0,  0x5bf3,  0x0,  0x5bff,  0x0,  0x5c06,  0x0,  0x5c22,  0x0,  0x5c3f,  0x0,  0x5c60,  0x0,  0x5c62,  0x0,  0x5c64,  0x0,  0x5c65,  0x0,  0x5c6e,  0x0,  0x5c8d,  0x0,  0x5cc0,  0x0,  0x5d19,  0x0,  0x5d43,  0x0,  0x5d50,  0x0,  0x5d6b,  0x0,  0x5d6e,  0x0,  0x5d7c,  0x0,  0x5db2,  0x0,  0x5dba,  0x0,  0x5de1,  0x0,  0x5de2,  0x0,  0x5dfd,  0x0,  0x5e28,  0x0,  0x5e3d,  0x0,  0x5e69,  0x0,  0x5e74,  0x0,  0x5ea6,  0x0,  0x5eb0,  0x0,  0x5eb3,  0x0,  0x5eb6,  0x0,  0x5ec9,  0x0,  0x5eca,  0x0,  0x5ed2,  0x0,  0x5ed3,  0x0,  0x5ed9,  0x0,  0x5eec,  0x0,  0x5efe,  0x0,  0x5f04,  0x0,  0x5f22,  0x0,  0x5f53,  0x0,  0x5f62,  0x0,  0x5f69,  0x0,  0x5f6b,  0x0,  0x5f8b,  0x0,  0x5f9a,  0x0,  0x5fa9,  0x0,  0x5fad,  0x0,  0x5fcd,  0x0,  0x5fd7,  0x0,  0x5ff5,  0x0,  0x5ff9,  0x0,  0x6012,  0x0,  0x601c,  0x0,  0x6075,  0x0,  0x6081,  0x0,  0x6094,  0x0,  0x60c7,  0x0,  0x60d8,  0x0,  0x60e1,  0x0,  0x6108,  0x0,  0x6144,  0x0,  0x6148,  0x0,  0x614c,  0x0,  0x614e,  0x0,  0x6160,  0x0,  0x6168,  0x0,  0x617a,  0x0,  0x618e,  0x0,  0x6190,  0x0,  0x61a4,  0x0,  0x61af,  0x0,  0x61b2,  0x0,  0x61de,  0x0,  0x61f2,  0x0,  0x61f6,  0x0,  0x6200,  0x0,  0x6210,  0x0,  0x621b,  0x0,  0x622e,  0x0,  0x6234,  0x0,  0x625d,  0x0,  0x62b1,  0x0,  0x62c9,  0x0,  0x62cf,  0x0,  0x62d3,  0x0,  0x62d4,  0x0,  0x62fc,  0x0,  0x62fe,  0x0,  0x633d,  0x0,  0x6350,  0x0,  0x6368,  0x0,  0x637b,  0x0,  0x6383,  0x0,  0x63a0,  0x0,  0x63a9,  0x0,  0x63c4,  0x0,  0x63c5,  0x0,  0x63e4,  0x0,  0x641c,  0x0,  0x6422,  0x0,  0x6452,  0x0,  0x6469,  0x0,  0x6477,  0x0,  0x647e,  0x0,  0x649a,  0x0,  0x649d,  0x0,  0x64c4,  0x0,  0x654f,  0x0,  0x6556,  0x0,  0x656c,  0x0,  0x6578,  0x0,  0x6599,  0x0,  0x65c5,  0x0,  0x65e2,  0x0,  0x65e3,  0x0,  0x6613,  0x0,  0x6649,  0x0,  0x6674,  0x0,  0x6688,  0x0,  0x6691,  0x0,  0x669c,  0x0,  0x66b4,  0x0,  0x66c6,  0x0,  0x66f4,  0x0,  0x66f8,  0x0,  0x6700,  0x0,  0x6717,  0x0,  0x671b,  0x0,  0x6721,  0x0,  0x674e,  0x0,  0x6753,  0x0,  0x6756,  0x0,  0x675e,  0x0,  0x677b,  0x0,  0x6785,  0x0,  0x6797,  0x0,  0x67f3,  0x0,  0x67fa,  0x0,  0x6817,  0x0,  0x681f,  0x0,  0x6852,  0x0,  0x6881,  0x0,  0x6885,  0x0,  0x688e,  0x0,  0x68a8,  0x0,  0x6914,  0x0,  0x6942,  0x0,  0x69a3,  0x0,  0x69ea,  0x0,  0x6a02,  0x0,  0x6a13,  0x0,  0x6aa8,  0x0,  0x6ad3,  0x0,  0x6adb,  0x0,  0x6b04,  0x0,  0x6b21,  0x0,  0x6b54,  0x0,  0x6b72,  0x0,  0x6b77,  0x0,  0x6b79,  0x0,  0x6b9f,  0x0,  0x6bae,  0x0,  0x6bba,  0x0,  0x6bbb,  0x0,  0x6c4e,  0x0,  0x6c67,  0x0,  0x6c88,  0x0,  0x6cbf,  0x0,  0x6ccc,  0x0,  0x6ccd,  0x0,  0x6ce5,  0x0,  0x6d16,  0x0,  0x6d1b,  0x0,  0x6d1e,  0x0,  0x6d34,  0x0,  0x6d3e,  0x0,  0x6d41,  0x0,  0x6d69,  0x0,  0x6d6a,  0x0,  0x6d77,  0x0,  0x6d78,  0x0,  0x6d85,  0x0,  0x6dcb,  0x0,  0x6dda,  0x0,  0x6dea,  0x0,  0x6df9,  0x0,  0x6e1a,  0x0,  0x6e2f,  0x0,  0x6e6e,  0x0,  0x6e9c,  0x0,  0x6eba,  0x0,  0x6ec7,  0x0,  0x6ecb,  0x0,  0x6ed1,  0x0,  0x6edb,  0x0,  0x6f0f,  0x0,  0x6f22,  0x0,  0x6f23,  0x0,  0x6f6e,  0x0,  0x6fc6,  0x0,  0x6feb,  0x0,  0x6ffe,  0x0,  0x701b,  0x0,  0x701e,  0x0,  0x7039,  0x0,  0x704a,  0x0,  0x7070,  0x0,  0x7077,  0x0,  0x707d,  0x0,  0x7099,  0x0,  0x70ad,  0x0,  0x70c8,  0x0,  0x70d9,  0x0,  0x7145,  0x0,  0x7149,  0x0,  0x716e,  0x0,  0x719c,  0x0,  0x71ce,  0x0,  0x71d0,  0x0,  0x7210,  0x0,  0x721b,  0x0,  0x7228,  0x0,  0x722b,  0x0,  0x7235,  0x0,  0x7250,  0x0,  0x7262,  0x0,  0x7280,  0x0,  0x7295,  0x0,  0x72af,  0x0,  0x72c0,  0x0,  0x72fc,  0x0,  0x732a,  0x0,  0x7375,  0x0,  0x737a,  0x0,  0x7387,  0x0,  0x738b,  0x0,  0x73a5,  0x0,  0x73b2,  0x0,  0x73de,  0x0,  0x7406,  0x0,  0x7409,  0x0,  0x7422,  0x0,  0x7447,  0x0,  0x745c,  0x0,  0x7469,  0x0,  0x7471,  0x0,  0x7485,  0x0,  0x7489,  0x0,  0x7498,  0x0,  0x74ca,  0x0,  0x7506,  0x0,  0x7524,  0x0,  0x753b,  0x0,  0x753e,  0x0,  0x7559,  0x0,  0x7565,  0x0,  0x7570,  0x0,  0x75e2,  0x0,  0x7610,  0x0,  0x761d,  0x0,  0x761f,  0x0,  0x7642,  0x0,  0x7669,  0x0,  0x76ca,  0x0,  0x76db,  0x0,  0x76e7,  0x0,  0x76f4,  0x0,  0x7701,  0x0,  0x771e,  0x0,  0x771f,  0x0,  0x7740,  0x0,  0x774a,  0x0,  0x778b,  0x0,  0x77a7,  0x0,  0x784e,  0x0,  0x786b,  0x0,  0x788c,  0x0,  0x7891,  0x0,  0x78ca,  0x0,  0x78cc,  0x0,  0x78fb,  0x0,  0x792a,  0x0,  0x793c,  0x0,  0x793e,  0x0,  0x7948,  0x0,  0x7949,  0x0,  0x7950,  0x0,  0x7956,  0x0,  0x795d,  0x0,  0x795e,  0x0,  0x7965,  0x0,  0x797f,  0x0,  0x798d,  0x0,  0x798e,  0x0,  0x798f,  0x0,  0x79ae,  0x0,  0x79ca,  0x0,  0x79eb,  0x0,  0x7a1c,  0x0,  0x7a40,  0x0,  0x7a4a,  0x0,  0x7a4f,  0x0,  0x7a81,  0x0,  0x7ab1,  0x0,  0x7acb,  0x0,  0x7aee,  0x0,  0x7b20,  0x0,  0x7bc0,  0x0,  0x7bc6,  0x0,  0x7bc9,  0x0,  0x7c3e,  0x0,  0x7c60,  0x0,  0x7c7b,  0x0,  0x7c92,  0x0,  0x7cbe,  0x0,  0x7cd2,  0x0,  0x7cd6,  0x0,  0x7ce3,  0x0,  0x7ce7,  0x0,  0x7ce8,  0x0,  0x7d00,  0x0,  0x7d10,  0x0,  0x7d22,  0x0,  0x7d2f,  0x0,  0x7d5b,  0x0,  0x7d63,  0x0,  0x7da0,  0x0,  0x7dbe,  0x0,  0x7dc7,  0x0,  0x7df4,  0x0,  0x7e02,  0x0,  0x7e09,  0x0,  0x7e37,  0x0,  0x7e41,  0x0,  0x7e45,  0x0,  0x7f3e,  0x0,  0x7f72,  0x0,  0x7f79,  0x0,  0x7f7a,  0x0,  0x7f85,  0x0,  0x7f95,  0x0,  0x7f9a,  0x0,  0x7fbd,  0x0,  0x7ffa,  0x0,  0x8001,  0x0,  0x8005,  0x0,  0x8046,  0x0,  0x8060,  0x0,  0x806f,  0x0,  0x8070,  0x0,  0x807e,  0x0,  0x808b,  0x0,  0x80ad,  0x0,  0x80b2,  0x0,  0x8103,  0x0,  0x813e,  0x0,  0x81d8,  0x0,  0x81e8,  0x0,  0x81ed,  0x0,  0x8201,  0x0,  0x8204,  0x0,  0x8218,  0x0,  0x826f,  0x0,  0x8279,  0x0,  0x828b,  0x0,  0x8291,  0x0,  0x829d,  0x0,  0x82b1,  0x0,  0x82b3,  0x0,  0x82bd,  0x0,  0x82e5,  0x0,  0x82e6,  0x0,  0x831d,  0x0,  0x8323,  0x0,  0x8336,  0x0,  0x8352,  0x0,  0x8353,  0x0,  0x8363,  0x0,  0x83ad,  0x0,  0x83bd,  0x0,  0x83c9,  0x0,  0x83ca,  0x0,  0x83cc,  0x0,  0x83dc,  0x0,  0x83e7,  0x0,  0x83ef,  0x0,  0x83f1,  0x0,  0x843d,  0x0,  0x8449,  0x0,  0x8457,  0x0,  0x84ee,  0x0,  0x84f1,  0x0,  0x84f3,  0x0,  0x84fc,  0x0,  0x8516,  0x0,  0x8564,  0x0,  0x85cd,  0x0,  0x85fa,  0x0,  0x8606,  0x0,  0x8612,  0x0,  0x862d,  0x0,  0x863f,  0x0,  0x8650,  0x0,  0x865c,  0x0,  0x8667,  0x0,  0x8669,  0x0,  0x8688,  0x0,  0x86a9,  0x0,  0x86e2,  0x0,  0x870e,  0x0,  0x8728,  0x0,  0x876b,  0x0,  0x8779,  0x0,  0x8786,  0x0,  0x87ba,  0x0,  0x87e1,  0x0,  0x8801,  0x0,  0x881f,  0x0,  0x884c,  0x0,  0x8860,  0x0,  0x8863,  0x0,  0x88c2,  0x0,  0x88cf,  0x0,  0x88d7,  0x0,  0x88de,  0x0,  0x88e1,  0x0,  0x88f8,  0x0,  0x88fa,  0x0,  0x8910,  0x0,  0x8941,  0x0,  0x8964,  0x0,  0x8986,  0x0,  0x898b,  0x0,  0x8996,  0x0,  0x8aa0,  0x0,  0x8aaa,  0x0,  0x8abf,  0x0,  0x8acb,  0x0,  0x8ad2,  0x0,  0x8ad6,  0x0,  0x8aed,  0x0,  0x8af8,  0x0,  0x8afe,  0x0,  0x8b01,  0x0,  0x8b39,  0x0,  0x8b58,  0x0,  0x8b80,  0x0,  0x8b8a,  0x0,  0x8c48,  0x0,  0x8c55,  0x0,  0x8cab,  0x0,  0x8cc1,  0x0,  0x8cc2,  0x0,  0x8cc8,  0x0,  0x8cd3,  0x0,  0x8d08,  0x0,  0x8d1b,  0x0,  0x8d77,  0x0,  0x8dbc,  0x0,  0x8dcb,  0x0,  0x8def,  0x0,  0x8df0,  0x0,  0x8eca,  0x0,  0x8ed4,  0x0,  0x8f26,  0x0,  0x8f2a,  0x0,  0x8f38,  0x0,  0x8f3b,  0x0,  0x8f62,  0x0,  0x8f9e,  0x0,  0x8fb0,  0x0,  0x8fb6,  0x0,  0x9023,  0x0,  0x9038,  0x0,  0x9072,  0x0,  0x907c,  0x0,  0x908f,  0x0,  0x9094,  0x0,  0x90ce,  0x0,  0x90de,  0x0,  0x90f1,  0x0,  0x90fd,  0x0,  0x9111,  0x0,  0x911b,  0x0,  0x916a,  0x0,  0x9199,  0x0,  0x91b4,  0x0,  0x91cc,  0x0,  0x91cf,  0x0,  0x91d1,  0x0,  0x9234,  0x0,  0x9238,  0x0,  0x9276,  0x0,  0x927c,  0x0,  0x92d7,  0x0,  0x92d8,  0x0,  0x9304,  0x0,  0x934a,  0x0,  0x93f9,  0x0,  0x9415,  0x0,  0x958b,  0x0,  0x95ad,  0x0,  0x95b7,  0x0,  0x962e,  0x0,  0x964b,  0x0,  0x964d,  0x0,  0x9675,  0x0,  0x9678,  0x0,  0x967c,  0x0,  0x9686,  0x0,  0x96a3,  0x0,  0x96b7,  0x0,  0x96b8,  0x0,  0x96c3,  0x0,  0x96e2,  0x0,  0x96e3,  0x0,  0x96f6,  0x0,  0x96f7,  0x0,  0x9723,  0x0,  0x9732,  0x0,  0x9748,  0x0,  0x9756,  0x0,  0x97db,  0x0,  0x97e0,  0x0,  0x97ff,  0x0,  0x980b,  0x0,  0x9818,  0x0,  0x9829,  0x0,  0x983b,  0x0,  0x985e,  0x0,  0x98e2,  0x0,  0x98ef,  0x0,  0x98fc,  0x0,  0x9928,  0x0,  0x9929,  0x0,  0x99a7,  0x0,  0x99c2,  0x0,  0x99f1,  0x0,  0x99fe,  0x0,  0x9a6a,  0x0,  0x9b12,  0x0,  0x9b6f,  0x0,  0x9c40,  0x0,  0x9c57,  0x0,  0x9cfd,  0x0,  0x9d67,  0x0,  0x9db4,  0x0,  0x9dfa,  0x0,  0x9e1e,  0x0,  0x9e7f,  0x0,  0x9e97,  0x0,  0x9e9f,  0x0,  0x9ebb,  0x0,  0x9ece,  0x0,  0x9ef9,  0x0,  0x9efe,  0x0,  0x9f05,  0x0,  0x9f0f,  0x0,  0x9f16,  0x0,  0x9f3b,  0x0,  0x9f43,  0x0,  0x9f8d,  0x0,  0x9f8e,  0x0,  0x9f9c,  0x0,  0x11099,  0x110ba,  0x0,  0x1109b,  0x110ba,  0x0,  0x110a5,  0x110ba,  0x0,  0x11131,  0x11127,  0x0,  0x11132,  0x11127,  0x0,  0x1d157,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x1d16e,  0x0,  0x1d158,  0x1d165,  0x1d16f,  0x0,  0x1d158,  0x1d165,  0x1d170,  0x0,  0x1d158,  0x1d165,  0x1d171,  0x0,  0x1d158,  0x1d165,  0x1d172,  0x0,  0x1d1b9,  0x1d165,  0x0,  0x1d1b9,  0x1d165,  0x1d16e,  0x0,  0x1d1b9,  0x1d165,  0x1d16f,  0x0,  0x1d1ba,  0x1d165,  0x0,  0x1d1ba,  0x1d165,  0x1d16e,  0x0,  0x1d1ba,  0x1d165,  0x1d16f,  0x0,  0x20122,  0x0,  0x2051c,  0x0,  0x20525,  0x0,  0x2054b,  0x0,  0x2063a,  0x0,  0x20804,  0x0,  0x208de,  0x0,  0x20a2c,  0x0,  0x20b63,  0x0,  0x214e4,  0x0,  0x216a8,  0x0,  0x216ea,  0x0,  0x219c8,  0x0,  0x21b18,  0x0,  0x21d0b,  0x0,  0x21de4,  0x0,  0x21de6,  0x0,  0x22183,  0x0,  0x2219f,  0x0,  0x22331,  0x0,  0x226d4,  0x0,  0x22844,  0x0,  0x2284a,  0x0,  0x22b0c,  0x0,  0x22bf1,  0x0,  0x2300a,  0x0,  0x232b8,  0x0,  0x2335f,  0x0,  0x23393,  0x0,  0x2339c,  0x0,  0x233c3,  0x0,  0x233d5,  0x0,  0x2346d,  0x0,  0x236a3,  0x0,  0x238a7,  0x0,  0x23a8d,  0x0,  0x23afa,  0x0,  0x23cbc,  0x0,  0x23d1e,  0x0,  0x23ed1,  0x0,  0x23f5e,  0x0,  0x23f8e,  0x0,  0x24263,  0x0,  0x242ee,  0x0,  0x243ab,  0x0,  0x24608,  0x0,  0x24735,  0x0,  0x24814,  0x0,  0x24c36,  0x0,  0x24c92,  0x0,  0x24fa1,  0x0,  0x24fb8,  0x0,  0x25044,  0x0,  0x250f2,  0x0,  0x250f3,  0x0,  0x25119,  0x0,  0x25133,  0x0,  0x25249,  0x0,  0x2541d,  0x0,  0x25626,  0x0,  0x2569a,  0x0,  0x256c5,  0x0,  0x2597c,  0x0,  0x25aa7,  0x0,  0x25bab,  0x0,  0x25c80,  0x0,  0x25cd0,  0x0,  0x25f86,  0x0,  0x261da,  0x0,  0x26228,  0x0,  0x26247,  0x0,  0x262d9,  0x0,  0x2633e,  0x0,  0x264da,  0x0,  0x26523,  0x0,  0x265a8,  0x0,  0x267a7,  0x0,  0x267b5,  0x0,  0x26b3c,  0x0,  0x26c36,  0x0,  0x26cd5,  0x0,  0x26d6b,  0x0,  0x26f2c,  0x0,  0x26fb1,  0x0,  0x270d2,  0x0,  0x273ca,  0x0,  0x27667,  0x0,  0x278ae,  0x0,  0x27966,  0x0,  0x27ca8,  0x0,  0x27ed3,  0x0,  0x27f2f,  0x0,  0x285d2,  0x0,  0x285ed,  0x0,  0x2872e,  0x0,  0x28bfa,  0x0,  0x28d77,  0x0,  0x29145,  0x0,  0x291df,  0x0,  0x2921a,  0x0,  0x2940a,  0x0,  0x29496,  0x0,  0x295b6,  0x0,  0x29b30,  0x0,  0x2a0ce,  0x0,  0x2a105,  0x0,  0x2a20e,  0x0,  0x2a291,  0x0,  0x2a392,  0x0,  0x2a600,  0x0]; return t; }
+_IDCA decompCompatTable() { static _IDCA t = [ 0x0,  0x20,  0x0,  0x20,  0x301,  0x0,  0x20,  0x303,  0x0,  0x20,  0x304,  0x0,  0x20,  0x305,  0x0,  0x20,  0x306,  0x0,  0x20,  0x307,  0x0,  0x20,  0x308,  0x0,  0x20,  0x308,  0x300,  0x0,  0x20,  0x308,  0x301,  0x0,  0x20,  0x308,  0x342,  0x0,  0x20,  0x30a,  0x0,  0x20,  0x30b,  0x0,  0x20,  0x313,  0x0,  0x20,  0x313,  0x300,  0x0,  0x20,  0x313,  0x301,  0x0,  0x20,  0x313,  0x342,  0x0,  0x20,  0x314,  0x0,  0x20,  0x314,  0x300,  0x0,  0x20,  0x314,  0x301,  0x0,  0x20,  0x314,  0x342,  0x0,  0x20,  0x327,  0x0,  0x20,  0x328,  0x0,  0x20,  0x333,  0x0,  0x20,  0x342,  0x0,  0x20,  0x345,  0x0,  0x20,  0x64b,  0x0,  0x20,  0x64c,  0x0,  0x20,  0x64c,  0x651,  0x0,  0x20,  0x64d,  0x0,  0x20,  0x64d,  0x651,  0x0,  0x20,  0x64e,  0x0,  0x20,  0x64e,  0x651,  0x0,  0x20,  0x64f,  0x0,  0x20,  0x64f,  0x651,  0x0,  0x20,  0x650,  0x0,  0x20,  0x650,  0x651,  0x0,  0x20,  0x651,  0x0,  0x20,  0x651,  0x670,  0x0,  0x20,  0x652,  0x0,  0x20,  0x3099,  0x0,  0x20,  0x309a,  0x0,  0x21,  0x0,  0x21,  0x21,  0x0,  0x21,  0x3f,  0x0,  0x22,  0x0,  0x23,  0x0,  0x24,  0x0,  0x25,  0x0,  0x26,  0x0,  0x27,  0x0,  0x28,  0x0,  0x28,  0x31,  0x29,  0x0,  0x28,  0x31,  0x30,  0x29,  0x0,  0x28,  0x31,  0x31,  0x29,  0x0,  0x28,  0x31,  0x32,  0x29,  0x0,  0x28,  0x31,  0x33,  0x29,  0x0,  0x28,  0x31,  0x34,  0x29,  0x0,  0x28,  0x31,  0x35,  0x29,  0x0,  0x28,  0x31,  0x36,  0x29,  0x0,  0x28,  0x31,  0x37,  0x29,  0x0,  0x28,  0x31,  0x38,  0x29,  0x0,  0x28,  0x31,  0x39,  0x29,  0x0,  0x28,  0x32,  0x29,  0x0,  0x28,  0x32,  0x30,  0x29,  0x0,  0x28,  0x33,  0x29,  0x0,  0x28,  0x34,  0x29,  0x0,  0x28,  0x35,  0x29,  0x0,  0x28,  0x36,  0x29,  0x0,  0x28,  0x37,  0x29,  0x0,  0x28,  0x38,  0x29,  0x0,  0x28,  0x39,  0x29,  0x0,  0x28,  0x41,  0x29,  0x0,  0x28,  0x42,  0x29,  0x0,  0x28,  0x43,  0x29,  0x0,  0x28,  0x44,  0x29,  0x0,  0x28,  0x45,  0x29,  0x0,  0x28,  0x46,  0x29,  0x0,  0x28,  0x47,  0x29,  0x0,  0x28,  0x48,  0x29,  0x0,  0x28,  0x49,  0x29,  0x0,  0x28,  0x4a,  0x29,  0x0,  0x28,  0x4b,  0x29,  0x0,  0x28,  0x4c,  0x29,  0x0,  0x28,  0x4d,  0x29,  0x0,  0x28,  0x4e,  0x29,  0x0,  0x28,  0x4f,  0x29,  0x0,  0x28,  0x50,  0x29,  0x0,  0x28,  0x51,  0x29,  0x0,  0x28,  0x52,  0x29,  0x0,  0x28,  0x53,  0x29,  0x0,  0x28,  0x54,  0x29,  0x0,  0x28,  0x55,  0x29,  0x0,  0x28,  0x56,  0x29,  0x0,  0x28,  0x57,  0x29,  0x0,  0x28,  0x58,  0x29,  0x0,  0x28,  0x59,  0x29,  0x0,  0x28,  0x5a,  0x29,  0x0,  0x28,  0x61,  0x29,  0x0,  0x28,  0x62,  0x29,  0x0,  0x28,  0x63,  0x29,  0x0,  0x28,  0x64,  0x29,  0x0,  0x28,  0x65,  0x29,  0x0,  0x28,  0x66,  0x29,  0x0,  0x28,  0x67,  0x29,  0x0,  0x28,  0x68,  0x29,  0x0,  0x28,  0x69,  0x29,  0x0,  0x28,  0x6a,  0x29,  0x0,  0x28,  0x6b,  0x29,  0x0,  0x28,  0x6c,  0x29,  0x0,  0x28,  0x6d,  0x29,  0x0,  0x28,  0x6e,  0x29,  0x0,  0x28,  0x6f,  0x29,  0x0,  0x28,  0x70,  0x29,  0x0,  0x28,  0x71,  0x29,  0x0,  0x28,  0x72,  0x29,  0x0,  0x28,  0x73,  0x29,  0x0,  0x28,  0x74,  0x29,  0x0,  0x28,  0x75,  0x29,  0x0,  0x28,  0x76,  0x29,  0x0,  0x28,  0x77,  0x29,  0x0,  0x28,  0x78,  0x29,  0x0,  0x28,  0x79,  0x29,  0x0,  0x28,  0x7a,  0x29,  0x0,  0x28,  0x1100,  0x29,  0x0,  0x28,  0x1100,  0x1161,  0x29,  0x0,  0x28,  0x1102,  0x29,  0x0,  0x28,  0x1102,  0x1161,  0x29,  0x0,  0x28,  0x1103,  0x29,  0x0,  0x28,  0x1103,  0x1161,  0x29,  0x0,  0x28,  0x1105,  0x29,  0x0,  0x28,  0x1105,  0x1161,  0x29,  0x0,  0x28,  0x1106,  0x29,  0x0,  0x28,  0x1106,  0x1161,  0x29,  0x0,  0x28,  0x1107,  0x29,  0x0,  0x28,  0x1107,  0x1161,  0x29,  0x0,  0x28,  0x1109,  0x29,  0x0,  0x28,  0x1109,  0x1161,  0x29,  0x0,  0x28,  0x110b,  0x29,  0x0,  0x28,  0x110b,  0x1161,  0x29,  0x0,  0x28,  0x110b,  0x1169,  0x110c,  0x1165,  0x11ab,  0x29,  0x0,  0x28,  0x110b,  0x1169,  0x1112,  0x116e,  0x29,  0x0,  0x28,  0x110c,  0x29,  0x0,  0x28,  0x110c,  0x1161,  0x29,  0x0,  0x28,  0x110c,  0x116e,  0x29,  0x0,  0x28,  0x110e,  0x29,  0x0,  0x28,  0x110e,  0x1161,  0x29,  0x0,  0x28,  0x110f,  0x29,  0x0,  0x28,  0x110f,  0x1161,  0x29,  0x0,  0x28,  0x1110,  0x29,  0x0,  0x28,  0x1110,  0x1161,  0x29,  0x0,  0x28,  0x1111,  0x29,  0x0,  0x28,  0x1111,  0x1161,  0x29,  0x0,  0x28,  0x1112,  0x29,  0x0,  0x28,  0x1112,  0x1161,  0x29,  0x0,  0x28,  0x4e00,  0x29,  0x0,  0x28,  0x4e03,  0x29,  0x0,  0x28,  0x4e09,  0x29,  0x0,  0x28,  0x4e5d,  0x29,  0x0,  0x28,  0x4e8c,  0x29,  0x0,  0x28,  0x4e94,  0x29,  0x0,  0x28,  0x4ee3,  0x29,  0x0,  0x28,  0x4f01,  0x29,  0x0,  0x28,  0x4f11,  0x29,  0x0,  0x28,  0x516b,  0x29,  0x0,  0x28,  0x516d,  0x29,  0x0,  0x28,  0x52b4,  0x29,  0x0,  0x28,  0x5341,  0x29,  0x0,  0x28,  0x5354,  0x29,  0x0,  0x28,  0x540d,  0x29,  0x0,  0x28,  0x547c,  0x29,  0x0,  0x28,  0x56db,  0x29,  0x0,  0x28,  0x571f,  0x29,  0x0,  0x28,  0x5b66,  0x29,  0x0,  0x28,  0x65e5,  0x29,  0x0,  0x28,  0x6708,  0x29,  0x0,  0x28,  0x6709,  0x29,  0x0,  0x28,  0x6728,  0x29,  0x0,  0x28,  0x682a,  0x29,  0x0,  0x28,  0x6c34,  0x29,  0x0,  0x28,  0x706b,  0x29,  0x0,  0x28,  0x7279,  0x29,  0x0,  0x28,  0x76e3,  0x29,  0x0,  0x28,  0x793e,  0x29,  0x0,  0x28,  0x795d,  0x29,  0x0,  0x28,  0x796d,  0x29,  0x0,  0x28,  0x81ea,  0x29,  0x0,  0x28,  0x81f3,  0x29,  0x0,  0x28,  0x8ca1,  0x29,  0x0,  0x28,  0x8cc7,  0x29,  0x0,  0x28,  0x91d1,  0x29,  0x0,  0x29,  0x0,  0x2a,  0x0,  0x2b,  0x0,  0x2c,  0x0,  0x2d,  0x0,  0x2e,  0x0,  0x2e,  0x2e,  0x0,  0x2e,  0x2e,  0x2e,  0x0,  0x2f,  0x0,  0x30,  0x0,  0x30,  0x2c,  0x0,  0x30,  0x2e,  0x0,  0x30,  0x2044,  0x33,  0x0,  0x30,  0x70b9,  0x0,  0x31,  0x0,  0x31,  0x2c,  0x0,  0x31,  0x2e,  0x0,  0x31,  0x30,  0x0,  0x31,  0x30,  0x2e,  0x0,  0x31,  0x30,  0x65e5,  0x0,  0x31,  0x30,  0x6708,  0x0,  0x31,  0x30,  0x70b9,  0x0,  0x31,  0x31,  0x0,  0x31,  0x31,  0x2e,  0x0,  0x31,  0x31,  0x65e5,  0x0,  0x31,  0x31,  0x6708,  0x0,  0x31,  0x31,  0x70b9,  0x0,  0x31,  0x32,  0x0,  0x31,  0x32,  0x2e,  0x0,  0x31,  0x32,  0x65e5,  0x0,  0x31,  0x32,  0x6708,  0x0,  0x31,  0x32,  0x70b9,  0x0,  0x31,  0x33,  0x0,  0x31,  0x33,  0x2e,  0x0,  0x31,  0x33,  0x65e5,  0x0,  0x31,  0x33,  0x70b9,  0x0,  0x31,  0x34,  0x0,  0x31,  0x34,  0x2e,  0x0,  0x31,  0x34,  0x65e5,  0x0,  0x31,  0x34,  0x70b9,  0x0,  0x31,  0x35,  0x0,  0x31,  0x35,  0x2e,  0x0,  0x31,  0x35,  0x65e5,  0x0,  0x31,  0x35,  0x70b9,  0x0,  0x31,  0x36,  0x0,  0x31,  0x36,  0x2e,  0x0,  0x31,  0x36,  0x65e5,  0x0,  0x31,  0x36,  0x70b9,  0x0,  0x31,  0x37,  0x0,  0x31,  0x37,  0x2e,  0x0,  0x31,  0x37,  0x65e5,  0x0,  0x31,  0x37,  0x70b9,  0x0,  0x31,  0x38,  0x0,  0x31,  0x38,  0x2e,  0x0,  0x31,  0x38,  0x65e5,  0x0,  0x31,  0x38,  0x70b9,  0x0,  0x31,  0x39,  0x0,  0x31,  0x39,  0x2e,  0x0,  0x31,  0x39,  0x65e5,  0x0,  0x31,  0x39,  0x70b9,  0x0,  0x31,  0x2044,  0x0,  0x31,  0x2044,  0x31,  0x30,  0x0,  0x31,  0x2044,  0x32,  0x0,  0x31,  0x2044,  0x33,  0x0,  0x31,  0x2044,  0x34,  0x0,  0x31,  0x2044,  0x35,  0x0,  0x31,  0x2044,  0x36,  0x0,  0x31,  0x2044,  0x37,  0x0,  0x31,  0x2044,  0x38,  0x0,  0x31,  0x2044,  0x39,  0x0,  0x31,  0x65e5,  0x0,  0x31,  0x6708,  0x0,  0x31,  0x70b9,  0x0,  0x32,  0x0,  0x32,  0x2c,  0x0,  0x32,  0x2e,  0x0,  0x32,  0x30,  0x0,  0x32,  0x30,  0x2e,  0x0,  0x32,  0x30,  0x65e5,  0x0,  0x32,  0x30,  0x70b9,  0x0,  0x32,  0x31,  0x0,  0x32,  0x31,  0x65e5,  0x0,  0x32,  0x31,  0x70b9,  0x0,  0x32,  0x32,  0x0,  0x32,  0x32,  0x65e5,  0x0,  0x32,  0x32,  0x70b9,  0x0,  0x32,  0x33,  0x0,  0x32,  0x33,  0x65e5,  0x0,  0x32,  0x33,  0x70b9,  0x0,  0x32,  0x34,  0x0,  0x32,  0x34,  0x65e5,  0x0,  0x32,  0x34,  0x70b9,  0x0,  0x32,  0x35,  0x0,  0x32,  0x35,  0x65e5,  0x0,  0x32,  0x36,  0x0,  0x32,  0x36,  0x65e5,  0x0,  0x32,  0x37,  0x0,  0x32,  0x37,  0x65e5,  0x0,  0x32,  0x38,  0x0,  0x32,  0x38,  0x65e5,  0x0,  0x32,  0x39,  0x0,  0x32,  0x39,  0x65e5,  0x0,  0x32,  0x2044,  0x33,  0x0,  0x32,  0x2044,  0x35,  0x0,  0x32,  0x65e5,  0x0,  0x32,  0x6708,  0x0,  0x32,  0x70b9,  0x0,  0x33,  0x0,  0x33,  0x2c,  0x0,  0x33,  0x2e,  0x0,  0x33,  0x30,  0x0,  0x33,  0x30,  0x65e5,  0x0,  0x33,  0x31,  0x0,  0x33,  0x31,  0x65e5,  0x0,  0x33,  0x32,  0x0,  0x33,  0x33,  0x0,  0x33,  0x34,  0x0,  0x33,  0x35,  0x0,  0x33,  0x36,  0x0,  0x33,  0x37,  0x0,  0x33,  0x38,  0x0,  0x33,  0x39,  0x0,  0x33,  0x2044,  0x34,  0x0,  0x33,  0x2044,  0x35,  0x0,  0x33,  0x2044,  0x38,  0x0,  0x33,  0x65e5,  0x0,  0x33,  0x6708,  0x0,  0x33,  0x70b9,  0x0,  0x34,  0x0,  0x34,  0x2c,  0x0,  0x34,  0x2e,  0x0,  0x34,  0x30,  0x0,  0x34,  0x31,  0x0,  0x34,  0x32,  0x0,  0x34,  0x33,  0x0,  0x34,  0x34,  0x0,  0x34,  0x35,  0x0,  0x34,  0x36,  0x0,  0x34,  0x37,  0x0,  0x34,  0x38,  0x0,  0x34,  0x39,  0x0,  0x34,  0x2044,  0x35,  0x0,  0x34,  0x65e5,  0x0,  0x34,  0x6708,  0x0,  0x34,  0x70b9,  0x0,  0x35,  0x0,  0x35,  0x2c,  0x0,  0x35,  0x2e,  0x0,  0x35,  0x30,  0x0,  0x35,  0x2044,  0x36,  0x0,  0x35,  0x2044,  0x38,  0x0,  0x35,  0x65e5,  0x0,  0x35,  0x6708,  0x0,  0x35,  0x70b9,  0x0,  0x36,  0x0,  0x36,  0x2c,  0x0,  0x36,  0x2e,  0x0,  0x36,  0x65e5,  0x0,  0x36,  0x6708,  0x0,  0x36,  0x70b9,  0x0,  0x37,  0x0,  0x37,  0x2c,  0x0,  0x37,  0x2e,  0x0,  0x37,  0x2044,  0x38,  0x0,  0x37,  0x65e5,  0x0,  0x37,  0x6708,  0x0,  0x37,  0x70b9,  0x0,  0x38,  0x0,  0x38,  0x2c,  0x0,  0x38,  0x2e,  0x0,  0x38,  0x65e5,  0x0,  0x38,  0x6708,  0x0,  0x38,  0x70b9,  0x0,  0x39,  0x0,  0x39,  0x2c,  0x0,  0x39,  0x2e,  0x0,  0x39,  0x65e5,  0x0,  0x39,  0x6708,  0x0,  0x39,  0x70b9,  0x0,  0x3a,  0x0,  0x3a,  0x3a,  0x3d,  0x0,  0x3b,  0x0,  0x3c,  0x0,  0x3c,  0x338,  0x0,  0x3d,  0x0,  0x3d,  0x3d,  0x0,  0x3d,  0x3d,  0x3d,  0x0,  0x3d,  0x338,  0x0,  0x3e,  0x0,  0x3e,  0x338,  0x0,  0x3f,  0x0,  0x3f,  0x21,  0x0,  0x3f,  0x3f,  0x0,  0x40,  0x0,  0x41,  0x0,  0x41,  0x55,  0x0,  0x41,  0x300,  0x0,  0x41,  0x301,  0x0,  0x41,  0x302,  0x0,  0x41,  0x302,  0x300,  0x0,  0x41,  0x302,  0x301,  0x0,  0x41,  0x302,  0x303,  0x0,  0x41,  0x302,  0x309,  0x0,  0x41,  0x303,  0x0,  0x41,  0x304,  0x0,  0x41,  0x306,  0x0,  0x41,  0x306,  0x300,  0x0,  0x41,  0x306,  0x301,  0x0,  0x41,  0x306,  0x303,  0x0,  0x41,  0x306,  0x309,  0x0,  0x41,  0x307,  0x0,  0x41,  0x307,  0x304,  0x0,  0x41,  0x308,  0x0,  0x41,  0x308,  0x304,  0x0,  0x41,  0x309,  0x0,  0x41,  0x30a,  0x0,  0x41,  0x30a,  0x301,  0x0,  0x41,  0x30c,  0x0,  0x41,  0x30f,  0x0,  0x41,  0x311,  0x0,  0x41,  0x323,  0x0,  0x41,  0x323,  0x302,  0x0,  0x41,  0x323,  0x306,  0x0,  0x41,  0x325,  0x0,  0x41,  0x328,  0x0,  0x41,  0x2215,  0x6d,  0x0,  0x42,  0x0,  0x42,  0x71,  0x0,  0x42,  0x307,  0x0,  0x42,  0x323,  0x0,  0x42,  0x331,  0x0,  0x43,  0x0,  0x43,  0x44,  0x0,  0x43,  0x6f,  0x2e,  0x0,  0x43,  0x301,  0x0,  0x43,  0x302,  0x0,  0x43,  0x307,  0x0,  0x43,  0x30c,  0x0,  0x43,  0x327,  0x0,  0x43,  0x327,  0x301,  0x0,  0x43,  0x2215,  0x6b,  0x67,  0x0,  0x44,  0x0,  0x44,  0x4a,  0x0,  0x44,  0x5a,  0x0,  0x44,  0x5a,  0x30c,  0x0,  0x44,  0x7a,  0x0,  0x44,  0x7a,  0x30c,  0x0,  0x44,  0x307,  0x0,  0x44,  0x30c,  0x0,  0x44,  0x323,  0x0,  0x44,  0x327,  0x0,  0x44,  0x32d,  0x0,  0x44,  0x331,  0x0,  0x45,  0x0,  0x45,  0x300,  0x0,  0x45,  0x301,  0x0,  0x45,  0x302,  0x0,  0x45,  0x302,  0x300,  0x0,  0x45,  0x302,  0x301,  0x0,  0x45,  0x302,  0x303,  0x0,  0x45,  0x302,  0x309,  0x0,  0x45,  0x303,  0x0,  0x45,  0x304,  0x0,  0x45,  0x304,  0x300,  0x0,  0x45,  0x304,  0x301,  0x0,  0x45,  0x306,  0x0,  0x45,  0x307,  0x0,  0x45,  0x308,  0x0,  0x45,  0x309,  0x0,  0x45,  0x30c,  0x0,  0x45,  0x30f,  0x0,  0x45,  0x311,  0x0,  0x45,  0x323,  0x0,  0x45,  0x323,  0x302,  0x0,  0x45,  0x327,  0x0,  0x45,  0x327,  0x306,  0x0,  0x45,  0x328,  0x0,  0x45,  0x32d,  0x0,  0x45,  0x330,  0x0,  0x46,  0x0,  0x46,  0x41,  0x58,  0x0,  0x46,  0x307,  0x0,  0x47,  0x0,  0x47,  0x42,  0x0,  0x47,  0x48,  0x7a,  0x0,  0x47,  0x50,  0x61,  0x0,  0x47,  0x79,  0x0,  0x47,  0x301,  0x0,  0x47,  0x302,  0x0,  0x47,  0x304,  0x0,  0x47,  0x306,  0x0,  0x47,  0x307,  0x0,  0x47,  0x30c,  0x0,  0x47,  0x327,  0x0,  0x48,  0x0,  0x48,  0x50,  0x0,  0x48,  0x56,  0x0,  0x48,  0x67,  0x0,  0x48,  0x7a,  0x0,  0x48,  0x302,  0x0,  0x48,  0x307,  0x0,  0x48,  0x308,  0x0,  0x48,  0x30c,  0x0,  0x48,  0x323,  0x0,  0x48,  0x327,  0x0,  0x48,  0x32e,  0x0,  0x49,  0x0,  0x49,  0x49,  0x0,  0x49,  0x49,  0x49,  0x0,  0x49,  0x4a,  0x0,  0x49,  0x55,  0x0,  0x49,  0x56,  0x0,  0x49,  0x58,  0x0,  0x49,  0x300,  0x0,  0x49,  0x301,  0x0,  0x49,  0x302,  0x0,  0x49,  0x303,  0x0,  0x49,  0x304,  0x0,  0x49,  0x306,  0x0,  0x49,  0x307,  0x0,  0x49,  0x308,  0x0,  0x49,  0x308,  0x301,  0x0,  0x49,  0x309,  0x0,  0x49,  0x30c,  0x0,  0x49,  0x30f,  0x0,  0x49,  0x311,  0x0,  0x49,  0x323,  0x0,  0x49,  0x328,  0x0,  0x49,  0x330,  0x0,  0x4a,  0x0,  0x4a,  0x302,  0x0,  0x4b,  0x0,  0x4b,  0x42,  0x0,  0x4b,  0x4b,  0x0,  0x4b,  0x4d,  0x0,  0x4b,  0x301,  0x0,  0x4b,  0x30c,  0x0,  0x4b,  0x323,  0x0,  0x4b,  0x327,  0x0,  0x4b,  0x331,  0x0,  0x4c,  0x0,  0x4c,  0x4a,  0x0,  0x4c,  0x54,  0x44,  0x0,  0x4c,  0x6a,  0x0,  0x4c,  0xb7,  0x0,  0x4c,  0x301,  0x0,  0x4c,  0x30c,  0x0,  0x4c,  0x323,  0x0,  0x4c,  0x323,  0x304,  0x0,  0x4c,  0x327,  0x0,  0x4c,  0x32d,  0x0,  0x4c,  0x331,  0x0,  0x4d,  0x0,  0x4d,  0x42,  0x0,  0x4d,  0x43,  0x0,  0x4d,  0x44,  0x0,  0x4d,  0x48,  0x7a,  0x0,  0x4d,  0x50,  0x61,  0x0,  0x4d,  0x56,  0x0,  0x4d,  0x57,  0x0,  0x4d,  0x301,  0x0,  0x4d,  0x307,  0x0,  0x4d,  0x323,  0x0,  0x4d,  0x3a9,  0x0,  0x4e,  0x0,  0x4e,  0x4a,  0x0,  0x4e,  0x6a,  0x0,  0x4e,  0x6f,  0x0,  0x4e,  0x300,  0x0,  0x4e,  0x301,  0x0,  0x4e,  0x303,  0x0,  0x4e,  0x307,  0x0,  0x4e,  0x30c,  0x0,  0x4e,  0x323,  0x0,  0x4e,  0x327,  0x0,  0x4e,  0x32d,  0x0,  0x4e,  0x331,  0x0,  0x4f,  0x0,  0x4f,  0x300,  0x0,  0x4f,  0x301,  0x0,  0x4f,  0x302,  0x0,  0x4f,  0x302,  0x300,  0x0,  0x4f,  0x302,  0x301,  0x0,  0x4f,  0x302,  0x303,  0x0,  0x4f,  0x302,  0x309,  0x0,  0x4f,  0x303,  0x0,  0x4f,  0x303,  0x301,  0x0,  0x4f,  0x303,  0x304,  0x0,  0x4f,  0x303,  0x308,  0x0,  0x4f,  0x304,  0x0,  0x4f,  0x304,  0x300,  0x0,  0x4f,  0x304,  0x301,  0x0,  0x4f,  0x306,  0x0,  0x4f,  0x307,  0x0,  0x4f,  0x307,  0x304,  0x0,  0x4f,  0x308,  0x0,  0x4f,  0x308,  0x304,  0x0,  0x4f,  0x309,  0x0,  0x4f,  0x30b,  0x0,  0x4f,  0x30c,  0x0,  0x4f,  0x30f,  0x0,  0x4f,  0x311,  0x0,  0x4f,  0x31b,  0x0,  0x4f,  0x31b,  0x300,  0x0,  0x4f,  0x31b,  0x301,  0x0,  0x4f,  0x31b,  0x303,  0x0,  0x4f,  0x31b,  0x309,  0x0,  0x4f,  0x31b,  0x323,  0x0,  0x4f,  0x323,  0x0,  0x4f,  0x323,  0x302,  0x0,  0x4f,  0x328,  0x0,  0x4f,  0x328,  0x304,  0x0,  0x50,  0x0,  0x50,  0x48,  0x0,  0x50,  0x50,  0x4d,  0x0,  0x50,  0x50,  0x56,  0x0,  0x50,  0x52,  0x0,  0x50,  0x54,  0x45,  0x0,  0x50,  0x61,  0x0,  0x50,  0x301,  0x0,  0x50,  0x307,  0x0,  0x51,  0x0,  0x52,  0x0,  0x52,  0x73,  0x0,  0x52,  0x301,  0x0,  0x52,  0x307,  0x0,  0x52,  0x30c,  0x0,  0x52,  0x30f,  0x0,  0x52,  0x311,  0x0,  0x52,  0x323,  0x0,  0x52,  0x323,  0x304,  0x0,  0x52,  0x327,  0x0,  0x52,  0x331,  0x0,  0x53,  0x0,  0x53,  0x44,  0x0,  0x53,  0x4d,  0x0,  0x53,  0x53,  0x0,  0x53,  0x76,  0x0,  0x53,  0x301,  0x0,  0x53,  0x301,  0x307,  0x0,  0x53,  0x302,  0x0,  0x53,  0x307,  0x0,  0x53,  0x30c,  0x0,  0x53,  0x30c,  0x307,  0x0,  0x53,  0x323,  0x0,  0x53,  0x323,  0x307,  0x0,  0x53,  0x326,  0x0,  0x53,  0x327,  0x0,  0x54,  0x0,  0x54,  0x45,  0x4c,  0x0,  0x54,  0x48,  0x7a,  0x0,  0x54,  0x4d,  0x0,  0x54,  0x307,  0x0,  0x54,  0x30c,  0x0,  0x54,  0x323,  0x0,  0x54,  0x326,  0x0,  0x54,  0x327,  0x0,  0x54,  0x32d,  0x0,  0x54,  0x331,  0x0,  0x55,  0x0,  0x55,  0x300,  0x0,  0x55,  0x301,  0x0,  0x55,  0x302,  0x0,  0x55,  0x303,  0x0,  0x55,  0x303,  0x301,  0x0,  0x55,  0x304,  0x0,  0x55,  0x304,  0x308,  0x0,  0x55,  0x306,  0x0,  0x55,  0x308,  0x0,  0x55,  0x308,  0x300,  0x0,  0x55,  0x308,  0x301,  0x0,  0x55,  0x308,  0x304,  0x0,  0x55,  0x308,  0x30c,  0x0,  0x55,  0x309,  0x0,  0x55,  0x30a,  0x0,  0x55,  0x30b,  0x0,  0x55,  0x30c,  0x0,  0x55,  0x30f,  0x0,  0x55,  0x311,  0x0,  0x55,  0x31b,  0x0,  0x55,  0x31b,  0x300,  0x0,  0x55,  0x31b,  0x301,  0x0,  0x55,  0x31b,  0x303,  0x0,  0x55,  0x31b,  0x309,  0x0,  0x55,  0x31b,  0x323,  0x0,  0x55,  0x323,  0x0,  0x55,  0x324,  0x0,  0x55,  0x328,  0x0,  0x55,  0x32d,  0x0,  0x55,  0x330,  0x0,  0x56,  0x0,  0x56,  0x49,  0x0,  0x56,  0x49,  0x49,  0x0,  0x56,  0x49,  0x49,  0x49,  0x0,  0x56,  0x303,  0x0,  0x56,  0x323,  0x0,  0x56,  0x2215,  0x6d,  0x0,  0x57,  0x0,  0x57,  0x43,  0x0,  0x57,  0x5a,  0x0,  0x57,  0x62,  0x0,  0x57,  0x300,  0x0,  0x57,  0x301,  0x0,  0x57,  0x302,  0x0,  0x57,  0x307,  0x0,  0x57,  0x308,  0x0,  0x57,  0x323,  0x0,  0x58,  0x0,  0x58,  0x49,  0x0,  0x58,  0x49,  0x49,  0x0,  0x58,  0x307,  0x0,  0x58,  0x308,  0x0,  0x59,  0x0,  0x59,  0x300,  0x0,  0x59,  0x301,  0x0,  0x59,  0x302,  0x0,  0x59,  0x303,  0x0,  0x59,  0x304,  0x0,  0x59,  0x307,  0x0,  0x59,  0x308,  0x0,  0x59,  0x309,  0x0,  0x59,  0x323,  0x0,  0x5a,  0x0,  0x5a,  0x301,  0x0,  0x5a,  0x302,  0x0,  0x5a,  0x307,  0x0,  0x5a,  0x30c,  0x0,  0x5a,  0x323,  0x0,  0x5a,  0x331,  0x0,  0x5b,  0x0,  0x5c,  0x0,  0x5d,  0x0,  0x5e,  0x0,  0x5f,  0x0,  0x60,  0x0,  0x61,  0x0,  0x61,  0x2e,  0x6d,  0x2e,  0x0,  0x61,  0x2f,  0x63,  0x0,  0x61,  0x2f,  0x73,  0x0,  0x61,  0x2be,  0x0,  0x61,  0x300,  0x0,  0x61,  0x301,  0x0,  0x61,  0x302,  0x0,  0x61,  0x302,  0x300,  0x0,  0x61,  0x302,  0x301,  0x0,  0x61,  0x302,  0x303,  0x0,  0x61,  0x302,  0x309,  0x0,  0x61,  0x303,  0x0,  0x61,  0x304,  0x0,  0x61,  0x306,  0x0,  0x61,  0x306,  0x300,  0x0,  0x61,  0x306,  0x301,  0x0,  0x61,  0x306,  0x303,  0x0,  0x61,  0x306,  0x309,  0x0,  0x61,  0x307,  0x0,  0x61,  0x307,  0x304,  0x0,  0x61,  0x308,  0x0,  0x61,  0x308,  0x304,  0x0,  0x61,  0x309,  0x0,  0x61,  0x30a,  0x0,  0x61,  0x30a,  0x301,  0x0,  0x61,  0x30c,  0x0,  0x61,  0x30f,  0x0,  0x61,  0x311,  0x0,  0x61,  0x323,  0x0,  0x61,  0x323,  0x302,  0x0,  0x61,  0x323,  0x306,  0x0,  0x61,  0x325,  0x0,  0x61,  0x328,  0x0,  0x62,  0x0,  0x62,  0x61,  0x72,  0x0,  0x62,  0x307,  0x0,  0x62,  0x323,  0x0,  0x62,  0x331,  0x0,  0x63,  0x0,  0x63,  0x2f,  0x6f,  0x0,  0x63,  0x2f,  0x75,  0x0,  0x63,  0x61,  0x6c,  0x0,  0x63,  0x63,  0x0,  0x63,  0x64,  0x0,  0x63,  0x6d,  0x0,  0x63,  0x6d,  0x32,  0x0,  0x63,  0x6d,  0x33,  0x0,  0x63,  0x301,  0x0,  0x63,  0x302,  0x0,  0x63,  0x307,  0x0,  0x63,  0x30c,  0x0,  0x63,  0x327,  0x0,  0x63,  0x327,  0x301,  0x0,  0x64,  0x0,  0x64,  0x42,  0x0,  0x64,  0x61,  0x0,  0x64,  0x6c,  0x0,  0x64,  0x6d,  0x0,  0x64,  0x6d,  0x32,  0x0,  0x64,  0x6d,  0x33,  0x0,  0x64,  0x7a,  0x0,  0x64,  0x7a,  0x30c,  0x0,  0x64,  0x307,  0x0,  0x64,  0x30c,  0x0,  0x64,  0x323,  0x0,  0x64,  0x327,  0x0,  0x64,  0x32d,  0x0,  0x64,  0x331,  0x0,  0x65,  0x0,  0x65,  0x56,  0x0,  0x65,  0x72,  0x67,  0x0,  0x65,  0x300,  0x0,  0x65,  0x301,  0x0,  0x65,  0x302,  0x0,  0x65,  0x302,  0x300,  0x0,  0x65,  0x302,  0x301,  0x0,  0x65,  0x302,  0x303,  0x0,  0x65,  0x302,  0x309,  0x0,  0x65,  0x303,  0x0,  0x65,  0x304,  0x0,  0x65,  0x304,  0x300,  0x0,  0x65,  0x304,  0x301,  0x0,  0x65,  0x306,  0x0,  0x65,  0x307,  0x0,  0x65,  0x308,  0x0,  0x65,  0x309,  0x0,  0x65,  0x30c,  0x0,  0x65,  0x30f,  0x0,  0x65,  0x311,  0x0,  0x65,  0x323,  0x0,  0x65,  0x323,  0x302,  0x0,  0x65,  0x327,  0x0,  0x65,  0x327,  0x306,  0x0,  0x65,  0x328,  0x0,  0x65,  0x32d,  0x0,  0x65,  0x330,  0x0,  0x66,  0x0,  0x66,  0x66,  0x0,  0x66,  0x66,  0x69,  0x0,  0x66,  0x66,  0x6c,  0x0,  0x66,  0x69,  0x0,  0x66,  0x6c,  0x0,  0x66,  0x6d,  0x0,  0x66,  0x307,  0x0,  0x67,  0x0,  0x67,  0x61,  0x6c,  0x0,  0x67,  0x301,  0x0,  0x67,  0x302,  0x0,  0x67,  0x304,  0x0,  0x67,  0x306,  0x0,  0x67,  0x307,  0x0,  0x67,  0x30c,  0x0,  0x67,  0x327,  0x0,  0x68,  0x0,  0x68,  0x50,  0x61,  0x0,  0x68,  0x61,  0x0,  0x68,  0x302,  0x0,  0x68,  0x307,  0x0,  0x68,  0x308,  0x0,  0x68,  0x30c,  0x0,  0x68,  0x323,  0x0,  0x68,  0x327,  0x0,  0x68,  0x32e,  0x0,  0x68,  0x331,  0x0,  0x69,  0x0,  0x69,  0x69,  0x0,  0x69,  0x69,  0x69,  0x0,  0x69,  0x6a,  0x0,  0x69,  0x6e,  0x0,  0x69,  0x76,  0x0,  0x69,  0x78,  0x0,  0x69,  0x300,  0x0,  0x69,  0x301,  0x0,  0x69,  0x302,  0x0,  0x69,  0x303,  0x0,  0x69,  0x304,  0x0,  0x69,  0x306,  0x0,  0x69,  0x308,  0x0,  0x69,  0x308,  0x301,  0x0,  0x69,  0x309,  0x0,  0x69,  0x30c,  0x0,  0x69,  0x30f,  0x0,  0x69,  0x311,  0x0,  0x69,  0x323,  0x0,  0x69,  0x328,  0x0,  0x69,  0x330,  0x0,  0x6a,  0x0,  0x6a,  0x302,  0x0,  0x6a,  0x30c,  0x0,  0x6b,  0x0,  0x6b,  0x41,  0x0,  0x6b,  0x48,  0x7a,  0x0,  0x6b,  0x50,  0x61,  0x0,  0x6b,  0x56,  0x0,  0x6b,  0x57,  0x0,  0x6b,  0x63,  0x61,  0x6c,  0x0,  0x6b,  0x67,  0x0,  0x6b,  0x6c,  0x0,  0x6b,  0x6d,  0x0,  0x6b,  0x6d,  0x32,  0x0,  0x6b,  0x6d,  0x33,  0x0,  0x6b,  0x74,  0x0,  0x6b,  0x301,  0x0,  0x6b,  0x30c,  0x0,  0x6b,  0x323,  0x0,  0x6b,  0x327,  0x0,  0x6b,  0x331,  0x0,  0x6b,  0x3a9,  0x0,  0x6c,  0x0,  0x6c,  0x6a,  0x0,  0x6c,  0x6d,  0x0,  0x6c,  0x6e,  0x0,  0x6c,  0x6f,  0x67,  0x0,  0x6c,  0x78,  0x0,  0x6c,  0xb7,  0x0,  0x6c,  0x301,  0x0,  0x6c,  0x30c,  0x0,  0x6c,  0x323,  0x0,  0x6c,  0x323,  0x304,  0x0,  0x6c,  0x327,  0x0,  0x6c,  0x32d,  0x0,  0x6c,  0x331,  0x0,  0x6d,  0x0,  0x6d,  0x32,  0x0,  0x6d,  0x33,  0x0,  0x6d,  0x41,  0x0,  0x6d,  0x56,  0x0,  0x6d,  0x57,  0x0,  0x6d,  0x62,  0x0,  0x6d,  0x67,  0x0,  0x6d,  0x69,  0x6c,  0x0,  0x6d,  0x6c,  0x0,  0x6d,  0x6d,  0x0,  0x6d,  0x6d,  0x32,  0x0,  0x6d,  0x6d,  0x33,  0x0,  0x6d,  0x6f,  0x6c,  0x0,  0x6d,  0x73,  0x0,  0x6d,  0x301,  0x0,  0x6d,  0x307,  0x0,  0x6d,  0x323,  0x0,  0x6d,  0x2215,  0x73,  0x0,  0x6d,  0x2215,  0x73,  0x32,  0x0,  0x6e,  0x0,  0x6e,  0x41,  0x0,  0x6e,  0x46,  0x0,  0x6e,  0x56,  0x0,  0x6e,  0x57,  0x0,  0x6e,  0x6a,  0x0,  0x6e,  0x6d,  0x0,  0x6e,  0x73,  0x0,  0x6e,  0x300,  0x0,  0x6e,  0x301,  0x0,  0x6e,  0x303,  0x0,  0x6e,  0x307,  0x0,  0x6e,  0x30c,  0x0,  0x6e,  0x323,  0x0,  0x6e,  0x327,  0x0,  0x6e,  0x32d,  0x0,  0x6e,  0x331,  0x0,  0x6f,  0x0,  0x6f,  0x56,  0x0,  0x6f,  0x300,  0x0,  0x6f,  0x301,  0x0,  0x6f,  0x302,  0x0,  0x6f,  0x302,  0x300,  0x0,  0x6f,  0x302,  0x301,  0x0,  0x6f,  0x302,  0x303,  0x0,  0x6f,  0x302,  0x309,  0x0,  0x6f,  0x303,  0x0,  0x6f,  0x303,  0x301,  0x0,  0x6f,  0x303,  0x304,  0x0,  0x6f,  0x303,  0x308,  0x0,  0x6f,  0x304,  0x0,  0x6f,  0x304,  0x300,  0x0,  0x6f,  0x304,  0x301,  0x0,  0x6f,  0x306,  0x0,  0x6f,  0x307,  0x0,  0x6f,  0x307,  0x304,  0x0,  0x6f,  0x308,  0x0,  0x6f,  0x308,  0x304,  0x0,  0x6f,  0x309,  0x0,  0x6f,  0x30b,  0x0,  0x6f,  0x30c,  0x0,  0x6f,  0x30f,  0x0,  0x6f,  0x311,  0x0,  0x6f,  0x31b,  0x0,  0x6f,  0x31b,  0x300,  0x0,  0x6f,  0x31b,  0x301,  0x0,  0x6f,  0x31b,  0x303,  0x0,  0x6f,  0x31b,  0x309,  0x0,  0x6f,  0x31b,  0x323,  0x0,  0x6f,  0x323,  0x0,  0x6f,  0x323,  0x302,  0x0,  0x6f,  0x328,  0x0,  0x6f,  0x328,  0x304,  0x0,  0x70,  0x0,  0x70,  0x2e,  0x6d,  0x2e,  0x0,  0x70,  0x41,  0x0,  0x70,  0x46,  0x0,  0x70,  0x56,  0x0,  0x70,  0x57,  0x0,  0x70,  0x63,  0x0,  0x70,  0x73,  0x0,  0x70,  0x301,  0x0,  0x70,  0x307,  0x0,  0x71,  0x0,  0x72,  0x0,  0x72,  0x61,  0x64,  0x0,  0x72,  0x61,  0x64,  0x2215,  0x73,  0x0,  0x72,  0x61,  0x64,  0x2215,  0x73,  0x32,  0x0,  0x72,  0x301,  0x0,  0x72,  0x307,  0x0,  0x72,  0x30c,  0x0,  0x72,  0x30f,  0x0,  0x72,  0x311,  0x0,  0x72,  0x323,  0x0,  0x72,  0x323,  0x304,  0x0,  0x72,  0x327,  0x0,  0x72,  0x331,  0x0,  0x73,  0x0,  0x73,  0x72,  0x0,  0x73,  0x74,  0x0,  0x73,  0x301,  0x0,  0x73,  0x301,  0x307,  0x0,  0x73,  0x302,  0x0,  0x73,  0x307,  0x0,  0x73,  0x30c,  0x0,  0x73,  0x30c,  0x307,  0x0,  0x73,  0x323,  0x0,  0x73,  0x323,  0x307,  0x0,  0x73,  0x326,  0x0,  0x73,  0x327,  0x0,  0x74,  0x0,  0x74,  0x307,  0x0,  0x74,  0x308,  0x0,  0x74,  0x30c,  0x0,  0x74,  0x323,  0x0,  0x74,  0x326,  0x0,  0x74,  0x327,  0x0,  0x74,  0x32d,  0x0,  0x74,  0x331,  0x0,  0x75,  0x0,  0x75,  0x300,  0x0,  0x75,  0x301,  0x0,  0x75,  0x302,  0x0,  0x75,  0x303,  0x0,  0x75,  0x303,  0x301,  0x0,  0x75,  0x304,  0x0,  0x75,  0x304,  0x308,  0x0,  0x75,  0x306,  0x0,  0x75,  0x308,  0x0,  0x75,  0x308,  0x300,  0x0,  0x75,  0x308,  0x301,  0x0,  0x75,  0x308,  0x304,  0x0,  0x75,  0x308,  0x30c,  0x0,  0x75,  0x309,  0x0,  0x75,  0x30a,  0x0,  0x75,  0x30b,  0x0,  0x75,  0x30c,  0x0,  0x75,  0x30f,  0x0,  0x75,  0x311,  0x0,  0x75,  0x31b,  0x0,  0x75,  0x31b,  0x300,  0x0,  0x75,  0x31b,  0x301,  0x0,  0x75,  0x31b,  0x303,  0x0,  0x75,  0x31b,  0x309,  0x0,  0x75,  0x31b,  0x323,  0x0,  0x75,  0x323,  0x0,  0x75,  0x324,  0x0,  0x75,  0x328,  0x0,  0x75,  0x32d,  0x0,  0x75,  0x330,  0x0,  0x76,  0x0,  0x76,  0x69,  0x0,  0x76,  0x69,  0x69,  0x0,  0x76,  0x69,  0x69,  0x69,  0x0,  0x76,  0x303,  0x0,  0x76,  0x323,  0x0,  0x77,  0x0,  0x77,  0x300,  0x0,  0x77,  0x301,  0x0,  0x77,  0x302,  0x0,  0x77,  0x307,  0x0,  0x77,  0x308,  0x0,  0x77,  0x30a,  0x0,  0x77,  0x323,  0x0,  0x78,  0x0,  0x78,  0x69,  0x0,  0x78,  0x69,  0x69,  0x0,  0x78,  0x307,  0x0,  0x78,  0x308,  0x0,  0x79,  0x0,  0x79,  0x300,  0x0,  0x79,  0x301,  0x0,  0x79,  0x302,  0x0,  0x79,  0x303,  0x0,  0x79,  0x304,  0x0,  0x79,  0x307,  0x0,  0x79,  0x308,  0x0,  0x79,  0x309,  0x0,  0x79,  0x30a,  0x0,  0x79,  0x323,  0x0,  0x7a,  0x0,  0x7a,  0x301,  0x0,  0x7a,  0x302,  0x0,  0x7a,  0x307,  0x0,  0x7a,  0x30c,  0x0,  0x7a,  0x323,  0x0,  0x7a,  0x331,  0x0,  0x7b,  0x0,  0x7c,  0x0,  0x7d,  0x0,  0x7e,  0x0,  0xa2,  0x0,  0xa3,  0x0,  0xa5,  0x0,  0xa6,  0x0,  0xac,  0x0,  0xb0,  0x43,  0x0,  0xb0,  0x46,  0x0,  0xb7,  0x0,  0xc6,  0x0,  0xc6,  0x301,  0x0,  0xc6,  0x304,  0x0,  0xd8,  0x301,  0x0,  0xe6,  0x301,  0x0,  0xe6,  0x304,  0x0,  0xf0,  0x0,  0xf8,  0x301,  0x0,  0x126,  0x0,  0x127,  0x0,  0x131,  0x0,  0x14b,  0x0,  0x153,  0x0,  0x18e,  0x0,  0x190,  0x0,  0x1ab,  0x0,  0x1b7,  0x30c,  0x0,  0x222,  0x0,  0x237,  0x0,  0x250,  0x0,  0x251,  0x0,  0x252,  0x0,  0x254,  0x0,  0x255,  0x0,  0x259,  0x0,  0x25b,  0x0,  0x25c,  0x0,  0x25f,  0x0,  0x261,  0x0,  0x263,  0x0,  0x265,  0x0,  0x266,  0x0,  0x268,  0x0,  0x269,  0x0,  0x26a,  0x0,  0x26d,  0x0,  0x26f,  0x0,  0x270,  0x0,  0x271,  0x0,  0x272,  0x0,  0x273,  0x0,  0x274,  0x0,  0x275,  0x0,  0x278,  0x0,  0x279,  0x0,  0x27b,  0x0,  0x281,  0x0,  0x282,  0x0,  0x283,  0x0,  0x289,  0x0,  0x28a,  0x0,  0x28b,  0x0,  0x28c,  0x0,  0x290,  0x0,  0x291,  0x0,  0x292,  0x0,  0x292,  0x30c,  0x0,  0x295,  0x0,  0x29d,  0x0,  0x29f,  0x0,  0x2b9,  0x0,  0x2bc,  0x6e,  0x0,  0x300,  0x0,  0x301,  0x0,  0x308,  0x301,  0x0,  0x313,  0x0,  0x391,  0x0,  0x391,  0x300,  0x0,  0x391,  0x301,  0x0,  0x391,  0x304,  0x0,  0x391,  0x306,  0x0,  0x391,  0x313,  0x0,  0x391,  0x313,  0x300,  0x0,  0x391,  0x313,  0x300,  0x345,  0x0,  0x391,  0x313,  0x301,  0x0,  0x391,  0x313,  0x301,  0x345,  0x0,  0x391,  0x313,  0x342,  0x0,  0x391,  0x313,  0x342,  0x345,  0x0,  0x391,  0x313,  0x345,  0x0,  0x391,  0x314,  0x0,  0x391,  0x314,  0x300,  0x0,  0x391,  0x314,  0x300,  0x345,  0x0,  0x391,  0x314,  0x301,  0x0,  0x391,  0x314,  0x301,  0x345,  0x0,  0x391,  0x314,  0x342,  0x0,  0x391,  0x314,  0x342,  0x345,  0x0,  0x391,  0x314,  0x345,  0x0,  0x391,  0x345,  0x0,  0x392,  0x0,  0x393,  0x0,  0x394,  0x0,  0x395,  0x0,  0x395,  0x300,  0x0,  0x395,  0x301,  0x0,  0x395,  0x313,  0x0,  0x395,  0x313,  0x300,  0x0,  0x395,  0x313,  0x301,  0x0,  0x395,  0x314,  0x0,  0x395,  0x314,  0x300,  0x0,  0x395,  0x314,  0x301,  0x0,  0x396,  0x0,  0x397,  0x0,  0x397,  0x300,  0x0,  0x397,  0x301,  0x0,  0x397,  0x313,  0x0,  0x397,  0x313,  0x300,  0x0,  0x397,  0x313,  0x300,  0x345,  0x0,  0x397,  0x313,  0x301,  0x0,  0x397,  0x313,  0x301,  0x345,  0x0,  0x397,  0x313,  0x342,  0x0,  0x397,  0x313,  0x342,  0x345,  0x0,  0x397,  0x313,  0x345,  0x0,  0x397,  0x314,  0x0,  0x397,  0x314,  0x300,  0x0,  0x397,  0x314,  0x300,  0x345,  0x0,  0x397,  0x314,  0x301,  0x0,  0x397,  0x314,  0x301,  0x345,  0x0,  0x397,  0x314,  0x342,  0x0,  0x397,  0x314,  0x342,  0x345,  0x0,  0x397,  0x314,  0x345,  0x0,  0x397,  0x345,  0x0,  0x398,  0x0,  0x399,  0x0,  0x399,  0x300,  0x0,  0x399,  0x301,  0x0,  0x399,  0x304,  0x0,  0x399,  0x306,  0x0,  0x399,  0x308,  0x0,  0x399,  0x313,  0x0,  0x399,  0x313,  0x300,  0x0,  0x399,  0x313,  0x301,  0x0,  0x399,  0x313,  0x342,  0x0,  0x399,  0x314,  0x0,  0x399,  0x314,  0x300,  0x0,  0x399,  0x314,  0x301,  0x0,  0x399,  0x314,  0x342,  0x0,  0x39a,  0x0,  0x39b,  0x0,  0x39c,  0x0,  0x39d,  0x0,  0x39e,  0x0,  0x39f,  0x0,  0x39f,  0x300,  0x0,  0x39f,  0x301,  0x0,  0x39f,  0x313,  0x0,  0x39f,  0x313,  0x300,  0x0,  0x39f,  0x313,  0x301,  0x0,  0x39f,  0x314,  0x0,  0x39f,  0x314,  0x300,  0x0,  0x39f,  0x314,  0x301,  0x0,  0x3a0,  0x0,  0x3a1,  0x0,  0x3a1,  0x314,  0x0,  0x3a3,  0x0,  0x3a4,  0x0,  0x3a5,  0x0,  0x3a5,  0x300,  0x0,  0x3a5,  0x301,  0x0,  0x3a5,  0x304,  0x0,  0x3a5,  0x306,  0x0,  0x3a5,  0x308,  0x0,  0x3a5,  0x314,  0x0,  0x3a5,  0x314,  0x300,  0x0,  0x3a5,  0x314,  0x301,  0x0,  0x3a5,  0x314,  0x342,  0x0,  0x3a6,  0x0,  0x3a7,  0x0,  0x3a8,  0x0,  0x3a9,  0x0,  0x3a9,  0x300,  0x0,  0x3a9,  0x301,  0x0,  0x3a9,  0x313,  0x0,  0x3a9,  0x313,  0x300,  0x0,  0x3a9,  0x313,  0x300,  0x345,  0x0,  0x3a9,  0x313,  0x301,  0x0,  0x3a9,  0x313,  0x301,  0x345,  0x0,  0x3a9,  0x313,  0x342,  0x0,  0x3a9,  0x313,  0x342,  0x345,  0x0,  0x3a9,  0x313,  0x345,  0x0,  0x3a9,  0x314,  0x0,  0x3a9,  0x314,  0x300,  0x0,  0x3a9,  0x314,  0x300,  0x345,  0x0,  0x3a9,  0x314,  0x301,  0x0,  0x3a9,  0x314,  0x301,  0x345,  0x0,  0x3a9,  0x314,  0x342,  0x0,  0x3a9,  0x314,  0x342,  0x345,  0x0,  0x3a9,  0x314,  0x345,  0x0,  0x3a9,  0x345,  0x0,  0x3b1,  0x0,  0x3b1,  0x300,  0x0,  0x3b1,  0x300,  0x345,  0x0,  0x3b1,  0x301,  0x0,  0x3b1,  0x301,  0x345,  0x0,  0x3b1,  0x304,  0x0,  0x3b1,  0x306,  0x0,  0x3b1,  0x313,  0x0,  0x3b1,  0x313,  0x300,  0x0,  0x3b1,  0x313,  0x300,  0x345,  0x0,  0x3b1,  0x313,  0x301,  0x0,  0x3b1,  0x313,  0x301,  0x345,  0x0,  0x3b1,  0x313,  0x342,  0x0,  0x3b1,  0x313,  0x342,  0x345,  0x0,  0x3b1,  0x313,  0x345,  0x0,  0x3b1,  0x314,  0x0,  0x3b1,  0x314,  0x300,  0x0,  0x3b1,  0x314,  0x300,  0x345,  0x0,  0x3b1,  0x314,  0x301,  0x0,  0x3b1,  0x314,  0x301,  0x345,  0x0,  0x3b1,  0x314,  0x342,  0x0,  0x3b1,  0x314,  0x342,  0x345,  0x0,  0x3b1,  0x314,  0x345,  0x0,  0x3b1,  0x342,  0x0,  0x3b1,  0x342,  0x345,  0x0,  0x3b1,  0x345,  0x0,  0x3b2,  0x0,  0x3b3,  0x0,  0x3b4,  0x0,  0x3b5,  0x0,  0x3b5,  0x300,  0x0,  0x3b5,  0x301,  0x0,  0x3b5,  0x313,  0x0,  0x3b5,  0x313,  0x300,  0x0,  0x3b5,  0x313,  0x301,  0x0,  0x3b5,  0x314,  0x0,  0x3b5,  0x314,  0x300,  0x0,  0x3b5,  0x314,  0x301,  0x0,  0x3b6,  0x0,  0x3b7,  0x0,  0x3b7,  0x300,  0x0,  0x3b7,  0x300,  0x345,  0x0,  0x3b7,  0x301,  0x0,  0x3b7,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x0,  0x3b7,  0x313,  0x300,  0x0,  0x3b7,  0x313,  0x300,  0x345,  0x0,  0x3b7,  0x313,  0x301,  0x0,  0x3b7,  0x313,  0x301,  0x345,  0x0,  0x3b7,  0x313,  0x342,  0x0,  0x3b7,  0x313,  0x342,  0x345,  0x0,  0x3b7,  0x313,  0x345,  0x0,  0x3b7,  0x314,  0x0,  0x3b7,  0x314,  0x300,  0x0,  0x3b7,  0x314,  0x300,  0x345,  0x0,  0x3b7,  0x314,  0x301,  0x0,  0x3b7,  0x314,  0x301,  0x345,  0x0,  0x3b7,  0x314,  0x342,  0x0,  0x3b7,  0x314,  0x342,  0x345,  0x0,  0x3b7,  0x314,  0x345,  0x0,  0x3b7,  0x342,  0x0,  0x3b7,  0x342,  0x345,  0x0,  0x3b7,  0x345,  0x0,  0x3b8,  0x0,  0x3b9,  0x0,  0x3b9,  0x300,  0x0,  0x3b9,  0x301,  0x0,  0x3b9,  0x304,  0x0,  0x3b9,  0x306,  0x0,  0x3b9,  0x308,  0x0,  0x3b9,  0x308,  0x300,  0x0,  0x3b9,  0x308,  0x301,  0x0,  0x3b9,  0x308,  0x342,  0x0,  0x3b9,  0x313,  0x0,  0x3b9,  0x313,  0x300,  0x0,  0x3b9,  0x313,  0x301,  0x0,  0x3b9,  0x313,  0x342,  0x0,  0x3b9,  0x314,  0x0,  0x3b9,  0x314,  0x300,  0x0,  0x3b9,  0x314,  0x301,  0x0,  0x3b9,  0x314,  0x342,  0x0,  0x3b9,  0x342,  0x0,  0x3ba,  0x0,  0x3bb,  0x0,  0x3bc,  0x0,  0x3bc,  0x41,  0x0,  0x3bc,  0x46,  0x0,  0x3bc,  0x56,  0x0,  0x3bc,  0x57,  0x0,  0x3bc,  0x67,  0x0,  0x3bc,  0x6c,  0x0,  0x3bc,  0x6d,  0x0,  0x3bc,  0x73,  0x0,  0x3bd,  0x0,  0x3be,  0x0,  0x3bf,  0x0,  0x3bf,  0x300,  0x0,  0x3bf,  0x301,  0x0,  0x3bf,  0x313,  0x0,  0x3bf,  0x313,  0x300,  0x0,  0x3bf,  0x313,  0x301,  0x0,  0x3bf,  0x314,  0x0,  0x3bf,  0x314,  0x300,  0x0,  0x3bf,  0x314,  0x301,  0x0,  0x3c0,  0x0,  0x3c1,  0x0,  0x3c1,  0x313,  0x0,  0x3c1,  0x314,  0x0,  0x3c2,  0x0,  0x3c3,  0x0,  0x3c4,  0x0,  0x3c5,  0x0,  0x3c5,  0x300,  0x0,  0x3c5,  0x301,  0x0,  0x3c5,  0x304,  0x0,  0x3c5,  0x306,  0x0,  0x3c5,  0x308,  0x0,  0x3c5,  0x308,  0x300,  0x0,  0x3c5,  0x308,  0x301,  0x0,  0x3c5,  0x308,  0x342,  0x0,  0x3c5,  0x313,  0x0,  0x3c5,  0x313,  0x300,  0x0,  0x3c5,  0x313,  0x301,  0x0,  0x3c5,  0x313,  0x342,  0x0,  0x3c5,  0x314,  0x0,  0x3c5,  0x314,  0x300,  0x0,  0x3c5,  0x314,  0x301,  0x0,  0x3c5,  0x314,  0x342,  0x0,  0x3c5,  0x342,  0x0,  0x3c6,  0x0,  0x3c7,  0x0,  0x3c8,  0x0,  0x3c9,  0x0,  0x3c9,  0x300,  0x0,  0x3c9,  0x300,  0x345,  0x0,  0x3c9,  0x301,  0x0,  0x3c9,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x0,  0x3c9,  0x313,  0x300,  0x0,  0x3c9,  0x313,  0x300,  0x345,  0x0,  0x3c9,  0x313,  0x301,  0x0,  0x3c9,  0x313,  0x301,  0x345,  0x0,  0x3c9,  0x313,  0x342,  0x0,  0x3c9,  0x313,  0x342,  0x345,  0x0,  0x3c9,  0x313,  0x345,  0x0,  0x3c9,  0x314,  0x0,  0x3c9,  0x314,  0x300,  0x0,  0x3c9,  0x314,  0x300,  0x345,  0x0,  0x3c9,  0x314,  0x301,  0x0,  0x3c9,  0x314,  0x301,  0x345,  0x0,  0x3c9,  0x314,  0x342,  0x0,  0x3c9,  0x314,  0x342,  0x345,  0x0,  0x3c9,  0x314,  0x345,  0x0,  0x3c9,  0x342,  0x0,  0x3c9,  0x342,  0x345,  0x0,  0x3c9,  0x345,  0x0,  0x3dc,  0x0,  0x3dd,  0x0,  0x406,  0x308,  0x0,  0x410,  0x306,  0x0,  0x410,  0x308,  0x0,  0x413,  0x301,  0x0,  0x415,  0x300,  0x0,  0x415,  0x306,  0x0,  0x415,  0x308,  0x0,  0x416,  0x306,  0x0,  0x416,  0x308,  0x0,  0x417,  0x308,  0x0,  0x418,  0x300,  0x0,  0x418,  0x304,  0x0,  0x418,  0x306,  0x0,  0x418,  0x308,  0x0,  0x41a,  0x301,  0x0,  0x41e,  0x308,  0x0,  0x423,  0x304,  0x0,  0x423,  0x306,  0x0,  0x423,  0x308,  0x0,  0x423,  0x30b,  0x0,  0x427,  0x308,  0x0,  0x42b,  0x308,  0x0,  0x42d,  0x308,  0x0,  0x430,  0x306,  0x0,  0x430,  0x308,  0x0,  0x433,  0x301,  0x0,  0x435,  0x300,  0x0,  0x435,  0x306,  0x0,  0x435,  0x308,  0x0,  0x436,  0x306,  0x0,  0x436,  0x308,  0x0,  0x437,  0x308,  0x0,  0x438,  0x300,  0x0,  0x438,  0x304,  0x0,  0x438,  0x306,  0x0,  0x438,  0x308,  0x0,  0x43a,  0x301,  0x0,  0x43d,  0x0,  0x43e,  0x308,  0x0,  0x443,  0x304,  0x0,  0x443,  0x306,  0x0,  0x443,  0x308,  0x0,  0x443,  0x30b,  0x0,  0x447,  0x308,  0x0,  0x44b,  0x308,  0x0,  0x44d,  0x308,  0x0,  0x456,  0x308,  0x0,  0x474,  0x30f,  0x0,  0x475,  0x30f,  0x0,  0x4d8,  0x308,  0x0,  0x4d9,  0x308,  0x0,  0x4e8,  0x308,  0x0,  0x4e9,  0x308,  0x0,  0x565,  0x582,  0x0,  0x574,  0x565,  0x0,  0x574,  0x56b,  0x0,  0x574,  0x56d,  0x0,  0x574,  0x576,  0x0,  0x57e,  0x576,  0x0,  0x5d0,  0x0,  0x5d0,  0x5b7,  0x0,  0x5d0,  0x5b8,  0x0,  0x5d0,  0x5bc,  0x0,  0x5d0,  0x5dc,  0x0,  0x5d1,  0x0,  0x5d1,  0x5bc,  0x0,  0x5d1,  0x5bf,  0x0,  0x5d2,  0x0,  0x5d2,  0x5bc,  0x0,  0x5d3,  0x0,  0x5d3,  0x5bc,  0x0,  0x5d4,  0x0,  0x5d4,  0x5bc,  0x0,  0x5d5,  0x5b9,  0x0,  0x5d5,  0x5bc,  0x0,  0x5d6,  0x5bc,  0x0,  0x5d8,  0x5bc,  0x0,  0x5d9,  0x5b4,  0x0,  0x5d9,  0x5bc,  0x0,  0x5da,  0x5bc,  0x0,  0x5db,  0x0,  0x5db,  0x5bc,  0x0,  0x5db,  0x5bf,  0x0,  0x5dc,  0x0,  0x5dc,  0x5bc,  0x0,  0x5dd,  0x0,  0x5de,  0x5bc,  0x0,  0x5e0,  0x5bc,  0x0,  0x5e1,  0x5bc,  0x0,  0x5e2,  0x0,  0x5e3,  0x5bc,  0x0,  0x5e4,  0x5bc,  0x0,  0x5e4,  0x5bf,  0x0,  0x5e6,  0x5bc,  0x0,  0x5e7,  0x5bc,  0x0,  0x5e8,  0x0,  0x5e8,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x0,  0x5e9,  0x5bc,  0x5c1,  0x0,  0x5e9,  0x5bc,  0x5c2,  0x0,  0x5e9,  0x5c1,  0x0,  0x5e9,  0x5c2,  0x0,  0x5ea,  0x0,  0x5ea,  0x5bc,  0x0,  0x5f2,  0x5b7,  0x0,  0x621,  0x0,  0x627,  0x0,  0x627,  0x643,  0x628,  0x631,  0x0,  0x627,  0x644,  0x644,  0x647,  0x0,  0x627,  0x64b,  0x0,  0x627,  0x653,  0x0,  0x627,  0x654,  0x0,  0x627,  0x655,  0x0,  0x627,  0x674,  0x0,  0x628,  0x0,  0x628,  0x62c,  0x0,  0x628,  0x62d,  0x0,  0x628,  0x62d,  0x64a,  0x0,  0x628,  0x62e,  0x0,  0x628,  0x62e,  0x64a,  0x0,  0x628,  0x631,  0x0,  0x628,  0x632,  0x0,  0x628,  0x645,  0x0,  0x628,  0x646,  0x0,  0x628,  0x647,  0x0,  0x628,  0x649,  0x0,  0x628,  0x64a,  0x0,  0x629,  0x0,  0x62a,  0x0,  0x62a,  0x62c,  0x0,  0x62a,  0x62c,  0x645,  0x0,  0x62a,  0x62c,  0x649,  0x0,  0x62a,  0x62c,  0x64a,  0x0,  0x62a,  0x62d,  0x0,  0x62a,  0x62d,  0x62c,  0x0,  0x62a,  0x62d,  0x645,  0x0,  0x62a,  0x62e,  0x0,  0x62a,  0x62e,  0x645,  0x0,  0x62a,  0x62e,  0x649,  0x0,  0x62a,  0x62e,  0x64a,  0x0,  0x62a,  0x631,  0x0,  0x62a,  0x632,  0x0,  0x62a,  0x645,  0x0,  0x62a,  0x645,  0x62c,  0x0,  0x62a,  0x645,  0x62d,  0x0,  0x62a,  0x645,  0x62e,  0x0,  0x62a,  0x645,  0x649,  0x0,  0x62a,  0x645,  0x64a,  0x0,  0x62a,  0x646,  0x0,  0x62a,  0x647,  0x0,  0x62a,  0x649,  0x0,  0x62a,  0x64a,  0x0,  0x62b,  0x0,  0x62b,  0x62c,  0x0,  0x62b,  0x631,  0x0,  0x62b,  0x632,  0x0,  0x62b,  0x645,  0x0,  0x62b,  0x646,  0x0,  0x62b,  0x647,  0x0,  0x62b,  0x649,  0x0,  0x62b,  0x64a,  0x0,  0x62c,  0x0,  0x62c,  0x62d,  0x0,  0x62c,  0x62d,  0x649,  0x0,  0x62c,  0x62d,  0x64a,  0x0,  0x62c,  0x644,  0x20,  0x62c,  0x644,  0x627,  0x644,  0x647,  0x0,  0x62c,  0x645,  0x0,  0x62c,  0x645,  0x62d,  0x0,  0x62c,  0x645,  0x649,  0x0,  0x62c,  0x645,  0x64a,  0x0,  0x62c,  0x649,  0x0,  0x62c,  0x64a,  0x0,  0x62d,  0x0,  0x62d,  0x62c,  0x0,  0x62d,  0x62c,  0x64a,  0x0,  0x62d,  0x645,  0x0,  0x62d,  0x645,  0x649,  0x0,  0x62d,  0x645,  0x64a,  0x0,  0x62d,  0x649,  0x0,  0x62d,  0x64a,  0x0,  0x62e,  0x0,  0x62e,  0x62c,  0x0,  0x62e,  0x62d,  0x0,  0x62e,  0x645,  0x0,  0x62e,  0x649,  0x0,  0x62e,  0x64a,  0x0,  0x62f,  0x0,  0x630,  0x0,  0x630,  0x670,  0x0,  0x631,  0x0,  0x631,  0x633,  0x648,  0x644,  0x0,  0x631,  0x670,  0x0,  0x631,  0x6cc,  0x627,  0x644,  0x0,  0x632,  0x0,  0x633,  0x0,  0x633,  0x62c,  0x0,  0x633,  0x62c,  0x62d,  0x0,  0x633,  0x62c,  0x649,  0x0,  0x633,  0x62d,  0x0,  0x633,  0x62d,  0x62c,  0x0,  0x633,  0x62e,  0x0,  0x633,  0x62e,  0x649,  0x0,  0x633,  0x62e,  0x64a,  0x0,  0x633,  0x631,  0x0,  0x633,  0x645,  0x0,  0x633,  0x645,  0x62c,  0x0,  0x633,  0x645,  0x62d,  0x0,  0x633,  0x645,  0x645,  0x0,  0x633,  0x647,  0x0,  0x633,  0x649,  0x0,  0x633,  0x64a,  0x0,  0x634,  0x0,  0x634,  0x62c,  0x0,  0x634,  0x62c,  0x64a,  0x0,  0x634,  0x62d,  0x0,  0x634,  0x62d,  0x645,  0x0,  0x634,  0x62d,  0x64a,  0x0,  0x634,  0x62e,  0x0,  0x634,  0x631,  0x0,  0x634,  0x645,  0x0,  0x634,  0x645,  0x62e,  0x0,  0x634,  0x645,  0x645,  0x0,  0x634,  0x647,  0x0,  0x634,  0x649,  0x0,  0x634,  0x64a,  0x0,  0x635,  0x0,  0x635,  0x62d,  0x0,  0x635,  0x62d,  0x62d,  0x0,  0x635,  0x62d,  0x64a,  0x0,  0x635,  0x62e,  0x0,  0x635,  0x631,  0x0,  0x635,  0x644,  0x639,  0x645,  0x0,  0x635,  0x644,  0x649,  0x0,  0x635,  0x644,  0x649,  0x20,  0x627,  0x644,  0x644,  0x647,  0x20,  0x639,  0x644,  0x64a,  0x647,  0x20,  0x648,  0x633,  0x644,  0x645,  0x0,  0x635,  0x644,  0x6d2,  0x0,  0x635,  0x645,  0x0,  0x635,  0x645,  0x645,  0x0,  0x635,  0x649,  0x0,  0x635,  0x64a,  0x0,  0x636,  0x0,  0x636,  0x62c,  0x0,  0x636,  0x62d,  0x0,  0x636,  0x62d,  0x649,  0x0,  0x636,  0x62d,  0x64a,  0x0,  0x636,  0x62e,  0x0,  0x636,  0x62e,  0x645,  0x0,  0x636,  0x631,  0x0,  0x636,  0x645,  0x0,  0x636,  0x649,  0x0,  0x636,  0x64a,  0x0,  0x637,  0x0,  0x637,  0x62d,  0x0,  0x637,  0x645,  0x0,  0x637,  0x645,  0x62d,  0x0,  0x637,  0x645,  0x645,  0x0,  0x637,  0x645,  0x64a,  0x0,  0x637,  0x649,  0x0,  0x637,  0x64a,  0x0,  0x638,  0x0,  0x638,  0x645,  0x0,  0x639,  0x0,  0x639,  0x62c,  0x0,  0x639,  0x62c,  0x645,  0x0,  0x639,  0x644,  0x64a,  0x647,  0x0,  0x639,  0x645,  0x0,  0x639,  0x645,  0x645,  0x0,  0x639,  0x645,  0x649,  0x0,  0x639,  0x645,  0x64a,  0x0,  0x639,  0x649,  0x0,  0x639,  0x64a,  0x0,  0x63a,  0x0,  0x63a,  0x62c,  0x0,  0x63a,  0x645,  0x0,  0x63a,  0x645,  0x645,  0x0,  0x63a,  0x645,  0x649,  0x0,  0x63a,  0x645,  0x64a,  0x0,  0x63a,  0x649,  0x0,  0x63a,  0x64a,  0x0,  0x640,  0x64b,  0x0,  0x640,  0x64e,  0x0,  0x640,  0x64e,  0x651,  0x0,  0x640,  0x64f,  0x0,  0x640,  0x64f,  0x651,  0x0,  0x640,  0x650,  0x0,  0x640,  0x650,  0x651,  0x0,  0x640,  0x651,  0x0,  0x640,  0x652,  0x0,  0x641,  0x0,  0x641,  0x62c,  0x0,  0x641,  0x62d,  0x0,  0x641,  0x62e,  0x0,  0x641,  0x62e,  0x645,  0x0,  0x641,  0x645,  0x0,  0x641,  0x645,  0x64a,  0x0,  0x641,  0x649,  0x0,  0x641,  0x64a,  0x0,  0x642,  0x0,  0x642,  0x62d,  0x0,  0x642,  0x644,  0x6d2,  0x0,  0x642,  0x645,  0x0,  0x642,  0x645,  0x62d,  0x0,  0x642,  0x645,  0x645,  0x0,  0x642,  0x645,  0x64a,  0x0,  0x642,  0x649,  0x0,  0x642,  0x64a,  0x0,  0x643,  0x0,  0x643,  0x627,  0x0,  0x643,  0x62c,  0x0,  0x643,  0x62d,  0x0,  0x643,  0x62e,  0x0,  0x643,  0x644,  0x0,  0x643,  0x645,  0x0,  0x643,  0x645,  0x645,  0x0,  0x643,  0x645,  0x64a,  0x0,  0x643,  0x649,  0x0,  0x643,  0x64a,  0x0,  0x644,  0x0,  0x644,  0x627,  0x0,  0x644,  0x627,  0x653,  0x0,  0x644,  0x627,  0x654,  0x0,  0x644,  0x627,  0x655,  0x0,  0x644,  0x62c,  0x0,  0x644,  0x62c,  0x62c,  0x0,  0x644,  0x62c,  0x645,  0x0,  0x644,  0x62c,  0x64a,  0x0,  0x644,  0x62d,  0x0,  0x644,  0x62d,  0x645,  0x0,  0x644,  0x62d,  0x649,  0x0,  0x644,  0x62d,  0x64a,  0x0,  0x644,  0x62e,  0x0,  0x644,  0x62e,  0x645,  0x0,  0x644,  0x645,  0x0,  0x644,  0x645,  0x62d,  0x0,  0x644,  0x645,  0x64a,  0x0,  0x644,  0x647,  0x0,  0x644,  0x649,  0x0,  0x644,  0x64a,  0x0,  0x645,  0x0,  0x645,  0x627,  0x0,  0x645,  0x62c,  0x0,  0x645,  0x62c,  0x62d,  0x0,  0x645,  0x62c,  0x62e,  0x0,  0x645,  0x62c,  0x645,  0x0,  0x645,  0x62c,  0x64a,  0x0,  0x645,  0x62d,  0x0,  0x645,  0x62d,  0x62c,  0x0,  0x645,  0x62d,  0x645,  0x0,  0x645,  0x62d,  0x645,  0x62f,  0x0,  0x645,  0x62d,  0x64a,  0x0,  0x645,  0x62e,  0x0,  0x645,  0x62e,  0x62c,  0x0,  0x645,  0x62e,  0x645,  0x0,  0x645,  0x62e,  0x64a,  0x0,  0x645,  0x645,  0x0,  0x645,  0x645,  0x64a,  0x0,  0x645,  0x649,  0x0,  0x645,  0x64a,  0x0,  0x646,  0x0,  0x646,  0x62c,  0x0,  0x646,  0x62c,  0x62d,  0x0,  0x646,  0x62c,  0x645,  0x0,  0x646,  0x62c,  0x649,  0x0,  0x646,  0x62c,  0x64a,  0x0,  0x646,  0x62d,  0x0,  0x646,  0x62d,  0x645,  0x0,  0x646,  0x62d,  0x649,  0x0,  0x646,  0x62d,  0x64a,  0x0,  0x646,  0x62e,  0x0,  0x646,  0x631,  0x0,  0x646,  0x632,  0x0,  0x646,  0x645,  0x0,  0x646,  0x645,  0x649,  0x0,  0x646,  0x645,  0x64a,  0x0,  0x646,  0x646,  0x0,  0x646,  0x647,  0x0,  0x646,  0x649,  0x0,  0x646,  0x64a,  0x0,  0x647,  0x0,  0x647,  0x62c,  0x0,  0x647,  0x645,  0x0,  0x647,  0x645,  0x62c,  0x0,  0x647,  0x645,  0x645,  0x0,  0x647,  0x649,  0x0,  0x647,  0x64a,  0x0,  0x647,  0x670,  0x0,  0x648,  0x0,  0x648,  0x633,  0x644,  0x645,  0x0,  0x648,  0x654,  0x0,  0x648,  0x674,  0x0,  0x649,  0x0,  0x649,  0x670,  0x0,  0x64a,  0x0,  0x64a,  0x62c,  0x0,  0x64a,  0x62c,  0x64a,  0x0,  0x64a,  0x62d,  0x0,  0x64a,  0x62d,  0x64a,  0x0,  0x64a,  0x62e,  0x0,  0x64a,  0x631,  0x0,  0x64a,  0x632,  0x0,  0x64a,  0x645,  0x0,  0x64a,  0x645,  0x645,  0x0,  0x64a,  0x645,  0x64a,  0x0,  0x64a,  0x646,  0x0,  0x64a,  0x647,  0x0,  0x64a,  0x649,  0x0,  0x64a,  0x64a,  0x0,  0x64a,  0x654,  0x0,  0x64a,  0x654,  0x627,  0x0,  0x64a,  0x654,  0x62c,  0x0,  0x64a,  0x654,  0x62d,  0x0,  0x64a,  0x654,  0x62e,  0x0,  0x64a,  0x654,  0x631,  0x0,  0x64a,  0x654,  0x632,  0x0,  0x64a,  0x654,  0x645,  0x0,  0x64a,  0x654,  0x646,  0x0,  0x64a,  0x654,  0x647,  0x0,  0x64a,  0x654,  0x648,  0x0,  0x64a,  0x654,  0x649,  0x0,  0x64a,  0x654,  0x64a,  0x0,  0x64a,  0x654,  0x6c6,  0x0,  0x64a,  0x654,  0x6c7,  0x0,  0x64a,  0x654,  0x6c8,  0x0,  0x64a,  0x654,  0x6d0,  0x0,  0x64a,  0x654,  0x6d5,  0x0,  0x64a,  0x674,  0x0,  0x66e,  0x0,  0x66f,  0x0,  0x671,  0x0,  0x679,  0x0,  0x67a,  0x0,  0x67b,  0x0,  0x67e,  0x0,  0x67f,  0x0,  0x680,  0x0,  0x683,  0x0,  0x684,  0x0,  0x686,  0x0,  0x687,  0x0,  0x688,  0x0,  0x68c,  0x0,  0x68d,  0x0,  0x68e,  0x0,  0x691,  0x0,  0x698,  0x0,  0x6a1,  0x0,  0x6a4,  0x0,  0x6a6,  0x0,  0x6a9,  0x0,  0x6ad,  0x0,  0x6af,  0x0,  0x6b1,  0x0,  0x6b3,  0x0,  0x6ba,  0x0,  0x6bb,  0x0,  0x6be,  0x0,  0x6c1,  0x0,  0x6c1,  0x654,  0x0,  0x6c5,  0x0,  0x6c6,  0x0,  0x6c7,  0x0,  0x6c7,  0x674,  0x0,  0x6c8,  0x0,  0x6c9,  0x0,  0x6cb,  0x0,  0x6cc,  0x0,  0x6d0,  0x0,  0x6d2,  0x0,  0x6d2,  0x654,  0x0,  0x6d5,  0x654,  0x0,  0x915,  0x93c,  0x0,  0x916,  0x93c,  0x0,  0x917,  0x93c,  0x0,  0x91c,  0x93c,  0x0,  0x921,  0x93c,  0x0,  0x922,  0x93c,  0x0,  0x928,  0x93c,  0x0,  0x92b,  0x93c,  0x0,  0x92f,  0x93c,  0x0,  0x930,  0x93c,  0x0,  0x933,  0x93c,  0x0,  0x9a1,  0x9bc,  0x0,  0x9a2,  0x9bc,  0x0,  0x9af,  0x9bc,  0x0,  0x9c7,  0x9be,  0x0,  0x9c7,  0x9d7,  0x0,  0xa16,  0xa3c,  0x0,  0xa17,  0xa3c,  0x0,  0xa1c,  0xa3c,  0x0,  0xa2b,  0xa3c,  0x0,  0xa32,  0xa3c,  0x0,  0xa38,  0xa3c,  0x0,  0xb21,  0xb3c,  0x0,  0xb22,  0xb3c,  0x0,  0xb47,  0xb3e,  0x0,  0xb47,  0xb56,  0x0,  0xb47,  0xb57,  0x0,  0xb92,  0xbd7,  0x0,  0xbc6,  0xbbe,  0x0,  0xbc6,  0xbd7,  0x0,  0xbc7,  0xbbe,  0x0,  0xc46,  0xc56,  0x0,  0xcbf,  0xcd5,  0x0,  0xcc6,  0xcc2,  0x0,  0xcc6,  0xcc2,  0xcd5,  0x0,  0xcc6,  0xcd5,  0x0,  0xcc6,  0xcd6,  0x0,  0xd46,  0xd3e,  0x0,  0xd46,  0xd57,  0x0,  0xd47,  0xd3e,  0x0,  0xdd9,  0xdca,  0x0,  0xdd9,  0xdcf,  0x0,  0xdd9,  0xdcf,  0xdca,  0x0,  0xdd9,  0xddf,  0x0,  0xe4d,  0xe32,  0x0,  0xeab,  0xe99,  0x0,  0xeab,  0xea1,  0x0,  0xecd,  0xeb2,  0x0,  0xf0b,  0x0,  0xf40,  0xfb5,  0x0,  0xf42,  0xfb7,  0x0,  0xf4c,  0xfb7,  0x0,  0xf51,  0xfb7,  0x0,  0xf56,  0xfb7,  0x0,  0xf5b,  0xfb7,  0x0,  0xf71,  0xf72,  0x0,  0xf71,  0xf74,  0x0,  0xf71,  0xf80,  0x0,  0xf90,  0xfb5,  0x0,  0xf92,  0xfb7,  0x0,  0xf9c,  0xfb7,  0x0,  0xfa1,  0xfb7,  0x0,  0xfa6,  0xfb7,  0x0,  0xfab,  0xfb7,  0x0,  0xfb2,  0xf71,  0xf80,  0x0,  0xfb2,  0xf80,  0x0,  0xfb3,  0xf71,  0xf80,  0x0,  0xfb3,  0xf80,  0x0,  0x1025,  0x102e,  0x0,  0x10dc,  0x0,  0x1100,  0x0,  0x1100,  0x1161,  0x0,  0x1101,  0x0,  0x1102,  0x0,  0x1102,  0x1161,  0x0,  0x1103,  0x0,  0x1103,  0x1161,  0x0,  0x1104,  0x0,  0x1105,  0x0,  0x1105,  0x1161,  0x0,  0x1106,  0x0,  0x1106,  0x1161,  0x0,  0x1107,  0x0,  0x1107,  0x1161,  0x0,  0x1108,  0x0,  0x1109,  0x0,  0x1109,  0x1161,  0x0,  0x110a,  0x0,  0x110b,  0x0,  0x110b,  0x1161,  0x0,  0x110b,  0x116e,  0x0,  0x110c,  0x0,  0x110c,  0x1161,  0x0,  0x110c,  0x116e,  0x110b,  0x1174,  0x0,  0x110d,  0x0,  0x110e,  0x0,  0x110e,  0x1161,  0x0,  0x110e,  0x1161,  0x11b7,  0x1100,  0x1169,  0x0,  0x110f,  0x0,  0x110f,  0x1161,  0x0,  0x1110,  0x0,  0x1110,  0x1161,  0x0,  0x1111,  0x0,  0x1111,  0x1161,  0x0,  0x1112,  0x0,  0x1112,  0x1161,  0x0,  0x1114,  0x0,  0x1115,  0x0,  0x111a,  0x0,  0x111c,  0x0,  0x111d,  0x0,  0x111e,  0x0,  0x1120,  0x0,  0x1121,  0x0,  0x1122,  0x0,  0x1123,  0x0,  0x1127,  0x0,  0x1129,  0x0,  0x112b,  0x0,  0x112c,  0x0,  0x112d,  0x0,  0x112e,  0x0,  0x112f,  0x0,  0x1132,  0x0,  0x1136,  0x0,  0x1140,  0x0,  0x1147,  0x0,  0x114c,  0x0,  0x1157,  0x0,  0x1158,  0x0,  0x1159,  0x0,  0x1160,  0x0,  0x1161,  0x0,  0x1162,  0x0,  0x1163,  0x0,  0x1164,  0x0,  0x1165,  0x0,  0x1166,  0x0,  0x1167,  0x0,  0x1168,  0x0,  0x1169,  0x0,  0x116a,  0x0,  0x116b,  0x0,  0x116c,  0x0,  0x116d,  0x0,  0x116e,  0x0,  0x116f,  0x0,  0x1170,  0x0,  0x1171,  0x0,  0x1172,  0x0,  0x1173,  0x0,  0x1174,  0x0,  0x1175,  0x0,  0x1184,  0x0,  0x1185,  0x0,  0x1188,  0x0,  0x1191,  0x0,  0x1192,  0x0,  0x1194,  0x0,  0x119e,  0x0,  0x11a1,  0x0,  0x11aa,  0x0,  0x11ac,  0x0,  0x11ad,  0x0,  0x11b0,  0x0,  0x11b1,  0x0,  0x11b2,  0x0,  0x11b3,  0x0,  0x11b4,  0x0,  0x11b5,  0x0,  0x11c7,  0x0,  0x11c8,  0x0,  0x11cc,  0x0,  0x11ce,  0x0,  0x11d3,  0x0,  0x11d7,  0x0,  0x11d9,  0x0,  0x11dd,  0x0,  0x11df,  0x0,  0x11f1,  0x0,  0x11f2,  0x0,  0x1b05,  0x1b35,  0x0,  0x1b07,  0x1b35,  0x0,  0x1b09,  0x1b35,  0x0,  0x1b0b,  0x1b35,  0x0,  0x1b0d,  0x1b35,  0x0,  0x1b11,  0x1b35,  0x0,  0x1b3a,  0x1b35,  0x0,  0x1b3c,  0x1b35,  0x0,  0x1b3e,  0x1b35,  0x0,  0x1b3f,  0x1b35,  0x0,  0x1b42,  0x1b35,  0x0,  0x1d02,  0x0,  0x1d16,  0x0,  0x1d17,  0x0,  0x1d1c,  0x0,  0x1d1d,  0x0,  0x1d25,  0x0,  0x1d7b,  0x0,  0x1d85,  0x0,  0x2010,  0x0,  0x2013,  0x0,  0x2014,  0x0,  0x2032,  0x2032,  0x0,  0x2032,  0x2032,  0x2032,  0x0,  0x2032,  0x2032,  0x2032,  0x2032,  0x0,  0x2035,  0x2035,  0x0,  0x2035,  0x2035,  0x2035,  0x0,  0x20a9,  0x0,  0x2190,  0x0,  0x2190,  0x338,  0x0,  0x2191,  0x0,  0x2192,  0x0,  0x2192,  0x338,  0x0,  0x2193,  0x0,  0x2194,  0x338,  0x0,  0x21d0,  0x338,  0x0,  0x21d2,  0x338,  0x0,  0x21d4,  0x338,  0x0,  0x2202,  0x0,  0x2203,  0x338,  0x0,  0x2207,  0x0,  0x2208,  0x338,  0x0,  0x220b,  0x338,  0x0,  0x2211,  0x0,  0x2212,  0x0,  0x2223,  0x338,  0x0,  0x2225,  0x338,  0x0,  0x222b,  0x222b,  0x0,  0x222b,  0x222b,  0x222b,  0x0,  0x222b,  0x222b,  0x222b,  0x222b,  0x0,  0x222e,  0x222e,  0x0,  0x222e,  0x222e,  0x222e,  0x0,  0x223c,  0x338,  0x0,  0x2243,  0x338,  0x0,  0x2245,  0x338,  0x0,  0x2248,  0x338,  0x0,  0x224d,  0x338,  0x0,  0x2261,  0x338,  0x0,  0x2264,  0x338,  0x0,  0x2265,  0x338,  0x0,  0x2272,  0x338,  0x0,  0x2273,  0x338,  0x0,  0x2276,  0x338,  0x0,  0x2277,  0x338,  0x0,  0x227a,  0x338,  0x0,  0x227b,  0x338,  0x0,  0x227c,  0x338,  0x0,  0x227d,  0x338,  0x0,  0x2282,  0x338,  0x0,  0x2283,  0x338,  0x0,  0x2286,  0x338,  0x0,  0x2287,  0x338,  0x0,  0x2291,  0x338,  0x0,  0x2292,  0x338,  0x0,  0x22a2,  0x338,  0x0,  0x22a8,  0x338,  0x0,  0x22a9,  0x338,  0x0,  0x22ab,  0x338,  0x0,  0x22b2,  0x338,  0x0,  0x22b3,  0x338,  0x0,  0x22b4,  0x338,  0x0,  0x22b5,  0x338,  0x0,  0x2502,  0x0,  0x25a0,  0x0,  0x25cb,  0x0,  0x2985,  0x0,  0x2986,  0x0,  0x2add,  0x338,  0x0,  0x2d61,  0x0,  0x3001,  0x0,  0x3002,  0x0,  0x3008,  0x0,  0x3009,  0x0,  0x300a,  0x0,  0x300b,  0x0,  0x300c,  0x0,  0x300d,  0x0,  0x300e,  0x0,  0x300f,  0x0,  0x3010,  0x0,  0x3011,  0x0,  0x3012,  0x0,  0x3014,  0x0,  0x3014,  0x53,  0x3015,  0x0,  0x3014,  0x4e09,  0x3015,  0x0,  0x3014,  0x4e8c,  0x3015,  0x0,  0x3014,  0x52dd,  0x3015,  0x0,  0x3014,  0x5b89,  0x3015,  0x0,  0x3014,  0x6253,  0x3015,  0x0,  0x3014,  0x6557,  0x3015,  0x0,  0x3014,  0x672c,  0x3015,  0x0,  0x3014,  0x70b9,  0x3015,  0x0,  0x3014,  0x76d7,  0x3015,  0x0,  0x3015,  0x0,  0x3016,  0x0,  0x3017,  0x0,  0x3046,  0x3099,  0x0,  0x304b,  0x3099,  0x0,  0x304d,  0x3099,  0x0,  0x304f,  0x3099,  0x0,  0x3051,  0x3099,  0x0,  0x3053,  0x3099,  0x0,  0x3055,  0x3099,  0x0,  0x3057,  0x3099,  0x0,  0x3059,  0x3099,  0x0,  0x305b,  0x3099,  0x0,  0x305d,  0x3099,  0x0,  0x305f,  0x3099,  0x0,  0x3061,  0x3099,  0x0,  0x3064,  0x3099,  0x0,  0x3066,  0x3099,  0x0,  0x3068,  0x3099,  0x0,  0x306f,  0x3099,  0x0,  0x306f,  0x309a,  0x0,  0x3072,  0x3099,  0x0,  0x3072,  0x309a,  0x0,  0x3075,  0x3099,  0x0,  0x3075,  0x309a,  0x0,  0x3078,  0x3099,  0x0,  0x3078,  0x309a,  0x0,  0x307b,  0x304b,  0x0,  0x307b,  0x3099,  0x0,  0x307b,  0x309a,  0x0,  0x3088,  0x308a,  0x0,  0x3099,  0x0,  0x309a,  0x0,  0x309d,  0x3099,  0x0,  0x30a1,  0x0,  0x30a2,  0x0,  0x30a2,  0x30cf,  0x309a,  0x30fc,  0x30c8,  0x0,  0x30a2,  0x30eb,  0x30d5,  0x30a1,  0x0,  0x30a2,  0x30f3,  0x30d8,  0x309a,  0x30a2,  0x0,  0x30a2,  0x30fc,  0x30eb,  0x0,  0x30a3,  0x0,  0x30a4,  0x0,  0x30a4,  0x30cb,  0x30f3,  0x30af,  0x3099,  0x0,  0x30a4,  0x30f3,  0x30c1,  0x0,  0x30a5,  0x0,  0x30a6,  0x0,  0x30a6,  0x3099,  0x0,  0x30a6,  0x30a9,  0x30f3,  0x0,  0x30a7,  0x0,  0x30a8,  0x0,  0x30a8,  0x30b9,  0x30af,  0x30fc,  0x30c8,  0x3099,  0x0,  0x30a8,  0x30fc,  0x30ab,  0x30fc,  0x0,  0x30a9,  0x0,  0x30aa,  0x0,  0x30aa,  0x30f3,  0x30b9,  0x0,  0x30aa,  0x30fc,  0x30e0,  0x0,  0x30ab,  0x0,  0x30ab,  0x3099,  0x0,  0x30ab,  0x3099,  0x30ed,  0x30f3,  0x0,  0x30ab,  0x3099,  0x30f3,  0x30de,  0x0,  0x30ab,  0x30a4,  0x30ea,  0x0,  0x30ab,  0x30e9,  0x30c3,  0x30c8,  0x0,  0x30ab,  0x30ed,  0x30ea,  0x30fc,  0x0,  0x30ad,  0x0,  0x30ad,  0x3099,  0x0,  0x30ad,  0x3099,  0x30ab,  0x3099,  0x0,  0x30ad,  0x3099,  0x30cb,  0x30fc,  0x0,  0x30ad,  0x3099,  0x30eb,  0x30bf,  0x3099,  0x30fc,  0x0,  0x30ad,  0x30e5,  0x30ea,  0x30fc,  0x0,  0x30ad,  0x30ed,  0x0,  0x30ad,  0x30ed,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x0,  0x30ad,  0x30ed,  0x30e1,  0x30fc,  0x30c8,  0x30eb,  0x0,  0x30ad,  0x30ed,  0x30ef,  0x30c3,  0x30c8,  0x0,  0x30af,  0x0,  0x30af,  0x3099,  0x0,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x0,  0x30af,  0x3099,  0x30e9,  0x30e0,  0x30c8,  0x30f3,  0x0,  0x30af,  0x30eb,  0x30bb,  0x3099,  0x30a4,  0x30ed,  0x0,  0x30af,  0x30ed,  0x30fc,  0x30cd,  0x0,  0x30b1,  0x0,  0x30b1,  0x3099,  0x0,  0x30b1,  0x30fc,  0x30b9,  0x0,  0x30b3,  0x0,  0x30b3,  0x3099,  0x0,  0x30b3,  0x30b3,  0x0,  0x30b3,  0x30c8,  0x0,  0x30b3,  0x30eb,  0x30ca,  0x0,  0x30b3,  0x30fc,  0x30db,  0x309a,  0x0,  0x30b5,  0x0,  0x30b5,  0x3099,  0x0,  0x30b5,  0x30a4,  0x30af,  0x30eb,  0x0,  0x30b5,  0x30f3,  0x30c1,  0x30fc,  0x30e0,  0x0,  0x30b7,  0x0,  0x30b7,  0x3099,  0x0,  0x30b7,  0x30ea,  0x30f3,  0x30af,  0x3099,  0x0,  0x30b9,  0x0,  0x30b9,  0x3099,  0x0,  0x30bb,  0x0,  0x30bb,  0x3099,  0x0,  0x30bb,  0x30f3,  0x30c1,  0x0,  0x30bb,  0x30f3,  0x30c8,  0x0,  0x30bd,  0x0,  0x30bd,  0x3099,  0x0,  0x30bf,  0x0,  0x30bf,  0x3099,  0x0,  0x30bf,  0x3099,  0x30fc,  0x30b9,  0x0,  0x30c1,  0x0,  0x30c1,  0x3099,  0x0,  0x30c3,  0x0,  0x30c4,  0x0,  0x30c4,  0x3099,  0x0,  0x30c6,  0x0,  0x30c6,  0x3099,  0x0,  0x30c6,  0x3099,  0x30b7,  0x0,  0x30c8,  0x0,  0x30c8,  0x3099,  0x0,  0x30c8,  0x3099,  0x30eb,  0x0,  0x30c8,  0x30f3,  0x0,  0x30ca,  0x0,  0x30ca,  0x30ce,  0x0,  0x30cb,  0x0,  0x30cc,  0x0,  0x30cd,  0x0,  0x30ce,  0x0,  0x30ce,  0x30c3,  0x30c8,  0x0,  0x30cf,  0x0,  0x30cf,  0x3099,  0x0,  0x30cf,  0x3099,  0x30fc,  0x30ec,  0x30eb,  0x0,  0x30cf,  0x309a,  0x0,  0x30cf,  0x309a,  0x30fc,  0x30bb,  0x30f3,  0x30c8,  0x0,  0x30cf,  0x309a,  0x30fc,  0x30c4,  0x0,  0x30cf,  0x30a4,  0x30c4,  0x0,  0x30d2,  0x0,  0x30d2,  0x3099,  0x0,  0x30d2,  0x3099,  0x30eb,  0x0,  0x30d2,  0x309a,  0x0,  0x30d2,  0x309a,  0x30a2,  0x30b9,  0x30c8,  0x30eb,  0x0,  0x30d2,  0x309a,  0x30af,  0x30eb,  0x0,  0x30d2,  0x309a,  0x30b3,  0x0,  0x30d5,  0x0,  0x30d5,  0x3099,  0x0,  0x30d5,  0x3099,  0x30c3,  0x30b7,  0x30a7,  0x30eb,  0x0,  0x30d5,  0x309a,  0x0,  0x30d5,  0x30a1,  0x30e9,  0x30c3,  0x30c8,  0x3099,  0x0,  0x30d5,  0x30a3,  0x30fc,  0x30c8,  0x0,  0x30d5,  0x30e9,  0x30f3,  0x0,  0x30d8,  0x0,  0x30d8,  0x3099,  0x0,  0x30d8,  0x3099,  0x30fc,  0x30bf,  0x0,  0x30d8,  0x309a,  0x0,  0x30d8,  0x309a,  0x30bd,  0x0,  0x30d8,  0x309a,  0x30cb,  0x30d2,  0x0,  0x30d8,  0x309a,  0x30f3,  0x30b9,  0x0,  0x30d8,  0x309a,  0x30fc,  0x30b7,  0x3099,  0x0,  0x30d8,  0x30af,  0x30bf,  0x30fc,  0x30eb,  0x0,  0x30d8,  0x30eb,  0x30c4,  0x0,  0x30db,  0x0,  0x30db,  0x3099,  0x0,  0x30db,  0x3099,  0x30eb,  0x30c8,  0x0,  0x30db,  0x309a,  0x0,  0x30db,  0x309a,  0x30a4,  0x30f3,  0x30c8,  0x0,  0x30db,  0x309a,  0x30f3,  0x30c8,  0x3099,  0x0,  0x30db,  0x30f3,  0x0,  0x30db,  0x30fc,  0x30eb,  0x0,  0x30db,  0x30fc,  0x30f3,  0x0,  0x30de,  0x0,  0x30de,  0x30a4,  0x30af,  0x30ed,  0x0,  0x30de,  0x30a4,  0x30eb,  0x0,  0x30de,  0x30c3,  0x30cf,  0x0,  0x30de,  0x30eb,  0x30af,  0x0,  0x30de,  0x30f3,  0x30b7,  0x30e7,  0x30f3,  0x0,  0x30df,  0x0,  0x30df,  0x30af,  0x30ed,  0x30f3,  0x0,  0x30df,  0x30ea,  0x0,  0x30df,  0x30ea,  0x30cf,  0x3099,  0x30fc,  0x30eb,  0x0,  0x30e0,  0x0,  0x30e1,  0x0,  0x30e1,  0x30ab,  0x3099,  0x0,  0x30e1,  0x30ab,  0x3099,  0x30c8,  0x30f3,  0x0,  0x30e1,  0x30fc,  0x30c8,  0x30eb,  0x0,  0x30e2,  0x0,  0x30e3,  0x0,  0x30e4,  0x0,  0x30e4,  0x30fc,  0x30c8,  0x3099,  0x0,  0x30e4,  0x30fc,  0x30eb,  0x0,  0x30e5,  0x0,  0x30e6,  0x0,  0x30e6,  0x30a2,  0x30f3,  0x0,  0x30e7,  0x0,  0x30e8,  0x0,  0x30e9,  0x0,  0x30ea,  0x0,  0x30ea,  0x30c3,  0x30c8,  0x30eb,  0x0,  0x30ea,  0x30e9,  0x0,  0x30eb,  0x0,  0x30eb,  0x30d2,  0x309a,  0x30fc,  0x0,  0x30eb,  0x30fc,  0x30d5,  0x3099,  0x30eb,  0x0,  0x30ec,  0x0,  0x30ec,  0x30e0,  0x0,  0x30ec,  0x30f3,  0x30c8,  0x30b1,  0x3099,  0x30f3,  0x0,  0x30ed,  0x0,  0x30ef,  0x0,  0x30ef,  0x3099,  0x0,  0x30ef,  0x30c3,  0x30c8,  0x0,  0x30f0,  0x0,  0x30f0,  0x3099,  0x0,  0x30f1,  0x0,  0x30f1,  0x3099,  0x0,  0x30f2,  0x0,  0x30f2,  0x3099,  0x0,  0x30f3,  0x0,  0x30fb,  0x0,  0x30fc,  0x0,  0x30fd,  0x3099,  0x0,  0x349e,  0x0,  0x34b9,  0x0,  0x34bb,  0x0,  0x34df,  0x0,  0x3515,  0x0,  0x36ee,  0x0,  0x36fc,  0x0,  0x3781,  0x0,  0x382f,  0x0,  0x3862,  0x0,  0x387c,  0x0,  0x38c7,  0x0,  0x38e3,  0x0,  0x391c,  0x0,  0x393a,  0x0,  0x3a2e,  0x0,  0x3a6c,  0x0,  0x3ae4,  0x0,  0x3b08,  0x0,  0x3b19,  0x0,  0x3b49,  0x0,  0x3b9d,  0x0,  0x3c18,  0x0,  0x3c4e,  0x0,  0x3d33,  0x0,  0x3d96,  0x0,  0x3eac,  0x0,  0x3eb8,  0x0,  0x3f1b,  0x0,  0x3ffc,  0x0,  0x4008,  0x0,  0x4018,  0x0,  0x4039,  0x0,  0x4046,  0x0,  0x4096,  0x0,  0x40e3,  0x0,  0x412f,  0x0,  0x4202,  0x0,  0x4227,  0x0,  0x42a0,  0x0,  0x4301,  0x0,  0x4334,  0x0,  0x4359,  0x0,  0x43d5,  0x0,  0x43d9,  0x0,  0x440b,  0x0,  0x446b,  0x0,  0x452b,  0x0,  0x455d,  0x0,  0x4561,  0x0,  0x456b,  0x0,  0x45d7,  0x0,  0x45f9,  0x0,  0x4635,  0x0,  0x46be,  0x0,  0x46c7,  0x0,  0x4995,  0x0,  0x49e6,  0x0,  0x4a6e,  0x0,  0x4a76,  0x0,  0x4ab2,  0x0,  0x4b33,  0x0,  0x4bce,  0x0,  0x4cce,  0x0,  0x4ced,  0x0,  0x4cf8,  0x0,  0x4d56,  0x0,  0x4e00,  0x0,  0x4e01,  0x0,  0x4e03,  0x0,  0x4e09,  0x0,  0x4e0a,  0x0,  0x4e0b,  0x0,  0x4e0d,  0x0,  0x4e19,  0x0,  0x4e26,  0x0,  0x4e28,  0x0,  0x4e2d,  0x0,  0x4e32,  0x0,  0x4e36,  0x0,  0x4e38,  0x0,  0x4e39,  0x0,  0x4e3d,  0x0,  0x4e3f,  0x0,  0x4e41,  0x0,  0x4e59,  0x0,  0x4e5d,  0x0,  0x4e82,  0x0,  0x4e85,  0x0,  0x4e86,  0x0,  0x4e8c,  0x0,  0x4e94,  0x0,  0x4ea0,  0x0,  0x4ea4,  0x0,  0x4eae,  0x0,  0x4eba,  0x0,  0x4ec0,  0x0,  0x4ecc,  0x0,  0x4ee4,  0x0,  0x4f01,  0x0,  0x4f11,  0x0,  0x4f60,  0x0,  0x4f80,  0x0,  0x4f86,  0x0,  0x4f8b,  0x0,  0x4fae,  0x0,  0x4fbb,  0x0,  0x4fbf,  0x0,  0x5002,  0x0,  0x502b,  0x0,  0x507a,  0x0,  0x5099,  0x0,  0x50cf,  0x0,  0x50da,  0x0,  0x50e7,  0x0,  0x512a,  0x0,  0x513f,  0x0,  0x5140,  0x0,  0x5145,  0x0,  0x514d,  0x0,  0x5154,  0x0,  0x5164,  0x0,  0x5165,  0x0,  0x5167,  0x0,  0x5168,  0x0,  0x5169,  0x0,  0x516b,  0x0,  0x516d,  0x0,  0x5177,  0x0,  0x5180,  0x0,  0x5182,  0x0,  0x518d,  0x0,  0x5192,  0x0,  0x5195,  0x0,  0x5196,  0x0,  0x5197,  0x0,  0x5199,  0x0,  0x51a4,  0x0,  0x51ab,  0x0,  0x51ac,  0x0,  0x51b5,  0x0,  0x51b7,  0x0,  0x51c9,  0x0,  0x51cc,  0x0,  0x51dc,  0x0,  0x51de,  0x0,  0x51e0,  0x0,  0x51f5,  0x0,  0x5200,  0x0,  0x5203,  0x0,  0x5207,  0x0,  0x5217,  0x0,  0x521d,  0x0,  0x5229,  0x0,  0x523a,  0x0,  0x523b,  0x0,  0x5246,  0x0,  0x524d,  0x0,  0x5272,  0x0,  0x5277,  0x0,  0x5289,  0x0,  0x529b,  0x0,  0x52a3,  0x0,  0x52b3,  0x0,  0x52b4,  0x0,  0x52c7,  0x0,  0x52c9,  0x0,  0x52d2,  0x0,  0x52de,  0x0,  0x52e4,  0x0,  0x52f5,  0x0,  0x52f9,  0x0,  0x52fa,  0x0,  0x5305,  0x0,  0x5306,  0x0,  0x5315,  0x0,  0x5317,  0x0,  0x531a,  0x0,  0x5338,  0x0,  0x533b,  0x0,  0x533f,  0x0,  0x5341,  0x0,  0x5344,  0x0,  0x5345,  0x0,  0x5349,  0x0,  0x5351,  0x0,  0x5354,  0x0,  0x535a,  0x0,  0x535c,  0x0,  0x5369,  0x0,  0x5370,  0x0,  0x5373,  0x0,  0x5375,  0x0,  0x537d,  0x0,  0x537f,  0x0,  0x5382,  0x0,  0x53b6,  0x0,  0x53c3,  0x0,  0x53c8,  0x0,  0x53ca,  0x0,  0x53cc,  0x0,  0x53df,  0x0,  0x53e3,  0x0,  0x53e5,  0x0,  0x53eb,  0x0,  0x53ef,  0x0,  0x53f1,  0x0,  0x53f3,  0x0,  0x5406,  0x0,  0x5408,  0x0,  0x540d,  0x0,  0x540f,  0x0,  0x541d,  0x0,  0x5438,  0x0,  0x5439,  0x0,  0x5442,  0x0,  0x5448,  0x0,  0x5468,  0x0,  0x549e,  0x0,  0x54a2,  0x0,  0x54bd,  0x0,  0x54f6,  0x0,  0x5510,  0x0,  0x554f,  0x0,  0x5553,  0x0,  0x5555,  0x0,  0x5563,  0x0,  0x5584,  0x0,  0x5587,  0x0,  0x5599,  0x0,  0x559d,  0x0,  0x55ab,  0x0,  0x55b3,  0x0,  0x55b6,  0x0,  0x55c0,  0x0,  0x55c2,  0x0,  0x55e2,  0x0,  0x5606,  0x0,  0x5651,  0x0,  0x5668,  0x0,  0x5674,  0x0,  0x56d7,  0x0,  0x56db,  0x0,  0x56f9,  0x0,  0x5716,  0x0,  0x5717,  0x0,  0x571f,  0x0,  0x5730,  0x0,  0x578b,  0x0,  0x57ce,  0x0,  0x57f4,  0x0,  0x580d,  0x0,  0x5831,  0x0,  0x5832,  0x0,  0x5840,  0x0,  0x585a,  0x0,  0x585e,  0x0,  0x58a8,  0x0,  0x58ac,  0x0,  0x58b3,  0x0,  0x58d8,  0x0,  0x58df,  0x0,  0x58eb,  0x0,  0x58ee,  0x0,  0x58f0,  0x0,  0x58f2,  0x0,  0x58f7,  0x0,  0x5902,  0x0,  0x5906,  0x0,  0x590a,  0x0,  0x5915,  0x0,  0x591a,  0x0,  0x591c,  0x0,  0x5922,  0x0,  0x5927,  0x0,  0x5927,  0x6b63,  0x0,  0x5929,  0x0,  0x5944,  0x0,  0x5948,  0x0,  0x5951,  0x0,  0x5954,  0x0,  0x5962,  0x0,  0x5973,  0x0,  0x59d8,  0x0,  0x59ec,  0x0,  0x5a1b,  0x0,  0x5a27,  0x0,  0x5a62,  0x0,  0x5a66,  0x0,  0x5ab5,  0x0,  0x5b08,  0x0,  0x5b28,  0x0,  0x5b3e,  0x0,  0x5b50,  0x0,  0x5b57,  0x0,  0x5b66,  0x0,  0x5b80,  0x0,  0x5b85,  0x0,  0x5b97,  0x0,  0x5bc3,  0x0,  0x5bd8,  0x0,  0x5be7,  0x0,  0x5bee,  0x0,  0x5bf3,  0x0,  0x5bf8,  0x0,  0x5bff,  0x0,  0x5c06,  0x0,  0x5c0f,  0x0,  0x5c22,  0x0,  0x5c38,  0x0,  0x5c3f,  0x0,  0x5c60,  0x0,  0x5c62,  0x0,  0x5c64,  0x0,  0x5c65,  0x0,  0x5c6e,  0x0,  0x5c71,  0x0,  0x5c8d,  0x0,  0x5cc0,  0x0,  0x5d19,  0x0,  0x5d43,  0x0,  0x5d50,  0x0,  0x5d6b,  0x0,  0x5d6e,  0x0,  0x5d7c,  0x0,  0x5db2,  0x0,  0x5dba,  0x0,  0x5ddb,  0x0,  0x5de1,  0x0,  0x5de2,  0x0,  0x5de5,  0x0,  0x5de6,  0x0,  0x5df1,  0x0,  0x5dfd,  0x0,  0x5dfe,  0x0,  0x5e28,  0x0,  0x5e3d,  0x0,  0x5e69,  0x0,  0x5e72,  0x0,  0x5e73,  0x6210,  0x0,  0x5e74,  0x0,  0x5e7a,  0x0,  0x5e7c,  0x0,  0x5e7f,  0x0,  0x5ea6,  0x0,  0x5eb0,  0x0,  0x5eb3,  0x0,  0x5eb6,  0x0,  0x5ec9,  0x0,  0x5eca,  0x0,  0x5ed2,  0x0,  0x5ed3,  0x0,  0x5ed9,  0x0,  0x5eec,  0x0,  0x5ef4,  0x0,  0x5efe,  0x0,  0x5f04,  0x0,  0x5f0b,  0x0,  0x5f13,  0x0,  0x5f22,  0x0,  0x5f50,  0x0,  0x5f53,  0x0,  0x5f61,  0x0,  0x5f62,  0x0,  0x5f69,  0x0,  0x5f6b,  0x0,  0x5f73,  0x0,  0x5f8b,  0x0,  0x5f8c,  0x0,  0x5f97,  0x0,  0x5f9a,  0x0,  0x5fa9,  0x0,  0x5fad,  0x0,  0x5fc3,  0x0,  0x5fcd,  0x0,  0x5fd7,  0x0,  0x5ff5,  0x0,  0x5ff9,  0x0,  0x6012,  0x0,  0x601c,  0x0,  0x6075,  0x0,  0x6081,  0x0,  0x6094,  0x0,  0x60c7,  0x0,  0x60d8,  0x0,  0x60e1,  0x0,  0x6108,  0x0,  0x6144,  0x0,  0x6148,  0x0,  0x614c,  0x0,  0x614e,  0x0,  0x6160,  0x0,  0x6168,  0x0,  0x617a,  0x0,  0x618e,  0x0,  0x6190,  0x0,  0x61a4,  0x0,  0x61af,  0x0,  0x61b2,  0x0,  0x61de,  0x0,  0x61f2,  0x0,  0x61f6,  0x0,  0x6200,  0x0,  0x6208,  0x0,  0x6210,  0x0,  0x621b,  0x0,  0x622e,  0x0,  0x6234,  0x0,  0x6236,  0x0,  0x624b,  0x0,  0x6253,  0x0,  0x625d,  0x0,  0x6295,  0x0,  0x62b1,  0x0,  0x62c9,  0x0,  0x62cf,  0x0,  0x62d3,  0x0,  0x62d4,  0x0,  0x62fc,  0x0,  0x62fe,  0x0,  0x6307,  0x0,  0x633d,  0x0,  0x6350,  0x0,  0x6355,  0x0,  0x6368,  0x0,  0x637b,  0x0,  0x6383,  0x0,  0x63a0,  0x0,  0x63a9,  0x0,  0x63c4,  0x0,  0x63c5,  0x0,  0x63e4,  0x0,  0x641c,  0x0,  0x6422,  0x0,  0x6452,  0x0,  0x6469,  0x0,  0x6477,  0x0,  0x647e,  0x0,  0x649a,  0x0,  0x649d,  0x0,  0x64c4,  0x0,  0x652f,  0x0,  0x6534,  0x0,  0x654f,  0x0,  0x6556,  0x0,  0x656c,  0x0,  0x6578,  0x0,  0x6587,  0x0,  0x6597,  0x0,  0x6599,  0x0,  0x65a4,  0x0,  0x65b0,  0x0,  0x65b9,  0x0,  0x65c5,  0x0,  0x65e0,  0x0,  0x65e2,  0x0,  0x65e3,  0x0,  0x65e5,  0x0,  0x660e,  0x6cbb,  0x0,  0x6613,  0x0,  0x6620,  0x0,  0x662d,  0x548c,  0x0,  0x6649,  0x0,  0x6674,  0x0,  0x6688,  0x0,  0x6691,  0x0,  0x669c,  0x0,  0x66b4,  0x0,  0x66c6,  0x0,  0x66f0,  0x0,  0x66f4,  0x0,  0x66f8,  0x0,  0x6700,  0x0,  0x6708,  0x0,  0x6709,  0x0,  0x6717,  0x0,  0x671b,  0x0,  0x6721,  0x0,  0x6728,  0x0,  0x674e,  0x0,  0x6753,  0x0,  0x6756,  0x0,  0x675e,  0x0,  0x677b,  0x0,  0x6785,  0x0,  0x6797,  0x0,  0x67f3,  0x0,  0x67fa,  0x0,  0x6817,  0x0,  0x681f,  0x0,  0x682a,  0x0,  0x682a,  0x5f0f,  0x4f1a,  0x793e,  0x0,  0x6852,  0x0,  0x6881,  0x0,  0x6885,  0x0,  0x688e,  0x0,  0x68a8,  0x0,  0x6914,  0x0,  0x6942,  0x0,  0x69a3,  0x0,  0x69ea,  0x0,  0x6a02,  0x0,  0x6a13,  0x0,  0x6aa8,  0x0,  0x6ad3,  0x0,  0x6adb,  0x0,  0x6b04,  0x0,  0x6b20,  0x0,  0x6b21,  0x0,  0x6b54,  0x0,  0x6b62,  0x0,  0x6b63,  0x0,  0x6b72,  0x0,  0x6b77,  0x0,  0x6b79,  0x0,  0x6b9f,  0x0,  0x6bae,  0x0,  0x6bb3,  0x0,  0x6bba,  0x0,  0x6bbb,  0x0,  0x6bcb,  0x0,  0x6bcd,  0x0,  0x6bd4,  0x0,  0x6bdb,  0x0,  0x6c0f,  0x0,  0x6c14,  0x0,  0x6c34,  0x0,  0x6c4e,  0x0,  0x6c67,  0x0,  0x6c88,  0x0,  0x6cbf,  0x0,  0x6ccc,  0x0,  0x6ccd,  0x0,  0x6ce5,  0x0,  0x6ce8,  0x0,  0x6d16,  0x0,  0x6d1b,  0x0,  0x6d1e,  0x0,  0x6d34,  0x0,  0x6d3e,  0x0,  0x6d41,  0x0,  0x6d69,  0x0,  0x6d6a,  0x0,  0x6d77,  0x0,  0x6d78,  0x0,  0x6d85,  0x0,  0x6dcb,  0x0,  0x6dda,  0x0,  0x6dea,  0x0,  0x6df9,  0x0,  0x6e1a,  0x0,  0x6e2f,  0x0,  0x6e6e,  0x0,  0x6e80,  0x0,  0x6e9c,  0x0,  0x6eba,  0x0,  0x6ec7,  0x0,  0x6ecb,  0x0,  0x6ed1,  0x0,  0x6edb,  0x0,  0x6f0f,  0x0,  0x6f14,  0x0,  0x6f22,  0x0,  0x6f23,  0x0,  0x6f6e,  0x0,  0x6fc6,  0x0,  0x6feb,  0x0,  0x6ffe,  0x0,  0x701b,  0x0,  0x701e,  0x0,  0x7039,  0x0,  0x704a,  0x0,  0x706b,  0x0,  0x7070,  0x0,  0x7077,  0x0,  0x707d,  0x0,  0x7099,  0x0,  0x70ad,  0x0,  0x70c8,  0x0,  0x70d9,  0x0,  0x7121,  0x0,  0x7145,  0x0,  0x7149,  0x0,  0x716e,  0x0,  0x719c,  0x0,  0x71ce,  0x0,  0x71d0,  0x0,  0x7210,  0x0,  0x721b,  0x0,  0x7228,  0x0,  0x722a,  0x0,  0x722b,  0x0,  0x7235,  0x0,  0x7236,  0x0,  0x723b,  0x0,  0x723f,  0x0,  0x7247,  0x0,  0x7250,  0x0,  0x7259,  0x0,  0x725b,  0x0,  0x7262,  0x0,  0x7279,  0x0,  0x7280,  0x0,  0x7295,  0x0,  0x72ac,  0x0,  0x72af,  0x0,  0x72c0,  0x0,  0x72fc,  0x0,  0x732a,  0x0,  0x7375,  0x0,  0x737a,  0x0,  0x7384,  0x0,  0x7387,  0x0,  0x7389,  0x0,  0x738b,  0x0,  0x73a5,  0x0,  0x73b2,  0x0,  0x73de,  0x0,  0x7406,  0x0,  0x7409,  0x0,  0x7422,  0x0,  0x7447,  0x0,  0x745c,  0x0,  0x7469,  0x0,  0x7471,  0x0,  0x7485,  0x0,  0x7489,  0x0,  0x7498,  0x0,  0x74ca,  0x0,  0x74dc,  0x0,  0x74e6,  0x0,  0x7506,  0x0,  0x7518,  0x0,  0x751f,  0x0,  0x7524,  0x0,  0x7528,  0x0,  0x7530,  0x0,  0x7532,  0x0,  0x7533,  0x0,  0x7537,  0x0,  0x753b,  0x0,  0x753e,  0x0,  0x7559,  0x0,  0x7565,  0x0,  0x7570,  0x0,  0x758b,  0x0,  0x7592,  0x0,  0x75e2,  0x0,  0x7610,  0x0,  0x761d,  0x0,  0x761f,  0x0,  0x7642,  0x0,  0x7669,  0x0,  0x7676,  0x0,  0x767d,  0x0,  0x76ae,  0x0,  0x76bf,  0x0,  0x76ca,  0x0,  0x76db,  0x0,  0x76e3,  0x0,  0x76e7,  0x0,  0x76ee,  0x0,  0x76f4,  0x0,  0x7701,  0x0,  0x771e,  0x0,  0x771f,  0x0,  0x7740,  0x0,  0x774a,  0x0,  0x778b,  0x0,  0x77a7,  0x0,  0x77db,  0x0,  0x77e2,  0x0,  0x77f3,  0x0,  0x784e,  0x0,  0x786b,  0x0,  0x788c,  0x0,  0x7891,  0x0,  0x78ca,  0x0,  0x78cc,  0x0,  0x78fb,  0x0,  0x792a,  0x0,  0x793a,  0x0,  0x793c,  0x0,  0x793e,  0x0,  0x7948,  0x0,  0x7949,  0x0,  0x7950,  0x0,  0x7956,  0x0,  0x795d,  0x0,  0x795e,  0x0,  0x7965,  0x0,  0x797f,  0x0,  0x7981,  0x0,  0x798d,  0x0,  0x798e,  0x0,  0x798f,  0x0,  0x79ae,  0x0,  0x79b8,  0x0,  0x79be,  0x0,  0x79ca,  0x0,  0x79d8,  0x0,  0x79eb,  0x0,  0x7a1c,  0x0,  0x7a40,  0x0,  0x7a4a,  0x0,  0x7a4f,  0x0,  0x7a74,  0x0,  0x7a7a,  0x0,  0x7a81,  0x0,  0x7ab1,  0x0,  0x7acb,  0x0,  0x7aee,  0x0,  0x7af9,  0x0,  0x7b20,  0x0,  0x7b8f,  0x0,  0x7bc0,  0x0,  0x7bc6,  0x0,  0x7bc9,  0x0,  0x7c3e,  0x0,  0x7c60,  0x0,  0x7c73,  0x0,  0x7c7b,  0x0,  0x7c92,  0x0,  0x7cbe,  0x0,  0x7cd2,  0x0,  0x7cd6,  0x0,  0x7ce3,  0x0,  0x7ce7,  0x0,  0x7ce8,  0x0,  0x7cf8,  0x0,  0x7d00,  0x0,  0x7d10,  0x0,  0x7d22,  0x0,  0x7d2f,  0x0,  0x7d42,  0x0,  0x7d5b,  0x0,  0x7d63,  0x0,  0x7da0,  0x0,  0x7dbe,  0x0,  0x7dc7,  0x0,  0x7df4,  0x0,  0x7e02,  0x0,  0x7e09,  0x0,  0x7e37,  0x0,  0x7e41,  0x0,  0x7e45,  0x0,  0x7f36,  0x0,  0x7f3e,  0x0,  0x7f51,  0x0,  0x7f72,  0x0,  0x7f79,  0x0,  0x7f7a,  0x0,  0x7f85,  0x0,  0x7f8a,  0x0,  0x7f95,  0x0,  0x7f9a,  0x0,  0x7fbd,  0x0,  0x7ffa,  0x0,  0x8001,  0x0,  0x8005,  0x0,  0x800c,  0x0,  0x8012,  0x0,  0x8033,  0x0,  0x8046,  0x0,  0x8060,  0x0,  0x806f,  0x0,  0x8070,  0x0,  0x807e,  0x0,  0x807f,  0x0,  0x8089,  0x0,  0x808b,  0x0,  0x80ad,  0x0,  0x80b2,  0x0,  0x8103,  0x0,  0x813e,  0x0,  0x81d8,  0x0,  0x81e3,  0x0,  0x81e8,  0x0,  0x81ea,  0x0,  0x81ed,  0x0,  0x81f3,  0x0,  0x81fc,  0x0,  0x8201,  0x0,  0x8204,  0x0,  0x820c,  0x0,  0x8218,  0x0,  0x821b,  0x0,  0x821f,  0x0,  0x826e,  0x0,  0x826f,  0x0,  0x8272,  0x0,  0x8278,  0x0,  0x8279,  0x0,  0x828b,  0x0,  0x8291,  0x0,  0x829d,  0x0,  0x82b1,  0x0,  0x82b3,  0x0,  0x82bd,  0x0,  0x82e5,  0x0,  0x82e6,  0x0,  0x831d,  0x0,  0x8323,  0x0,  0x8336,  0x0,  0x8352,  0x0,  0x8353,  0x0,  0x8363,  0x0,  0x83ad,  0x0,  0x83bd,  0x0,  0x83c9,  0x0,  0x83ca,  0x0,  0x83cc,  0x0,  0x83dc,  0x0,  0x83e7,  0x0,  0x83ef,  0x0,  0x83f1,  0x0,  0x843d,  0x0,  0x8449,  0x0,  0x8457,  0x0,  0x84ee,  0x0,  0x84f1,  0x0,  0x84f3,  0x0,  0x84fc,  0x0,  0x8516,  0x0,  0x8564,  0x0,  0x85cd,  0x0,  0x85fa,  0x0,  0x8606,  0x0,  0x8612,  0x0,  0x862d,  0x0,  0x863f,  0x0,  0x864d,  0x0,  0x8650,  0x0,  0x865c,  0x0,  0x8667,  0x0,  0x8669,  0x0,  0x866b,  0x0,  0x8688,  0x0,  0x86a9,  0x0,  0x86e2,  0x0,  0x870e,  0x0,  0x8728,  0x0,  0x876b,  0x0,  0x8779,  0x0,  0x8786,  0x0,  0x87ba,  0x0,  0x87e1,  0x0,  0x8801,  0x0,  0x881f,  0x0,  0x8840,  0x0,  0x884c,  0x0,  0x8860,  0x0,  0x8863,  0x0,  0x88c2,  0x0,  0x88cf,  0x0,  0x88d7,  0x0,  0x88de,  0x0,  0x88e1,  0x0,  0x88f8,  0x0,  0x88fa,  0x0,  0x8910,  0x0,  0x8941,  0x0,  0x8964,  0x0,  0x897e,  0x0,  0x8986,  0x0,  0x898b,  0x0,  0x8996,  0x0,  0x89d2,  0x0,  0x89e3,  0x0,  0x8a00,  0x0,  0x8aa0,  0x0,  0x8aaa,  0x0,  0x8abf,  0x0,  0x8acb,  0x0,  0x8ad2,  0x0,  0x8ad6,  0x0,  0x8aed,  0x0,  0x8af8,  0x0,  0x8afe,  0x0,  0x8b01,  0x0,  0x8b39,  0x0,  0x8b58,  0x0,  0x8b80,  0x0,  0x8b8a,  0x0,  0x8c37,  0x0,  0x8c46,  0x0,  0x8c48,  0x0,  0x8c55,  0x0,  0x8c78,  0x0,  0x8c9d,  0x0,  0x8ca1,  0x0,  0x8ca9,  0x0,  0x8cab,  0x0,  0x8cc1,  0x0,  0x8cc2,  0x0,  0x8cc7,  0x0,  0x8cc8,  0x0,  0x8cd3,  0x0,  0x8d08,  0x0,  0x8d1b,  0x0,  0x8d64,  0x0,  0x8d70,  0x0,  0x8d77,  0x0,  0x8db3,  0x0,  0x8dbc,  0x0,  0x8dcb,  0x0,  0x8def,  0x0,  0x8df0,  0x0,  0x8eab,  0x0,  0x8eca,  0x0,  0x8ed4,  0x0,  0x8f26,  0x0,  0x8f2a,  0x0,  0x8f38,  0x0,  0x8f3b,  0x0,  0x8f62,  0x0,  0x8f9b,  0x0,  0x8f9e,  0x0,  0x8fb0,  0x0,  0x8fb5,  0x0,  0x8fb6,  0x0,  0x9023,  0x0,  0x9038,  0x0,  0x904a,  0x0,  0x9069,  0x0,  0x9072,  0x0,  0x907c,  0x0,  0x908f,  0x0,  0x9091,  0x0,  0x9094,  0x0,  0x90ce,  0x0,  0x90de,  0x0,  0x90f1,  0x0,  0x90fd,  0x0,  0x9111,  0x0,  0x911b,  0x0,  0x9149,  0x0,  0x916a,  0x0,  0x9199,  0x0,  0x91b4,  0x0,  0x91c6,  0x0,  0x91cc,  0x0,  0x91cf,  0x0,  0x91d1,  0x0,  0x9234,  0x0,  0x9238,  0x0,  0x9276,  0x0,  0x927c,  0x0,  0x92d7,  0x0,  0x92d8,  0x0,  0x9304,  0x0,  0x934a,  0x0,  0x93f9,  0x0,  0x9415,  0x0,  0x9577,  0x0,  0x9580,  0x0,  0x958b,  0x0,  0x95ad,  0x0,  0x95b7,  0x0,  0x961c,  0x0,  0x962e,  0x0,  0x964b,  0x0,  0x964d,  0x0,  0x9675,  0x0,  0x9678,  0x0,  0x967c,  0x0,  0x9686,  0x0,  0x96a3,  0x0,  0x96b6,  0x0,  0x96b7,  0x0,  0x96b8,  0x0,  0x96b9,  0x0,  0x96c3,  0x0,  0x96e2,  0x0,  0x96e3,  0x0,  0x96e8,  0x0,  0x96f6,  0x0,  0x96f7,  0x0,  0x9723,  0x0,  0x9732,  0x0,  0x9748,  0x0,  0x9751,  0x0,  0x9756,  0x0,  0x975e,  0x0,  0x9762,  0x0,  0x9769,  0x0,  0x97cb,  0x0,  0x97db,  0x0,  0x97e0,  0x0,  0x97ed,  0x0,  0x97f3,  0x0,  0x97ff,  0x0,  0x9801,  0x0,  0x9805,  0x0,  0x980b,  0x0,  0x9818,  0x0,  0x9829,  0x0,  0x983b,  0x0,  0x985e,  0x0,  0x98a8,  0x0,  0x98db,  0x0,  0x98df,  0x0,  0x98e2,  0x0,  0x98ef,  0x0,  0x98fc,  0x0,  0x9928,  0x0,  0x9929,  0x0,  0x9996,  0x0,  0x9999,  0x0,  0x99a7,  0x0,  0x99ac,  0x0,  0x99c2,  0x0,  0x99f1,  0x0,  0x99fe,  0x0,  0x9a6a,  0x0,  0x9aa8,  0x0,  0x9ad8,  0x0,  0x9adf,  0x0,  0x9b12,  0x0,  0x9b25,  0x0,  0x9b2f,  0x0,  0x9b32,  0x0,  0x9b3c,  0x0,  0x9b5a,  0x0,  0x9b6f,  0x0,  0x9c40,  0x0,  0x9c57,  0x0,  0x9ce5,  0x0,  0x9cfd,  0x0,  0x9d67,  0x0,  0x9db4,  0x0,  0x9dfa,  0x0,  0x9e1e,  0x0,  0x9e75,  0x0,  0x9e7f,  0x0,  0x9e97,  0x0,  0x9e9f,  0x0,  0x9ea5,  0x0,  0x9ebb,  0x0,  0x9ec3,  0x0,  0x9ecd,  0x0,  0x9ece,  0x0,  0x9ed1,  0x0,  0x9ef9,  0x0,  0x9efd,  0x0,  0x9efe,  0x0,  0x9f05,  0x0,  0x9f0e,  0x0,  0x9f0f,  0x0,  0x9f13,  0x0,  0x9f16,  0x0,  0x9f20,  0x0,  0x9f3b,  0x0,  0x9f43,  0x0,  0x9f4a,  0x0,  0x9f52,  0x0,  0x9f8d,  0x0,  0x9f8e,  0x0,  0x9f9c,  0x0,  0x9f9f,  0x0,  0x9fa0,  0x0,  0xa76f,  0x0,  0x11099,  0x110ba,  0x0,  0x1109b,  0x110ba,  0x0,  0x110a5,  0x110ba,  0x0,  0x11131,  0x11127,  0x0,  0x11132,  0x11127,  0x0,  0x1d157,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x0,  0x1d158,  0x1d165,  0x1d16e,  0x0,  0x1d158,  0x1d165,  0x1d16f,  0x0,  0x1d158,  0x1d165,  0x1d170,  0x0,  0x1d158,  0x1d165,  0x1d171,  0x0,  0x1d158,  0x1d165,  0x1d172,  0x0,  0x1d1b9,  0x1d165,  0x0,  0x1d1b9,  0x1d165,  0x1d16e,  0x0,  0x1d1b9,  0x1d165,  0x1d16f,  0x0,  0x1d1ba,  0x1d165,  0x0,  0x1d1ba,  0x1d165,  0x1d16e,  0x0,  0x1d1ba,  0x1d165,  0x1d16f,  0x0,  0x20122,  0x0,  0x2051c,  0x0,  0x20525,  0x0,  0x2054b,  0x0,  0x2063a,  0x0,  0x20804,  0x0,  0x208de,  0x0,  0x20a2c,  0x0,  0x20b63,  0x0,  0x214e4,  0x0,  0x216a8,  0x0,  0x216ea,  0x0,  0x219c8,  0x0,  0x21b18,  0x0,  0x21d0b,  0x0,  0x21de4,  0x0,  0x21de6,  0x0,  0x22183,  0x0,  0x2219f,  0x0,  0x22331,  0x0,  0x226d4,  0x0,  0x22844,  0x0,  0x2284a,  0x0,  0x22b0c,  0x0,  0x22bf1,  0x0,  0x2300a,  0x0,  0x232b8,  0x0,  0x2335f,  0x0,  0x23393,  0x0,  0x2339c,  0x0,  0x233c3,  0x0,  0x233d5,  0x0,  0x2346d,  0x0,  0x236a3,  0x0,  0x238a7,  0x0,  0x23a8d,  0x0,  0x23afa,  0x0,  0x23cbc,  0x0,  0x23d1e,  0x0,  0x23ed1,  0x0,  0x23f5e,  0x0,  0x23f8e,  0x0,  0x24263,  0x0,  0x242ee,  0x0,  0x243ab,  0x0,  0x24608,  0x0,  0x24735,  0x0,  0x24814,  0x0,  0x24c36,  0x0,  0x24c92,  0x0,  0x24fa1,  0x0,  0x24fb8,  0x0,  0x25044,  0x0,  0x250f2,  0x0,  0x250f3,  0x0,  0x25119,  0x0,  0x25133,  0x0,  0x25249,  0x0,  0x2541d,  0x0,  0x25626,  0x0,  0x2569a,  0x0,  0x256c5,  0x0,  0x2597c,  0x0,  0x25aa7,  0x0,  0x25bab,  0x0,  0x25c80,  0x0,  0x25cd0,  0x0,  0x25f86,  0x0,  0x261da,  0x0,  0x26228,  0x0,  0x26247,  0x0,  0x262d9,  0x0,  0x2633e,  0x0,  0x264da,  0x0,  0x26523,  0x0,  0x265a8,  0x0,  0x267a7,  0x0,  0x267b5,  0x0,  0x26b3c,  0x0,  0x26c36,  0x0,  0x26cd5,  0x0,  0x26d6b,  0x0,  0x26f2c,  0x0,  0x26fb1,  0x0,  0x270d2,  0x0,  0x273ca,  0x0,  0x27667,  0x0,  0x278ae,  0x0,  0x27966,  0x0,  0x27ca8,  0x0,  0x27ed3,  0x0,  0x27f2f,  0x0,  0x285d2,  0x0,  0x285ed,  0x0,  0x2872e,  0x0,  0x28bfa,  0x0,  0x28d77,  0x0,  0x29145,  0x0,  0x291df,  0x0,  0x2921a,  0x0,  0x2940a,  0x0,  0x29496,  0x0,  0x295b6,  0x0,  0x29b30,  0x0,  0x2a0ce,  0x0,  0x2a105,  0x0,  0x2a20e,  0x0,  0x2a291,  0x0,  0x2a392,  0x0,  0x2a600,  0x0]; return t; }
+}
+
+}
+
diff --git a/libphobos/src/std/internal/unicode_grapheme.d b/libphobos/src/std/internal/unicode_grapheme.d
new file mode 100644
index 00000000000..ffc5c061db3
--- /dev/null
+++ b/libphobos/src/std/internal/unicode_grapheme.d
@@ -0,0 +1,28 @@
+module std.internal.unicode_grapheme;
+import std.internal.unicode_tables;
+
+static if(size_t.sizeof == 8) {
+//832 bytes
+enum hangulLVTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x40], [ 0x100,  0x80,  0xa00], [ 0x2010000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4000300020001,  0x1000700060005,  0x5000400030002,  0x2000100070006,  0x6000500040003,  0x3000200010007,  0x7000600050004,  0x4000300020001,  0x1000700060005,  0x5000400030002,  0x8000100070006,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x100000010000001,  0x1000000100000,  0x10000001000,  0x1000000100000010,  0x10000001000000,  0x100000010000,  0x1000000100,  0x100000010000001,  0x1000000100000,  0x10000001000,  0x1000000100000010,  0x10000001000000,  0x100000010000,  0x1000000100,  0x100000010000001,  0x1000000100000,  0x10000001000,  0x1000000100000010,  0x10000001000000,  0x100000010000,  0x1000000100,  0x100000010000001,  0x1000000100000,  0x10000001000,  0x1000000100000010,  0x10000001000000,  0x100000010000,  0x1000000100,  0x10000001000000,  0x100000010000,  0x100,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//832 bytes
+enum hangulLVTTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x40], [ 0x100,  0x80,  0xa00], [ 0x2010000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4000300020001,  0x1000700060005,  0x5000400030002,  0x2000100070006,  0x6000500040003,  0x3000200010007,  0x7000600050004,  0x4000300020001,  0x1000700060005,  0x5000400030002,  0x8000100070006,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfeffffffeffffffe,  0xfffeffffffefffff,  0xfffffeffffffefff,  0xeffffffeffffffef,  0xffeffffffeffffff,  0xffffeffffffeffff,  0xffffffeffffffeff,  0xfeffffffeffffffe,  0xfffeffffffefffff,  0xfffffeffffffefff,  0xeffffffeffffffef,  0xffeffffffeffffff,  0xffffeffffffeffff,  0xffffffeffffffeff,  0xfeffffffeffffffe,  0xfffeffffffefffff,  0xfffffeffffffefff,  0xeffffffeffffffef,  0xffeffffffeffffff,  0xffffeffffffeffff,  0xffffffeffffffeff,  0xfeffffffeffffffe,  0xfffeffffffefffff,  0xfffffeffffffefff,  0xeffffffeffffffef,  0xffeffffffeffffff,  0xffffeffffffeffff,  0xffffffeffffffeff,  0xffeffffffeffffff,  0xffffeffffffeffff,  0xffffffeff,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1536 bytes
+enum mcTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x60], [ 0x100,  0x100,  0x1800], [ 0x202030202020100,  0x206020205020204,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x6000000050004,  0x7,  0x8000000000000,  0xb000a00090000,  0xc,  0x0,  0x0,  0x0,  0x0,  0xd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x110010000f000e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x130012,  0x1400000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x15000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc800000000000008,  0xde01,  0xc00000000000000c,  0x801981,  0xc000000000000008,  0x1,  0xc000000000000008,  0x1a01,  0x400000000000000c,  0x801981,  0xc000000000000000,  0x801dc6,  0xe,  0x1e,  0x400000000000000c,  0x600d9f,  0xc00000000000000c,  0x801dc1,  0xc,  0xc0000ff038000,  0xc000000000000000,  0x8000000000000000,  0x0,  0x0,  0x1902180000000000,  0x3f9c00c00000,  0x1c009f98,  0x0,  0x0,  0x0,  0xc040000000000000,  0x1bf,  0x1fb0e7800000000,  0x0,  0xffff000000000000,  0x301,  0x6000000,  0x7e01a00a00000,  0x0,  0x0,  0xe820000000000010,  0x1b,  0x34c200000004,  0xc5c8000000000,  0x300ff000000000,  0x0,  0x0,  0xc000200000000,  0xc00000000000,  0x0,  0x0,  0x0,  0x9800000000,  0x0,  0xfff0000000000003,  0xf,  0x0,  0xc0000,  0xec30000000000008,  0x1,  0x19800000000000,  0x800000000002000,  0x0,  0x20c80000000000,  0x0,  0x0,  0x0,  0x16d800000000,  0x5,  0x0,  0x187000000000004,  0x0,  0x100000000000,  0x0,  0x8038000000000004,  0x1,  0x0,  0x0,  0x40d00000000000,  0x0,  0x0,  0x7ffffffffffe0000,  0x0,  0x0,  0x0,  0x7e06000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2336 bytes
+enum graphemeExtendTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x70], [ 0x100,  0x140,  0x2d00], [ 0x402030202020100,  0x207020206020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020208,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1000000000000,  0x5000400030002,  0x9000800070006,  0xd000c000b000a,  0xf00000000000e,  0x10000000000000,  0x14001300120011,  0x160015,  0x17,  0x0,  0x0,  0x190018,  0x1a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b00000000,  0x1f001e001d001c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000000000000,  0x22002100000000,  0x230000,  0x0,  0x2400000000,  0x0,  0x260025,  0x2700000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x28000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a00290000,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffff,  0x0,  0x0,  0x0,  0x0,  0x3f8,  0x0,  0x0,  0x0,  0xbffffffffffe0000,  0xb6,  0x7ff0000,  0x10000fffff800,  0x0,  0x3d9f9fc00000,  0xffff000000020000,  0x7ff,  0x1ffc000000000,  0xff80000000000,  0x3eeffbc00000,  0xe000000,  0x0,  0x7ffffff000000000,  0x1400000000000007,  0xc00fe21fe,  0x5000000000000002,  0xc0080201e,  0x1000000000000006,  0x23000000023986,  0x1000000000000006,  0xc000021be,  0xd000000000000002,  0xc00c0201e,  0x4000000000000004,  0x802001,  0xc000000000000000,  0xc00603dc1,  0x9000000000000000,  0xc00603044,  0x4000000000000000,  0xc0080201e,  0x0,  0x805c8400,  0x7f2000000000000,  0x7f80,  0x1bf2000000000000,  0x3f00,  0x2a0000003000000,  0x7ffe000000000000,  0x1ffffffffeffe0df,  0x40,  0x66fde00000000000,  0x1e0001c3000000,  0x20002064,  0x0,  0x0,  0xe0000000,  0x0,  0x0,  0x1c0000001c0000,  0xc0000000c0000,  0x3fb0000000000000,  0x200ffe40,  0x3800,  0x0,  0x20000000000,  0x0,  0xe04018700000000,  0x0,  0x0,  0x0,  0x9800000,  0x9ff81fe57f400000,  0x0,  0x0,  0x17d000000000000f,  0xff80000000004,  0xb3c00000003,  0x3a34000000000,  0xcff00000000000,  0x0,  0x0,  0x1021fdfff70000,  0x0,  0x0,  0x0,  0xf000007fffffffff,  0x3000,  0x0,  0x0,  0x1ffffffff0000,  0x0,  0x0,  0x0,  0x3800000000000,  0x0,  0x8000000000000000,  0x0,  0xffffffff00000000,  0xfc0000000000,  0x0,  0x6000000,  0x0,  0x0,  0x3ff7800000000000,  0x80000000,  0x3000000000000,  0x6000000844,  0x0,  0x0,  0x3ffff00000010,  0x3fc000000000,  0x3ff80,  0x13c8000000000007,  0x0,  0x667e0000000000,  0x1008,  0xc19d000000000000,  0x40300000000002,  0x0,  0x0,  0x0,  0x212000000000,  0x40000000,  0x0,  0x0,  0x0,  0x7f0000ffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x0,  0x2000000000000000,  0x870000000000f06e,  0x0,  0x0,  0x0,  0xff00000000000002,  0x7f,  0x678000000000003,  0x0,  0x1fef8000000007,  0x0,  0x7fc0000000000003,  0x0,  0x0,  0x0,  0xbf280000000000,  0x0,  0x0,  0x0,  0x78000,  0x0,  0x0,  0xf807c3a000000000,  0x3c0000000fe7,  0x0,  0x0,  0x1c,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff,  0x0,  0x0,  0x0,  0x0]);
+
+}
+
+
+static if(size_t.sizeof == 4) {
+//832 bytes
+enum hangulLVTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0x80], [ 0x100,  0x80,  0xa00], [ 0x0,  0x20100,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x80001,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x10000001,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x10,  0x1000000,  0x100000,  0x10000,  0x1000,  0x100,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//832 bytes
+enum hangulLVTTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0x80], [ 0x100,  0x80,  0xa00], [ 0x0,  0x20100,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x20001,  0x40003,  0x60005,  0x10007,  0x30002,  0x50004,  0x70006,  0x80001,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xeffffffe,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xffffffef,  0xfeffffff,  0xffefffff,  0xfffeffff,  0xffffefff,  0xfffffeff,  0xf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1536 bytes
+enum mcTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xc0], [ 0x100,  0x100,  0x1800], [ 0x2020100,  0x2020302,  0x5020204,  0x2060202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x60000,  0x7,  0x0,  0x0,  0x80000,  0x90000,  0xb000a,  0xc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf000e,  0x110010,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x130012,  0x0,  0x0,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8,  0xc8000000,  0xde01,  0x0,  0xc,  0xc0000000,  0x801981,  0x0,  0x8,  0xc0000000,  0x1,  0x0,  0x8,  0xc0000000,  0x1a01,  0x0,  0xc,  0x40000000,  0x801981,  0x0,  0x0,  0xc0000000,  0x801dc6,  0x0,  0xe,  0x0,  0x1e,  0x0,  0xc,  0x40000000,  0x600d9f,  0x0,  0xc,  0xc0000000,  0x801dc1,  0x0,  0xc,  0x0,  0xff038000,  0xc0000,  0x0,  0xc0000000,  0x0,  0x80000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x19021800,  0xc00000,  0x3f9c,  0x1c009f98,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0400000,  0x1bf,  0x0,  0x0,  0x1fb0e78,  0x0,  0x0,  0x0,  0xffff0000,  0x301,  0x0,  0x6000000,  0x0,  0xa00000,  0x7e01a,  0x0,  0x0,  0x0,  0x0,  0x10,  0xe8200000,  0x1b,  0x0,  0x4,  0x34c2,  0x0,  0xc5c80,  0x0,  0x300ff0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0002,  0x0,  0xc000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x98,  0x0,  0x0,  0x3,  0xfff00000,  0xf,  0x0,  0x0,  0x0,  0xc0000,  0x0,  0x8,  0xec300000,  0x1,  0x0,  0x0,  0x198000,  0x2000,  0x8000000,  0x0,  0x0,  0x0,  0x20c800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16d8,  0x5,  0x0,  0x0,  0x0,  0x4,  0x1870000,  0x0,  0x0,  0x0,  0x1000,  0x0,  0x0,  0x4,  0x80380000,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40d000,  0x0,  0x0,  0x0,  0x0,  0xfffe0000,  0x7fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7e060,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2336 bytes
+enum graphemeExtendTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xe0], [ 0x100,  0x140,  0x2d00], [ 0x2020100,  0x4020302,  0x6020205,  0x2070202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020208,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xb000a,  0xd000c,  0xe,  0xf0000,  0x0,  0x100000,  0x120011,  0x140013,  0x160015,  0x0,  0x17,  0x0,  0x0,  0x0,  0x0,  0x0,  0x190018,  0x0,  0x1a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b,  0x1d001c,  0x1f001e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x220021,  0x230000,  0x0,  0x0,  0x0,  0x0,  0x24,  0x0,  0x0,  0x260025,  0x0,  0x0,  0x27,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x280000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x290000,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfffe0000,  0xbfffffff,  0xb6,  0x0,  0x7ff0000,  0x0,  0xfffff800,  0x10000,  0x0,  0x0,  0x9fc00000,  0x3d9f,  0x20000,  0xffff0000,  0x7ff,  0x0,  0x0,  0x1ffc0,  0x0,  0xff800,  0xfbc00000,  0x3eef,  0xe000000,  0x0,  0x0,  0x0,  0x0,  0x7ffffff0,  0x7,  0x14000000,  0xfe21fe,  0xc,  0x2,  0x50000000,  0x80201e,  0xc,  0x6,  0x10000000,  0x23986,  0x230000,  0x6,  0x10000000,  0x21be,  0xc,  0x2,  0xd0000000,  0xc0201e,  0xc,  0x4,  0x40000000,  0x802001,  0x0,  0x0,  0xc0000000,  0x603dc1,  0xc,  0x0,  0x90000000,  0x603044,  0xc,  0x0,  0x40000000,  0x80201e,  0xc,  0x0,  0x0,  0x805c8400,  0x0,  0x0,  0x7f20000,  0x7f80,  0x0,  0x0,  0x1bf20000,  0x3f00,  0x0,  0x3000000,  0x2a00000,  0x0,  0x7ffe0000,  0xfeffe0df,  0x1fffffff,  0x40,  0x0,  0x0,  0x66fde000,  0xc3000000,  0x1e0001,  0x20002064,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe0000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1c0000,  0x1c0000,  0xc0000,  0xc0000,  0x0,  0x3fb00000,  0x200ffe40,  0x0,  0x3800,  0x0,  0x0,  0x0,  0x0,  0x200,  0x0,  0x0,  0x0,  0xe040187,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9800000,  0x0,  0x7f400000,  0x9ff81fe5,  0x0,  0x0,  0x0,  0x0,  0xf,  0x17d00000,  0x4,  0xff800,  0x3,  0xb3c,  0x0,  0x3a340,  0x0,  0xcff000,  0x0,  0x0,  0x0,  0x0,  0xfff70000,  0x1021fd,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xf000007f,  0x3000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0x1ffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x38000,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x0,  0xffffffff,  0x0,  0xfc00,  0x0,  0x0,  0x6000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff78000,  0x80000000,  0x0,  0x0,  0x30000,  0x844,  0x60,  0x0,  0x0,  0x0,  0x0,  0x10,  0x3ffff,  0x0,  0x3fc0,  0x3ff80,  0x0,  0x7,  0x13c80000,  0x0,  0x0,  0x0,  0x667e00,  0x1008,  0x0,  0x0,  0xc19d0000,  0x2,  0x403000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2120,  0x40000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff,  0x7f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000000,  0xf06e,  0x87000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0xff000000,  0x7f,  0x0,  0x3,  0x6780000,  0x0,  0x0,  0x7,  0x1fef80,  0x0,  0x0,  0x3,  0x7fc00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbf2800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x78000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf807c3a0,  0xfe7,  0x3c00,  0x0,  0x0,  0x0,  0x0,  0x1c,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+
+}
+
diff --git a/libphobos/src/std/internal/unicode_norm.d b/libphobos/src/std/internal/unicode_norm.d
new file mode 100644
index 00000000000..7d142d8b654
--- /dev/null
+++ b/libphobos/src/std/internal/unicode_norm.d
@@ -0,0 +1,28 @@
+module std.internal.unicode_norm;
+import std.internal.unicode_tables;
+
+static if(size_t.sizeof == 8) {
+//1600 bytes
+enum nfcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x60], [ 0x100,  0x100,  0x1a00], [ 0x302020202020100,  0x205020202020204,  0x602020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1000000000000,  0x200000000,  0x5000400030000,  0x8000000070006,  0xa0009,  0x0,  0xb000000000000,  0xc000000000000,  0xf0000000e000d,  0x0,  0x1000000000,  0x0,  0x11,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x14001300120000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160015,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1800120012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10361f8081a9fdf,  0x401000000000003f,  0x80,  0x0,  0x0,  0x380000,  0x0,  0x0,  0x1000000000000000,  0xff000000,  0x4000000000000000,  0xb0800000,  0x48000000000000,  0x4e000000,  0x0,  0x0,  0x4000000000000000,  0x30c00000,  0x4000000000000000,  0x800000,  0x0,  0x400000,  0x0,  0x600004,  0x4000000000000000,  0x800000,  0x0,  0x80008400,  0x0,  0x168020010842008,  0x200108420080002,  0x0,  0x400000000000,  0x0,  0x0,  0x0,  0x0,  0x3ffffe00000000,  0xffffff0000000000,  0x7,  0x20000000000000,  0x0,  0x0,  0x0,  0x0,  0x2aaa000000000000,  0x4800000000000000,  0x2a00c80808080a00,  0x3,  0x0,  0x0,  0x0,  0xc4000000000,  0x0,  0x0,  0x0,  0x60000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x6000000,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffc657fe53fff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7ffc00a0000000,  0x7fdb,  0x0,  0x0,  0x0,  0x0,  0x400000000000000,  0x0,  0x8000000000,  0x0,  0x0,  0x0,  0x0,  0x1fc0000000,  0xf800000000000000,  0x1,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1920 bytes
+enum nfdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x70], [ 0x100,  0x140,  0x2000], [ 0x504030202020100,  0x207020202020206,  0x802020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x5000600050004,  0x9000800070005,  0xc0005000b000a,  0x500050005000d,  0x5000500050005,  0xe000500050005,  0x10000f00050005,  0x14001300120011,  0x5000500050005,  0x5001500050005,  0x5000500050005,  0x5000500050016,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x17001700170017,  0x18001700170017,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x1a001900170005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x50005001c001b,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x50005001d0005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5001e00170017,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x5000500050005,  0x0,  0x0,  0x0,  0xbe7effbf3e7effbf,  0x7ef1ff3ffffcffff,  0x7fffff3ffff3f1f8,  0x1800300000000,  0xff31ffcfdfffe000,  0xfffc0cfffffff,  0x0,  0x0,  0x0,  0x0,  0x401000000000001b,  0x1fc000001d7e0,  0x187c00,  0x20000000200708b,  0xc00000708b0000,  0x0,  0x33ffcfcfccf0006,  0x0,  0x0,  0x0,  0x0,  0x7c00000000,  0x0,  0x0,  0x80005,  0x12020000000000,  0xff000000,  0x0,  0xb0001800,  0x48000000000000,  0x4e000000,  0x0,  0x0,  0x0,  0x30001900,  0x100000,  0x1c00,  0x0,  0x100,  0x0,  0xd81,  0x0,  0x1c00,  0x0,  0x74000000,  0x0,  0x168020010842008,  0x200108420080002,  0x0,  0x4000000000,  0x0,  0x0,  0x0,  0x2800000000045540,  0xb,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0bffffff,  0x3ffffffffffffff,  0xffffffff3f3fffff,  0x3fffffffaaff3f3f,  0x5fdfffffffffffff,  0x3fdcffffefcfffde,  0x3,  0x0,  0x0,  0x0,  0xc4000000000,  0x0,  0x40000c000000,  0xe000,  0x5000001210,  0x333e00500000292,  0xf00000000333,  0x3c0f00000000,  0x60000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x0,  0x36db02a555555000,  0x5555500040100000,  0x4790000036db02a5,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffff,  0x0,  0xfffffc657fe53fff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7ffc00a0000000,  0x7fdb,  0x0,  0x0,  0x0,  0x0,  0x80014000000,  0x0,  0xc00000000000,  0x0,  0x0,  0x0,  0x0,  0x1fc0000000,  0xf800000000000000,  0x1,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2560 bytes
+enum nfkcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x70], [ 0x100,  0x140,  0x3400], [ 0x402030202020100,  0x706020202020205,  0x802020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x4000600050004,  0x9000800070004,  0xd000c000b000a,  0x40004000f000e,  0x4000400040004,  0x10000400040004,  0x13001200110004,  0x17001600150014,  0x4000400040018,  0x4001900040004,  0x1d001c001b001a,  0x210020001f001e,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x22000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x24002300210004,  0x27002600250021,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400290028,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x40004002a0004,  0x2e002d002c002b,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4002f00040004,  0x4003100300004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4003200210021,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x4000400040004,  0x0,  0x0,  0x773c850100000000,  0x0,  0x800c000000000000,  0x8000000000000201,  0x0,  0xe000000001ff0,  0x0,  0x0,  0x1ff000000000000,  0x1f3f000000,  0x10361f8081a9fdf,  0x441000000000003f,  0xb0,  0x2370000007f0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80,  0x0,  0x0,  0x1e0000000380000,  0x0,  0x0,  0x1000000000000000,  0xff000000,  0x4000000000000000,  0xb0800000,  0x48000000000000,  0x4e000000,  0x0,  0x0,  0x4000000000000000,  0x30c00000,  0x4000000000000000,  0x800000,  0x0,  0x400000,  0x0,  0x600004,  0x4000000000000000,  0x800000,  0x0,  0x80008400,  0x8000000000000,  0x0,  0x8000000000000,  0x30000000,  0x1000,  0x3e8020010842008,  0x200108420080002,  0x0,  0x400000000000,  0x0,  0x0,  0x1000000000000000,  0x0,  0x3ffffe00000000,  0xffffff0000000000,  0x7,  0x20000000000000,  0x0,  0x0,  0x0,  0xf7ff700000000000,  0x10007ffffffbfff,  0xfffffffff8000000,  0x0,  0x0,  0x0,  0xc000000,  0x0,  0x0,  0x2aaa000000000000,  0xe800000000000000,  0x6a00e808e808ea03,  0x50d88070008207ff,  0xfff3000080800380,  0x1001fff7fff,  0x0,  0xfbfbbd573e6ffeef,  0xffffffffffff03e1,  0x200,  0x0,  0x1b00000000000,  0x0,  0x0,  0x0,  0x60000000000,  0x0,  0x0,  0x0,  0x0,  0xffffffff00000000,  0xffffffffffffffff,  0x7ffffffffff,  0x1000,  0x70000000000000,  0x0,  0x10000000,  0x0,  0x3000000000000000,  0x0,  0x0,  0x0,  0x800000000000,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x8000000000000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffff,  0x740000000000001,  0x0,  0x9e000000,  0x8000000000000000,  0xfffe000000000000,  0xffffffffffffffff,  0xfffc7fff,  0x0,  0xffffffff7fffffff,  0x7fffffffffff00ff,  0xffffffffffffffff,  0x7fffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x0,  0x1000000000000,  0x0,  0x300000000000000,  0xfffffc657fe53fff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7fffffa0f8007f,  0xffffffffffffffdb,  0x3ffffffffffff,  0xfffffffffff80000,  0x3fffffffffffffff,  0xffffffffffff0000,  0xfffffffffffcffff,  0x1fff0000000000ff,  0xffff000003ff0000,  0xffd70f7ffff7ff9f,  0xffffffffffffffff,  0x1fffffffffffffff,  0xfffffffffffffffe,  0xffffffffffffffff,  0x7fffffffffffffff,  0x7f7f1cfcfcfc,  0x0,  0x0,  0x400000000000000,  0x0,  0x8000000000,  0x0,  0x0,  0x0,  0x0,  0x1fc0000000,  0xf800000000000000,  0x1,  0xffffffffffffffff,  0xffffffffffdfffff,  0xebffde64dfffffff,  0xffffffffffffffef,  0x7bffffffdfdfe7bf,  0xfffffffffffdfc5f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffff3fffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffcfff,  0xaf7fe96ffffffef,  0x5ef7f796aa96ea84,  0xffffbee0ffffbff,  0x0,  0xffff7fffffff07ff,  0xc000000ffff,  0x10000,  0x0,  0x7ffffffffff0007,  0x301ff,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2656 bytes
+enum nfkdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x78], [ 0x100,  0x160,  0x3500], [ 0x504030202020100,  0x807020202020206,  0x902020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x7000600050004,  0xa000900080007,  0xe000d000c000b,  0x700070007000f,  0x7000700070007,  0x10000700070007,  0x13001200110007,  0x17001600150014,  0x7000700070018,  0x7001900070007,  0x1d001c001b001a,  0x210020001f001e,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x22000700070007,  0x7000700070007,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x21002100210021,  0x23002100210021,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x25002400210007,  0x28002700260021,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x70007002a0029,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x70007002b0007,  0x2f002e002d002c,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7003000070007,  0x7003200310007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7003300210021,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x7000700070007,  0x0,  0x0,  0x773c850100000000,  0xbe7effbf3e7effbf,  0xfefdff3ffffcffff,  0xffffff3ffff3f3f9,  0x1800300000000,  0xff3fffcfdffffff0,  0xfffc0cfffffff,  0x0,  0x1ff000000000000,  0x1f3f000000,  0x0,  0x441000000000001b,  0x1fc000001d7f0,  0x2370000007f7c00,  0x20000000200708b,  0xc00000708b0000,  0x0,  0x33ffcfcfccf0006,  0x0,  0x0,  0x80,  0x0,  0x7c00000000,  0x1e0000000000000,  0x0,  0x80005,  0x0,  0x0,  0x0,  0x0,  0x12020000000000,  0xff000000,  0x0,  0xb0001800,  0x48000000000000,  0x4e000000,  0x0,  0x0,  0x0,  0x30001900,  0x100000,  0x1c00,  0x0,  0x100,  0x0,  0xd81,  0x0,  0x1c00,  0x0,  0x74000000,  0x8000000000000,  0x0,  0x8000000000000,  0x30000000,  0x1000,  0x3e8020010842008,  0x200108420080002,  0x0,  0x4000000000,  0x0,  0x0,  0x1000000000000000,  0x2800000000045540,  0xb,  0x0,  0x0,  0xf7ff700000000000,  0x10007ffffffbfff,  0xfffffffff8000000,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0fffffff,  0x3ffffffffffffff,  0xffffffff3f3fffff,  0x3fffffffaaff3f3f,  0xffdfffffffffffff,  0x7fdcffffefcfffdf,  0x50d88070008207ff,  0xfff3000080800380,  0x1001fff7fff,  0x0,  0xfbfbbd573e6ffeef,  0xffffffffffff03e1,  0x40000c000200,  0xe000,  0x1b05000001210,  0x333e00500000292,  0xf00000000333,  0x3c0f00000000,  0x60000000000,  0x0,  0x0,  0x0,  0x0,  0xffffffff00000000,  0xffffffffffffffff,  0x7ffffffffff,  0x1000,  0x70000000000000,  0x0,  0x10000000,  0x0,  0x3000000000000000,  0x0,  0x0,  0x0,  0x800000000000,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x8000000000000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffff,  0x740000000000001,  0x36db02a555555000,  0x55555000d8100000,  0xc790000036db02a5,  0xfffe000000000000,  0xffffffffffffffff,  0xfffc7fff,  0x0,  0xffffffff7fffffff,  0x7fffffffffff00ff,  0xffffffffffffffff,  0x7fffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x0,  0x1000000000000,  0x0,  0x300000000000000,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffff,  0x0,  0xfffffc657fe53fff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7fffffa0f8007f,  0xffffffffffffffdb,  0x3ffffffffffff,  0xfffffffffff80000,  0x3fffffffffffffff,  0xffffffffffff0000,  0xfffffffffffcffff,  0x1fff0000000000ff,  0xffff000003ff0000,  0xffd70f7ffff7ff9f,  0xffffffffffffffff,  0x1fffffffffffffff,  0xfffffffffffffffe,  0xffffffffffffffff,  0x7fffffffffffffff,  0x7f7f1cfcfcfc,  0x0,  0x0,  0x80014000000,  0x0,  0xc00000000000,  0x0,  0x0,  0x0,  0x0,  0x1fc0000000,  0xf800000000000000,  0x1,  0xffffffffffffffff,  0xffffffffffdfffff,  0xebffde64dfffffff,  0xffffffffffffffef,  0x7bffffffdfdfe7bf,  0xfffffffffffdfc5f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffff3fffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffcfff,  0xaf7fe96ffffffef,  0x5ef7f796aa96ea84,  0xffffbee0ffffbff,  0x0,  0xffff7fffffff07ff,  0xc000000ffff,  0x10000,  0x0,  0x7ffffffffff0007,  0x301ff,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+
+}
+
+
+static if(size_t.sizeof == 4) {
+//1600 bytes
+enum nfcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xc0], [ 0x100,  0x100,  0x1a00], [ 0x2020100,  0x3020202,  0x2020204,  0x2050202,  0x2020202,  0x6020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x2,  0x30000,  0x50004,  0x70006,  0x80000,  0xa0009,  0x0,  0x0,  0x0,  0x0,  0xb0000,  0x0,  0xc0000,  0xe000d,  0xf0000,  0x0,  0x0,  0x0,  0x10,  0x0,  0x0,  0x11,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x120000,  0x140013,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160015,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x120012,  0x18,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x81a9fdf,  0x10361f8,  0x3f,  0x40100000,  0x80,  0x0,  0x0,  0x0,  0x0,  0x0,  0x380000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0xff000000,  0x0,  0x0,  0x40000000,  0xb0800000,  0x0,  0x0,  0x480000,  0x4e000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40000000,  0x30c00000,  0x0,  0x0,  0x40000000,  0x800000,  0x0,  0x0,  0x0,  0x400000,  0x0,  0x0,  0x0,  0x600004,  0x0,  0x0,  0x40000000,  0x800000,  0x0,  0x0,  0x0,  0x80008400,  0x0,  0x0,  0x0,  0x10842008,  0x1680200,  0x20080002,  0x2001084,  0x0,  0x0,  0x0,  0x4000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ffffe,  0x0,  0xffffff00,  0x7,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2aaa0000,  0x0,  0x48000000,  0x8080a00,  0x2a00c808,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc40,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6000000,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fe53fff,  0xfffffc65,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xa0000000,  0x5f7ffc00,  0x7fdb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4000000,  0x0,  0x0,  0x0,  0x80,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x1f,  0x0,  0xf8000000,  0x1,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1920 bytes
+enum nfdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xe0], [ 0x100,  0x140,  0x2000], [ 0x2020100,  0x5040302,  0x2020206,  0x2070202,  0x2020202,  0x8020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x50006,  0x70005,  0x90008,  0xb000a,  0xc0005,  0x5000d,  0x50005,  0x50005,  0x50005,  0x50005,  0xe0005,  0x50005,  0x10000f,  0x120011,  0x140013,  0x50005,  0x50005,  0x50005,  0x50015,  0x50005,  0x50005,  0x50016,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x170017,  0x180017,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x170005,  0x1a0019,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x1c001b,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x1d0005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x170017,  0x5001e,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x50005,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3e7effbf,  0xbe7effbf,  0xfffcffff,  0x7ef1ff3f,  0xfff3f1f8,  0x7fffff3f,  0x0,  0x18003,  0xdfffe000,  0xff31ffcf,  0xcfffffff,  0xfffc0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b,  0x40100000,  0x1d7e0,  0x1fc00,  0x187c00,  0x0,  0x200708b,  0x2000000,  0x708b0000,  0xc00000,  0x0,  0x0,  0xfccf0006,  0x33ffcfc,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7c,  0x0,  0x0,  0x0,  0x0,  0x80005,  0x0,  0x0,  0x120200,  0xff000000,  0x0,  0x0,  0x0,  0xb0001800,  0x0,  0x0,  0x480000,  0x4e000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30001900,  0x0,  0x100000,  0x0,  0x1c00,  0x0,  0x0,  0x0,  0x100,  0x0,  0x0,  0x0,  0xd81,  0x0,  0x0,  0x0,  0x1c00,  0x0,  0x0,  0x0,  0x74000000,  0x0,  0x0,  0x0,  0x10842008,  0x1680200,  0x20080002,  0x2001084,  0x0,  0x0,  0x0,  0x40,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x45540,  0x28000000,  0xb,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xbffffff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x3f3fffff,  0xffffffff,  0xaaff3f3f,  0x3fffffff,  0xffffffff,  0x5fdfffff,  0xefcfffde,  0x3fdcffff,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc40,  0x0,  0x0,  0xc000000,  0x4000,  0xe000,  0x0,  0x1210,  0x50,  0x292,  0x333e005,  0x333,  0xf000,  0x0,  0x3c0f,  0x0,  0x600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x0,  0x55555000,  0x36db02a5,  0x40100000,  0x55555000,  0x36db02a5,  0x47900000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf,  0x0,  0x0,  0x7fe53fff,  0xfffffc65,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xa0000000,  0x5f7ffc00,  0x7fdb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x14000000,  0x800,  0x0,  0x0,  0x0,  0xc000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x1f,  0x0,  0xf8000000,  0x1,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2560 bytes
+enum nfkcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xe0], [ 0x100,  0x140,  0x3400], [ 0x2020100,  0x4020302,  0x2020205,  0x7060202,  0x2020202,  0x8020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x40006,  0x70004,  0x90008,  0xb000a,  0xd000c,  0xf000e,  0x40004,  0x40004,  0x40004,  0x40004,  0x100004,  0x110004,  0x130012,  0x150014,  0x170016,  0x40018,  0x40004,  0x40004,  0x40019,  0x1b001a,  0x1d001c,  0x1f001e,  0x210020,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x220004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x210004,  0x240023,  0x250021,  0x270026,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x290028,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x2a0004,  0x40004,  0x2c002b,  0x2e002d,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x4002f,  0x300004,  0x40031,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x210021,  0x40032,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x40004,  0x0,  0x0,  0x0,  0x0,  0x0,  0x773c8501,  0x0,  0x0,  0x0,  0x800c0000,  0x201,  0x80000000,  0x0,  0x0,  0x1ff0,  0xe0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ff0000,  0x3f000000,  0x1f,  0x81a9fdf,  0x10361f8,  0x3f,  0x44100000,  0xb0,  0x0,  0x7f0000,  0x2370000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80,  0x0,  0x0,  0x0,  0x0,  0x0,  0x380000,  0x1e00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0xff000000,  0x0,  0x0,  0x40000000,  0xb0800000,  0x0,  0x0,  0x480000,  0x4e000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40000000,  0x30c00000,  0x0,  0x0,  0x40000000,  0x800000,  0x0,  0x0,  0x0,  0x400000,  0x0,  0x0,  0x0,  0x600004,  0x0,  0x0,  0x40000000,  0x800000,  0x0,  0x0,  0x0,  0x80008400,  0x0,  0x0,  0x80000,  0x0,  0x0,  0x0,  0x80000,  0x30000000,  0x0,  0x1000,  0x0,  0x10842008,  0x3e80200,  0x20080002,  0x2001084,  0x0,  0x0,  0x0,  0x4000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x0,  0x3ffffe,  0x0,  0xffffff00,  0x7,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf7ff7000,  0xffffbfff,  0x10007ff,  0xf8000000,  0xffffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2aaa0000,  0x0,  0xe8000000,  0xe808ea03,  0x6a00e808,  0x8207ff,  0x50d88070,  0x80800380,  0xfff30000,  0x1fff7fff,  0x100,  0x0,  0x0,  0x3e6ffeef,  0xfbfbbd57,  0xffff03e1,  0xffffffff,  0x200,  0x0,  0x0,  0x0,  0x0,  0x1b000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7ff,  0x1000,  0x0,  0x0,  0x700000,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x0,  0x0,  0x30000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x80000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0x0,  0x1,  0x7400000,  0x0,  0x0,  0x9e000000,  0x0,  0x0,  0x80000000,  0x0,  0xfffe0000,  0xffffffff,  0xffffffff,  0xfffc7fff,  0x0,  0x0,  0x0,  0x7fffffff,  0xffffffff,  0xffff00ff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x3000000,  0x7fe53fff,  0xfffffc65,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xa0f8007f,  0x5f7fffff,  0xffffffdb,  0xffffffff,  0xffffffff,  0x3ffff,  0xfff80000,  0xffffffff,  0xffffffff,  0x3fffffff,  0xffff0000,  0xffffffff,  0xfffcffff,  0xffffffff,  0xff,  0x1fff0000,  0x3ff0000,  0xffff0000,  0xfff7ff9f,  0xffd70f7f,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fffffff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0x1cfcfcfc,  0x7f7f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4000000,  0x0,  0x0,  0x0,  0x80,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x1f,  0x0,  0xf8000000,  0x1,  0x0,  0xffffffff,  0xffffffff,  0xffdfffff,  0xffffffff,  0xdfffffff,  0xebffde64,  0xffffffef,  0xffffffff,  0xdfdfe7bf,  0x7bffffff,  0xfffdfc5f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffff3f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffcfff,  0xffffffff,  0xffffffef,  0xaf7fe96,  0xaa96ea84,  0x5ef7f796,  0xffffbff,  0xffffbee,  0x0,  0x0,  0xffff07ff,  0xffff7fff,  0xffff,  0xc00,  0x10000,  0x0,  0x0,  0x0,  0xffff0007,  0x7ffffff,  0x301ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2656 bytes
+enum nfkdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xf0], [ 0x100,  0x160,  0x3500], [ 0x2020100,  0x5040302,  0x2020206,  0x8070202,  0x2020202,  0x9020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x7000f,  0x70007,  0x70007,  0x70007,  0x70007,  0x100007,  0x110007,  0x130012,  0x150014,  0x170016,  0x70018,  0x70007,  0x70007,  0x70019,  0x1b001a,  0x1d001c,  0x1f001e,  0x210020,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x220007,  0x70007,  0x70007,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x210021,  0x230021,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x210007,  0x250024,  0x260021,  0x280027,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x2a0029,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x2b0007,  0x70007,  0x2d002c,  0x2f002e,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70030,  0x310007,  0x70032,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x210021,  0x70033,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x70007,  0x0,  0x0,  0x0,  0x0,  0x0,  0x773c8501,  0x3e7effbf,  0xbe7effbf,  0xfffcffff,  0xfefdff3f,  0xfff3f3f9,  0xffffff3f,  0x0,  0x18003,  0xdffffff0,  0xff3fffcf,  0xcfffffff,  0xfffc0,  0x0,  0x0,  0x0,  0x1ff0000,  0x3f000000,  0x1f,  0x0,  0x0,  0x1b,  0x44100000,  0x1d7f0,  0x1fc00,  0x7f7c00,  0x2370000,  0x200708b,  0x2000000,  0x708b0000,  0xc00000,  0x0,  0x0,  0xfccf0006,  0x33ffcfc,  0x0,  0x0,  0x0,  0x0,  0x80,  0x0,  0x0,  0x0,  0x0,  0x7c,  0x0,  0x1e00000,  0x0,  0x0,  0x80005,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x120200,  0xff000000,  0x0,  0x0,  0x0,  0xb0001800,  0x0,  0x0,  0x480000,  0x4e000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30001900,  0x0,  0x100000,  0x0,  0x1c00,  0x0,  0x0,  0x0,  0x100,  0x0,  0x0,  0x0,  0xd81,  0x0,  0x0,  0x0,  0x1c00,  0x0,  0x0,  0x0,  0x74000000,  0x0,  0x0,  0x80000,  0x0,  0x0,  0x0,  0x80000,  0x30000000,  0x0,  0x1000,  0x0,  0x10842008,  0x3e80200,  0x20080002,  0x2001084,  0x0,  0x0,  0x0,  0x40,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000000,  0x45540,  0x28000000,  0xb,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf7ff7000,  0xffffbfff,  0x10007ff,  0xf8000000,  0xffffffff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffffff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x3f3fffff,  0xffffffff,  0xaaff3f3f,  0x3fffffff,  0xffffffff,  0xffdfffff,  0xefcfffdf,  0x7fdcffff,  0x8207ff,  0x50d88070,  0x80800380,  0xfff30000,  0x1fff7fff,  0x100,  0x0,  0x0,  0x3e6ffeef,  0xfbfbbd57,  0xffff03e1,  0xffffffff,  0xc000200,  0x4000,  0xe000,  0x0,  0x1210,  0x1b050,  0x292,  0x333e005,  0x333,  0xf000,  0x0,  0x3c0f,  0x0,  0x600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7ff,  0x1000,  0x0,  0x0,  0x700000,  0x0,  0x0,  0x10000000,  0x0,  0x0,  0x0,  0x0,  0x30000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x80000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0x0,  0x1,  0x7400000,  0x55555000,  0x36db02a5,  0xd8100000,  0x55555000,  0x36db02a5,  0xc7900000,  0x0,  0xfffe0000,  0xffffffff,  0xffffffff,  0xfffc7fff,  0x0,  0x0,  0x0,  0x7fffffff,  0xffffffff,  0xffff00ff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x3000000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf,  0x0,  0x0,  0x7fe53fff,  0xfffffc65,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xa0f8007f,  0x5f7fffff,  0xffffffdb,  0xffffffff,  0xffffffff,  0x3ffff,  0xfff80000,  0xffffffff,  0xffffffff,  0x3fffffff,  0xffff0000,  0xffffffff,  0xfffcffff,  0xffffffff,  0xff,  0x1fff0000,  0x3ff0000,  0xffff0000,  0xfff7ff9f,  0xffd70f7f,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fffffff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0x1cfcfcfc,  0x7f7f,  0x0,  0x0,  0x0,  0x0,  0x14000000,  0x800,  0x0,  0x0,  0x0,  0xc000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x1f,  0x0,  0xf8000000,  0x1,  0x0,  0xffffffff,  0xffffffff,  0xffdfffff,  0xffffffff,  0xdfffffff,  0xebffde64,  0xffffffef,  0xffffffff,  0xdfdfe7bf,  0x7bffffff,  0xfffdfc5f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffff3f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffcfff,  0xffffffff,  0xffffffef,  0xaf7fe96,  0xaa96ea84,  0x5ef7f796,  0xffffbff,  0xffffbee,  0x0,  0x0,  0xffff07ff,  0xffff7fff,  0xffff,  0xc00,  0x10000,  0x0,  0x0,  0x0,  0xffff0007,  0x7ffffff,  0x301ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+
+}
+
diff --git a/libphobos/src/std/internal/unicode_tables.d b/libphobos/src/std/internal/unicode_tables.d
new file mode 100644
index 00000000000..38cc7239bf3
--- /dev/null
+++ b/libphobos/src/std/internal/unicode_tables.d
@@ -0,0 +1,2372 @@
+//Written in the D programming language
+/**
+ * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *
+ * Authors: Dmitry Olshansky
+ *
+ */
+//Automatically generated from Unicode Character Database files
+
+module std.internal.unicode_tables;
+@safe pure nothrow @nogc:
+
+
+struct SimpleCaseEntry
+{
+    uint ch;
+    ubyte n, bucket;// n - number in bucket
+
+pure nothrow @nogc:
+
+    @property ubyte size() const
+    {
+        return bucket & 0x3F;
+    }
+    @property auto isLower() const
+    {
+        return bucket & 0x40;
+    }
+    @property auto isUpper() const
+    {
+        return bucket & 0x80;
+    }
+}
+
+struct FullCaseEntry
+{
+    dchar[3] seq;
+    ubyte n, size;// n number in batch, size - size of batch
+    ubyte entry_len;
+
+    @property auto value() const @trusted pure nothrow @nogc return
+    {
+        return seq[0..entry_len];
+    }
+}
+
+struct CompEntry
+{
+    dchar rhs, composed;
+}
+
+struct UnicodeProperty
+{
+    string name;
+    ubyte[] compressed;
+}
+
+struct TrieEntry(T...)
+{
+    size_t[] offsets;
+    size_t[] sizes;
+    size_t[] data;
+}
+
+@property immutable(SimpleCaseEntry[]) simpleCaseTable()
+{
+alias SCE = SimpleCaseEntry;
+static immutable SCE[] t = [
+SCE(0x2c00, 0, 0x82),
+SCE(0x2c30, 1, 0x42),SCE(0x24c3, 0, 0x82),SCE(0x24dd, 1, 0x42),SCE(0x2c01, 0, 0x82),
+SCE(0x2c31, 1, 0x42),SCE(0x2c1d, 0, 0x82),SCE(0x2c4d, 1, 0x42),SCE(0x2c02, 0, 0x82),
+SCE(0x2c32, 1, 0x42),SCE(0x2c03, 0, 0x82),SCE(0x2c33, 1, 0x42),SCE(0x2c04, 0, 0x82),
+SCE(0x2c34, 1, 0x42),SCE(0x2c05, 0, 0x82),SCE(0x2c35, 1, 0x42),SCE(0x2c06, 0, 0x82),
+SCE(0x2c36, 1, 0x42),SCE(0x10400, 0, 0x82),SCE(0x10428, 1, 0x42),SCE(0x2cc2, 0, 0x82),
+SCE(0x2cc3, 1, 0x42),SCE(0x2c07, 0, 0x82),SCE(0x2c37, 1, 0x42),SCE(0x2c08, 0, 0x82),
+SCE(0x2c38, 1, 0x42),SCE(0x2c09, 0, 0x82),SCE(0x2c39, 1, 0x42),SCE(0x2c0a, 0, 0x82),
+SCE(0x2c3a, 1, 0x42),SCE(0xa68c, 0, 0x82),SCE(0xa68d, 1, 0x42),SCE(0x0041, 0, 0x82),
+SCE(0x0061, 1, 0x42),SCE(0x0042, 0, 0x82),SCE(0x0062, 1, 0x42),SCE(0x0043, 0, 0x82),
+SCE(0x0063, 1, 0x42),SCE(0x0044, 0, 0x82),SCE(0x0064, 1, 0x42),SCE(0x0045, 0, 0x82),
+SCE(0x0065, 1, 0x42),SCE(0x0046, 0, 0x82),SCE(0x0066, 1, 0x42),SCE(0x0047, 0, 0x82),
+SCE(0x0067, 1, 0x42),SCE(0x0048, 0, 0x82),SCE(0x0068, 1, 0x42),SCE(0x0049, 0, 0x82),
+SCE(0x0069, 1, 0x42),SCE(0x004a, 0, 0x82),SCE(0x006a, 1, 0x42),SCE(0x004b, 0, 0x83),
+SCE(0x006b, 1, 0x43),SCE(0x212a, 2, 0x83),SCE(0x004c, 0, 0x82),SCE(0x006c, 1, 0x42),
+SCE(0x004d, 0, 0x82),SCE(0x006d, 1, 0x42),SCE(0x004e, 0, 0x82),SCE(0x006e, 1, 0x42),
+SCE(0x004f, 0, 0x82),SCE(0x006f, 1, 0x42),SCE(0x0050, 0, 0x82),SCE(0x0070, 1, 0x42),
+SCE(0x0051, 0, 0x82),SCE(0x0071, 1, 0x42),SCE(0x0052, 0, 0x82),SCE(0x0072, 1, 0x42),
+SCE(0x0053, 0, 0x83),SCE(0x0073, 1, 0x43),SCE(0x017f, 2, 0x43),SCE(0x0054, 0, 0x82),
+SCE(0x0074, 1, 0x42),SCE(0x0055, 0, 0x82),SCE(0x0075, 1, 0x42),SCE(0x0056, 0, 0x82),
+SCE(0x0076, 1, 0x42),SCE(0x0057, 0, 0x82),SCE(0x0077, 1, 0x42),SCE(0x0058, 0, 0x82),
+SCE(0x0078, 1, 0x42),SCE(0x0059, 0, 0x82),SCE(0x0079, 1, 0x42),SCE(0x005a, 0, 0x82),
+SCE(0x007a, 1, 0x42),SCE(0x2c0f, 0, 0x82),SCE(0x2c3f, 1, 0x42),SCE(0x2c10, 0, 0x82),
+SCE(0x2c40, 1, 0x42),SCE(0x10402, 0, 0x82),SCE(0x1042a, 1, 0x42),SCE(0x2cc4, 0, 0x82),
+SCE(0x2cc5, 1, 0x42),SCE(0x2166, 0, 0x82),SCE(0x2176, 1, 0x42),SCE(0x2c11, 0, 0x82),
+SCE(0x2c41, 1, 0x42),SCE(0x2c12, 0, 0x82),SCE(0x2c42, 1, 0x42),SCE(0x2168, 0, 0x82),
+SCE(0x2178, 1, 0x42),SCE(0x2c13, 0, 0x82),SCE(0x2c43, 1, 0x42),SCE(0xa682, 0, 0x82),
+SCE(0xa683, 1, 0x42),SCE(0x2c14, 0, 0x82),SCE(0x2c44, 1, 0x42),SCE(0x216a, 0, 0x82),
+SCE(0x217a, 1, 0x42),SCE(0x24c7, 0, 0x82),SCE(0x24e1, 1, 0x42),SCE(0x2c15, 0, 0x82),
+SCE(0x2c45, 1, 0x42),SCE(0x10403, 0, 0x82),SCE(0x1042b, 1, 0x42),SCE(0x2c16, 0, 0x82),
+SCE(0x2c46, 1, 0x42),SCE(0x216c, 0, 0x82),SCE(0x217c, 1, 0x42),SCE(0x2c17, 0, 0x82),
+SCE(0x2c47, 1, 0x42),SCE(0xff38, 0, 0x82),SCE(0xff58, 1, 0x42),SCE(0x2c18, 0, 0x82),
+SCE(0x2c48, 1, 0x42),SCE(0x216e, 0, 0x82),SCE(0x217e, 1, 0x42),SCE(0x2c19, 0, 0x82),
+SCE(0x2c49, 1, 0x42),SCE(0x2c1a, 0, 0x82),SCE(0x2c4a, 1, 0x42),SCE(0x2c1e, 0, 0x82),
+SCE(0x2c4e, 1, 0x42),SCE(0x10a0, 0, 0x82),SCE(0x2d00, 1, 0x42),SCE(0x10a1, 0, 0x82),
+SCE(0x2d01, 1, 0x42),SCE(0x10a2, 0, 0x82),SCE(0x2d02, 1, 0x42),SCE(0x10a3, 0, 0x82),
+SCE(0x2d03, 1, 0x42),SCE(0x10a4, 0, 0x82),SCE(0x2d04, 1, 0x42),SCE(0x10a5, 0, 0x82),
+SCE(0x2d05, 1, 0x42),SCE(0x10a6, 0, 0x82),SCE(0x2d06, 1, 0x42),SCE(0x10a7, 0, 0x82),
+SCE(0x2d07, 1, 0x42),SCE(0x10a8, 0, 0x82),SCE(0x2d08, 1, 0x42),SCE(0x10a9, 0, 0x82),
+SCE(0x2d09, 1, 0x42),SCE(0x10aa, 0, 0x82),SCE(0x2d0a, 1, 0x42),SCE(0x10ab, 0, 0x82),
+SCE(0x2d0b, 1, 0x42),SCE(0x10ac, 0, 0x82),SCE(0x2d0c, 1, 0x42),SCE(0x10ad, 0, 0x82),
+SCE(0x2d0d, 1, 0x42),SCE(0x10ae, 0, 0x82),SCE(0x2d0e, 1, 0x42),SCE(0x10af, 0, 0x82),
+SCE(0x2d0f, 1, 0x42),SCE(0x10b0, 0, 0x82),SCE(0x2d10, 1, 0x42),SCE(0x10b1, 0, 0x82),
+SCE(0x2d11, 1, 0x42),SCE(0x10b2, 0, 0x82),SCE(0x2d12, 1, 0x42),SCE(0x10b3, 0, 0x82),
+SCE(0x2d13, 1, 0x42),SCE(0x10b4, 0, 0x82),SCE(0x2d14, 1, 0x42),SCE(0x10b5, 0, 0x82),
+SCE(0x2d15, 1, 0x42),SCE(0x10b6, 0, 0x82),SCE(0x2d16, 1, 0x42),SCE(0x10b7, 0, 0x82),
+SCE(0x2d17, 1, 0x42),SCE(0x10b8, 0, 0x82),SCE(0x2d18, 1, 0x42),SCE(0x10b9, 0, 0x82),
+SCE(0x2d19, 1, 0x42),SCE(0x10ba, 0, 0x82),SCE(0x2d1a, 1, 0x42),SCE(0x10bb, 0, 0x82),
+SCE(0x2d1b, 1, 0x42),SCE(0x10bc, 0, 0x82),SCE(0x2d1c, 1, 0x42),SCE(0x10bd, 0, 0x82),
+SCE(0x2d1d, 1, 0x42),SCE(0x10be, 0, 0x82),SCE(0x2d1e, 1, 0x42),SCE(0x10bf, 0, 0x82),
+SCE(0x2d1f, 1, 0x42),SCE(0x00c0, 0, 0x82),SCE(0x00e0, 1, 0x42),SCE(0x00c1, 0, 0x82),
+SCE(0x00e1, 1, 0x42),SCE(0x10c2, 0, 0x82),SCE(0x2d22, 1, 0x42),SCE(0x00c3, 0, 0x82),
+SCE(0x00e3, 1, 0x42),SCE(0x10c4, 0, 0x82),SCE(0x2d24, 1, 0x42),SCE(0x00c5, 0, 0x83),
+SCE(0x00e5, 1, 0x43),SCE(0x212b, 2, 0x83),SCE(0x00c6, 0, 0x82),SCE(0x00e6, 1, 0x42),
+SCE(0x00c7, 0, 0x82),SCE(0x00e7, 1, 0x42),SCE(0x00c8, 0, 0x82),SCE(0x00e8, 1, 0x42),
+SCE(0x00c9, 0, 0x82),SCE(0x00e9, 1, 0x42),SCE(0x00ca, 0, 0x82),SCE(0x00ea, 1, 0x42),
+SCE(0x00cb, 0, 0x82),SCE(0x00eb, 1, 0x42),SCE(0x00cc, 0, 0x82),SCE(0x00ec, 1, 0x42),
+SCE(0x00cd, 0, 0x82),SCE(0x00ed, 1, 0x42),SCE(0x00ce, 0, 0x82),SCE(0x00ee, 1, 0x42),
+SCE(0x00cf, 0, 0x82),SCE(0x00ef, 1, 0x42),SCE(0x00d0, 0, 0x82),SCE(0x00f0, 1, 0x42),
+SCE(0x00d1, 0, 0x82),SCE(0x00f1, 1, 0x42),SCE(0x00d2, 0, 0x82),SCE(0x00f2, 1, 0x42),
+SCE(0x00d3, 0, 0x82),SCE(0x00f3, 1, 0x42),SCE(0x00d4, 0, 0x82),SCE(0x00f4, 1, 0x42),
+SCE(0x00d5, 0, 0x82),SCE(0x00f5, 1, 0x42),SCE(0x00d6, 0, 0x82),SCE(0x00f6, 1, 0x42),
+SCE(0x00d8, 0, 0x82),SCE(0x00f8, 1, 0x42),SCE(0x00d9, 0, 0x82),SCE(0x00f9, 1, 0x42),
+SCE(0x00da, 0, 0x82),SCE(0x00fa, 1, 0x42),SCE(0x00db, 0, 0x82),SCE(0x00fb, 1, 0x42),
+SCE(0x00dc, 0, 0x82),SCE(0x00fc, 1, 0x42),SCE(0x00dd, 0, 0x82),SCE(0x00fd, 1, 0x42),
+SCE(0x00de, 0, 0x82),SCE(0x00fe, 1, 0x42),SCE(0x2c25, 0, 0x82),SCE(0x2c55, 1, 0x42),
+SCE(0x2c26, 0, 0x82),SCE(0x2c56, 1, 0x42),SCE(0x2c27, 0, 0x82),SCE(0x2c57, 1, 0x42),
+SCE(0x2c28, 0, 0x82),SCE(0x2c58, 1, 0x42),SCE(0x1040f, 0, 0x82),SCE(0x10437, 1, 0x42),
+SCE(0x24cb, 0, 0x82),SCE(0x24e5, 1, 0x42),SCE(0x2c29, 0, 0x82),SCE(0x2c59, 1, 0x42),
+SCE(0x10407, 0, 0x82),SCE(0x1042f, 1, 0x42),SCE(0x2c2a, 0, 0x82),SCE(0x2c5a, 1, 0x42),
+SCE(0x0100, 0, 0x82),SCE(0x0101, 1, 0x42),SCE(0x0102, 0, 0x82),SCE(0x0103, 1, 0x42),
+SCE(0x2c2b, 0, 0x82),SCE(0x2c5b, 1, 0x42),SCE(0x0104, 0, 0x82),SCE(0x0105, 1, 0x42),
+SCE(0x0106, 0, 0x82),SCE(0x0107, 1, 0x42),SCE(0x0108, 0, 0x82),SCE(0x0109, 1, 0x42),
+SCE(0x2c2c, 0, 0x82),SCE(0x2c5c, 1, 0x42),SCE(0x010a, 0, 0x82),SCE(0x010b, 1, 0x42),
+SCE(0x010c, 0, 0x82),SCE(0x010d, 1, 0x42),SCE(0x010e, 0, 0x82),SCE(0x010f, 1, 0x42),
+SCE(0x2c2d, 0, 0x82),SCE(0x2c5d, 1, 0x42),SCE(0x0110, 0, 0x82),SCE(0x0111, 1, 0x42),
+SCE(0x0112, 0, 0x82),SCE(0x0113, 1, 0x42),SCE(0x0114, 0, 0x82),SCE(0x0115, 1, 0x42),
+SCE(0x2c2e, 0, 0x82),SCE(0x2c5e, 1, 0x42),SCE(0x0116, 0, 0x82),SCE(0x0117, 1, 0x42),
+SCE(0x0118, 0, 0x82),SCE(0x0119, 1, 0x42),SCE(0x011a, 0, 0x82),SCE(0x011b, 1, 0x42),
+SCE(0x011c, 0, 0x82),SCE(0x011d, 1, 0x42),SCE(0x011e, 0, 0x82),SCE(0x011f, 1, 0x42),
+SCE(0x0120, 0, 0x82),SCE(0x0121, 1, 0x42),SCE(0x0122, 0, 0x82),SCE(0x0123, 1, 0x42),
+SCE(0x0124, 0, 0x82),SCE(0x0125, 1, 0x42),SCE(0x0126, 0, 0x82),SCE(0x0127, 1, 0x42),
+SCE(0x0128, 0, 0x82),SCE(0x0129, 1, 0x42),SCE(0x012a, 0, 0x82),SCE(0x012b, 1, 0x42),
+SCE(0x00c5, 0, 0x83),SCE(0x00e5, 1, 0x43),SCE(0x212b, 2, 0x83),SCE(0x012c, 0, 0x82),
+SCE(0x012d, 1, 0x42),SCE(0x012e, 0, 0x82),SCE(0x012f, 1, 0x42),SCE(0x0132, 0, 0x82),
+SCE(0x0133, 1, 0x42),SCE(0x0134, 0, 0x82),SCE(0x0135, 1, 0x42),SCE(0x0136, 0, 0x82),
+SCE(0x0137, 1, 0x42),SCE(0x0139, 0, 0x82),SCE(0x013a, 1, 0x42),SCE(0x013b, 0, 0x82),
+SCE(0x013c, 1, 0x42),SCE(0x2cde, 0, 0x82),SCE(0x2cdf, 1, 0x42),SCE(0x013d, 0, 0x82),
+SCE(0x013e, 1, 0x42),SCE(0x013f, 0, 0x82),SCE(0x0140, 1, 0x42),SCE(0x0141, 0, 0x82),
+SCE(0x0142, 1, 0x42),SCE(0x0143, 0, 0x82),SCE(0x0144, 1, 0x42),SCE(0x0145, 0, 0x82),
+SCE(0x0146, 1, 0x42),SCE(0x0147, 0, 0x82),SCE(0x0148, 1, 0x42),SCE(0x014a, 0, 0x82),
+SCE(0x014b, 1, 0x42),SCE(0x014c, 0, 0x82),SCE(0x014d, 1, 0x42),SCE(0x014e, 0, 0x82),
+SCE(0x014f, 1, 0x42),SCE(0x0150, 0, 0x82),SCE(0x0151, 1, 0x42),SCE(0x0152, 0, 0x82),
+SCE(0x0153, 1, 0x42),SCE(0x0154, 0, 0x82),SCE(0x0155, 1, 0x42),SCE(0x0156, 0, 0x82),
+SCE(0x0157, 1, 0x42),SCE(0x0158, 0, 0x82),SCE(0x0159, 1, 0x42),SCE(0x015a, 0, 0x82),
+SCE(0x015b, 1, 0x42),SCE(0x015c, 0, 0x82),SCE(0x015d, 1, 0x42),SCE(0x015e, 0, 0x82),
+SCE(0x015f, 1, 0x42),SCE(0x0160, 0, 0x82),SCE(0x0161, 1, 0x42),SCE(0x2161, 0, 0x82),
+SCE(0x2171, 1, 0x42),SCE(0x0162, 0, 0x82),SCE(0x0163, 1, 0x42),SCE(0x2163, 0, 0x82),
+SCE(0x2173, 1, 0x42),SCE(0x0164, 0, 0x82),SCE(0x0165, 1, 0x42),SCE(0x2165, 0, 0x82),
+SCE(0x2175, 1, 0x42),SCE(0x0166, 0, 0x82),SCE(0x0167, 1, 0x42),SCE(0x2167, 0, 0x82),
+SCE(0x2177, 1, 0x42),SCE(0x0168, 0, 0x82),SCE(0x0169, 1, 0x42),SCE(0x2169, 0, 0x82),
+SCE(0x2179, 1, 0x42),SCE(0x016a, 0, 0x82),SCE(0x016b, 1, 0x42),SCE(0x216b, 0, 0x82),
+SCE(0x217b, 1, 0x42),SCE(0x016c, 0, 0x82),SCE(0x016d, 1, 0x42),SCE(0x216d, 0, 0x82),
+SCE(0x217d, 1, 0x42),SCE(0x016e, 0, 0x82),SCE(0x016f, 1, 0x42),SCE(0x216f, 0, 0x82),
+SCE(0x217f, 1, 0x42),SCE(0x0170, 0, 0x82),SCE(0x0171, 1, 0x42),SCE(0x2ccc, 0, 0x82),
+SCE(0x2ccd, 1, 0x42),SCE(0x0172, 0, 0x82),SCE(0x0173, 1, 0x42),SCE(0x0174, 0, 0x82),
+SCE(0x0175, 1, 0x42),SCE(0x0176, 0, 0x82),SCE(0x0177, 1, 0x42),SCE(0x00ff, 0, 0x42),
+SCE(0x0178, 1, 0x82),SCE(0x0179, 0, 0x82),SCE(0x017a, 1, 0x42),SCE(0x017b, 0, 0x82),
+SCE(0x017c, 1, 0x42),SCE(0x017d, 0, 0x82),SCE(0x017e, 1, 0x42),SCE(0x0053, 0, 0x83),
+SCE(0x0073, 1, 0x43),SCE(0x017f, 2, 0x43),SCE(0x0181, 0, 0x82),SCE(0x0253, 1, 0x42),
+SCE(0x0182, 0, 0x82),SCE(0x0183, 1, 0x42),SCE(0x2183, 0, 0x82),SCE(0x2184, 1, 0x42),
+SCE(0x0184, 0, 0x82),SCE(0x0185, 1, 0x42),SCE(0x0186, 0, 0x82),SCE(0x0254, 1, 0x42),
+SCE(0x0187, 0, 0x82),SCE(0x0188, 1, 0x42),SCE(0x0189, 0, 0x82),SCE(0x0256, 1, 0x42),
+SCE(0x018a, 0, 0x82),SCE(0x0257, 1, 0x42),SCE(0x018b, 0, 0x82),SCE(0x018c, 1, 0x42),
+SCE(0x018e, 0, 0x82),SCE(0x01dd, 1, 0x42),SCE(0x018f, 0, 0x82),SCE(0x0259, 1, 0x42),
+SCE(0x0190, 0, 0x82),SCE(0x025b, 1, 0x42),SCE(0x0191, 0, 0x82),SCE(0x0192, 1, 0x42),
+SCE(0x0193, 0, 0x82),SCE(0x0260, 1, 0x42),SCE(0x0194, 0, 0x82),SCE(0x0263, 1, 0x42),
+SCE(0x0196, 0, 0x82),SCE(0x0269, 1, 0x42),SCE(0x0197, 0, 0x82),SCE(0x0268, 1, 0x42),
+SCE(0x0198, 0, 0x82),SCE(0x0199, 1, 0x42),SCE(0x019c, 0, 0x82),SCE(0x026f, 1, 0x42),
+SCE(0x019d, 0, 0x82),SCE(0x0272, 1, 0x42),SCE(0x019f, 0, 0x82),SCE(0x0275, 1, 0x42),
+SCE(0x01a0, 0, 0x82),SCE(0x01a1, 1, 0x42),SCE(0x01a2, 0, 0x82),SCE(0x01a3, 1, 0x42),
+SCE(0x01a4, 0, 0x82),SCE(0x01a5, 1, 0x42),SCE(0x01a6, 0, 0x82),SCE(0x0280, 1, 0x42),
+SCE(0x01a7, 0, 0x82),SCE(0x01a8, 1, 0x42),SCE(0x01a9, 0, 0x82),SCE(0x0283, 1, 0x42),
+SCE(0x01ac, 0, 0x82),SCE(0x01ad, 1, 0x42),SCE(0x01ae, 0, 0x82),SCE(0x0288, 1, 0x42),
+SCE(0x01af, 0, 0x82),SCE(0x01b0, 1, 0x42),SCE(0x01b1, 0, 0x82),SCE(0x028a, 1, 0x42),
+SCE(0x01b2, 0, 0x82),SCE(0x028b, 1, 0x42),SCE(0x01b3, 0, 0x82),SCE(0x01b4, 1, 0x42),
+SCE(0x01b5, 0, 0x82),SCE(0x01b6, 1, 0x42),SCE(0x01b7, 0, 0x82),SCE(0x0292, 1, 0x42),
+SCE(0x01b8, 0, 0x82),SCE(0x01b9, 1, 0x42),SCE(0x01bc, 0, 0x82),SCE(0x01bd, 1, 0x42),
+SCE(0x01c4, 0, 0x83),SCE(0x01c5, 1, 0x3),SCE(0x01c6, 2, 0x43),SCE(0x01c4, 0, 0x83),
+SCE(0x01c5, 1, 0x3),SCE(0x01c6, 2, 0x43),SCE(0x01c7, 0, 0x83),SCE(0x01c8, 1, 0x3),
+SCE(0x01c9, 2, 0x43),SCE(0x01c7, 0, 0x83),SCE(0x01c8, 1, 0x3),SCE(0x01c9, 2, 0x43),
+SCE(0x01ca, 0, 0x83),SCE(0x01cb, 1, 0x3),SCE(0x01cc, 2, 0x43),SCE(0x01ca, 0, 0x83),
+SCE(0x01cb, 1, 0x3),SCE(0x01cc, 2, 0x43),SCE(0x01cd, 0, 0x82),SCE(0x01ce, 1, 0x42),
+SCE(0x01cf, 0, 0x82),SCE(0x01d0, 1, 0x42),SCE(0x01d1, 0, 0x82),SCE(0x01d2, 1, 0x42),
+SCE(0x01d3, 0, 0x82),SCE(0x01d4, 1, 0x42),SCE(0x01d5, 0, 0x82),SCE(0x01d6, 1, 0x42),
+SCE(0x01d7, 0, 0x82),SCE(0x01d8, 1, 0x42),SCE(0x01d9, 0, 0x82),SCE(0x01da, 1, 0x42),
+SCE(0x01db, 0, 0x82),SCE(0x01dc, 1, 0x42),SCE(0x01de, 0, 0x82),SCE(0x01df, 1, 0x42),
+SCE(0xff36, 0, 0x82),SCE(0xff56, 1, 0x42),SCE(0x01e0, 0, 0x82),SCE(0x01e1, 1, 0x42),
+SCE(0x01e2, 0, 0x82),SCE(0x01e3, 1, 0x42),SCE(0x01e4, 0, 0x82),SCE(0x01e5, 1, 0x42),
+SCE(0x01e6, 0, 0x82),SCE(0x01e7, 1, 0x42),SCE(0x01e8, 0, 0x82),SCE(0x01e9, 1, 0x42),
+SCE(0x01ea, 0, 0x82),SCE(0x01eb, 1, 0x42),SCE(0x01ec, 0, 0x82),SCE(0x01ed, 1, 0x42),
+SCE(0x01ee, 0, 0x82),SCE(0x01ef, 1, 0x42),SCE(0x01f1, 0, 0x83),SCE(0x01f2, 1, 0x3),
+SCE(0x01f3, 2, 0x43),SCE(0x01f1, 0, 0x83),SCE(0x01f2, 1, 0x3),SCE(0x01f3, 2, 0x43),
+SCE(0x01f4, 0, 0x82),SCE(0x01f5, 1, 0x42),SCE(0x0195, 0, 0x42),SCE(0x01f6, 1, 0x82),
+SCE(0x01bf, 0, 0x42),SCE(0x01f7, 1, 0x82),SCE(0x01f8, 0, 0x82),SCE(0x01f9, 1, 0x42),
+SCE(0x1041d, 0, 0x82),SCE(0x10445, 1, 0x42),SCE(0x01fa, 0, 0x82),SCE(0x01fb, 1, 0x42),
+SCE(0x01fc, 0, 0x82),SCE(0x01fd, 1, 0x42),SCE(0x01fe, 0, 0x82),SCE(0x01ff, 1, 0x42),
+SCE(0x0200, 0, 0x82),SCE(0x0201, 1, 0x42),SCE(0x0202, 0, 0x82),SCE(0x0203, 1, 0x42),
+SCE(0x0204, 0, 0x82),SCE(0x0205, 1, 0x42),SCE(0x0206, 0, 0x82),SCE(0x0207, 1, 0x42),
+SCE(0x0208, 0, 0x82),SCE(0x0209, 1, 0x42),SCE(0x020a, 0, 0x82),SCE(0x020b, 1, 0x42),
+SCE(0x020c, 0, 0x82),SCE(0x020d, 1, 0x42),SCE(0x020e, 0, 0x82),SCE(0x020f, 1, 0x42),
+SCE(0x0210, 0, 0x82),SCE(0x0211, 1, 0x42),SCE(0x0212, 0, 0x82),SCE(0x0213, 1, 0x42),
+SCE(0x0214, 0, 0x82),SCE(0x0215, 1, 0x42),SCE(0x0216, 0, 0x82),SCE(0x0217, 1, 0x42),
+SCE(0x0218, 0, 0x82),SCE(0x0219, 1, 0x42),SCE(0x021a, 0, 0x82),SCE(0x021b, 1, 0x42),
+SCE(0x021c, 0, 0x82),SCE(0x021d, 1, 0x42),SCE(0x021e, 0, 0x82),SCE(0x021f, 1, 0x42),
+SCE(0x019e, 0, 0x42),SCE(0x0220, 1, 0x82),SCE(0x0222, 0, 0x82),SCE(0x0223, 1, 0x42),
+SCE(0x0224, 0, 0x82),SCE(0x0225, 1, 0x42),SCE(0x0226, 0, 0x82),SCE(0x0227, 1, 0x42),
+SCE(0x0228, 0, 0x82),SCE(0x0229, 1, 0x42),SCE(0x022a, 0, 0x82),SCE(0x022b, 1, 0x42),
+SCE(0x022c, 0, 0x82),SCE(0x022d, 1, 0x42),SCE(0x022e, 0, 0x82),SCE(0x022f, 1, 0x42),
+SCE(0x0230, 0, 0x82),SCE(0x0231, 1, 0x42),SCE(0x0232, 0, 0x82),SCE(0x0233, 1, 0x42),
+SCE(0xa684, 0, 0x82),SCE(0xa685, 1, 0x42),SCE(0x023a, 0, 0x82),SCE(0x2c65, 1, 0x42),
+SCE(0x023b, 0, 0x82),SCE(0x023c, 1, 0x42),SCE(0x019a, 0, 0x42),SCE(0x023d, 1, 0x82),
+SCE(0x023e, 0, 0x82),SCE(0x2c66, 1, 0x42),SCE(0x0241, 0, 0x82),SCE(0x0242, 1, 0x42),
+SCE(0x10412, 0, 0x82),SCE(0x1043a, 1, 0x42),SCE(0x0180, 0, 0x42),SCE(0x0243, 1, 0x82),
+SCE(0x0244, 0, 0x82),SCE(0x0289, 1, 0x42),SCE(0x0245, 0, 0x82),SCE(0x028c, 1, 0x42),
+SCE(0x0246, 0, 0x82),SCE(0x0247, 1, 0x42),SCE(0x0248, 0, 0x82),SCE(0x0249, 1, 0x42),
+SCE(0x024a, 0, 0x82),SCE(0x024b, 1, 0x42),SCE(0x024c, 0, 0x82),SCE(0x024d, 1, 0x42),
+SCE(0x2c1b, 0, 0x82),SCE(0x2c4b, 1, 0x42),SCE(0x024e, 0, 0x82),SCE(0x024f, 1, 0x42),
+SCE(0x1040a, 0, 0x82),SCE(0x10432, 1, 0x42),SCE(0x2160, 0, 0x82),SCE(0x2170, 1, 0x42),
+SCE(0xa692, 0, 0x82),SCE(0xa693, 1, 0x42),SCE(0x027d, 0, 0x42),SCE(0x2c64, 1, 0x82),
+SCE(0x10410, 0, 0x82),SCE(0x10438, 1, 0x42),SCE(0x2c21, 0, 0x82),SCE(0x2c51, 1, 0x42),
+SCE(0x2c69, 0, 0x82),SCE(0x2c6a, 1, 0x42),SCE(0x10409, 0, 0x82),SCE(0x10431, 1, 0x42),
+SCE(0x10414, 0, 0x82),SCE(0x1043c, 1, 0x42),SCE(0x2162, 0, 0x82),SCE(0x2172, 1, 0x42),
+SCE(0x1041e, 0, 0x82),SCE(0x10446, 1, 0x42),SCE(0x0271, 0, 0x42),SCE(0x2c6e, 1, 0x82),
+SCE(0x10415, 0, 0x82),SCE(0x1043d, 1, 0x42),SCE(0x0252, 0, 0x42),SCE(0x2c70, 1, 0x82),
+SCE(0x2c72, 0, 0x82),SCE(0x2c73, 1, 0x42),SCE(0x2c0b, 0, 0x82),SCE(0x2c3b, 1, 0x42),
+SCE(0x10416, 0, 0x82),SCE(0x1043e, 1, 0x42),SCE(0x2c75, 0, 0x82),SCE(0x2c76, 1, 0x42),
+SCE(0x2164, 0, 0x82),SCE(0x2174, 1, 0x42),SCE(0xa640, 0, 0x82),SCE(0xa641, 1, 0x42),
+SCE(0xff22, 0, 0x82),SCE(0xff42, 1, 0x42),SCE(0x2c0c, 0, 0x82),SCE(0x2c3c, 1, 0x42),
+SCE(0x10417, 0, 0x82),SCE(0x1043f, 1, 0x42),SCE(0xff24, 0, 0x82),SCE(0xff44, 1, 0x42),
+SCE(0xff25, 0, 0x82),SCE(0xff45, 1, 0x42),SCE(0xff26, 0, 0x82),SCE(0xff46, 1, 0x42),
+SCE(0x2c0d, 0, 0x82),SCE(0x2c3d, 1, 0x42),SCE(0x24c1, 0, 0x82),SCE(0x24db, 1, 0x42),
+SCE(0xa728, 0, 0x82),SCE(0xa729, 1, 0x42),SCE(0x023f, 0, 0x42),SCE(0x2c7e, 1, 0x82),
+SCE(0x10411, 0, 0x82),SCE(0x10439, 1, 0x42),SCE(0xff29, 0, 0x82),SCE(0xff49, 1, 0x42),
+SCE(0x1040b, 0, 0x82),SCE(0x10433, 1, 0x42),SCE(0xa72a, 0, 0x82),SCE(0xa72b, 1, 0x42),
+SCE(0x2c80, 0, 0x82),SCE(0x2c81, 1, 0x42),SCE(0xff2b, 0, 0x82),SCE(0xff4b, 1, 0x42),
+SCE(0xa72c, 0, 0x82),SCE(0xa72d, 1, 0x42),SCE(0x2c0e, 0, 0x82),SCE(0x2c3e, 1, 0x42),
+SCE(0xff2d, 0, 0x82),SCE(0xff4d, 1, 0x42),SCE(0x10419, 0, 0x82),SCE(0x10441, 1, 0x42),
+SCE(0xa72e, 0, 0x82),SCE(0xa72f, 1, 0x42),SCE(0x1040d, 0, 0x82),SCE(0x10435, 1, 0x42),
+SCE(0xff2f, 0, 0x82),SCE(0xff4f, 1, 0x42),SCE(0xff31, 0, 0x82),SCE(0xff51, 1, 0x42),
+SCE(0xff32, 0, 0x82),SCE(0xff52, 1, 0x42),SCE(0x1041a, 0, 0x82),SCE(0x10442, 1, 0x42),
+SCE(0xff34, 0, 0x82),SCE(0xff54, 1, 0x42),SCE(0x2c98, 0, 0x82),SCE(0x2c99, 1, 0x42),
+SCE(0x2c8a, 0, 0x82),SCE(0x2c8b, 1, 0x42),SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84),
+SCE(0x03b9, 2, 0x44),SCE(0x1fbe, 3, 0x44),SCE(0x2c8c, 0, 0x82),SCE(0x2c8d, 1, 0x42),
+SCE(0xff37, 0, 0x82),SCE(0xff57, 1, 0x42),SCE(0xa656, 0, 0x82),SCE(0xa657, 1, 0x42),
+SCE(0x1041b, 0, 0x82),SCE(0x10443, 1, 0x42),SCE(0xa738, 0, 0x82),SCE(0xa739, 1, 0x42),
+SCE(0x2c8e, 0, 0x82),SCE(0x2c8f, 1, 0x42),SCE(0xff39, 0, 0x82),SCE(0xff59, 1, 0x42),
+SCE(0x10404, 0, 0x82),SCE(0x1042c, 1, 0x42),SCE(0xa73a, 0, 0x82),SCE(0xa73b, 1, 0x42),
+SCE(0x2c90, 0, 0x82),SCE(0x2c91, 1, 0x42),SCE(0xa73c, 0, 0x82),SCE(0xa73d, 1, 0x42),
+SCE(0x2c92, 0, 0x82),SCE(0x2c93, 1, 0x42),SCE(0x1041c, 0, 0x82),SCE(0x10444, 1, 0x42),
+SCE(0x0370, 0, 0x82),SCE(0x0371, 1, 0x42),SCE(0x0372, 0, 0x82),SCE(0x0373, 1, 0x42),
+SCE(0xa73e, 0, 0x82),SCE(0xa73f, 1, 0x42),SCE(0x0376, 0, 0x82),SCE(0x0377, 1, 0x42),
+SCE(0x2c94, 0, 0x82),SCE(0x2c95, 1, 0x42),SCE(0x2c96, 0, 0x82),SCE(0x2c97, 1, 0x42),
+SCE(0x0386, 0, 0x82),SCE(0x03ac, 1, 0x42),SCE(0x10405, 0, 0x82),SCE(0x1042d, 1, 0x42),
+SCE(0x0388, 0, 0x82),SCE(0x03ad, 1, 0x42),SCE(0x0389, 0, 0x82),SCE(0x03ae, 1, 0x42),
+SCE(0x038a, 0, 0x82),SCE(0x03af, 1, 0x42),SCE(0x038c, 0, 0x82),SCE(0x03cc, 1, 0x42),
+SCE(0x038e, 0, 0x82),SCE(0x03cd, 1, 0x42),SCE(0x038f, 0, 0x82),SCE(0x03ce, 1, 0x42),
+SCE(0x0391, 0, 0x82),SCE(0x03b1, 1, 0x42),SCE(0x0392, 0, 0x83),SCE(0x03b2, 1, 0x43),
+SCE(0x03d0, 2, 0x43),SCE(0x0393, 0, 0x82),SCE(0x03b3, 1, 0x42),SCE(0x0394, 0, 0x82),
+SCE(0x03b4, 1, 0x42),SCE(0x0395, 0, 0x83),SCE(0x03b5, 1, 0x43),SCE(0x03f5, 2, 0x43),
+SCE(0x0396, 0, 0x82),SCE(0x03b6, 1, 0x42),SCE(0x0397, 0, 0x82),SCE(0x03b7, 1, 0x42),
+SCE(0x0398, 0, 0x84),SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84),
+SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84),SCE(0x03b9, 2, 0x44),SCE(0x1fbe, 3, 0x44),
+SCE(0x039a, 0, 0x83),SCE(0x03ba, 1, 0x43),SCE(0x03f0, 2, 0x43),SCE(0x039b, 0, 0x82),
+SCE(0x03bb, 1, 0x42),SCE(0x00b5, 0, 0x43),SCE(0x039c, 1, 0x83),SCE(0x03bc, 2, 0x43),
+SCE(0x039d, 0, 0x82),SCE(0x03bd, 1, 0x42),SCE(0x039e, 0, 0x82),SCE(0x03be, 1, 0x42),
+SCE(0x039f, 0, 0x82),SCE(0x03bf, 1, 0x42),SCE(0x03a0, 0, 0x83),SCE(0x03c0, 1, 0x43),
+SCE(0x03d6, 2, 0x43),SCE(0x03a1, 0, 0x83),SCE(0x03c1, 1, 0x43),SCE(0x03f1, 2, 0x43),
+SCE(0x03a3, 0, 0x83),SCE(0x03c2, 1, 0x43),SCE(0x03c3, 2, 0x43),SCE(0x03a4, 0, 0x82),
+SCE(0x03c4, 1, 0x42),SCE(0x03a5, 0, 0x82),SCE(0x03c5, 1, 0x42),SCE(0x03a6, 0, 0x83),
+SCE(0x03c6, 1, 0x43),SCE(0x03d5, 2, 0x43),SCE(0x03a7, 0, 0x82),SCE(0x03c7, 1, 0x42),
+SCE(0x03a8, 0, 0x82),SCE(0x03c8, 1, 0x42),SCE(0x03a9, 0, 0x83),SCE(0x03c9, 1, 0x43),
+SCE(0x2126, 2, 0x83),SCE(0x03aa, 0, 0x82),SCE(0x03ca, 1, 0x42),SCE(0x03ab, 0, 0x82),
+SCE(0x03cb, 1, 0x42),SCE(0x24c9, 0, 0x82),SCE(0x24e3, 1, 0x42),SCE(0x2ce0, 0, 0x82),
+SCE(0x2ce1, 1, 0x42),SCE(0xa748, 0, 0x82),SCE(0xa749, 1, 0x42),SCE(0x2c9c, 0, 0x82),
+SCE(0x2c9d, 1, 0x42),SCE(0x2c9e, 0, 0x82),SCE(0x2c9f, 1, 0x42),SCE(0xa74a, 0, 0x82),
+SCE(0xa74b, 1, 0x42),SCE(0x2ca0, 0, 0x82),SCE(0x2ca1, 1, 0x42),SCE(0x03a3, 0, 0x83),
+SCE(0x03c2, 1, 0x43),SCE(0x03c3, 2, 0x43),SCE(0x1041f, 0, 0x82),SCE(0x10447, 1, 0x42),
+SCE(0xa74c, 0, 0x82),SCE(0xa74d, 1, 0x42),SCE(0xa68a, 0, 0x82),SCE(0xa68b, 1, 0x42),
+SCE(0x2ca2, 0, 0x82),SCE(0x2ca3, 1, 0x42),SCE(0x03cf, 0, 0x82),SCE(0x03d7, 1, 0x42),
+SCE(0x0392, 0, 0x83),SCE(0x03b2, 1, 0x43),SCE(0x03d0, 2, 0x43),SCE(0x0398, 0, 0x84),
+SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84),SCE(0x03a6, 0, 0x83),
+SCE(0x03c6, 1, 0x43),SCE(0x03d5, 2, 0x43),SCE(0x03a0, 0, 0x83),SCE(0x03c0, 1, 0x43),
+SCE(0x03d6, 2, 0x43),SCE(0x03d8, 0, 0x82),SCE(0x03d9, 1, 0x42),SCE(0x2ca4, 0, 0x82),
+SCE(0x2ca5, 1, 0x42),SCE(0x03da, 0, 0x82),SCE(0x03db, 1, 0x42),SCE(0x03dc, 0, 0x82),
+SCE(0x03dd, 1, 0x42),SCE(0x03de, 0, 0x82),SCE(0x03df, 1, 0x42),SCE(0x03e0, 0, 0x82),
+SCE(0x03e1, 1, 0x42),SCE(0x03e2, 0, 0x82),SCE(0x03e3, 1, 0x42),SCE(0x03e4, 0, 0x82),
+SCE(0x03e5, 1, 0x42),SCE(0x2ca6, 0, 0x82),SCE(0x2ca7, 1, 0x42),SCE(0x03e6, 0, 0x82),
+SCE(0x03e7, 1, 0x42),SCE(0x10420, 0, 0x82),SCE(0x10448, 1, 0x42),SCE(0x03e8, 0, 0x82),
+SCE(0x03e9, 1, 0x42),SCE(0x2ce2, 0, 0x82),SCE(0x2ce3, 1, 0x42),SCE(0x03ea, 0, 0x82),
+SCE(0x03eb, 1, 0x42),SCE(0x03ec, 0, 0x82),SCE(0x03ed, 1, 0x42),SCE(0x03ee, 0, 0x82),
+SCE(0x03ef, 1, 0x42),SCE(0x039a, 0, 0x83),SCE(0x03ba, 1, 0x43),SCE(0x03f0, 2, 0x43),
+SCE(0x03a1, 0, 0x83),SCE(0x03c1, 1, 0x43),SCE(0x03f1, 2, 0x43),SCE(0x0398, 0, 0x84),
+SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84),SCE(0x0395, 0, 0x83),
+SCE(0x03b5, 1, 0x43),SCE(0x03f5, 2, 0x43),SCE(0x03f7, 0, 0x82),SCE(0x03f8, 1, 0x42),
+SCE(0x03f2, 0, 0x42),SCE(0x03f9, 1, 0x82),SCE(0x03fa, 0, 0x82),SCE(0x03fb, 1, 0x42),
+SCE(0x037b, 0, 0x42),SCE(0x03fd, 1, 0x82),SCE(0x037c, 0, 0x42),SCE(0x03fe, 1, 0x82),
+SCE(0x037d, 0, 0x42),SCE(0x03ff, 1, 0x82),SCE(0x0400, 0, 0x82),SCE(0x0450, 1, 0x42),
+SCE(0x0401, 0, 0x82),SCE(0x0451, 1, 0x42),SCE(0x0402, 0, 0x82),SCE(0x0452, 1, 0x42),
+SCE(0x0403, 0, 0x82),SCE(0x0453, 1, 0x42),SCE(0x0404, 0, 0x82),SCE(0x0454, 1, 0x42),
+SCE(0x0405, 0, 0x82),SCE(0x0455, 1, 0x42),SCE(0x0406, 0, 0x82),SCE(0x0456, 1, 0x42),
+SCE(0x0407, 0, 0x82),SCE(0x0457, 1, 0x42),SCE(0x0408, 0, 0x82),SCE(0x0458, 1, 0x42),
+SCE(0x0409, 0, 0x82),SCE(0x0459, 1, 0x42),SCE(0x040a, 0, 0x82),SCE(0x045a, 1, 0x42),
+SCE(0x040b, 0, 0x82),SCE(0x045b, 1, 0x42),SCE(0x040c, 0, 0x82),SCE(0x045c, 1, 0x42),
+SCE(0x040d, 0, 0x82),SCE(0x045d, 1, 0x42),SCE(0x040e, 0, 0x82),SCE(0x045e, 1, 0x42),
+SCE(0x040f, 0, 0x82),SCE(0x045f, 1, 0x42),SCE(0x0410, 0, 0x82),SCE(0x0430, 1, 0x42),
+SCE(0x0411, 0, 0x82),SCE(0x0431, 1, 0x42),SCE(0x0412, 0, 0x82),SCE(0x0432, 1, 0x42),
+SCE(0x0413, 0, 0x82),SCE(0x0433, 1, 0x42),SCE(0x0414, 0, 0x82),SCE(0x0434, 1, 0x42),
+SCE(0x0415, 0, 0x82),SCE(0x0435, 1, 0x42),SCE(0x0416, 0, 0x82),SCE(0x0436, 1, 0x42),
+SCE(0x0417, 0, 0x82),SCE(0x0437, 1, 0x42),SCE(0x0418, 0, 0x82),SCE(0x0438, 1, 0x42),
+SCE(0x0419, 0, 0x82),SCE(0x0439, 1, 0x42),SCE(0x041a, 0, 0x82),SCE(0x043a, 1, 0x42),
+SCE(0x041b, 0, 0x82),SCE(0x043b, 1, 0x42),SCE(0x041c, 0, 0x82),SCE(0x043c, 1, 0x42),
+SCE(0x041d, 0, 0x82),SCE(0x043d, 1, 0x42),SCE(0x041e, 0, 0x82),SCE(0x043e, 1, 0x42),
+SCE(0x041f, 0, 0x82),SCE(0x043f, 1, 0x42),SCE(0x0420, 0, 0x82),SCE(0x0440, 1, 0x42),
+SCE(0x0421, 0, 0x82),SCE(0x0441, 1, 0x42),SCE(0x0422, 0, 0x82),SCE(0x0442, 1, 0x42),
+SCE(0x0423, 0, 0x82),SCE(0x0443, 1, 0x42),SCE(0x0424, 0, 0x82),SCE(0x0444, 1, 0x42),
+SCE(0x0425, 0, 0x82),SCE(0x0445, 1, 0x42),SCE(0x0426, 0, 0x82),SCE(0x0446, 1, 0x42),
+SCE(0x0427, 0, 0x82),SCE(0x0447, 1, 0x42),SCE(0x0428, 0, 0x82),SCE(0x0448, 1, 0x42),
+SCE(0x0429, 0, 0x82),SCE(0x0449, 1, 0x42),SCE(0x042a, 0, 0x82),SCE(0x044a, 1, 0x42),
+SCE(0x042b, 0, 0x82),SCE(0x044b, 1, 0x42),SCE(0x042c, 0, 0x82),SCE(0x044c, 1, 0x42),
+SCE(0x042d, 0, 0x82),SCE(0x044d, 1, 0x42),SCE(0x042e, 0, 0x82),SCE(0x044e, 1, 0x42),
+SCE(0x042f, 0, 0x82),SCE(0x044f, 1, 0x42),SCE(0xff3a, 0, 0x82),SCE(0xff5a, 1, 0x42),
+SCE(0x2cb4, 0, 0x82),SCE(0x2cb5, 1, 0x42),SCE(0x00b5, 0, 0x43),SCE(0x039c, 1, 0x83),
+SCE(0x03bc, 2, 0x43),SCE(0x10423, 0, 0x82),SCE(0x1044b, 1, 0x42),SCE(0x24b6, 0, 0x82),
+SCE(0x24d0, 1, 0x42),SCE(0x24b8, 0, 0x82),SCE(0x24d2, 1, 0x42),SCE(0xff2c, 0, 0x82),
+SCE(0xff4c, 1, 0x42),SCE(0x10421, 0, 0x82),SCE(0x10449, 1, 0x42),SCE(0x24ba, 0, 0x82),
+SCE(0x24d4, 1, 0x42),SCE(0x10424, 0, 0x82),SCE(0x1044c, 1, 0x42),SCE(0x0460, 0, 0x82),
+SCE(0x0461, 1, 0x42),SCE(0x0462, 0, 0x82),SCE(0x0463, 1, 0x42),SCE(0x1d7d, 0, 0x42),
+SCE(0x2c63, 1, 0x82),SCE(0x0464, 0, 0x82),SCE(0x0465, 1, 0x42),SCE(0x0466, 0, 0x82),
+SCE(0x0467, 1, 0x42),SCE(0x2c67, 0, 0x82),SCE(0x2c68, 1, 0x42),SCE(0x0468, 0, 0x82),
+SCE(0x0469, 1, 0x42),SCE(0x24bc, 0, 0x82),SCE(0x24d6, 1, 0x42),SCE(0x046a, 0, 0x82),
+SCE(0x046b, 1, 0x42),SCE(0x2c6b, 0, 0x82),SCE(0x2c6c, 1, 0x42),SCE(0x046c, 0, 0x82),
+SCE(0x046d, 1, 0x42),SCE(0x0251, 0, 0x42),SCE(0x2c6d, 1, 0x82),SCE(0x046e, 0, 0x82),
+SCE(0x046f, 1, 0x42),SCE(0x0250, 0, 0x42),SCE(0x2c6f, 1, 0x82),SCE(0x0470, 0, 0x82),
+SCE(0x0471, 1, 0x42),SCE(0xa768, 0, 0x82),SCE(0xa769, 1, 0x42),SCE(0x0472, 0, 0x82),
+SCE(0x0473, 1, 0x42),SCE(0x0474, 0, 0x82),SCE(0x0475, 1, 0x42),SCE(0x24be, 0, 0x82),
+SCE(0x24d8, 1, 0x42),SCE(0x0476, 0, 0x82),SCE(0x0477, 1, 0x42),SCE(0x0478, 0, 0x82),
+SCE(0x0479, 1, 0x42),SCE(0x047a, 0, 0x82),SCE(0x047b, 1, 0x42),SCE(0x047c, 0, 0x82),
+SCE(0x047d, 1, 0x42),SCE(0xa76a, 0, 0x82),SCE(0xa76b, 1, 0x42),SCE(0x047e, 0, 0x82),
+SCE(0x047f, 1, 0x42),SCE(0x0240, 0, 0x42),SCE(0x2c7f, 1, 0x82),SCE(0x0480, 0, 0x82),
+SCE(0x0481, 1, 0x42),SCE(0x10c0, 0, 0x82),SCE(0x2d20, 1, 0x42),SCE(0x2c82, 0, 0x82),
+SCE(0x2c83, 1, 0x42),SCE(0x2c84, 0, 0x82),SCE(0x2c85, 1, 0x42),SCE(0x2c86, 0, 0x82),
+SCE(0x2c87, 1, 0x42),SCE(0x10c1, 0, 0x82),SCE(0x2d21, 1, 0x42),SCE(0x2c88, 0, 0x82),
+SCE(0x2c89, 1, 0x42),SCE(0xa76c, 0, 0x82),SCE(0xa76d, 1, 0x42),SCE(0x048a, 0, 0x82),
+SCE(0x048b, 1, 0x42),SCE(0x048c, 0, 0x82),SCE(0x048d, 1, 0x42),SCE(0x00c2, 0, 0x82),
+SCE(0x00e2, 1, 0x42),SCE(0x048e, 0, 0x82),SCE(0x048f, 1, 0x42),SCE(0x0490, 0, 0x82),
+SCE(0x0491, 1, 0x42),SCE(0x0492, 0, 0x82),SCE(0x0493, 1, 0x42),SCE(0x10c3, 0, 0x82),
+SCE(0x2d23, 1, 0x42),SCE(0x0494, 0, 0x82),SCE(0x0495, 1, 0x42),SCE(0xa76e, 0, 0x82),
+SCE(0xa76f, 1, 0x42),SCE(0x0496, 0, 0x82),SCE(0x0497, 1, 0x42),SCE(0x0498, 0, 0x82),
+SCE(0x0499, 1, 0x42),SCE(0x00c4, 0, 0x82),SCE(0x00e4, 1, 0x42),SCE(0x049a, 0, 0x82),
+SCE(0x049b, 1, 0x42),SCE(0x10426, 0, 0x82),SCE(0x1044e, 1, 0x42),SCE(0x049c, 0, 0x82),
+SCE(0x049d, 1, 0x42),SCE(0x049e, 0, 0x82),SCE(0x049f, 1, 0x42),SCE(0x10c5, 0, 0x82),
+SCE(0x2d25, 1, 0x42),SCE(0x04a0, 0, 0x82),SCE(0x04a1, 1, 0x42),SCE(0x04a2, 0, 0x82),
+SCE(0x04a3, 1, 0x42),SCE(0x04a4, 0, 0x82),SCE(0x04a5, 1, 0x42),SCE(0x2cc6, 0, 0x82),
+SCE(0x2cc7, 1, 0x42),SCE(0x04a6, 0, 0x82),SCE(0x04a7, 1, 0x42),SCE(0x04a8, 0, 0x82),
+SCE(0x04a9, 1, 0x42),SCE(0x2c60, 0, 0x82),SCE(0x2c61, 1, 0x42),SCE(0x04aa, 0, 0x82),
+SCE(0x04ab, 1, 0x42),SCE(0x10c7, 0, 0x82),SCE(0x2d27, 1, 0x42),SCE(0x04ac, 0, 0x82),
+SCE(0x04ad, 1, 0x42),SCE(0x10413, 0, 0x82),SCE(0x1043b, 1, 0x42),SCE(0x04ae, 0, 0x82),
+SCE(0x04af, 1, 0x42),SCE(0x04b0, 0, 0x82),SCE(0x04b1, 1, 0x42),SCE(0x2cc8, 0, 0x82),
+SCE(0x2cc9, 1, 0x42),SCE(0x04b2, 0, 0x82),SCE(0x04b3, 1, 0x42),SCE(0x04b4, 0, 0x82),
+SCE(0x04b5, 1, 0x42),SCE(0x04b6, 0, 0x82),SCE(0x04b7, 1, 0x42),SCE(0x24b7, 0, 0x82),
+SCE(0x24d1, 1, 0x42),SCE(0x04b8, 0, 0x82),SCE(0x04b9, 1, 0x42),SCE(0x24b9, 0, 0x82),
+SCE(0x24d3, 1, 0x42),SCE(0x04ba, 0, 0x82),SCE(0x04bb, 1, 0x42),SCE(0x24bb, 0, 0x82),
+SCE(0x24d5, 1, 0x42),SCE(0x04bc, 0, 0x82),SCE(0x04bd, 1, 0x42),SCE(0x24bd, 0, 0x82),
+SCE(0x24d7, 1, 0x42),SCE(0x04be, 0, 0x82),SCE(0x04bf, 1, 0x42),SCE(0x24bf, 0, 0x82),
+SCE(0x24d9, 1, 0x42),SCE(0x04c0, 0, 0x82),SCE(0x04cf, 1, 0x42),SCE(0x04c1, 0, 0x82),
+SCE(0x04c2, 1, 0x42),SCE(0x24c2, 0, 0x82),SCE(0x24dc, 1, 0x42),SCE(0x04c3, 0, 0x82),
+SCE(0x04c4, 1, 0x42),SCE(0x24c4, 0, 0x82),SCE(0x24de, 1, 0x42),SCE(0x04c5, 0, 0x82),
+SCE(0x04c6, 1, 0x42),SCE(0x24c6, 0, 0x82),SCE(0x24e0, 1, 0x42),SCE(0x04c7, 0, 0x82),
+SCE(0x04c8, 1, 0x42),SCE(0x24c8, 0, 0x82),SCE(0x24e2, 1, 0x42),SCE(0x04c9, 0, 0x82),
+SCE(0x04ca, 1, 0x42),SCE(0x24ca, 0, 0x82),SCE(0x24e4, 1, 0x42),SCE(0x04cb, 0, 0x82),
+SCE(0x04cc, 1, 0x42),SCE(0x24cc, 0, 0x82),SCE(0x24e6, 1, 0x42),SCE(0x04cd, 0, 0x82),
+SCE(0x04ce, 1, 0x42),SCE(0x24ce, 0, 0x82),SCE(0x24e8, 1, 0x42),SCE(0x10cd, 0, 0x82),
+SCE(0x2d2d, 1, 0x42),SCE(0x04d0, 0, 0x82),SCE(0x04d1, 1, 0x42),SCE(0x04d2, 0, 0x82),
+SCE(0x04d3, 1, 0x42),SCE(0x04d4, 0, 0x82),SCE(0x04d5, 1, 0x42),SCE(0x2cce, 0, 0x82),
+SCE(0x2ccf, 1, 0x42),SCE(0x04d6, 0, 0x82),SCE(0x04d7, 1, 0x42),SCE(0xa779, 0, 0x82),
+SCE(0xa77a, 1, 0x42),SCE(0x04d8, 0, 0x82),SCE(0x04d9, 1, 0x42),SCE(0x04da, 0, 0x82),
+SCE(0x04db, 1, 0x42),SCE(0x24cf, 0, 0x82),SCE(0x24e9, 1, 0x42),SCE(0x04dc, 0, 0x82),
+SCE(0x04dd, 1, 0x42),SCE(0x04de, 0, 0x82),SCE(0x04df, 1, 0x42),SCE(0x04e0, 0, 0x82),
+SCE(0x04e1, 1, 0x42),SCE(0x2cd0, 0, 0x82),SCE(0x2cd1, 1, 0x42),SCE(0x04e2, 0, 0x82),
+SCE(0x04e3, 1, 0x42),SCE(0x04e4, 0, 0x82),SCE(0x04e5, 1, 0x42),SCE(0x026b, 0, 0x42),
+SCE(0x2c62, 1, 0x82),SCE(0x04e6, 0, 0x82),SCE(0x04e7, 1, 0x42),SCE(0x04e8, 0, 0x82),
+SCE(0x04e9, 1, 0x42),SCE(0x04ea, 0, 0x82),SCE(0x04eb, 1, 0x42),SCE(0x2132, 0, 0x82),
+SCE(0x214e, 1, 0x42),SCE(0x04ec, 0, 0x82),SCE(0x04ed, 1, 0x42),SCE(0x2cd2, 0, 0x82),
+SCE(0x2cd3, 1, 0x42),SCE(0x04ee, 0, 0x82),SCE(0x04ef, 1, 0x42),SCE(0x04f0, 0, 0x82),
+SCE(0x04f1, 1, 0x42),SCE(0x10422, 0, 0x82),SCE(0x1044a, 1, 0x42),SCE(0x04f2, 0, 0x82),
+SCE(0x04f3, 1, 0x42),SCE(0x04f4, 0, 0x82),SCE(0x04f5, 1, 0x42),SCE(0x04f6, 0, 0x82),
+SCE(0x04f7, 1, 0x42),SCE(0x04f8, 0, 0x82),SCE(0x04f9, 1, 0x42),SCE(0x2cd4, 0, 0x82),
+SCE(0x2cd5, 1, 0x42),SCE(0x04fa, 0, 0x82),SCE(0x04fb, 1, 0x42),SCE(0x04fc, 0, 0x82),
+SCE(0x04fd, 1, 0x42),SCE(0x04fe, 0, 0x82),SCE(0x04ff, 1, 0x42),SCE(0x0500, 0, 0x82),
+SCE(0x0501, 1, 0x42),SCE(0x0502, 0, 0x82),SCE(0x0503, 1, 0x42),SCE(0x0504, 0, 0x82),
+SCE(0x0505, 1, 0x42),SCE(0x2cd6, 0, 0x82),SCE(0x2cd7, 1, 0x42),SCE(0x0506, 0, 0x82),
+SCE(0x0507, 1, 0x42),SCE(0x0508, 0, 0x82),SCE(0x0509, 1, 0x42),SCE(0x050a, 0, 0x82),
+SCE(0x050b, 1, 0x42),SCE(0x050c, 0, 0x82),SCE(0x050d, 1, 0x42),SCE(0x050e, 0, 0x82),
+SCE(0x050f, 1, 0x42),SCE(0x0510, 0, 0x82),SCE(0x0511, 1, 0x42),SCE(0x2cd8, 0, 0x82),
+SCE(0x2cd9, 1, 0x42),SCE(0x0512, 0, 0x82),SCE(0x0513, 1, 0x42),SCE(0x0514, 0, 0x82),
+SCE(0x0515, 1, 0x42),SCE(0x0516, 0, 0x82),SCE(0x0517, 1, 0x42),SCE(0x0518, 0, 0x82),
+SCE(0x0519, 1, 0x42),SCE(0x051a, 0, 0x82),SCE(0x051b, 1, 0x42),SCE(0x2ca8, 0, 0x82),
+SCE(0x2ca9, 1, 0x42),SCE(0x051c, 0, 0x82),SCE(0x051d, 1, 0x42),SCE(0x2cda, 0, 0x82),
+SCE(0x2cdb, 1, 0x42),SCE(0x051e, 0, 0x82),SCE(0x051f, 1, 0x42),SCE(0x0520, 0, 0x82),
+SCE(0x0521, 1, 0x42),SCE(0x0522, 0, 0x82),SCE(0x0523, 1, 0x42),SCE(0x0524, 0, 0x82),
+SCE(0x0525, 1, 0x42),SCE(0x0526, 0, 0x82),SCE(0x0527, 1, 0x42),SCE(0x2c20, 0, 0x82),
+SCE(0x2c50, 1, 0x42),SCE(0x2cdc, 0, 0x82),SCE(0x2cdd, 1, 0x42),SCE(0x0531, 0, 0x82),
+SCE(0x0561, 1, 0x42),SCE(0x0532, 0, 0x82),SCE(0x0562, 1, 0x42),SCE(0x0533, 0, 0x82),
+SCE(0x0563, 1, 0x42),SCE(0x0534, 0, 0x82),SCE(0x0564, 1, 0x42),SCE(0x0535, 0, 0x82),
+SCE(0x0565, 1, 0x42),SCE(0x0536, 0, 0x82),SCE(0x0566, 1, 0x42),SCE(0x0537, 0, 0x82),
+SCE(0x0567, 1, 0x42),SCE(0x0538, 0, 0x82),SCE(0x0568, 1, 0x42),SCE(0x0539, 0, 0x82),
+SCE(0x0569, 1, 0x42),SCE(0x053a, 0, 0x82),SCE(0x056a, 1, 0x42),SCE(0x053b, 0, 0x82),
+SCE(0x056b, 1, 0x42),SCE(0x053c, 0, 0x82),SCE(0x056c, 1, 0x42),SCE(0x053d, 0, 0x82),
+SCE(0x056d, 1, 0x42),SCE(0x053e, 0, 0x82),SCE(0x056e, 1, 0x42),SCE(0x053f, 0, 0x82),
+SCE(0x056f, 1, 0x42),SCE(0x0540, 0, 0x82),SCE(0x0570, 1, 0x42),SCE(0x0541, 0, 0x82),
+SCE(0x0571, 1, 0x42),SCE(0x0542, 0, 0x82),SCE(0x0572, 1, 0x42),SCE(0x0543, 0, 0x82),
+SCE(0x0573, 1, 0x42),SCE(0x0544, 0, 0x82),SCE(0x0574, 1, 0x42),SCE(0x0545, 0, 0x82),
+SCE(0x0575, 1, 0x42),SCE(0x0546, 0, 0x82),SCE(0x0576, 1, 0x42),SCE(0x0547, 0, 0x82),
+SCE(0x0577, 1, 0x42),SCE(0x0548, 0, 0x82),SCE(0x0578, 1, 0x42),SCE(0x0549, 0, 0x82),
+SCE(0x0579, 1, 0x42),SCE(0x054a, 0, 0x82),SCE(0x057a, 1, 0x42),SCE(0x054b, 0, 0x82),
+SCE(0x057b, 1, 0x42),SCE(0x054c, 0, 0x82),SCE(0x057c, 1, 0x42),SCE(0x054d, 0, 0x82),
+SCE(0x057d, 1, 0x42),SCE(0x054e, 0, 0x82),SCE(0x057e, 1, 0x42),SCE(0x054f, 0, 0x82),
+SCE(0x057f, 1, 0x42),SCE(0x0550, 0, 0x82),SCE(0x0580, 1, 0x42),SCE(0x0551, 0, 0x82),
+SCE(0x0581, 1, 0x42),SCE(0x0552, 0, 0x82),SCE(0x0582, 1, 0x42),SCE(0x0553, 0, 0x82),
+SCE(0x0583, 1, 0x42),SCE(0x0554, 0, 0x82),SCE(0x0584, 1, 0x42),SCE(0x0555, 0, 0x82),
+SCE(0x0585, 1, 0x42),SCE(0x0556, 0, 0x82),SCE(0x0586, 1, 0x42),SCE(0x2caa, 0, 0x82),
+SCE(0x2cab, 1, 0x42),SCE(0x2c22, 0, 0x82),SCE(0x2c52, 1, 0x42),SCE(0x2c23, 0, 0x82),
+SCE(0x2c53, 1, 0x42),SCE(0x2ceb, 0, 0x82),SCE(0x2cec, 1, 0x42),SCE(0x2cca, 0, 0x82),
+SCE(0x2ccb, 1, 0x42),SCE(0xa642, 0, 0x82),SCE(0xa643, 1, 0x42),SCE(0x2ced, 0, 0x82),
+SCE(0x2cee, 1, 0x42),SCE(0x2cac, 0, 0x82),SCE(0x2cad, 1, 0x42),SCE(0xa644, 0, 0x82),
+SCE(0xa645, 1, 0x42),SCE(0x2c24, 0, 0x82),SCE(0x2c54, 1, 0x42),SCE(0xa646, 0, 0x82),
+SCE(0xa647, 1, 0x42),SCE(0x2cf2, 0, 0x82),SCE(0x2cf3, 1, 0x42),SCE(0x10408, 0, 0x82),
+SCE(0x10430, 1, 0x42),SCE(0xa648, 0, 0x82),SCE(0xa649, 1, 0x42),SCE(0xa64a, 0, 0x82),
+SCE(0xa64b, 1, 0x42),SCE(0xa64c, 0, 0x82),SCE(0xa64d, 1, 0x42),SCE(0x2cae, 0, 0x82),
+SCE(0x2caf, 1, 0x42),SCE(0xa64e, 0, 0x82),SCE(0xa64f, 1, 0x42),SCE(0xa650, 0, 0x82),
+SCE(0xa651, 1, 0x42),SCE(0xa78b, 0, 0x82),SCE(0xa78c, 1, 0x42),SCE(0xa652, 0, 0x82),
+SCE(0xa653, 1, 0x42),SCE(0xa7a8, 0, 0x82),SCE(0xa7a9, 1, 0x42),SCE(0xa654, 0, 0x82),
+SCE(0xa655, 1, 0x42),SCE(0x0266, 0, 0x42),SCE(0xa7aa, 1, 0x82),SCE(0x1e00, 0, 0x82),
+SCE(0x1e01, 1, 0x42),SCE(0x1e02, 0, 0x82),SCE(0x1e03, 1, 0x42),SCE(0x1e04, 0, 0x82),
+SCE(0x1e05, 1, 0x42),SCE(0x2c1f, 0, 0x82),SCE(0x2c4f, 1, 0x42),SCE(0x1e06, 0, 0x82),
+SCE(0x1e07, 1, 0x42),SCE(0x1040e, 0, 0x82),SCE(0x10436, 1, 0x42),SCE(0x1e08, 0, 0x82),
+SCE(0x1e09, 1, 0x42),SCE(0x1e0a, 0, 0x82),SCE(0x1e0b, 1, 0x42),SCE(0x2cb0, 0, 0x82),
+SCE(0x2cb1, 1, 0x42),SCE(0x1e0c, 0, 0x82),SCE(0x1e0d, 1, 0x42),SCE(0x1e0e, 0, 0x82),
+SCE(0x1e0f, 1, 0x42),SCE(0x1e10, 0, 0x82),SCE(0x1e11, 1, 0x42),SCE(0xa658, 0, 0x82),
+SCE(0xa659, 1, 0x42),SCE(0x1e12, 0, 0x82),SCE(0x1e13, 1, 0x42),SCE(0x1e14, 0, 0x82),
+SCE(0x1e15, 1, 0x42),SCE(0x24cd, 0, 0x82),SCE(0x24e7, 1, 0x42),SCE(0x1e16, 0, 0x82),
+SCE(0x1e17, 1, 0x42),SCE(0x1e18, 0, 0x82),SCE(0x1e19, 1, 0x42),SCE(0x1e1a, 0, 0x82),
+SCE(0x1e1b, 1, 0x42),SCE(0x1e1c, 0, 0x82),SCE(0x1e1d, 1, 0x42),SCE(0xa65a, 0, 0x82),
+SCE(0xa65b, 1, 0x42),SCE(0x1e1e, 0, 0x82),SCE(0x1e1f, 1, 0x42),SCE(0x1e20, 0, 0x82),
+SCE(0x1e21, 1, 0x42),SCE(0x1e22, 0, 0x82),SCE(0x1e23, 1, 0x42),SCE(0x1e24, 0, 0x82),
+SCE(0x1e25, 1, 0x42),SCE(0x1e26, 0, 0x82),SCE(0x1e27, 1, 0x42),SCE(0x1e28, 0, 0x82),
+SCE(0x1e29, 1, 0x42),SCE(0xa65c, 0, 0x82),SCE(0xa65d, 1, 0x42),SCE(0x1e2a, 0, 0x82),
+SCE(0x1e2b, 1, 0x42),SCE(0x1e2c, 0, 0x82),SCE(0x1e2d, 1, 0x42),SCE(0x1e2e, 0, 0x82),
+SCE(0x1e2f, 1, 0x42),SCE(0x1e30, 0, 0x82),SCE(0x1e31, 1, 0x42),SCE(0x1e32, 0, 0x82),
+SCE(0x1e33, 1, 0x42),SCE(0x1e34, 0, 0x82),SCE(0x1e35, 1, 0x42),SCE(0xa65e, 0, 0x82),
+SCE(0xa65f, 1, 0x42),SCE(0x1e36, 0, 0x82),SCE(0x1e37, 1, 0x42),SCE(0x1e38, 0, 0x82),
+SCE(0x1e39, 1, 0x42),SCE(0x1e3a, 0, 0x82),SCE(0x1e3b, 1, 0x42),SCE(0x1e3c, 0, 0x82),
+SCE(0x1e3d, 1, 0x42),SCE(0x1e3e, 0, 0x82),SCE(0x1e3f, 1, 0x42),SCE(0x1e40, 0, 0x82),
+SCE(0x1e41, 1, 0x42),SCE(0xa660, 0, 0x82),SCE(0xa661, 1, 0x42),SCE(0x1e42, 0, 0x82),
+SCE(0x1e43, 1, 0x42),SCE(0x1e44, 0, 0x82),SCE(0x1e45, 1, 0x42),SCE(0x1e46, 0, 0x82),
+SCE(0x1e47, 1, 0x42),SCE(0x2cb2, 0, 0x82),SCE(0x2cb3, 1, 0x42),SCE(0x1e48, 0, 0x82),
+SCE(0x1e49, 1, 0x42),SCE(0x2cc0, 0, 0x82),SCE(0x2cc1, 1, 0x42),SCE(0x1e4a, 0, 0x82),
+SCE(0x1e4b, 1, 0x42),SCE(0x1e4c, 0, 0x82),SCE(0x1e4d, 1, 0x42),SCE(0xa662, 0, 0x82),
+SCE(0xa663, 1, 0x42),SCE(0x1e4e, 0, 0x82),SCE(0x1e4f, 1, 0x42),SCE(0x1e50, 0, 0x82),
+SCE(0x1e51, 1, 0x42),SCE(0x1e52, 0, 0x82),SCE(0x1e53, 1, 0x42),SCE(0x1e54, 0, 0x82),
+SCE(0x1e55, 1, 0x42),SCE(0x1e56, 0, 0x82),SCE(0x1e57, 1, 0x42),SCE(0x1e58, 0, 0x82),
+SCE(0x1e59, 1, 0x42),SCE(0xa664, 0, 0x82),SCE(0xa665, 1, 0x42),SCE(0x1e5a, 0, 0x82),
+SCE(0x1e5b, 1, 0x42),SCE(0x1e5c, 0, 0x82),SCE(0x1e5d, 1, 0x42),SCE(0x1e5e, 0, 0x82),
+SCE(0x1e5f, 1, 0x42),SCE(0x1e60, 0, 0x83),SCE(0x1e61, 1, 0x43),SCE(0x1e9b, 2, 0x43),
+SCE(0x1e62, 0, 0x82),SCE(0x1e63, 1, 0x42),SCE(0x1e64, 0, 0x82),SCE(0x1e65, 1, 0x42),
+SCE(0xa666, 0, 0x82),SCE(0xa667, 1, 0x42),SCE(0x1e66, 0, 0x82),SCE(0x1e67, 1, 0x42),
+SCE(0x1e68, 0, 0x82),SCE(0x1e69, 1, 0x42),SCE(0x1e6a, 0, 0x82),SCE(0x1e6b, 1, 0x42),
+SCE(0x1e6c, 0, 0x82),SCE(0x1e6d, 1, 0x42),SCE(0x1e6e, 0, 0x82),SCE(0x1e6f, 1, 0x42),
+SCE(0x1e70, 0, 0x82),SCE(0x1e71, 1, 0x42),SCE(0xa668, 0, 0x82),SCE(0xa669, 1, 0x42),
+SCE(0x1e72, 0, 0x82),SCE(0x1e73, 1, 0x42),SCE(0x1e74, 0, 0x82),SCE(0x1e75, 1, 0x42),
+SCE(0x1e76, 0, 0x82),SCE(0x1e77, 1, 0x42),SCE(0x2cbe, 0, 0x82),SCE(0x2cbf, 1, 0x42),
+SCE(0x1e78, 0, 0x82),SCE(0x1e79, 1, 0x42),SCE(0x1e7a, 0, 0x82),SCE(0x1e7b, 1, 0x42),
+SCE(0x1e7c, 0, 0x82),SCE(0x1e7d, 1, 0x42),SCE(0xa66a, 0, 0x82),SCE(0xa66b, 1, 0x42),
+SCE(0x1e7e, 0, 0x82),SCE(0x1e7f, 1, 0x42),SCE(0x1e80, 0, 0x82),SCE(0x1e81, 1, 0x42),
+SCE(0x1e82, 0, 0x82),SCE(0x1e83, 1, 0x42),SCE(0x1e84, 0, 0x82),SCE(0x1e85, 1, 0x42),
+SCE(0x1e86, 0, 0x82),SCE(0x1e87, 1, 0x42),SCE(0x1e88, 0, 0x82),SCE(0x1e89, 1, 0x42),
+SCE(0xa66c, 0, 0x82),SCE(0xa66d, 1, 0x42),SCE(0x1e8a, 0, 0x82),SCE(0x1e8b, 1, 0x42),
+SCE(0x1e8c, 0, 0x82),SCE(0x1e8d, 1, 0x42),SCE(0x1e8e, 0, 0x82),SCE(0x1e8f, 1, 0x42),
+SCE(0x1e90, 0, 0x82),SCE(0x1e91, 1, 0x42),SCE(0x1e92, 0, 0x82),SCE(0x1e93, 1, 0x42),
+SCE(0x1e94, 0, 0x82),SCE(0x1e95, 1, 0x42),SCE(0xa696, 0, 0x82),SCE(0xa697, 1, 0x42),
+SCE(0x10406, 0, 0x82),SCE(0x1042e, 1, 0x42),SCE(0x1e60, 0, 0x83),SCE(0x1e61, 1, 0x43),
+SCE(0x1e9b, 2, 0x43),SCE(0x00df, 0, 0x42),SCE(0x1e9e, 1, 0x82),SCE(0x1ea0, 0, 0x82),
+SCE(0x1ea1, 1, 0x42),SCE(0x1ea2, 0, 0x82),SCE(0x1ea3, 1, 0x42),SCE(0x1ea4, 0, 0x82),
+SCE(0x1ea5, 1, 0x42),SCE(0x24c5, 0, 0x82),SCE(0x24df, 1, 0x42),SCE(0x1ea6, 0, 0x82),
+SCE(0x1ea7, 1, 0x42),SCE(0x1ea8, 0, 0x82),SCE(0x1ea9, 1, 0x42),SCE(0x1eaa, 0, 0x82),
+SCE(0x1eab, 1, 0x42),SCE(0x1eac, 0, 0x82),SCE(0x1ead, 1, 0x42),SCE(0x1eae, 0, 0x82),
+SCE(0x1eaf, 1, 0x42),SCE(0xff28, 0, 0x82),SCE(0xff48, 1, 0x42),SCE(0x1eb0, 0, 0x82),
+SCE(0x1eb1, 1, 0x42),SCE(0x1eb2, 0, 0x82),SCE(0x1eb3, 1, 0x42),SCE(0x10425, 0, 0x82),
+SCE(0x1044d, 1, 0x42),SCE(0x1eb4, 0, 0x82),SCE(0x1eb5, 1, 0x42),SCE(0x1eb6, 0, 0x82),
+SCE(0x1eb7, 1, 0x42),SCE(0x1eb8, 0, 0x82),SCE(0x1eb9, 1, 0x42),SCE(0x1eba, 0, 0x82),
+SCE(0x1ebb, 1, 0x42),SCE(0x1ebc, 0, 0x82),SCE(0x1ebd, 1, 0x42),SCE(0x1ebe, 0, 0x82),
+SCE(0x1ebf, 1, 0x42),SCE(0x2cb6, 0, 0x82),SCE(0x2cb7, 1, 0x42),SCE(0x1ec0, 0, 0x82),
+SCE(0x1ec1, 1, 0x42),SCE(0x1ec2, 0, 0x82),SCE(0x1ec3, 1, 0x42),SCE(0x2c9a, 0, 0x82),
+SCE(0x2c9b, 1, 0x42),SCE(0x1ec4, 0, 0x82),SCE(0x1ec5, 1, 0x42),SCE(0x1ec6, 0, 0x82),
+SCE(0x1ec7, 1, 0x42),SCE(0x1ec8, 0, 0x82),SCE(0x1ec9, 1, 0x42),SCE(0x1eca, 0, 0x82),
+SCE(0x1ecb, 1, 0x42),SCE(0x1ecc, 0, 0x82),SCE(0x1ecd, 1, 0x42),SCE(0x1ece, 0, 0x82),
+SCE(0x1ecf, 1, 0x42),SCE(0x1ed0, 0, 0x82),SCE(0x1ed1, 1, 0x42),SCE(0x1ed2, 0, 0x82),
+SCE(0x1ed3, 1, 0x42),SCE(0x1ed4, 0, 0x82),SCE(0x1ed5, 1, 0x42),SCE(0x1ed6, 0, 0x82),
+SCE(0x1ed7, 1, 0x42),SCE(0x1ed8, 0, 0x82),SCE(0x1ed9, 1, 0x42),SCE(0x1eda, 0, 0x82),
+SCE(0x1edb, 1, 0x42),SCE(0x1edc, 0, 0x82),SCE(0x1edd, 1, 0x42),SCE(0x1ede, 0, 0x82),
+SCE(0x1edf, 1, 0x42),SCE(0x1ee0, 0, 0x82),SCE(0x1ee1, 1, 0x42),SCE(0x1ee2, 0, 0x82),
+SCE(0x1ee3, 1, 0x42),SCE(0x1ee4, 0, 0x82),SCE(0x1ee5, 1, 0x42),SCE(0x03a9, 0, 0x83),
+SCE(0x03c9, 1, 0x43),SCE(0x2126, 2, 0x83),SCE(0x1ee6, 0, 0x82),SCE(0x1ee7, 1, 0x42),
+SCE(0x1ee8, 0, 0x82),SCE(0x1ee9, 1, 0x42),SCE(0x1eea, 0, 0x82),SCE(0x1eeb, 1, 0x42),
+SCE(0xff2a, 0, 0x82),SCE(0xff4a, 1, 0x42),SCE(0x1eec, 0, 0x82),SCE(0x1eed, 1, 0x42),
+SCE(0x1eee, 0, 0x82),SCE(0x1eef, 1, 0x42),SCE(0x1ef0, 0, 0x82),SCE(0x1ef1, 1, 0x42),
+SCE(0x1ef2, 0, 0x82),SCE(0x1ef3, 1, 0x42),SCE(0x1ef4, 0, 0x82),SCE(0x1ef5, 1, 0x42),
+SCE(0x1ef6, 0, 0x82),SCE(0x1ef7, 1, 0x42),SCE(0x1ef8, 0, 0x82),SCE(0x1ef9, 1, 0x42),
+SCE(0x1efa, 0, 0x82),SCE(0x1efb, 1, 0x42),SCE(0x2cb8, 0, 0x82),SCE(0x2cb9, 1, 0x42),
+SCE(0x1efc, 0, 0x82),SCE(0x1efd, 1, 0x42),SCE(0x004b, 0, 0x83),SCE(0x006b, 1, 0x43),
+SCE(0x212a, 2, 0x83),SCE(0x1efe, 0, 0x82),SCE(0x1eff, 1, 0x42),SCE(0xa680, 0, 0x82),
+SCE(0xa681, 1, 0x42),SCE(0x1f00, 0, 0x42),SCE(0x1f08, 1, 0x82),SCE(0x1f01, 0, 0x42),
+SCE(0x1f09, 1, 0x82),SCE(0x1f02, 0, 0x42),SCE(0x1f0a, 1, 0x82),SCE(0x1f03, 0, 0x42),
+SCE(0x1f0b, 1, 0x82),SCE(0x1f04, 0, 0x42),SCE(0x1f0c, 1, 0x82),SCE(0x1f05, 0, 0x42),
+SCE(0x1f0d, 1, 0x82),SCE(0x1f06, 0, 0x42),SCE(0x1f0e, 1, 0x82),SCE(0x1f07, 0, 0x42),
+SCE(0x1f0f, 1, 0x82),SCE(0x10418, 0, 0x82),SCE(0x10440, 1, 0x42),SCE(0x0265, 0, 0x42),
+SCE(0xa78d, 1, 0x82),SCE(0x1f10, 0, 0x42),SCE(0x1f18, 1, 0x82),SCE(0x1f11, 0, 0x42),
+SCE(0x1f19, 1, 0x82),SCE(0x1f12, 0, 0x42),SCE(0x1f1a, 1, 0x82),SCE(0x1f13, 0, 0x42),
+SCE(0x1f1b, 1, 0x82),SCE(0x1f14, 0, 0x42),SCE(0x1f1c, 1, 0x82),SCE(0x1f15, 0, 0x42),
+SCE(0x1f1d, 1, 0x82),SCE(0xff21, 0, 0x82),SCE(0xff41, 1, 0x42),SCE(0xa722, 0, 0x82),
+SCE(0xa723, 1, 0x42),SCE(0xff23, 0, 0x82),SCE(0xff43, 1, 0x42),SCE(0xa724, 0, 0x82),
+SCE(0xa725, 1, 0x42),SCE(0xa686, 0, 0x82),SCE(0xa687, 1, 0x42),SCE(0xa726, 0, 0x82),
+SCE(0xa727, 1, 0x42),SCE(0xff27, 0, 0x82),SCE(0xff47, 1, 0x42),SCE(0x1f20, 0, 0x42),
+SCE(0x1f28, 1, 0x82),SCE(0x1f21, 0, 0x42),SCE(0x1f29, 1, 0x82),SCE(0x1f22, 0, 0x42),
+SCE(0x1f2a, 1, 0x82),SCE(0x1f23, 0, 0x42),SCE(0x1f2b, 1, 0x82),SCE(0x1f24, 0, 0x42),
+SCE(0x1f2c, 1, 0x82),SCE(0x1f25, 0, 0x42),SCE(0x1f2d, 1, 0x82),SCE(0x1f26, 0, 0x42),
+SCE(0x1f2e, 1, 0x82),SCE(0x1f27, 0, 0x42),SCE(0x1f2f, 1, 0x82),SCE(0xff30, 0, 0x82),
+SCE(0xff50, 1, 0x42),SCE(0xa688, 0, 0x82),SCE(0xa689, 1, 0x42),SCE(0xa732, 0, 0x82),
+SCE(0xa733, 1, 0x42),SCE(0xff33, 0, 0x82),SCE(0xff53, 1, 0x42),SCE(0xa734, 0, 0x82),
+SCE(0xa735, 1, 0x42),SCE(0xff35, 0, 0x82),SCE(0xff55, 1, 0x42),SCE(0xa736, 0, 0x82),
+SCE(0xa737, 1, 0x42),SCE(0x2cba, 0, 0x82),SCE(0x2cbb, 1, 0x42),SCE(0x1f30, 0, 0x42),
+SCE(0x1f38, 1, 0x82),SCE(0x1f31, 0, 0x42),SCE(0x1f39, 1, 0x82),SCE(0x1f32, 0, 0x42),
+SCE(0x1f3a, 1, 0x82),SCE(0x1f33, 0, 0x42),SCE(0x1f3b, 1, 0x82),SCE(0x1f34, 0, 0x42),
+SCE(0x1f3c, 1, 0x82),SCE(0x1f35, 0, 0x42),SCE(0x1f3d, 1, 0x82),SCE(0x1f36, 0, 0x42),
+SCE(0x1f3e, 1, 0x82),SCE(0x1f37, 0, 0x42),SCE(0x1f3f, 1, 0x82),SCE(0xa740, 0, 0x82),
+SCE(0xa741, 1, 0x42),SCE(0xa742, 0, 0x82),SCE(0xa743, 1, 0x42),SCE(0xa744, 0, 0x82),
+SCE(0xa745, 1, 0x42),SCE(0xa746, 0, 0x82),SCE(0xa747, 1, 0x42),SCE(0x1f40, 0, 0x42),
+SCE(0x1f48, 1, 0x82),SCE(0x1f41, 0, 0x42),SCE(0x1f49, 1, 0x82),SCE(0x1f42, 0, 0x42),
+SCE(0x1f4a, 1, 0x82),SCE(0x1f43, 0, 0x42),SCE(0x1f4b, 1, 0x82),SCE(0x1f44, 0, 0x42),
+SCE(0x1f4c, 1, 0x82),SCE(0x1f45, 0, 0x42),SCE(0x1f4d, 1, 0x82),SCE(0xa74e, 0, 0x82),
+SCE(0xa74f, 1, 0x42),SCE(0xa750, 0, 0x82),SCE(0xa751, 1, 0x42),SCE(0xa752, 0, 0x82),
+SCE(0xa753, 1, 0x42),SCE(0xa754, 0, 0x82),SCE(0xa755, 1, 0x42),SCE(0xa68e, 0, 0x82),
+SCE(0xa68f, 1, 0x42),SCE(0xa756, 0, 0x82),SCE(0xa757, 1, 0x42),SCE(0xa758, 0, 0x82),
+SCE(0xa759, 1, 0x42),SCE(0x1f51, 0, 0x42),SCE(0x1f59, 1, 0x82),SCE(0xa75a, 0, 0x82),
+SCE(0xa75b, 1, 0x42),SCE(0x1f53, 0, 0x42),SCE(0x1f5b, 1, 0x82),SCE(0xa75c, 0, 0x82),
+SCE(0xa75d, 1, 0x42),SCE(0x1f55, 0, 0x42),SCE(0x1f5d, 1, 0x82),SCE(0xa75e, 0, 0x82),
+SCE(0xa75f, 1, 0x42),SCE(0x1f57, 0, 0x42),SCE(0x1f5f, 1, 0x82),SCE(0xa760, 0, 0x82),
+SCE(0xa761, 1, 0x42),SCE(0xa690, 0, 0x82),SCE(0xa691, 1, 0x42),SCE(0xa762, 0, 0x82),
+SCE(0xa763, 1, 0x42),SCE(0xff2e, 0, 0x82),SCE(0xff4e, 1, 0x42),SCE(0xa764, 0, 0x82),
+SCE(0xa765, 1, 0x42),SCE(0xa766, 0, 0x82),SCE(0xa767, 1, 0x42),SCE(0x1f60, 0, 0x42),
+SCE(0x1f68, 1, 0x82),SCE(0x1f61, 0, 0x42),SCE(0x1f69, 1, 0x82),SCE(0x1f62, 0, 0x42),
+SCE(0x1f6a, 1, 0x82),SCE(0x1f63, 0, 0x42),SCE(0x1f6b, 1, 0x82),SCE(0x1f64, 0, 0x42),
+SCE(0x1f6c, 1, 0x82),SCE(0x1f65, 0, 0x42),SCE(0x1f6d, 1, 0x82),SCE(0x1f66, 0, 0x42),
+SCE(0x1f6e, 1, 0x82),SCE(0x1f67, 0, 0x42),SCE(0x1f6f, 1, 0x82),SCE(0x2c1c, 0, 0x82),
+SCE(0x2c4c, 1, 0x42),SCE(0x2cbc, 0, 0x82),SCE(0x2cbd, 1, 0x42),SCE(0xa694, 0, 0x82),
+SCE(0xa695, 1, 0x42),SCE(0xa77b, 0, 0x82),SCE(0xa77c, 1, 0x42),SCE(0x1d79, 0, 0x42),
+SCE(0xa77d, 1, 0x82),SCE(0xa77e, 0, 0x82),SCE(0xa77f, 1, 0x42),SCE(0xa780, 0, 0x82),
+SCE(0xa781, 1, 0x42),SCE(0xa782, 0, 0x82),SCE(0xa783, 1, 0x42),SCE(0xa784, 0, 0x82),
+SCE(0xa785, 1, 0x42),SCE(0xa786, 0, 0x82),SCE(0xa787, 1, 0x42),SCE(0x1f80, 0, 0x42),
+SCE(0x1f88, 1, 0x2),SCE(0x1f81, 0, 0x42),SCE(0x1f89, 1, 0x2),SCE(0x1f82, 0, 0x42),
+SCE(0x1f8a, 1, 0x2),SCE(0x1f83, 0, 0x42),SCE(0x1f8b, 1, 0x2),SCE(0x1f84, 0, 0x42),
+SCE(0x1f8c, 1, 0x2),SCE(0x1f85, 0, 0x42),SCE(0x1f8d, 1, 0x2),SCE(0x1f86, 0, 0x42),
+SCE(0x1f8e, 1, 0x2),SCE(0x1f87, 0, 0x42),SCE(0x1f8f, 1, 0x2),SCE(0xa790, 0, 0x82),
+SCE(0xa791, 1, 0x42),SCE(0xa792, 0, 0x82),SCE(0xa793, 1, 0x42),SCE(0x1f90, 0, 0x42),
+SCE(0x1f98, 1, 0x2),SCE(0x1f91, 0, 0x42),SCE(0x1f99, 1, 0x2),SCE(0x1f92, 0, 0x42),
+SCE(0x1f9a, 1, 0x2),SCE(0x1f93, 0, 0x42),SCE(0x1f9b, 1, 0x2),SCE(0x1f94, 0, 0x42),
+SCE(0x1f9c, 1, 0x2),SCE(0x1f95, 0, 0x42),SCE(0x1f9d, 1, 0x2),SCE(0x1f96, 0, 0x42),
+SCE(0x1f9e, 1, 0x2),SCE(0x1f97, 0, 0x42),SCE(0x1f9f, 1, 0x2),SCE(0xa7a0, 0, 0x82),
+SCE(0xa7a1, 1, 0x42),SCE(0xa7a2, 0, 0x82),SCE(0xa7a3, 1, 0x42),SCE(0xa7a4, 0, 0x82),
+SCE(0xa7a5, 1, 0x42),SCE(0xa7a6, 0, 0x82),SCE(0xa7a7, 1, 0x42),SCE(0x1fa0, 0, 0x42),
+SCE(0x1fa8, 1, 0x2),SCE(0x1fa1, 0, 0x42),SCE(0x1fa9, 1, 0x2),SCE(0x1fa2, 0, 0x42),
+SCE(0x1faa, 1, 0x2),SCE(0x1fa3, 0, 0x42),SCE(0x1fab, 1, 0x2),SCE(0x1fa4, 0, 0x42),
+SCE(0x1fac, 1, 0x2),SCE(0x1fa5, 0, 0x42),SCE(0x1fad, 1, 0x2),SCE(0x1fa6, 0, 0x42),
+SCE(0x1fae, 1, 0x2),SCE(0x1fa7, 0, 0x42),SCE(0x1faf, 1, 0x2),SCE(0x1fb0, 0, 0x42),
+SCE(0x1fb8, 1, 0x82),SCE(0x1fb1, 0, 0x42),SCE(0x1fb9, 1, 0x82),SCE(0x1f70, 0, 0x42),
+SCE(0x1fba, 1, 0x82),SCE(0x1f71, 0, 0x42),SCE(0x1fbb, 1, 0x82),SCE(0x1fb3, 0, 0x42),
+SCE(0x1fbc, 1, 0x2),SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84),SCE(0x03b9, 2, 0x44),
+SCE(0x1fbe, 3, 0x44),SCE(0x1f72, 0, 0x42),SCE(0x1fc8, 1, 0x82),SCE(0x1f73, 0, 0x42),
+SCE(0x1fc9, 1, 0x82),SCE(0x1f74, 0, 0x42),SCE(0x1fca, 1, 0x82),SCE(0x1f75, 0, 0x42),
+SCE(0x1fcb, 1, 0x82),SCE(0x1fc3, 0, 0x42),SCE(0x1fcc, 1, 0x2),SCE(0x1fd0, 0, 0x42),
+SCE(0x1fd8, 1, 0x82),SCE(0x1fd1, 0, 0x42),SCE(0x1fd9, 1, 0x82),SCE(0x1f76, 0, 0x42),
+SCE(0x1fda, 1, 0x82),SCE(0x1f77, 0, 0x42),SCE(0x1fdb, 1, 0x82),SCE(0x10427, 0, 0x82),
+SCE(0x1044f, 1, 0x42),SCE(0x1fe0, 0, 0x42),SCE(0x1fe8, 1, 0x82),SCE(0x1fe1, 0, 0x42),
+SCE(0x1fe9, 1, 0x82),SCE(0x1f7a, 0, 0x42),SCE(0x1fea, 1, 0x82),SCE(0x1f7b, 0, 0x42),
+SCE(0x1feb, 1, 0x82),SCE(0x1fe5, 0, 0x42),SCE(0x1fec, 1, 0x82),SCE(0x10401, 0, 0x82),
+SCE(0x10429, 1, 0x42),SCE(0x1040c, 0, 0x82),SCE(0x10434, 1, 0x42),SCE(0x1f78, 0, 0x42),
+SCE(0x1ff8, 1, 0x82),SCE(0x1f79, 0, 0x42),SCE(0x1ff9, 1, 0x82),SCE(0x1f7c, 0, 0x42),
+SCE(0x1ffa, 1, 0x82),SCE(0x1f7d, 0, 0x42),SCE(0x1ffb, 1, 0x82),SCE(0x1ff3, 0, 0x42),
+SCE(0x1ffc, 1, 0x2),SCE(0x24c0, 0, 0x82),SCE(0x24da, 1, 0x42),];
+return t;
+}
+@property immutable(FullCaseEntry[]) fullCaseTable()
+{
+alias FCE = FullCaseEntry;
+static immutable FCE[] t = [
+FCE("Ⰰ", 0, 2, 1),
+FCE("ⰰ", 1, 2, 1),FCE("Ⓝ", 0, 2, 1),FCE("ⓝ", 1, 2, 1),FCE("Ⰱ", 0, 2, 1),
+FCE("ⰱ", 1, 2, 1),FCE("Ⱍ", 0, 2, 1),FCE("ⱍ", 1, 2, 1),FCE("Ⰲ", 0, 2, 1),
+FCE("ⰲ", 1, 2, 1),FCE("Ⰳ", 0, 2, 1),FCE("ⰳ", 1, 2, 1),FCE("Ⰴ", 0, 2, 1),
+FCE("ⰴ", 1, 2, 1),FCE("Ⰵ", 0, 2, 1),FCE("ⰵ", 1, 2, 1),FCE("Ⰶ", 0, 2, 1),
+FCE("ⰶ", 1, 2, 1),FCE("𐐀", 0, 2, 1),FCE("𐐨", 1, 2, 1),FCE("Ⳃ", 0, 2, 1),
+FCE("ⳃ", 1, 2, 1),FCE("Ⰷ", 0, 2, 1),FCE("ⰷ", 1, 2, 1),FCE("Ⰸ", 0, 2, 1),
+FCE("ⰸ", 1, 2, 1),FCE("Ⰹ", 0, 2, 1),FCE("ⰹ", 1, 2, 1),FCE("Ⰺ", 0, 2, 1),
+FCE("ⰺ", 1, 2, 1),FCE("Ꚍ", 0, 2, 1),FCE("ꚍ", 1, 2, 1),FCE("A", 0, 2, 1),
+FCE("a", 1, 2, 1),FCE("B", 0, 2, 1),FCE("b", 1, 2, 1),FCE("C", 0, 2, 1),
+FCE("c", 1, 2, 1),FCE("D", 0, 2, 1),FCE("d", 1, 2, 1),FCE("E", 0, 2, 1),
+FCE("e", 1, 2, 1),FCE("F", 0, 2, 1),FCE("f", 1, 2, 1),FCE("G", 0, 2, 1),
+FCE("g", 1, 2, 1),FCE("H", 0, 2, 1),FCE("h", 1, 2, 1),FCE("I", 0, 2, 1),
+FCE("i", 1, 2, 1),FCE("J", 0, 2, 1),FCE("j", 1, 2, 1),FCE("K", 0, 3, 1),
+FCE("k", 1, 3, 1),FCE("K", 2, 3, 1),FCE("L", 0, 2, 1),FCE("l", 1, 2, 1),
+FCE("M", 0, 2, 1),FCE("m", 1, 2, 1),FCE("N", 0, 2, 1),FCE("n", 1, 2, 1),
+FCE("O", 0, 2, 1),FCE("o", 1, 2, 1),FCE("P", 0, 2, 1),FCE("p", 1, 2, 1),
+FCE("Q", 0, 2, 1),FCE("q", 1, 2, 1),FCE("R", 0, 2, 1),FCE("r", 1, 2, 1),
+FCE("S", 0, 3, 1),FCE("s", 1, 3, 1),FCE("ſ", 2, 3, 1),FCE("T", 0, 2, 1),
+FCE("t", 1, 2, 1),FCE("U", 0, 2, 1),FCE("u", 1, 2, 1),FCE("V", 0, 2, 1),
+FCE("v", 1, 2, 1),FCE("W", 0, 2, 1),FCE("w", 1, 2, 1),FCE("X", 0, 2, 1),
+FCE("x", 1, 2, 1),FCE("Y", 0, 2, 1),FCE("y", 1, 2, 1),FCE("Z", 0, 2, 1),
+FCE("z", 1, 2, 1),FCE("Ⰿ", 0, 2, 1),FCE("ⰿ", 1, 2, 1),FCE("Ⱀ", 0, 2, 1),
+FCE("ⱀ", 1, 2, 1),FCE("𐐂", 0, 2, 1),FCE("𐐪", 1, 2, 1),FCE("Ⳅ", 0, 2, 1),
+FCE("ⳅ", 1, 2, 1),FCE("Ⅶ", 0, 2, 1),FCE("ⅶ", 1, 2, 1),FCE("Ⱁ", 0, 2, 1),
+FCE("ⱁ", 1, 2, 1),FCE("Ⱂ", 0, 2, 1),FCE("ⱂ", 1, 2, 1),FCE("Ⅸ", 0, 2, 1),
+FCE("ⅸ", 1, 2, 1),FCE("Ⱃ", 0, 2, 1),FCE("ⱃ", 1, 2, 1),FCE("Ꚃ", 0, 2, 1),
+FCE("ꚃ", 1, 2, 1),FCE("Ⱄ", 0, 2, 1),FCE("ⱄ", 1, 2, 1),FCE("Ⅺ", 0, 2, 1),
+FCE("ⅺ", 1, 2, 1),FCE("Ⓡ", 0, 2, 1),FCE("ⓡ", 1, 2, 1),FCE("Ⱅ", 0, 2, 1),
+FCE("ⱅ", 1, 2, 1),FCE("𐐃", 0, 2, 1),FCE("𐐫", 1, 2, 1),FCE("Ⱆ", 0, 2, 1),
+FCE("ⱆ", 1, 2, 1),FCE("Ⅼ", 0, 2, 1),FCE("ⅼ", 1, 2, 1),FCE("Ⱇ", 0, 2, 1),
+FCE("ⱇ", 1, 2, 1),FCE("Ｘ", 0, 2, 1),FCE("ｘ", 1, 2, 1),FCE("Ⱈ", 0, 2, 1),
+FCE("ⱈ", 1, 2, 1),FCE("Ⅾ", 0, 2, 1),FCE("ⅾ", 1, 2, 1),FCE("Ⱉ", 0, 2, 1),
+FCE("ⱉ", 1, 2, 1),FCE("Ⱊ", 0, 2, 1),FCE("ⱊ", 1, 2, 1),FCE("Ⱎ", 0, 2, 1),
+FCE("ⱎ", 1, 2, 1),FCE("Ⴀ", 0, 2, 1),FCE("ⴀ", 1, 2, 1),FCE("Ⴁ", 0, 2, 1),
+FCE("ⴁ", 1, 2, 1),FCE("Ⴂ", 0, 2, 1),FCE("ⴂ", 1, 2, 1),FCE("Ⴃ", 0, 2, 1),
+FCE("ⴃ", 1, 2, 1),FCE("Ⴄ", 0, 2, 1),FCE("ⴄ", 1, 2, 1),FCE("Ⴅ", 0, 2, 1),
+FCE("ⴅ", 1, 2, 1),FCE("Ⴆ", 0, 2, 1),FCE("ⴆ", 1, 2, 1),FCE("Ⴇ", 0, 2, 1),
+FCE("ⴇ", 1, 2, 1),FCE("Ⴈ", 0, 2, 1),FCE("ⴈ", 1, 2, 1),FCE("Ⴉ", 0, 2, 1),
+FCE("ⴉ", 1, 2, 1),FCE("Ⴊ", 0, 2, 1),FCE("ⴊ", 1, 2, 1),FCE("Ⴋ", 0, 2, 1),
+FCE("ⴋ", 1, 2, 1),FCE("Ⴌ", 0, 2, 1),FCE("ⴌ", 1, 2, 1),FCE("Ⴍ", 0, 2, 1),
+FCE("ⴍ", 1, 2, 1),FCE("Ⴎ", 0, 2, 1),FCE("ⴎ", 1, 2, 1),FCE("Ⴏ", 0, 2, 1),
+FCE("ⴏ", 1, 2, 1),FCE("Ⴐ", 0, 2, 1),FCE("ⴐ", 1, 2, 1),FCE("Ⴑ", 0, 2, 1),
+FCE("ⴑ", 1, 2, 1),FCE("Ⴒ", 0, 2, 1),FCE("ⴒ", 1, 2, 1),FCE("Ⴓ", 0, 2, 1),
+FCE("ⴓ", 1, 2, 1),FCE("Ⴔ", 0, 2, 1),FCE("ⴔ", 1, 2, 1),FCE("Ⴕ", 0, 2, 1),
+FCE("ⴕ", 1, 2, 1),FCE("Ⴖ", 0, 2, 1),FCE("ⴖ", 1, 2, 1),FCE("Ⴗ", 0, 2, 1),
+FCE("ⴗ", 1, 2, 1),FCE("Ⴘ", 0, 2, 1),FCE("ⴘ", 1, 2, 1),FCE("Ⴙ", 0, 2, 1),
+FCE("ⴙ", 1, 2, 1),FCE("Ⴚ", 0, 2, 1),FCE("ⴚ", 1, 2, 1),FCE("Ⴛ", 0, 2, 1),
+FCE("ⴛ", 1, 2, 1),FCE("Ⴜ", 0, 2, 1),FCE("ⴜ", 1, 2, 1),FCE("Ⴝ", 0, 2, 1),
+FCE("ⴝ", 1, 2, 1),FCE("Ⴞ", 0, 2, 1),FCE("ⴞ", 1, 2, 1),FCE("Ⴟ", 0, 2, 1),
+FCE("ⴟ", 1, 2, 1),FCE("À", 0, 2, 1),FCE("à", 1, 2, 1),FCE("Á", 0, 2, 1),
+FCE("á", 1, 2, 1),FCE("Ⴢ", 0, 2, 1),FCE("ⴢ", 1, 2, 1),FCE("Ã", 0, 2, 1),
+FCE("ã", 1, 2, 1),FCE("Ⴤ", 0, 2, 1),FCE("ⴤ", 1, 2, 1),FCE("Å", 0, 3, 1),
+FCE("å", 1, 3, 1),FCE("Å", 2, 3, 1),FCE("Æ", 0, 2, 1),FCE("æ", 1, 2, 1),
+FCE("Ç", 0, 2, 1),FCE("ç", 1, 2, 1),FCE("È", 0, 2, 1),FCE("è", 1, 2, 1),
+FCE("É", 0, 2, 1),FCE("é", 1, 2, 1),FCE("Ê", 0, 2, 1),FCE("ê", 1, 2, 1),
+FCE("Ë", 0, 2, 1),FCE("ë", 1, 2, 1),FCE("Ì", 0, 2, 1),FCE("ì", 1, 2, 1),
+FCE("Í", 0, 2, 1),FCE("í", 1, 2, 1),FCE("Î", 0, 2, 1),FCE("î", 1, 2, 1),
+FCE("Ï", 0, 2, 1),FCE("ï", 1, 2, 1),FCE("Ð", 0, 2, 1),FCE("ð", 1, 2, 1),
+FCE("Ñ", 0, 2, 1),FCE("ñ", 1, 2, 1),FCE("Ò", 0, 2, 1),FCE("ò", 1, 2, 1),
+FCE("Ó", 0, 2, 1),FCE("ó", 1, 2, 1),FCE("Ô", 0, 2, 1),FCE("ô", 1, 2, 1),
+FCE("Õ", 0, 2, 1),FCE("õ", 1, 2, 1),FCE("Ö", 0, 2, 1),FCE("ö", 1, 2, 1),
+FCE("Ø", 0, 2, 1),FCE("ø", 1, 2, 1),FCE("Ù", 0, 2, 1),FCE("ù", 1, 2, 1),
+FCE("Ú", 0, 2, 1),FCE("ú", 1, 2, 1),FCE("Û", 0, 2, 1),FCE("û", 1, 2, 1),
+FCE("Ü", 0, 2, 1),FCE("ü", 1, 2, 1),FCE("Ý", 0, 2, 1),FCE("ý", 1, 2, 1),
+FCE("Þ", 0, 2, 1),FCE("þ", 1, 2, 1),FCE("ß", 0, 3, 1),FCE("ẞ", 1, 3, 1),
+FCE("ss", 2, 3, 2),FCE("Ⱖ", 0, 2, 1),FCE("ⱖ", 1, 2, 1),FCE("Ⱗ", 0, 2, 1),
+FCE("ⱗ", 1, 2, 1),FCE("Ⱘ", 0, 2, 1),FCE("ⱘ", 1, 2, 1),FCE("𐐆", 0, 2, 1),
+FCE("𐐮", 1, 2, 1),FCE("𐐏", 0, 2, 1),FCE("𐐷", 1, 2, 1),FCE("Ⓥ", 0, 2, 1),
+FCE("ⓥ", 1, 2, 1),FCE("Ⱙ", 0, 2, 1),FCE("ⱙ", 1, 2, 1),FCE("𐐇", 0, 2, 1),
+FCE("𐐯", 1, 2, 1),FCE("Ⱚ", 0, 2, 1),FCE("ⱚ", 1, 2, 1),FCE("Ā", 0, 2, 1),
+FCE("ā", 1, 2, 1),FCE("Ă", 0, 2, 1),FCE("ă", 1, 2, 1),FCE("Ⱛ", 0, 2, 1),
+FCE("ⱛ", 1, 2, 1),FCE("Ą", 0, 2, 1),FCE("ą", 1, 2, 1),FCE("Ć", 0, 2, 1),
+FCE("ć", 1, 2, 1),FCE("Ĉ", 0, 2, 1),FCE("ĉ", 1, 2, 1),FCE("Ⱜ", 0, 2, 1),
+FCE("ⱜ", 1, 2, 1),FCE("Ċ", 0, 2, 1),FCE("ċ", 1, 2, 1),FCE("Č", 0, 2, 1),
+FCE("č", 1, 2, 1),FCE("Ď", 0, 2, 1),FCE("ď", 1, 2, 1),FCE("Ⱝ", 0, 2, 1),
+FCE("ⱝ", 1, 2, 1),FCE("Đ", 0, 2, 1),FCE("đ", 1, 2, 1),FCE("Ē", 0, 2, 1),
+FCE("ē", 1, 2, 1),FCE("Ĕ", 0, 2, 1),FCE("ĕ", 1, 2, 1),FCE("Ⱞ", 0, 2, 1),
+FCE("ⱞ", 1, 2, 1),FCE("Ė", 0, 2, 1),FCE("ė", 1, 2, 1),FCE("Ę", 0, 2, 1),
+FCE("ę", 1, 2, 1),FCE("Ě", 0, 2, 1),FCE("ě", 1, 2, 1),FCE("Ĝ", 0, 2, 1),
+FCE("ĝ", 1, 2, 1),FCE("Ğ", 0, 2, 1),FCE("ğ", 1, 2, 1),FCE("Ġ", 0, 2, 1),
+FCE("ġ", 1, 2, 1),FCE("Ģ", 0, 2, 1),FCE("ģ", 1, 2, 1),FCE("Ｋ", 0, 2, 1),
+FCE("ｋ", 1, 2, 1),FCE("Ĥ", 0, 2, 1),FCE("ĥ", 1, 2, 1),FCE("Ħ", 0, 2, 1),
+FCE("ħ", 1, 2, 1),FCE("Ĩ", 0, 2, 1),FCE("ĩ", 1, 2, 1),FCE("Ī", 0, 2, 1),
+FCE("ī", 1, 2, 1),FCE("Å", 0, 3, 1),FCE("å", 1, 3, 1),FCE("Å", 2, 3, 1),
+FCE("Ĭ", 0, 2, 1),FCE("ĭ", 1, 2, 1),FCE("Į", 0, 2, 1),FCE("į", 1, 2, 1),
+FCE("İ", 0, 2, 1),FCE("i̇", 1, 2, 2),FCE("Ĳ", 0, 2, 1),FCE("ĳ", 1, 2, 1),
+FCE("Ĵ", 0, 2, 1),FCE("ĵ", 1, 2, 1),FCE("Ķ", 0, 2, 1),FCE("ķ", 1, 2, 1),
+FCE("Ĺ", 0, 2, 1),FCE("ĺ", 1, 2, 1),FCE("Ļ", 0, 2, 1),FCE("ļ", 1, 2, 1),
+FCE("Ⳟ", 0, 2, 1),FCE("ⳟ", 1, 2, 1),FCE("Ľ", 0, 2, 1),FCE("ľ", 1, 2, 1),
+FCE("Ŀ", 0, 2, 1),FCE("ŀ", 1, 2, 1),FCE("Ł", 0, 2, 1),FCE("ł", 1, 2, 1),
+FCE("Ń", 0, 2, 1),FCE("ń", 1, 2, 1),FCE("Ņ", 0, 2, 1),FCE("ņ", 1, 2, 1),
+FCE("Ň", 0, 2, 1),FCE("ň", 1, 2, 1),FCE("ŉ", 0, 2, 1),FCE("ʼn", 1, 2, 2),
+FCE("Ŋ", 0, 2, 1),FCE("ŋ", 1, 2, 1),FCE("Ō", 0, 2, 1),FCE("ō", 1, 2, 1),
+FCE("Ŏ", 0, 2, 1),FCE("ŏ", 1, 2, 1),FCE("Ő", 0, 2, 1),FCE("ő", 1, 2, 1),
+FCE("Œ", 0, 2, 1),FCE("œ", 1, 2, 1),FCE("Ŕ", 0, 2, 1),FCE("ŕ", 1, 2, 1),
+FCE("Ŗ", 0, 2, 1),FCE("ŗ", 1, 2, 1),FCE("Ř", 0, 2, 1),FCE("ř", 1, 2, 1),
+FCE("Ś", 0, 2, 1),FCE("ś", 1, 2, 1),FCE("Ŝ", 0, 2, 1),FCE("ŝ", 1, 2, 1),
+FCE("Ş", 0, 2, 1),FCE("ş", 1, 2, 1),FCE("Š", 0, 2, 1),FCE("š", 1, 2, 1),
+FCE("Ⅱ", 0, 2, 1),FCE("ⅱ", 1, 2, 1),FCE("Ţ", 0, 2, 1),FCE("ţ", 1, 2, 1),
+FCE("Ⅳ", 0, 2, 1),FCE("ⅳ", 1, 2, 1),FCE("Ť", 0, 2, 1),FCE("ť", 1, 2, 1),
+FCE("Ⅵ", 0, 2, 1),FCE("ⅵ", 1, 2, 1),FCE("Ŧ", 0, 2, 1),FCE("ŧ", 1, 2, 1),
+FCE("Ⅷ", 0, 2, 1),FCE("ⅷ", 1, 2, 1),FCE("Ũ", 0, 2, 1),FCE("ũ", 1, 2, 1),
+FCE("Ⅹ", 0, 2, 1),FCE("ⅹ", 1, 2, 1),FCE("Ū", 0, 2, 1),FCE("ū", 1, 2, 1),
+FCE("Ⅻ", 0, 2, 1),FCE("ⅻ", 1, 2, 1),FCE("Ŭ", 0, 2, 1),FCE("ŭ", 1, 2, 1),
+FCE("Ⅽ", 0, 2, 1),FCE("ⅽ", 1, 2, 1),FCE("Ů", 0, 2, 1),FCE("ů", 1, 2, 1),
+FCE("Ⅿ", 0, 2, 1),FCE("ⅿ", 1, 2, 1),FCE("Ű", 0, 2, 1),FCE("ű", 1, 2, 1),
+FCE("Ⳍ", 0, 2, 1),FCE("ⳍ", 1, 2, 1),FCE("Ų", 0, 2, 1),FCE("ų", 1, 2, 1),
+FCE("Ŵ", 0, 2, 1),FCE("ŵ", 1, 2, 1),FCE("Ŷ", 0, 2, 1),FCE("ŷ", 1, 2, 1),
+FCE("ÿ", 0, 2, 1),FCE("Ÿ", 1, 2, 1),FCE("Ź", 0, 2, 1),FCE("ź", 1, 2, 1),
+FCE("Ż", 0, 2, 1),FCE("ż", 1, 2, 1),FCE("Ž", 0, 2, 1),FCE("ž", 1, 2, 1),
+FCE("S", 0, 3, 1),FCE("s", 1, 3, 1),FCE("ſ", 2, 3, 1),FCE("Ɓ", 0, 2, 1),
+FCE("ɓ", 1, 2, 1),FCE("Ƃ", 0, 2, 1),FCE("ƃ", 1, 2, 1),FCE("Ↄ", 0, 2, 1),
+FCE("ↄ", 1, 2, 1),FCE("Ƅ", 0, 2, 1),FCE("ƅ", 1, 2, 1),FCE("Ɔ", 0, 2, 1),
+FCE("ɔ", 1, 2, 1),FCE("Ƈ", 0, 2, 1),FCE("ƈ", 1, 2, 1),FCE("Ɖ", 0, 2, 1),
+FCE("ɖ", 1, 2, 1),FCE("Ɗ", 0, 2, 1),FCE("ɗ", 1, 2, 1),FCE("Ƌ", 0, 2, 1),
+FCE("ƌ", 1, 2, 1),FCE("Ǝ", 0, 2, 1),FCE("ǝ", 1, 2, 1),FCE("Ə", 0, 2, 1),
+FCE("ə", 1, 2, 1),FCE("Ɛ", 0, 2, 1),FCE("ɛ", 1, 2, 1),FCE("Ƒ", 0, 2, 1),
+FCE("ƒ", 1, 2, 1),FCE("Ɠ", 0, 2, 1),FCE("ɠ", 1, 2, 1),FCE("Ɣ", 0, 2, 1),
+FCE("ɣ", 1, 2, 1),FCE("Ɩ", 0, 2, 1),FCE("ɩ", 1, 2, 1),FCE("Ɨ", 0, 2, 1),
+FCE("ɨ", 1, 2, 1),FCE("Ƙ", 0, 2, 1),FCE("ƙ", 1, 2, 1),FCE("Ɯ", 0, 2, 1),
+FCE("ɯ", 1, 2, 1),FCE("Ɲ", 0, 2, 1),FCE("ɲ", 1, 2, 1),FCE("Ꙋ", 0, 2, 1),
+FCE("ꙋ", 1, 2, 1),FCE("Ɵ", 0, 2, 1),FCE("ɵ", 1, 2, 1),FCE("Ơ", 0, 2, 1),
+FCE("ơ", 1, 2, 1),FCE("Ƣ", 0, 2, 1),FCE("ƣ", 1, 2, 1),FCE("Ƥ", 0, 2, 1),
+FCE("ƥ", 1, 2, 1),FCE("Ʀ", 0, 2, 1),FCE("ʀ", 1, 2, 1),FCE("Ƨ", 0, 2, 1),
+FCE("ƨ", 1, 2, 1),FCE("Ʃ", 0, 2, 1),FCE("ʃ", 1, 2, 1),FCE("Ƭ", 0, 2, 1),
+FCE("ƭ", 1, 2, 1),FCE("Ʈ", 0, 2, 1),FCE("ʈ", 1, 2, 1),FCE("Ư", 0, 2, 1),
+FCE("ư", 1, 2, 1),FCE("Ʊ", 0, 2, 1),FCE("ʊ", 1, 2, 1),FCE("Ʋ", 0, 2, 1),
+FCE("ʋ", 1, 2, 1),FCE("Ƴ", 0, 2, 1),FCE("ƴ", 1, 2, 1),FCE("Ƶ", 0, 2, 1),
+FCE("ƶ", 1, 2, 1),FCE("Ʒ", 0, 2, 1),FCE("ʒ", 1, 2, 1),FCE("Ƹ", 0, 2, 1),
+FCE("ƹ", 1, 2, 1),FCE("Ƽ", 0, 2, 1),FCE("ƽ", 1, 2, 1),FCE("Ǆ", 0, 3, 1),
+FCE("ǅ", 1, 3, 1),FCE("ǆ", 2, 3, 1),FCE("Ǆ", 0, 3, 1),FCE("ǅ", 1, 3, 1),
+FCE("ǆ", 2, 3, 1),FCE("Ǉ", 0, 3, 1),FCE("ǈ", 1, 3, 1),FCE("ǉ", 2, 3, 1),
+FCE("Ǉ", 0, 3, 1),FCE("ǈ", 1, 3, 1),FCE("ǉ", 2, 3, 1),FCE("Ǌ", 0, 3, 1),
+FCE("ǋ", 1, 3, 1),FCE("ǌ", 2, 3, 1),FCE("Ǌ", 0, 3, 1),FCE("ǋ", 1, 3, 1),
+FCE("ǌ", 2, 3, 1),FCE("Ǎ", 0, 2, 1),FCE("ǎ", 1, 2, 1),FCE("Ǐ", 0, 2, 1),
+FCE("ǐ", 1, 2, 1),FCE("Ǒ", 0, 2, 1),FCE("ǒ", 1, 2, 1),FCE("Ǔ", 0, 2, 1),
+FCE("ǔ", 1, 2, 1),FCE("Ǖ", 0, 2, 1),FCE("ǖ", 1, 2, 1),FCE("Ǘ", 0, 2, 1),
+FCE("ǘ", 1, 2, 1),FCE("Ǚ", 0, 2, 1),FCE("ǚ", 1, 2, 1),FCE("Ǜ", 0, 2, 1),
+FCE("ǜ", 1, 2, 1),FCE("Ǟ", 0, 2, 1),FCE("ǟ", 1, 2, 1),FCE("Ｖ", 0, 2, 1),
+FCE("ｖ", 1, 2, 1),FCE("Ǡ", 0, 2, 1),FCE("ǡ", 1, 2, 1),FCE("Ǣ", 0, 2, 1),
+FCE("ǣ", 1, 2, 1),FCE("Ǥ", 0, 2, 1),FCE("ǥ", 1, 2, 1),FCE("Ǧ", 0, 2, 1),
+FCE("ǧ", 1, 2, 1),FCE("Ǩ", 0, 2, 1),FCE("ǩ", 1, 2, 1),FCE("Ǫ", 0, 2, 1),
+FCE("ǫ", 1, 2, 1),FCE("Ǭ", 0, 2, 1),FCE("ǭ", 1, 2, 1),FCE("Ǯ", 0, 2, 1),
+FCE("ǯ", 1, 2, 1),FCE("ǰ", 0, 2, 1),FCE("ǰ", 1, 2, 2),FCE("Ǳ", 0, 3, 1),
+FCE("ǲ", 1, 3, 1),FCE("ǳ", 2, 3, 1),FCE("Ǳ", 0, 3, 1),FCE("ǲ", 1, 3, 1),
+FCE("ǳ", 2, 3, 1),FCE("Ǵ", 0, 2, 1),FCE("ǵ", 1, 2, 1),FCE("ƕ", 0, 2, 1),
+FCE("Ƕ", 1, 2, 1),FCE("ƿ", 0, 2, 1),FCE("Ƿ", 1, 2, 1),FCE("Ǹ", 0, 2, 1),
+FCE("ǹ", 1, 2, 1),FCE("𐐝", 0, 2, 1),FCE("𐑅", 1, 2, 1),FCE("Ǻ", 0, 2, 1),
+FCE("ǻ", 1, 2, 1),FCE("Ǽ", 0, 2, 1),FCE("ǽ", 1, 2, 1),FCE("Ǿ", 0, 2, 1),
+FCE("ǿ", 1, 2, 1),FCE("Ȁ", 0, 2, 1),FCE("ȁ", 1, 2, 1),FCE("Ȃ", 0, 2, 1),
+FCE("ȃ", 1, 2, 1),FCE("Ȅ", 0, 2, 1),FCE("ȅ", 1, 2, 1),FCE("Ȇ", 0, 2, 1),
+FCE("ȇ", 1, 2, 1),FCE("ﬁ", 0, 2, 1),FCE("fi", 1, 2, 2),FCE("Ȉ", 0, 2, 1),
+FCE("ȉ", 1, 2, 1),FCE("Ȋ", 0, 2, 1),FCE("ȋ", 1, 2, 1),FCE("Ȍ", 0, 2, 1),
+FCE("ȍ", 1, 2, 1),FCE("Ȏ", 0, 2, 1),FCE("ȏ", 1, 2, 1),FCE("Ȑ", 0, 2, 1),
+FCE("ȑ", 1, 2, 1),FCE("Ȓ", 0, 2, 1),FCE("ȓ", 1, 2, 1),FCE("Ȕ", 0, 2, 1),
+FCE("ȕ", 1, 2, 1),FCE("Ȗ", 0, 2, 1),FCE("ȗ", 1, 2, 1),FCE("Ș", 0, 2, 1),
+FCE("ș", 1, 2, 1),FCE("Ț", 0, 2, 1),FCE("ț", 1, 2, 1),FCE("Ȝ", 0, 2, 1),
+FCE("ȝ", 1, 2, 1),FCE("Ȟ", 0, 2, 1),FCE("ȟ", 1, 2, 1),FCE("ƞ", 0, 2, 1),
+FCE("Ƞ", 1, 2, 1),FCE("Ȣ", 0, 2, 1),FCE("ȣ", 1, 2, 1),FCE("Ȥ", 0, 2, 1),
+FCE("ȥ", 1, 2, 1),FCE("Ȧ", 0, 2, 1),FCE("ȧ", 1, 2, 1),FCE("Ȩ", 0, 2, 1),
+FCE("ȩ", 1, 2, 1),FCE("Ꝗ", 0, 2, 1),FCE("ꝗ", 1, 2, 1),FCE("Ȫ", 0, 2, 1),
+FCE("ȫ", 1, 2, 1),FCE("Ȭ", 0, 2, 1),FCE("ȭ", 1, 2, 1),FCE("Ȯ", 0, 2, 1),
+FCE("ȯ", 1, 2, 1),FCE("Ȱ", 0, 2, 1),FCE("ȱ", 1, 2, 1),FCE("Ȳ", 0, 2, 1),
+FCE("ȳ", 1, 2, 1),FCE("Ꚅ", 0, 2, 1),FCE("ꚅ", 1, 2, 1),FCE("Ⱥ", 0, 2, 1),
+FCE("ⱥ", 1, 2, 1),FCE("Ȼ", 0, 2, 1),FCE("ȼ", 1, 2, 1),FCE("ƚ", 0, 2, 1),
+FCE("Ƚ", 1, 2, 1),FCE("Ⱦ", 0, 2, 1),FCE("ⱦ", 1, 2, 1),FCE("Ɂ", 0, 2, 1),
+FCE("ɂ", 1, 2, 1),FCE("𐐒", 0, 2, 1),FCE("𐐺", 1, 2, 1),FCE("ƀ", 0, 2, 1),
+FCE("Ƀ", 1, 2, 1),FCE("Ʉ", 0, 2, 1),FCE("ʉ", 1, 2, 1),FCE("Ʌ", 0, 2, 1),
+FCE("ʌ", 1, 2, 1),FCE("Ɇ", 0, 2, 1),FCE("ɇ", 1, 2, 1),FCE("Ɉ", 0, 2, 1),
+FCE("ɉ", 1, 2, 1),FCE("Ɋ", 0, 2, 1),FCE("ɋ", 1, 2, 1),FCE("Ɍ", 0, 2, 1),
+FCE("ɍ", 1, 2, 1),FCE("Ⱋ", 0, 2, 1),FCE("ⱋ", 1, 2, 1),FCE("Ɏ", 0, 2, 1),
+FCE("ɏ", 1, 2, 1),FCE("𐐊", 0, 2, 1),FCE("𐐲", 1, 2, 1),FCE("Ⅰ", 0, 2, 1),
+FCE("ⅰ", 1, 2, 1),FCE("Ꚓ", 0, 2, 1),FCE("ꚓ", 1, 2, 1),FCE("ɽ", 0, 2, 1),
+FCE("Ɽ", 1, 2, 1),FCE("𐐐", 0, 2, 1),FCE("𐐸", 1, 2, 1),FCE("Ⱑ", 0, 2, 1),
+FCE("ⱑ", 1, 2, 1),FCE("Ⱪ", 0, 2, 1),FCE("ⱪ", 1, 2, 1),FCE("𐐉", 0, 2, 1),
+FCE("𐐱", 1, 2, 1),FCE("𐐔", 0, 2, 1),FCE("𐐼", 1, 2, 1),FCE("ﬕ", 0, 2, 1),
+FCE("մի", 1, 2, 2),FCE("Ⅲ", 0, 2, 1),FCE("ⅲ", 1, 2, 1),FCE("𐐞", 0, 2, 1),
+FCE("𐑆", 1, 2, 1),FCE("ɱ", 0, 2, 1),FCE("Ɱ", 1, 2, 1),FCE("𐐕", 0, 2, 1),
+FCE("𐐽", 1, 2, 1),FCE("ɒ", 0, 2, 1),FCE("Ɒ", 1, 2, 1),FCE("Ⱳ", 0, 2, 1),
+FCE("ⱳ", 1, 2, 1),FCE("Ⰻ", 0, 2, 1),FCE("ⰻ", 1, 2, 1),FCE("𐐖", 0, 2, 1),
+FCE("𐐾", 1, 2, 1),FCE("Ꚗ", 0, 2, 1),FCE("ꚗ", 1, 2, 1),FCE("Ⱶ", 0, 2, 1),
+FCE("ⱶ", 1, 2, 1),FCE("Ⅴ", 0, 2, 1),FCE("ⅴ", 1, 2, 1),FCE("Ꙁ", 0, 2, 1),
+FCE("ꙁ", 1, 2, 1),FCE("Ｂ", 0, 2, 1),FCE("ｂ", 1, 2, 1),FCE("Ⰼ", 0, 2, 1),
+FCE("ⰼ", 1, 2, 1),FCE("𐐗", 0, 2, 1),FCE("𐐿", 1, 2, 1),FCE("Ｄ", 0, 2, 1),
+FCE("ｄ", 1, 2, 1),FCE("Ｅ", 0, 2, 1),FCE("ｅ", 1, 2, 1),FCE("Ｆ", 0, 2, 1),
+FCE("ｆ", 1, 2, 1),FCE("Ⰽ", 0, 2, 1),FCE("ⰽ", 1, 2, 1),FCE("Ⓛ", 0, 2, 1),
+FCE("ⓛ", 1, 2, 1),FCE("Ꜩ", 0, 2, 1),FCE("ꜩ", 1, 2, 1),FCE("ȿ", 0, 2, 1),
+FCE("Ȿ", 1, 2, 1),FCE("𐐑", 0, 2, 1),FCE("𐐹", 1, 2, 1),FCE("Ｉ", 0, 2, 1),
+FCE("ｉ", 1, 2, 1),FCE("𐐋", 0, 2, 1),FCE("𐐳", 1, 2, 1),FCE("Ꜫ", 0, 2, 1),
+FCE("ꜫ", 1, 2, 1),FCE("ﬀ", 0, 2, 1),FCE("ff", 1, 2, 2),FCE("Ⲁ", 0, 2, 1),
+FCE("ⲁ", 1, 2, 1),FCE("ﬂ", 0, 2, 1),FCE("fl", 1, 2, 2),FCE("ﬃ", 0, 2, 1),
+FCE("ffi", 1, 2, 3),FCE("ﬄ", 0, 2, 1),FCE("ffl", 1, 2, 3),FCE("ﬅ", 0, 3, 1),
+FCE("ﬆ", 1, 3, 1),FCE("st", 2, 3, 2),FCE("ﬅ", 0, 3, 1),FCE("ﬆ", 1, 3, 1),
+FCE("st", 2, 3, 2),FCE("Ꜭ", 0, 2, 1),FCE("ꜭ", 1, 2, 1),FCE("Ⰾ", 0, 2, 1),
+FCE("ⰾ", 1, 2, 1),FCE("Ｍ", 0, 2, 1),FCE("ｍ", 1, 2, 1),FCE("ﬓ", 0, 2, 1),
+FCE("մն", 1, 2, 2),FCE("ﬔ", 0, 2, 1),FCE("մե", 1, 2, 2),FCE("Ꜯ", 0, 2, 1),
+FCE("ꜯ", 1, 2, 1),FCE("ﬖ", 0, 2, 1),FCE("վն", 1, 2, 2),FCE("ﬗ", 0, 2, 1),
+FCE("մխ", 1, 2, 2),FCE("𐐍", 0, 2, 1),FCE("𐐵", 1, 2, 1),FCE("Ｏ", 0, 2, 1),
+FCE("ｏ", 1, 2, 1),FCE("Ｑ", 0, 2, 1),FCE("ｑ", 1, 2, 1),FCE("Ｒ", 0, 2, 1),
+FCE("ｒ", 1, 2, 1),FCE("𐐚", 0, 2, 1),FCE("𐑂", 1, 2, 1),FCE("Ｔ", 0, 2, 1),
+FCE("ｔ", 1, 2, 1),FCE("Ⲙ", 0, 2, 1),FCE("ⲙ", 1, 2, 1),FCE("Ⲋ", 0, 2, 1),
+FCE("ⲋ", 1, 2, 1),FCE("ͅ", 0, 4, 1),FCE("Ι", 1, 4, 1),FCE("ι", 2, 4, 1),
+FCE("ι", 3, 4, 1),FCE("Ⲍ", 0, 2, 1),FCE("ⲍ", 1, 2, 1),FCE("Ｗ", 0, 2, 1),
+FCE("ｗ", 1, 2, 1),FCE("Ꙗ", 0, 2, 1),FCE("ꙗ", 1, 2, 1),FCE("𐐛", 0, 2, 1),
+FCE("𐑃", 1, 2, 1),FCE("Ꜹ", 0, 2, 1),FCE("ꜹ", 1, 2, 1),FCE("Ⲏ", 0, 2, 1),
+FCE("ⲏ", 1, 2, 1),FCE("Ｙ", 0, 2, 1),FCE("ｙ", 1, 2, 1),FCE("𐐄", 0, 2, 1),
+FCE("𐐬", 1, 2, 1),FCE("Ꜻ", 0, 2, 1),FCE("ꜻ", 1, 2, 1),FCE("Ⲑ", 0, 2, 1),
+FCE("ⲑ", 1, 2, 1),FCE("Ꜽ", 0, 2, 1),FCE("ꜽ", 1, 2, 1),FCE("Ⲓ", 0, 2, 1),
+FCE("ⲓ", 1, 2, 1),FCE("𐐜", 0, 2, 1),FCE("𐑄", 1, 2, 1),FCE("Ͱ", 0, 2, 1),
+FCE("ͱ", 1, 2, 1),FCE("Ͳ", 0, 2, 1),FCE("ͳ", 1, 2, 1),FCE("Ꜿ", 0, 2, 1),
+FCE("ꜿ", 1, 2, 1),FCE("Ͷ", 0, 2, 1),FCE("ͷ", 1, 2, 1),FCE("Ⲕ", 0, 2, 1),
+FCE("ⲕ", 1, 2, 1),FCE("Ⲗ", 0, 2, 1),FCE("ⲗ", 1, 2, 1),FCE("Ά", 0, 2, 1),
+FCE("ά", 1, 2, 1),FCE("𐐅", 0, 2, 1),FCE("𐐭", 1, 2, 1),FCE("Έ", 0, 2, 1),
+FCE("έ", 1, 2, 1),FCE("Ή", 0, 2, 1),FCE("ή", 1, 2, 1),FCE("Ί", 0, 2, 1),
+FCE("ί", 1, 2, 1),FCE("Ό", 0, 2, 1),FCE("ό", 1, 2, 1),FCE("Ύ", 0, 2, 1),
+FCE("ύ", 1, 2, 1),FCE("Ώ", 0, 2, 1),FCE("ώ", 1, 2, 1),FCE("ΐ", 0, 3, 1),
+FCE("ΐ", 1, 3, 1),FCE("ΐ", 2, 3, 3),FCE("Α", 0, 2, 1),FCE("α", 1, 2, 1),
+FCE("Β", 0, 3, 1),FCE("β", 1, 3, 1),FCE("ϐ", 2, 3, 1),FCE("Γ", 0, 2, 1),
+FCE("γ", 1, 2, 1),FCE("Δ", 0, 2, 1),FCE("δ", 1, 2, 1),FCE("Ε", 0, 3, 1),
+FCE("ε", 1, 3, 1),FCE("ϵ", 2, 3, 1),FCE("Ζ", 0, 2, 1),FCE("ζ", 1, 2, 1),
+FCE("Η", 0, 2, 1),FCE("η", 1, 2, 1),FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1),
+FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1),FCE("ͅ", 0, 4, 1),FCE("Ι", 1, 4, 1),
+FCE("ι", 2, 4, 1),FCE("ι", 3, 4, 1),FCE("Κ", 0, 3, 1),FCE("κ", 1, 3, 1),
+FCE("ϰ", 2, 3, 1),FCE("Λ", 0, 2, 1),FCE("λ", 1, 2, 1),FCE("µ", 0, 3, 1),
+FCE("Μ", 1, 3, 1),FCE("μ", 2, 3, 1),FCE("Ν", 0, 2, 1),FCE("ν", 1, 2, 1),
+FCE("Ξ", 0, 2, 1),FCE("ξ", 1, 2, 1),FCE("Ο", 0, 2, 1),FCE("ο", 1, 2, 1),
+FCE("Π", 0, 3, 1),FCE("π", 1, 3, 1),FCE("ϖ", 2, 3, 1),FCE("Ρ", 0, 3, 1),
+FCE("ρ", 1, 3, 1),FCE("ϱ", 2, 3, 1),FCE("Σ", 0, 3, 1),FCE("ς", 1, 3, 1),
+FCE("σ", 2, 3, 1),FCE("Τ", 0, 2, 1),FCE("τ", 1, 2, 1),FCE("Υ", 0, 2, 1),
+FCE("υ", 1, 2, 1),FCE("Φ", 0, 3, 1),FCE("φ", 1, 3, 1),FCE("ϕ", 2, 3, 1),
+FCE("Χ", 0, 2, 1),FCE("χ", 1, 2, 1),FCE("Ψ", 0, 2, 1),FCE("ψ", 1, 2, 1),
+FCE("Ω", 0, 3, 1),FCE("ω", 1, 3, 1),FCE("Ω", 2, 3, 1),FCE("Ϊ", 0, 2, 1),
+FCE("ϊ", 1, 2, 1),FCE("Ϋ", 0, 2, 1),FCE("ϋ", 1, 2, 1),FCE("Ⓣ", 0, 2, 1),
+FCE("ⓣ", 1, 2, 1),FCE("Ⳡ", 0, 2, 1),FCE("ⳡ", 1, 2, 1),FCE("ΰ", 0, 3, 1),
+FCE("ΰ", 1, 3, 1),FCE("ΰ", 2, 3, 3),FCE("Ꝉ", 0, 2, 1),FCE("ꝉ", 1, 2, 1),
+FCE("Ⲝ", 0, 2, 1),FCE("ⲝ", 1, 2, 1),FCE("Ⲟ", 0, 2, 1),FCE("ⲟ", 1, 2, 1),
+FCE("Ꝋ", 0, 2, 1),FCE("ꝋ", 1, 2, 1),FCE("Ⲡ", 0, 2, 1),FCE("ⲡ", 1, 2, 1),
+FCE("Σ", 0, 3, 1),FCE("ς", 1, 3, 1),FCE("σ", 2, 3, 1),FCE("𐐟", 0, 2, 1),
+FCE("𐑇", 1, 2, 1),FCE("Ꝍ", 0, 2, 1),FCE("ꝍ", 1, 2, 1),FCE("Ꚋ", 0, 2, 1),
+FCE("ꚋ", 1, 2, 1),FCE("Ⲣ", 0, 2, 1),FCE("ⲣ", 1, 2, 1),FCE("Ϗ", 0, 2, 1),
+FCE("ϗ", 1, 2, 1),FCE("Β", 0, 3, 1),FCE("β", 1, 3, 1),FCE("ϐ", 2, 3, 1),
+FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1),FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1),
+FCE("Φ", 0, 3, 1),FCE("φ", 1, 3, 1),FCE("ϕ", 2, 3, 1),FCE("Π", 0, 3, 1),
+FCE("π", 1, 3, 1),FCE("ϖ", 2, 3, 1),FCE("Ϙ", 0, 2, 1),FCE("ϙ", 1, 2, 1),
+FCE("Ⲥ", 0, 2, 1),FCE("ⲥ", 1, 2, 1),FCE("Ϛ", 0, 2, 1),FCE("ϛ", 1, 2, 1),
+FCE("Ϝ", 0, 2, 1),FCE("ϝ", 1, 2, 1),FCE("Ϟ", 0, 2, 1),FCE("ϟ", 1, 2, 1),
+FCE("Ϡ", 0, 2, 1),FCE("ϡ", 1, 2, 1),FCE("Ꝑ", 0, 2, 1),FCE("ꝑ", 1, 2, 1),
+FCE("Ϣ", 0, 2, 1),FCE("ϣ", 1, 2, 1),FCE("Ϥ", 0, 2, 1),FCE("ϥ", 1, 2, 1),
+FCE("Ⲧ", 0, 2, 1),FCE("ⲧ", 1, 2, 1),FCE("Ϧ", 0, 2, 1),FCE("ϧ", 1, 2, 1),
+FCE("𐐠", 0, 2, 1),FCE("𐑈", 1, 2, 1),FCE("Ϩ", 0, 2, 1),FCE("ϩ", 1, 2, 1),
+FCE("Ⳣ", 0, 2, 1),FCE("ⳣ", 1, 2, 1),FCE("Ϫ", 0, 2, 1),FCE("ϫ", 1, 2, 1),
+FCE("Ϭ", 0, 2, 1),FCE("ϭ", 1, 2, 1),FCE("Ꝓ", 0, 2, 1),FCE("ꝓ", 1, 2, 1),
+FCE("Ϯ", 0, 2, 1),FCE("ϯ", 1, 2, 1),FCE("Κ", 0, 3, 1),FCE("κ", 1, 3, 1),
+FCE("ϰ", 2, 3, 1),FCE("Ρ", 0, 3, 1),FCE("ρ", 1, 3, 1),FCE("ϱ", 2, 3, 1),
+FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1),FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1),
+FCE("Ε", 0, 3, 1),FCE("ε", 1, 3, 1),FCE("ϵ", 2, 3, 1),FCE("Ϸ", 0, 2, 1),
+FCE("ϸ", 1, 2, 1),FCE("ϲ", 0, 2, 1),FCE("Ϲ", 1, 2, 1),FCE("Ϻ", 0, 2, 1),
+FCE("ϻ", 1, 2, 1),FCE("ͻ", 0, 2, 1),FCE("Ͻ", 1, 2, 1),FCE("ͼ", 0, 2, 1),
+FCE("Ͼ", 1, 2, 1),FCE("ͽ", 0, 2, 1),FCE("Ͽ", 1, 2, 1),FCE("Ѐ", 0, 2, 1),
+FCE("ѐ", 1, 2, 1),FCE("Ё", 0, 2, 1),FCE("ё", 1, 2, 1),FCE("Ђ", 0, 2, 1),
+FCE("ђ", 1, 2, 1),FCE("Ѓ", 0, 2, 1),FCE("ѓ", 1, 2, 1),FCE("Є", 0, 2, 1),
+FCE("є", 1, 2, 1),FCE("Ѕ", 0, 2, 1),FCE("ѕ", 1, 2, 1),FCE("І", 0, 2, 1),
+FCE("і", 1, 2, 1),FCE("Ї", 0, 2, 1),FCE("ї", 1, 2, 1),FCE("Ј", 0, 2, 1),
+FCE("ј", 1, 2, 1),FCE("Љ", 0, 2, 1),FCE("љ", 1, 2, 1),FCE("Њ", 0, 2, 1),
+FCE("њ", 1, 2, 1),FCE("Ћ", 0, 2, 1),FCE("ћ", 1, 2, 1),FCE("Ќ", 0, 2, 1),
+FCE("ќ", 1, 2, 1),FCE("Ѝ", 0, 2, 1),FCE("ѝ", 1, 2, 1),FCE("Ў", 0, 2, 1),
+FCE("ў", 1, 2, 1),FCE("Џ", 0, 2, 1),FCE("џ", 1, 2, 1),FCE("А", 0, 2, 1),
+FCE("а", 1, 2, 1),FCE("Б", 0, 2, 1),FCE("б", 1, 2, 1),FCE("В", 0, 2, 1),
+FCE("в", 1, 2, 1),FCE("Г", 0, 2, 1),FCE("г", 1, 2, 1),FCE("Д", 0, 2, 1),
+FCE("д", 1, 2, 1),FCE("Е", 0, 2, 1),FCE("е", 1, 2, 1),FCE("Ж", 0, 2, 1),
+FCE("ж", 1, 2, 1),FCE("З", 0, 2, 1),FCE("з", 1, 2, 1),FCE("И", 0, 2, 1),
+FCE("и", 1, 2, 1),FCE("Й", 0, 2, 1),FCE("й", 1, 2, 1),FCE("К", 0, 2, 1),
+FCE("к", 1, 2, 1),FCE("Л", 0, 2, 1),FCE("л", 1, 2, 1),FCE("М", 0, 2, 1),
+FCE("м", 1, 2, 1),FCE("Н", 0, 2, 1),FCE("н", 1, 2, 1),FCE("О", 0, 2, 1),
+FCE("о", 1, 2, 1),FCE("П", 0, 2, 1),FCE("п", 1, 2, 1),FCE("Р", 0, 2, 1),
+FCE("р", 1, 2, 1),FCE("С", 0, 2, 1),FCE("с", 1, 2, 1),FCE("Т", 0, 2, 1),
+FCE("т", 1, 2, 1),FCE("У", 0, 2, 1),FCE("у", 1, 2, 1),FCE("Ф", 0, 2, 1),
+FCE("ф", 1, 2, 1),FCE("Х", 0, 2, 1),FCE("х", 1, 2, 1),FCE("Ц", 0, 2, 1),
+FCE("ц", 1, 2, 1),FCE("Ч", 0, 2, 1),FCE("ч", 1, 2, 1),FCE("Ш", 0, 2, 1),
+FCE("ш", 1, 2, 1),FCE("Щ", 0, 2, 1),FCE("щ", 1, 2, 1),FCE("Ъ", 0, 2, 1),
+FCE("ъ", 1, 2, 1),FCE("Ы", 0, 2, 1),FCE("ы", 1, 2, 1),FCE("Ь", 0, 2, 1),
+FCE("ь", 1, 2, 1),FCE("Э", 0, 2, 1),FCE("э", 1, 2, 1),FCE("Ю", 0, 2, 1),
+FCE("ю", 1, 2, 1),FCE("Я", 0, 2, 1),FCE("я", 1, 2, 1),FCE("Ｚ", 0, 2, 1),
+FCE("ｚ", 1, 2, 1),FCE("Ⲵ", 0, 2, 1),FCE("ⲵ", 1, 2, 1),FCE("µ", 0, 3, 1),
+FCE("Μ", 1, 3, 1),FCE("μ", 2, 3, 1),FCE("𐐣", 0, 2, 1),FCE("𐑋", 1, 2, 1),
+FCE("Ⓐ", 0, 2, 1),FCE("ⓐ", 1, 2, 1),FCE("Ⓒ", 0, 2, 1),FCE("ⓒ", 1, 2, 1),
+FCE("Ｌ", 0, 2, 1),FCE("ｌ", 1, 2, 1),FCE("𐐡", 0, 2, 1),FCE("𐑉", 1, 2, 1),
+FCE("Ⓔ", 0, 2, 1),FCE("ⓔ", 1, 2, 1),FCE("𐐙", 0, 2, 1),FCE("𐑁", 1, 2, 1),
+FCE("Ѡ", 0, 2, 1),FCE("ѡ", 1, 2, 1),FCE("Ѣ", 0, 2, 1),FCE("ѣ", 1, 2, 1),
+FCE("ᵽ", 0, 2, 1),FCE("Ᵽ", 1, 2, 1),FCE("Ѥ", 0, 2, 1),FCE("ѥ", 1, 2, 1),
+FCE("Ѧ", 0, 2, 1),FCE("ѧ", 1, 2, 1),FCE("Ⱨ", 0, 2, 1),FCE("ⱨ", 1, 2, 1),
+FCE("Ѩ", 0, 2, 1),FCE("ѩ", 1, 2, 1),FCE("Ⓖ", 0, 2, 1),FCE("ⓖ", 1, 2, 1),
+FCE("Ѫ", 0, 2, 1),FCE("ѫ", 1, 2, 1),FCE("Ⱬ", 0, 2, 1),FCE("ⱬ", 1, 2, 1),
+FCE("Ѭ", 0, 2, 1),FCE("ѭ", 1, 2, 1),FCE("ɑ", 0, 2, 1),FCE("Ɑ", 1, 2, 1),
+FCE("Ѯ", 0, 2, 1),FCE("ѯ", 1, 2, 1),FCE("ɐ", 0, 2, 1),FCE("Ɐ", 1, 2, 1),
+FCE("Ѱ", 0, 2, 1),FCE("ѱ", 1, 2, 1),FCE("Ꝩ", 0, 2, 1),FCE("ꝩ", 1, 2, 1),
+FCE("Ѳ", 0, 2, 1),FCE("ѳ", 1, 2, 1),FCE("Ѵ", 0, 2, 1),FCE("ѵ", 1, 2, 1),
+FCE("Ⓘ", 0, 2, 1),FCE("ⓘ", 1, 2, 1),FCE("Ѷ", 0, 2, 1),FCE("ѷ", 1, 2, 1),
+FCE("Ѹ", 0, 2, 1),FCE("ѹ", 1, 2, 1),FCE("Ѻ", 0, 2, 1),FCE("ѻ", 1, 2, 1),
+FCE("Ѽ", 0, 2, 1),FCE("ѽ", 1, 2, 1),FCE("Ꝫ", 0, 2, 1),FCE("ꝫ", 1, 2, 1),
+FCE("Ѿ", 0, 2, 1),FCE("ѿ", 1, 2, 1),FCE("ɀ", 0, 2, 1),FCE("Ɀ", 1, 2, 1),
+FCE("Ҁ", 0, 2, 1),FCE("ҁ", 1, 2, 1),FCE("Ⴠ", 0, 2, 1),FCE("ⴠ", 1, 2, 1),
+FCE("Ⲃ", 0, 2, 1),FCE("ⲃ", 1, 2, 1),FCE("Ⲅ", 0, 2, 1),FCE("ⲅ", 1, 2, 1),
+FCE("Ⲇ", 0, 2, 1),FCE("ⲇ", 1, 2, 1),FCE("Ⴡ", 0, 2, 1),FCE("ⴡ", 1, 2, 1),
+FCE("Ⲉ", 0, 2, 1),FCE("ⲉ", 1, 2, 1),FCE("Ꝭ", 0, 2, 1),FCE("ꝭ", 1, 2, 1),
+FCE("Ҋ", 0, 2, 1),FCE("ҋ", 1, 2, 1),FCE("Ҍ", 0, 2, 1),FCE("ҍ", 1, 2, 1),
+FCE("Â", 0, 2, 1),FCE("â", 1, 2, 1),FCE("Ҏ", 0, 2, 1),FCE("ҏ", 1, 2, 1),
+FCE("Ґ", 0, 2, 1),FCE("ґ", 1, 2, 1),FCE("Ғ", 0, 2, 1),FCE("ғ", 1, 2, 1),
+FCE("Ⴣ", 0, 2, 1),FCE("ⴣ", 1, 2, 1),FCE("Ҕ", 0, 2, 1),FCE("ҕ", 1, 2, 1),
+FCE("Ꝯ", 0, 2, 1),FCE("ꝯ", 1, 2, 1),FCE("Җ", 0, 2, 1),FCE("җ", 1, 2, 1),
+FCE("Ҙ", 0, 2, 1),FCE("ҙ", 1, 2, 1),FCE("Ä", 0, 2, 1),FCE("ä", 1, 2, 1),
+FCE("Қ", 0, 2, 1),FCE("қ", 1, 2, 1),FCE("𐐦", 0, 2, 1),FCE("𐑎", 1, 2, 1),
+FCE("Ҝ", 0, 2, 1),FCE("ҝ", 1, 2, 1),FCE("Ҟ", 0, 2, 1),FCE("ҟ", 1, 2, 1),
+FCE("Ⴥ", 0, 2, 1),FCE("ⴥ", 1, 2, 1),FCE("Ҡ", 0, 2, 1),FCE("ҡ", 1, 2, 1),
+FCE("Ң", 0, 2, 1),FCE("ң", 1, 2, 1),FCE("Ҥ", 0, 2, 1),FCE("ҥ", 1, 2, 1),
+FCE("Ⳇ", 0, 2, 1),FCE("ⳇ", 1, 2, 1),FCE("Ҧ", 0, 2, 1),FCE("ҧ", 1, 2, 1),
+FCE("Ҩ", 0, 2, 1),FCE("ҩ", 1, 2, 1),FCE("Ⱡ", 0, 2, 1),FCE("ⱡ", 1, 2, 1),
+FCE("Ҫ", 0, 2, 1),FCE("ҫ", 1, 2, 1),FCE("Ⴧ", 0, 2, 1),FCE("ⴧ", 1, 2, 1),
+FCE("Ҭ", 0, 2, 1),FCE("ҭ", 1, 2, 1),FCE("𐐓", 0, 2, 1),FCE("𐐻", 1, 2, 1),
+FCE("Ү", 0, 2, 1),FCE("ү", 1, 2, 1),FCE("Ұ", 0, 2, 1),FCE("ұ", 1, 2, 1),
+FCE("Ⳉ", 0, 2, 1),FCE("ⳉ", 1, 2, 1),FCE("Ҳ", 0, 2, 1),FCE("ҳ", 1, 2, 1),
+FCE("Ҵ", 0, 2, 1),FCE("ҵ", 1, 2, 1),FCE("Ҷ", 0, 2, 1),FCE("ҷ", 1, 2, 1),
+FCE("Ⓑ", 0, 2, 1),FCE("ⓑ", 1, 2, 1),FCE("Ҹ", 0, 2, 1),FCE("ҹ", 1, 2, 1),
+FCE("Ⓓ", 0, 2, 1),FCE("ⓓ", 1, 2, 1),FCE("Һ", 0, 2, 1),FCE("һ", 1, 2, 1),
+FCE("Ⓕ", 0, 2, 1),FCE("ⓕ", 1, 2, 1),FCE("Ҽ", 0, 2, 1),FCE("ҽ", 1, 2, 1),
+FCE("Ⓗ", 0, 2, 1),FCE("ⓗ", 1, 2, 1),FCE("Ҿ", 0, 2, 1),FCE("ҿ", 1, 2, 1),
+FCE("Ⓙ", 0, 2, 1),FCE("ⓙ", 1, 2, 1),FCE("Ӏ", 0, 2, 1),FCE("ӏ", 1, 2, 1),
+FCE("Ӂ", 0, 2, 1),FCE("ӂ", 1, 2, 1),FCE("Ⓜ", 0, 2, 1),FCE("ⓜ", 1, 2, 1),
+FCE("Ӄ", 0, 2, 1),FCE("ӄ", 1, 2, 1),FCE("Ⓞ", 0, 2, 1),FCE("ⓞ", 1, 2, 1),
+FCE("Ӆ", 0, 2, 1),FCE("ӆ", 1, 2, 1),FCE("Ⓠ", 0, 2, 1),FCE("ⓠ", 1, 2, 1),
+FCE("Ӈ", 0, 2, 1),FCE("ӈ", 1, 2, 1),FCE("Ⓢ", 0, 2, 1),FCE("ⓢ", 1, 2, 1),
+FCE("Ӊ", 0, 2, 1),FCE("ӊ", 1, 2, 1),FCE("Ⓤ", 0, 2, 1),FCE("ⓤ", 1, 2, 1),
+FCE("Ӌ", 0, 2, 1),FCE("ӌ", 1, 2, 1),FCE("Ⓦ", 0, 2, 1),FCE("ⓦ", 1, 2, 1),
+FCE("Ӎ", 0, 2, 1),FCE("ӎ", 1, 2, 1),FCE("Ⓨ", 0, 2, 1),FCE("ⓨ", 1, 2, 1),
+FCE("Ⴭ", 0, 2, 1),FCE("ⴭ", 1, 2, 1),FCE("Ӑ", 0, 2, 1),FCE("ӑ", 1, 2, 1),
+FCE("Ӓ", 0, 2, 1),FCE("ӓ", 1, 2, 1),FCE("Ӕ", 0, 2, 1),FCE("ӕ", 1, 2, 1),
+FCE("Ⳏ", 0, 2, 1),FCE("ⳏ", 1, 2, 1),FCE("Ӗ", 0, 2, 1),FCE("ӗ", 1, 2, 1),
+FCE("Ꝺ", 0, 2, 1),FCE("ꝺ", 1, 2, 1),FCE("Ә", 0, 2, 1),FCE("ә", 1, 2, 1),
+FCE("Ӛ", 0, 2, 1),FCE("ӛ", 1, 2, 1),FCE("Ⓩ", 0, 2, 1),FCE("ⓩ", 1, 2, 1),
+FCE("Ӝ", 0, 2, 1),FCE("ӝ", 1, 2, 1),FCE("Ӟ", 0, 2, 1),FCE("ӟ", 1, 2, 1),
+FCE("Ӡ", 0, 2, 1),FCE("ӡ", 1, 2, 1),FCE("Ⳑ", 0, 2, 1),FCE("ⳑ", 1, 2, 1),
+FCE("Ӣ", 0, 2, 1),FCE("ӣ", 1, 2, 1),FCE("Ӥ", 0, 2, 1),FCE("ӥ", 1, 2, 1),
+FCE("ɫ", 0, 2, 1),FCE("Ɫ", 1, 2, 1),FCE("Ӧ", 0, 2, 1),FCE("ӧ", 1, 2, 1),
+FCE("Ө", 0, 2, 1),FCE("ө", 1, 2, 1),FCE("Ӫ", 0, 2, 1),FCE("ӫ", 1, 2, 1),
+FCE("Ⅎ", 0, 2, 1),FCE("ⅎ", 1, 2, 1),FCE("Ӭ", 0, 2, 1),FCE("ӭ", 1, 2, 1),
+FCE("Ⳓ", 0, 2, 1),FCE("ⳓ", 1, 2, 1),FCE("Ӯ", 0, 2, 1),FCE("ӯ", 1, 2, 1),
+FCE("Ӱ", 0, 2, 1),FCE("ӱ", 1, 2, 1),FCE("𐐢", 0, 2, 1),FCE("𐑊", 1, 2, 1),
+FCE("Ӳ", 0, 2, 1),FCE("ӳ", 1, 2, 1),FCE("Ӵ", 0, 2, 1),FCE("ӵ", 1, 2, 1),
+FCE("Ӷ", 0, 2, 1),FCE("ӷ", 1, 2, 1),FCE("Ӹ", 0, 2, 1),FCE("ӹ", 1, 2, 1),
+FCE("Ⳕ", 0, 2, 1),FCE("ⳕ", 1, 2, 1),FCE("Ӻ", 0, 2, 1),FCE("ӻ", 1, 2, 1),
+FCE("Ӽ", 0, 2, 1),FCE("ӽ", 1, 2, 1),FCE("Ӿ", 0, 2, 1),FCE("ӿ", 1, 2, 1),
+FCE("Ԁ", 0, 2, 1),FCE("ԁ", 1, 2, 1),FCE("Ꞁ", 0, 2, 1),FCE("ꞁ", 1, 2, 1),
+FCE("Ԃ", 0, 2, 1),FCE("ԃ", 1, 2, 1),FCE("Ԅ", 0, 2, 1),FCE("ԅ", 1, 2, 1),
+FCE("Ⳗ", 0, 2, 1),FCE("ⳗ", 1, 2, 1),FCE("Ԇ", 0, 2, 1),FCE("ԇ", 1, 2, 1),
+FCE("Ԉ", 0, 2, 1),FCE("ԉ", 1, 2, 1),FCE("Ԋ", 0, 2, 1),FCE("ԋ", 1, 2, 1),
+FCE("Ԍ", 0, 2, 1),FCE("ԍ", 1, 2, 1),FCE("Ꞃ", 0, 2, 1),FCE("ꞃ", 1, 2, 1),
+FCE("Ԏ", 0, 2, 1),FCE("ԏ", 1, 2, 1),FCE("Ԑ", 0, 2, 1),FCE("ԑ", 1, 2, 1),
+FCE("Ⳙ", 0, 2, 1),FCE("ⳙ", 1, 2, 1),FCE("Ԓ", 0, 2, 1),FCE("ԓ", 1, 2, 1),
+FCE("Ԕ", 0, 2, 1),FCE("ԕ", 1, 2, 1),FCE("Ԗ", 0, 2, 1),FCE("ԗ", 1, 2, 1),
+FCE("Ԙ", 0, 2, 1),FCE("ԙ", 1, 2, 1),FCE("Ꞅ", 0, 2, 1),FCE("ꞅ", 1, 2, 1),
+FCE("Ԛ", 0, 2, 1),FCE("ԛ", 1, 2, 1),FCE("Ⲩ", 0, 2, 1),FCE("ⲩ", 1, 2, 1),
+FCE("Ԝ", 0, 2, 1),FCE("ԝ", 1, 2, 1),FCE("Ⳛ", 0, 2, 1),FCE("ⳛ", 1, 2, 1),
+FCE("Ԟ", 0, 2, 1),FCE("ԟ", 1, 2, 1),FCE("Ԡ", 0, 2, 1),FCE("ԡ", 1, 2, 1),
+FCE("Ԣ", 0, 2, 1),FCE("ԣ", 1, 2, 1),FCE("Ԥ", 0, 2, 1),FCE("ԥ", 1, 2, 1),
+FCE("Ꞇ", 0, 2, 1),FCE("ꞇ", 1, 2, 1),FCE("Ԧ", 0, 2, 1),FCE("ԧ", 1, 2, 1),
+FCE("Ⱐ", 0, 2, 1),FCE("ⱐ", 1, 2, 1),FCE("Ⳝ", 0, 2, 1),FCE("ⳝ", 1, 2, 1),
+FCE("Ա", 0, 2, 1),FCE("ա", 1, 2, 1),FCE("Բ", 0, 2, 1),FCE("բ", 1, 2, 1),
+FCE("Գ", 0, 2, 1),FCE("գ", 1, 2, 1),FCE("Դ", 0, 2, 1),FCE("դ", 1, 2, 1),
+FCE("Ե", 0, 2, 1),FCE("ե", 1, 2, 1),FCE("Զ", 0, 2, 1),FCE("զ", 1, 2, 1),
+FCE("Է", 0, 2, 1),FCE("է", 1, 2, 1),FCE("Ը", 0, 2, 1),FCE("ը", 1, 2, 1),
+FCE("Թ", 0, 2, 1),FCE("թ", 1, 2, 1),FCE("Ժ", 0, 2, 1),FCE("ժ", 1, 2, 1),
+FCE("Ի", 0, 2, 1),FCE("ի", 1, 2, 1),FCE("Լ", 0, 2, 1),FCE("լ", 1, 2, 1),
+FCE("Խ", 0, 2, 1),FCE("խ", 1, 2, 1),FCE("Ծ", 0, 2, 1),FCE("ծ", 1, 2, 1),
+FCE("Կ", 0, 2, 1),FCE("կ", 1, 2, 1),FCE("Հ", 0, 2, 1),FCE("հ", 1, 2, 1),
+FCE("Ձ", 0, 2, 1),FCE("ձ", 1, 2, 1),FCE("Ղ", 0, 2, 1),FCE("ղ", 1, 2, 1),
+FCE("Ճ", 0, 2, 1),FCE("ճ", 1, 2, 1),FCE("Մ", 0, 2, 1),FCE("մ", 1, 2, 1),
+FCE("Յ", 0, 2, 1),FCE("յ", 1, 2, 1),FCE("Ն", 0, 2, 1),FCE("ն", 1, 2, 1),
+FCE("Շ", 0, 2, 1),FCE("շ", 1, 2, 1),FCE("Ո", 0, 2, 1),FCE("ո", 1, 2, 1),
+FCE("Չ", 0, 2, 1),FCE("չ", 1, 2, 1),FCE("Պ", 0, 2, 1),FCE("պ", 1, 2, 1),
+FCE("Ջ", 0, 2, 1),FCE("ջ", 1, 2, 1),FCE("Ռ", 0, 2, 1),FCE("ռ", 1, 2, 1),
+FCE("Ս", 0, 2, 1),FCE("ս", 1, 2, 1),FCE("Վ", 0, 2, 1),FCE("վ", 1, 2, 1),
+FCE("Տ", 0, 2, 1),FCE("տ", 1, 2, 1),FCE("Ր", 0, 2, 1),FCE("ր", 1, 2, 1),
+FCE("Ց", 0, 2, 1),FCE("ց", 1, 2, 1),FCE("Ւ", 0, 2, 1),FCE("ւ", 1, 2, 1),
+FCE("Փ", 0, 2, 1),FCE("փ", 1, 2, 1),FCE("Ք", 0, 2, 1),FCE("ք", 1, 2, 1),
+FCE("Օ", 0, 2, 1),FCE("օ", 1, 2, 1),FCE("Ֆ", 0, 2, 1),FCE("ֆ", 1, 2, 1),
+FCE("Ⲫ", 0, 2, 1),FCE("ⲫ", 1, 2, 1),FCE("Ꞑ", 0, 2, 1),FCE("ꞑ", 1, 2, 1),
+FCE("Ⱒ", 0, 2, 1),FCE("ⱒ", 1, 2, 1),FCE("Ꞓ", 0, 2, 1),FCE("ꞓ", 1, 2, 1),
+FCE("Ⱓ", 0, 2, 1),FCE("ⱓ", 1, 2, 1),FCE("Ⳬ", 0, 2, 1),FCE("ⳬ", 1, 2, 1),
+FCE("Ⳋ", 0, 2, 1),FCE("ⳋ", 1, 2, 1),FCE("և", 0, 2, 1),FCE("եւ", 1, 2, 2),
+FCE("Ꙃ", 0, 2, 1),FCE("ꙃ", 1, 2, 1),FCE("Ⳮ", 0, 2, 1),FCE("ⳮ", 1, 2, 1),
+FCE("Ⲭ", 0, 2, 1),FCE("ⲭ", 1, 2, 1),FCE("Ꙅ", 0, 2, 1),FCE("ꙅ", 1, 2, 1),
+FCE("Ⱔ", 0, 2, 1),FCE("ⱔ", 1, 2, 1),FCE("Ꝕ", 0, 2, 1),FCE("ꝕ", 1, 2, 1),
+FCE("Ꙇ", 0, 2, 1),FCE("ꙇ", 1, 2, 1),FCE("Ⳳ", 0, 2, 1),FCE("ⳳ", 1, 2, 1),
+FCE("𐐈", 0, 2, 1),FCE("𐐰", 1, 2, 1),FCE("Ꙉ", 0, 2, 1),FCE("ꙉ", 1, 2, 1),
+FCE("Ⱕ", 0, 2, 1),FCE("ⱕ", 1, 2, 1),FCE("Ꞡ", 0, 2, 1),FCE("ꞡ", 1, 2, 1),
+FCE("Ꙍ", 0, 2, 1),FCE("ꙍ", 1, 2, 1),FCE("Ꞣ", 0, 2, 1),FCE("ꞣ", 1, 2, 1),
+FCE("Ⲯ", 0, 2, 1),FCE("ⲯ", 1, 2, 1),FCE("Ꙏ", 0, 2, 1),FCE("ꙏ", 1, 2, 1),
+FCE("Ꞥ", 0, 2, 1),FCE("ꞥ", 1, 2, 1),FCE("Ꙑ", 0, 2, 1),FCE("ꙑ", 1, 2, 1),
+FCE("Ꞧ", 0, 2, 1),FCE("ꞧ", 1, 2, 1),FCE("Ꞌ", 0, 2, 1),FCE("ꞌ", 1, 2, 1),
+FCE("Ꙓ", 0, 2, 1),FCE("ꙓ", 1, 2, 1),FCE("Ꞩ", 0, 2, 1),FCE("ꞩ", 1, 2, 1),
+FCE("Ꙕ", 0, 2, 1),FCE("ꙕ", 1, 2, 1),FCE("ɦ", 0, 2, 1),FCE("Ɦ", 1, 2, 1),
+FCE("Ḁ", 0, 2, 1),FCE("ḁ", 1, 2, 1),FCE("Ḃ", 0, 2, 1),FCE("ḃ", 1, 2, 1),
+FCE("Ḅ", 0, 2, 1),FCE("ḅ", 1, 2, 1),FCE("Ⱏ", 0, 2, 1),FCE("ⱏ", 1, 2, 1),
+FCE("Ḇ", 0, 2, 1),FCE("ḇ", 1, 2, 1),FCE("𐐎", 0, 2, 1),FCE("𐐶", 1, 2, 1),
+FCE("Ḉ", 0, 2, 1),FCE("ḉ", 1, 2, 1),FCE("Ḋ", 0, 2, 1),FCE("ḋ", 1, 2, 1),
+FCE("Ⲱ", 0, 2, 1),FCE("ⲱ", 1, 2, 1),FCE("Ḍ", 0, 2, 1),FCE("ḍ", 1, 2, 1),
+FCE("Ḏ", 0, 2, 1),FCE("ḏ", 1, 2, 1),FCE("Ḑ", 0, 2, 1),FCE("ḑ", 1, 2, 1),
+FCE("Ꙙ", 0, 2, 1),FCE("ꙙ", 1, 2, 1),FCE("Ḓ", 0, 2, 1),FCE("ḓ", 1, 2, 1),
+FCE("Ḕ", 0, 2, 1),FCE("ḕ", 1, 2, 1),FCE("Ⓧ", 0, 2, 1),FCE("ⓧ", 1, 2, 1),
+FCE("Ḗ", 0, 2, 1),FCE("ḗ", 1, 2, 1),FCE("Ḙ", 0, 2, 1),FCE("ḙ", 1, 2, 1),
+FCE("Ḛ", 0, 2, 1),FCE("ḛ", 1, 2, 1),FCE("Ḝ", 0, 2, 1),FCE("ḝ", 1, 2, 1),
+FCE("Ꙛ", 0, 2, 1),FCE("ꙛ", 1, 2, 1),FCE("Ḟ", 0, 2, 1),FCE("ḟ", 1, 2, 1),
+FCE("Ḡ", 0, 2, 1),FCE("ḡ", 1, 2, 1),FCE("Ḣ", 0, 2, 1),FCE("ḣ", 1, 2, 1),
+FCE("Ḥ", 0, 2, 1),FCE("ḥ", 1, 2, 1),FCE("Ḧ", 0, 2, 1),FCE("ḧ", 1, 2, 1),
+FCE("Ḩ", 0, 2, 1),FCE("ḩ", 1, 2, 1),FCE("Ꙝ", 0, 2, 1),FCE("ꙝ", 1, 2, 1),
+FCE("Ḫ", 0, 2, 1),FCE("ḫ", 1, 2, 1),FCE("Ḭ", 0, 2, 1),FCE("ḭ", 1, 2, 1),
+FCE("Ḯ", 0, 2, 1),FCE("ḯ", 1, 2, 1),FCE("Ḱ", 0, 2, 1),FCE("ḱ", 1, 2, 1),
+FCE("Ḳ", 0, 2, 1),FCE("ḳ", 1, 2, 1),FCE("Ḵ", 0, 2, 1),FCE("ḵ", 1, 2, 1),
+FCE("Ꙟ", 0, 2, 1),FCE("ꙟ", 1, 2, 1),FCE("Ḷ", 0, 2, 1),FCE("ḷ", 1, 2, 1),
+FCE("Ḹ", 0, 2, 1),FCE("ḹ", 1, 2, 1),FCE("Ḻ", 0, 2, 1),FCE("ḻ", 1, 2, 1),
+FCE("Ḽ", 0, 2, 1),FCE("ḽ", 1, 2, 1),FCE("Ḿ", 0, 2, 1),FCE("ḿ", 1, 2, 1),
+FCE("Ṁ", 0, 2, 1),FCE("ṁ", 1, 2, 1),FCE("Ꙡ", 0, 2, 1),FCE("ꙡ", 1, 2, 1),
+FCE("Ṃ", 0, 2, 1),FCE("ṃ", 1, 2, 1),FCE("Ṅ", 0, 2, 1),FCE("ṅ", 1, 2, 1),
+FCE("Ṇ", 0, 2, 1),FCE("ṇ", 1, 2, 1),FCE("Ⲳ", 0, 2, 1),FCE("ⲳ", 1, 2, 1),
+FCE("Ṉ", 0, 2, 1),FCE("ṉ", 1, 2, 1),FCE("Ⳁ", 0, 2, 1),FCE("ⳁ", 1, 2, 1),
+FCE("Ṋ", 0, 2, 1),FCE("ṋ", 1, 2, 1),FCE("Ṍ", 0, 2, 1),FCE("ṍ", 1, 2, 1),
+FCE("Ꙣ", 0, 2, 1),FCE("ꙣ", 1, 2, 1),FCE("Ṏ", 0, 2, 1),FCE("ṏ", 1, 2, 1),
+FCE("Ṑ", 0, 2, 1),FCE("ṑ", 1, 2, 1),FCE("Ṓ", 0, 2, 1),FCE("ṓ", 1, 2, 1),
+FCE("Ṕ", 0, 2, 1),FCE("ṕ", 1, 2, 1),FCE("Ṗ", 0, 2, 1),FCE("ṗ", 1, 2, 1),
+FCE("Ṙ", 0, 2, 1),FCE("ṙ", 1, 2, 1),FCE("Ꙥ", 0, 2, 1),FCE("ꙥ", 1, 2, 1),
+FCE("Ṛ", 0, 2, 1),FCE("ṛ", 1, 2, 1),FCE("Ṝ", 0, 2, 1),FCE("ṝ", 1, 2, 1),
+FCE("Ṟ", 0, 2, 1),FCE("ṟ", 1, 2, 1),FCE("Ṡ", 0, 3, 1),FCE("ṡ", 1, 3, 1),
+FCE("ẛ", 2, 3, 1),FCE("Ṣ", 0, 2, 1),FCE("ṣ", 1, 2, 1),FCE("𐐤", 0, 2, 1),
+FCE("𐑌", 1, 2, 1),FCE("Ṥ", 0, 2, 1),FCE("ṥ", 1, 2, 1),FCE("Ꙧ", 0, 2, 1),
+FCE("ꙧ", 1, 2, 1),FCE("Ṧ", 0, 2, 1),FCE("ṧ", 1, 2, 1),FCE("Ṩ", 0, 2, 1),
+FCE("ṩ", 1, 2, 1),FCE("Ṫ", 0, 2, 1),FCE("ṫ", 1, 2, 1),FCE("Ṭ", 0, 2, 1),
+FCE("ṭ", 1, 2, 1),FCE("Ṯ", 0, 2, 1),FCE("ṯ", 1, 2, 1),FCE("Ṱ", 0, 2, 1),
+FCE("ṱ", 1, 2, 1),FCE("Ꙩ", 0, 2, 1),FCE("ꙩ", 1, 2, 1),FCE("Ṳ", 0, 2, 1),
+FCE("ṳ", 1, 2, 1),FCE("Ṵ", 0, 2, 1),FCE("ṵ", 1, 2, 1),FCE("Ṷ", 0, 2, 1),
+FCE("ṷ", 1, 2, 1),FCE("Ⲿ", 0, 2, 1),FCE("ⲿ", 1, 2, 1),FCE("Ṹ", 0, 2, 1),
+FCE("ṹ", 1, 2, 1),FCE("Ṻ", 0, 2, 1),FCE("ṻ", 1, 2, 1),FCE("Ṽ", 0, 2, 1),
+FCE("ṽ", 1, 2, 1),FCE("Ꙫ", 0, 2, 1),FCE("ꙫ", 1, 2, 1),FCE("Ṿ", 0, 2, 1),
+FCE("ṿ", 1, 2, 1),FCE("Ẁ", 0, 2, 1),FCE("ẁ", 1, 2, 1),FCE("Ẃ", 0, 2, 1),
+FCE("ẃ", 1, 2, 1),FCE("Ẅ", 0, 2, 1),FCE("ẅ", 1, 2, 1),FCE("Ẇ", 0, 2, 1),
+FCE("ẇ", 1, 2, 1),FCE("Ẉ", 0, 2, 1),FCE("ẉ", 1, 2, 1),FCE("Ꙭ", 0, 2, 1),
+FCE("ꙭ", 1, 2, 1),FCE("Ẋ", 0, 2, 1),FCE("ẋ", 1, 2, 1),FCE("Ẍ", 0, 2, 1),
+FCE("ẍ", 1, 2, 1),FCE("Ẏ", 0, 2, 1),FCE("ẏ", 1, 2, 1),FCE("Ẑ", 0, 2, 1),
+FCE("ẑ", 1, 2, 1),FCE("Ẓ", 0, 2, 1),FCE("ẓ", 1, 2, 1),FCE("Ẕ", 0, 2, 1),
+FCE("ẕ", 1, 2, 1),FCE("ẖ", 0, 2, 1),FCE("ẖ", 1, 2, 2),FCE("ẗ", 0, 2, 1),
+FCE("ẗ", 1, 2, 2),FCE("ẘ", 0, 2, 1),FCE("ẘ", 1, 2, 2),FCE("ẙ", 0, 2, 1),
+FCE("ẙ", 1, 2, 2),FCE("ẚ", 0, 2, 1),FCE("aʾ", 1, 2, 2),FCE("Ṡ", 0, 3, 1),
+FCE("ṡ", 1, 3, 1),FCE("ẛ", 2, 3, 1),FCE("ß", 0, 3, 1),FCE("ẞ", 1, 3, 1),
+FCE("ss", 2, 3, 2),FCE("Ạ", 0, 2, 1),FCE("ạ", 1, 2, 1),FCE("Ả", 0, 2, 1),
+FCE("ả", 1, 2, 1),FCE("Ấ", 0, 2, 1),FCE("ấ", 1, 2, 1),FCE("Ⓟ", 0, 2, 1),
+FCE("ⓟ", 1, 2, 1),FCE("Ầ", 0, 2, 1),FCE("ầ", 1, 2, 1),FCE("Ẩ", 0, 2, 1),
+FCE("ẩ", 1, 2, 1),FCE("Ẫ", 0, 2, 1),FCE("ẫ", 1, 2, 1),FCE("Ậ", 0, 2, 1),
+FCE("ậ", 1, 2, 1),FCE("Ắ", 0, 2, 1),FCE("ắ", 1, 2, 1),FCE("Ｈ", 0, 2, 1),
+FCE("ｈ", 1, 2, 1),FCE("Ằ", 0, 2, 1),FCE("ằ", 1, 2, 1),FCE("Ẳ", 0, 2, 1),
+FCE("ẳ", 1, 2, 1),FCE("𐐥", 0, 2, 1),FCE("𐑍", 1, 2, 1),FCE("Ẵ", 0, 2, 1),
+FCE("ẵ", 1, 2, 1),FCE("Ặ", 0, 2, 1),FCE("ặ", 1, 2, 1),FCE("Ẹ", 0, 2, 1),
+FCE("ẹ", 1, 2, 1),FCE("Ẻ", 0, 2, 1),FCE("ẻ", 1, 2, 1),FCE("Ẽ", 0, 2, 1),
+FCE("ẽ", 1, 2, 1),FCE("Ế", 0, 2, 1),FCE("ế", 1, 2, 1),FCE("Ⲷ", 0, 2, 1),
+FCE("ⲷ", 1, 2, 1),FCE("Ề", 0, 2, 1),FCE("ề", 1, 2, 1),FCE("Ể", 0, 2, 1),
+FCE("ể", 1, 2, 1),FCE("Ⲛ", 0, 2, 1),FCE("ⲛ", 1, 2, 1),FCE("Ễ", 0, 2, 1),
+FCE("ễ", 1, 2, 1),FCE("Ệ", 0, 2, 1),FCE("ệ", 1, 2, 1),FCE("Ỉ", 0, 2, 1),
+FCE("ỉ", 1, 2, 1),FCE("Ị", 0, 2, 1),FCE("ị", 1, 2, 1),FCE("Ọ", 0, 2, 1),
+FCE("ọ", 1, 2, 1),FCE("Ỏ", 0, 2, 1),FCE("ỏ", 1, 2, 1),FCE("Ố", 0, 2, 1),
+FCE("ố", 1, 2, 1),FCE("Ồ", 0, 2, 1),FCE("ồ", 1, 2, 1),FCE("Ổ", 0, 2, 1),
+FCE("ổ", 1, 2, 1),FCE("Ỗ", 0, 2, 1),FCE("ỗ", 1, 2, 1),FCE("Ộ", 0, 2, 1),
+FCE("ộ", 1, 2, 1),FCE("Ớ", 0, 2, 1),FCE("ớ", 1, 2, 1),FCE("Ờ", 0, 2, 1),
+FCE("ờ", 1, 2, 1),FCE("Ở", 0, 2, 1),FCE("ở", 1, 2, 1),FCE("Ỡ", 0, 2, 1),
+FCE("ỡ", 1, 2, 1),FCE("Ợ", 0, 2, 1),FCE("ợ", 1, 2, 1),FCE("Ụ", 0, 2, 1),
+FCE("ụ", 1, 2, 1),FCE("Ω", 0, 3, 1),FCE("ω", 1, 3, 1),FCE("Ω", 2, 3, 1),
+FCE("Ủ", 0, 2, 1),FCE("ủ", 1, 2, 1),FCE("Ứ", 0, 2, 1),FCE("ứ", 1, 2, 1),
+FCE("Ừ", 0, 2, 1),FCE("ừ", 1, 2, 1),FCE("Ｊ", 0, 2, 1),FCE("ｊ", 1, 2, 1),
+FCE("Ử", 0, 2, 1),FCE("ử", 1, 2, 1),FCE("Ữ", 0, 2, 1),FCE("ữ", 1, 2, 1),
+FCE("Ự", 0, 2, 1),FCE("ự", 1, 2, 1),FCE("Ỳ", 0, 2, 1),FCE("ỳ", 1, 2, 1),
+FCE("Ỵ", 0, 2, 1),FCE("ỵ", 1, 2, 1),FCE("Ỷ", 0, 2, 1),FCE("ỷ", 1, 2, 1),
+FCE("Ỹ", 0, 2, 1),FCE("ỹ", 1, 2, 1),FCE("Ỻ", 0, 2, 1),FCE("ỻ", 1, 2, 1),
+FCE("Ⲹ", 0, 2, 1),FCE("ⲹ", 1, 2, 1),FCE("Ỽ", 0, 2, 1),FCE("ỽ", 1, 2, 1),
+FCE("K", 0, 3, 1),FCE("k", 1, 3, 1),FCE("K", 2, 3, 1),FCE("Ỿ", 0, 2, 1),
+FCE("ỿ", 1, 2, 1),FCE("Ꚁ", 0, 2, 1),FCE("ꚁ", 1, 2, 1),FCE("ἀ", 0, 2, 1),
+FCE("Ἀ", 1, 2, 1),FCE("ἁ", 0, 2, 1),FCE("Ἁ", 1, 2, 1),FCE("ἂ", 0, 2, 1),
+FCE("Ἂ", 1, 2, 1),FCE("ἃ", 0, 2, 1),FCE("Ἃ", 1, 2, 1),FCE("ἄ", 0, 2, 1),
+FCE("Ἄ", 1, 2, 1),FCE("ἅ", 0, 2, 1),FCE("Ἅ", 1, 2, 1),FCE("ἆ", 0, 2, 1),
+FCE("Ἆ", 1, 2, 1),FCE("ἇ", 0, 2, 1),FCE("Ἇ", 1, 2, 1),FCE("𐐘", 0, 2, 1),
+FCE("𐑀", 1, 2, 1),FCE("ɥ", 0, 2, 1),FCE("Ɥ", 1, 2, 1),FCE("ἐ", 0, 2, 1),
+FCE("Ἐ", 1, 2, 1),FCE("ἑ", 0, 2, 1),FCE("Ἑ", 1, 2, 1),FCE("ἒ", 0, 2, 1),
+FCE("Ἒ", 1, 2, 1),FCE("ἓ", 0, 2, 1),FCE("Ἓ", 1, 2, 1),FCE("ἔ", 0, 2, 1),
+FCE("Ἔ", 1, 2, 1),FCE("ἕ", 0, 2, 1),FCE("Ἕ", 1, 2, 1),FCE("Ａ", 0, 2, 1),
+FCE("ａ", 1, 2, 1),FCE("Ꜣ", 0, 2, 1),FCE("ꜣ", 1, 2, 1),FCE("Ｃ", 0, 2, 1),
+FCE("ｃ", 1, 2, 1),FCE("Ꜥ", 0, 2, 1),FCE("ꜥ", 1, 2, 1),FCE("Ꚇ", 0, 2, 1),
+FCE("ꚇ", 1, 2, 1),FCE("Ꜧ", 0, 2, 1),FCE("ꜧ", 1, 2, 1),FCE("Ｇ", 0, 2, 1),
+FCE("ｇ", 1, 2, 1),FCE("ἠ", 0, 2, 1),FCE("Ἠ", 1, 2, 1),FCE("ἡ", 0, 2, 1),
+FCE("Ἡ", 1, 2, 1),FCE("ἢ", 0, 2, 1),FCE("Ἢ", 1, 2, 1),FCE("ἣ", 0, 2, 1),
+FCE("Ἣ", 1, 2, 1),FCE("ἤ", 0, 2, 1),FCE("Ἤ", 1, 2, 1),FCE("ἥ", 0, 2, 1),
+FCE("Ἥ", 1, 2, 1),FCE("ἦ", 0, 2, 1),FCE("Ἦ", 1, 2, 1),FCE("ἧ", 0, 2, 1),
+FCE("Ἧ", 1, 2, 1),FCE("Ｐ", 0, 2, 1),FCE("ｐ", 1, 2, 1),FCE("Ꚉ", 0, 2, 1),
+FCE("ꚉ", 1, 2, 1),FCE("Ꜳ", 0, 2, 1),FCE("ꜳ", 1, 2, 1),FCE("Ｓ", 0, 2, 1),
+FCE("ｓ", 1, 2, 1),FCE("Ꜵ", 0, 2, 1),FCE("ꜵ", 1, 2, 1),FCE("Ｕ", 0, 2, 1),
+FCE("ｕ", 1, 2, 1),FCE("Ꜷ", 0, 2, 1),FCE("ꜷ", 1, 2, 1),FCE("Ⲻ", 0, 2, 1),
+FCE("ⲻ", 1, 2, 1),FCE("ἰ", 0, 2, 1),FCE("Ἰ", 1, 2, 1),FCE("ἱ", 0, 2, 1),
+FCE("Ἱ", 1, 2, 1),FCE("ἲ", 0, 2, 1),FCE("Ἲ", 1, 2, 1),FCE("ἳ", 0, 2, 1),
+FCE("Ἳ", 1, 2, 1),FCE("ἴ", 0, 2, 1),FCE("Ἴ", 1, 2, 1),FCE("ἵ", 0, 2, 1),
+FCE("Ἵ", 1, 2, 1),FCE("ἶ", 0, 2, 1),FCE("Ἶ", 1, 2, 1),FCE("ἷ", 0, 2, 1),
+FCE("Ἷ", 1, 2, 1),FCE("Ꝁ", 0, 2, 1),FCE("ꝁ", 1, 2, 1),FCE("Ꝃ", 0, 2, 1),
+FCE("ꝃ", 1, 2, 1),FCE("Ꝅ", 0, 2, 1),FCE("ꝅ", 1, 2, 1),FCE("Ꝇ", 0, 2, 1),
+FCE("ꝇ", 1, 2, 1),FCE("ὀ", 0, 2, 1),FCE("Ὀ", 1, 2, 1),FCE("ὁ", 0, 2, 1),
+FCE("Ὁ", 1, 2, 1),FCE("ὂ", 0, 2, 1),FCE("Ὂ", 1, 2, 1),FCE("ὃ", 0, 2, 1),
+FCE("Ὃ", 1, 2, 1),FCE("ὄ", 0, 2, 1),FCE("Ὄ", 1, 2, 1),FCE("ὅ", 0, 2, 1),
+FCE("Ὅ", 1, 2, 1),FCE("Ꝏ", 0, 2, 1),FCE("ꝏ", 1, 2, 1),FCE("ὐ", 0, 2, 1),
+FCE("ὐ", 1, 2, 2),FCE("ὒ", 0, 2, 1),FCE("ὒ", 1, 2, 3),FCE("ὔ", 0, 2, 1),
+FCE("ὔ", 1, 2, 3),FCE("Ꚏ", 0, 2, 1),FCE("ꚏ", 1, 2, 1),FCE("ὖ", 0, 2, 1),
+FCE("ὖ", 1, 2, 3),FCE("Ꝙ", 0, 2, 1),FCE("ꝙ", 1, 2, 1),FCE("ὑ", 0, 2, 1),
+FCE("Ὑ", 1, 2, 1),FCE("Ꝛ", 0, 2, 1),FCE("ꝛ", 1, 2, 1),FCE("ὓ", 0, 2, 1),
+FCE("Ὓ", 1, 2, 1),FCE("Ꝝ", 0, 2, 1),FCE("ꝝ", 1, 2, 1),FCE("ὕ", 0, 2, 1),
+FCE("Ὕ", 1, 2, 1),FCE("Ꝟ", 0, 2, 1),FCE("ꝟ", 1, 2, 1),FCE("ὗ", 0, 2, 1),
+FCE("Ὗ", 1, 2, 1),FCE("Ꝡ", 0, 2, 1),FCE("ꝡ", 1, 2, 1),FCE("Ꚑ", 0, 2, 1),
+FCE("ꚑ", 1, 2, 1),FCE("Ꝣ", 0, 2, 1),FCE("ꝣ", 1, 2, 1),FCE("Ｎ", 0, 2, 1),
+FCE("ｎ", 1, 2, 1),FCE("Ꝥ", 0, 2, 1),FCE("ꝥ", 1, 2, 1),FCE("Ꝧ", 0, 2, 1),
+FCE("ꝧ", 1, 2, 1),FCE("ὠ", 0, 2, 1),FCE("Ὠ", 1, 2, 1),FCE("ὡ", 0, 2, 1),
+FCE("Ὡ", 1, 2, 1),FCE("ὢ", 0, 2, 1),FCE("Ὢ", 1, 2, 1),FCE("ὣ", 0, 2, 1),
+FCE("Ὣ", 1, 2, 1),FCE("ὤ", 0, 2, 1),FCE("Ὤ", 1, 2, 1),FCE("ὥ", 0, 2, 1),
+FCE("Ὥ", 1, 2, 1),FCE("ὦ", 0, 2, 1),FCE("Ὦ", 1, 2, 1),FCE("ὧ", 0, 2, 1),
+FCE("Ὧ", 1, 2, 1),FCE("Ⱌ", 0, 2, 1),FCE("ⱌ", 1, 2, 1),FCE("Ⲽ", 0, 2, 1),
+FCE("ⲽ", 1, 2, 1),FCE("Ꚕ", 0, 2, 1),FCE("ꚕ", 1, 2, 1),FCE("Ꝼ", 0, 2, 1),
+FCE("ꝼ", 1, 2, 1),FCE("ᵹ", 0, 2, 1),FCE("Ᵹ", 1, 2, 1),FCE("Ꝿ", 0, 2, 1),
+FCE("ꝿ", 1, 2, 1),FCE("ᾀ", 0, 3, 1),FCE("ᾈ", 1, 3, 1),FCE("ἀι", 2, 3, 2),
+FCE("ᾁ", 0, 3, 1),FCE("ᾉ", 1, 3, 1),FCE("ἁι", 2, 3, 2),FCE("ᾂ", 0, 3, 1),
+FCE("ᾊ", 1, 3, 1),FCE("ἂι", 2, 3, 2),FCE("ᾃ", 0, 3, 1),FCE("ᾋ", 1, 3, 1),
+FCE("ἃι", 2, 3, 2),FCE("ᾄ", 0, 3, 1),FCE("ᾌ", 1, 3, 1),FCE("ἄι", 2, 3, 2),
+FCE("ᾅ", 0, 3, 1),FCE("ᾍ", 1, 3, 1),FCE("ἅι", 2, 3, 2),FCE("ᾆ", 0, 3, 1),
+FCE("ᾎ", 1, 3, 1),FCE("ἆι", 2, 3, 2),FCE("ᾇ", 0, 3, 1),FCE("ᾏ", 1, 3, 1),
+FCE("ἇι", 2, 3, 2),FCE("ᾀ", 0, 3, 1),FCE("ᾈ", 1, 3, 1),FCE("ἀι", 2, 3, 2),
+FCE("ᾁ", 0, 3, 1),FCE("ᾉ", 1, 3, 1),FCE("ἁι", 2, 3, 2),FCE("ᾂ", 0, 3, 1),
+FCE("ᾊ", 1, 3, 1),FCE("ἂι", 2, 3, 2),FCE("ᾃ", 0, 3, 1),FCE("ᾋ", 1, 3, 1),
+FCE("ἃι", 2, 3, 2),FCE("ᾄ", 0, 3, 1),FCE("ᾌ", 1, 3, 1),FCE("ἄι", 2, 3, 2),
+FCE("ᾅ", 0, 3, 1),FCE("ᾍ", 1, 3, 1),FCE("ἅι", 2, 3, 2),FCE("ᾆ", 0, 3, 1),
+FCE("ᾎ", 1, 3, 1),FCE("ἆι", 2, 3, 2),FCE("ᾇ", 0, 3, 1),FCE("ᾏ", 1, 3, 1),
+FCE("ἇι", 2, 3, 2),FCE("ᾐ", 0, 3, 1),FCE("ᾘ", 1, 3, 1),FCE("ἠι", 2, 3, 2),
+FCE("ᾑ", 0, 3, 1),FCE("ᾙ", 1, 3, 1),FCE("ἡι", 2, 3, 2),FCE("ᾒ", 0, 3, 1),
+FCE("ᾚ", 1, 3, 1),FCE("ἢι", 2, 3, 2),FCE("ᾓ", 0, 3, 1),FCE("ᾛ", 1, 3, 1),
+FCE("ἣι", 2, 3, 2),FCE("ᾔ", 0, 3, 1),FCE("ᾜ", 1, 3, 1),FCE("ἤι", 2, 3, 2),
+FCE("ᾕ", 0, 3, 1),FCE("ᾝ", 1, 3, 1),FCE("ἥι", 2, 3, 2),FCE("ᾖ", 0, 3, 1),
+FCE("ᾞ", 1, 3, 1),FCE("ἦι", 2, 3, 2),FCE("ᾗ", 0, 3, 1),FCE("ᾟ", 1, 3, 1),
+FCE("ἧι", 2, 3, 2),FCE("ᾐ", 0, 3, 1),FCE("ᾘ", 1, 3, 1),FCE("ἠι", 2, 3, 2),
+FCE("ᾑ", 0, 3, 1),FCE("ᾙ", 1, 3, 1),FCE("ἡι", 2, 3, 2),FCE("ᾒ", 0, 3, 1),
+FCE("ᾚ", 1, 3, 1),FCE("ἢι", 2, 3, 2),FCE("ᾓ", 0, 3, 1),FCE("ᾛ", 1, 3, 1),
+FCE("ἣι", 2, 3, 2),FCE("ᾔ", 0, 3, 1),FCE("ᾜ", 1, 3, 1),FCE("ἤι", 2, 3, 2),
+FCE("ᾕ", 0, 3, 1),FCE("ᾝ", 1, 3, 1),FCE("ἥι", 2, 3, 2),FCE("ᾖ", 0, 3, 1),
+FCE("ᾞ", 1, 3, 1),FCE("ἦι", 2, 3, 2),FCE("ᾗ", 0, 3, 1),FCE("ᾟ", 1, 3, 1),
+FCE("ἧι", 2, 3, 2),FCE("ᾠ", 0, 3, 1),FCE("ᾨ", 1, 3, 1),FCE("ὠι", 2, 3, 2),
+FCE("ᾡ", 0, 3, 1),FCE("ᾩ", 1, 3, 1),FCE("ὡι", 2, 3, 2),FCE("ᾢ", 0, 3, 1),
+FCE("ᾪ", 1, 3, 1),FCE("ὢι", 2, 3, 2),FCE("ᾣ", 0, 3, 1),FCE("ᾫ", 1, 3, 1),
+FCE("ὣι", 2, 3, 2),FCE("ᾤ", 0, 3, 1),FCE("ᾬ", 1, 3, 1),FCE("ὤι", 2, 3, 2),
+FCE("ᾥ", 0, 3, 1),FCE("ᾭ", 1, 3, 1),FCE("ὥι", 2, 3, 2),FCE("ᾦ", 0, 3, 1),
+FCE("ᾮ", 1, 3, 1),FCE("ὦι", 2, 3, 2),FCE("ᾧ", 0, 3, 1),FCE("ᾯ", 1, 3, 1),
+FCE("ὧι", 2, 3, 2),FCE("ᾠ", 0, 3, 1),FCE("ᾨ", 1, 3, 1),FCE("ὠι", 2, 3, 2),
+FCE("ᾡ", 0, 3, 1),FCE("ᾩ", 1, 3, 1),FCE("ὡι", 2, 3, 2),FCE("ᾢ", 0, 3, 1),
+FCE("ᾪ", 1, 3, 1),FCE("ὢι", 2, 3, 2),FCE("ᾣ", 0, 3, 1),FCE("ᾫ", 1, 3, 1),
+FCE("ὣι", 2, 3, 2),FCE("ᾤ", 0, 3, 1),FCE("ᾬ", 1, 3, 1),FCE("ὤι", 2, 3, 2),
+FCE("ᾥ", 0, 3, 1),FCE("ᾭ", 1, 3, 1),FCE("ὥι", 2, 3, 2),FCE("ᾦ", 0, 3, 1),
+FCE("ᾮ", 1, 3, 1),FCE("ὦι", 2, 3, 2),FCE("ᾧ", 0, 3, 1),FCE("ᾯ", 1, 3, 1),
+FCE("ὧι", 2, 3, 2),FCE("ᾲ", 0, 2, 1),FCE("ὰι", 1, 2, 2),FCE("ᾳ", 0, 3, 1),
+FCE("ᾼ", 1, 3, 1),FCE("αι", 2, 3, 2),FCE("ᾴ", 0, 2, 1),FCE("άι", 1, 2, 2),
+FCE("ᾶ", 0, 2, 1),FCE("ᾶ", 1, 2, 2),FCE("ᾷ", 0, 2, 1),FCE("ᾶι", 1, 2, 3),
+FCE("ᾰ", 0, 2, 1),FCE("Ᾰ", 1, 2, 1),FCE("ᾱ", 0, 2, 1),FCE("Ᾱ", 1, 2, 1),
+FCE("ὰ", 0, 2, 1),FCE("Ὰ", 1, 2, 1),FCE("ά", 0, 2, 1),FCE("Ά", 1, 2, 1),
+FCE("ᾳ", 0, 3, 1),FCE("ᾼ", 1, 3, 1),FCE("αι", 2, 3, 2),FCE("ͅ", 0, 4, 1),
+FCE("Ι", 1, 4, 1),FCE("ι", 2, 4, 1),FCE("ι", 3, 4, 1),FCE("ῂ", 0, 2, 1),
+FCE("ὴι", 1, 2, 2),FCE("ῃ", 0, 3, 1),FCE("ῌ", 1, 3, 1),FCE("ηι", 2, 3, 2),
+FCE("ῄ", 0, 2, 1),FCE("ήι", 1, 2, 2),FCE("𐐌", 0, 2, 1),FCE("𐐴", 1, 2, 1),
+FCE("ῆ", 0, 2, 1),FCE("ῆ", 1, 2, 2),FCE("ῇ", 0, 2, 1),FCE("ῆι", 1, 2, 3),
+FCE("ὲ", 0, 2, 1),FCE("Ὲ", 1, 2, 1),FCE("έ", 0, 2, 1),FCE("Έ", 1, 2, 1),
+FCE("ὴ", 0, 2, 1),FCE("Ὴ", 1, 2, 1),FCE("ή", 0, 2, 1),FCE("Ή", 1, 2, 1),
+FCE("ῃ", 0, 3, 1),FCE("ῌ", 1, 3, 1),FCE("ηι", 2, 3, 2),FCE("ῒ", 0, 2, 1),
+FCE("ῒ", 1, 2, 3),FCE("ΐ", 0, 3, 1),FCE("ΐ", 1, 3, 1),FCE("ΐ", 2, 3, 3),
+FCE("ῖ", 0, 2, 1),FCE("ῖ", 1, 2, 2),FCE("ῗ", 0, 2, 1),FCE("ῗ", 1, 2, 3),
+FCE("ῐ", 0, 2, 1),FCE("Ῐ", 1, 2, 1),FCE("ῑ", 0, 2, 1),FCE("Ῑ", 1, 2, 1),
+FCE("ὶ", 0, 2, 1),FCE("Ὶ", 1, 2, 1),FCE("ί", 0, 2, 1),FCE("Ί", 1, 2, 1),
+FCE("𐐧", 0, 2, 1),FCE("𐑏", 1, 2, 1),FCE("ῢ", 0, 2, 1),FCE("ῢ", 1, 2, 3),
+FCE("ΰ", 0, 3, 1),FCE("ΰ", 1, 3, 1),FCE("ΰ", 2, 3, 3),FCE("ῤ", 0, 2, 1),
+FCE("ῤ", 1, 2, 2),FCE("ῦ", 0, 2, 1),FCE("ῦ", 1, 2, 2),FCE("ῧ", 0, 2, 1),
+FCE("ῧ", 1, 2, 3),FCE("ῠ", 0, 2, 1),FCE("Ῠ", 1, 2, 1),FCE("ῡ", 0, 2, 1),
+FCE("Ῡ", 1, 2, 1),FCE("ὺ", 0, 2, 1),FCE("Ὺ", 1, 2, 1),FCE("ύ", 0, 2, 1),
+FCE("Ύ", 1, 2, 1),FCE("ῥ", 0, 2, 1),FCE("Ῥ", 1, 2, 1),FCE("𐐁", 0, 2, 1),
+FCE("𐐩", 1, 2, 1),FCE("ῲ", 0, 2, 1),FCE("ὼι", 1, 2, 2),FCE("ῳ", 0, 3, 1),
+FCE("ῼ", 1, 3, 1),FCE("ωι", 2, 3, 2),FCE("ῴ", 0, 2, 1),FCE("ώι", 1, 2, 2),
+FCE("ῶ", 0, 2, 1),FCE("ῶ", 1, 2, 2),FCE("ῷ", 0, 2, 1),FCE("ῶι", 1, 2, 3),
+FCE("ὸ", 0, 2, 1),FCE("Ὸ", 1, 2, 1),FCE("ό", 0, 2, 1),FCE("Ό", 1, 2, 1),
+FCE("ὼ", 0, 2, 1),FCE("Ὼ", 1, 2, 1),FCE("ώ", 0, 2, 1),FCE("Ώ", 1, 2, 1),
+FCE("ῳ", 0, 3, 1),FCE("ῼ", 1, 3, 1),FCE("ωι", 2, 3, 2),FCE("Ⓚ", 0, 2, 1),
+FCE("ⓚ", 1, 2, 1),];
+return t;
+}
+
+struct uniProps
+{
+private alias _U = immutable(UnicodeProperty);
+@property static _U[] tab() pure { return _tab; }
+static immutable:
+private alias _T = ubyte[];
+_T So = [0x80, 0xa6, 0x1, 0x2, 0x1, 0x4, 0x1, 0x1, 0x1, 0x83, 0xd1, 0x1, 0x81, 0x8b, 0x2, 0x80, 0xce, 0x1, 0xa, 0x1, 0x13, 0x2, 0x80, 0xf7, 0x1, 0x82, 0x3, 0x1, 0x81, 0x75, 0x1, 0x80, 0x82, 0x6, 0x1, 0x1, 0x80, 0x84, 0x1, 0x80, 0xf9, 0x1, 0x81, 0x87, 0x3, 0xf, 0x1, 0x1, 0x3, 0x2, 0x6, 0x14, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x85, 0x8, 0x1, 0x6, 0x1, 0x2, 0x5, 0x4, 0x80, 0xc5, 0x2, 0x82, 0xf0, 0xa, 0x85, 0xa6, 0x1, 0x80, 0x9d, 0x22, 0x81, 0x61, 0xa, 0x9, 0x9, 0x85, 0x83, 0x2, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0xb, 0x2, 0xe, 0x1, 0x1, 0x2, 0x1, 0x1, 0x45, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7, 0x1, 0x1f, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1f, 0x81, 0xc, 0x8, 0x4, 0x14, 0x2, 0x7, 0x2, 0x51, 0x1, 0x1e, 0x19, 0x28, 0x6, 0x12, 0xc, 0x27, 0x19, 0xb, 0x51, 0x4e, 0x16, 0x80, 0xb7, 0x1, 0x9, 0x1, 0x36, 0x8, 0x6f, 0x1, 0x80, 0x90, 0x1, 0x67, 0x2c, 0x2c, 0x40, 0x81, 0x0, 0x82, 0x0, 0x30, 0x15, 0x2, 0x9, 0xa, 0x81, 0x8b, 0x6, 0x81, 0x95, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x1a, 0xc, 0x8, 0x1, 0xd, 0x2, 0xc, 0x1, 0x15, 0x2, 0x6, 0x2, 0x81, 0x50, 0x2, 0x4, 0xa, 0x20, 0x24, 0x1c, 0x1f, 0xb, 0x1e, 0x8, 0x1, 0xf, 0x20, 0xa, 0x27, 0xf, 0x3f, 0x1, 0x81, 0x0, 0x99, 0xc0, 0x40, 0xa0, 0x56, 0x90, 0x37, 0x83, 0x61, 0x4, 0xa, 0x2, 0x1, 0x1, 0x82, 0x3d, 0x3, 0xa0, 0x53, 0x83, 0x1, 0x81, 0xe6, 0x1, 0x3, 0x1, 0x4, 0x2, 0xd, 0x2, 0x81, 0x39, 0x9, 0x39, 0x11, 0x6, 0xc, 0x34, 0x2d, 0xa0, 0xce, 0x3, 0x80, 0xf6, 0xa, 0x27, 0x2, 0x3c, 0x5, 0x3, 0x16, 0x2, 0x7, 0x1e, 0x4, 0x30, 0x22, 0x42, 0x3, 0x1, 0x80, 0xba, 0x57, 0x9c, 0xa9, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x30, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74];
+_T Pf = [0x80, 0xbb, 0x1, 0x9f, 0x5d, 0x1, 0x3, 0x1, 0x1c, 0x1, 0x8d, 0xc8, 0x1, 0x1, 0x1, 0x4, 0x1, 0x2, 0x1, 0xf, 0x1, 0x3, 0x1];
+_T Bidi_Control = [0x86, 0x1c, 0x1, 0x99, 0xf1, 0x2, 0x1a, 0x5, 0x37, 0x4];
+_T Hex_Digit = [0x30, 0xa, 0x7, 0x6, 0x1a, 0x6, 0xa0, 0xfe, 0xa9, 0xa, 0x7, 0x6, 0x1a, 0x6];
+_T Other_Lowercase = [0x80, 0xaa, 0x1, 0xf, 0x1, 0x81, 0xf5, 0x9, 0x7, 0x2, 0x1e, 0x5, 0x60, 0x1, 0x34, 0x1, 0x99, 0xb1, 0x3f, 0xd, 0x1, 0x22, 0x25, 0x82, 0xb1, 0x1, 0xd, 0x1, 0x10, 0xd, 0x80, 0xd3, 0x10, 0x83, 0x50, 0x1a, 0x87, 0x92, 0x2, 0xa0, 0x7a, 0xf2, 0x1, 0x80, 0x87, 0x2];
+_T Quotation_Mark = [0x22, 0x1, 0x4, 0x1, 0x80, 0x83, 0x1, 0xf, 0x1, 0x9f, 0x5c, 0x8, 0x19, 0x2, 0x8f, 0xd1, 0x4, 0xd, 0x3, 0xa0, 0xce, 0x21, 0x4, 0x80, 0xbd, 0x1, 0x4, 0x1, 0x5a, 0x2];
+_T XID_Start = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, 0x81, 0x5, 0x1, 0x2, 0x3, 0x3, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x48, 0x1b, 0x5, 0x3, 0x2d, 0x2b, 0x23, 0x2, 0x1, 0x63, 0x1, 0x1, 0xf, 0x2, 0x7, 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, 0x18, 0x21, 0x9, 0x2, 0x4, 0x1, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x17, 0x19, 0x47, 0x1, 0x1, 0xb, 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, 0xa, 0xf, 0x7, 0x1, 0x7, 0x5, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, 0x1, 0x1, 0xd, 0x7, 0x3a, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, 0x1, 0xa, 0x1, 0x2, 0x5, 0x1, 0x1, 0x15, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, 0x4, 0x4, 0x3, 0x1, 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x11, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x4, 0xe, 0x12, 0xe, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x23, 0x1, 0x4, 0x1, 0x43, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, 0x5, 0xb, 0x2c, 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x52, 0x1, 0x5d, 0x2f, 0x11, 0x7, 0x37, 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, 0x24, 0x6b, 0x4, 0x1, 0x4, 0x3, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x82, 0x26, 0x3, 0x19, 0x9, 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, 0x6, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, 0x14, 0x2f, 0x10, 0x19, 0x8, 0x50, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x17, 0x1d, 0x34, 0xe, 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, 0x17, 0x19, 0x1d, 0x7, 0x2f, 0x1c, 0x1, 0x30, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x17, 0x3, 0x1, 0x5, 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x3, 0x2, 0xb, 0x7, 0x3, 0xc, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, 0xa, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x80, 0x8b, 0x6, 0x80, 0xda, 0x12, 0x40, 0x2, 0x36, 0x28, 0xa, 0x77, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7e, 0x24, 0x1a, 0x6, 0x1a, 0xb, 0x38, 0x2, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, 0x2d, 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, 0x55, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x1, 0x42, 0xd, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x96, 0x34, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Terminal_Punctuation = [0x21, 0x1, 0xa, 0x1, 0x1, 0x1, 0xb, 0x2, 0x3, 0x1, 0x83, 0x3e, 0x1, 0x8, 0x1, 0x82, 0x1, 0x1, 0x39, 0x1, 0x48, 0x1, 0xe, 0x1, 0x3, 0x1, 0x80, 0xb4, 0x1, 0x2b, 0xb, 0x1, 0x1, 0x80, 0xeb, 0x2, 0x36, 0xf, 0x1f, 0x1, 0x81, 0x5, 0x2, 0x84, 0xf4, 0x2, 0x80, 0xac, 0x1, 0x4, 0x6, 0x81, 0x37, 0x2, 0x83, 0x15, 0x8, 0x83, 0x4, 0x2, 0x7c, 0x3, 0x80, 0xe6, 0x3, 0x3, 0x1, 0x27, 0x4, 0x2, 0x2, 0x81, 0x3a, 0x2, 0x81, 0x62, 0x4, 0x80, 0xae, 0x2, 0x1, 0x3, 0x80, 0xdb, 0x5, 0x3e, 0x2, 0x83, 0xbc, 0x2, 0x9, 0x3, 0x8d, 0xe4, 0x1, 0x81, 0xd2, 0x2, 0xa0, 0x74, 0xfb, 0x2, 0x81, 0xd, 0x3, 0x80, 0xe3, 0x5, 0x81, 0x7e, 0x2, 0x56, 0x2, 0x5f, 0x1, 0x80, 0x97, 0x3, 0x80, 0x93, 0x3, 0x7f, 0x1, 0x10, 0x2, 0x80, 0xf9, 0x1, 0xa0, 0x52, 0x64, 0x3, 0x1, 0x4, 0x80, 0xa9, 0x1, 0xa, 0x1, 0x1, 0x1, 0xb, 0x2, 0x3, 0x1, 0x41, 0x1, 0x2, 0x1, 0x84, 0x3a, 0x1, 0x30, 0x1, 0x84, 0x86, 0x1, 0x80, 0xc7, 0x1, 0x82, 0x1a, 0x6, 0x85, 0x7, 0x7, 0x70, 0x4, 0x7f, 0x3, 0x80, 0x81, 0x2, 0x92, 0xa9, 0x4];
+_T Math = [0x2b, 0x1, 0x10, 0x3, 0x1f, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x2d, 0x1, 0x4, 0x1, 0x25, 0x1, 0x1f, 0x1, 0x82, 0xd8, 0x3, 0x2, 0x1, 0x1a, 0x2, 0x2, 0x3, 0x82, 0xf, 0x3, 0x9a, 0xd, 0x1, 0x1b, 0x3, 0xb, 0x1, 0x3, 0x1, 0xd, 0x1, 0xe, 0x4, 0x15, 0x5, 0xb, 0x5, 0x41, 0xd, 0x4, 0x1, 0x3, 0x2, 0x4, 0x5, 0x12, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x3, 0x2, 0x2, 0x2, 0x1, 0x3, 0x1, 0x6, 0x3, 0xe, 0x1, 0x1, 0x44, 0x18, 0x1, 0x6, 0x1, 0x2, 0x4, 0x2, 0x4, 0x20, 0x1, 0x1, 0x6, 0x2, 0xe, 0x81, 0xc, 0x8, 0x4, 0x14, 0x2, 0x5a, 0x1, 0x1e, 0x1b, 0x1, 0x1, 0x18, 0x1, 0xb, 0x7, 0x81, 0xbd, 0x2, 0xc, 0xa, 0x4, 0x6, 0x4, 0x2, 0x2, 0x2, 0x3, 0x5, 0xe, 0x1, 0x1, 0x1, 0x2, 0x6, 0xb, 0x8, 0x5, 0x2, 0x39, 0x1, 0x1, 0x1, 0x1d, 0x4, 0x9, 0x3, 0x81, 0x50, 0x40, 0x81, 0x0, 0x82, 0x0, 0x30, 0x15, 0x2, 0x6, 0xa0, 0xcf, 0xdc, 0x1, 0x83, 0x37, 0x6, 0x1, 0x1, 0x80, 0xa2, 0x1, 0x10, 0x3, 0x1d, 0x1, 0x1, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x80, 0x83, 0x1, 0x6, 0x4, 0xa0, 0xd4, 0x13, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2];
+_T Lu = [0x41, 0x1a, 0x65, 0x17, 0x1, 0x7, 0x21, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x3, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, 0xf, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x1, 0x11, 0x1, 0x9, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x2, 0x1, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x26, 0x8b, 0x49, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8d, 0x32, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x8, 0x48, 0x4, 0xc, 0x4, 0xc, 0x4, 0xc, 0x5, 0xb, 0x4, 0x81, 0x6, 0x1, 0x4, 0x1, 0x3, 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x2, 0x4, 0xa, 0x2, 0x5, 0x1, 0x3d, 0x1, 0x8a, 0x7c, 0x2f, 0x31, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x1, 0x1, 0x1, 0x4, 0x1, 0xa0, 0x79, 0x4d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa0, 0x57, 0x76, 0x1a, 0x84, 0xc5, 0x28, 0xa0, 0xcf, 0xd8, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0x8, 0x1a, 0x1a, 0x1a, 0x2, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1b, 0x2, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1b, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1e, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x1];
+_T Other_Uppercase = [0xa0, 0x21, 0x60, 0x10, 0x83, 0x46, 0x1a];
+_T Sk = [0x5e, 0x1, 0x1, 0x1, 0x47, 0x1, 0x6, 0x1, 0x4, 0x1, 0x3, 0x1, 0x82, 0x9, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0xe, 0x2, 0x9c, 0x37, 0x1, 0x1, 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, 0x2, 0x90, 0x9c, 0x2, 0xa0, 0x76, 0x63, 0x17, 0x9, 0x2, 0x67, 0x2, 0xa0, 0x54, 0x27, 0x10, 0x83, 0x7c, 0x1, 0x1, 0x1, 0x80, 0xa2, 0x1];
+_T Other_ID_Start = [0xa0, 0x21, 0x18, 0x1, 0x15, 0x1, 0x8f, 0x6c, 0x2];
+_T Nl = [0x96, 0xee, 0x3, 0x8a, 0x6f, 0x23, 0x2, 0x4, 0x8e, 0x7e, 0x1, 0x19, 0x9, 0xe, 0x3, 0xa0, 0x76, 0xab, 0xa, 0xa0, 0x5a, 0x50, 0x35, 0x81, 0xcc, 0x1, 0x8, 0x1, 0x80, 0x86, 0x5, 0xa0, 0x20, 0x2a, 0x63];
+_T Other_Alphabetic = [0x83, 0x45, 0x1, 0x82, 0x6a, 0xe, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x48, 0xb, 0x30, 0xd, 0x1, 0x7, 0x10, 0x1, 0x65, 0x7, 0x4, 0x4, 0x2, 0x2, 0x4, 0x1, 0x23, 0x1, 0x1e, 0x10, 0x66, 0xb, 0x65, 0x2, 0x3, 0x9, 0x1, 0x3, 0x1, 0x4, 0x80, 0xb7, 0x6, 0x6, 0xf, 0x1, 0x4, 0x36, 0x2, 0x2, 0xf, 0x1, 0x2, 0x5, 0x3, 0xa, 0x2, 0x1d, 0x3, 0x3a, 0x7, 0x2, 0x2, 0x2, 0x2, 0xa, 0x1, 0xa, 0x2, 0x1d, 0x3, 0x3a, 0x5, 0x4, 0x2, 0x2, 0x2, 0x4, 0x1, 0x1e, 0x2, 0x3, 0x1, 0xb, 0x3, 0x3a, 0x8, 0x1, 0x3, 0x1, 0x2, 0x15, 0x2, 0x1d, 0x3, 0x3a, 0x7, 0x2, 0x2, 0x2, 0x2, 0x9, 0x2, 0xa, 0x2, 0x1e, 0x1, 0x3b, 0x5, 0x3, 0x3, 0x1, 0x3, 0xa, 0x1, 0x29, 0x3, 0x3a, 0x7, 0x1, 0x3, 0x1, 0x3, 0x8, 0x2, 0xb, 0x2, 0x1e, 0x2, 0x3a, 0x7, 0x1, 0x3, 0x1, 0x3, 0x8, 0x2, 0xb, 0x2, 0x1e, 0x2, 0x3a, 0x7, 0x1, 0x3, 0x1, 0x3, 0xa, 0x1, 0xa, 0x2, 0x1e, 0x2, 0x4b, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0x3d, 0x1, 0x2, 0x7, 0x12, 0x1, 0x63, 0x1, 0x2, 0x6, 0x1, 0x2, 0x10, 0x1, 0x80, 0xa3, 0x11, 0xb, 0xb, 0x1, 0x24, 0x6e, 0xc, 0x1, 0x1, 0x2, 0x4, 0x17, 0x4, 0x4, 0x3, 0x1, 0x1, 0x4, 0x2, 0x8, 0x4, 0xd, 0x5, 0x15, 0x2, 0x82, 0xc1, 0x1, 0x83, 0xb2, 0x2, 0x1e, 0x2, 0x1e, 0x2, 0x1e, 0x2, 0x42, 0x13, 0x80, 0xe0, 0x1, 0x76, 0xc, 0x4, 0x9, 0x77, 0x11, 0x7, 0x2, 0x4d, 0x5, 0x39, 0xa, 0x2, 0x14, 0x80, 0x8b, 0x5, 0x30, 0xf, 0x3c, 0x3, 0x1e, 0x9, 0x2, 0x2, 0x39, 0xb, 0x32, 0x12, 0x80, 0xbc, 0x2, 0x87, 0xc2, 0x34, 0x88, 0xf6, 0x20, 0xa0, 0x78, 0x74, 0x8, 0x23, 0x1, 0x81, 0x83, 0x5, 0x58, 0x2, 0x32, 0x10, 0x62, 0x5, 0x1c, 0xc, 0x2d, 0x4, 0x30, 0xc, 0x69, 0xe, 0xc, 0x1, 0x8, 0x2, 0x62, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x1, 0x2c, 0x5, 0x5, 0x1, 0x80, 0xed, 0x8, 0xa0, 0x4f, 0x33, 0x1, 0x8e, 0xe2, 0x3, 0x1, 0x2, 0x5, 0x4, 0x85, 0xf0, 0x3, 0x35, 0xe, 0x3c, 0x1, 0x2d, 0x9, 0x47, 0x3, 0x24, 0xc, 0x4d, 0x3, 0x30, 0xd, 0x84, 0xeb, 0xb, 0xa0, 0x58, 0x9b, 0x2e];
+_T Alphabetic = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x56, 0x1, 0x2a, 0x5, 0x1, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x28, 0xe, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x8, 0x1b, 0x5, 0x3, 0x1d, 0xb, 0x5, 0x38, 0x1, 0x7, 0xe, 0x66, 0x1, 0x8, 0x4, 0x8, 0x4, 0x3, 0xa, 0x3, 0x2, 0x1, 0x10, 0x30, 0xd, 0x65, 0x18, 0x21, 0x9, 0x2, 0x4, 0x1, 0x5, 0x18, 0x2, 0x13, 0x13, 0x19, 0x47, 0x1, 0x1, 0xb, 0x37, 0x6, 0x6, 0xf, 0x1, 0x3c, 0x1, 0x10, 0x1, 0x3, 0x4, 0xf, 0xd, 0x7, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x8, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0xc, 0x2, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x4, 0x5, 0x4, 0x2, 0x2, 0x2, 0x4, 0x1, 0x7, 0x4, 0x1, 0x1, 0x11, 0x6, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x9, 0x1, 0x3, 0x1, 0x2, 0x3, 0x1, 0xf, 0x4, 0x1d, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x8, 0x2, 0x2, 0x2, 0x2, 0x9, 0x2, 0x4, 0x2, 0x1, 0x5, 0xd, 0x1, 0x10, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x6, 0x1, 0x29, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x3, 0x8, 0x2, 0x1, 0x2, 0x6, 0x4, 0x1e, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x3, 0x8, 0x2, 0x7, 0x1, 0x1, 0x4, 0xd, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x3, 0x1, 0x1, 0x8, 0x1, 0x8, 0x4, 0x16, 0x6, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x8, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0x7, 0x6, 0x1, 0x33, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x6, 0x1, 0xe, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, 0x24, 0x4, 0x11, 0x6, 0x10, 0x1, 0x24, 0x43, 0x37, 0x1, 0x1, 0x2, 0x5, 0x10, 0x13, 0x2, 0x4, 0x5, 0x19, 0x7, 0x1, 0xd, 0x2, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x4, 0x1, 0x20, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x6, 0xc, 0x14, 0xc, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x34, 0x2, 0x13, 0xe, 0x1, 0x4, 0x1, 0x43, 0x58, 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0x9, 0x17, 0x1e, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x36, 0x1c, 0x4, 0x3f, 0x2, 0x14, 0x32, 0x1, 0x58, 0x34, 0x1, 0xf, 0x1, 0x7, 0x34, 0x2a, 0x2, 0x4, 0xa, 0x2c, 0x1, 0xb, 0xe, 0x36, 0x17, 0x3, 0xa, 0x24, 0x6b, 0x4, 0x1, 0x6, 0x1, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0xb, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x83, 0x2d, 0x34, 0x87, 0x16, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, 0x2f, 0x1, 0x81, 0xd5, 0x3, 0x19, 0x9, 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, 0x6, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, 0x14, 0x2f, 0x5, 0x8, 0x3, 0x19, 0x7, 0x51, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x1c, 0x18, 0x34, 0xc, 0x44, 0x2e, 0x6, 0x3, 0x1, 0xe, 0x21, 0x5, 0x23, 0xd, 0x1d, 0x3, 0x33, 0x1, 0xc, 0xf, 0x1, 0x30, 0x37, 0x9, 0xe, 0x12, 0x17, 0x3, 0x1, 0x5, 0x3f, 0x1, 0x1, 0x1, 0x1, 0x18, 0x3, 0x2, 0x10, 0x2, 0x4, 0xb, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2b, 0x15, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0xc, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, 0x24, 0x1a, 0x6, 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xb7, 0x46, 0x3c, 0x37, 0x17, 0x19, 0x17, 0x33, 0x4d, 0x40, 0x1, 0x4, 0x84, 0xbb, 0x36, 0x89, 0x4a, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x14, 0xd, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x96, 0x34, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Zs = [0x20, 0x1, 0x7f, 0x1, 0x95, 0xdf, 0x1, 0x89, 0x7f, 0xb, 0x24, 0x1, 0x2f, 0x1, 0x8f, 0xa0, 0x1];
+_T Variation_Selector = [0x98, 0xb, 0x3, 0xa0, 0xe5, 0xf2, 0x10, 0xad, 0x2, 0xf0, 0x80, 0xf0];
+_T Other_Default_Ignorable_Code_Point = [0x83, 0x4f, 0x1, 0x8e, 0xf, 0x2, 0x86, 0x53, 0x2, 0x88, 0xaf, 0x1, 0x90, 0xfe, 0x1, 0xa0, 0xce, 0x3b, 0x1, 0x4f, 0x9, 0xad, 0x0, 0x7, 0x1, 0x1, 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0, 0x8e, 0x10];
+_T IDS_Binary_Operator = [0xa0, 0x2f, 0xf0, 0x2, 0x2, 0x8];
+_T Grapheme_Base = [0x20, 0x5f, 0x21, 0xd, 0x1, 0x82, 0x52, 0x70, 0x8, 0x2, 0x5, 0x5, 0x7, 0x1, 0x1, 0x1, 0x14, 0x1, 0x80, 0xe0, 0x7, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x7, 0x1, 0x27, 0x1, 0x2, 0x4, 0x1, 0x2e, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x9, 0x1b, 0x5, 0x5, 0x11, 0xa, 0xb, 0x1, 0x2, 0x2d, 0x15, 0x10, 0x1, 0x65, 0x8, 0x1, 0x6, 0x2, 0x2, 0x1, 0x4, 0x20, 0x2, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, 0xe, 0x2b, 0x9, 0x7, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x7, 0xf, 0x1, 0x19, 0x5, 0x1, 0x41, 0x1, 0x1, 0xb, 0x56, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0x14, 0x1, 0x7, 0x2, 0x2, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x1, 0x1, 0x2, 0x6, 0x2, 0x2, 0x2, 0x1, 0x1, 0xd, 0x2, 0x1, 0x3, 0x4, 0x16, 0x7, 0x1, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x4, 0x3, 0x18, 0x4, 0x1, 0x1, 0x7, 0xa, 0x2, 0x3, 0xe, 0x1, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x4, 0x8, 0x1, 0x1, 0x2, 0x3, 0x1, 0xf, 0x2, 0x4, 0xc, 0x10, 0x2, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x2, 0x1, 0x6, 0x2, 0x2, 0x2, 0xf, 0x2, 0x1, 0x3, 0x4, 0x12, 0xb, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x5, 0x1, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x15, 0x15, 0x6, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x3, 0x4, 0x13, 0x2, 0x6, 0x2, 0x4, 0xa, 0x8, 0x8, 0x2, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x2, 0x1, 0x2, 0x12, 0x1, 0x1, 0x2, 0x4, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x1, 0x2, 0x5, 0x3, 0x1, 0x3, 0x1, 0x1, 0x11, 0x2, 0x4, 0x10, 0x3, 0x7, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x9, 0x2, 0x6, 0x7, 0x13, 0x3, 0xc, 0x30, 0x1, 0x2, 0xb, 0x8, 0x8, 0xd, 0x25, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, 0x2, 0x9, 0x1, 0x2, 0x5, 0x1, 0x1, 0x9, 0xa, 0x2, 0x4, 0x20, 0x18, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0xe, 0x1, 0x24, 0x12, 0x1, 0x5, 0x1, 0x2, 0x5, 0x31, 0x8, 0x1, 0x6, 0x1, 0xd, 0x25, 0x2d, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x28, 0x1, 0x1, 0x5, 0x1, 0x2, 0x81, 0x79, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x5, 0x1d, 0x3, 0x1a, 0x6, 0x55, 0xb, 0x82, 0x9d, 0x3, 0x51, 0xf, 0xd, 0x1, 0x4, 0xe, 0x12, 0x3, 0x2, 0x9, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x9, 0x3, 0xa, 0x6, 0xa, 0x6, 0xb, 0x5, 0xa, 0x6, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x6, 0x4, 0x2, 0x3, 0x4, 0x2, 0x1, 0x6, 0x7, 0x1, 0x3, 0x2a, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x3, 0x39, 0x2, 0x2, 0x3, 0x38, 0x1, 0x1, 0x9, 0x1, 0x1, 0x2, 0x8, 0x6, 0xd, 0xa, 0x6, 0xa, 0x6, 0xe, 0x56, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x9, 0x4, 0x1b, 0x9, 0x9, 0x5, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x2, 0x8, 0x30, 0x8, 0x2, 0x5, 0xf, 0x3, 0x33, 0x40, 0x8, 0xb, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0xf, 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, 0x2, 0x3, 0x1, 0x9, 0x1, 0xb, 0x5, 0x18, 0x7, 0x31, 0x10, 0x2, 0x2, 0x1b, 0x1, 0xd, 0x3, 0x1b, 0x45, 0x80, 0x8a, 0x6, 0x82, 0x64, 0xc, 0x27, 0x19, 0xb, 0x15, 0x82, 0xa0, 0x1, 0x84, 0x4c, 0x3, 0xa, 0x80, 0xa6, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x8f, 0x3, 0x2, 0x5, 0x2d, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x2, 0xf, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x21, 0x3c, 0x44, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x1a, 0xc, 0x4, 0x2a, 0x6, 0x10, 0x1, 0x56, 0x4, 0x65, 0x5, 0x29, 0x3, 0x5e, 0x1, 0x2b, 0x5, 0x24, 0xc, 0x2f, 0x1, 0x80, 0xdf, 0x1, 0x9a, 0xb6, 0xa, 0xa0, 0x52, 0xd, 0x33, 0x84, 0x8d, 0x3, 0x37, 0x9, 0x81, 0x5c, 0x14, 0x2f, 0x4, 0x1, 0xa, 0x1a, 0x8, 0x50, 0x2, 0x6, 0x8, 0x80, 0x8f, 0x1, 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x5, 0x4, 0xa, 0x6, 0x38, 0x8, 0x44, 0xa, 0xc, 0x18, 0xa, 0x4, 0x26, 0x8, 0x19, 0xb, 0x2, 0xb, 0x1e, 0x6, 0x30, 0x1, 0x2, 0x4, 0x2, 0x1, 0x11, 0x1, 0xb, 0x4, 0x2, 0x20, 0x29, 0x6, 0x2, 0x2, 0x2, 0xb, 0x3, 0x1, 0x8, 0x1, 0x1, 0x2, 0xa, 0x2, 0x20, 0x4, 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x11, 0x2, 0x8, 0xb, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x25, 0x1, 0x2, 0x1, 0x4, 0x3, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, 0x18, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7c, 0x11, 0x81, 0x6d, 0x10, 0x40, 0x2, 0x36, 0x28, 0xe, 0x12, 0xa, 0x16, 0x23, 0x1, 0x13, 0x1, 0x4, 0x4, 0x5, 0x1, 0x80, 0x87, 0x4, 0x80, 0x9d, 0x2, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x3, 0x7, 0x1, 0x7, 0xd, 0x2, 0x2, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x5, 0x3, 0x4, 0x2d, 0x3, 0x54, 0x5, 0xc, 0x34, 0x2d, 0x80, 0x83, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x1, 0x4, 0xc, 0x1b, 0x35, 0x1e, 0x1, 0x25, 0x4, 0xe, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x1, 0x9, 0x80, 0xa0, 0x1c, 0x3, 0x1b, 0x5, 0x1, 0x40, 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0xc, 0x8, 0x8, 0x9, 0x7, 0x20, 0x80, 0x80, 0x36, 0x3, 0x1d, 0x2, 0x1b, 0x5, 0x8, 0x80, 0x80, 0x49, 0x82, 0x17, 0x1f, 0x81, 0x81, 0x1, 0x1, 0x36, 0xf, 0x7, 0x4, 0x1e, 0x12, 0x31, 0x4, 0x2, 0x2, 0x2, 0x1, 0x4, 0xe, 0x19, 0x7, 0xa, 0x9, 0x24, 0x5, 0x1, 0x9, 0xe, 0x3e, 0x34, 0x9, 0xa, 0x7, 0xa, 0x84, 0xa6, 0x2b, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x9, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0xd, 0x4, 0x8b, 0x8c, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x14, 0xd, 0xa0, 0x40, 0x60, 0x2, 0x9f, 0xfe, 0x80, 0xf6, 0xa, 0x27, 0x2, 0x3c, 0x1, 0x1, 0x3, 0x4, 0x15, 0x2, 0x7, 0x1e, 0x4, 0x30, 0x22, 0x42, 0x3, 0x1, 0x80, 0xba, 0x57, 0x9, 0x12, 0x80, 0x8e, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2, 0x81, 0xe, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0x88, 0x8c, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Case_Ignorable = [0x27, 0x1, 0x6, 0x1, 0xb, 0x1, 0x23, 0x1, 0x1, 0x1, 0x47, 0x1, 0x4, 0x1, 0x1, 0x1, 0x4, 0x1, 0x2, 0x2, 0x81, 0xf7, 0x80, 0xc0, 0x4, 0x2, 0x4, 0x1, 0x9, 0x2, 0x1, 0x1, 0x80, 0xfb, 0x7, 0x80, 0xcf, 0x1, 0x37, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x2c, 0x1, 0xb, 0x5, 0xb, 0xb, 0x1, 0x1, 0x23, 0x1, 0xa, 0x15, 0x10, 0x1, 0x65, 0x8, 0x1, 0xa, 0x1, 0x4, 0x21, 0x1, 0x1, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0xb, 0x4, 0x1, 0x1b, 0x18, 0x2b, 0x3, 0x80, 0x88, 0x1b, 0x1, 0x3, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0xd, 0x1, 0xf, 0x1, 0x3a, 0x1, 0x4, 0x4, 0x8, 0x1, 0x14, 0x2, 0x1d, 0x2, 0x39, 0x1, 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x1e, 0x2, 0x3, 0x1, 0xb, 0x2, 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, 0x14, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x2, 0x1, 0x1, 0x4, 0x8, 0x1, 0x8, 0x1, 0xb, 0x2, 0x1e, 0x1, 0x3d, 0x1, 0xc, 0x1, 0x70, 0x3, 0x5, 0x3, 0x1, 0x4, 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, 0x2, 0x1, 0x6, 0x1, 0x5, 0x2, 0x14, 0x2, 0x5d, 0x4, 0x8, 0x1, 0x14, 0x2, 0x66, 0x1, 0x7, 0x3, 0x1, 0x1, 0x5a, 0x1, 0x2, 0x7, 0xb, 0x9, 0x62, 0x1, 0x2, 0x6, 0x1, 0x2, 0x9, 0x1, 0x1, 0x6, 0x4a, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, 0x5, 0xb, 0x1, 0x24, 0x9, 0x1, 0x66, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x5e, 0x1, 0x82, 0x60, 0x3, 0x83, 0xb2, 0x3, 0x1d, 0x3, 0x1d, 0x2, 0x1e, 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x3, 0x1, 0x5, 0x1, 0x2d, 0x4, 0x34, 0x1, 0x65, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, 0x1, 0x6, 0x3, 0x80, 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x2, 0x8, 0x6, 0xa, 0x2, 0x1, 0x27, 0x1, 0x58, 0x4, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x40, 0x6, 0x52, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x37, 0x3f, 0xd, 0x1, 0x22, 0x4c, 0x15, 0x4, 0x81, 0xbd, 0x1, 0x1, 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, 0x2, 0xc, 0x5, 0x8, 0x2, 0xa, 0x1, 0x2, 0x1, 0x2, 0x5, 0x31, 0x5, 0x1, 0xa, 0x1, 0x1, 0xd, 0x1, 0x10, 0xd, 0x33, 0x21, 0x8b, 0x8b, 0x2, 0x71, 0x3, 0x7d, 0x1, 0xf, 0x1, 0x60, 0x20, 0x2f, 0x1, 0x81, 0xd5, 0x1, 0x24, 0x4, 0x3, 0x5, 0x5, 0x1, 0x5d, 0x6, 0x5d, 0x3, 0xa0, 0x6f, 0x16, 0x1, 0x84, 0xe2, 0x6, 0x81, 0xe, 0x1, 0x62, 0x4, 0x1, 0xa, 0x1, 0x1, 0x1f, 0x1, 0x50, 0x2, 0xe, 0x22, 0x4e, 0x1, 0x17, 0x3, 0x6d, 0x2, 0x8, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x80, 0x9d, 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, 0xb, 0x2e, 0x3, 0x30, 0x1, 0x2, 0x4, 0x2, 0x1, 0x12, 0x1, 0x59, 0x6, 0x2, 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, 0x23, 0x1, 0x3f, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1b, 0x1, 0xe, 0x2, 0x5, 0x2, 0x1, 0x1, 0x80, 0xee, 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x80, 0x93, 0x10, 0x82, 0x3e, 0x10, 0x3, 0x1, 0xc, 0x7, 0x2b, 0x1, 0x2, 0x1, 0x80, 0xa9, 0x1, 0x7, 0x1, 0x6, 0x1, 0xb, 0x1, 0x23, 0x1, 0x1, 0x1, 0x2f, 0x1, 0x2d, 0x2, 0x43, 0x1, 0x15, 0x3, 0x82, 0x1, 0x1, 0x88, 0x3, 0x3, 0x1, 0x2, 0x5, 0x4, 0x28, 0x3, 0x4, 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, 0x2, 0x31, 0x4, 0x2, 0x2, 0x2, 0x1, 0x42, 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, 0x34, 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, 0x11, 0xa0, 0x61, 0xc7, 0x3, 0x9, 0x10, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0xac, 0x2d, 0xbc, 0x1, 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0];
+_T STerm = [0x21, 0x1, 0xc, 0x1, 0x10, 0x1, 0x85, 0x1c, 0x1, 0x1, 0x1, 0x2a, 0x1, 0x80, 0x95, 0x1, 0x80, 0xb4, 0x1, 0x2b, 0x3, 0x80, 0xf6, 0x1, 0x81, 0x6a, 0x2, 0x86, 0xe4, 0x2, 0x83, 0x16, 0x1, 0x4, 0x2, 0x83, 0x5, 0x1, 0x80, 0xc6, 0x2, 0x80, 0xcc, 0x1, 0x5, 0x1, 0x81, 0x3a, 0x2, 0x81, 0x62, 0x4, 0x80, 0xae, 0x2, 0x2, 0x2, 0x80, 0xdb, 0x2, 0x41, 0x2, 0x83, 0xbc, 0x2, 0x9, 0x3, 0x8d, 0xe4, 0x1, 0x81, 0xd3, 0x1, 0xa0, 0x74, 0xfc, 0x1, 0x81, 0xe, 0x2, 0x80, 0xe3, 0x1, 0x3, 0x1, 0x81, 0x7e, 0x2, 0x56, 0x2, 0x5f, 0x1, 0x80, 0x98, 0x2, 0x80, 0x93, 0x3, 0x80, 0x90, 0x2, 0x80, 0xf9, 0x1, 0xa0, 0x52, 0x66, 0x1, 0x3, 0x2, 0x80, 0xa9, 0x1, 0xc, 0x1, 0x10, 0x1, 0x41, 0x1, 0x8a, 0xf4, 0x2, 0x85, 0xef, 0x2, 0x75, 0x4, 0x7f, 0x3, 0x80, 0x81, 0x2];
+_T Diacritic = [0x5e, 0x1, 0x1, 0x1, 0x47, 0x1, 0x6, 0x1, 0x4, 0x1, 0x2, 0x2, 0x81, 0xf7, 0x80, 0x9f, 0x1, 0x8, 0x5, 0x6, 0x11, 0x2, 0x4, 0x1, 0x9, 0x2, 0x80, 0xfd, 0x5, 0x80, 0xd1, 0x1, 0x37, 0x11, 0x1, 0x1b, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x80, 0x86, 0x8, 0x4, 0x2, 0x80, 0x86, 0x2, 0x4, 0x2, 0x3, 0x3, 0x43, 0x1b, 0x5b, 0xb, 0x3a, 0xb, 0x22, 0x2, 0x80, 0xca, 0x1b, 0x3d, 0x1, 0x10, 0x1, 0x3, 0x4, 0x1c, 0x1, 0x4a, 0x1, 0x10, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x7f, 0x1, 0x7c, 0x1, 0x7c, 0x6, 0x1, 0x1, 0x79, 0x5, 0x4b, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x2, 0x42, 0x3, 0x1, 0x2, 0x3e, 0x1, 0x70, 0x1, 0x1, 0x2, 0x4c, 0x7, 0x1, 0x1, 0xa, 0x2, 0x87, 0x2d, 0xb, 0x9, 0x1, 0x81, 0x5b, 0x3, 0x81, 0x39, 0x8, 0x2, 0x1, 0x80, 0xb4, 0x1, 0xf, 0x1, 0x26, 0x9, 0x36, 0x2, 0x80, 0x8a, 0x2, 0x40, 0x6, 0x52, 0x19, 0x4, 0x1, 0x6, 0x1, 0x37, 0x3f, 0x59, 0xc, 0x2d, 0x3, 0x81, 0xbd, 0x1, 0x1, 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, 0x2, 0x8c, 0xf0, 0x3, 0x81, 0x3d, 0x1, 0x81, 0xfa, 0x6, 0x69, 0x4, 0x5f, 0x1, 0xa0, 0x75, 0x72, 0x1, 0xc, 0x2, 0x1, 0x1, 0x70, 0x2, 0x25, 0xb, 0x66, 0x1, 0x6f, 0x2, 0x80, 0xca, 0x1, 0x1b, 0x12, 0x39, 0x4, 0x24, 0x1, 0x5f, 0x1, 0xc, 0x1, 0x80, 0xba, 0x1, 0x43, 0x4, 0x33, 0x1, 0x80, 0xf5, 0x2, 0xa0, 0x4f, 0x30, 0x1, 0x83, 0x1, 0x7, 0x81, 0x17, 0x1, 0x1, 0x1, 0x2f, 0x1, 0x2d, 0x2, 0x43, 0x1, 0x90, 0xd5, 0x2, 0x78, 0x2, 0x80, 0x8b, 0x1, 0x84, 0xf5, 0x2, 0xa0, 0x58, 0xd7, 0x11, 0xa0, 0x61, 0xc7, 0x3, 0x3, 0x6, 0x8, 0x8, 0x2, 0x7, 0x1e, 0x4];
+_T Lm = [0x82, 0xb0, 0x12, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, 0x85, 0x1, 0x5, 0x1, 0x81, 0xde, 0x1, 0x80, 0xe6, 0x1, 0x80, 0xa4, 0x2, 0x81, 0xd, 0x2, 0x4, 0x1, 0x1f, 0x1, 0x9, 0x1, 0x3, 0x1, 0x81, 0x48, 0x1, 0x84, 0xd4, 0x1, 0x7f, 0x1, 0x82, 0x35, 0x1, 0x86, 0xda, 0x1, 0x6b, 0x1, 0x82, 0x63, 0x1, 0x81, 0xd0, 0x6, 0x80, 0xae, 0x3f, 0xd, 0x1, 0x22, 0x25, 0x82, 0xb1, 0x1, 0xd, 0x1, 0x10, 0xd, 0x8b, 0xdf, 0x2, 0x80, 0xf1, 0x1, 0x80, 0xbf, 0x1, 0x81, 0xd5, 0x1, 0x2b, 0x5, 0x5, 0x1, 0x61, 0x2, 0x5d, 0x3, 0xa0, 0x6f, 0x16, 0x1, 0x84, 0xe2, 0x6, 0x81, 0xe, 0x1, 0x72, 0x1, 0x80, 0x97, 0x9, 0x50, 0x1, 0x17, 0x1, 0x6f, 0x2, 0x81, 0xd5, 0x1, 0x80, 0xa0, 0x1, 0x6c, 0x1, 0x15, 0x2, 0xa0, 0x54, 0x7b, 0x1, 0x2d, 0x2, 0xa0, 0x6f, 0xf3, 0xd];
+_T Mc = [0x89, 0x3, 0x1, 0x37, 0x1, 0x2, 0x3, 0x8, 0x4, 0x1, 0x2, 0x32, 0x2, 0x3a, 0x3, 0x6, 0x2, 0x2, 0x2, 0xa, 0x1, 0x2b, 0x1, 0x3a, 0x3, 0x42, 0x1, 0x3a, 0x3, 0x8, 0x1, 0x1, 0x2, 0x35, 0x2, 0x3a, 0x1, 0x1, 0x1, 0x6, 0x2, 0x2, 0x2, 0xa, 0x1, 0x66, 0x2, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0xa, 0x1, 0x29, 0x3, 0x3d, 0x4, 0x3d, 0x2, 0x3a, 0x1, 0x1, 0x5, 0x2, 0x2, 0x1, 0x2, 0x9, 0x2, 0x2b, 0x2, 0x3a, 0x3, 0x5, 0x3, 0x1, 0x3, 0xa, 0x1, 0x2a, 0x2, 0x4b, 0x3, 0x6, 0x8, 0x12, 0x2, 0x81, 0x4a, 0x2, 0x3f, 0x1, 0x80, 0xab, 0x2, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x19, 0x2, 0xa, 0x3, 0x2, 0x7, 0x15, 0x2, 0x2, 0x6, 0x2, 0x1, 0xa, 0x3, 0x87, 0x19, 0x1, 0x7, 0x8, 0x1, 0x2, 0x81, 0x5a, 0x4, 0x2, 0x3, 0x4, 0x2, 0x1, 0x6, 0x77, 0x11, 0x7, 0x2, 0x4f, 0x2, 0x3a, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x2, 0x8, 0x6, 0x80, 0x91, 0x1, 0x30, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x2, 0x3d, 0x1, 0x1e, 0x1, 0x4, 0x2, 0x2, 0x1, 0x1, 0x2, 0x39, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x2, 0x30, 0x8, 0x8, 0x2, 0x80, 0xab, 0x1, 0x10, 0x2, 0x93, 0x3a, 0x2, 0xa0, 0x77, 0xf3, 0x2, 0x2, 0x1, 0x58, 0x2, 0x32, 0x10, 0x80, 0x8e, 0x2, 0x2f, 0x1, 0x30, 0x2, 0x4, 0x2, 0x1, 0x4, 0x6e, 0x2, 0x2, 0x2, 0x18, 0x1, 0x2d, 0x1, 0x6f, 0x1, 0x2, 0x2, 0x5, 0x1, 0x80, 0xed, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0xa0, 0x64, 0x13, 0x1, 0x1, 0x1, 0x7f, 0x1, 0x2d, 0x3, 0x4, 0x2, 0x73, 0x1, 0x55, 0x1, 0x30, 0x3, 0x9, 0x2, 0x84, 0xeb, 0x1, 0x1, 0x2, 0x6, 0x1, 0xa0, 0x58, 0x9a, 0x2e, 0xa0, 0x61, 0xe6, 0x2, 0x6, 0x6];
+_T Lo = [0x80, 0xaa, 0x1, 0xf, 0x1, 0x81, 0x0, 0x1, 0x4, 0x4, 0x80, 0xd0, 0x1, 0x83, 0x3b, 0x1b, 0x5, 0x3, 0x2d, 0x20, 0x1, 0xa, 0x23, 0x2, 0x1, 0x63, 0x1, 0x1, 0x18, 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, 0x18, 0x21, 0x15, 0x16, 0x2a, 0x19, 0x47, 0x1, 0x1, 0xb, 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, 0xa, 0x10, 0x6, 0x1, 0x7, 0x5, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, 0x1, 0x2, 0xc, 0x6, 0x3b, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, 0x2, 0x9, 0x1, 0x2, 0x5, 0x17, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, 0x4, 0x4, 0x3, 0x1, 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x41, 0x2b, 0x2, 0x81, 0x4c, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x15, 0xd, 0x1, 0x4, 0xe, 0x12, 0xe, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x28, 0x1, 0x43, 0x23, 0x1, 0x34, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, 0x5, 0xb, 0x2c, 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x80, 0xb0, 0x2f, 0x11, 0x7, 0x37, 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, 0x1e, 0x71, 0x4, 0x1, 0x4, 0x3, 0x2, 0x84, 0x3e, 0x4, 0x8b, 0xf7, 0x38, 0x18, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x82, 0x27, 0x1, 0x35, 0x1, 0x4, 0x56, 0x8, 0x1, 0x1, 0x5a, 0x4, 0x1, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x15, 0x1, 0x84, 0x77, 0x43, 0x28, 0x8, 0x81, 0xc, 0x4, 0x10, 0xa, 0x2, 0x42, 0x1, 0x31, 0x46, 0x81, 0x15, 0x7, 0x1, 0x3, 0x1, 0x4, 0x1, 0x17, 0x1d, 0x34, 0xe, 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, 0x17, 0x19, 0x1d, 0x7, 0x2f, 0x4d, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x10, 0x1, 0x6, 0x3, 0x1, 0x5, 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x2, 0x3, 0xb, 0x7, 0x1, 0xe, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x43, 0x1, 0x1, 0xa, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, 0x69, 0xa, 0x1, 0x2d, 0x2, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x81, 0x85, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x11, 0x1, 0x8, 0x36, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x80, 0x80, 0x4e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, 0x2d, 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, 0x55, 0x83, 0x6f, 0x8c, 0x91, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x1, 0xa0, 0x40, 0xaf, 0x2, 0xa0, 0x3d, 0xfe, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Me = [0x84, 0x88, 0x2, 0x9c, 0x53, 0x4, 0x1, 0x3, 0xa0, 0x85, 0x8b, 0x3];
+_T ID_Start = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, 0x81, 0x5, 0x1, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x48, 0x1b, 0x5, 0x3, 0x2d, 0x2b, 0x23, 0x2, 0x1, 0x63, 0x1, 0x1, 0xf, 0x2, 0x7, 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, 0x18, 0x21, 0x9, 0x2, 0x4, 0x1, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x17, 0x19, 0x47, 0x1, 0x1, 0xb, 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, 0xa, 0xf, 0x7, 0x1, 0x7, 0x5, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, 0x1, 0x2, 0xc, 0x7, 0x3a, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, 0x2, 0x9, 0x1, 0x2, 0x5, 0x1, 0x1, 0x15, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, 0x4, 0x4, 0x3, 0x1, 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x11, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x4, 0xe, 0x12, 0xe, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x23, 0x1, 0x4, 0x1, 0x43, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, 0x5, 0xb, 0x2c, 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x52, 0x1, 0x5d, 0x2f, 0x11, 0x7, 0x37, 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, 0x24, 0x6b, 0x4, 0x1, 0x4, 0x3, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x82, 0x26, 0x3, 0x19, 0x9, 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, 0x4, 0x5, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, 0x14, 0x2f, 0x10, 0x19, 0x8, 0x50, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x17, 0x1d, 0x34, 0xe, 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, 0x17, 0x19, 0x1d, 0x7, 0x2f, 0x1c, 0x1, 0x30, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x17, 0x3, 0x1, 0x5, 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x3, 0x2, 0xb, 0x7, 0x3, 0xc, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, 0xa, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, 0x24, 0x1a, 0x6, 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, 0x2d, 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, 0x55, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x1, 0x42, 0xd, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x96, 0x34, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Other_Grapheme_Extend = [0x89, 0xbe, 0x1, 0x18, 0x1, 0x81, 0x66, 0x1, 0x18, 0x1, 0x66, 0x1, 0x18, 0x1, 0x80, 0xea, 0x1, 0x12, 0x2, 0x67, 0x1, 0x18, 0x1, 0x77, 0x1, 0xf, 0x1, 0x92, 0x2c, 0x2, 0x90, 0x20, 0x2, 0xa0, 0xcf, 0x6e, 0x2, 0xa0, 0xd1, 0xc5, 0x1, 0x8, 0x5];
+_T Lt = [0x81, 0xc5, 0x1, 0x2, 0x1, 0x2, 0x1, 0x26, 0x1, 0x9d, 0x95, 0x8, 0x8, 0x8, 0x8, 0x8, 0xc, 0x1, 0xf, 0x1, 0x2f, 0x1];
+_T Pattern_White_Space = [0x9, 0x5, 0x12, 0x1, 0x64, 0x1, 0x9f, 0x88, 0x2, 0x18, 0x2];
+_T Cased = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x80, 0xc3, 0x1, 0x4, 0x4, 0x80, 0xd0, 0x1, 0x24, 0x7, 0x2, 0x1e, 0x5, 0x60, 0x1, 0x2a, 0x4, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0xa, 0x27, 0x8b, 0x18, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8c, 0x32, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x6, 0x4, 0x1, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x20, 0x3, 0x2, 0x83, 0x31, 0x34, 0x87, 0x16, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0xa0, 0x79, 0x12, 0x2e, 0x12, 0x18, 0x80, 0x8a, 0x66, 0x3, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x3, 0xa0, 0x53, 0x5, 0x7, 0xc, 0x5, 0x84, 0x9, 0x1a, 0x6, 0x1a, 0x84, 0xa5, 0x50, 0xa0, 0xcf, 0xb0, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8];
+_T Mn = [0x83, 0x0, 0x70, 0x81, 0x13, 0x5, 0x81, 0x9, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x48, 0xb, 0x30, 0x15, 0x10, 0x1, 0x65, 0x7, 0x2, 0x6, 0x2, 0x2, 0x1, 0x4, 0x23, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0x9, 0x22, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x5, 0x2b, 0x3, 0x80, 0x88, 0x1b, 0x1, 0x3, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x4, 0x4, 0x8, 0x1, 0x14, 0x2, 0x1d, 0x2, 0x39, 0x1, 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x1e, 0x2, 0x3, 0x1, 0xb, 0x2, 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, 0x14, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x2, 0x1, 0x1, 0x4, 0x8, 0x1, 0x8, 0x1, 0xb, 0x2, 0x1e, 0x1, 0x3d, 0x1, 0xc, 0x1, 0x70, 0x3, 0x5, 0x3, 0x1, 0x4, 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, 0x2, 0x1, 0x6, 0x1, 0x5, 0x2, 0x14, 0x2, 0x5d, 0x4, 0x8, 0x1, 0x14, 0x2, 0x66, 0x1, 0x7, 0x3, 0x1, 0x1, 0x5a, 0x1, 0x2, 0x7, 0xc, 0x8, 0x62, 0x1, 0x2, 0x6, 0x1, 0x2, 0xb, 0x6, 0x4a, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, 0x5, 0xb, 0x1, 0x24, 0x9, 0x1, 0x66, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x82, 0xbf, 0x3, 0x83, 0xb2, 0x3, 0x1d, 0x3, 0x1d, 0x2, 0x1e, 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x9, 0x1, 0x2d, 0x3, 0x80, 0x9b, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, 0x1, 0x6, 0x3, 0x80, 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x2, 0x8, 0x6, 0xa, 0x2, 0x1, 0x80, 0x80, 0x4, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x80, 0x98, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, 0x27, 0x15, 0x4, 0x82, 0xd0, 0xd, 0x4, 0x1, 0x3, 0xc, 0x8b, 0xfe, 0x3, 0x80, 0x8d, 0x1, 0x60, 0x20, 0x82, 0x2a, 0x4, 0x6b, 0x2, 0xa0, 0x75, 0xd4, 0x1, 0x4, 0xa, 0x21, 0x1, 0x50, 0x2, 0x81, 0x10, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x80, 0x9d, 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, 0xb, 0x2e, 0x3, 0x30, 0x1, 0x2, 0x4, 0x2, 0x1, 0x6c, 0x6, 0x2, 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, 0x63, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x2a, 0x2, 0x8, 0x1, 0x80, 0xee, 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x82, 0xe1, 0x10, 0x10, 0x7, 0x83, 0xd6, 0x1, 0x88, 0x3, 0x3, 0x1, 0x2, 0x5, 0x4, 0x28, 0x3, 0x4, 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, 0x2, 0x31, 0x4, 0x2, 0x2, 0x45, 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, 0x34, 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, 0x4, 0xa0, 0x61, 0xd4, 0x3, 0x11, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0xac, 0x2e, 0xbb, 0x80, 0xf0];
+_T Dash = [0x2d, 0x1, 0x85, 0x5c, 0x1, 0x33, 0x1, 0x8e, 0x41, 0x1, 0x84, 0x5, 0x1, 0x88, 0x9, 0x6, 0x3d, 0x1, 0x27, 0x1, 0xf, 0x1, 0x81, 0x86, 0x1, 0x8c, 0x4, 0x1, 0x2, 0x1, 0x1f, 0x2, 0x81, 0xe0, 0x1, 0x13, 0x1, 0x6f, 0x1, 0xa0, 0xcd, 0x90, 0x2, 0x25, 0x1, 0xa, 0x1, 0x80, 0xa9, 0x1];
+_T ID_Continue = [0x30, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x1, 0x1, 0x2, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0x2, 0x2, 0x4, 0x8, 0x5, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x1, 0x5, 0x2, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x9, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x8, 0x1b, 0x5, 0x3, 0x1d, 0xb, 0x5, 0x4a, 0x4, 0x66, 0x1, 0x8, 0x2, 0xa, 0x1, 0x13, 0x2, 0x1, 0x10, 0x3b, 0x2, 0x65, 0xe, 0x36, 0x4, 0x1, 0x5, 0x2e, 0x12, 0x1c, 0x44, 0x1, 0x1, 0xb, 0x37, 0x1b, 0x1, 0x64, 0x2, 0xa, 0x1, 0x7, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0x2, 0xc, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, 0x4, 0x2, 0xa, 0x11, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x1, 0x10, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0xa, 0x11, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x12, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0xa, 0xa, 0x6, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0xf, 0x1, 0xa, 0x27, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4, 0x20, 0x1, 0x17, 0x2, 0x6, 0xa, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0xa, 0x1, 0x24, 0x4, 0x14, 0x1, 0x12, 0x1, 0x24, 0x9, 0x1, 0x39, 0x4a, 0x6, 0x4e, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x2, 0x3, 0x9, 0x9, 0xe, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x7, 0xb, 0x15, 0xb, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x54, 0x3, 0x1, 0x4, 0x2, 0x2, 0xa, 0x21, 0x3, 0x2, 0xa, 0x6, 0x58, 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0xa, 0x28, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x25, 0x1c, 0x4, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, 0xd, 0x1, 0x58, 0x4c, 0x4, 0xa, 0x11, 0x9, 0xc, 0x74, 0xc, 0x38, 0x8, 0xa, 0x3, 0x31, 0x52, 0x3, 0x1, 0x23, 0x9, 0x80, 0xe7, 0x15, 0x81, 0x1a, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x42, 0x2, 0x13, 0x1, 0x1c, 0x1, 0xd, 0x1, 0x10, 0xd, 0x33, 0xd, 0x4, 0x1, 0x3, 0xc, 0x11, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x9, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0xf, 0x18, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, 0x82, 0x5, 0x3, 0x19, 0xf, 0x1, 0x5, 0x2, 0x5, 0x4, 0x56, 0x2, 0x7, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x1c, 0x14, 0x30, 0x4, 0xa, 0x1, 0x19, 0x7, 0x53, 0x25, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x30, 0x18, 0x34, 0xc, 0x45, 0xb, 0xa, 0x6, 0x18, 0x3, 0x1, 0x4, 0x2e, 0x2, 0x24, 0xc, 0x1d, 0x3, 0x41, 0xe, 0xb, 0x26, 0x37, 0x9, 0xe, 0x2, 0xa, 0x6, 0x17, 0x3, 0x2, 0x4, 0x43, 0x18, 0x3, 0x2, 0x10, 0x2, 0x5, 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2b, 0x1, 0x2, 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0xc, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x4, 0x10, 0x10, 0x7, 0xc, 0x2, 0x18, 0x3, 0x20, 0x5, 0x1, 0x80, 0x87, 0x13, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x80, 0x88, 0x1, 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x1, 0x20, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xb7, 0x47, 0x1f, 0xa, 0x10, 0x3b, 0x15, 0x19, 0x7, 0xa, 0x6, 0x35, 0x1, 0xa, 0x40, 0x45, 0xb, 0xa, 0x84, 0xa6, 0x38, 0x8, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x10, 0x11, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x21, 0x63, 0x5, 0x3, 0x6, 0x8, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0x81, 0xbb, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e, 0xab, 0x6, 0xe2, 0x80, 0xf0];
+_T White_Space = [0x9, 0x5, 0x12, 0x1, 0x64, 0x1, 0x1a, 0x1, 0x95, 0xdf, 0x1, 0x89, 0x7f, 0xb, 0x1d, 0x2, 0x5, 0x1, 0x2f, 0x1, 0x8f, 0xa0, 0x1];
+_T Grapheme_Link = [0x89, 0x4d, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7c, 0x1, 0x6f, 0x1, 0x81, 0x49, 0x1, 0x80, 0xb4, 0x2, 0x86, 0xd9, 0x1, 0x1f, 0x1, 0x80, 0x9d, 0x1, 0x82, 0x8d, 0x1, 0x80, 0xe3, 0x1, 0x65, 0x2, 0x46, 0x2, 0x91, 0x8b, 0x1, 0xa0, 0x7a, 0x86, 0x1, 0x80, 0xbd, 0x1, 0x80, 0x8e, 0x1, 0x6c, 0x1, 0x81, 0x35, 0x1, 0x80, 0xf6, 0x1, 0xa0, 0x5e, 0x51, 0x1, 0x86, 0x6, 0x1, 0x72, 0x1, 0x79, 0x2, 0x80, 0x8b, 0x1, 0x84, 0xf5, 0x1];
+_T Ll = [0x61, 0x1a, 0x3a, 0x1, 0x29, 0x18, 0x1, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x3, 0x6, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x45, 0x1, 0x1b, 0x80, 0xc1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x3, 0x3, 0x12, 0x1, 0x1b, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x2, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x39, 0x27, 0x97, 0x78, 0x2c, 0x3f, 0xd, 0x1, 0x22, 0x66, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0xe, 0x2, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x5, 0x1, 0x2, 0x6, 0x1, 0x3, 0x3, 0x1, 0x2, 0x8, 0x4, 0x2, 0x2, 0x8, 0x8, 0xa, 0x3, 0x1, 0x2, 0x81, 0x12, 0x1, 0x3, 0x2, 0x3, 0x1, 0x1b, 0x1, 0x4, 0x1, 0x4, 0x1, 0x2, 0x2, 0x8, 0x4, 0x4, 0x1, 0x35, 0x1, 0x8a, 0xab, 0x2f, 0x2, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x6, 0x5, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x1, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0xa0, 0x79, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x1, 0xa0, 0x53, 0x5, 0x7, 0xc, 0x5, 0x84, 0x29, 0x1a, 0x84, 0xcd, 0x28, 0xa0, 0xcf, 0xca, 0x1a, 0x1a, 0x7, 0x1, 0x12, 0x1a, 0x1a, 0x1a, 0x4, 0x1, 0x1, 0x1, 0x7, 0x1, 0xb, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1c, 0x1c, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1, 0x1];
+_T Cc = [0x0, 0x20, 0x5f, 0x21];
+_T Pattern_Syntax = [0x21, 0xf, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x4, 0x22, 0x7, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x2, 0x4, 0x1, 0x4, 0x1, 0x3, 0x1, 0x17, 0x1, 0x1f, 0x1, 0x9f, 0x18, 0x18, 0x8, 0xf, 0x2, 0x13, 0x1, 0xa, 0x81, 0x31, 0x82, 0xd0, 0x80, 0xa0, 0x82, 0x76, 0x1e, 0x84, 0x6c, 0x82, 0x0, 0x80, 0x80, 0x81, 0x81, 0x3, 0x4, 0x19, 0xf, 0x1, 0xa0, 0xcd, 0xd, 0x2, 0x81, 0x5, 0x2];
+_T XID_Continue = [0x30, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x1, 0x1, 0x2, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0x2, 0x3, 0x3, 0x8, 0x5, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x1, 0x5, 0x2, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x9, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x8, 0x1b, 0x5, 0x3, 0x1d, 0xb, 0x5, 0x4a, 0x4, 0x66, 0x1, 0x8, 0x2, 0xa, 0x1, 0x13, 0x2, 0x1, 0x10, 0x3b, 0x2, 0x65, 0xe, 0x36, 0x4, 0x1, 0x5, 0x2e, 0x12, 0x1c, 0x44, 0x1, 0x1, 0xb, 0x37, 0x1b, 0x1, 0x64, 0x2, 0xa, 0x1, 0x7, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0x2, 0xc, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, 0x4, 0x2, 0xa, 0x11, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x1, 0x10, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0xa, 0x11, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x12, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0xa, 0xa, 0x6, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0xf, 0x1, 0xa, 0x27, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4, 0x20, 0x1, 0x17, 0x2, 0x6, 0xa, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0xa, 0x1, 0x24, 0x4, 0x14, 0x1, 0x12, 0x1, 0x24, 0x9, 0x1, 0x39, 0x4a, 0x6, 0x4e, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x2, 0x3, 0x9, 0x9, 0xe, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x7, 0xb, 0x15, 0xb, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x54, 0x3, 0x1, 0x4, 0x2, 0x2, 0xa, 0x21, 0x3, 0x2, 0xa, 0x6, 0x58, 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0xa, 0x28, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x25, 0x1c, 0x4, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, 0xd, 0x1, 0x58, 0x4c, 0x4, 0xa, 0x11, 0x9, 0xc, 0x74, 0xc, 0x38, 0x8, 0xa, 0x3, 0x31, 0x52, 0x3, 0x1, 0x23, 0x9, 0x80, 0xe7, 0x15, 0x81, 0x1a, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x42, 0x2, 0x13, 0x1, 0x1c, 0x1, 0xd, 0x1, 0x10, 0xd, 0x33, 0xd, 0x4, 0x1, 0x3, 0xc, 0x11, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x9, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0xf, 0x18, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, 0x82, 0x5, 0x3, 0x19, 0xf, 0x1, 0x5, 0x2, 0x5, 0x4, 0x56, 0x2, 0x2, 0x2, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x1c, 0x14, 0x30, 0x4, 0xa, 0x1, 0x19, 0x7, 0x53, 0x25, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x30, 0x18, 0x34, 0xc, 0x45, 0xb, 0xa, 0x6, 0x18, 0x3, 0x1, 0x4, 0x2e, 0x2, 0x24, 0xc, 0x1d, 0x3, 0x41, 0xe, 0xb, 0x26, 0x37, 0x9, 0xe, 0x2, 0xa, 0x6, 0x17, 0x3, 0x2, 0x4, 0x43, 0x18, 0x3, 0x2, 0x10, 0x2, 0x5, 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2b, 0x1, 0x2, 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0xc, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x80, 0x8b, 0x6, 0x80, 0xda, 0x12, 0x40, 0x2, 0x36, 0x28, 0xa, 0x6, 0x10, 0x10, 0x7, 0xc, 0x2, 0x18, 0x3, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7e, 0x13, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x80, 0x88, 0x1, 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x1, 0x20, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xb7, 0x47, 0x1f, 0xa, 0x10, 0x3b, 0x15, 0x19, 0x7, 0xa, 0x6, 0x35, 0x1, 0xa, 0x40, 0x45, 0xb, 0xa, 0x84, 0xa6, 0x38, 0x8, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x10, 0x11, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x21, 0x63, 0x5, 0x3, 0x6, 0x8, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0x81, 0xbb, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e, 0xab, 0x6, 0xe2, 0x80, 0xf0];
+_T Lowercase = [0x61, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x24, 0x18, 0x1, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x3, 0x6, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x45, 0x1, 0x24, 0x7, 0x2, 0x1e, 0x5, 0x60, 0x1, 0x2b, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x4, 0x12, 0x1, 0x1b, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x2, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x39, 0x27, 0x97, 0x78, 0x80, 0xc0, 0x41, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0xe, 0x2, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x5, 0x1, 0x2, 0x6, 0x1, 0x3, 0x3, 0x1, 0x2, 0x8, 0x4, 0x2, 0x2, 0x8, 0x8, 0xa, 0x3, 0x1, 0x2, 0x79, 0x1, 0xd, 0x1, 0x10, 0xd, 0x6d, 0x1, 0x3, 0x2, 0x3, 0x1, 0x1b, 0x1, 0x4, 0x1, 0x4, 0x1, 0x2, 0x2, 0x8, 0x4, 0x4, 0x1, 0x21, 0x10, 0x4, 0x1, 0x83, 0x4b, 0x1a, 0x87, 0x46, 0x2f, 0x2, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x1, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0xa0, 0x79, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4e, 0x3, 0xa0, 0x53, 0x5, 0x7, 0xc, 0x5, 0x84, 0x29, 0x1a, 0x84, 0xcd, 0x28, 0xa0, 0xcf, 0xca, 0x1a, 0x1a, 0x7, 0x1, 0x12, 0x1a, 0x1a, 0x1a, 0x4, 0x1, 0x1, 0x1, 0x7, 0x1, 0xb, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1c, 0x1c, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1, 0x1];
+_T Zl = [0xa0, 0x20, 0x28, 0x1];
+_T Zp = [0xa0, 0x20, 0x29, 0x1];
+_T Radical = [0xa0, 0x2e, 0x80, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6];
+_T Extender = [0x80, 0xb7, 0x1, 0x82, 0x18, 0x2, 0x83, 0x6e, 0x1, 0x81, 0xb9, 0x1, 0x86, 0x4b, 0x1, 0x7f, 0x1, 0x89, 0x43, 0x1, 0x38, 0x1, 0x82, 0x63, 0x1, 0x81, 0x8e, 0x1, 0x44, 0x1, 0x93, 0x89, 0x1, 0x2b, 0x5, 0x67, 0x2, 0x5d, 0x3, 0xa0, 0x6f, 0x16, 0x1, 0x85, 0xf6, 0x1, 0x83, 0xc2, 0x1, 0x80, 0xa0, 0x1, 0x6c, 0x1, 0x15, 0x2, 0xa0, 0x54, 0x7b, 0x1];
+_T Co = [0xa0, 0xe0, 0x0, 0x99, 0x0, 0xae, 0x7, 0x0, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe];
+_T Unified_Ideograph = [0xa0, 0x34, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0xa0, 0x5a, 0x41, 0x2, 0x1, 0x1, 0x1, 0x2, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0xa1, 0x5, 0xd6, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde];
+_T Pc = [0x5f, 0x1, 0x9f, 0xdf, 0x2, 0x13, 0x1, 0xa0, 0xdd, 0xde, 0x2, 0x18, 0x3, 0x80, 0xef, 0x1];
+_T Cs = [0xa0, 0xd8, 0x0, 0x88, 0x0];
+_T Noncharacter_Code_Point = [0xa0, 0xfd, 0xd0, 0x20, 0x82, 0xe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe];
+_T Uppercase = [0x41, 0x1a, 0x65, 0x17, 0x1, 0x7, 0x21, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x3, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, 0xf, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x1, 0x11, 0x1, 0x9, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x2, 0x1, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x26, 0x8b, 0x49, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8d, 0x32, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x8, 0x48, 0x4, 0xc, 0x4, 0xc, 0x4, 0xc, 0x5, 0xb, 0x4, 0x81, 0x6, 0x1, 0x4, 0x1, 0x3, 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x2, 0x4, 0xa, 0x2, 0x5, 0x1, 0x1a, 0x10, 0x13, 0x1, 0x83, 0x32, 0x1a, 0x87, 0x30, 0x2f, 0x31, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x1, 0x1, 0x1, 0x4, 0x1, 0xa0, 0x79, 0x4d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa0, 0x57, 0x76, 0x1a, 0x84, 0xc5, 0x28, 0xa0, 0xcf, 0xd8, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0x8, 0x1a, 0x1a, 0x1a, 0x2, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1b, 0x2, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1b, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1e, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x1];
+_T IDS_Trinary_Operator = [0xa0, 0x2f, 0xf2, 0x2];
+_T Logical_Order_Exception = [0x8e, 0x40, 0x5, 0x7b, 0x5, 0xa0, 0x9b, 0xf0, 0x2, 0x2, 0x1, 0x1, 0x2];
+_T Pi = [0x80, 0xab, 0x1, 0x9f, 0x6c, 0x1, 0x2, 0x2, 0x2, 0x1, 0x19, 0x1, 0x8d, 0xc8, 0x1, 0x1, 0x1, 0x4, 0x1, 0x2, 0x1, 0xf, 0x1, 0x3, 0x1];
+_T Soft_Dotted = [0x69, 0x2, 0x80, 0xc4, 0x1, 0x81, 0x19, 0x1, 0x1e, 0x1, 0x34, 0x1, 0x14, 0x1, 0x81, 0x40, 0x1, 0x62, 0x1, 0x1, 0x1, 0x99, 0x9, 0x1, 0x33, 0x1, 0xd, 0x1, 0x3, 0x1, 0x80, 0x84, 0x1, 0x80, 0x9d, 0x1, 0x81, 0xa5, 0x1, 0x80, 0xd6, 0x2, 0x8b, 0x32, 0x1, 0xa1, 0xa7, 0xa5, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2];
+_T Po = [0x21, 0x3, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0xa, 0x2, 0x3, 0x2, 0x1b, 0x1, 0x44, 0x1, 0x5, 0x1, 0xe, 0x2, 0x7, 0x1, 0x82, 0xbe, 0x1, 0x8, 0x1, 0x81, 0xd2, 0x6, 0x29, 0x1, 0x36, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2c, 0x2, 0x14, 0x2, 0x1, 0x2, 0xd, 0x1, 0x2, 0x2, 0x4a, 0x4, 0x66, 0x1, 0x2b, 0xe, 0x80, 0xe9, 0x3, 0x36, 0xf, 0x1f, 0x1, 0x81, 0x5, 0x2, 0xa, 0x1, 0x81, 0x7f, 0x1, 0x83, 0x3, 0x1, 0x5a, 0x1, 0xa, 0x2, 0x80, 0xa8, 0xf, 0x1, 0x1, 0x70, 0x1, 0x4a, 0x5, 0x4, 0x2, 0x6f, 0x6, 0x80, 0xab, 0x1, 0x82, 0x64, 0x9, 0x83, 0x4, 0x2, 0x7c, 0x3, 0x47, 0x2, 0x80, 0x9d, 0x3, 0x1, 0x3, 0x25, 0x6, 0x1, 0x4, 0x81, 0x39, 0x2, 0x80, 0xd8, 0x2, 0x80, 0x80, 0x7, 0x1, 0x6, 0x80, 0xac, 0x7, 0x80, 0x9b, 0x4, 0x3b, 0x5, 0x3e, 0x2, 0x40, 0x8, 0xb, 0x1, 0x83, 0x42, 0x2, 0x8, 0x8, 0x8, 0x9, 0x2, 0x4, 0x2, 0x3, 0x3, 0xb, 0x1, 0x1, 0x1, 0xa, 0x8c, 0x9a, 0x4, 0x1, 0x2, 0x70, 0x1, 0x80, 0x8f, 0x2, 0x4, 0x3, 0x2, 0x1, 0x2, 0x9, 0x1, 0x2, 0x1, 0x1, 0x2, 0x2, 0xa, 0x5, 0x1, 0xa, 0x81, 0xc7, 0x3, 0x39, 0x1, 0x80, 0xbd, 0x1, 0xa0, 0x74, 0x2, 0x2, 0x81, 0xd, 0x3, 0x63, 0x1, 0xa, 0x1, 0x73, 0x6, 0x81, 0x7c, 0x4, 0x56, 0x2, 0x28, 0x3, 0x33, 0x2, 0x2f, 0x1, 0x61, 0xd, 0x10, 0x2, 0x7c, 0x4, 0x7e, 0x2, 0x10, 0x2, 0x80, 0xf9, 0x1, 0xa0, 0x52, 0x24, 0x7, 0x2, 0x1, 0x16, 0x1, 0x14, 0x2, 0x2, 0x4, 0x3, 0x3, 0x1, 0x4, 0x7, 0x3, 0x6, 0x1, 0x1, 0x2, 0x80, 0x95, 0x3, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0xa, 0x2, 0x3, 0x2, 0x1b, 0x1, 0x24, 0x1, 0x2, 0x2, 0x81, 0x9a, 0x3, 0x82, 0x9c, 0x1, 0x30, 0x1, 0x84, 0x86, 0x1, 0x80, 0xc7, 0x1, 0x1f, 0x1, 0x81, 0x10, 0x9, 0x26, 0x1, 0x80, 0xb9, 0x7, 0x85, 0x7, 0x7, 0x6d, 0x2, 0x1, 0x4, 0x7e, 0x4, 0x80, 0x81, 0x4, 0x92, 0xa7, 0x4];
+_T Cn = [0x83, 0x78, 0x2, 0x5, 0x5, 0x7, 0x1, 0x1, 0x1, 0x14, 0x1, 0x81, 0x85, 0x9, 0x26, 0x2, 0x7, 0x1, 0x27, 0x1, 0x2, 0x4, 0x1, 0x1, 0x37, 0x8, 0x1b, 0x5, 0x5, 0xb, 0x5, 0x1, 0x17, 0x1, 0x80, 0xf0, 0x1, 0x3c, 0x2, 0x65, 0xe, 0x3b, 0x5, 0x2e, 0x2, 0xf, 0x1, 0x1c, 0x2, 0x1, 0x41, 0x1, 0x1, 0xb, 0x37, 0x1b, 0x1, 0x78, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0x2, 0x16, 0x5, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, 0x4, 0x2, 0xc, 0xf, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, 0x2, 0x12, 0xa, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0x15, 0x6, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x8, 0x8, 0x2, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0x10, 0x3, 0x7, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x3, 0xc, 0x3a, 0x4, 0x1d, 0x25, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4, 0x20, 0x48, 0x1, 0x24, 0x4, 0x27, 0x1, 0x24, 0x1, 0xf, 0x1, 0xd, 0x25, 0x80, 0xc6, 0x1, 0x1, 0x5, 0x1, 0x2, 0x81, 0x79, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x2, 0x20, 0x3, 0x1a, 0x6, 0x55, 0xb, 0x82, 0x9d, 0x3, 0x51, 0xf, 0xd, 0x1, 0x7, 0xb, 0x17, 0x9, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x5e, 0x2, 0xa, 0x6, 0xa, 0x6, 0xf, 0x1, 0xa, 0x6, 0x58, 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0x4, 0x1, 0x3, 0x2a, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x3, 0x3e, 0x2, 0x41, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, 0x6, 0xe, 0x52, 0x4c, 0x4, 0x2d, 0x3, 0x74, 0x8, 0x3c, 0x3, 0xf, 0x3, 0x33, 0x40, 0x8, 0x8, 0x27, 0x9, 0x80, 0xe7, 0x15, 0x81, 0x1a, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0xf, 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, 0x2, 0x3, 0x1, 0x9, 0x1, 0x65, 0x1, 0xc, 0x2, 0x1b, 0x1, 0xd, 0x3, 0x1b, 0x15, 0x21, 0xf, 0x80, 0x8a, 0x6, 0x82, 0x64, 0xc, 0x27, 0x19, 0xb, 0x15, 0x82, 0xa0, 0x1, 0x84, 0x4c, 0x3, 0xa, 0x80, 0xa6, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x94, 0x5, 0x2d, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x2, 0xe, 0x18, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x5c, 0x44, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x1a, 0xc, 0x4, 0x40, 0x1, 0x56, 0x2, 0x67, 0x5, 0x29, 0x3, 0x5e, 0x1, 0x2b, 0x5, 0x24, 0xc, 0x2f, 0x1, 0x80, 0xdf, 0x1, 0x9a, 0xb6, 0xa, 0xa0, 0x52, 0xd, 0x33, 0x84, 0x8d, 0x3, 0x37, 0x9, 0x81, 0x5c, 0x14, 0x58, 0x7, 0x59, 0x8, 0x80, 0x8f, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x34, 0x4, 0xa, 0x6, 0x38, 0x8, 0x45, 0x9, 0xc, 0x6, 0x1c, 0x4, 0x54, 0xb, 0x1e, 0x3, 0x4e, 0x1, 0xb, 0x4, 0x2, 0x20, 0x37, 0x9, 0xe, 0x2, 0xa, 0x2, 0x20, 0x4, 0x43, 0x18, 0x1c, 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2e, 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0x4, 0xa0, 0x22, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1a, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7c, 0x11, 0x81, 0x6d, 0x10, 0x40, 0x2, 0x36, 0x28, 0xe, 0x2, 0x1a, 0x6, 0x7, 0x9, 0x23, 0x1, 0x13, 0x1, 0x4, 0x4, 0x5, 0x1, 0x80, 0x87, 0x2, 0x1, 0x1, 0x80, 0xbe, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x3, 0x7, 0x1, 0x7, 0xa, 0x5, 0x2, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x5, 0x3, 0x4, 0x2d, 0x3, 0x54, 0x5, 0xc, 0x34, 0x2e, 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x1, 0x4, 0xc, 0x1b, 0x35, 0x1e, 0x1, 0x25, 0x4, 0xe, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x1, 0x9, 0x80, 0xa0, 0x1c, 0x3, 0x1b, 0x5, 0x1, 0x40, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x9, 0x8, 0x9, 0x7, 0x20, 0x80, 0x80, 0x36, 0x3, 0x1d, 0x2, 0x1b, 0x5, 0x8, 0x80, 0x80, 0x49, 0x82, 0x17, 0x1f, 0x81, 0x81, 0x4e, 0x4, 0x1e, 0x10, 0x42, 0xe, 0x19, 0x7, 0xa, 0x6, 0x35, 0x1, 0xe, 0x3c, 0x49, 0x7, 0xa, 0x84, 0xa6, 0x38, 0x8, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0xd, 0x4, 0x8b, 0x8c, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x10, 0x11, 0xa0, 0x40, 0x60, 0x2, 0x9f, 0xfe, 0x80, 0xf6, 0xa, 0x27, 0x2, 0x80, 0xb5, 0x22, 0x46, 0x80, 0xba, 0x57, 0x9, 0x12, 0x80, 0x8e, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2, 0x81, 0xe, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0x88, 0x8c, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e, 0xab, 0x5, 0xe3, 0x1, 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0, 0xa0, 0xfe, 0x10, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe];
+_T Ps = [0x28, 0x1, 0x32, 0x1, 0x1f, 0x1, 0x8e, 0xbe, 0x1, 0x1, 0x1, 0x87, 0x5e, 0x1, 0x89, 0x7e, 0x1, 0x3, 0x1, 0x26, 0x1, 0x37, 0x1, 0xf, 0x1, 0x82, 0x7a, 0x1, 0x1, 0x1, 0x1e, 0x1, 0x84, 0x3e, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x1, 0x20, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x94, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x40, 0x1, 0x1, 0x1, 0x21, 0x1, 0x84, 0x25, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0xdf, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0xa0, 0xcd, 0x20, 0x1, 0x80, 0xd8, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x11, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0xaa, 0x1, 0x32, 0x1, 0x1f, 0x1, 0x3, 0x1, 0x2, 0x1];
+_T ASCII_Hex_Digit = [0x30, 0xa, 0x7, 0x6, 0x1a, 0x6];
+_T No = [0x80, 0xb2, 0x2, 0x5, 0x1, 0x2, 0x3, 0x89, 0x35, 0x6, 0x81, 0x78, 0x6, 0x78, 0x3, 0x80, 0x85, 0x7, 0x80, 0xf1, 0x6, 0x81, 0xb4, 0xa, 0x84, 0x35, 0x14, 0x84, 0x73, 0xa, 0x81, 0xe0, 0x1, 0x86, 0x95, 0x1, 0x3, 0x6, 0x6, 0xa, 0x80, 0xc6, 0x10, 0x29, 0x1, 0x82, 0xd6, 0x3c, 0x4e, 0x16, 0x82, 0x76, 0x1e, 0x85, 0x69, 0x1, 0x84, 0x94, 0x4, 0x80, 0x8a, 0xa, 0x1e, 0x8, 0x1, 0xf, 0x20, 0xa, 0x27, 0xf, 0xa0, 0x75, 0x70, 0x6, 0xa0, 0x58, 0xd1, 0x2d, 0x41, 0x4, 0x11, 0x1, 0x81, 0x95, 0x4, 0x85, 0x34, 0x8, 0x80, 0xb6, 0x6, 0x81, 0x24, 0x8, 0x35, 0x2, 0x80, 0xd9, 0x8, 0x18, 0x8, 0x82, 0xe0, 0x1f, 0x81, 0xd3, 0x14, 0xa0, 0xc2, 0xfa, 0x12, 0x9d, 0x8e, 0xb];
+_T Sm = [0x2b, 0x1, 0x10, 0x3, 0x3d, 0x1, 0x1, 0x1, 0x2d, 0x1, 0x4, 0x1, 0x25, 0x1, 0x1f, 0x1, 0x82, 0xfe, 0x1, 0x82, 0xf, 0x3, 0x9a, 0x3b, 0x1, 0xd, 0x1, 0x27, 0x3, 0xd, 0x3, 0x80, 0x8b, 0x1, 0x27, 0x5, 0x6, 0x1, 0x44, 0x5, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7, 0x1, 0x1f, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1f, 0x81, 0xc, 0x20, 0x2, 0x5a, 0x1, 0x1e, 0x19, 0x28, 0x6, 0x81, 0xd5, 0x1, 0x9, 0x1, 0x36, 0x8, 0x6f, 0x1, 0x81, 0x50, 0x5, 0x2, 0x1f, 0xa, 0x10, 0x81, 0x0, 0x80, 0x83, 0x16, 0x3f, 0x4, 0x20, 0x2, 0x81, 0x2, 0x30, 0x15, 0x2, 0x6, 0xa0, 0xcf, 0xdc, 0x1, 0x83, 0x38, 0x1, 0x1, 0x3, 0x80, 0xa4, 0x1, 0x10, 0x3, 0x3d, 0x1, 0x1, 0x1, 0x80, 0x83, 0x1, 0x6, 0x4, 0xa0, 0xd6, 0xd4, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x97, 0x2c, 0x2];
+_T Other_Math = [0x5e, 0x1, 0x83, 0x71, 0x3, 0x2, 0x1, 0x1a, 0x2, 0x2, 0x2, 0x9c, 0x20, 0x1, 0x1b, 0x3, 0xb, 0x1, 0x20, 0x4, 0x18, 0x2, 0xe, 0x2, 0x41, 0xd, 0x4, 0x1, 0x3, 0x2, 0x4, 0x5, 0x12, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, 0x5, 0x6, 0x1, 0x3, 0x2, 0x2, 0x2, 0x1, 0x3, 0x1, 0x6, 0x3, 0x4, 0x5, 0x5, 0x4b, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x5, 0x2, 0x2, 0x4, 0x2, 0x4, 0x12, 0x2, 0x2, 0x1, 0x1, 0x1, 0x7, 0x1, 0x1, 0x6, 0x2, 0x81, 0x22, 0x4, 0x80, 0xa8, 0x2, 0x1, 0x1, 0x18, 0x1, 0x11, 0x1, 0x81, 0xbd, 0x2, 0xc, 0x9, 0x5, 0x5, 0x5, 0x2, 0x2, 0x2, 0x3, 0x5, 0xe, 0x1, 0x1, 0x1, 0x2, 0x6, 0x18, 0x2, 0x39, 0x1, 0x1, 0x1, 0x1d, 0x4, 0x9, 0x2, 0x81, 0x56, 0x2, 0x1f, 0xa, 0x81, 0x93, 0x16, 0x3f, 0x4, 0x20, 0x2, 0xa0, 0xd4, 0x63, 0x1, 0x1, 0x1, 0x4, 0x1, 0x80, 0xd3, 0x1, 0x1, 0x1, 0xa0, 0xd4, 0xc1, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11];
+_T Join_Control = [0xa0, 0x20, 0xc, 0x2];
+_T Cf = [0x80, 0xad, 0x1, 0x85, 0x52, 0x5, 0x17, 0x1, 0x80, 0xc0, 0x1, 0x31, 0x1, 0x90, 0xfe, 0x1, 0x87, 0xfc, 0x5, 0x1a, 0x5, 0x31, 0x5, 0x1, 0xa, 0xa0, 0xde, 0x8f, 0x1, 0x80, 0xf9, 0x3, 0x90, 0xc1, 0x1, 0xa0, 0xc0, 0xb5, 0x8, 0xac, 0x2e, 0x86, 0x1, 0x1e, 0x60];
+_T Ideographic = [0xa0, 0x30, 0x6, 0x2, 0x19, 0x9, 0xe, 0x3, 0x83, 0xc5, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0xa0, 0x59, 0x33, 0x81, 0x6e, 0x2, 0x6a, 0xa1, 0x5, 0x26, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Sc = [0x24, 0x1, 0x7d, 0x4, 0x84, 0xe9, 0x1, 0x7b, 0x1, 0x83, 0xe6, 0x2, 0x7, 0x1, 0x80, 0xf5, 0x1, 0x81, 0x7, 0x1, 0x82, 0x45, 0x1, 0x89, 0x9b, 0x1, 0x88, 0xc4, 0x1b, 0xa0, 0x87, 0x7d, 0x1, 0xa0, 0x55, 0xc3, 0x1, 0x6c, 0x1, 0x80, 0x9a, 0x1, 0x80, 0xdb, 0x2, 0x3, 0x2];
+_T Nd = [0x30, 0xa, 0x86, 0x26, 0xa, 0x80, 0x86, 0xa, 0x80, 0xc6, 0xa, 0x81, 0x9c, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x80, 0xe0, 0xa, 0x76, 0xa, 0x46, 0xa, 0x81, 0x16, 0xa, 0x46, 0xa, 0x87, 0x46, 0xa, 0x26, 0xa, 0x81, 0x2c, 0xa, 0x80, 0x80, 0xa, 0x80, 0xa6, 0xa, 0x6, 0xa, 0x80, 0xb6, 0xa, 0x56, 0xa, 0x80, 0x86, 0xa, 0x6, 0xa, 0xa0, 0x89, 0xc6, 0xa, 0x82, 0xa6, 0xa, 0x26, 0xa, 0x80, 0xc6, 0xa, 0x76, 0xa, 0x81, 0x96, 0xa, 0xa0, 0x53, 0x16, 0xa, 0x85, 0x86, 0xa, 0x8b, 0xbc, 0xa, 0x80, 0x80, 0xa, 0x3c, 0xa, 0x80, 0x90, 0xa, 0x84, 0xe6, 0xa, 0xa0, 0xc1, 0x4, 0x32];
+_T Default_Ignorable_Code_Point = [0x80, 0xad, 0x1, 0x82, 0xa1, 0x1, 0x82, 0xcc, 0x1, 0x8b, 0x42, 0x2, 0x86, 0x53, 0x2, 0x55, 0x4, 0x87, 0xfc, 0x5, 0x1a, 0x5, 0x31, 0x10, 0x90, 0xf4, 0x1, 0xa0, 0xcc, 0x9b, 0x10, 0x80, 0xef, 0x1, 0x80, 0xa0, 0x1, 0x4f, 0x9, 0xa0, 0xd1, 0x7a, 0x8, 0xac, 0x2e, 0x85, 0x90, 0x0];
+_T Other_ID_Continue = [0x80, 0xb7, 0x1, 0x82, 0xcf, 0x1, 0x8f, 0xe1, 0x9, 0x86, 0x68, 0x1];
+_T Pd = [0x2d, 0x1, 0x85, 0x5c, 0x1, 0x33, 0x1, 0x8e, 0x41, 0x1, 0x84, 0x5, 0x1, 0x88, 0x9, 0x6, 0x8e, 0x1, 0x1, 0x2, 0x1, 0x1f, 0x2, 0x81, 0xe0, 0x1, 0x13, 0x1, 0x6f, 0x1, 0xa0, 0xcd, 0x90, 0x2, 0x25, 0x1, 0xa, 0x1, 0x80, 0xa9, 0x1];
+_T Deprecated = [0x81, 0x49, 0x1, 0x85, 0x29, 0x1, 0x89, 0x3, 0x1, 0x1, 0x1, 0x88, 0x29, 0x2, 0x88, 0xc5, 0x6, 0x82, 0xb9, 0x2, 0xad, 0xdc, 0xd6, 0x1, 0x1e, 0x60];
+_T Grapheme_Extend = [0x83, 0x0, 0x70, 0x81, 0x13, 0x7, 0x81, 0x7, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x48, 0xb, 0x30, 0x15, 0x10, 0x1, 0x65, 0x7, 0x2, 0x6, 0x2, 0x2, 0x1, 0x4, 0x23, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0x9, 0x22, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x5, 0x2b, 0x3, 0x80, 0x88, 0x1b, 0x1, 0x3, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x1, 0x1, 0x2, 0x4, 0x8, 0x1, 0x9, 0x1, 0xa, 0x2, 0x1d, 0x2, 0x39, 0x1, 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x1e, 0x2, 0x3, 0x1, 0xb, 0x2, 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, 0x14, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x1, 0x4, 0x8, 0x1, 0x8, 0x2, 0xa, 0x2, 0x1e, 0x1, 0x3b, 0x1, 0x1, 0x1, 0xc, 0x1, 0x9, 0x1, 0x66, 0x3, 0x5, 0x3, 0x1, 0x4, 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, 0x2, 0x1, 0x2, 0x1, 0x3, 0x1, 0x5, 0x2, 0x7, 0x2, 0xb, 0x2, 0x5a, 0x1, 0x2, 0x4, 0x8, 0x1, 0x9, 0x1, 0xa, 0x2, 0x66, 0x1, 0x4, 0x1, 0x2, 0x3, 0x1, 0x1, 0x8, 0x1, 0x51, 0x1, 0x2, 0x7, 0xc, 0x8, 0x62, 0x1, 0x2, 0x6, 0x1, 0x2, 0xb, 0x6, 0x4a, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, 0x5, 0xb, 0x1, 0x24, 0x9, 0x1, 0x66, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x82, 0xbf, 0x3, 0x83, 0xb2, 0x3, 0x1d, 0x3, 0x1d, 0x2, 0x1e, 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x9, 0x1, 0x2d, 0x3, 0x80, 0x9b, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, 0x1, 0x6, 0x3, 0x80, 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x2, 0x8, 0x6, 0xa, 0x2, 0x1, 0x80, 0x80, 0x4, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x80, 0x98, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, 0x27, 0x15, 0x4, 0x82, 0xc, 0x2, 0x80, 0xc2, 0x21, 0x8b, 0xfe, 0x3, 0x80, 0x8d, 0x1, 0x60, 0x20, 0x82, 0x2a, 0x6, 0x69, 0x2, 0xa0, 0x75, 0xd4, 0x4, 0x1, 0xa, 0x21, 0x1, 0x50, 0x2, 0x81, 0x10, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x80, 0x9d, 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, 0xb, 0x2e, 0x3, 0x30, 0x1, 0x2, 0x4, 0x2, 0x1, 0x6c, 0x6, 0x2, 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, 0x63, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x2a, 0x2, 0x8, 0x1, 0x80, 0xee, 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x82, 0xe1, 0x10, 0x10, 0x7, 0x81, 0x77, 0x2, 0x82, 0x5d, 0x1, 0x88, 0x3, 0x3, 0x1, 0x2, 0x5, 0x4, 0x28, 0x3, 0x4, 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, 0x2, 0x31, 0x4, 0x2, 0x2, 0x45, 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, 0x34, 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, 0x4, 0xa0, 0x61, 0xd2, 0x1, 0x1, 0x3, 0x4, 0x5, 0x8, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0xac, 0x2e, 0xbb, 0x80, 0xf0];
+_T Hyphen = [0x2d, 0x1, 0x7f, 0x1, 0x84, 0xdc, 0x1, 0x92, 0x7b, 0x1, 0x88, 0x9, 0x2, 0x8e, 0x5, 0x1, 0x82, 0xe3, 0x1, 0xa0, 0xcd, 0x67, 0x1, 0x80, 0xa9, 0x1, 0x57, 0x1];
+_T Pe = [0x29, 0x1, 0x33, 0x1, 0x1f, 0x1, 0x8e, 0xbd, 0x1, 0x1, 0x1, 0x87, 0x5e, 0x1, 0x89, 0xa9, 0x1, 0x37, 0x1, 0xf, 0x1, 0x82, 0x7a, 0x1, 0x1, 0x1, 0x1e, 0x1, 0x84, 0x3e, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x1, 0x20, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x94, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x40, 0x1, 0x1, 0x1, 0x21, 0x1, 0x84, 0x25, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0xdf, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0xa0, 0xcd, 0x1f, 0x1, 0x80, 0xd8, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x11, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0xaa, 0x1, 0x33, 0x1, 0x1f, 0x1, 0x2, 0x1, 0x2, 0x1];
+_U[] _tab = [
+_U("Alphabetic", Alphabetic),
+_U("ASCII_Hex_Digit", ASCII_Hex_Digit),
+_U("Bidi_Control", Bidi_Control),
+_U("Cased", Cased),
+_U("Case_Ignorable", Case_Ignorable),
+_U("Cc", Cc),
+_U("Cf", Cf),
+_U("Close_Punctuation", Pe),
+_U("Cn", Cn),
+_U("Co", Co),
+_U("Connector_Punctuation", Pc),
+_U("Control", Cc),
+_U("Cs", Cs),
+_U("Currency_Symbol", Sc),
+_U("Dash", Dash),
+_U("Dash_Punctuation", Pd),
+_U("Decimal_Number", Nd),
+_U("Default_Ignorable_Code_Point", Default_Ignorable_Code_Point),
+_U("Deprecated", Deprecated),
+_U("Diacritic", Diacritic),
+_U("Enclosing_Mark", Me),
+_U("Extender", Extender),
+_U("Final_Punctuation", Pf),
+_U("Format", Cf),
+_U("Grapheme_Base", Grapheme_Base),
+_U("Grapheme_Extend", Grapheme_Extend),
+_U("Grapheme_Link", Grapheme_Link),
+_U("Hex_Digit", Hex_Digit),
+_U("Hyphen", Hyphen),
+_U("ID_Continue", ID_Continue),
+_U("Ideographic", Ideographic),
+_U("IDS_Binary_Operator", IDS_Binary_Operator),
+_U("ID_Start", ID_Start),
+_U("IDS_Trinary_Operator", IDS_Trinary_Operator),
+_U("Initial_Punctuation", Pi),
+_U("Join_Control", Join_Control),
+_U("Letter_Number", Nl),
+_U("Line_Separator", Zl),
+_U("Ll", Ll),
+_U("Lm", Lm),
+_U("Lo", Lo),
+_U("Logical_Order_Exception", Logical_Order_Exception),
+_U("Lowercase", Lowercase),
+_U("Lowercase_Letter", Ll),
+_U("Lt", Lt),
+_U("Lu", Lu),
+_U("Math", Math),
+_U("Math_Symbol", Sm),
+_U("Mc", Mc),
+_U("Me", Me),
+_U("Mn", Mn),
+_U("Modifier_Letter", Lm),
+_U("Modifier_Symbol", Sk),
+_U("Nd", Nd),
+_U("Nl", Nl),
+_U("No", No),
+_U("Noncharacter_Code_Point", Noncharacter_Code_Point),
+_U("Nonspacing_Mark", Mn),
+_U("Open_Punctuation", Ps),
+_U("Other_Alphabetic", Other_Alphabetic),
+_U("Other_Default_Ignorable_Code_Point", Other_Default_Ignorable_Code_Point),
+_U("Other_Grapheme_Extend", Other_Grapheme_Extend),
+_U("Other_ID_Continue", Other_ID_Continue),
+_U("Other_ID_Start", Other_ID_Start),
+_U("Other_Letter", Lo),
+_U("Other_Lowercase", Other_Lowercase),
+_U("Other_Math", Other_Math),
+_U("Other_Number", No),
+_U("Other_Punctuation", Po),
+_U("Other_Symbol", So),
+_U("Other_Uppercase", Other_Uppercase),
+_U("Paragraph_Separator", Zp),
+_U("Pattern_Syntax", Pattern_Syntax),
+_U("Pattern_White_Space", Pattern_White_Space),
+_U("Pc", Pc),
+_U("Pd", Pd),
+_U("Pe", Pe),
+_U("Pf", Pf),
+_U("Pi", Pi),
+_U("Po", Po),
+_U("Private_Use", Co),
+_U("Ps", Ps),
+_U("Quotation_Mark", Quotation_Mark),
+_U("Radical", Radical),
+_U("Sc", Sc),
+_U("Sk", Sk),
+_U("Sm", Sm),
+_U("So", So),
+_U("Soft_Dotted", Soft_Dotted),
+_U("Space_Separator", Zs),
+_U("Spacing_Mark", Mc),
+_U("STerm", STerm),
+_U("Surrogate", Cs),
+_U("Terminal_Punctuation", Terminal_Punctuation),
+_U("Titlecase_Letter", Lt),
+_U("Unassigned", Cn),
+_U("Unified_Ideograph", Unified_Ideograph),
+_U("Uppercase", Uppercase),
+_U("Uppercase_Letter", Lu),
+_U("Variation_Selector", Variation_Selector),
+_U("White_Space", White_Space),
+_U("XID_Continue", XID_Continue),
+_U("XID_Start", XID_Start),
+_U("Zl", Zl),
+_U("Zp", Zp),
+_U("Zs", Zs),
+];
+}
+
+struct blocks
+{
+private alias _U = immutable(UnicodeProperty);
+@property static _U[] tab() pure { return _tab; }
+static immutable:
+private alias _T = ubyte[];
+_T Number_Forms = [0xa0, 0x21, 0x50, 0x40];
+_T Sinhala = [0x8d, 0x80, 0x80, 0x80];
+_T Domino_Tiles = [0xa1, 0xf0, 0x30, 0x70];
+_T Oriya = [0x8b, 0x0, 0x80, 0x80];
+_T Thaana = [0x87, 0x80, 0x40];
+_T New_Tai_Lue = [0x99, 0x80, 0x60];
+_T Byzantine_Musical_Symbols = [0xa1, 0xd0, 0x0, 0x81, 0x0];
+_T Cham = [0xa0, 0xaa, 0x0, 0x60];
+_T IPA_Extensions = [0x82, 0x50, 0x60];
+_T Bopomofo = [0xa0, 0x31, 0x0, 0x30];
+_T Katakana_Phonetic_Extensions = [0xa0, 0x31, 0xf0, 0x10];
+_T Khmer_Symbols = [0x99, 0xe0, 0x20];
+_T Hebrew = [0x85, 0x90, 0x70];
+_T Saurashtra = [0xa0, 0xa8, 0x80, 0x60];
+_T Inscriptional_Parthian = [0xa1, 0xb, 0x40, 0x20];
+_T Lisu = [0xa0, 0xa4, 0xd0, 0x30];
+_T Latin_1_Supplement = [0x80, 0x80, 0x80, 0x80];
+_T Arabic_Extended_A = [0x88, 0xa0, 0x60];
+_T Tai_Tham = [0x9a, 0x20, 0x80, 0x90];
+_T Latin_Extended_A = [0x81, 0x0, 0x80, 0x80];
+_T Latin_Extended_B = [0x81, 0x80, 0x80, 0xd0];
+_T Latin_Extended_C = [0xa0, 0x2c, 0x60, 0x20];
+_T Latin_Extended_D = [0xa0, 0xa7, 0x20, 0x80, 0xe0];
+_T CJK_Radicals_Supplement = [0xa0, 0x2e, 0x80, 0x80, 0x80];
+_T Meroitic_Hieroglyphs = [0xa1, 0x9, 0x80, 0x20];
+_T Linear_B_Syllabary = [0xa1, 0x0, 0x0, 0x80, 0x80];
+_T Phonetic_Extensions_Supplement = [0x9d, 0x80, 0x40];
+_T Meroitic_Cursive = [0xa1, 0x9, 0xa0, 0x60];
+_T Enclosed_Ideographic_Supplement = [0xa1, 0xf2, 0x0, 0x81, 0x0];
+_T Halfwidth_and_Fullwidth_Forms = [0xa0, 0xff, 0x0, 0x80, 0xf0];
+_T Takri = [0xa1, 0x16, 0x80, 0x50];
+_T Supplemental_Punctuation = [0xa0, 0x2e, 0x0, 0x80, 0x80];
+_T Malayalam = [0x8d, 0x0, 0x80, 0x80];
+_T Lepcha = [0x9c, 0x0, 0x50];
+_T Miscellaneous_Symbols_And_Pictographs = [0xa1, 0xf3, 0x0, 0x83, 0x0];
+_T Arabic_Presentation_Forms_A = [0xa0, 0xfb, 0x50, 0x82, 0xb0];
+_T Sora_Sompeng = [0xa1, 0x10, 0xd0, 0x30];
+_T Lydian = [0xa1, 0x9, 0x20, 0x20];
+_T Hangul_Jamo_Extended_B = [0xa0, 0xd7, 0xb0, 0x50];
+_T Private_Use_Area = [0xa0, 0xe0, 0x0, 0x99, 0x0];
+_T Coptic = [0xa0, 0x2c, 0x80, 0x80, 0x80];
+_T Phaistos_Disc = [0xa1, 0x1, 0xd0, 0x30];
+_T Batak = [0x9b, 0xc0, 0x40];
+_T Khmer = [0x97, 0x80, 0x80, 0x80];
+_T Counting_Rod_Numerals = [0xa1, 0xd3, 0x60, 0x20];
+_T Old_South_Arabian = [0xa1, 0xa, 0x60, 0x20];
+_T Kannada = [0x8c, 0x80, 0x80, 0x80];
+_T Arrows = [0xa0, 0x21, 0x90, 0x70];
+_T CJK_Compatibility_Ideographs_Supplement = [0xa2, 0xf8, 0x0, 0x82, 0x20];
+_T Combining_Half_Marks = [0xa0, 0xfe, 0x20, 0x10];
+_T Miscellaneous_Technical = [0xa0, 0x23, 0x0, 0x81, 0x0];
+_T Thai = [0x8e, 0x0, 0x80, 0x80];
+_T Alphabetic_Presentation_Forms = [0xa0, 0xfb, 0x0, 0x50];
+_T CJK_Unified_Ideographs = [0xa0, 0x4e, 0x0, 0xa0, 0x52, 0x0];
+_T Phonetic_Extensions = [0x9d, 0x0, 0x80, 0x80];
+_T Kayah_Li = [0xa0, 0xa9, 0x0, 0x30];
+_T Supplementary_Private_Use_Area_B = [0xb0, 0x0, 0x0];
+_T Gujarati = [0x8a, 0x80, 0x80, 0x80];
+_T Unified_Canadian_Aboriginal_Syllabics_Extended = [0x98, 0xb0, 0x50];
+_T Hangul_Syllables = [0xa0, 0xac, 0x0, 0xa0, 0x2b, 0xb0];
+_T Vertical_Forms = [0xa0, 0xfe, 0x10, 0x10];
+_T Inscriptional_Pahlavi = [0xa1, 0xb, 0x60, 0x20];
+_T Control_Pictures = [0xa0, 0x24, 0x0, 0x40];
+_T Carian = [0xa1, 0x2, 0xa0, 0x40];
+_T Mahjong_Tiles = [0xa1, 0xf0, 0x0, 0x30];
+_T Geometric_Shapes = [0xa0, 0x25, 0xa0, 0x60];
+_T Cherokee = [0x93, 0xa0, 0x60];
+_T Imperial_Aramaic = [0xa1, 0x8, 0x40, 0x20];
+_T Rumi_Numeral_Symbols = [0xa1, 0xe, 0x60, 0x20];
+_T Combining_Diacritical_Marks = [0x83, 0x0, 0x70];
+_T Specials = [0xa0, 0xff, 0xf0, 0x10];
+_T Greek_Extended = [0x9f, 0x0, 0x81, 0x0];
+_T Ethiopic_Supplement = [0x93, 0x80, 0x20];
+_T Limbu = [0x99, 0x0, 0x50];
+_T Basic_Latin = [0x0, 0x80, 0x80];
+_T Enclosed_Alphanumeric_Supplement = [0xa1, 0xf1, 0x0, 0x81, 0x0];
+_T Cyrillic_Supplement = [0x85, 0x0, 0x30];
+_T Hangul_Compatibility_Jamo = [0xa0, 0x31, 0x30, 0x60];
+_T Supplemental_Arrows_A = [0xa0, 0x27, 0xf0, 0x10];
+_T Supplemental_Arrows_B = [0xa0, 0x29, 0x0, 0x80, 0x80];
+_T Katakana = [0xa0, 0x30, 0xa0, 0x60];
+_T Ancient_Greek_Musical_Notation = [0xa1, 0xd2, 0x0, 0x50];
+_T CJK_Compatibility = [0xa0, 0x33, 0x0, 0x81, 0x0];
+_T Old_Persian = [0xa1, 0x3, 0xa0, 0x40];
+_T Small_Form_Variants = [0xa0, 0xfe, 0x50, 0x20];
+_T General_Punctuation = [0xa0, 0x20, 0x0, 0x70];
+_T Miscellaneous_Mathematical_Symbols_A = [0xa0, 0x27, 0xc0, 0x30];
+_T Latin_Extended_Additional = [0x9e, 0x0, 0x81, 0x0];
+_T Playing_Cards = [0xa1, 0xf0, 0xa0, 0x60];
+_T Syriac = [0x87, 0x0, 0x50];
+_T Alchemical_Symbols = [0xa1, 0xf7, 0x0, 0x80, 0x80];
+_T Tibetan = [0x8f, 0x0, 0x81, 0x0];
+_T CJK_Strokes = [0xa0, 0x31, 0xc0, 0x30];
+_T Tamil = [0x8b, 0x80, 0x80, 0x80];
+_T Balinese = [0x9b, 0x0, 0x80, 0x80];
+_T Shavian = [0xa1, 0x4, 0x50, 0x30];
+_T Greek_and_Coptic = [0x83, 0x70, 0x80, 0x90];
+_T Telugu = [0x8c, 0x0, 0x80, 0x80];
+_T Runic = [0x96, 0xa0, 0x60];
+_T Javanese = [0xa0, 0xa9, 0x80, 0x60];
+_T Bopomofo_Extended = [0xa0, 0x31, 0xa0, 0x20];
+_T Ideographic_Description_Characters = [0xa0, 0x2f, 0xf0, 0x10];
+_T Old_Turkic = [0xa1, 0xc, 0x0, 0x50];
+_T Unified_Canadian_Aboriginal_Syllabics = [0x94, 0x0, 0x82, 0x80];
+_T Ugaritic = [0xa1, 0x3, 0x80, 0x20];
+_T Egyptian_Hieroglyphs = [0xa1, 0x30, 0x0, 0x84, 0x30];
+_T Buginese = [0x9a, 0x0, 0x20];
+_T Kangxi_Radicals = [0xa0, 0x2f, 0x0, 0x80, 0xe0];
+_T Cuneiform = [0xa1, 0x20, 0x0, 0x84, 0x0];
+_T NKo = [0x87, 0xc0, 0x40];
+_T Sundanese_Supplement = [0x9c, 0xc0, 0x10];
+_T Buhid = [0x97, 0x40, 0x20];
+_T Modifier_Tone_Letters = [0xa0, 0xa7, 0x0, 0x20];
+_T Kanbun = [0xa0, 0x31, 0x90, 0x10];
+_T Superscripts_and_Subscripts = [0xa0, 0x20, 0x70, 0x30];
+_T Lao = [0x8e, 0x80, 0x80, 0x80];
+_T Ol_Chiki = [0x9c, 0x50, 0x30];
+_T Common_Indic_Number_Forms = [0xa0, 0xa8, 0x30, 0x10];
+_T Hangul_Jamo_Extended_A = [0xa0, 0xa9, 0x60, 0x20];
+_T Arabic_Presentation_Forms_B = [0xa0, 0xfe, 0x70, 0x80, 0x90];
+_T Sharada = [0xa1, 0x11, 0x80, 0x60];
+_T Miscellaneous_Symbols = [0xa0, 0x26, 0x0, 0x81, 0x0];
+_T Variation_Selectors_Supplement = [0xae, 0x1, 0x0, 0x80, 0xf0];
+_T Rejang = [0xa0, 0xa9, 0x30, 0x30];
+_T Georgian_Supplement = [0xa0, 0x2d, 0x0, 0x30];
+_T Braille_Patterns = [0xa0, 0x28, 0x0, 0x81, 0x0];
+_T Lycian = [0xa1, 0x2, 0x80, 0x20];
+_T Tai_Le = [0x99, 0x50, 0x30];
+_T Miscellaneous_Mathematical_Symbols_B = [0xa0, 0x29, 0x80, 0x80, 0x80];
+_T Musical_Symbols = [0xa1, 0xd1, 0x0, 0x81, 0x0];
+_T Avestan = [0xa1, 0xb, 0x0, 0x40];
+_T Ethiopic = [0x92, 0x0, 0x81, 0x80];
+_T Arabic_Supplement = [0x87, 0x50, 0x30];
+_T Samaritan = [0x88, 0x0, 0x40];
+_T Cuneiform_Numbers_and_Punctuation = [0xa1, 0x24, 0x0, 0x80, 0x80];
+_T Mongolian = [0x98, 0x0, 0x80, 0xb0];
+_T Arabic = [0x86, 0x0, 0x81, 0x0];
+_T Vai = [0xa0, 0xa5, 0x0, 0x81, 0x40];
+_T Tifinagh = [0xa0, 0x2d, 0x30, 0x50];
+_T Bamum_Supplement = [0xa1, 0x68, 0x0, 0x82, 0x40];
+_T Tai_Viet = [0xa0, 0xaa, 0x80, 0x60];
+_T Mandaic = [0x88, 0x40, 0x20];
+_T Sundanese = [0x9b, 0x80, 0x40];
+_T Block_Elements = [0xa0, 0x25, 0x80, 0x20];
+_T Phoenician = [0xa1, 0x9, 0x0, 0x20];
+_T Hanunoo = [0x97, 0x20, 0x20];
+_T Supplemental_Mathematical_Operators = [0xa0, 0x2a, 0x0, 0x81, 0x0];
+_T Deseret = [0xa1, 0x4, 0x0, 0x50];
+_T Brahmi = [0xa1, 0x10, 0x0, 0x80, 0x80];
+_T Devanagari_Extended = [0xa0, 0xa8, 0xe0, 0x20];
+_T Supplementary_Private_Use_Area_A = [0xaf, 0x0, 0x0, 0xa1, 0x0, 0x0];
+_T Box_Drawing = [0xa0, 0x25, 0x0, 0x80, 0x80];
+_T Mathematical_Operators = [0xa0, 0x22, 0x0, 0x81, 0x0];
+_T Ogham = [0x96, 0x80, 0x20];
+_T Meetei_Mayek_Extensions = [0xa0, 0xaa, 0xe0, 0x20];
+_T Hangul_Jamo = [0x91, 0x0, 0x81, 0x0];
+_T Miao = [0xa1, 0x6f, 0x0, 0x80, 0xa0];
+_T Emoticons = [0xa1, 0xf6, 0x0, 0x50];
+_T Tags = [0xae, 0x0, 0x0, 0x80, 0x80];
+_T Yi_Syllables = [0xa0, 0xa0, 0x0, 0x84, 0x90];
+_T Gurmukhi = [0x8a, 0x0, 0x80, 0x80];
+_T Syloti_Nagri = [0xa0, 0xa8, 0x0, 0x30];
+_T Spacing_Modifier_Letters = [0x82, 0xb0, 0x50];
+_T Yi_Radicals = [0xa0, 0xa4, 0x90, 0x40];
+_T Ancient_Greek_Numbers = [0xa1, 0x1, 0x40, 0x50];
+_T Glagolitic = [0xa0, 0x2c, 0x0, 0x60];
+_T Georgian = [0x90, 0xa0, 0x60];
+_T Osmanya = [0xa1, 0x4, 0x80, 0x30];
+_T Variation_Selectors = [0xa0, 0xfe, 0x0, 0x10];
+_T Mathematical_Alphanumeric_Symbols = [0xa1, 0xd4, 0x0, 0x84, 0x0];
+_T Yijing_Hexagram_Symbols = [0xa0, 0x4d, 0xc0, 0x40];
+_T Ethiopic_Extended = [0xa0, 0x2d, 0x80, 0x60];
+_T Transport_And_Map_Symbols = [0xa1, 0xf6, 0x80, 0x80, 0x80];
+_T High_Private_Use_Surrogates = [0xa0, 0xdb, 0x80, 0x80, 0x80];
+_T Meetei_Mayek = [0xa0, 0xab, 0xc0, 0x40];
+_T CJK_Compatibility_Forms = [0xa0, 0xfe, 0x30, 0x20];
+_T Enclosed_Alphanumerics = [0xa0, 0x24, 0x60, 0x80, 0xa0];
+_T Ancient_Symbols = [0xa1, 0x1, 0x90, 0x40];
+_T Ethiopic_Extended_A = [0xa0, 0xab, 0x0, 0x30];
+_T Bengali = [0x89, 0x80, 0x80, 0x80];
+_T Currency_Symbols = [0xa0, 0x20, 0xa0, 0x30];
+_T Myanmar = [0x90, 0x0, 0x80, 0xa0];
+_T Cyrillic_Extended_A = [0xa0, 0x2d, 0xe0, 0x20];
+_T Cyrillic_Extended_B = [0xa0, 0xa6, 0x40, 0x60];
+_T Myanmar_Extended_A = [0xa0, 0xaa, 0x60, 0x20];
+_T Hiragana = [0xa0, 0x30, 0x40, 0x60];
+_T Dingbats = [0xa0, 0x27, 0x0, 0x80, 0xc0];
+_T Armenian = [0x85, 0x30, 0x60];
+_T Tai_Xuan_Jing_Symbols = [0xa1, 0xd3, 0x0, 0x60];
+_T Linear_B_Ideograms = [0xa1, 0x0, 0x80, 0x80, 0x80];
+_T Kharoshthi = [0xa1, 0xa, 0x0, 0x60];
+_T Optical_Character_Recognition = [0xa0, 0x24, 0x40, 0x20];
+_T Enclosed_CJK_Letters_and_Months = [0xa0, 0x32, 0x0, 0x81, 0x0];
+_T Cypriot_Syllabary = [0xa1, 0x8, 0x0, 0x40];
+_T Vedic_Extensions = [0x9c, 0xd0, 0x30];
+_T Kaithi = [0xa1, 0x10, 0x80, 0x50];
+_T Low_Surrogates = [0xa0, 0xdc, 0x0, 0x84, 0x0];
+_T Letterlike_Symbols = [0xa0, 0x21, 0x0, 0x50];
+_T Combining_Diacritical_Marks_for_Symbols = [0xa0, 0x20, 0xd0, 0x30];
+_T Aegean_Numbers = [0xa1, 0x1, 0x0, 0x40];
+_T High_Surrogates = [0xa0, 0xd8, 0x0, 0x83, 0x80];
+_T CJK_Compatibility_Ideographs = [0xa0, 0xf9, 0x0, 0x82, 0x0];
+_T CJK_Symbols_and_Punctuation = [0xa0, 0x30, 0x0, 0x40];
+_T Gothic = [0xa1, 0x3, 0x30, 0x20];
+_T Combining_Diacritical_Marks_Supplement = [0x9d, 0xc0, 0x40];
+_T Phags_pa = [0xa0, 0xa8, 0x40, 0x40];
+_T Miscellaneous_Symbols_and_Arrows = [0xa0, 0x2b, 0x0, 0x81, 0x0];
+_T Bamum = [0xa0, 0xa6, 0xa0, 0x60];
+_T Chakma = [0xa1, 0x11, 0x0, 0x50];
+_T Kana_Supplement = [0xa1, 0xb0, 0x0, 0x81, 0x0];
+_T Tagalog = [0x97, 0x0, 0x20];
+_T Tagbanwa = [0x97, 0x60, 0x20];
+_T Devanagari = [0x89, 0x0, 0x80, 0x80];
+_T Old_Italic = [0xa1, 0x3, 0x0, 0x30];
+_T Arabic_Mathematical_Alphabetic_Symbols = [0xa1, 0xee, 0x0, 0x81, 0x0];
+_T CJK_Unified_Ideographs_Extension_D = [0xa2, 0xb7, 0x40, 0x80, 0xe0];
+_T CJK_Unified_Ideographs_Extension_A = [0xa0, 0x34, 0x0, 0x99, 0xc0];
+_T CJK_Unified_Ideographs_Extension_B = [0xa2, 0x0, 0x0, 0xa0, 0xa6, 0xe0];
+_T CJK_Unified_Ideographs_Extension_C = [0xa2, 0xa7, 0x0, 0x90, 0x40];
+_T Cyrillic = [0x84, 0x0, 0x81, 0x0];
+_U[] _tab = [
+_U("Aegean Numbers", Aegean_Numbers),
+_U("Alchemical Symbols", Alchemical_Symbols),
+_U("Alphabetic Presentation Forms", Alphabetic_Presentation_Forms),
+_U("Ancient Greek Musical Notation", Ancient_Greek_Musical_Notation),
+_U("Ancient Greek Numbers", Ancient_Greek_Numbers),
+_U("Ancient Symbols", Ancient_Symbols),
+_U("Arabic", Arabic),
+_U("Arabic Extended-A", Arabic_Extended_A),
+_U("Arabic Mathematical Alphabetic Symbols", Arabic_Mathematical_Alphabetic_Symbols),
+_U("Arabic Presentation Forms-A", Arabic_Presentation_Forms_A),
+_U("Arabic Presentation Forms-B", Arabic_Presentation_Forms_B),
+_U("Arabic Supplement", Arabic_Supplement),
+_U("Armenian", Armenian),
+_U("Arrows", Arrows),
+_U("Avestan", Avestan),
+_U("Balinese", Balinese),
+_U("Bamum", Bamum),
+_U("Bamum Supplement", Bamum_Supplement),
+_U("Basic Latin", Basic_Latin),
+_U("Batak", Batak),
+_U("Bengali", Bengali),
+_U("Block Elements", Block_Elements),
+_U("Bopomofo", Bopomofo),
+_U("Bopomofo Extended", Bopomofo_Extended),
+_U("Box Drawing", Box_Drawing),
+_U("Brahmi", Brahmi),
+_U("Braille Patterns", Braille_Patterns),
+_U("Buginese", Buginese),
+_U("Buhid", Buhid),
+_U("Byzantine Musical Symbols", Byzantine_Musical_Symbols),
+_U("Carian", Carian),
+_U("Chakma", Chakma),
+_U("Cham", Cham),
+_U("Cherokee", Cherokee),
+_U("CJK Compatibility", CJK_Compatibility),
+_U("CJK Compatibility Forms", CJK_Compatibility_Forms),
+_U("CJK Compatibility Ideographs", CJK_Compatibility_Ideographs),
+_U("CJK Compatibility Ideographs Supplement", CJK_Compatibility_Ideographs_Supplement),
+_U("CJK Radicals Supplement", CJK_Radicals_Supplement),
+_U("CJK Strokes", CJK_Strokes),
+_U("CJK Symbols and Punctuation", CJK_Symbols_and_Punctuation),
+_U("CJK Unified Ideographs", CJK_Unified_Ideographs),
+_U("CJK Unified Ideographs Extension A", CJK_Unified_Ideographs_Extension_A),
+_U("CJK Unified Ideographs Extension B", CJK_Unified_Ideographs_Extension_B),
+_U("CJK Unified Ideographs Extension C", CJK_Unified_Ideographs_Extension_C),
+_U("CJK Unified Ideographs Extension D", CJK_Unified_Ideographs_Extension_D),
+_U("Combining Diacritical Marks", Combining_Diacritical_Marks),
+_U("Combining Diacritical Marks for Symbols", Combining_Diacritical_Marks_for_Symbols),
+_U("Combining Diacritical Marks Supplement", Combining_Diacritical_Marks_Supplement),
+_U("Combining Half Marks", Combining_Half_Marks),
+_U("Common Indic Number Forms", Common_Indic_Number_Forms),
+_U("Control Pictures", Control_Pictures),
+_U("Coptic", Coptic),
+_U("Counting Rod Numerals", Counting_Rod_Numerals),
+_U("Cuneiform", Cuneiform),
+_U("Cuneiform Numbers and Punctuation", Cuneiform_Numbers_and_Punctuation),
+_U("Currency Symbols", Currency_Symbols),
+_U("Cypriot Syllabary", Cypriot_Syllabary),
+_U("Cyrillic", Cyrillic),
+_U("Cyrillic Extended-A", Cyrillic_Extended_A),
+_U("Cyrillic Extended-B", Cyrillic_Extended_B),
+_U("Cyrillic Supplement", Cyrillic_Supplement),
+_U("Deseret", Deseret),
+_U("Devanagari", Devanagari),
+_U("Devanagari Extended", Devanagari_Extended),
+_U("Dingbats", Dingbats),
+_U("Domino Tiles", Domino_Tiles),
+_U("Egyptian Hieroglyphs", Egyptian_Hieroglyphs),
+_U("Emoticons", Emoticons),
+_U("Enclosed Alphanumerics", Enclosed_Alphanumerics),
+_U("Enclosed Alphanumeric Supplement", Enclosed_Alphanumeric_Supplement),
+_U("Enclosed CJK Letters and Months", Enclosed_CJK_Letters_and_Months),
+_U("Enclosed Ideographic Supplement", Enclosed_Ideographic_Supplement),
+_U("Ethiopic", Ethiopic),
+_U("Ethiopic Extended", Ethiopic_Extended),
+_U("Ethiopic Extended-A", Ethiopic_Extended_A),
+_U("Ethiopic Supplement", Ethiopic_Supplement),
+_U("General Punctuation", General_Punctuation),
+_U("Geometric Shapes", Geometric_Shapes),
+_U("Georgian", Georgian),
+_U("Georgian Supplement", Georgian_Supplement),
+_U("Glagolitic", Glagolitic),
+_U("Gothic", Gothic),
+_U("Greek and Coptic", Greek_and_Coptic),
+_U("Greek Extended", Greek_Extended),
+_U("Gujarati", Gujarati),
+_U("Gurmukhi", Gurmukhi),
+_U("Halfwidth and Fullwidth Forms", Halfwidth_and_Fullwidth_Forms),
+_U("Hangul Compatibility Jamo", Hangul_Compatibility_Jamo),
+_U("Hangul Jamo", Hangul_Jamo),
+_U("Hangul Jamo Extended-A", Hangul_Jamo_Extended_A),
+_U("Hangul Jamo Extended-B", Hangul_Jamo_Extended_B),
+_U("Hangul Syllables", Hangul_Syllables),
+_U("Hanunoo", Hanunoo),
+_U("Hebrew", Hebrew),
+_U("High Private Use Surrogates", High_Private_Use_Surrogates),
+_U("High Surrogates", High_Surrogates),
+_U("Hiragana", Hiragana),
+_U("Ideographic Description Characters", Ideographic_Description_Characters),
+_U("Imperial Aramaic", Imperial_Aramaic),
+_U("Inscriptional Pahlavi", Inscriptional_Pahlavi),
+_U("Inscriptional Parthian", Inscriptional_Parthian),
+_U("IPA Extensions", IPA_Extensions),
+_U("Javanese", Javanese),
+_U("Kaithi", Kaithi),
+_U("Kana Supplement", Kana_Supplement),
+_U("Kanbun", Kanbun),
+_U("Kangxi Radicals", Kangxi_Radicals),
+_U("Kannada", Kannada),
+_U("Katakana", Katakana),
+_U("Katakana Phonetic Extensions", Katakana_Phonetic_Extensions),
+_U("Kayah Li", Kayah_Li),
+_U("Kharoshthi", Kharoshthi),
+_U("Khmer", Khmer),
+_U("Khmer Symbols", Khmer_Symbols),
+_U("Lao", Lao),
+_U("Latin-1 Supplement", Latin_1_Supplement),
+_U("Latin Extended-A", Latin_Extended_A),
+_U("Latin Extended Additional", Latin_Extended_Additional),
+_U("Latin Extended-B", Latin_Extended_B),
+_U("Latin Extended-C", Latin_Extended_C),
+_U("Latin Extended-D", Latin_Extended_D),
+_U("Lepcha", Lepcha),
+_U("Letterlike Symbols", Letterlike_Symbols),
+_U("Limbu", Limbu),
+_U("Linear B Ideograms", Linear_B_Ideograms),
+_U("Linear B Syllabary", Linear_B_Syllabary),
+_U("Lisu", Lisu),
+_U("Low Surrogates", Low_Surrogates),
+_U("Lycian", Lycian),
+_U("Lydian", Lydian),
+_U("Mahjong Tiles", Mahjong_Tiles),
+_U("Malayalam", Malayalam),
+_U("Mandaic", Mandaic),
+_U("Mathematical Alphanumeric Symbols", Mathematical_Alphanumeric_Symbols),
+_U("Mathematical Operators", Mathematical_Operators),
+_U("Meetei Mayek", Meetei_Mayek),
+_U("Meetei Mayek Extensions", Meetei_Mayek_Extensions),
+_U("Meroitic Cursive", Meroitic_Cursive),
+_U("Meroitic Hieroglyphs", Meroitic_Hieroglyphs),
+_U("Miao", Miao),
+_U("Miscellaneous Mathematical Symbols-A", Miscellaneous_Mathematical_Symbols_A),
+_U("Miscellaneous Mathematical Symbols-B", Miscellaneous_Mathematical_Symbols_B),
+_U("Miscellaneous Symbols", Miscellaneous_Symbols),
+_U("Miscellaneous Symbols and Arrows", Miscellaneous_Symbols_and_Arrows),
+_U("Miscellaneous Symbols And Pictographs", Miscellaneous_Symbols_And_Pictographs),
+_U("Miscellaneous Technical", Miscellaneous_Technical),
+_U("Modifier Tone Letters", Modifier_Tone_Letters),
+_U("Mongolian", Mongolian),
+_U("Musical Symbols", Musical_Symbols),
+_U("Myanmar", Myanmar),
+_U("Myanmar Extended-A", Myanmar_Extended_A),
+_U("New Tai Lue", New_Tai_Lue),
+_U("NKo", NKo),
+_U("Number Forms", Number_Forms),
+_U("Ogham", Ogham),
+_U("Ol Chiki", Ol_Chiki),
+_U("Old Italic", Old_Italic),
+_U("Old Persian", Old_Persian),
+_U("Old South Arabian", Old_South_Arabian),
+_U("Old Turkic", Old_Turkic),
+_U("Optical Character Recognition", Optical_Character_Recognition),
+_U("Oriya", Oriya),
+_U("Osmanya", Osmanya),
+_U("Phags-pa", Phags_pa),
+_U("Phaistos Disc", Phaistos_Disc),
+_U("Phoenician", Phoenician),
+_U("Phonetic Extensions", Phonetic_Extensions),
+_U("Phonetic Extensions Supplement", Phonetic_Extensions_Supplement),
+_U("Playing Cards", Playing_Cards),
+_U("Private Use Area", Private_Use_Area),
+_U("Rejang", Rejang),
+_U("Rumi Numeral Symbols", Rumi_Numeral_Symbols),
+_U("Runic", Runic),
+_U("Samaritan", Samaritan),
+_U("Saurashtra", Saurashtra),
+_U("Sharada", Sharada),
+_U("Shavian", Shavian),
+_U("Sinhala", Sinhala),
+_U("Small Form Variants", Small_Form_Variants),
+_U("Sora Sompeng", Sora_Sompeng),
+_U("Spacing Modifier Letters", Spacing_Modifier_Letters),
+_U("Specials", Specials),
+_U("Sundanese", Sundanese),
+_U("Sundanese Supplement", Sundanese_Supplement),
+_U("Superscripts and Subscripts", Superscripts_and_Subscripts),
+_U("Supplemental Arrows-A", Supplemental_Arrows_A),
+_U("Supplemental Arrows-B", Supplemental_Arrows_B),
+_U("Supplemental Mathematical Operators", Supplemental_Mathematical_Operators),
+_U("Supplemental Punctuation", Supplemental_Punctuation),
+_U("Supplementary Private Use Area-A", Supplementary_Private_Use_Area_A),
+_U("Supplementary Private Use Area-B", Supplementary_Private_Use_Area_B),
+_U("Syloti Nagri", Syloti_Nagri),
+_U("Syriac", Syriac),
+_U("Tagalog", Tagalog),
+_U("Tagbanwa", Tagbanwa),
+_U("Tags", Tags),
+_U("Tai Le", Tai_Le),
+_U("Tai Tham", Tai_Tham),
+_U("Tai Viet", Tai_Viet),
+_U("Tai Xuan Jing Symbols", Tai_Xuan_Jing_Symbols),
+_U("Takri", Takri),
+_U("Tamil", Tamil),
+_U("Telugu", Telugu),
+_U("Thaana", Thaana),
+_U("Thai", Thai),
+_U("Tibetan", Tibetan),
+_U("Tifinagh", Tifinagh),
+_U("Transport And Map Symbols", Transport_And_Map_Symbols),
+_U("Ugaritic", Ugaritic),
+_U("Unified Canadian Aboriginal Syllabics", Unified_Canadian_Aboriginal_Syllabics),
+_U("Unified Canadian Aboriginal Syllabics Extended", Unified_Canadian_Aboriginal_Syllabics_Extended),
+_U("Vai", Vai),
+_U("Variation Selectors", Variation_Selectors),
+_U("Variation Selectors Supplement", Variation_Selectors_Supplement),
+_U("Vedic Extensions", Vedic_Extensions),
+_U("Vertical Forms", Vertical_Forms),
+_U("Yijing Hexagram Symbols", Yijing_Hexagram_Symbols),
+_U("Yi Radicals", Yi_Radicals),
+_U("Yi Syllables", Yi_Syllables),
+];
+}
+
+struct scripts
+{
+private alias _U = immutable(UnicodeProperty);
+@property static _U[] tab() pure nothrow @nogc { return _tab; }
+static immutable:
+private alias _T = ubyte[];
+_T Buhid = [0x97, 0x40, 0x14];
+_T Sinhala = [0x8d, 0x82, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x3];
+_T Phags_Pa = [0xa0, 0xa8, 0x40, 0x38];
+_T Old_Turkic = [0xa1, 0xc, 0x0, 0x49];
+_T Oriya = [0x8b, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, 0x2, 0x12];
+_T Thaana = [0x87, 0x80, 0x32];
+_T Inherited = [0x83, 0x0, 0x70, 0x81, 0x15, 0x2, 0x81, 0xc4, 0xb, 0x1a, 0x1, 0x82, 0xe0, 0x2, 0x93, 0x7d, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, 0x27, 0x15, 0x4, 0x82, 0xc, 0x2, 0x80, 0xc2, 0x21, 0x8f, 0x39, 0x4, 0x6b, 0x2, 0xa0, 0xcd, 0x65, 0x10, 0x10, 0x7, 0x83, 0xd6, 0x1, 0xa0, 0xcf, 0x69, 0x3, 0x11, 0x8, 0x2, 0x7, 0x1e, 0x4, 0xac, 0x2f, 0x52, 0x80, 0xf0];
+_T Sharada = [0xa1, 0x11, 0x80, 0x49, 0x7, 0xa];
+_T Rejang = [0xa0, 0xa9, 0x30, 0x24, 0xb, 0x1];
+_T Imperial_Aramaic = [0xa1, 0x8, 0x40, 0x16, 0x1, 0x9];
+_T Cham = [0xa0, 0xaa, 0x0, 0x37, 0x9, 0xe, 0x2, 0xa, 0x2, 0x4];
+_T Kaithi = [0xa1, 0x10, 0x80, 0x42];
+_T Bopomofo = [0x82, 0xea, 0x2, 0xa0, 0x2e, 0x19, 0x29, 0x72, 0x1b];
+_T Deseret = [0xa1, 0x4, 0x0, 0x50];
+_T Syloti_Nagri = [0xa0, 0xa8, 0x0, 0x2c];
+_T Lycian = [0xa1, 0x2, 0x80, 0x1d];
+_T Linear_B = [0xa1, 0x0, 0x0, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b];
+_T Hebrew = [0x85, 0x91, 0x37, 0x8, 0x1b, 0x5, 0x5, 0xa0, 0xf5, 0x28, 0x1a, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0xa];
+_T Saurashtra = [0xa0, 0xa8, 0x80, 0x45, 0x9, 0xc];
+_T Avestan = [0xa1, 0xb, 0x0, 0x36, 0x3, 0x7];
+_T Ethiopic = [0x92, 0x0, 0x49, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x2, 0x20, 0x3, 0x1a, 0x99, 0xe6, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0xa0, 0x7d, 0x22, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7];
+_T Braille = [0xa0, 0x28, 0x0, 0x81, 0x0];
+_T Lisu = [0xa0, 0xa4, 0xd0, 0x30];
+_T Samaritan = [0x88, 0x0, 0x2e, 0x2, 0xf];
+_T Mongolian = [0x98, 0x0, 0x2, 0x2, 0x1, 0x1, 0x9, 0x1, 0xa, 0x6, 0x58, 0x8, 0x2b];
+_T Hangul = [0x91, 0x0, 0x81, 0x0, 0x9e, 0x2e, 0x2, 0x81, 0x1, 0x5e, 0x71, 0x1f, 0x41, 0x1f, 0xa0, 0x76, 0xe1, 0x1d, 0x82, 0x83, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x27, 0xa4, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3];
+_T Takri = [0xa1, 0x16, 0x80, 0x38, 0x8, 0xa];
+_T Phoenician = [0xa1, 0x9, 0x0, 0x1c, 0x3, 0x1];
+_T Vai = [0xa0, 0xa5, 0x0, 0x81, 0x2c];
+_T Batak = [0x9b, 0xc0, 0x34, 0x8, 0x4];
+_T Yi = [0xa0, 0xa0, 0x0, 0x84, 0x8d, 0x3, 0x37];
+_T Tifinagh = [0xa0, 0x2d, 0x30, 0x38, 0x7, 0x2, 0xe, 0x1];
+_T Glagolitic = [0xa0, 0x2c, 0x0, 0x2f, 0x1, 0x2f];
+_T Tai_Tham = [0x9a, 0x20, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, 0x6, 0xe];
+_T Canadian_Aboriginal = [0x94, 0x0, 0x82, 0x80, 0x82, 0x30, 0x46];
+_T Meetei_Mayek = [0xa0, 0xaa, 0xe0, 0x17, 0x80, 0xc9, 0x2e, 0x2, 0xa];
+_T Balinese = [0x9b, 0x0, 0x4c, 0x4, 0x2d];
+_T Kayah_Li = [0xa0, 0xa9, 0x0, 0x30];
+_T Kharoshthi = [0xa1, 0xa, 0x0, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x9, 0x8, 0x9];
+_T Lepcha = [0x9c, 0x0, 0x38, 0x3, 0xf, 0x3, 0x3];
+_T New_Tai_Lue = [0x99, 0x80, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x3, 0x2];
+_T Sora_Sompeng = [0xa1, 0x10, 0xd0, 0x19, 0x7, 0xa];
+_T Arabic = [0x86, 0x0, 0x5, 0x1, 0x6, 0x1, 0xe, 0x1, 0x1, 0x1, 0x1, 0x1, 0x20, 0x1, 0xa, 0xb, 0xa, 0xa, 0x6, 0x1, 0x6c, 0x1, 0x22, 0x50, 0x30, 0x81, 0x20, 0x1, 0x1, 0xb, 0x37, 0x1b, 0xa0, 0xf2, 0x51, 0x72, 0x11, 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xd, 0x73, 0x5, 0x1, 0x80, 0x87, 0x8f, 0x63, 0x1f, 0xa0, 0xdf, 0x81, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2];
+_T Hanunoo = [0x97, 0x20, 0x15];
+_T Lydian = [0xa1, 0x9, 0x20, 0x1a, 0x5, 0x1];
+_T Tai_Viet = [0xa0, 0xaa, 0x80, 0x43, 0x18, 0x5];
+_T Coptic = [0x83, 0xe2, 0xe, 0xa0, 0x28, 0x90, 0x74, 0x5, 0x7];
+_T Brahmi = [0xa1, 0x10, 0x0, 0x4e, 0x4, 0x1e];
+_T Runic = [0x96, 0xa0, 0x4b, 0x3, 0x3];
+_T Egyptian_Hieroglyphs = [0xa1, 0x30, 0x0, 0x84, 0x2f];
+_T Khmer = [0x97, 0x80, 0x5e, 0x2, 0xa, 0x6, 0xa, 0x81, 0xe6, 0x20];
+_T Ogham = [0x96, 0x80, 0x1d];
+_T Gothic = [0xa1, 0x3, 0x30, 0x1b];
+_T Katakana = [0xa0, 0x30, 0xa1, 0x5a, 0x2, 0x3, 0x80, 0xf0, 0x10, 0x80, 0xd0, 0x2f, 0x1, 0x58, 0xa0, 0xcc, 0xe, 0xa, 0x1, 0x2d, 0xa0, 0xb0, 0x62, 0x1];
+_T Miao = [0xa1, 0x6f, 0x0, 0x45, 0xb, 0x2f, 0x10, 0x11];
+_T Meroitic_Hieroglyphs = [0xa1, 0x9, 0x80, 0x20];
+_T Thai = [0x8e, 0x1, 0x3a, 0x5, 0x1c];
+_T Cypriot = [0xa1, 0x8, 0x0, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x1];
+_T Meroitic_Cursive = [0xa1, 0x9, 0xa0, 0x18, 0x6, 0x2];
+_T Gujarati = [0x8a, 0x81, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, 0x4, 0x2, 0xc];
+_T Lao = [0x8e, 0x81, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4];
+_T Georgian = [0x90, 0xa0, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x4, 0x9c, 0x0, 0x26, 0x1, 0x1, 0x5, 0x1];
+_T Osmanya = [0xa1, 0x4, 0x80, 0x1e, 0x2, 0xa];
+_T Inscriptional_Pahlavi = [0xa1, 0xb, 0x60, 0x13, 0x5, 0x8];
+_T Shavian = [0xa1, 0x4, 0x50, 0x30];
+_T Carian = [0xa1, 0x2, 0xa0, 0x31];
+_T Cherokee = [0x93, 0xa0, 0x55];
+_T Mandaic = [0x88, 0x40, 0x1c, 0x2, 0x1];
+_T Han = [0xa0, 0x2e, 0x80, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x2f, 0x1, 0x1, 0x1, 0x19, 0x9, 0xe, 0x4, 0x83, 0xc4, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0xa0, 0x59, 0x33, 0x81, 0x6e, 0x2, 0x6a, 0xa1, 0x5, 0x26, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e];
+_T Latin = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xf, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xc1, 0x27, 0x5, 0x9a, 0x1b, 0x26, 0x6, 0x31, 0x5, 0x4, 0x5, 0xd, 0x1, 0x46, 0x41, 0x81, 0x0, 0x81, 0x71, 0x1, 0xd, 0x1, 0x10, 0xd, 0x80, 0x8d, 0x2, 0x6, 0x1, 0x1b, 0x1, 0x11, 0x29, 0x8a, 0xd7, 0x20, 0xa0, 0x7a, 0xa2, 0x66, 0x3, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x8, 0xa0, 0x53, 0x0, 0x7, 0x84, 0x1a, 0x1a, 0x6, 0x1a];
+_T Limbu = [0x99, 0x0, 0x1d, 0x3, 0xc, 0x4, 0xc, 0x4, 0x1, 0x3, 0xc];
+_T Ol_Chiki = [0x9c, 0x50, 0x30];
+_T Bengali = [0x89, 0x81, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0x2, 0x16];
+_T Myanmar = [0x90, 0x0, 0x80, 0xa0, 0xa0, 0x99, 0xc0, 0x1c];
+_T Malayalam = [0x8d, 0x2, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0x10, 0x3, 0x7];
+_T Hiragana = [0xa0, 0x30, 0x41, 0x56, 0x6, 0x3, 0xa1, 0x7f, 0x61, 0x1, 0xa0, 0x41, 0xfe, 0x1];
+_T Kannada = [0x8c, 0x82, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, 0x2];
+_T Armenian = [0x85, 0x31, 0x26, 0x2, 0x7, 0x1, 0x27, 0x2, 0x1, 0x4, 0x1, 0xa0, 0xf5, 0x83, 0x5];
+_T Common = [0x0, 0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xf, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xc1, 0x27, 0x5, 0x5, 0x2, 0x14, 0x74, 0x1, 0x9, 0x1, 0x6, 0x1, 0x1, 0x1, 0x82, 0x1, 0x1, 0x80, 0x82, 0x1, 0xe, 0x1, 0x3, 0x1, 0x20, 0x1, 0x1f, 0xa, 0x73, 0x1, 0x82, 0x86, 0x2, 0x84, 0xd9, 0x1, 0x81, 0x95, 0x4, 0x81, 0x22, 0x1, 0x85, 0xef, 0x3, 0x47, 0x2, 0x80, 0xcb, 0x2, 0x1, 0x1, 0x84, 0xcd, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x2, 0x83, 0x9, 0xc, 0x2, 0x57, 0x1, 0xb, 0x3, 0xb, 0x1, 0xf, 0x11, 0x1b, 0x45, 0x26, 0x1, 0x3, 0x2, 0x6, 0x1, 0x1b, 0x1, 0x11, 0x29, 0x1, 0x6, 0x82, 0x64, 0xc, 0x27, 0x19, 0xb, 0x15, 0x82, 0xa0, 0x1, 0x80, 0xff, 0x81, 0x0, 0x82, 0x4d, 0x3, 0xa, 0x82, 0xa6, 0x3c, 0x81, 0xb4, 0xc, 0x4, 0x5, 0x1, 0x1, 0x1, 0x19, 0xf, 0x8, 0x4, 0x4, 0x5b, 0x2, 0x3, 0x1, 0x5a, 0x2, 0x80, 0x93, 0x10, 0x20, 0x24, 0x3c, 0x40, 0x1f, 0x51, 0x80, 0x88, 0x80, 0xa8, 0x99, 0xc0, 0x40, 0xa0, 0x59, 0x0, 0x22, 0x66, 0x3, 0x80, 0xa5, 0xa, 0x81, 0x95, 0x1, 0xa0, 0x53, 0x6e, 0x2, 0x80, 0xbd, 0x1, 0x12, 0xa, 0x16, 0x23, 0x1, 0x13, 0x1, 0x4, 0x80, 0x93, 0x1, 0x1, 0x20, 0x1a, 0x6, 0x1a, 0xb, 0xa, 0x1, 0x2d, 0x2, 0x40, 0x7, 0x1, 0x7, 0xa, 0x5, 0x81, 0x2, 0x3, 0x4, 0x2d, 0x3, 0x9, 0x50, 0xc, 0x34, 0x2d, 0xa0, 0xce, 0x3, 0x80, 0xf6, 0xa, 0x27, 0x2, 0x3e, 0x3, 0x11, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x30, 0x81, 0x22, 0x57, 0x9, 0x12, 0x80, 0x8e, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, 0x98, 0x0, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1a, 0x1, 0x2, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0xac, 0x8, 0x8d, 0x1, 0x1e, 0x60];
+_T Old_Italic = [0xa1, 0x3, 0x0, 0x1f, 0x1, 0x4];
+_T Old_Persian = [0xa1, 0x3, 0xa0, 0x24, 0x4, 0xe];
+_T Greek = [0x83, 0x70, 0x4, 0x1, 0x3, 0x2, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x3f, 0xe, 0x10, 0x99, 0x26, 0x5, 0x32, 0x5, 0x4, 0x5, 0x54, 0x1, 0x81, 0x40, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0xf, 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, 0x2, 0x3, 0x1, 0x9, 0x81, 0x27, 0x1, 0xa0, 0xe0, 0x19, 0x4b, 0xa0, 0xd0, 0x75, 0x46];
+_T Sundanese = [0x9b, 0x80, 0x40, 0x81, 0x0, 0x8];
+_T Syriac = [0x87, 0x0, 0xe, 0x1, 0x3c, 0x2, 0x3];
+_T Gurmukhi = [0x8a, 0x1, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, 0x4, 0x1, 0x1, 0x7, 0x10];
+_T Tibetan = [0x8f, 0x0, 0x48, 0x1, 0x24, 0x4, 0x27, 0x1, 0x24, 0x1, 0xf, 0x1, 0x7, 0x4, 0x2];
+_T Tamil = [0x8b, 0x82, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0x15];
+_T Telugu = [0x8c, 0x1, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x8, 0x8];
+_T Inscriptional_Parthian = [0xa1, 0xb, 0x40, 0x16, 0x2, 0x8];
+_T Nko = [0x87, 0xc0, 0x3b];
+_T Javanese = [0xa0, 0xa9, 0x80, 0x4e, 0x2, 0xa, 0x4, 0x2];
+_T Tai_Le = [0x99, 0x50, 0x1e, 0x2, 0x5];
+_T Old_South_Arabian = [0xa1, 0xa, 0x60, 0x20];
+_T Bamum = [0xa0, 0xa6, 0xa0, 0x58, 0xa0, 0xc1, 0x8, 0x82, 0x39];
+_T Chakma = [0xa1, 0x11, 0x0, 0x35, 0x1, 0xe];
+_T Ugaritic = [0xa1, 0x3, 0x80, 0x1e, 0x1, 0x1];
+_T Tagalog = [0x97, 0x0, 0xd, 0x1, 0x7];
+_T Tagbanwa = [0x97, 0x60, 0xd, 0x1, 0x3, 0x1, 0x2];
+_T Devanagari = [0x89, 0x0, 0x51, 0x2, 0x11, 0x2, 0x12, 0x1, 0x7, 0xa0, 0x9f, 0x60, 0x1c];
+_T Buginese = [0x9a, 0x0, 0x1c, 0x2, 0x2];
+_T Cuneiform = [0xa1, 0x20, 0x0, 0x83, 0x6f, 0x80, 0x91, 0x63, 0xd, 0x4];
+_T Cyrillic = [0x84, 0x0, 0x80, 0x85, 0x2, 0x80, 0xa1, 0x98, 0x3, 0x1, 0x4c, 0x1, 0x90, 0x67, 0x20, 0xa0, 0x78, 0x40, 0x58, 0x7, 0x1];
+_U[] _tab = [
+_U("Arabic", Arabic),
+_U("Armenian", Armenian),
+_U("Avestan", Avestan),
+_U("Balinese", Balinese),
+_U("Bamum", Bamum),
+_U("Batak", Batak),
+_U("Bengali", Bengali),
+_U("Bopomofo", Bopomofo),
+_U("Brahmi", Brahmi),
+_U("Braille", Braille),
+_U("Buginese", Buginese),
+_U("Buhid", Buhid),
+_U("Canadian_Aboriginal", Canadian_Aboriginal),
+_U("Carian", Carian),
+_U("Chakma", Chakma),
+_U("Cham", Cham),
+_U("Cherokee", Cherokee),
+_U("Common", Common),
+_U("Coptic", Coptic),
+_U("Cuneiform", Cuneiform),
+_U("Cypriot", Cypriot),
+_U("Cyrillic", Cyrillic),
+_U("Deseret", Deseret),
+_U("Devanagari", Devanagari),
+_U("Egyptian_Hieroglyphs", Egyptian_Hieroglyphs),
+_U("Ethiopic", Ethiopic),
+_U("Georgian", Georgian),
+_U("Glagolitic", Glagolitic),
+_U("Gothic", Gothic),
+_U("Greek", Greek),
+_U("Gujarati", Gujarati),
+_U("Gurmukhi", Gurmukhi),
+_U("Han", Han),
+_U("Hangul", Hangul),
+_U("Hanunoo", Hanunoo),
+_U("Hebrew", Hebrew),
+_U("Hiragana", Hiragana),
+_U("Imperial_Aramaic", Imperial_Aramaic),
+_U("Inherited", Inherited),
+_U("Inscriptional_Pahlavi", Inscriptional_Pahlavi),
+_U("Inscriptional_Parthian", Inscriptional_Parthian),
+_U("Javanese", Javanese),
+_U("Kaithi", Kaithi),
+_U("Kannada", Kannada),
+_U("Katakana", Katakana),
+_U("Kayah_Li", Kayah_Li),
+_U("Kharoshthi", Kharoshthi),
+_U("Khmer", Khmer),
+_U("Lao", Lao),
+_U("Latin", Latin),
+_U("Lepcha", Lepcha),
+_U("Limbu", Limbu),
+_U("Linear_B", Linear_B),
+_U("Lisu", Lisu),
+_U("Lycian", Lycian),
+_U("Lydian", Lydian),
+_U("Malayalam", Malayalam),
+_U("Mandaic", Mandaic),
+_U("Meetei_Mayek", Meetei_Mayek),
+_U("Meroitic_Cursive", Meroitic_Cursive),
+_U("Meroitic_Hieroglyphs", Meroitic_Hieroglyphs),
+_U("Miao", Miao),
+_U("Mongolian", Mongolian),
+_U("Myanmar", Myanmar),
+_U("New_Tai_Lue", New_Tai_Lue),
+_U("Nko", Nko),
+_U("Ogham", Ogham),
+_U("Ol_Chiki", Ol_Chiki),
+_U("Old_Italic", Old_Italic),
+_U("Old_Persian", Old_Persian),
+_U("Old_South_Arabian", Old_South_Arabian),
+_U("Old_Turkic", Old_Turkic),
+_U("Oriya", Oriya),
+_U("Osmanya", Osmanya),
+_U("Phags_Pa", Phags_Pa),
+_U("Phoenician", Phoenician),
+_U("Rejang", Rejang),
+_U("Runic", Runic),
+_U("Samaritan", Samaritan),
+_U("Saurashtra", Saurashtra),
+_U("Sharada", Sharada),
+_U("Shavian", Shavian),
+_U("Sinhala", Sinhala),
+_U("Sora_Sompeng", Sora_Sompeng),
+_U("Sundanese", Sundanese),
+_U("Syloti_Nagri", Syloti_Nagri),
+_U("Syriac", Syriac),
+_U("Tagalog", Tagalog),
+_U("Tagbanwa", Tagbanwa),
+_U("Tai_Le", Tai_Le),
+_U("Tai_Tham", Tai_Tham),
+_U("Tai_Viet", Tai_Viet),
+_U("Takri", Takri),
+_U("Tamil", Tamil),
+_U("Telugu", Telugu),
+_U("Thaana", Thaana),
+_U("Thai", Thai),
+_U("Tibetan", Tibetan),
+_U("Tifinagh", Tifinagh),
+_U("Ugaritic", Ugaritic),
+_U("Vai", Vai),
+_U("Yi", Yi),
+];
+}
+
+struct hangul
+{
+private alias _U = immutable(UnicodeProperty);
+@property static _U[] tab() pure nothrow @nogc { return _tab; }
+static immutable:
+private alias _T = ubyte[];
+_T LVT = [0xa0, 0xac, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b];
+_T T = [0x91, 0xa8, 0x58, 0xa0, 0xc5, 0xcb, 0x31];
+_T V = [0x91, 0x60, 0x48, 0xa0, 0xc6, 0x8, 0x17];
+_T LV = [0xa0, 0xac, 0x0, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1];
+_T L = [0x91, 0x0, 0x60, 0xa0, 0x98, 0x0, 0x1d];
+_U[] _tab = [
+_U("L", L),
+_U("Leading_Jamo", L),
+_U("LV", LV),
+_U("LV_Syllable", LV),
+_U("LVT", LVT),
+_U("LVT_Syllable", LVT),
+_U("T", T),
+_U("Trailing_Jamo", T),
+_U("V", V),
+_U("Vowel_Jamo", V),
+];
+}
+bool isFormatGen(dchar ch) @safe pure nothrow
+{
+    if(ch < 8288)
+    {
+        if(ch < 1807)
+        {
+            if(ch < 1564)
+            {
+                if(ch == 173) return true;
+                if(ch < 1536) return false;
+                if(ch < 1541) return true;
+                return false;
+            }
+            else if (ch < 1565) return true;
+            else
+            {
+                if(ch == 1757) return true;
+                return false;
+            }
+        }
+        else if (ch < 1808) return true;
+        else
+        {
+            if(ch < 8203)
+            {
+                if(ch == 6158) return true;
+                return false;
+            }
+            else if (ch < 8208) return true;
+            else
+            {
+                if(ch < 8234) return false;
+                if(ch < 8239) return true;
+                return false;
+            }
+        }
+    }
+    else if (ch < 8293) return true;
+    else
+    {
+        if(ch < 69821)
+        {
+            if(ch < 65279)
+            {
+                if(ch < 8294) return false;
+                if(ch < 8304) return true;
+                return false;
+            }
+            else if (ch < 65280) return true;
+            else
+            {
+                if(ch < 65529) return false;
+                if(ch < 65532) return true;
+                return false;
+            }
+        }
+        else if (ch < 69822) return true;
+        else
+        {
+            if(ch < 917505)
+            {
+                if(ch < 119155) return false;
+                if(ch < 119163) return true;
+                return false;
+            }
+            else if (ch < 917506) return true;
+            else
+            {
+                if(ch < 917536) return false;
+                if(ch < 917632) return true;
+                return false;
+            }
+        }
+    }
+}
+
+bool isControlGen(dchar ch) @safe pure nothrow
+{
+    if(ch < 32) return true;
+    if(ch < 127) return false;
+    if(ch < 160) return true;
+    return false;
+}
+
+bool isSpaceGen(dchar ch) @safe pure nothrow
+{
+    if(ch < 160)
+    {
+        if(ch == 32) return true;
+        return false;
+    }
+    else if (ch < 161) return true;
+    else
+    {
+        if(ch < 8239)
+        {
+            if(ch == 5760) return true;
+            if(ch < 8192) return false;
+            if(ch < 8203) return true;
+            return false;
+        }
+        else if (ch < 8240) return true;
+        else
+        {
+            if(ch == 8287) return true;
+            if(ch == 12288) return true;
+            return false;
+        }
+    }
+}
+
+bool isWhiteGen(dchar ch) @safe pure nothrow @nogc
+{
+    if(ch < 133)
+    {
+        if(ch < 9) return false;
+        if(ch < 14) return true;
+        if(ch == 32) return true;
+        return false;
+    }
+    else if (ch < 134) return true;
+    else
+    {
+        if(ch < 8232)
+        {
+            if(ch < 5760)
+            {
+                if(ch == 160) return true;
+                return false;
+            }
+            else if (ch < 5761) return true;
+            else
+            {
+                if(ch < 8192) return false;
+                if(ch < 8203) return true;
+                return false;
+            }
+        }
+        else if (ch < 8234) return true;
+        else
+        {
+            if(ch < 8287)
+            {
+                if(ch == 8239) return true;
+                return false;
+            }
+            else if (ch < 8288) return true;
+            else
+            {
+                if(ch == 12288) return true;
+                return false;
+            }
+        }
+    }
+}
+
+bool isHangL(dchar ch) @safe pure nothrow
+{
+    if(ch < 4352) return false;
+    if(ch < 4448) return true;
+    if(ch < 43360) return false;
+    if(ch < 43389) return true;
+    return false;
+}
+
+bool isHangV(dchar ch) @safe pure nothrow
+{
+    if(ch < 4448) return false;
+    if(ch < 4520) return true;
+    if(ch < 55216) return false;
+    if(ch < 55239) return true;
+    return false;
+}
+
+bool isHangT(dchar ch) @safe pure nothrow
+{
+    if(ch < 4520) return false;
+    if(ch < 4608) return true;
+    if(ch < 55243) return false;
+    if(ch < 55292) return true;
+    return false;
+}
+
+
+static if(size_t.sizeof == 8) {
+//1536 bytes
+enum lowerCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([ 0x0,  0x20,  0x40], [ 0x100,  0x80,  0x2000], [ 0x402030202020100,  0x206020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x3000300030003,  0x3000300030003,  0x5000400030003,  0x3000700030006,  0x3000800030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x9000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0xb0003000a0003,  0x3000c00030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0xe000d00030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x0,  0x7fffffe00000000,  0x420040000000000,  0xff7fffff80000000,  0x55aaaaaaaaaaaaaa,  0xd4aaaaaaaaaaab55,  0xe6512d2a4e243129,  0xaa29aaaab5555240,  0x93faaaaaaaaaaaaa,  0xffffffffffffaa85,  0x1ffffffffefffff,  0x1f00000003,  0x0,  0x3c8a000000000020,  0xfffff00000010000,  0x192faaaaaae37fff,  0xffff000000000000,  0xaaaaaaaaffffffff,  0xaaaaaaaaaaaaa802,  0xaaaaaaaaaaaad554,  0xaaaaaaaaaa,  0xfffffffe00000000,  0xff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x0,  0xaaaaaaaaaaaaaaaa,  0xaaaaaaaaaaaaaaaa,  0xaaaaaaaabfeaaaaa,  0xaaaaaaaaaaaaaaaa,  0xff00ff003f00ff,  0x3fff00ff00ff003f,  0x40df00ff00ff00ff,  0xdc00ff00cf00dc,  0x0,  0x8002000000000000,  0x1fff0000,  0x0,  0x321080000008c400,  0xffff0000000043c0,  0x10,  0x0,  0x0,  0x0,  0x0,  0x3ffffff0000,  0x0,  0x0,  0x0,  0x0,  0xffff000000000000,  0x3fda15627fffffff,  0xaaaaaaaaaaaaaaaa,  0x8501aaaaaaaaa,  0x20bfffffffff,  0x0,  0x0,  0x0,  0x0,  0x2aaaaaaaaaaa,  0xaaaaaa,  0x0,  0xaaabaaa800000000,  0x95ffaaaaaaaaaaaa,  0x2aa000a50aa,  0x700000000000000,  0x0,  0x0,  0x0,  0x0,  0xf8007f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7fffffe,  0x0,  0x0,  0xffffff0000000000,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffc000000,  0xffffdfc000,  0xebc000000ffffffc,  0xfffffc000000ffef,  0xffffffc000000f,  0xffffffc0000,  0xfc000000ffffffc0,  0xffffc000000fffff,  0xffffffc000000ff,  0xffffffc00000,  0x3ffffffc00,  0xf0000003f7fffffc,  0xffc000000fdfffff,  0xffff0000003f7fff,  0xfffffc000000fdff,  0xbf7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1472 bytes
+enum upperCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([ 0x0,  0x20,  0x40], [ 0x100,  0x80,  0x1e00], [ 0x402030202020100,  0x206020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x3000300030003,  0x3000300030004,  0x5000300030003,  0x3000700030006,  0x3000800030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x9000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0xa000300030003,  0x3000b00030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0xd000c00030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x3000300030003,  0x0,  0x7fffffe,  0x0,  0x7f7fffff,  0xaa55555555555555,  0x2b555555555554aa,  0x11aed2d5b1dbced6,  0x55d255554aaaa490,  0x6c05555555555555,  0x557a,  0x0,  0x0,  0x0,  0x45000000000000,  0xffbfffed740,  0xe6905555551c8000,  0xffffffffffff,  0x5555555500000000,  0x5555555555555401,  0x5555555555552aab,  0xfffe005555555555,  0x7fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff00000000,  0x20bf,  0x0,  0x0,  0x0,  0x0,  0x5555555555555555,  0x5555555555555555,  0x5555555540155555,  0x5555555555555555,  0xff00ff003f00ff00,  0xff00aa003f00,  0xf00000000000000,  0xf001f000f000f00,  0x0,  0x0,  0x0,  0x0,  0xc00f3d503e273884,  0xffff00000020,  0x8,  0x0,  0x0,  0x0,  0xffc0000000000000,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x7fffffffffff,  0xc025ea9d00000000,  0x5555555555555555,  0x4280555555555,  0x0,  0x0,  0x0,  0x0,  0x0,  0x155555555555,  0x555555,  0x0,  0x5554555400000000,  0x6a00555555555555,  0x55500052855,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7fffffe00000000,  0x0,  0x0,  0x0,  0xffffffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfff0000003ffffff,  0xffffff0000003fff,  0x3fde64d0000003,  0x3ffffff0000,  0x7b0000001fdfe7b0,  0xfffff0000001fc5f,  0x3ffffff0000003f,  0x3ffffff00000,  0xf0000003ffffff00,  0xffff0000003fffff,  0xffffff00000003ff,  0x7fffffc00000001,  0x1ffffff0000000,  0x7fffffc00000,  0x1ffffff0000,  0x400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//8704 bytes
+enum simpleCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xd00], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16001500000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x1b001a00190018,  0x1f001e001d001c,  0x0,  0x2200210020,  0x0,  0x0,  0x24002300000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x28002700260025,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b002a0000,  0x2e002d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30002f,  0x0,  0x0,  0x0,  0x0,  0x320031,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2400220020ffff,  0x2c002a00280026,  0x72f00320030002e,  0x3d003b00390037,  0x1b000430041003f,  0x4e004c004a0048,  0xffff005400520050,  0xffffffffffffffff,  0x2500230021ffff,  0x2d002b00290027,  0x73000330031002f,  0x3e003c003a0038,  0x1b1004400420040,  0x4f004d004b0049,  0xffff005500530051,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff043fffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xcc049800c800c6,  0xd500d3014904aa,  0xdd00db00d900d7,  0xe500e300e100df,  0xed00eb00e900e7,  0xffff00f300f100ef,  0xfb00f900f700f5,  0x6be010100ff00fd,  0xcd049900c900c7,  0xd600d4014a04ab,  0xde00dc00da00d8,  0xe600e400e200e0,  0xee00ec00ea00e8,  0xffff00f400f200f0,  0xfc00fa00f800f6,  0x1a80102010000fe,  0x118011701160115,  0x11e011d011c011b,  0x12401230120011f,  0x128012701260125,  0x12e012d012c012b,  0x13401330130012f,  0x138013701360135,  0x13c013b013a0139,  0x140013f013e013d,  0x144014301420141,  0x148014701460145,  0x14f014e014d014c,  0x1510150ffffffff,  0x155015401530152,  0x15801570156ffff,  0x15e015d015c0159,  0x16201610160015f,  0x166016501640163,  0x1690168ffff0167,  0x16d016c016b016a,  0x1710170016f016e,  0x175017401730172,  0x179017801770176,  0x17d017c017b017a,  0x1830182017f017e,  0x18b018a01870186,  0x1930192018f018e,  0x19b019a01970196,  0x1a301a2019f019e,  0x1a701a601a501a4,  0x1ac01ab01aa01a9,  0x1b201af01ae01ad,  0x1b601b501b3028b,  0x1bd01bb01ba01b9,  0x1c301c101bf01be,  0x1c701c5ffff01c4,  0x1cd01cc01cb01c9,  0x1d301d1023b01cf,  0xffff028301d601d5,  0x1db026901d901d7,  0x1e001df01de01dd,  0x1e501e301e201e1,  0xffffffff01e701e6,  0x1ed01eb01ea01e9,  0x1f301f101ef01ee,  0x1f701f601f501f4,  0xffffffff01fa01f9,  0x23dffff01fc01fb,  0xffffffffffffffff,  0x206020202010200,  0x20d020c02080207,  0x2110210020f020e,  0x215021402130212,  0x219021802170216,  0x21d021c021b021a,  0x220021f01c6021e,  0x226022502240223,  0x22a022902280227,  0x22e022d022c022b,  0x23202310230022f,  0x23802370236ffff,  0x23e023c023a0239,  0x24402430240023f,  0x248024702460245,  0x24c024b024a0249,  0x250024f024e024d,  0x254025302520251,  0x258025702560255,  0x25c025b025a0259,  0x260025f025e025d,  0x264026302620261,  0x268026702660265,  0x26c026bffff026a,  0x270026f026e026d,  0x274027302720271,  0x278027702760275,  0x27c027b027a0279,  0xffffffffffffffff,  0x281027fffffffff,  0x2d7028502840282,  0x28c028802870482,  0x2920291028f028d,  0x296029502940293,  0x29c029b02980297,  0x1b402b70466046a,  0x1c201c0ffff01bc,  0x1caffff01c8ffff,  0xffffffffffffffff,  0x1d0ffffffff01ce,  0xffff05fa0748ffff,  0x528ffff01d201d4,  0x1d8ffffffffffff,  0xffff01da02b3ffff,  0xffffffff01dcffff,  0xffffffffffffffff,  0xffffffff02a3ffff,  0x1e8ffffffff01e4,  0xffffffffffffffff,  0x1f201f0028e01ec,  0xffffffffffff0290,  0xffff01f8ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff083affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x320031f031e031d,  0x3240323ffffffff,  0x3d5ffffffffffff,  0xffffffff03d903d7,  0xffffffffffffffff,  0xffff0329ffffffff,  0xffff0331032f032d,  0x3370335ffff0333,  0x33e03950339ffff,  0x347034503cc0340,  0x35403c2083b03c8,  0x35d035b03590440,  0x388ffff03c5039f,  0x36f039c036a0368,  0x378037607100371,  0x3320330032e032a,  0x33f0396033affff,  0x348034603cd0341,  0x35503c3083c03c9,  0x35e035c035a0441,  0x38a038903c603a0,  0x370039d036b0369,  0x379037707110372,  0x393033803360334,  0xffffffff03ca0397,  0x39403a1039effff,  0x3a703a603a303a2,  0x3ab03aa03a903a8,  0x3af03ae03ad03ac,  0x3b503b403b103b0,  0x3bd03bc03b903b8,  0x3c103c003bf03be,  0xffff03d103c703c4,  0x3cfffff03ce03cb,  0x3d403d303d203d0,  0x3da03d803d6ffff,  0x3e103df03dd03db,  0x3e903e703e503e3,  0x3f103ef03ed03eb,  0x3f903f703f503f3,  0x40103ff03fd03fb,  0x409040704050403,  0x411040f040d040b,  0x419041704150413,  0x421041f041d041b,  0x429042704250423,  0x431042f042d042b,  0x439043704350433,  0x402040003fe03fc,  0x40a040804060404,  0x4120410040e040c,  0x41a041804160414,  0x4220420041e041c,  0x42a042804260424,  0x4320430042e042c,  0x43a043804360434,  0x3e203e003de03dc,  0x3ea03e803e603e4,  0x3f203f003ee03ec,  0x3fa03f803f603f4,  0x453045204510450,  0x459045804570456,  0x4610460045d045c,  0x469046804650464,  0x4710470046d046c,  0x477047604730472,  0x47b047a04790478,  0x4810480047d047c,  0xffffffff04850484,  0xffffffffffffffff,  0x4950494ffffffff,  0x49b049a04970496,  0x49f049e049d049c,  0x4a704a604a304a2,  0x4ad04ac04a904a8,  0x4b304b204b104b0,  0x4b904b804b704b6,  0x4bf04be04bb04ba,  0x4c504c404c104c0,  0x4cd04cc04c904c8,  0x4d304d204cf04ce,  0x4d704d604d504d4,  0x4df04de04db04da,  0x4e704e604e304e2,  0x4f004ed04ec04ea,  0x4f804f504f404f1,  0x50004fd04fc04f9,  0x4eb050505040501,  0x50d050c050b050a,  0x5130512050f050e,  0x519051805170516,  0x51f051e051d051c,  0x525052405210520,  0x52b052a05270526,  0x52f052e052d052c,  0x537053605330532,  0x53d053c05390538,  0x5410540053f053e,  0x547054605430542,  0x54b054a05490548,  0x54f054e054d054c,  0x555055405510550,  0x559055805570556,  0x55d055c055b055a,  0x5630562055f055e,  0x567056605650564,  0x56b056a05690568,  0x5730572056f056e,  0x577057605750574,  0x57b057a05790578,  0xffffffffffffffff,  0xffffffffffffffff,  0x58405820580ffff,  0x58c058a05880586,  0x59405920590058e,  0x59c059a05980596,  0x5a405a205a0059e,  0x5ac05aa05a805a6,  0x5b405b205b005ae,  0x5bc05ba05b805b6,  0x5c405c205c005be,  0xffff05ca05c805c6,  0xffffffffffffffff,  0xffffffffffffffff,  0x58505830581ffff,  0x58d058b05890587,  0x59505930591058f,  0x59d059b05990597,  0x5a505a305a1059f,  0x5ad05ab05a905a7,  0x5b505b305b105af,  0x5bd05bb05b905b7,  0x5c505c305c105bf,  0xffff05cb05c905c7,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x8c008a00880086,  0x9400920090008e,  0x9c009a00980096,  0xa400a200a0009e,  0xac00aa00a800a6,  0xb400b200b000ae,  0xbc00ba00b800b6,  0xc400c200c000be,  0x4a000ca048e0486,  0x4c6ffff04b400ce,  0xffffffffffffffff,  0xffffffff0508ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff07e8ffff,  0xffffffff0454ffff,  0x5ff05fe05fd05fc,  0x605060406010600,  0x60b060a06090608,  0x6110610060f060e,  0x617061606130612,  0x61d061c06190618,  0x6210620061f061e,  0x627062606230622,  0x62b062a06290628,  0x62f062e062d062c,  0x635063406310630,  0x639063806370636,  0x63d063c063b063a,  0x6430642063f063e,  0x647064606450644,  0x64b064a06490648,  0x6510650064d064c,  0x655065406530652,  0x65d065c06590658,  0x6630662065f065e,  0x667066606650664,  0x66b066a06690668,  0x6710670066d066c,  0x675067406730672,  0x67a067906bc06bb,  0x680067f067c067b,  0x684068306820681,  0x688068706860685,  0x68e068d068a0689,  0x69206910690068f,  0x698069706960695,  0x69e069d069a0699,  0x6a206a106a0069f,  0x6a606a506a406a3,  0x6ac06ab06a806a7,  0x6b006af06ae06ad,  0x6b406b306b206b1,  0xffffffff06b606b5,  0x6bdffffffffffff,  0xffff06bfffffffff,  0x6c306c206c106c0,  0x6c906c806c506c4,  0x6cd06cc06cb06ca,  0x6d106d006cf06ce,  0x6d706d606d506d4,  0x6dd06dc06db06da,  0x6e106e006df06de,  0x6e506e406e306e2,  0x6eb06ea06e906e8,  0x6f106f006ef06ee,  0x6f506f406f306f2,  0x6f906f806f706f6,  0x6fd06fc06fb06fa,  0x701070006ff06fe,  0x705070407030702,  0x709070807070706,  0x70d070c070b070a,  0x7140713070f070e,  0x718071707160715,  0x71e071d071c071b,  0x72207210720071f,  0x726072507240723,  0x72a072907280727,  0x7330732072e072d,  0x73c073a07380736,  0x74407420740073e,  0x73d073b07390737,  0x74507430741073f,  0x750074e074c074a,  0xffffffff07540752,  0x751074f074d074b,  0xffffffff07550753,  0x76a076807660764,  0x7720770076e076c,  0x76b076907670765,  0x7730771076f076d,  0x78a078807860784,  0x7920790078e078c,  0x78b078907870785,  0x7930791078f078d,  0x7a207a0079e079c,  0xffffffff07a607a4,  0x7a307a1079f079d,  0xffffffff07a707a5,  0x7baffff07b6ffff,  0x7c2ffff07beffff,  0x7bbffff07b7ffff,  0x7c3ffff07bfffff,  0x7d607d407d207d0,  0x7de07dc07da07d8,  0x7d707d507d307d1,  0x7df07dd07db07d9,  0x840083e08360834,  0x84e084c08440842,  0x858085608620860,  0xffffffff08660864,  0x7fa07f807f607f4,  0x802080007fe07fc,  0x7fb07f907f707f5,  0x803080107ff07fd,  0x80e080c080a0808,  0x816081408120810,  0x80f080d080b0809,  0x817081508130811,  0x826082408220820,  0x82e082c082a0828,  0x827082508230821,  0x82f082d082b0829,  0x838ffff08320830,  0xffffffffffffffff,  0x837083508330831,  0xffff083dffff0839,  0x846ffffffffffff,  0xffffffffffffffff,  0x84508430841083f,  0xffffffffffff0847,  0xffffffff084a0848,  0xffffffffffffffff,  0x84f084d084b0849,  0xffffffffffffffff,  0xffffffff08540852,  0xffffffff085affff,  0x859085708550853,  0xffffffffffff085b,  0x868ffffffffffff,  0xffffffffffffffff,  0x867086508630861,  0xffffffffffff0869,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0712ffffffff,  0x14b0731ffffffff,  0xffffffffffffffff,  0xffff0530ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0531ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x18402af0180029f,  0x18c005e018802c1,  0x194006c01900064,  0x19c007e01980076,  0x18502b0018102a0,  0x18d005f018902c2,  0x195006d01910065,  0x19d007f01990077,  0x1b7ffffffffffff,  0xffffffffffff01b8,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x4d80444ffffffff,  0x4e0044c04dc0446,  0x4e8047404e4045e,  0x204ee02d3086a,  0x6e04f606c604f2,  0x10d04fe037a04fa,  0x51a0506061a0502,  0x4dd044704d90445,  0x4e5045f04e1044d,  0x2d4086b04e90475,  0x6c704f3000304ef,  0x37b04fb006f04f7,  0x61b0503010e04ff,  0xffffffff051b0507,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa000800040000,  0x160010000e000c,  0x2bb001c001a0018,  0x5602e702d102c7,  0x66006200600058,  0x7800740070006a,  0x29900820080007c,  0x6020084000607e0,  0x5d005ce02a7057c,  0x1070105010305de,  0x1190113010f0109,  0xffff013101290121,  0xb000900050001,  0x170011000f000d,  0x2bc001d001b0019,  0x5702e802d202c8,  0x67006300610059,  0x7900750071006b,  0x29a00830081007d,  0x6030085000707e1,  0x5d105cf02a8057d,  0x1080106010405df,  0x11a01140110010a,  0xffff0132012a0122,  0x455052904c304c2,  0x45a0286028002a4,  0x46202aa02a9045b,  0x46b02b404670463,  0x2ba02b9ffff02b8,  0xffff02c002bfffff,  0xffffffffffffffff,  0x48302d8ffffffff,  0x489048802e202e1,  0x48d048c048b048a,  0x2fe02fd04910490,  0x30e030d03040303,  0x31a031903160315,  0x328032703260325,  0x6ed06ec02fc02fb,  0x383038203810380,  0x392039103870386,  0x3b303b203a503a4,  0x5cd05cc056d056c,  0x5ed05ec05db05da,  0x6570656060d060c,  0x6e706e6043e043d,  0x7830782072c072b,  0x694069307e307e2,  0x150014065b065a,  0x4bd04bc005d005c,  0x5d505d404d104d0,  0x511051001a101a0,  0x535053405230522,  0x553055205450544,  0x571057005610560,  0x15b015a057f057e,  0x3bb03ba037d037c,  0xffffffffffffffff,  0x5d2ffffffffffff,  0xffff05d905d805d3,  0x5e305e2ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x8d008b00890087,  0x9500930091008f,  0x9d009b00990097,  0xa500a300a1009f,  0xad00ab00a900a7,  0xb500b300b100af,  0xbd00bb00b900b7,  0xc500c300c100bf,  0x4a100cb048f0487,  0x4c7ffff04b500cf,  0xffffffffffffffff,  0xffffffff0509ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x5d705d602c402c3,  0x5e105e005dd05dc,  0x5e905e805e705e6,  0x5ef05ee05eb05ea,  0x5f505f405f105f0,  0x308030705f905f8,  0x625062406150614,  0x641064006330632,  0x6610660064f064e,  0x67e067d066f066e,  0x69c069b068c068b,  0xffffffff06aa06a9,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x69006807350734,  0x75f075e027e027d,  0x390038f07770776,  0x7b107b0001f001e,  0x2a202a107c707c6,  0x6b806b707e507e4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7590758ffffffff,  0x7610760075d075c,  0x2e002df02d602d5,  0x2ee02ed02e602e5,  0x7790778ffffffff,  0x7810780077d077c,  0x3140313030c030b,  0x322032103180317,  0x797079607950794,  0x79b079a07990798,  0x3850384037f037e,  0x7a907a8038e038d,  0x7ad07ac07ab07aa,  0x7b307b207af07ae,  0x7b907b807b507b4,  0x7c107c007bd07bc,  0x7c907c807c507c4,  0x7cf07ce07cd07cc,  0x47f047e046f046e,  0x4a504a404930492,  0xffffffffffffffff,  0xffffffffffffffff,  0x7e605150514ffff,  0x7eb07ea07e907e7,  0x7ef07ee07ed07ec,  0x7f307f207f107f0,  0x5f2ffffffffffff,  0xffffffff074905f3,  0x807080608050804,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x81b081a08190818,  0x81f081e081d081c,  0xffff05fb05f705f6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x75a02c50756ffff,  0x76202cf02cd02cb,  0x2e3071902db06d2,  0x2f107ca02e90448,  0x77a02f502f30774,  0x3050221077e02f9,  0xffff043b030f007a,  0xffffffffffffffff,  0x75b02c60757ffff,  0x76302d002ce02cc,  0x2e4071a02dc06d3,  0x2f207cb02ea0449,  0x77b02f602f40775,  0x3060222077f02fa,  0xffff043c0310007b,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x72005a085c0012,  0x11106b9032b0311,  0x2dd029d02ab05e4,  0x10b060602ef085e,  0x4ca028902d902a5,  0x2c902bd02b502ad,  0x30902f702eb0746,  0x38b02b10241031b,  0x442053a044a03b6,  0x85004ae06d8044e,  0x73005b085d0013,  0x11206ba032c0312,  0x2de029e02ac05e5,  0x10c060702f0085f,  0x4cb028a02da02a6,  0x2ca02be02b602ae,  0x30a02f802ec0747,  0x38c02b20242031c,  0x443053b044b03b7,  0x85104af06d9044f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//8832 bytes
+enum fullCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xd40], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16001500000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x1b001a00190018,  0x1f001e001d001c,  0x0,  0x2200210020,  0x0,  0x0,  0x24002300000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x28002700260025,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b002a0000,  0x2e002d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2f,  0x0,  0x0,  0x0,  0x310030,  0x0,  0x0,  0x0,  0x0,  0x330032,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2400220020ffff,  0x2c002a00280026,  0x78100320030002e,  0x3d003b00390037,  0x1b900430041003f,  0x4e004c004a0048,  0xffff005400520050,  0xffffffffffffffff,  0x2500230021ffff,  0x2d002b00290027,  0x78200330031002f,  0x3e003c003a0038,  0x1ba004400420040,  0x4f004d004b0049,  0xffff005500530051,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff0470ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xcc04c900c800c6,  0xd500d3014e04db,  0xdd00db00d900d7,  0xe500e300e100df,  0xed00eb00e900e7,  0xffff00f300f100ef,  0xfb00f900f700f5,  0x70f010100ff00fd,  0xcd04ca00c900c7,  0xd600d4014f04dc,  0xde00dc00da00d8,  0xe600e400e200e0,  0xee00ec00ea00e8,  0xffff00f400f200f0,  0xfc00fa00f800f6,  0x1b10102010000fe,  0x11b011a01190118,  0x1210120011f011e,  0x127012601230122,  0x12b012a01290128,  0x1310130012f012e,  0x137013601330132,  0x13b013a01390138,  0x13f013e013d013c,  0x143014201410140,  0x149014801470146,  0x14d014c014b014a,  0x154015301520151,  0x1580157ffff0155,  0x15c015b015a0159,  0x15f015e015dffff,  0x165016401630160,  0x169016801670166,  0x16d016c016b016a,  0x1720171016f016e,  0x176017501740173,  0x17a017901780177,  0x17e017d017c017b,  0x18201810180017f,  0x186018501840183,  0x18c018b01880187,  0x19401930190018f,  0x19c019b01980197,  0x1a401a301a0019f,  0x1ac01ab01a801a7,  0x1b001af01ae01ad,  0x1b501b401b301b2,  0x1bb01b801b701b6,  0x1bf01be01bc029c,  0x1c601c401c301c2,  0x1cc01ca01c801c7,  0x1d001ceffff01cd,  0x1d601d501d401d2,  0x1dc01da024801d8,  0xffff029401df01de,  0x1e6027801e201e0,  0x1eb01ea01e901e8,  0x1f001ee01ed01ec,  0xffffffff01f201f1,  0x1f801f601f501f4,  0x1fe01fc01fa01f9,  0x2020201020001ff,  0xffffffff02050204,  0x24affff02070206,  0xffffffffffffffff,  0x211020d020c020b,  0x218021702130212,  0x21c021b021a0219,  0x220021f021e021d,  0x224022302220221,  0x228022702260225,  0x22b022a01cf0229,  0x2310230022f022e,  0x235023402330232,  0x239023802370236,  0x23d023c023b023a,  0x24502440243023e,  0x24b024902470246,  0x2510250024d024c,  0x255025402530252,  0x259025802570256,  0x25d025c025b025a,  0x263026202610260,  0x267026602650264,  0x26b026a02690268,  0x26f026e026d026c,  0x273027202710270,  0x277027602750274,  0x27b027affff0279,  0x27f027e027d027c,  0x285028402810280,  0x289028802870286,  0x28d028c028b028a,  0xffffffffffffffff,  0x2920290ffffffff,  0x2ec029602950293,  0x29d0299029804b3,  0x2a302a202a0029e,  0x2a702a602a502a4,  0x2ad02ac02a902a8,  0x1bd02ca0497049b,  0x1cb01c9ffff01c5,  0x1d3ffff01d1ffff,  0xffffffffffffffff,  0x1d9ffffffff01d7,  0xffff0643079affff,  0x559ffff01db01dd,  0x1e1ffffffffffff,  0xffff01e302c6ffff,  0xffffffff01e7ffff,  0xffffffffffffffff,  0xffffffff02b4ffff,  0x1f3ffffffff01ef,  0xffffffffffffffff,  0x1fd01fb029f01f7,  0xffffffffffff02a1,  0xffff0203ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff08e4ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x347034603450344,  0x34b034affffffff,  0x406ffffffffffff,  0xffffffff040a0408,  0xffffffffffffffff,  0xffff0350ffffffff,  0xffff035803560354,  0x35e035cffff035a,  0x36803c203630902,  0x371036f03fd036a,  0x37e03f308e503f9,  0x387038503830471,  0x3b5ffff03f603cc,  0x39903c903940392,  0x3a203a00762039b,  0x359035703550351,  0x36903c303640915,  0x372037003fe036b,  0x37f03f408e603fa,  0x388038603840472,  0x3b703b603f703cd,  0x39a03ca03950393,  0x3a303a10763039c,  0x3c0035f035d035b,  0xffffffff03fb03c4,  0x3c103ce03cbffff,  0x3d403d303d003cf,  0x3d803d703d603d5,  0x3de03dd03da03d9,  0x3e403e303e003df,  0x3ec03eb03e803e7,  0x3f203f103ee03ed,  0xffff040203f803f5,  0x400ffff03ff03fc,  0x405040404030401,  0x40b04090407ffff,  0x4120410040e040c,  0x41a041804160414,  0x4220420041e041c,  0x42a042804260424,  0x4320430042e042c,  0x43a043804360434,  0x4420440043e043c,  0x44a044804460444,  0x4520450044e044c,  0x45a045804560454,  0x4620460045e045c,  0x46a046804660464,  0x4330431042f042d,  0x43b043904370435,  0x4430441043f043d,  0x44b044904470445,  0x4530451044f044d,  0x45b045904570455,  0x4630461045f045d,  0x46b046904670465,  0x4130411040f040d,  0x41b041904170415,  0x4230421041f041d,  0x42b042904270425,  0x484048304820481,  0x48a048904880487,  0x4920491048e048d,  0x49a049904960495,  0x4a204a1049e049d,  0x4a804a704a404a3,  0x4ac04ab04aa04a9,  0x4b204b104ae04ad,  0xffffffff04b604b5,  0xffffffffffffffff,  0x4c604c5ffffffff,  0x4cc04cb04c804c7,  0x4d004cf04ce04cd,  0x4d804d704d404d3,  0x4de04dd04da04d9,  0x4e404e304e204e1,  0x4ea04e904e804e7,  0x4f004ef04ec04eb,  0x4f604f504f204f1,  0x4fe04fd04fa04f9,  0x5040503050004ff,  0x508050705060505,  0x510050f050c050b,  0x518051705140513,  0x521051e051d051b,  0x529052605250522,  0x531052e052d052a,  0x51c053605350532,  0x53e053d053c053b,  0x54405430540053f,  0x54a054905480547,  0x550054f054e054d,  0x556055505520551,  0x55c055b05580557,  0x560055f055e055d,  0x568056705640563,  0x56e056d056a0569,  0x57205710570056f,  0x578057705740573,  0x57c057b057a0579,  0x5820581057e057d,  0x588058705840583,  0x58c058b058a0589,  0x5920591058e058d,  0x598059705940593,  0x59c059b059a0599,  0x5a205a1059e059d,  0x5aa05a905a605a5,  0x5ae05ad05ac05ab,  0x5b405b305b005af,  0xffffffffffffffff,  0xffffffffffffffff,  0x5bd05bb05b9ffff,  0x5c505c305c105bf,  0x5cd05cb05c905c7,  0x5d505d305d105cf,  0x5dd05db05d905d7,  0x5e505e305e105df,  0x5ed05eb05e905e7,  0x5f505f305f105ef,  0x5fd05fb05f905f7,  0xffff0603060105ff,  0xffffffffffffffff,  0xffffffffffffffff,  0x5be05bc05baffff,  0x5c605c405c205c0,  0x5ce05cc05ca05c8,  0x5d605d405d205d0,  0x5de05dc05da05d8,  0x5e605e405e205e0,  0x5ee05ec05ea05e8,  0x5f605f405f205f0,  0x5fe05fc05fa05f8,  0x613060406020600,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x8c008a00880086,  0x9400920090008e,  0x9c009a00980096,  0xa400a200a0009e,  0xac00aa00a800a6,  0xb400b200b000ae,  0xbc00ba00b800b6,  0xc400c200c000be,  0x4d100ca04bf04b7,  0x4f7ffff04e500ce,  0xffffffffffffffff,  0xffffffff0539ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff083affff,  0xffffffff0485ffff,  0x648064706460645,  0x64e064d064a0649,  0x654065306520651,  0x65a065906580657,  0x660065f065c065b,  0x666066506620661,  0x66a066906680667,  0x670066f066c066b,  0x674067306720671,  0x678067706760675,  0x67e067d067a0679,  0x68206810680067f,  0x686068506840683,  0x68c068b06880687,  0x690068f068e068d,  0x694069306920691,  0x69a069906960695,  0x69e069d069c069b,  0x6a606a506a206a1,  0x6ac06ab06a806a7,  0x6b006af06ae06ad,  0x6b406b306b206b1,  0x6ba06b906b606b5,  0x6be06bd06bc06bb,  0x6c306c2070d070c,  0x6cb06ca06c706c6,  0x6cf06ce06cd06cc,  0x6d306d206d106d0,  0x6d906d806d506d4,  0x6dd06dc06db06da,  0x6e306e206e106e0,  0x6e906e806e506e4,  0x6ed06ec06eb06ea,  0x6f106f006ef06ee,  0x6f706f606f306f2,  0x6fb06fa06f906f8,  0x6ff06fe06fd06fc,  0x704070207010700,  0x70e070a07080706,  0xffff0710ffffffff,  0x715071407130712,  0x71b071a07170716,  0x71f071e071d071c,  0x723072207210720,  0x729072807270726,  0x72f072e072d072c,  0x733073207310730,  0x737073607350734,  0x73d073c073b073a,  0x743074207410740,  0x747074607450744,  0x74b074a07490748,  0x74f074e074d074c,  0x753075207510750,  0x757075607550754,  0x75b075a07590758,  0x75f075e075d075c,  0x766076507610760,  0x76a076907680767,  0x770076f076e076d,  0x774077307720771,  0x778077707760775,  0x77c077b077a0779,  0x78507840780077f,  0x78e078c078a0788,  0x796079407920790,  0x78f078d078b0789,  0x797079507930791,  0x7a207a0079e079c,  0xffffffff07a607a4,  0x7a307a1079f079d,  0xffffffff07a707a5,  0x7bc07ba07b807b6,  0x7c407c207c007be,  0x7bd07bb07b907b7,  0x7c507c307c107bf,  0x7dc07da07d807d6,  0x7e407e207e007de,  0x7dd07db07d907d7,  0x7e507e307e107df,  0x7f407f207f007ee,  0xffffffff07f807f6,  0x7f507f307f107ef,  0xffffffff07f907f7,  0x80c07fe080807fc,  0x814080408100800,  0x80dffff0809ffff,  0x815ffff0811ffff,  0x828082608240822,  0x830082e082c082a,  0x829082708250823,  0x831082f082d082b,  0x8f708f508df08dd,  0x90f090d08fb08f9,  0x924092209370935,  0xffffffff093b0939,  0x85f085c08590856,  0x86b086808650862,  0x860085d085a0857,  0x86c086908660863,  0x88f088c08890886,  0x89b089808950892,  0x890088d088a0887,  0x89c089908960893,  0x8bf08bc08b908b6,  0x8cb08c808c508c2,  0x8c008bd08ba08b7,  0x8cc08c908c608c3,  0x8e108ce08db08d9,  0x8d708d5ffff08d3,  0x8e008de08dc08da,  0xffff08e7ffff08e2,  0x8fd08e8ffffffff,  0x8f308f1ffff08ed,  0x8fc08fa08f808f6,  0xffffffffffff08fe,  0x9030900090b0909,  0x9070905ffffffff,  0x910090e090c090a,  0xffffffffffffffff,  0x91609130920091e,  0x91c091a09260918,  0x92509230921091f,  0xffffffffffff0927,  0x93d092affffffff,  0x9330931ffff092f,  0x93c093a09380936,  0xffffffffffff093e,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0764ffffffff,  0x1500783ffffffff,  0xffffffffffffffff,  0xffff0561ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0562ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x18d02c2018902b0,  0x195005e019102d6,  0x19d006c01990064,  0x1a5007e01a10076,  0x18e02c3018a02b1,  0x196005f019202d7,  0x19e006d019a0065,  0x1a6007f01a20077,  0x1c0ffffffffffff,  0xffffffffffff01c1,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x5090475ffffffff,  0x511047d050d0477,  0x51904a50515048f,  0x2051f02e80940,  0x6e052707180523,  0x110052f03a4052b,  0x54b053706630533,  0x50e0478050a0476,  0x51604900512047e,  0x2e90941051a04a6,  0x719052400030520,  0x3a5052c006f0528,  0x664053401110530,  0xffffffff054c0538,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xa000800040000,  0x160010000e000c,  0x2ce001c001a0018,  0x56030802e602dc,  0x66006200600058,  0x7800740070006a,  0x2aa00820080007c,  0x64b008400060832,  0x60d060902b805b5,  0x10801060629061d,  0x11c01160112010a,  0xffff0134012c0124,  0xb000900050001,  0x170011000f000d,  0x2cf001d001b0019,  0x57030902e702dd,  0x67006300610059,  0x7900750071006b,  0x2ab00830081007d,  0x64c008500070833,  0x60e060a02b905b6,  0x1090107062a061e,  0x11d01170113010b,  0xffff0135012d0125,  0x486055a04f404f3,  0x48b0297029102b5,  0x49302bb02ba048c,  0x49c02c704980494,  0x2cd02ccffff02cb,  0xffff02d502d4ffff,  0xffffffffffffffff,  0x4b402edffffffff,  0x4ba04b902f902f8,  0x4be04bd04bc04bb,  0x325032404c204c1,  0x3350334032b032a,  0x3410340033d033c,  0x34f034e034d034c,  0x73f073e03230322,  0x3b003af03ae03ad,  0x3bf03be03b403b3,  0x3e203e103d203d1,  0x606060505a405a3,  0x6320631061a0619,  0x6a0069f06560655,  0x7390738046f046e,  0x7d507d4077e077d,  0x6df06de08350834,  0x15001406a406a3,  0x4ee04ed005d005c,  0x612061105020501,  0x542054101aa01a9,  0x566056505540553,  0x586058505760575,  0x5a805a705960595,  0x162016105b805b7,  0x3ea03e903a703a6,  0xffffffffffffffff,  0x60fffffffffffff,  0xffff061806170610,  0x6240623ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x8d008b00890087,  0x9500930091008f,  0x9d009b00990097,  0xa500a300a1009f,  0xad00ab00a900a7,  0xb500b300b100af,  0xbd00bb00b900b7,  0xc500c300c100bf,  0x4d200cb04c004b8,  0x4f8ffff04e600cf,  0xffffffffffffffff,  0xffffffff053affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x616061502d902d8,  0x6220621061c061b,  0x1e501e406280627,  0x6340633062e062d,  0x63e063d06380637,  0x32f032e06420641,  0x66e066d065e065d,  0x68a0689067c067b,  0x6aa06a906980697,  0x6c906c806b806b7,  0x6e706e606d706d6,  0xffffffff06f506f4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x69006807870786,  0x7b107b0028f028e,  0x3bd03bc07c907c8,  0x8030802001f001e,  0x2b302b208190818,  0x2d302d208370836,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7ab07aaffffffff,  0x7b307b207af07ae,  0x2f502f402eb02ea,  0x311031003070306,  0x7cb07caffffffff,  0x7d307d207cf07ce,  0x33b033a03330332,  0x3490348033f033e,  0x7e907e807e707e6,  0x7ed07ec07eb07ea,  0x3b203b103ac03ab,  0x7fb07fa03bb03ba,  0x3f003ef03dc03db,  0x28302820620061f,  0x80b080a08070806,  0x8130812080f080e,  0x81b081a08170816,  0x8210820081f081e,  0x4b004af04a0049f,  0x4d604d504c404c3,  0xffffffffffffffff,  0xffffffffffffffff,  0x83805460545ffff,  0x83d083c083b0839,  0x590058f0580057f,  0x5b205b105a0059f,  0x63bffffffffffff,  0xffffffff079b063c,  0x60c060b06080607,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x630062f062c062b,  0x63a063906360635,  0xffff06440640063f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2fc02fa025e02f6,  0xffff0304030302fe,  0xffffffffffffffff,  0xffffffffffffffff,  0x30cffffffffffff,  0x314031202c0030e,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7ac02da07a8ffff,  0x7b402e402e202e0,  0x144076b02f00724,  0x318081c030a0479,  0x7cc031c031a07c6,  0x32c022c07d00320,  0xffff046c0336007a,  0xffffffffffffffff,  0x7ad02db07a9ffff,  0x7b502e502e302e1,  0x145076c02f10725,  0x319081d030b047a,  0x7cd031d031b07c7,  0x32d022d07d10321,  0xffff046d0337007b,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x72005a09280012,  0x114010c03520338,  0x2f202ae02bc0625,  0x10e064f031608ef,  0x4fb029a02ee02b6,  0x2de02d002c802be,  0x330031e047f0798,  0x3b802c4024e0342,  0x473056b047b03e5,  0x91104df072a06c4,  0x73005b09290013,  0x115010d03530339,  0x2f302af02bd0626,  0x10f0650031708f0,  0x4fc029b02ef02b7,  0x2df02d102c902bf,  0x331031f04800799,  0x3b902c5024f0343,  0x474056c047c03e6,  0x91204e0072b06c5,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//4000 bytes
+enum alphaTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0xb0], [ 0x100,  0x240,  0x5100], [ 0x706050403020100,  0xe0d0c0a0b0a0908,  0x100a0f0303030303,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x7000600050004,  0xb000a00090008,  0xf000e000d000c,  0x12001100010010,  0x15001400010013,  0x19001800170016,  0x1c0001001b001a,  0x1f001f001e001d,  0x1f001f001f0020,  0x1f001f001f001f,  0x1f002300220021,  0x1f001f00250024,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100260001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x27000100010001,  0x1000100010001,  0x2a002900010028,  0x2e002d002c002b,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x2f000100010001,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x3100300001001f,  0x34003300320001,  0x38003700360035,  0x1f001f001f0039,  0x3d003c003b003a,  0x1f001f001f003e,  0x1f001f0040003f,  0x1f0041001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x42000100010001,  0x1f001f001f0043,  0x1f001f001f001f,  0x1f001f001f001f,  0x1000100010001,  0x1f001f001f0044,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f004500010001,  0x46001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f0047,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x4b004a00490048,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f004c001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1000100010001,  0x1004d00010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x4e000100010001,  0x1f001f001f004f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f004f00010001,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x1f001f001f001f,  0x0,  0x7fffffe07fffffe,  0x420040000000000,  0xff7fffffff7fffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x501f0003ffc3,  0x0,  0x3cdf000000000020,  0xfffffffbffffd740,  0xffbfffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffffffffc03,  0xffffffffffffffff,  0xfffe00ffffffffff,  0xfffffffe027fffff,  0xbfff0000000000ff,  0x707ffffff00b6,  0xffffffff07ff0000,  0xffffc000feffffff,  0xffffffffffffffff,  0x9c00e1fe1fefffff,  0xffffffffffff0000,  0xffffffffffffe000,  0x3ffffffffffff,  0x43007fffffffc00,  0x1ffffcffffff,  0x1ffffff,  0x1ffd00000000,  0x7fff03f000000000,  0xefffffffffffffff,  0xfefe000fffe1dfff,  0xe3c5fdfffff99fee,  0x3000fb080599f,  0xc36dfdfffff987ee,  0x3f00005e021987,  0xe3edfdfffffbbfee,  0xf00011bbf,  0xe3edfdfffff99fee,  0x2000fb0c0199f,  0xc3ffc718d63dc7ec,  0x811dc7,  0xe3effdfffffddfee,  0xf03601ddf,  0xe3effdfffffddfec,  0x6000f40601ddf,  0xe7fffffffffddfec,  0xfc00000f00805ddf,  0x2ffbfffffc7fffec,  0xc0000ff5f807f,  0x7fffffffffffffe,  0x207f,  0x3bffecaefef02596,  0xf000205f,  0x1,  0xfffe1ffffffffeff,  0x1ffffffffeffff03,  0x0,  0xf97fffffffffffff,  0xffffc1e7ffff0000,  0xffffffff3000407f,  0xf7ffffffffff20bf,  0xffffffffffffffff,  0xffffffff3d7f3dff,  0x7f3dffffffff3dff,  0xffffffffff7fff3d,  0xffffffffff3dffff,  0x87ffffff,  0xffffffff0000ffff,  0x1fffffffffffff,  0xfffffffffffffffe,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff9fffffffffff,  0xffffffff07fffffe,  0x1c7ffffffffff,  0xfffff000fdfff,  0xddfff000fffff,  0xffcfffffffffffff,  0x108001ff,  0xffffffff00000000,  0xffffffffffffff,  0xffff07ffffffffff,  0x3fffffffffffff,  0x1ff0fff1fffffff,  0x1f3fffffff0000,  0xffff0fffffffffff,  0x3ff,  0xffffffff0fffffff,  0x1ffffe7fffffff,  0x8000000000,  0x0,  0xffefffffffffffff,  0xfef,  0xfc00f3ffffffffff,  0x3ffbfffffffff,  0x3fffffffffffff,  0x3ffffffffc00e000,  0x0,  0x6fde0000000000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x0,  0xffffffff3f3fffff,  0x3fffffffaaff3f3f,  0x5fdfffffffffffff,  0x1fdc1fff0fcf1fdc,  0x0,  0x8002000000000000,  0x1fff0000,  0x0,  0xf3ffbd503e2ffc84,  0xffffffff000043e0,  0x1ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffc0000000000000,  0x3ffffffffff,  0xffff7fffffffffff,  0xffffffff7fffffff,  0xffffffffffffffff,  0xc781fffffffff,  0xffff20bfffffffff,  0x80ffffffffff,  0x7f7f7f7f007fffff,  0xffffffff7f7f7f7f,  0x800000000000,  0x0,  0x0,  0x0,  0x1f3e03fe000000e0,  0xfffffffffffffffe,  0xfffffffee07fffff,  0xf7ffffffffffffff,  0xfffe3fffffffffe0,  0xffffffffffffffff,  0x7ffffff00007fff,  0xffff000000000000,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffffffffff,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1fff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1fff,  0x3fffffffffff0000,  0xc00ffff1fff,  0x8ff07fffffffffff,  0xffffffff80ffffff,  0xffffffffffff,  0xfffffffcff800000,  0xffffffffffffffff,  0x7ff000f79ff,  0xff00000000000000,  0xfffffff7bb,  0xfffffffffffff,  0xffffffffffffffff,  0x8fc00000000000f,  0xffff07fffffffc00,  0x1fffffff0007ffff,  0xfff7ffffffffffff,  0x8000,  0x7fffffffffffff,  0x47fffff00003fff,  0x7fffffffffffffff,  0x3cffff38000005,  0x7f7f007e7e7e,  0x0,  0x0,  0x7ffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff000fffffffff,  0xffffffffffff87f,  0xffffffffffffffff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7ffdffe0f8007f,  0xffffffffffffffdb,  0x3ffffffffffff,  0xfffffffffff80000,  0x3fffffffffffffff,  0xffffffffffff0000,  0xfffffffffffcffff,  0xfff0000000000ff,  0x0,  0xffdf000000000000,  0xffffffffffffffff,  0x1fffffffffffffff,  0x7fffffe00000000,  0xffffffc007fffffe,  0x7fffffffffffffff,  0x1cfcfcfc,  0xb7ffff7fffffefff,  0x3fff3fff,  0xffffffffffffffff,  0x7ffffffffffffff,  0x0,  0x1fffffffffffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff1fffffff,  0x1ffff,  0xffff00007fffffff,  0x7ff,  0xffffffff3fffffff,  0x3eff0f,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffff,  0x0,  0x91bffffffffffd3f,  0x3fffff,  0x0,  0x0,  0x3ffffff003fffff,  0x0,  0xc0ffffffffffffff,  0x0,  0xffffffeeff06f,  0x1fffffff00000000,  0x0,  0x0,  0x3fffffffffffff,  0x7ffff003fffff,  0x0,  0x0,  0xffffffffffffffff,  0x1ff,  0x0,  0x0,  0xffffffffffffffff,  0x3f,  0x1fffffffffffffc,  0x1ffffff0000,  0x7ffffffffffff,  0x0,  0xffffffffffffffff,  0x1e,  0x0,  0x0,  0x3fffffffffffff,  0x0,  0xffffffffffffffff,  0x7fffffffffff,  0x0,  0x0,  0xffffffffffffffff,  0x7ffffffff,  0x0,  0x0,  0x7fffffffffff,  0x0,  0x0,  0x0,  0x1ffffffffffffff,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0x7fffffffffff001f,  0xfff80000,  0x0,  0x3,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffdfffff,  0xebffde64dfffffff,  0xffffffffffffffef,  0x7bffffffdfdfe7bf,  0xfffffffffffdfc5f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffff3fffffffff,  0xf7fffffff7fffffd,  0xffdfffffffdfffff,  0xffff7fffffff7fff,  0xfffffdfffffffdff,  0xff7,  0xaf7fe96ffffffef,  0x5ef7f796aa96ea84,  0xffffbee0ffffbff,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7fffff,  0x1fffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffff,  0x0,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0]);
+//2304 bytes
+enum markTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x70], [ 0x100,  0x140,  0x2c00], [ 0x402030202020100,  0x207020206020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020208,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1000000000000,  0x5000400030002,  0x9000800070006,  0xd000c000b000a,  0xf00000000000e,  0x10000000000000,  0x14001300120011,  0x160015,  0x17,  0x0,  0x0,  0x190018,  0x1a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b00000000,  0x1f001e001d001c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000000000000,  0x2100000000,  0x220000,  0x0,  0x2300000000,  0x0,  0x250024,  0x2600000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x27000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2900280000,  0x0,  0x0,  0x0,  0x2a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffff,  0x0,  0x0,  0x0,  0x0,  0x3f8,  0x0,  0x0,  0x0,  0xbffffffffffe0000,  0xb6,  0x7ff0000,  0x10000fffff800,  0x0,  0x3d9f9fc00000,  0xffff000000020000,  0x7ff,  0x1ffc000000000,  0xff80000000000,  0x3eeffbc00000,  0xe000000,  0x0,  0x7ffffff000000000,  0xdc0000000000000f,  0xc00feffff,  0xd00000000000000e,  0xc0080399f,  0xd00000000000000e,  0x23000000023987,  0xd00000000000000e,  0xc00003bbf,  0xd00000000000000e,  0xc00c0399f,  0xc000000000000004,  0x803dc7,  0xc00000000000000e,  0xc00603ddf,  0xd00000000000000c,  0xc00603ddf,  0xc00000000000000c,  0xc00803ddf,  0xc,  0xc0000ff5f8400,  0x7f2000000000000,  0x7f80,  0x1bf2000000000000,  0x3f00,  0xc2a0000003000000,  0xfffe000000000000,  0x1ffffffffeffe0df,  0x40,  0x7ffff80000000000,  0x1e3f9dc3c00000,  0x3c00bffc,  0x0,  0x0,  0xe0000000,  0x0,  0x0,  0x1c0000001c0000,  0xc0000000c0000,  0xfff0000000000000,  0x200fffff,  0x3800,  0x0,  0x20000000000,  0x0,  0xfff0fff00000000,  0x0,  0xffff000000000000,  0x301,  0xf800000,  0x9fffffff7fe00000,  0x0,  0x0,  0xfff000000000001f,  0xff8000000001f,  0x3ffe00000007,  0xfffc000000000,  0xfffff000000000,  0x0,  0x0,  0x1c21fffff70000,  0x0,  0x0,  0x0,  0xf000007fffffffff,  0x0,  0x0,  0x0,  0x1ffffffff0000,  0x0,  0x0,  0x0,  0x3800000000000,  0x0,  0x8000000000000000,  0x0,  0xffffffff00000000,  0xfc0000000000,  0x0,  0x6000000,  0x0,  0x0,  0x3ff7800000000000,  0x80000000,  0x3000000000000,  0xf800000844,  0x0,  0xfff0000000000003,  0x3ffff0000001f,  0x3fc000000000,  0xfff80,  0xfff800000000000f,  0x1,  0x7ffe0000000000,  0x800000000003008,  0xc19d000000000000,  0x60f80000000002,  0x0,  0x0,  0x0,  0x37f800000000,  0x40000000,  0x0,  0x0,  0x0,  0x7f0000ffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2000000000000000,  0x870000000000f06e,  0x0,  0x0,  0x0,  0xff00000000000007,  0x7f,  0x7ff000000000007,  0x0,  0x1fff8000000007,  0x0,  0xfff8000000000007,  0x1,  0x0,  0x0,  0xfff80000000000,  0x0,  0x0,  0x7ffffffffffe0000,  0x78000,  0x0,  0x0,  0xf807e3e000000000,  0x3c0000000fe7,  0x0,  0x0,  0x1c,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff,  0x0,  0x0,  0x0,  0x0]);
+//2384 bytes
+enum numberTrieEntries = TrieEntry!(bool, 8, 6, 7)([ 0x0,  0x20,  0xc0], [ 0x100,  0x280,  0x1a80], [ 0x402030202020100,  0x807020202020605,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2000200010000,  0x2000200020002,  0x2000200020002,  0x5000200040003,  0x7000600020002,  0x9000800060006,  0x2000b0006000a,  0x2000d000c000c,  0x20002000e0005,  0x2000f00020002,  0x2000200020002,  0x11000200100002,  0x1300120002000e,  0xc00140002,  0x2000200020015,  0x2000200020002,  0x19001800170016,  0x2000200020002,  0x20002001b001a,  0x1d001c00020002,  0x2000200020002,  0x2000200020002,  0x20002001e0002,  0x2000200020002,  0x2000020002001f,  0x2000200220021,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200060023,  0xc0017000c0024,  0x400020002000c,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000e00020002,  0x26002500020002,  0x28002700020002,  0x2000200230002,  0x2000200020002,  0x2002a00020029,  0x2002c0002002b,  0x2000200020002,  0x200020002002d,  0xc002f0004002e,  0x2000200020002,  0x2000200020002,  0x2000200050002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020030,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2003100020002,  0x2000200020002,  0x32000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2003300020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x2000200020002,  0x3ff000000000000,  0x0,  0x720c000000000000,  0x0,  0x0,  0x0,  0x0,  0x3ff00000000,  0x0,  0x3ff000000000000,  0x0,  0x3ff,  0x0,  0xffc000000000,  0x0,  0x3f0ffc000000000,  0x0,  0xfcffc000000000,  0x0,  0x7ffc000000000,  0x0,  0x7f00ffc000000000,  0x0,  0x3fffc000000000,  0x0,  0x3ff0000,  0xfffff00000000,  0x0,  0x3ff0000,  0x0,  0x0,  0x1ffffe0000000000,  0x0,  0x1c00000000000,  0x0,  0x3ff03ff00000000,  0x0,  0xffc0,  0x0,  0x7ff0000,  0x3ff03ff,  0x0,  0x0,  0x3ff03ff,  0x0,  0x3f1000000000000,  0x3ff,  0x0,  0x0,  0xffffffffffff0000,  0x3e7,  0x0,  0x0,  0xffffffff00000000,  0xfffffff,  0xfffffc0000000000,  0x0,  0xffc0000000000000,  0xfffff,  0x0,  0x0,  0x2000000000000000,  0x70003fe00000080,  0x0,  0x3c0000,  0x0,  0x3ff00000000,  0xfffeff00,  0xfffe0000000003ff,  0x0,  0x3ff00000000,  0x0,  0x3f000000000000,  0x0,  0xfffffffffff80,  0x1ffffffffffffff,  0x400,  0x0,  0xf00000000,  0x402,  0x0,  0x3e0000,  0x0,  0xff000000,  0xfc00000,  0x0,  0x0,  0x60000000000000ff,  0x0,  0xff000000ff000000,  0x0,  0x7fffffff00000000,  0x0,  0xfffffffc0000,  0xffc0000000000000,  0x0,  0xffffffffffffffff,  0x7ffffffff,  0x0,  0x3ffff00000000,  0x0,  0xffffffffffffc000,  0x7ff,  0x0,  0x0,  0x0]);
+//2336 bytes
+enum punctuationTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x60], [ 0x100,  0x100,  0x3100], [ 0x402030202020100,  0x202020202020605,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2000100010000,  0x5000400030001,  0x1000800070006,  0xb000a00090001,  0xd00010001000c,  0x10000f0001000e,  0x14001300120011,  0x1000100010015,  0x17000100010016,  0x18000100010001,  0x1000100190001,  0x1001c001b001a,  0x100010001001d,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1001f0001001e,  0x23002200210020,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x26002500240001,  0x28000100270001,  0x1000100010001,  0x2c002b002a0029,  0x1000100010001,  0x10001002e002d,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x100010001002f,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x8c00f7ee00000000,  0x28000000b8000001,  0x88c0088200000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4000000000000000,  0x80,  0x0,  0x0,  0xfc000000,  0x4000000000000600,  0x18000000000049,  0xc8003600,  0x3c0000000000,  0x0,  0x100000,  0x3fff,  0x0,  0x0,  0x380000000000000,  0x7fff000000000000,  0x40000000,  0x0,  0x0,  0x0,  0x1003000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1000000000000,  0x0,  0x0,  0x0,  0x10000000000000,  0x0,  0xc008000,  0x0,  0x0,  0x3c0000000017fff0,  0x0,  0x20,  0x61f0000,  0x0,  0xfc00,  0x0,  0x800000000000000,  0x0,  0x1ff00000000,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x600000000000,  0x18000000,  0x380000000000,  0x60000000000000,  0x0,  0x0,  0x7700000,  0x7ff,  0x0,  0x0,  0x0,  0x0,  0x30,  0x0,  0x0,  0xc0000000,  0x0,  0x3f7f00000000,  0x0,  0x0,  0x1fc000000,  0x0,  0xf000000000000000,  0xf800000000000000,  0xc000000000000000,  0x0,  0x800ff,  0xffff00ffffff0000,  0x600000007ffbffef,  0x6000,  0x0,  0x60000000f00,  0x0,  0x0,  0x0,  0x0,  0x3fff0000000000,  0x0,  0xffc000000060,  0x0,  0x0,  0x1fffff8,  0x300000000f000000,  0x0,  0x0,  0x0,  0xde00000000000000,  0x0,  0x1000000000000,  0x0,  0x0,  0xfff7fffffffffff,  0x0,  0x0,  0x0,  0x20010000fff3ff0e,  0x0,  0x100000000,  0x800000000000000,  0x0,  0x0,  0x0,  0xc000000000000000,  0xe000,  0x4008000000000000,  0x0,  0xfc000000000000,  0x0,  0xf0000000000000,  0x0,  0x70000000000c000,  0xc00000000000,  0x80000000,  0x0,  0xc0003ffe,  0x0,  0xf0000000,  0x0,  0x30000c0000000,  0x0,  0x0,  0x0,  0x80000000000,  0xc000000000000000,  0x0,  0x0,  0x0,  0xffff000003ff0000,  0xd0bfff7ffff,  0x0,  0x0,  0xb80000018c00f7ee,  0x3fa8000000,  0x0,  0x0,  0x7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x10000,  0x0,  0x800000,  0x0,  0x0,  0x8000000080000000,  0x0,  0x0,  0x0,  0x0,  0x8000000001ff0000,  0x0,  0x0,  0xfe00000000000000,  0x0,  0x0,  0x0,  0x0,  0x3f80,  0xd800000000000000,  0x3,  0x0,  0xf,  0x0,  0x1e0,  0x0,  0xf000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2848 bytes
+enum symbolTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0x70], [ 0x100,  0x140,  0x3d00], [ 0x503040303020100,  0x807030303030306,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x303030303030303,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x7000600050004,  0xa000900080001,  0xe000d000c000b,  0x1000010001000f,  0x11000100010001,  0x13000100120001,  0x14000100010001,  0x18001700160015,  0x1a001700170019,  0x1c0017001b0017,  0x1f001e0001001d,  0x17002200210020,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100230001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x25000100010024,  0x1002700010026,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x28000100010001,  0x2b002a00290001,  0x10001002c0001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x30002f002e002d,  0x32003100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1003300010001,  0x37003600350034,  0x3b003a00390038,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x7000081000000000,  0x5000000140000000,  0x113d37c00000000,  0x80000000800000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffafe0fffc003c,  0x0,  0x20000000000000,  0x30,  0x40000000000000,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x8000,  0x0,  0xc9c0,  0x0,  0x0,  0x6000020040000000,  0x0,  0x0,  0x0,  0x40000000000000,  0x0,  0x0,  0x0,  0xc0c000000000000,  0x0,  0x0,  0x0,  0x2000000000000,  0x0,  0x1000000000000,  0x0,  0x7f8000000000000,  0x0,  0x8000000000000000,  0x0,  0x0,  0x0,  0x200000000000000,  0x0,  0x0,  0x8000000000000000,  0x0,  0x0,  0x0,  0x1500000fce8000e,  0x0,  0xc000000000000000,  0x1e0dfbf,  0x0,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x3ff0000,  0x0,  0x0,  0x0,  0x0,  0x8000000,  0x0,  0x1,  0x0,  0xffffffffc0000000,  0x0,  0x1ff007fe00000000,  0x0,  0x0,  0x0,  0x0,  0xa000000000000000,  0x6000e000e000e003,  0x0,  0x1c00000000040010,  0x7ffffff00001c00,  0x0,  0xc0042afc1d0037b,  0xbc1f,  0xffffffffffff0000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffff9fffffff0ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffffffff,  0x7fffffffff,  0x7ff,  0xfffffffff0000000,  0x3ffffffffff,  0xfffffffffffffffe,  0xffffffffff,  0xfffffffffff00000,  0xffff003fffffff9f,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffffe000007,  0xcffffffff0ffffff,  0xffffffffffffffff,  0x3ff1fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7e000000000,  0x0,  0x0,  0xfffffffffbffffff,  0xfffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfff0000003fffff,  0xc0c00001000c0010,  0x0,  0x18000000,  0x0,  0x0,  0x0,  0xffc30000,  0xfffffffff,  0xfffffc007fffffff,  0xffffffff000100ff,  0x1fffffffffc00,  0x7fffffffffffffff,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0x0,  0x0,  0xffffffffffff0000,  0x7f,  0x3007fffff,  0x0,  0x600,  0x0,  0x3c00f0000000000,  0x0,  0x0,  0x0,  0x0,  0x380000000000000,  0x0,  0x0,  0x20000000000,  0x0,  0xfffc000000000000,  0x3,  0x0,  0x0,  0x0,  0x3000000000000000,  0x0,  0x27400000000,  0x0,  0x0,  0x4000000070000810,  0x50000001,  0x0,  0x30007f7f00000000,  0xff80000000000000,  0xfe00000000000000,  0xfff03ff,  0x1fffffffffff0000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffffffffff,  0xfffffe7fffffffff,  0x1c1fffffffff,  0xffffc3fffffff018,  0x3fffffff,  0xffffffffffffffff,  0x23,  0x0,  0x0,  0xffffffffffffffff,  0x7fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x800000008000002,  0x20000000200000,  0x800000008000,  0x20000000200,  0x8,  0x0,  0x0,  0x0,  0x3000000000000,  0xffff0fffffffffff,  0xffffffffffffffff,  0x7ffe7fff000fffff,  0xfffefffe,  0xffff7fffffff0000,  0xffff0fffffffffff,  0x7ffffff,  0xffffffc000000000,  0x7ffffffffff0007,  0x301ff,  0x0,  0x0,  0xffbf0001ffffffff,  0x1fffffffffffffff,  0xffffffff000fffff,  0x1ffff000007df,  0x7fffffffffffffff,  0xfffffffffffffffd,  0xffffffffffffffff,  0x1effffffffffffff,  0x3fffffffffffffff,  0xffffff000f,  0x0,  0xf800000000000000,  0xffffffffffffffff,  0xffe1,  0xffffffffffffffff,  0x3f,  0xffffffffffffffff,  0xfffffffffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//4576 bytes
+enum graphicalTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x20,  0xb8], [ 0x100,  0x260,  0x6100], [ 0x706050403020100,  0xe0d0c0a0b0a0908,  0x100a0f0303030303,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a11,  0xa0a0a0a0a0a0a0a,  0xa0a0a0a0a0a0a0a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2000100010000,  0x5000400030001,  0x9000800070006,  0xd000c000b000a,  0x10000f0001000e,  0x12001100010001,  0x16001500140013,  0x19000100180017,  0x1c0001001b001a,  0x1e00010001001d,  0x1f000100010001,  0x23002200210020,  0x1002600250024,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100270001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x28000100010001,  0x1000100010001,  0x2b002a00010029,  0x2f002e002d002c,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x30000100010001,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x33003200010031,  0x36003500340001,  0x3a003900380037,  0x3100310031003b,  0x3f003e003d003c,  0x31004100310040,  0x31003100430042,  0x31004400310031,  0x31003100310031,  0x31003100310031,  0x45000100010001,  0x31003100310046,  0x31003100310031,  0x31003100310031,  0x1000100010001,  0x31003100310047,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31004800010001,  0x49003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x3100310031004a,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x4e004d004c004b,  0x5200510050004f,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31005300310031,  0x57005600550054,  0x5b005a00590058,  0x31003100310031,  0x31003100310031,  0x1000100010001,  0x1005c00010001,  0x1000100010001,  0x1000100010001,  0x1000100010001,  0x5d000100010001,  0x3100310031005e,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31005e00010001,  0x31003100310031,  0x310031005f0031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0x31003100310031,  0xffffffff00000000,  0x7fffffffffffffff,  0xffffdfff00000000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7cffffffffffffff,  0xfffffffbffffd7f0,  0xffffffffffffffff,  0xfffe00ffffffffff,  0xfffffffefe7fffff,  0xfffffffffffe86ff,  0x1f07ffffff00ff,  0xffffffffcfffffc0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffdfffffff,  0xffffffffffff3fff,  0xffffffffffffe7ff,  0x3ffffffffffff,  0x7ffffffffffffff,  0x7fff3fffffffffff,  0x4fffffff,  0x1ffd00000000,  0x7ffffff000000000,  0xffffffffffffffff,  0xfeffffffffffffff,  0xf3c5fdfffff99fee,  0xfffffcfb080799f,  0xd36dfdfffff987ee,  0x3fffc05e023987,  0xf3edfdfffffbbfee,  0x3ffcf00013bbf,  0xf3edfdfffff99fee,  0xffffcfb0c0399f,  0xc3ffc718d63dc7ec,  0x7ffffc000813dc7,  0xe3effdfffffddfee,  0xff00ffcf03603ddf,  0xf3effdfffffddfec,  0x6ffcf40603ddf,  0xe7fffffffffddfec,  0xfe3fffcf00807ddf,  0x2ffbfffffc7fffec,  0x1c0000ff5f847f,  0x87fffffffffffffe,  0xfffffff,  0x3bffecaefef02596,  0xf3ff3f5f,  0xffffffffffffffff,  0xfffe1ffffffffeff,  0xdffffffffeffffff,  0x7ffdfff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff20bf,  0xffffffffffffffff,  0xffffffff3d7f3dff,  0x7f3dffffffff3dff,  0xffffffffff7fff3d,  0xffffffffff3dffff,  0x1fffffffe7ffffff,  0xffffffff03ffffff,  0x1fffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff1fffffff,  0x1ffffffffffff,  0x7fffff001fdfff,  0xddfff000fffff,  0xffffffffffffffff,  0x3ff03ff3fffffff,  0xffffffff03ff3fff,  0xffffffffffffff,  0xffff07ffffffffff,  0x3fffffffffffff,  0xfff0fff1fffffff,  0x1f3ffffffffff1,  0xffff0fffffffffff,  0xffffffffc7ff03ff,  0xffffffffcfffffff,  0x9fffffff7fffffff,  0x3fff03ff03ff,  0x0,  0xffffffffffffffff,  0x1fffffffffff0fff,  0xffffffffffffffff,  0xf00fffffffffffff,  0xf8ffffffffffffff,  0xffffffffffffe3ff,  0x0,  0x7fffffffff00ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xf000007fffffffff,  0xffffffff3f3fffff,  0x3fffffffaaff3f3f,  0xffdfffffffffffff,  0x7fdcffffefcfffdf,  0xffff80ffffff07ff,  0xfff30000ffffffff,  0x7ffffff1fff7fff,  0x1ffffffff0000,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff03ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffffffff,  0x7fffffffff,  0xffffffff000007ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfffffffffffffffe,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3ff1fff,  0x0,  0x0,  0xffff7fffffffffff,  0xffffffff7fffffff,  0xffffffffffffffff,  0xfe0fffffffffffff,  0xffff20bfffffffff,  0x800180ffffffffff,  0x7f7f7f7f007fffff,  0xffffffff7f7f7f7f,  0xfffffffffffffff,  0x0,  0xfffffffffbffffff,  0xfffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfff0000003fffff,  0xffffffffffffffff,  0xfffffffffffffffe,  0xfffffffffe7fffff,  0xffffffffffffffff,  0xfffe3fffffffffe0,  0xffffffffffffffff,  0x7ffffffffff7fff,  0xffff000fffffffff,  0xffffffff7fffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7fffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1fff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff1fff,  0xffffffffffff007f,  0xfffffffffff,  0xffffffffffffffff,  0xffffffff80ffffff,  0xffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7ff000f7fff,  0xff00000000000000,  0x3ff0fffffffffff,  0xffffffffffffff,  0xffffffffffffffff,  0xfffffff03ffc01f,  0xffffffffffffffff,  0x1fffffff800fffff,  0xffffffffffffffff,  0xc3ffbfff,  0x7fffffffffffff,  0xffffffff3ff3fff,  0xffffffffffffffff,  0x7ffffff8000007,  0x7f7f007e7e7e,  0x0,  0x0,  0x3ff3fffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff000fffffffff,  0xffffffffffff87f,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffff3fffffffffff,  0xffffffffffffffff,  0x3ffffff,  0x5f7fffffe0f8007f,  0xffffffffffffffdb,  0xffffffffffffffff,  0xfffffffffff80003,  0xffffffffffffffff,  0xffffffffffff0000,  0xfffffffffffcffff,  0x3fff0000000000ff,  0xffff007f03ffffff,  0xffdf0f7ffff7ffff,  0xffffffffffffffff,  0x1fffffffffffffff,  0xfffffffffffffffe,  0xffffffffffffffff,  0x7fffffffffffffff,  0x30007f7f1cfcfcfc,  0xb7ffff7fffffefff,  0x3fff3fff,  0xffffffffffffffff,  0x7ffffffffffffff,  0xff8fffffffffff87,  0xffffffffffffffff,  0xfff07ff,  0x3fffffffffff0000,  0x0,  0x0,  0xffffffff1fffffff,  0x1ffff,  0xffff000f7fffffff,  0x7ff,  0xffffffffbfffffff,  0x3fff0f,  0xffffffffffffffff,  0xffffffffffffffff,  0x3ff3fffffff,  0x0,  0x91bffffffffffd3f,  0xffbfffff,  0x0,  0x0,  0x83ffffff8fffffff,  0x0,  0xc0ffffffffffffff,  0x0,  0x870ffffffeeff06f,  0xffffffff01ff00ff,  0x0,  0x0,  0xfe3fffffffffffff,  0xff07ffffff3fffff,  0x0,  0x0,  0xffffffffffffffff,  0x1ff,  0x0,  0x0,  0x0,  0x7fffffff00000000,  0x0,  0x0,  0xffffffffffffffff,  0xfffffffc3fff,  0xdfffffffffffffff,  0x3ff01ffffff0003,  0xffdfffffffffffff,  0xf,  0xffffffffffffffff,  0x3ff01ff,  0x0,  0x0,  0xffffffffffffff,  0x3ff,  0xffffffffffffffff,  0x7fffffffffff,  0x0,  0x0,  0xffffffffffffffff,  0xf0007ffffffff,  0x0,  0x0,  0x7fffffffffff,  0x0,  0x0,  0x0,  0x1ffffffffffffff,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0x7fffffffffff001f,  0xffff8000,  0x0,  0x3,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffffffffff,  0xfffffe7fffffffff,  0xf807ffffffffffff,  0xffffffffffffffff,  0x3fffffff,  0xffffffffffffffff,  0x3f,  0x0,  0x0,  0xffffffffffffffff,  0x3ffff007fffff,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffdfffff,  0xebffde64dfffffff,  0xffffffffffffffef,  0x7bffffffdfdfe7bf,  0xfffffffffffdfc5f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffff3fffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffcfff,  0xaf7fe96ffffffef,  0x5ef7f796aa96ea84,  0xffffbee0ffffbff,  0x3000000000000,  0xffff0fffffffffff,  0xffffffffffffffff,  0x7ffe7fff000fffff,  0xfffefffe,  0xffff7fffffff07ff,  0xffff0fffffffffff,  0x7ffffff,  0xffffffc000000000,  0x7ffffffffff0007,  0x301ff,  0x0,  0x0,  0xffbf0001ffffffff,  0x1fffffffffffffff,  0xffffffff000fffff,  0x1ffff000007df,  0x7fffffffffffffff,  0xfffffffffffffffd,  0xffffffffffffffff,  0x1effffffffffffff,  0x3fffffffffffffff,  0xffffff000f,  0x0,  0xf800000000000000,  0xffffffffffffffff,  0xffe1,  0xffffffffffffffff,  0x3f,  0xffffffffffffffff,  0xfffffffffffff,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x7fffff,  0x1fffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fffffff,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff,  0x0,  0x0,  0x0,  0x0]);
+//3664 bytes
+enum nonCharacterTrieEntries = TrieEntry!(bool, 7, 4, 4, 6)([ 0x0,  0x10,  0x4c,  0x104], [ 0x80,  0xf0,  0x2e0,  0x3180], [ 0x706050403020100,  0xb0b0b0b0a090808,  0xb0b0b0b0b0b0b0b,  0xb0b0b0b0b0b0b0b,  0xb0b0b0b0b0b0b0b,  0xb0b0b0b0b0b0b0b,  0xb0b0b0b0b0b0b0b,  0xd0808080b0b0b0c,  0xd080808,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3000200010000,  0x7000600050004,  0xb000a00090008,  0xd000d000d000c,  0xe000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xf000d000d000d,  0xd00110010000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d0012000d,  0xd000d000d000d,  0x140013000d000d,  0x18001700160015,  0x1b001b001a0019,  0x1b001b001d001c,  0x1b001b001e000d,  0x1b001b001b001b,  0x1b001b001b001b,  0x20001f001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b0021,  0x1b001b001b001b,  0x1b001b00230022,  0x24001b001b001b,  0x1b001b00260025,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d0027000d,  0x1b00290028000d,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b002a001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b002b,  0x1b001b001b001b,  0x1b001b001b001b,  0x1b001b001b001b,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0x2c000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0xd000d000d000d,  0x2c000d000d000d,  0x0,  0x0,  0x0,  0x200010000,  0x0,  0x6000500040003,  0x7,  0xb000a00090008,  0xf000e000d000c,  0x12001100100000,  0x16001500140013,  0x1a001900180017,  0x1e001d001c001b,  0x2200210020001f,  0x26002500240023,  0x29002800270000,  0x2a000000000000,  0x0,  0x2d002c002b0000,  0x310030002f002e,  0x0,  0x0,  0x33003200000000,  0x36000000350034,  0x3a003900380037,  0x3e003d003c003b,  0x4200410040003f,  0x44000000430000,  0x47004200460045,  0x48000000000000,  0x0,  0x4c004b004a0049,  0x4f004e004d0000,  0x5000000000,  0x0,  0x51000000000000,  0x530052,  0x0,  0x0,  0x54,  0x0,  0x0,  0x0,  0x42004200550000,  0x58000000570056,  0x5c005b005a0059,  0x51005e0042005d,  0x5f000000000000,  0x6000540000,  0x63006200000061,  0x64000000000057,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3a00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x65000000000000,  0x67006600000000,  0x0,  0x38006900000068,  0x6b006a00000000,  0x6d00000038006c,  0x6f0000006e0000,  0x72000000710070,  0x74004200420073,  0x0,  0x0,  0x0,  0x75006300000000,  0x0,  0x0,  0x77000000760000,  0x7a000000790078,  0x0,  0x7d007c007b0000,  0x800000007f007e,  0x81006400000054,  0xb000000830082,  0x86008500000084,  0x87003200420042,  0x8b008a00890088,  0x42008c00000000,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x420042008e008d,  0x4200900042008f,  0x42004200920091,  0x42004200940093,  0x42004200950000,  0x42004200420042,  0x42004200960042,  0x42004200420042,  0x98000000970000,  0x9a00000099004b,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x9b003800420042,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x0,  0x0,  0x0,  0x420042009c0000,  0x420042009d0000,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x4200420042009c,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x0,  0x0,  0x4200420042009e,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x4200a0009f0000,  0x420042004200a1,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x3a000000000000,  0xa30000000000a2,  0x42004200a40000,  0x42004200a50000,  0xa800a700a60000,  0xaa00a9,  0xab00000000,  0xac000000000000,  0x42004200420042,  0x42004200420042,  0xb000af00ae00ad,  0x42004200420042,  0xb200b10000003d,  0xb500b4003d00b3,  0x42004200b700b6,  0xbb00ba00b900b8,  0xbd000000bc0064,  0xc0004200bf00be,  0xa4000000c10000,  0x42004200510000,  0x0,  0x0,  0xc2000000000000,  0x0,  0x0,  0x0,  0x0,  0x31,  0x420042004200a3,  0x42004200420042,  0x42004200420042,  0x42004200420042,  0x0,  0x0,  0x420042004200a3,  0x42004200420042,  0x420042000000c3,  0xc4000000000000,  0x42004200420042,  0x42004200420042,  0x0,  0x0,  0x0,  0xbe000000000000,  0x0,  0x0,  0x0,  0xbe000000000000,  0x0,  0x8300000000000000,  0x40000280f,  0x1ff0000000000,  0x101800000,  0x17900,  0xffe0f8000000ff00,  0x20000020,  0x4000,  0x1800,  0xfffc000000000000,  0xf800000000000000,  0x8000c00000000000,  0xffffffffb0000000,  0xffffe002ffffffff,  0x8000000fffffffff,  0x100000000000000,  0xc3a020000066011,  0xf00000304f7f8660,  0x2c92020000067811,  0xffc0003fa1fdc678,  0xc12020000044011,  0xfffc0030fffec440,  0xc12020000066011,  0xff0000304f3fc660,  0x3c0038e729c23813,  0xf800003fff7ec238,  0x1c10020000022011,  0xff0030fc9fc220,  0xc10020000022013,  0xfff90030bf9fc220,  0x1800000000022013,  0x1c00030ff7f8220,  0xd004000003800013,  0xffe3ffff00a07b80,  0x7800000000000001,  0xfffffffff0000000,  0xc4001351010fda69,  0xffffffff0c00c0a0,  0x1e00000000100,  0x2000000001000000,  0xfffffffff8002000,  0xdf40,  0xc280c200,  0x80c200000000c200,  0x8000c2,  0xc20000,  0xe000000018000000,  0xfc000000,  0xffe0000000000000,  0xe0000000,  0xfffe000000000000,  0xff800000ffe02000,  0xfff22000fff00000,  0xfc00fc00c0000000,  0xfc008000,  0xff00000000000000,  0xf80000000000,  0xffc0000000000000,  0xf000f000e0000000,  0xffe0c0000000000e,  0xf00000000000,  0x3800fc00,  0x30000000,  0x6000000080000000,  0xffffc000fc00fc00,  0xffffffffffffffff,  0xe00000000000f000,  0xff0000000000000,  0x700000000000000,  0x1c00,  0xff8000000000ff00,  0xfffff8000000000,  0xc0c00000,  0xc00000005500c0c0,  0x20000000000000,  0x8023000010300020,  0xc002000000000,  0xf8000000e0008000,  0xfffe00000000ffff,  0xfc00,  0xfff0000000000000,  0xffffff8000000000,  0xfffff800,  0x1,  0xfffffffffc00e000,  0x800000000000,  0x80000000,  0x1f0000000000000,  0xdf4000000000,  0x7ffe7f0000000000,  0x80808080ff800000,  0x80808080,  0xf000000000000000,  0x4000000,  0xf000ffffffc00000,  0x1800000,  0x1c0000000001f,  0xf800000000008000,  0xfff000000000,  0x8000000000000000,  0xffffffffffffe000,  0xe000,  0xff80,  0xfffff00000000000,  0x7f000000,  0xfffff800fff08000,  0xffffffffffffff,  0xfc00f00000000000,  0xf0000000fc003fe0,  0xe00000007ff00000,  0xffffffff3c004000,  0xff80000000000000,  0xf00000000c00c000,  0xff80000007fffff8,  0xffff8080ff818181,  0xfc00c00000000000,  0xf000000000000780,  0xc00000000000,  0xfffffffffc000000,  0xa08000001f07ff80,  0x24,  0x7fffc,  0xffff,  0x30000,  0xc000ffffffffff00,  0xff80fc000000,  0x20f08000080000,  0x6000000000000000,  0xc1ff8080e3030303,  0x4800008000001000,  0xffffffffc000c000,  0x70000000000078,  0xfffffffff000f800,  0xc00000000000ffff,  0xfffffffffffe0000,  0xfff080000000,  0xfffffffffffff800,  0x40000000,  0xffffffffffc000f0,  0xfffffc00c0000000,  0x6e400000000002c0,  0xffffffff00400000,  0x7c00000070000000,  0x3f00000000000000,  0x78f0000001100f90,  0xfe00ff00,  0x1c0000000000000,  0xf8000000c00000,  0xfffffffffffffe00,  0x80000000ffffffff,  0xffff00000003c000,  0xfc00fe000000fffc,  0xfffffffffffffff0,  0xfffffffffc00fe00,  0xfffffffffffffc00,  0xffff800000000000,  0xfff0fff800000000,  0xfe00000000000000,  0x800000000000ffe0,  0xffffffff00007fff,  0xfffffffffffffffc,  0x18000000000,  0xffffffffc0000000,  0xffffffffffffffc0,  0xfffc0000ff800000,  0x200000,  0x1400219b20000000,  0x10,  0x8400000020201840,  0x203a0,  0xc000000000,  0x3000,  0xf508016900000010,  0xa10808695569157b,  0xf0000411f0000400,  0xfffcffffffffffff,  0x80018000fff00000,  0xffffffff00010001,  0x80000000f800,  0xfffffffff8000000,  0x3fffffffff,  0xf80000000000fff8,  0xfffffffffffcfe00,  0x40fffe00000000,  0xe000000000000000,  0xfff00000,  0xfffe0000fffff820,  0x2,  0xe100000000000000,  0xc000000000000000,  0xffffff000000fff0,  0x7ffffffffffffff,  0xffffffffffff001e,  0xffffffffff800000,  0xfffffffd,  0xffff000000000000,  0xc000000000000000]);
+enum MAX_SIMPLE_LOWER = 1043;
+enum MAX_SIMPLE_UPPER = 1051;
+enum MAX_SIMPLE_TITLE = 1055;
+//8192 bytes
+enum toUpperIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xc00], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x19001800170016,  0x1d001c001b001a,  0x0,  0x1f001e0000,  0x0,  0x0,  0x20000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x24002300220021,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2700260000,  0x2a00290028,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x2c0000,  0x0,  0x0,  0x0,  0x0,  0x2e002d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff001affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x41bffffffffffff,  0x1e001d001c001b,  0x2200210020001f,  0x26002500240023,  0x2a002900280027,  0x2e002d002c002b,  0xffff00310030002f,  0x35003400330032,  0x39003800370036,  0x3bffff003affff,  0x3dffff003cffff,  0x3fffff003effff,  0x41ffff0040ffff,  0x43ffff0042ffff,  0x45ffff0044ffff,  0x47ffff0046ffff,  0x49ffff0048ffff,  0x4bffff004affff,  0x4dffff004cffff,  0x4fffff004effff,  0x51ffff0050ffff,  0x53ffff0052041d,  0x55ffff0054ffff,  0xffff0056ffffffff,  0xffff0058ffff0057,  0xffff005affff0059,  0xffff005cffff005b,  0x5effff043a005d,  0x60ffff005fffff,  0x62ffff0061ffff,  0x64ffff0063ffff,  0x66ffff0065ffff,  0x68ffff0067ffff,  0x6affff0069ffff,  0x6cffff006bffff,  0x6effff006dffff,  0x70ffff006fffff,  0x72ffff0071ffff,  0x74ffff0073ffff,  0xffff0075ffffffff,  0x780077ffff0076,  0x7affffffff0079,  0xffffffff007bffff,  0xffffffffffff007c,  0xffffffffffff007d,  0xffff007effffffff,  0xffffffff007fffff,  0xffff00810080ffff,  0xffff0082ffffffff,  0x84ffff0083ffff,  0xffffffff0085ffff,  0xffffffffffff0086,  0xffffffff0087ffff,  0xffffffffffff0088,  0xffff008affff0089,  0xffffffff008bffff,  0x8dffff008cffff,  0xffffffffffffffff,  0xffff008f008effff,  0x92ffff00910090,  0xffff0094ffff0093,  0xffff0096ffff0095,  0xffff0098ffff0097,  0xffff009affff0099,  0x9dffff009c009b,  0x9fffff009effff,  0xa1ffff00a0ffff,  0xa3ffff00a2ffff,  0xa5ffff00a4ffff,  0xa700a6ffff0442,  0xffffffff00a8ffff,  0xaaffff00a9ffff,  0xacffff00abffff,  0xaeffff00adffff,  0xb0ffff00afffff,  0xb2ffff00b1ffff,  0xb4ffff00b3ffff,  0xb6ffff00b5ffff,  0xb8ffff00b7ffff,  0xbaffff00b9ffff,  0xbcffff00bbffff,  0xbdffffffffffff,  0xbfffff00beffff,  0xc1ffff00c0ffff,  0xc3ffff00c2ffff,  0xc5ffff00c4ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xc7ffffffff00c6,  0xffff00c9ffff00c8,  0xcaffffffffffff,  0xccffff00cbffff,  0xceffff00cdffff,  0xd200d100d000cf,  0xd500d4ffff00d3,  0xd7ffff00d6ffff,  0xffffffffffffffff,  0xd9ffffffff00d8,  0xffff00db00daffff,  0xdeffff00dd00dc,  0xdfffffffffffff,  0xffff00e100e0ffff,  0xffffffff00e2ffff,  0xffffffffffffffff,  0xffffffff00e3ffff,  0xe5ffffffff00e4,  0xffffffffffffffff,  0xe900e800e700e6,  0xffffffffffff00ea,  0xffff00ebffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff00ecffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xeeffff00edffff,  0xefffffffffffff,  0xf0ffffffffffff,  0xffffffff00f200f1,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff043c,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xf600f500f400f3,  0xf900f800f7043f,  0xfd00fc00fb00fa,  0x101010000ff00fe,  0x105010401030102,  0x109010801070106,  0x10d010c010b010a,  0x1110110010f010e,  0xffff011401130112,  0xffffffff01160115,  0x11901180117ffff,  0x11bffff011affff,  0x11dffff011cffff,  0x11fffff011effff,  0x121ffff0120ffff,  0x123ffff0122ffff,  0x125ffff0124ffff,  0xffff012801270126,  0xffffffff0129ffff,  0x12bffffffff012a,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x12f012e012d012c,  0x133013201310130,  0x137013601350134,  0x13b013a01390138,  0x13f013e013d013c,  0x143014201410140,  0x147014601450144,  0x14b014a01490148,  0x14f014e014d014c,  0x153015201510150,  0x157015601550154,  0x15b015a01590158,  0x15dffff015cffff,  0x15fffff015effff,  0x161ffff0160ffff,  0x163ffff0162ffff,  0x165ffff0164ffff,  0x167ffff0166ffff,  0x169ffff0168ffff,  0x16bffff016affff,  0xffffffff016cffff,  0xffffffffffffffff,  0x16dffffffffffff,  0x16fffff016effff,  0x171ffff0170ffff,  0x173ffff0172ffff,  0x175ffff0174ffff,  0x177ffff0176ffff,  0x179ffff0178ffff,  0x17bffff017affff,  0x17dffff017cffff,  0x17fffff017effff,  0x181ffff0180ffff,  0x183ffff0182ffff,  0x185ffff0184ffff,  0x187ffff0186ffff,  0xffff0188ffffffff,  0xffff018affff0189,  0xffff018cffff018b,  0x18f018effff018d,  0x191ffff0190ffff,  0x193ffff0192ffff,  0x195ffff0194ffff,  0x197ffff0196ffff,  0x199ffff0198ffff,  0x19bffff019affff,  0x19dffff019cffff,  0x19fffff019effff,  0x1a1ffff01a0ffff,  0x1a3ffff01a2ffff,  0x1a5ffff01a4ffff,  0x1a7ffff01a6ffff,  0x1a9ffff01a8ffff,  0x1abffff01aaffff,  0x1adffff01acffff,  0x1afffff01aeffff,  0x1b1ffff01b0ffff,  0x1b3ffff01b2ffff,  0x1b5ffff01b4ffff,  0x1b7ffff01b6ffff,  0x1b9ffff01b8ffff,  0x1bbffff01baffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1be01bd01bcffff,  0x1c201c101c001bf,  0x1c601c501c401c3,  0x1ca01c901c801c7,  0x1ce01cd01cc01cb,  0x1d201d101d001cf,  0x1d601d501d401d3,  0x1da01d901d801d7,  0x1de01dd01dc01db,  0x42e01e101e001df,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff01e2ffff,  0xffffffff01e3ffff,  0x1e5ffff01e4ffff,  0x1e7ffff01e6ffff,  0x1e9ffff01e8ffff,  0x1ebffff01eaffff,  0x1edffff01ecffff,  0x1efffff01eeffff,  0x1f1ffff01f0ffff,  0x1f3ffff01f2ffff,  0x1f5ffff01f4ffff,  0x1f7ffff01f6ffff,  0x1f9ffff01f8ffff,  0x1fbffff01faffff,  0x1fdffff01fcffff,  0x1ffffff01feffff,  0x201ffff0200ffff,  0x203ffff0202ffff,  0x205ffff0204ffff,  0x207ffff0206ffff,  0x209ffff0208ffff,  0x20bffff020affff,  0x20dffff020cffff,  0x20fffff020effff,  0x211ffff0210ffff,  0x213ffff0212ffff,  0x215ffff0214ffff,  0x217ffff0216ffff,  0x219ffff0218ffff,  0x21bffff021affff,  0x21dffff021cffff,  0x21fffff021effff,  0x221ffff0220ffff,  0x223ffff0222ffff,  0x225ffff0224ffff,  0x227ffff0226ffff,  0x229ffff0228ffff,  0x22bffff022affff,  0x22dffff022cffff,  0x4460444022effff,  0x22f044c044a0448,  0xffffffffffffffff,  0x231ffff0230ffff,  0x233ffff0232ffff,  0x235ffff0234ffff,  0x237ffff0236ffff,  0x239ffff0238ffff,  0x23bffff023affff,  0x23dffff023cffff,  0x23fffff023effff,  0x241ffff0240ffff,  0x243ffff0242ffff,  0x245ffff0244ffff,  0x247ffff0246ffff,  0x249ffff0248ffff,  0x24bffff024affff,  0x24dffff024cffff,  0x24fffff024effff,  0x251ffff0250ffff,  0x253ffff0252ffff,  0x255ffff0254ffff,  0x257ffff0256ffff,  0x259ffff0258ffff,  0x25bffff025affff,  0x25dffff025cffff,  0x25fffff025effff,  0x263026202610260,  0x267026602650264,  0xffffffffffffffff,  0xffffffffffffffff,  0x26b026a02690268,  0xffffffff026d026c,  0xffffffffffffffff,  0xffffffffffffffff,  0x2710270026f026e,  0x275027402730272,  0xffffffffffffffff,  0xffffffffffffffff,  0x279027802770276,  0x27d027c027b027a,  0xffffffffffffffff,  0xffffffffffffffff,  0x2810280027f027e,  0xffffffff02830282,  0xffffffffffffffff,  0xffffffffffffffff,  0x28504500284044e,  0x287045602860453,  0xffffffffffffffff,  0xffffffffffffffff,  0x28b028a02890288,  0x28f028e028d028c,  0xffffffffffffffff,  0xffffffffffffffff,  0x293029202910290,  0x297029602950294,  0x29b029a02990298,  0xffffffff029d029c,  0x47d047b04790477,  0x48504830481047f,  0x48d048b04890487,  0x49504930491048f,  0x49d049b04990497,  0x4a504a304a1049f,  0x4ad04ab04a904a7,  0x4b504b304b104af,  0x4bd04bb04b904b7,  0x4c504c304c104bf,  0x4cd04cb04c904c7,  0x4d504d304d104cf,  0x4d704e302b702b6,  0x4ef0459ffff04e5,  0xffffffffffffffff,  0xffff02b9ffff04d9,  0x4db04e7ffffffff,  0x4f2045bffff04e9,  0xffffffffffffffff,  0xffffffffffff04dd,  0x460045d02bc02bb,  0x4650463ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x46b046802be02bd,  0x472047002bf046e,  0xffffffffffffffff,  0xffffffffffffffff,  0x4df04ebffffffff,  0x4f50475ffff04ed,  0xffffffffffffffff,  0xffffffffffff04e1,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02c1ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c502c402c302c2,  0x2c902c802c702c6,  0x2cd02cc02cb02ca,  0x2d102d002cf02ce,  0xffffffffffffffff,  0xffffffffffff02d2,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2d602d502d402d3,  0x2da02d902d802d7,  0x2de02dd02dc02db,  0x2e202e102e002df,  0x2e602e502e402e3,  0x2ea02e902e802e7,  0xffffffff02ec02eb,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2f002ef02ee02ed,  0x2f402f302f202f1,  0x2f802f702f602f5,  0x2fc02fb02fa02f9,  0x30002ff02fe02fd,  0x304030303020301,  0x308030703060305,  0x30c030b030a0309,  0x310030f030e030d,  0x314031303120311,  0x318031703160315,  0xffff031b031a0319,  0xffffffff031cffff,  0xffff031e031dffff,  0xffff0320ffff031f,  0xffffffffffff0321,  0x322ffffffffffff,  0xffff0323ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x325ffff0324ffff,  0x327ffff0326ffff,  0x329ffff0328ffff,  0x32bffff032affff,  0x32dffff032cffff,  0x32fffff032effff,  0x331ffff0330ffff,  0x333ffff0332ffff,  0x335ffff0334ffff,  0x337ffff0336ffff,  0x339ffff0338ffff,  0x33bffff033affff,  0x33dffff033cffff,  0x33fffff033effff,  0x341ffff0340ffff,  0x343ffff0342ffff,  0x345ffff0344ffff,  0x347ffff0346ffff,  0x349ffff0348ffff,  0x34bffff034affff,  0x34dffff034cffff,  0x34fffff034effff,  0x351ffff0350ffff,  0x353ffff0352ffff,  0x355ffff0354ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0357ffff0356,  0x358ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x35c035b035a0359,  0x360035f035e035d,  0x364036303620361,  0x368036703660365,  0x36c036b036a0369,  0x370036f036e036d,  0x374037303720371,  0x378037703760375,  0x37c037b037a0379,  0x37fffff037e037d,  0xffffffffffffffff,  0xffffffff0380ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x382ffff0381ffff,  0x384ffff0383ffff,  0x386ffff0385ffff,  0x388ffff0387ffff,  0x38affff0389ffff,  0x38cffff038bffff,  0x38effff038dffff,  0x390ffff038fffff,  0x392ffff0391ffff,  0x394ffff0393ffff,  0x396ffff0395ffff,  0xffffffff0397ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x399ffff0398ffff,  0x39bffff039affff,  0x39dffff039cffff,  0x39fffff039effff,  0x3a1ffff03a0ffff,  0x3a3ffff03a2ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3a4ffffffffffff,  0x3a6ffff03a5ffff,  0x3a8ffff03a7ffff,  0x3aaffff03a9ffff,  0x3abffffffffffff,  0x3adffff03acffff,  0x3afffff03aeffff,  0x3b1ffff03b0ffff,  0x3b3ffff03b2ffff,  0x3b5ffff03b4ffff,  0x3b7ffff03b6ffff,  0x3b9ffff03b8ffff,  0x3bbffff03baffff,  0x3bdffff03bcffff,  0x3bfffff03beffff,  0x3c1ffff03c0ffff,  0x3c3ffff03c2ffff,  0x3c5ffff03c4ffff,  0x3c7ffff03c6ffff,  0x3c9ffff03c8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03caffffffff,  0x3ccffffffff03cb,  0x3ceffff03cdffff,  0x3d0ffff03cfffff,  0xffffffffffffffff,  0xffffffffffff03d1,  0x3d3ffff03d2ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d5ffff03d4ffff,  0x3d7ffff03d6ffff,  0xffffffff03d8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x42404220420041e,  0xffff042c042a0427,  0xffffffffffffffff,  0xffffffffffffffff,  0x430ffffffffffff,  0x438043604340432,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3db03da03d9ffff,  0x3df03de03dd03dc,  0x3e303e203e103e0,  0x3e703e603e503e4,  0x3eb03ea03e903e8,  0x3ef03ee03ed03ec,  0xffff03f203f103f0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3f603f503f403f3,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0x416041504140413,  0x41a041904180417,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//8064 bytes
+enum toLowerIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xbc0], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x2000000010000,  0x6000500040003,  0x80007,  0xb000a00090000,  0xf000e000d000c,  0x1200110010,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x14001300000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x18001700160015,  0x1c001b001a0019,  0x0,  0x1f001e001d,  0x0,  0x0,  0x21002000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x25002400230022,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2700260000,  0x2a00290028,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x2c,  0x0,  0x0,  0x0,  0x0,  0x2d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1d001c001b001a,  0x210020001f001e,  0x25002400230022,  0x29002800270026,  0x2d002c002b002a,  0xffff0030002f002e,  0x34003300320031,  0x413003700360035,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0039ffff0038,  0xffff003bffff003a,  0xffff003dffff003c,  0xffff003fffff003e,  0xffff0041ffff0040,  0xffff0043ffff0042,  0xffff0045ffff0044,  0xffff0047ffff0046,  0xffff0049ffff0048,  0xffff004bffff004a,  0xffff004dffff004c,  0xffff004fffff004e,  0xffff0051ffff0414,  0xffff0053ffff0052,  0x55ffff0054ffff,  0x57ffff0056ffff,  0x59ffff0058ffff,  0x5bffff005affff,  0xffff005c0423ffff,  0xffff005effff005d,  0xffff0060ffff005f,  0xffff0062ffff0061,  0xffff0064ffff0063,  0xffff0066ffff0065,  0xffff0068ffff0067,  0xffff006affff0069,  0xffff006cffff006b,  0xffff006effff006d,  0xffff0070ffff006f,  0xffff0072ffff0071,  0x75ffff00740073,  0xffffffff0076ffff,  0xffff00780077ffff,  0x7b007affff0079,  0x7e007d007cffff,  0x80007fffffffff,  0x83ffff00820081,  0x860085ffff0084,  0xffffffffffff0087,  0x8affff00890088,  0xffff008cffff008b,  0x8f008effff008d,  0xffffffff0090ffff,  0x930092ffff0091,  0x9600950094ffff,  0x98ffff0097ffff,  0xffffffffffff0099,  0xffffffffffff009a,  0xffffffffffffffff,  0x9dffff009c009b,  0xa0009fffff009e,  0xa2ffff00a1ffff,  0xa4ffff00a3ffff,  0xa6ffff00a5ffff,  0xa8ffff00a7ffff,  0xffff00a9ffffffff,  0xffff00abffff00aa,  0xffff00adffff00ac,  0xffff00afffff00ae,  0xffff00b1ffff00b0,  0xffff00b300b20426,  0xb600b5ffff00b4,  0xffff00b8ffff00b7,  0xffff00baffff00b9,  0xffff00bcffff00bb,  0xffff00beffff00bd,  0xffff00c0ffff00bf,  0xffff00c2ffff00c1,  0xffff00c4ffff00c3,  0xffff00c6ffff00c5,  0xffff00c8ffff00c7,  0xffff00caffff00c9,  0xffff00ccffff00cb,  0xffff00ceffff00cd,  0xffff00d0ffff00cf,  0xffff00d2ffff00d1,  0xffff00d4ffff00d3,  0xffffffffffffffff,  0xd600d5ffffffff,  0xffff00d800d7ffff,  0xdaffff00d9ffff,  0xffff00dd00dc00db,  0xffff00dfffff00de,  0xffff00e1ffff00e0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff00e3ffff00e2,  0xffff00e4ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff00e5ffffffff,  0xffff00e800e700e6,  0xeb00eaffff00e9,  0xee00ed00ec0424,  0xf200f100f000ef,  0xf600f500f400f3,  0xfa00f900f800f7,  0xfdffff00fc00fb,  0x101010000ff00fe,  0x105010401030102,  0xffffffffffffffff,  0xffffffffffff0425,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x106ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0108ffff0107,  0xffff010affff0109,  0xffff010cffff010b,  0xffff010effff010d,  0xffff0110ffff010f,  0xffff0112ffff0111,  0xffffffffffffffff,  0x114ffffffff0113,  0xffff01160115ffff,  0x11901180117ffff,  0x11d011c011b011a,  0x1210120011f011e,  0x125012401230122,  0x129012801270126,  0x12d012c012b012a,  0x1310130012f012e,  0x135013401330132,  0x139013801370136,  0x13d013c013b013a,  0x1410140013f013e,  0x145014401430142,  0x149014801470146,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff014bffff014a,  0xffff014dffff014c,  0xffff014fffff014e,  0xffff0151ffff0150,  0xffff0153ffff0152,  0xffff0155ffff0154,  0xffff0157ffff0156,  0xffff0159ffff0158,  0xffffffffffff015a,  0xffffffffffffffff,  0xffff015bffffffff,  0xffff015dffff015c,  0xffff015fffff015e,  0xffff0161ffff0160,  0xffff0163ffff0162,  0xffff0165ffff0164,  0xffff0167ffff0166,  0xffff0169ffff0168,  0xffff016bffff016a,  0xffff016dffff016c,  0xffff016fffff016e,  0xffff0171ffff0170,  0xffff0173ffff0172,  0xffff0175ffff0174,  0x178ffff01770176,  0x17affff0179ffff,  0x17cffff017bffff,  0xffffffff017dffff,  0xffff017fffff017e,  0xffff0181ffff0180,  0xffff0183ffff0182,  0xffff0185ffff0184,  0xffff0187ffff0186,  0xffff0189ffff0188,  0xffff018bffff018a,  0xffff018dffff018c,  0xffff018fffff018e,  0xffff0191ffff0190,  0xffff0193ffff0192,  0xffff0195ffff0194,  0xffff0197ffff0196,  0xffff0199ffff0198,  0xffff019bffff019a,  0xffff019dffff019c,  0xffff019fffff019e,  0xffff01a1ffff01a0,  0xffff01a3ffff01a2,  0xffff01a5ffff01a4,  0xffff01a7ffff01a6,  0xffff01a9ffff01a8,  0xffffffffffffffff,  0xffffffffffffffff,  0x1ac01ab01aaffff,  0x1b001af01ae01ad,  0x1b401b301b201b1,  0x1b801b701b601b5,  0x1bc01bb01ba01b9,  0x1c001bf01be01bd,  0x1c401c301c201c1,  0x1c801c701c601c5,  0x1cc01cb01ca01c9,  0xffff01cf01ce01cd,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x41dffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1d301d201d101d0,  0x1d701d601d501d4,  0x1db01da01d901d8,  0x1df01de01dd01dc,  0x1e301e201e101e0,  0x1e701e601e501e4,  0x1eb01ea01e901e8,  0x1ef01ee01ed01ec,  0x1f301f201f101f0,  0x1f6ffff01f501f4,  0xffffffffffffffff,  0xffffffff01f7ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff01f9ffff01f8,  0xffff01fbffff01fa,  0xffff01fdffff01fc,  0xffff01ffffff01fe,  0xffff0201ffff0200,  0xffff0203ffff0202,  0xffff0205ffff0204,  0xffff0207ffff0206,  0xffff0209ffff0208,  0xffff020bffff020a,  0xffff020dffff020c,  0xffff020fffff020e,  0xffff0211ffff0210,  0xffff0213ffff0212,  0xffff0215ffff0214,  0xffff0217ffff0216,  0xffff0219ffff0218,  0xffff021bffff021a,  0xffff021dffff021c,  0xffff021fffff021e,  0xffff0221ffff0220,  0xffff0223ffff0222,  0xffff0225ffff0224,  0xffff0227ffff0226,  0xffff0229ffff0228,  0xffff022bffff022a,  0xffff022dffff022c,  0xffff022fffff022e,  0xffff0231ffff0230,  0xffff0233ffff0232,  0xffff0235ffff0234,  0xffff0237ffff0236,  0xffff0239ffff0238,  0xffff023bffff023a,  0xffff023dffff023c,  0xffff023fffff023e,  0xffff0241ffff0240,  0x4280427ffff0242,  0xffff042b042a0429,  0xffff0243ffffffff,  0xffff0245ffff0244,  0xffff0247ffff0246,  0xffff0249ffff0248,  0xffff024bffff024a,  0xffff024dffff024c,  0xffff024fffff024e,  0xffff0251ffff0250,  0xffff0253ffff0252,  0xffff0255ffff0254,  0xffff0257ffff0256,  0xffff0259ffff0258,  0xffff025bffff025a,  0xffff025dffff025c,  0xffff025fffff025e,  0xffff0261ffff0260,  0xffff0263ffff0262,  0xffff0265ffff0264,  0xffff0267ffff0266,  0xffff0269ffff0268,  0xffff026bffff026a,  0xffff026dffff026c,  0xffff026fffff026e,  0xffff0271ffff0270,  0xffff0273ffff0272,  0xffffffffffffffff,  0xffffffffffffffff,  0x277027602750274,  0x27b027a02790278,  0xffffffffffffffff,  0xffffffffffffffff,  0x27f027e027d027c,  0xffffffff02810280,  0xffffffffffffffff,  0xffffffffffffffff,  0x285028402830282,  0x289028802870286,  0xffffffffffffffff,  0xffffffffffffffff,  0x28d028c028b028a,  0x2910290028f028e,  0xffffffffffffffff,  0xffffffffffffffff,  0x295029402930292,  0xffffffff02970296,  0xffff042dffff042c,  0xffff042fffff042e,  0x299ffff0298ffff,  0x29bffff029affff,  0xffffffffffffffff,  0xffffffffffffffff,  0x29f029e029d029c,  0x2a302a202a102a0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x43f043e043d043c,  0x443044204410440,  0x447044604450444,  0x44b044a04490448,  0x44f044e044d044c,  0x453045204510450,  0x457045604550454,  0x45b045a04590458,  0x45f045e045d045c,  0x463046204610460,  0x467046604650464,  0x46b046a04690468,  0x46c0472ffffffff,  0x4780430ffff0473,  0x2bf02be02bd02bc,  0xffffffffffff046d,  0x46e0474ffffffff,  0x4790431ffff0475,  0x2c402c302c202c1,  0xffffffffffff046f,  0x4330432ffffffff,  0x4350434ffffffff,  0x2c902c802c702c6,  0xffffffffffffffff,  0x4370436ffffffff,  0x43a0439ffff0438,  0x2cd02cc02cb02ca,  0xffffffffffff02ce,  0x4700476ffffffff,  0x47a043bffff0477,  0x2d202d102d002cf,  0xffffffffffff0471,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02d4ffffffff,  0x2d602d5ffffffff,  0xffffffffffffffff,  0xffff02d7ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2db02da02d902d8,  0x2df02de02dd02dc,  0x2e302e202e102e0,  0x2e702e602e502e4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2e8ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2ea02e9ffffffff,  0x2ee02ed02ec02eb,  0x2f202f102f002ef,  0x2f602f502f402f3,  0x2fa02f902f802f7,  0x2fe02fd02fc02fb,  0x3020301030002ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x306030503040303,  0x30a030903080307,  0x30e030d030c030b,  0x31203110310030f,  0x316031503140313,  0x31a031903180317,  0x31e031d031c031b,  0x32203210320031f,  0x326032503240323,  0x32a032903280327,  0x32e032d032c032b,  0xffff03310330032f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3340333ffff0332,  0x336ffffffff0335,  0x338ffff0337ffff,  0x33b033a0339ffff,  0xffff033dffff033c,  0xffffffff033effff,  0xffffffffffffffff,  0x340033fffffffff,  0xffff0342ffff0341,  0xffff0344ffff0343,  0xffff0346ffff0345,  0xffff0348ffff0347,  0xffff034affff0349,  0xffff034cffff034b,  0xffff034effff034d,  0xffff0350ffff034f,  0xffff0352ffff0351,  0xffff0354ffff0353,  0xffff0356ffff0355,  0xffff0358ffff0357,  0xffff035affff0359,  0xffff035cffff035b,  0xffff035effff035d,  0xffff0360ffff035f,  0xffff0362ffff0361,  0xffff0364ffff0363,  0xffff0366ffff0365,  0xffff0368ffff0367,  0xffff036affff0369,  0xffff036cffff036b,  0xffff036effff036d,  0xffff0370ffff036f,  0xffff0372ffff0371,  0xffffffffffffffff,  0x373ffffffffffff,  0xffffffff0374ffff,  0xffff0375ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0377ffff0376,  0xffff0379ffff0378,  0xffff037bffff037a,  0xffff037dffff037c,  0xffff037fffff037e,  0xffff0381ffff0380,  0xffff0383ffff0382,  0xffff0385ffff0384,  0xffff0387ffff0386,  0xffff0389ffff0388,  0xffff038bffff038a,  0xffffffffffff038c,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff038effff038d,  0xffff0390ffff038f,  0xffff0392ffff0391,  0xffff0394ffff0393,  0xffff0396ffff0395,  0xffff0398ffff0397,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0399ffffffff,  0xffff039bffff039a,  0xffff039dffff039c,  0xffff039fffff039e,  0xffff03a0ffffffff,  0xffff03a2ffff03a1,  0xffff03a4ffff03a3,  0xffff03a6ffff03a5,  0xffff03a8ffff03a7,  0xffff03aaffff03a9,  0xffff03acffff03ab,  0xffff03aeffff03ad,  0xffff03b0ffff03af,  0xffff03b2ffff03b1,  0xffff03b4ffff03b3,  0xffff03b6ffff03b5,  0xffff03b8ffff03b7,  0xffff03baffff03b9,  0xffff03bcffff03bb,  0xffff03beffff03bd,  0xffffffffffffffff,  0xffffffffffffffff,  0x3c0ffff03bfffff,  0xffff03c203c1ffff,  0xffff03c4ffff03c3,  0xffff03c6ffff03c5,  0x3c7ffffffffffff,  0xffffffff03c8ffff,  0xffff03caffff03c9,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03ccffff03cb,  0xffff03ceffff03cd,  0xffff03d0ffff03cf,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x419041804170416,  0xffff041c041b041a,  0xffffffffffffffff,  0xffffffffffffffff,  0x41effffffffffff,  0x42204210420041f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d303d203d1ffff,  0x3d703d603d503d4,  0x3db03da03d903d8,  0x3df03de03dd03dc,  0x3e303e203e103e0,  0x3e703e603e503e4,  0xffff03ea03e903e8,  0xffffffffffffffff,  0x3ee03ed03ec03eb,  0x3f203f103f003ef,  0x3f603f503f403f3,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//8192 bytes
+enum toTitleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xc00], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x19001800170016,  0x1d001c001b001a,  0x0,  0x1f001e0000,  0x0,  0x0,  0x20000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x24002300220021,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2700260000,  0x2a00290028,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x2c0000,  0x0,  0x0,  0x0,  0x0,  0x2e002d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff001affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x41fffffffffffff,  0x1e001d001c001b,  0x2200210020001f,  0x26002500240023,  0x2a002900280027,  0x2e002d002c002b,  0xffff00310030002f,  0x35003400330032,  0x39003800370036,  0x3bffff003affff,  0x3dffff003cffff,  0x3fffff003effff,  0x41ffff0040ffff,  0x43ffff0042ffff,  0x45ffff0044ffff,  0x47ffff0046ffff,  0x49ffff0048ffff,  0x4bffff004affff,  0x4dffff004cffff,  0x4fffff004effff,  0x51ffff0050ffff,  0x53ffff00520421,  0x55ffff0054ffff,  0xffff0056ffffffff,  0xffff0058ffff0057,  0xffff005affff0059,  0xffff005cffff005b,  0x5effff043e005d,  0x60ffff005fffff,  0x62ffff0061ffff,  0x64ffff0063ffff,  0x66ffff0065ffff,  0x68ffff0067ffff,  0x6affff0069ffff,  0x6cffff006bffff,  0x6effff006dffff,  0x70ffff006fffff,  0x72ffff0071ffff,  0x74ffff0073ffff,  0xffff0075ffffffff,  0x780077ffff0076,  0x7affffffff0079,  0xffffffff007bffff,  0xffffffffffff007c,  0xffffffffffff007d,  0xffff007effffffff,  0xffffffff007fffff,  0xffff00810080ffff,  0xffff0082ffffffff,  0x84ffff0083ffff,  0xffffffff0085ffff,  0xffffffffffff0086,  0xffffffff0087ffff,  0xffffffffffff0088,  0xffff008affff0089,  0xffffffff008bffff,  0x8dffff008cffff,  0xffffffffffffffff,  0x910090008f008e,  0x95009400930092,  0xffff0097ffff0096,  0xffff0099ffff0098,  0xffff009bffff009a,  0xffff009dffff009c,  0xa0ffff009f009e,  0xa2ffff00a1ffff,  0xa4ffff00a3ffff,  0xa6ffff00a5ffff,  0xa8ffff00a7ffff,  0xab00aa00a90446,  0xffffffff00acffff,  0xaeffff00adffff,  0xb0ffff00afffff,  0xb2ffff00b1ffff,  0xb4ffff00b3ffff,  0xb6ffff00b5ffff,  0xb8ffff00b7ffff,  0xbaffff00b9ffff,  0xbcffff00bbffff,  0xbeffff00bdffff,  0xc0ffff00bfffff,  0xc1ffffffffffff,  0xc3ffff00c2ffff,  0xc5ffff00c4ffff,  0xc7ffff00c6ffff,  0xc9ffff00c8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xcbffffffff00ca,  0xffff00cdffff00cc,  0xceffffffffffff,  0xd0ffff00cfffff,  0xd2ffff00d1ffff,  0xd600d500d400d3,  0xd900d8ffff00d7,  0xdbffff00daffff,  0xffffffffffffffff,  0xddffffffff00dc,  0xffff00df00deffff,  0xe2ffff00e100e0,  0xe3ffffffffffff,  0xffff00e500e4ffff,  0xffffffff00e6ffff,  0xffffffffffffffff,  0xffffffff00e7ffff,  0xe9ffffffff00e8,  0xffffffffffffffff,  0xed00ec00eb00ea,  0xffffffffffff00ee,  0xffff00efffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff00f0ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xf2ffff00f1ffff,  0xf3ffffffffffff,  0xf4ffffffffffff,  0xffffffff00f600f5,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffff0440,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfa00f900f800f7,  0xfd00fc00fb0443,  0x101010000ff00fe,  0x105010401030102,  0x109010801070106,  0x10d010c010b010a,  0x1110110010f010e,  0x115011401130112,  0xffff011801170116,  0xffffffff011a0119,  0x11d011c011bffff,  0x11fffff011effff,  0x121ffff0120ffff,  0x123ffff0122ffff,  0x125ffff0124ffff,  0x127ffff0126ffff,  0x129ffff0128ffff,  0xffff012c012b012a,  0xffffffff012dffff,  0x12fffffffff012e,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x133013201310130,  0x137013601350134,  0x13b013a01390138,  0x13f013e013d013c,  0x143014201410140,  0x147014601450144,  0x14b014a01490148,  0x14f014e014d014c,  0x153015201510150,  0x157015601550154,  0x15b015a01590158,  0x15f015e015d015c,  0x161ffff0160ffff,  0x163ffff0162ffff,  0x165ffff0164ffff,  0x167ffff0166ffff,  0x169ffff0168ffff,  0x16bffff016affff,  0x16dffff016cffff,  0x16fffff016effff,  0xffffffff0170ffff,  0xffffffffffffffff,  0x171ffffffffffff,  0x173ffff0172ffff,  0x175ffff0174ffff,  0x177ffff0176ffff,  0x179ffff0178ffff,  0x17bffff017affff,  0x17dffff017cffff,  0x17fffff017effff,  0x181ffff0180ffff,  0x183ffff0182ffff,  0x185ffff0184ffff,  0x187ffff0186ffff,  0x189ffff0188ffff,  0x18bffff018affff,  0xffff018cffffffff,  0xffff018effff018d,  0xffff0190ffff018f,  0x1930192ffff0191,  0x195ffff0194ffff,  0x197ffff0196ffff,  0x199ffff0198ffff,  0x19bffff019affff,  0x19dffff019cffff,  0x19fffff019effff,  0x1a1ffff01a0ffff,  0x1a3ffff01a2ffff,  0x1a5ffff01a4ffff,  0x1a7ffff01a6ffff,  0x1a9ffff01a8ffff,  0x1abffff01aaffff,  0x1adffff01acffff,  0x1afffff01aeffff,  0x1b1ffff01b0ffff,  0x1b3ffff01b2ffff,  0x1b5ffff01b4ffff,  0x1b7ffff01b6ffff,  0x1b9ffff01b8ffff,  0x1bbffff01baffff,  0x1bdffff01bcffff,  0x1bfffff01beffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1c201c101c0ffff,  0x1c601c501c401c3,  0x1ca01c901c801c7,  0x1ce01cd01cc01cb,  0x1d201d101d001cf,  0x1d601d501d401d3,  0x1da01d901d801d7,  0x1de01dd01dc01db,  0x1e201e101e001df,  0x43201e501e401e3,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff01e6ffff,  0xffffffff01e7ffff,  0x1e9ffff01e8ffff,  0x1ebffff01eaffff,  0x1edffff01ecffff,  0x1efffff01eeffff,  0x1f1ffff01f0ffff,  0x1f3ffff01f2ffff,  0x1f5ffff01f4ffff,  0x1f7ffff01f6ffff,  0x1f9ffff01f8ffff,  0x1fbffff01faffff,  0x1fdffff01fcffff,  0x1ffffff01feffff,  0x201ffff0200ffff,  0x203ffff0202ffff,  0x205ffff0204ffff,  0x207ffff0206ffff,  0x209ffff0208ffff,  0x20bffff020affff,  0x20dffff020cffff,  0x20fffff020effff,  0x211ffff0210ffff,  0x213ffff0212ffff,  0x215ffff0214ffff,  0x217ffff0216ffff,  0x219ffff0218ffff,  0x21bffff021affff,  0x21dffff021cffff,  0x21fffff021effff,  0x221ffff0220ffff,  0x223ffff0222ffff,  0x225ffff0224ffff,  0x227ffff0226ffff,  0x229ffff0228ffff,  0x22bffff022affff,  0x22dffff022cffff,  0x22fffff022effff,  0x231ffff0230ffff,  0x44a04480232ffff,  0x2330450044e044c,  0xffffffffffffffff,  0x235ffff0234ffff,  0x237ffff0236ffff,  0x239ffff0238ffff,  0x23bffff023affff,  0x23dffff023cffff,  0x23fffff023effff,  0x241ffff0240ffff,  0x243ffff0242ffff,  0x245ffff0244ffff,  0x247ffff0246ffff,  0x249ffff0248ffff,  0x24bffff024affff,  0x24dffff024cffff,  0x24fffff024effff,  0x251ffff0250ffff,  0x253ffff0252ffff,  0x255ffff0254ffff,  0x257ffff0256ffff,  0x259ffff0258ffff,  0x25bffff025affff,  0x25dffff025cffff,  0x25fffff025effff,  0x261ffff0260ffff,  0x263ffff0262ffff,  0x267026602650264,  0x26b026a02690268,  0xffffffffffffffff,  0xffffffffffffffff,  0x26f026e026d026c,  0xffffffff02710270,  0xffffffffffffffff,  0xffffffffffffffff,  0x275027402730272,  0x279027802770276,  0xffffffffffffffff,  0xffffffffffffffff,  0x27d027c027b027a,  0x2810280027f027e,  0xffffffffffffffff,  0xffffffffffffffff,  0x285028402830282,  0xffffffff02870286,  0xffffffffffffffff,  0xffffffffffffffff,  0x289045402880452,  0x28b045a028a0457,  0xffffffffffffffff,  0xffffffffffffffff,  0x28f028e028d028c,  0x293029202910290,  0xffffffffffffffff,  0xffffffffffffffff,  0x297029602950294,  0x29b029a02990298,  0x29f029e029d029c,  0xffffffff02a102a0,  0x47e047d047c047b,  0x48204810480047f,  0x486048504840483,  0x48a048904880487,  0x48e048d048c048b,  0x49204910490048f,  0x496049504940493,  0x49a049904980497,  0x49e049d049c049b,  0x4a204a104a0049f,  0x4a604a504a404a3,  0x4aa04a904a804a7,  0x4ab04b102bb02ba,  0x4bd045dffff04b3,  0xffffffffffffffff,  0xffff02bdffff04ac,  0x4ad04b5ffffffff,  0x4c0045fffff04b7,  0xffffffffffffffff,  0xffffffffffff04ae,  0x464046102c002bf,  0x4690467ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x46f046c02c202c1,  0x476047402c30472,  0xffffffffffffffff,  0xffffffffffffffff,  0x4af04b9ffffffff,  0x4c30479ffff04bb,  0xffffffffffffffff,  0xffffffffffff04b0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02c5ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c902c802c702c6,  0x2cd02cc02cb02ca,  0x2d102d002cf02ce,  0x2d502d402d302d2,  0xffffffffffffffff,  0xffffffffffff02d6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2da02d902d802d7,  0x2de02dd02dc02db,  0x2e202e102e002df,  0x2e602e502e402e3,  0x2ea02e902e802e7,  0x2ee02ed02ec02eb,  0xffffffff02f002ef,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2f402f302f202f1,  0x2f802f702f602f5,  0x2fc02fb02fa02f9,  0x30002ff02fe02fd,  0x304030303020301,  0x308030703060305,  0x30c030b030a0309,  0x310030f030e030d,  0x314031303120311,  0x318031703160315,  0x31c031b031a0319,  0xffff031f031e031d,  0xffffffff0320ffff,  0xffff03220321ffff,  0xffff0324ffff0323,  0xffffffffffff0325,  0x326ffffffffffff,  0xffff0327ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x329ffff0328ffff,  0x32bffff032affff,  0x32dffff032cffff,  0x32fffff032effff,  0x331ffff0330ffff,  0x333ffff0332ffff,  0x335ffff0334ffff,  0x337ffff0336ffff,  0x339ffff0338ffff,  0x33bffff033affff,  0x33dffff033cffff,  0x33fffff033effff,  0x341ffff0340ffff,  0x343ffff0342ffff,  0x345ffff0344ffff,  0x347ffff0346ffff,  0x349ffff0348ffff,  0x34bffff034affff,  0x34dffff034cffff,  0x34fffff034effff,  0x351ffff0350ffff,  0x353ffff0352ffff,  0x355ffff0354ffff,  0x357ffff0356ffff,  0x359ffff0358ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff035bffff035a,  0x35cffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x360035f035e035d,  0x364036303620361,  0x368036703660365,  0x36c036b036a0369,  0x370036f036e036d,  0x374037303720371,  0x378037703760375,  0x37c037b037a0379,  0x380037f037e037d,  0x383ffff03820381,  0xffffffffffffffff,  0xffffffff0384ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x386ffff0385ffff,  0x388ffff0387ffff,  0x38affff0389ffff,  0x38cffff038bffff,  0x38effff038dffff,  0x390ffff038fffff,  0x392ffff0391ffff,  0x394ffff0393ffff,  0x396ffff0395ffff,  0x398ffff0397ffff,  0x39affff0399ffff,  0xffffffff039bffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x39dffff039cffff,  0x39fffff039effff,  0x3a1ffff03a0ffff,  0x3a3ffff03a2ffff,  0x3a5ffff03a4ffff,  0x3a7ffff03a6ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3a8ffffffffffff,  0x3aaffff03a9ffff,  0x3acffff03abffff,  0x3aeffff03adffff,  0x3afffffffffffff,  0x3b1ffff03b0ffff,  0x3b3ffff03b2ffff,  0x3b5ffff03b4ffff,  0x3b7ffff03b6ffff,  0x3b9ffff03b8ffff,  0x3bbffff03baffff,  0x3bdffff03bcffff,  0x3bfffff03beffff,  0x3c1ffff03c0ffff,  0x3c3ffff03c2ffff,  0x3c5ffff03c4ffff,  0x3c7ffff03c6ffff,  0x3c9ffff03c8ffff,  0x3cbffff03caffff,  0x3cdffff03ccffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03ceffffffff,  0x3d0ffffffff03cf,  0x3d2ffff03d1ffff,  0x3d4ffff03d3ffff,  0xffffffffffffffff,  0xffffffffffff03d5,  0x3d7ffff03d6ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d9ffff03d8ffff,  0x3dbffff03daffff,  0xffffffff03dcffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x428042604240422,  0xffff0430042e042b,  0xffffffffffffffff,  0xffffffffffffffff,  0x434ffffffffffff,  0x43c043a04380436,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3df03de03ddffff,  0x3e303e203e103e0,  0x3e703e603e503e4,  0x3eb03ea03e903e8,  0x3ef03ee03ed03ec,  0x3f303f203f103f0,  0xffff03f603f503f4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0x416041504140413,  0x41a041904180417,  0x41e041d041c041b,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//8064 bytes
+enum toUpperSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xbc0], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x19001800170016,  0x1d001c001b001a,  0x0,  0x1f001e0000,  0x0,  0x0,  0x20000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x24002300220021,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2700260000,  0x2a00290028,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x2d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff001affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1e001d001c001b,  0x2200210020001f,  0x26002500240023,  0x2a002900280027,  0x2e002d002c002b,  0xffff00310030002f,  0x35003400330032,  0x39003800370036,  0x3bffff003affff,  0x3dffff003cffff,  0x3fffff003effff,  0x41ffff0040ffff,  0x43ffff0042ffff,  0x45ffff0044ffff,  0x47ffff0046ffff,  0x49ffff0048ffff,  0x4bffff004affff,  0x4dffff004cffff,  0x4fffff004effff,  0x51ffff0050ffff,  0x53ffff0052ffff,  0x55ffff0054ffff,  0xffff0056ffffffff,  0xffff0058ffff0057,  0xffff005affff0059,  0xffff005cffff005b,  0x5effffffff005d,  0x60ffff005fffff,  0x62ffff0061ffff,  0x64ffff0063ffff,  0x66ffff0065ffff,  0x68ffff0067ffff,  0x6affff0069ffff,  0x6cffff006bffff,  0x6effff006dffff,  0x70ffff006fffff,  0x72ffff0071ffff,  0x74ffff0073ffff,  0xffff0075ffffffff,  0x780077ffff0076,  0x7affffffff0079,  0xffffffff007bffff,  0xffffffffffff007c,  0xffffffffffff007d,  0xffff007effffffff,  0xffffffff007fffff,  0xffff00810080ffff,  0xffff0082ffffffff,  0x84ffff0083ffff,  0xffffffff0085ffff,  0xffffffffffff0086,  0xffffffff0087ffff,  0xffffffffffff0088,  0xffff008affff0089,  0xffffffff008bffff,  0x8dffff008cffff,  0xffffffffffffffff,  0xffff008f008effff,  0x92ffff00910090,  0xffff0094ffff0093,  0xffff0096ffff0095,  0xffff0098ffff0097,  0xffff009affff0099,  0x9dffff009c009b,  0x9fffff009effff,  0xa1ffff00a0ffff,  0xa3ffff00a2ffff,  0xa5ffff00a4ffff,  0xa700a6ffffffff,  0xffffffff00a8ffff,  0xaaffff00a9ffff,  0xacffff00abffff,  0xaeffff00adffff,  0xb0ffff00afffff,  0xb2ffff00b1ffff,  0xb4ffff00b3ffff,  0xb6ffff00b5ffff,  0xb8ffff00b7ffff,  0xbaffff00b9ffff,  0xbcffff00bbffff,  0xbdffffffffffff,  0xbfffff00beffff,  0xc1ffff00c0ffff,  0xc3ffff00c2ffff,  0xc5ffff00c4ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xc7ffffffff00c6,  0xffff00c9ffff00c8,  0xcaffffffffffff,  0xccffff00cbffff,  0xceffff00cdffff,  0xd200d100d000cf,  0xd500d4ffff00d3,  0xd7ffff00d6ffff,  0xffffffffffffffff,  0xd9ffffffff00d8,  0xffff00db00daffff,  0xdeffff00dd00dc,  0xdfffffffffffff,  0xffff00e100e0ffff,  0xffffffff00e2ffff,  0xffffffffffffffff,  0xffffffff00e3ffff,  0xe5ffffffff00e4,  0xffffffffffffffff,  0xe900e800e700e6,  0xffffffffffff00ea,  0xffff00ebffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff00ecffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xeeffff00edffff,  0xefffffffffffff,  0xf0ffffffffffff,  0xffffffff00f200f1,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xf600f500f400f3,  0xf900f800f7ffff,  0xfd00fc00fb00fa,  0x101010000ff00fe,  0x105010401030102,  0x109010801070106,  0x10d010c010b010a,  0x1110110010f010e,  0xffff011401130112,  0xffffffff01160115,  0x11901180117ffff,  0x11bffff011affff,  0x11dffff011cffff,  0x11fffff011effff,  0x121ffff0120ffff,  0x123ffff0122ffff,  0x125ffff0124ffff,  0xffff012801270126,  0xffffffff0129ffff,  0x12bffffffff012a,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x12f012e012d012c,  0x133013201310130,  0x137013601350134,  0x13b013a01390138,  0x13f013e013d013c,  0x143014201410140,  0x147014601450144,  0x14b014a01490148,  0x14f014e014d014c,  0x153015201510150,  0x157015601550154,  0x15b015a01590158,  0x15dffff015cffff,  0x15fffff015effff,  0x161ffff0160ffff,  0x163ffff0162ffff,  0x165ffff0164ffff,  0x167ffff0166ffff,  0x169ffff0168ffff,  0x16bffff016affff,  0xffffffff016cffff,  0xffffffffffffffff,  0x16dffffffffffff,  0x16fffff016effff,  0x171ffff0170ffff,  0x173ffff0172ffff,  0x175ffff0174ffff,  0x177ffff0176ffff,  0x179ffff0178ffff,  0x17bffff017affff,  0x17dffff017cffff,  0x17fffff017effff,  0x181ffff0180ffff,  0x183ffff0182ffff,  0x185ffff0184ffff,  0x187ffff0186ffff,  0xffff0188ffffffff,  0xffff018affff0189,  0xffff018cffff018b,  0x18f018effff018d,  0x191ffff0190ffff,  0x193ffff0192ffff,  0x195ffff0194ffff,  0x197ffff0196ffff,  0x199ffff0198ffff,  0x19bffff019affff,  0x19dffff019cffff,  0x19fffff019effff,  0x1a1ffff01a0ffff,  0x1a3ffff01a2ffff,  0x1a5ffff01a4ffff,  0x1a7ffff01a6ffff,  0x1a9ffff01a8ffff,  0x1abffff01aaffff,  0x1adffff01acffff,  0x1afffff01aeffff,  0x1b1ffff01b0ffff,  0x1b3ffff01b2ffff,  0x1b5ffff01b4ffff,  0x1b7ffff01b6ffff,  0x1b9ffff01b8ffff,  0x1bbffff01baffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1be01bd01bcffff,  0x1c201c101c001bf,  0x1c601c501c401c3,  0x1ca01c901c801c7,  0x1ce01cd01cc01cb,  0x1d201d101d001cf,  0x1d601d501d401d3,  0x1da01d901d801d7,  0x1de01dd01dc01db,  0xffff01e101e001df,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff01e2ffff,  0xffffffff01e3ffff,  0x1e5ffff01e4ffff,  0x1e7ffff01e6ffff,  0x1e9ffff01e8ffff,  0x1ebffff01eaffff,  0x1edffff01ecffff,  0x1efffff01eeffff,  0x1f1ffff01f0ffff,  0x1f3ffff01f2ffff,  0x1f5ffff01f4ffff,  0x1f7ffff01f6ffff,  0x1f9ffff01f8ffff,  0x1fbffff01faffff,  0x1fdffff01fcffff,  0x1ffffff01feffff,  0x201ffff0200ffff,  0x203ffff0202ffff,  0x205ffff0204ffff,  0x207ffff0206ffff,  0x209ffff0208ffff,  0x20bffff020affff,  0x20dffff020cffff,  0x20fffff020effff,  0x211ffff0210ffff,  0x213ffff0212ffff,  0x215ffff0214ffff,  0x217ffff0216ffff,  0x219ffff0218ffff,  0x21bffff021affff,  0x21dffff021cffff,  0x21fffff021effff,  0x221ffff0220ffff,  0x223ffff0222ffff,  0x225ffff0224ffff,  0x227ffff0226ffff,  0x229ffff0228ffff,  0x22bffff022affff,  0x22dffff022cffff,  0xffffffff022effff,  0x22fffffffffffff,  0xffffffffffffffff,  0x231ffff0230ffff,  0x233ffff0232ffff,  0x235ffff0234ffff,  0x237ffff0236ffff,  0x239ffff0238ffff,  0x23bffff023affff,  0x23dffff023cffff,  0x23fffff023effff,  0x241ffff0240ffff,  0x243ffff0242ffff,  0x245ffff0244ffff,  0x247ffff0246ffff,  0x249ffff0248ffff,  0x24bffff024affff,  0x24dffff024cffff,  0x24fffff024effff,  0x251ffff0250ffff,  0x253ffff0252ffff,  0x255ffff0254ffff,  0x257ffff0256ffff,  0x259ffff0258ffff,  0x25bffff025affff,  0x25dffff025cffff,  0x25fffff025effff,  0x263026202610260,  0x267026602650264,  0xffffffffffffffff,  0xffffffffffffffff,  0x26b026a02690268,  0xffffffff026d026c,  0xffffffffffffffff,  0xffffffffffffffff,  0x2710270026f026e,  0x275027402730272,  0xffffffffffffffff,  0xffffffffffffffff,  0x279027802770276,  0x27d027c027b027a,  0xffffffffffffffff,  0xffffffffffffffff,  0x2810280027f027e,  0xffffffff02830282,  0xffffffffffffffff,  0xffffffffffffffff,  0x285ffff0284ffff,  0x287ffff0286ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x28b028a02890288,  0x28f028e028d028c,  0xffffffffffffffff,  0xffffffffffffffff,  0x293029202910290,  0x297029602950294,  0x29b029a02990298,  0xffffffff029d029c,  0x2a102a0029f029e,  0x2a502a402a302a2,  0xffffffffffffffff,  0xffffffffffffffff,  0x2a902a802a702a6,  0x2ad02ac02ab02aa,  0xffffffffffffffff,  0xffffffffffffffff,  0x2b102b002af02ae,  0x2b502b402b302b2,  0xffffffffffffffff,  0xffffffffffffffff,  0x2b8ffff02b702b6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02b9ffffffff,  0x2baffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff02bc02bb,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff02be02bd,  0xffffffff02bfffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c0ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02c1ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c502c402c302c2,  0x2c902c802c702c6,  0x2cd02cc02cb02ca,  0x2d102d002cf02ce,  0xffffffffffffffff,  0xffffffffffff02d2,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2d602d502d402d3,  0x2da02d902d802d7,  0x2de02dd02dc02db,  0x2e202e102e002df,  0x2e602e502e402e3,  0x2ea02e902e802e7,  0xffffffff02ec02eb,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2f002ef02ee02ed,  0x2f402f302f202f1,  0x2f802f702f602f5,  0x2fc02fb02fa02f9,  0x30002ff02fe02fd,  0x304030303020301,  0x308030703060305,  0x30c030b030a0309,  0x310030f030e030d,  0x314031303120311,  0x318031703160315,  0xffff031b031a0319,  0xffffffff031cffff,  0xffff031e031dffff,  0xffff0320ffff031f,  0xffffffffffff0321,  0x322ffffffffffff,  0xffff0323ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x325ffff0324ffff,  0x327ffff0326ffff,  0x329ffff0328ffff,  0x32bffff032affff,  0x32dffff032cffff,  0x32fffff032effff,  0x331ffff0330ffff,  0x333ffff0332ffff,  0x335ffff0334ffff,  0x337ffff0336ffff,  0x339ffff0338ffff,  0x33bffff033affff,  0x33dffff033cffff,  0x33fffff033effff,  0x341ffff0340ffff,  0x343ffff0342ffff,  0x345ffff0344ffff,  0x347ffff0346ffff,  0x349ffff0348ffff,  0x34bffff034affff,  0x34dffff034cffff,  0x34fffff034effff,  0x351ffff0350ffff,  0x353ffff0352ffff,  0x355ffff0354ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0357ffff0356,  0x358ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x35c035b035a0359,  0x360035f035e035d,  0x364036303620361,  0x368036703660365,  0x36c036b036a0369,  0x370036f036e036d,  0x374037303720371,  0x378037703760375,  0x37c037b037a0379,  0x37fffff037e037d,  0xffffffffffffffff,  0xffffffff0380ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x382ffff0381ffff,  0x384ffff0383ffff,  0x386ffff0385ffff,  0x388ffff0387ffff,  0x38affff0389ffff,  0x38cffff038bffff,  0x38effff038dffff,  0x390ffff038fffff,  0x392ffff0391ffff,  0x394ffff0393ffff,  0x396ffff0395ffff,  0xffffffff0397ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x399ffff0398ffff,  0x39bffff039affff,  0x39dffff039cffff,  0x39fffff039effff,  0x3a1ffff03a0ffff,  0x3a3ffff03a2ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3a4ffffffffffff,  0x3a6ffff03a5ffff,  0x3a8ffff03a7ffff,  0x3aaffff03a9ffff,  0x3abffffffffffff,  0x3adffff03acffff,  0x3afffff03aeffff,  0x3b1ffff03b0ffff,  0x3b3ffff03b2ffff,  0x3b5ffff03b4ffff,  0x3b7ffff03b6ffff,  0x3b9ffff03b8ffff,  0x3bbffff03baffff,  0x3bdffff03bcffff,  0x3bfffff03beffff,  0x3c1ffff03c0ffff,  0x3c3ffff03c2ffff,  0x3c5ffff03c4ffff,  0x3c7ffff03c6ffff,  0x3c9ffff03c8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03caffffffff,  0x3ccffffffff03cb,  0x3ceffff03cdffff,  0x3d0ffff03cfffff,  0xffffffffffffffff,  0xffffffffffff03d1,  0x3d3ffff03d2ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d5ffff03d4ffff,  0x3d7ffff03d6ffff,  0xffffffff03d8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3db03da03d9ffff,  0x3df03de03dd03dc,  0x3e303e203e103e0,  0x3e703e603e503e4,  0x3eb03ea03e903e8,  0x3ef03ee03ed03ec,  0xffff03f203f103f0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3f603f503f403f3,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0x416041504140413,  0x41a041904180417,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//7808 bytes
+enum toLowerSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xb40], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x2000000010000,  0x6000500040003,  0x80007,  0xb000a00090000,  0xf000e000d000c,  0x110010,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13001200000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x17001600150014,  0x1b001a00190018,  0x0,  0x1e001d001c,  0x0,  0x0,  0x20001f00000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x24002300220021,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2600250000,  0x2900280027,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1d001c001b001a,  0x210020001f001e,  0x25002400230022,  0x29002800270026,  0x2d002c002b002a,  0xffff0030002f002e,  0x34003300320031,  0xffff003700360035,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0039ffff0038,  0xffff003bffff003a,  0xffff003dffff003c,  0xffff003fffff003e,  0xffff0041ffff0040,  0xffff0043ffff0042,  0xffff0045ffff0044,  0xffff0047ffff0046,  0xffff0049ffff0048,  0xffff004bffff004a,  0xffff004dffff004c,  0xffff004fffff004e,  0xffff0051ffff0050,  0xffff0053ffff0052,  0x55ffff0054ffff,  0x57ffff0056ffff,  0x59ffff0058ffff,  0x5bffff005affff,  0xffff005cffffffff,  0xffff005effff005d,  0xffff0060ffff005f,  0xffff0062ffff0061,  0xffff0064ffff0063,  0xffff0066ffff0065,  0xffff0068ffff0067,  0xffff006affff0069,  0xffff006cffff006b,  0xffff006effff006d,  0xffff0070ffff006f,  0xffff0072ffff0071,  0x75ffff00740073,  0xffffffff0076ffff,  0xffff00780077ffff,  0x7b007affff0079,  0x7e007d007cffff,  0x80007fffffffff,  0x83ffff00820081,  0x860085ffff0084,  0xffffffffffff0087,  0x8affff00890088,  0xffff008cffff008b,  0x8f008effff008d,  0xffffffff0090ffff,  0x930092ffff0091,  0x9600950094ffff,  0x98ffff0097ffff,  0xffffffffffff0099,  0xffffffffffff009a,  0xffffffffffffffff,  0x9dffff009c009b,  0xa0009fffff009e,  0xa2ffff00a1ffff,  0xa4ffff00a3ffff,  0xa6ffff00a5ffff,  0xa8ffff00a7ffff,  0xffff00a9ffffffff,  0xffff00abffff00aa,  0xffff00adffff00ac,  0xffff00afffff00ae,  0xffff00b1ffff00b0,  0xffff00b300b2ffff,  0xb600b5ffff00b4,  0xffff00b8ffff00b7,  0xffff00baffff00b9,  0xffff00bcffff00bb,  0xffff00beffff00bd,  0xffff00c0ffff00bf,  0xffff00c2ffff00c1,  0xffff00c4ffff00c3,  0xffff00c6ffff00c5,  0xffff00c8ffff00c7,  0xffff00caffff00c9,  0xffff00ccffff00cb,  0xffff00ceffff00cd,  0xffff00d0ffff00cf,  0xffff00d2ffff00d1,  0xffff00d4ffff00d3,  0xffffffffffffffff,  0xd600d5ffffffff,  0xffff00d800d7ffff,  0xdaffff00d9ffff,  0xffff00dd00dc00db,  0xffff00dfffff00de,  0xffff00e1ffff00e0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff00e3ffff00e2,  0xffff00e4ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff00e5ffffffff,  0xffff00e800e700e6,  0xeb00eaffff00e9,  0xee00ed00ecffff,  0xf200f100f000ef,  0xf600f500f400f3,  0xfa00f900f800f7,  0xfdffff00fc00fb,  0x101010000ff00fe,  0x105010401030102,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x106ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0108ffff0107,  0xffff010affff0109,  0xffff010cffff010b,  0xffff010effff010d,  0xffff0110ffff010f,  0xffff0112ffff0111,  0xffffffffffffffff,  0x114ffffffff0113,  0xffff01160115ffff,  0x11901180117ffff,  0x11d011c011b011a,  0x1210120011f011e,  0x125012401230122,  0x129012801270126,  0x12d012c012b012a,  0x1310130012f012e,  0x135013401330132,  0x139013801370136,  0x13d013c013b013a,  0x1410140013f013e,  0x145014401430142,  0x149014801470146,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff014bffff014a,  0xffff014dffff014c,  0xffff014fffff014e,  0xffff0151ffff0150,  0xffff0153ffff0152,  0xffff0155ffff0154,  0xffff0157ffff0156,  0xffff0159ffff0158,  0xffffffffffff015a,  0xffffffffffffffff,  0xffff015bffffffff,  0xffff015dffff015c,  0xffff015fffff015e,  0xffff0161ffff0160,  0xffff0163ffff0162,  0xffff0165ffff0164,  0xffff0167ffff0166,  0xffff0169ffff0168,  0xffff016bffff016a,  0xffff016dffff016c,  0xffff016fffff016e,  0xffff0171ffff0170,  0xffff0173ffff0172,  0xffff0175ffff0174,  0x178ffff01770176,  0x17affff0179ffff,  0x17cffff017bffff,  0xffffffff017dffff,  0xffff017fffff017e,  0xffff0181ffff0180,  0xffff0183ffff0182,  0xffff0185ffff0184,  0xffff0187ffff0186,  0xffff0189ffff0188,  0xffff018bffff018a,  0xffff018dffff018c,  0xffff018fffff018e,  0xffff0191ffff0190,  0xffff0193ffff0192,  0xffff0195ffff0194,  0xffff0197ffff0196,  0xffff0199ffff0198,  0xffff019bffff019a,  0xffff019dffff019c,  0xffff019fffff019e,  0xffff01a1ffff01a0,  0xffff01a3ffff01a2,  0xffff01a5ffff01a4,  0xffff01a7ffff01a6,  0xffff01a9ffff01a8,  0xffffffffffffffff,  0xffffffffffffffff,  0x1ac01ab01aaffff,  0x1b001af01ae01ad,  0x1b401b301b201b1,  0x1b801b701b601b5,  0x1bc01bb01ba01b9,  0x1c001bf01be01bd,  0x1c401c301c201c1,  0x1c801c701c601c5,  0x1cc01cb01ca01c9,  0xffff01cf01ce01cd,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1d301d201d101d0,  0x1d701d601d501d4,  0x1db01da01d901d8,  0x1df01de01dd01dc,  0x1e301e201e101e0,  0x1e701e601e501e4,  0x1eb01ea01e901e8,  0x1ef01ee01ed01ec,  0x1f301f201f101f0,  0x1f6ffff01f501f4,  0xffffffffffffffff,  0xffffffff01f7ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff01f9ffff01f8,  0xffff01fbffff01fa,  0xffff01fdffff01fc,  0xffff01ffffff01fe,  0xffff0201ffff0200,  0xffff0203ffff0202,  0xffff0205ffff0204,  0xffff0207ffff0206,  0xffff0209ffff0208,  0xffff020bffff020a,  0xffff020dffff020c,  0xffff020fffff020e,  0xffff0211ffff0210,  0xffff0213ffff0212,  0xffff0215ffff0214,  0xffff0217ffff0216,  0xffff0219ffff0218,  0xffff021bffff021a,  0xffff021dffff021c,  0xffff021fffff021e,  0xffff0221ffff0220,  0xffff0223ffff0222,  0xffff0225ffff0224,  0xffff0227ffff0226,  0xffff0229ffff0228,  0xffff022bffff022a,  0xffff022dffff022c,  0xffff022fffff022e,  0xffff0231ffff0230,  0xffff0233ffff0232,  0xffff0235ffff0234,  0xffff0237ffff0236,  0xffff0239ffff0238,  0xffff023bffff023a,  0xffff023dffff023c,  0xffff023fffff023e,  0xffff0241ffff0240,  0xffffffffffff0242,  0xffffffffffffffff,  0xffff0243ffffffff,  0xffff0245ffff0244,  0xffff0247ffff0246,  0xffff0249ffff0248,  0xffff024bffff024a,  0xffff024dffff024c,  0xffff024fffff024e,  0xffff0251ffff0250,  0xffff0253ffff0252,  0xffff0255ffff0254,  0xffff0257ffff0256,  0xffff0259ffff0258,  0xffff025bffff025a,  0xffff025dffff025c,  0xffff025fffff025e,  0xffff0261ffff0260,  0xffff0263ffff0262,  0xffff0265ffff0264,  0xffff0267ffff0266,  0xffff0269ffff0268,  0xffff026bffff026a,  0xffff026dffff026c,  0xffff026fffff026e,  0xffff0271ffff0270,  0xffff0273ffff0272,  0xffffffffffffffff,  0xffffffffffffffff,  0x277027602750274,  0x27b027a02790278,  0xffffffffffffffff,  0xffffffffffffffff,  0x27f027e027d027c,  0xffffffff02810280,  0xffffffffffffffff,  0xffffffffffffffff,  0x285028402830282,  0x289028802870286,  0xffffffffffffffff,  0xffffffffffffffff,  0x28d028c028b028a,  0x2910290028f028e,  0xffffffffffffffff,  0xffffffffffffffff,  0x295029402930292,  0xffffffff02970296,  0xffffffffffffffff,  0xffffffffffffffff,  0x299ffff0298ffff,  0x29bffff029affff,  0xffffffffffffffff,  0xffffffffffffffff,  0x29f029e029d029c,  0x2a302a202a102a0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2a702a602a502a4,  0x2ab02aa02a902a8,  0xffffffffffffffff,  0xffffffffffffffff,  0x2af02ae02ad02ac,  0x2b302b202b102b0,  0xffffffffffffffff,  0xffffffffffffffff,  0x2b702b602b502b4,  0x2bb02ba02b902b8,  0xffffffffffffffff,  0xffffffffffffffff,  0x2bf02be02bd02bc,  0xffffffffffff02c0,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c402c302c202c1,  0xffffffffffff02c5,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c902c802c702c6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2cd02cc02cb02ca,  0xffffffffffff02ce,  0xffffffffffffffff,  0xffffffffffffffff,  0x2d202d102d002cf,  0xffffffffffff02d3,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02d4ffffffff,  0x2d602d5ffffffff,  0xffffffffffffffff,  0xffff02d7ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2db02da02d902d8,  0x2df02de02dd02dc,  0x2e302e202e102e0,  0x2e702e602e502e4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2e8ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2ea02e9ffffffff,  0x2ee02ed02ec02eb,  0x2f202f102f002ef,  0x2f602f502f402f3,  0x2fa02f902f802f7,  0x2fe02fd02fc02fb,  0x3020301030002ff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x306030503040303,  0x30a030903080307,  0x30e030d030c030b,  0x31203110310030f,  0x316031503140313,  0x31a031903180317,  0x31e031d031c031b,  0x32203210320031f,  0x326032503240323,  0x32a032903280327,  0x32e032d032c032b,  0xffff03310330032f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3340333ffff0332,  0x336ffffffff0335,  0x338ffff0337ffff,  0x33b033a0339ffff,  0xffff033dffff033c,  0xffffffff033effff,  0xffffffffffffffff,  0x340033fffffffff,  0xffff0342ffff0341,  0xffff0344ffff0343,  0xffff0346ffff0345,  0xffff0348ffff0347,  0xffff034affff0349,  0xffff034cffff034b,  0xffff034effff034d,  0xffff0350ffff034f,  0xffff0352ffff0351,  0xffff0354ffff0353,  0xffff0356ffff0355,  0xffff0358ffff0357,  0xffff035affff0359,  0xffff035cffff035b,  0xffff035effff035d,  0xffff0360ffff035f,  0xffff0362ffff0361,  0xffff0364ffff0363,  0xffff0366ffff0365,  0xffff0368ffff0367,  0xffff036affff0369,  0xffff036cffff036b,  0xffff036effff036d,  0xffff0370ffff036f,  0xffff0372ffff0371,  0xffffffffffffffff,  0x373ffffffffffff,  0xffffffff0374ffff,  0xffff0375ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0377ffff0376,  0xffff0379ffff0378,  0xffff037bffff037a,  0xffff037dffff037c,  0xffff037fffff037e,  0xffff0381ffff0380,  0xffff0383ffff0382,  0xffff0385ffff0384,  0xffff0387ffff0386,  0xffff0389ffff0388,  0xffff038bffff038a,  0xffffffffffff038c,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff038effff038d,  0xffff0390ffff038f,  0xffff0392ffff0391,  0xffff0394ffff0393,  0xffff0396ffff0395,  0xffff0398ffff0397,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff0399ffffffff,  0xffff039bffff039a,  0xffff039dffff039c,  0xffff039fffff039e,  0xffff03a0ffffffff,  0xffff03a2ffff03a1,  0xffff03a4ffff03a3,  0xffff03a6ffff03a5,  0xffff03a8ffff03a7,  0xffff03aaffff03a9,  0xffff03acffff03ab,  0xffff03aeffff03ad,  0xffff03b0ffff03af,  0xffff03b2ffff03b1,  0xffff03b4ffff03b3,  0xffff03b6ffff03b5,  0xffff03b8ffff03b7,  0xffff03baffff03b9,  0xffff03bcffff03bb,  0xffff03beffff03bd,  0xffffffffffffffff,  0xffffffffffffffff,  0x3c0ffff03bfffff,  0xffff03c203c1ffff,  0xffff03c4ffff03c3,  0xffff03c6ffff03c5,  0x3c7ffffffffffff,  0xffffffff03c8ffff,  0xffff03caffff03c9,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03ccffff03cb,  0xffff03ceffff03cd,  0xffff03d0ffff03cf,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d303d203d1ffff,  0x3d703d603d503d4,  0x3db03da03d903d8,  0x3df03de03dd03dc,  0x3e303e203e103e0,  0x3e703e603e503e4,  0xffff03ea03e903e8,  0xffffffffffffffff,  0x3ee03ed03ec03eb,  0x3f203f103f003ef,  0x3f603f503f403f3,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+//8064 bytes
+enum toTitleSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x20,  0x100], [ 0x100,  0x380,  0xbc0], [ 0x402030202020100,  0x202020202020205,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x202020202020202,  0x3000200010000,  0x7000600050004,  0xa00090008,  0xd000c000b0000,  0x110010000f000e,  0x1400130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x19001800170016,  0x1d001c001b001a,  0x0,  0x1f001e0000,  0x0,  0x0,  0x20000000000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x24002300220021,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2700260000,  0x2a00290028,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x2d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x200010000ffff,  0x6000500040003,  0xa000900080007,  0xe000d000c000b,  0x1200110010000f,  0x16001500140013,  0xffff001900180017,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff001affff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1e001d001c001b,  0x2200210020001f,  0x26002500240023,  0x2a002900280027,  0x2e002d002c002b,  0xffff00310030002f,  0x35003400330032,  0x39003800370036,  0x3bffff003affff,  0x3dffff003cffff,  0x3fffff003effff,  0x41ffff0040ffff,  0x43ffff0042ffff,  0x45ffff0044ffff,  0x47ffff0046ffff,  0x49ffff0048ffff,  0x4bffff004affff,  0x4dffff004cffff,  0x4fffff004effff,  0x51ffff0050ffff,  0x53ffff0052ffff,  0x55ffff0054ffff,  0xffff0056ffffffff,  0xffff0058ffff0057,  0xffff005affff0059,  0xffff005cffff005b,  0x5effffffff005d,  0x60ffff005fffff,  0x62ffff0061ffff,  0x64ffff0063ffff,  0x66ffff0065ffff,  0x68ffff0067ffff,  0x6affff0069ffff,  0x6cffff006bffff,  0x6effff006dffff,  0x70ffff006fffff,  0x72ffff0071ffff,  0x74ffff0073ffff,  0xffff0075ffffffff,  0x780077ffff0076,  0x7affffffff0079,  0xffffffff007bffff,  0xffffffffffff007c,  0xffffffffffff007d,  0xffff007effffffff,  0xffffffff007fffff,  0xffff00810080ffff,  0xffff0082ffffffff,  0x84ffff0083ffff,  0xffffffff0085ffff,  0xffffffffffff0086,  0xffffffff0087ffff,  0xffffffffffff0088,  0xffff008affff0089,  0xffffffff008bffff,  0x8dffff008cffff,  0xffffffffffffffff,  0x910090008f008e,  0x95009400930092,  0xffff0097ffff0096,  0xffff0099ffff0098,  0xffff009bffff009a,  0xffff009dffff009c,  0xa0ffff009f009e,  0xa2ffff00a1ffff,  0xa4ffff00a3ffff,  0xa6ffff00a5ffff,  0xa8ffff00a7ffff,  0xab00aa00a9ffff,  0xffffffff00acffff,  0xaeffff00adffff,  0xb0ffff00afffff,  0xb2ffff00b1ffff,  0xb4ffff00b3ffff,  0xb6ffff00b5ffff,  0xb8ffff00b7ffff,  0xbaffff00b9ffff,  0xbcffff00bbffff,  0xbeffff00bdffff,  0xc0ffff00bfffff,  0xc1ffffffffffff,  0xc3ffff00c2ffff,  0xc5ffff00c4ffff,  0xc7ffff00c6ffff,  0xc9ffff00c8ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xcbffffffff00ca,  0xffff00cdffff00cc,  0xceffffffffffff,  0xd0ffff00cfffff,  0xd2ffff00d1ffff,  0xd600d500d400d3,  0xd900d8ffff00d7,  0xdbffff00daffff,  0xffffffffffffffff,  0xddffffffff00dc,  0xffff00df00deffff,  0xe2ffff00e100e0,  0xe3ffffffffffff,  0xffff00e500e4ffff,  0xffffffff00e6ffff,  0xffffffffffffffff,  0xffffffff00e7ffff,  0xe9ffffffff00e8,  0xffffffffffffffff,  0xed00ec00eb00ea,  0xffffffffffff00ee,  0xffff00efffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff00f0ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xf2ffff00f1ffff,  0xf3ffffffffffff,  0xf4ffffffffffff,  0xffffffff00f600f5,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xfa00f900f800f7,  0xfd00fc00fbffff,  0x101010000ff00fe,  0x105010401030102,  0x109010801070106,  0x10d010c010b010a,  0x1110110010f010e,  0x115011401130112,  0xffff011801170116,  0xffffffff011a0119,  0x11d011c011bffff,  0x11fffff011effff,  0x121ffff0120ffff,  0x123ffff0122ffff,  0x125ffff0124ffff,  0x127ffff0126ffff,  0x129ffff0128ffff,  0xffff012c012b012a,  0xffffffff012dffff,  0x12fffffffff012e,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x133013201310130,  0x137013601350134,  0x13b013a01390138,  0x13f013e013d013c,  0x143014201410140,  0x147014601450144,  0x14b014a01490148,  0x14f014e014d014c,  0x153015201510150,  0x157015601550154,  0x15b015a01590158,  0x15f015e015d015c,  0x161ffff0160ffff,  0x163ffff0162ffff,  0x165ffff0164ffff,  0x167ffff0166ffff,  0x169ffff0168ffff,  0x16bffff016affff,  0x16dffff016cffff,  0x16fffff016effff,  0xffffffff0170ffff,  0xffffffffffffffff,  0x171ffffffffffff,  0x173ffff0172ffff,  0x175ffff0174ffff,  0x177ffff0176ffff,  0x179ffff0178ffff,  0x17bffff017affff,  0x17dffff017cffff,  0x17fffff017effff,  0x181ffff0180ffff,  0x183ffff0182ffff,  0x185ffff0184ffff,  0x187ffff0186ffff,  0x189ffff0188ffff,  0x18bffff018affff,  0xffff018cffffffff,  0xffff018effff018d,  0xffff0190ffff018f,  0x1930192ffff0191,  0x195ffff0194ffff,  0x197ffff0196ffff,  0x199ffff0198ffff,  0x19bffff019affff,  0x19dffff019cffff,  0x19fffff019effff,  0x1a1ffff01a0ffff,  0x1a3ffff01a2ffff,  0x1a5ffff01a4ffff,  0x1a7ffff01a6ffff,  0x1a9ffff01a8ffff,  0x1abffff01aaffff,  0x1adffff01acffff,  0x1afffff01aeffff,  0x1b1ffff01b0ffff,  0x1b3ffff01b2ffff,  0x1b5ffff01b4ffff,  0x1b7ffff01b6ffff,  0x1b9ffff01b8ffff,  0x1bbffff01baffff,  0x1bdffff01bcffff,  0x1bfffff01beffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x1c201c101c0ffff,  0x1c601c501c401c3,  0x1ca01c901c801c7,  0x1ce01cd01cc01cb,  0x1d201d101d001cf,  0x1d601d501d401d3,  0x1da01d901d801d7,  0x1de01dd01dc01db,  0x1e201e101e001df,  0xffff01e501e401e3,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff01e6ffff,  0xffffffff01e7ffff,  0x1e9ffff01e8ffff,  0x1ebffff01eaffff,  0x1edffff01ecffff,  0x1efffff01eeffff,  0x1f1ffff01f0ffff,  0x1f3ffff01f2ffff,  0x1f5ffff01f4ffff,  0x1f7ffff01f6ffff,  0x1f9ffff01f8ffff,  0x1fbffff01faffff,  0x1fdffff01fcffff,  0x1ffffff01feffff,  0x201ffff0200ffff,  0x203ffff0202ffff,  0x205ffff0204ffff,  0x207ffff0206ffff,  0x209ffff0208ffff,  0x20bffff020affff,  0x20dffff020cffff,  0x20fffff020effff,  0x211ffff0210ffff,  0x213ffff0212ffff,  0x215ffff0214ffff,  0x217ffff0216ffff,  0x219ffff0218ffff,  0x21bffff021affff,  0x21dffff021cffff,  0x21fffff021effff,  0x221ffff0220ffff,  0x223ffff0222ffff,  0x225ffff0224ffff,  0x227ffff0226ffff,  0x229ffff0228ffff,  0x22bffff022affff,  0x22dffff022cffff,  0x22fffff022effff,  0x231ffff0230ffff,  0xffffffff0232ffff,  0x233ffffffffffff,  0xffffffffffffffff,  0x235ffff0234ffff,  0x237ffff0236ffff,  0x239ffff0238ffff,  0x23bffff023affff,  0x23dffff023cffff,  0x23fffff023effff,  0x241ffff0240ffff,  0x243ffff0242ffff,  0x245ffff0244ffff,  0x247ffff0246ffff,  0x249ffff0248ffff,  0x24bffff024affff,  0x24dffff024cffff,  0x24fffff024effff,  0x251ffff0250ffff,  0x253ffff0252ffff,  0x255ffff0254ffff,  0x257ffff0256ffff,  0x259ffff0258ffff,  0x25bffff025affff,  0x25dffff025cffff,  0x25fffff025effff,  0x261ffff0260ffff,  0x263ffff0262ffff,  0x267026602650264,  0x26b026a02690268,  0xffffffffffffffff,  0xffffffffffffffff,  0x26f026e026d026c,  0xffffffff02710270,  0xffffffffffffffff,  0xffffffffffffffff,  0x275027402730272,  0x279027802770276,  0xffffffffffffffff,  0xffffffffffffffff,  0x27d027c027b027a,  0x2810280027f027e,  0xffffffffffffffff,  0xffffffffffffffff,  0x285028402830282,  0xffffffff02870286,  0xffffffffffffffff,  0xffffffffffffffff,  0x289ffff0288ffff,  0x28bffff028affff,  0xffffffffffffffff,  0xffffffffffffffff,  0x28f028e028d028c,  0x293029202910290,  0xffffffffffffffff,  0xffffffffffffffff,  0x297029602950294,  0x29b029a02990298,  0x29f029e029d029c,  0xffffffff02a102a0,  0x2a502a402a302a2,  0x2a902a802a702a6,  0xffffffffffffffff,  0xffffffffffffffff,  0x2ad02ac02ab02aa,  0x2b102b002af02ae,  0xffffffffffffffff,  0xffffffffffffffff,  0x2b502b402b302b2,  0x2b902b802b702b6,  0xffffffffffffffff,  0xffffffffffffffff,  0x2bcffff02bb02ba,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02bdffffffff,  0x2beffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff02c002bf,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffff02c202c1,  0xffffffff02c3ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c4ffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff02c5ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2c902c802c702c6,  0x2cd02cc02cb02ca,  0x2d102d002cf02ce,  0x2d502d402d302d2,  0xffffffffffffffff,  0xffffffffffff02d6,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2da02d902d802d7,  0x2de02dd02dc02db,  0x2e202e102e002df,  0x2e602e502e402e3,  0x2ea02e902e802e7,  0x2ee02ed02ec02eb,  0xffffffff02f002ef,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x2f402f302f202f1,  0x2f802f702f602f5,  0x2fc02fb02fa02f9,  0x30002ff02fe02fd,  0x304030303020301,  0x308030703060305,  0x30c030b030a0309,  0x310030f030e030d,  0x314031303120311,  0x318031703160315,  0x31c031b031a0319,  0xffff031f031e031d,  0xffffffff0320ffff,  0xffff03220321ffff,  0xffff0324ffff0323,  0xffffffffffff0325,  0x326ffffffffffff,  0xffff0327ffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x329ffff0328ffff,  0x32bffff032affff,  0x32dffff032cffff,  0x32fffff032effff,  0x331ffff0330ffff,  0x333ffff0332ffff,  0x335ffff0334ffff,  0x337ffff0336ffff,  0x339ffff0338ffff,  0x33bffff033affff,  0x33dffff033cffff,  0x33fffff033effff,  0x341ffff0340ffff,  0x343ffff0342ffff,  0x345ffff0344ffff,  0x347ffff0346ffff,  0x349ffff0348ffff,  0x34bffff034affff,  0x34dffff034cffff,  0x34fffff034effff,  0x351ffff0350ffff,  0x353ffff0352ffff,  0x355ffff0354ffff,  0x357ffff0356ffff,  0x359ffff0358ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff035bffff035a,  0x35cffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x360035f035e035d,  0x364036303620361,  0x368036703660365,  0x36c036b036a0369,  0x370036f036e036d,  0x374037303720371,  0x378037703760375,  0x37c037b037a0379,  0x380037f037e037d,  0x383ffff03820381,  0xffffffffffffffff,  0xffffffff0384ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x386ffff0385ffff,  0x388ffff0387ffff,  0x38affff0389ffff,  0x38cffff038bffff,  0x38effff038dffff,  0x390ffff038fffff,  0x392ffff0391ffff,  0x394ffff0393ffff,  0x396ffff0395ffff,  0x398ffff0397ffff,  0x39affff0399ffff,  0xffffffff039bffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x39dffff039cffff,  0x39fffff039effff,  0x3a1ffff03a0ffff,  0x3a3ffff03a2ffff,  0x3a5ffff03a4ffff,  0x3a7ffff03a6ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3a8ffffffffffff,  0x3aaffff03a9ffff,  0x3acffff03abffff,  0x3aeffff03adffff,  0x3afffffffffffff,  0x3b1ffff03b0ffff,  0x3b3ffff03b2ffff,  0x3b5ffff03b4ffff,  0x3b7ffff03b6ffff,  0x3b9ffff03b8ffff,  0x3bbffff03baffff,  0x3bdffff03bcffff,  0x3bfffff03beffff,  0x3c1ffff03c0ffff,  0x3c3ffff03c2ffff,  0x3c5ffff03c4ffff,  0x3c7ffff03c6ffff,  0x3c9ffff03c8ffff,  0x3cbffff03caffff,  0x3cdffff03ccffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffff03ceffffffff,  0x3d0ffffffff03cf,  0x3d2ffff03d1ffff,  0x3d4ffff03d3ffff,  0xffffffffffffffff,  0xffffffffffff03d5,  0x3d7ffff03d6ffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3d9ffff03d8ffff,  0x3dbffff03daffff,  0xffffffff03dcffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3df03de03ddffff,  0x3e303e203e103e0,  0x3e703e603e503e4,  0x3eb03ea03e903e8,  0x3ef03ee03ed03ec,  0x3f303f203f103f0,  0xffff03f603f503f4,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0x3fa03f903f803f7,  0x3fe03fd03fc03fb,  0x4020401040003ff,  0x406040504040403,  0x40a040904080407,  0x40e040d040c040b,  0x41204110410040f,  0x416041504140413,  0x41a041904180417,  0x41e041d041c041b,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff,  0xffffffffffffffff]);
+@property
+{
+private alias _IUA = immutable(uint[]);
+_IUA toUpperTable() { static _IUA t = [ 0x41,  0x42,  0x43,  0x44,  0x45,  0x46,  0x47,  0x48,  0x49,  0x4a,  0x4b,  0x4c,  0x4d,  0x4e,  0x4f,  0x50,  0x51,  0x52,  0x53,  0x54,  0x55,  0x56,  0x57,  0x58,  0x59,  0x5a,  0x39c,  0xc0,  0xc1,  0xc2,  0xc3,  0xc4,  0xc5,  0xc6,  0xc7,  0xc8,  0xc9,  0xca,  0xcb,  0xcc,  0xcd,  0xce,  0xcf,  0xd0,  0xd1,  0xd2,  0xd3,  0xd4,  0xd5,  0xd6,  0xd8,  0xd9,  0xda,  0xdb,  0xdc,  0xdd,  0xde,  0x178,  0x100,  0x102,  0x104,  0x106,  0x108,  0x10a,  0x10c,  0x10e,  0x110,  0x112,  0x114,  0x116,  0x118,  0x11a,  0x11c,  0x11e,  0x120,  0x122,  0x124,  0x126,  0x128,  0x12a,  0x12c,  0x12e,  0x49,  0x132,  0x134,  0x136,  0x139,  0x13b,  0x13d,  0x13f,  0x141,  0x143,  0x145,  0x147,  0x14a,  0x14c,  0x14e,  0x150,  0x152,  0x154,  0x156,  0x158,  0x15a,  0x15c,  0x15e,  0x160,  0x162,  0x164,  0x166,  0x168,  0x16a,  0x16c,  0x16e,  0x170,  0x172,  0x174,  0x176,  0x179,  0x17b,  0x17d,  0x53,  0x243,  0x182,  0x184,  0x187,  0x18b,  0x191,  0x1f6,  0x198,  0x23d,  0x220,  0x1a0,  0x1a2,  0x1a4,  0x1a7,  0x1ac,  0x1af,  0x1b3,  0x1b5,  0x1b8,  0x1bc,  0x1f7,  0x1c4,  0x1c4,  0x1c7,  0x1c7,  0x1ca,  0x1ca,  0x1cd,  0x1cf,  0x1d1,  0x1d3,  0x1d5,  0x1d7,  0x1d9,  0x1db,  0x18e,  0x1de,  0x1e0,  0x1e2,  0x1e4,  0x1e6,  0x1e8,  0x1ea,  0x1ec,  0x1ee,  0x1f1,  0x1f1,  0x1f4,  0x1f8,  0x1fa,  0x1fc,  0x1fe,  0x200,  0x202,  0x204,  0x206,  0x208,  0x20a,  0x20c,  0x20e,  0x210,  0x212,  0x214,  0x216,  0x218,  0x21a,  0x21c,  0x21e,  0x222,  0x224,  0x226,  0x228,  0x22a,  0x22c,  0x22e,  0x230,  0x232,  0x23b,  0x2c7e,  0x2c7f,  0x241,  0x246,  0x248,  0x24a,  0x24c,  0x24e,  0x2c6f,  0x2c6d,  0x2c70,  0x181,  0x186,  0x189,  0x18a,  0x18f,  0x190,  0x193,  0x194,  0xa78d,  0xa7aa,  0x197,  0x196,  0x2c62,  0x19c,  0x2c6e,  0x19d,  0x19f,  0x2c64,  0x1a6,  0x1a9,  0x1ae,  0x244,  0x1b1,  0x1b2,  0x245,  0x1b7,  0x399,  0x370,  0x372,  0x376,  0x3fd,  0x3fe,  0x3ff,  0x386,  0x388,  0x389,  0x38a,  0x391,  0x392,  0x393,  0x394,  0x395,  0x396,  0x397,  0x398,  0x399,  0x39a,  0x39b,  0x39c,  0x39d,  0x39e,  0x39f,  0x3a0,  0x3a1,  0x3a3,  0x3a3,  0x3a4,  0x3a5,  0x3a6,  0x3a7,  0x3a8,  0x3a9,  0x3aa,  0x3ab,  0x38c,  0x38e,  0x38f,  0x392,  0x398,  0x3a6,  0x3a0,  0x3cf,  0x3d8,  0x3da,  0x3dc,  0x3de,  0x3e0,  0x3e2,  0x3e4,  0x3e6,  0x3e8,  0x3ea,  0x3ec,  0x3ee,  0x39a,  0x3a1,  0x3f9,  0x395,  0x3f7,  0x3fa,  0x410,  0x411,  0x412,  0x413,  0x414,  0x415,  0x416,  0x417,  0x418,  0x419,  0x41a,  0x41b,  0x41c,  0x41d,  0x41e,  0x41f,  0x420,  0x421,  0x422,  0x423,  0x424,  0x425,  0x426,  0x427,  0x428,  0x429,  0x42a,  0x42b,  0x42c,  0x42d,  0x42e,  0x42f,  0x400,  0x401,  0x402,  0x403,  0x404,  0x405,  0x406,  0x407,  0x408,  0x409,  0x40a,  0x40b,  0x40c,  0x40d,  0x40e,  0x40f,  0x460,  0x462,  0x464,  0x466,  0x468,  0x46a,  0x46c,  0x46e,  0x470,  0x472,  0x474,  0x476,  0x478,  0x47a,  0x47c,  0x47e,  0x480,  0x48a,  0x48c,  0x48e,  0x490,  0x492,  0x494,  0x496,  0x498,  0x49a,  0x49c,  0x49e,  0x4a0,  0x4a2,  0x4a4,  0x4a6,  0x4a8,  0x4aa,  0x4ac,  0x4ae,  0x4b0,  0x4b2,  0x4b4,  0x4b6,  0x4b8,  0x4ba,  0x4bc,  0x4be,  0x4c1,  0x4c3,  0x4c5,  0x4c7,  0x4c9,  0x4cb,  0x4cd,  0x4c0,  0x4d0,  0x4d2,  0x4d4,  0x4d6,  0x4d8,  0x4da,  0x4dc,  0x4de,  0x4e0,  0x4e2,  0x4e4,  0x4e6,  0x4e8,  0x4ea,  0x4ec,  0x4ee,  0x4f0,  0x4f2,  0x4f4,  0x4f6,  0x4f8,  0x4fa,  0x4fc,  0x4fe,  0x500,  0x502,  0x504,  0x506,  0x508,  0x50a,  0x50c,  0x50e,  0x510,  0x512,  0x514,  0x516,  0x518,  0x51a,  0x51c,  0x51e,  0x520,  0x522,  0x524,  0x526,  0x531,  0x532,  0x533,  0x534,  0x535,  0x536,  0x537,  0x538,  0x539,  0x53a,  0x53b,  0x53c,  0x53d,  0x53e,  0x53f,  0x540,  0x541,  0x542,  0x543,  0x544,  0x545,  0x546,  0x547,  0x548,  0x549,  0x54a,  0x54b,  0x54c,  0x54d,  0x54e,  0x54f,  0x550,  0x551,  0x552,  0x553,  0x554,  0x555,  0x556,  0xa77d,  0x2c63,  0x1e00,  0x1e02,  0x1e04,  0x1e06,  0x1e08,  0x1e0a,  0x1e0c,  0x1e0e,  0x1e10,  0x1e12,  0x1e14,  0x1e16,  0x1e18,  0x1e1a,  0x1e1c,  0x1e1e,  0x1e20,  0x1e22,  0x1e24,  0x1e26,  0x1e28,  0x1e2a,  0x1e2c,  0x1e2e,  0x1e30,  0x1e32,  0x1e34,  0x1e36,  0x1e38,  0x1e3a,  0x1e3c,  0x1e3e,  0x1e40,  0x1e42,  0x1e44,  0x1e46,  0x1e48,  0x1e4a,  0x1e4c,  0x1e4e,  0x1e50,  0x1e52,  0x1e54,  0x1e56,  0x1e58,  0x1e5a,  0x1e5c,  0x1e5e,  0x1e60,  0x1e62,  0x1e64,  0x1e66,  0x1e68,  0x1e6a,  0x1e6c,  0x1e6e,  0x1e70,  0x1e72,  0x1e74,  0x1e76,  0x1e78,  0x1e7a,  0x1e7c,  0x1e7e,  0x1e80,  0x1e82,  0x1e84,  0x1e86,  0x1e88,  0x1e8a,  0x1e8c,  0x1e8e,  0x1e90,  0x1e92,  0x1e94,  0x1e60,  0x1ea0,  0x1ea2,  0x1ea4,  0x1ea6,  0x1ea8,  0x1eaa,  0x1eac,  0x1eae,  0x1eb0,  0x1eb2,  0x1eb4,  0x1eb6,  0x1eb8,  0x1eba,  0x1ebc,  0x1ebe,  0x1ec0,  0x1ec2,  0x1ec4,  0x1ec6,  0x1ec8,  0x1eca,  0x1ecc,  0x1ece,  0x1ed0,  0x1ed2,  0x1ed4,  0x1ed6,  0x1ed8,  0x1eda,  0x1edc,  0x1ede,  0x1ee0,  0x1ee2,  0x1ee4,  0x1ee6,  0x1ee8,  0x1eea,  0x1eec,  0x1eee,  0x1ef0,  0x1ef2,  0x1ef4,  0x1ef6,  0x1ef8,  0x1efa,  0x1efc,  0x1efe,  0x1f08,  0x1f09,  0x1f0a,  0x1f0b,  0x1f0c,  0x1f0d,  0x1f0e,  0x1f0f,  0x1f18,  0x1f19,  0x1f1a,  0x1f1b,  0x1f1c,  0x1f1d,  0x1f28,  0x1f29,  0x1f2a,  0x1f2b,  0x1f2c,  0x1f2d,  0x1f2e,  0x1f2f,  0x1f38,  0x1f39,  0x1f3a,  0x1f3b,  0x1f3c,  0x1f3d,  0x1f3e,  0x1f3f,  0x1f48,  0x1f49,  0x1f4a,  0x1f4b,  0x1f4c,  0x1f4d,  0x1f59,  0x1f5b,  0x1f5d,  0x1f5f,  0x1f68,  0x1f69,  0x1f6a,  0x1f6b,  0x1f6c,  0x1f6d,  0x1f6e,  0x1f6f,  0x1fba,  0x1fbb,  0x1fc8,  0x1fc9,  0x1fca,  0x1fcb,  0x1fda,  0x1fdb,  0x1ff8,  0x1ff9,  0x1fea,  0x1feb,  0x1ffa,  0x1ffb,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fb8,  0x1fb9,  0x1fbc,  0x399,  0x1fcc,  0x1fd8,  0x1fd9,  0x1fe8,  0x1fe9,  0x1fec,  0x1ffc,  0x2132,  0x2160,  0x2161,  0x2162,  0x2163,  0x2164,  0x2165,  0x2166,  0x2167,  0x2168,  0x2169,  0x216a,  0x216b,  0x216c,  0x216d,  0x216e,  0x216f,  0x2183,  0x24b6,  0x24b7,  0x24b8,  0x24b9,  0x24ba,  0x24bb,  0x24bc,  0x24bd,  0x24be,  0x24bf,  0x24c0,  0x24c1,  0x24c2,  0x24c3,  0x24c4,  0x24c5,  0x24c6,  0x24c7,  0x24c8,  0x24c9,  0x24ca,  0x24cb,  0x24cc,  0x24cd,  0x24ce,  0x24cf,  0x2c00,  0x2c01,  0x2c02,  0x2c03,  0x2c04,  0x2c05,  0x2c06,  0x2c07,  0x2c08,  0x2c09,  0x2c0a,  0x2c0b,  0x2c0c,  0x2c0d,  0x2c0e,  0x2c0f,  0x2c10,  0x2c11,  0x2c12,  0x2c13,  0x2c14,  0x2c15,  0x2c16,  0x2c17,  0x2c18,  0x2c19,  0x2c1a,  0x2c1b,  0x2c1c,  0x2c1d,  0x2c1e,  0x2c1f,  0x2c20,  0x2c21,  0x2c22,  0x2c23,  0x2c24,  0x2c25,  0x2c26,  0x2c27,  0x2c28,  0x2c29,  0x2c2a,  0x2c2b,  0x2c2c,  0x2c2d,  0x2c2e,  0x2c60,  0x23a,  0x23e,  0x2c67,  0x2c69,  0x2c6b,  0x2c72,  0x2c75,  0x2c80,  0x2c82,  0x2c84,  0x2c86,  0x2c88,  0x2c8a,  0x2c8c,  0x2c8e,  0x2c90,  0x2c92,  0x2c94,  0x2c96,  0x2c98,  0x2c9a,  0x2c9c,  0x2c9e,  0x2ca0,  0x2ca2,  0x2ca4,  0x2ca6,  0x2ca8,  0x2caa,  0x2cac,  0x2cae,  0x2cb0,  0x2cb2,  0x2cb4,  0x2cb6,  0x2cb8,  0x2cba,  0x2cbc,  0x2cbe,  0x2cc0,  0x2cc2,  0x2cc4,  0x2cc6,  0x2cc8,  0x2cca,  0x2ccc,  0x2cce,  0x2cd0,  0x2cd2,  0x2cd4,  0x2cd6,  0x2cd8,  0x2cda,  0x2cdc,  0x2cde,  0x2ce0,  0x2ce2,  0x2ceb,  0x2ced,  0x2cf2,  0x10a0,  0x10a1,  0x10a2,  0x10a3,  0x10a4,  0x10a5,  0x10a6,  0x10a7,  0x10a8,  0x10a9,  0x10aa,  0x10ab,  0x10ac,  0x10ad,  0x10ae,  0x10af,  0x10b0,  0x10b1,  0x10b2,  0x10b3,  0x10b4,  0x10b5,  0x10b6,  0x10b7,  0x10b8,  0x10b9,  0x10ba,  0x10bb,  0x10bc,  0x10bd,  0x10be,  0x10bf,  0x10c0,  0x10c1,  0x10c2,  0x10c3,  0x10c4,  0x10c5,  0x10c7,  0x10cd,  0xa640,  0xa642,  0xa644,  0xa646,  0xa648,  0xa64a,  0xa64c,  0xa64e,  0xa650,  0xa652,  0xa654,  0xa656,  0xa658,  0xa65a,  0xa65c,  0xa65e,  0xa660,  0xa662,  0xa664,  0xa666,  0xa668,  0xa66a,  0xa66c,  0xa680,  0xa682,  0xa684,  0xa686,  0xa688,  0xa68a,  0xa68c,  0xa68e,  0xa690,  0xa692,  0xa694,  0xa696,  0xa722,  0xa724,  0xa726,  0xa728,  0xa72a,  0xa72c,  0xa72e,  0xa732,  0xa734,  0xa736,  0xa738,  0xa73a,  0xa73c,  0xa73e,  0xa740,  0xa742,  0xa744,  0xa746,  0xa748,  0xa74a,  0xa74c,  0xa74e,  0xa750,  0xa752,  0xa754,  0xa756,  0xa758,  0xa75a,  0xa75c,  0xa75e,  0xa760,  0xa762,  0xa764,  0xa766,  0xa768,  0xa76a,  0xa76c,  0xa76e,  0xa779,  0xa77b,  0xa77e,  0xa780,  0xa782,  0xa784,  0xa786,  0xa78b,  0xa790,  0xa792,  0xa7a0,  0xa7a2,  0xa7a4,  0xa7a6,  0xa7a8,  0xff21,  0xff22,  0xff23,  0xff24,  0xff25,  0xff26,  0xff27,  0xff28,  0xff29,  0xff2a,  0xff2b,  0xff2c,  0xff2d,  0xff2e,  0xff2f,  0xff30,  0xff31,  0xff32,  0xff33,  0xff34,  0xff35,  0xff36,  0xff37,  0xff38,  0xff39,  0xff3a,  0x10400,  0x10401,  0x10402,  0x10403,  0x10404,  0x10405,  0x10406,  0x10407,  0x10408,  0x10409,  0x1040a,  0x1040b,  0x1040c,  0x1040d,  0x1040e,  0x1040f,  0x10410,  0x10411,  0x10412,  0x10413,  0x10414,  0x10415,  0x10416,  0x10417,  0x10418,  0x10419,  0x1041a,  0x1041b,  0x1041c,  0x1041d,  0x1041e,  0x1041f,  0x10420,  0x10421,  0x10422,  0x10423,  0x10424,  0x10425,  0x10426,  0x10427,  0x2000053,  0x53,  0x130,  0x2000046,  0x46,  0x2000046,  0x49,  0x2000046,  0x4c,  0x3000046,  0x46,  0x49,  0x3000046,  0x46,  0x4c,  0x2000053,  0x54,  0x2000053,  0x54,  0x2000535,  0x552,  0x2000544,  0x546,  0x2000544,  0x535,  0x2000544,  0x53b,  0x200054e,  0x546,  0x2000544,  0x53d,  0x20002bc,  0x4e,  0x3000399,  0x308,  0x301,  0x30003a5,  0x308,  0x301,  0x200004a,  0x30c,  0x2000048,  0x331,  0x2000054,  0x308,  0x2000057,  0x30a,  0x2000059,  0x30a,  0x2000041,  0x2be,  0x20003a5,  0x313,  0x30003a5,  0x313,  0x300,  0x30003a5,  0x313,  0x301,  0x30003a5,  0x313,  0x342,  0x2000391,  0x342,  0x2000397,  0x342,  0x3000399,  0x308,  0x300,  0x3000399,  0x308,  0x301,  0x2000399,  0x342,  0x3000399,  0x308,  0x342,  0x30003a5,  0x308,  0x300,  0x30003a5,  0x308,  0x301,  0x20003a1,  0x313,  0x20003a5,  0x342,  0x30003a5,  0x308,  0x342,  0x20003a9,  0x342,  0x2001f08,  0x399,  0x2001f09,  0x399,  0x2001f0a,  0x399,  0x2001f0b,  0x399,  0x2001f0c,  0x399,  0x2001f0d,  0x399,  0x2001f0e,  0x399,  0x2001f0f,  0x399,  0x2001f08,  0x399,  0x2001f09,  0x399,  0x2001f0a,  0x399,  0x2001f0b,  0x399,  0x2001f0c,  0x399,  0x2001f0d,  0x399,  0x2001f0e,  0x399,  0x2001f0f,  0x399,  0x2001f28,  0x399,  0x2001f29,  0x399,  0x2001f2a,  0x399,  0x2001f2b,  0x399,  0x2001f2c,  0x399,  0x2001f2d,  0x399,  0x2001f2e,  0x399,  0x2001f2f,  0x399,  0x2001f28,  0x399,  0x2001f29,  0x399,  0x2001f2a,  0x399,  0x2001f2b,  0x399,  0x2001f2c,  0x399,  0x2001f2d,  0x399,  0x2001f2e,  0x399,  0x2001f2f,  0x399,  0x2001f68,  0x399,  0x2001f69,  0x399,  0x2001f6a,  0x399,  0x2001f6b,  0x399,  0x2001f6c,  0x399,  0x2001f6d,  0x399,  0x2001f6e,  0x399,  0x2001f6f,  0x399,  0x2001f68,  0x399,  0x2001f69,  0x399,  0x2001f6a,  0x399,  0x2001f6b,  0x399,  0x2001f6c,  0x399,  0x2001f6d,  0x399,  0x2001f6e,  0x399,  0x2001f6f,  0x399,  0x2000391,  0x399,  0x2000391,  0x399,  0x2000397,  0x399,  0x2000397,  0x399,  0x20003a9,  0x399,  0x20003a9,  0x399,  0x2001fba,  0x399,  0x2000386,  0x399,  0x2001fca,  0x399,  0x2000389,  0x399,  0x2001ffa,  0x399,  0x200038f,  0x399,  0x3000391,  0x342,  0x399,  0x3000397,  0x342,  0x399,  0x30003a9,  0x342,  0x399]; return t; }
+_IUA toLowerTable() { static _IUA t = [ 0x61,  0x62,  0x63,  0x64,  0x65,  0x66,  0x67,  0x68,  0x69,  0x6a,  0x6b,  0x6c,  0x6d,  0x6e,  0x6f,  0x70,  0x71,  0x72,  0x73,  0x74,  0x75,  0x76,  0x77,  0x78,  0x79,  0x7a,  0xe0,  0xe1,  0xe2,  0xe3,  0xe4,  0xe5,  0xe6,  0xe7,  0xe8,  0xe9,  0xea,  0xeb,  0xec,  0xed,  0xee,  0xef,  0xf0,  0xf1,  0xf2,  0xf3,  0xf4,  0xf5,  0xf6,  0xf8,  0xf9,  0xfa,  0xfb,  0xfc,  0xfd,  0xfe,  0x101,  0x103,  0x105,  0x107,  0x109,  0x10b,  0x10d,  0x10f,  0x111,  0x113,  0x115,  0x117,  0x119,  0x11b,  0x11d,  0x11f,  0x121,  0x123,  0x125,  0x127,  0x129,  0x12b,  0x12d,  0x12f,  0x69,  0x133,  0x135,  0x137,  0x13a,  0x13c,  0x13e,  0x140,  0x142,  0x144,  0x146,  0x148,  0x14b,  0x14d,  0x14f,  0x151,  0x153,  0x155,  0x157,  0x159,  0x15b,  0x15d,  0x15f,  0x161,  0x163,  0x165,  0x167,  0x169,  0x16b,  0x16d,  0x16f,  0x171,  0x173,  0x175,  0x177,  0xff,  0x17a,  0x17c,  0x17e,  0x253,  0x183,  0x185,  0x254,  0x188,  0x256,  0x257,  0x18c,  0x1dd,  0x259,  0x25b,  0x192,  0x260,  0x263,  0x269,  0x268,  0x199,  0x26f,  0x272,  0x275,  0x1a1,  0x1a3,  0x1a5,  0x280,  0x1a8,  0x283,  0x1ad,  0x288,  0x1b0,  0x28a,  0x28b,  0x1b4,  0x1b6,  0x292,  0x1b9,  0x1bd,  0x1c6,  0x1c6,  0x1c9,  0x1c9,  0x1cc,  0x1cc,  0x1ce,  0x1d0,  0x1d2,  0x1d4,  0x1d6,  0x1d8,  0x1da,  0x1dc,  0x1df,  0x1e1,  0x1e3,  0x1e5,  0x1e7,  0x1e9,  0x1eb,  0x1ed,  0x1ef,  0x1f3,  0x1f3,  0x1f5,  0x195,  0x1bf,  0x1f9,  0x1fb,  0x1fd,  0x1ff,  0x201,  0x203,  0x205,  0x207,  0x209,  0x20b,  0x20d,  0x20f,  0x211,  0x213,  0x215,  0x217,  0x219,  0x21b,  0x21d,  0x21f,  0x19e,  0x223,  0x225,  0x227,  0x229,  0x22b,  0x22d,  0x22f,  0x231,  0x233,  0x2c65,  0x23c,  0x19a,  0x2c66,  0x242,  0x180,  0x289,  0x28c,  0x247,  0x249,  0x24b,  0x24d,  0x24f,  0x371,  0x373,  0x377,  0x3ac,  0x3ad,  0x3ae,  0x3af,  0x3cc,  0x3cd,  0x3ce,  0x3b1,  0x3b2,  0x3b3,  0x3b4,  0x3b5,  0x3b6,  0x3b7,  0x3b8,  0x3b9,  0x3ba,  0x3bb,  0x3bc,  0x3bd,  0x3be,  0x3bf,  0x3c0,  0x3c1,  0x3c3,  0x3c4,  0x3c5,  0x3c6,  0x3c7,  0x3c8,  0x3c9,  0x3ca,  0x3cb,  0x3d7,  0x3d9,  0x3db,  0x3dd,  0x3df,  0x3e1,  0x3e3,  0x3e5,  0x3e7,  0x3e9,  0x3eb,  0x3ed,  0x3ef,  0x3b8,  0x3f8,  0x3f2,  0x3fb,  0x37b,  0x37c,  0x37d,  0x450,  0x451,  0x452,  0x453,  0x454,  0x455,  0x456,  0x457,  0x458,  0x459,  0x45a,  0x45b,  0x45c,  0x45d,  0x45e,  0x45f,  0x430,  0x431,  0x432,  0x433,  0x434,  0x435,  0x436,  0x437,  0x438,  0x439,  0x43a,  0x43b,  0x43c,  0x43d,  0x43e,  0x43f,  0x440,  0x441,  0x442,  0x443,  0x444,  0x445,  0x446,  0x447,  0x448,  0x449,  0x44a,  0x44b,  0x44c,  0x44d,  0x44e,  0x44f,  0x461,  0x463,  0x465,  0x467,  0x469,  0x46b,  0x46d,  0x46f,  0x471,  0x473,  0x475,  0x477,  0x479,  0x47b,  0x47d,  0x47f,  0x481,  0x48b,  0x48d,  0x48f,  0x491,  0x493,  0x495,  0x497,  0x499,  0x49b,  0x49d,  0x49f,  0x4a1,  0x4a3,  0x4a5,  0x4a7,  0x4a9,  0x4ab,  0x4ad,  0x4af,  0x4b1,  0x4b3,  0x4b5,  0x4b7,  0x4b9,  0x4bb,  0x4bd,  0x4bf,  0x4cf,  0x4c2,  0x4c4,  0x4c6,  0x4c8,  0x4ca,  0x4cc,  0x4ce,  0x4d1,  0x4d3,  0x4d5,  0x4d7,  0x4d9,  0x4db,  0x4dd,  0x4df,  0x4e1,  0x4e3,  0x4e5,  0x4e7,  0x4e9,  0x4eb,  0x4ed,  0x4ef,  0x4f1,  0x4f3,  0x4f5,  0x4f7,  0x4f9,  0x4fb,  0x4fd,  0x4ff,  0x501,  0x503,  0x505,  0x507,  0x509,  0x50b,  0x50d,  0x50f,  0x511,  0x513,  0x515,  0x517,  0x519,  0x51b,  0x51d,  0x51f,  0x521,  0x523,  0x525,  0x527,  0x561,  0x562,  0x563,  0x564,  0x565,  0x566,  0x567,  0x568,  0x569,  0x56a,  0x56b,  0x56c,  0x56d,  0x56e,  0x56f,  0x570,  0x571,  0x572,  0x573,  0x574,  0x575,  0x576,  0x577,  0x578,  0x579,  0x57a,  0x57b,  0x57c,  0x57d,  0x57e,  0x57f,  0x580,  0x581,  0x582,  0x583,  0x584,  0x585,  0x586,  0x2d00,  0x2d01,  0x2d02,  0x2d03,  0x2d04,  0x2d05,  0x2d06,  0x2d07,  0x2d08,  0x2d09,  0x2d0a,  0x2d0b,  0x2d0c,  0x2d0d,  0x2d0e,  0x2d0f,  0x2d10,  0x2d11,  0x2d12,  0x2d13,  0x2d14,  0x2d15,  0x2d16,  0x2d17,  0x2d18,  0x2d19,  0x2d1a,  0x2d1b,  0x2d1c,  0x2d1d,  0x2d1e,  0x2d1f,  0x2d20,  0x2d21,  0x2d22,  0x2d23,  0x2d24,  0x2d25,  0x2d27,  0x2d2d,  0x1e01,  0x1e03,  0x1e05,  0x1e07,  0x1e09,  0x1e0b,  0x1e0d,  0x1e0f,  0x1e11,  0x1e13,  0x1e15,  0x1e17,  0x1e19,  0x1e1b,  0x1e1d,  0x1e1f,  0x1e21,  0x1e23,  0x1e25,  0x1e27,  0x1e29,  0x1e2b,  0x1e2d,  0x1e2f,  0x1e31,  0x1e33,  0x1e35,  0x1e37,  0x1e39,  0x1e3b,  0x1e3d,  0x1e3f,  0x1e41,  0x1e43,  0x1e45,  0x1e47,  0x1e49,  0x1e4b,  0x1e4d,  0x1e4f,  0x1e51,  0x1e53,  0x1e55,  0x1e57,  0x1e59,  0x1e5b,  0x1e5d,  0x1e5f,  0x1e61,  0x1e63,  0x1e65,  0x1e67,  0x1e69,  0x1e6b,  0x1e6d,  0x1e6f,  0x1e71,  0x1e73,  0x1e75,  0x1e77,  0x1e79,  0x1e7b,  0x1e7d,  0x1e7f,  0x1e81,  0x1e83,  0x1e85,  0x1e87,  0x1e89,  0x1e8b,  0x1e8d,  0x1e8f,  0x1e91,  0x1e93,  0x1e95,  0xdf,  0x1ea1,  0x1ea3,  0x1ea5,  0x1ea7,  0x1ea9,  0x1eab,  0x1ead,  0x1eaf,  0x1eb1,  0x1eb3,  0x1eb5,  0x1eb7,  0x1eb9,  0x1ebb,  0x1ebd,  0x1ebf,  0x1ec1,  0x1ec3,  0x1ec5,  0x1ec7,  0x1ec9,  0x1ecb,  0x1ecd,  0x1ecf,  0x1ed1,  0x1ed3,  0x1ed5,  0x1ed7,  0x1ed9,  0x1edb,  0x1edd,  0x1edf,  0x1ee1,  0x1ee3,  0x1ee5,  0x1ee7,  0x1ee9,  0x1eeb,  0x1eed,  0x1eef,  0x1ef1,  0x1ef3,  0x1ef5,  0x1ef7,  0x1ef9,  0x1efb,  0x1efd,  0x1eff,  0x1f00,  0x1f01,  0x1f02,  0x1f03,  0x1f04,  0x1f05,  0x1f06,  0x1f07,  0x1f10,  0x1f11,  0x1f12,  0x1f13,  0x1f14,  0x1f15,  0x1f20,  0x1f21,  0x1f22,  0x1f23,  0x1f24,  0x1f25,  0x1f26,  0x1f27,  0x1f30,  0x1f31,  0x1f32,  0x1f33,  0x1f34,  0x1f35,  0x1f36,  0x1f37,  0x1f40,  0x1f41,  0x1f42,  0x1f43,  0x1f44,  0x1f45,  0x1f51,  0x1f53,  0x1f55,  0x1f57,  0x1f60,  0x1f61,  0x1f62,  0x1f63,  0x1f64,  0x1f65,  0x1f66,  0x1f67,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fb0,  0x1fb1,  0x1f70,  0x1f71,  0x1fb3,  0x1f72,  0x1f73,  0x1f74,  0x1f75,  0x1fc3,  0x1fd0,  0x1fd1,  0x1f76,  0x1f77,  0x1fe0,  0x1fe1,  0x1f7a,  0x1f7b,  0x1fe5,  0x1f78,  0x1f79,  0x1f7c,  0x1f7d,  0x1ff3,  0x3c9,  0x6b,  0xe5,  0x214e,  0x2170,  0x2171,  0x2172,  0x2173,  0x2174,  0x2175,  0x2176,  0x2177,  0x2178,  0x2179,  0x217a,  0x217b,  0x217c,  0x217d,  0x217e,  0x217f,  0x2184,  0x24d0,  0x24d1,  0x24d2,  0x24d3,  0x24d4,  0x24d5,  0x24d6,  0x24d7,  0x24d8,  0x24d9,  0x24da,  0x24db,  0x24dc,  0x24dd,  0x24de,  0x24df,  0x24e0,  0x24e1,  0x24e2,  0x24e3,  0x24e4,  0x24e5,  0x24e6,  0x24e7,  0x24e8,  0x24e9,  0x2c30,  0x2c31,  0x2c32,  0x2c33,  0x2c34,  0x2c35,  0x2c36,  0x2c37,  0x2c38,  0x2c39,  0x2c3a,  0x2c3b,  0x2c3c,  0x2c3d,  0x2c3e,  0x2c3f,  0x2c40,  0x2c41,  0x2c42,  0x2c43,  0x2c44,  0x2c45,  0x2c46,  0x2c47,  0x2c48,  0x2c49,  0x2c4a,  0x2c4b,  0x2c4c,  0x2c4d,  0x2c4e,  0x2c4f,  0x2c50,  0x2c51,  0x2c52,  0x2c53,  0x2c54,  0x2c55,  0x2c56,  0x2c57,  0x2c58,  0x2c59,  0x2c5a,  0x2c5b,  0x2c5c,  0x2c5d,  0x2c5e,  0x2c61,  0x26b,  0x1d7d,  0x27d,  0x2c68,  0x2c6a,  0x2c6c,  0x251,  0x271,  0x250,  0x252,  0x2c73,  0x2c76,  0x23f,  0x240,  0x2c81,  0x2c83,  0x2c85,  0x2c87,  0x2c89,  0x2c8b,  0x2c8d,  0x2c8f,  0x2c91,  0x2c93,  0x2c95,  0x2c97,  0x2c99,  0x2c9b,  0x2c9d,  0x2c9f,  0x2ca1,  0x2ca3,  0x2ca5,  0x2ca7,  0x2ca9,  0x2cab,  0x2cad,  0x2caf,  0x2cb1,  0x2cb3,  0x2cb5,  0x2cb7,  0x2cb9,  0x2cbb,  0x2cbd,  0x2cbf,  0x2cc1,  0x2cc3,  0x2cc5,  0x2cc7,  0x2cc9,  0x2ccb,  0x2ccd,  0x2ccf,  0x2cd1,  0x2cd3,  0x2cd5,  0x2cd7,  0x2cd9,  0x2cdb,  0x2cdd,  0x2cdf,  0x2ce1,  0x2ce3,  0x2cec,  0x2cee,  0x2cf3,  0xa641,  0xa643,  0xa645,  0xa647,  0xa649,  0xa64b,  0xa64d,  0xa64f,  0xa651,  0xa653,  0xa655,  0xa657,  0xa659,  0xa65b,  0xa65d,  0xa65f,  0xa661,  0xa663,  0xa665,  0xa667,  0xa669,  0xa66b,  0xa66d,  0xa681,  0xa683,  0xa685,  0xa687,  0xa689,  0xa68b,  0xa68d,  0xa68f,  0xa691,  0xa693,  0xa695,  0xa697,  0xa723,  0xa725,  0xa727,  0xa729,  0xa72b,  0xa72d,  0xa72f,  0xa733,  0xa735,  0xa737,  0xa739,  0xa73b,  0xa73d,  0xa73f,  0xa741,  0xa743,  0xa745,  0xa747,  0xa749,  0xa74b,  0xa74d,  0xa74f,  0xa751,  0xa753,  0xa755,  0xa757,  0xa759,  0xa75b,  0xa75d,  0xa75f,  0xa761,  0xa763,  0xa765,  0xa767,  0xa769,  0xa76b,  0xa76d,  0xa76f,  0xa77a,  0xa77c,  0x1d79,  0xa77f,  0xa781,  0xa783,  0xa785,  0xa787,  0xa78c,  0x265,  0xa791,  0xa793,  0xa7a1,  0xa7a3,  0xa7a5,  0xa7a7,  0xa7a9,  0x266,  0xff41,  0xff42,  0xff43,  0xff44,  0xff45,  0xff46,  0xff47,  0xff48,  0xff49,  0xff4a,  0xff4b,  0xff4c,  0xff4d,  0xff4e,  0xff4f,  0xff50,  0xff51,  0xff52,  0xff53,  0xff54,  0xff55,  0xff56,  0xff57,  0xff58,  0xff59,  0xff5a,  0x10428,  0x10429,  0x1042a,  0x1042b,  0x1042c,  0x1042d,  0x1042e,  0x1042f,  0x10430,  0x10431,  0x10432,  0x10433,  0x10434,  0x10435,  0x10436,  0x10437,  0x10438,  0x10439,  0x1043a,  0x1043b,  0x1043c,  0x1043d,  0x1043e,  0x1043f,  0x10440,  0x10441,  0x10442,  0x10443,  0x10444,  0x10445,  0x10446,  0x10447,  0x10448,  0x10449,  0x1044a,  0x1044b,  0x1044c,  0x1044d,  0x1044e,  0x1044f,  0xdf,  0x2000069,  0x307,  0xfb00,  0xfb01,  0xfb02,  0xfb03,  0xfb04,  0xfb05,  0xfb06,  0x587,  0xfb13,  0xfb14,  0xfb15,  0xfb16,  0xfb17,  0x149,  0x390,  0x3b0,  0x1f0,  0x1e96,  0x1e97,  0x1e98,  0x1e99,  0x1e9a,  0x1f50,  0x1f52,  0x1f54,  0x1f56,  0x1fb6,  0x1fc6,  0x1fd2,  0x1fd3,  0x1fd6,  0x1fd7,  0x1fe2,  0x1fe3,  0x1fe4,  0x1fe6,  0x1fe7,  0x1ff6,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fb3,  0x1fb3,  0x1fc3,  0x1fc3,  0x1ff3,  0x1ff3,  0x1fb2,  0x1fb4,  0x1fc2,  0x1fc4,  0x1ff2,  0x1ff4,  0x1fb7,  0x1fc7,  0x1ff7]; return t; }
+_IUA toTitleTable() { static _IUA t = [ 0x41,  0x42,  0x43,  0x44,  0x45,  0x46,  0x47,  0x48,  0x49,  0x4a,  0x4b,  0x4c,  0x4d,  0x4e,  0x4f,  0x50,  0x51,  0x52,  0x53,  0x54,  0x55,  0x56,  0x57,  0x58,  0x59,  0x5a,  0x39c,  0xc0,  0xc1,  0xc2,  0xc3,  0xc4,  0xc5,  0xc6,  0xc7,  0xc8,  0xc9,  0xca,  0xcb,  0xcc,  0xcd,  0xce,  0xcf,  0xd0,  0xd1,  0xd2,  0xd3,  0xd4,  0xd5,  0xd6,  0xd8,  0xd9,  0xda,  0xdb,  0xdc,  0xdd,  0xde,  0x178,  0x100,  0x102,  0x104,  0x106,  0x108,  0x10a,  0x10c,  0x10e,  0x110,  0x112,  0x114,  0x116,  0x118,  0x11a,  0x11c,  0x11e,  0x120,  0x122,  0x124,  0x126,  0x128,  0x12a,  0x12c,  0x12e,  0x49,  0x132,  0x134,  0x136,  0x139,  0x13b,  0x13d,  0x13f,  0x141,  0x143,  0x145,  0x147,  0x14a,  0x14c,  0x14e,  0x150,  0x152,  0x154,  0x156,  0x158,  0x15a,  0x15c,  0x15e,  0x160,  0x162,  0x164,  0x166,  0x168,  0x16a,  0x16c,  0x16e,  0x170,  0x172,  0x174,  0x176,  0x179,  0x17b,  0x17d,  0x53,  0x243,  0x182,  0x184,  0x187,  0x18b,  0x191,  0x1f6,  0x198,  0x23d,  0x220,  0x1a0,  0x1a2,  0x1a4,  0x1a7,  0x1ac,  0x1af,  0x1b3,  0x1b5,  0x1b8,  0x1bc,  0x1f7,  0x1c5,  0x1c5,  0x1c5,  0x1c8,  0x1c8,  0x1c8,  0x1cb,  0x1cb,  0x1cb,  0x1cd,  0x1cf,  0x1d1,  0x1d3,  0x1d5,  0x1d7,  0x1d9,  0x1db,  0x18e,  0x1de,  0x1e0,  0x1e2,  0x1e4,  0x1e6,  0x1e8,  0x1ea,  0x1ec,  0x1ee,  0x1f2,  0x1f2,  0x1f2,  0x1f4,  0x1f8,  0x1fa,  0x1fc,  0x1fe,  0x200,  0x202,  0x204,  0x206,  0x208,  0x20a,  0x20c,  0x20e,  0x210,  0x212,  0x214,  0x216,  0x218,  0x21a,  0x21c,  0x21e,  0x222,  0x224,  0x226,  0x228,  0x22a,  0x22c,  0x22e,  0x230,  0x232,  0x23b,  0x2c7e,  0x2c7f,  0x241,  0x246,  0x248,  0x24a,  0x24c,  0x24e,  0x2c6f,  0x2c6d,  0x2c70,  0x181,  0x186,  0x189,  0x18a,  0x18f,  0x190,  0x193,  0x194,  0xa78d,  0xa7aa,  0x197,  0x196,  0x2c62,  0x19c,  0x2c6e,  0x19d,  0x19f,  0x2c64,  0x1a6,  0x1a9,  0x1ae,  0x244,  0x1b1,  0x1b2,  0x245,  0x1b7,  0x399,  0x370,  0x372,  0x376,  0x3fd,  0x3fe,  0x3ff,  0x386,  0x388,  0x389,  0x38a,  0x391,  0x392,  0x393,  0x394,  0x395,  0x396,  0x397,  0x398,  0x399,  0x39a,  0x39b,  0x39c,  0x39d,  0x39e,  0x39f,  0x3a0,  0x3a1,  0x3a3,  0x3a3,  0x3a4,  0x3a5,  0x3a6,  0x3a7,  0x3a8,  0x3a9,  0x3aa,  0x3ab,  0x38c,  0x38e,  0x38f,  0x392,  0x398,  0x3a6,  0x3a0,  0x3cf,  0x3d8,  0x3da,  0x3dc,  0x3de,  0x3e0,  0x3e2,  0x3e4,  0x3e6,  0x3e8,  0x3ea,  0x3ec,  0x3ee,  0x39a,  0x3a1,  0x3f9,  0x395,  0x3f7,  0x3fa,  0x410,  0x411,  0x412,  0x413,  0x414,  0x415,  0x416,  0x417,  0x418,  0x419,  0x41a,  0x41b,  0x41c,  0x41d,  0x41e,  0x41f,  0x420,  0x421,  0x422,  0x423,  0x424,  0x425,  0x426,  0x427,  0x428,  0x429,  0x42a,  0x42b,  0x42c,  0x42d,  0x42e,  0x42f,  0x400,  0x401,  0x402,  0x403,  0x404,  0x405,  0x406,  0x407,  0x408,  0x409,  0x40a,  0x40b,  0x40c,  0x40d,  0x40e,  0x40f,  0x460,  0x462,  0x464,  0x466,  0x468,  0x46a,  0x46c,  0x46e,  0x470,  0x472,  0x474,  0x476,  0x478,  0x47a,  0x47c,  0x47e,  0x480,  0x48a,  0x48c,  0x48e,  0x490,  0x492,  0x494,  0x496,  0x498,  0x49a,  0x49c,  0x49e,  0x4a0,  0x4a2,  0x4a4,  0x4a6,  0x4a8,  0x4aa,  0x4ac,  0x4ae,  0x4b0,  0x4b2,  0x4b4,  0x4b6,  0x4b8,  0x4ba,  0x4bc,  0x4be,  0x4c1,  0x4c3,  0x4c5,  0x4c7,  0x4c9,  0x4cb,  0x4cd,  0x4c0,  0x4d0,  0x4d2,  0x4d4,  0x4d6,  0x4d8,  0x4da,  0x4dc,  0x4de,  0x4e0,  0x4e2,  0x4e4,  0x4e6,  0x4e8,  0x4ea,  0x4ec,  0x4ee,  0x4f0,  0x4f2,  0x4f4,  0x4f6,  0x4f8,  0x4fa,  0x4fc,  0x4fe,  0x500,  0x502,  0x504,  0x506,  0x508,  0x50a,  0x50c,  0x50e,  0x510,  0x512,  0x514,  0x516,  0x518,  0x51a,  0x51c,  0x51e,  0x520,  0x522,  0x524,  0x526,  0x531,  0x532,  0x533,  0x534,  0x535,  0x536,  0x537,  0x538,  0x539,  0x53a,  0x53b,  0x53c,  0x53d,  0x53e,  0x53f,  0x540,  0x541,  0x542,  0x543,  0x544,  0x545,  0x546,  0x547,  0x548,  0x549,  0x54a,  0x54b,  0x54c,  0x54d,  0x54e,  0x54f,  0x550,  0x551,  0x552,  0x553,  0x554,  0x555,  0x556,  0xa77d,  0x2c63,  0x1e00,  0x1e02,  0x1e04,  0x1e06,  0x1e08,  0x1e0a,  0x1e0c,  0x1e0e,  0x1e10,  0x1e12,  0x1e14,  0x1e16,  0x1e18,  0x1e1a,  0x1e1c,  0x1e1e,  0x1e20,  0x1e22,  0x1e24,  0x1e26,  0x1e28,  0x1e2a,  0x1e2c,  0x1e2e,  0x1e30,  0x1e32,  0x1e34,  0x1e36,  0x1e38,  0x1e3a,  0x1e3c,  0x1e3e,  0x1e40,  0x1e42,  0x1e44,  0x1e46,  0x1e48,  0x1e4a,  0x1e4c,  0x1e4e,  0x1e50,  0x1e52,  0x1e54,  0x1e56,  0x1e58,  0x1e5a,  0x1e5c,  0x1e5e,  0x1e60,  0x1e62,  0x1e64,  0x1e66,  0x1e68,  0x1e6a,  0x1e6c,  0x1e6e,  0x1e70,  0x1e72,  0x1e74,  0x1e76,  0x1e78,  0x1e7a,  0x1e7c,  0x1e7e,  0x1e80,  0x1e82,  0x1e84,  0x1e86,  0x1e88,  0x1e8a,  0x1e8c,  0x1e8e,  0x1e90,  0x1e92,  0x1e94,  0x1e60,  0x1ea0,  0x1ea2,  0x1ea4,  0x1ea6,  0x1ea8,  0x1eaa,  0x1eac,  0x1eae,  0x1eb0,  0x1eb2,  0x1eb4,  0x1eb6,  0x1eb8,  0x1eba,  0x1ebc,  0x1ebe,  0x1ec0,  0x1ec2,  0x1ec4,  0x1ec6,  0x1ec8,  0x1eca,  0x1ecc,  0x1ece,  0x1ed0,  0x1ed2,  0x1ed4,  0x1ed6,  0x1ed8,  0x1eda,  0x1edc,  0x1ede,  0x1ee0,  0x1ee2,  0x1ee4,  0x1ee6,  0x1ee8,  0x1eea,  0x1eec,  0x1eee,  0x1ef0,  0x1ef2,  0x1ef4,  0x1ef6,  0x1ef8,  0x1efa,  0x1efc,  0x1efe,  0x1f08,  0x1f09,  0x1f0a,  0x1f0b,  0x1f0c,  0x1f0d,  0x1f0e,  0x1f0f,  0x1f18,  0x1f19,  0x1f1a,  0x1f1b,  0x1f1c,  0x1f1d,  0x1f28,  0x1f29,  0x1f2a,  0x1f2b,  0x1f2c,  0x1f2d,  0x1f2e,  0x1f2f,  0x1f38,  0x1f39,  0x1f3a,  0x1f3b,  0x1f3c,  0x1f3d,  0x1f3e,  0x1f3f,  0x1f48,  0x1f49,  0x1f4a,  0x1f4b,  0x1f4c,  0x1f4d,  0x1f59,  0x1f5b,  0x1f5d,  0x1f5f,  0x1f68,  0x1f69,  0x1f6a,  0x1f6b,  0x1f6c,  0x1f6d,  0x1f6e,  0x1f6f,  0x1fba,  0x1fbb,  0x1fc8,  0x1fc9,  0x1fca,  0x1fcb,  0x1fda,  0x1fdb,  0x1ff8,  0x1ff9,  0x1fea,  0x1feb,  0x1ffa,  0x1ffb,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fb8,  0x1fb9,  0x1fbc,  0x399,  0x1fcc,  0x1fd8,  0x1fd9,  0x1fe8,  0x1fe9,  0x1fec,  0x1ffc,  0x2132,  0x2160,  0x2161,  0x2162,  0x2163,  0x2164,  0x2165,  0x2166,  0x2167,  0x2168,  0x2169,  0x216a,  0x216b,  0x216c,  0x216d,  0x216e,  0x216f,  0x2183,  0x24b6,  0x24b7,  0x24b8,  0x24b9,  0x24ba,  0x24bb,  0x24bc,  0x24bd,  0x24be,  0x24bf,  0x24c0,  0x24c1,  0x24c2,  0x24c3,  0x24c4,  0x24c5,  0x24c6,  0x24c7,  0x24c8,  0x24c9,  0x24ca,  0x24cb,  0x24cc,  0x24cd,  0x24ce,  0x24cf,  0x2c00,  0x2c01,  0x2c02,  0x2c03,  0x2c04,  0x2c05,  0x2c06,  0x2c07,  0x2c08,  0x2c09,  0x2c0a,  0x2c0b,  0x2c0c,  0x2c0d,  0x2c0e,  0x2c0f,  0x2c10,  0x2c11,  0x2c12,  0x2c13,  0x2c14,  0x2c15,  0x2c16,  0x2c17,  0x2c18,  0x2c19,  0x2c1a,  0x2c1b,  0x2c1c,  0x2c1d,  0x2c1e,  0x2c1f,  0x2c20,  0x2c21,  0x2c22,  0x2c23,  0x2c24,  0x2c25,  0x2c26,  0x2c27,  0x2c28,  0x2c29,  0x2c2a,  0x2c2b,  0x2c2c,  0x2c2d,  0x2c2e,  0x2c60,  0x23a,  0x23e,  0x2c67,  0x2c69,  0x2c6b,  0x2c72,  0x2c75,  0x2c80,  0x2c82,  0x2c84,  0x2c86,  0x2c88,  0x2c8a,  0x2c8c,  0x2c8e,  0x2c90,  0x2c92,  0x2c94,  0x2c96,  0x2c98,  0x2c9a,  0x2c9c,  0x2c9e,  0x2ca0,  0x2ca2,  0x2ca4,  0x2ca6,  0x2ca8,  0x2caa,  0x2cac,  0x2cae,  0x2cb0,  0x2cb2,  0x2cb4,  0x2cb6,  0x2cb8,  0x2cba,  0x2cbc,  0x2cbe,  0x2cc0,  0x2cc2,  0x2cc4,  0x2cc6,  0x2cc8,  0x2cca,  0x2ccc,  0x2cce,  0x2cd0,  0x2cd2,  0x2cd4,  0x2cd6,  0x2cd8,  0x2cda,  0x2cdc,  0x2cde,  0x2ce0,  0x2ce2,  0x2ceb,  0x2ced,  0x2cf2,  0x10a0,  0x10a1,  0x10a2,  0x10a3,  0x10a4,  0x10a5,  0x10a6,  0x10a7,  0x10a8,  0x10a9,  0x10aa,  0x10ab,  0x10ac,  0x10ad,  0x10ae,  0x10af,  0x10b0,  0x10b1,  0x10b2,  0x10b3,  0x10b4,  0x10b5,  0x10b6,  0x10b7,  0x10b8,  0x10b9,  0x10ba,  0x10bb,  0x10bc,  0x10bd,  0x10be,  0x10bf,  0x10c0,  0x10c1,  0x10c2,  0x10c3,  0x10c4,  0x10c5,  0x10c7,  0x10cd,  0xa640,  0xa642,  0xa644,  0xa646,  0xa648,  0xa64a,  0xa64c,  0xa64e,  0xa650,  0xa652,  0xa654,  0xa656,  0xa658,  0xa65a,  0xa65c,  0xa65e,  0xa660,  0xa662,  0xa664,  0xa666,  0xa668,  0xa66a,  0xa66c,  0xa680,  0xa682,  0xa684,  0xa686,  0xa688,  0xa68a,  0xa68c,  0xa68e,  0xa690,  0xa692,  0xa694,  0xa696,  0xa722,  0xa724,  0xa726,  0xa728,  0xa72a,  0xa72c,  0xa72e,  0xa732,  0xa734,  0xa736,  0xa738,  0xa73a,  0xa73c,  0xa73e,  0xa740,  0xa742,  0xa744,  0xa746,  0xa748,  0xa74a,  0xa74c,  0xa74e,  0xa750,  0xa752,  0xa754,  0xa756,  0xa758,  0xa75a,  0xa75c,  0xa75e,  0xa760,  0xa762,  0xa764,  0xa766,  0xa768,  0xa76a,  0xa76c,  0xa76e,  0xa779,  0xa77b,  0xa77e,  0xa780,  0xa782,  0xa784,  0xa786,  0xa78b,  0xa790,  0xa792,  0xa7a0,  0xa7a2,  0xa7a4,  0xa7a6,  0xa7a8,  0xff21,  0xff22,  0xff23,  0xff24,  0xff25,  0xff26,  0xff27,  0xff28,  0xff29,  0xff2a,  0xff2b,  0xff2c,  0xff2d,  0xff2e,  0xff2f,  0xff30,  0xff31,  0xff32,  0xff33,  0xff34,  0xff35,  0xff36,  0xff37,  0xff38,  0xff39,  0xff3a,  0x10400,  0x10401,  0x10402,  0x10403,  0x10404,  0x10405,  0x10406,  0x10407,  0x10408,  0x10409,  0x1040a,  0x1040b,  0x1040c,  0x1040d,  0x1040e,  0x1040f,  0x10410,  0x10411,  0x10412,  0x10413,  0x10414,  0x10415,  0x10416,  0x10417,  0x10418,  0x10419,  0x1041a,  0x1041b,  0x1041c,  0x1041d,  0x1041e,  0x1041f,  0x10420,  0x10421,  0x10422,  0x10423,  0x10424,  0x10425,  0x10426,  0x10427,  0x2000053,  0x73,  0x130,  0x2000046,  0x66,  0x2000046,  0x69,  0x2000046,  0x6c,  0x3000046,  0x66,  0x69,  0x3000046,  0x66,  0x6c,  0x2000053,  0x74,  0x2000053,  0x74,  0x2000535,  0x582,  0x2000544,  0x576,  0x2000544,  0x565,  0x2000544,  0x56b,  0x200054e,  0x576,  0x2000544,  0x56d,  0x20002bc,  0x4e,  0x3000399,  0x308,  0x301,  0x30003a5,  0x308,  0x301,  0x200004a,  0x30c,  0x2000048,  0x331,  0x2000054,  0x308,  0x2000057,  0x30a,  0x2000059,  0x30a,  0x2000041,  0x2be,  0x20003a5,  0x313,  0x30003a5,  0x313,  0x300,  0x30003a5,  0x313,  0x301,  0x30003a5,  0x313,  0x342,  0x2000391,  0x342,  0x2000397,  0x342,  0x3000399,  0x308,  0x300,  0x3000399,  0x308,  0x301,  0x2000399,  0x342,  0x3000399,  0x308,  0x342,  0x30003a5,  0x308,  0x300,  0x30003a5,  0x308,  0x301,  0x20003a1,  0x313,  0x20003a5,  0x342,  0x30003a5,  0x308,  0x342,  0x20003a9,  0x342,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fbc,  0x1fbc,  0x1fcc,  0x1fcc,  0x1ffc,  0x1ffc,  0x2001fba,  0x345,  0x2000386,  0x345,  0x2001fca,  0x345,  0x2000389,  0x345,  0x2001ffa,  0x345,  0x200038f,  0x345,  0x3000391,  0x342,  0x345,  0x3000397,  0x342,  0x345,  0x30003a9,  0x342,  0x345]; return t; }
+}
+
+}
+
+
+static if(size_t.sizeof == 4) {
+//1536 bytes
+enum lowerCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([ 0x0,  0x40,  0x80], [ 0x100,  0x80,  0x2000], [ 0x2020100,  0x4020302,  0x2020205,  0x2060202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x50004,  0x30006,  0x30007,  0x30003,  0x30008,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x90003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0xa0003,  0xb0003,  0x30003,  0x3000c,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0xe000d,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x0,  0x0,  0x0,  0x7fffffe,  0x0,  0x4200400,  0x80000000,  0xff7fffff,  0xaaaaaaaa,  0x55aaaaaa,  0xaaaaab55,  0xd4aaaaaa,  0x4e243129,  0xe6512d2a,  0xb5555240,  0xaa29aaaa,  0xaaaaaaaa,  0x93faaaaa,  0xffffaa85,  0xffffffff,  0xffefffff,  0x1ffffff,  0x3,  0x1f,  0x0,  0x0,  0x20,  0x3c8a0000,  0x10000,  0xfffff000,  0xaae37fff,  0x192faaaa,  0x0,  0xffff0000,  0xffffffff,  0xaaaaaaaa,  0xaaaaa802,  0xaaaaaaaa,  0xaaaad554,  0xaaaaaaaa,  0xaaaaaaaa,  0xaa,  0x0,  0xfffffffe,  0xff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x0,  0x0,  0xaaaaaaaa,  0xaaaaaaaa,  0xaaaaaaaa,  0xaaaaaaaa,  0xbfeaaaaa,  0xaaaaaaaa,  0xaaaaaaaa,  0xaaaaaaaa,  0x3f00ff,  0xff00ff,  0xff003f,  0x3fff00ff,  0xff00ff,  0x40df00ff,  0xcf00dc,  0xdc00ff,  0x0,  0x0,  0x0,  0x80020000,  0x1fff0000,  0x0,  0x0,  0x0,  0x8c400,  0x32108000,  0x43c0,  0xffff0000,  0x10,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0x3ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0x7fffffff,  0x3fda1562,  0xaaaaaaaa,  0xaaaaaaaa,  0xaaaaaaaa,  0x8501a,  0xffffffff,  0x20bf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xaaaaaaaa,  0x2aaa,  0xaaaaaa,  0x0,  0x0,  0x0,  0x0,  0xaaabaaa8,  0xaaaaaaaa,  0x95ffaaaa,  0xa50aa,  0x2aa,  0x0,  0x7000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf8007f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7fffffe,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffff00,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfc000000,  0xfffff,  0xffdfc000,  0xff,  0xffffffc,  0xebc00000,  0xffef,  0xfffffc00,  0xc000000f,  0xffffff,  0xfffc0000,  0xfff,  0xffffffc0,  0xfc000000,  0xfffff,  0xffffc000,  0xff,  0xffffffc,  0xffc00000,  0xffff,  0xfffffc00,  0x3f,  0xf7fffffc,  0xf0000003,  0xfdfffff,  0xffc00000,  0x3f7fff,  0xffff0000,  0xfdff,  0xfffffc00,  0xbf7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//1472 bytes
+enum upperCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([ 0x0,  0x40,  0x80], [ 0x100,  0x80,  0x1e00], [ 0x2020100,  0x4020302,  0x2020205,  0x2060202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x30003,  0x30003,  0x30004,  0x30003,  0x30003,  0x50003,  0x30006,  0x30007,  0x30003,  0x30008,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x90003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0xa0003,  0x30003,  0x3000b,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0xd000c,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x30003,  0x0,  0x0,  0x7fffffe,  0x0,  0x0,  0x0,  0x7f7fffff,  0x0,  0x55555555,  0xaa555555,  0x555554aa,  0x2b555555,  0xb1dbced6,  0x11aed2d5,  0x4aaaa490,  0x55d25555,  0x55555555,  0x6c055555,  0x557a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x450000,  0xfffed740,  0xffb,  0x551c8000,  0xe6905555,  0xffffffff,  0xffff,  0x0,  0x55555555,  0x55555401,  0x55555555,  0x55552aab,  0x55555555,  0x55555555,  0xfffe0055,  0x7fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x20bf,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x55555555,  0x55555555,  0x55555555,  0x55555555,  0x40155555,  0x55555555,  0x55555555,  0x55555555,  0x3f00ff00,  0xff00ff00,  0xaa003f00,  0xff00,  0x0,  0xf000000,  0xf000f00,  0xf001f00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3e273884,  0xc00f3d50,  0x20,  0xffff,  0x8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffc00000,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x7fff,  0x0,  0xc025ea9d,  0x55555555,  0x55555555,  0x55555555,  0x42805,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x55555555,  0x1555,  0x555555,  0x0,  0x0,  0x0,  0x0,  0x55545554,  0x55555555,  0x6a005555,  0x52855,  0x555,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7fffffe,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ffffff,  0xfff00000,  0x3fff,  0xffffff00,  0xd0000003,  0x3fde64,  0xffff0000,  0x3ff,  0x1fdfe7b0,  0x7b000000,  0x1fc5f,  0xfffff000,  0x3f,  0x3ffffff,  0xfff00000,  0x3fff,  0xffffff00,  0xf0000003,  0x3fffff,  0xffff0000,  0x3ff,  0xffffff00,  0x1,  0x7fffffc,  0xf0000000,  0x1fffff,  0xffc00000,  0x7fff,  0xffff0000,  0x1ff,  0x400,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//8704 bytes
+enum simpleCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xd00], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160015,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x0,  0x190018,  0x1b001a,  0x1d001c,  0x1f001e,  0x0,  0x0,  0x210020,  0x22,  0x0,  0x0,  0x0,  0x0,  0x0,  0x240023,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260025,  0x280027,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a0000,  0x2b,  0x2d002c,  0x2e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30002f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x320031,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x20ffff,  0x240022,  0x280026,  0x2c002a,  0x30002e,  0x72f0032,  0x390037,  0x3d003b,  0x41003f,  0x1b00043,  0x4a0048,  0x4e004c,  0x520050,  0xffff0054,  0xffffffff,  0xffffffff,  0x21ffff,  0x250023,  0x290027,  0x2d002b,  0x31002f,  0x7300033,  0x3a0038,  0x3e003c,  0x420040,  0x1b10044,  0x4b0049,  0x4f004d,  0x530051,  0xffff0055,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x43fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xc800c6,  0xcc0498,  0x14904aa,  0xd500d3,  0xd900d7,  0xdd00db,  0xe100df,  0xe500e3,  0xe900e7,  0xed00eb,  0xf100ef,  0xffff00f3,  0xf700f5,  0xfb00f9,  0xff00fd,  0x6be0101,  0xc900c7,  0xcd0499,  0x14a04ab,  0xd600d4,  0xda00d8,  0xde00dc,  0xe200e0,  0xe600e4,  0xea00e8,  0xee00ec,  0xf200f0,  0xffff00f4,  0xf800f6,  0xfc00fa,  0x10000fe,  0x1a80102,  0x1160115,  0x1180117,  0x11c011b,  0x11e011d,  0x120011f,  0x1240123,  0x1260125,  0x1280127,  0x12c012b,  0x12e012d,  0x130012f,  0x1340133,  0x1360135,  0x1380137,  0x13a0139,  0x13c013b,  0x13e013d,  0x140013f,  0x1420141,  0x1440143,  0x1460145,  0x1480147,  0x14d014c,  0x14f014e,  0xffffffff,  0x1510150,  0x1530152,  0x1550154,  0x156ffff,  0x1580157,  0x15c0159,  0x15e015d,  0x160015f,  0x1620161,  0x1640163,  0x1660165,  0xffff0167,  0x1690168,  0x16b016a,  0x16d016c,  0x16f016e,  0x1710170,  0x1730172,  0x1750174,  0x1770176,  0x1790178,  0x17b017a,  0x17d017c,  0x17f017e,  0x1830182,  0x1870186,  0x18b018a,  0x18f018e,  0x1930192,  0x1970196,  0x19b019a,  0x19f019e,  0x1a301a2,  0x1a501a4,  0x1a701a6,  0x1aa01a9,  0x1ac01ab,  0x1ae01ad,  0x1b201af,  0x1b3028b,  0x1b601b5,  0x1ba01b9,  0x1bd01bb,  0x1bf01be,  0x1c301c1,  0xffff01c4,  0x1c701c5,  0x1cb01c9,  0x1cd01cc,  0x23b01cf,  0x1d301d1,  0x1d601d5,  0xffff0283,  0x1d901d7,  0x1db0269,  0x1de01dd,  0x1e001df,  0x1e201e1,  0x1e501e3,  0x1e701e6,  0xffffffff,  0x1ea01e9,  0x1ed01eb,  0x1ef01ee,  0x1f301f1,  0x1f501f4,  0x1f701f6,  0x1fa01f9,  0xffffffff,  0x1fc01fb,  0x23dffff,  0xffffffff,  0xffffffff,  0x2010200,  0x2060202,  0x2080207,  0x20d020c,  0x20f020e,  0x2110210,  0x2130212,  0x2150214,  0x2170216,  0x2190218,  0x21b021a,  0x21d021c,  0x1c6021e,  0x220021f,  0x2240223,  0x2260225,  0x2280227,  0x22a0229,  0x22c022b,  0x22e022d,  0x230022f,  0x2320231,  0x236ffff,  0x2380237,  0x23a0239,  0x23e023c,  0x240023f,  0x2440243,  0x2460245,  0x2480247,  0x24a0249,  0x24c024b,  0x24e024d,  0x250024f,  0x2520251,  0x2540253,  0x2560255,  0x2580257,  0x25a0259,  0x25c025b,  0x25e025d,  0x260025f,  0x2620261,  0x2640263,  0x2660265,  0x2680267,  0xffff026a,  0x26c026b,  0x26e026d,  0x270026f,  0x2720271,  0x2740273,  0x2760275,  0x2780277,  0x27a0279,  0x27c027b,  0xffffffff,  0xffffffff,  0xffffffff,  0x281027f,  0x2840282,  0x2d70285,  0x2870482,  0x28c0288,  0x28f028d,  0x2920291,  0x2940293,  0x2960295,  0x2980297,  0x29c029b,  0x466046a,  0x1b402b7,  0xffff01bc,  0x1c201c0,  0x1c8ffff,  0x1caffff,  0xffffffff,  0xffffffff,  0xffff01ce,  0x1d0ffff,  0x748ffff,  0xffff05fa,  0x1d201d4,  0x528ffff,  0xffffffff,  0x1d8ffff,  0x2b3ffff,  0xffff01da,  0x1dcffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2a3ffff,  0xffffffff,  0xffff01e4,  0x1e8ffff,  0xffffffff,  0xffffffff,  0x28e01ec,  0x1f201f0,  0xffff0290,  0xffffffff,  0xffffffff,  0xffff01f8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x83affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x31e031d,  0x320031f,  0xffffffff,  0x3240323,  0xffffffff,  0x3d5ffff,  0x3d903d7,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0329,  0x32f032d,  0xffff0331,  0xffff0333,  0x3370335,  0x339ffff,  0x33e0395,  0x3cc0340,  0x3470345,  0x83b03c8,  0x35403c2,  0x3590440,  0x35d035b,  0x3c5039f,  0x388ffff,  0x36a0368,  0x36f039c,  0x7100371,  0x3780376,  0x32e032a,  0x3320330,  0x33affff,  0x33f0396,  0x3cd0341,  0x3480346,  0x83c03c9,  0x35503c3,  0x35a0441,  0x35e035c,  0x3c603a0,  0x38a0389,  0x36b0369,  0x370039d,  0x7110372,  0x3790377,  0x3360334,  0x3930338,  0x3ca0397,  0xffffffff,  0x39effff,  0x39403a1,  0x3a303a2,  0x3a703a6,  0x3a903a8,  0x3ab03aa,  0x3ad03ac,  0x3af03ae,  0x3b103b0,  0x3b503b4,  0x3b903b8,  0x3bd03bc,  0x3bf03be,  0x3c103c0,  0x3c703c4,  0xffff03d1,  0x3ce03cb,  0x3cfffff,  0x3d203d0,  0x3d403d3,  0x3d6ffff,  0x3da03d8,  0x3dd03db,  0x3e103df,  0x3e503e3,  0x3e903e7,  0x3ed03eb,  0x3f103ef,  0x3f503f3,  0x3f903f7,  0x3fd03fb,  0x40103ff,  0x4050403,  0x4090407,  0x40d040b,  0x411040f,  0x4150413,  0x4190417,  0x41d041b,  0x421041f,  0x4250423,  0x4290427,  0x42d042b,  0x431042f,  0x4350433,  0x4390437,  0x3fe03fc,  0x4020400,  0x4060404,  0x40a0408,  0x40e040c,  0x4120410,  0x4160414,  0x41a0418,  0x41e041c,  0x4220420,  0x4260424,  0x42a0428,  0x42e042c,  0x4320430,  0x4360434,  0x43a0438,  0x3de03dc,  0x3e203e0,  0x3e603e4,  0x3ea03e8,  0x3ee03ec,  0x3f203f0,  0x3f603f4,  0x3fa03f8,  0x4510450,  0x4530452,  0x4570456,  0x4590458,  0x45d045c,  0x4610460,  0x4650464,  0x4690468,  0x46d046c,  0x4710470,  0x4730472,  0x4770476,  0x4790478,  0x47b047a,  0x47d047c,  0x4810480,  0x4850484,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4950494,  0x4970496,  0x49b049a,  0x49d049c,  0x49f049e,  0x4a304a2,  0x4a704a6,  0x4a904a8,  0x4ad04ac,  0x4b104b0,  0x4b304b2,  0x4b704b6,  0x4b904b8,  0x4bb04ba,  0x4bf04be,  0x4c104c0,  0x4c504c4,  0x4c904c8,  0x4cd04cc,  0x4cf04ce,  0x4d304d2,  0x4d504d4,  0x4d704d6,  0x4db04da,  0x4df04de,  0x4e304e2,  0x4e704e6,  0x4ec04ea,  0x4f004ed,  0x4f404f1,  0x4f804f5,  0x4fc04f9,  0x50004fd,  0x5040501,  0x4eb0505,  0x50b050a,  0x50d050c,  0x50f050e,  0x5130512,  0x5170516,  0x5190518,  0x51d051c,  0x51f051e,  0x5210520,  0x5250524,  0x5270526,  0x52b052a,  0x52d052c,  0x52f052e,  0x5330532,  0x5370536,  0x5390538,  0x53d053c,  0x53f053e,  0x5410540,  0x5430542,  0x5470546,  0x5490548,  0x54b054a,  0x54d054c,  0x54f054e,  0x5510550,  0x5550554,  0x5570556,  0x5590558,  0x55b055a,  0x55d055c,  0x55f055e,  0x5630562,  0x5650564,  0x5670566,  0x5690568,  0x56b056a,  0x56f056e,  0x5730572,  0x5750574,  0x5770576,  0x5790578,  0x57b057a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x580ffff,  0x5840582,  0x5880586,  0x58c058a,  0x590058e,  0x5940592,  0x5980596,  0x59c059a,  0x5a0059e,  0x5a405a2,  0x5a805a6,  0x5ac05aa,  0x5b005ae,  0x5b405b2,  0x5b805b6,  0x5bc05ba,  0x5c005be,  0x5c405c2,  0x5c805c6,  0xffff05ca,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x581ffff,  0x5850583,  0x5890587,  0x58d058b,  0x591058f,  0x5950593,  0x5990597,  0x59d059b,  0x5a1059f,  0x5a505a3,  0x5a905a7,  0x5ad05ab,  0x5b105af,  0x5b505b3,  0x5b905b7,  0x5bd05bb,  0x5c105bf,  0x5c505c3,  0x5c905c7,  0xffff05cb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x880086,  0x8c008a,  0x90008e,  0x940092,  0x980096,  0x9c009a,  0xa0009e,  0xa400a2,  0xa800a6,  0xac00aa,  0xb000ae,  0xb400b2,  0xb800b6,  0xbc00ba,  0xc000be,  0xc400c2,  0x48e0486,  0x4a000ca,  0x4b400ce,  0x4c6ffff,  0xffffffff,  0xffffffff,  0x508ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7e8ffff,  0xffffffff,  0x454ffff,  0xffffffff,  0x5fd05fc,  0x5ff05fe,  0x6010600,  0x6050604,  0x6090608,  0x60b060a,  0x60f060e,  0x6110610,  0x6130612,  0x6170616,  0x6190618,  0x61d061c,  0x61f061e,  0x6210620,  0x6230622,  0x6270626,  0x6290628,  0x62b062a,  0x62d062c,  0x62f062e,  0x6310630,  0x6350634,  0x6370636,  0x6390638,  0x63b063a,  0x63d063c,  0x63f063e,  0x6430642,  0x6450644,  0x6470646,  0x6490648,  0x64b064a,  0x64d064c,  0x6510650,  0x6530652,  0x6550654,  0x6590658,  0x65d065c,  0x65f065e,  0x6630662,  0x6650664,  0x6670666,  0x6690668,  0x66b066a,  0x66d066c,  0x6710670,  0x6730672,  0x6750674,  0x6bc06bb,  0x67a0679,  0x67c067b,  0x680067f,  0x6820681,  0x6840683,  0x6860685,  0x6880687,  0x68a0689,  0x68e068d,  0x690068f,  0x6920691,  0x6960695,  0x6980697,  0x69a0699,  0x69e069d,  0x6a0069f,  0x6a206a1,  0x6a406a3,  0x6a606a5,  0x6a806a7,  0x6ac06ab,  0x6ae06ad,  0x6b006af,  0x6b206b1,  0x6b406b3,  0x6b606b5,  0xffffffff,  0xffffffff,  0x6bdffff,  0xffffffff,  0xffff06bf,  0x6c106c0,  0x6c306c2,  0x6c506c4,  0x6c906c8,  0x6cb06ca,  0x6cd06cc,  0x6cf06ce,  0x6d106d0,  0x6d506d4,  0x6d706d6,  0x6db06da,  0x6dd06dc,  0x6df06de,  0x6e106e0,  0x6e306e2,  0x6e506e4,  0x6e906e8,  0x6eb06ea,  0x6ef06ee,  0x6f106f0,  0x6f306f2,  0x6f506f4,  0x6f706f6,  0x6f906f8,  0x6fb06fa,  0x6fd06fc,  0x6ff06fe,  0x7010700,  0x7030702,  0x7050704,  0x7070706,  0x7090708,  0x70b070a,  0x70d070c,  0x70f070e,  0x7140713,  0x7160715,  0x7180717,  0x71c071b,  0x71e071d,  0x720071f,  0x7220721,  0x7240723,  0x7260725,  0x7280727,  0x72a0729,  0x72e072d,  0x7330732,  0x7380736,  0x73c073a,  0x740073e,  0x7440742,  0x7390737,  0x73d073b,  0x741073f,  0x7450743,  0x74c074a,  0x750074e,  0x7540752,  0xffffffff,  0x74d074b,  0x751074f,  0x7550753,  0xffffffff,  0x7660764,  0x76a0768,  0x76e076c,  0x7720770,  0x7670765,  0x76b0769,  0x76f076d,  0x7730771,  0x7860784,  0x78a0788,  0x78e078c,  0x7920790,  0x7870785,  0x78b0789,  0x78f078d,  0x7930791,  0x79e079c,  0x7a207a0,  0x7a607a4,  0xffffffff,  0x79f079d,  0x7a307a1,  0x7a707a5,  0xffffffff,  0x7b6ffff,  0x7baffff,  0x7beffff,  0x7c2ffff,  0x7b7ffff,  0x7bbffff,  0x7bfffff,  0x7c3ffff,  0x7d207d0,  0x7d607d4,  0x7da07d8,  0x7de07dc,  0x7d307d1,  0x7d707d5,  0x7db07d9,  0x7df07dd,  0x8360834,  0x840083e,  0x8440842,  0x84e084c,  0x8620860,  0x8580856,  0x8660864,  0xffffffff,  0x7f607f4,  0x7fa07f8,  0x7fe07fc,  0x8020800,  0x7f707f5,  0x7fb07f9,  0x7ff07fd,  0x8030801,  0x80a0808,  0x80e080c,  0x8120810,  0x8160814,  0x80b0809,  0x80f080d,  0x8130811,  0x8170815,  0x8220820,  0x8260824,  0x82a0828,  0x82e082c,  0x8230821,  0x8270825,  0x82b0829,  0x82f082d,  0x8320830,  0x838ffff,  0xffffffff,  0xffffffff,  0x8330831,  0x8370835,  0xffff0839,  0xffff083d,  0xffffffff,  0x846ffff,  0xffffffff,  0xffffffff,  0x841083f,  0x8450843,  0xffff0847,  0xffffffff,  0x84a0848,  0xffffffff,  0xffffffff,  0xffffffff,  0x84b0849,  0x84f084d,  0xffffffff,  0xffffffff,  0x8540852,  0xffffffff,  0x85affff,  0xffffffff,  0x8550853,  0x8590857,  0xffff085b,  0xffffffff,  0xffffffff,  0x868ffff,  0xffffffff,  0xffffffff,  0x8630861,  0x8670865,  0xffff0869,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0712,  0xffffffff,  0x14b0731,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0530,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0531,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x180029f,  0x18402af,  0x18802c1,  0x18c005e,  0x1900064,  0x194006c,  0x1980076,  0x19c007e,  0x18102a0,  0x18502b0,  0x18902c2,  0x18d005f,  0x1910065,  0x195006d,  0x1990077,  0x19d007f,  0xffffffff,  0x1b7ffff,  0xffff01b8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4d80444,  0x4dc0446,  0x4e0044c,  0x4e4045e,  0x4e80474,  0x2d3086a,  0x204ee,  0x6c604f2,  0x6e04f6,  0x37a04fa,  0x10d04fe,  0x61a0502,  0x51a0506,  0x4d90445,  0x4dd0447,  0x4e1044d,  0x4e5045f,  0x4e90475,  0x2d4086b,  0x304ef,  0x6c704f3,  0x6f04f7,  0x37b04fb,  0x10e04ff,  0x61b0503,  0x51b0507,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x40000,  0xa0008,  0xe000c,  0x160010,  0x1a0018,  0x2bb001c,  0x2d102c7,  0x5602e7,  0x600058,  0x660062,  0x70006a,  0x780074,  0x80007c,  0x2990082,  0x607e0,  0x6020084,  0x2a7057c,  0x5d005ce,  0x10305de,  0x1070105,  0x10f0109,  0x1190113,  0x1290121,  0xffff0131,  0x50001,  0xb0009,  0xf000d,  0x170011,  0x1b0019,  0x2bc001d,  0x2d202c8,  0x5702e8,  0x610059,  0x670063,  0x71006b,  0x790075,  0x81007d,  0x29a0083,  0x707e1,  0x6030085,  0x2a8057d,  0x5d105cf,  0x10405df,  0x1080106,  0x110010a,  0x11a0114,  0x12a0122,  0xffff0132,  0x4c304c2,  0x4550529,  0x28002a4,  0x45a0286,  0x2a9045b,  0x46202aa,  0x4670463,  0x46b02b4,  0xffff02b8,  0x2ba02b9,  0x2bfffff,  0xffff02c0,  0xffffffff,  0xffffffff,  0xffffffff,  0x48302d8,  0x2e202e1,  0x4890488,  0x48b048a,  0x48d048c,  0x4910490,  0x2fe02fd,  0x3040303,  0x30e030d,  0x3160315,  0x31a0319,  0x3260325,  0x3280327,  0x2fc02fb,  0x6ed06ec,  0x3810380,  0x3830382,  0x3870386,  0x3920391,  0x3a503a4,  0x3b303b2,  0x56d056c,  0x5cd05cc,  0x5db05da,  0x5ed05ec,  0x60d060c,  0x6570656,  0x43e043d,  0x6e706e6,  0x72c072b,  0x7830782,  0x7e307e2,  0x6940693,  0x65b065a,  0x150014,  0x5d005c,  0x4bd04bc,  0x4d104d0,  0x5d505d4,  0x1a101a0,  0x5110510,  0x5230522,  0x5350534,  0x5450544,  0x5530552,  0x5610560,  0x5710570,  0x57f057e,  0x15b015a,  0x37d037c,  0x3bb03ba,  0xffffffff,  0xffffffff,  0xffffffff,  0x5d2ffff,  0x5d805d3,  0xffff05d9,  0xffffffff,  0x5e305e2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x890087,  0x8d008b,  0x91008f,  0x950093,  0x990097,  0x9d009b,  0xa1009f,  0xa500a3,  0xa900a7,  0xad00ab,  0xb100af,  0xb500b3,  0xb900b7,  0xbd00bb,  0xc100bf,  0xc500c3,  0x48f0487,  0x4a100cb,  0x4b500cf,  0x4c7ffff,  0xffffffff,  0xffffffff,  0x509ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c402c3,  0x5d705d6,  0x5dd05dc,  0x5e105e0,  0x5e705e6,  0x5e905e8,  0x5eb05ea,  0x5ef05ee,  0x5f105f0,  0x5f505f4,  0x5f905f8,  0x3080307,  0x6150614,  0x6250624,  0x6330632,  0x6410640,  0x64f064e,  0x6610660,  0x66f066e,  0x67e067d,  0x68c068b,  0x69c069b,  0x6aa06a9,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7350734,  0x690068,  0x27e027d,  0x75f075e,  0x7770776,  0x390038f,  0x1f001e,  0x7b107b0,  0x7c707c6,  0x2a202a1,  0x7e507e4,  0x6b806b7,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7590758,  0x75d075c,  0x7610760,  0x2d602d5,  0x2e002df,  0x2e602e5,  0x2ee02ed,  0xffffffff,  0x7790778,  0x77d077c,  0x7810780,  0x30c030b,  0x3140313,  0x3180317,  0x3220321,  0x7950794,  0x7970796,  0x7990798,  0x79b079a,  0x37f037e,  0x3850384,  0x38e038d,  0x7a907a8,  0x7ab07aa,  0x7ad07ac,  0x7af07ae,  0x7b307b2,  0x7b507b4,  0x7b907b8,  0x7bd07bc,  0x7c107c0,  0x7c507c4,  0x7c907c8,  0x7cd07cc,  0x7cf07ce,  0x46f046e,  0x47f047e,  0x4930492,  0x4a504a4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x514ffff,  0x7e60515,  0x7e907e7,  0x7eb07ea,  0x7ed07ec,  0x7ef07ee,  0x7f107f0,  0x7f307f2,  0xffffffff,  0x5f2ffff,  0x74905f3,  0xffffffff,  0x8050804,  0x8070806,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x8190818,  0x81b081a,  0x81d081c,  0x81f081e,  0x5f705f6,  0xffff05fb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x756ffff,  0x75a02c5,  0x2cd02cb,  0x76202cf,  0x2db06d2,  0x2e30719,  0x2e90448,  0x2f107ca,  0x2f30774,  0x77a02f5,  0x77e02f9,  0x3050221,  0x30f007a,  0xffff043b,  0xffffffff,  0xffffffff,  0x757ffff,  0x75b02c6,  0x2ce02cc,  0x76302d0,  0x2dc06d3,  0x2e4071a,  0x2ea0449,  0x2f207cb,  0x2f40775,  0x77b02f6,  0x77f02fa,  0x3060222,  0x310007b,  0xffff043c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x85c0012,  0x72005a,  0x32b0311,  0x11106b9,  0x2ab05e4,  0x2dd029d,  0x2ef085e,  0x10b0606,  0x2d902a5,  0x4ca0289,  0x2b502ad,  0x2c902bd,  0x2eb0746,  0x30902f7,  0x241031b,  0x38b02b1,  0x44a03b6,  0x442053a,  0x6d8044e,  0x85004ae,  0x85d0013,  0x73005b,  0x32c0312,  0x11206ba,  0x2ac05e5,  0x2de029e,  0x2f0085f,  0x10c0607,  0x2da02a6,  0x4cb028a,  0x2b602ae,  0x2ca02be,  0x2ec0747,  0x30a02f8,  0x242031c,  0x38c02b2,  0x44b03b7,  0x443053b,  0x6d9044f,  0x85104af,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//8832 bytes
+enum fullCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xd40], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160015,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x170000,  0x0,  0x190018,  0x1b001a,  0x1d001c,  0x1f001e,  0x0,  0x0,  0x210020,  0x22,  0x0,  0x0,  0x0,  0x0,  0x0,  0x240023,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260025,  0x280027,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a0000,  0x2b,  0x2d002c,  0x2e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x310030,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x330032,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x20ffff,  0x240022,  0x280026,  0x2c002a,  0x30002e,  0x7810032,  0x390037,  0x3d003b,  0x41003f,  0x1b90043,  0x4a0048,  0x4e004c,  0x520050,  0xffff0054,  0xffffffff,  0xffffffff,  0x21ffff,  0x250023,  0x290027,  0x2d002b,  0x31002f,  0x7820033,  0x3a0038,  0x3e003c,  0x420040,  0x1ba0044,  0x4b0049,  0x4f004d,  0x530051,  0xffff0055,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x470ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xc800c6,  0xcc04c9,  0x14e04db,  0xd500d3,  0xd900d7,  0xdd00db,  0xe100df,  0xe500e3,  0xe900e7,  0xed00eb,  0xf100ef,  0xffff00f3,  0xf700f5,  0xfb00f9,  0xff00fd,  0x70f0101,  0xc900c7,  0xcd04ca,  0x14f04dc,  0xd600d4,  0xda00d8,  0xde00dc,  0xe200e0,  0xe600e4,  0xea00e8,  0xee00ec,  0xf200f0,  0xffff00f4,  0xf800f6,  0xfc00fa,  0x10000fe,  0x1b10102,  0x1190118,  0x11b011a,  0x11f011e,  0x1210120,  0x1230122,  0x1270126,  0x1290128,  0x12b012a,  0x12f012e,  0x1310130,  0x1330132,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1470146,  0x1490148,  0x14b014a,  0x14d014c,  0x1520151,  0x1540153,  0xffff0155,  0x1580157,  0x15a0159,  0x15c015b,  0x15dffff,  0x15f015e,  0x1630160,  0x1650164,  0x1670166,  0x1690168,  0x16b016a,  0x16d016c,  0x16f016e,  0x1720171,  0x1740173,  0x1760175,  0x1780177,  0x17a0179,  0x17c017b,  0x17e017d,  0x180017f,  0x1820181,  0x1840183,  0x1860185,  0x1880187,  0x18c018b,  0x190018f,  0x1940193,  0x1980197,  0x19c019b,  0x1a0019f,  0x1a401a3,  0x1a801a7,  0x1ac01ab,  0x1ae01ad,  0x1b001af,  0x1b301b2,  0x1b501b4,  0x1b701b6,  0x1bb01b8,  0x1bc029c,  0x1bf01be,  0x1c301c2,  0x1c601c4,  0x1c801c7,  0x1cc01ca,  0xffff01cd,  0x1d001ce,  0x1d401d2,  0x1d601d5,  0x24801d8,  0x1dc01da,  0x1df01de,  0xffff0294,  0x1e201e0,  0x1e60278,  0x1e901e8,  0x1eb01ea,  0x1ed01ec,  0x1f001ee,  0x1f201f1,  0xffffffff,  0x1f501f4,  0x1f801f6,  0x1fa01f9,  0x1fe01fc,  0x20001ff,  0x2020201,  0x2050204,  0xffffffff,  0x2070206,  0x24affff,  0xffffffff,  0xffffffff,  0x20c020b,  0x211020d,  0x2130212,  0x2180217,  0x21a0219,  0x21c021b,  0x21e021d,  0x220021f,  0x2220221,  0x2240223,  0x2260225,  0x2280227,  0x1cf0229,  0x22b022a,  0x22f022e,  0x2310230,  0x2330232,  0x2350234,  0x2370236,  0x2390238,  0x23b023a,  0x23d023c,  0x243023e,  0x2450244,  0x2470246,  0x24b0249,  0x24d024c,  0x2510250,  0x2530252,  0x2550254,  0x2570256,  0x2590258,  0x25b025a,  0x25d025c,  0x2610260,  0x2630262,  0x2650264,  0x2670266,  0x2690268,  0x26b026a,  0x26d026c,  0x26f026e,  0x2710270,  0x2730272,  0x2750274,  0x2770276,  0xffff0279,  0x27b027a,  0x27d027c,  0x27f027e,  0x2810280,  0x2850284,  0x2870286,  0x2890288,  0x28b028a,  0x28d028c,  0xffffffff,  0xffffffff,  0xffffffff,  0x2920290,  0x2950293,  0x2ec0296,  0x29804b3,  0x29d0299,  0x2a0029e,  0x2a302a2,  0x2a502a4,  0x2a702a6,  0x2a902a8,  0x2ad02ac,  0x497049b,  0x1bd02ca,  0xffff01c5,  0x1cb01c9,  0x1d1ffff,  0x1d3ffff,  0xffffffff,  0xffffffff,  0xffff01d7,  0x1d9ffff,  0x79affff,  0xffff0643,  0x1db01dd,  0x559ffff,  0xffffffff,  0x1e1ffff,  0x2c6ffff,  0xffff01e3,  0x1e7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2b4ffff,  0xffffffff,  0xffff01ef,  0x1f3ffff,  0xffffffff,  0xffffffff,  0x29f01f7,  0x1fd01fb,  0xffff02a1,  0xffffffff,  0xffffffff,  0xffff0203,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x8e4ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3450344,  0x3470346,  0xffffffff,  0x34b034a,  0xffffffff,  0x406ffff,  0x40a0408,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0350,  0x3560354,  0xffff0358,  0xffff035a,  0x35e035c,  0x3630902,  0x36803c2,  0x3fd036a,  0x371036f,  0x8e503f9,  0x37e03f3,  0x3830471,  0x3870385,  0x3f603cc,  0x3b5ffff,  0x3940392,  0x39903c9,  0x762039b,  0x3a203a0,  0x3550351,  0x3590357,  0x3640915,  0x36903c3,  0x3fe036b,  0x3720370,  0x8e603fa,  0x37f03f4,  0x3840472,  0x3880386,  0x3f703cd,  0x3b703b6,  0x3950393,  0x39a03ca,  0x763039c,  0x3a303a1,  0x35d035b,  0x3c0035f,  0x3fb03c4,  0xffffffff,  0x3cbffff,  0x3c103ce,  0x3d003cf,  0x3d403d3,  0x3d603d5,  0x3d803d7,  0x3da03d9,  0x3de03dd,  0x3e003df,  0x3e403e3,  0x3e803e7,  0x3ec03eb,  0x3ee03ed,  0x3f203f1,  0x3f803f5,  0xffff0402,  0x3ff03fc,  0x400ffff,  0x4030401,  0x4050404,  0x407ffff,  0x40b0409,  0x40e040c,  0x4120410,  0x4160414,  0x41a0418,  0x41e041c,  0x4220420,  0x4260424,  0x42a0428,  0x42e042c,  0x4320430,  0x4360434,  0x43a0438,  0x43e043c,  0x4420440,  0x4460444,  0x44a0448,  0x44e044c,  0x4520450,  0x4560454,  0x45a0458,  0x45e045c,  0x4620460,  0x4660464,  0x46a0468,  0x42f042d,  0x4330431,  0x4370435,  0x43b0439,  0x43f043d,  0x4430441,  0x4470445,  0x44b0449,  0x44f044d,  0x4530451,  0x4570455,  0x45b0459,  0x45f045d,  0x4630461,  0x4670465,  0x46b0469,  0x40f040d,  0x4130411,  0x4170415,  0x41b0419,  0x41f041d,  0x4230421,  0x4270425,  0x42b0429,  0x4820481,  0x4840483,  0x4880487,  0x48a0489,  0x48e048d,  0x4920491,  0x4960495,  0x49a0499,  0x49e049d,  0x4a204a1,  0x4a404a3,  0x4a804a7,  0x4aa04a9,  0x4ac04ab,  0x4ae04ad,  0x4b204b1,  0x4b604b5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4c604c5,  0x4c804c7,  0x4cc04cb,  0x4ce04cd,  0x4d004cf,  0x4d404d3,  0x4d804d7,  0x4da04d9,  0x4de04dd,  0x4e204e1,  0x4e404e3,  0x4e804e7,  0x4ea04e9,  0x4ec04eb,  0x4f004ef,  0x4f204f1,  0x4f604f5,  0x4fa04f9,  0x4fe04fd,  0x50004ff,  0x5040503,  0x5060505,  0x5080507,  0x50c050b,  0x510050f,  0x5140513,  0x5180517,  0x51d051b,  0x521051e,  0x5250522,  0x5290526,  0x52d052a,  0x531052e,  0x5350532,  0x51c0536,  0x53c053b,  0x53e053d,  0x540053f,  0x5440543,  0x5480547,  0x54a0549,  0x54e054d,  0x550054f,  0x5520551,  0x5560555,  0x5580557,  0x55c055b,  0x55e055d,  0x560055f,  0x5640563,  0x5680567,  0x56a0569,  0x56e056d,  0x570056f,  0x5720571,  0x5740573,  0x5780577,  0x57a0579,  0x57c057b,  0x57e057d,  0x5820581,  0x5840583,  0x5880587,  0x58a0589,  0x58c058b,  0x58e058d,  0x5920591,  0x5940593,  0x5980597,  0x59a0599,  0x59c059b,  0x59e059d,  0x5a205a1,  0x5a605a5,  0x5aa05a9,  0x5ac05ab,  0x5ae05ad,  0x5b005af,  0x5b405b3,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x5b9ffff,  0x5bd05bb,  0x5c105bf,  0x5c505c3,  0x5c905c7,  0x5cd05cb,  0x5d105cf,  0x5d505d3,  0x5d905d7,  0x5dd05db,  0x5e105df,  0x5e505e3,  0x5e905e7,  0x5ed05eb,  0x5f105ef,  0x5f505f3,  0x5f905f7,  0x5fd05fb,  0x60105ff,  0xffff0603,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x5baffff,  0x5be05bc,  0x5c205c0,  0x5c605c4,  0x5ca05c8,  0x5ce05cc,  0x5d205d0,  0x5d605d4,  0x5da05d8,  0x5de05dc,  0x5e205e0,  0x5e605e4,  0x5ea05e8,  0x5ee05ec,  0x5f205f0,  0x5f605f4,  0x5fa05f8,  0x5fe05fc,  0x6020600,  0x6130604,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x880086,  0x8c008a,  0x90008e,  0x940092,  0x980096,  0x9c009a,  0xa0009e,  0xa400a2,  0xa800a6,  0xac00aa,  0xb000ae,  0xb400b2,  0xb800b6,  0xbc00ba,  0xc000be,  0xc400c2,  0x4bf04b7,  0x4d100ca,  0x4e500ce,  0x4f7ffff,  0xffffffff,  0xffffffff,  0x539ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x83affff,  0xffffffff,  0x485ffff,  0xffffffff,  0x6460645,  0x6480647,  0x64a0649,  0x64e064d,  0x6520651,  0x6540653,  0x6580657,  0x65a0659,  0x65c065b,  0x660065f,  0x6620661,  0x6660665,  0x6680667,  0x66a0669,  0x66c066b,  0x670066f,  0x6720671,  0x6740673,  0x6760675,  0x6780677,  0x67a0679,  0x67e067d,  0x680067f,  0x6820681,  0x6840683,  0x6860685,  0x6880687,  0x68c068b,  0x68e068d,  0x690068f,  0x6920691,  0x6940693,  0x6960695,  0x69a0699,  0x69c069b,  0x69e069d,  0x6a206a1,  0x6a606a5,  0x6a806a7,  0x6ac06ab,  0x6ae06ad,  0x6b006af,  0x6b206b1,  0x6b406b3,  0x6b606b5,  0x6ba06b9,  0x6bc06bb,  0x6be06bd,  0x70d070c,  0x6c306c2,  0x6c706c6,  0x6cb06ca,  0x6cd06cc,  0x6cf06ce,  0x6d106d0,  0x6d306d2,  0x6d506d4,  0x6d906d8,  0x6db06da,  0x6dd06dc,  0x6e106e0,  0x6e306e2,  0x6e506e4,  0x6e906e8,  0x6eb06ea,  0x6ed06ec,  0x6ef06ee,  0x6f106f0,  0x6f306f2,  0x6f706f6,  0x6f906f8,  0x6fb06fa,  0x6fd06fc,  0x6ff06fe,  0x7010700,  0x7040702,  0x7080706,  0x70e070a,  0xffffffff,  0xffff0710,  0x7130712,  0x7150714,  0x7170716,  0x71b071a,  0x71d071c,  0x71f071e,  0x7210720,  0x7230722,  0x7270726,  0x7290728,  0x72d072c,  0x72f072e,  0x7310730,  0x7330732,  0x7350734,  0x7370736,  0x73b073a,  0x73d073c,  0x7410740,  0x7430742,  0x7450744,  0x7470746,  0x7490748,  0x74b074a,  0x74d074c,  0x74f074e,  0x7510750,  0x7530752,  0x7550754,  0x7570756,  0x7590758,  0x75b075a,  0x75d075c,  0x75f075e,  0x7610760,  0x7660765,  0x7680767,  0x76a0769,  0x76e076d,  0x770076f,  0x7720771,  0x7740773,  0x7760775,  0x7780777,  0x77a0779,  0x77c077b,  0x780077f,  0x7850784,  0x78a0788,  0x78e078c,  0x7920790,  0x7960794,  0x78b0789,  0x78f078d,  0x7930791,  0x7970795,  0x79e079c,  0x7a207a0,  0x7a607a4,  0xffffffff,  0x79f079d,  0x7a307a1,  0x7a707a5,  0xffffffff,  0x7b807b6,  0x7bc07ba,  0x7c007be,  0x7c407c2,  0x7b907b7,  0x7bd07bb,  0x7c107bf,  0x7c507c3,  0x7d807d6,  0x7dc07da,  0x7e007de,  0x7e407e2,  0x7d907d7,  0x7dd07db,  0x7e107df,  0x7e507e3,  0x7f007ee,  0x7f407f2,  0x7f807f6,  0xffffffff,  0x7f107ef,  0x7f507f3,  0x7f907f7,  0xffffffff,  0x80807fc,  0x80c07fe,  0x8100800,  0x8140804,  0x809ffff,  0x80dffff,  0x811ffff,  0x815ffff,  0x8240822,  0x8280826,  0x82c082a,  0x830082e,  0x8250823,  0x8290827,  0x82d082b,  0x831082f,  0x8df08dd,  0x8f708f5,  0x8fb08f9,  0x90f090d,  0x9370935,  0x9240922,  0x93b0939,  0xffffffff,  0x8590856,  0x85f085c,  0x8650862,  0x86b0868,  0x85a0857,  0x860085d,  0x8660863,  0x86c0869,  0x8890886,  0x88f088c,  0x8950892,  0x89b0898,  0x88a0887,  0x890088d,  0x8960893,  0x89c0899,  0x8b908b6,  0x8bf08bc,  0x8c508c2,  0x8cb08c8,  0x8ba08b7,  0x8c008bd,  0x8c608c3,  0x8cc08c9,  0x8db08d9,  0x8e108ce,  0xffff08d3,  0x8d708d5,  0x8dc08da,  0x8e008de,  0xffff08e2,  0xffff08e7,  0xffffffff,  0x8fd08e8,  0xffff08ed,  0x8f308f1,  0x8f808f6,  0x8fc08fa,  0xffff08fe,  0xffffffff,  0x90b0909,  0x9030900,  0xffffffff,  0x9070905,  0x90c090a,  0x910090e,  0xffffffff,  0xffffffff,  0x920091e,  0x9160913,  0x9260918,  0x91c091a,  0x921091f,  0x9250923,  0xffff0927,  0xffffffff,  0xffffffff,  0x93d092a,  0xffff092f,  0x9330931,  0x9380936,  0x93c093a,  0xffff093e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0764,  0xffffffff,  0x1500783,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0561,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0562,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x18902b0,  0x18d02c2,  0x19102d6,  0x195005e,  0x1990064,  0x19d006c,  0x1a10076,  0x1a5007e,  0x18a02b1,  0x18e02c3,  0x19202d7,  0x196005f,  0x19a0065,  0x19e006d,  0x1a20077,  0x1a6007f,  0xffffffff,  0x1c0ffff,  0xffff01c1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x5090475,  0x50d0477,  0x511047d,  0x515048f,  0x51904a5,  0x2e80940,  0x2051f,  0x7180523,  0x6e0527,  0x3a4052b,  0x110052f,  0x6630533,  0x54b0537,  0x50a0476,  0x50e0478,  0x512047e,  0x5160490,  0x51a04a6,  0x2e90941,  0x30520,  0x7190524,  0x6f0528,  0x3a5052c,  0x1110530,  0x6640534,  0x54c0538,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x40000,  0xa0008,  0xe000c,  0x160010,  0x1a0018,  0x2ce001c,  0x2e602dc,  0x560308,  0x600058,  0x660062,  0x70006a,  0x780074,  0x80007c,  0x2aa0082,  0x60832,  0x64b0084,  0x2b805b5,  0x60d0609,  0x629061d,  0x1080106,  0x112010a,  0x11c0116,  0x12c0124,  0xffff0134,  0x50001,  0xb0009,  0xf000d,  0x170011,  0x1b0019,  0x2cf001d,  0x2e702dd,  0x570309,  0x610059,  0x670063,  0x71006b,  0x790075,  0x81007d,  0x2ab0083,  0x70833,  0x64c0085,  0x2b905b6,  0x60e060a,  0x62a061e,  0x1090107,  0x113010b,  0x11d0117,  0x12d0125,  0xffff0135,  0x4f404f3,  0x486055a,  0x29102b5,  0x48b0297,  0x2ba048c,  0x49302bb,  0x4980494,  0x49c02c7,  0xffff02cb,  0x2cd02cc,  0x2d4ffff,  0xffff02d5,  0xffffffff,  0xffffffff,  0xffffffff,  0x4b402ed,  0x2f902f8,  0x4ba04b9,  0x4bc04bb,  0x4be04bd,  0x4c204c1,  0x3250324,  0x32b032a,  0x3350334,  0x33d033c,  0x3410340,  0x34d034c,  0x34f034e,  0x3230322,  0x73f073e,  0x3ae03ad,  0x3b003af,  0x3b403b3,  0x3bf03be,  0x3d203d1,  0x3e203e1,  0x5a405a3,  0x6060605,  0x61a0619,  0x6320631,  0x6560655,  0x6a0069f,  0x46f046e,  0x7390738,  0x77e077d,  0x7d507d4,  0x8350834,  0x6df06de,  0x6a406a3,  0x150014,  0x5d005c,  0x4ee04ed,  0x5020501,  0x6120611,  0x1aa01a9,  0x5420541,  0x5540553,  0x5660565,  0x5760575,  0x5860585,  0x5960595,  0x5a805a7,  0x5b805b7,  0x1620161,  0x3a703a6,  0x3ea03e9,  0xffffffff,  0xffffffff,  0xffffffff,  0x60fffff,  0x6170610,  0xffff0618,  0xffffffff,  0x6240623,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x890087,  0x8d008b,  0x91008f,  0x950093,  0x990097,  0x9d009b,  0xa1009f,  0xa500a3,  0xa900a7,  0xad00ab,  0xb100af,  0xb500b3,  0xb900b7,  0xbd00bb,  0xc100bf,  0xc500c3,  0x4c004b8,  0x4d200cb,  0x4e600cf,  0x4f8ffff,  0xffffffff,  0xffffffff,  0x53affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d902d8,  0x6160615,  0x61c061b,  0x6220621,  0x6280627,  0x1e501e4,  0x62e062d,  0x6340633,  0x6380637,  0x63e063d,  0x6420641,  0x32f032e,  0x65e065d,  0x66e066d,  0x67c067b,  0x68a0689,  0x6980697,  0x6aa06a9,  0x6b806b7,  0x6c906c8,  0x6d706d6,  0x6e706e6,  0x6f506f4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7870786,  0x690068,  0x28f028e,  0x7b107b0,  0x7c907c8,  0x3bd03bc,  0x1f001e,  0x8030802,  0x8190818,  0x2b302b2,  0x8370836,  0x2d302d2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7ab07aa,  0x7af07ae,  0x7b307b2,  0x2eb02ea,  0x2f502f4,  0x3070306,  0x3110310,  0xffffffff,  0x7cb07ca,  0x7cf07ce,  0x7d307d2,  0x3330332,  0x33b033a,  0x33f033e,  0x3490348,  0x7e707e6,  0x7e907e8,  0x7eb07ea,  0x7ed07ec,  0x3ac03ab,  0x3b203b1,  0x3bb03ba,  0x7fb07fa,  0x3dc03db,  0x3f003ef,  0x620061f,  0x2830282,  0x8070806,  0x80b080a,  0x80f080e,  0x8130812,  0x8170816,  0x81b081a,  0x81f081e,  0x8210820,  0x4a0049f,  0x4b004af,  0x4c404c3,  0x4d604d5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x545ffff,  0x8380546,  0x83b0839,  0x83d083c,  0x580057f,  0x590058f,  0x5a0059f,  0x5b205b1,  0xffffffff,  0x63bffff,  0x79b063c,  0xffffffff,  0x6080607,  0x60c060b,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x62c062b,  0x630062f,  0x6360635,  0x63a0639,  0x640063f,  0xffff0644,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x25e02f6,  0x2fc02fa,  0x30302fe,  0xffff0304,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x30cffff,  0x2c0030e,  0x3140312,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7a8ffff,  0x7ac02da,  0x2e202e0,  0x7b402e4,  0x2f00724,  0x144076b,  0x30a0479,  0x318081c,  0x31a07c6,  0x7cc031c,  0x7d00320,  0x32c022c,  0x336007a,  0xffff046c,  0xffffffff,  0xffffffff,  0x7a9ffff,  0x7ad02db,  0x2e302e1,  0x7b502e5,  0x2f10725,  0x145076c,  0x30b047a,  0x319081d,  0x31b07c7,  0x7cd031d,  0x7d10321,  0x32d022d,  0x337007b,  0xffff046d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x9280012,  0x72005a,  0x3520338,  0x114010c,  0x2bc0625,  0x2f202ae,  0x31608ef,  0x10e064f,  0x2ee02b6,  0x4fb029a,  0x2c802be,  0x2de02d0,  0x47f0798,  0x330031e,  0x24e0342,  0x3b802c4,  0x47b03e5,  0x473056b,  0x72a06c4,  0x91104df,  0x9290013,  0x73005b,  0x3530339,  0x115010d,  0x2bd0626,  0x2f302af,  0x31708f0,  0x10f0650,  0x2ef02b7,  0x4fc029b,  0x2c902bf,  0x2df02d1,  0x4800799,  0x331031f,  0x24f0343,  0x3b902c5,  0x47c03e6,  0x474056c,  0x72b06c5,  0x91204e0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//4000 bytes
+enum alphaTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0x160], [ 0x100,  0x240,  0x5100], [ 0x3020100,  0x7060504,  0xb0a0908,  0xe0d0c0a,  0x3030303,  0x100a0f03,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xb000a,  0xd000c,  0xf000e,  0x10010,  0x120011,  0x10013,  0x150014,  0x170016,  0x190018,  0x1b001a,  0x1c0001,  0x1e001d,  0x1f001f,  0x1f0020,  0x1f001f,  0x1f001f,  0x1f001f,  0x220021,  0x1f0023,  0x250024,  0x1f001f,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x260001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x270001,  0x10001,  0x10001,  0x10028,  0x2a0029,  0x2c002b,  0x2e002d,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x2f0001,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1001f,  0x310030,  0x320001,  0x340033,  0x360035,  0x380037,  0x1f0039,  0x1f001f,  0x3b003a,  0x3d003c,  0x1f003e,  0x1f001f,  0x40003f,  0x1f001f,  0x1f001f,  0x1f0041,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x10001,  0x420001,  0x1f0043,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x10001,  0x10001,  0x1f0044,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x10001,  0x1f0045,  0x1f001f,  0x46001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f0047,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x490048,  0x4b004a,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f004c,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x10001,  0x10001,  0x10001,  0x1004d,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x4e0001,  0x1f004f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x10001,  0x1f004f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x1f001f,  0x0,  0x0,  0x7fffffe,  0x7fffffe,  0x0,  0x4200400,  0xff7fffff,  0xff7fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3ffc3,  0x501f,  0x0,  0x0,  0x20,  0x3cdf0000,  0xffffd740,  0xfffffffb,  0xffffffff,  0xffbfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffffc03,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffe00ff,  0x27fffff,  0xfffffffe,  0xff,  0xbfff0000,  0xffff00b6,  0x707ff,  0x7ff0000,  0xffffffff,  0xfeffffff,  0xffffc000,  0xffffffff,  0xffffffff,  0x1fefffff,  0x9c00e1fe,  0xffff0000,  0xffffffff,  0xffffe000,  0xffffffff,  0xffffffff,  0x3ffff,  0xfffffc00,  0x43007ff,  0xfcffffff,  0x1fff,  0x1ffffff,  0x0,  0x0,  0x1ffd,  0x0,  0x7fff03f0,  0xffffffff,  0xefffffff,  0xffe1dfff,  0xfefe000f,  0xfff99fee,  0xe3c5fdff,  0xb080599f,  0x3000f,  0xfff987ee,  0xc36dfdff,  0x5e021987,  0x3f0000,  0xfffbbfee,  0xe3edfdff,  0x11bbf,  0xf,  0xfff99fee,  0xe3edfdff,  0xb0c0199f,  0x2000f,  0xd63dc7ec,  0xc3ffc718,  0x811dc7,  0x0,  0xfffddfee,  0xe3effdff,  0x3601ddf,  0xf,  0xfffddfec,  0xe3effdff,  0x40601ddf,  0x6000f,  0xfffddfec,  0xe7ffffff,  0x805ddf,  0xfc00000f,  0xfc7fffec,  0x2ffbffff,  0xff5f807f,  0xc0000,  0xfffffffe,  0x7ffffff,  0x207f,  0x0,  0xfef02596,  0x3bffecae,  0xf000205f,  0x0,  0x1,  0x0,  0xfffffeff,  0xfffe1fff,  0xfeffff03,  0x1fffffff,  0x0,  0x0,  0xffffffff,  0xf97fffff,  0xffff0000,  0xffffc1e7,  0x3000407f,  0xffffffff,  0xffff20bf,  0xf7ffffff,  0xffffffff,  0xffffffff,  0x3d7f3dff,  0xffffffff,  0xffff3dff,  0x7f3dffff,  0xff7fff3d,  0xffffffff,  0xff3dffff,  0xffffffff,  0x87ffffff,  0x0,  0xffff,  0xffffffff,  0xffffffff,  0x1fffff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff9fff,  0x7fffffe,  0xffffffff,  0xffffffff,  0x1c7ff,  0xfdfff,  0xfffff,  0xfffff,  0xddfff,  0xffffffff,  0xffcfffff,  0x108001ff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffff,  0xffffffff,  0xffff07ff,  0xffffffff,  0x3fffff,  0x1fffffff,  0x1ff0fff,  0xffff0000,  0x1f3fff,  0xffffffff,  0xffff0fff,  0x3ff,  0x0,  0xfffffff,  0xffffffff,  0x7fffffff,  0x1ffffe,  0x0,  0x80,  0x0,  0x0,  0xffffffff,  0xffefffff,  0xfef,  0x0,  0xffffffff,  0xfc00f3ff,  0xffffffff,  0x3ffbf,  0xffffffff,  0x3fffff,  0xfc00e000,  0x3fffffff,  0x0,  0x0,  0x0,  0x6fde00,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x0,  0x0,  0x3f3fffff,  0xffffffff,  0xaaff3f3f,  0x3fffffff,  0xffffffff,  0x5fdfffff,  0xfcf1fdc,  0x1fdc1fff,  0x0,  0x0,  0x0,  0x80020000,  0x1fff0000,  0x0,  0x0,  0x0,  0x3e2ffc84,  0xf3ffbd50,  0x43e0,  0xffffffff,  0x1ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffc00000,  0xffffffff,  0x3ff,  0xffffffff,  0xffff7fff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xc781f,  0xffffffff,  0xffff20bf,  0xffffffff,  0x80ff,  0x7fffff,  0x7f7f7f7f,  0x7f7f7f7f,  0xffffffff,  0x0,  0x8000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe0,  0x1f3e03fe,  0xfffffffe,  0xffffffff,  0xe07fffff,  0xfffffffe,  0xffffffff,  0xf7ffffff,  0xffffffe0,  0xfffe3fff,  0xffffffff,  0xffffffff,  0x7fff,  0x7ffffff,  0x0,  0xffff0000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fff,  0x0,  0xffff0000,  0x3fffffff,  0xffff1fff,  0xc00,  0xffffffff,  0x8ff07fff,  0x80ffffff,  0xffffffff,  0xffffffff,  0xffff,  0xff800000,  0xfffffffc,  0xffffffff,  0xffffffff,  0xf79ff,  0x7ff,  0x0,  0xff000000,  0xfffff7bb,  0xff,  0xffffffff,  0xfffff,  0xffffffff,  0xffffffff,  0xf,  0x8fc0000,  0xfffffc00,  0xffff07ff,  0x7ffff,  0x1fffffff,  0xffffffff,  0xfff7ffff,  0x8000,  0x0,  0xffffffff,  0x7fffff,  0x3fff,  0x47fffff,  0xffffffff,  0x7fffffff,  0x38000005,  0x3cffff,  0x7e7e7e,  0x7f7f,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x7ff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff000f,  0xfffff87f,  0xfffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xe0f8007f,  0x5f7ffdff,  0xffffffdb,  0xffffffff,  0xffffffff,  0x3ffff,  0xfff80000,  0xffffffff,  0xffffffff,  0x3fffffff,  0xffff0000,  0xffffffff,  0xfffcffff,  0xffffffff,  0xff,  0xfff0000,  0x0,  0x0,  0x0,  0xffdf0000,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fffffff,  0x0,  0x7fffffe,  0x7fffffe,  0xffffffc0,  0xffffffff,  0x7fffffff,  0x1cfcfcfc,  0x0,  0xffffefff,  0xb7ffff7f,  0x3fff3fff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7ffffff,  0x0,  0x0,  0xffffffff,  0x1fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1fffffff,  0xffffffff,  0x1ffff,  0x0,  0x7fffffff,  0xffff0000,  0x7ff,  0x0,  0x3fffffff,  0xffffffff,  0x3eff0f,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x0,  0x0,  0x0,  0xfffffd3f,  0x91bfffff,  0x3fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3fffff,  0x3ffffff,  0x0,  0x0,  0xffffffff,  0xc0ffffff,  0x0,  0x0,  0xfeeff06f,  0xfffff,  0x0,  0x1fffffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x3fffff,  0x3fffff,  0x7ffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x1ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x3f,  0x0,  0xfffffffc,  0x1ffffff,  0xffff0000,  0x1ff,  0xffffffff,  0x7ffff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x1e,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x3fffff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x7fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x1ffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffff001f,  0x7fffffff,  0xfff80000,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffdfffff,  0xffffffff,  0xdfffffff,  0xebffde64,  0xffffffef,  0xffffffff,  0xdfdfe7bf,  0x7bffffff,  0xfffdfc5f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffff3f,  0xf7fffffd,  0xf7ffffff,  0xffdfffff,  0xffdfffff,  0xffff7fff,  0xffff7fff,  0xfffffdff,  0xfffffdff,  0xff7,  0x0,  0xffffffef,  0xaf7fe96,  0xaa96ea84,  0x5ef7f796,  0xffffbff,  0xffffbee,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffff,  0x0,  0xffffffff,  0x1fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2304 bytes
+enum markTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xe0], [ 0x100,  0x140,  0x2c00], [ 0x2020100,  0x4020302,  0x6020205,  0x2070202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020208,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xb000a,  0xd000c,  0xe,  0xf0000,  0x0,  0x100000,  0x120011,  0x140013,  0x160015,  0x0,  0x17,  0x0,  0x0,  0x0,  0x0,  0x0,  0x190018,  0x0,  0x1a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1b,  0x1d001c,  0x1f001e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x21,  0x220000,  0x0,  0x0,  0x0,  0x0,  0x23,  0x0,  0x0,  0x250024,  0x0,  0x0,  0x26,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x270000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x280000,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfffe0000,  0xbfffffff,  0xb6,  0x0,  0x7ff0000,  0x0,  0xfffff800,  0x10000,  0x0,  0x0,  0x9fc00000,  0x3d9f,  0x20000,  0xffff0000,  0x7ff,  0x0,  0x0,  0x1ffc0,  0x0,  0xff800,  0xfbc00000,  0x3eef,  0xe000000,  0x0,  0x0,  0x0,  0x0,  0x7ffffff0,  0xf,  0xdc000000,  0xfeffff,  0xc,  0xe,  0xd0000000,  0x80399f,  0xc,  0xe,  0xd0000000,  0x23987,  0x230000,  0xe,  0xd0000000,  0x3bbf,  0xc,  0xe,  0xd0000000,  0xc0399f,  0xc,  0x4,  0xc0000000,  0x803dc7,  0x0,  0xe,  0xc0000000,  0x603ddf,  0xc,  0xc,  0xd0000000,  0x603ddf,  0xc,  0xc,  0xc0000000,  0x803ddf,  0xc,  0xc,  0x0,  0xff5f8400,  0xc0000,  0x0,  0x7f20000,  0x7f80,  0x0,  0x0,  0x1bf20000,  0x3f00,  0x0,  0x3000000,  0xc2a00000,  0x0,  0xfffe0000,  0xfeffe0df,  0x1fffffff,  0x40,  0x0,  0x0,  0x7ffff800,  0xc3c00000,  0x1e3f9d,  0x3c00bffc,  0x0,  0x0,  0x0,  0x0,  0x0,  0xe0000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1c0000,  0x1c0000,  0xc0000,  0xc0000,  0x0,  0xfff00000,  0x200fffff,  0x0,  0x3800,  0x0,  0x0,  0x0,  0x0,  0x200,  0x0,  0x0,  0x0,  0xfff0fff,  0x0,  0x0,  0x0,  0xffff0000,  0x301,  0x0,  0xf800000,  0x0,  0x7fe00000,  0x9fffffff,  0x0,  0x0,  0x0,  0x0,  0x1f,  0xfff00000,  0x1f,  0xff800,  0x7,  0x3ffe,  0x0,  0xfffc0,  0x0,  0xfffff0,  0x0,  0x0,  0x0,  0x0,  0xfff70000,  0x1c21ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xf000007f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0x1ffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x38000,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x0,  0xffffffff,  0x0,  0xfc00,  0x0,  0x0,  0x6000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff78000,  0x80000000,  0x0,  0x0,  0x30000,  0x844,  0xf8,  0x0,  0x0,  0x3,  0xfff00000,  0x1f,  0x3ffff,  0x0,  0x3fc0,  0xfff80,  0x0,  0xf,  0xfff80000,  0x1,  0x0,  0x0,  0x7ffe00,  0x3008,  0x8000000,  0x0,  0xc19d0000,  0x2,  0x60f800,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x37f8,  0x40000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff,  0x7f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000000,  0xf06e,  0x87000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0xff000000,  0x7f,  0x0,  0x7,  0x7ff0000,  0x0,  0x0,  0x7,  0x1fff80,  0x0,  0x0,  0x7,  0xfff80000,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfff800,  0x0,  0x0,  0x0,  0x0,  0xfffe0000,  0x7fffffff,  0x78000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xf807e3e0,  0xfe7,  0x3c00,  0x0,  0x0,  0x0,  0x0,  0x1c,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2384 bytes
+enum numberTrieEntries = TrieEntry!(bool, 8, 6, 7)([ 0x0,  0x40,  0x180], [ 0x100,  0x280,  0x1a80], [ 0x2020100,  0x4020302,  0x2020605,  0x8070202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x40003,  0x50002,  0x20002,  0x70006,  0x60006,  0x90008,  0x6000a,  0x2000b,  0xc000c,  0x2000d,  0xe0005,  0x20002,  0x20002,  0x2000f,  0x20002,  0x20002,  0x100002,  0x110002,  0x2000e,  0x130012,  0x140002,  0xc,  0x20015,  0x20002,  0x20002,  0x20002,  0x170016,  0x190018,  0x20002,  0x20002,  0x1b001a,  0x20002,  0x20002,  0x1d001c,  0x20002,  0x20002,  0x20002,  0x20002,  0x1e0002,  0x20002,  0x20002,  0x20002,  0x2001f,  0x200002,  0x220021,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x60023,  0x20002,  0xc0024,  0xc0017,  0x2000c,  0x40002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x2000e,  0x20002,  0x260025,  0x20002,  0x280027,  0x230002,  0x20002,  0x20002,  0x20002,  0x20029,  0x2002a,  0x2002b,  0x2002c,  0x20002,  0x20002,  0x2002d,  0x20002,  0x4002e,  0xc002f,  0x20002,  0x20002,  0x20002,  0x20002,  0x50002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20030,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20031,  0x20002,  0x20002,  0x20002,  0x320002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20033,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x20002,  0x0,  0x3ff0000,  0x0,  0x0,  0x0,  0x720c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff,  0x0,  0x0,  0x0,  0x3ff0000,  0x0,  0x0,  0x3ff,  0x0,  0x0,  0x0,  0x0,  0xffc0,  0x0,  0x0,  0x0,  0x3f0ffc0,  0x0,  0x0,  0x0,  0xfcffc0,  0x0,  0x0,  0x0,  0x7ffc0,  0x0,  0x0,  0x0,  0x7f00ffc0,  0x0,  0x0,  0x0,  0x3fffc0,  0x0,  0x0,  0x3ff0000,  0x0,  0x0,  0xfffff,  0x0,  0x0,  0x3ff0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ffffe00,  0x0,  0x0,  0x0,  0x1c000,  0x0,  0x0,  0x0,  0x3ff03ff,  0x0,  0x0,  0xffc0,  0x0,  0x0,  0x0,  0x7ff0000,  0x0,  0x3ff03ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff03ff,  0x0,  0x0,  0x0,  0x0,  0x3f10000,  0x3ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0xffffffff,  0x3e7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xfffffff,  0x0,  0x0,  0xfffffc00,  0x0,  0x0,  0x0,  0xffc00000,  0xfffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000000,  0x80,  0x70003fe,  0x0,  0x0,  0x3c0000,  0x0,  0x0,  0x0,  0x0,  0x3ff,  0xfffeff00,  0x0,  0x3ff,  0xfffe0000,  0x0,  0x0,  0x0,  0x3ff,  0x0,  0x0,  0x0,  0x3f0000,  0x0,  0x0,  0xffffff80,  0xfffff,  0xffffffff,  0x1ffffff,  0x400,  0x0,  0x0,  0x0,  0x0,  0xf,  0x402,  0x0,  0x0,  0x0,  0x3e0000,  0x0,  0x0,  0x0,  0xff000000,  0x0,  0xfc00000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xff,  0x60000000,  0x0,  0x0,  0xff000000,  0xff000000,  0x0,  0x0,  0x0,  0x7fffffff,  0x0,  0x0,  0xfffc0000,  0xffff,  0x0,  0xffc00000,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7,  0x0,  0x0,  0x0,  0x3ffff,  0x0,  0x0,  0xffffc000,  0xffffffff,  0x7ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2336 bytes
+enum punctuationTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xc0], [ 0x100,  0x100,  0x3100], [ 0x2020100,  0x4020302,  0x2020605,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x20001,  0x30001,  0x50004,  0x70006,  0x10008,  0x90001,  0xb000a,  0x1000c,  0xd0001,  0x1000e,  0x10000f,  0x120011,  0x140013,  0x10015,  0x10001,  0x10016,  0x170001,  0x10001,  0x180001,  0x190001,  0x10001,  0x1b001a,  0x1001c,  0x1001d,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x1001e,  0x1001f,  0x210020,  0x230022,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x240001,  0x260025,  0x270001,  0x280001,  0x10001,  0x10001,  0x2a0029,  0x2c002b,  0x10001,  0x10001,  0x2e002d,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x1002f,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x0,  0x8c00f7ee,  0xb8000001,  0x28000000,  0x0,  0x88c00882,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40000000,  0x80,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfc000000,  0x0,  0x600,  0x40000000,  0x49,  0x180000,  0xc8003600,  0x0,  0x0,  0x3c00,  0x0,  0x0,  0x100000,  0x0,  0x3fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3800000,  0x0,  0x7fff0000,  0x40000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10030,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x100000,  0x0,  0x0,  0xc008000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x17fff0,  0x3c000000,  0x0,  0x0,  0x20,  0x0,  0x61f0000,  0x0,  0x0,  0x0,  0xfc00,  0x0,  0x0,  0x0,  0x0,  0x8000000,  0x0,  0x0,  0x0,  0x1ff,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6000,  0x18000000,  0x0,  0x0,  0x3800,  0x0,  0x600000,  0x0,  0x0,  0x0,  0x0,  0x7700000,  0x0,  0x7ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x0,  0x3f7f,  0x0,  0x0,  0x0,  0x0,  0xfc000000,  0x1,  0x0,  0x0,  0x0,  0xf0000000,  0x0,  0xf8000000,  0x0,  0xc0000000,  0x0,  0x0,  0x800ff,  0x0,  0xffff0000,  0xffff00ff,  0x7ffbffef,  0x60000000,  0x6000,  0x0,  0x0,  0x0,  0xf00,  0x600,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3fff00,  0x0,  0x0,  0x60,  0xffc0,  0x0,  0x0,  0x0,  0x0,  0x1fffff8,  0x0,  0xf000000,  0x30000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xde000000,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xfff7fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfff3ff0e,  0x20010000,  0x0,  0x0,  0x0,  0x1,  0x0,  0x8000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0xe000,  0x0,  0x0,  0x40080000,  0x0,  0x0,  0x0,  0xfc0000,  0x0,  0x0,  0x0,  0xf00000,  0x0,  0x0,  0xc000,  0x7000000,  0x0,  0xc000,  0x80000000,  0x0,  0x0,  0x0,  0xc0003ffe,  0x0,  0x0,  0x0,  0xf0000000,  0x0,  0x0,  0x0,  0xc0000000,  0x30000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x800,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff0000,  0xffff0000,  0xfff7ffff,  0xd0b,  0x0,  0x0,  0x0,  0x0,  0x8c00f7ee,  0xb8000001,  0xa8000000,  0x3f,  0x0,  0x0,  0x0,  0x0,  0x7,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x800000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x80000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1ff0000,  0x80000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfe000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3f80,  0x0,  0x0,  0xd8000000,  0x3,  0x0,  0x0,  0x0,  0xf,  0x0,  0x0,  0x0,  0x1e0,  0x0,  0x0,  0x0,  0x0,  0xf0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//2848 bytes
+enum symbolTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0xe0], [ 0x100,  0x140,  0x3d00], [ 0x3020100,  0x5030403,  0x3030306,  0x8070303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x3030303,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x80001,  0xa0009,  0xc000b,  0xe000d,  0x1000f,  0x100001,  0x10001,  0x110001,  0x120001,  0x130001,  0x10001,  0x140001,  0x160015,  0x180017,  0x170019,  0x1a0017,  0x1b0017,  0x1c0017,  0x1001d,  0x1f001e,  0x210020,  0x170022,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x230001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10024,  0x250001,  0x10026,  0x10027,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x280001,  0x290001,  0x2b002a,  0x2c0001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x2e002d,  0x30002f,  0x10001,  0x320031,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10033,  0x350034,  0x370036,  0x390038,  0x3b003a,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x0,  0x70000810,  0x40000000,  0x50000001,  0x0,  0x113d37c,  0x800000,  0x800000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfffc003c,  0xffffafe0,  0x0,  0x0,  0x0,  0x200000,  0x30,  0x0,  0x0,  0x400000,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8000,  0x0,  0x0,  0x0,  0xc9c0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40000000,  0x60000200,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x400000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20000,  0x0,  0x0,  0x0,  0x10000,  0x0,  0x0,  0x0,  0x7f80000,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x80000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xfce8000e,  0x1500000,  0x0,  0x0,  0x0,  0xc0000000,  0x1e0dfbf,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc0000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3ff0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8000000,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0xc0000000,  0xffffffff,  0x0,  0x0,  0x0,  0x1ff007fe,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xa0000000,  0xe000e003,  0x6000e000,  0x0,  0x0,  0x40010,  0x1c000000,  0x1c00,  0x7ffffff,  0x0,  0x0,  0xc1d0037b,  0xc0042af,  0xbc1f,  0x0,  0xffff0000,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffff0ff,  0xfffff9ff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffff,  0xffffffff,  0x7f,  0x7ff,  0x0,  0xf0000000,  0xffffffff,  0xffffffff,  0x3ff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xff,  0xfff00000,  0xffffffff,  0xffffff9f,  0xffff003f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfe000007,  0xffffffff,  0xf0ffffff,  0xcfffffff,  0xffffffff,  0xffffffff,  0x3ff1fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7e0,  0x0,  0x0,  0x0,  0x0,  0xfbffffff,  0xffffffff,  0xffffffff,  0xfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0xfff0000,  0xc0010,  0xc0c00001,  0x0,  0x0,  0x18000000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffc30000,  0x0,  0xffffffff,  0xf,  0x7fffffff,  0xfffffc00,  0x100ff,  0xffffffff,  0xfffffc00,  0x1ffff,  0xffffffff,  0x7fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x0,  0x0,  0x0,  0x0,  0xffff0000,  0xffffffff,  0x7f,  0x0,  0x7fffff,  0x3,  0x0,  0x0,  0x600,  0x0,  0x0,  0x0,  0x0,  0x3c00f00,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3800000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200,  0x0,  0x0,  0x0,  0xfffc0000,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30000000,  0x0,  0x0,  0x0,  0x274,  0x0,  0x0,  0x0,  0x0,  0x70000810,  0x40000000,  0x50000001,  0x0,  0x0,  0x0,  0x0,  0x30007f7f,  0x0,  0xff800000,  0x0,  0xfe000000,  0xfff03ff,  0x0,  0xffff0000,  0x1fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0xffffffff,  0xfffffe7f,  0xffffffff,  0x1c1f,  0xfffff018,  0xffffc3ff,  0x3fffffff,  0x0,  0xffffffff,  0xffffffff,  0x23,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x7fffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8000002,  0x8000000,  0x200000,  0x200000,  0x8000,  0x8000,  0x200,  0x200,  0x8,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x30000,  0xffffffff,  0xffff0fff,  0xffffffff,  0xffffffff,  0xfffff,  0x7ffe7fff,  0xfffefffe,  0x0,  0xffff0000,  0xffff7fff,  0xffffffff,  0xffff0fff,  0x7ffffff,  0x0,  0x0,  0xffffffc0,  0xffff0007,  0x7ffffff,  0x301ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffbf0001,  0xffffffff,  0x1fffffff,  0xfffff,  0xffffffff,  0x7df,  0x1ffff,  0xffffffff,  0x7fffffff,  0xfffffffd,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1effffff,  0xffffffff,  0x3fffffff,  0xffff000f,  0xff,  0x0,  0x0,  0x0,  0xf8000000,  0xffffffff,  0xffffffff,  0xffe1,  0x0,  0xffffffff,  0xffffffff,  0x3f,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//4576 bytes
+enum graphicalTrieEntries = TrieEntry!(bool, 8, 5, 8)([ 0x0,  0x40,  0x170], [ 0x100,  0x260,  0x6100], [ 0x3020100,  0x7060504,  0xb0a0908,  0xe0d0c0a,  0x3030303,  0x100a0f03,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a11,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0xa0a0a0a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x20001,  0x30001,  0x50004,  0x70006,  0x90008,  0xb000a,  0xd000c,  0x1000e,  0x10000f,  0x10001,  0x120011,  0x140013,  0x160015,  0x180017,  0x190001,  0x1b001a,  0x1c0001,  0x1001d,  0x1e0001,  0x10001,  0x1f0001,  0x210020,  0x230022,  0x250024,  0x10026,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x270001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x280001,  0x10001,  0x10001,  0x10029,  0x2b002a,  0x2d002c,  0x2f002e,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x300001,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x10031,  0x330032,  0x340001,  0x360035,  0x380037,  0x3a0039,  0x31003b,  0x310031,  0x3d003c,  0x3f003e,  0x310040,  0x310041,  0x430042,  0x310031,  0x310031,  0x310044,  0x310031,  0x310031,  0x310031,  0x310031,  0x10001,  0x450001,  0x310046,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x10001,  0x10001,  0x310047,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x10001,  0x310048,  0x310031,  0x490031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x31004a,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x4c004b,  0x4e004d,  0x50004f,  0x520051,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310053,  0x550054,  0x570056,  0x590058,  0x5b005a,  0x310031,  0x310031,  0x310031,  0x310031,  0x10001,  0x10001,  0x10001,  0x1005c,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x10001,  0x5d0001,  0x31005e,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x10001,  0x31005e,  0x310031,  0x310031,  0x5f0031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x310031,  0x0,  0xffffffff,  0xffffffff,  0x7fffffff,  0x0,  0xffffdfff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7cffffff,  0xffffd7f0,  0xfffffffb,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffe00ff,  0xfe7fffff,  0xfffffffe,  0xfffe86ff,  0xffffffff,  0xffff00ff,  0x1f07ff,  0xcfffffc0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xdfffffff,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffe7ff,  0xffffffff,  0xffffffff,  0x3ffff,  0xffffffff,  0x7ffffff,  0xffffffff,  0x7fff3fff,  0x4fffffff,  0x0,  0x0,  0x1ffd,  0x0,  0x7ffffff0,  0xffffffff,  0xffffffff,  0xffffffff,  0xfeffffff,  0xfff99fee,  0xf3c5fdff,  0xb080799f,  0xfffffcf,  0xfff987ee,  0xd36dfdff,  0x5e023987,  0x3fffc0,  0xfffbbfee,  0xf3edfdff,  0x13bbf,  0x3ffcf,  0xfff99fee,  0xf3edfdff,  0xb0c0399f,  0xffffcf,  0xd63dc7ec,  0xc3ffc718,  0x813dc7,  0x7ffffc0,  0xfffddfee,  0xe3effdff,  0x3603ddf,  0xff00ffcf,  0xfffddfec,  0xf3effdff,  0x40603ddf,  0x6ffcf,  0xfffddfec,  0xe7ffffff,  0x807ddf,  0xfe3fffcf,  0xfc7fffec,  0x2ffbffff,  0xff5f847f,  0x1c0000,  0xfffffffe,  0x87ffffff,  0xfffffff,  0x0,  0xfef02596,  0x3bffecae,  0xf3ff3f5f,  0x0,  0xffffffff,  0xffffffff,  0xfffffeff,  0xfffe1fff,  0xfeffffff,  0xdfffffff,  0x7ffdfff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff20bf,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d7f3dff,  0xffffffff,  0xffff3dff,  0x7f3dffff,  0xff7fff3d,  0xffffffff,  0xff3dffff,  0xffffffff,  0xe7ffffff,  0x1fffffff,  0x3ffffff,  0xffffffff,  0xffffffff,  0x1fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fffffff,  0xffffffff,  0xffffffff,  0x1ffff,  0x1fdfff,  0x7fffff,  0xfffff,  0xddfff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x3ff03ff,  0x3ff3fff,  0xffffffff,  0xffffffff,  0xffffff,  0xffffffff,  0xffff07ff,  0xffffffff,  0x3fffff,  0x1fffffff,  0xfff0fff,  0xfffffff1,  0x1f3fff,  0xffffffff,  0xffff0fff,  0xc7ff03ff,  0xffffffff,  0xcfffffff,  0xffffffff,  0x7fffffff,  0x9fffffff,  0x3ff03ff,  0x3fff,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffff0fff,  0x1fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf00fffff,  0xffffffff,  0xf8ffffff,  0xffffe3ff,  0xffffffff,  0x0,  0x0,  0xffff00ff,  0x7fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf000007f,  0x3f3fffff,  0xffffffff,  0xaaff3f3f,  0x3fffffff,  0xffffffff,  0xffdfffff,  0xefcfffdf,  0x7fdcffff,  0xffff07ff,  0xffff80ff,  0xffffffff,  0xfff30000,  0x1fff7fff,  0x7ffffff,  0xffff0000,  0x1ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03ff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffff,  0xffffffff,  0x7f,  0x7ff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3ff1fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffff7fff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xfe0fffff,  0xffffffff,  0xffff20bf,  0xffffffff,  0x800180ff,  0x7fffff,  0x7f7f7f7f,  0x7f7f7f7f,  0xffffffff,  0xffffffff,  0xfffffff,  0x0,  0x0,  0xfbffffff,  0xffffffff,  0xffffffff,  0xfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0xfff0000,  0xffffffff,  0xffffffff,  0xfffffffe,  0xffffffff,  0xfe7fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffe0,  0xfffe3fff,  0xffffffff,  0xffffffff,  0xffff7fff,  0x7ffffff,  0xffffffff,  0xffff000f,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff1fff,  0xffffffff,  0xffff007f,  0xffffffff,  0xffffffff,  0xfff,  0xffffffff,  0xffffffff,  0x80ffffff,  0xffffffff,  0xffffffff,  0xffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf7fff,  0x7ff,  0x0,  0xff000000,  0xffffffff,  0x3ff0fff,  0xffffffff,  0xffffff,  0xffffffff,  0xffffffff,  0x3ffc01f,  0xfffffff,  0xffffffff,  0xffffffff,  0x800fffff,  0x1fffffff,  0xffffffff,  0xffffffff,  0xc3ffbfff,  0x0,  0xffffffff,  0x7fffff,  0xf3ff3fff,  0xfffffff,  0xffffffff,  0xffffffff,  0xf8000007,  0x7fffff,  0x7e7e7e,  0x7f7f,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x3ff3fff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff000f,  0xfffff87f,  0xfffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff3fff,  0xffffffff,  0xffffffff,  0x3ffffff,  0x0,  0xe0f8007f,  0x5f7fffff,  0xffffffdb,  0xffffffff,  0xffffffff,  0xffffffff,  0xfff80003,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0000,  0xffffffff,  0xfffcffff,  0xffffffff,  0xff,  0x3fff0000,  0x3ffffff,  0xffff007f,  0xfff7ffff,  0xffdf0f7f,  0xffffffff,  0xffffffff,  0xffffffff,  0x1fffffff,  0xfffffffe,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffffff,  0x1cfcfcfc,  0x30007f7f,  0xffffefff,  0xb7ffff7f,  0x3fff3fff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7ffffff,  0xffffff87,  0xff8fffff,  0xffffffff,  0xffffffff,  0xfff07ff,  0x0,  0xffff0000,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x1fffffff,  0xffffffff,  0x1ffff,  0x0,  0x7fffffff,  0xffff000f,  0x7ff,  0x0,  0xbfffffff,  0xffffffff,  0x3fff0f,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x3ff,  0x0,  0x0,  0xfffffd3f,  0x91bfffff,  0xffbfffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x8fffffff,  0x83ffffff,  0x0,  0x0,  0xffffffff,  0xc0ffffff,  0x0,  0x0,  0xfeeff06f,  0x870fffff,  0x1ff00ff,  0xffffffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xfe3fffff,  0xff3fffff,  0xff07ffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x1ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7fffffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xfffc3fff,  0xffff,  0xffffffff,  0xdfffffff,  0xffff0003,  0x3ff01ff,  0xffffffff,  0xffdfffff,  0xf,  0x0,  0xffffffff,  0xffffffff,  0x3ff01ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffff,  0x3ff,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xf0007,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x7fff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0x1ffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffff001f,  0x7fffffff,  0xffff8000,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffff,  0xffffffff,  0xfffffe7f,  0xffffffff,  0xf807ffff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x0,  0xffffffff,  0xffffffff,  0x3f,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0x7fffff,  0x3ffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffdfffff,  0xffffffff,  0xdfffffff,  0xebffde64,  0xffffffef,  0xffffffff,  0xdfdfe7bf,  0x7bffffff,  0xfffdfc5f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffff3f,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffcfff,  0xffffffff,  0xffffffef,  0xaf7fe96,  0xaa96ea84,  0x5ef7f796,  0xffffbff,  0xffffbee,  0x0,  0x30000,  0xffffffff,  0xffff0fff,  0xffffffff,  0xffffffff,  0xfffff,  0x7ffe7fff,  0xfffefffe,  0x0,  0xffff07ff,  0xffff7fff,  0xffffffff,  0xffff0fff,  0x7ffffff,  0x0,  0x0,  0xffffffc0,  0xffff0007,  0x7ffffff,  0x301ff,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffbf0001,  0xffffffff,  0x1fffffff,  0xfffff,  0xffffffff,  0x7df,  0x1ffff,  0xffffffff,  0x7fffffff,  0xfffffffd,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1effffff,  0xffffffff,  0x3fffffff,  0xffff000f,  0xff,  0x0,  0x0,  0x0,  0xf8000000,  0xffffffff,  0xffffffff,  0xffe1,  0x0,  0xffffffff,  0xffffffff,  0x3f,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xfffff,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x7fffff,  0x0,  0xffffffff,  0x1fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3fffffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0]);
+//3664 bytes
+enum nonCharacterTrieEntries = TrieEntry!(bool, 7, 4, 4, 6)([ 0x0,  0x20,  0x98,  0x208], [ 0x80,  0xf0,  0x2e0,  0x3180], [ 0x3020100,  0x7060504,  0xa090808,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0b,  0xb0b0b0c,  0xd080808,  0xd080808,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xb000a,  0xd000c,  0xd000d,  0xd000d,  0xe000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xf000d,  0x10000d,  0xd0011,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0x12000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0x140013,  0x160015,  0x180017,  0x1a0019,  0x1b001b,  0x1d001c,  0x1b001b,  0x1e000d,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x20001f,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b0021,  0x1b001b,  0x1b001b,  0x1b001b,  0x230022,  0x1b001b,  0x1b001b,  0x24001b,  0x260025,  0x1b001b,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0x27000d,  0xd000d,  0x28000d,  0x1b0029,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b002a,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b002b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0x1b001b,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0x2c000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0xd000d,  0x2c000d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x10000,  0x2,  0x0,  0x0,  0x40003,  0x60005,  0x7,  0x0,  0x90008,  0xb000a,  0xd000c,  0xf000e,  0x100000,  0x120011,  0x140013,  0x160015,  0x180017,  0x1a0019,  0x1c001b,  0x1e001d,  0x20001f,  0x220021,  0x240023,  0x260025,  0x270000,  0x290028,  0x0,  0x2a0000,  0x0,  0x0,  0x2b0000,  0x2d002c,  0x2f002e,  0x310030,  0x0,  0x0,  0x0,  0x0,  0x0,  0x330032,  0x350034,  0x360000,  0x380037,  0x3a0039,  0x3c003b,  0x3e003d,  0x40003f,  0x420041,  0x430000,  0x440000,  0x460045,  0x470042,  0x0,  0x480000,  0x0,  0x0,  0x4a0049,  0x4c004b,  0x4d0000,  0x4f004e,  0x0,  0x50,  0x0,  0x0,  0x0,  0x510000,  0x530052,  0x0,  0x0,  0x0,  0x0,  0x0,  0x54,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x550000,  0x420042,  0x570056,  0x580000,  0x5a0059,  0x5c005b,  0x42005d,  0x51005e,  0x0,  0x5f0000,  0x540000,  0x60,  0x61,  0x630062,  0x57,  0x640000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x650000,  0x0,  0x670066,  0x0,  0x0,  0x68,  0x380069,  0x0,  0x6b006a,  0x38006c,  0x6d0000,  0x6e0000,  0x6f0000,  0x710070,  0x720000,  0x420073,  0x740042,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x750063,  0x0,  0x0,  0x0,  0x0,  0x760000,  0x770000,  0x790078,  0x7a0000,  0x0,  0x0,  0x7b0000,  0x7d007c,  0x7f007e,  0x800000,  0x54,  0x810064,  0x830082,  0xb0000,  0x84,  0x860085,  0x420042,  0x870032,  0x890088,  0x8b008a,  0x0,  0x42008c,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x8e008d,  0x420042,  0x42008f,  0x420090,  0x920091,  0x420042,  0x940093,  0x420042,  0x950000,  0x420042,  0x420042,  0x420042,  0x960042,  0x420042,  0x420042,  0x420042,  0x970000,  0x980000,  0x99004b,  0x9a0000,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x9b0038,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x9c0000,  0x420042,  0x9d0000,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x42009c,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x0,  0x0,  0x0,  0x0,  0x42009e,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x9f0000,  0x4200a0,  0x4200a1,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x0,  0x3a0000,  0xa2,  0xa30000,  0xa40000,  0x420042,  0xa50000,  0x420042,  0xa60000,  0xa800a7,  0xaa00a9,  0x0,  0x0,  0xab,  0x0,  0xac0000,  0x420042,  0x420042,  0x420042,  0x420042,  0xae00ad,  0xb000af,  0x420042,  0x420042,  0x3d,  0xb200b1,  0x3d00b3,  0xb500b4,  0xb700b6,  0x420042,  0xb900b8,  0xbb00ba,  0xbc0064,  0xbd0000,  0xbf00be,  0xc00042,  0xc10000,  0xa40000,  0x510000,  0x420042,  0x0,  0x0,  0x0,  0x0,  0x0,  0xc20000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x31,  0x0,  0x4200a3,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x420042,  0x0,  0x0,  0x0,  0x0,  0x4200a3,  0x420042,  0x420042,  0x420042,  0xc3,  0x420042,  0x0,  0xc40000,  0x420042,  0x420042,  0x420042,  0x420042,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbe0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xbe0000,  0x0,  0x0,  0x0,  0x83000000,  0x280f,  0x4,  0x0,  0x1ff00,  0x1800000,  0x1,  0x17900,  0x0,  0xff00,  0xffe0f800,  0x20000020,  0x0,  0x4000,  0x0,  0x1800,  0x0,  0x0,  0xfffc0000,  0x0,  0xf8000000,  0x0,  0x8000c000,  0xb0000000,  0xffffffff,  0xffffffff,  0xffffe002,  0xffffffff,  0x8000000f,  0x0,  0x1000000,  0x66011,  0xc3a0200,  0x4f7f8660,  0xf0000030,  0x67811,  0x2c920200,  0xa1fdc678,  0xffc0003f,  0x44011,  0xc120200,  0xfffec440,  0xfffc0030,  0x66011,  0xc120200,  0x4f3fc660,  0xff000030,  0x29c23813,  0x3c0038e7,  0xff7ec238,  0xf800003f,  0x22011,  0x1c100200,  0xfc9fc220,  0xff0030,  0x22013,  0xc100200,  0xbf9fc220,  0xfff90030,  0x22013,  0x18000000,  0xff7f8220,  0x1c00030,  0x3800013,  0xd0040000,  0xa07b80,  0xffe3ffff,  0x1,  0x78000000,  0xf0000000,  0xffffffff,  0x10fda69,  0xc4001351,  0xc00c0a0,  0xffffffff,  0x100,  0x1e000,  0x1000000,  0x20000000,  0xf8002000,  0xffffffff,  0xdf40,  0x0,  0xc280c200,  0x0,  0xc200,  0x80c20000,  0x8000c2,  0x0,  0xc20000,  0x0,  0x18000000,  0xe0000000,  0xfc000000,  0x0,  0x0,  0xffe00000,  0xe0000000,  0x0,  0x0,  0xfffe0000,  0xffe02000,  0xff800000,  0xfff00000,  0xfff22000,  0xc0000000,  0xfc00fc00,  0xfc008000,  0x0,  0x0,  0xff000000,  0x0,  0xf800,  0x0,  0xffc00000,  0xe0000000,  0xf000f000,  0xe,  0xffe0c000,  0x0,  0xf000,  0x3800fc00,  0x0,  0x30000000,  0x0,  0x80000000,  0x60000000,  0xfc00fc00,  0xffffc000,  0xffffffff,  0xffffffff,  0xf000,  0xe0000000,  0x0,  0xff00000,  0x0,  0x7000000,  0x1c00,  0x0,  0xff00,  0xff800000,  0x0,  0xfffff80,  0xc0c00000,  0x0,  0x5500c0c0,  0xc0000000,  0x0,  0x200000,  0x10300020,  0x80230000,  0x0,  0xc0020,  0xe0008000,  0xf8000000,  0xffff,  0xfffe0000,  0xfc00,  0x0,  0x0,  0xfff00000,  0x0,  0xffffff80,  0xfffff800,  0x0,  0x1,  0x0,  0xfc00e000,  0xffffffff,  0x0,  0x8000,  0x80000000,  0x0,  0x0,  0x1f00000,  0x0,  0xdf40,  0x0,  0x7ffe7f00,  0xff800000,  0x80808080,  0x80808080,  0x0,  0x0,  0xf0000000,  0x4000000,  0x0,  0xffc00000,  0xf000ffff,  0x1800000,  0x0,  0x1f,  0x1c000,  0x8000,  0xf8000000,  0x0,  0xfff0,  0x0,  0x80000000,  0xffffe000,  0xffffffff,  0xe000,  0x0,  0xff80,  0x0,  0x0,  0xfffff000,  0x7f000000,  0x0,  0xfff08000,  0xfffff800,  0xffffffff,  0xffffff,  0x0,  0xfc00f000,  0xfc003fe0,  0xf0000000,  0x7ff00000,  0xe0000000,  0x3c004000,  0xffffffff,  0x0,  0xff800000,  0xc00c000,  0xf0000000,  0x7fffff8,  0xff800000,  0xff818181,  0xffff8080,  0x0,  0xfc00c000,  0x780,  0xf0000000,  0x0,  0xc000,  0xfc000000,  0xffffffff,  0x1f07ff80,  0xa0800000,  0x24,  0x0,  0x7fffc,  0x0,  0xffff,  0x0,  0x30000,  0x0,  0xffffff00,  0xc000ffff,  0xfc000000,  0xff80,  0x80000,  0x20f080,  0x0,  0x60000000,  0xe3030303,  0xc1ff8080,  0x1000,  0x48000080,  0xc000c000,  0xffffffff,  0x78,  0x700000,  0xf000f800,  0xffffffff,  0xffff,  0xc0000000,  0xfffe0000,  0xffffffff,  0x80000000,  0xfff0,  0xfffff800,  0xffffffff,  0x40000000,  0x0,  0xffc000f0,  0xffffffff,  0xc0000000,  0xfffffc00,  0x2c0,  0x6e400000,  0x400000,  0xffffffff,  0x70000000,  0x7c000000,  0x0,  0x3f000000,  0x1100f90,  0x78f00000,  0xfe00ff00,  0x0,  0x0,  0x1c00000,  0xc00000,  0xf80000,  0xfffffe00,  0xffffffff,  0xffffffff,  0x80000000,  0x3c000,  0xffff0000,  0xfffc,  0xfc00fe00,  0xfffffff0,  0xffffffff,  0xfc00fe00,  0xffffffff,  0xfffffc00,  0xffffffff,  0x0,  0xffff8000,  0x0,  0xfff0fff8,  0x0,  0xfe000000,  0xffe0,  0x80000000,  0x7fff,  0xffffffff,  0xfffffffc,  0xffffffff,  0x0,  0x180,  0xc0000000,  0xffffffff,  0xffffffc0,  0xffffffff,  0xff800000,  0xfffc0000,  0x200000,  0x0,  0x20000000,  0x1400219b,  0x10,  0x0,  0x20201840,  0x84000000,  0x203a0,  0x0,  0x0,  0xc0,  0x3000,  0x0,  0x10,  0xf5080169,  0x5569157b,  0xa1080869,  0xf0000400,  0xf0000411,  0xffffffff,  0xfffcffff,  0xfff00000,  0x80018000,  0x10001,  0xffffffff,  0xf800,  0x8000,  0xf8000000,  0xffffffff,  0xffffffff,  0x3f,  0xfff8,  0xf8000000,  0xfffcfe00,  0xffffffff,  0x0,  0x40fffe,  0x0,  0xe0000000,  0xfff00000,  0x0,  0xfffff820,  0xfffe0000,  0x2,  0x0,  0x0,  0xe1000000,  0x0,  0xc0000000,  0xfff0,  0xffffff00,  0xffffffff,  0x7ffffff,  0xffff001e,  0xffffffff,  0xff800000,  0xffffffff,  0xfffffffd,  0x0,  0x0,  0xffff0000,  0x0,  0xc0000000]);
+enum MAX_SIMPLE_LOWER = 1043;
+enum MAX_SIMPLE_UPPER = 1051;
+enum MAX_SIMPLE_TITLE = 1055;
+//8192 bytes
+enum toUpperIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xc00], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x0,  0x170016,  0x190018,  0x1b001a,  0x1d001c,  0x0,  0x0,  0x1e0000,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x220021,  0x240023,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260000,  0x27,  0x290028,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2e002d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x41bffff,  0x1c001b,  0x1e001d,  0x20001f,  0x220021,  0x240023,  0x260025,  0x280027,  0x2a0029,  0x2c002b,  0x2e002d,  0x30002f,  0xffff0031,  0x330032,  0x350034,  0x370036,  0x390038,  0x3affff,  0x3bffff,  0x3cffff,  0x3dffff,  0x3effff,  0x3fffff,  0x40ffff,  0x41ffff,  0x42ffff,  0x43ffff,  0x44ffff,  0x45ffff,  0x46ffff,  0x47ffff,  0x48ffff,  0x49ffff,  0x4affff,  0x4bffff,  0x4cffff,  0x4dffff,  0x4effff,  0x4fffff,  0x50ffff,  0x51ffff,  0x52041d,  0x53ffff,  0x54ffff,  0x55ffff,  0xffffffff,  0xffff0056,  0xffff0057,  0xffff0058,  0xffff0059,  0xffff005a,  0xffff005b,  0xffff005c,  0x43a005d,  0x5effff,  0x5fffff,  0x60ffff,  0x61ffff,  0x62ffff,  0x63ffff,  0x64ffff,  0x65ffff,  0x66ffff,  0x67ffff,  0x68ffff,  0x69ffff,  0x6affff,  0x6bffff,  0x6cffff,  0x6dffff,  0x6effff,  0x6fffff,  0x70ffff,  0x71ffff,  0x72ffff,  0x73ffff,  0x74ffff,  0xffffffff,  0xffff0075,  0xffff0076,  0x780077,  0xffff0079,  0x7affff,  0x7bffff,  0xffffffff,  0xffff007c,  0xffffffff,  0xffff007d,  0xffffffff,  0xffffffff,  0xffff007e,  0x7fffff,  0xffffffff,  0x80ffff,  0xffff0081,  0xffffffff,  0xffff0082,  0x83ffff,  0x84ffff,  0x85ffff,  0xffffffff,  0xffff0086,  0xffffffff,  0x87ffff,  0xffffffff,  0xffff0088,  0xffffffff,  0xffff0089,  0xffff008a,  0x8bffff,  0xffffffff,  0x8cffff,  0x8dffff,  0xffffffff,  0xffffffff,  0x8effff,  0xffff008f,  0x910090,  0x92ffff,  0xffff0093,  0xffff0094,  0xffff0095,  0xffff0096,  0xffff0097,  0xffff0098,  0xffff0099,  0xffff009a,  0x9c009b,  0x9dffff,  0x9effff,  0x9fffff,  0xa0ffff,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xffff0442,  0xa700a6,  0xa8ffff,  0xffffffff,  0xa9ffff,  0xaaffff,  0xabffff,  0xacffff,  0xadffff,  0xaeffff,  0xafffff,  0xb0ffff,  0xb1ffff,  0xb2ffff,  0xb3ffff,  0xb4ffff,  0xb5ffff,  0xb6ffff,  0xb7ffff,  0xb8ffff,  0xb9ffff,  0xbaffff,  0xbbffff,  0xbcffff,  0xffffffff,  0xbdffff,  0xbeffff,  0xbfffff,  0xc0ffff,  0xc1ffff,  0xc2ffff,  0xc3ffff,  0xc4ffff,  0xc5ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00c6,  0xc7ffff,  0xffff00c8,  0xffff00c9,  0xffffffff,  0xcaffff,  0xcbffff,  0xccffff,  0xcdffff,  0xceffff,  0xd000cf,  0xd200d1,  0xffff00d3,  0xd500d4,  0xd6ffff,  0xd7ffff,  0xffffffff,  0xffffffff,  0xffff00d8,  0xd9ffff,  0xdaffff,  0xffff00db,  0xdd00dc,  0xdeffff,  0xffffffff,  0xdfffff,  0xe0ffff,  0xffff00e1,  0xe2ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xe3ffff,  0xffffffff,  0xffff00e4,  0xe5ffff,  0xffffffff,  0xffffffff,  0xe700e6,  0xe900e8,  0xffff00ea,  0xffffffff,  0xffffffff,  0xffff00eb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xecffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xedffff,  0xeeffff,  0xffffffff,  0xefffff,  0xffffffff,  0xf0ffff,  0xf200f1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff043c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf400f3,  0xf600f5,  0xf7043f,  0xf900f8,  0xfb00fa,  0xfd00fc,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0x1070106,  0x1090108,  0x10b010a,  0x10d010c,  0x10f010e,  0x1110110,  0x1130112,  0xffff0114,  0x1160115,  0xffffffff,  0x117ffff,  0x1190118,  0x11affff,  0x11bffff,  0x11cffff,  0x11dffff,  0x11effff,  0x11fffff,  0x120ffff,  0x121ffff,  0x122ffff,  0x123ffff,  0x124ffff,  0x125ffff,  0x1270126,  0xffff0128,  0x129ffff,  0xffffffff,  0xffff012a,  0x12bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x12d012c,  0x12f012e,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0x14b014a,  0x14d014c,  0x14f014e,  0x1510150,  0x1530152,  0x1550154,  0x1570156,  0x1590158,  0x15b015a,  0x15cffff,  0x15dffff,  0x15effff,  0x15fffff,  0x160ffff,  0x161ffff,  0x162ffff,  0x163ffff,  0x164ffff,  0x165ffff,  0x166ffff,  0x167ffff,  0x168ffff,  0x169ffff,  0x16affff,  0x16bffff,  0x16cffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x16dffff,  0x16effff,  0x16fffff,  0x170ffff,  0x171ffff,  0x172ffff,  0x173ffff,  0x174ffff,  0x175ffff,  0x176ffff,  0x177ffff,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0x17effff,  0x17fffff,  0x180ffff,  0x181ffff,  0x182ffff,  0x183ffff,  0x184ffff,  0x185ffff,  0x186ffff,  0x187ffff,  0xffffffff,  0xffff0188,  0xffff0189,  0xffff018a,  0xffff018b,  0xffff018c,  0xffff018d,  0x18f018e,  0x190ffff,  0x191ffff,  0x192ffff,  0x193ffff,  0x194ffff,  0x195ffff,  0x196ffff,  0x197ffff,  0x198ffff,  0x199ffff,  0x19affff,  0x19bffff,  0x19cffff,  0x19dffff,  0x19effff,  0x19fffff,  0x1a0ffff,  0x1a1ffff,  0x1a2ffff,  0x1a3ffff,  0x1a4ffff,  0x1a5ffff,  0x1a6ffff,  0x1a7ffff,  0x1a8ffff,  0x1a9ffff,  0x1aaffff,  0x1abffff,  0x1acffff,  0x1adffff,  0x1aeffff,  0x1afffff,  0x1b0ffff,  0x1b1ffff,  0x1b2ffff,  0x1b3ffff,  0x1b4ffff,  0x1b5ffff,  0x1b6ffff,  0x1b7ffff,  0x1b8ffff,  0x1b9ffff,  0x1baffff,  0x1bbffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1bcffff,  0x1be01bd,  0x1c001bf,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0x1d001cf,  0x1d201d1,  0x1d401d3,  0x1d601d5,  0x1d801d7,  0x1da01d9,  0x1dc01db,  0x1de01dd,  0x1e001df,  0x42e01e1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1e2ffff,  0xffffffff,  0x1e3ffff,  0xffffffff,  0x1e4ffff,  0x1e5ffff,  0x1e6ffff,  0x1e7ffff,  0x1e8ffff,  0x1e9ffff,  0x1eaffff,  0x1ebffff,  0x1ecffff,  0x1edffff,  0x1eeffff,  0x1efffff,  0x1f0ffff,  0x1f1ffff,  0x1f2ffff,  0x1f3ffff,  0x1f4ffff,  0x1f5ffff,  0x1f6ffff,  0x1f7ffff,  0x1f8ffff,  0x1f9ffff,  0x1faffff,  0x1fbffff,  0x1fcffff,  0x1fdffff,  0x1feffff,  0x1ffffff,  0x200ffff,  0x201ffff,  0x202ffff,  0x203ffff,  0x204ffff,  0x205ffff,  0x206ffff,  0x207ffff,  0x208ffff,  0x209ffff,  0x20affff,  0x20bffff,  0x20cffff,  0x20dffff,  0x20effff,  0x20fffff,  0x210ffff,  0x211ffff,  0x212ffff,  0x213ffff,  0x214ffff,  0x215ffff,  0x216ffff,  0x217ffff,  0x218ffff,  0x219ffff,  0x21affff,  0x21bffff,  0x21cffff,  0x21dffff,  0x21effff,  0x21fffff,  0x220ffff,  0x221ffff,  0x222ffff,  0x223ffff,  0x224ffff,  0x225ffff,  0x226ffff,  0x227ffff,  0x228ffff,  0x229ffff,  0x22affff,  0x22bffff,  0x22cffff,  0x22dffff,  0x22effff,  0x4460444,  0x44a0448,  0x22f044c,  0xffffffff,  0xffffffff,  0x230ffff,  0x231ffff,  0x232ffff,  0x233ffff,  0x234ffff,  0x235ffff,  0x236ffff,  0x237ffff,  0x238ffff,  0x239ffff,  0x23affff,  0x23bffff,  0x23cffff,  0x23dffff,  0x23effff,  0x23fffff,  0x240ffff,  0x241ffff,  0x242ffff,  0x243ffff,  0x244ffff,  0x245ffff,  0x246ffff,  0x247ffff,  0x248ffff,  0x249ffff,  0x24affff,  0x24bffff,  0x24cffff,  0x24dffff,  0x24effff,  0x24fffff,  0x250ffff,  0x251ffff,  0x252ffff,  0x253ffff,  0x254ffff,  0x255ffff,  0x256ffff,  0x257ffff,  0x258ffff,  0x259ffff,  0x25affff,  0x25bffff,  0x25cffff,  0x25dffff,  0x25effff,  0x25fffff,  0x2610260,  0x2630262,  0x2650264,  0x2670266,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2690268,  0x26b026a,  0x26d026c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x26f026e,  0x2710270,  0x2730272,  0x2750274,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2770276,  0x2790278,  0x27b027a,  0x27d027c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27f027e,  0x2810280,  0x2830282,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x284044e,  0x2850450,  0x2860453,  0x2870456,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2890288,  0x28b028a,  0x28d028c,  0x28f028e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2910290,  0x2930292,  0x2950294,  0x2970296,  0x2990298,  0x29b029a,  0x29d029c,  0xffffffff,  0x4790477,  0x47d047b,  0x481047f,  0x4850483,  0x4890487,  0x48d048b,  0x491048f,  0x4950493,  0x4990497,  0x49d049b,  0x4a1049f,  0x4a504a3,  0x4a904a7,  0x4ad04ab,  0x4b104af,  0x4b504b3,  0x4b904b7,  0x4bd04bb,  0x4c104bf,  0x4c504c3,  0x4c904c7,  0x4cd04cb,  0x4d104cf,  0x4d504d3,  0x2b702b6,  0x4d704e3,  0xffff04e5,  0x4ef0459,  0xffffffff,  0xffffffff,  0xffff04d9,  0xffff02b9,  0xffffffff,  0x4db04e7,  0xffff04e9,  0x4f2045b,  0xffffffff,  0xffffffff,  0xffff04dd,  0xffffffff,  0x2bc02bb,  0x460045d,  0xffffffff,  0x4650463,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2be02bd,  0x46b0468,  0x2bf046e,  0x4720470,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4df04eb,  0xffff04ed,  0x4f50475,  0xffffffff,  0xffffffff,  0xffff04e1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02c1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c302c2,  0x2c502c4,  0x2c702c6,  0x2c902c8,  0x2cb02ca,  0x2cd02cc,  0x2cf02ce,  0x2d102d0,  0xffffffff,  0xffffffff,  0xffff02d2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d402d3,  0x2d602d5,  0x2d802d7,  0x2da02d9,  0x2dc02db,  0x2de02dd,  0x2e002df,  0x2e202e1,  0x2e402e3,  0x2e602e5,  0x2e802e7,  0x2ea02e9,  0x2ec02eb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ee02ed,  0x2f002ef,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0xffff031b,  0x31cffff,  0xffffffff,  0x31dffff,  0xffff031e,  0xffff031f,  0xffff0320,  0xffff0321,  0xffffffff,  0xffffffff,  0x322ffff,  0xffffffff,  0xffff0323,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x324ffff,  0x325ffff,  0x326ffff,  0x327ffff,  0x328ffff,  0x329ffff,  0x32affff,  0x32bffff,  0x32cffff,  0x32dffff,  0x32effff,  0x32fffff,  0x330ffff,  0x331ffff,  0x332ffff,  0x333ffff,  0x334ffff,  0x335ffff,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33affff,  0x33bffff,  0x33cffff,  0x33dffff,  0x33effff,  0x33fffff,  0x340ffff,  0x341ffff,  0x342ffff,  0x343ffff,  0x344ffff,  0x345ffff,  0x346ffff,  0x347ffff,  0x348ffff,  0x349ffff,  0x34affff,  0x34bffff,  0x34cffff,  0x34dffff,  0x34effff,  0x34fffff,  0x350ffff,  0x351ffff,  0x352ffff,  0x353ffff,  0x354ffff,  0x355ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0356,  0xffff0357,  0xffffffff,  0x358ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x35a0359,  0x35c035b,  0x35e035d,  0x360035f,  0x3620361,  0x3640363,  0x3660365,  0x3680367,  0x36a0369,  0x36c036b,  0x36e036d,  0x370036f,  0x3720371,  0x3740373,  0x3760375,  0x3780377,  0x37a0379,  0x37c037b,  0x37e037d,  0x37fffff,  0xffffffff,  0xffffffff,  0x380ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x381ffff,  0x382ffff,  0x383ffff,  0x384ffff,  0x385ffff,  0x386ffff,  0x387ffff,  0x388ffff,  0x389ffff,  0x38affff,  0x38bffff,  0x38cffff,  0x38dffff,  0x38effff,  0x38fffff,  0x390ffff,  0x391ffff,  0x392ffff,  0x393ffff,  0x394ffff,  0x395ffff,  0x396ffff,  0x397ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x398ffff,  0x399ffff,  0x39affff,  0x39bffff,  0x39cffff,  0x39dffff,  0x39effff,  0x39fffff,  0x3a0ffff,  0x3a1ffff,  0x3a2ffff,  0x3a3ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3a4ffff,  0x3a5ffff,  0x3a6ffff,  0x3a7ffff,  0x3a8ffff,  0x3a9ffff,  0x3aaffff,  0xffffffff,  0x3abffff,  0x3acffff,  0x3adffff,  0x3aeffff,  0x3afffff,  0x3b0ffff,  0x3b1ffff,  0x3b2ffff,  0x3b3ffff,  0x3b4ffff,  0x3b5ffff,  0x3b6ffff,  0x3b7ffff,  0x3b8ffff,  0x3b9ffff,  0x3baffff,  0x3bbffff,  0x3bcffff,  0x3bdffff,  0x3beffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0x3c2ffff,  0x3c3ffff,  0x3c4ffff,  0x3c5ffff,  0x3c6ffff,  0x3c7ffff,  0x3c8ffff,  0x3c9ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03ca,  0xffff03cb,  0x3ccffff,  0x3cdffff,  0x3ceffff,  0x3cfffff,  0x3d0ffff,  0xffffffff,  0xffffffff,  0xffff03d1,  0xffffffff,  0x3d2ffff,  0x3d3ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d4ffff,  0x3d5ffff,  0x3d6ffff,  0x3d7ffff,  0x3d8ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x420041e,  0x4240422,  0x42a0427,  0xffff042c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x430ffff,  0x4340432,  0x4380436,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d9ffff,  0x3db03da,  0x3dd03dc,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0x3eb03ea,  0x3ed03ec,  0x3ef03ee,  0x3f103f0,  0xffff03f2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3f403f3,  0x3f603f5,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0x4140413,  0x4160415,  0x4180417,  0x41a0419,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//8064 bytes
+enum toLowerIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xbc0], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x20000,  0x40003,  0x60005,  0x80007,  0x0,  0x90000,  0xb000a,  0xd000c,  0xf000e,  0x110010,  0x12,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x140013,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x160015,  0x180017,  0x1a0019,  0x1c001b,  0x0,  0x0,  0x1e001d,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x210020,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x230022,  0x250024,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260000,  0x27,  0x290028,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b001a,  0x1d001c,  0x1f001e,  0x210020,  0x230022,  0x250024,  0x270026,  0x290028,  0x2b002a,  0x2d002c,  0x2f002e,  0xffff0030,  0x320031,  0x340033,  0x360035,  0x4130037,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0038,  0xffff0039,  0xffff003a,  0xffff003b,  0xffff003c,  0xffff003d,  0xffff003e,  0xffff003f,  0xffff0040,  0xffff0041,  0xffff0042,  0xffff0043,  0xffff0044,  0xffff0045,  0xffff0046,  0xffff0047,  0xffff0048,  0xffff0049,  0xffff004a,  0xffff004b,  0xffff004c,  0xffff004d,  0xffff004e,  0xffff004f,  0xffff0414,  0xffff0051,  0xffff0052,  0xffff0053,  0x54ffff,  0x55ffff,  0x56ffff,  0x57ffff,  0x58ffff,  0x59ffff,  0x5affff,  0x5bffff,  0x423ffff,  0xffff005c,  0xffff005d,  0xffff005e,  0xffff005f,  0xffff0060,  0xffff0061,  0xffff0062,  0xffff0063,  0xffff0064,  0xffff0065,  0xffff0066,  0xffff0067,  0xffff0068,  0xffff0069,  0xffff006a,  0xffff006b,  0xffff006c,  0xffff006d,  0xffff006e,  0xffff006f,  0xffff0070,  0xffff0071,  0xffff0072,  0x740073,  0x75ffff,  0x76ffff,  0xffffffff,  0x77ffff,  0xffff0078,  0xffff0079,  0x7b007a,  0x7cffff,  0x7e007d,  0xffffffff,  0x80007f,  0x820081,  0x83ffff,  0xffff0084,  0x860085,  0xffff0087,  0xffffffff,  0x890088,  0x8affff,  0xffff008b,  0xffff008c,  0xffff008d,  0x8f008e,  0x90ffff,  0xffffffff,  0xffff0091,  0x930092,  0x94ffff,  0x960095,  0x97ffff,  0x98ffff,  0xffff0099,  0xffffffff,  0xffff009a,  0xffffffff,  0xffffffff,  0xffffffff,  0x9c009b,  0x9dffff,  0xffff009e,  0xa0009f,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xa6ffff,  0xa7ffff,  0xa8ffff,  0xffffffff,  0xffff00a9,  0xffff00aa,  0xffff00ab,  0xffff00ac,  0xffff00ad,  0xffff00ae,  0xffff00af,  0xffff00b0,  0xffff00b1,  0xb20426,  0xffff00b3,  0xffff00b4,  0xb600b5,  0xffff00b7,  0xffff00b8,  0xffff00b9,  0xffff00ba,  0xffff00bb,  0xffff00bc,  0xffff00bd,  0xffff00be,  0xffff00bf,  0xffff00c0,  0xffff00c1,  0xffff00c2,  0xffff00c3,  0xffff00c4,  0xffff00c5,  0xffff00c6,  0xffff00c7,  0xffff00c8,  0xffff00c9,  0xffff00ca,  0xffff00cb,  0xffff00cc,  0xffff00cd,  0xffff00ce,  0xffff00cf,  0xffff00d0,  0xffff00d1,  0xffff00d2,  0xffff00d3,  0xffff00d4,  0xffffffff,  0xffffffff,  0xffffffff,  0xd600d5,  0xd7ffff,  0xffff00d8,  0xd9ffff,  0xdaffff,  0xdc00db,  0xffff00dd,  0xffff00de,  0xffff00df,  0xffff00e0,  0xffff00e1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00e2,  0xffff00e3,  0xffffffff,  0xffff00e4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00e5,  0xe700e6,  0xffff00e8,  0xffff00e9,  0xeb00ea,  0xec0424,  0xee00ed,  0xf000ef,  0xf200f1,  0xf400f3,  0xf600f5,  0xf800f7,  0xfa00f9,  0xfc00fb,  0xfdffff,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0xffffffff,  0xffffffff,  0xffff0425,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x106ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0107,  0xffff0108,  0xffff0109,  0xffff010a,  0xffff010b,  0xffff010c,  0xffff010d,  0xffff010e,  0xffff010f,  0xffff0110,  0xffff0111,  0xffff0112,  0xffffffff,  0xffffffff,  0xffff0113,  0x114ffff,  0x115ffff,  0xffff0116,  0x117ffff,  0x1190118,  0x11b011a,  0x11d011c,  0x11f011e,  0x1210120,  0x1230122,  0x1250124,  0x1270126,  0x1290128,  0x12b012a,  0x12d012c,  0x12f012e,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff014a,  0xffff014b,  0xffff014c,  0xffff014d,  0xffff014e,  0xffff014f,  0xffff0150,  0xffff0151,  0xffff0152,  0xffff0153,  0xffff0154,  0xffff0155,  0xffff0156,  0xffff0157,  0xffff0158,  0xffff0159,  0xffff015a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff015b,  0xffff015c,  0xffff015d,  0xffff015e,  0xffff015f,  0xffff0160,  0xffff0161,  0xffff0162,  0xffff0163,  0xffff0164,  0xffff0165,  0xffff0166,  0xffff0167,  0xffff0168,  0xffff0169,  0xffff016a,  0xffff016b,  0xffff016c,  0xffff016d,  0xffff016e,  0xffff016f,  0xffff0170,  0xffff0171,  0xffff0172,  0xffff0173,  0xffff0174,  0xffff0175,  0x1770176,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0xffffffff,  0xffff017e,  0xffff017f,  0xffff0180,  0xffff0181,  0xffff0182,  0xffff0183,  0xffff0184,  0xffff0185,  0xffff0186,  0xffff0187,  0xffff0188,  0xffff0189,  0xffff018a,  0xffff018b,  0xffff018c,  0xffff018d,  0xffff018e,  0xffff018f,  0xffff0190,  0xffff0191,  0xffff0192,  0xffff0193,  0xffff0194,  0xffff0195,  0xffff0196,  0xffff0197,  0xffff0198,  0xffff0199,  0xffff019a,  0xffff019b,  0xffff019c,  0xffff019d,  0xffff019e,  0xffff019f,  0xffff01a0,  0xffff01a1,  0xffff01a2,  0xffff01a3,  0xffff01a4,  0xffff01a5,  0xffff01a6,  0xffff01a7,  0xffff01a8,  0xffff01a9,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1aaffff,  0x1ac01ab,  0x1ae01ad,  0x1b001af,  0x1b201b1,  0x1b401b3,  0x1b601b5,  0x1b801b7,  0x1ba01b9,  0x1bc01bb,  0x1be01bd,  0x1c001bf,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0xffff01cf,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x41dffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1d101d0,  0x1d301d2,  0x1d501d4,  0x1d701d6,  0x1d901d8,  0x1db01da,  0x1dd01dc,  0x1df01de,  0x1e101e0,  0x1e301e2,  0x1e501e4,  0x1e701e6,  0x1e901e8,  0x1eb01ea,  0x1ed01ec,  0x1ef01ee,  0x1f101f0,  0x1f301f2,  0x1f501f4,  0x1f6ffff,  0xffffffff,  0xffffffff,  0x1f7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff01f8,  0xffff01f9,  0xffff01fa,  0xffff01fb,  0xffff01fc,  0xffff01fd,  0xffff01fe,  0xffff01ff,  0xffff0200,  0xffff0201,  0xffff0202,  0xffff0203,  0xffff0204,  0xffff0205,  0xffff0206,  0xffff0207,  0xffff0208,  0xffff0209,  0xffff020a,  0xffff020b,  0xffff020c,  0xffff020d,  0xffff020e,  0xffff020f,  0xffff0210,  0xffff0211,  0xffff0212,  0xffff0213,  0xffff0214,  0xffff0215,  0xffff0216,  0xffff0217,  0xffff0218,  0xffff0219,  0xffff021a,  0xffff021b,  0xffff021c,  0xffff021d,  0xffff021e,  0xffff021f,  0xffff0220,  0xffff0221,  0xffff0222,  0xffff0223,  0xffff0224,  0xffff0225,  0xffff0226,  0xffff0227,  0xffff0228,  0xffff0229,  0xffff022a,  0xffff022b,  0xffff022c,  0xffff022d,  0xffff022e,  0xffff022f,  0xffff0230,  0xffff0231,  0xffff0232,  0xffff0233,  0xffff0234,  0xffff0235,  0xffff0236,  0xffff0237,  0xffff0238,  0xffff0239,  0xffff023a,  0xffff023b,  0xffff023c,  0xffff023d,  0xffff023e,  0xffff023f,  0xffff0240,  0xffff0241,  0xffff0242,  0x4280427,  0x42a0429,  0xffff042b,  0xffffffff,  0xffff0243,  0xffff0244,  0xffff0245,  0xffff0246,  0xffff0247,  0xffff0248,  0xffff0249,  0xffff024a,  0xffff024b,  0xffff024c,  0xffff024d,  0xffff024e,  0xffff024f,  0xffff0250,  0xffff0251,  0xffff0252,  0xffff0253,  0xffff0254,  0xffff0255,  0xffff0256,  0xffff0257,  0xffff0258,  0xffff0259,  0xffff025a,  0xffff025b,  0xffff025c,  0xffff025d,  0xffff025e,  0xffff025f,  0xffff0260,  0xffff0261,  0xffff0262,  0xffff0263,  0xffff0264,  0xffff0265,  0xffff0266,  0xffff0267,  0xffff0268,  0xffff0269,  0xffff026a,  0xffff026b,  0xffff026c,  0xffff026d,  0xffff026e,  0xffff026f,  0xffff0270,  0xffff0271,  0xffff0272,  0xffff0273,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2750274,  0x2770276,  0x2790278,  0x27b027a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27d027c,  0x27f027e,  0x2810280,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2830282,  0x2850284,  0x2870286,  0x2890288,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x28b028a,  0x28d028c,  0x28f028e,  0x2910290,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2930292,  0x2950294,  0x2970296,  0xffffffff,  0xffff042c,  0xffff042d,  0xffff042e,  0xffff042f,  0x298ffff,  0x299ffff,  0x29affff,  0x29bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x29d029c,  0x29f029e,  0x2a102a0,  0x2a302a2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x43d043c,  0x43f043e,  0x4410440,  0x4430442,  0x4450444,  0x4470446,  0x4490448,  0x44b044a,  0x44d044c,  0x44f044e,  0x4510450,  0x4530452,  0x4550454,  0x4570456,  0x4590458,  0x45b045a,  0x45d045c,  0x45f045e,  0x4610460,  0x4630462,  0x4650464,  0x4670466,  0x4690468,  0x46b046a,  0xffffffff,  0x46c0472,  0xffff0473,  0x4780430,  0x2bd02bc,  0x2bf02be,  0xffff046d,  0xffffffff,  0xffffffff,  0x46e0474,  0xffff0475,  0x4790431,  0x2c202c1,  0x2c402c3,  0xffff046f,  0xffffffff,  0xffffffff,  0x4330432,  0xffffffff,  0x4350434,  0x2c702c6,  0x2c902c8,  0xffffffff,  0xffffffff,  0xffffffff,  0x4370436,  0xffff0438,  0x43a0439,  0x2cb02ca,  0x2cd02cc,  0xffff02ce,  0xffffffff,  0xffffffff,  0x4700476,  0xffff0477,  0x47a043b,  0x2d002cf,  0x2d202d1,  0xffff0471,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02d4,  0xffffffff,  0x2d602d5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02d7,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d902d8,  0x2db02da,  0x2dd02dc,  0x2df02de,  0x2e102e0,  0x2e302e2,  0x2e502e4,  0x2e702e6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2e8ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ea02e9,  0x2ec02eb,  0x2ee02ed,  0x2f002ef,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0x31c031b,  0x31e031d,  0x320031f,  0x3220321,  0x3240323,  0x3260325,  0x3280327,  0x32a0329,  0x32c032b,  0x32e032d,  0x330032f,  0xffff0331,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0332,  0x3340333,  0xffff0335,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33b033a,  0xffff033c,  0xffff033d,  0x33effff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x340033f,  0xffff0341,  0xffff0342,  0xffff0343,  0xffff0344,  0xffff0345,  0xffff0346,  0xffff0347,  0xffff0348,  0xffff0349,  0xffff034a,  0xffff034b,  0xffff034c,  0xffff034d,  0xffff034e,  0xffff034f,  0xffff0350,  0xffff0351,  0xffff0352,  0xffff0353,  0xffff0354,  0xffff0355,  0xffff0356,  0xffff0357,  0xffff0358,  0xffff0359,  0xffff035a,  0xffff035b,  0xffff035c,  0xffff035d,  0xffff035e,  0xffff035f,  0xffff0360,  0xffff0361,  0xffff0362,  0xffff0363,  0xffff0364,  0xffff0365,  0xffff0366,  0xffff0367,  0xffff0368,  0xffff0369,  0xffff036a,  0xffff036b,  0xffff036c,  0xffff036d,  0xffff036e,  0xffff036f,  0xffff0370,  0xffff0371,  0xffff0372,  0xffffffff,  0xffffffff,  0xffffffff,  0x373ffff,  0x374ffff,  0xffffffff,  0xffffffff,  0xffff0375,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0376,  0xffff0377,  0xffff0378,  0xffff0379,  0xffff037a,  0xffff037b,  0xffff037c,  0xffff037d,  0xffff037e,  0xffff037f,  0xffff0380,  0xffff0381,  0xffff0382,  0xffff0383,  0xffff0384,  0xffff0385,  0xffff0386,  0xffff0387,  0xffff0388,  0xffff0389,  0xffff038a,  0xffff038b,  0xffff038c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff038d,  0xffff038e,  0xffff038f,  0xffff0390,  0xffff0391,  0xffff0392,  0xffff0393,  0xffff0394,  0xffff0395,  0xffff0396,  0xffff0397,  0xffff0398,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0399,  0xffff039a,  0xffff039b,  0xffff039c,  0xffff039d,  0xffff039e,  0xffff039f,  0xffffffff,  0xffff03a0,  0xffff03a1,  0xffff03a2,  0xffff03a3,  0xffff03a4,  0xffff03a5,  0xffff03a6,  0xffff03a7,  0xffff03a8,  0xffff03a9,  0xffff03aa,  0xffff03ab,  0xffff03ac,  0xffff03ad,  0xffff03ae,  0xffff03af,  0xffff03b0,  0xffff03b1,  0xffff03b2,  0xffff03b3,  0xffff03b4,  0xffff03b5,  0xffff03b6,  0xffff03b7,  0xffff03b8,  0xffff03b9,  0xffff03ba,  0xffff03bb,  0xffff03bc,  0xffff03bd,  0xffff03be,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0xffff03c2,  0xffff03c3,  0xffff03c4,  0xffff03c5,  0xffff03c6,  0xffffffff,  0x3c7ffff,  0x3c8ffff,  0xffffffff,  0xffff03c9,  0xffff03ca,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03cb,  0xffff03cc,  0xffff03cd,  0xffff03ce,  0xffff03cf,  0xffff03d0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4170416,  0x4190418,  0x41b041a,  0xffff041c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x41effff,  0x420041f,  0x4220421,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d1ffff,  0x3d303d2,  0x3d503d4,  0x3d703d6,  0x3d903d8,  0x3db03da,  0x3dd03dc,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0xffff03ea,  0xffffffff,  0xffffffff,  0x3ec03eb,  0x3ee03ed,  0x3f003ef,  0x3f203f1,  0x3f403f3,  0x3f603f5,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//8192 bytes
+enum toTitleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xc00], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x0,  0x170016,  0x190018,  0x1b001a,  0x1d001c,  0x0,  0x0,  0x1e0000,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x220021,  0x240023,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260000,  0x27,  0x290028,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2c0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2e002d,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x41fffff,  0x1c001b,  0x1e001d,  0x20001f,  0x220021,  0x240023,  0x260025,  0x280027,  0x2a0029,  0x2c002b,  0x2e002d,  0x30002f,  0xffff0031,  0x330032,  0x350034,  0x370036,  0x390038,  0x3affff,  0x3bffff,  0x3cffff,  0x3dffff,  0x3effff,  0x3fffff,  0x40ffff,  0x41ffff,  0x42ffff,  0x43ffff,  0x44ffff,  0x45ffff,  0x46ffff,  0x47ffff,  0x48ffff,  0x49ffff,  0x4affff,  0x4bffff,  0x4cffff,  0x4dffff,  0x4effff,  0x4fffff,  0x50ffff,  0x51ffff,  0x520421,  0x53ffff,  0x54ffff,  0x55ffff,  0xffffffff,  0xffff0056,  0xffff0057,  0xffff0058,  0xffff0059,  0xffff005a,  0xffff005b,  0xffff005c,  0x43e005d,  0x5effff,  0x5fffff,  0x60ffff,  0x61ffff,  0x62ffff,  0x63ffff,  0x64ffff,  0x65ffff,  0x66ffff,  0x67ffff,  0x68ffff,  0x69ffff,  0x6affff,  0x6bffff,  0x6cffff,  0x6dffff,  0x6effff,  0x6fffff,  0x70ffff,  0x71ffff,  0x72ffff,  0x73ffff,  0x74ffff,  0xffffffff,  0xffff0075,  0xffff0076,  0x780077,  0xffff0079,  0x7affff,  0x7bffff,  0xffffffff,  0xffff007c,  0xffffffff,  0xffff007d,  0xffffffff,  0xffffffff,  0xffff007e,  0x7fffff,  0xffffffff,  0x80ffff,  0xffff0081,  0xffffffff,  0xffff0082,  0x83ffff,  0x84ffff,  0x85ffff,  0xffffffff,  0xffff0086,  0xffffffff,  0x87ffff,  0xffffffff,  0xffff0088,  0xffffffff,  0xffff0089,  0xffff008a,  0x8bffff,  0xffffffff,  0x8cffff,  0x8dffff,  0xffffffff,  0xffffffff,  0x8f008e,  0x910090,  0x930092,  0x950094,  0xffff0096,  0xffff0097,  0xffff0098,  0xffff0099,  0xffff009a,  0xffff009b,  0xffff009c,  0xffff009d,  0x9f009e,  0xa0ffff,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xa6ffff,  0xa7ffff,  0xa8ffff,  0xa90446,  0xab00aa,  0xacffff,  0xffffffff,  0xadffff,  0xaeffff,  0xafffff,  0xb0ffff,  0xb1ffff,  0xb2ffff,  0xb3ffff,  0xb4ffff,  0xb5ffff,  0xb6ffff,  0xb7ffff,  0xb8ffff,  0xb9ffff,  0xbaffff,  0xbbffff,  0xbcffff,  0xbdffff,  0xbeffff,  0xbfffff,  0xc0ffff,  0xffffffff,  0xc1ffff,  0xc2ffff,  0xc3ffff,  0xc4ffff,  0xc5ffff,  0xc6ffff,  0xc7ffff,  0xc8ffff,  0xc9ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00ca,  0xcbffff,  0xffff00cc,  0xffff00cd,  0xffffffff,  0xceffff,  0xcfffff,  0xd0ffff,  0xd1ffff,  0xd2ffff,  0xd400d3,  0xd600d5,  0xffff00d7,  0xd900d8,  0xdaffff,  0xdbffff,  0xffffffff,  0xffffffff,  0xffff00dc,  0xddffff,  0xdeffff,  0xffff00df,  0xe100e0,  0xe2ffff,  0xffffffff,  0xe3ffff,  0xe4ffff,  0xffff00e5,  0xe6ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xe7ffff,  0xffffffff,  0xffff00e8,  0xe9ffff,  0xffffffff,  0xffffffff,  0xeb00ea,  0xed00ec,  0xffff00ee,  0xffffffff,  0xffffffff,  0xffff00ef,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf0ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf1ffff,  0xf2ffff,  0xffffffff,  0xf3ffff,  0xffffffff,  0xf4ffff,  0xf600f5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0440,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf800f7,  0xfa00f9,  0xfb0443,  0xfd00fc,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0x1070106,  0x1090108,  0x10b010a,  0x10d010c,  0x10f010e,  0x1110110,  0x1130112,  0x1150114,  0x1170116,  0xffff0118,  0x11a0119,  0xffffffff,  0x11bffff,  0x11d011c,  0x11effff,  0x11fffff,  0x120ffff,  0x121ffff,  0x122ffff,  0x123ffff,  0x124ffff,  0x125ffff,  0x126ffff,  0x127ffff,  0x128ffff,  0x129ffff,  0x12b012a,  0xffff012c,  0x12dffff,  0xffffffff,  0xffff012e,  0x12fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0x14b014a,  0x14d014c,  0x14f014e,  0x1510150,  0x1530152,  0x1550154,  0x1570156,  0x1590158,  0x15b015a,  0x15d015c,  0x15f015e,  0x160ffff,  0x161ffff,  0x162ffff,  0x163ffff,  0x164ffff,  0x165ffff,  0x166ffff,  0x167ffff,  0x168ffff,  0x169ffff,  0x16affff,  0x16bffff,  0x16cffff,  0x16dffff,  0x16effff,  0x16fffff,  0x170ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x171ffff,  0x172ffff,  0x173ffff,  0x174ffff,  0x175ffff,  0x176ffff,  0x177ffff,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0x17effff,  0x17fffff,  0x180ffff,  0x181ffff,  0x182ffff,  0x183ffff,  0x184ffff,  0x185ffff,  0x186ffff,  0x187ffff,  0x188ffff,  0x189ffff,  0x18affff,  0x18bffff,  0xffffffff,  0xffff018c,  0xffff018d,  0xffff018e,  0xffff018f,  0xffff0190,  0xffff0191,  0x1930192,  0x194ffff,  0x195ffff,  0x196ffff,  0x197ffff,  0x198ffff,  0x199ffff,  0x19affff,  0x19bffff,  0x19cffff,  0x19dffff,  0x19effff,  0x19fffff,  0x1a0ffff,  0x1a1ffff,  0x1a2ffff,  0x1a3ffff,  0x1a4ffff,  0x1a5ffff,  0x1a6ffff,  0x1a7ffff,  0x1a8ffff,  0x1a9ffff,  0x1aaffff,  0x1abffff,  0x1acffff,  0x1adffff,  0x1aeffff,  0x1afffff,  0x1b0ffff,  0x1b1ffff,  0x1b2ffff,  0x1b3ffff,  0x1b4ffff,  0x1b5ffff,  0x1b6ffff,  0x1b7ffff,  0x1b8ffff,  0x1b9ffff,  0x1baffff,  0x1bbffff,  0x1bcffff,  0x1bdffff,  0x1beffff,  0x1bfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1c0ffff,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0x1d001cf,  0x1d201d1,  0x1d401d3,  0x1d601d5,  0x1d801d7,  0x1da01d9,  0x1dc01db,  0x1de01dd,  0x1e001df,  0x1e201e1,  0x1e401e3,  0x43201e5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1e6ffff,  0xffffffff,  0x1e7ffff,  0xffffffff,  0x1e8ffff,  0x1e9ffff,  0x1eaffff,  0x1ebffff,  0x1ecffff,  0x1edffff,  0x1eeffff,  0x1efffff,  0x1f0ffff,  0x1f1ffff,  0x1f2ffff,  0x1f3ffff,  0x1f4ffff,  0x1f5ffff,  0x1f6ffff,  0x1f7ffff,  0x1f8ffff,  0x1f9ffff,  0x1faffff,  0x1fbffff,  0x1fcffff,  0x1fdffff,  0x1feffff,  0x1ffffff,  0x200ffff,  0x201ffff,  0x202ffff,  0x203ffff,  0x204ffff,  0x205ffff,  0x206ffff,  0x207ffff,  0x208ffff,  0x209ffff,  0x20affff,  0x20bffff,  0x20cffff,  0x20dffff,  0x20effff,  0x20fffff,  0x210ffff,  0x211ffff,  0x212ffff,  0x213ffff,  0x214ffff,  0x215ffff,  0x216ffff,  0x217ffff,  0x218ffff,  0x219ffff,  0x21affff,  0x21bffff,  0x21cffff,  0x21dffff,  0x21effff,  0x21fffff,  0x220ffff,  0x221ffff,  0x222ffff,  0x223ffff,  0x224ffff,  0x225ffff,  0x226ffff,  0x227ffff,  0x228ffff,  0x229ffff,  0x22affff,  0x22bffff,  0x22cffff,  0x22dffff,  0x22effff,  0x22fffff,  0x230ffff,  0x231ffff,  0x232ffff,  0x44a0448,  0x44e044c,  0x2330450,  0xffffffff,  0xffffffff,  0x234ffff,  0x235ffff,  0x236ffff,  0x237ffff,  0x238ffff,  0x239ffff,  0x23affff,  0x23bffff,  0x23cffff,  0x23dffff,  0x23effff,  0x23fffff,  0x240ffff,  0x241ffff,  0x242ffff,  0x243ffff,  0x244ffff,  0x245ffff,  0x246ffff,  0x247ffff,  0x248ffff,  0x249ffff,  0x24affff,  0x24bffff,  0x24cffff,  0x24dffff,  0x24effff,  0x24fffff,  0x250ffff,  0x251ffff,  0x252ffff,  0x253ffff,  0x254ffff,  0x255ffff,  0x256ffff,  0x257ffff,  0x258ffff,  0x259ffff,  0x25affff,  0x25bffff,  0x25cffff,  0x25dffff,  0x25effff,  0x25fffff,  0x260ffff,  0x261ffff,  0x262ffff,  0x263ffff,  0x2650264,  0x2670266,  0x2690268,  0x26b026a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x26d026c,  0x26f026e,  0x2710270,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2730272,  0x2750274,  0x2770276,  0x2790278,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27b027a,  0x27d027c,  0x27f027e,  0x2810280,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2830282,  0x2850284,  0x2870286,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2880452,  0x2890454,  0x28a0457,  0x28b045a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x28d028c,  0x28f028e,  0x2910290,  0x2930292,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2950294,  0x2970296,  0x2990298,  0x29b029a,  0x29d029c,  0x29f029e,  0x2a102a0,  0xffffffff,  0x47c047b,  0x47e047d,  0x480047f,  0x4820481,  0x4840483,  0x4860485,  0x4880487,  0x48a0489,  0x48c048b,  0x48e048d,  0x490048f,  0x4920491,  0x4940493,  0x4960495,  0x4980497,  0x49a0499,  0x49c049b,  0x49e049d,  0x4a0049f,  0x4a204a1,  0x4a404a3,  0x4a604a5,  0x4a804a7,  0x4aa04a9,  0x2bb02ba,  0x4ab04b1,  0xffff04b3,  0x4bd045d,  0xffffffff,  0xffffffff,  0xffff04ac,  0xffff02bd,  0xffffffff,  0x4ad04b5,  0xffff04b7,  0x4c0045f,  0xffffffff,  0xffffffff,  0xffff04ae,  0xffffffff,  0x2c002bf,  0x4640461,  0xffffffff,  0x4690467,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c202c1,  0x46f046c,  0x2c30472,  0x4760474,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4af04b9,  0xffff04bb,  0x4c30479,  0xffffffff,  0xffffffff,  0xffff04b0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02c5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c702c6,  0x2c902c8,  0x2cb02ca,  0x2cd02cc,  0x2cf02ce,  0x2d102d0,  0x2d302d2,  0x2d502d4,  0xffffffff,  0xffffffff,  0xffff02d6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d802d7,  0x2da02d9,  0x2dc02db,  0x2de02dd,  0x2e002df,  0x2e202e1,  0x2e402e3,  0x2e602e5,  0x2e802e7,  0x2ea02e9,  0x2ec02eb,  0x2ee02ed,  0x2f002ef,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0x31c031b,  0x31e031d,  0xffff031f,  0x320ffff,  0xffffffff,  0x321ffff,  0xffff0322,  0xffff0323,  0xffff0324,  0xffff0325,  0xffffffff,  0xffffffff,  0x326ffff,  0xffffffff,  0xffff0327,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x328ffff,  0x329ffff,  0x32affff,  0x32bffff,  0x32cffff,  0x32dffff,  0x32effff,  0x32fffff,  0x330ffff,  0x331ffff,  0x332ffff,  0x333ffff,  0x334ffff,  0x335ffff,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33affff,  0x33bffff,  0x33cffff,  0x33dffff,  0x33effff,  0x33fffff,  0x340ffff,  0x341ffff,  0x342ffff,  0x343ffff,  0x344ffff,  0x345ffff,  0x346ffff,  0x347ffff,  0x348ffff,  0x349ffff,  0x34affff,  0x34bffff,  0x34cffff,  0x34dffff,  0x34effff,  0x34fffff,  0x350ffff,  0x351ffff,  0x352ffff,  0x353ffff,  0x354ffff,  0x355ffff,  0x356ffff,  0x357ffff,  0x358ffff,  0x359ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff035a,  0xffff035b,  0xffffffff,  0x35cffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x35e035d,  0x360035f,  0x3620361,  0x3640363,  0x3660365,  0x3680367,  0x36a0369,  0x36c036b,  0x36e036d,  0x370036f,  0x3720371,  0x3740373,  0x3760375,  0x3780377,  0x37a0379,  0x37c037b,  0x37e037d,  0x380037f,  0x3820381,  0x383ffff,  0xffffffff,  0xffffffff,  0x384ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x385ffff,  0x386ffff,  0x387ffff,  0x388ffff,  0x389ffff,  0x38affff,  0x38bffff,  0x38cffff,  0x38dffff,  0x38effff,  0x38fffff,  0x390ffff,  0x391ffff,  0x392ffff,  0x393ffff,  0x394ffff,  0x395ffff,  0x396ffff,  0x397ffff,  0x398ffff,  0x399ffff,  0x39affff,  0x39bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x39cffff,  0x39dffff,  0x39effff,  0x39fffff,  0x3a0ffff,  0x3a1ffff,  0x3a2ffff,  0x3a3ffff,  0x3a4ffff,  0x3a5ffff,  0x3a6ffff,  0x3a7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3a8ffff,  0x3a9ffff,  0x3aaffff,  0x3abffff,  0x3acffff,  0x3adffff,  0x3aeffff,  0xffffffff,  0x3afffff,  0x3b0ffff,  0x3b1ffff,  0x3b2ffff,  0x3b3ffff,  0x3b4ffff,  0x3b5ffff,  0x3b6ffff,  0x3b7ffff,  0x3b8ffff,  0x3b9ffff,  0x3baffff,  0x3bbffff,  0x3bcffff,  0x3bdffff,  0x3beffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0x3c2ffff,  0x3c3ffff,  0x3c4ffff,  0x3c5ffff,  0x3c6ffff,  0x3c7ffff,  0x3c8ffff,  0x3c9ffff,  0x3caffff,  0x3cbffff,  0x3ccffff,  0x3cdffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03ce,  0xffff03cf,  0x3d0ffff,  0x3d1ffff,  0x3d2ffff,  0x3d3ffff,  0x3d4ffff,  0xffffffff,  0xffffffff,  0xffff03d5,  0xffffffff,  0x3d6ffff,  0x3d7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d8ffff,  0x3d9ffff,  0x3daffff,  0x3dbffff,  0x3dcffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x4240422,  0x4280426,  0x42e042b,  0xffff0430,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x434ffff,  0x4380436,  0x43c043a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3ddffff,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0x3eb03ea,  0x3ed03ec,  0x3ef03ee,  0x3f103f0,  0x3f303f2,  0x3f503f4,  0xffff03f6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0x4140413,  0x4160415,  0x4180417,  0x41a0419,  0x41c041b,  0x41e041d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//8064 bytes
+enum toUpperSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xbc0], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x0,  0x170016,  0x190018,  0x1b001a,  0x1d001c,  0x0,  0x0,  0x1e0000,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x220021,  0x240023,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260000,  0x27,  0x290028,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1c001b,  0x1e001d,  0x20001f,  0x220021,  0x240023,  0x260025,  0x280027,  0x2a0029,  0x2c002b,  0x2e002d,  0x30002f,  0xffff0031,  0x330032,  0x350034,  0x370036,  0x390038,  0x3affff,  0x3bffff,  0x3cffff,  0x3dffff,  0x3effff,  0x3fffff,  0x40ffff,  0x41ffff,  0x42ffff,  0x43ffff,  0x44ffff,  0x45ffff,  0x46ffff,  0x47ffff,  0x48ffff,  0x49ffff,  0x4affff,  0x4bffff,  0x4cffff,  0x4dffff,  0x4effff,  0x4fffff,  0x50ffff,  0x51ffff,  0x52ffff,  0x53ffff,  0x54ffff,  0x55ffff,  0xffffffff,  0xffff0056,  0xffff0057,  0xffff0058,  0xffff0059,  0xffff005a,  0xffff005b,  0xffff005c,  0xffff005d,  0x5effff,  0x5fffff,  0x60ffff,  0x61ffff,  0x62ffff,  0x63ffff,  0x64ffff,  0x65ffff,  0x66ffff,  0x67ffff,  0x68ffff,  0x69ffff,  0x6affff,  0x6bffff,  0x6cffff,  0x6dffff,  0x6effff,  0x6fffff,  0x70ffff,  0x71ffff,  0x72ffff,  0x73ffff,  0x74ffff,  0xffffffff,  0xffff0075,  0xffff0076,  0x780077,  0xffff0079,  0x7affff,  0x7bffff,  0xffffffff,  0xffff007c,  0xffffffff,  0xffff007d,  0xffffffff,  0xffffffff,  0xffff007e,  0x7fffff,  0xffffffff,  0x80ffff,  0xffff0081,  0xffffffff,  0xffff0082,  0x83ffff,  0x84ffff,  0x85ffff,  0xffffffff,  0xffff0086,  0xffffffff,  0x87ffff,  0xffffffff,  0xffff0088,  0xffffffff,  0xffff0089,  0xffff008a,  0x8bffff,  0xffffffff,  0x8cffff,  0x8dffff,  0xffffffff,  0xffffffff,  0x8effff,  0xffff008f,  0x910090,  0x92ffff,  0xffff0093,  0xffff0094,  0xffff0095,  0xffff0096,  0xffff0097,  0xffff0098,  0xffff0099,  0xffff009a,  0x9c009b,  0x9dffff,  0x9effff,  0x9fffff,  0xa0ffff,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xffffffff,  0xa700a6,  0xa8ffff,  0xffffffff,  0xa9ffff,  0xaaffff,  0xabffff,  0xacffff,  0xadffff,  0xaeffff,  0xafffff,  0xb0ffff,  0xb1ffff,  0xb2ffff,  0xb3ffff,  0xb4ffff,  0xb5ffff,  0xb6ffff,  0xb7ffff,  0xb8ffff,  0xb9ffff,  0xbaffff,  0xbbffff,  0xbcffff,  0xffffffff,  0xbdffff,  0xbeffff,  0xbfffff,  0xc0ffff,  0xc1ffff,  0xc2ffff,  0xc3ffff,  0xc4ffff,  0xc5ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00c6,  0xc7ffff,  0xffff00c8,  0xffff00c9,  0xffffffff,  0xcaffff,  0xcbffff,  0xccffff,  0xcdffff,  0xceffff,  0xd000cf,  0xd200d1,  0xffff00d3,  0xd500d4,  0xd6ffff,  0xd7ffff,  0xffffffff,  0xffffffff,  0xffff00d8,  0xd9ffff,  0xdaffff,  0xffff00db,  0xdd00dc,  0xdeffff,  0xffffffff,  0xdfffff,  0xe0ffff,  0xffff00e1,  0xe2ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xe3ffff,  0xffffffff,  0xffff00e4,  0xe5ffff,  0xffffffff,  0xffffffff,  0xe700e6,  0xe900e8,  0xffff00ea,  0xffffffff,  0xffffffff,  0xffff00eb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xecffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xedffff,  0xeeffff,  0xffffffff,  0xefffff,  0xffffffff,  0xf0ffff,  0xf200f1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf400f3,  0xf600f5,  0xf7ffff,  0xf900f8,  0xfb00fa,  0xfd00fc,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0x1070106,  0x1090108,  0x10b010a,  0x10d010c,  0x10f010e,  0x1110110,  0x1130112,  0xffff0114,  0x1160115,  0xffffffff,  0x117ffff,  0x1190118,  0x11affff,  0x11bffff,  0x11cffff,  0x11dffff,  0x11effff,  0x11fffff,  0x120ffff,  0x121ffff,  0x122ffff,  0x123ffff,  0x124ffff,  0x125ffff,  0x1270126,  0xffff0128,  0x129ffff,  0xffffffff,  0xffff012a,  0x12bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x12d012c,  0x12f012e,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0x14b014a,  0x14d014c,  0x14f014e,  0x1510150,  0x1530152,  0x1550154,  0x1570156,  0x1590158,  0x15b015a,  0x15cffff,  0x15dffff,  0x15effff,  0x15fffff,  0x160ffff,  0x161ffff,  0x162ffff,  0x163ffff,  0x164ffff,  0x165ffff,  0x166ffff,  0x167ffff,  0x168ffff,  0x169ffff,  0x16affff,  0x16bffff,  0x16cffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x16dffff,  0x16effff,  0x16fffff,  0x170ffff,  0x171ffff,  0x172ffff,  0x173ffff,  0x174ffff,  0x175ffff,  0x176ffff,  0x177ffff,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0x17effff,  0x17fffff,  0x180ffff,  0x181ffff,  0x182ffff,  0x183ffff,  0x184ffff,  0x185ffff,  0x186ffff,  0x187ffff,  0xffffffff,  0xffff0188,  0xffff0189,  0xffff018a,  0xffff018b,  0xffff018c,  0xffff018d,  0x18f018e,  0x190ffff,  0x191ffff,  0x192ffff,  0x193ffff,  0x194ffff,  0x195ffff,  0x196ffff,  0x197ffff,  0x198ffff,  0x199ffff,  0x19affff,  0x19bffff,  0x19cffff,  0x19dffff,  0x19effff,  0x19fffff,  0x1a0ffff,  0x1a1ffff,  0x1a2ffff,  0x1a3ffff,  0x1a4ffff,  0x1a5ffff,  0x1a6ffff,  0x1a7ffff,  0x1a8ffff,  0x1a9ffff,  0x1aaffff,  0x1abffff,  0x1acffff,  0x1adffff,  0x1aeffff,  0x1afffff,  0x1b0ffff,  0x1b1ffff,  0x1b2ffff,  0x1b3ffff,  0x1b4ffff,  0x1b5ffff,  0x1b6ffff,  0x1b7ffff,  0x1b8ffff,  0x1b9ffff,  0x1baffff,  0x1bbffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1bcffff,  0x1be01bd,  0x1c001bf,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0x1d001cf,  0x1d201d1,  0x1d401d3,  0x1d601d5,  0x1d801d7,  0x1da01d9,  0x1dc01db,  0x1de01dd,  0x1e001df,  0xffff01e1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1e2ffff,  0xffffffff,  0x1e3ffff,  0xffffffff,  0x1e4ffff,  0x1e5ffff,  0x1e6ffff,  0x1e7ffff,  0x1e8ffff,  0x1e9ffff,  0x1eaffff,  0x1ebffff,  0x1ecffff,  0x1edffff,  0x1eeffff,  0x1efffff,  0x1f0ffff,  0x1f1ffff,  0x1f2ffff,  0x1f3ffff,  0x1f4ffff,  0x1f5ffff,  0x1f6ffff,  0x1f7ffff,  0x1f8ffff,  0x1f9ffff,  0x1faffff,  0x1fbffff,  0x1fcffff,  0x1fdffff,  0x1feffff,  0x1ffffff,  0x200ffff,  0x201ffff,  0x202ffff,  0x203ffff,  0x204ffff,  0x205ffff,  0x206ffff,  0x207ffff,  0x208ffff,  0x209ffff,  0x20affff,  0x20bffff,  0x20cffff,  0x20dffff,  0x20effff,  0x20fffff,  0x210ffff,  0x211ffff,  0x212ffff,  0x213ffff,  0x214ffff,  0x215ffff,  0x216ffff,  0x217ffff,  0x218ffff,  0x219ffff,  0x21affff,  0x21bffff,  0x21cffff,  0x21dffff,  0x21effff,  0x21fffff,  0x220ffff,  0x221ffff,  0x222ffff,  0x223ffff,  0x224ffff,  0x225ffff,  0x226ffff,  0x227ffff,  0x228ffff,  0x229ffff,  0x22affff,  0x22bffff,  0x22cffff,  0x22dffff,  0x22effff,  0xffffffff,  0xffffffff,  0x22fffff,  0xffffffff,  0xffffffff,  0x230ffff,  0x231ffff,  0x232ffff,  0x233ffff,  0x234ffff,  0x235ffff,  0x236ffff,  0x237ffff,  0x238ffff,  0x239ffff,  0x23affff,  0x23bffff,  0x23cffff,  0x23dffff,  0x23effff,  0x23fffff,  0x240ffff,  0x241ffff,  0x242ffff,  0x243ffff,  0x244ffff,  0x245ffff,  0x246ffff,  0x247ffff,  0x248ffff,  0x249ffff,  0x24affff,  0x24bffff,  0x24cffff,  0x24dffff,  0x24effff,  0x24fffff,  0x250ffff,  0x251ffff,  0x252ffff,  0x253ffff,  0x254ffff,  0x255ffff,  0x256ffff,  0x257ffff,  0x258ffff,  0x259ffff,  0x25affff,  0x25bffff,  0x25cffff,  0x25dffff,  0x25effff,  0x25fffff,  0x2610260,  0x2630262,  0x2650264,  0x2670266,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2690268,  0x26b026a,  0x26d026c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x26f026e,  0x2710270,  0x2730272,  0x2750274,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2770276,  0x2790278,  0x27b027a,  0x27d027c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27f027e,  0x2810280,  0x2830282,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x284ffff,  0x285ffff,  0x286ffff,  0x287ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2890288,  0x28b028a,  0x28d028c,  0x28f028e,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2910290,  0x2930292,  0x2950294,  0x2970296,  0x2990298,  0x29b029a,  0x29d029c,  0xffffffff,  0x29f029e,  0x2a102a0,  0x2a302a2,  0x2a502a4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2a702a6,  0x2a902a8,  0x2ab02aa,  0x2ad02ac,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2af02ae,  0x2b102b0,  0x2b302b2,  0x2b502b4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2b702b6,  0x2b8ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02b9,  0xffffffff,  0x2baffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2bc02bb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2be02bd,  0xffffffff,  0x2bfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c0ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02c1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c302c2,  0x2c502c4,  0x2c702c6,  0x2c902c8,  0x2cb02ca,  0x2cd02cc,  0x2cf02ce,  0x2d102d0,  0xffffffff,  0xffffffff,  0xffff02d2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d402d3,  0x2d602d5,  0x2d802d7,  0x2da02d9,  0x2dc02db,  0x2de02dd,  0x2e002df,  0x2e202e1,  0x2e402e3,  0x2e602e5,  0x2e802e7,  0x2ea02e9,  0x2ec02eb,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ee02ed,  0x2f002ef,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0xffff031b,  0x31cffff,  0xffffffff,  0x31dffff,  0xffff031e,  0xffff031f,  0xffff0320,  0xffff0321,  0xffffffff,  0xffffffff,  0x322ffff,  0xffffffff,  0xffff0323,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x324ffff,  0x325ffff,  0x326ffff,  0x327ffff,  0x328ffff,  0x329ffff,  0x32affff,  0x32bffff,  0x32cffff,  0x32dffff,  0x32effff,  0x32fffff,  0x330ffff,  0x331ffff,  0x332ffff,  0x333ffff,  0x334ffff,  0x335ffff,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33affff,  0x33bffff,  0x33cffff,  0x33dffff,  0x33effff,  0x33fffff,  0x340ffff,  0x341ffff,  0x342ffff,  0x343ffff,  0x344ffff,  0x345ffff,  0x346ffff,  0x347ffff,  0x348ffff,  0x349ffff,  0x34affff,  0x34bffff,  0x34cffff,  0x34dffff,  0x34effff,  0x34fffff,  0x350ffff,  0x351ffff,  0x352ffff,  0x353ffff,  0x354ffff,  0x355ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0356,  0xffff0357,  0xffffffff,  0x358ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x35a0359,  0x35c035b,  0x35e035d,  0x360035f,  0x3620361,  0x3640363,  0x3660365,  0x3680367,  0x36a0369,  0x36c036b,  0x36e036d,  0x370036f,  0x3720371,  0x3740373,  0x3760375,  0x3780377,  0x37a0379,  0x37c037b,  0x37e037d,  0x37fffff,  0xffffffff,  0xffffffff,  0x380ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x381ffff,  0x382ffff,  0x383ffff,  0x384ffff,  0x385ffff,  0x386ffff,  0x387ffff,  0x388ffff,  0x389ffff,  0x38affff,  0x38bffff,  0x38cffff,  0x38dffff,  0x38effff,  0x38fffff,  0x390ffff,  0x391ffff,  0x392ffff,  0x393ffff,  0x394ffff,  0x395ffff,  0x396ffff,  0x397ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x398ffff,  0x399ffff,  0x39affff,  0x39bffff,  0x39cffff,  0x39dffff,  0x39effff,  0x39fffff,  0x3a0ffff,  0x3a1ffff,  0x3a2ffff,  0x3a3ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3a4ffff,  0x3a5ffff,  0x3a6ffff,  0x3a7ffff,  0x3a8ffff,  0x3a9ffff,  0x3aaffff,  0xffffffff,  0x3abffff,  0x3acffff,  0x3adffff,  0x3aeffff,  0x3afffff,  0x3b0ffff,  0x3b1ffff,  0x3b2ffff,  0x3b3ffff,  0x3b4ffff,  0x3b5ffff,  0x3b6ffff,  0x3b7ffff,  0x3b8ffff,  0x3b9ffff,  0x3baffff,  0x3bbffff,  0x3bcffff,  0x3bdffff,  0x3beffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0x3c2ffff,  0x3c3ffff,  0x3c4ffff,  0x3c5ffff,  0x3c6ffff,  0x3c7ffff,  0x3c8ffff,  0x3c9ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03ca,  0xffff03cb,  0x3ccffff,  0x3cdffff,  0x3ceffff,  0x3cfffff,  0x3d0ffff,  0xffffffff,  0xffffffff,  0xffff03d1,  0xffffffff,  0x3d2ffff,  0x3d3ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d4ffff,  0x3d5ffff,  0x3d6ffff,  0x3d7ffff,  0x3d8ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d9ffff,  0x3db03da,  0x3dd03dc,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0x3eb03ea,  0x3ed03ec,  0x3ef03ee,  0x3f103f0,  0xffff03f2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3f403f3,  0x3f603f5,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0x4140413,  0x4160415,  0x4180417,  0x41a0419,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//7808 bytes
+enum toLowerSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xb40], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x20000,  0x40003,  0x60005,  0x80007,  0x0,  0x90000,  0xb000a,  0xd000c,  0xf000e,  0x110010,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x130012,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150014,  0x170016,  0x190018,  0x1b001a,  0x0,  0x0,  0x1d001c,  0x1e,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20001f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x220021,  0x240023,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x250000,  0x26,  0x280027,  0x29,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1b001a,  0x1d001c,  0x1f001e,  0x210020,  0x230022,  0x250024,  0x270026,  0x290028,  0x2b002a,  0x2d002c,  0x2f002e,  0xffff0030,  0x320031,  0x340033,  0x360035,  0xffff0037,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0038,  0xffff0039,  0xffff003a,  0xffff003b,  0xffff003c,  0xffff003d,  0xffff003e,  0xffff003f,  0xffff0040,  0xffff0041,  0xffff0042,  0xffff0043,  0xffff0044,  0xffff0045,  0xffff0046,  0xffff0047,  0xffff0048,  0xffff0049,  0xffff004a,  0xffff004b,  0xffff004c,  0xffff004d,  0xffff004e,  0xffff004f,  0xffff0050,  0xffff0051,  0xffff0052,  0xffff0053,  0x54ffff,  0x55ffff,  0x56ffff,  0x57ffff,  0x58ffff,  0x59ffff,  0x5affff,  0x5bffff,  0xffffffff,  0xffff005c,  0xffff005d,  0xffff005e,  0xffff005f,  0xffff0060,  0xffff0061,  0xffff0062,  0xffff0063,  0xffff0064,  0xffff0065,  0xffff0066,  0xffff0067,  0xffff0068,  0xffff0069,  0xffff006a,  0xffff006b,  0xffff006c,  0xffff006d,  0xffff006e,  0xffff006f,  0xffff0070,  0xffff0071,  0xffff0072,  0x740073,  0x75ffff,  0x76ffff,  0xffffffff,  0x77ffff,  0xffff0078,  0xffff0079,  0x7b007a,  0x7cffff,  0x7e007d,  0xffffffff,  0x80007f,  0x820081,  0x83ffff,  0xffff0084,  0x860085,  0xffff0087,  0xffffffff,  0x890088,  0x8affff,  0xffff008b,  0xffff008c,  0xffff008d,  0x8f008e,  0x90ffff,  0xffffffff,  0xffff0091,  0x930092,  0x94ffff,  0x960095,  0x97ffff,  0x98ffff,  0xffff0099,  0xffffffff,  0xffff009a,  0xffffffff,  0xffffffff,  0xffffffff,  0x9c009b,  0x9dffff,  0xffff009e,  0xa0009f,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xa6ffff,  0xa7ffff,  0xa8ffff,  0xffffffff,  0xffff00a9,  0xffff00aa,  0xffff00ab,  0xffff00ac,  0xffff00ad,  0xffff00ae,  0xffff00af,  0xffff00b0,  0xffff00b1,  0xb2ffff,  0xffff00b3,  0xffff00b4,  0xb600b5,  0xffff00b7,  0xffff00b8,  0xffff00b9,  0xffff00ba,  0xffff00bb,  0xffff00bc,  0xffff00bd,  0xffff00be,  0xffff00bf,  0xffff00c0,  0xffff00c1,  0xffff00c2,  0xffff00c3,  0xffff00c4,  0xffff00c5,  0xffff00c6,  0xffff00c7,  0xffff00c8,  0xffff00c9,  0xffff00ca,  0xffff00cb,  0xffff00cc,  0xffff00cd,  0xffff00ce,  0xffff00cf,  0xffff00d0,  0xffff00d1,  0xffff00d2,  0xffff00d3,  0xffff00d4,  0xffffffff,  0xffffffff,  0xffffffff,  0xd600d5,  0xd7ffff,  0xffff00d8,  0xd9ffff,  0xdaffff,  0xdc00db,  0xffff00dd,  0xffff00de,  0xffff00df,  0xffff00e0,  0xffff00e1,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00e2,  0xffff00e3,  0xffffffff,  0xffff00e4,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00e5,  0xe700e6,  0xffff00e8,  0xffff00e9,  0xeb00ea,  0xecffff,  0xee00ed,  0xf000ef,  0xf200f1,  0xf400f3,  0xf600f5,  0xf800f7,  0xfa00f9,  0xfc00fb,  0xfdffff,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x106ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0107,  0xffff0108,  0xffff0109,  0xffff010a,  0xffff010b,  0xffff010c,  0xffff010d,  0xffff010e,  0xffff010f,  0xffff0110,  0xffff0111,  0xffff0112,  0xffffffff,  0xffffffff,  0xffff0113,  0x114ffff,  0x115ffff,  0xffff0116,  0x117ffff,  0x1190118,  0x11b011a,  0x11d011c,  0x11f011e,  0x1210120,  0x1230122,  0x1250124,  0x1270126,  0x1290128,  0x12b012a,  0x12d012c,  0x12f012e,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff014a,  0xffff014b,  0xffff014c,  0xffff014d,  0xffff014e,  0xffff014f,  0xffff0150,  0xffff0151,  0xffff0152,  0xffff0153,  0xffff0154,  0xffff0155,  0xffff0156,  0xffff0157,  0xffff0158,  0xffff0159,  0xffff015a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff015b,  0xffff015c,  0xffff015d,  0xffff015e,  0xffff015f,  0xffff0160,  0xffff0161,  0xffff0162,  0xffff0163,  0xffff0164,  0xffff0165,  0xffff0166,  0xffff0167,  0xffff0168,  0xffff0169,  0xffff016a,  0xffff016b,  0xffff016c,  0xffff016d,  0xffff016e,  0xffff016f,  0xffff0170,  0xffff0171,  0xffff0172,  0xffff0173,  0xffff0174,  0xffff0175,  0x1770176,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0xffffffff,  0xffff017e,  0xffff017f,  0xffff0180,  0xffff0181,  0xffff0182,  0xffff0183,  0xffff0184,  0xffff0185,  0xffff0186,  0xffff0187,  0xffff0188,  0xffff0189,  0xffff018a,  0xffff018b,  0xffff018c,  0xffff018d,  0xffff018e,  0xffff018f,  0xffff0190,  0xffff0191,  0xffff0192,  0xffff0193,  0xffff0194,  0xffff0195,  0xffff0196,  0xffff0197,  0xffff0198,  0xffff0199,  0xffff019a,  0xffff019b,  0xffff019c,  0xffff019d,  0xffff019e,  0xffff019f,  0xffff01a0,  0xffff01a1,  0xffff01a2,  0xffff01a3,  0xffff01a4,  0xffff01a5,  0xffff01a6,  0xffff01a7,  0xffff01a8,  0xffff01a9,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1aaffff,  0x1ac01ab,  0x1ae01ad,  0x1b001af,  0x1b201b1,  0x1b401b3,  0x1b601b5,  0x1b801b7,  0x1ba01b9,  0x1bc01bb,  0x1be01bd,  0x1c001bf,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0xffff01cf,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1d101d0,  0x1d301d2,  0x1d501d4,  0x1d701d6,  0x1d901d8,  0x1db01da,  0x1dd01dc,  0x1df01de,  0x1e101e0,  0x1e301e2,  0x1e501e4,  0x1e701e6,  0x1e901e8,  0x1eb01ea,  0x1ed01ec,  0x1ef01ee,  0x1f101f0,  0x1f301f2,  0x1f501f4,  0x1f6ffff,  0xffffffff,  0xffffffff,  0x1f7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff01f8,  0xffff01f9,  0xffff01fa,  0xffff01fb,  0xffff01fc,  0xffff01fd,  0xffff01fe,  0xffff01ff,  0xffff0200,  0xffff0201,  0xffff0202,  0xffff0203,  0xffff0204,  0xffff0205,  0xffff0206,  0xffff0207,  0xffff0208,  0xffff0209,  0xffff020a,  0xffff020b,  0xffff020c,  0xffff020d,  0xffff020e,  0xffff020f,  0xffff0210,  0xffff0211,  0xffff0212,  0xffff0213,  0xffff0214,  0xffff0215,  0xffff0216,  0xffff0217,  0xffff0218,  0xffff0219,  0xffff021a,  0xffff021b,  0xffff021c,  0xffff021d,  0xffff021e,  0xffff021f,  0xffff0220,  0xffff0221,  0xffff0222,  0xffff0223,  0xffff0224,  0xffff0225,  0xffff0226,  0xffff0227,  0xffff0228,  0xffff0229,  0xffff022a,  0xffff022b,  0xffff022c,  0xffff022d,  0xffff022e,  0xffff022f,  0xffff0230,  0xffff0231,  0xffff0232,  0xffff0233,  0xffff0234,  0xffff0235,  0xffff0236,  0xffff0237,  0xffff0238,  0xffff0239,  0xffff023a,  0xffff023b,  0xffff023c,  0xffff023d,  0xffff023e,  0xffff023f,  0xffff0240,  0xffff0241,  0xffff0242,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0243,  0xffff0244,  0xffff0245,  0xffff0246,  0xffff0247,  0xffff0248,  0xffff0249,  0xffff024a,  0xffff024b,  0xffff024c,  0xffff024d,  0xffff024e,  0xffff024f,  0xffff0250,  0xffff0251,  0xffff0252,  0xffff0253,  0xffff0254,  0xffff0255,  0xffff0256,  0xffff0257,  0xffff0258,  0xffff0259,  0xffff025a,  0xffff025b,  0xffff025c,  0xffff025d,  0xffff025e,  0xffff025f,  0xffff0260,  0xffff0261,  0xffff0262,  0xffff0263,  0xffff0264,  0xffff0265,  0xffff0266,  0xffff0267,  0xffff0268,  0xffff0269,  0xffff026a,  0xffff026b,  0xffff026c,  0xffff026d,  0xffff026e,  0xffff026f,  0xffff0270,  0xffff0271,  0xffff0272,  0xffff0273,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2750274,  0x2770276,  0x2790278,  0x27b027a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27d027c,  0x27f027e,  0x2810280,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2830282,  0x2850284,  0x2870286,  0x2890288,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x28b028a,  0x28d028c,  0x28f028e,  0x2910290,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2930292,  0x2950294,  0x2970296,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x298ffff,  0x299ffff,  0x29affff,  0x29bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x29d029c,  0x29f029e,  0x2a102a0,  0x2a302a2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2a502a4,  0x2a702a6,  0x2a902a8,  0x2ab02aa,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ad02ac,  0x2af02ae,  0x2b102b0,  0x2b302b2,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2b502b4,  0x2b702b6,  0x2b902b8,  0x2bb02ba,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2bd02bc,  0x2bf02be,  0xffff02c0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c202c1,  0x2c402c3,  0xffff02c5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c702c6,  0x2c902c8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2cb02ca,  0x2cd02cc,  0xffff02ce,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d002cf,  0x2d202d1,  0xffff02d3,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02d4,  0xffffffff,  0x2d602d5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02d7,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d902d8,  0x2db02da,  0x2dd02dc,  0x2df02de,  0x2e102e0,  0x2e302e2,  0x2e502e4,  0x2e702e6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2e8ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ea02e9,  0x2ec02eb,  0x2ee02ed,  0x2f002ef,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0x31c031b,  0x31e031d,  0x320031f,  0x3220321,  0x3240323,  0x3260325,  0x3280327,  0x32a0329,  0x32c032b,  0x32e032d,  0x330032f,  0xffff0331,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0332,  0x3340333,  0xffff0335,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33b033a,  0xffff033c,  0xffff033d,  0x33effff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x340033f,  0xffff0341,  0xffff0342,  0xffff0343,  0xffff0344,  0xffff0345,  0xffff0346,  0xffff0347,  0xffff0348,  0xffff0349,  0xffff034a,  0xffff034b,  0xffff034c,  0xffff034d,  0xffff034e,  0xffff034f,  0xffff0350,  0xffff0351,  0xffff0352,  0xffff0353,  0xffff0354,  0xffff0355,  0xffff0356,  0xffff0357,  0xffff0358,  0xffff0359,  0xffff035a,  0xffff035b,  0xffff035c,  0xffff035d,  0xffff035e,  0xffff035f,  0xffff0360,  0xffff0361,  0xffff0362,  0xffff0363,  0xffff0364,  0xffff0365,  0xffff0366,  0xffff0367,  0xffff0368,  0xffff0369,  0xffff036a,  0xffff036b,  0xffff036c,  0xffff036d,  0xffff036e,  0xffff036f,  0xffff0370,  0xffff0371,  0xffff0372,  0xffffffff,  0xffffffff,  0xffffffff,  0x373ffff,  0x374ffff,  0xffffffff,  0xffffffff,  0xffff0375,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0376,  0xffff0377,  0xffff0378,  0xffff0379,  0xffff037a,  0xffff037b,  0xffff037c,  0xffff037d,  0xffff037e,  0xffff037f,  0xffff0380,  0xffff0381,  0xffff0382,  0xffff0383,  0xffff0384,  0xffff0385,  0xffff0386,  0xffff0387,  0xffff0388,  0xffff0389,  0xffff038a,  0xffff038b,  0xffff038c,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff038d,  0xffff038e,  0xffff038f,  0xffff0390,  0xffff0391,  0xffff0392,  0xffff0393,  0xffff0394,  0xffff0395,  0xffff0396,  0xffff0397,  0xffff0398,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff0399,  0xffff039a,  0xffff039b,  0xffff039c,  0xffff039d,  0xffff039e,  0xffff039f,  0xffffffff,  0xffff03a0,  0xffff03a1,  0xffff03a2,  0xffff03a3,  0xffff03a4,  0xffff03a5,  0xffff03a6,  0xffff03a7,  0xffff03a8,  0xffff03a9,  0xffff03aa,  0xffff03ab,  0xffff03ac,  0xffff03ad,  0xffff03ae,  0xffff03af,  0xffff03b0,  0xffff03b1,  0xffff03b2,  0xffff03b3,  0xffff03b4,  0xffff03b5,  0xffff03b6,  0xffff03b7,  0xffff03b8,  0xffff03b9,  0xffff03ba,  0xffff03bb,  0xffff03bc,  0xffff03bd,  0xffff03be,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0xffff03c2,  0xffff03c3,  0xffff03c4,  0xffff03c5,  0xffff03c6,  0xffffffff,  0x3c7ffff,  0x3c8ffff,  0xffffffff,  0xffff03c9,  0xffff03ca,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03cb,  0xffff03cc,  0xffff03cd,  0xffff03ce,  0xffff03cf,  0xffff03d0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d1ffff,  0x3d303d2,  0x3d503d4,  0x3d703d6,  0x3d903d8,  0x3db03da,  0x3dd03dc,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0xffff03ea,  0xffffffff,  0xffffffff,  0x3ec03eb,  0x3ee03ed,  0x3f003ef,  0x3f203f1,  0x3f403f3,  0x3f603f5,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+//8064 bytes
+enum toTitleSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([ 0x0,  0x40,  0x200], [ 0x100,  0x380,  0xbc0], [ 0x2020100,  0x4020302,  0x2020205,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x2020202,  0x10000,  0x30002,  0x50004,  0x70006,  0x90008,  0xa,  0xb0000,  0xd000c,  0xf000e,  0x110010,  0x130012,  0x14,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x150000,  0x0,  0x170016,  0x190018,  0x1b001a,  0x1d001c,  0x0,  0x0,  0x1e0000,  0x1f,  0x0,  0x0,  0x0,  0x0,  0x0,  0x200000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x220021,  0x240023,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x260000,  0x27,  0x290028,  0x2a,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2b0000,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2d002c,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff,  0x20001,  0x40003,  0x60005,  0x80007,  0xa0009,  0xc000b,  0xe000d,  0x10000f,  0x120011,  0x140013,  0x160015,  0x180017,  0xffff0019,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1affff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1c001b,  0x1e001d,  0x20001f,  0x220021,  0x240023,  0x260025,  0x280027,  0x2a0029,  0x2c002b,  0x2e002d,  0x30002f,  0xffff0031,  0x330032,  0x350034,  0x370036,  0x390038,  0x3affff,  0x3bffff,  0x3cffff,  0x3dffff,  0x3effff,  0x3fffff,  0x40ffff,  0x41ffff,  0x42ffff,  0x43ffff,  0x44ffff,  0x45ffff,  0x46ffff,  0x47ffff,  0x48ffff,  0x49ffff,  0x4affff,  0x4bffff,  0x4cffff,  0x4dffff,  0x4effff,  0x4fffff,  0x50ffff,  0x51ffff,  0x52ffff,  0x53ffff,  0x54ffff,  0x55ffff,  0xffffffff,  0xffff0056,  0xffff0057,  0xffff0058,  0xffff0059,  0xffff005a,  0xffff005b,  0xffff005c,  0xffff005d,  0x5effff,  0x5fffff,  0x60ffff,  0x61ffff,  0x62ffff,  0x63ffff,  0x64ffff,  0x65ffff,  0x66ffff,  0x67ffff,  0x68ffff,  0x69ffff,  0x6affff,  0x6bffff,  0x6cffff,  0x6dffff,  0x6effff,  0x6fffff,  0x70ffff,  0x71ffff,  0x72ffff,  0x73ffff,  0x74ffff,  0xffffffff,  0xffff0075,  0xffff0076,  0x780077,  0xffff0079,  0x7affff,  0x7bffff,  0xffffffff,  0xffff007c,  0xffffffff,  0xffff007d,  0xffffffff,  0xffffffff,  0xffff007e,  0x7fffff,  0xffffffff,  0x80ffff,  0xffff0081,  0xffffffff,  0xffff0082,  0x83ffff,  0x84ffff,  0x85ffff,  0xffffffff,  0xffff0086,  0xffffffff,  0x87ffff,  0xffffffff,  0xffff0088,  0xffffffff,  0xffff0089,  0xffff008a,  0x8bffff,  0xffffffff,  0x8cffff,  0x8dffff,  0xffffffff,  0xffffffff,  0x8f008e,  0x910090,  0x930092,  0x950094,  0xffff0096,  0xffff0097,  0xffff0098,  0xffff0099,  0xffff009a,  0xffff009b,  0xffff009c,  0xffff009d,  0x9f009e,  0xa0ffff,  0xa1ffff,  0xa2ffff,  0xa3ffff,  0xa4ffff,  0xa5ffff,  0xa6ffff,  0xa7ffff,  0xa8ffff,  0xa9ffff,  0xab00aa,  0xacffff,  0xffffffff,  0xadffff,  0xaeffff,  0xafffff,  0xb0ffff,  0xb1ffff,  0xb2ffff,  0xb3ffff,  0xb4ffff,  0xb5ffff,  0xb6ffff,  0xb7ffff,  0xb8ffff,  0xb9ffff,  0xbaffff,  0xbbffff,  0xbcffff,  0xbdffff,  0xbeffff,  0xbfffff,  0xc0ffff,  0xffffffff,  0xc1ffff,  0xc2ffff,  0xc3ffff,  0xc4ffff,  0xc5ffff,  0xc6ffff,  0xc7ffff,  0xc8ffff,  0xc9ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff00ca,  0xcbffff,  0xffff00cc,  0xffff00cd,  0xffffffff,  0xceffff,  0xcfffff,  0xd0ffff,  0xd1ffff,  0xd2ffff,  0xd400d3,  0xd600d5,  0xffff00d7,  0xd900d8,  0xdaffff,  0xdbffff,  0xffffffff,  0xffffffff,  0xffff00dc,  0xddffff,  0xdeffff,  0xffff00df,  0xe100e0,  0xe2ffff,  0xffffffff,  0xe3ffff,  0xe4ffff,  0xffff00e5,  0xe6ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xe7ffff,  0xffffffff,  0xffff00e8,  0xe9ffff,  0xffffffff,  0xffffffff,  0xeb00ea,  0xed00ec,  0xffff00ee,  0xffffffff,  0xffffffff,  0xffff00ef,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf0ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf1ffff,  0xf2ffff,  0xffffffff,  0xf3ffff,  0xffffffff,  0xf4ffff,  0xf600f5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xf800f7,  0xfa00f9,  0xfbffff,  0xfd00fc,  0xff00fe,  0x1010100,  0x1030102,  0x1050104,  0x1070106,  0x1090108,  0x10b010a,  0x10d010c,  0x10f010e,  0x1110110,  0x1130112,  0x1150114,  0x1170116,  0xffff0118,  0x11a0119,  0xffffffff,  0x11bffff,  0x11d011c,  0x11effff,  0x11fffff,  0x120ffff,  0x121ffff,  0x122ffff,  0x123ffff,  0x124ffff,  0x125ffff,  0x126ffff,  0x127ffff,  0x128ffff,  0x129ffff,  0x12b012a,  0xffff012c,  0x12dffff,  0xffffffff,  0xffff012e,  0x12fffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1310130,  0x1330132,  0x1350134,  0x1370136,  0x1390138,  0x13b013a,  0x13d013c,  0x13f013e,  0x1410140,  0x1430142,  0x1450144,  0x1470146,  0x1490148,  0x14b014a,  0x14d014c,  0x14f014e,  0x1510150,  0x1530152,  0x1550154,  0x1570156,  0x1590158,  0x15b015a,  0x15d015c,  0x15f015e,  0x160ffff,  0x161ffff,  0x162ffff,  0x163ffff,  0x164ffff,  0x165ffff,  0x166ffff,  0x167ffff,  0x168ffff,  0x169ffff,  0x16affff,  0x16bffff,  0x16cffff,  0x16dffff,  0x16effff,  0x16fffff,  0x170ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x171ffff,  0x172ffff,  0x173ffff,  0x174ffff,  0x175ffff,  0x176ffff,  0x177ffff,  0x178ffff,  0x179ffff,  0x17affff,  0x17bffff,  0x17cffff,  0x17dffff,  0x17effff,  0x17fffff,  0x180ffff,  0x181ffff,  0x182ffff,  0x183ffff,  0x184ffff,  0x185ffff,  0x186ffff,  0x187ffff,  0x188ffff,  0x189ffff,  0x18affff,  0x18bffff,  0xffffffff,  0xffff018c,  0xffff018d,  0xffff018e,  0xffff018f,  0xffff0190,  0xffff0191,  0x1930192,  0x194ffff,  0x195ffff,  0x196ffff,  0x197ffff,  0x198ffff,  0x199ffff,  0x19affff,  0x19bffff,  0x19cffff,  0x19dffff,  0x19effff,  0x19fffff,  0x1a0ffff,  0x1a1ffff,  0x1a2ffff,  0x1a3ffff,  0x1a4ffff,  0x1a5ffff,  0x1a6ffff,  0x1a7ffff,  0x1a8ffff,  0x1a9ffff,  0x1aaffff,  0x1abffff,  0x1acffff,  0x1adffff,  0x1aeffff,  0x1afffff,  0x1b0ffff,  0x1b1ffff,  0x1b2ffff,  0x1b3ffff,  0x1b4ffff,  0x1b5ffff,  0x1b6ffff,  0x1b7ffff,  0x1b8ffff,  0x1b9ffff,  0x1baffff,  0x1bbffff,  0x1bcffff,  0x1bdffff,  0x1beffff,  0x1bfffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1c0ffff,  0x1c201c1,  0x1c401c3,  0x1c601c5,  0x1c801c7,  0x1ca01c9,  0x1cc01cb,  0x1ce01cd,  0x1d001cf,  0x1d201d1,  0x1d401d3,  0x1d601d5,  0x1d801d7,  0x1da01d9,  0x1dc01db,  0x1de01dd,  0x1e001df,  0x1e201e1,  0x1e401e3,  0xffff01e5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x1e6ffff,  0xffffffff,  0x1e7ffff,  0xffffffff,  0x1e8ffff,  0x1e9ffff,  0x1eaffff,  0x1ebffff,  0x1ecffff,  0x1edffff,  0x1eeffff,  0x1efffff,  0x1f0ffff,  0x1f1ffff,  0x1f2ffff,  0x1f3ffff,  0x1f4ffff,  0x1f5ffff,  0x1f6ffff,  0x1f7ffff,  0x1f8ffff,  0x1f9ffff,  0x1faffff,  0x1fbffff,  0x1fcffff,  0x1fdffff,  0x1feffff,  0x1ffffff,  0x200ffff,  0x201ffff,  0x202ffff,  0x203ffff,  0x204ffff,  0x205ffff,  0x206ffff,  0x207ffff,  0x208ffff,  0x209ffff,  0x20affff,  0x20bffff,  0x20cffff,  0x20dffff,  0x20effff,  0x20fffff,  0x210ffff,  0x211ffff,  0x212ffff,  0x213ffff,  0x214ffff,  0x215ffff,  0x216ffff,  0x217ffff,  0x218ffff,  0x219ffff,  0x21affff,  0x21bffff,  0x21cffff,  0x21dffff,  0x21effff,  0x21fffff,  0x220ffff,  0x221ffff,  0x222ffff,  0x223ffff,  0x224ffff,  0x225ffff,  0x226ffff,  0x227ffff,  0x228ffff,  0x229ffff,  0x22affff,  0x22bffff,  0x22cffff,  0x22dffff,  0x22effff,  0x22fffff,  0x230ffff,  0x231ffff,  0x232ffff,  0xffffffff,  0xffffffff,  0x233ffff,  0xffffffff,  0xffffffff,  0x234ffff,  0x235ffff,  0x236ffff,  0x237ffff,  0x238ffff,  0x239ffff,  0x23affff,  0x23bffff,  0x23cffff,  0x23dffff,  0x23effff,  0x23fffff,  0x240ffff,  0x241ffff,  0x242ffff,  0x243ffff,  0x244ffff,  0x245ffff,  0x246ffff,  0x247ffff,  0x248ffff,  0x249ffff,  0x24affff,  0x24bffff,  0x24cffff,  0x24dffff,  0x24effff,  0x24fffff,  0x250ffff,  0x251ffff,  0x252ffff,  0x253ffff,  0x254ffff,  0x255ffff,  0x256ffff,  0x257ffff,  0x258ffff,  0x259ffff,  0x25affff,  0x25bffff,  0x25cffff,  0x25dffff,  0x25effff,  0x25fffff,  0x260ffff,  0x261ffff,  0x262ffff,  0x263ffff,  0x2650264,  0x2670266,  0x2690268,  0x26b026a,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x26d026c,  0x26f026e,  0x2710270,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2730272,  0x2750274,  0x2770276,  0x2790278,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x27b027a,  0x27d027c,  0x27f027e,  0x2810280,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2830282,  0x2850284,  0x2870286,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x288ffff,  0x289ffff,  0x28affff,  0x28bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x28d028c,  0x28f028e,  0x2910290,  0x2930292,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2950294,  0x2970296,  0x2990298,  0x29b029a,  0x29d029c,  0x29f029e,  0x2a102a0,  0xffffffff,  0x2a302a2,  0x2a502a4,  0x2a702a6,  0x2a902a8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2ab02aa,  0x2ad02ac,  0x2af02ae,  0x2b102b0,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2b302b2,  0x2b502b4,  0x2b702b6,  0x2b902b8,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2bb02ba,  0x2bcffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02bd,  0xffffffff,  0x2beffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c002bf,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c202c1,  0xffffffff,  0x2c3ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c4ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff02c5,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2c702c6,  0x2c902c8,  0x2cb02ca,  0x2cd02cc,  0x2cf02ce,  0x2d102d0,  0x2d302d2,  0x2d502d4,  0xffffffff,  0xffffffff,  0xffff02d6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2d802d7,  0x2da02d9,  0x2dc02db,  0x2de02dd,  0x2e002df,  0x2e202e1,  0x2e402e3,  0x2e602e5,  0x2e802e7,  0x2ea02e9,  0x2ec02eb,  0x2ee02ed,  0x2f002ef,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x2f202f1,  0x2f402f3,  0x2f602f5,  0x2f802f7,  0x2fa02f9,  0x2fc02fb,  0x2fe02fd,  0x30002ff,  0x3020301,  0x3040303,  0x3060305,  0x3080307,  0x30a0309,  0x30c030b,  0x30e030d,  0x310030f,  0x3120311,  0x3140313,  0x3160315,  0x3180317,  0x31a0319,  0x31c031b,  0x31e031d,  0xffff031f,  0x320ffff,  0xffffffff,  0x321ffff,  0xffff0322,  0xffff0323,  0xffff0324,  0xffff0325,  0xffffffff,  0xffffffff,  0x326ffff,  0xffffffff,  0xffff0327,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x328ffff,  0x329ffff,  0x32affff,  0x32bffff,  0x32cffff,  0x32dffff,  0x32effff,  0x32fffff,  0x330ffff,  0x331ffff,  0x332ffff,  0x333ffff,  0x334ffff,  0x335ffff,  0x336ffff,  0x337ffff,  0x338ffff,  0x339ffff,  0x33affff,  0x33bffff,  0x33cffff,  0x33dffff,  0x33effff,  0x33fffff,  0x340ffff,  0x341ffff,  0x342ffff,  0x343ffff,  0x344ffff,  0x345ffff,  0x346ffff,  0x347ffff,  0x348ffff,  0x349ffff,  0x34affff,  0x34bffff,  0x34cffff,  0x34dffff,  0x34effff,  0x34fffff,  0x350ffff,  0x351ffff,  0x352ffff,  0x353ffff,  0x354ffff,  0x355ffff,  0x356ffff,  0x357ffff,  0x358ffff,  0x359ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff035a,  0xffff035b,  0xffffffff,  0x35cffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x35e035d,  0x360035f,  0x3620361,  0x3640363,  0x3660365,  0x3680367,  0x36a0369,  0x36c036b,  0x36e036d,  0x370036f,  0x3720371,  0x3740373,  0x3760375,  0x3780377,  0x37a0379,  0x37c037b,  0x37e037d,  0x380037f,  0x3820381,  0x383ffff,  0xffffffff,  0xffffffff,  0x384ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x385ffff,  0x386ffff,  0x387ffff,  0x388ffff,  0x389ffff,  0x38affff,  0x38bffff,  0x38cffff,  0x38dffff,  0x38effff,  0x38fffff,  0x390ffff,  0x391ffff,  0x392ffff,  0x393ffff,  0x394ffff,  0x395ffff,  0x396ffff,  0x397ffff,  0x398ffff,  0x399ffff,  0x39affff,  0x39bffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x39cffff,  0x39dffff,  0x39effff,  0x39fffff,  0x3a0ffff,  0x3a1ffff,  0x3a2ffff,  0x3a3ffff,  0x3a4ffff,  0x3a5ffff,  0x3a6ffff,  0x3a7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3a8ffff,  0x3a9ffff,  0x3aaffff,  0x3abffff,  0x3acffff,  0x3adffff,  0x3aeffff,  0xffffffff,  0x3afffff,  0x3b0ffff,  0x3b1ffff,  0x3b2ffff,  0x3b3ffff,  0x3b4ffff,  0x3b5ffff,  0x3b6ffff,  0x3b7ffff,  0x3b8ffff,  0x3b9ffff,  0x3baffff,  0x3bbffff,  0x3bcffff,  0x3bdffff,  0x3beffff,  0x3bfffff,  0x3c0ffff,  0x3c1ffff,  0x3c2ffff,  0x3c3ffff,  0x3c4ffff,  0x3c5ffff,  0x3c6ffff,  0x3c7ffff,  0x3c8ffff,  0x3c9ffff,  0x3caffff,  0x3cbffff,  0x3ccffff,  0x3cdffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffff03ce,  0xffff03cf,  0x3d0ffff,  0x3d1ffff,  0x3d2ffff,  0x3d3ffff,  0x3d4ffff,  0xffffffff,  0xffffffff,  0xffff03d5,  0xffffffff,  0x3d6ffff,  0x3d7ffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3d8ffff,  0x3d9ffff,  0x3daffff,  0x3dbffff,  0x3dcffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3ddffff,  0x3df03de,  0x3e103e0,  0x3e303e2,  0x3e503e4,  0x3e703e6,  0x3e903e8,  0x3eb03ea,  0x3ed03ec,  0x3ef03ee,  0x3f103f0,  0x3f303f2,  0x3f503f4,  0xffff03f6,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0x3f803f7,  0x3fa03f9,  0x3fc03fb,  0x3fe03fd,  0x40003ff,  0x4020401,  0x4040403,  0x4060405,  0x4080407,  0x40a0409,  0x40c040b,  0x40e040d,  0x410040f,  0x4120411,  0x4140413,  0x4160415,  0x4180417,  0x41a0419,  0x41c041b,  0x41e041d,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff,  0xffffffff]);
+@property
+{
+private alias _IUA = immutable(uint[]);
+_IUA toUpperTable() { static _IUA t = [ 0x41,  0x42,  0x43,  0x44,  0x45,  0x46,  0x47,  0x48,  0x49,  0x4a,  0x4b,  0x4c,  0x4d,  0x4e,  0x4f,  0x50,  0x51,  0x52,  0x53,  0x54,  0x55,  0x56,  0x57,  0x58,  0x59,  0x5a,  0x39c,  0xc0,  0xc1,  0xc2,  0xc3,  0xc4,  0xc5,  0xc6,  0xc7,  0xc8,  0xc9,  0xca,  0xcb,  0xcc,  0xcd,  0xce,  0xcf,  0xd0,  0xd1,  0xd2,  0xd3,  0xd4,  0xd5,  0xd6,  0xd8,  0xd9,  0xda,  0xdb,  0xdc,  0xdd,  0xde,  0x178,  0x100,  0x102,  0x104,  0x106,  0x108,  0x10a,  0x10c,  0x10e,  0x110,  0x112,  0x114,  0x116,  0x118,  0x11a,  0x11c,  0x11e,  0x120,  0x122,  0x124,  0x126,  0x128,  0x12a,  0x12c,  0x12e,  0x49,  0x132,  0x134,  0x136,  0x139,  0x13b,  0x13d,  0x13f,  0x141,  0x143,  0x145,  0x147,  0x14a,  0x14c,  0x14e,  0x150,  0x152,  0x154,  0x156,  0x158,  0x15a,  0x15c,  0x15e,  0x160,  0x162,  0x164,  0x166,  0x168,  0x16a,  0x16c,  0x16e,  0x170,  0x172,  0x174,  0x176,  0x179,  0x17b,  0x17d,  0x53,  0x243,  0x182,  0x184,  0x187,  0x18b,  0x191,  0x1f6,  0x198,  0x23d,  0x220,  0x1a0,  0x1a2,  0x1a4,  0x1a7,  0x1ac,  0x1af,  0x1b3,  0x1b5,  0x1b8,  0x1bc,  0x1f7,  0x1c4,  0x1c4,  0x1c7,  0x1c7,  0x1ca,  0x1ca,  0x1cd,  0x1cf,  0x1d1,  0x1d3,  0x1d5,  0x1d7,  0x1d9,  0x1db,  0x18e,  0x1de,  0x1e0,  0x1e2,  0x1e4,  0x1e6,  0x1e8,  0x1ea,  0x1ec,  0x1ee,  0x1f1,  0x1f1,  0x1f4,  0x1f8,  0x1fa,  0x1fc,  0x1fe,  0x200,  0x202,  0x204,  0x206,  0x208,  0x20a,  0x20c,  0x20e,  0x210,  0x212,  0x214,  0x216,  0x218,  0x21a,  0x21c,  0x21e,  0x222,  0x224,  0x226,  0x228,  0x22a,  0x22c,  0x22e,  0x230,  0x232,  0x23b,  0x2c7e,  0x2c7f,  0x241,  0x246,  0x248,  0x24a,  0x24c,  0x24e,  0x2c6f,  0x2c6d,  0x2c70,  0x181,  0x186,  0x189,  0x18a,  0x18f,  0x190,  0x193,  0x194,  0xa78d,  0xa7aa,  0x197,  0x196,  0x2c62,  0x19c,  0x2c6e,  0x19d,  0x19f,  0x2c64,  0x1a6,  0x1a9,  0x1ae,  0x244,  0x1b1,  0x1b2,  0x245,  0x1b7,  0x399,  0x370,  0x372,  0x376,  0x3fd,  0x3fe,  0x3ff,  0x386,  0x388,  0x389,  0x38a,  0x391,  0x392,  0x393,  0x394,  0x395,  0x396,  0x397,  0x398,  0x399,  0x39a,  0x39b,  0x39c,  0x39d,  0x39e,  0x39f,  0x3a0,  0x3a1,  0x3a3,  0x3a3,  0x3a4,  0x3a5,  0x3a6,  0x3a7,  0x3a8,  0x3a9,  0x3aa,  0x3ab,  0x38c,  0x38e,  0x38f,  0x392,  0x398,  0x3a6,  0x3a0,  0x3cf,  0x3d8,  0x3da,  0x3dc,  0x3de,  0x3e0,  0x3e2,  0x3e4,  0x3e6,  0x3e8,  0x3ea,  0x3ec,  0x3ee,  0x39a,  0x3a1,  0x3f9,  0x395,  0x3f7,  0x3fa,  0x410,  0x411,  0x412,  0x413,  0x414,  0x415,  0x416,  0x417,  0x418,  0x419,  0x41a,  0x41b,  0x41c,  0x41d,  0x41e,  0x41f,  0x420,  0x421,  0x422,  0x423,  0x424,  0x425,  0x426,  0x427,  0x428,  0x429,  0x42a,  0x42b,  0x42c,  0x42d,  0x42e,  0x42f,  0x400,  0x401,  0x402,  0x403,  0x404,  0x405,  0x406,  0x407,  0x408,  0x409,  0x40a,  0x40b,  0x40c,  0x40d,  0x40e,  0x40f,  0x460,  0x462,  0x464,  0x466,  0x468,  0x46a,  0x46c,  0x46e,  0x470,  0x472,  0x474,  0x476,  0x478,  0x47a,  0x47c,  0x47e,  0x480,  0x48a,  0x48c,  0x48e,  0x490,  0x492,  0x494,  0x496,  0x498,  0x49a,  0x49c,  0x49e,  0x4a0,  0x4a2,  0x4a4,  0x4a6,  0x4a8,  0x4aa,  0x4ac,  0x4ae,  0x4b0,  0x4b2,  0x4b4,  0x4b6,  0x4b8,  0x4ba,  0x4bc,  0x4be,  0x4c1,  0x4c3,  0x4c5,  0x4c7,  0x4c9,  0x4cb,  0x4cd,  0x4c0,  0x4d0,  0x4d2,  0x4d4,  0x4d6,  0x4d8,  0x4da,  0x4dc,  0x4de,  0x4e0,  0x4e2,  0x4e4,  0x4e6,  0x4e8,  0x4ea,  0x4ec,  0x4ee,  0x4f0,  0x4f2,  0x4f4,  0x4f6,  0x4f8,  0x4fa,  0x4fc,  0x4fe,  0x500,  0x502,  0x504,  0x506,  0x508,  0x50a,  0x50c,  0x50e,  0x510,  0x512,  0x514,  0x516,  0x518,  0x51a,  0x51c,  0x51e,  0x520,  0x522,  0x524,  0x526,  0x531,  0x532,  0x533,  0x534,  0x535,  0x536,  0x537,  0x538,  0x539,  0x53a,  0x53b,  0x53c,  0x53d,  0x53e,  0x53f,  0x540,  0x541,  0x542,  0x543,  0x544,  0x545,  0x546,  0x547,  0x548,  0x549,  0x54a,  0x54b,  0x54c,  0x54d,  0x54e,  0x54f,  0x550,  0x551,  0x552,  0x553,  0x554,  0x555,  0x556,  0xa77d,  0x2c63,  0x1e00,  0x1e02,  0x1e04,  0x1e06,  0x1e08,  0x1e0a,  0x1e0c,  0x1e0e,  0x1e10,  0x1e12,  0x1e14,  0x1e16,  0x1e18,  0x1e1a,  0x1e1c,  0x1e1e,  0x1e20,  0x1e22,  0x1e24,  0x1e26,  0x1e28,  0x1e2a,  0x1e2c,  0x1e2e,  0x1e30,  0x1e32,  0x1e34,  0x1e36,  0x1e38,  0x1e3a,  0x1e3c,  0x1e3e,  0x1e40,  0x1e42,  0x1e44,  0x1e46,  0x1e48,  0x1e4a,  0x1e4c,  0x1e4e,  0x1e50,  0x1e52,  0x1e54,  0x1e56,  0x1e58,  0x1e5a,  0x1e5c,  0x1e5e,  0x1e60,  0x1e62,  0x1e64,  0x1e66,  0x1e68,  0x1e6a,  0x1e6c,  0x1e6e,  0x1e70,  0x1e72,  0x1e74,  0x1e76,  0x1e78,  0x1e7a,  0x1e7c,  0x1e7e,  0x1e80,  0x1e82,  0x1e84,  0x1e86,  0x1e88,  0x1e8a,  0x1e8c,  0x1e8e,  0x1e90,  0x1e92,  0x1e94,  0x1e60,  0x1ea0,  0x1ea2,  0x1ea4,  0x1ea6,  0x1ea8,  0x1eaa,  0x1eac,  0x1eae,  0x1eb0,  0x1eb2,  0x1eb4,  0x1eb6,  0x1eb8,  0x1eba,  0x1ebc,  0x1ebe,  0x1ec0,  0x1ec2,  0x1ec4,  0x1ec6,  0x1ec8,  0x1eca,  0x1ecc,  0x1ece,  0x1ed0,  0x1ed2,  0x1ed4,  0x1ed6,  0x1ed8,  0x1eda,  0x1edc,  0x1ede,  0x1ee0,  0x1ee2,  0x1ee4,  0x1ee6,  0x1ee8,  0x1eea,  0x1eec,  0x1eee,  0x1ef0,  0x1ef2,  0x1ef4,  0x1ef6,  0x1ef8,  0x1efa,  0x1efc,  0x1efe,  0x1f08,  0x1f09,  0x1f0a,  0x1f0b,  0x1f0c,  0x1f0d,  0x1f0e,  0x1f0f,  0x1f18,  0x1f19,  0x1f1a,  0x1f1b,  0x1f1c,  0x1f1d,  0x1f28,  0x1f29,  0x1f2a,  0x1f2b,  0x1f2c,  0x1f2d,  0x1f2e,  0x1f2f,  0x1f38,  0x1f39,  0x1f3a,  0x1f3b,  0x1f3c,  0x1f3d,  0x1f3e,  0x1f3f,  0x1f48,  0x1f49,  0x1f4a,  0x1f4b,  0x1f4c,  0x1f4d,  0x1f59,  0x1f5b,  0x1f5d,  0x1f5f,  0x1f68,  0x1f69,  0x1f6a,  0x1f6b,  0x1f6c,  0x1f6d,  0x1f6e,  0x1f6f,  0x1fba,  0x1fbb,  0x1fc8,  0x1fc9,  0x1fca,  0x1fcb,  0x1fda,  0x1fdb,  0x1ff8,  0x1ff9,  0x1fea,  0x1feb,  0x1ffa,  0x1ffb,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fb8,  0x1fb9,  0x1fbc,  0x399,  0x1fcc,  0x1fd8,  0x1fd9,  0x1fe8,  0x1fe9,  0x1fec,  0x1ffc,  0x2132,  0x2160,  0x2161,  0x2162,  0x2163,  0x2164,  0x2165,  0x2166,  0x2167,  0x2168,  0x2169,  0x216a,  0x216b,  0x216c,  0x216d,  0x216e,  0x216f,  0x2183,  0x24b6,  0x24b7,  0x24b8,  0x24b9,  0x24ba,  0x24bb,  0x24bc,  0x24bd,  0x24be,  0x24bf,  0x24c0,  0x24c1,  0x24c2,  0x24c3,  0x24c4,  0x24c5,  0x24c6,  0x24c7,  0x24c8,  0x24c9,  0x24ca,  0x24cb,  0x24cc,  0x24cd,  0x24ce,  0x24cf,  0x2c00,  0x2c01,  0x2c02,  0x2c03,  0x2c04,  0x2c05,  0x2c06,  0x2c07,  0x2c08,  0x2c09,  0x2c0a,  0x2c0b,  0x2c0c,  0x2c0d,  0x2c0e,  0x2c0f,  0x2c10,  0x2c11,  0x2c12,  0x2c13,  0x2c14,  0x2c15,  0x2c16,  0x2c17,  0x2c18,  0x2c19,  0x2c1a,  0x2c1b,  0x2c1c,  0x2c1d,  0x2c1e,  0x2c1f,  0x2c20,  0x2c21,  0x2c22,  0x2c23,  0x2c24,  0x2c25,  0x2c26,  0x2c27,  0x2c28,  0x2c29,  0x2c2a,  0x2c2b,  0x2c2c,  0x2c2d,  0x2c2e,  0x2c60,  0x23a,  0x23e,  0x2c67,  0x2c69,  0x2c6b,  0x2c72,  0x2c75,  0x2c80,  0x2c82,  0x2c84,  0x2c86,  0x2c88,  0x2c8a,  0x2c8c,  0x2c8e,  0x2c90,  0x2c92,  0x2c94,  0x2c96,  0x2c98,  0x2c9a,  0x2c9c,  0x2c9e,  0x2ca0,  0x2ca2,  0x2ca4,  0x2ca6,  0x2ca8,  0x2caa,  0x2cac,  0x2cae,  0x2cb0,  0x2cb2,  0x2cb4,  0x2cb6,  0x2cb8,  0x2cba,  0x2cbc,  0x2cbe,  0x2cc0,  0x2cc2,  0x2cc4,  0x2cc6,  0x2cc8,  0x2cca,  0x2ccc,  0x2cce,  0x2cd0,  0x2cd2,  0x2cd4,  0x2cd6,  0x2cd8,  0x2cda,  0x2cdc,  0x2cde,  0x2ce0,  0x2ce2,  0x2ceb,  0x2ced,  0x2cf2,  0x10a0,  0x10a1,  0x10a2,  0x10a3,  0x10a4,  0x10a5,  0x10a6,  0x10a7,  0x10a8,  0x10a9,  0x10aa,  0x10ab,  0x10ac,  0x10ad,  0x10ae,  0x10af,  0x10b0,  0x10b1,  0x10b2,  0x10b3,  0x10b4,  0x10b5,  0x10b6,  0x10b7,  0x10b8,  0x10b9,  0x10ba,  0x10bb,  0x10bc,  0x10bd,  0x10be,  0x10bf,  0x10c0,  0x10c1,  0x10c2,  0x10c3,  0x10c4,  0x10c5,  0x10c7,  0x10cd,  0xa640,  0xa642,  0xa644,  0xa646,  0xa648,  0xa64a,  0xa64c,  0xa64e,  0xa650,  0xa652,  0xa654,  0xa656,  0xa658,  0xa65a,  0xa65c,  0xa65e,  0xa660,  0xa662,  0xa664,  0xa666,  0xa668,  0xa66a,  0xa66c,  0xa680,  0xa682,  0xa684,  0xa686,  0xa688,  0xa68a,  0xa68c,  0xa68e,  0xa690,  0xa692,  0xa694,  0xa696,  0xa722,  0xa724,  0xa726,  0xa728,  0xa72a,  0xa72c,  0xa72e,  0xa732,  0xa734,  0xa736,  0xa738,  0xa73a,  0xa73c,  0xa73e,  0xa740,  0xa742,  0xa744,  0xa746,  0xa748,  0xa74a,  0xa74c,  0xa74e,  0xa750,  0xa752,  0xa754,  0xa756,  0xa758,  0xa75a,  0xa75c,  0xa75e,  0xa760,  0xa762,  0xa764,  0xa766,  0xa768,  0xa76a,  0xa76c,  0xa76e,  0xa779,  0xa77b,  0xa77e,  0xa780,  0xa782,  0xa784,  0xa786,  0xa78b,  0xa790,  0xa792,  0xa7a0,  0xa7a2,  0xa7a4,  0xa7a6,  0xa7a8,  0xff21,  0xff22,  0xff23,  0xff24,  0xff25,  0xff26,  0xff27,  0xff28,  0xff29,  0xff2a,  0xff2b,  0xff2c,  0xff2d,  0xff2e,  0xff2f,  0xff30,  0xff31,  0xff32,  0xff33,  0xff34,  0xff35,  0xff36,  0xff37,  0xff38,  0xff39,  0xff3a,  0x10400,  0x10401,  0x10402,  0x10403,  0x10404,  0x10405,  0x10406,  0x10407,  0x10408,  0x10409,  0x1040a,  0x1040b,  0x1040c,  0x1040d,  0x1040e,  0x1040f,  0x10410,  0x10411,  0x10412,  0x10413,  0x10414,  0x10415,  0x10416,  0x10417,  0x10418,  0x10419,  0x1041a,  0x1041b,  0x1041c,  0x1041d,  0x1041e,  0x1041f,  0x10420,  0x10421,  0x10422,  0x10423,  0x10424,  0x10425,  0x10426,  0x10427,  0x2000053,  0x53,  0x130,  0x2000046,  0x46,  0x2000046,  0x49,  0x2000046,  0x4c,  0x3000046,  0x46,  0x49,  0x3000046,  0x46,  0x4c,  0x2000053,  0x54,  0x2000053,  0x54,  0x2000535,  0x552,  0x2000544,  0x546,  0x2000544,  0x535,  0x2000544,  0x53b,  0x200054e,  0x546,  0x2000544,  0x53d,  0x20002bc,  0x4e,  0x3000399,  0x308,  0x301,  0x30003a5,  0x308,  0x301,  0x200004a,  0x30c,  0x2000048,  0x331,  0x2000054,  0x308,  0x2000057,  0x30a,  0x2000059,  0x30a,  0x2000041,  0x2be,  0x20003a5,  0x313,  0x30003a5,  0x313,  0x300,  0x30003a5,  0x313,  0x301,  0x30003a5,  0x313,  0x342,  0x2000391,  0x342,  0x2000397,  0x342,  0x3000399,  0x308,  0x300,  0x3000399,  0x308,  0x301,  0x2000399,  0x342,  0x3000399,  0x308,  0x342,  0x30003a5,  0x308,  0x300,  0x30003a5,  0x308,  0x301,  0x20003a1,  0x313,  0x20003a5,  0x342,  0x30003a5,  0x308,  0x342,  0x20003a9,  0x342,  0x2001f08,  0x399,  0x2001f09,  0x399,  0x2001f0a,  0x399,  0x2001f0b,  0x399,  0x2001f0c,  0x399,  0x2001f0d,  0x399,  0x2001f0e,  0x399,  0x2001f0f,  0x399,  0x2001f08,  0x399,  0x2001f09,  0x399,  0x2001f0a,  0x399,  0x2001f0b,  0x399,  0x2001f0c,  0x399,  0x2001f0d,  0x399,  0x2001f0e,  0x399,  0x2001f0f,  0x399,  0x2001f28,  0x399,  0x2001f29,  0x399,  0x2001f2a,  0x399,  0x2001f2b,  0x399,  0x2001f2c,  0x399,  0x2001f2d,  0x399,  0x2001f2e,  0x399,  0x2001f2f,  0x399,  0x2001f28,  0x399,  0x2001f29,  0x399,  0x2001f2a,  0x399,  0x2001f2b,  0x399,  0x2001f2c,  0x399,  0x2001f2d,  0x399,  0x2001f2e,  0x399,  0x2001f2f,  0x399,  0x2001f68,  0x399,  0x2001f69,  0x399,  0x2001f6a,  0x399,  0x2001f6b,  0x399,  0x2001f6c,  0x399,  0x2001f6d,  0x399,  0x2001f6e,  0x399,  0x2001f6f,  0x399,  0x2001f68,  0x399,  0x2001f69,  0x399,  0x2001f6a,  0x399,  0x2001f6b,  0x399,  0x2001f6c,  0x399,  0x2001f6d,  0x399,  0x2001f6e,  0x399,  0x2001f6f,  0x399,  0x2000391,  0x399,  0x2000391,  0x399,  0x2000397,  0x399,  0x2000397,  0x399,  0x20003a9,  0x399,  0x20003a9,  0x399,  0x2001fba,  0x399,  0x2000386,  0x399,  0x2001fca,  0x399,  0x2000389,  0x399,  0x2001ffa,  0x399,  0x200038f,  0x399,  0x3000391,  0x342,  0x399,  0x3000397,  0x342,  0x399,  0x30003a9,  0x342,  0x399]; return t; }
+_IUA toLowerTable() { static _IUA t = [ 0x61,  0x62,  0x63,  0x64,  0x65,  0x66,  0x67,  0x68,  0x69,  0x6a,  0x6b,  0x6c,  0x6d,  0x6e,  0x6f,  0x70,  0x71,  0x72,  0x73,  0x74,  0x75,  0x76,  0x77,  0x78,  0x79,  0x7a,  0xe0,  0xe1,  0xe2,  0xe3,  0xe4,  0xe5,  0xe6,  0xe7,  0xe8,  0xe9,  0xea,  0xeb,  0xec,  0xed,  0xee,  0xef,  0xf0,  0xf1,  0xf2,  0xf3,  0xf4,  0xf5,  0xf6,  0xf8,  0xf9,  0xfa,  0xfb,  0xfc,  0xfd,  0xfe,  0x101,  0x103,  0x105,  0x107,  0x109,  0x10b,  0x10d,  0x10f,  0x111,  0x113,  0x115,  0x117,  0x119,  0x11b,  0x11d,  0x11f,  0x121,  0x123,  0x125,  0x127,  0x129,  0x12b,  0x12d,  0x12f,  0x69,  0x133,  0x135,  0x137,  0x13a,  0x13c,  0x13e,  0x140,  0x142,  0x144,  0x146,  0x148,  0x14b,  0x14d,  0x14f,  0x151,  0x153,  0x155,  0x157,  0x159,  0x15b,  0x15d,  0x15f,  0x161,  0x163,  0x165,  0x167,  0x169,  0x16b,  0x16d,  0x16f,  0x171,  0x173,  0x175,  0x177,  0xff,  0x17a,  0x17c,  0x17e,  0x253,  0x183,  0x185,  0x254,  0x188,  0x256,  0x257,  0x18c,  0x1dd,  0x259,  0x25b,  0x192,  0x260,  0x263,  0x269,  0x268,  0x199,  0x26f,  0x272,  0x275,  0x1a1,  0x1a3,  0x1a5,  0x280,  0x1a8,  0x283,  0x1ad,  0x288,  0x1b0,  0x28a,  0x28b,  0x1b4,  0x1b6,  0x292,  0x1b9,  0x1bd,  0x1c6,  0x1c6,  0x1c9,  0x1c9,  0x1cc,  0x1cc,  0x1ce,  0x1d0,  0x1d2,  0x1d4,  0x1d6,  0x1d8,  0x1da,  0x1dc,  0x1df,  0x1e1,  0x1e3,  0x1e5,  0x1e7,  0x1e9,  0x1eb,  0x1ed,  0x1ef,  0x1f3,  0x1f3,  0x1f5,  0x195,  0x1bf,  0x1f9,  0x1fb,  0x1fd,  0x1ff,  0x201,  0x203,  0x205,  0x207,  0x209,  0x20b,  0x20d,  0x20f,  0x211,  0x213,  0x215,  0x217,  0x219,  0x21b,  0x21d,  0x21f,  0x19e,  0x223,  0x225,  0x227,  0x229,  0x22b,  0x22d,  0x22f,  0x231,  0x233,  0x2c65,  0x23c,  0x19a,  0x2c66,  0x242,  0x180,  0x289,  0x28c,  0x247,  0x249,  0x24b,  0x24d,  0x24f,  0x371,  0x373,  0x377,  0x3ac,  0x3ad,  0x3ae,  0x3af,  0x3cc,  0x3cd,  0x3ce,  0x3b1,  0x3b2,  0x3b3,  0x3b4,  0x3b5,  0x3b6,  0x3b7,  0x3b8,  0x3b9,  0x3ba,  0x3bb,  0x3bc,  0x3bd,  0x3be,  0x3bf,  0x3c0,  0x3c1,  0x3c3,  0x3c4,  0x3c5,  0x3c6,  0x3c7,  0x3c8,  0x3c9,  0x3ca,  0x3cb,  0x3d7,  0x3d9,  0x3db,  0x3dd,  0x3df,  0x3e1,  0x3e3,  0x3e5,  0x3e7,  0x3e9,  0x3eb,  0x3ed,  0x3ef,  0x3b8,  0x3f8,  0x3f2,  0x3fb,  0x37b,  0x37c,  0x37d,  0x450,  0x451,  0x452,  0x453,  0x454,  0x455,  0x456,  0x457,  0x458,  0x459,  0x45a,  0x45b,  0x45c,  0x45d,  0x45e,  0x45f,  0x430,  0x431,  0x432,  0x433,  0x434,  0x435,  0x436,  0x437,  0x438,  0x439,  0x43a,  0x43b,  0x43c,  0x43d,  0x43e,  0x43f,  0x440,  0x441,  0x442,  0x443,  0x444,  0x445,  0x446,  0x447,  0x448,  0x449,  0x44a,  0x44b,  0x44c,  0x44d,  0x44e,  0x44f,  0x461,  0x463,  0x465,  0x467,  0x469,  0x46b,  0x46d,  0x46f,  0x471,  0x473,  0x475,  0x477,  0x479,  0x47b,  0x47d,  0x47f,  0x481,  0x48b,  0x48d,  0x48f,  0x491,  0x493,  0x495,  0x497,  0x499,  0x49b,  0x49d,  0x49f,  0x4a1,  0x4a3,  0x4a5,  0x4a7,  0x4a9,  0x4ab,  0x4ad,  0x4af,  0x4b1,  0x4b3,  0x4b5,  0x4b7,  0x4b9,  0x4bb,  0x4bd,  0x4bf,  0x4cf,  0x4c2,  0x4c4,  0x4c6,  0x4c8,  0x4ca,  0x4cc,  0x4ce,  0x4d1,  0x4d3,  0x4d5,  0x4d7,  0x4d9,  0x4db,  0x4dd,  0x4df,  0x4e1,  0x4e3,  0x4e5,  0x4e7,  0x4e9,  0x4eb,  0x4ed,  0x4ef,  0x4f1,  0x4f3,  0x4f5,  0x4f7,  0x4f9,  0x4fb,  0x4fd,  0x4ff,  0x501,  0x503,  0x505,  0x507,  0x509,  0x50b,  0x50d,  0x50f,  0x511,  0x513,  0x515,  0x517,  0x519,  0x51b,  0x51d,  0x51f,  0x521,  0x523,  0x525,  0x527,  0x561,  0x562,  0x563,  0x564,  0x565,  0x566,  0x567,  0x568,  0x569,  0x56a,  0x56b,  0x56c,  0x56d,  0x56e,  0x56f,  0x570,  0x571,  0x572,  0x573,  0x574,  0x575,  0x576,  0x577,  0x578,  0x579,  0x57a,  0x57b,  0x57c,  0x57d,  0x57e,  0x57f,  0x580,  0x581,  0x582,  0x583,  0x584,  0x585,  0x586,  0x2d00,  0x2d01,  0x2d02,  0x2d03,  0x2d04,  0x2d05,  0x2d06,  0x2d07,  0x2d08,  0x2d09,  0x2d0a,  0x2d0b,  0x2d0c,  0x2d0d,  0x2d0e,  0x2d0f,  0x2d10,  0x2d11,  0x2d12,  0x2d13,  0x2d14,  0x2d15,  0x2d16,  0x2d17,  0x2d18,  0x2d19,  0x2d1a,  0x2d1b,  0x2d1c,  0x2d1d,  0x2d1e,  0x2d1f,  0x2d20,  0x2d21,  0x2d22,  0x2d23,  0x2d24,  0x2d25,  0x2d27,  0x2d2d,  0x1e01,  0x1e03,  0x1e05,  0x1e07,  0x1e09,  0x1e0b,  0x1e0d,  0x1e0f,  0x1e11,  0x1e13,  0x1e15,  0x1e17,  0x1e19,  0x1e1b,  0x1e1d,  0x1e1f,  0x1e21,  0x1e23,  0x1e25,  0x1e27,  0x1e29,  0x1e2b,  0x1e2d,  0x1e2f,  0x1e31,  0x1e33,  0x1e35,  0x1e37,  0x1e39,  0x1e3b,  0x1e3d,  0x1e3f,  0x1e41,  0x1e43,  0x1e45,  0x1e47,  0x1e49,  0x1e4b,  0x1e4d,  0x1e4f,  0x1e51,  0x1e53,  0x1e55,  0x1e57,  0x1e59,  0x1e5b,  0x1e5d,  0x1e5f,  0x1e61,  0x1e63,  0x1e65,  0x1e67,  0x1e69,  0x1e6b,  0x1e6d,  0x1e6f,  0x1e71,  0x1e73,  0x1e75,  0x1e77,  0x1e79,  0x1e7b,  0x1e7d,  0x1e7f,  0x1e81,  0x1e83,  0x1e85,  0x1e87,  0x1e89,  0x1e8b,  0x1e8d,  0x1e8f,  0x1e91,  0x1e93,  0x1e95,  0xdf,  0x1ea1,  0x1ea3,  0x1ea5,  0x1ea7,  0x1ea9,  0x1eab,  0x1ead,  0x1eaf,  0x1eb1,  0x1eb3,  0x1eb5,  0x1eb7,  0x1eb9,  0x1ebb,  0x1ebd,  0x1ebf,  0x1ec1,  0x1ec3,  0x1ec5,  0x1ec7,  0x1ec9,  0x1ecb,  0x1ecd,  0x1ecf,  0x1ed1,  0x1ed3,  0x1ed5,  0x1ed7,  0x1ed9,  0x1edb,  0x1edd,  0x1edf,  0x1ee1,  0x1ee3,  0x1ee5,  0x1ee7,  0x1ee9,  0x1eeb,  0x1eed,  0x1eef,  0x1ef1,  0x1ef3,  0x1ef5,  0x1ef7,  0x1ef9,  0x1efb,  0x1efd,  0x1eff,  0x1f00,  0x1f01,  0x1f02,  0x1f03,  0x1f04,  0x1f05,  0x1f06,  0x1f07,  0x1f10,  0x1f11,  0x1f12,  0x1f13,  0x1f14,  0x1f15,  0x1f20,  0x1f21,  0x1f22,  0x1f23,  0x1f24,  0x1f25,  0x1f26,  0x1f27,  0x1f30,  0x1f31,  0x1f32,  0x1f33,  0x1f34,  0x1f35,  0x1f36,  0x1f37,  0x1f40,  0x1f41,  0x1f42,  0x1f43,  0x1f44,  0x1f45,  0x1f51,  0x1f53,  0x1f55,  0x1f57,  0x1f60,  0x1f61,  0x1f62,  0x1f63,  0x1f64,  0x1f65,  0x1f66,  0x1f67,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fb0,  0x1fb1,  0x1f70,  0x1f71,  0x1fb3,  0x1f72,  0x1f73,  0x1f74,  0x1f75,  0x1fc3,  0x1fd0,  0x1fd1,  0x1f76,  0x1f77,  0x1fe0,  0x1fe1,  0x1f7a,  0x1f7b,  0x1fe5,  0x1f78,  0x1f79,  0x1f7c,  0x1f7d,  0x1ff3,  0x3c9,  0x6b,  0xe5,  0x214e,  0x2170,  0x2171,  0x2172,  0x2173,  0x2174,  0x2175,  0x2176,  0x2177,  0x2178,  0x2179,  0x217a,  0x217b,  0x217c,  0x217d,  0x217e,  0x217f,  0x2184,  0x24d0,  0x24d1,  0x24d2,  0x24d3,  0x24d4,  0x24d5,  0x24d6,  0x24d7,  0x24d8,  0x24d9,  0x24da,  0x24db,  0x24dc,  0x24dd,  0x24de,  0x24df,  0x24e0,  0x24e1,  0x24e2,  0x24e3,  0x24e4,  0x24e5,  0x24e6,  0x24e7,  0x24e8,  0x24e9,  0x2c30,  0x2c31,  0x2c32,  0x2c33,  0x2c34,  0x2c35,  0x2c36,  0x2c37,  0x2c38,  0x2c39,  0x2c3a,  0x2c3b,  0x2c3c,  0x2c3d,  0x2c3e,  0x2c3f,  0x2c40,  0x2c41,  0x2c42,  0x2c43,  0x2c44,  0x2c45,  0x2c46,  0x2c47,  0x2c48,  0x2c49,  0x2c4a,  0x2c4b,  0x2c4c,  0x2c4d,  0x2c4e,  0x2c4f,  0x2c50,  0x2c51,  0x2c52,  0x2c53,  0x2c54,  0x2c55,  0x2c56,  0x2c57,  0x2c58,  0x2c59,  0x2c5a,  0x2c5b,  0x2c5c,  0x2c5d,  0x2c5e,  0x2c61,  0x26b,  0x1d7d,  0x27d,  0x2c68,  0x2c6a,  0x2c6c,  0x251,  0x271,  0x250,  0x252,  0x2c73,  0x2c76,  0x23f,  0x240,  0x2c81,  0x2c83,  0x2c85,  0x2c87,  0x2c89,  0x2c8b,  0x2c8d,  0x2c8f,  0x2c91,  0x2c93,  0x2c95,  0x2c97,  0x2c99,  0x2c9b,  0x2c9d,  0x2c9f,  0x2ca1,  0x2ca3,  0x2ca5,  0x2ca7,  0x2ca9,  0x2cab,  0x2cad,  0x2caf,  0x2cb1,  0x2cb3,  0x2cb5,  0x2cb7,  0x2cb9,  0x2cbb,  0x2cbd,  0x2cbf,  0x2cc1,  0x2cc3,  0x2cc5,  0x2cc7,  0x2cc9,  0x2ccb,  0x2ccd,  0x2ccf,  0x2cd1,  0x2cd3,  0x2cd5,  0x2cd7,  0x2cd9,  0x2cdb,  0x2cdd,  0x2cdf,  0x2ce1,  0x2ce3,  0x2cec,  0x2cee,  0x2cf3,  0xa641,  0xa643,  0xa645,  0xa647,  0xa649,  0xa64b,  0xa64d,  0xa64f,  0xa651,  0xa653,  0xa655,  0xa657,  0xa659,  0xa65b,  0xa65d,  0xa65f,  0xa661,  0xa663,  0xa665,  0xa667,  0xa669,  0xa66b,  0xa66d,  0xa681,  0xa683,  0xa685,  0xa687,  0xa689,  0xa68b,  0xa68d,  0xa68f,  0xa691,  0xa693,  0xa695,  0xa697,  0xa723,  0xa725,  0xa727,  0xa729,  0xa72b,  0xa72d,  0xa72f,  0xa733,  0xa735,  0xa737,  0xa739,  0xa73b,  0xa73d,  0xa73f,  0xa741,  0xa743,  0xa745,  0xa747,  0xa749,  0xa74b,  0xa74d,  0xa74f,  0xa751,  0xa753,  0xa755,  0xa757,  0xa759,  0xa75b,  0xa75d,  0xa75f,  0xa761,  0xa763,  0xa765,  0xa767,  0xa769,  0xa76b,  0xa76d,  0xa76f,  0xa77a,  0xa77c,  0x1d79,  0xa77f,  0xa781,  0xa783,  0xa785,  0xa787,  0xa78c,  0x265,  0xa791,  0xa793,  0xa7a1,  0xa7a3,  0xa7a5,  0xa7a7,  0xa7a9,  0x266,  0xff41,  0xff42,  0xff43,  0xff44,  0xff45,  0xff46,  0xff47,  0xff48,  0xff49,  0xff4a,  0xff4b,  0xff4c,  0xff4d,  0xff4e,  0xff4f,  0xff50,  0xff51,  0xff52,  0xff53,  0xff54,  0xff55,  0xff56,  0xff57,  0xff58,  0xff59,  0xff5a,  0x10428,  0x10429,  0x1042a,  0x1042b,  0x1042c,  0x1042d,  0x1042e,  0x1042f,  0x10430,  0x10431,  0x10432,  0x10433,  0x10434,  0x10435,  0x10436,  0x10437,  0x10438,  0x10439,  0x1043a,  0x1043b,  0x1043c,  0x1043d,  0x1043e,  0x1043f,  0x10440,  0x10441,  0x10442,  0x10443,  0x10444,  0x10445,  0x10446,  0x10447,  0x10448,  0x10449,  0x1044a,  0x1044b,  0x1044c,  0x1044d,  0x1044e,  0x1044f,  0xdf,  0x2000069,  0x307,  0xfb00,  0xfb01,  0xfb02,  0xfb03,  0xfb04,  0xfb05,  0xfb06,  0x587,  0xfb13,  0xfb14,  0xfb15,  0xfb16,  0xfb17,  0x149,  0x390,  0x3b0,  0x1f0,  0x1e96,  0x1e97,  0x1e98,  0x1e99,  0x1e9a,  0x1f50,  0x1f52,  0x1f54,  0x1f56,  0x1fb6,  0x1fc6,  0x1fd2,  0x1fd3,  0x1fd6,  0x1fd7,  0x1fe2,  0x1fe3,  0x1fe4,  0x1fe6,  0x1fe7,  0x1ff6,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f80,  0x1f81,  0x1f82,  0x1f83,  0x1f84,  0x1f85,  0x1f86,  0x1f87,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1f90,  0x1f91,  0x1f92,  0x1f93,  0x1f94,  0x1f95,  0x1f96,  0x1f97,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fa0,  0x1fa1,  0x1fa2,  0x1fa3,  0x1fa4,  0x1fa5,  0x1fa6,  0x1fa7,  0x1fb3,  0x1fb3,  0x1fc3,  0x1fc3,  0x1ff3,  0x1ff3,  0x1fb2,  0x1fb4,  0x1fc2,  0x1fc4,  0x1ff2,  0x1ff4,  0x1fb7,  0x1fc7,  0x1ff7]; return t; }
+_IUA toTitleTable() { static _IUA t = [ 0x41,  0x42,  0x43,  0x44,  0x45,  0x46,  0x47,  0x48,  0x49,  0x4a,  0x4b,  0x4c,  0x4d,  0x4e,  0x4f,  0x50,  0x51,  0x52,  0x53,  0x54,  0x55,  0x56,  0x57,  0x58,  0x59,  0x5a,  0x39c,  0xc0,  0xc1,  0xc2,  0xc3,  0xc4,  0xc5,  0xc6,  0xc7,  0xc8,  0xc9,  0xca,  0xcb,  0xcc,  0xcd,  0xce,  0xcf,  0xd0,  0xd1,  0xd2,  0xd3,  0xd4,  0xd5,  0xd6,  0xd8,  0xd9,  0xda,  0xdb,  0xdc,  0xdd,  0xde,  0x178,  0x100,  0x102,  0x104,  0x106,  0x108,  0x10a,  0x10c,  0x10e,  0x110,  0x112,  0x114,  0x116,  0x118,  0x11a,  0x11c,  0x11e,  0x120,  0x122,  0x124,  0x126,  0x128,  0x12a,  0x12c,  0x12e,  0x49,  0x132,  0x134,  0x136,  0x139,  0x13b,  0x13d,  0x13f,  0x141,  0x143,  0x145,  0x147,  0x14a,  0x14c,  0x14e,  0x150,  0x152,  0x154,  0x156,  0x158,  0x15a,  0x15c,  0x15e,  0x160,  0x162,  0x164,  0x166,  0x168,  0x16a,  0x16c,  0x16e,  0x170,  0x172,  0x174,  0x176,  0x179,  0x17b,  0x17d,  0x53,  0x243,  0x182,  0x184,  0x187,  0x18b,  0x191,  0x1f6,  0x198,  0x23d,  0x220,  0x1a0,  0x1a2,  0x1a4,  0x1a7,  0x1ac,  0x1af,  0x1b3,  0x1b5,  0x1b8,  0x1bc,  0x1f7,  0x1c5,  0x1c5,  0x1c5,  0x1c8,  0x1c8,  0x1c8,  0x1cb,  0x1cb,  0x1cb,  0x1cd,  0x1cf,  0x1d1,  0x1d3,  0x1d5,  0x1d7,  0x1d9,  0x1db,  0x18e,  0x1de,  0x1e0,  0x1e2,  0x1e4,  0x1e6,  0x1e8,  0x1ea,  0x1ec,  0x1ee,  0x1f2,  0x1f2,  0x1f2,  0x1f4,  0x1f8,  0x1fa,  0x1fc,  0x1fe,  0x200,  0x202,  0x204,  0x206,  0x208,  0x20a,  0x20c,  0x20e,  0x210,  0x212,  0x214,  0x216,  0x218,  0x21a,  0x21c,  0x21e,  0x222,  0x224,  0x226,  0x228,  0x22a,  0x22c,  0x22e,  0x230,  0x232,  0x23b,  0x2c7e,  0x2c7f,  0x241,  0x246,  0x248,  0x24a,  0x24c,  0x24e,  0x2c6f,  0x2c6d,  0x2c70,  0x181,  0x186,  0x189,  0x18a,  0x18f,  0x190,  0x193,  0x194,  0xa78d,  0xa7aa,  0x197,  0x196,  0x2c62,  0x19c,  0x2c6e,  0x19d,  0x19f,  0x2c64,  0x1a6,  0x1a9,  0x1ae,  0x244,  0x1b1,  0x1b2,  0x245,  0x1b7,  0x399,  0x370,  0x372,  0x376,  0x3fd,  0x3fe,  0x3ff,  0x386,  0x388,  0x389,  0x38a,  0x391,  0x392,  0x393,  0x394,  0x395,  0x396,  0x397,  0x398,  0x399,  0x39a,  0x39b,  0x39c,  0x39d,  0x39e,  0x39f,  0x3a0,  0x3a1,  0x3a3,  0x3a3,  0x3a4,  0x3a5,  0x3a6,  0x3a7,  0x3a8,  0x3a9,  0x3aa,  0x3ab,  0x38c,  0x38e,  0x38f,  0x392,  0x398,  0x3a6,  0x3a0,  0x3cf,  0x3d8,  0x3da,  0x3dc,  0x3de,  0x3e0,  0x3e2,  0x3e4,  0x3e6,  0x3e8,  0x3ea,  0x3ec,  0x3ee,  0x39a,  0x3a1,  0x3f9,  0x395,  0x3f7,  0x3fa,  0x410,  0x411,  0x412,  0x413,  0x414,  0x415,  0x416,  0x417,  0x418,  0x419,  0x41a,  0x41b,  0x41c,  0x41d,  0x41e,  0x41f,  0x420,  0x421,  0x422,  0x423,  0x424,  0x425,  0x426,  0x427,  0x428,  0x429,  0x42a,  0x42b,  0x42c,  0x42d,  0x42e,  0x42f,  0x400,  0x401,  0x402,  0x403,  0x404,  0x405,  0x406,  0x407,  0x408,  0x409,  0x40a,  0x40b,  0x40c,  0x40d,  0x40e,  0x40f,  0x460,  0x462,  0x464,  0x466,  0x468,  0x46a,  0x46c,  0x46e,  0x470,  0x472,  0x474,  0x476,  0x478,  0x47a,  0x47c,  0x47e,  0x480,  0x48a,  0x48c,  0x48e,  0x490,  0x492,  0x494,  0x496,  0x498,  0x49a,  0x49c,  0x49e,  0x4a0,  0x4a2,  0x4a4,  0x4a6,  0x4a8,  0x4aa,  0x4ac,  0x4ae,  0x4b0,  0x4b2,  0x4b4,  0x4b6,  0x4b8,  0x4ba,  0x4bc,  0x4be,  0x4c1,  0x4c3,  0x4c5,  0x4c7,  0x4c9,  0x4cb,  0x4cd,  0x4c0,  0x4d0,  0x4d2,  0x4d4,  0x4d6,  0x4d8,  0x4da,  0x4dc,  0x4de,  0x4e0,  0x4e2,  0x4e4,  0x4e6,  0x4e8,  0x4ea,  0x4ec,  0x4ee,  0x4f0,  0x4f2,  0x4f4,  0x4f6,  0x4f8,  0x4fa,  0x4fc,  0x4fe,  0x500,  0x502,  0x504,  0x506,  0x508,  0x50a,  0x50c,  0x50e,  0x510,  0x512,  0x514,  0x516,  0x518,  0x51a,  0x51c,  0x51e,  0x520,  0x522,  0x524,  0x526,  0x531,  0x532,  0x533,  0x534,  0x535,  0x536,  0x537,  0x538,  0x539,  0x53a,  0x53b,  0x53c,  0x53d,  0x53e,  0x53f,  0x540,  0x541,  0x542,  0x543,  0x544,  0x545,  0x546,  0x547,  0x548,  0x549,  0x54a,  0x54b,  0x54c,  0x54d,  0x54e,  0x54f,  0x550,  0x551,  0x552,  0x553,  0x554,  0x555,  0x556,  0xa77d,  0x2c63,  0x1e00,  0x1e02,  0x1e04,  0x1e06,  0x1e08,  0x1e0a,  0x1e0c,  0x1e0e,  0x1e10,  0x1e12,  0x1e14,  0x1e16,  0x1e18,  0x1e1a,  0x1e1c,  0x1e1e,  0x1e20,  0x1e22,  0x1e24,  0x1e26,  0x1e28,  0x1e2a,  0x1e2c,  0x1e2e,  0x1e30,  0x1e32,  0x1e34,  0x1e36,  0x1e38,  0x1e3a,  0x1e3c,  0x1e3e,  0x1e40,  0x1e42,  0x1e44,  0x1e46,  0x1e48,  0x1e4a,  0x1e4c,  0x1e4e,  0x1e50,  0x1e52,  0x1e54,  0x1e56,  0x1e58,  0x1e5a,  0x1e5c,  0x1e5e,  0x1e60,  0x1e62,  0x1e64,  0x1e66,  0x1e68,  0x1e6a,  0x1e6c,  0x1e6e,  0x1e70,  0x1e72,  0x1e74,  0x1e76,  0x1e78,  0x1e7a,  0x1e7c,  0x1e7e,  0x1e80,  0x1e82,  0x1e84,  0x1e86,  0x1e88,  0x1e8a,  0x1e8c,  0x1e8e,  0x1e90,  0x1e92,  0x1e94,  0x1e60,  0x1ea0,  0x1ea2,  0x1ea4,  0x1ea6,  0x1ea8,  0x1eaa,  0x1eac,  0x1eae,  0x1eb0,  0x1eb2,  0x1eb4,  0x1eb6,  0x1eb8,  0x1eba,  0x1ebc,  0x1ebe,  0x1ec0,  0x1ec2,  0x1ec4,  0x1ec6,  0x1ec8,  0x1eca,  0x1ecc,  0x1ece,  0x1ed0,  0x1ed2,  0x1ed4,  0x1ed6,  0x1ed8,  0x1eda,  0x1edc,  0x1ede,  0x1ee0,  0x1ee2,  0x1ee4,  0x1ee6,  0x1ee8,  0x1eea,  0x1eec,  0x1eee,  0x1ef0,  0x1ef2,  0x1ef4,  0x1ef6,  0x1ef8,  0x1efa,  0x1efc,  0x1efe,  0x1f08,  0x1f09,  0x1f0a,  0x1f0b,  0x1f0c,  0x1f0d,  0x1f0e,  0x1f0f,  0x1f18,  0x1f19,  0x1f1a,  0x1f1b,  0x1f1c,  0x1f1d,  0x1f28,  0x1f29,  0x1f2a,  0x1f2b,  0x1f2c,  0x1f2d,  0x1f2e,  0x1f2f,  0x1f38,  0x1f39,  0x1f3a,  0x1f3b,  0x1f3c,  0x1f3d,  0x1f3e,  0x1f3f,  0x1f48,  0x1f49,  0x1f4a,  0x1f4b,  0x1f4c,  0x1f4d,  0x1f59,  0x1f5b,  0x1f5d,  0x1f5f,  0x1f68,  0x1f69,  0x1f6a,  0x1f6b,  0x1f6c,  0x1f6d,  0x1f6e,  0x1f6f,  0x1fba,  0x1fbb,  0x1fc8,  0x1fc9,  0x1fca,  0x1fcb,  0x1fda,  0x1fdb,  0x1ff8,  0x1ff9,  0x1fea,  0x1feb,  0x1ffa,  0x1ffb,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fb8,  0x1fb9,  0x1fbc,  0x399,  0x1fcc,  0x1fd8,  0x1fd9,  0x1fe8,  0x1fe9,  0x1fec,  0x1ffc,  0x2132,  0x2160,  0x2161,  0x2162,  0x2163,  0x2164,  0x2165,  0x2166,  0x2167,  0x2168,  0x2169,  0x216a,  0x216b,  0x216c,  0x216d,  0x216e,  0x216f,  0x2183,  0x24b6,  0x24b7,  0x24b8,  0x24b9,  0x24ba,  0x24bb,  0x24bc,  0x24bd,  0x24be,  0x24bf,  0x24c0,  0x24c1,  0x24c2,  0x24c3,  0x24c4,  0x24c5,  0x24c6,  0x24c7,  0x24c8,  0x24c9,  0x24ca,  0x24cb,  0x24cc,  0x24cd,  0x24ce,  0x24cf,  0x2c00,  0x2c01,  0x2c02,  0x2c03,  0x2c04,  0x2c05,  0x2c06,  0x2c07,  0x2c08,  0x2c09,  0x2c0a,  0x2c0b,  0x2c0c,  0x2c0d,  0x2c0e,  0x2c0f,  0x2c10,  0x2c11,  0x2c12,  0x2c13,  0x2c14,  0x2c15,  0x2c16,  0x2c17,  0x2c18,  0x2c19,  0x2c1a,  0x2c1b,  0x2c1c,  0x2c1d,  0x2c1e,  0x2c1f,  0x2c20,  0x2c21,  0x2c22,  0x2c23,  0x2c24,  0x2c25,  0x2c26,  0x2c27,  0x2c28,  0x2c29,  0x2c2a,  0x2c2b,  0x2c2c,  0x2c2d,  0x2c2e,  0x2c60,  0x23a,  0x23e,  0x2c67,  0x2c69,  0x2c6b,  0x2c72,  0x2c75,  0x2c80,  0x2c82,  0x2c84,  0x2c86,  0x2c88,  0x2c8a,  0x2c8c,  0x2c8e,  0x2c90,  0x2c92,  0x2c94,  0x2c96,  0x2c98,  0x2c9a,  0x2c9c,  0x2c9e,  0x2ca0,  0x2ca2,  0x2ca4,  0x2ca6,  0x2ca8,  0x2caa,  0x2cac,  0x2cae,  0x2cb0,  0x2cb2,  0x2cb4,  0x2cb6,  0x2cb8,  0x2cba,  0x2cbc,  0x2cbe,  0x2cc0,  0x2cc2,  0x2cc4,  0x2cc6,  0x2cc8,  0x2cca,  0x2ccc,  0x2cce,  0x2cd0,  0x2cd2,  0x2cd4,  0x2cd6,  0x2cd8,  0x2cda,  0x2cdc,  0x2cde,  0x2ce0,  0x2ce2,  0x2ceb,  0x2ced,  0x2cf2,  0x10a0,  0x10a1,  0x10a2,  0x10a3,  0x10a4,  0x10a5,  0x10a6,  0x10a7,  0x10a8,  0x10a9,  0x10aa,  0x10ab,  0x10ac,  0x10ad,  0x10ae,  0x10af,  0x10b0,  0x10b1,  0x10b2,  0x10b3,  0x10b4,  0x10b5,  0x10b6,  0x10b7,  0x10b8,  0x10b9,  0x10ba,  0x10bb,  0x10bc,  0x10bd,  0x10be,  0x10bf,  0x10c0,  0x10c1,  0x10c2,  0x10c3,  0x10c4,  0x10c5,  0x10c7,  0x10cd,  0xa640,  0xa642,  0xa644,  0xa646,  0xa648,  0xa64a,  0xa64c,  0xa64e,  0xa650,  0xa652,  0xa654,  0xa656,  0xa658,  0xa65a,  0xa65c,  0xa65e,  0xa660,  0xa662,  0xa664,  0xa666,  0xa668,  0xa66a,  0xa66c,  0xa680,  0xa682,  0xa684,  0xa686,  0xa688,  0xa68a,  0xa68c,  0xa68e,  0xa690,  0xa692,  0xa694,  0xa696,  0xa722,  0xa724,  0xa726,  0xa728,  0xa72a,  0xa72c,  0xa72e,  0xa732,  0xa734,  0xa736,  0xa738,  0xa73a,  0xa73c,  0xa73e,  0xa740,  0xa742,  0xa744,  0xa746,  0xa748,  0xa74a,  0xa74c,  0xa74e,  0xa750,  0xa752,  0xa754,  0xa756,  0xa758,  0xa75a,  0xa75c,  0xa75e,  0xa760,  0xa762,  0xa764,  0xa766,  0xa768,  0xa76a,  0xa76c,  0xa76e,  0xa779,  0xa77b,  0xa77e,  0xa780,  0xa782,  0xa784,  0xa786,  0xa78b,  0xa790,  0xa792,  0xa7a0,  0xa7a2,  0xa7a4,  0xa7a6,  0xa7a8,  0xff21,  0xff22,  0xff23,  0xff24,  0xff25,  0xff26,  0xff27,  0xff28,  0xff29,  0xff2a,  0xff2b,  0xff2c,  0xff2d,  0xff2e,  0xff2f,  0xff30,  0xff31,  0xff32,  0xff33,  0xff34,  0xff35,  0xff36,  0xff37,  0xff38,  0xff39,  0xff3a,  0x10400,  0x10401,  0x10402,  0x10403,  0x10404,  0x10405,  0x10406,  0x10407,  0x10408,  0x10409,  0x1040a,  0x1040b,  0x1040c,  0x1040d,  0x1040e,  0x1040f,  0x10410,  0x10411,  0x10412,  0x10413,  0x10414,  0x10415,  0x10416,  0x10417,  0x10418,  0x10419,  0x1041a,  0x1041b,  0x1041c,  0x1041d,  0x1041e,  0x1041f,  0x10420,  0x10421,  0x10422,  0x10423,  0x10424,  0x10425,  0x10426,  0x10427,  0x2000053,  0x73,  0x130,  0x2000046,  0x66,  0x2000046,  0x69,  0x2000046,  0x6c,  0x3000046,  0x66,  0x69,  0x3000046,  0x66,  0x6c,  0x2000053,  0x74,  0x2000053,  0x74,  0x2000535,  0x582,  0x2000544,  0x576,  0x2000544,  0x565,  0x2000544,  0x56b,  0x200054e,  0x576,  0x2000544,  0x56d,  0x20002bc,  0x4e,  0x3000399,  0x308,  0x301,  0x30003a5,  0x308,  0x301,  0x200004a,  0x30c,  0x2000048,  0x331,  0x2000054,  0x308,  0x2000057,  0x30a,  0x2000059,  0x30a,  0x2000041,  0x2be,  0x20003a5,  0x313,  0x30003a5,  0x313,  0x300,  0x30003a5,  0x313,  0x301,  0x30003a5,  0x313,  0x342,  0x2000391,  0x342,  0x2000397,  0x342,  0x3000399,  0x308,  0x300,  0x3000399,  0x308,  0x301,  0x2000399,  0x342,  0x3000399,  0x308,  0x342,  0x30003a5,  0x308,  0x300,  0x30003a5,  0x308,  0x301,  0x20003a1,  0x313,  0x20003a5,  0x342,  0x30003a5,  0x308,  0x342,  0x20003a9,  0x342,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f88,  0x1f89,  0x1f8a,  0x1f8b,  0x1f8c,  0x1f8d,  0x1f8e,  0x1f8f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1f98,  0x1f99,  0x1f9a,  0x1f9b,  0x1f9c,  0x1f9d,  0x1f9e,  0x1f9f,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fa8,  0x1fa9,  0x1faa,  0x1fab,  0x1fac,  0x1fad,  0x1fae,  0x1faf,  0x1fbc,  0x1fbc,  0x1fcc,  0x1fcc,  0x1ffc,  0x1ffc,  0x2001fba,  0x345,  0x2000386,  0x345,  0x2001fca,  0x345,  0x2000389,  0x345,  0x2001ffa,  0x345,  0x200038f,  0x345,  0x3000391,  0x342,  0x345,  0x3000397,  0x342,  0x345,  0x30003a9,  0x342,  0x345]; return t; }
+}
+
+}
+
diff --git a/libphobos/src/std/internal/windows/advapi32.d b/libphobos/src/std/internal/windows/advapi32.d
new file mode 100644
index 00000000000..1c155101011
--- /dev/null
+++ b/libphobos/src/std/internal/windows/advapi32.d
@@ -0,0 +1,69 @@
+// Written in the D programming language.
+
+/**
+ * The only purpose of this module is to do the static construction for
+ * std.windows.registry, to eliminate cyclic construction errors.
+ *
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Kenji Hara
+ * Source:    $(PHOBOSSRC std/internal/windows/_advapi32.d)
+ */
+module std.internal.windows.advapi32;
+
+version(Windows):
+
+private import core.sys.windows.windows;
+
+version(GNU) {}
+else pragma(lib, "advapi32.lib");
+
+immutable bool isWow64;
+
+shared static this()
+{
+    // WOW64 is the x86 emulator that allows 32-bit Windows-based applications to run seamlessly on 64-bit Windows
+    // IsWow64Process Function - Minimum supported client - Windows Vista, Windows XP with SP2
+    alias fptr_t = extern(Windows) BOOL function(HANDLE, PBOOL);
+    auto hKernel = GetModuleHandleA("kernel32");
+    auto IsWow64Process = cast(fptr_t) GetProcAddress(hKernel, "IsWow64Process");
+    BOOL bIsWow64;
+    isWow64 = IsWow64Process && IsWow64Process(GetCurrentProcess(), &bIsWow64) && bIsWow64;
+}
+
+HMODULE hAdvapi32 = null;
+extern (Windows)
+{
+    LONG function(in HKEY hkey, in LPCWSTR lpSubKey, in REGSAM samDesired, in DWORD reserved) pRegDeleteKeyExW;
+}
+
+void loadAdvapi32()
+{
+    if (!hAdvapi32)
+    {
+        hAdvapi32 = LoadLibraryA("Advapi32.dll");
+        if (!hAdvapi32)
+            throw new Exception(`LoadLibraryA("Advapi32.dll")`);
+
+        pRegDeleteKeyExW = cast(typeof(pRegDeleteKeyExW)) GetProcAddress(hAdvapi32 , "RegDeleteKeyExW");
+        if (!pRegDeleteKeyExW)
+            throw new Exception(`GetProcAddress(hAdvapi32 , "RegDeleteKeyExW")`);
+    }
+}
+
+// It will free Advapi32.dll, which may be loaded for RegDeleteKeyEx function
+private void freeAdvapi32()
+{
+    if (hAdvapi32)
+    {
+        if (!FreeLibrary(hAdvapi32))
+            throw new Exception(`FreeLibrary("Advapi32.dll")`);
+        hAdvapi32 = null;
+
+        pRegDeleteKeyExW = null;
+    }
+}
+
+static ~this()
+{
+    freeAdvapi32();
+}
diff --git a/libphobos/src/std/json.d b/libphobos/src/std/json.d
new file mode 100644
index 00000000000..aac536874af
--- /dev/null
+++ b/libphobos/src/std/json.d
@@ -0,0 +1,1586 @@
+// Written in the D programming language.
+
+/**
+JavaScript Object Notation
+
+Synopsis:
+----
+    //parse a file or string of json into a usable structure
+    string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }";
+    JSONValue j = parseJSON(s);
+    writeln("Language: ", j["language"].str(),
+            " Rating: ", j["rating"].floating()
+    );
+
+    // j and j["language"] return JSONValue,
+    // j["language"].str returns a string
+
+    //check a type
+    long x;
+    if (const(JSONValue)* code = "code" in j)
+    {
+        if (code.type() == JSON_TYPE.INTEGER)
+            x = code.integer;
+        else
+            x = to!int(code.str);
+    }
+
+    // create a json struct
+    JSONValue jj = [ "language": "D" ];
+    // rating doesnt exist yet, so use .object to assign
+    jj.object["rating"] = JSONValue(3.14);
+    // create an array to assign to list
+    jj.object["list"] = JSONValue( ["a", "b", "c"] );
+    // list already exists, so .object optional
+    jj["list"].array ~= JSONValue("D");
+
+    s = j.toString();
+    writeln(s);
+----
+
+Copyright: Copyright Jeremie Pelletier 2008 - 2009.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   Jeremie Pelletier, David Herberth
+References: $(LINK http://json.org/)
+Source:    $(PHOBOSSRC std/_json.d)
+*/
+/*
+         Copyright Jeremie Pelletier 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.json;
+
+import std.conv;
+import std.range.primitives;
+import std.array;
+import std.traits;
+
+/**
+String literals used to represent special float values within JSON strings.
+*/
+enum JSONFloatLiteral : string
+{
+    nan         = "NaN",       /// string representation of floating-point NaN
+    inf         = "Infinite",  /// string representation of floating-point Infinity
+    negativeInf = "-Infinite", /// string representation of floating-point negative Infinity
+}
+
+/**
+Flags that control how json is encoded and parsed.
+*/
+enum JSONOptions {
+    none,                       /// standard parsing
+    specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings
+}
+
+/**
+JSON type enumeration
+*/
+enum JSON_TYPE : byte
+{
+    /// Indicates the type of a $(D JSONValue).
+    NULL,
+    STRING,  /// ditto
+    INTEGER, /// ditto
+    UINTEGER,/// ditto
+    FLOAT,   /// ditto
+    OBJECT,  /// ditto
+    ARRAY,   /// ditto
+    TRUE,    /// ditto
+    FALSE    /// ditto
+}
+
+/**
+JSON value node
+*/
+struct JSONValue
+{
+    import std.exception : enforceEx, enforce;
+
+    union Store
+    {
+        string                          str;
+        long                            integer;
+        ulong                           uinteger;
+        double                          floating;
+        JSONValue[string]               object;
+        JSONValue[]                     array;
+    }
+    private Store store;
+    private JSON_TYPE type_tag;
+
+    /**
+      Returns the JSON_TYPE of the value stored in this structure.
+    */
+    @property JSON_TYPE type() const pure nothrow @safe @nogc
+    {
+        return type_tag;
+    }
+    ///
+    unittest
+    {
+          string s = "{ \"language\": \"D\" }";
+          JSONValue j = parseJSON(s);
+          assert(j.type == JSON_TYPE.OBJECT);
+          assert(j["language"].type == JSON_TYPE.STRING);
+    }
+
+    // Explicitly undocumented. It will be removed in June 2016. @@@DEPRECATED_2016-06@@@
+    deprecated("Please assign the value with the adequate type to JSONValue directly.")
+    @property JSON_TYPE type(JSON_TYPE newType)
+    {
+        if (type_tag != newType
+         && ((type_tag != JSON_TYPE.INTEGER && type_tag != JSON_TYPE.UINTEGER)
+          || (newType  != JSON_TYPE.INTEGER && newType  != JSON_TYPE.UINTEGER)))
+        {
+            final switch (newType)
+            {
+                case JSON_TYPE.STRING:
+                    store.str = store.str.init;
+                    break;
+                case JSON_TYPE.INTEGER:
+                    store.integer = store.integer.init;
+                    break;
+                case JSON_TYPE.UINTEGER:
+                    store.uinteger = store.uinteger.init;
+                    break;
+                case JSON_TYPE.FLOAT:
+                    store.floating = store.floating.init;
+                    break;
+                case JSON_TYPE.OBJECT:
+                    store.object = store.object.init;
+                    break;
+                case JSON_TYPE.ARRAY:
+                    store.array = store.array.init;
+                    break;
+                case JSON_TYPE.TRUE:
+                case JSON_TYPE.FALSE:
+                case JSON_TYPE.NULL:
+                    break;
+            }
+        }
+        return type_tag = newType;
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.STRING).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.STRING).
+    @property inout(string) str() inout pure
+    {
+        enforce!JSONException(type == JSON_TYPE.STRING,
+                                "JSONValue is not a string");
+        return store.str;
+    }
+    /// ditto
+    @property string str(string v) pure nothrow @nogc
+    {
+        assign(v);
+        return store.str;
+    }
+    ///
+    unittest
+    {
+        JSONValue j = [ "language": "D" ];
+
+        // get value
+        assert(j["language"].str == "D");
+
+        // change existing key to new string
+        j["language"].str = "Perl";
+        assert(j["language"].str == "Perl");
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.INTEGER).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.INTEGER).
+    @property inout(long) integer() inout pure @safe
+    {
+        enforce!JSONException(type == JSON_TYPE.INTEGER,
+                                "JSONValue is not an integer");
+        return store.integer;
+    }
+    /// ditto
+    @property long integer(long v) pure nothrow @safe @nogc
+    {
+        assign(v);
+        return store.integer;
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.UINTEGER).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.UINTEGER).
+    @property inout(ulong) uinteger() inout pure @safe
+    {
+        enforce!JSONException(type == JSON_TYPE.UINTEGER,
+                                "JSONValue is not an unsigned integer");
+        return store.uinteger;
+    }
+    /// ditto
+    @property ulong uinteger(ulong v) pure nothrow @safe @nogc
+    {
+        assign(v);
+        return store.uinteger;
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.FLOAT).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.FLOAT).
+    @property inout(double) floating() inout pure @safe
+    {
+        enforce!JSONException(type == JSON_TYPE.FLOAT,
+                                "JSONValue is not a floating type");
+        return store.floating;
+    }
+    /// ditto
+    @property double floating(double v) pure nothrow @safe @nogc
+    {
+        assign(v);
+        return store.floating;
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.OBJECT).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.OBJECT).
+    @property ref inout(JSONValue[string]) object() inout pure
+    {
+        enforce!JSONException(type == JSON_TYPE.OBJECT,
+                                "JSONValue is not an object");
+        return store.object;
+    }
+    /// ditto
+    @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc
+    {
+        assign(v);
+        return store.object;
+    }
+
+    /// Value getter/setter for $(D JSON_TYPE.ARRAY).
+    /// Throws: $(D JSONException) for read access if $(D type) is not
+    /// $(D JSON_TYPE.ARRAY).
+    @property ref inout(JSONValue[]) array() inout pure
+    {
+        enforce!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        return store.array;
+    }
+    /// ditto
+    @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc
+    {
+        assign(v);
+        return store.array;
+    }
+
+    /// Test whether the type is $(D JSON_TYPE.NULL)
+    @property bool isNull() const pure nothrow @safe @nogc
+    {
+        return type == JSON_TYPE.NULL;
+    }
+
+    private void assign(T)(T arg)
+    {
+        static if(is(T : typeof(null)))
+        {
+            type_tag = JSON_TYPE.NULL;
+        }
+        else static if(is(T : string))
+        {
+            type_tag = JSON_TYPE.STRING;
+            store.str = arg;
+        }
+        else static if(is(T : bool))
+        {
+            type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE;
+        }
+        else static if(is(T : ulong) && isUnsigned!T)
+        {
+            type_tag = JSON_TYPE.UINTEGER;
+            store.uinteger = arg;
+        }
+        else static if(is(T : long))
+        {
+            type_tag = JSON_TYPE.INTEGER;
+            store.integer = arg;
+        }
+        else static if(isFloatingPoint!T)
+        {
+            type_tag = JSON_TYPE.FLOAT;
+            store.floating = arg;
+        }
+        else static if(is(T : Value[Key], Key, Value))
+        {
+            static assert(is(Key : string), "AA key must be string");
+            type_tag = JSON_TYPE.OBJECT;
+            static if(is(Value : JSONValue)) {
+                store.object = arg;
+            }
+            else
+            {
+                JSONValue[string] aa;
+                foreach(key, value; arg)
+                    aa[key] = JSONValue(value);
+                store.object = aa;
+            }
+        }
+        else static if(isArray!T)
+        {
+            type_tag = JSON_TYPE.ARRAY;
+            static if(is(ElementEncodingType!T : JSONValue))
+            {
+                store.array = arg;
+            }
+            else
+            {
+                JSONValue[] new_arg = new JSONValue[arg.length];
+                foreach(i, e; arg)
+                    new_arg[i] = JSONValue(e);
+                store.array = new_arg;
+            }
+        }
+        else static if(is(T : JSONValue))
+        {
+            type_tag = arg.type;
+            store = arg.store;
+        }
+        else
+        {
+            static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
+        }
+    }
+
+    private void assignRef(T)(ref T arg) if(isStaticArray!T)
+    {
+        type_tag = JSON_TYPE.ARRAY;
+        static if(is(ElementEncodingType!T : JSONValue))
+        {
+            store.array = arg;
+        }
+        else
+        {
+            JSONValue[] new_arg = new JSONValue[arg.length];
+            foreach(i, e; arg)
+                new_arg[i] = JSONValue(e);
+            store.array = new_arg;
+        }
+    }
+
+    /**
+     * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue)
+     * its value and type will be copied to the new $(D JSONValue).
+     * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT)
+     * or $(D JSON_TYPE.ARRAY) then only the reference to the data will
+     * be copied.
+     * Otherwise, $(D arg) must be implicitly convertible to one of the
+     * following types: $(D typeof(null)), $(D string), $(D ulong),
+     * $(D long), $(D double), an associative array $(D V[K]) for any $(D V)
+     * and $(D K) i.e. a JSON object, any array or $(D bool). The type will
+     * be set accordingly.
+    */
+    this(T)(T arg) if(!isStaticArray!T)
+    {
+        assign(arg);
+    }
+    /// Ditto
+    this(T)(ref T arg) if(isStaticArray!T)
+    {
+        assignRef(arg);
+    }
+    /// Ditto
+    this(T : JSONValue)(inout T arg) inout
+    {
+        store = arg.store;
+        type_tag = arg.type;
+    }
+    ///
+    unittest
+    {
+        JSONValue j = JSONValue( "a string" );
+        j = JSONValue(42);
+
+        j = JSONValue( [1, 2, 3] );
+        assert(j.type == JSON_TYPE.ARRAY);
+
+        j = JSONValue( ["language": "D"] );
+        assert(j.type == JSON_TYPE.OBJECT);
+    }
+
+    void opAssign(T)(T arg) if(!isStaticArray!T && !is(T : JSONValue))
+    {
+        assign(arg);
+    }
+
+    void opAssign(T)(ref T arg) if(isStaticArray!T)
+    {
+        assignRef(arg);
+    }
+
+    /// Array syntax for json arrays.
+    /// Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY).
+    ref inout(JSONValue) opIndex(size_t i) inout pure
+    {
+        enforce!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        enforceEx!JSONException(i < store.array.length,
+                                "JSONValue array index is out of range");
+        return store.array[i];
+    }
+    ///
+    unittest
+    {
+        JSONValue j = JSONValue( [42, 43, 44] );
+        assert( j[0].integer == 42 );
+        assert( j[1].integer == 43 );
+    }
+
+    /// Hash syntax for json objects.
+    /// Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT).
+    ref inout(JSONValue) opIndex(string k) inout pure
+    {
+        enforce!JSONException(type == JSON_TYPE.OBJECT,
+                                "JSONValue is not an object");
+        return *enforce!JSONException(k in store.object,
+                                        "Key not found: " ~ k);
+    }
+    ///
+    unittest
+    {
+        JSONValue j = JSONValue( ["language": "D"] );
+        assert( j["language"].str == "D" );
+    }
+
+    /// Operator sets $(D value) for element of JSON object by $(D key).
+    ///
+    /// If JSON value is null, then operator initializes it with object and then
+    /// sets $(D value) for it.
+    ///
+    /// Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT)
+    /// or $(D JSON_TYPE.NULL).
+    void opIndexAssign(T)(auto ref T value, string key) pure
+    {
+        enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL,
+                                "JSONValue must be object or null");
+
+        if(type == JSON_TYPE.NULL)
+            this = (JSONValue[string]).init;
+
+        store.object[key] = value;
+    }
+    ///
+    unittest
+    {
+            JSONValue j = JSONValue( ["language": "D"] );
+            j["language"].str = "Perl";
+            assert( j["language"].str == "Perl" );
+    }
+
+    void opIndexAssign(T)(T arg, size_t i) pure
+    {
+        enforceEx!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        enforceEx!JSONException(i < store.array.length,
+                                "JSONValue array index is out of range");
+        store.array[i] = arg;
+    }
+    ///
+    unittest
+    {
+            JSONValue j = JSONValue( ["Perl", "C"] );
+            j[1].str = "D";
+            assert( j[1].str == "D" );
+    }
+
+    JSONValue opBinary(string op : "~", T)(T arg)
+    {
+        enforceEx!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        static if(isArray!T)
+        {
+            JSONValue newArray = JSONValue(this.store.array.dup);
+            newArray.store.array ~= JSONValue(arg).store.array;
+            return newArray;
+        }
+        else static if(is(T : JSONValue))
+        {
+            enforceEx!JSONException(arg.type == JSON_TYPE.ARRAY,
+                                    "JSONValue is not an array");
+            JSONValue newArray = JSONValue(this.store.array.dup);
+            newArray.store.array ~= arg.store.array;
+            return newArray;
+        }
+        else
+        {
+            static assert(false, "argument is not an array or a JSONValue array");
+        }
+    }
+
+    void opOpAssign(string op : "~", T)(T arg)
+    {
+        enforceEx!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        static if(isArray!T)
+        {
+            store.array ~= JSONValue(arg).store.array;
+        }
+        else static if(is(T : JSONValue))
+        {
+            enforceEx!JSONException(arg.type == JSON_TYPE.ARRAY,
+                                    "JSONValue is not an array");
+            store.array ~= arg.store.array;
+        }
+        else
+        {
+            static assert(false, "argument is not an array or a JSONValue array");
+        }
+    }
+
+    /**
+     * Support for the $(D in) operator.
+     *
+     * Tests wether a key can be found in an object.
+     *
+     * Returns:
+     *      when found, the $(D const(JSONValue)*) that matches to the key,
+     *      otherwise $(D null).
+     *
+     * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE)
+     * is not $(D OBJECT).
+     */
+    auto opBinaryRight(string op : "in")(string k) const
+    {
+        enforce!JSONException(type == JSON_TYPE.OBJECT,
+                                "JSONValue is not an object");
+        return k in store.object;
+    }
+    ///
+    unittest
+    {
+        JSONValue j = [ "language": "D", "author": "walter" ];
+        string a = ("author" in j).str;
+    }
+
+    bool opEquals(const JSONValue rhs) const pure nothrow @nogc
+    {
+        return opEquals(rhs);
+    }
+
+    bool opEquals(ref const JSONValue rhs) const pure nothrow @nogc
+    {
+        // Default doesn't work well since store is a union.  Compare only
+        // what should be in store.
+        if (type_tag != rhs.type_tag) return false;
+
+        final switch (type_tag)
+        {
+        case JSON_TYPE.STRING:
+            return store.str == rhs.store.str;
+        case JSON_TYPE.INTEGER:
+            return store.integer == rhs.store.integer;
+        case JSON_TYPE.UINTEGER:
+            return store.uinteger == rhs.store.uinteger;
+        case JSON_TYPE.FLOAT:
+            return store.floating == rhs.store.floating;
+        case JSON_TYPE.OBJECT:
+            return store.object == rhs.store.object;
+        case JSON_TYPE.ARRAY:
+            return store.array == rhs.store.array;
+        case JSON_TYPE.TRUE:
+        case JSON_TYPE.FALSE:
+        case JSON_TYPE.NULL:
+            return true;
+        }
+    }
+
+    /// Implements the foreach $(D opApply) interface for json arrays.
+    int opApply(int delegate(size_t index, ref JSONValue) dg)
+    {
+        enforce!JSONException(type == JSON_TYPE.ARRAY,
+                                "JSONValue is not an array");
+        int result;
+
+        foreach(size_t index, ref value; store.array)
+        {
+            result = dg(index, value);
+            if(result)
+                break;
+        }
+
+        return result;
+    }
+
+    /// Implements the foreach $(D opApply) interface for json objects.
+    int opApply(int delegate(string key, ref JSONValue) dg)
+    {
+        enforce!JSONException(type == JSON_TYPE.OBJECT,
+                                "JSONValue is not an object");
+        int result;
+
+        foreach(string key, ref value; store.object)
+        {
+            result = dg(key, value);
+            if(result)
+                break;
+        }
+
+        return result;
+    }
+
+    /// Implicitly calls $(D toJSON) on this JSONValue.
+    ///
+    /// $(I options) can be used to tweak the conversion behavior.
+    string toString(in JSONOptions options = JSONOptions.none) const
+    {
+        return toJSON(&this, false, options);
+    }
+
+    /// Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
+    /// also passes $(I true) as $(I pretty) argument.
+    ///
+    /// $(I options) can be used to tweak the conversion behavior
+    string toPrettyString(in JSONOptions options = JSONOptions.none) const
+    {
+        return toJSON(&this, true, options);
+    }
+}
+
+/**
+Parses a serialized string and returns a tree of JSON values.
+Throws: $(XREF json,JSONException) if the depth exceeds the max depth.
+Params:
+    json = json-formatted string to parse
+    maxDepth = maximum depth of nesting allowed, -1 disables depth checking
+    options = enable decoding string representations of NaN/Inf as float values
+*/
+JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
+if(isInputRange!T)
+{
+    import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
+    import std.utf : toUTF8;
+
+    JSONValue root = void;
+    root.type_tag = JSON_TYPE.NULL;
+
+    if(json.empty) return root;
+
+    int depth = -1;
+    dchar next = 0;
+    int line = 1, pos = 0;
+
+    void error(string msg)
+    {
+        throw new JSONException(msg, line, pos);
+    }
+
+    dchar popChar()
+    {
+        if (json.empty) error("Unexpected end of data.");
+        dchar c = json.front;
+        json.popFront();
+
+        if(c == '\n')
+        {
+            line++;
+            pos = 0;
+        }
+        else
+        {
+            pos++;
+        }
+
+        return c;
+    }
+
+    dchar peekChar()
+    {
+        if(!next)
+        {
+            if(json.empty) return '\0';
+            next = popChar();
+        }
+        return next;
+    }
+
+    void skipWhitespace()
+    {
+        while(isWhite(peekChar())) next = 0;
+    }
+
+    dchar getChar(bool SkipWhitespace = false)()
+    {
+        static if(SkipWhitespace) skipWhitespace();
+
+        dchar c = void;
+        if(next)
+        {
+            c = next;
+            next = 0;
+        }
+        else
+            c = popChar();
+
+        return c;
+    }
+
+    void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
+    {
+        static if(SkipWhitespace) skipWhitespace();
+        auto c2 = getChar();
+        static if(!CaseSensitive) c2 = toLower(c2);
+
+        if(c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
+    }
+
+    bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
+    {
+        static if(SkipWhitespace) skipWhitespace();
+        auto c2 = peekChar();
+        static if (!CaseSensitive) c2 = toLower(c2);
+
+        if(c2 != c) return false;
+
+        getChar();
+        return true;
+    }
+
+    string parseString()
+    {
+        auto str = appender!string();
+
+    Next:
+        switch(peekChar())
+        {
+            case '"':
+                getChar();
+                break;
+
+            case '\\':
+                getChar();
+                auto c = getChar();
+                switch(c)
+                {
+                    case '"':       str.put('"');   break;
+                    case '\\':      str.put('\\');  break;
+                    case '/':       str.put('/');   break;
+                    case 'b':       str.put('\b');  break;
+                    case 'f':       str.put('\f');  break;
+                    case 'n':       str.put('\n');  break;
+                    case 'r':       str.put('\r');  break;
+                    case 't':       str.put('\t');  break;
+                    case 'u':
+                        dchar val = 0;
+                        foreach_reverse(i; 0 .. 4)
+                        {
+                            auto hex = toUpper(getChar());
+                            if(!isHexDigit(hex)) error("Expecting hex character");
+                            val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
+                        }
+                        char[4] buf = void;
+                        str.put(toUTF8(buf, val));
+                        break;
+
+                    default:
+                        error(text("Invalid escape sequence '\\", c, "'."));
+                }
+                goto Next;
+
+            default:
+                auto c = getChar();
+                appendJSONChar(&str, c, &error);
+                goto Next;
+        }
+
+        return str.data.length ? str.data : "";
+    }
+
+    bool tryGetSpecialFloat(string str, out double val) {
+        switch(str) {
+            case JSONFloatLiteral.nan:
+                val = double.nan;
+                return true;
+            case JSONFloatLiteral.inf:
+                val = double.infinity;
+                return true;
+            case JSONFloatLiteral.negativeInf:
+                val = -double.infinity;
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    void parseValue(out JSONValue value)
+    {
+        depth++;
+
+        if(maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
+
+        auto c = getChar!true();
+
+        switch(c)
+        {
+            case '{':
+                value.type_tag = JSON_TYPE.OBJECT;
+                value.store.object = null;
+
+                if(testChar('}')) break;
+
+                do
+                {
+                    checkChar('"');
+                    string name = parseString();
+                    checkChar(':');
+                    JSONValue member = void;
+                    parseValue(member);
+                    value.store.object[name] = member;
+                }
+                while(testChar(','));
+
+                checkChar('}');
+                break;
+
+            case '[':
+                value.type_tag = JSON_TYPE.ARRAY;
+
+                if(testChar(']'))
+                {
+                    value.store.array = cast(JSONValue[]) "";
+                    break;
+                }
+
+                value.store.array = null;
+
+                do
+                {
+                    JSONValue element = void;
+                    parseValue(element);
+                    value.store.array ~= element;
+                }
+                while(testChar(','));
+
+                checkChar(']');
+                break;
+
+            case '"':
+                auto str = parseString();
+
+                // if special float parsing is enabled, check if string represents NaN/Inf
+                if ((options & JSONOptions.specialFloatLiterals) &&
+                    tryGetSpecialFloat(str, value.store.floating))
+                {
+                    // found a special float, its value was placed in value.store.floating
+                    value.type_tag = JSON_TYPE.FLOAT;
+                    break;
+                }
+
+                value.type_tag = JSON_TYPE.STRING;
+                value.store.str = str;
+                break;
+
+            case '0': .. case '9':
+            case '-':
+                auto number = appender!string();
+                bool isFloat, isNegative;
+
+                void readInteger()
+                {
+                    if(!isDigit(c)) error("Digit expected");
+
+                Next: number.put(c);
+
+                    if(isDigit(peekChar()))
+                    {
+                        c = getChar();
+                        goto Next;
+                    }
+                }
+
+                if(c == '-')
+                {
+                    number.put('-');
+                    c = getChar();
+                    isNegative = true;
+                }
+
+                readInteger();
+
+                if(testChar('.'))
+                {
+                    isFloat = true;
+                    number.put('.');
+                    c = getChar();
+                    readInteger();
+                }
+                if(testChar!(false, false)('e'))
+                {
+                    isFloat = true;
+                    number.put('e');
+                    if(testChar('+')) number.put('+');
+                    else if(testChar('-')) number.put('-');
+                    c = getChar();
+                    readInteger();
+                }
+
+                string data = number.data;
+                if(isFloat)
+                {
+                    value.type_tag = JSON_TYPE.FLOAT;
+                    value.store.floating = parse!double(data);
+                }
+                else
+                {
+                    if (isNegative)
+                        value.store.integer = parse!long(data);
+                    else
+                        value.store.uinteger = parse!ulong(data);
+
+                    value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ? JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER;
+                }
+                break;
+
+            case 't':
+            case 'T':
+                value.type_tag = JSON_TYPE.TRUE;
+                checkChar!(false, false)('r');
+                checkChar!(false, false)('u');
+                checkChar!(false, false)('e');
+                break;
+
+            case 'f':
+            case 'F':
+                value.type_tag = JSON_TYPE.FALSE;
+                checkChar!(false, false)('a');
+                checkChar!(false, false)('l');
+                checkChar!(false, false)('s');
+                checkChar!(false, false)('e');
+                break;
+
+            case 'n':
+            case 'N':
+                value.type_tag = JSON_TYPE.NULL;
+                checkChar!(false, false)('u');
+                checkChar!(false, false)('l');
+                checkChar!(false, false)('l');
+                break;
+
+            default:
+                error(text("Unexpected character '", c, "'."));
+        }
+
+        depth--;
+    }
+
+    parseValue(root);
+    return root;
+}
+
+unittest
+{
+    enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
+    static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT);
+
+    enum issue15742arrayOfArray = `[[1]]`;
+    static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY);
+}
+
+/**
+Parses a serialized string and returns a tree of JSON values.
+Throws: $(XREF json,JSONException) if the depth exceeds the max depth.
+Params:
+    json = json-formatted string to parse
+    options = enable decoding string representations of NaN/Inf as float values
+*/
+JSONValue parseJSON(T)(T json, JSONOptions options)
+if(isInputRange!T)
+{
+    return parseJSON!T(json, -1, options);
+}
+
+/**
+Takes a tree of JSON values and returns the serialized string.
+
+Any Object types will be serialized in a key-sorted order.
+
+If $(D pretty) is false no whitespaces are generated.
+If $(D pretty) is true serialized string is formatted to be human-readable.
+Set the $(specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings.
+*/
+string toJSON(in JSONValue* root, in bool pretty = false, in JSONOptions options = JSONOptions.none)
+{
+    auto json = appender!string();
+
+    void toString(string str)
+    {
+        json.put('"');
+
+        foreach (dchar c; str)
+        {
+            switch(c)
+            {
+                case '"':       json.put("\\\"");       break;
+                case '\\':      json.put("\\\\");       break;
+                case '/':       json.put("\\/");        break;
+                case '\b':      json.put("\\b");        break;
+                case '\f':      json.put("\\f");        break;
+                case '\n':      json.put("\\n");        break;
+                case '\r':      json.put("\\r");        break;
+                case '\t':      json.put("\\t");        break;
+                default:
+                    appendJSONChar(&json, c,
+                                   (msg) { throw new JSONException(msg); });
+            }
+        }
+
+        json.put('"');
+    }
+
+    void toValue(in JSONValue* value, ulong indentLevel)
+    {
+        void putTabs(ulong additionalIndent = 0)
+        {
+            if(pretty)
+                foreach(i; 0 .. indentLevel + additionalIndent)
+                    json.put("    ");
+        }
+        void putEOL()
+        {
+            if(pretty)
+                json.put('\n');
+        }
+        void putCharAndEOL(char ch)
+        {
+            json.put(ch);
+            putEOL();
+        }
+
+        final switch(value.type)
+        {
+            case JSON_TYPE.OBJECT:
+                if(!value.store.object.length)
+                {
+                    json.put("{}");
+                }
+                else
+                {
+                    putCharAndEOL('{');
+                    bool first = true;
+
+                    void emit(R)(R names)
+                    {
+                        foreach (name; names)
+                        {
+                            auto member = value.store.object[name];
+                            if(!first)
+                                putCharAndEOL(',');
+                            first = false;
+                            putTabs(1);
+                            toString(name);
+                            json.put(':');
+                            if(pretty)
+                                json.put(' ');
+                            toValue(&member, indentLevel + 1);
+                        }
+                    }
+
+                    import std.algorithm : sort;
+                    auto names = value.store.object.keys;
+                    sort(names);
+                    emit(names);
+
+                    putEOL();
+                    putTabs();
+                    json.put('}');
+                }
+                break;
+
+            case JSON_TYPE.ARRAY:
+                if(value.store.array.empty)
+                {
+                    json.put("[]");
+                }
+                else
+                {
+                    putCharAndEOL('[');
+                    foreach (i, ref el; value.store.array)
+                    {
+                        if(i)
+                            putCharAndEOL(',');
+                        putTabs(1);
+                        toValue(&el, indentLevel + 1);
+                    }
+                    putEOL();
+                    putTabs();
+                    json.put(']');
+                }
+                break;
+
+            case JSON_TYPE.STRING:
+                toString(value.store.str);
+                break;
+
+            case JSON_TYPE.INTEGER:
+                json.put(to!string(value.store.integer));
+                break;
+
+            case JSON_TYPE.UINTEGER:
+                json.put(to!string(value.store.uinteger));
+                break;
+
+            case JSON_TYPE.FLOAT:
+                import std.math : isNaN, isInfinity;
+
+                auto val = value.store.floating;
+
+                if (val.isNaN) {
+                    if (options & JSONOptions.specialFloatLiterals) {
+                        toString(JSONFloatLiteral.nan);
+                    }
+                    else {
+                        throw new JSONException(
+                            "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
+                    }
+                }
+                else if (val.isInfinity) {
+                    if (options & JSONOptions.specialFloatLiterals) {
+                        toString((val > 0) ?  JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
+                    }
+                    else {
+                        throw new JSONException(
+                            "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
+                    }
+                }
+                else {
+                    json.put(to!string(val));
+                }
+                break;
+
+            case JSON_TYPE.TRUE:
+                json.put("true");
+                break;
+
+            case JSON_TYPE.FALSE:
+                json.put("false");
+                break;
+
+            case JSON_TYPE.NULL:
+                json.put("null");
+                break;
+        }
+    }
+
+    toValue(root, 0);
+    return json.data;
+}
+
+private void appendJSONChar(Appender!string* dst, dchar c,
+                            scope void delegate(string) error)
+{
+    import std.uni : isControl;
+
+    if(isControl(c))
+    {
+        dst.put("\\u");
+        foreach_reverse (i; 0 .. 4)
+        {
+            char ch = (c >>> (4 * i)) & 0x0f;
+            ch += ch < 10 ? '0' : 'A' - 10;
+            dst.put(ch);
+        }
+    }
+    else
+    {
+        dst.put(c);
+    }
+}
+
+/**
+Exception thrown on JSON errors
+*/
+class JSONException : Exception
+{
+    this(string msg, int line = 0, int pos = 0) pure nothrow @safe
+    {
+        if(line)
+            super(text(msg, " (Line ", line, ":", pos, ")"));
+        else
+            super(msg);
+    }
+
+    this(string msg, string file, size_t line) pure nothrow @safe
+    {
+        super(msg, file, line);
+    }
+}
+
+
+unittest
+{
+    import std.exception;
+    JSONValue jv = "123";
+    assert(jv.type == JSON_TYPE.STRING);
+    assertNotThrown(jv.str);
+    assertThrown!JSONException(jv.integer);
+    assertThrown!JSONException(jv.uinteger);
+    assertThrown!JSONException(jv.floating);
+    assertThrown!JSONException(jv.object);
+    assertThrown!JSONException(jv.array);
+    assertThrown!JSONException(jv["aa"]);
+    assertThrown!JSONException(jv[2]);
+
+    jv = -3;
+    assert(jv.type == JSON_TYPE.INTEGER);
+    assertNotThrown(jv.integer);
+
+    jv = cast(uint)3;
+    assert(jv.type == JSON_TYPE.UINTEGER);
+    assertNotThrown(jv.uinteger);
+
+    jv = 3.0f;
+    assert(jv.type == JSON_TYPE.FLOAT);
+    assertNotThrown(jv.floating);
+
+    jv = ["key" : "value"];
+    assert(jv.type == JSON_TYPE.OBJECT);
+    assertNotThrown(jv.object);
+    assertNotThrown(jv["key"]);
+    assert("key" in jv);
+    assert("notAnElement" !in jv);
+    assertThrown!JSONException(jv["notAnElement"]);
+    const cjv = jv;
+    assert("key" in cjv);
+    assertThrown!JSONException(cjv["notAnElement"]);
+
+    foreach(string key, value; jv)
+    {
+        static assert(is(typeof(value) == JSONValue));
+        assert(key == "key");
+        assert(value.type == JSON_TYPE.STRING);
+        assertNotThrown(value.str);
+        assert(value.str == "value");
+    }
+
+    jv = [3, 4, 5];
+    assert(jv.type == JSON_TYPE.ARRAY);
+    assertNotThrown(jv.array);
+    assertNotThrown(jv[2]);
+    foreach(size_t index, value; jv)
+    {
+        static assert(is(typeof(value) == JSONValue));
+        assert(value.type == JSON_TYPE.INTEGER);
+        assertNotThrown(value.integer);
+        assert(index == (value.integer-3));
+    }
+
+    jv = null;
+    assert(jv.type == JSON_TYPE.NULL);
+    assert(jv.isNull);
+    jv = "foo";
+    assert(!jv.isNull);
+
+    jv = JSONValue("value");
+    assert(jv.type == JSON_TYPE.STRING);
+    assert(jv.str == "value");
+
+    JSONValue jv2 = JSONValue("value");
+    assert(jv2.type == JSON_TYPE.STRING);
+    assert(jv2.str == "value");
+
+    JSONValue jv3 = JSONValue("\u001c");
+    assert(jv3.type == JSON_TYPE.STRING);
+    assert(jv3.str == "\u001C");
+}
+
+unittest
+{
+    // Bugzilla 11504
+
+    JSONValue jv = 1;
+    assert(jv.type == JSON_TYPE.INTEGER);
+
+    jv.str = "123";
+    assert(jv.type == JSON_TYPE.STRING);
+    assert(jv.str == "123");
+
+    jv.integer = 1;
+    assert(jv.type == JSON_TYPE.INTEGER);
+    assert(jv.integer == 1);
+
+    jv.uinteger = 2u;
+    assert(jv.type == JSON_TYPE.UINTEGER);
+    assert(jv.uinteger == 2u);
+
+    jv.floating = 1.5f;
+    assert(jv.type == JSON_TYPE.FLOAT);
+    assert(jv.floating == 1.5f);
+
+    jv.object = ["key" : JSONValue("value")];
+    assert(jv.type == JSON_TYPE.OBJECT);
+    assert(jv.object == ["key" : JSONValue("value")]);
+
+    jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
+    assert(jv.type == JSON_TYPE.ARRAY);
+    assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
+
+    jv = true;
+    assert(jv.type == JSON_TYPE.TRUE);
+
+    jv = false;
+    assert(jv.type == JSON_TYPE.FALSE);
+
+    enum E{True = true}
+    jv = E.True;
+    assert(jv.type == JSON_TYPE.TRUE);
+}
+
+pure unittest
+{
+    // Adding new json element via array() / object() directly
+
+    JSONValue jarr = JSONValue([10]);
+    foreach (i; 0..9)
+        jarr.array ~= JSONValue(i);
+    assert(jarr.array.length == 10);
+
+    JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
+    foreach (i; 0..9)
+        jobj.object[text("key", i)] = JSONValue(text("value", i));
+    assert(jobj.object.length == 10);
+}
+
+pure unittest
+{
+    // Adding new json element without array() / object() access
+
+    JSONValue jarr = JSONValue([10]);
+    foreach (i; 0..9)
+        jarr ~= [JSONValue(i)];
+    assert(jarr.array.length == 10);
+
+    JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
+    foreach (i; 0..9)
+        jobj[text("key", i)] = JSONValue(text("value", i));
+    assert(jobj.object.length == 10);
+
+    // No array alias
+    auto jarr2 = jarr ~ [1,2,3];
+    jarr2[0] = 999;
+    assert(jarr[0] == JSONValue(10));
+}
+
+unittest
+{
+    import std.exception;
+
+    // An overly simple test suite, if it can parse a serializated string and
+    // then use the resulting values tree to generate an identical
+    // serialization, both the decoder and encoder works.
+
+    auto jsons = [
+        `null`,
+        `true`,
+        `false`,
+        `0`,
+        `123`,
+        `-4321`,
+        `0.23`,
+        `-0.23`,
+        `""`,
+        `"hello\nworld"`,
+        `"\"\\\/\b\f\n\r\t"`,
+        `[]`,
+        `[12,"foo",true,false]`,
+        `{}`,
+        `{"a":1,"b":null}`,
+        `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.54,"b":0.0012}}]],"hello":{"array":[12,null,{}],"json":"is great"}}`,
+    ];
+
+    version (MinGW)
+        jsons ~= `1.223e+024`;
+    else
+        jsons ~= `1.223e+24`;
+
+    JSONValue val;
+    string result;
+    foreach (json; jsons)
+    {
+        try
+        {
+            val = parseJSON(json);
+            enum pretty = false;
+            result = toJSON(&val, pretty);
+            assert(result == json, text(result, " should be ", json));
+        }
+        catch (JSONException e)
+        {
+            import std.stdio : writefln;
+            writefln(text(json, "\n", e.toString()));
+        }
+    }
+
+    // Should be able to correctly interpret unicode entities
+    val = parseJSON(`"\u003C\u003E"`);
+    assert(toJSON(&val) == "\"\&lt;\&gt;\"");
+    assert(val.to!string() == "\"\&lt;\&gt;\"");
+    val = parseJSON(`"\u0391\u0392\u0393"`);
+    assert(toJSON(&val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
+    assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
+    val = parseJSON(`"\u2660\u2666"`);
+    assert(toJSON(&val) == "\"\&spades;\&diams;\"");
+    assert(val.to!string() == "\"\&spades;\&diams;\"");
+
+    //0x7F is a control character (see Unicode spec)
+    val = parseJSON(`"\u007F"`);
+    assert(toJSON(&val) == "\"\\u007F\"");
+    assert(val.to!string() == "\"\\u007F\"");
+
+    with(parseJSON(`""`))
+        assert(str == "" && str !is null);
+    with(parseJSON(`[]`))
+        assert(!array.length && array !is null);
+
+    // Formatting
+    val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
+    assert(toJSON(&val, true) == `{
+    "a": [
+        null,
+        {
+            "x": 1
+        },
+        {},
+        []
+    ]
+}`);
+}
+
+unittest {
+  auto json = `"hello\nworld"`;
+  const jv = parseJSON(json);
+  assert(jv.toString == json);
+  assert(jv.toPrettyString == json);
+}
+
+deprecated unittest
+{
+    // Bugzilla 12332
+    import std.exception;
+
+    JSONValue jv;
+    jv.type = JSON_TYPE.INTEGER;
+    jv = 1;
+    assert(jv.type == JSON_TYPE.INTEGER);
+    assert(jv.integer == 1);
+    jv.type = JSON_TYPE.UINTEGER;
+    assert(jv.uinteger == 1);
+
+    jv.type = JSON_TYPE.STRING;
+    assertThrown!JSONException(jv.integer == 1);
+    assert(jv.str is null);
+    jv.str = "123";
+    assert(jv.str == "123");
+    jv.type = JSON_TYPE.STRING;
+    assert(jv.str == "123");
+
+    jv.type = JSON_TYPE.TRUE;
+    assert(jv.type == JSON_TYPE.TRUE);
+}
+
+pure unittest
+{
+    // Bugzilla 12969
+
+    JSONValue jv;
+    jv["int"] = 123;
+
+    assert(jv.type == JSON_TYPE.OBJECT);
+    assert("int" in jv);
+    assert(jv["int"].integer == 123);
+
+    jv["array"] = [1, 2, 3, 4, 5];
+
+    assert(jv["array"].type == JSON_TYPE.ARRAY);
+    assert(jv["array"][2].integer == 3);
+
+    jv["str"] = "D language";
+    assert(jv["str"].type == JSON_TYPE.STRING);
+    assert(jv["str"].str == "D language");
+
+    jv["bool"] = false;
+    assert(jv["bool"].type == JSON_TYPE.FALSE);
+
+    assert(jv.object.length == 4);
+
+    jv = [5, 4, 3, 2, 1];
+    assert( jv.type == JSON_TYPE.ARRAY );
+    assert( jv[3].integer == 2 );
+}
+
+unittest
+{
+    auto s = q"EOF
+[
+  1,
+  2,
+  3,
+  potato
+]
+EOF";
+
+    import std.exception;
+
+    auto e = collectException!JSONException(parseJSON(s));
+    assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
+}
+
+// handling of special float values (NaN, Inf, -Inf)
+unittest
+{
+    import std.math      : isNaN, isInfinity;
+    import std.exception : assertThrown;
+
+    // expected representations of NaN and Inf
+    enum {
+        nanString         = '"' ~ JSONFloatLiteral.nan         ~ '"',
+        infString         = '"' ~ JSONFloatLiteral.inf         ~ '"',
+        negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
+    }
+
+    // with the specialFloatLiterals option, encode NaN/Inf as strings
+    assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals)       == nanString);
+    assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
+    assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals)  == negativeInfString);
+
+    // without the specialFloatLiterals option, throw on encoding NaN/Inf
+    assertThrown!JSONException(JSONValue(float.nan).toString);
+    assertThrown!JSONException(JSONValue(double.infinity).toString);
+    assertThrown!JSONException(JSONValue(-real.infinity).toString);
+
+    // when parsing json with specialFloatLiterals option, decode special strings as floats
+    JSONValue jvNan    = parseJSON(nanString, JSONOptions.specialFloatLiterals);
+    JSONValue jvInf    = parseJSON(infString, JSONOptions.specialFloatLiterals);
+    JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
+
+    assert(jvNan.floating.isNaN);
+    assert(jvInf.floating.isInfinity    && jvInf.floating > 0);
+    assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
+
+    // when parsing json without the specialFloatLiterals option, decode special strings as strings
+    jvNan    = parseJSON(nanString);
+    jvInf    = parseJSON(infString);
+    jvNegInf = parseJSON(negativeInfString);
+
+    assert(jvNan.str    == JSONFloatLiteral.nan);
+    assert(jvInf.str    == JSONFloatLiteral.inf);
+    assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
+}
+
+pure nothrow @safe @nogc unittest
+{
+    JSONValue testVal;
+    testVal = "test";
+    testVal = 10;
+    testVal = 10u;
+    testVal = 1.0;
+    testVal = (JSONValue[string]).init;
+    testVal = JSONValue[].init;
+    testVal = null;
+    assert(testVal.isNull);
+}
diff --git a/libphobos/src/std/math.d b/libphobos/src/std/math.d
new file mode 100644
index 00000000000..455cab41e23
--- /dev/null
+++ b/libphobos/src/std/math.d
@@ -0,0 +1,7753 @@
+// Written in the D programming language.
+
+/**
+ * Contains the elementary mathematical functions (powers, roots,
+ * and trigonometric functions), and low-level floating-point operations.
+ * Mathematical special functions are available in $(D std.mathspecial).
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Members) )
+$(TR $(TDNW Constants) $(TD
+    $(MYREF E) $(MYREF PI) $(MYREF PI_2) $(MYREF PI_4) $(MYREF M_1_PI)
+    $(MYREF M_2_PI) $(MYREF M_2_SQRTPI) $(MYREF LN10) $(MYREF LN2)
+    $(MYREF LOG2) $(MYREF LOG2E) $(MYREF LOG2T) $(MYREF LOG10E)
+    $(MYREF SQRT2) $(MYREF SQRT1_2)
+))
+$(TR $(TDNW Classics) $(TD
+    $(MYREF abs) $(MYREF fabs) $(MYREF sqrt) $(MYREF cbrt) $(MYREF hypot)
+    $(MYREF poly) $(MYREF nextPow2) $(MYREF truncPow2)
+))
+$(TR $(TDNW Trigonometry) $(TD
+    $(MYREF sin) $(MYREF cos) $(MYREF tan) $(MYREF asin) $(MYREF acos)
+    $(MYREF atan) $(MYREF atan2) $(MYREF sinh) $(MYREF cosh) $(MYREF tanh)
+    $(MYREF asinh) $(MYREF acosh) $(MYREF atanh) $(MYREF expi)
+))
+$(TR $(TDNW Rounding) $(TD
+    $(MYREF ceil) $(MYREF floor) $(MYREF round) $(MYREF lround)
+    $(MYREF trunc) $(MYREF rint) $(MYREF lrint) $(MYREF nearbyint)
+    $(MYREF rndtol)
+))
+$(TR $(TDNW Exponentiation & Logarithms) $(TD
+    $(MYREF pow) $(MYREF exp) $(MYREF exp2) $(MYREF expm1) $(MYREF ldexp)
+    $(MYREF frexp) $(MYREF log) $(MYREF log2) $(MYREF log10) $(MYREF logb)
+    $(MYREF ilogb) $(MYREF log1p) $(MYREF scalbn)
+))
+$(TR $(TDNW Modulus) $(TD
+    $(MYREF fmod) $(MYREF modf) $(MYREF remainder)
+))
+$(TR $(TDNW Floating-point operations) $(TD
+    $(MYREF approxEqual) $(MYREF feqrel) $(MYREF fdim) $(MYREF fmax)
+    $(MYREF fmin) $(MYREF fma) $(MYREF nextDown) $(MYREF nextUp)
+    $(MYREF nextafter) $(MYREF NaN) $(MYREF getNaNPayload)
+    $(MYREF cmp)
+))
+$(TR $(TDNW Introspection) $(TD
+    $(MYREF isFinite) $(MYREF isIdentical) $(MYREF isInfinity) $(MYREF isNaN)
+    $(MYREF isNormal) $(MYREF isSubnormal) $(MYREF signbit) $(MYREF sgn)
+    $(MYREF copysign)
+))
+$(TR $(TDNW Complex Numbers) $(TD
+  $(MYREF abs) $(MYREF conj) $(MYREF sin) $(MYREF cos) $(MYREF expi)
+))
+$(TR $(TDNW Hardware Control) $(TD
+    $(MYREF IeeeFlags) $(MYREF FloatingPointControl)
+))
+)
+)
+
+ * The functionality closely follows the IEEE754-2008 standard for
+ * floating-point arithmetic, including the use of camelCase names rather
+ * than C99-style lower case names. All of these functions behave correctly
+ * when presented with an infinity or NaN.
+ *
+ * The following IEEE 'real' formats are currently supported:
+ * $(UL
+ * $(LI 64 bit Big-endian  'double' (eg PowerPC))
+ * $(LI 128 bit Big-endian 'quadruple' (eg SPARC))
+ * $(LI 64 bit Little-endian 'double' (eg x86-SSE2))
+ * $(LI 80 bit Little-endian, with implied bit 'real80' (eg x87, Itanium))
+ * $(LI 128 bit Little-endian 'quadruple' (not implemented on any known processor!))
+ * $(LI Non-IEEE 128 bit Big-endian 'doubledouble' (eg PowerPC) has partial support)
+ * )
+ * Unlike C, there is no global 'errno' variable. Consequently, almost all of
+ * these functions are pure nothrow.
+ *
+ * Status:
+ * The semantics and names of feqrel and approxEqual will be revised.
+ *
+ * Macros:
+ *      WIKI = Phobos/StdMath
+ *
+ *      TABLE_SV = <table border="1" cellpadding="4" cellspacing="0">
+ *              <caption>Special Values</caption>
+ *              $0</table>
+ *      SVH = $(TR $(TH $1) $(TH $2))
+ *      SV  = $(TR $(TD $1) $(TD $2))
+ *      TH3 = $(TR $(TH $1) $(TH $2) $(TH $3))
+ *      TD3 = $(TR $(TD $1) $(TD $2) $(TD $3))
+ *
+ *      NAN = $(RED NAN)
+ *      SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
+ *      GAMMA = &#915;
+ *      THETA = &theta;
+ *      INTEGRAL = &#8747;
+ *      INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
+ *      POWER = $1<sup>$2</sup>
+ *      SUB = $1<sub>$2</sub>
+ *      BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
+ *      CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
+ *      PLUSMN = &plusmn;
+ *      INFIN = &infin;
+ *      PLUSMNINF = &plusmn;&infin;
+ *      PI = &pi;
+ *      LT = &lt;
+ *      GT = &gt;
+ *      SQRT = &radic;
+ *      HALF = &frac12;
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2011.
+ *            D implementations of tan, atan, atan2, exp, expm1, exp2, log, log10, log1p,
+ *            log2, floor, ceil and lrint functions are based on the CEPHES math library,
+ *            which is Copyright (C) 2001 Stephen L. Moshier $(LT)steve@moshier.net$(GT)
+ *            and are incorporated herein by permission of the author.  The author
+ *            reserves the right to distribute this material elsewhere under different
+ *            copying permissions.  These modifications are distributed here under
+ *            the following terms:
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright), Don Clugston,
+ *            Conversion of CEPHES math library to D by Iain Buclaw
+ * Source: $(PHOBOSSRC std/_math.d)
+ */
+
+/* NOTE: This file has been patched from the original DMD distribution to
+ * work with the GDC compiler.
+ */
+module std.math;
+
+version (Win64)
+{
+    version (D_InlineAsm_X86_64)
+        version = Win64_DMD_InlineAsm;
+}
+
+import core.bitop;
+import core.math;
+import core.stdc.math;
+import std.traits;
+
+version(LDC)
+{
+    import ldc.intrinsics;
+}
+
+version(DigitalMars)
+{
+    version = INLINE_YL2X;        // x87 has opcodes for these
+}
+
+version (X86)    version = X86_Any;
+version (X86_64) version = X86_Any;
+version (PPC)    version = PPC_Any;
+version (PPC64)  version = PPC_Any;
+
+version(D_InlineAsm_X86)
+{
+    version = InlineAsm_X86_Any;
+}
+else version(D_InlineAsm_X86_64)
+{
+    version = InlineAsm_X86_Any;
+}
+
+
+version(unittest)
+{
+    import core.stdc.stdio;
+
+    static if(real.sizeof > double.sizeof)
+        enum uint useDigits = 16;
+    else
+        enum uint useDigits = 15;
+
+    /******************************************
+     * Compare floating point numbers to n decimal digits of precision.
+     * Returns:
+     *  1       match
+     *  0       nomatch
+     */
+
+    private bool equalsDigit(real x, real y, uint ndigits)
+    {
+        if (signbit(x) != signbit(y))
+            return 0;
+
+        if (isInfinity(x) && isInfinity(y))
+            return 1;
+        if (isInfinity(x) || isInfinity(y))
+            return 0;
+
+        if (isNaN(x) && isNaN(y))
+            return 1;
+        if (isNaN(x) || isNaN(y))
+            return 0;
+
+        char[30] bufx;
+        char[30] bufy;
+        assert(ndigits < bufx.length);
+
+        int ix;
+        int iy;
+        version(CRuntime_Microsoft)
+            alias real_t = double;
+        else
+            alias real_t = real;
+        ix = sprintf(bufx.ptr, "%.*Lg", ndigits, cast(real_t) x);
+        iy = sprintf(bufy.ptr, "%.*Lg", ndigits, cast(real_t) y);
+        assert(ix < bufx.length && ix > 0);
+        assert(ix < bufy.length && ix > 0);
+
+        return bufx[0 .. ix] == bufy[0 .. iy];
+    }
+}
+
+
+
+package:
+// The following IEEE 'real' formats are currently supported.
+version(LittleEndian)
+{
+    static assert(real.mant_dig == 53 || real.mant_dig == 64
+               || real.mant_dig == 113,
+      "Only 64-bit, 80-bit, and 128-bit reals"~
+      " are supported for LittleEndian CPUs");
+}
+else
+{
+    static assert(real.mant_dig == 53 || real.mant_dig == 106
+               || real.mant_dig == 113,
+    "Only 64-bit and 128-bit reals are supported for BigEndian CPUs."~
+    " double-double reals have partial support");
+}
+
+// Underlying format exposed through floatTraits
+enum RealFormat
+{
+    ieeeHalf,
+    ieeeSingle,
+    ieeeDouble,
+    ieeeExtended,   // x87 80-bit real
+    ieeeExtended53, // x87 real rounded to precision of double.
+    ibmExtended,    // IBM 128-bit extended
+    ieeeQuadruple,
+}
+
+// Constants used for extracting the components of the representation.
+// They supplement the built-in floating point properties.
+template floatTraits(T)
+{
+    // EXPMASK is a ushort mask to select the exponent portion (without sign)
+    // EXPSHIFT is the number of bits the exponent is left-shifted by in its ushort
+    // EXPPOS_SHORT is the index of the exponent when represented as a ushort array.
+    // SIGNPOS_BYTE is the index of the sign when represented as a ubyte array.
+    // RECIP_EPSILON is the value such that (smallest_subnormal) * RECIP_EPSILON == T.min_normal
+    enum T RECIP_EPSILON = (1/T.epsilon);
+    static if (T.mant_dig == 24)
+    {
+        // Single precision float
+        enum ushort EXPMASK = 0x7F80;
+        enum ushort EXPSHIFT = 7;
+        enum ushort EXPBIAS = 0x3F00;
+        enum uint EXPMASK_INT = 0x7F80_0000;
+        enum uint MANTISSAMASK_INT = 0x007F_FFFF;
+        enum realFormat = RealFormat.ieeeSingle;
+        version(LittleEndian)
+        {
+            enum EXPPOS_SHORT = 1;
+            enum SIGNPOS_BYTE = 3;
+        }
+        else
+        {
+            enum EXPPOS_SHORT = 0;
+            enum SIGNPOS_BYTE = 0;
+        }
+    }
+    else static if (T.mant_dig == 53)
+    {
+        static if (T.sizeof == 8)
+        {
+            // Double precision float, or real == double
+            enum ushort EXPMASK = 0x7FF0;
+            enum ushort EXPSHIFT = 4;
+            enum ushort EXPBIAS = 0x3FE0;
+            enum uint EXPMASK_INT = 0x7FF0_0000;
+            enum uint MANTISSAMASK_INT = 0x000F_FFFF; // for the MSB only
+            enum realFormat = RealFormat.ieeeDouble;
+            version(LittleEndian)
+            {
+                enum EXPPOS_SHORT = 3;
+                enum SIGNPOS_BYTE = 7;
+            }
+            else
+            {
+                enum EXPPOS_SHORT = 0;
+                enum SIGNPOS_BYTE = 0;
+            }
+        }
+        else static if (T.sizeof == 12)
+        {
+            // Intel extended real80 rounded to double
+            enum ushort EXPMASK = 0x7FFF;
+            enum ushort EXPSHIFT = 0;
+            enum ushort EXPBIAS = 0x3FFE;
+            enum realFormat = RealFormat.ieeeExtended53;
+            version(LittleEndian)
+            {
+                enum EXPPOS_SHORT = 4;
+                enum SIGNPOS_BYTE = 9;
+            }
+            else
+            {
+                enum EXPPOS_SHORT = 0;
+                enum SIGNPOS_BYTE = 0;
+            }
+        }
+        else
+            static assert(false, "No traits support for " ~ T.stringof);
+    }
+    else static if (T.mant_dig == 64)
+    {
+        // Intel extended real80
+        enum ushort EXPMASK = 0x7FFF;
+        enum ushort EXPSHIFT = 0;
+        enum ushort EXPBIAS = 0x3FFE;
+        enum realFormat = RealFormat.ieeeExtended;
+        version(LittleEndian)
+        {
+            enum EXPPOS_SHORT = 4;
+            enum SIGNPOS_BYTE = 9;
+        }
+        else
+        {
+            enum EXPPOS_SHORT = 0;
+            enum SIGNPOS_BYTE = 0;
+        }
+    }
+    else static if (T.mant_dig == 113)
+    {
+        // Quadruple precision float
+        enum ushort EXPMASK = 0x7FFF;
+        enum ushort EXPSHIFT = 0;
+        enum ushort EXPBIAS = 0x3FFF;
+        enum realFormat = RealFormat.ieeeQuadruple;
+        version(LittleEndian)
+        {
+            enum EXPPOS_SHORT = 7;
+            enum SIGNPOS_BYTE = 15;
+        }
+        else
+        {
+            enum EXPPOS_SHORT = 0;
+            enum SIGNPOS_BYTE = 0;
+        }
+    }
+    else static if (T.mant_dig == 106)
+    {
+        // IBM Extended doubledouble
+        enum ushort EXPMASK = 0x7FF0;
+        enum ushort EXPSHIFT = 4;
+        enum realFormat = RealFormat.ibmExtended;
+        // the exponent byte is not unique
+        version(LittleEndian)
+        {
+            enum EXPPOS_SHORT = 7; // [3] is also an exp short
+            enum SIGNPOS_BYTE = 15;
+        }
+        else
+        {
+            enum EXPPOS_SHORT = 0; // [4] is also an exp short
+            enum SIGNPOS_BYTE = 0;
+        }
+    }
+    else
+        static assert(false, "No traits support for " ~ T.stringof);
+}
+
+// These apply to all floating-point types
+version(LittleEndian)
+{
+    enum MANTISSA_LSB = 0;
+    enum MANTISSA_MSB = 1;
+}
+else
+{
+    enum MANTISSA_LSB = 1;
+    enum MANTISSA_MSB = 0;
+}
+
+// Common code for math implementations.
+
+// Helper for floor/ceil
+T floorImpl(T)(const T x) @trusted pure nothrow @nogc
+{
+    alias F = floatTraits!(T);
+    // Take care not to trigger library calls from the compiler,
+    // while ensuring that we don't get defeated by some optimizers.
+    union floatBits
+    {
+        T rv;
+        ushort[T.sizeof/2] vu;
+    }
+    floatBits y = void;
+    y.rv = x;
+
+    // Find the exponent (power of 2)
+    static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        int exp = ((y.vu[F.EXPPOS_SHORT] >> 7) & 0xff) - 0x7f;
+
+        version (LittleEndian)
+            int pos = 0;
+        else
+            int pos = 3;
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        int exp = ((y.vu[F.EXPPOS_SHORT] >> 4) & 0x7ff) - 0x3ff;
+
+        version (LittleEndian)
+            int pos = 0;
+        else
+            int pos = 3;
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;
+
+        version (LittleEndian)
+            int pos = 0;
+        else
+            int pos = 4;
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;
+
+        version (LittleEndian)
+            int pos = 0;
+        else
+            int pos = 7;
+        }
+    else
+        static assert(false, "Not implemented for this architecture");
+
+    if (exp < 0)
+    {
+        if (x < 0.0)
+            return -1.0;
+        else
+            return 0.0;
+    }
+
+    exp = (T.mant_dig - 1) - exp;
+
+    // Zero 16 bits at a time.
+    while (exp >= 16)
+    {
+        version (LittleEndian)
+            y.vu[pos++] = 0;
+        else
+            y.vu[pos--] = 0;
+        exp -= 16;
+    }
+
+    // Clear the remaining bits.
+    if (exp > 0)
+        y.vu[pos] &= 0xffff ^ ((1 << exp) - 1);
+
+    if ((x < 0.0) && (x != y.rv))
+        y.rv -= 1.0;
+
+    return y.rv;
+}
+
+public:
+
+// Values obtained from Wolfram Alpha. 116 bits ought to be enough for anybody.
+// Wolfram Alpha LLC. 2011. Wolfram|Alpha. http://www.wolframalpha.com/input/?i=e+in+base+16 (access July 6, 2011).
+enum real E =          0x1.5bf0a8b1457695355fb8ac404e7a8p+1L; /** e = 2.718281... */
+enum real LOG2T =      0x1.a934f0979a3715fc9257edfe9b5fbp+1L; /** $(SUB log, 2)10 = 3.321928... */
+enum real LOG2E =      0x1.71547652b82fe1777d0ffda0d23a8p+0L; /** $(SUB log, 2)e = 1.442695... */
+enum real LOG2 =       0x1.34413509f79fef311f12b35816f92p-2L; /** $(SUB log, 10)2 = 0.301029... */
+enum real LOG10E =     0x1.bcb7b1526e50e32a6ab7555f5a67cp-2L; /** $(SUB log, 10)e = 0.434294... */
+enum real LN2 =        0x1.62e42fefa39ef35793c7673007e5fp-1L; /** ln 2  = 0.693147... */
+enum real LN10 =       0x1.26bb1bbb5551582dd4adac5705a61p+1L; /** ln 10 = 2.302585... */
+enum real PI =         0x1.921fb54442d18469898cc51701b84p+1L; /** $(_PI) = 3.141592... */
+enum real PI_2 =       PI/2;                                  /** $(PI) / 2 = 1.570796... */
+enum real PI_4 =       PI/4;                                  /** $(PI) / 4 = 0.785398... */
+enum real M_1_PI =     0x1.45f306dc9c882a53f84eafa3ea69cp-2L; /** 1 / $(PI) = 0.318309... */
+enum real M_2_PI =     2*M_1_PI;                              /** 2 / $(PI) = 0.636619... */
+enum real M_2_SQRTPI = 0x1.20dd750429b6d11ae3a914fed7fd8p+0L; /** 2 / $(SQRT)$(PI) = 1.128379... */
+enum real SQRT2 =      0x1.6a09e667f3bcc908b2fb1366ea958p+0L; /** $(SQRT)2 = 1.414213... */
+enum real SQRT1_2 =    SQRT2/2;                               /** $(SQRT)$(HALF) = 0.707106... */
+// Note: Make sure the magic numbers in compiler backend for x87 match these.
+
+
+/***********************************
+ * Calculates the absolute value of a number
+ *
+ * Params:
+ *     Num = (template parameter) type of number
+ *       x = real number value
+ *       z = complex number value
+ *       y = imaginary number value
+ *
+ * Returns:
+ *     The absolute value of the number.  If floating-point or integral,
+ *     the return type will be the same as the input; if complex or
+ *     imaginary, the returned value will be the corresponding floating
+ *     point type.
+ *
+ * For complex numbers, abs(z) = sqrt( $(POWER z.re, 2) + $(POWER z.im, 2) )
+ * = hypot(z.re, z.im).
+ */
+Num abs(Num)(Num x) @safe pure nothrow
+    if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) &&
+            !(is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
+                    || is(Num* : const(ireal*))))
+{
+    static if (isFloatingPoint!(Num))
+        return fabs(x);
+    else
+        return x>=0 ? x : -x;
+}
+
+/// ditto
+auto abs(Num)(Num z) @safe pure nothrow @nogc
+    if (is(Num* : const(cfloat*)) || is(Num* : const(cdouble*))
+            || is(Num* : const(creal*)))
+{
+    return hypot(z.re, z.im);
+}
+
+/// ditto
+auto abs(Num)(Num y) @safe pure nothrow @nogc
+    if (is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
+            || is(Num* : const(ireal*)))
+{
+    return fabs(y.im);
+}
+
+/// ditto
+@safe pure nothrow @nogc unittest
+{
+    assert(isIdentical(abs(-0.0L), 0.0L));
+    assert(isNaN(abs(real.nan)));
+    assert(abs(-real.infinity) == real.infinity);
+    assert(abs(-3.2Li) == 3.2L);
+    assert(abs(71.6Li) == 71.6L);
+    assert(abs(-56) == 56);
+    assert(abs(2321312L)  == 2321312L);
+    assert(abs(-1L+1i) == sqrt(2.0L));
+}
+
+@safe pure nothrow @nogc unittest
+{
+    import std.meta : AliasSeq;
+    foreach (T; AliasSeq!(float, double, real))
+    {
+        T f = 3;
+        assert(abs(f) == f);
+        assert(abs(-f) == f);
+    }
+    foreach (T; AliasSeq!(cfloat, cdouble, creal))
+    {
+        T f = -12+3i;
+        assert(abs(f) == hypot(f.re, f.im));
+        assert(abs(-f) == hypot(f.re, f.im));
+    }
+}
+
+/***********************************
+ * Complex conjugate
+ *
+ *  conj(x + iy) = x - iy
+ *
+ * Note that z * conj(z) = $(POWER z.re, 2) - $(POWER z.im, 2)
+ * is always a real number
+ */
+auto conj(Num)(Num z) @safe pure nothrow @nogc
+    if (is(Num* : const(cfloat*)) || is(Num* : const(cdouble*))
+            || is(Num* : const(creal*)))
+{
+    //FIXME
+    //Issue 14206
+    static if(is(Num* : const(cdouble*)))
+        return cast(cdouble) conj(cast(creal)z);
+    else
+        return z.re - z.im*1fi;
+}
+
+/** ditto */
+auto conj(Num)(Num y) @safe pure nothrow @nogc
+    if (is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
+            || is(Num* : const(ireal*)))
+{
+    return -y;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    creal c = 7 + 3Li;
+    assert(conj(c) == 7-3Li);
+    ireal z = -3.2Li;
+    assert(conj(z) == -z);
+}
+//Issue 14206
+@safe pure nothrow @nogc unittest
+{
+    cdouble c = 7 + 3i;
+    assert(conj(c) == 7-3i);
+    idouble z = -3.2i;
+    assert(conj(z) == -z);
+}
+//Issue 14206
+@safe pure nothrow @nogc unittest
+{
+    cfloat c = 7f + 3fi;
+    assert(conj(c) == 7f-3fi);
+    ifloat z = -3.2fi;
+    assert(conj(z) == -z);
+}
+
+/***********************************
+ * Returns cosine of x. x is in radians.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH cos(x)) $(TH invalid?))
+ *      $(TR $(TD $(NAN))            $(TD $(NAN)) $(TD yes)     )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN)) $(TD yes)     )
+ *      )
+ * Bugs:
+ *      Results are undefined if |x| >= $(POWER 2,64).
+ */
+
+real cos(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.cos(x); }
+//FIXME
+///ditto
+double cos(double x) @safe pure nothrow @nogc { return cos(cast(real)x); }
+//FIXME
+///ditto
+float cos(float x) @safe pure nothrow @nogc { return cos(cast(real)x); }
+
+unittest
+{
+    real function(real) pcos = &cos;
+    assert(pcos != null);
+}
+
+/***********************************
+ * Returns $(WEB en.wikipedia.org/wiki/Sine, sine) of x. x is in $(WEB en.wikipedia.org/wiki/Radian, radians).
+ *
+ *      $(TABLE_SV
+ *      $(TH3 x           ,  sin(x)      ,  invalid?)
+ *      $(TD3 $(NAN)      ,  $(NAN)      ,  yes     )
+ *      $(TD3 $(PLUSMN)0.0,  $(PLUSMN)0.0,  no      )
+ *      $(TD3 $(PLUSMNINF),  $(NAN)      ,  yes     )
+ *      )
+ *
+ * Params:
+ *      x = angle in radians (not degrees)
+ * Returns:
+ *      sine of x
+ * See_Also:
+ *      $(MYREF cos), $(MYREF tan), $(MYREF asin)
+ * Bugs:
+ *      Results are undefined if |x| >= $(POWER 2,64).
+ */
+
+real sin(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.sin(x); }
+//FIXME
+///ditto
+double sin(double x) @safe pure nothrow @nogc { return sin(cast(real)x); }
+//FIXME
+///ditto
+float sin(float x) @safe pure nothrow @nogc { return sin(cast(real)x); }
+
+///
+unittest
+{
+    import std.math : sin, PI;
+    import std.stdio : writefln;
+
+    void someFunc()
+    {
+      real x = 30.0;
+      auto result = sin(x * (PI / 180)); // convert degrees to radians
+      writefln("The sine of %s degrees is %s", x, result);
+    }
+}
+
+unittest
+{
+    real function(real) psin = &sin;
+    assert(psin != null);
+}
+
+/***********************************
+ *  Returns sine for complex and imaginary arguments.
+ *
+ *  sin(z) = sin(z.re)*cosh(z.im) + cos(z.re)*sinh(z.im)i
+ *
+ * If both sin($(THETA)) and cos($(THETA)) are required,
+ * it is most efficient to use expi($(THETA)).
+ */
+creal sin(creal z) @safe pure nothrow @nogc
+{
+    creal cs = expi(z.re);
+    creal csh = coshisinh(z.im);
+    return cs.im * csh.re + cs.re * csh.im * 1i;
+}
+
+/** ditto */
+ireal sin(ireal y) @safe pure nothrow @nogc
+{
+    return cosh(y.im)*1i;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+  assert(sin(0.0+0.0i) == 0.0);
+  assert(sin(2.0+0.0i) == sin(2.0L) );
+}
+
+/***********************************
+ *  cosine, complex and imaginary
+ *
+ *  cos(z) = cos(z.re)*cosh(z.im) - sin(z.re)*sinh(z.im)i
+ */
+creal cos(creal z) @safe pure nothrow @nogc
+{
+    creal cs = expi(z.re);
+    creal csh = coshisinh(z.im);
+    return cs.re * csh.re - cs.im * csh.im * 1i;
+}
+
+/** ditto */
+real cos(ireal y) @safe pure nothrow @nogc
+{
+    return cosh(y.im);
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(cos(0.0+0.0i)==1.0);
+    assert(cos(1.3L+0.0i)==cos(1.3L));
+    assert(cos(5.2Li)== cosh(5.2L));
+}
+
+/****************************************************************************
+ * Returns tangent of x. x is in radians.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)             $(TH tan(x))       $(TH invalid?))
+ *      $(TR $(TD $(NAN))        $(TD $(NAN))       $(TD yes))
+ *      $(TR $(TD $(PLUSMN)0.0)  $(TD $(PLUSMN)0.0) $(TD no))
+ *      $(TR $(TD $(PLUSMNINF))  $(TD $(NAN))       $(TD yes))
+ *      )
+ */
+
+real tan(real x) @trusted pure nothrow @nogc
+{
+    version(D_InlineAsm_X86)
+    {
+    asm pure nothrow @nogc
+    {
+        fld     x[EBP]                  ; // load theta
+        fxam                            ; // test for oddball values
+        fstsw   AX                      ;
+        sahf                            ;
+        jc      trigerr                 ; // x is NAN, infinity, or empty
+                                          // 387's can handle subnormals
+SC18:   fptan                           ;
+        fstp    ST(0)                   ; // dump X, which is always 1
+        fstsw   AX                      ;
+        sahf                            ;
+        jnp     Lret                    ; // C2 = 1 (x is out of range)
+
+        // Do argument reduction to bring x into range
+        fldpi                           ;
+        fxch                            ;
+SC17:   fprem1                          ;
+        fstsw   AX                      ;
+        sahf                            ;
+        jp      SC17                    ;
+        fstp    ST(1)                   ; // remove pi from stack
+        jmp     SC18                    ;
+
+trigerr:
+        jnp     Lret                    ; // if theta is NAN, return theta
+        fstp    ST(0)                   ; // dump theta
+    }
+    return real.nan;
+
+Lret: {}
+    }
+    else version(D_InlineAsm_X86_64)
+    {
+        version (Win64)
+        {
+            asm pure nothrow @nogc
+            {
+                fld     real ptr [RCX]  ; // load theta
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc
+            {
+                fld     x[RBP]          ; // load theta
+            }
+        }
+    asm pure nothrow @nogc
+    {
+        fxam                            ; // test for oddball values
+        fstsw   AX                      ;
+        test    AH,1                    ;
+        jnz     trigerr                 ; // x is NAN, infinity, or empty
+                                          // 387's can handle subnormals
+SC18:   fptan                           ;
+        fstp    ST(0)                   ; // dump X, which is always 1
+        fstsw   AX                      ;
+        test    AH,4                    ;
+        jz      Lret                    ; // C2 = 1 (x is out of range)
+
+        // Do argument reduction to bring x into range
+        fldpi                           ;
+        fxch                            ;
+SC17:   fprem1                          ;
+        fstsw   AX                      ;
+        test    AH,4                    ;
+        jnz     SC17                    ;
+        fstp    ST(1)                   ; // remove pi from stack
+        jmp     SC18                    ;
+
+trigerr:
+        test    AH,4                    ;
+        jz      Lret                    ; // if theta is NAN, return theta
+        fstp    ST(0)                   ; // dump theta
+    }
+    return real.nan;
+
+Lret: {}
+    }
+    else
+    {
+        // Coefficients for tan(x)
+        static immutable real[3] P = [
+           -1.7956525197648487798769E7L,
+            1.1535166483858741613983E6L,
+           -1.3093693918138377764608E4L,
+        ];
+        static immutable real[5] Q = [
+           -5.3869575592945462988123E7L,
+            2.5008380182335791583922E7L,
+           -1.3208923444021096744731E6L,
+            1.3681296347069295467845E4L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // PI/4 split into three parts.
+        enum real P1 = 7.853981554508209228515625E-1L;
+        enum real P2 = 7.946627356147928367136046290398E-9L;
+        enum real P3 = 3.061616997868382943065164830688E-17L;
+
+        // Special cases.
+        if (x == 0.0 || isNaN(x))
+            return x;
+        if (isInfinity(x))
+            return real.nan;
+
+        // Make argument positive but save the sign.
+        bool sign = false;
+        if (signbit(x))
+        {
+            sign = true;
+            x = -x;
+        }
+
+        // Compute x mod PI/4.
+        real y = floor(x / PI_4);
+        // Strip high bits of integer part.
+        real z = ldexp(y, -4);
+        // Compute y - 16 * (y / 16).
+        z = y - ldexp(floor(z), 4);
+
+        // Integer and fraction part modulo one octant.
+        int j = cast(int)(z);
+
+        // Map zeros and singularities to origin.
+        if (j & 1)
+        {
+            j += 1;
+            y += 1.0;
+        }
+
+        z = ((x - y * P1) - y * P2) - y * P3;
+        real zz = z * z;
+
+        if (zz > 1.0e-20L)
+            y = z + z * (zz * poly(zz, P) / poly(zz, Q));
+        else
+            y = z;
+
+        if (j & 2)
+            y = -1.0 / y;
+
+        return (sign) ? -y : y;
+    }
+}
+
+@safe nothrow @nogc unittest
+{
+    static real[2][] vals =     // angle,tan
+        [
+         [   0,   0],
+         [   .5,  .5463024898],
+         [   1,   1.557407725],
+         [   1.5, 14.10141995],
+         [   2,  -2.185039863],
+         [   2.5,-.7470222972],
+         [   3,  -.1425465431],
+         [   3.5, .3745856402],
+         [   4,   1.157821282],
+         [   4.5, 4.637332055],
+         [   5,  -3.380515006],
+         [   5.5,-.9955840522],
+         [   6,  -.2910061914],
+         [   6.5, .2202772003],
+         [   10,  .6483608275],
+
+         // special angles
+         [   PI_4,   1],
+         //[   PI_2,   real.infinity], // PI_2 is not _exactly_ pi/2.
+         [   3*PI_4, -1],
+         [   PI,     0],
+         [   5*PI_4, 1],
+         //[   3*PI_2, -real.infinity],
+         [   7*PI_4, -1],
+         [   2*PI,   0],
+         ];
+    int i;
+
+    for (i = 0; i < vals.length; i++)
+    {
+        real x = vals[i][0];
+        real r = vals[i][1];
+        real t = tan(x);
+
+        //printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r);
+        if (!isIdentical(r, t)) assert(fabs(r-t) <= .0000001);
+
+        x = -x;
+        r = -r;
+        t = tan(x);
+        //printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r);
+        if (!isIdentical(r, t) && !(r!=r && t!=t)) assert(fabs(r-t) <= .0000001);
+    }
+    // overflow
+    assert(isNaN(tan(real.infinity)));
+    assert(isNaN(tan(-real.infinity)));
+    // NaN propagation
+    assert(isIdentical( tan(NaN(0x0123L)), NaN(0x0123L) ));
+}
+
+unittest
+{
+    assert(equalsDigit(tan(PI / 3), std.math.sqrt(3.0), useDigits));
+}
+
+/***************
+ * Calculates the arc cosine of x,
+ * returning a value ranging from 0 to $(PI).
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)         $(TH acos(x)) $(TH invalid?))
+ *      $(TR $(TD $(GT)1.0)  $(TD $(NAN))  $(TD yes))
+ *      $(TR $(TD $(LT)-1.0) $(TD $(NAN))  $(TD yes))
+ *      $(TR $(TD $(NAN))    $(TD $(NAN))  $(TD yes))
+ *  )
+ */
+real acos(real x) @safe pure nothrow @nogc
+{
+    return atan2(sqrt(1-x*x), x);
+}
+
+/// ditto
+double acos(double x) @safe pure nothrow @nogc { return acos(cast(real)x); }
+
+/// ditto
+float acos(float x) @safe pure nothrow @nogc  { return acos(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(acos(0.5), std.math.PI / 3, useDigits));
+}
+
+/***************
+ * Calculates the arc sine of x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)            $(TH asin(x))      $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no))
+ *      $(TR $(TD $(GT)1.0)     $(TD $(NAN))       $(TD yes))
+ *      $(TR $(TD $(LT)-1.0)    $(TD $(NAN))       $(TD yes))
+ *  )
+ */
+real asin(real x) @safe pure nothrow @nogc
+{
+    return atan2(x, sqrt(1-x*x));
+}
+
+/// ditto
+double asin(double x) @safe pure nothrow @nogc { return asin(cast(real)x); }
+
+/// ditto
+float asin(float x) @safe pure nothrow @nogc  { return asin(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(asin(0.5), PI / 6, useDigits));
+}
+
+/***************
+ * Calculates the arc tangent of x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH atan(x))      $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) $(TD no))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN))       $(TD yes))
+ *  )
+ */
+real atan(real x) @safe pure nothrow @nogc
+{
+    version(InlineAsm_X86_Any)
+    {
+        return atan2(x, 1.0L);
+    }
+    else
+    {
+        // Coefficients for atan(x)
+        static immutable real[5] P = [
+           -5.0894116899623603312185E1L,
+           -9.9988763777265819915721E1L,
+           -6.3976888655834347413154E1L,
+           -1.4683508633175792446076E1L,
+           -8.6863818178092187535440E-1L,
+        ];
+        static immutable real[6] Q = [
+            1.5268235069887081006606E2L,
+            3.9157570175111990631099E2L,
+            3.6144079386152023162701E2L,
+            1.4399096122250781605352E2L,
+            2.2981886733594175366172E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // tan(PI/8)
+        enum real TAN_PI_8 = 4.1421356237309504880169e-1L;
+        // tan(3 * PI/8)
+        enum real TAN3_PI_8 = 2.41421356237309504880169L;
+
+        // Special cases.
+        if (x == 0.0)
+            return x;
+        if (isInfinity(x))
+            return copysign(PI_2, x);
+
+        // Make argument positive but save the sign.
+        bool sign = false;
+        if (signbit(x))
+        {
+            sign = true;
+            x = -x;
+        }
+
+        // Range reduction.
+        real y;
+        if (x > TAN3_PI_8)
+        {
+            y = PI_2;
+            x = -(1.0 / x);
+        }
+        else if (x > TAN_PI_8)
+        {
+            y = PI_4;
+            x = (x - 1.0)/(x + 1.0);
+        }
+        else
+            y = 0.0;
+
+        // Rational form in x^^2.
+        real z = x * x;
+        y = y + (poly(z, P) / poly(z, Q)) * z * x + x;
+
+        return (sign) ? -y : y;
+    }
+}
+
+/// ditto
+double atan(double x) @safe pure nothrow @nogc { return atan(cast(real)x); }
+
+/// ditto
+float atan(float x)  @safe pure nothrow @nogc { return atan(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(atan(std.math.sqrt(3.0)), PI / 3, useDigits));
+}
+
+/***************
+ * Calculates the arc tangent of y / x,
+ * returning a value ranging from -$(PI) to $(PI).
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH y)                 $(TH x)            $(TH atan(y, x)))
+ *      $(TR $(TD $(NAN))            $(TD anything)     $(TD $(NAN)) )
+ *      $(TR $(TD anything)          $(TD $(NAN))       $(TD $(NAN)) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(GT)0.0)     $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD +0.0)         $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(LT)0.0)     $(TD $(PLUSMN)$(PI)))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD -0.0)         $(TD $(PLUSMN)$(PI)))
+ *      $(TR $(TD $(GT)0.0)          $(TD $(PLUSMN)0.0) $(TD $(PI)/2) )
+ *      $(TR $(TD $(LT)0.0)          $(TD $(PLUSMN)0.0) $(TD -$(PI)/2) )
+ *      $(TR $(TD $(GT)0.0)          $(TD $(INFIN))     $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD anything)     $(TD $(PLUSMN)$(PI)/2))
+ *      $(TR $(TD $(GT)0.0)          $(TD -$(INFIN))    $(TD $(PLUSMN)$(PI)) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(INFIN))     $(TD $(PLUSMN)$(PI)/4))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD -$(INFIN))    $(TD $(PLUSMN)3$(PI)/4))
+ *      )
+ */
+real atan2(real y, real x) @trusted pure nothrow @nogc
+{
+    version(InlineAsm_X86_Any)
+    {
+        version (Win64)
+        {
+            asm pure nothrow @nogc {
+                naked;
+                fld real ptr [RDX]; // y
+                fld real ptr [RCX]; // x
+                fpatan;
+                ret;
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc {
+                fld y;
+                fld x;
+                fpatan;
+            }
+        }
+    }
+    else
+    {
+        // Special cases.
+        if (isNaN(x) || isNaN(y))
+            return real.nan;
+        if (y == 0.0)
+        {
+            if (x >= 0 && !signbit(x))
+                return copysign(0, y);
+            else
+                return copysign(PI, y);
+        }
+        if (x == 0.0)
+            return copysign(PI_2, y);
+        if (isInfinity(x))
+        {
+            if (signbit(x))
+            {
+                if (isInfinity(y))
+                    return copysign(3*PI_4, y);
+                else
+                    return copysign(PI, y);
+            }
+            else
+            {
+                if (isInfinity(y))
+                    return copysign(PI_4, y);
+                else
+                    return copysign(0.0, y);
+            }
+        }
+        if (isInfinity(y))
+            return copysign(PI_2, y);
+
+        // Call atan and determine the quadrant.
+        real z = atan(y / x);
+
+        if (signbit(x))
+        {
+            if (signbit(y))
+                z = z - PI;
+            else
+                z = z + PI;
+        }
+
+        if (z == 0.0)
+            return copysign(z, y);
+
+        return z;
+    }
+}
+
+/// ditto
+double atan2(double y, double x) @safe pure nothrow @nogc
+{
+    return atan2(cast(real)y, cast(real)x);
+}
+
+/// ditto
+float atan2(float y, float x) @safe pure nothrow @nogc
+{
+    return atan2(cast(real)y, cast(real)x);
+}
+
+unittest
+{
+    assert(equalsDigit(atan2(1.0L, std.math.sqrt(3.0L)), PI / 6, useDigits));
+}
+
+/***********************************
+ * Calculates the hyperbolic cosine of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH cosh(x))      $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)0.0) $(TD no) )
+ *      )
+ */
+real cosh(real x) @safe pure nothrow @nogc
+{
+    //  cosh = (exp(x)+exp(-x))/2.
+    // The naive implementation works correctly.
+    real y = exp(x);
+    return (y + 1.0/y) * 0.5;
+}
+
+/// ditto
+double cosh(double x) @safe pure nothrow @nogc { return cosh(cast(real)x); }
+
+/// ditto
+float cosh(float x) @safe pure nothrow @nogc  { return cosh(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(cosh(1.0), (E + 1.0 / E) / 2, useDigits));
+}
+
+/***********************************
+ * Calculates the hyperbolic sine of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH sinh(x))           $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0)      $(TD no))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no))
+ *      )
+ */
+real sinh(real x) @safe pure nothrow @nogc
+{
+    //  sinh(x) =  (exp(x)-exp(-x))/2;
+    // Very large arguments could cause an overflow, but
+    // the maximum value of x for which exp(x) + exp(-x)) != exp(x)
+    // is x = 0.5 * (real.mant_dig) * LN2. // = 22.1807 for real80.
+    if (fabs(x) > real.mant_dig * LN2)
+    {
+        return copysign(0.5 * exp(fabs(x)), x);
+    }
+
+    real y = expm1(x);
+    return 0.5 * y / (y+1) * (y+2);
+}
+
+/// ditto
+double sinh(double x) @safe pure nothrow @nogc { return sinh(cast(real)x); }
+
+/// ditto
+float sinh(float x) @safe pure nothrow @nogc  { return sinh(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(sinh(1.0), (E - 1.0 / E) / 2, useDigits));
+}
+
+/***********************************
+ * Calculates the hyperbolic tangent of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH tanh(x))      $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) $(TD no) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)1.0) $(TD no))
+ *      )
+ */
+real tanh(real x) @safe pure nothrow @nogc
+{
+    //  tanh(x) = (exp(x) - exp(-x))/(exp(x)+exp(-x))
+    if (fabs(x) > real.mant_dig * LN2)
+    {
+        return copysign(1, x);
+    }
+
+    real y = expm1(2*x);
+    return y / (y + 2);
+}
+
+/// ditto
+double tanh(double x) @safe pure nothrow @nogc { return tanh(cast(real)x); }
+
+/// ditto
+float tanh(float x) @safe pure nothrow @nogc { return tanh(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(tanh(1.0), sinh(1.0) / cosh(1.0), 15));
+}
+
+package:
+
+/* Returns cosh(x) + I * sinh(x)
+ * Only one call to exp() is performed.
+ */
+creal coshisinh(real x) @safe pure nothrow @nogc
+{
+    // See comments for cosh, sinh.
+    if (fabs(x) > real.mant_dig * LN2)
+    {
+        real y = exp(fabs(x));
+        return y * 0.5 + 0.5i * copysign(y, x);
+    }
+    else
+    {
+        real y = expm1(x);
+        return (y + 1.0 + 1.0/(y + 1.0)) * 0.5 + 0.5i * y / (y+1) * (y+2);
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    creal c = coshisinh(3.0L);
+    assert(c.re == cosh(3.0L));
+    assert(c.im == sinh(3.0L));
+}
+
+public:
+
+/***********************************
+ * Calculates the inverse hyperbolic cosine of x.
+ *
+ *  Mathematically, acosh(x) = log(x + sqrt( x*x - 1))
+ *
+ * $(TABLE_DOMRG
+ *  $(DOMAIN 1..$(INFIN))
+ *  $(RANGE  1..log(real.max), $(INFIN)) )
+ *      $(TABLE_SV
+ *    $(SVH  x,     acosh(x) )
+ *    $(SV  $(NAN), $(NAN) )
+ *    $(SV  $(LT)1,     $(NAN) )
+ *    $(SV  1,      0       )
+ *    $(SV  +$(INFIN),+$(INFIN))
+ *  )
+ */
+real acosh(real x) @safe pure nothrow @nogc
+{
+    if (x > 1/real.epsilon)
+        return LN2 + log(x);
+    else
+        return log(x + sqrt(x*x - 1));
+}
+
+/// ditto
+double acosh(double x) @safe pure nothrow @nogc { return acosh(cast(real)x); }
+
+/// ditto
+float acosh(float x) @safe pure nothrow @nogc  { return acosh(cast(real)x); }
+
+
+unittest
+{
+    assert(isNaN(acosh(0.9)));
+    assert(isNaN(acosh(real.nan)));
+    assert(acosh(1.0)==0.0);
+    assert(acosh(real.infinity) == real.infinity);
+    assert(isNaN(acosh(0.5)));
+    assert(equalsDigit(acosh(cosh(3.0)), 3, useDigits));
+}
+
+/***********************************
+ * Calculates the inverse hyperbolic sine of x.
+ *
+ *  Mathematically,
+ *  ---------------
+ *  asinh(x) =  log( x + sqrt( x*x + 1 )) // if x >= +0
+ *  asinh(x) = -log(-x + sqrt( x*x + 1 )) // if x <= -0
+ *  -------------
+ *
+ *    $(TABLE_SV
+ *    $(SVH x,                asinh(x)       )
+ *    $(SV  $(NAN),           $(NAN)         )
+ *    $(SV  $(PLUSMN)0,       $(PLUSMN)0      )
+ *    $(SV  $(PLUSMN)$(INFIN),$(PLUSMN)$(INFIN))
+ *    )
+ */
+real asinh(real x) @safe pure nothrow @nogc
+{
+    return (fabs(x) > 1 / real.epsilon)
+       // beyond this point, x*x + 1 == x*x
+       ?  copysign(LN2 + log(fabs(x)), x)
+       // sqrt(x*x + 1) ==  1 + x * x / ( 1 + sqrt(x*x + 1) )
+       : copysign(log1p(fabs(x) + x*x / (1 + sqrt(x*x + 1)) ), x);
+}
+
+/// ditto
+double asinh(double x) @safe pure nothrow @nogc { return asinh(cast(real)x); }
+
+/// ditto
+float asinh(float x) @safe pure nothrow @nogc { return asinh(cast(real)x); }
+
+unittest
+{
+    assert(isIdentical(asinh(0.0), 0.0));
+    assert(isIdentical(asinh(-0.0), -0.0));
+    assert(asinh(real.infinity) == real.infinity);
+    assert(asinh(-real.infinity) == -real.infinity);
+    assert(isNaN(asinh(real.nan)));
+    assert(equalsDigit(asinh(sinh(3.0)), 3, useDigits));
+}
+
+/***********************************
+ * Calculates the inverse hyperbolic tangent of x,
+ * returning a value from ranging from -1 to 1.
+ *
+ * Mathematically, atanh(x) = log( (1+x)/(1-x) ) / 2
+ *
+ *
+ * $(TABLE_DOMRG
+ *  $(DOMAIN -$(INFIN)..$(INFIN))
+ *  $(RANGE  -1..1) )
+ * $(TABLE_SV
+ *    $(SVH  x,     acosh(x) )
+ *    $(SV  $(NAN), $(NAN) )
+ *    $(SV  $(PLUSMN)0, $(PLUSMN)0)
+ *    $(SV  -$(INFIN), -0)
+ * )
+ */
+real atanh(real x) @safe pure nothrow @nogc
+{
+    // log( (1+x)/(1-x) ) == log ( 1 + (2*x)/(1-x) )
+    return  0.5 * log1p( 2 * x / (1 - x) );
+}
+
+/// ditto
+double atanh(double x) @safe pure nothrow @nogc { return atanh(cast(real)x); }
+
+/// ditto
+float atanh(float x) @safe pure nothrow @nogc { return atanh(cast(real)x); }
+
+
+unittest
+{
+    assert(isIdentical(atanh(0.0), 0.0));
+    assert(isIdentical(atanh(-0.0),-0.0));
+    assert(isNaN(atanh(real.nan)));
+    assert(isNaN(atanh(-real.infinity)));
+    assert(atanh(0.0) == 0);
+    assert(equalsDigit(atanh(tanh(0.5L)), 0.5, useDigits));
+}
+
+/*****************************************
+ * Returns x rounded to a long value using the current rounding mode.
+ * If the integer value of x is
+ * greater than long.max, the result is
+ * indeterminate.
+ */
+long rndtol(real x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.rndtol(x); }
+//FIXME
+///ditto
+long rndtol(double x) @safe pure nothrow @nogc { return rndtol(cast(real)x); }
+//FIXME
+///ditto
+long rndtol(float x) @safe pure nothrow @nogc { return rndtol(cast(real)x); }
+
+unittest
+{
+    long function(real) prndtol = &rndtol;
+    assert(prndtol != null);
+}
+
+/*****************************************
+ * Returns x rounded to a long value using the FE_TONEAREST rounding mode.
+ * If the integer value of x is
+ * greater than long.max, the result is
+ * indeterminate.
+ */
+extern (C) real rndtonl(real x);
+
+/***************************************
+ * Compute square root of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)         $(TH sqrt(x))   $(TH invalid?))
+ *      $(TR $(TD -0.0)      $(TD -0.0)      $(TD no))
+ *      $(TR $(TD $(LT)0.0)  $(TD $(NAN))    $(TD yes))
+ *      $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no))
+ *      )
+ */
+float sqrt(float x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); }
+
+/// ditto
+double sqrt(double x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); }
+
+/// ditto
+real sqrt(real x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); }
+
+@safe pure nothrow @nogc unittest
+{
+    //ctfe
+    enum ZX80 = sqrt(7.0f);
+    enum ZX81 = sqrt(7.0);
+    enum ZX82 = sqrt(7.0L);
+
+    assert(isNaN(sqrt(-1.0f)));
+    assert(isNaN(sqrt(-1.0)));
+    assert(isNaN(sqrt(-1.0L)));
+}
+
+unittest
+{
+    float function(float) psqrtf = &sqrt;
+    assert(psqrtf != null);
+    double function(double) psqrtd = &sqrt;
+    assert(psqrtd != null);
+    real function(real) psqrtr = &sqrt;
+    assert(psqrtr != null);
+}
+
+creal sqrt(creal z) @nogc @safe pure nothrow
+{
+    creal c;
+    real x,y,w,r;
+
+    if (z == 0)
+    {
+        c = 0 + 0i;
+    }
+    else
+    {
+        real z_re = z.re;
+        real z_im = z.im;
+
+        x = fabs(z_re);
+        y = fabs(z_im);
+        if (x >= y)
+        {
+            r = y / x;
+            w = sqrt(x) * sqrt(0.5 * (1 + sqrt(1 + r * r)));
+        }
+        else
+        {
+            r = x / y;
+            w = sqrt(y) * sqrt(0.5 * (r + sqrt(1 + r * r)));
+        }
+
+        if (z_re >= 0)
+        {
+            c = w + (z_im / (w + w)) * 1.0i;
+        }
+        else
+        {
+            if (z_im < 0)
+                w = -w;
+            c = z_im / (w + w) + w * 1.0i;
+        }
+    }
+    return c;
+}
+
+/**
+ * Calculates e$(SUPERSCRIPT x).
+ *
+ *  $(TABLE_SV
+ *    $(TR $(TH x)             $(TH e$(SUPERSCRIPT x)) )
+ *    $(TR $(TD +$(INFIN))     $(TD +$(INFIN)) )
+ *    $(TR $(TD -$(INFIN))     $(TD +0.0)      )
+ *    $(TR $(TD $(NAN))        $(TD $(NAN))    )
+ *  )
+ */
+real exp(real x) @trusted pure nothrow @nogc
+{
+    version(D_InlineAsm_X86)
+    {
+        //  e^^x = 2^^(LOG2E*x)
+        // (This is valid because the overflow & underflow limits for exp
+        // and exp2 are so similar).
+        return exp2(LOG2E*x);
+    }
+    else version(D_InlineAsm_X86_64)
+    {
+        //  e^^x = 2^^(LOG2E*x)
+        // (This is valid because the overflow & underflow limits for exp
+        // and exp2 are so similar).
+        return exp2(LOG2E*x);
+    }
+    else
+    {
+        alias F = floatTraits!real;
+        static if (F.realFormat == RealFormat.ieeeDouble)
+        {
+            // Coefficients for exp(x)
+            static immutable real[3] P = [
+                9.99999999999999999910E-1L,
+                3.02994407707441961300E-2L,
+                1.26177193074810590878E-4L,
+            ];
+            static immutable real[4] Q = [
+                2.00000000000000000009E0L,
+                2.27265548208155028766E-1L,
+                2.52448340349684104192E-3L,
+                3.00198505138664455042E-6L,
+            ];
+
+            // C1 + C2 = LN2.
+            enum real C1 = 6.93145751953125E-1;
+            enum real C2 = 1.42860682030941723212E-6;
+
+            // Overflow and Underflow limits.
+            enum real OF =  7.09782712893383996732E2;  // ln((1-2^-53) * 2^1024)
+            enum real UF = -7.451332191019412076235E2; // ln(2^-1075)
+        }
+        else static if (F.realFormat == RealFormat.ieeeExtended)
+        {
+            // Coefficients for exp(x)
+            static immutable real[3] P = [
+                9.9999999999999999991025E-1L,
+                3.0299440770744196129956E-2L,
+                1.2617719307481059087798E-4L,
+            ];
+            static immutable real[4] Q = [
+                2.0000000000000000000897E0L,
+                2.2726554820815502876593E-1L,
+                2.5244834034968410419224E-3L,
+                3.0019850513866445504159E-6L,
+            ];
+
+            // C1 + C2 = LN2.
+            enum real C1 = 6.9314575195312500000000E-1L;
+            enum real C2 = 1.4286068203094172321215E-6L;
+
+            // Overflow and Underflow limits.
+            enum real OF =  1.1356523406294143949492E4L;  // ln((1-2^-64) * 2^16384)
+            enum real UF = -1.13994985314888605586758E4L; // ln(2^-16446)
+        }
+        else
+            static assert(0, "Not implemented for this architecture");
+
+        // Special cases.
+        // FIXME: set IEEE flags accordingly
+        if (isNaN(x))
+            return x;
+        if (x > OF)
+            return real.infinity;
+        if (x < UF)
+            return 0.0;
+
+        // Express: e^^x = e^^g * 2^^n
+        //   = e^^g * e^^(n * LOG2E)
+        //   = e^^(g + n * LOG2E)
+        int n = cast(int)floor(LOG2E * x + 0.5);
+        x -= n * C1;
+        x -= n * C2;
+
+        // Rational approximation for exponential of the fractional part:
+        //  e^^x = 1 + 2x P(x^^2) / (Q(x^^2) - P(x^^2))
+        real xx = x * x;
+        real px = x * poly(xx, P);
+        x = px / (poly(xx, Q) - px);
+        x = 1.0 + ldexp(x, 1);
+
+        // Scale by power of 2.
+        x = ldexp(x, n);
+
+        return x;
+    }
+}
+
+/// ditto
+double exp(double x) @safe pure nothrow @nogc  { return exp(cast(real)x); }
+
+/// ditto
+float exp(float x)  @safe pure nothrow @nogc   { return exp(cast(real)x); }
+
+unittest
+{
+    assert(equalsDigit(exp(3.0L), E * E * E, useDigits));
+}
+
+/**
+ * Calculates the value of the natural logarithm base (e)
+ * raised to the power of x, minus 1.
+ *
+ * For very small x, expm1(x) is more accurate
+ * than exp(x)-1.
+ *
+ *  $(TABLE_SV
+ *    $(TR $(TH x)             $(TH e$(SUPERSCRIPT x)-1)  )
+ *    $(TR $(TD $(PLUSMN)0.0)  $(TD $(PLUSMN)0.0) )
+ *    $(TR $(TD +$(INFIN))     $(TD +$(INFIN))    )
+ *    $(TR $(TD -$(INFIN))     $(TD -1.0)         )
+ *    $(TR $(TD $(NAN))        $(TD $(NAN))       )
+ *  )
+ */
+real expm1(real x) @trusted pure nothrow @nogc
+{
+    version(D_InlineAsm_X86)
+    {
+        enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4
+        asm pure nothrow @nogc
+        {
+            /*  expm1() for x87 80-bit reals, IEEE754-2008 conformant.
+             * Author: Don Clugston.
+             *
+             *    expm1(x) = 2^^(rndint(y))* 2^^(y-rndint(y)) - 1 where y = LN2*x.
+             *    = 2rndy * 2ym1 + 2rndy - 1, where 2rndy = 2^^(rndint(y))
+             *     and 2ym1 = (2^^(y-rndint(y))-1).
+             *    If 2rndy  < 0.5*real.epsilon, result is -1.
+             *    Implementation is otherwise the same as for exp2()
+             */
+            naked;
+            fld real ptr [ESP+4] ; // x
+            mov AX, [ESP+4+8]; // AX = exponent and sign
+            sub ESP, 12+8; // Create scratch space on the stack
+            // [ESP,ESP+2] = scratchint
+            // [ESP+4..+6, +8..+10, +10] = scratchreal
+            // set scratchreal mantissa = 1.0
+            mov dword ptr [ESP+8], 0;
+            mov dword ptr [ESP+8+4], 0x80000000;
+            and AX, 0x7FFF; // drop sign bit
+            cmp AX, 0x401D; // avoid InvalidException in fist
+            jae L_extreme;
+            fldl2e;
+            fmulp ST(1), ST; // y = x*log2(e)
+            fist dword ptr [ESP]; // scratchint = rndint(y)
+            fisub dword ptr [ESP]; // y - rndint(y)
+            // and now set scratchreal exponent
+            mov EAX, [ESP];
+            add EAX, 0x3fff;
+            jle short L_largenegative;
+            cmp EAX,0x8000;
+            jge short L_largepositive;
+            mov [ESP+8+8],AX;
+            f2xm1; // 2ym1 = 2^^(y-rndint(y)) -1
+            fld real ptr [ESP+8] ; // 2rndy = 2^^rndint(y)
+            fmul ST(1), ST;  // ST=2rndy, ST(1)=2rndy*2ym1
+            fld1;
+            fsubp ST(1), ST; // ST = 2rndy-1, ST(1) = 2rndy * 2ym1 - 1
+            faddp ST(1), ST; // ST = 2rndy * 2ym1 + 2rndy - 1
+            add ESP,12+8;
+            ret PARAMSIZE;
+
+L_extreme:  // Extreme exponent. X is very large positive, very
+            // large negative, infinity, or NaN.
+            fxam;
+            fstsw AX;
+            test AX, 0x0400; // NaN_or_zero, but we already know x!=0
+            jz L_was_nan;  // if x is NaN, returns x
+            test AX, 0x0200;
+            jnz L_largenegative;
+L_largepositive:
+            // Set scratchreal = real.max.
+            // squaring it will create infinity, and set overflow flag.
+            mov word  ptr [ESP+8+8], 0x7FFE;
+            fstp ST(0);
+            fld real ptr [ESP+8];  // load scratchreal
+            fmul ST(0), ST;        // square it, to create havoc!
+L_was_nan:
+            add ESP,12+8;
+            ret PARAMSIZE;
+L_largenegative:
+            fstp ST(0);
+            fld1;
+            fchs; // return -1. Underflow flag is not set.
+            add ESP,12+8;
+            ret PARAMSIZE;
+        }
+    }
+    else version(D_InlineAsm_X86_64)
+    {
+        asm pure nothrow @nogc
+        {
+            naked;
+        }
+        version (Win64)
+        {
+            asm pure nothrow @nogc
+            {
+                fld   real ptr [RCX];  // x
+                mov   AX,[RCX+8];      // AX = exponent and sign
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc
+            {
+                fld   real ptr [RSP+8];  // x
+                mov   AX,[RSP+8+8];      // AX = exponent and sign
+            }
+        }
+        asm pure nothrow @nogc
+        {
+            /*  expm1() for x87 80-bit reals, IEEE754-2008 conformant.
+             * Author: Don Clugston.
+             *
+             *    expm1(x) = 2^(rndint(y))* 2^(y-rndint(y)) - 1 where y = LN2*x.
+             *    = 2rndy * 2ym1 + 2rndy - 1, where 2rndy = 2^(rndint(y))
+             *     and 2ym1 = (2^(y-rndint(y))-1).
+             *    If 2rndy  < 0.5*real.epsilon, result is -1.
+             *    Implementation is otherwise the same as for exp2()
+             */
+            sub RSP, 24;       // Create scratch space on the stack
+            // [RSP,RSP+2] = scratchint
+            // [RSP+4..+6, +8..+10, +10] = scratchreal
+            // set scratchreal mantissa = 1.0
+            mov dword ptr [RSP+8], 0;
+            mov dword ptr [RSP+8+4], 0x80000000;
+            and AX, 0x7FFF; // drop sign bit
+            cmp AX, 0x401D; // avoid InvalidException in fist
+            jae L_extreme;
+            fldl2e;
+            fmul ; // y = x*log2(e)
+            fist dword ptr [RSP]; // scratchint = rndint(y)
+            fisub dword ptr [RSP]; // y - rndint(y)
+            // and now set scratchreal exponent
+            mov EAX, [RSP];
+            add EAX, 0x3fff;
+            jle short L_largenegative;
+            cmp EAX,0x8000;
+            jge short L_largepositive;
+            mov [RSP+8+8],AX;
+            f2xm1; // 2^(y-rndint(y)) -1
+            fld real ptr [RSP+8] ; // 2^rndint(y)
+            fmul ST(1), ST;
+            fld1;
+            fsubp ST(1), ST;
+            fadd;
+            add RSP,24;
+            ret;
+
+L_extreme:  // Extreme exponent. X is very large positive, very
+            // large negative, infinity, or NaN.
+            fxam;
+            fstsw AX;
+            test AX, 0x0400; // NaN_or_zero, but we already know x!=0
+            jz L_was_nan;  // if x is NaN, returns x
+            test AX, 0x0200;
+            jnz L_largenegative;
+L_largepositive:
+            // Set scratchreal = real.max.
+            // squaring it will create infinity, and set overflow flag.
+            mov word  ptr [RSP+8+8], 0x7FFE;
+            fstp ST(0);
+            fld real ptr [RSP+8];  // load scratchreal
+            fmul ST(0), ST;        // square it, to create havoc!
+L_was_nan:
+            add RSP,24;
+            ret;
+
+L_largenegative:
+            fstp ST(0);
+            fld1;
+            fchs; // return -1. Underflow flag is not set.
+            add RSP,24;
+            ret;
+        }
+    }
+    else
+    {
+        // Coefficients for exp(x) - 1
+        static immutable real[5] P = [
+           -1.586135578666346600772998894928250240826E4L,
+            2.642771505685952966904660652518429479531E3L,
+           -3.423199068835684263987132888286791620673E2L,
+            1.800826371455042224581246202420972737840E1L,
+           -5.238523121205561042771939008061958820811E-1L,
+        ];
+        static immutable real[6] Q = [
+           -9.516813471998079611319047060563358064497E4L,
+            3.964866271411091674556850458227710004570E4L,
+           -7.207678383830091850230366618190187434796E3L,
+            7.206038318724600171970199625081491823079E2L,
+           -4.002027679107076077238836622982900945173E1L,
+            1.000000000000000000000000000000000000000E0L,
+        ];
+
+        // C1 + C2 = LN2.
+        enum real C1 = 6.9314575195312500000000E-1L;
+        enum real C2 = 1.4286068203094172321215E-6L;
+
+        // Overflow and Underflow limits.
+        enum real OF =  1.1356523406294143949492E4L;
+        enum real UF = -4.5054566736396445112120088E1L;
+
+        // Special cases.
+        if (x > OF)
+            return real.infinity;
+        if (x == 0.0)
+            return x;
+        if (x < UF)
+            return -1.0;
+
+        // Express x = LN2 (n + remainder), remainder not exceeding 1/2.
+        int n = cast(int)floor(0.5 + x / LN2);
+        x -= n * C1;
+        x -= n * C2;
+
+        // Rational approximation:
+        //  exp(x) - 1 = x + 0.5 x^^2 + x^^3 P(x) / Q(x)
+        real px = x * poly(x, P);
+        real qx = poly(x, Q);
+        real xx = x * x;
+        qx = x + (0.5 * xx + xx * px / qx);
+
+        // We have qx = exp(remainder LN2) - 1, so:
+        //  exp(x) - 1 = 2^^n (qx + 1) - 1 = 2^^n qx + 2^^n - 1.
+        px = ldexp(1.0, n);
+        x = px * qx + (px - 1.0);
+
+        return x;
+    }
+}
+
+
+
+/**
+ * Calculates 2$(SUPERSCRIPT x).
+ *
+ *  $(TABLE_SV
+ *    $(TR $(TH x)             $(TH exp2(x))   )
+ *    $(TR $(TD +$(INFIN))     $(TD +$(INFIN)) )
+ *    $(TR $(TD -$(INFIN))     $(TD +0.0)      )
+ *    $(TR $(TD $(NAN))        $(TD $(NAN))    )
+ *  )
+ */
+real exp2(real x) @nogc @trusted pure nothrow
+{
+    version(D_InlineAsm_X86)
+    {
+        enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4
+
+        asm pure nothrow @nogc
+        {
+            /*  exp2() for x87 80-bit reals, IEEE754-2008 conformant.
+             * Author: Don Clugston.
+             *
+             * exp2(x) = 2^^(rndint(x))* 2^^(y-rndint(x))
+             * The trick for high performance is to avoid the fscale(28cycles on core2),
+             * frndint(19 cycles), leaving f2xm1(19 cycles) as the only slow instruction.
+             *
+             * We can do frndint by using fist. BUT we can't use it for huge numbers,
+             * because it will set the Invalid Operation flag if overflow or NaN occurs.
+             * Fortunately, whenever this happens the result would be zero or infinity.
+             *
+             * We can perform fscale by directly poking into the exponent. BUT this doesn't
+             * work for the (very rare) cases where the result is subnormal. So we fall back
+             * to the slow method in that case.
+             */
+            naked;
+            fld real ptr [ESP+4] ; // x
+            mov AX, [ESP+4+8]; // AX = exponent and sign
+            sub ESP, 12+8; // Create scratch space on the stack
+            // [ESP,ESP+2] = scratchint
+            // [ESP+4..+6, +8..+10, +10] = scratchreal
+            // set scratchreal mantissa = 1.0
+            mov dword ptr [ESP+8], 0;
+            mov dword ptr [ESP+8+4], 0x80000000;
+            and AX, 0x7FFF; // drop sign bit
+            cmp AX, 0x401D; // avoid InvalidException in fist
+            jae L_extreme;
+            fist dword ptr [ESP]; // scratchint = rndint(x)
+            fisub dword ptr [ESP]; // x - rndint(x)
+            // and now set scratchreal exponent
+            mov EAX, [ESP];
+            add EAX, 0x3fff;
+            jle short L_subnormal;
+            cmp EAX,0x8000;
+            jge short L_overflow;
+            mov [ESP+8+8],AX;
+L_normal:
+            f2xm1;
+            fld1;
+            faddp ST(1), ST; // 2^^(x-rndint(x))
+            fld real ptr [ESP+8] ; // 2^^rndint(x)
+            add ESP,12+8;
+            fmulp ST(1), ST;
+            ret PARAMSIZE;
+
+L_subnormal:
+            // Result will be subnormal.
+            // In this rare case, the simple poking method doesn't work.
+            // The speed doesn't matter, so use the slow fscale method.
+            fild dword ptr [ESP];  // scratchint
+            fld1;
+            fscale;
+            fstp real ptr [ESP+8]; // scratchreal = 2^^scratchint
+            fstp ST(0);         // drop scratchint
+            jmp L_normal;
+
+L_extreme:  // Extreme exponent. X is very large positive, very
+            // large negative, infinity, or NaN.
+            fxam;
+            fstsw AX;
+            test AX, 0x0400; // NaN_or_zero, but we already know x!=0
+            jz L_was_nan;  // if x is NaN, returns x
+            // set scratchreal = real.min_normal
+            // squaring it will return 0, setting underflow flag
+            mov word  ptr [ESP+8+8], 1;
+            test AX, 0x0200;
+            jnz L_waslargenegative;
+L_overflow:
+            // Set scratchreal = real.max.
+            // squaring it will create infinity, and set overflow flag.
+            mov word  ptr [ESP+8+8], 0x7FFE;
+L_waslargenegative:
+            fstp ST(0);
+            fld real ptr [ESP+8];  // load scratchreal
+            fmul ST(0), ST;        // square it, to create havoc!
+L_was_nan:
+            add ESP,12+8;
+            ret PARAMSIZE;
+        }
+    }
+    else version(D_InlineAsm_X86_64)
+    {
+        asm pure nothrow @nogc
+        {
+            naked;
+        }
+        version (Win64)
+        {
+            asm pure nothrow @nogc
+            {
+                fld   real ptr [RCX];  // x
+                mov   AX,[RCX+8];      // AX = exponent and sign
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc
+            {
+                fld   real ptr [RSP+8];  // x
+                mov   AX,[RSP+8+8];      // AX = exponent and sign
+            }
+        }
+        asm pure nothrow @nogc
+        {
+            /*  exp2() for x87 80-bit reals, IEEE754-2008 conformant.
+             * Author: Don Clugston.
+             *
+             * exp2(x) = 2^(rndint(x))* 2^(y-rndint(x))
+             * The trick for high performance is to avoid the fscale(28cycles on core2),
+             * frndint(19 cycles), leaving f2xm1(19 cycles) as the only slow instruction.
+             *
+             * We can do frndint by using fist. BUT we can't use it for huge numbers,
+             * because it will set the Invalid Operation flag is overflow or NaN occurs.
+             * Fortunately, whenever this happens the result would be zero or infinity.
+             *
+             * We can perform fscale by directly poking into the exponent. BUT this doesn't
+             * work for the (very rare) cases where the result is subnormal. So we fall back
+             * to the slow method in that case.
+             */
+            sub RSP, 24; // Create scratch space on the stack
+            // [RSP,RSP+2] = scratchint
+            // [RSP+4..+6, +8..+10, +10] = scratchreal
+            // set scratchreal mantissa = 1.0
+            mov dword ptr [RSP+8], 0;
+            mov dword ptr [RSP+8+4], 0x80000000;
+            and AX, 0x7FFF; // drop sign bit
+            cmp AX, 0x401D; // avoid InvalidException in fist
+            jae L_extreme;
+            fist dword ptr [RSP]; // scratchint = rndint(x)
+            fisub dword ptr [RSP]; // x - rndint(x)
+            // and now set scratchreal exponent
+            mov EAX, [RSP];
+            add EAX, 0x3fff;
+            jle short L_subnormal;
+            cmp EAX,0x8000;
+            jge short L_overflow;
+            mov [RSP+8+8],AX;
+L_normal:
+            f2xm1;
+            fld1;
+            fadd; // 2^(x-rndint(x))
+            fld real ptr [RSP+8] ; // 2^rndint(x)
+            add RSP,24;
+            fmulp ST(1), ST;
+            ret;
+
+L_subnormal:
+            // Result will be subnormal.
+            // In this rare case, the simple poking method doesn't work.
+            // The speed doesn't matter, so use the slow fscale method.
+            fild dword ptr [RSP];  // scratchint
+            fld1;
+            fscale;
+            fstp real ptr [RSP+8]; // scratchreal = 2^scratchint
+            fstp ST(0);         // drop scratchint
+            jmp L_normal;
+
+L_extreme:  // Extreme exponent. X is very large positive, very
+            // large negative, infinity, or NaN.
+            fxam;
+            fstsw AX;
+            test AX, 0x0400; // NaN_or_zero, but we already know x!=0
+            jz L_was_nan;  // if x is NaN, returns x
+            // set scratchreal = real.min
+            // squaring it will return 0, setting underflow flag
+            mov word  ptr [RSP+8+8], 1;
+            test AX, 0x0200;
+            jnz L_waslargenegative;
+L_overflow:
+            // Set scratchreal = real.max.
+            // squaring it will create infinity, and set overflow flag.
+            mov word  ptr [RSP+8+8], 0x7FFE;
+L_waslargenegative:
+            fstp ST(0);
+            fld real ptr [RSP+8];  // load scratchreal
+            fmul ST(0), ST;        // square it, to create havoc!
+L_was_nan:
+            add RSP,24;
+            ret;
+        }
+    }
+    else
+    {
+        // Coefficients for exp2(x)
+        static immutable real[3] P = [
+            2.0803843631901852422887E6L,
+            3.0286971917562792508623E4L,
+            6.0614853552242266094567E1L,
+        ];
+        static immutable real[4] Q = [
+            6.0027204078348487957118E6L,
+            3.2772515434906797273099E5L,
+            1.7492876999891839021063E3L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // Overflow and Underflow limits.
+        enum real OF =  16384.0L;
+        enum real UF = -16382.0L;
+
+        // Special cases.
+        if (isNaN(x))
+            return x;
+        if (x > OF)
+            return real.infinity;
+        if (x < UF)
+            return 0.0;
+
+        // Separate into integer and fractional parts.
+        int n = cast(int)floor(x + 0.5);
+        x -= n;
+
+        // Rational approximation:
+        //  exp2(x) = 1.0 + 2x P(x^^2) / (Q(x^^2) - P(x^^2))
+        real xx = x * x;
+        real px = x * poly(xx, P);
+        x = px / (poly(xx, Q) - px);
+        x = 1.0 + ldexp(x, 1);
+
+        // Scale by power of 2.
+        x = ldexp(x, n);
+
+        return x;
+    }
+}
+
+///
+unittest
+{
+    assert(feqrel(exp2(0.5L), SQRT2) >= real.mant_dig -1);
+    assert(exp2(8.0L) == 256.0);
+    assert(exp2(-9.0L)== 1.0L/512.0);
+}
+
+unittest
+{
+    version(CRuntime_Microsoft) {} else // aexp2/exp2f/exp2l not implemented
+    {
+        assert( core.stdc.math.exp2f(0.0f) == 1 );
+        assert( core.stdc.math.exp2 (0.0)  == 1 );
+        assert( core.stdc.math.exp2l(0.0L) == 1 );
+    }
+}
+
+unittest
+{
+    FloatingPointControl ctrl;
+    if(FloatingPointControl.hasExceptionTraps)
+        ctrl.disableExceptions(FloatingPointControl.allExceptions);
+    ctrl.rounding = FloatingPointControl.roundToNearest;
+
+    static if (real.mant_dig == 64) // 80-bit reals
+    {
+        static immutable real[2][] exptestpoints =
+        [ //  x               exp(x)
+            [ 1.0L,           E                            ],
+            [ 0.5L,           0x1.a61298e1e069bc97p+0L     ],
+            [ 3.0L,           E*E*E                        ],
+            [ 0x1.1p+13L,     0x1.29aeffefc8ec645p+12557L  ], // near overflow
+            [ 0x1.7p+13L,     real.infinity                ], // close overflow
+            [ 0x1p+80L,       real.infinity                ], // far overflow
+            [ real.infinity,  real.infinity                ],
+            [-0x1.18p+13L,    0x1.5e4bf54b4806db9p-12927L  ], // near underflow
+            [-0x1.625p+13L,   0x1.a6bd68a39d11f35cp-16358L ], // ditto
+            [-0x1.62dafp+13L, 0x1.96c53d30277021dp-16383L  ], // near underflow - subnormal
+            [-0x1.643p+13L,   0x1p-16444L                  ], // ditto
+            [-0x1.645p+13L,   0                            ], // close underflow
+            [-0x1p+30L,       0                            ], // far underflow
+        ];
+    }
+    else static if (real.mant_dig == 53) // 64-bit reals
+    {
+        static immutable real[2][] exptestpoints =
+        [ //  x,             exp(x)
+            [ 1.0L,          E                        ],
+            [ 0.5L,          0x1.a61298e1e069cp+0L    ],
+            [ 3.0L,          E*E*E                    ],
+            [ 0x1.6p+9L,     0x1.93bf4ec282efbp+1015L ], // near overflow
+            [ 0x1.7p+9L,     real.infinity            ], // close overflow
+            [ 0x1p+80L,      real.infinity            ], // far overflow
+            [ real.infinity, real.infinity            ],
+            [-0x1.6p+9L,     0x1.44a3824e5285fp-1016L ], // near underflow
+            [-0x1.64p+9L,    0x0.06f84920bb2d3p-1022L ], // near underflow - subnormal
+            [-0x1.743p+9L,   0x0.0000000000001p-1022L ], // ditto
+            [-0x1.8p+9L,     0                        ], // close underflow
+            [-0x1p30L,       0                        ], // far underflow
+        ];
+    }
+    else
+        static assert(0, "No exp() tests for real type!");
+
+    const minEqualDecimalDigits = real.dig - 3;
+    real x;
+    IeeeFlags f;
+    foreach (ref pair; exptestpoints)
+    {
+        resetIeeeFlags();
+        x = exp(pair[0]);
+        f = ieeeFlags;
+        assert(equalsDigit(x, pair[1], minEqualDecimalDigits));
+
+        version (IeeeFlagsSupport)
+        {
+            // Check the overflow bit
+            //if (x == real.infinity)
+            //{
+            //    // don't care about the overflow bit if input was inf
+            //    // (e.g., the LLVM intrinsic doesn't set it on Linux x86_64)
+            //    assert(pair[0] == real.infinity || f.overflow);
+            //}
+            //else
+            //    assert(!f.overflow);
+            // Check the underflow bit
+            //assert(f.underflow == (fabs(x) < real.min_normal));
+            // Invalid and div by zero shouldn't be affected.
+            assert(!f.invalid);
+            assert(!f.divByZero);
+        }
+    }
+    // Ideally, exp(0) would not set the inexact flag.
+    // Unfortunately, fldl2e sets it!
+    // So it's not realistic to avoid setting it.
+    assert(exp(0.0L) == 1.0);
+
+    // NaN propagation. Doesn't set flags, bcos was already NaN.
+    resetIeeeFlags();
+    x = exp(real.nan);
+    f = ieeeFlags;
+    assert(isIdentical(abs(x), real.nan));
+    assert(f.flags == 0);
+
+    resetIeeeFlags();
+    x = exp(-real.nan);
+    f = ieeeFlags;
+    assert(isIdentical(abs(x), real.nan));
+    assert(f.flags == 0);
+
+    x = exp(NaN(0x123));
+    assert(isIdentical(x, NaN(0x123)));
+
+    // High resolution test
+    assert(exp(0.5L) == 0x1.A612_98E1_E069_BC97_2DFE_FAB6D_33Fp+0L);
+}
+
+
+/**
+ * Calculate cos(y) + i sin(y).
+ *
+ * On many CPUs (such as x86), this is a very efficient operation;
+ * almost twice as fast as calculating sin(y) and cos(y) separately,
+ * and is the preferred method when both are required.
+ */
+creal expi(real y) @trusted pure nothrow @nogc
+{
+    version(InlineAsm_X86_Any)
+    {
+        version (Win64)
+        {
+            asm pure nothrow @nogc
+            {
+                naked;
+                fld     real ptr [ECX];
+                fsincos;
+                fxch    ST(1), ST(0);
+                ret;
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc
+            {
+                fld y;
+                fsincos;
+                fxch ST(1), ST(0);
+            }
+        }
+    }
+    else
+    {
+        return cos(y) + sin(y)*1i;
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(expi(1.3e5L) == cos(1.3e5L) + sin(1.3e5L) * 1i);
+    assert(expi(0.0L) == 1L + 0.0Li);
+}
+
+/*********************************************************************
+ * Separate floating point value into significand and exponent.
+ *
+ * Returns:
+ *      Calculate and return $(I x) and $(I exp) such that
+ *      value =$(I x)*2$(SUPERSCRIPT exp) and
+ *      .5 $(LT)= |$(I x)| $(LT) 1.0
+ *
+ *      $(I x) has same sign as value.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH value)           $(TH returns)         $(TH exp))
+ *      $(TR $(TD $(PLUSMN)0.0)    $(TD $(PLUSMN)0.0)    $(TD 0))
+ *      $(TR $(TD +$(INFIN))       $(TD +$(INFIN))       $(TD int.max))
+ *      $(TR $(TD -$(INFIN))       $(TD -$(INFIN))       $(TD int.min))
+ *      $(TR $(TD $(PLUSMN)$(NAN)) $(TD $(PLUSMN)$(NAN)) $(TD int.min))
+ *      )
+ */
+T frexp(T)(const T value, out int exp) @trusted pure nothrow @nogc
+    if(isFloatingPoint!T)
+{
+    Unqual!T vf = value;
+    ushort* vu = cast(ushort*)&vf;
+    static if(is(Unqual!T == float))
+        int* vi = cast(int*)&vf;
+    else
+        long* vl = cast(long*)&vf;
+    int ex;
+    alias F = floatTraits!T;
+
+    ex = vu[F.EXPPOS_SHORT] & F.EXPMASK;
+    static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        if (ex)
+        {   // If exponent is non-zero
+            if (ex == F.EXPMASK) // infinity or NaN
+            {
+                if (*vl &  0x7FFF_FFFF_FFFF_FFFF)  // NaN
+                {
+                    *vl |= 0xC000_0000_0000_0000;  // convert NaNS to NaNQ
+                    exp = int.min;
+                }
+                else if (vu[F.EXPPOS_SHORT] & 0x8000)   // negative infinity
+                    exp = int.min;
+                else   // positive infinity
+                    exp = int.max;
+
+            }
+            else
+            {
+                exp = ex - F.EXPBIAS;
+                vu[F.EXPPOS_SHORT] = (0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE;
+            }
+        }
+        else if (!*vl)
+        {
+            // vf is +-0.0
+            exp = 0;
+        }
+        else
+        {
+            // subnormal
+
+            vf *= F.RECIP_EPSILON;
+            ex = vu[F.EXPPOS_SHORT] & F.EXPMASK;
+            exp = ex - F.EXPBIAS - T.mant_dig + 1;
+            vu[F.EXPPOS_SHORT] = (0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE;
+        }
+        return vf;
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        if (ex)     // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)
+            {   // infinity or NaN
+                if (vl[MANTISSA_LSB] |
+                    ( vl[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF))  // NaN
+                {
+                    // convert NaNS to NaNQ
+                    vl[MANTISSA_MSB] |= 0x0000_8000_0000_0000;
+                    exp = int.min;
+                }
+                else if (vu[F.EXPPOS_SHORT] & 0x8000)   // negative infinity
+                    exp = int.min;
+                else   // positive infinity
+                    exp = int.max;
+
+            }
+            else
+            {
+                exp = ex - F.EXPBIAS;
+                vu[F.EXPPOS_SHORT] =
+                    cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE);
+            }
+        }
+        else if ((vl[MANTISSA_LSB]
+                       |(vl[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) == 0)
+        {
+            // vf is +-0.0
+            exp = 0;
+        }
+        else
+        {
+            // subnormal
+            vf *= F.RECIP_EPSILON;
+            ex = vu[F.EXPPOS_SHORT] & F.EXPMASK;
+            exp = ex - F.EXPBIAS - T.mant_dig + 1;
+            vu[F.EXPPOS_SHORT] =
+                cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE);
+        }
+        return vf;
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        if (ex) // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)   // infinity or NaN
+            {
+                if (*vl == 0x7FF0_0000_0000_0000)  // positive infinity
+                {
+                    exp = int.max;
+                }
+                else if (*vl == 0xFFF0_0000_0000_0000) // negative infinity
+                    exp = int.min;
+                else
+                { // NaN
+                    *vl |= 0x0008_0000_0000_0000;  // convert NaNS to NaNQ
+                    exp = int.min;
+                }
+            }
+            else
+            {
+                exp = (ex - F.EXPBIAS) >> 4;
+                vu[F.EXPPOS_SHORT] = cast(ushort)((0x800F & vu[F.EXPPOS_SHORT]) | 0x3FE0);
+            }
+        }
+        else if (!(*vl & 0x7FFF_FFFF_FFFF_FFFF))
+        {
+            // vf is +-0.0
+            exp = 0;
+        }
+        else
+        {
+            // subnormal
+            vf *= F.RECIP_EPSILON;
+            ex = vu[F.EXPPOS_SHORT] & F.EXPMASK;
+            exp = ((ex - F.EXPBIAS) >> 4) - T.mant_dig + 1;
+            vu[F.EXPPOS_SHORT] =
+                cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FE0);
+        }
+        return vf;
+    }
+    else static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        if (ex) // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)   // infinity or NaN
+            {
+                if (*vi == 0x7F80_0000)  // positive infinity
+                {
+                    exp = int.max;
+                }
+                else if (*vi == 0xFF80_0000) // negative infinity
+                    exp = int.min;
+                else
+                { // NaN
+                    *vi |= 0x0040_0000;  // convert NaNS to NaNQ
+                    exp = int.min;
+                }
+            }
+            else
+            {
+                exp = (ex - F.EXPBIAS) >> 7;
+                vu[F.EXPPOS_SHORT] = cast(ushort)((0x807F & vu[F.EXPPOS_SHORT]) | 0x3F00);
+            }
+        }
+        else if (!(*vi & 0x7FFF_FFFF))
+        {
+            // vf is +-0.0
+            exp = 0;
+        }
+        else
+        {
+            // subnormal
+            vf *= F.RECIP_EPSILON;
+            ex = vu[F.EXPPOS_SHORT] & F.EXPMASK;
+            exp = ((ex - F.EXPBIAS) >> 7) - T.mant_dig + 1;
+            vu[F.EXPPOS_SHORT] =
+                cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3F00);
+        }
+        return vf;
+    }
+    else // static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        assert (0, "frexp not implemented");
+    }
+}
+
+///
+unittest
+{
+    int exp;
+    real mantissa = frexp(123.456L, exp);
+
+    // check if values are equal to 19 decimal digits of precision
+    assert(equalsDigit(mantissa * pow(2.0L, cast(real)exp), 123.456L, 19));
+
+    assert(frexp(-real.nan, exp) && exp == int.min);
+    assert(frexp(real.nan, exp) && exp == int.min);
+    assert(frexp(-real.infinity, exp) == -real.infinity && exp == int.min);
+    assert(frexp(real.infinity, exp) == real.infinity && exp == int.max);
+    assert(frexp(-0.0, exp) == -0.0 && exp == 0);
+    assert(frexp(0.0, exp) == 0.0 && exp == 0);
+}
+
+unittest
+{
+    import std.meta, std.typecons;
+
+    foreach (T; AliasSeq!(real, double, float))
+    {
+        Tuple!(T, T, int)[] vals =     // x,frexp,exp
+            [
+             tuple(T(0.0),  T( 0.0 ), 0),
+             tuple(T(-0.0), T( -0.0), 0),
+             tuple(T(1.0),  T( .5  ), 1),
+             tuple(T(-1.0), T( -.5 ), 1),
+             tuple(T(2.0),  T( .5  ), 2),
+             tuple(T(float.min_normal/2.0f), T(.5), -126),
+             tuple(T.infinity, T.infinity, int.max),
+             tuple(-T.infinity, -T.infinity, int.min),
+             tuple(T.nan, T.nan, int.min),
+             tuple(-T.nan, -T.nan, int.min),
+             ];
+
+        foreach(elem; vals)
+        {
+            T x = elem[0];
+            T e = elem[1];
+            int exp = elem[2];
+            int eptr;
+            T v = frexp(x, eptr);
+            assert(isIdentical(e, v));
+            assert(exp == eptr);
+
+        }
+
+        static if (floatTraits!(T).realFormat == RealFormat.ieeeExtended)
+        {
+            static T[3][] extendedvals = [ // x,frexp,exp
+                [0x1.a5f1c2eb3fe4efp+73L, 0x1.A5F1C2EB3FE4EFp-1L,   74],    // normal
+                [0x1.fa01712e8f0471ap-1064L,  0x1.fa01712e8f0471ap-1L,     -1063],
+                [T.min_normal,  .5,     -16381],
+                [T.min_normal/2.0L, .5,     -16382]    // subnormal
+            ];
+            foreach(elem; extendedvals)
+            {
+                T x = elem[0];
+                T e = elem[1];
+                int exp = cast(int)elem[2];
+                int eptr;
+                T v = frexp(x, eptr);
+                assert(isIdentical(e, v));
+                assert(exp == eptr);
+
+            }
+        }
+    }
+}
+
+unittest
+{
+    import std.meta: AliasSeq;
+    void foo() {
+        foreach (T; AliasSeq!(real, double, float))
+        {
+            int exp;
+            const T a = 1;
+            immutable T b = 2;
+            auto c = frexp(a, exp);
+            auto d = frexp(b, exp);
+        }
+    }
+}
+
+/******************************************
+ * Extracts the exponent of x as a signed integral value.
+ *
+ * If x is not a special value, the result is the same as
+ * $(D cast(int)logb(x)).
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                $(TH ilogb(x))     $(TH Range error?))
+ *      $(TR $(TD 0)                 $(TD FP_ILOGB0)   $(TD yes))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD int.max)     $(TD no))
+ *      $(TR $(TD $(NAN))            $(TD FP_ILOGBNAN) $(TD no))
+ *      )
+ */
+int ilogb(T)(const T x) @trusted pure nothrow @nogc
+    if(isFloatingPoint!T)
+{
+    alias F = floatTraits!T;
+
+    union floatBits
+    {
+        T rv;
+        ushort[T.sizeof/2] vu;
+        uint[T.sizeof/4] vui;
+        static if(T.sizeof >= 8)
+            ulong[T.sizeof/8] vul;
+    }
+    floatBits y = void;
+    y.rv = x;
+
+    int ex = y.vu[F.EXPPOS_SHORT] & F.EXPMASK;
+    static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        if (ex)
+        {
+            // If exponent is non-zero
+            if (ex == F.EXPMASK) // infinity or NaN
+            {
+                if (y.vul[0] &  0x7FFF_FFFF_FFFF_FFFF)  // NaN
+                    return FP_ILOGBNAN;
+                else // +-infinity
+                    return int.max;
+            }
+            else
+            {
+                return ex - F.EXPBIAS - 1;
+            }
+        }
+        else if (!y.vul[0])
+        {
+            // vf is +-0.0
+            return FP_ILOGB0;
+        }
+        else
+        {
+            // subnormal
+            return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(y.vul[0]);
+        }
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        if (ex)    // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)
+            {
+                // infinity or NaN
+                if (y.vul[MANTISSA_LSB] | ( y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF))  // NaN
+                    return FP_ILOGBNAN;
+                else // +- infinity
+                    return int.max;
+            }
+            else
+            {
+                return ex - F.EXPBIAS - 1;
+            }
+        }
+        else if ((y.vul[MANTISSA_LSB] | (y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) == 0)
+        {
+            // vf is +-0.0
+            return FP_ILOGB0;
+        }
+        else
+        {
+            // subnormal
+            ulong msb = y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF;
+            ulong lsb = y.vul[MANTISSA_LSB];
+            if (msb)
+                return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(msb) + 64;
+            else
+                return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(lsb);
+        }
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        if (ex) // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)   // infinity or NaN
+            {
+                if ((y.vul[0] & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FF0_0000_0000_0000)  // +- infinity
+                    return int.max;
+                else // NaN
+                    return FP_ILOGBNAN;
+            }
+            else
+            {
+                return ((ex - F.EXPBIAS) >> 4) - 1;
+            }
+        }
+        else if (!(y.vul[0] & 0x7FFF_FFFF_FFFF_FFFF))
+        {
+            // vf is +-0.0
+            return FP_ILOGB0;
+        }
+        else
+        {
+            // subnormal
+            enum MANTISSAMASK_64 = ((cast(ulong)F.MANTISSAMASK_INT) << 32) | 0xFFFF_FFFF;
+            return ((ex - F.EXPBIAS) >> 4) - T.mant_dig + 1 + bsr(y.vul[0] & MANTISSAMASK_64);
+        }
+    }
+    else static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        if (ex) // If exponent is non-zero
+        {
+            if (ex == F.EXPMASK)   // infinity or NaN
+            {
+                if ((y.vui[0] & 0x7FFF_FFFF) == 0x7F80_0000)  // +- infinity
+                    return int.max;
+                else // NaN
+                    return FP_ILOGBNAN;
+            }
+            else
+            {
+                return ((ex - F.EXPBIAS) >> 7) - 1;
+            }
+        }
+        else if (!(y.vui[0] & 0x7FFF_FFFF))
+        {
+            // vf is +-0.0
+            return FP_ILOGB0;
+        }
+        else
+        {
+            // subnormal
+            uint mantissa = y.vui[0] & F.MANTISSAMASK_INT;
+            return ((ex - F.EXPBIAS) >> 7) - T.mant_dig + 1 + bsr(mantissa);
+        }
+    }
+    else // static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        core.stdc.math.ilogbl(x);
+    }
+}
+/// ditto
+int ilogb(T)(const T x) @safe pure nothrow @nogc
+    if(isIntegral!T && isUnsigned!T)
+{
+    if (x == 0)
+        return FP_ILOGB0;
+    else
+    {
+        static assert (T.sizeof <= ulong.sizeof, "integer size too large for the current ilogb implementation");
+        return bsr(x);
+    }
+}
+/// ditto
+int ilogb(T)(const T x) @safe pure nothrow @nogc
+    if(isIntegral!T && isSigned!T)
+{
+    // Note: abs(x) can not be used because the return type is not Unsigned and
+    //       the return value would be wrong for x == int.min
+    Unsigned!T absx =  x>=0 ? x : -x;
+    return ilogb(absx);
+}
+
+alias FP_ILOGB0   = core.stdc.math.FP_ILOGB0;
+alias FP_ILOGBNAN = core.stdc.math.FP_ILOGBNAN;
+
+@trusted nothrow @nogc unittest
+{
+    import std.meta, std.typecons;
+    foreach (F; AliasSeq!(float, double, real))
+    {
+        alias T = Tuple!(F, int);
+        T[13] vals =   // x, ilogb(x)
+        [
+            T(  F.nan     , FP_ILOGBNAN ),
+            T( -F.nan     , FP_ILOGBNAN ),
+            T(  F.infinity, int.max     ),
+            T( -F.infinity, int.max     ),
+            T(  0.0       , FP_ILOGB0   ),
+            T( -0.0       , FP_ILOGB0   ),
+            T(  2.0       , 1           ),
+            T(  2.0001    , 1           ),
+            T(  1.9999    , 0           ),
+            T(  0.5       , -1          ),
+            T(  123.123   , 6           ),
+            T( -123.123   , 6           ),
+            T(  0.123     , -4          ),
+        ];
+
+        foreach(elem; vals)
+        {
+            assert(ilogb(elem[0]) == elem[1]);
+        }
+    }
+
+    // min_normal and subnormals
+    assert(ilogb(-float.min_normal) == -126);
+    assert(ilogb(nextUp(-float.min_normal)) == -127);
+    assert(ilogb(nextUp(-float(0.0))) == -149);
+    assert(ilogb(-double.min_normal) == -1022);
+    assert(ilogb(nextUp(-double.min_normal)) == -1023);
+    assert(ilogb(nextUp(-double(0.0))) == -1074);
+    static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended)
+    {
+        assert(ilogb(-real.min_normal) == -16382);
+        assert(ilogb(nextUp(-real.min_normal)) == -16383);
+        assert(ilogb(nextUp(-real(0.0))) == -16445);
+    }
+    else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble)
+    {
+        assert(ilogb(-real.min_normal) == -1022);
+        assert(ilogb(nextUp(-real.min_normal)) == -1023);
+        assert(ilogb(nextUp(-real(0.0))) == -1074);
+    }
+
+    // test integer types
+    assert(ilogb(0) == FP_ILOGB0);
+    assert(ilogb(int.max) == 30);
+    assert(ilogb(int.min) == 31);
+    assert(ilogb(uint.max) == 31);
+    assert(ilogb(long.max) == 62);
+    assert(ilogb(long.min) == 63);
+    assert(ilogb(ulong.max) == 63);
+}
+
+/*******************************************
+ * Compute n * 2$(SUPERSCRIPT exp)
+ * References: frexp
+ */
+
+real ldexp(real n, int exp) @nogc @safe pure nothrow { pragma(inline, true); return core.math.ldexp(n, exp); }
+//FIXME
+///ditto
+double ldexp(double n, int exp) @safe pure nothrow @nogc { return ldexp(cast(real)n, exp); }
+//FIXME
+///ditto
+float ldexp(float n, int exp) @safe pure nothrow @nogc { return ldexp(cast(real)n, exp); }
+
+///
+@nogc @safe pure nothrow unittest
+{
+    import std.meta;
+    foreach(T; AliasSeq!(float, double, real))
+    {
+        T r;
+
+        r = ldexp(3.0L, 3);
+        assert(r == 24);
+
+        r = ldexp(cast(T)3.0, cast(int) 3);
+        assert(r == 24);
+
+        T n = 3.0;
+        int exp = 3;
+        r = ldexp(n, exp);
+        assert(r == 24);
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended)
+    {
+        assert(ldexp(1.0L, -16384) == 0x1p-16384L);
+        assert(ldexp(1.0L, -16382) == 0x1p-16382L);
+        int x;
+        real n = frexp(0x1p-16384L, x);
+        assert(n==0.5L);
+        assert(x==-16383);
+        assert(ldexp(n, x)==0x1p-16384L);
+    }
+    else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble)
+    {
+        assert(ldexp(1.0L, -1024) == 0x1p-1024L);
+        assert(ldexp(1.0L, -1022) == 0x1p-1022L);
+        int x;
+        real n = frexp(0x1p-1024L, x);
+        assert(n==0.5L);
+        assert(x==-1023);
+        assert(ldexp(n, x)==0x1p-1024L);
+    }
+    else static assert(false, "Floating point type real not supported");
+}
+
+/* workaround Issue 14718, float parsing depends on platform strtold
+@safe pure nothrow @nogc unittest
+{
+    assert(ldexp(1.0, -1024) == 0x1p-1024);
+    assert(ldexp(1.0, -1022) == 0x1p-1022);
+    int x;
+    double n = frexp(0x1p-1024, x);
+    assert(n==0.5);
+    assert(x==-1023);
+    assert(ldexp(n, x)==0x1p-1024);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(ldexp(1.0f, -128) == 0x1p-128f);
+    assert(ldexp(1.0f, -126) == 0x1p-126f);
+    int x;
+    float n = frexp(0x1p-128f, x);
+    assert(n==0.5f);
+    assert(x==-127);
+    assert(ldexp(n, x)==0x1p-128f);
+}
+*/
+
+unittest
+{
+    static real[3][] vals =    // value,exp,ldexp
+    [
+    [    0,    0,    0],
+    [    1,    0,    1],
+    [    -1,    0,    -1],
+    [    1,    1,    2],
+    [    123,    10,    125952],
+    [    real.max,    int.max,    real.infinity],
+    [    real.max,    -int.max,    0],
+    [    real.min_normal,    -int.max,    0],
+    ];
+    int i;
+
+    for (i = 0; i < vals.length; i++)
+    {
+        real x = vals[i][0];
+        int exp = cast(int)vals[i][1];
+        real z = vals[i][2];
+        real l = ldexp(x, exp);
+
+        assert(equalsDigit(z, l, 7));
+    }
+
+    real function(real, int) pldexp = &ldexp;
+    assert(pldexp != null);
+}
+
+/**************************************
+ * Calculate the natural logarithm of x.
+ *
+ *    $(TABLE_SV
+ *    $(TR $(TH x)            $(TH log(x))    $(TH divide by 0?) $(TH invalid?))
+ *    $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no))
+ *    $(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes))
+ *    $(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no))
+ *    )
+ */
+real log(real x) @safe pure nothrow @nogc
+{
+    version (INLINE_YL2X)
+        return yl2x(x, LN2);
+    else
+    {
+        // Coefficients for log(1 + x)
+        static immutable real[7] P = [
+            2.0039553499201281259648E1L,
+            5.7112963590585538103336E1L,
+            6.0949667980987787057556E1L,
+            2.9911919328553073277375E1L,
+            6.5787325942061044846969E0L,
+            4.9854102823193375972212E-1L,
+            4.5270000862445199635215E-5L,
+        ];
+        static immutable real[7] Q = [
+            6.0118660497603843919306E1L,
+            2.1642788614495947685003E2L,
+            3.0909872225312059774938E2L,
+            2.2176239823732856465394E2L,
+            8.3047565967967209469434E1L,
+            1.5062909083469192043167E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // Coefficients for log(x)
+        static immutable real[4] R = [
+           -3.5717684488096787370998E1L,
+            1.0777257190312272158094E1L,
+           -7.1990767473014147232598E-1L,
+            1.9757429581415468984296E-3L,
+        ];
+        static immutable real[4] S = [
+           -4.2861221385716144629696E2L,
+            1.9361891836232102174846E2L,
+           -2.6201045551331104417768E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // C1 + C2 = LN2.
+        enum real C1 = 6.9314575195312500000000E-1L;
+        enum real C2 = 1.4286068203094172321215E-6L;
+
+        // Special cases.
+        if (isNaN(x))
+            return x;
+        if (isInfinity(x) && !signbit(x))
+            return x;
+        if (x == 0.0)
+            return -real.infinity;
+        if (x < 0.0)
+            return real.nan;
+
+        // Separate mantissa from exponent.
+        // Note, frexp is used so that denormal numbers will be handled properly.
+        real y, z;
+        int exp;
+
+        x = frexp(x, exp);
+
+        // Logarithm using log(x) = z + z^^3 P(z) / Q(z),
+        // where z = 2(x - 1)/(x + 1)
+        if((exp > 2) || (exp < -2))
+        {
+            if(x < SQRT1_2)
+            {   // 2(2x - 1)/(2x + 1)
+                exp -= 1;
+                z = x - 0.5;
+                y = 0.5 * z + 0.5;
+            }
+            else
+            {   // 2(x - 1)/(x + 1)
+                z = x - 0.5;
+                z -= 0.5;
+                y = 0.5 * x  + 0.5;
+            }
+            x = z / y;
+            z = x * x;
+            z = x * (z * poly(z, R) / poly(z, S));
+            z += exp * C2;
+            z += x;
+            z += exp * C1;
+
+            return z;
+        }
+
+        // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x)
+        if (x < SQRT1_2)
+        {   // 2x - 1
+            exp -= 1;
+            x = ldexp(x, 1) - 1.0;
+        }
+        else
+        {
+            x = x - 1.0;
+        }
+        z = x * x;
+        y = x * (z * poly(x, P) / poly(x, Q));
+        y += exp * C2;
+        z = y - ldexp(z, -1);
+
+        // Note, the sum of above terms does not exceed x/4,
+        // so it contributes at most about 1/4 lsb to the error.
+        z += x;
+        z += exp * C1;
+
+        return z;
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(log(E) == 1);
+}
+
+/**************************************
+ * Calculate the base-10 logarithm of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)            $(TH log10(x))  $(TH divide by 0?) $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no))
+ *      $(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes))
+ *      $(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no))
+ *      )
+ */
+real log10(real x) @safe pure nothrow @nogc
+{
+    version (INLINE_YL2X)
+        return yl2x(x, LOG2);
+    else
+    {
+        // Coefficients for log(1 + x)
+        static immutable real[7] P = [
+            2.0039553499201281259648E1L,
+            5.7112963590585538103336E1L,
+            6.0949667980987787057556E1L,
+            2.9911919328553073277375E1L,
+            6.5787325942061044846969E0L,
+            4.9854102823193375972212E-1L,
+            4.5270000862445199635215E-5L,
+        ];
+        static immutable real[7] Q = [
+            6.0118660497603843919306E1L,
+            2.1642788614495947685003E2L,
+            3.0909872225312059774938E2L,
+            2.2176239823732856465394E2L,
+            8.3047565967967209469434E1L,
+            1.5062909083469192043167E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // Coefficients for log(x)
+        static immutable real[4] R = [
+           -3.5717684488096787370998E1L,
+            1.0777257190312272158094E1L,
+           -7.1990767473014147232598E-1L,
+            1.9757429581415468984296E-3L,
+        ];
+        static immutable real[4] S = [
+           -4.2861221385716144629696E2L,
+            1.9361891836232102174846E2L,
+           -2.6201045551331104417768E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // log10(2) split into two parts.
+        enum real L102A =  0.3125L;
+        enum real L102B = -1.14700043360188047862611052755069732318101185E-2L;
+
+        // log10(e) split into two parts.
+        enum real L10EA =  0.5L;
+        enum real L10EB = -6.570551809674817234887108108339491770560299E-2L;
+
+        // Special cases are the same as for log.
+        if (isNaN(x))
+            return x;
+        if (isInfinity(x) && !signbit(x))
+            return x;
+        if (x == 0.0)
+            return -real.infinity;
+        if (x < 0.0)
+            return real.nan;
+
+        // Separate mantissa from exponent.
+        // Note, frexp is used so that denormal numbers will be handled properly.
+        real y, z;
+        int exp;
+
+        x = frexp(x, exp);
+
+        // Logarithm using log(x) = z + z^^3 P(z) / Q(z),
+        // where z = 2(x - 1)/(x + 1)
+        if((exp > 2) || (exp < -2))
+        {
+            if(x < SQRT1_2)
+            {   // 2(2x - 1)/(2x + 1)
+                exp -= 1;
+                z = x - 0.5;
+                y = 0.5 * z + 0.5;
+            }
+            else
+            {   // 2(x - 1)/(x + 1)
+                z = x - 0.5;
+                z -= 0.5;
+                y = 0.5 * x  + 0.5;
+            }
+            x = z / y;
+            z = x * x;
+            y = x * (z * poly(z, R) / poly(z, S));
+            goto Ldone;
+        }
+
+        // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x)
+        if (x < SQRT1_2)
+        {   // 2x - 1
+            exp -= 1;
+            x = ldexp(x, 1) - 1.0;
+        }
+        else
+            x = x - 1.0;
+
+        z = x * x;
+        y = x * (z * poly(x, P) / poly(x, Q));
+        y = y - ldexp(z, -1);
+
+        // Multiply log of fraction by log10(e) and base 2 exponent by log10(2).
+        // This sequence of operations is critical and it may be horribly
+        // defeated by some compiler optimizers.
+    Ldone:
+        z = y * L10EB;
+        z += x * L10EB;
+        z += exp * L102B;
+        z += y * L10EA;
+        z += x * L10EA;
+        z += exp * L102A;
+
+        return z;
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(fabs(log10(1000) - 3) < .000001);
+}
+
+/******************************************
+ *      Calculates the natural logarithm of 1 + x.
+ *
+ *      For very small x, log1p(x) will be more accurate than
+ *      log(1 + x).
+ *
+ *  $(TABLE_SV
+ *  $(TR $(TH x)            $(TH log1p(x))     $(TH divide by 0?) $(TH invalid?))
+ *  $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)           $(TD no))
+ *  $(TR $(TD -1.0)         $(TD -$(INFIN))    $(TD yes)          $(TD no))
+ *  $(TR $(TD $(LT)-1.0)    $(TD $(NAN))       $(TD no)           $(TD yes))
+ *  $(TR $(TD +$(INFIN))    $(TD -$(INFIN))    $(TD no)           $(TD no))
+ *  )
+ */
+real log1p(real x) @safe pure nothrow @nogc
+{
+    version(INLINE_YL2X)
+    {
+        // On x87, yl2xp1 is valid if and only if -0.5 <= lg(x) <= 0.5,
+        //    ie if -0.29<=x<=0.414
+        return (fabs(x) <= 0.25)  ? yl2xp1(x, LN2) : yl2x(x+1, LN2);
+    }
+    else
+    {
+        // Special cases.
+        if (isNaN(x) || x == 0.0)
+            return x;
+        if (isInfinity(x) && !signbit(x))
+            return x;
+        if (x == -1.0)
+            return -real.infinity;
+        if (x < -1.0)
+            return real.nan;
+
+        return log(x + 1.0);
+    }
+}
+
+/***************************************
+ * Calculates the base-2 logarithm of x:
+ * $(SUB log, 2)x
+ *
+ *  $(TABLE_SV
+ *  $(TR $(TH x)            $(TH log2(x))   $(TH divide by 0?) $(TH invalid?))
+ *  $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no) )
+ *  $(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes) )
+ *  $(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no) )
+ *  )
+ */
+real log2(real x) @safe pure nothrow @nogc
+{
+    version (INLINE_YL2X)
+        return yl2x(x, 1);
+    else
+    {
+        // Coefficients for log(1 + x)
+        static immutable real[7] P = [
+            2.0039553499201281259648E1L,
+            5.7112963590585538103336E1L,
+            6.0949667980987787057556E1L,
+            2.9911919328553073277375E1L,
+            6.5787325942061044846969E0L,
+            4.9854102823193375972212E-1L,
+            4.5270000862445199635215E-5L,
+        ];
+        static immutable real[7] Q = [
+            6.0118660497603843919306E1L,
+            2.1642788614495947685003E2L,
+            3.0909872225312059774938E2L,
+            2.2176239823732856465394E2L,
+            8.3047565967967209469434E1L,
+            1.5062909083469192043167E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // Coefficients for log(x)
+        static immutable real[4] R = [
+           -3.5717684488096787370998E1L,
+            1.0777257190312272158094E1L,
+           -7.1990767473014147232598E-1L,
+            1.9757429581415468984296E-3L,
+        ];
+        static immutable real[4] S = [
+           -4.2861221385716144629696E2L,
+            1.9361891836232102174846E2L,
+           -2.6201045551331104417768E1L,
+            1.0000000000000000000000E0L,
+        ];
+
+        // Special cases are the same as for log.
+        if (isNaN(x))
+            return x;
+        if (isInfinity(x) && !signbit(x))
+            return x;
+        if (x == 0.0)
+            return -real.infinity;
+        if (x < 0.0)
+            return real.nan;
+
+        // Separate mantissa from exponent.
+        // Note, frexp is used so that denormal numbers will be handled properly.
+        real y, z;
+        int exp;
+
+        x = frexp(x, exp);
+
+        // Logarithm using log(x) = z + z^^3 P(z) / Q(z),
+        // where z = 2(x - 1)/(x + 1)
+        if((exp > 2) || (exp < -2))
+        {
+            if(x < SQRT1_2)
+            {   // 2(2x - 1)/(2x + 1)
+                exp -= 1;
+                z = x - 0.5;
+                y = 0.5 * z + 0.5;
+            }
+            else
+            {   // 2(x - 1)/(x + 1)
+                z = x - 0.5;
+                z -= 0.5;
+                y = 0.5 * x  + 0.5;
+            }
+            x = z / y;
+            z = x * x;
+            y = x * (z * poly(z, R) / poly(z, S));
+            goto Ldone;
+        }
+
+        // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x)
+        if (x < SQRT1_2)
+        {   // 2x - 1
+            exp -= 1;
+            x = ldexp(x, 1) - 1.0;
+        }
+        else
+            x = x - 1.0;
+
+        z = x * x;
+        y = x * (z * poly(x, P) / poly(x, Q));
+        y = y - ldexp(z, -1);
+
+        // Multiply log of fraction by log10(e) and base 2 exponent by log10(2).
+        // This sequence of operations is critical and it may be horribly
+        // defeated by some compiler optimizers.
+    Ldone:
+        z = y * (LOG2E - 1.0);
+        z += x * (LOG2E - 1.0);
+        z += y;
+        z += x;
+        z += exp;
+
+        return z;
+    }
+}
+
+///
+unittest
+{
+    // check if values are equal to 19 decimal digits of precision
+    assert(equalsDigit(log2(1024.0L), 10, 19));
+}
+
+/*****************************************
+ * Extracts the exponent of x as a signed integral value.
+ *
+ * If x is subnormal, it is treated as if it were normalized.
+ * For a positive, finite x:
+ *
+ * 1 $(LT)= $(I x) * FLT_RADIX$(SUPERSCRIPT -logb(x)) $(LT) FLT_RADIX
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH logb(x))   $(TH divide by 0?) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) $(TD no))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD -$(INFIN)) $(TD yes) )
+ *      )
+ */
+real logb(real x) @trusted nothrow @nogc
+{
+    version (Win64_DMD_InlineAsm)
+    {
+        asm pure nothrow @nogc
+        {
+            naked                       ;
+            fld     real ptr [RCX]      ;
+            fxtract                     ;
+            fstp    ST(0)               ;
+            ret                         ;
+        }
+    }
+    else version (CRuntime_Microsoft)
+    {
+        asm pure nothrow @nogc
+        {
+            fld     x                   ;
+            fxtract                     ;
+            fstp    ST(0)               ;
+        }
+    }
+    else
+        return core.stdc.math.logbl(x);
+}
+
+/************************************
+ * Calculates the remainder from the calculation x/y.
+ * Returns:
+ * The value of x - i * y, where i is the number of times that y can
+ * be completely subtracted from x. The result has the same sign as x.
+ *
+ * $(TABLE_SV
+ *  $(TR $(TH x)              $(TH y)             $(TH fmod(x, y))   $(TH invalid?))
+ *  $(TR $(TD $(PLUSMN)0.0)   $(TD not 0.0)       $(TD $(PLUSMN)0.0) $(TD no))
+ *  $(TR $(TD $(PLUSMNINF))   $(TD anything)      $(TD $(NAN))       $(TD yes))
+ *  $(TR $(TD anything)       $(TD $(PLUSMN)0.0)  $(TD $(NAN))       $(TD yes))
+ *  $(TR $(TD !=$(PLUSMNINF)) $(TD $(PLUSMNINF))  $(TD x)            $(TD no))
+ * )
+ */
+real fmod(real x, real y) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        return x % y;
+    }
+    else
+        return core.stdc.math.fmodl(x, y);
+}
+
+/************************************
+ * Breaks x into an integral part and a fractional part, each of which has
+ * the same sign as x. The integral part is stored in i.
+ * Returns:
+ * The fractional part of x.
+ *
+ * $(TABLE_SV
+ *  $(TR $(TH x)              $(TH i (on input))  $(TH modf(x, i))   $(TH i (on return)))
+ *  $(TR $(TD $(PLUSMNINF))   $(TD anything)      $(TD $(PLUSMN)0.0) $(TD $(PLUSMNINF)))
+ * )
+ */
+real modf(real x, ref real i) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        i = trunc(x);
+        return copysign(isInfinity(x) ? 0.0 : x - i, x);
+    }
+    else
+        return core.stdc.math.modfl(x,&i);
+}
+
+/*************************************
+ * Efficiently calculates x * 2$(SUPERSCRIPT n).
+ *
+ * scalbn handles underflow and overflow in
+ * the same fashion as the basic arithmetic operators.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH scalb(x)))
+ *      $(TR $(TD $(PLUSMNINF))      $(TD $(PLUSMNINF)) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) )
+ *      )
+ */
+real scalbn(real x, int n) @trusted nothrow @nogc
+{
+    version(InlineAsm_X86_Any) {
+        // scalbnl is not supported on DMD-Windows, so use asm pure nothrow @nogc.
+        version (Win64)
+        {
+            asm pure nothrow @nogc {
+                naked                           ;
+                mov     16[RSP],RCX             ;
+                fild    word ptr 16[RSP]        ;
+                fld     real ptr [RDX]          ;
+                fscale                          ;
+                fstp    ST(1)                   ;
+                ret                             ;
+            }
+        }
+        else
+        {
+            asm pure nothrow @nogc {
+                fild n;
+                fld x;
+                fscale;
+                fstp ST(1);
+            }
+        }
+    }
+    else
+    {
+        return core.stdc.math.scalbnl(x, n);
+    }
+}
+
+///
+@safe nothrow @nogc unittest
+{
+    assert(scalbn(-real.infinity, 5) == -real.infinity);
+}
+
+/***************
+ * Calculates the cube root of x.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH $(I x))            $(TH cbrt(x))           $(TH invalid?))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0)      $(TD no) )
+ *      $(TR $(TD $(NAN))            $(TD $(NAN))            $(TD yes) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no) )
+ *      )
+ */
+real cbrt(real x) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        version (INLINE_YL2X)
+            return copysign(exp2(yl2x(fabs(x), 1.0L/3.0L)), x);
+        else
+            return core.stdc.math.cbrtl(x);
+    }
+    else
+        return core.stdc.math.cbrtl(x);
+}
+
+
+/*******************************
+ * Returns |x|
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)                 $(TH fabs(x)))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD +0.0) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) )
+ *      )
+ */
+real fabs(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.fabs(x); }
+//FIXME
+///ditto
+double fabs(double x) @safe pure nothrow @nogc { return fabs(cast(real)x); }
+//FIXME
+///ditto
+float fabs(float x) @safe pure nothrow @nogc { return fabs(cast(real)x); }
+
+unittest
+{
+    real function(real) pfabs = &fabs;
+    assert(pfabs != null);
+}
+
+/***********************************************************************
+ * Calculates the length of the
+ * hypotenuse of a right-angled triangle with sides of length x and y.
+ * The hypotenuse is the value of the square root of
+ * the sums of the squares of x and y:
+ *
+ *      sqrt($(POWER x, 2) + $(POWER y, 2))
+ *
+ * Note that hypot(x, y), hypot(y, x) and
+ * hypot(x, -y) are equivalent.
+ *
+ *  $(TABLE_SV
+ *  $(TR $(TH x)            $(TH y)            $(TH hypot(x, y)) $(TH invalid?))
+ *  $(TR $(TD x)            $(TD $(PLUSMN)0.0) $(TD |x|)         $(TD no))
+ *  $(TR $(TD $(PLUSMNINF)) $(TD y)            $(TD +$(INFIN))   $(TD no))
+ *  $(TR $(TD $(PLUSMNINF)) $(TD $(NAN))       $(TD +$(INFIN))   $(TD no))
+ *  )
+ */
+
+real hypot(real x, real y) @safe pure nothrow @nogc
+{
+    // Scale x and y to avoid underflow and overflow.
+    // If one is huge and the other tiny, return the larger.
+    // If both are huge, avoid overflow by scaling by 1/sqrt(real.max/2).
+    // If both are tiny, avoid underflow by scaling by sqrt(real.min_normal*real.epsilon).
+
+    enum real SQRTMIN = 0.5 * sqrt(real.min_normal); // This is a power of 2.
+    enum real SQRTMAX = 1.0L / SQRTMIN; // 2^^((max_exp)/2) = nextUp(sqrt(real.max))
+
+    static assert(2*(SQRTMAX/2)*(SQRTMAX/2) <= real.max);
+
+    // Proves that sqrt(real.max) ~~  0.5/sqrt(real.min_normal)
+    static assert(real.min_normal*real.max > 2 && real.min_normal*real.max <= 4);
+
+    real u = fabs(x);
+    real v = fabs(y);
+    if (!(u >= v))  // check for NaN as well.
+    {
+        v = u;
+        u = fabs(y);
+        if (u == real.infinity) return u; // hypot(inf, nan) == inf
+        if (v == real.infinity) return v; // hypot(nan, inf) == inf
+    }
+
+    // Now u >= v, or else one is NaN.
+    if (v >= SQRTMAX*0.5)
+    {
+            // hypot(huge, huge) -- avoid overflow
+        u *= SQRTMIN*0.5;
+        v *= SQRTMIN*0.5;
+        return sqrt(u*u + v*v) * SQRTMAX * 2.0;
+    }
+
+    if (u <= SQRTMIN)
+    {
+        // hypot (tiny, tiny) -- avoid underflow
+        // This is only necessary to avoid setting the underflow
+        // flag.
+        u *= SQRTMAX / real.epsilon;
+        v *= SQRTMAX / real.epsilon;
+        return sqrt(u*u + v*v) * SQRTMIN * real.epsilon;
+    }
+
+    if (u * real.epsilon > v)
+    {
+        // hypot (huge, tiny) = huge
+        return u;
+    }
+
+    // both are in the normal range
+    return sqrt(u*u + v*v);
+}
+
+unittest
+{
+    static real[3][] vals =     // x,y,hypot
+        [
+            [ 0.0,     0.0,   0.0],
+            [ 0.0,    -0.0,   0.0],
+            [ -0.0,   -0.0,   0.0],
+            [ 3.0,     4.0,   5.0],
+            [ -300,   -400,   500],
+            [0.0,      7.0,   7.0],
+            [9.0,   9*real.epsilon,   9.0],
+            [88/(64*sqrt(real.min_normal)), 105/(64*sqrt(real.min_normal)), 137/(64*sqrt(real.min_normal))],
+            [88/(128*sqrt(real.min_normal)), 105/(128*sqrt(real.min_normal)), 137/(128*sqrt(real.min_normal))],
+            [3*real.min_normal*real.epsilon, 4*real.min_normal*real.epsilon, 5*real.min_normal*real.epsilon],
+            [ real.min_normal, real.min_normal, sqrt(2.0L)*real.min_normal],
+            [ real.max/sqrt(2.0L), real.max/sqrt(2.0L), real.max],
+            [ real.infinity, real.nan, real.infinity],
+            [ real.nan, real.infinity, real.infinity],
+            [ real.nan, real.nan, real.nan],
+            [ real.nan, real.max, real.nan],
+            [ real.max, real.nan, real.nan],
+        ];
+        for (int i = 0; i < vals.length; i++)
+        {
+            real x = vals[i][0];
+            real y = vals[i][1];
+            real z = vals[i][2];
+            real h = hypot(x, y);
+            assert(isIdentical(z,h) || feqrel(z, h) >= real.mant_dig - 1);
+        }
+}
+
+/**************************************
+ * Returns the value of x rounded upward to the next integer
+ * (toward positive infinity).
+ */
+real ceil(real x) @trusted pure nothrow @nogc
+{
+    version (Win64_DMD_InlineAsm)
+    {
+        asm pure nothrow @nogc
+        {
+            naked                       ;
+            fld     real ptr [RCX]      ;
+            fstcw   8[RSP]              ;
+            mov     AL,9[RSP]           ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x08             ; // round to +infinity
+            mov     9[RSP],AL           ;
+            fldcw   8[RSP]              ;
+            frndint                     ;
+            mov     9[RSP],DL           ;
+            fldcw   8[RSP]              ;
+            ret                         ;
+        }
+    }
+    else version(CRuntime_Microsoft)
+    {
+        short cw;
+        asm pure nothrow @nogc
+        {
+            fld     x                   ;
+            fstcw   cw                  ;
+            mov     AL,byte ptr cw+1    ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x08             ; // round to +infinity
+            mov     byte ptr cw+1,AL    ;
+            fldcw   cw                  ;
+            frndint                     ;
+            mov     byte ptr cw+1,DL    ;
+            fldcw   cw                  ;
+        }
+    }
+    else
+    {
+        // Special cases.
+        if (isNaN(x) || isInfinity(x))
+            return x;
+
+        real y = floorImpl(x);
+        if (y < x)
+            y += 1.0;
+
+        return y;
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(ceil(+123.456L) == +124);
+    assert(ceil(-123.456L) == -123);
+    assert(ceil(-1.234L) == -1);
+    assert(ceil(-0.123L) == 0);
+    assert(ceil(0.0L) == 0);
+    assert(ceil(+0.123L) == 1);
+    assert(ceil(+1.234L) == 2);
+    assert(ceil(real.infinity) == real.infinity);
+    assert(isNaN(ceil(real.nan)));
+    assert(isNaN(ceil(real.init)));
+}
+
+// ditto
+double ceil(double x) @trusted pure nothrow @nogc
+{
+    // Special cases.
+    if (isNaN(x) || isInfinity(x))
+        return x;
+
+    double y = floorImpl(x);
+    if (y < x)
+        y += 1.0;
+
+    return y;
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(ceil(+123.456) == +124);
+    assert(ceil(-123.456) == -123);
+    assert(ceil(-1.234) == -1);
+    assert(ceil(-0.123) == 0);
+    assert(ceil(0.0) == 0);
+    assert(ceil(+0.123) == 1);
+    assert(ceil(+1.234) == 2);
+    assert(ceil(double.infinity) == double.infinity);
+    assert(isNaN(ceil(double.nan)));
+    assert(isNaN(ceil(double.init)));
+}
+
+// ditto
+float ceil(float x) @trusted pure nothrow @nogc
+{
+    // Special cases.
+    if (isNaN(x) || isInfinity(x))
+        return x;
+
+    float y = floorImpl(x);
+    if (y < x)
+        y += 1.0;
+
+    return y;
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(ceil(+123.456f) == +124);
+    assert(ceil(-123.456f) == -123);
+    assert(ceil(-1.234f) == -1);
+    assert(ceil(-0.123f) == 0);
+    assert(ceil(0.0f) == 0);
+    assert(ceil(+0.123f) == 1);
+    assert(ceil(+1.234f) == 2);
+    assert(ceil(float.infinity) == float.infinity);
+    assert(isNaN(ceil(float.nan)));
+    assert(isNaN(ceil(float.init)));
+}
+
+/**************************************
+ * Returns the value of x rounded downward to the next integer
+ * (toward negative infinity).
+ */
+real floor(real x) @trusted pure nothrow @nogc
+{
+    version (Win64_DMD_InlineAsm)
+    {
+        asm pure nothrow @nogc
+        {
+            naked                       ;
+            fld     real ptr [RCX]      ;
+            fstcw   8[RSP]              ;
+            mov     AL,9[RSP]           ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x04             ; // round to -infinity
+            mov     9[RSP],AL           ;
+            fldcw   8[RSP]              ;
+            frndint                     ;
+            mov     9[RSP],DL           ;
+            fldcw   8[RSP]              ;
+            ret                         ;
+        }
+    }
+    else version(CRuntime_Microsoft)
+    {
+        short cw;
+        asm pure nothrow @nogc
+        {
+            fld     x                   ;
+            fstcw   cw                  ;
+            mov     AL,byte ptr cw+1    ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x04             ; // round to -infinity
+            mov     byte ptr cw+1,AL    ;
+            fldcw   cw                  ;
+            frndint                     ;
+            mov     byte ptr cw+1,DL    ;
+            fldcw   cw                  ;
+        }
+    }
+    else
+    {
+        // Special cases.
+        if (isNaN(x) || isInfinity(x) || x == 0.0)
+            return x;
+
+        return floorImpl(x);
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(floor(+123.456L) == +123);
+    assert(floor(-123.456L) == -124);
+    assert(floor(-1.234L) == -2);
+    assert(floor(-0.123L) == -1);
+    assert(floor(0.0L) == 0);
+    assert(floor(+0.123L) == 0);
+    assert(floor(+1.234L) == 1);
+    assert(floor(real.infinity) == real.infinity);
+    assert(isNaN(floor(real.nan)));
+    assert(isNaN(floor(real.init)));
+}
+
+// ditto
+double floor(double x) @trusted pure nothrow @nogc
+{
+    // Special cases.
+    if (isNaN(x) || isInfinity(x) || x == 0.0)
+        return x;
+
+    return floorImpl(x);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(floor(+123.456) == +123);
+    assert(floor(-123.456) == -124);
+    assert(floor(-1.234) == -2);
+    assert(floor(-0.123) == -1);
+    assert(floor(0.0) == 0);
+    assert(floor(+0.123) == 0);
+    assert(floor(+1.234) == 1);
+    assert(floor(double.infinity) == double.infinity);
+    assert(isNaN(floor(double.nan)));
+    assert(isNaN(floor(double.init)));
+}
+
+// ditto
+float floor(float x) @trusted pure nothrow @nogc
+{
+    // Special cases.
+    if (isNaN(x) || isInfinity(x) || x == 0.0)
+        return x;
+
+    return floorImpl(x);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(floor(+123.456f) == +123);
+    assert(floor(-123.456f) == -124);
+    assert(floor(-1.234f) == -2);
+    assert(floor(-0.123f) == -1);
+    assert(floor(0.0f) == 0);
+    assert(floor(+0.123f) == 0);
+    assert(floor(+1.234f) == 1);
+    assert(floor(float.infinity) == float.infinity);
+    assert(isNaN(floor(float.nan)));
+    assert(isNaN(floor(float.init)));
+}
+
+/******************************************
+ * Rounds x to the nearest integer value, using the current rounding
+ * mode.
+ *
+ * Unlike the rint functions, nearbyint does not raise the
+ * FE_INEXACT exception.
+ */
+real nearbyint(real x) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        assert(0);      // not implemented in C library
+    }
+    else
+        return core.stdc.math.nearbyintl(x);
+}
+
+/**********************************
+ * Rounds x to the nearest integer value, using the current rounding
+ * mode.
+ * If the return value is not equal to x, the FE_INEXACT
+ * exception is raised.
+ * $(B nearbyint) performs
+ * the same operation, but does not set the FE_INEXACT exception.
+ */
+real rint(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.rint(x); }
+//FIXME
+///ditto
+double rint(double x) @safe pure nothrow @nogc { return rint(cast(real)x); }
+//FIXME
+///ditto
+float rint(float x) @safe pure nothrow @nogc { return rint(cast(real)x); }
+
+unittest
+{
+    real function(real) print = &rint;
+    assert(print != null);
+}
+
+/***************************************
+ * Rounds x to the nearest integer value, using the current rounding
+ * mode.
+ *
+ * This is generally the fastest method to convert a floating-point number
+ * to an integer. Note that the results from this function
+ * depend on the rounding mode, if the fractional part of x is exactly 0.5.
+ * If using the default rounding mode (ties round to even integers)
+ * lrint(4.5) == 4, lrint(5.5)==6.
+ */
+long lrint(real x) @trusted pure nothrow @nogc
+{
+    version(InlineAsm_X86_Any)
+    {
+        version (Win64)
+        {
+            asm pure nothrow @nogc
+            {
+                naked;
+                fld     real ptr [RCX];
+                fistp   qword ptr 8[RSP];
+                mov     RAX,8[RSP];
+                ret;
+            }
+        }
+        else
+        {
+            long n;
+            asm pure nothrow @nogc
+            {
+                fld x;
+                fistp n;
+            }
+            return n;
+        }
+    }
+    else
+    {
+        alias F = floatTraits!(real);
+        static if (F.realFormat == RealFormat.ieeeDouble)
+        {
+            long result;
+
+            // Rounding limit when casting from real(double) to ulong.
+            enum real OF = 4.50359962737049600000E15L;
+
+            uint* vi = cast(uint*)(&x);
+
+            // Find the exponent and sign
+            uint msb = vi[MANTISSA_MSB];
+            uint lsb = vi[MANTISSA_LSB];
+            int exp = ((msb >> 20) & 0x7ff) - 0x3ff;
+            int sign = msb >> 31;
+            msb &= 0xfffff;
+            msb |= 0x100000;
+
+            if (exp < 63)
+            {
+                if (exp >= 52)
+                    result = (cast(long) msb << (exp - 20)) | (lsb << (exp - 52));
+                else
+                {
+                    // Adjust x and check result.
+                    real j = sign ? -OF : OF;
+                    x = (j + x) - j;
+                    msb = vi[MANTISSA_MSB];
+                    lsb = vi[MANTISSA_LSB];
+                    exp = ((msb >> 20) & 0x7ff) - 0x3ff;
+                    msb &= 0xfffff;
+                    msb |= 0x100000;
+
+                    if (exp < 0)
+                        result = 0;
+                    else if (exp < 20)
+                        result = cast(long) msb >> (20 - exp);
+                    else if (exp == 20)
+                        result = cast(long) msb;
+                    else
+                        result = (cast(long) msb << (exp - 20)) | (lsb >> (52 - exp));
+                }
+            }
+            else
+            {
+                // It is left implementation defined when the number is too large.
+                return cast(long) x;
+            }
+
+            return sign ? -result : result;
+        }
+        else static if (F.realFormat == RealFormat.ieeeExtended)
+        {
+            long result;
+
+            // Rounding limit when casting from real(80-bit) to ulong.
+            enum real OF = 9.22337203685477580800E18L;
+
+            ushort* vu = cast(ushort*)(&x);
+            uint* vi = cast(uint*)(&x);
+
+            // Find the exponent and sign
+            int exp = (vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;
+            int sign = (vu[F.EXPPOS_SHORT] >> 15) & 1;
+
+            if (exp < 63)
+            {
+                // Adjust x and check result.
+                real j = sign ? -OF : OF;
+                x = (j + x) - j;
+                exp = (vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;
+
+                version (LittleEndian)
+                {
+                    if (exp < 0)
+                        result = 0;
+                    else if (exp <= 31)
+                        result = vi[1] >> (31 - exp);
+                    else
+                        result = (cast(long) vi[1] << (exp - 31)) | (vi[0] >> (63 - exp));
+                }
+                else
+                {
+                    if (exp < 0)
+                        result = 0;
+                    else if (exp <= 31)
+                        result = vi[1] >> (31 - exp);
+                    else
+                        result = (cast(long) vi[1] << (exp - 31)) | (vi[2] >> (63 - exp));
+                }
+            }
+            else
+            {
+                // It is left implementation defined when the number is too large
+                // to fit in a 64bit long.
+                return cast(long) x;
+            }
+
+            return sign ? -result : result;
+        }
+        else
+        {
+            static assert(false, "Only 64-bit and 80-bit reals are supported by lrint()");
+        }
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(lrint(4.5) == 4);
+    assert(lrint(5.5) == 6);
+    assert(lrint(-4.5) == -4);
+    assert(lrint(-5.5) == -6);
+
+    assert(lrint(int.max - 0.5) == 2147483646L);
+    assert(lrint(int.max + 0.5) == 2147483648L);
+    assert(lrint(int.min - 0.5) == -2147483648L);
+    assert(lrint(int.min + 0.5) == -2147483648L);
+}
+
+/*******************************************
+ * Return the value of x rounded to the nearest integer.
+ * If the fractional part of x is exactly 0.5, the return value is
+ * rounded away from zero.
+ */
+real round(real x) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        auto old = FloatingPointControl.getControlState();
+        FloatingPointControl.setControlState((old & ~FloatingPointControl.ROUNDING_MASK) | FloatingPointControl.roundToZero);
+        x = rint((x >= 0) ? x + 0.5 : x - 0.5);
+        FloatingPointControl.setControlState(old);
+        return x;
+    }
+    else
+        return core.stdc.math.roundl(x);
+}
+
+/**********************************************
+ * Return the value of x rounded to the nearest integer.
+ *
+ * If the fractional part of x is exactly 0.5, the return value is rounded
+ * away from zero.
+ */
+long lround(real x) @trusted nothrow @nogc
+{
+    version (Posix)
+        return core.stdc.math.llroundl(x);
+    else
+        assert (0, "lround not implemented");
+}
+
+version(Posix)
+{
+    @safe nothrow @nogc unittest
+    {
+        assert(lround(0.49) == 0);
+        assert(lround(0.5) == 1);
+        assert(lround(1.5) == 2);
+    }
+}
+
+/****************************************************
+ * Returns the integer portion of x, dropping the fractional portion.
+ *
+ * This is also known as "chop" rounding.
+ */
+real trunc(real x) @trusted nothrow @nogc
+{
+    version (Win64_DMD_InlineAsm)
+    {
+        asm pure nothrow @nogc
+        {
+            naked                       ;
+            fld     real ptr [RCX]      ;
+            fstcw   8[RSP]              ;
+            mov     AL,9[RSP]           ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x0C             ; // round to 0
+            mov     9[RSP],AL           ;
+            fldcw   8[RSP]              ;
+            frndint                     ;
+            mov     9[RSP],DL           ;
+            fldcw   8[RSP]              ;
+            ret                         ;
+        }
+    }
+    else version(CRuntime_Microsoft)
+    {
+        short cw;
+        asm pure nothrow @nogc
+        {
+            fld     x                   ;
+            fstcw   cw                  ;
+            mov     AL,byte ptr cw+1    ;
+            mov     DL,AL               ;
+            and     AL,0xC3             ;
+            or      AL,0x0C             ; // round to 0
+            mov     byte ptr cw+1,AL    ;
+            fldcw   cw                  ;
+            frndint                     ;
+            mov     byte ptr cw+1,DL    ;
+            fldcw   cw                  ;
+        }
+    }
+    else
+        return core.stdc.math.truncl(x);
+}
+
+/****************************************************
+ * Calculate the remainder x REM y, following IEC 60559.
+ *
+ * REM is the value of x - y * n, where n is the integer nearest the exact
+ * value of x / y.
+ * If |n - x / y| == 0.5, n is even.
+ * If the result is zero, it has the same sign as x.
+ * Otherwise, the sign of the result is the sign of x / y.
+ * Precision mode has no effect on the remainder functions.
+ *
+ * remquo returns n in the parameter n.
+ *
+ * $(TABLE_SV
+ *  $(TR $(TH x)               $(TH y)            $(TH remainder(x, y)) $(TH n)   $(TH invalid?))
+ *  $(TR $(TD $(PLUSMN)0.0)    $(TD not 0.0)      $(TD $(PLUSMN)0.0)    $(TD 0.0) $(TD no))
+ *  $(TR $(TD $(PLUSMNINF))    $(TD anything)     $(TD $(NAN))          $(TD ?)   $(TD yes))
+ *  $(TR $(TD anything)        $(TD $(PLUSMN)0.0) $(TD $(NAN))          $(TD ?)   $(TD yes))
+ *  $(TR $(TD != $(PLUSMNINF)) $(TD $(PLUSMNINF)) $(TD x)               $(TD ?)   $(TD no))
+ * )
+ *
+ * Note: remquo not supported on windows
+ */
+real remainder(real x, real y) @trusted nothrow @nogc
+{
+    version (CRuntime_Microsoft)
+    {
+        int n;
+        return remquo(x, y, n);
+    }
+    else
+        return core.stdc.math.remainderl(x, y);
+}
+
+real remquo(real x, real y, out int n) @trusted nothrow @nogc  /// ditto
+{
+    version (Posix)
+        return core.stdc.math.remquol(x, y, &n);
+    else
+        assert (0, "remquo not implemented");
+}
+
+/** IEEE exception status flags ('sticky bits')
+
+ These flags indicate that an exceptional floating-point condition has occurred.
+ They indicate that a NaN or an infinity has been generated, that a result
+ is inexact, or that a signalling NaN has been encountered. If floating-point
+ exceptions are enabled (unmasked), a hardware exception will be generated
+ instead of setting these flags.
+ */
+struct IeeeFlags
+{
+private:
+    // The x87 FPU status register is 16 bits.
+    // The Pentium SSE2 status register is 32 bits.
+    uint flags;
+    version (X86_Any)
+    {
+        // Applies to both x87 status word (16 bits) and SSE2 status word(32 bits).
+        enum : int
+        {
+            INEXACT_MASK   = 0x20,
+            UNDERFLOW_MASK = 0x10,
+            OVERFLOW_MASK  = 0x08,
+            DIVBYZERO_MASK = 0x04,
+            INVALID_MASK   = 0x01
+        }
+        // Don't bother about subnormals, they are not supported on most CPUs.
+        //  SUBNORMAL_MASK = 0x02;
+    }
+    else version (PPC_Any)
+    {
+        // PowerPC FPSCR is a 32-bit register.
+        enum : int
+        {
+            INEXACT_MASK   = 0x02000000,
+            DIVBYZERO_MASK = 0x04000000,
+            UNDERFLOW_MASK = 0x08000000,
+            OVERFLOW_MASK  = 0x10000000,
+            INVALID_MASK   = 0x20000000 // Summary as PowerPC has five types of invalid exceptions.
+        }
+    }
+    else version (ARM)
+    {
+        // ARM FPSCR is a 32bit register
+        enum : int
+        {
+            INEXACT_MASK   = 0x10,
+            UNDERFLOW_MASK = 0x08,
+            OVERFLOW_MASK  = 0x04,
+            DIVBYZERO_MASK = 0x02,
+            INVALID_MASK   = 0x01
+        }
+    }
+    else version(SPARC)
+    {
+        // SPARC FSR is a 32bit register
+             //(64 bits for Sparc 7 & 8, but high 32 bits are uninteresting).
+        enum : int
+        {
+            INEXACT_MASK   = 0x020,
+            UNDERFLOW_MASK = 0x080,
+            OVERFLOW_MASK  = 0x100,
+            DIVBYZERO_MASK = 0x040,
+            INVALID_MASK   = 0x200
+        }
+    }
+    else
+        static assert(0, "Not implemented");
+private:
+    static uint getIeeeFlags()
+    {
+        version(D_InlineAsm_X86)
+        {
+            asm pure nothrow @nogc
+            {
+                 fstsw AX;
+                 // NOTE: If compiler supports SSE2, need to OR the result with
+                 // the SSE2 status register.
+                 // Clear all irrelevant bits
+                 and EAX, 0x03D;
+            }
+        }
+        else version(D_InlineAsm_X86_64)
+        {
+            asm pure nothrow @nogc
+            {
+                 fstsw AX;
+                 // NOTE: If compiler supports SSE2, need to OR the result with
+                 // the SSE2 status register.
+                 // Clear all irrelevant bits
+                 and RAX, 0x03D;
+            }
+        }
+        else version (GNU)
+        {
+            uint result;
+            version (X86)
+            {
+                asm pure nothrow @nogc
+                {
+                    "fstsw %%ax; andl $0x03D, %%eax;" : "=a" result;
+                }
+            }
+            else version (X86_64)
+            {
+                asm pure nothrow @nogc
+                {
+                    "fstsw %%ax; andq $0x03D, %%rax;" : "=a" result;
+                }
+            }
+            else version (ARM)
+            {
+                version (ARM_SoftFloat)
+                    result = 0;
+                else
+                {
+                    asm pure nothrow @nogc
+                    {
+                        "vmrs %0, FPSCR; and %0, %0, #0x1F;" : "=r" result;
+                    }
+                }
+            }
+            else
+                assert(0, "Not yet supported");
+
+            return result;
+        }
+        else version (SPARC)
+        {
+           /*
+               int retval;
+               asm pure nothrow @nogc { st %fsr, retval; }
+               return retval;
+            */
+           assert(0, "Not yet supported");
+        }
+        else version (ARM)
+        {
+            assert(false, "Not yet supported.");
+        }
+        else
+            assert(0, "Not yet supported");
+    }
+    static void resetIeeeFlags()
+    {
+        version(InlineAsm_X86_Any)
+        {
+            asm pure nothrow @nogc
+            {
+                fnclex;
+            }
+        }
+        else
+        version (GNU)
+        {
+            version (X86_Any)
+            {
+                asm pure nothrow @nogc
+                {
+                    "fnclex;";
+                }
+            }
+            else version (ARM)
+            {
+                version (ARM_SoftFloat)
+                    return;
+                else
+                {
+                    uint old = void;
+                    asm pure nothrow @nogc
+                    {
+                        "vmrs %0, FPSCR" : "=r" (old);
+                    }
+                    old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html
+                    asm pure nothrow @nogc
+                    {
+                        "vmsr FPSCR, %0;" : : "r" (old);
+                    }
+                }
+            }
+            else
+                assert(0, "Not yet supported");
+        }
+        else
+        {
+            /* SPARC:
+              int tmpval;
+              asm pure nothrow @nogc { st %fsr, tmpval; }
+              tmpval &=0xFFFF_FC00;
+              asm pure nothrow @nogc { ld tmpval, %fsr; }
+            */
+           assert(0, "Not yet supported");
+        }
+    }
+public:
+    version (IeeeFlagsSupport) {
+
+     /// The result cannot be represented exactly, so rounding occurred.
+     /// (example: x = sin(0.1); )
+     @property bool inexact() { return (flags & INEXACT_MASK) != 0; }
+
+     /// A zero was generated by underflow (example: x = real.min*real.epsilon/2;)
+     @property bool underflow() { return (flags & UNDERFLOW_MASK) != 0; }
+
+     /// An infinity was generated by overflow (example: x = real.max*2;)
+     @property bool overflow() { return (flags & OVERFLOW_MASK) != 0; }
+
+     /// An infinity was generated by division by zero (example: x = 3/0.0; )
+     @property bool divByZero() { return (flags & DIVBYZERO_MASK) != 0; }
+
+     /// A machine NaN was generated. (example: x = real.infinity * 0.0; )
+     @property bool invalid() { return (flags & INVALID_MASK) != 0; }
+
+     }
+}
+
+///
+version (GNU)
+{
+    unittest
+    {
+        pragma(msg, "ieeeFlags test disabled, see LDC Issue #888");
+    }
+}
+else
+unittest
+{
+    static void func() {
+        int a = 10 * 10;
+    }
+
+    real a=3.5;
+    // Set all the flags to zero
+    resetIeeeFlags();
+    assert(!ieeeFlags.divByZero);
+    // Perform a division by zero.
+    a/=0.0L;
+    assert(a==real.infinity);
+    assert(ieeeFlags.divByZero);
+    // Create a NaN
+    a*=0.0L;
+    assert(ieeeFlags.invalid);
+    assert(isNaN(a));
+
+    // Check that calling func() has no effect on the
+    // status flags.
+    IeeeFlags f = ieeeFlags;
+    func();
+    assert(ieeeFlags == f);
+}
+
+version(X86_Any)
+{
+    version = IeeeFlagsSupport;
+}
+else version(ARM)
+{
+    version(GNU)
+    {
+        version(ARM_HardFloat)
+            version = IeeeFlagsSupport;
+        else version(ARM_SoftFP)
+            version = IeeeFlagsSupport;
+    }
+    else
+    {
+        version = IeeeFlagsSupport;
+    }
+}
+
+/// Set all of the floating-point status flags to false.
+void resetIeeeFlags() { IeeeFlags.resetIeeeFlags(); }
+
+/// Return a snapshot of the current state of the floating-point status flags.
+@property IeeeFlags ieeeFlags()
+{
+   return IeeeFlags(IeeeFlags.getIeeeFlags());
+}
+
+/** Control the Floating point hardware
+
+  Change the IEEE754 floating-point rounding mode and the floating-point
+  hardware exceptions.
+
+  By default, the rounding mode is roundToNearest and all hardware exceptions
+  are disabled. For most applications, debugging is easier if the $(I division
+  by zero), $(I overflow), and $(I invalid operation) exceptions are enabled.
+  These three are combined into a $(I severeExceptions) value for convenience.
+  Note in particular that if $(I invalidException) is enabled, a hardware trap
+  will be generated whenever an uninitialized floating-point variable is used.
+
+  All changes are temporary. The previous state is restored at the
+  end of the scope.
+
+
+Example:
+----
+{
+    FloatingPointControl fpctrl;
+
+    // Enable hardware exceptions for division by zero, overflow to infinity,
+    // invalid operations, and uninitialized floating-point variables.
+    fpctrl.enableExceptions(FloatingPointControl.severeExceptions);
+
+    // This will generate a hardware exception, if x is a
+    // default-initialized floating point variable:
+    real x; // Add `= 0` or even `= real.nan` to not throw the exception.
+    real y = x * 3.0;
+
+    // The exception is only thrown for default-uninitialized NaN-s.
+    // NaN-s with other payload are valid:
+    real z = y * real.nan; // ok
+
+    // Changing the rounding mode:
+    fpctrl.rounding = FloatingPointControl.roundUp;
+    assert(rint(1.1) == 2);
+
+    // The set hardware exceptions will be disabled when leaving this scope.
+    // The original rounding mode will also be restored.
+}
+
+// Ensure previous values are returned:
+assert(!FloatingPointControl.enabledExceptions);
+assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest);
+assert(rint(1.1) == 1);
+----
+
+ */
+struct FloatingPointControl
+{
+    alias RoundingMode = uint;
+
+    /** IEEE rounding modes.
+     * The default mode is roundToNearest.
+     */
+    version(ARM)
+    {
+        enum : RoundingMode
+        {
+            roundToNearest = 0x000000,
+            roundDown      = 0x800000,
+            roundUp        = 0x400000,
+            roundToZero    = 0xC00000
+        }
+    }
+    else version(PPC_Any)
+    {
+        enum : RoundingMode
+        {
+            roundToNearest = 0x00000000,
+            roundDown      = 0x00000003,
+            roundUp        = 0x00000002,
+            roundToZero    = 0x00000001
+        }
+    }
+    else
+    {
+        enum : RoundingMode
+        {
+            roundToNearest = 0x0000,
+            roundDown      = 0x0400,
+            roundUp        = 0x0800,
+            roundToZero    = 0x0C00
+        }
+    }
+
+    /** IEEE hardware exceptions.
+     *  By default, all exceptions are masked (disabled).
+     */
+    version(ARM)
+    {
+        enum : uint
+        {
+            subnormalException    = 0x8000,
+            inexactException      = 0x1000,
+            underflowException    = 0x0800,
+            overflowException     = 0x0400,
+            divByZeroException    = 0x0200,
+            invalidException      = 0x0100,
+            /// Severe = The overflow, division by zero, and invalid exceptions.
+            severeExceptions   = overflowException | divByZeroException
+                                 | invalidException,
+            allExceptions      = severeExceptions | underflowException
+                                 | inexactException | subnormalException,
+        }
+    }
+    else version(PPC_Any)
+    {
+        enum : uint
+        {
+            inexactException      = 0x0008,
+            divByZeroException    = 0x0010,
+            underflowException    = 0x0020,
+            overflowException     = 0x0040,
+            invalidException      = 0x0080,
+            /// Severe = The overflow, division by zero, and invalid exceptions.
+            severeExceptions   = overflowException | divByZeroException
+                                 | invalidException,
+            allExceptions      = severeExceptions | underflowException
+                                 | inexactException,
+        }
+    }
+    else
+    {
+        enum : uint
+        {
+            inexactException      = 0x20,
+            underflowException    = 0x10,
+            overflowException     = 0x08,
+            divByZeroException    = 0x04,
+            subnormalException    = 0x02,
+            invalidException      = 0x01,
+            /// Severe = The overflow, division by zero, and invalid exceptions.
+            severeExceptions   = overflowException | divByZeroException
+                                 | invalidException,
+            allExceptions      = severeExceptions | underflowException
+                                 | inexactException | subnormalException,
+        }
+    }
+
+private:
+    version(ARM)
+    {
+        enum uint EXCEPTION_MASK = 0x9F00;
+        enum uint ROUNDING_MASK = 0xC00000;
+    }
+    else version(PPC_Any)
+    {
+        enum uint EXCEPTION_MASK = 0x00F8;
+        enum uint ROUNDING_MASK = 0x0003;
+    }
+    else version(X86)
+    {
+        enum ushort EXCEPTION_MASK = 0x3F;
+        enum ushort ROUNDING_MASK = 0xC00;
+    }
+    else version(X86_64)
+    {
+        enum ushort EXCEPTION_MASK = 0x3F;
+        enum ushort ROUNDING_MASK = 0xC00;
+    }
+    else
+        static assert(false, "Architecture not supported");
+
+public:
+    /// Returns true if the current FPU supports exception trapping
+    @property static bool hasExceptionTraps() @safe nothrow @nogc
+    {
+        version(X86_Any)
+            return true;
+        else version(PPC_Any)
+            return true;
+        else version(ARM)
+        {
+            auto oldState = getControlState();
+            // If exceptions are not supported, we set the bit but read it back as zero
+            // https://sourceware.org/ml/libc-ports/2012-06/msg00091.html
+            setControlState(oldState | (divByZeroException & EXCEPTION_MASK));
+            bool result = (getControlState() & EXCEPTION_MASK) != 0;
+            setControlState(oldState);
+            return result;
+        }
+        else
+            static assert(false, "Not implemented for this architecture");
+    }
+
+    /// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together.
+    void enableExceptions(uint exceptions) @nogc
+    {
+        assert(hasExceptionTraps);
+        initialize();
+        version(X86_Any)
+            setControlState(getControlState() & ~(exceptions & EXCEPTION_MASK));
+        else
+            setControlState(getControlState() | (exceptions & EXCEPTION_MASK));
+    }
+
+    /// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together.
+    void disableExceptions(uint exceptions) @nogc
+    {
+        assert(hasExceptionTraps);
+        initialize();
+        version(X86_Any)
+            setControlState(getControlState() | (exceptions & EXCEPTION_MASK));
+        else
+            setControlState(getControlState() & ~(exceptions & EXCEPTION_MASK));
+    }
+
+    //// Change the floating-point hardware rounding mode
+    @property void rounding(RoundingMode newMode) @nogc
+    {
+        initialize();
+        setControlState((getControlState() & ~ROUNDING_MASK) | (newMode & ROUNDING_MASK));
+    }
+
+    /// Return the exceptions which are currently enabled (unmasked)
+    @property static uint enabledExceptions() @nogc
+    {
+        assert(hasExceptionTraps);
+        version(X86_Any)
+            return (getControlState() & EXCEPTION_MASK) ^ EXCEPTION_MASK;
+        else
+            return (getControlState() & EXCEPTION_MASK);
+    }
+
+    /// Return the currently active rounding mode
+    @property static RoundingMode rounding() @nogc
+    {
+        return cast(RoundingMode)(getControlState() & ROUNDING_MASK);
+    }
+
+    ///  Clear all pending exceptions, then restore the original exception state and rounding mode.
+    ~this() @nogc
+    {
+        clearExceptions();
+        if (initialized)
+            setControlState(savedState);
+    }
+
+private:
+    ControlState savedState;
+
+    bool initialized = false;
+
+    version(ARM)
+    {
+        alias ControlState = uint;
+    }
+    else version(PPC_Any)
+    {
+        alias ControlState = uint;
+    }
+    else
+    {
+        alias ControlState = ushort;
+    }
+
+    void initialize() @nogc
+    {
+        // BUG: This works around the absence of this() constructors.
+        if (initialized) return;
+        clearExceptions();
+        savedState = getControlState();
+        initialized = true;
+    }
+
+    // Clear all pending exceptions
+    static void clearExceptions() @nogc
+    {
+        version (InlineAsm_X86_Any)
+        {
+            asm nothrow @nogc
+            {
+                fclex;
+            }
+        }
+        else version (GNU)
+        {
+            version (X86_Any)
+            {
+                asm pure nothrow @nogc
+                {
+                    "fclex;";
+                }
+            }
+            else version (ARM)
+            {
+                version (ARM_SoftFloat)
+                    return;
+                else
+                {
+                    uint old = getControlState();
+                    old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html
+                    asm pure nothrow @nogc
+                    {
+                        "vmsr FPSCR, %0;" : : "r" (old);
+                    }
+                }
+            }
+            else
+                assert(0, "Not yet supported");
+        }
+        else
+            assert(0, "Not yet supported");
+    }
+
+    // Read from the control register
+    static ControlState getControlState() @trusted nothrow @nogc
+    {
+        version (D_InlineAsm_X86)
+        {
+            short cont;
+            asm nothrow @nogc
+            {
+                xor EAX, EAX;
+                fstcw cont;
+            }
+            return cont;
+        }
+        else
+        version (D_InlineAsm_X86_64)
+        {
+            short cont;
+            asm nothrow @nogc
+            {
+                xor RAX, RAX;
+                fstcw cont;
+            }
+            return cont;
+        }
+        else
+        version (GNU)
+        {
+            ControlState cont;
+            version (X86)
+            {
+                asm pure nothrow @nogc
+                {
+                    "xor %%eax, %%eax; fstcw %[cw];" : [cw] "=m" cont :: "eax";
+                }
+            }
+            else version (X86_64)
+            {
+                asm pure nothrow @nogc
+                {
+                    "xor %%rax, %%rax; fstcw %[cw];" : [cw] "=m" cont :: "rax";
+                }
+            }
+            else version (ARM)
+            {
+                version (ARM_SoftFloat)
+                   cont = 0;
+                else
+                {
+                    asm pure nothrow @nogc
+                    {
+                        "vmrs %0, FPSCR;" : "=r" cont;
+                    }
+                }
+            }
+            else
+                assert(0, "Not yet supported");
+            return cont;
+        }
+        else
+            assert(0, "Not yet supported");
+    }
+
+    // Set the control register
+    static void setControlState(ControlState newState) @trusted nothrow @nogc
+    {
+        version (InlineAsm_X86_Any)
+        {
+            version (Win64)
+            {
+                asm nothrow @nogc
+                {
+                    naked;
+                    mov     8[RSP],RCX;
+                    fclex;
+                    fldcw   8[RSP];
+                    ret;
+                }
+            }
+            else
+            {
+                asm nothrow @nogc
+                {
+                    fclex;
+                    fldcw newState;
+                }
+            }
+        }
+        else version (GNU)
+        {
+            version (X86_Any)
+            {
+                asm pure nothrow @nogc
+                {
+                    "fclex; fldcw %[cw]" : : [cw] "m" newState;
+                }
+            }
+            else version (ARM)
+            {
+                version (ARM_SoftFloat)
+                   return;
+                else
+                {
+                    asm pure nothrow @nogc
+                    {
+                        "vmsr FPSCR, %0;" : : "r" (newState);
+                    }
+                }
+            }
+            else
+                assert(0, "Not yet supported");
+        }
+        else
+            assert(0, "Not yet supported");
+    }
+}
+
+unittest
+{
+    // GCC floating point emulation doesn't allow changing
+    // rounding modes, getting error bits etc
+    version(GNU) version(D_SoftFloat)
+        return;
+
+    void ensureDefaults()
+    {
+        assert(FloatingPointControl.rounding
+               == FloatingPointControl.roundToNearest);
+        if(FloatingPointControl.hasExceptionTraps)
+            assert(FloatingPointControl.enabledExceptions == 0);
+    }
+
+    {
+        FloatingPointControl ctrl;
+    }
+    ensureDefaults();
+
+    {
+        FloatingPointControl ctrl;
+        ctrl.rounding = FloatingPointControl.roundDown;
+        assert(FloatingPointControl.rounding == FloatingPointControl.roundDown);
+    }
+    ensureDefaults();
+
+    if(FloatingPointControl.hasExceptionTraps)
+    {
+        FloatingPointControl ctrl;
+        ctrl.enableExceptions(FloatingPointControl.divByZeroException
+                              | FloatingPointControl.overflowException);
+        assert(ctrl.enabledExceptions ==
+               (FloatingPointControl.divByZeroException
+                | FloatingPointControl.overflowException));
+
+        ctrl.rounding = FloatingPointControl.roundUp;
+        assert(FloatingPointControl.rounding == FloatingPointControl.roundUp);
+    }
+    ensureDefaults();
+}
+
+
+/*********************************
+ * Determines if $(D_PARAM x) is NaN.
+ * params:
+ *  x = a floating point number.
+ * returns:
+ *  $(D true) if $(D_PARAM x) is Nan.
+ */
+bool isNaN(X)(X x) @nogc @trusted pure nothrow
+    if (isFloatingPoint!(X))
+{
+    alias F = floatTraits!(X);
+    static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        const uint p = *cast(uint *)&x;
+        return ((p & 0x7F80_0000) == 0x7F80_0000)
+            && p & 0x007F_FFFF; // not infinity
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        const ulong  p = *cast(ulong *)&x;
+        return ((p & 0x7FF0_0000_0000_0000) == 0x7FF0_0000_0000_0000)
+            && p & 0x000F_FFFF_FFFF_FFFF; // not infinity
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
+        const ulong ps = *cast(ulong *)&x;
+        return e == F.EXPMASK &&
+            ps & 0x7FFF_FFFF_FFFF_FFFF; // not infinity
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
+        const ulong psLsb = (cast(ulong *)&x)[MANTISSA_LSB];
+        const ulong psMsb = (cast(ulong *)&x)[MANTISSA_MSB];
+        return e == F.EXPMASK &&
+            (psLsb | (psMsb& 0x0000_FFFF_FFFF_FFFF)) != 0;
+    }
+    else
+    {
+        return x != x;
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isNaN(float.init));
+    assert( isNaN(-double.init));
+    assert( isNaN(real.nan));
+    assert( isNaN(-real.nan));
+    assert(!isNaN(cast(float)53.6));
+    assert(!isNaN(cast(real)-53.6));
+}
+
+// Explicitly undocumented. It will be removed in July 2016. @@@DEPRECATED_2016-07@@@
+deprecated("isNaN is not defined for integer types")
+bool isNaN(X)(X x) @nogc @trusted pure nothrow
+    if (isIntegral!(X))
+{
+    return isNaN(cast(float)x);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    import std.meta;
+
+    foreach(T; AliasSeq!(float, double, real))
+    {
+        // CTFE-able tests
+        assert(isNaN(T.init));
+        assert(isNaN(-T.init));
+        assert(isNaN(T.nan));
+        assert(isNaN(-T.nan));
+        assert(!isNaN(T.infinity));
+        assert(!isNaN(-T.infinity));
+        assert(!isNaN(cast(T)53.6));
+        assert(!isNaN(cast(T)-53.6));
+
+        // Runtime tests
+        shared T f;
+        f = T.init;
+        assert(isNaN(f));
+        assert(isNaN(-f));
+        f = T.nan;
+        assert(isNaN(f));
+        assert(isNaN(-f));
+        f = T.infinity;
+        assert(!isNaN(f));
+        assert(!isNaN(-f));
+        f = cast(T)53.6;
+        assert(!isNaN(f));
+        assert(!isNaN(-f));
+    }
+}
+
+/*********************************
+ * Determines if $(D_PARAM x) is finite.
+ * params:
+ *  x = a floating point number.
+ * returns:
+ *  $(D true) if $(D_PARAM x) is finite.
+ */
+bool isFinite(X)(X x) @trusted pure nothrow @nogc
+{
+    alias F = floatTraits!(X);
+    ushort* pe = cast(ushort *)&x;
+    return (pe[F.EXPPOS_SHORT] & F.EXPMASK) != F.EXPMASK;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( isFinite(1.23f));
+    assert( isFinite(float.max));
+    assert( isFinite(float.min_normal));
+    assert(!isFinite(float.nan));
+    assert(!isFinite(float.infinity));
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(isFinite(1.23));
+    assert(isFinite(double.max));
+    assert(isFinite(double.min_normal));
+    assert(!isFinite(double.nan));
+    assert(!isFinite(double.infinity));
+
+    assert(isFinite(1.23L));
+    assert(isFinite(real.max));
+    assert(isFinite(real.min_normal));
+    assert(!isFinite(real.nan));
+    assert(!isFinite(real.infinity));
+}
+
+// Explicitly undocumented. It will be removed in July 2016. @@@DEPRECATED_2016-07@@@
+deprecated("isFinite is not defined for integer types")
+int isFinite(X)(X x) @trusted pure nothrow @nogc
+    if (isIntegral!(X))
+{
+    return isFinite(cast(float)x);
+}
+
+
+/*********************************
+ * Determines if $(D_PARAM x) is normalized.
+ *
+ * A normalized number must not be zero, subnormal, infinite nor $(NAN).
+ *
+ * params:
+ *  x = a floating point number.
+ * returns:
+ *  $(D true) if $(D_PARAM x) is normalized.
+ */
+
+/* Need one for each format because subnormal floats might
+ * be converted to normal reals.
+ */
+bool isNormal(X)(X x) @trusted pure nothrow @nogc
+{
+    alias F = floatTraits!(X);
+    static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        // doubledouble is normal if the least significant part is normal.
+        return isNormal((cast(double*)&x)[MANTISSA_LSB]);
+    }
+    else
+    {
+        ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
+        return (e != F.EXPMASK && e != 0);
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    float f = 3;
+    double d = 500;
+    real e = 10e+48;
+
+    assert(isNormal(f));
+    assert(isNormal(d));
+    assert(isNormal(e));
+    f = d = e = 0;
+    assert(!isNormal(f));
+    assert(!isNormal(d));
+    assert(!isNormal(e));
+    assert(!isNormal(real.infinity));
+    assert(isNormal(-real.max));
+    assert(!isNormal(real.min_normal/4));
+
+}
+
+/*********************************
+ * Determines if $(D_PARAM x) is subnormal.
+ *
+ * Subnormals (also known as "denormal number"), have a 0 exponent
+ * and a 0 most significant mantissa bit.
+ *
+ * params:
+ *  x = a floating point number.
+ * returns:
+ *  $(D true) if $(D_PARAM x) is a denormal number.
+ */
+bool isSubnormal(X)(X x) @trusted pure nothrow @nogc
+{
+    /*
+        Need one for each format because subnormal floats might
+        be converted to normal reals.
+    */
+    alias F = floatTraits!(X);
+    static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        uint *p = cast(uint *)&x;
+        return (*p & F.EXPMASK_INT) == 0 && *p & F.MANTISSAMASK_INT;
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        uint *p = cast(uint *)&x;
+        return (p[MANTISSA_MSB] & F.EXPMASK_INT) == 0
+            && (p[MANTISSA_LSB] || p[MANTISSA_MSB] & F.MANTISSAMASK_INT);
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
+        long*   ps = cast(long *)&x;
+        return (e == 0 &&
+          (((ps[MANTISSA_LSB]|(ps[MANTISSA_MSB]& 0x0000_FFFF_FFFF_FFFF))) != 0));
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        ushort* pe = cast(ushort *)&x;
+        long*   ps = cast(long *)&x;
+
+        return (pe[F.EXPPOS_SHORT] & F.EXPMASK) == 0 && *ps > 0;
+    }
+    else static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        return isSubnormal((cast(double*)&x)[MANTISSA_MSB]);
+    }
+    else
+    {
+        static assert(false, "Not implemented for this architecture");
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    import std.meta;
+
+    foreach (T; AliasSeq!(float, double, real))
+    {
+        T f;
+        for (f = 1.0; !isSubnormal(f); f /= 2)
+            assert(f != 0);
+    }
+}
+
+// Explicitly undocumented. It will be removed in July 2016. @@@DEPRECATED_2016-07@@@
+deprecated("isSubnormal is not defined for integer types")
+int isSubnormal(X)(X x) @trusted pure nothrow @nogc
+    if (isIntegral!X)
+{
+    return isSubnormal(cast(double)x);
+}
+
+/*********************************
+ * Determines if $(D_PARAM x) is $(PLUSMN)$(INFIN).
+ * params:
+ *  x = a floating point number.
+ * returns:
+ *  $(D true) if $(D_PARAM x) is $(PLUSMN)$(INFIN).
+ */
+bool isInfinity(X)(X x) @nogc @trusted pure nothrow
+    if (isFloatingPoint!(X))
+{
+    alias F = floatTraits!(X);
+    static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        return ((*cast(uint *)&x) & 0x7FFF_FFFF) == 0x7F80_0000;
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        return ((*cast(ulong *)&x) & 0x7FFF_FFFF_FFFF_FFFF)
+            == 0x7FF0_0000_0000_0000;
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        const ushort e = cast(ushort)(F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]);
+        const ulong ps = *cast(ulong *)&x;
+
+        // On Motorola 68K, infinity can have hidden bit = 1 or 0. On x86, it is always 1.
+        return e == F.EXPMASK && (ps & 0x7FFF_FFFF_FFFF_FFFF) == 0;
+    }
+    else static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        return (((cast(ulong *)&x)[MANTISSA_MSB]) & 0x7FFF_FFFF_FFFF_FFFF)
+            == 0x7FF8_0000_0000_0000;
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        const long psLsb = (cast(long *)&x)[MANTISSA_LSB];
+        const long psMsb = (cast(long *)&x)[MANTISSA_MSB];
+        return (psLsb == 0)
+            && (psMsb & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_0000_0000_0000;
+    }
+    else
+    {
+        return (x - 1) == x;
+    }
+}
+
+///
+@nogc @safe pure nothrow unittest
+{
+    assert(!isInfinity(float.init));
+    assert(!isInfinity(-float.init));
+    assert(!isInfinity(float.nan));
+    assert(!isInfinity(-float.nan));
+    assert(isInfinity(float.infinity));
+    assert(isInfinity(-float.infinity));
+    assert(isInfinity(-1.0f / 0.0f));
+}
+
+@safe pure nothrow @nogc unittest
+{
+    // CTFE-able tests
+    assert(!isInfinity(double.init));
+    assert(!isInfinity(-double.init));
+    assert(!isInfinity(double.nan));
+    assert(!isInfinity(-double.nan));
+    assert(isInfinity(double.infinity));
+    assert(isInfinity(-double.infinity));
+    assert(isInfinity(-1.0 / 0.0));
+
+    assert(!isInfinity(real.init));
+    assert(!isInfinity(-real.init));
+    assert(!isInfinity(real.nan));
+    assert(!isInfinity(-real.nan));
+    assert(isInfinity(real.infinity));
+    assert(isInfinity(-real.infinity));
+    assert(isInfinity(-1.0L / 0.0L));
+
+    // Runtime tests
+    shared float f;
+    f = float.init;
+    assert(!isInfinity(f));
+    assert(!isInfinity(-f));
+    f = float.nan;
+    assert(!isInfinity(f));
+    assert(!isInfinity(-f));
+    f = float.infinity;
+    assert(isInfinity(f));
+    assert(isInfinity(-f));
+    f = (-1.0f / 0.0f);
+    assert(isInfinity(f));
+
+    shared double d;
+    d = double.init;
+    assert(!isInfinity(d));
+    assert(!isInfinity(-d));
+    d = double.nan;
+    assert(!isInfinity(d));
+    assert(!isInfinity(-d));
+    d = double.infinity;
+    assert(isInfinity(d));
+    assert(isInfinity(-d));
+    d = (-1.0 / 0.0);
+    assert(isInfinity(d));
+
+    shared real e;
+    e = real.init;
+    assert(!isInfinity(e));
+    assert(!isInfinity(-e));
+    e = real.nan;
+    assert(!isInfinity(e));
+    assert(!isInfinity(-e));
+    e = real.infinity;
+    assert(isInfinity(e));
+    assert(isInfinity(-e));
+    e = (-1.0L / 0.0L);
+    assert(isInfinity(e));
+}
+
+/*********************************
+ * Is the binary representation of x identical to y?
+ *
+ * Same as ==, except that positive and negative zero are not identical,
+ * and two $(NAN)s are identical if they have the same 'payload'.
+ */
+bool isIdentical(real x, real y) @trusted pure nothrow @nogc
+{
+    // We're doing a bitwise comparison so the endianness is irrelevant.
+    long*   pxs = cast(long *)&x;
+    long*   pys = cast(long *)&y;
+    alias F = floatTraits!(real);
+    static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        return pxs[0] == pys[0];
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple
+                 || F.realFormat == RealFormat.ibmExtended)
+    {
+        return pxs[0] == pys[0] && pxs[1] == pys[1];
+    }
+    else
+    {
+        ushort* pxe = cast(ushort *)&x;
+        ushort* pye = cast(ushort *)&y;
+        return pxe[4] == pye[4] && pxs[0] == pys[0];
+    }
+}
+
+/*********************************
+ * Return 1 if sign bit of e is set, 0 if not.
+ */
+int signbit(X)(X x) @nogc @trusted pure nothrow
+{
+    alias F = floatTraits!(X);
+    return ((cast(ubyte *)&x)[F.SIGNPOS_BYTE] & 0x80) != 0;
+}
+
+///
+@nogc @safe pure nothrow unittest
+{
+    debug (math) printf("math.signbit.unittest\n");
+    assert(!signbit(float.nan));
+    assert(signbit(-float.nan));
+    assert(!signbit(168.1234f));
+    assert(signbit(-168.1234f));
+    assert(!signbit(0.0f));
+    assert(signbit(-0.0f));
+    assert(signbit(-float.max));
+    assert(!signbit(float.max));
+
+    assert(!signbit(double.nan));
+    assert(signbit(-double.nan));
+    assert(!signbit(168.1234));
+    assert(signbit(-168.1234));
+    assert(!signbit(0.0));
+    assert(signbit(-0.0));
+    assert(signbit(-double.max));
+    assert(!signbit(double.max));
+
+    assert(!signbit(real.nan));
+    assert(signbit(-real.nan));
+    assert(!signbit(168.1234L));
+    assert(signbit(-168.1234L));
+    assert(!signbit(0.0L));
+    assert(signbit(-0.0L));
+    assert(signbit(-real.max));
+    assert(!signbit(real.max));
+}
+
+
+// Explicitly undocumented. It will be removed in July 2016. @@@DEPRECATED_2016-07@@@
+deprecated("signbit is not defined for integer types")
+int signbit(X)(X x) @nogc @trusted pure nothrow
+    if (isIntegral!X)
+{
+    return signbit(cast(float)x);
+}
+
+/*********************************
+ * Return a value composed of to with from's sign bit.
+ */
+R copysign(R, X)(R to, X from) @trusted pure nothrow @nogc
+    if (isFloatingPoint!(R) && isFloatingPoint!(X))
+{
+    ubyte* pto   = cast(ubyte *)&to;
+    const ubyte* pfrom = cast(ubyte *)&from;
+
+    alias T = floatTraits!(R);
+    alias F = floatTraits!(X);
+    pto[T.SIGNPOS_BYTE] &= 0x7F;
+    pto[T.SIGNPOS_BYTE] |= pfrom[F.SIGNPOS_BYTE] & 0x80;
+    return to;
+}
+
+// ditto
+R copysign(R, X)(X to, R from) @trusted pure nothrow @nogc
+    if (isIntegral!(X) && isFloatingPoint!(R))
+{
+    return copysign(cast(R)to, from);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    import std.meta;
+
+    foreach (X; AliasSeq!(float, double, real, int, long))
+    {
+        foreach (Y; AliasSeq!(float, double, real))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            X x = 21;
+            Y y = 23.8;
+            Y e = void;
+
+            e = copysign(x, y);
+            assert(e == 21.0);
+
+            e = copysign(-x, y);
+            assert(e == 21.0);
+
+            e = copysign(x, -y);
+            assert(e == -21.0);
+
+            e = copysign(-x, -y);
+            assert(e == -21.0);
+
+            static if (isFloatingPoint!X)
+            {
+                e = copysign(X.nan, y);
+                assert(isNaN(e) && !signbit(e));
+
+                e = copysign(X.nan, -y);
+                assert(isNaN(e) && signbit(e));
+            }
+        }();
+    }
+}
+
+// Explicitly undocumented. It will be removed in July 2016. @@@DEPRECATED_2016-07@@@
+deprecated("copysign : from can't be of integer type")
+R copysign(R, X)(X to, R from) @trusted pure nothrow @nogc
+    if (isIntegral!R)
+{
+    return copysign(to, cast(float)from);
+}
+
+/*********************************
+Returns $(D -1) if $(D x < 0), $(D x) if $(D x == 0), $(D 1) if
+$(D x > 0), and $(NAN) if x==$(NAN).
+ */
+F sgn(F)(F x) @safe pure nothrow @nogc
+{
+    // @@@TODO@@@: make this faster
+    return x > 0 ? 1 : x < 0 ? -1 : x;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert(sgn(168.1234) == 1);
+    assert(sgn(-168.1234) == -1);
+    assert(sgn(0.0) == 0);
+    assert(sgn(-0.0) == 0);
+}
+
+// Functions for NaN payloads
+/*
+ * A 'payload' can be stored in the significand of a $(NAN). One bit is required
+ * to distinguish between a quiet and a signalling $(NAN). This leaves 22 bits
+ * of payload for a float; 51 bits for a double; 62 bits for an 80-bit real;
+ * and 111 bits for a 128-bit quad.
+*/
+/**
+ * Create a quiet $(NAN), storing an integer inside the payload.
+ *
+ * For floats, the largest possible payload is 0x3F_FFFF.
+ * For doubles, it is 0x3_FFFF_FFFF_FFFF.
+ * For 80-bit or 128-bit reals, it is 0x3FFF_FFFF_FFFF_FFFF.
+ */
+real NaN(ulong payload) @trusted pure nothrow @nogc
+{
+    alias F = floatTraits!(real);
+    static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        // real80 (in x86 real format, the implied bit is actually
+        // not implied but a real bit which is stored in the real)
+        ulong v = 3; // implied bit = 1, quiet bit = 1
+    }
+    else
+    {
+        ulong v = 1; // no implied bit. quiet bit = 1
+    }
+
+    ulong a = payload;
+
+    // 22 Float bits
+    ulong w = a & 0x3F_FFFF;
+    a -= w;
+
+    v <<=22;
+    v |= w;
+    a >>=22;
+
+    // 29 Double bits
+    v <<=29;
+    w = a & 0xFFF_FFFF;
+    v |= w;
+    a -= w;
+    a >>=29;
+
+    static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        v |= 0x7FF0_0000_0000_0000;
+        real x;
+        * cast(ulong *)(&x) = v;
+        return x;
+    }
+    else
+    {
+        v <<=11;
+        a &= 0x7FF;
+        v |= a;
+        real x = real.nan;
+
+        // Extended real bits
+        static if (F.realFormat == RealFormat.ieeeQuadruple)
+        {
+            v <<= 1; // there's no implicit bit
+
+            version(LittleEndian)
+            {
+                *cast(ulong*)(6+cast(ubyte*)(&x)) = v;
+            }
+            else
+            {
+                *cast(ulong*)(2+cast(ubyte*)(&x)) = v;
+            }
+        }
+        else
+        {
+            *cast(ulong *)(&x) = v;
+        }
+        return x;
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble)
+    {
+        auto x = NaN(1);
+        auto xl = *cast(ulong*)&x;
+        assert(xl & 0x8_0000_0000_0000UL); //non-signaling bit, bit 52
+        assert((xl & 0x7FF0_0000_0000_0000UL) == 0x7FF0_0000_0000_0000UL); //all exp bits set
+    }
+}
+
+/**
+ * Extract an integral payload from a $(NAN).
+ *
+ * Returns:
+ * the integer payload as a ulong.
+ *
+ * For floats, the largest possible payload is 0x3F_FFFF.
+ * For doubles, it is 0x3_FFFF_FFFF_FFFF.
+ * For 80-bit or 128-bit reals, it is 0x3FFF_FFFF_FFFF_FFFF.
+ */
+ulong getNaNPayload(real x) @trusted pure nothrow @nogc
+{
+    //  assert(isNaN(x));
+    alias F = floatTraits!(real);
+    static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        ulong m = *cast(ulong *)(&x);
+        // Make it look like an 80-bit significand.
+        // Skip exponent, and quiet bit
+        m &= 0x0007_FFFF_FFFF_FFFF;
+        m <<= 10;
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        version(LittleEndian)
+        {
+            ulong m = *cast(ulong*)(6+cast(ubyte*)(&x));
+        }
+        else
+        {
+            ulong m = *cast(ulong*)(2+cast(ubyte*)(&x));
+        }
+
+        m >>= 1; // there's no implicit bit
+    }
+    else
+    {
+        ulong m = *cast(ulong *)(&x);
+    }
+
+    // ignore implicit bit and quiet bit
+
+    ulong f = m & 0x3FFF_FF00_0000_0000L;
+
+    ulong w = f >>> 40;
+            w |= (m & 0x00FF_FFFF_F800L) << (22 - 11);
+            w |= (m & 0x7FF) << 51;
+            return w;
+}
+
+debug(UnitTest)
+{
+    @safe pure nothrow @nogc unittest
+    {
+        real nan4 = NaN(0x789_ABCD_EF12_3456);
+        static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended
+                || floatTraits!(real).realFormat == RealFormat.ieeeQuadruple)
+        {
+            assert (getNaNPayload(nan4) == 0x789_ABCD_EF12_3456);
+        }
+        else
+        {
+            assert (getNaNPayload(nan4) == 0x1_ABCD_EF12_3456);
+        }
+        double nan5 = nan4;
+        assert (getNaNPayload(nan5) == 0x1_ABCD_EF12_3456);
+        float nan6 = nan4;
+        assert (getNaNPayload(nan6) == 0x12_3456);
+        nan4 = NaN(0xFABCD);
+        assert (getNaNPayload(nan4) == 0xFABCD);
+        nan6 = nan4;
+        assert (getNaNPayload(nan6) == 0xFABCD);
+        nan5 = NaN(0x100_0000_0000_3456);
+        assert(getNaNPayload(nan5) == 0x0000_0000_3456);
+    }
+}
+
+/**
+ * Calculate the next largest floating point value after x.
+ *
+ * Return the least number greater than x that is representable as a real;
+ * thus, it gives the next point on the IEEE number line.
+ *
+ *  $(TABLE_SV
+ *    $(SVH x,            nextUp(x)   )
+ *    $(SV  -$(INFIN),    -real.max   )
+ *    $(SV  $(PLUSMN)0.0, real.min_normal*real.epsilon )
+ *    $(SV  real.max,     $(INFIN) )
+ *    $(SV  $(INFIN),     $(INFIN) )
+ *    $(SV  $(NAN),       $(NAN)   )
+ * )
+ */
+real nextUp(real x) @trusted pure nothrow @nogc
+{
+    alias F = floatTraits!(real);
+    static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        return nextUp(cast(double)x);
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
+        if (e == F.EXPMASK)
+        {
+            // NaN or Infinity
+            if (x == -real.infinity) return -real.max;
+
+            return x; // +Inf and NaN are unchanged.
+        }
+
+        ulong*   ps = cast(ulong *)&e;
+        if (ps[MANTISSA_LSB] & 0x8000_0000_0000_0000)
+        {
+            // Negative number
+
+            if (ps[MANTISSA_LSB] == 0
+                && ps[MANTISSA_MSB] == 0x8000_0000_0000_0000)
+            {
+                // it was negative zero, change to smallest subnormal
+                ps[MANTISSA_LSB] = 0x0000_0000_0000_0001;
+                ps[MANTISSA_MSB] = 0;
+                return x;
+            }
+            --*ps;
+            if (ps[MANTISSA_LSB] == 0) --ps[MANTISSA_MSB];
+        }
+        else
+        {
+            // Positive number
+            ++ps[MANTISSA_LSB];
+            if (ps[MANTISSA_LSB] == 0) ++ps[MANTISSA_MSB];
+        }
+        return x;
+
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        // For 80-bit reals, the "implied bit" is a nuisance...
+        ushort *pe = cast(ushort *)&x;
+        ulong  *ps = cast(ulong  *)&x;
+
+        if ((pe[F.EXPPOS_SHORT] & F.EXPMASK) == F.EXPMASK)
+        {
+            // First, deal with NANs and infinity
+            if (x == -real.infinity) return -real.max;
+            return x; // +Inf and NaN are unchanged.
+        }
+        if (pe[F.EXPPOS_SHORT] & 0x8000)
+        {
+            // Negative number -- need to decrease the significand
+            --*ps;
+            // Need to mask with 0x7FFF... so subnormals are treated correctly.
+            if ((*ps & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_FFFF_FFFF_FFFF)
+            {
+                if (pe[F.EXPPOS_SHORT] == 0x8000)   // it was negative zero
+                {
+                    *ps = 1;
+                    pe[F.EXPPOS_SHORT] = 0; // smallest subnormal.
+                    return x;
+                }
+
+                --pe[F.EXPPOS_SHORT];
+
+                if (pe[F.EXPPOS_SHORT] == 0x8000)
+                    return x; // it's become a subnormal, implied bit stays low.
+
+                *ps = 0xFFFF_FFFF_FFFF_FFFF; // set the implied bit
+                return x;
+            }
+            return x;
+        }
+        else
+        {
+            // Positive number -- need to increase the significand.
+            // Works automatically for positive zero.
+            ++*ps;
+            if ((*ps & 0x7FFF_FFFF_FFFF_FFFF) == 0)
+            {
+                // change in exponent
+                ++pe[F.EXPPOS_SHORT];
+                *ps = 0x8000_0000_0000_0000; // set the high bit
+            }
+        }
+        return x;
+    }
+    else // static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        assert (0, "nextUp not implemented");
+    }
+}
+
+/** ditto */
+double nextUp(double x) @trusted pure nothrow @nogc
+{
+    ulong *ps = cast(ulong *)&x;
+
+    if ((*ps & 0x7FF0_0000_0000_0000) == 0x7FF0_0000_0000_0000)
+    {
+        // First, deal with NANs and infinity
+        if (x == -x.infinity) return -x.max;
+        return x; // +INF and NAN are unchanged.
+    }
+    if (*ps & 0x8000_0000_0000_0000)    // Negative number
+    {
+        if (*ps == 0x8000_0000_0000_0000) // it was negative zero
+        {
+            *ps = 0x0000_0000_0000_0001; // change to smallest subnormal
+            return x;
+        }
+        --*ps;
+    }
+    else
+    {   // Positive number
+        ++*ps;
+    }
+    return x;
+}
+
+/** ditto */
+float nextUp(float x) @trusted pure nothrow @nogc
+{
+    uint *ps = cast(uint *)&x;
+
+    if ((*ps & 0x7F80_0000) == 0x7F80_0000)
+    {
+        // First, deal with NANs and infinity
+        if (x == -x.infinity) return -x.max;
+
+        return x; // +INF and NAN are unchanged.
+    }
+    if (*ps & 0x8000_0000)   // Negative number
+    {
+        if (*ps == 0x8000_0000) // it was negative zero
+        {
+            *ps = 0x0000_0001; // change to smallest subnormal
+            return x;
+        }
+
+        --*ps;
+    }
+    else
+    {
+        // Positive number
+        ++*ps;
+    }
+    return x;
+}
+
+/**
+ * Calculate the next smallest floating point value before x.
+ *
+ * Return the greatest number less than x that is representable as a real;
+ * thus, it gives the previous point on the IEEE number line.
+ *
+ *  $(TABLE_SV
+ *    $(SVH x,            nextDown(x)   )
+ *    $(SV  $(INFIN),     real.max  )
+ *    $(SV  $(PLUSMN)0.0, -real.min_normal*real.epsilon )
+ *    $(SV  -real.max,    -$(INFIN) )
+ *    $(SV  -$(INFIN),    -$(INFIN) )
+ *    $(SV  $(NAN),       $(NAN)    )
+ * )
+ */
+real nextDown(real x) @safe pure nothrow @nogc
+{
+    return -nextUp(-x);
+}
+
+/** ditto */
+double nextDown(double x) @safe pure nothrow @nogc
+{
+    return -nextUp(-x);
+}
+
+/** ditto */
+float nextDown(float x) @safe pure nothrow @nogc
+{
+    return -nextUp(-x);
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    assert( nextDown(1.0 + real.epsilon) == 1.0);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended)
+    {
+
+        // Tests for 80-bit reals
+        assert(isIdentical(nextUp(NaN(0xABC)), NaN(0xABC)));
+        // negative numbers
+        assert( nextUp(-real.infinity) == -real.max );
+        assert( nextUp(-1.0L-real.epsilon) == -1.0 );
+        assert( nextUp(-2.0L) == -2.0 + real.epsilon);
+        // subnormals and zero
+        assert( nextUp(-real.min_normal) == -real.min_normal*(1-real.epsilon) );
+        assert( nextUp(-real.min_normal*(1-real.epsilon)) == -real.min_normal*(1-2*real.epsilon) );
+        assert( isIdentical(-0.0L, nextUp(-real.min_normal*real.epsilon)) );
+        assert( nextUp(-0.0L) == real.min_normal*real.epsilon );
+        assert( nextUp(0.0L) == real.min_normal*real.epsilon );
+        assert( nextUp(real.min_normal*(1-real.epsilon)) == real.min_normal );
+        assert( nextUp(real.min_normal) == real.min_normal*(1+real.epsilon) );
+        // positive numbers
+        assert( nextUp(1.0L) == 1.0 + real.epsilon );
+        assert( nextUp(2.0L-real.epsilon) == 2.0 );
+        assert( nextUp(real.max) == real.infinity );
+        assert( nextUp(real.infinity)==real.infinity );
+    }
+
+    double n = NaN(0xABC);
+    assert(isIdentical(nextUp(n), n));
+    // negative numbers
+    assert( nextUp(-double.infinity) == -double.max );
+    assert( nextUp(-1-double.epsilon) == -1.0 );
+    assert( nextUp(-2.0) == -2.0 + double.epsilon);
+    // subnormals and zero
+
+    assert( nextUp(-double.min_normal) == -double.min_normal*(1-double.epsilon) );
+    assert( nextUp(-double.min_normal*(1-double.epsilon)) == -double.min_normal*(1-2*double.epsilon) );
+    assert( isIdentical(-0.0, nextUp(-double.min_normal*double.epsilon)) );
+    assert( nextUp(0.0) == double.min_normal*double.epsilon );
+    assert( nextUp(-0.0) == double.min_normal*double.epsilon );
+    assert( nextUp(double.min_normal*(1-double.epsilon)) == double.min_normal );
+    assert( nextUp(double.min_normal) == double.min_normal*(1+double.epsilon) );
+    // positive numbers
+    assert( nextUp(1.0) == 1.0 + double.epsilon );
+    assert( nextUp(2.0-double.epsilon) == 2.0 );
+    assert( nextUp(double.max) == double.infinity );
+
+    float fn = NaN(0xABC);
+    assert(isIdentical(nextUp(fn), fn));
+    float f = -float.min_normal*(1-float.epsilon);
+    float f1 = -float.min_normal;
+    assert( nextUp(f1) ==  f);
+    f = 1.0f+float.epsilon;
+    f1 = 1.0f;
+    assert( nextUp(f1) == f );
+    f1 = -0.0f;
+    assert( nextUp(f1) == float.min_normal*float.epsilon);
+    assert( nextUp(float.infinity)==float.infinity );
+
+    assert(nextDown(1.0L+real.epsilon)==1.0);
+    assert(nextDown(1.0+double.epsilon)==1.0);
+    f = 1.0f+float.epsilon;
+    assert(nextDown(f)==1.0);
+    assert(nextafter(1.0+real.epsilon, -real.infinity)==1.0);
+}
+
+
+
+/******************************************
+ * Calculates the next representable value after x in the direction of y.
+ *
+ * If y > x, the result will be the next largest floating-point value;
+ * if y < x, the result will be the next smallest value.
+ * If x == y, the result is y.
+ *
+ * Remarks:
+ * This function is not generally very useful; it's almost always better to use
+ * the faster functions nextUp() or nextDown() instead.
+ *
+ * The FE_INEXACT and FE_OVERFLOW exceptions will be raised if x is finite and
+ * the function result is infinite. The FE_INEXACT and FE_UNDERFLOW
+ * exceptions will be raised if the function value is subnormal, and x is
+ * not equal to y.
+ */
+T nextafter(T)(const T x, const T y) @safe pure nothrow @nogc
+{
+    if (x == y) return y;
+    return ((y>x) ? nextUp(x) :  nextDown(x));
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    float a = 1;
+    assert(is(typeof(nextafter(a, a)) == float));
+    assert(nextafter(a, a.infinity) > a);
+
+    double b = 2;
+    assert(is(typeof(nextafter(b, b)) == double));
+    assert(nextafter(b, b.infinity) > b);
+
+    real c = 3;
+    assert(is(typeof(nextafter(c, c)) == real));
+    assert(nextafter(c, c.infinity) > c);
+}
+
+//real nexttoward(real x, real y) { return core.stdc.math.nexttowardl(x, y); }
+
+/*******************************************
+ * Returns the positive difference between x and y.
+ * Returns:
+ *      $(TABLE_SV
+ *      $(TR $(TH x, y)       $(TH fdim(x, y)))
+ *      $(TR $(TD x $(GT) y)  $(TD x - y))
+ *      $(TR $(TD x $(LT)= y) $(TD +0.0))
+ *      )
+ */
+real fdim(real x, real y) @safe pure nothrow @nogc { return (x > y) ? x - y : +0.0; }
+
+/****************************************
+ * Returns the larger of x and y.
+ */
+real fmax(real x, real y) @safe pure nothrow @nogc { return x > y ? x : y; }
+
+/****************************************
+ * Returns the smaller of x and y.
+ */
+real fmin(real x, real y) @safe pure nothrow @nogc { return x < y ? x : y; }
+
+/**************************************
+ * Returns (x * y) + z, rounding only once according to the
+ * current rounding mode.
+ *
+ * BUGS: Not currently implemented - rounds twice.
+ */
+real fma(real x, real y, real z) @safe pure nothrow @nogc { return (x * y) + z; }
+
+/*******************************************************************
+ * Compute the value of x $(SUPERSCRIPT n), where n is an integer
+ */
+Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow
+    if (isFloatingPoint!(F) && isIntegral!(G))
+{
+    real p = 1.0, v = void;
+    Unsigned!(Unqual!G) m = n;
+    if (n < 0)
+    {
+        switch (n)
+        {
+        case -1:
+            return 1 / x;
+        case -2:
+            return 1 / (x * x);
+        default:
+        }
+
+        m = -n;
+        v = p / x;
+    }
+    else
+    {
+        switch (n)
+        {
+        case 0:
+            return 1.0;
+        case 1:
+            return x;
+        case 2:
+            return x * x;
+        default:
+        }
+
+        v = x;
+    }
+
+    while (1)
+    {
+        if (m & 1)
+            p *= v;
+        m >>= 1;
+        if (!m)
+            break;
+        v *= v;
+    }
+    return p;
+}
+
+@safe pure nothrow @nogc unittest
+{
+    // Make sure it instantiates and works properly on immutable values and
+    // with various integer and float types.
+    immutable real x = 46;
+    immutable float xf = x;
+    immutable double xd = x;
+    immutable uint one = 1;
+    immutable ushort two = 2;
+    immutable ubyte three = 3;
+    immutable ulong eight = 8;
+
+    immutable int neg1 = -1;
+    immutable short neg2 = -2;
+    immutable byte neg3 = -3;
+    immutable long neg8 = -8;
+
+
+    assert(pow(x,0) == 1.0);
+    assert(pow(xd,one) == x);
+    assert(pow(xf,two) == x * x);
+    assert(pow(x,three) == x * x * x);
+    assert(pow(x,eight) == (x * x) * (x * x) * (x * x) * (x * x));
+
+    assert(pow(x, neg1) == 1 / x);
+
+    version(X86_64)
+    {
+        pragma(msg, "test disabled on x86_64, see bug 5628");
+    }
+    else version(ARM)
+    {
+        pragma(msg, "test disabled on ARM, see bug 5628");
+    }
+    else
+    {
+        assert(pow(xd, neg2) == 1 / (x * x));
+        assert(pow(xf, neg8) == 1 / ((x * x) * (x * x) * (x * x) * (x * x)));
+    }
+
+    assert(feqrel(pow(x, neg3),  1 / (x * x * x)) >= real.mant_dig - 1);
+}
+
+unittest
+{
+    assert(equalsDigit(pow(2.0L, 10.0L), 1024, 19));
+}
+
+/** Compute the value of an integer x, raised to the power of a positive
+ * integer n.
+ *
+ *  If both x and n are 0, the result is 1.
+ *  If n is negative, an integer divide error will occur at runtime,
+ * regardless of the value of x.
+ */
+typeof(Unqual!(F).init * Unqual!(G).init) pow(F, G)(F x, G n) @nogc @trusted pure nothrow
+    if (isIntegral!(F) && isIntegral!(G))
+{
+    if (n<0) return x/0; // Only support positive powers
+    typeof(return) p, v = void;
+    Unqual!G m = n;
+
+    switch (m)
+    {
+    case 0:
+        p = 1;
+        break;
+
+    case 1:
+        p = x;
+        break;
+
+    case 2:
+        p = x * x;
+        break;
+
+    default:
+        v = x;
+        p = 1;
+        while (1){
+            if (m & 1)
+                p *= v;
+            m >>= 1;
+            if (!m)
+                break;
+            v *= v;
+        }
+        break;
+    }
+    return p;
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    immutable int one = 1;
+    immutable byte two = 2;
+    immutable ubyte three = 3;
+    immutable short four = 4;
+    immutable long ten = 10;
+
+    assert(pow(two, three) == 8);
+    assert(pow(two, ten) == 1024);
+    assert(pow(one, ten) == 1);
+    assert(pow(ten, four) == 10_000);
+    assert(pow(four, 10) == 1_048_576);
+    assert(pow(three, four) == 81);
+
+}
+
+/**Computes integer to floating point powers.*/
+real pow(I, F)(I x, F y) @nogc @trusted pure nothrow
+    if(isIntegral!I && isFloatingPoint!F)
+{
+    return pow(cast(real) x, cast(Unqual!F) y);
+}
+
+/*********************************************
+ * Calculates x$(SUPERSCRIPT y).
+ *
+ * $(TABLE_SV
+ * $(TR $(TH x) $(TH y) $(TH pow(x, y))
+ *      $(TH div 0) $(TH invalid?))
+ * $(TR $(TD anything)      $(TD $(PLUSMN)0.0)                $(TD 1.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD |x| $(GT) 1)    $(TD +$(INFIN))                  $(TD +$(INFIN))
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD |x| $(LT) 1)    $(TD +$(INFIN))                  $(TD +0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD |x| $(GT) 1)    $(TD -$(INFIN))                  $(TD +0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD |x| $(LT) 1)    $(TD -$(INFIN))                  $(TD +$(INFIN))
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD +$(INFIN))      $(TD $(GT) 0.0)                  $(TD +$(INFIN))
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD +$(INFIN))      $(TD $(LT) 0.0)                  $(TD +0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD -$(INFIN))      $(TD odd integer $(GT) 0.0)      $(TD -$(INFIN))
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD -$(INFIN))      $(TD $(GT) 0.0, not odd integer) $(TD +$(INFIN))
+ *      $(TD no)        $(TD no))
+ * $(TR $(TD -$(INFIN))      $(TD odd integer $(LT) 0.0)      $(TD -0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD -$(INFIN))      $(TD $(LT) 0.0, not odd integer) $(TD +0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD $(PLUSMN)1.0)   $(TD $(PLUSMN)$(INFIN))          $(TD $(NAN))
+ *      $(TD no)        $(TD yes) )
+ * $(TR $(TD $(LT) 0.0)      $(TD finite, nonintegral)        $(TD $(NAN))
+ *      $(TD no)        $(TD yes))
+ * $(TR $(TD $(PLUSMN)0.0)   $(TD odd integer $(LT) 0.0)      $(TD $(PLUSMNINF))
+ *      $(TD yes)       $(TD no) )
+ * $(TR $(TD $(PLUSMN)0.0)   $(TD $(LT) 0.0, not odd integer) $(TD +$(INFIN))
+ *      $(TD yes)       $(TD no))
+ * $(TR $(TD $(PLUSMN)0.0)   $(TD odd integer $(GT) 0.0)      $(TD $(PLUSMN)0.0)
+ *      $(TD no)        $(TD no) )
+ * $(TR $(TD $(PLUSMN)0.0)   $(TD $(GT) 0.0, not odd integer) $(TD +0.0)
+ *      $(TD no)        $(TD no) )
+ * )
+ */
+Unqual!(Largest!(F, G)) pow(F, G)(F x, G y) @nogc @trusted pure nothrow
+    if (isFloatingPoint!(F) && isFloatingPoint!(G))
+{
+    alias Float = typeof(return);
+
+    static real impl(real x, real y) @nogc pure nothrow
+    {
+        // Special cases.
+        if (isNaN(y))
+            return y;
+        if (isNaN(x) && y != 0.0)
+            return x;
+
+        // Even if x is NaN.
+        if (y == 0.0)
+            return 1.0;
+        if (y == 1.0)
+            return x;
+
+        if (isInfinity(y))
+        {
+            if (fabs(x) > 1)
+            {
+                if (signbit(y))
+                    return +0.0;
+                else
+                    return F.infinity;
+            }
+            else if (fabs(x) == 1)
+            {
+                return y * 0; // generate NaN.
+            }
+            else // < 1
+            {
+                if (signbit(y))
+                    return F.infinity;
+                else
+                    return +0.0;
+            }
+        }
+        if (isInfinity(x))
+        {
+            if (signbit(x))
+            {
+                long i = cast(long)y;
+                if (y > 0.0)
+                {
+                    if (i == y && i & 1)
+                        return -F.infinity;
+                    else
+                        return F.infinity;
+                }
+                else if (y < 0.0)
+                {
+                    if (i == y && i & 1)
+                        return -0.0;
+                    else
+                        return +0.0;
+                }
+            }
+            else
+            {
+                if (y > 0.0)
+                    return F.infinity;
+                else if (y < 0.0)
+                    return +0.0;
+            }
+        }
+
+        if (x == 0.0)
+        {
+            if (signbit(x))
+            {
+                long i = cast(long)y;
+                if (y > 0.0)
+                {
+                    if (i == y && i & 1)
+                        return -0.0;
+                    else
+                        return +0.0;
+                }
+                else if (y < 0.0)
+                {
+                    if (i == y && i & 1)
+                        return -F.infinity;
+                    else
+                        return F.infinity;
+                }
+            }
+            else
+            {
+                if (y > 0.0)
+                    return +0.0;
+                else if (y < 0.0)
+                    return F.infinity;
+            }
+        }
+        if (x == 1.0)
+            return 1.0;
+
+        if (y >= F.max)
+        {
+            if ((x > 0.0 && x < 1.0) || (x > -1.0 && x < 0.0))
+                return 0.0;
+            if (x > 1.0 || x < -1.0)
+                return F.infinity;
+        }
+        if (y <= -F.max)
+        {
+            if ((x > 0.0 && x < 1.0) || (x > -1.0 && x < 0.0))
+                return F.infinity;
+            if (x > 1.0 || x < -1.0)
+                return 0.0;
+        }
+
+        if (x >= F.max)
+        {
+            if (y > 0.0)
+                return F.infinity;
+            else
+                return 0.0;
+        }
+        if (x <= -F.max)
+        {
+            long i = cast(long)y;
+            if (y > 0.0)
+            {
+                if (i == y && i & 1)
+                    return -F.infinity;
+                else
+                    return F.infinity;
+            }
+            else if (y < 0.0)
+            {
+                if (i == y && i & 1)
+                    return -0.0;
+                else
+                    return +0.0;
+            }
+        }
+
+        // Integer power of x.
+        long iy = cast(long)y;
+        if (iy == y && fabs(y) < 32768.0)
+            return pow(x, iy);
+
+        double sign = 1.0;
+        if (x < 0)
+        {
+            // Result is real only if y is an integer
+            // Check for a non-zero fractional part
+            enum maxOdd = pow(2.0L, real.mant_dig) - 1.0L;
+            static if(maxOdd > ulong.max)
+            {
+                // Generic method, for any FP type
+                if(floor(y) != y)
+                    return sqrt(x); // Complex result -- create a NaN
+
+                const hy = ldexp(y, -1);
+                if(floor(hy) != hy)
+                    sign = -1.0;
+            }
+            else
+            {
+                // Much faster, if ulong has enough precision
+                const absY = fabs(y);
+                if(absY <= maxOdd)
+                {
+                    const uy = cast(ulong)absY;
+                    if(uy != absY)
+                        return sqrt(x); // Complex result -- create a NaN
+
+                    if(uy & 1)
+                        sign = -1.0;
+                }
+            }
+            x = -x;
+        }
+        version(INLINE_YL2X)
+        {
+            // If x > 0, x ^^ y == 2 ^^ ( y * log2(x) )
+            // TODO: This is not accurate in practice. A fast and accurate
+            // (though complicated) method is described in:
+            // "An efficient rounding boundary test for pow(x, y)
+            // in double precision", C.Q. Lauter and V. Lefèvre, INRIA (2007).
+            return sign * exp2( yl2x(x, y) );
+        }
+        else
+        {
+            // If x > 0, x ^^ y == 2 ^^ ( y * log2(x) )
+            // TODO: This is not accurate in practice. A fast and accurate
+            // (though complicated) method is described in:
+            // "An efficient rounding boundary test for pow(x, y)
+            // in double precision", C.Q. Lauter and V. Lefèvre, INRIA (2007).
+            Float w = exp2(y * log2(x));
+            return sign * w;
+        }
+    }
+    return impl(x, y);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    // Test all the special values.  These unittests can be run on Windows
+    // by temporarily changing the version(linux) to version(all).
+    immutable float zero = 0;
+    immutable real one = 1;
+    immutable double two = 2;
+    immutable float three = 3;
+    immutable float fnan = float.nan;
+    immutable double dnan = double.nan;
+    immutable real rnan = real.nan;
+    immutable dinf = double.infinity;
+    immutable rninf = -real.infinity;
+
+    assert(pow(fnan, zero) == 1);
+    assert(pow(dnan, zero) == 1);
+    assert(pow(rnan, zero) == 1);
+
+    assert(pow(two, dinf) == double.infinity);
+    assert(isIdentical(pow(0.2f, dinf), +0.0));
+    assert(pow(0.99999999L, rninf) == real.infinity);
+    assert(isIdentical(pow(1.000000001, rninf), +0.0));
+    assert(pow(dinf, 0.001) == dinf);
+    assert(isIdentical(pow(dinf, -0.001), +0.0));
+    assert(pow(rninf, 3.0L) == rninf);
+    assert(pow(rninf, 2.0L) == real.infinity);
+    assert(isIdentical(pow(rninf, -3.0), -0.0));
+    assert(isIdentical(pow(rninf, -2.0), +0.0));
+
+    // @@@BUG@@@ somewhere
+    version(OSX) {} else assert(isNaN(pow(one, dinf)));
+    version(OSX) {} else assert(isNaN(pow(-one, dinf)));
+    assert(isNaN(pow(-0.2, PI)));
+    // boundary cases. Note that epsilon == 2^^-n for some n,
+    // so 1/epsilon == 2^^n is always even.
+    assert(pow(-1.0L, 1/real.epsilon - 1.0L) == -1.0L);
+    assert(pow(-1.0L, 1/real.epsilon) == 1.0L);
+    assert(isNaN(pow(-1.0L, 1/real.epsilon-0.5L)));
+    assert(isNaN(pow(-1.0L, -1/real.epsilon+0.5L)));
+
+    assert(pow(0.0, -3.0) == double.infinity);
+    assert(pow(-0.0, -3.0) == -double.infinity);
+    assert(pow(0.0, -PI) == double.infinity);
+    assert(pow(-0.0, -PI) == double.infinity);
+    assert(isIdentical(pow(0.0, 5.0), 0.0));
+    assert(isIdentical(pow(-0.0, 5.0), -0.0));
+    assert(isIdentical(pow(0.0, 6.0), 0.0));
+    assert(isIdentical(pow(-0.0, 6.0), 0.0));
+
+    // Issue #14786 fixed
+    immutable real maxOdd = pow(2.0L, real.mant_dig) - 1.0L;
+    assert(pow(-1.0L,  maxOdd) == -1.0L);
+    assert(pow(-1.0L, -maxOdd) == -1.0L);
+    assert(pow(-1.0L, maxOdd + 1.0L) == 1.0L);
+    assert(pow(-1.0L, -maxOdd + 1.0L) == 1.0L);
+    assert(pow(-1.0L, maxOdd - 1.0L) == 1.0L);
+    assert(pow(-1.0L, -maxOdd - 1.0L) == 1.0L);
+
+    // Now, actual numbers.
+    assert(approxEqual(pow(two, three), 8.0));
+    assert(approxEqual(pow(two, -2.5), 0.1767767));
+
+    // Test integer to float power.
+    immutable uint twoI = 2;
+    assert(approxEqual(pow(twoI, three), 8.0));
+}
+
+/**************************************
+ * To what precision is x equal to y?
+ *
+ * Returns: the number of mantissa bits which are equal in x and y.
+ * eg, 0x1.F8p+60 and 0x1.F1p+60 are equal to 5 bits of precision.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH x)      $(TH y)          $(TH feqrel(x, y)))
+ *      $(TR $(TD x)      $(TD x)          $(TD real.mant_dig))
+ *      $(TR $(TD x)      $(TD $(GT)= 2*x) $(TD 0))
+ *      $(TR $(TD x)      $(TD $(LT)= x/2) $(TD 0))
+ *      $(TR $(TD $(NAN)) $(TD any)        $(TD 0))
+ *      $(TR $(TD any)    $(TD $(NAN))     $(TD 0))
+ *      )
+ */
+int feqrel(X)(const X x, const X y) @trusted pure nothrow @nogc
+    if (isFloatingPoint!(X))
+{
+    /* Public Domain. Author: Don Clugston, 18 Aug 2005.
+     */
+    alias F = floatTraits!(X);
+    static if (F.realFormat == RealFormat.ibmExtended)
+    {
+        if (cast(double*)(&x)[MANTISSA_MSB] == cast(double*)(&y)[MANTISSA_MSB])
+        {
+            return double.mant_dig
+            + feqrel(cast(double*)(&x)[MANTISSA_LSB],
+                    cast(double*)(&y)[MANTISSA_LSB]);
+        }
+        else
+        {
+            return feqrel(cast(double*)(&x)[MANTISSA_MSB],
+                    cast(double*)(&y)[MANTISSA_MSB]);
+        }
+    }
+    else
+    {
+        static assert (F.realFormat == RealFormat.ieeeSingle
+                    || F.realFormat == RealFormat.ieeeDouble
+                    || F.realFormat == RealFormat.ieeeExtended
+                    || F.realFormat == RealFormat.ieeeQuadruple);
+
+        if (x == y)
+            return X.mant_dig; // ensure diff!=0, cope with INF.
+
+        Unqual!X diff = fabs(x - y);
+
+        ushort *pa = cast(ushort *)(&x);
+        ushort *pb = cast(ushort *)(&y);
+        ushort *pd = cast(ushort *)(&diff);
+
+
+        // The difference in abs(exponent) between x or y and abs(x-y)
+        // is equal to the number of significand bits of x which are
+        // equal to y. If negative, x and y have different exponents.
+        // If positive, x and y are equal to 'bitsdiff' bits.
+        // AND with 0x7FFF to form the absolute value.
+        // To avoid out-by-1 errors, we subtract 1 so it rounds down
+        // if the exponents were different. This means 'bitsdiff' is
+        // always 1 lower than we want, except that if bitsdiff==0,
+        // they could have 0 or 1 bits in common.
+
+        int bitsdiff = (((  (pa[F.EXPPOS_SHORT] & F.EXPMASK)
+                          + (pb[F.EXPPOS_SHORT] & F.EXPMASK)
+                          - (1 << F.EXPSHIFT)) >> 1)
+                        - (pd[F.EXPPOS_SHORT] & F.EXPMASK)) >> F.EXPSHIFT;
+        if ( (pd[F.EXPPOS_SHORT] & F.EXPMASK) == 0)
+        {   // Difference is subnormal
+            // For subnormals, we need to add the number of zeros that
+            // lie at the start of diff's significand.
+            // We do this by multiplying by 2^^real.mant_dig
+            diff *= F.RECIP_EPSILON;
+            return bitsdiff + X.mant_dig - ((pd[F.EXPPOS_SHORT] & F.EXPMASK) >> F.EXPSHIFT);
+        }
+
+        if (bitsdiff > 0)
+            return bitsdiff + 1; // add the 1 we subtracted before
+
+        // Avoid out-by-1 errors when factor is almost 2.
+        if (bitsdiff == 0
+            && ((pa[F.EXPPOS_SHORT] ^ pb[F.EXPPOS_SHORT]) & F.EXPMASK) == 0)
+        {
+            return 1;
+        } else return 0;
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    void testFeqrel(F)()
+    {
+       // Exact equality
+       assert(feqrel(F.max, F.max) == F.mant_dig);
+       assert(feqrel!(F)(0.0, 0.0) == F.mant_dig);
+       assert(feqrel(F.infinity, F.infinity) == F.mant_dig);
+
+       // a few bits away from exact equality
+       F w=1;
+       for (int i = 1; i < F.mant_dig - 1; ++i)
+       {
+          assert(feqrel!(F)(1.0 + w * F.epsilon, 1.0) == F.mant_dig-i);
+          assert(feqrel!(F)(1.0 - w * F.epsilon, 1.0) == F.mant_dig-i);
+          assert(feqrel!(F)(1.0, 1 + (w-1) * F.epsilon) == F.mant_dig - i + 1);
+          w*=2;
+       }
+
+       assert(feqrel!(F)(1.5+F.epsilon, 1.5) == F.mant_dig-1);
+       assert(feqrel!(F)(1.5-F.epsilon, 1.5) == F.mant_dig-1);
+       assert(feqrel!(F)(1.5-F.epsilon, 1.5+F.epsilon) == F.mant_dig-2);
+
+
+       // Numbers that are close
+       assert(feqrel!(F)(0x1.Bp+84, 0x1.B8p+84) == 5);
+       assert(feqrel!(F)(0x1.8p+10, 0x1.Cp+10) == 2);
+       assert(feqrel!(F)(1.5 * (1 - F.epsilon), 1.0L) == 2);
+       assert(feqrel!(F)(1.5, 1.0) == 1);
+       assert(feqrel!(F)(2 * (1 - F.epsilon), 1.0L) == 1);
+
+       // Factors of 2
+       assert(feqrel(F.max, F.infinity) == 0);
+       assert(feqrel!(F)(2 * (1 - F.epsilon), 1.0L) == 1);
+       assert(feqrel!(F)(1.0, 2.0) == 0);
+       assert(feqrel!(F)(4.0, 1.0) == 0);
+
+       // Extreme inequality
+       assert(feqrel(F.nan, F.nan) == 0);
+       assert(feqrel!(F)(0.0L, -F.nan) == 0);
+       assert(feqrel(F.nan, F.infinity) == 0);
+       assert(feqrel(F.infinity, -F.infinity) == 0);
+       assert(feqrel(F.max, -F.max) == 0);
+
+       assert(feqrel(F.min_normal / 8, F.min_normal / 17) == 3);
+
+       const F Const = 2;
+       immutable F Immutable = 2;
+       auto Compiles = feqrel(Const, Immutable);
+    }
+
+    assert(feqrel(7.1824L, 7.1824L) == real.mant_dig);
+
+    testFeqrel!(real)();
+    testFeqrel!(double)();
+    testFeqrel!(float)();
+}
+
+package: // Not public yet
+/* Return the value that lies halfway between x and y on the IEEE number line.
+ *
+ * Formally, the result is the arithmetic mean of the binary significands of x
+ * and y, multiplied by the geometric mean of the binary exponents of x and y.
+ * x and y must have the same sign, and must not be NaN.
+ * Note: this function is useful for ensuring O(log n) behaviour in algorithms
+ * involving a 'binary chop'.
+ *
+ * Special cases:
+ * If x and y are within a factor of 2, (ie, feqrel(x, y) > 0), the return value
+ * is the arithmetic mean (x + y) / 2.
+ * If x and y are even powers of 2, the return value is the geometric mean,
+ *   ieeeMean(x, y) = sqrt(x * y).
+ *
+ */
+T ieeeMean(T)(const T x, const T y)  @trusted pure nothrow @nogc
+in
+{
+    // both x and y must have the same sign, and must not be NaN.
+    assert(signbit(x) == signbit(y));
+    assert(x == x && y == y);
+}
+body
+{
+    // Runtime behaviour for contract violation:
+    // If signs are opposite, or one is a NaN, return 0.
+    if (!((x>=0 && y>=0) || (x<=0 && y<=0))) return 0.0;
+
+    // The implementation is simple: cast x and y to integers,
+    // average them (avoiding overflow), and cast the result back to a floating-point number.
+
+    alias F = floatTraits!(T);
+    T u;
+    static if (F.realFormat == RealFormat.ieeeExtended)
+    {
+        // There's slight additional complexity because they are actually
+        // 79-bit reals...
+        ushort *ue = cast(ushort *)&u;
+        ulong *ul = cast(ulong *)&u;
+        ushort *xe = cast(ushort *)&x;
+        ulong *xl = cast(ulong *)&x;
+        ushort *ye = cast(ushort *)&y;
+        ulong *yl = cast(ulong *)&y;
+
+        // Ignore the useless implicit bit. (Bonus: this prevents overflows)
+        ulong m = ((*xl) & 0x7FFF_FFFF_FFFF_FFFFL) + ((*yl) & 0x7FFF_FFFF_FFFF_FFFFL);
+
+        // @@@ BUG? @@@
+        // Cast shouldn't be here
+        ushort e = cast(ushort) ((xe[F.EXPPOS_SHORT] & F.EXPMASK)
+                                 + (ye[F.EXPPOS_SHORT] & F.EXPMASK));
+        if (m & 0x8000_0000_0000_0000L)
+        {
+            ++e;
+            m &= 0x7FFF_FFFF_FFFF_FFFFL;
+        }
+        // Now do a multi-byte right shift
+        uint c = e & 1; // carry
+        e >>= 1;
+        m >>>= 1;
+        if (c)
+            m |= 0x4000_0000_0000_0000L; // shift carry into significand
+        if (e)
+            *ul = m | 0x8000_0000_0000_0000L; // set implicit bit...
+        else
+            *ul = m; // ... unless exponent is 0 (subnormal or zero).
+
+        ue[4]= e | (xe[F.EXPPOS_SHORT]& 0x8000); // restore sign bit
+    }
+    else static if (F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        // This would be trivial if 'ucent' were implemented...
+        ulong *ul = cast(ulong *)&u;
+        ulong *xl = cast(ulong *)&x;
+        ulong *yl = cast(ulong *)&y;
+
+        // Multi-byte add, then multi-byte right shift.
+        ulong mh = ((xl[MANTISSA_MSB] & 0x7FFF_FFFF_FFFF_FFFFL)
+                    + (yl[MANTISSA_MSB] & 0x7FFF_FFFF_FFFF_FFFFL));
+
+        // Discard the lowest bit (to avoid overflow)
+        ulong ml = (xl[MANTISSA_LSB]>>>1) + (yl[MANTISSA_LSB]>>>1);
+
+        // add the lowest bit back in, if necessary.
+        if (xl[MANTISSA_LSB] & yl[MANTISSA_LSB] & 1)
+        {
+            ++ml;
+            if (ml == 0) ++mh;
+        }
+        mh >>>=1;
+        ul[MANTISSA_MSB] = mh | (xl[MANTISSA_MSB] & 0x8000_0000_0000_0000);
+        ul[MANTISSA_LSB] = ml;
+    }
+    else static if (F.realFormat == RealFormat.ieeeDouble)
+    {
+        ulong *ul = cast(ulong *)&u;
+        ulong *xl = cast(ulong *)&x;
+        ulong *yl = cast(ulong *)&y;
+        ulong m = (((*xl) & 0x7FFF_FFFF_FFFF_FFFFL)
+                   + ((*yl) & 0x7FFF_FFFF_FFFF_FFFFL)) >>> 1;
+                   m |= ((*xl) & 0x8000_0000_0000_0000L);
+                   *ul = m;
+    }
+    else static if (F.realFormat == RealFormat.ieeeSingle)
+    {
+        uint *ul = cast(uint *)&u;
+        uint *xl = cast(uint *)&x;
+        uint *yl = cast(uint *)&y;
+        uint m = (((*xl) & 0x7FFF_FFFF) + ((*yl) & 0x7FFF_FFFF)) >>> 1;
+        m |= ((*xl) & 0x8000_0000);
+        *ul = m;
+    }
+    else
+    {
+        assert(0, "Not implemented");
+    }
+    return u;
+}
+
+@safe pure nothrow @nogc unittest
+{
+    assert(ieeeMean(-0.0,-1e-20)<0);
+    assert(ieeeMean(0.0,1e-20)>0);
+
+    assert(ieeeMean(1.0L,4.0L)==2L);
+    assert(ieeeMean(2.0*1.013,8.0*1.013)==4*1.013);
+    assert(ieeeMean(-1.0L,-4.0L)==-2L);
+    assert(ieeeMean(-1.0,-4.0)==-2);
+    assert(ieeeMean(-1.0f,-4.0f)==-2f);
+    assert(ieeeMean(-1.0,-2.0)==-1.5);
+    assert(ieeeMean(-1*(1+8*real.epsilon),-2*(1+8*real.epsilon))
+                 ==-1.5*(1+5*real.epsilon));
+    assert(ieeeMean(0x1p60,0x1p-10)==0x1p25);
+
+    static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended)
+    {
+      assert(ieeeMean(1.0L,real.infinity)==0x1p8192L);
+      assert(ieeeMean(0.0L,real.infinity)==1.5);
+    }
+    assert(ieeeMean(0.5*real.min_normal*(1-4*real.epsilon),0.5*real.min_normal)
+           == 0.5*real.min_normal*(1-2*real.epsilon));
+}
+
+public:
+
+
+/***********************************
+ * Evaluate polynomial A(x) = $(SUB a, 0) + $(SUB a, 1)x + $(SUB a, 2)$(POWER x,2)
+ *                          + $(SUB a,3)$(POWER x,3); ...
+ *
+ * Uses Horner's rule A(x) = $(SUB a, 0) + x($(SUB a, 1) + x($(SUB a, 2)
+ *                         + x($(SUB a, 3) + ...)))
+ * Params:
+ *      x =     the value to evaluate.
+ *      A =     array of coefficients $(SUB a, 0), $(SUB a, 1), etc.
+ */
+Unqual!(CommonType!(T1, T2)) poly(T1, T2)(T1 x, in T2[] A) @trusted pure nothrow @nogc
+    if (isFloatingPoint!T1 && isFloatingPoint!T2)
+in
+{
+    assert(A.length > 0);
+}
+body
+{
+    static if(is(Unqual!T2 == real))
+    {
+        return polyImpl(x, A);
+    }
+    else
+    {
+        return polyImplBase(x, A);
+    }
+}
+
+///
+@safe nothrow @nogc unittest
+{
+    real x = 3.1;
+    static real[] pp = [56.1, 32.7, 6];
+
+    assert(poly(x, pp) == (56.1L + (32.7L + 6.0L * x) * x));
+}
+
+@safe nothrow @nogc unittest
+{
+    double x = 3.1;
+    static double[] pp = [56.1, 32.7, 6];
+    double y = x;
+    y *= 6.0;
+    y += 32.7;
+    y *= x;
+    y += 56.1;
+    assert(poly(x, pp) == y);
+}
+
+unittest {
+    static assert(poly(3.0, [1.0, 2.0, 3.0]) == 34);
+}
+
+private Unqual!(CommonType!(T1, T2)) polyImplBase(T1, T2)(T1 x, in T2[] A) @trusted pure nothrow @nogc
+    if (isFloatingPoint!T1 && isFloatingPoint!T2)
+{
+    ptrdiff_t i = A.length - 1;
+    typeof(return) r = A[i];
+    while (--i >= 0)
+    {
+        r *= x;
+        r += A[i];
+    }
+    return r;
+}
+
+private real polyImpl(real x, in real[] A) @trusted pure nothrow @nogc
+{
+    version (D_InlineAsm_X86)
+    {
+        if(__ctfe)
+        {
+            return polyImplBase(x, A);
+        }
+        version (Windows)
+        {
+        // BUG: This code assumes a frame pointer in EBP.
+            asm pure nothrow @nogc // assembler by W. Bright
+            {
+                // EDX = (A.length - 1) * real.sizeof
+                mov     ECX,A[EBP]              ; // ECX = A.length
+                dec     ECX                     ;
+                lea     EDX,[ECX][ECX*8]        ;
+                add     EDX,ECX                 ;
+                add     EDX,A+4[EBP]            ;
+                fld     real ptr [EDX]          ; // ST0 = coeff[ECX]
+                jecxz   return_ST               ;
+                fld     x[EBP]                  ; // ST0 = x
+                fxch    ST(1)                   ; // ST1 = x, ST0 = r
+                align   4                       ;
+        L2:     fmul    ST,ST(1)                ; // r *= x
+                fld     real ptr -10[EDX]       ;
+                sub     EDX,10                  ; // deg--
+                faddp   ST(1),ST                ;
+                dec     ECX                     ;
+                jne     L2                      ;
+                fxch    ST(1)                   ; // ST1 = r, ST0 = x
+                fstp    ST(0)                   ; // dump x
+                align   4                       ;
+        return_ST:                              ;
+                ;
+            }
+        }
+        else version (linux)
+        {
+            asm pure nothrow @nogc // assembler by W. Bright
+            {
+                // EDX = (A.length - 1) * real.sizeof
+                mov     ECX,A[EBP]              ; // ECX = A.length
+                dec     ECX                     ;
+                lea     EDX,[ECX*8]             ;
+                lea     EDX,[EDX][ECX*4]        ;
+                add     EDX,A+4[EBP]            ;
+                fld     real ptr [EDX]          ; // ST0 = coeff[ECX]
+                jecxz   return_ST               ;
+                fld     x[EBP]                  ; // ST0 = x
+                fxch    ST(1)                   ; // ST1 = x, ST0 = r
+                align   4                       ;
+        L2:     fmul    ST,ST(1)                ; // r *= x
+                fld     real ptr -12[EDX]       ;
+                sub     EDX,12                  ; // deg--
+                faddp   ST(1),ST                ;
+                dec     ECX                     ;
+                jne     L2                      ;
+                fxch    ST(1)                   ; // ST1 = r, ST0 = x
+                fstp    ST(0)                   ; // dump x
+                align   4                       ;
+        return_ST:                              ;
+                ;
+            }
+        }
+        else version (OSX)
+        {
+            asm pure nothrow @nogc // assembler by W. Bright
+            {
+                // EDX = (A.length - 1) * real.sizeof
+                mov     ECX,A[EBP]              ; // ECX = A.length
+                dec     ECX                     ;
+                lea     EDX,[ECX*8]             ;
+                add     EDX,EDX                 ;
+                add     EDX,A+4[EBP]            ;
+                fld     real ptr [EDX]          ; // ST0 = coeff[ECX]
+                jecxz   return_ST               ;
+                fld     x[EBP]                  ; // ST0 = x
+                fxch    ST(1)                   ; // ST1 = x, ST0 = r
+                align   4                       ;
+        L2:     fmul    ST,ST(1)                ; // r *= x
+                fld     real ptr -16[EDX]       ;
+                sub     EDX,16                  ; // deg--
+                faddp   ST(1),ST                ;
+                dec     ECX                     ;
+                jne     L2                      ;
+                fxch    ST(1)                   ; // ST1 = r, ST0 = x
+                fstp    ST(0)                   ; // dump x
+                align   4                       ;
+        return_ST:                              ;
+                ;
+            }
+        }
+        else version (FreeBSD)
+        {
+            asm pure nothrow @nogc // assembler by W. Bright
+            {
+                // EDX = (A.length - 1) * real.sizeof
+                mov     ECX,A[EBP]              ; // ECX = A.length
+                dec     ECX                     ;
+                lea     EDX,[ECX*8]             ;
+                lea     EDX,[EDX][ECX*4]        ;
+                add     EDX,A+4[EBP]            ;
+                fld     real ptr [EDX]          ; // ST0 = coeff[ECX]
+                jecxz   return_ST               ;
+                fld     x[EBP]                  ; // ST0 = x
+                fxch    ST(1)                   ; // ST1 = x, ST0 = r
+                align   4                       ;
+        L2:     fmul    ST,ST(1)                ; // r *= x
+                fld     real ptr -12[EDX]       ;
+                sub     EDX,12                  ; // deg--
+                faddp   ST(1),ST                ;
+                dec     ECX                     ;
+                jne     L2                      ;
+                fxch    ST(1)                   ; // ST1 = r, ST0 = x
+                fstp    ST(0)                   ; // dump x
+                align   4                       ;
+        return_ST:                              ;
+                ;
+            }
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+    else
+    {
+        return polyImplBase(x, A);
+    }
+}
+
+
+/**
+   Computes whether $(D lhs) is approximately equal to $(D rhs)
+   admitting a maximum relative difference $(D maxRelDiff) and a
+   maximum absolute difference $(D maxAbsDiff).
+
+   If the two inputs are ranges, $(D approxEqual) returns true if and
+   only if the ranges have the same number of elements and if $(D
+   approxEqual) evaluates to $(D true) for each pair of elements.
+ */
+bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 1e-5)
+{
+    import std.range;
+    static if (isInputRange!T)
+    {
+        static if (isInputRange!U)
+        {
+            // Two ranges
+            for (;; lhs.popFront(), rhs.popFront())
+            {
+                if (lhs.empty) return rhs.empty;
+                if (rhs.empty) return lhs.empty;
+                if (!approxEqual(lhs.front, rhs.front, maxRelDiff, maxAbsDiff))
+                    return false;
+            }
+        }
+        else static if (isIntegral!U)
+        {
+            // convert rhs to real
+            return approxEqual(lhs, real(rhs), maxRelDiff, maxAbsDiff);
+        }
+        else
+        {
+            // lhs is range, rhs is number
+            for (; !lhs.empty; lhs.popFront())
+            {
+                if (!approxEqual(lhs.front, rhs, maxRelDiff, maxAbsDiff))
+                    return false;
+            }
+            return true;
+        }
+    }
+    else
+    {
+        static if (isInputRange!U)
+        {
+            // lhs is number, rhs is array
+            return approxEqual(rhs, lhs, maxRelDiff, maxAbsDiff);
+        }
+        else static if (isIntegral!T || isIntegral!U)
+        {
+            // convert both lhs and rhs to real
+            return approxEqual(real(lhs), real(rhs), maxRelDiff, maxAbsDiff);
+        }
+        else
+        {
+            // two numbers
+            //static assert(is(T : real) && is(U : real));
+            if (rhs == 0)
+            {
+                return fabs(lhs) <= maxAbsDiff;
+            }
+            static if (is(typeof(lhs.infinity)) && is(typeof(rhs.infinity)))
+            {
+                if (lhs == lhs.infinity && rhs == rhs.infinity ||
+                    lhs == -lhs.infinity && rhs == -rhs.infinity) return true;
+            }
+            return fabs((lhs - rhs) / rhs) <= maxRelDiff
+                || maxAbsDiff != 0 && fabs(lhs - rhs) <= maxAbsDiff;
+        }
+    }
+}
+
+/**
+   Returns $(D approxEqual(lhs, rhs, 1e-2, 1e-5)).
+ */
+bool approxEqual(T, U)(T lhs, U rhs)
+{
+    return approxEqual(lhs, rhs, 1e-2, 1e-5);
+}
+
+///
+@safe pure nothrow unittest
+{
+    assert(approxEqual(1.0, 1.0099));
+    assert(!approxEqual(1.0, 1.011));
+    float[] arr1 = [ 1.0, 2.0, 3.0 ];
+    double[] arr2 = [ 1.001, 1.999, 3 ];
+    assert(approxEqual(arr1, arr2));
+
+    real num = real.infinity;
+    assert(num == real.infinity);  // Passes.
+    assert(approxEqual(num, real.infinity));  // Fails.
+    num = -real.infinity;
+    assert(num == -real.infinity);  // Passes.
+    assert(approxEqual(num, -real.infinity));  // Fails.
+
+    assert(!approxEqual(3, 0));
+    assert(approxEqual(3, 3));
+    assert(approxEqual(3.0, 3));
+    assert(approxEqual([3, 3, 3], 3.0));
+    assert(approxEqual([3.0, 3.0, 3.0], 3));
+    int a = 10;
+    assert(approxEqual(10, a));
+}
+
+// Explicitly undocumented. They will be removed in March 2017. @@@DEPRECATED_2017-03@@@
+// Included for backwards compatibility with Phobos1
+deprecated("Phobos1 math functions are deprecated, use isNaN") alias isnan = isNaN;
+deprecated("Phobos1 math functions are deprecated, use isFinite ") alias isfinite = isFinite;
+deprecated("Phobos1 math functions are deprecated, use isNormal ") alias isnormal = isNormal;
+deprecated("Phobos1 math functions are deprecated, use isSubnormal ") alias issubnormal = isSubnormal;
+deprecated("Phobos1 math functions are deprecated, use isInfinity ") alias isinf = isInfinity;
+
+/* **********************************
+ * Building block functions, they
+ * translate to a single x87 instruction.
+ */
+
+real yl2x(real x, real y)   @nogc @safe pure nothrow;       // y * log2(x)
+real yl2xp1(real x, real y) @nogc @safe pure nothrow;       // y * log2(x + 1)
+
+@safe pure nothrow @nogc unittest
+{
+    version (INLINE_YL2X)
+    {
+        assert(yl2x(1024, 1) == 10);
+        assert(yl2xp1(1023, 1) == 10);
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    real num = real.infinity;
+    assert(num == real.infinity);  // Passes.
+    assert(approxEqual(num, real.infinity));  // Fails.
+}
+
+
+@safe pure nothrow @nogc unittest
+{
+    float f = sqrt(2.0f);
+    assert(fabs(f * f - 2.0f) < .00001);
+
+    double d = sqrt(2.0);
+    assert(fabs(d * d - 2.0) < .00001);
+
+    real r = sqrt(2.0L);
+    assert(fabs(r * r - 2.0) < .00001);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    float f = fabs(-2.0f);
+    assert(f == 2);
+
+    double d = fabs(-2.0);
+    assert(d == 2);
+
+    real r = fabs(-2.0L);
+    assert(r == 2);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    float f = sin(-2.0f);
+    assert(fabs(f - -0.909297f) < .00001);
+
+    double d = sin(-2.0);
+    assert(fabs(d - -0.909297f) < .00001);
+
+    real r = sin(-2.0L);
+    assert(fabs(r - -0.909297f) < .00001);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    float f = cos(-2.0f);
+    assert(fabs(f - -0.416147f) < .00001);
+
+    double d = cos(-2.0);
+    assert(fabs(d - -0.416147f) < .00001);
+
+    real r = cos(-2.0L);
+    assert(fabs(r - -0.416147f) < .00001);
+}
+
+@safe pure nothrow @nogc unittest
+{
+    float f = tan(-2.0f);
+    assert(fabs(f - 2.18504f) < .00001);
+
+    double d = tan(-2.0);
+    assert(fabs(d - 2.18504f) < .00001);
+
+    real r = tan(-2.0L);
+    assert(fabs(r - 2.18504f) < .00001);
+}
+
+@safe pure nothrow unittest
+{
+    // issue 6381: floor/ceil should be usable in pure function.
+    auto x = floor(1.2);
+    auto y = ceil(1.2);
+}
+
+/***********************************
+ * Defines a total order on all floating-point numbers.
+ *
+ * The order is defined as follows:
+ * $(UL
+ *      $(LI All numbers in [-$(INFIN), +$(INFIN)] are ordered
+ *          the same way as by built-in comparison, with the exception of
+ *          -0.0, which is less than +0.0;)
+ *      $(LI If the sign bit is set (that is, it's 'negative'), $(NAN) is less
+ *          than any number; if the sign bit is not set (it is 'positive'),
+ *          $(NAN) is greater than any number;)
+ *      $(LI $(NAN)s of the same sign are ordered by the payload ('negative'
+ *          ones - in reverse order).)
+ * )
+ *
+ * Returns:
+ *      negative value if $(D x) precedes $(D y) in the order specified above;
+ *      0 if $(D x) and $(D y) are identical, and positive value otherwise.
+ *
+ * See_Also:
+ *      $(MYREF isIdentical)
+ * Standards: Conforms to IEEE 754-2008
+ */
+int cmp(T)(const(T) x, const(T) y) @nogc @trusted pure nothrow
+    if (isFloatingPoint!T)
+{
+    alias F = floatTraits!T;
+
+    static if (F.realFormat == RealFormat.ieeeSingle
+               || F.realFormat == RealFormat.ieeeDouble)
+    {
+        static if (T.sizeof == 4)
+            alias UInt = uint;
+        else
+            alias UInt = ulong;
+
+        union Repainter
+        {
+            T number;
+            UInt bits;
+        }
+
+        enum msb = ~(UInt.max >>> 1);
+
+        import std.typecons : Tuple;
+        Tuple!(Repainter, Repainter) vars = void;
+        vars[0].number = x;
+        vars[1].number = y;
+
+        foreach (ref var; vars)
+            if (var.bits & msb)
+                var.bits = ~var.bits;
+            else
+                var.bits |= msb;
+
+        if (vars[0].bits < vars[1].bits)
+            return -1;
+        else if (vars[0].bits > vars[1].bits)
+            return 1;
+        else
+            return 0;
+    }
+    else static if (F.realFormat == RealFormat.ieeeExtended53
+                    || F.realFormat == RealFormat.ieeeExtended
+                    || F.realFormat == RealFormat.ieeeQuadruple)
+    {
+        static if (F.realFormat == RealFormat.ieeeQuadruple)
+            alias RemT = ulong;
+        else
+            alias RemT = ushort;
+
+        struct Bits
+        {
+            ulong bulk;
+            RemT rem;
+        }
+
+        union Repainter
+        {
+            T number;
+            Bits bits;
+            ubyte[T.sizeof] bytes;
+        }
+
+        import std.typecons : Tuple;
+        Tuple!(Repainter, Repainter) vars = void;
+        vars[0].number = x;
+        vars[1].number = y;
+
+        foreach (ref var; vars)
+            if (var.bytes[F.SIGNPOS_BYTE] & 0x80)
+            {
+                var.bits.bulk = ~var.bits.bulk;
+                var.bits.rem = ~var.bits.rem;
+            }
+            else
+            {
+                var.bytes[F.SIGNPOS_BYTE] |= 0x80;
+            }
+
+        version(LittleEndian)
+        {
+            if (vars[0].bits.rem < vars[1].bits.rem)
+                return -1;
+            else if (vars[0].bits.rem > vars[1].bits.rem)
+                return 1;
+            else if (vars[0].bits.bulk < vars[1].bits.bulk)
+                return -1;
+            else if (vars[0].bits.bulk > vars[1].bits.bulk)
+                return 1;
+            else
+                return 0;
+        }
+        else
+        {
+            if (vars[0].bits.bulk < vars[1].bits.bulk)
+                return -1;
+            else if (vars[0].bits.bulk > vars[1].bits.bulk)
+                return 1;
+            else if (vars[0].bits.rem < vars[1].bits.rem)
+                return -1;
+            else if (vars[0].bits.rem > vars[1].bits.rem)
+                return 1;
+            else
+                return 0;
+        }
+    }
+    else
+    {
+        // IBM Extended doubledouble does not follow the general
+        // sign-exponent-significand layout, so has to be handled generically
+
+        int xSign = signbit(x),
+            ySign = signbit(y);
+
+        if (xSign == 1 && ySign == 1)
+            return cmp(-y, -x);
+        else if (xSign == 1)
+            return -1;
+        else if (ySign == 1)
+            return 1;
+        else if (x < y)
+            return -1;
+        else if (x == y)
+            return 0;
+        else if (x > y)
+            return 1;
+        else if (isNaN(x) && !isNaN(y))
+            return 1;
+        else if (isNaN(y) && !isNaN(x))
+            return -1;
+        else if (getNaNPayload(x) < getNaNPayload(y))
+            return -1;
+        else if (getNaNPayload(x) > getNaNPayload(y))
+            return 1;
+        else
+            return 0;
+    }
+}
+
+/// Most numbers are ordered naturally.
+unittest
+{
+    assert(cmp(-double.infinity, -double.max) < 0);
+    assert(cmp(-double.max, -100.0) < 0);
+    assert(cmp(-100.0, -0.5) < 0);
+    assert(cmp(-0.5, 0.0) < 0);
+    assert(cmp(0.0, 0.5) < 0);
+    assert(cmp(0.5, 100.0) < 0);
+    assert(cmp(100.0, double.max) < 0);
+    assert(cmp(double.max, double.infinity) < 0);
+
+    assert(cmp(1.0, 1.0) == 0);
+}
+
+/// Positive and negative zeroes are distinct.
+unittest
+{
+    assert(cmp(-0.0, +0.0) < 0);
+    assert(cmp(+0.0, -0.0) > 0);
+}
+
+/// Depending on the sign, $(NAN)s go to either end of the spectrum.
+unittest
+{
+    assert(cmp(-double.nan, -double.infinity) < 0);
+    assert(cmp(double.infinity, double.nan) < 0);
+    assert(cmp(-double.nan, double.nan) < 0);
+}
+
+/// $(NAN)s of the same sign are ordered by the payload.
+unittest
+{
+    assert(cmp(NaN(10), NaN(20)) < 0);
+    assert(cmp(-NaN(20), -NaN(10)) < 0);
+}
+
+unittest
+{
+    import std.meta;
+    foreach (T; AliasSeq!(float, double, real))
+    {
+        T[] values = [-cast(T)NaN(20), -cast(T)NaN(10), -T.nan, -T.infinity,
+                      -T.max, -T.max / 2, T(-16.0), T(-1.0).nextDown,
+                      T(-1.0), T(-1.0).nextUp,
+                      T(-0.5), -T.min_normal, (-T.min_normal).nextUp,
+                      -2 * T.min_normal * T.epsilon,
+                      -T.min_normal * T.epsilon,
+                      T(-0.0), T(0.0),
+                      T.min_normal * T.epsilon,
+                      2 * T.min_normal * T.epsilon,
+                      T.min_normal.nextDown, T.min_normal, T(0.5),
+                      T(1.0).nextDown, T(1.0),
+                      T(1.0).nextUp, T(16.0), T.max / 2, T.max,
+                      T.infinity, T.nan, cast(T)NaN(10), cast(T)NaN(20)];
+
+        foreach (i, x; values)
+        {
+            foreach (y; values[i + 1 .. $])
+            {
+                assert(cmp(x, y) < 0);
+                assert(cmp(y, x) > 0);
+            }
+            assert(cmp(x, x) == 0);
+        }
+    }
+}
+
+private enum PowType
+{
+    floor,
+    ceil
+}
+
+pragma(inline, true)
+private T powIntegralImpl(PowType type, T)(T val)
+{
+    import core.bitop : bsr;
+
+    Unqual!T x = val;
+
+    static if (isSigned!T)
+    if (x < 0)
+        x = abs(x);
+
+    if (x == 0)
+        return x;
+
+    static if (type == PowType.ceil)
+        x = cast(Unqual!T) (Unqual!T(1) << bsr(x) + 1);
+    else
+        x = cast(Unqual!T) (Unqual!T(1) << bsr(x));
+
+    static if (isSigned!T)
+    if (val < 0)
+        return -x;
+
+    return x;
+}
+
+pragma(inline, true)
+private T powFloatingPointImpl(PowType type, T)(T x)
+{
+    if (!x.isFinite)
+        return x;
+
+    if (!x)
+        return x;
+
+    int exp;
+    auto y = frexp(x, exp);
+
+    static if (type == PowType.ceil)
+        y = ldexp(cast(T) 0.5, exp + 1);
+    else
+        y = ldexp(cast(T) 0.5, exp);
+
+    if (!y.isFinite)
+        return cast(T) 0.0;
+
+    y = copysign(y, x);
+
+    return y;
+}
+
+/**
+ * Gives the next power of two after $(D val). $(T) can be any built-in
+ * numerical type.
+ *
+ * Params:
+ *     val = any number
+ *
+ * Returns:
+ *     the next power of two after $(D val)
+ */
+T nextPow2(T)(const T val) if (isIntegral!T)
+{
+    return powIntegralImpl!(PowType.ceil)(val);
+}
+
+/// ditto
+T nextPow2(T)(const T val) if (isFloatingPoint!T)
+{
+    return powFloatingPointImpl!(PowType.ceil)(val);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    assert(nextPow2(2) == 4);
+    assert(nextPow2(10) == 16);
+    assert(nextPow2(4000) == 4096);
+
+    assert(nextPow2(-2) == -4);
+    assert(nextPow2(-10) == -16);
+
+    assert(nextPow2(uint.max) == 1);
+    assert(nextPow2(uint.min) == 0);
+    assert(nextPow2(size_t.max) == 1);
+    assert(nextPow2(size_t.min) == 0);
+
+    assert(nextPow2(int.max) == int.min);
+    assert(nextPow2(int.min) == -1);
+    assert(nextPow2(long.max) == long.min);
+    assert(nextPow2(long.min) == -1);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    assert(nextPow2(2.1) == 4.0);
+    assert(nextPow2(-2.0) == -4.0);
+    assert(nextPow2(0.25) == 0.5);
+    assert(nextPow2(-4.0) == -8.0);
+
+    assert(nextPow2(double.max) == 0.0);
+    assert(nextPow2(double.infinity) == double.infinity);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    assert(nextPow2(ubyte(2)) == 4);
+    assert(nextPow2(ubyte(10)) == 16);
+
+    assert(nextPow2(byte(2)) == 4);
+    assert(nextPow2(byte(10)) == 16);
+
+    assert(nextPow2(short(2)) == 4);
+    assert(nextPow2(short(10)) == 16);
+    assert(nextPow2(short(4000)) == 4096);
+
+    assert(nextPow2(ushort(2)) == 4);
+    assert(nextPow2(ushort(10)) == 16);
+    assert(nextPow2(ushort(4000)) == 4096);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    foreach (ulong i; 1 .. 62)
+    {
+        assert(nextPow2(1UL<<i) == 2UL<<i);
+        assert(nextPow2((1UL<<i) - 1) == 1UL<<i);
+        assert(nextPow2((1UL<<i) + 1) == 2UL<<i);
+        assert(nextPow2((1UL<<i) + (1UL<<(i-1))) == 2UL<<i);
+    }
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.meta: AliasSeq;
+
+    foreach(T; AliasSeq!(float, double, real))
+    {
+        enum T subNormal = T.min_normal / 2;
+
+        static if (subNormal) assert(nextPow2(subNormal) == T.min_normal);
+
+        assert(nextPow2(T(0.0)) == 0.0);
+
+        assert(nextPow2(T(2.0)) == 4.0);
+        assert(nextPow2(T(2.1)) == 4.0);
+        assert(nextPow2(T(3.1)) == 4.0);
+        assert(nextPow2(T(4.0)) == 8.0);
+        assert(nextPow2(T(0.25)) == 0.5);
+
+        assert(nextPow2(T(-2.0)) == -4.0);
+        assert(nextPow2(T(-2.1)) == -4.0);
+        assert(nextPow2(T(-3.1)) == -4.0);
+        assert(nextPow2(T(-4.0)) == -8.0);
+        assert(nextPow2(T(-0.25)) == -0.5);
+
+        assert(nextPow2(T.max) == 0);
+        assert(nextPow2(-T.max) == 0);
+
+        assert(nextPow2(T.infinity) == T.infinity);
+        assert(nextPow2(T.init).isNaN);
+    }
+}
+
+/**
+ * Gives the last power of two before $(D val). $(T) can be any built-in
+ * numerical type.
+ *
+ * Params:
+ *     val = any number
+ *
+ * Returns:
+ *     the last power of two before $(D val)
+ */
+T truncPow2(T)(const T val) if (isIntegral!T)
+{
+    return powIntegralImpl!(PowType.floor)(val);
+}
+
+/// ditto
+T truncPow2(T)(const T val) if (isFloatingPoint!T)
+{
+    return powFloatingPointImpl!(PowType.floor)(val);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    assert(truncPow2(3) == 2);
+    assert(truncPow2(4) == 4);
+    assert(truncPow2(10) == 8);
+    assert(truncPow2(4000) == 2048);
+
+    assert(truncPow2(-5) == -4);
+    assert(truncPow2(-20) == -16);
+
+    assert(truncPow2(uint.max) == int.max + 1);
+    assert(truncPow2(uint.min) == 0);
+    assert(truncPow2(ulong.max) == long.max + 1);
+    assert(truncPow2(ulong.min) == 0);
+
+    assert(truncPow2(int.max) == (int.max / 2) + 1);
+    assert(truncPow2(int.min) == int.min);
+    assert(truncPow2(long.max) == (long.max / 2) + 1);
+    assert(truncPow2(long.min) == long.min);
+}
+
+///
+@safe @nogc pure nothrow unittest
+{
+    assert(truncPow2(2.1) == 2.0);
+    assert(truncPow2(7.0) == 4.0);
+    assert(truncPow2(-1.9) == -1.0);
+    assert(truncPow2(0.24) == 0.125);
+    assert(truncPow2(-7.0) == -4.0);
+
+    assert(truncPow2(double.infinity) == double.infinity);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    assert(truncPow2(ubyte(3)) == 2);
+    assert(truncPow2(ubyte(4)) == 4);
+    assert(truncPow2(ubyte(10)) == 8);
+
+    assert(truncPow2(byte(3)) == 2);
+    assert(truncPow2(byte(4)) == 4);
+    assert(truncPow2(byte(10)) == 8);
+
+    assert(truncPow2(ushort(3)) == 2);
+    assert(truncPow2(ushort(4)) == 4);
+    assert(truncPow2(ushort(10)) == 8);
+    assert(truncPow2(ushort(4000)) == 2048);
+
+    assert(truncPow2(short(3)) == 2);
+    assert(truncPow2(short(4)) == 4);
+    assert(truncPow2(short(10)) == 8);
+    assert(truncPow2(short(4000)) == 2048);
+}
+
+@safe @nogc pure nothrow unittest
+{
+    foreach (ulong i; 1 .. 62)
+    {
+        assert(truncPow2(2UL<<i) == 2UL<<i);
+        assert(truncPow2((2UL<<i) + 1) == 2UL<<i);
+        assert(truncPow2((2UL<<i) - 1) == 1UL<<i);
+        assert(truncPow2((2UL<<i) - (2UL<<(i-1))) == 1UL<<i);
+    }
+}
+
+@safe @nogc pure nothrow unittest
+{
+    import std.meta: AliasSeq;
+
+    foreach(T; AliasSeq!(float, double, real))
+    {
+        assert(truncPow2(T(0.0)) == 0.0);
+
+        assert(truncPow2(T(4.0)) == 4.0);
+        assert(truncPow2(T(2.1)) == 2.0);
+        assert(truncPow2(T(3.5)) == 2.0);
+        assert(truncPow2(T(7.0)) == 4.0);
+        assert(truncPow2(T(0.24)) == 0.125);
+
+        assert(truncPow2(T(-2.0)) == -2.0);
+        assert(truncPow2(T(-2.1)) == -2.0);
+        assert(truncPow2(T(-3.1)) == -2.0);
+        assert(truncPow2(T(-7.0)) == -4.0);
+        assert(truncPow2(T(-0.24)) == -0.125);
+
+        assert(truncPow2(T.infinity) == T.infinity);
+        assert(truncPow2(T.init).isNaN);
+    }
+}
diff --git a/libphobos/src/std/mathspecial.d b/libphobos/src/std/mathspecial.d
new file mode 100644
index 00000000000..6cd5bbc5399
--- /dev/null
+++ b/libphobos/src/std/mathspecial.d
@@ -0,0 +1,357 @@
+// Written in the D programming language.
+
+/**
+ * Mathematical Special Functions
+ *
+ * The technical term 'Special Functions' includes several families of
+ * transcendental functions, which have important applications in particular
+ * branches of mathematics and physics.
+ *
+ * The gamma and related functions, and the error function are crucial for
+ * mathematical statistics.
+ * The Bessel and related functions arise in problems involving wave propagation
+ * (especially in optics).
+ * Other major categories of special functions include the elliptic integrals
+ * (related to the arc length of an ellipse), and the hypergeometric functions.
+ *
+ * Status:
+ *  Many more functions will be added to this module.
+ *  The naming convention for the distribution functions (gammaIncomplete, etc)
+ *  is not yet finalized and will probably change.
+ *
+ * Macros:
+ *      WIKI = Phobos/StdMathSpecial
+ *
+ *      TABLE_SV = <table border="1" cellpadding="4" cellspacing="0">
+ *              <caption>Special Values</caption>
+ *              $0</table>
+ *      SVH = $(TR $(TH $1) $(TH $2))
+ *      SV  = $(TR $(TD $1) $(TD $2))
+ *
+ *      NAN = $(RED NAN)
+ *      SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
+ *      GAMMA = &#915;
+ *      THETA = &theta;
+ *      INTEGRAL = &#8747;
+ *      INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
+ *      POWER = $1<sup>$2</sup>
+ *      SUB = $1<sub>$2</sub>
+ *      BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
+ *      CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
+ *      PLUSMN = &plusmn;
+ *      INFIN = &infin;
+ *      PLUSMNINF = &plusmn;&infin;
+ *      PI = &pi;
+ *      LT = &lt;
+ *      GT = &gt;
+ *      SQRT = &radic;
+ *      HALF = &frac12;
+ *
+ *
+ * Copyright: Based on the CEPHES math library, which is
+ *            Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com).
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Stephen L. Moshier (original C code). Conversion to D by Don Clugston
+ * Source:    $(PHOBOSSRC std/_mathspecial.d)
+ */
+module std.mathspecial;
+public import std.math;
+private import std.internal.math.gammafunction;
+private import std.internal.math.errorfunction;
+
+/* ***********************************************
+ *            GAMMA AND RELATED FUNCTIONS        *
+ * ***********************************************/
+
+pure:
+nothrow:
+@safe:
+@nogc:
+
+/** The Gamma function, $(GAMMA)(x)
+ *
+ *  $(GAMMA)(x) is a generalisation of the factorial function
+ *  to real and complex numbers.
+ *  Like x!, $(GAMMA)(x+1) = x * $(GAMMA)(x).
+ *
+ *  Mathematically, if z.re > 0 then
+ *   $(GAMMA)(z) = $(INTEGRATE 0, $(INFIN)) $(POWER t, z-1)$(POWER e, -t) dt
+ *
+ *  $(TABLE_SV
+ *    $(SVH  x,           $(GAMMA)(x) )
+ *    $(SV  $(NAN),       $(NAN)      )
+ *    $(SV  $(PLUSMN)0.0, $(PLUSMNINF))
+ *    $(SV integer > 0,   (x-1)!      )
+ *    $(SV integer < 0,   $(NAN)      )
+ *    $(SV +$(INFIN),      +$(INFIN)   )
+ *    $(SV -$(INFIN),      $(NAN)      )
+ *  )
+ */
+real gamma(real x)
+{
+    return std.internal.math.gammafunction.gamma(x);
+}
+
+/** Natural logarithm of the gamma function, $(GAMMA)(x)
+ *
+ * Returns the base e (2.718...) logarithm of the absolute
+ * value of the gamma function of the argument.
+ *
+ * For reals, logGamma is equivalent to log(fabs(gamma(x))).
+ *
+ *  $(TABLE_SV
+ *    $(SVH  x,             logGamma(x)   )
+ *    $(SV  $(NAN),         $(NAN)      )
+ *    $(SV integer <= 0,    +$(INFIN)    )
+ *    $(SV $(PLUSMNINF),    +$(INFIN)    )
+ *  )
+ */
+real logGamma(real x)
+{
+    return std.internal.math.gammafunction.logGamma(x);
+}
+
+/** The sign of $(GAMMA)(x).
+ *
+ * Returns -1 if $(GAMMA)(x) < 0,  +1 if $(GAMMA)(x) > 0,
+ * $(NAN) if sign is indeterminate.
+ *
+ * Note that this function can be used in conjunction with logGamma(x) to
+ * evaluate gamma for very large values of x.
+ */
+real sgnGamma(real x)
+{
+    /* Author: Don Clugston. */
+    if (isNaN(x)) return x;
+    if (x > 0) return 1.0;
+    if (x < -1/real.epsilon)
+    {
+        // Large negatives lose all precision
+        return real.nan;
+    }
+    long n = rndtol(x);
+    if (x == n) {
+        return x == 0 ?  copysign(1, x) : real.nan;
+    }
+    return n & 1 ? 1.0 : -1.0;
+}
+
+unittest {
+    assert(sgnGamma(5.0) == 1.0);
+    assert(isNaN(sgnGamma(-3.0)));
+    assert(sgnGamma(-0.1) == -1.0);
+    assert(sgnGamma(-55.1) == 1.0);
+    assert(isNaN(sgnGamma(-real.infinity)));
+    assert(isIdentical(sgnGamma(NaN(0xABC)), NaN(0xABC)));
+}
+
+/** Beta function
+ *
+ * The beta function is defined as
+ *
+ * beta(x, y) = ($(GAMMA)(x) * $(GAMMA)(y)) / $(GAMMA)(x + y)
+ */
+real beta(real x, real y)
+{
+    if ((x+y)> MAXGAMMA) {
+        return exp(logGamma(x) + logGamma(y) - logGamma(x+y));
+    } else return gamma(x) * gamma(y) / gamma(x+y);
+}
+
+unittest {
+    assert(isIdentical(beta(NaN(0xABC), 4), NaN(0xABC)));
+    assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC)));
+}
+
+/** Digamma function
+ *
+ *  The digamma function is the logarithmic derivative of the gamma function.
+ *
+ *  digamma(x) = d/dx logGamma(x)
+ *
+ *  See_Also: $(LREF logmdigamma), $(LREF logmdigammaInverse).
+ */
+real digamma(real x)
+{
+    return std.internal.math.gammafunction.digamma(x);
+}
+
+/** Log Minus Digamma function
+ *
+ *  logmdigamma(x) = log(x) - digamma(x)
+ *
+ *  See_Also: $(LREF digamma), $(LREF logmdigammaInverse).
+ */
+real logmdigamma(real x)
+{
+    return std.internal.math.gammafunction.logmdigamma(x);
+}
+
+/** Inverse of the Log Minus Digamma function
+ *
+ *  Given y, the function finds x such log(x) - digamma(x) = y.
+ *
+ *  See_Also: $(LREF logmdigamma), $(LREF digamma).
+ */
+real logmdigammaInverse(real x)
+{
+    return std.internal.math.gammafunction.logmdigammaInverse(x);
+}
+
+/** Incomplete beta integral
+ *
+ * Returns incomplete beta integral of the arguments, evaluated
+ * from zero to x. The regularized incomplete beta function is defined as
+ *
+ * betaIncomplete(a, b, x) = $(GAMMA)(a + b) / ( $(GAMMA)(a) $(GAMMA)(b) ) *
+ * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t), b-1) dt
+ *
+ * and is the same as the the cumulative distribution function.
+ *
+ * The domain of definition is 0 <= x <= 1.  In this
+ * implementation a and b are restricted to positive values.
+ * The integral from x to 1 may be obtained by the symmetry
+ * relation
+ *
+ *    betaIncompleteCompl(a, b, x )  =  betaIncomplete( b, a, 1-x )
+ *
+ * The integral is evaluated by a continued fraction expansion
+ * or, when b * x is small, by a power series.
+ */
+real betaIncomplete(real a, real b, real x )
+{
+    return std.internal.math.gammafunction.betaIncomplete(a, b, x);
+}
+
+/** Inverse of incomplete beta integral
+ *
+ * Given y, the function finds x such that
+ *
+ *  betaIncomplete(a, b, x) == y
+ *
+ *  Newton iterations or interval halving is used.
+ */
+real betaIncompleteInverse(real a, real b, real y )
+{
+    return std.internal.math.gammafunction.betaIncompleteInv(a, b, y);
+}
+
+/** Incomplete gamma integral and its complement
+ *
+ * These functions are defined by
+ *
+ *   gammaIncomplete = ( $(INTEGRATE 0, x) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
+ *
+ *  gammaIncompleteCompl(a,x)   =   1 - gammaIncomplete(a,x)
+ * = ($(INTEGRATE x, $(INFIN)) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
+ *
+ * In this implementation both arguments must be positive.
+ * The integral is evaluated by either a power series or
+ * continued fraction expansion, depending on the relative
+ * values of a and x.
+ */
+real gammaIncomplete(real a, real x )
+in {
+   assert(x >= 0);
+   assert(a > 0);
+}
+body {
+    return std.internal.math.gammafunction.gammaIncomplete(a, x);
+}
+
+/** ditto */
+real gammaIncompleteCompl(real a, real x )
+in {
+   assert(x >= 0);
+   assert(a > 0);
+}
+body {
+    return std.internal.math.gammafunction.gammaIncompleteCompl(a, x);
+}
+
+/** Inverse of complemented incomplete gamma integral
+ *
+ * Given a and p, the function finds x such that
+ *
+ *  gammaIncompleteCompl( a, x ) = p.
+ */
+real gammaIncompleteComplInverse(real a, real p)
+in {
+  assert(p >= 0 && p <= 1);
+  assert(a > 0);
+}
+body {
+    return std.internal.math.gammafunction.gammaIncompleteComplInv(a, p);
+}
+
+
+/* ***********************************************
+ *     ERROR FUNCTIONS & NORMAL DISTRIBUTION     *
+ * ***********************************************/
+
+ /** Error function
+ *
+ * The integral is
+ *
+ *  erf(x) =  2/ $(SQRT)($(PI))
+ *     $(INTEGRATE 0, x) exp( - $(POWER t, 2)) dt
+ *
+ * The magnitude of x is limited to about 106.56 for IEEE 80-bit
+ * arithmetic; 1 or -1 is returned outside this range.
+ */
+real erf(real x)
+{
+    return std.internal.math.errorfunction.erf(x);
+}
+
+/** Complementary error function
+ *
+ * erfc(x) = 1 - erf(x)
+ *         = 2/ $(SQRT)($(PI))
+ *     $(INTEGRATE x, $(INFIN)) exp( - $(POWER t, 2)) dt
+ *
+ * This function has high relative accuracy for
+ * values of x far from zero. (For values near zero, use erf(x)).
+ */
+real erfc(real x)
+{
+    return std.internal.math.errorfunction.erfc(x);
+}
+
+
+/** Normal distribution function.
+ *
+ * The normal (or Gaussian, or bell-shaped) distribution is
+ * defined as:
+ *
+ * normalDist(x) = 1/$(SQRT)(2$(PI)) $(INTEGRATE -$(INFIN), x) exp( - $(POWER t, 2)/2) dt
+ *   = 0.5 + 0.5 * erf(x/sqrt(2))
+ *   = 0.5 * erfc(- x/sqrt(2))
+ *
+ * To maintain accuracy at values of x near 1.0, use
+ *      normalDistribution(x) = 1.0 - normalDistribution(-x).
+ *
+ * References:
+ * $(LINK http://www.netlib.org/cephes/ldoubdoc.html),
+ * G. Marsaglia, "Evaluating the Normal Distribution",
+ * Journal of Statistical Software <b>11</b>, (July 2004).
+ */
+real normalDistribution(real x)
+{
+    return std.internal.math.errorfunction.normalDistributionImpl(x);
+}
+
+/** Inverse of Normal distribution function
+ *
+ * Returns the argument, x, for which the area under the
+ * Normal probability density function (integrated from
+ * minus infinity to x) is equal to p.
+ */
+real normalDistributionInverse(real p)
+in {
+  assert(p>=0.0L && p<=1.0L, "Domain error");
+}
+body
+{
+    return std.internal.math.errorfunction.normalDistributionInvImpl(p);
+}
diff --git a/libphobos/src/std/meta.d b/libphobos/src/std/meta.d
new file mode 100644
index 00000000000..cc02c4519e0
--- /dev/null
+++ b/libphobos/src/std/meta.d
@@ -0,0 +1,1437 @@
+// Written in the D programming language.
+
+/**
+ * Templates to manipulate template argument lists (also known as type lists).
+ *
+ * Some operations on alias sequences are built in to the language,
+ * such as TL[$(I n)] which gets the $(I n)th type from the
+ * alias sequence. TL[$(I lwr) .. $(I upr)] returns a new type
+ * list that is a slice of the old one.
+ *
+ * Several templates in this module use or operate on eponymous templates that
+ * take a single argument and evaluate to a boolean constant. Such templates
+ * are referred to as $(I template predicates).
+ *
+ * References:
+ *  Based on ideas in Table 3.1 from
+ *  $(LINK2 http://amazon.com/exec/obidos/ASIN/0201704315/ref=ase_classicempire/102-2957199-2585768,
+ *      Modern C++ Design),
+ *   Andrei Alexandrescu (Addison-Wesley Professional, 2001)
+ * Macros:
+ *  WIKI = Phobos/StdMeta
+ *
+ * Copyright: Copyright Digital Mars 2005 - 2015.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:
+ *     $(WEB digitalmars.com, Walter Bright),
+ *     $(WEB klickverbot.at, David Nadlinger)
+ * Source:    $(PHOBOSSRC std/_meta.d)
+ */
+
+module std.meta;
+
+/**
+ * Creates a sequence of zero or more aliases. This is most commonly
+ * used as template parameters or arguments.
+ */
+template AliasSeq(TList...)
+{
+    alias AliasSeq = TList;
+}
+
+///
+unittest
+{
+    import std.meta;
+    alias TL = AliasSeq!(int, double);
+
+    int foo(TL td)  // same as int foo(int, double);
+    {
+        return td[0] + cast(int)td[1];
+    }
+}
+
+///
+unittest
+{
+    alias TL = AliasSeq!(int, double);
+
+    alias Types = AliasSeq!(TL, char);
+    static assert(is(Types == AliasSeq!(int, double, char)));
+}
+
+/**
+ * Allows `alias`ing of any single symbol, type or compile-time expression.
+ *
+ * Not everything can be directly aliased. An alias cannot be declared
+ * of - for example - a literal:
+ *
+ * `alias a = 4; //Error`
+ *
+ * With this template any single entity can be aliased:
+ *
+ * `alias b = Alias!4; //OK`
+ *
+ * See_Also:
+ * To alias more than one thing at once, use $(LREF AliasSeq)
+ */
+template Alias(alias a)
+{
+    static if (__traits(compiles, { alias x = a; }))
+        alias Alias = a;
+    else static if (__traits(compiles, { enum x = a; }))
+        enum Alias = a;
+    else
+        static assert(0, "Cannot alias " ~ a.stringof);
+}
+
+/// Ditto
+template Alias(T)
+{
+    alias Alias = T;
+}
+
+///
+unittest
+{
+    // Without Alias this would fail if Args[0] was e.g. a value and
+    // some logic would be needed to detect when to use enum instead
+    alias Head(Args ...) = Alias!(Args[0]);
+    alias Tail(Args ...) = Args[1 .. $];
+
+    alias Blah = AliasSeq!(3, int, "hello");
+    static assert(Head!Blah == 3);
+    static assert(is(Head!(Tail!Blah) == int));
+    static assert((Tail!Blah)[1] == "hello");
+}
+
+///
+unittest
+{
+    alias a = Alias!(123);
+    static assert(a == 123);
+
+    enum abc = 1;
+    alias b = Alias!(abc);
+    static assert(b == 1);
+
+    alias c = Alias!(3 + 4);
+    static assert(c == 7);
+
+    alias concat = (s0, s1) => s0 ~ s1;
+    alias d = Alias!(concat("Hello", " World!"));
+    static assert(d == "Hello World!");
+
+    alias e = Alias!(int);
+    static assert(is(e == int));
+
+    alias f = Alias!(AliasSeq!(int));
+    static assert(!is(typeof(f[0]))); //not an AliasSeq
+    static assert(is(f == int));
+
+    auto g = 6;
+    alias h = Alias!g;
+    ++h;
+    assert(g == 7);
+}
+
+package template OldAlias(alias a)
+{
+    static if (__traits(compiles, { alias x = a; }))
+        alias OldAlias = a;
+    else static if (__traits(compiles, { enum x = a; }))
+        enum OldAlias = a;
+    else
+        static assert(0, "Cannot alias " ~ a.stringof);
+}
+
+import std.traits : isAggregateType, Unqual;
+
+package template OldAlias(T) if (!isAggregateType!T || is(Unqual!T == T))
+{
+    alias OldAlias = T;
+}
+
+deprecated("Alias will stop to unqualify user defined types.")
+package template OldAlias(T) if (isAggregateType!T && !is(Unqual!T == T))
+{
+    alias OldAlias = Unqual!T;
+}
+
+unittest
+{
+    static struct Foo {}
+    static assert(is(OldAlias!(const(Foo)) == Foo));
+    static assert(is(OldAlias!(const(int)) == const(int)));
+    static assert(OldAlias!123 == 123);
+    enum abc = 123;
+    static assert(OldAlias!abc == 123);
+}
+
+/**
+ * Returns the index of the first occurrence of type T in the
+ * sequence of zero or more types TList.
+ * If not found, -1 is returned.
+ */
+template staticIndexOf(T, TList...)
+{
+    enum staticIndexOf = genericIndexOf!(T, TList).index;
+}
+
+/// Ditto
+template staticIndexOf(alias T, TList...)
+{
+    enum staticIndexOf = genericIndexOf!(T, TList).index;
+}
+
+///
+unittest
+{
+    import std.stdio;
+
+    void foo()
+    {
+        writefln("The index of long is %s",
+                 staticIndexOf!(long, AliasSeq!(int, long, double)));
+        // prints: The index of long is 1
+    }
+}
+
+// [internal]
+private template genericIndexOf(args...)
+    if (args.length >= 1)
+{
+    alias e     = OldAlias!(args[0]);
+    alias tuple = args[1 .. $];
+
+    static if (tuple.length)
+    {
+        alias head = OldAlias!(tuple[0]);
+        alias tail = tuple[1 .. $];
+
+        static if (isSame!(e, head))
+        {
+            enum index = 0;
+        }
+        else
+        {
+            enum next  = genericIndexOf!(e, tail).index;
+            enum index = (next == -1) ? -1 : 1 + next;
+        }
+    }
+    else
+    {
+        enum index = -1;
+    }
+}
+
+unittest
+{
+    static assert(staticIndexOf!( byte, byte, short, int, long) ==  0);
+    static assert(staticIndexOf!(short, byte, short, int, long) ==  1);
+    static assert(staticIndexOf!(  int, byte, short, int, long) ==  2);
+    static assert(staticIndexOf!( long, byte, short, int, long) ==  3);
+    static assert(staticIndexOf!( char, byte, short, int, long) == -1);
+    static assert(staticIndexOf!(   -1, byte, short, int, long) == -1);
+    static assert(staticIndexOf!(void) == -1);
+
+    static assert(staticIndexOf!("abc", "abc", "def", "ghi", "jkl") ==  0);
+    static assert(staticIndexOf!("def", "abc", "def", "ghi", "jkl") ==  1);
+    static assert(staticIndexOf!("ghi", "abc", "def", "ghi", "jkl") ==  2);
+    static assert(staticIndexOf!("jkl", "abc", "def", "ghi", "jkl") ==  3);
+    static assert(staticIndexOf!("mno", "abc", "def", "ghi", "jkl") == -1);
+    static assert(staticIndexOf!( void, "abc", "def", "ghi", "jkl") == -1);
+    static assert(staticIndexOf!(42) == -1);
+
+    static assert(staticIndexOf!(void, 0, "void", void) == 2);
+    static assert(staticIndexOf!("void", 0, void, "void") == 2);
+}
+
+/// Kept for backwards compatibility
+alias IndexOf = staticIndexOf;
+
+/**
+ * Returns a typetuple created from TList with the first occurrence,
+ * if any, of T removed.
+ */
+template Erase(T, TList...)
+{
+    alias Erase = GenericErase!(T, TList).result;
+}
+
+/// Ditto
+template Erase(alias T, TList...)
+{
+    alias Erase = GenericErase!(T, TList).result;
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, double, char);
+    alias TL = Erase!(long, Types);
+    static assert(is(TL == AliasSeq!(int, double, char)));
+}
+
+// [internal]
+private template GenericErase(args...)
+    if (args.length >= 1)
+{
+    alias e     = OldAlias!(args[0]);
+    alias tuple = args[1 .. $] ;
+
+    static if (tuple.length)
+    {
+        alias head = OldAlias!(tuple[0]);
+        alias tail = tuple[1 .. $];
+
+        static if (isSame!(e, head))
+            alias result = tail;
+        else
+            alias result = AliasSeq!(head, GenericErase!(e, tail).result);
+    }
+    else
+    {
+        alias result = AliasSeq!();
+    }
+}
+
+unittest
+{
+    static assert(Pack!(Erase!(int,
+                short, int, int, 4)).
+        equals!(short,      int, 4));
+
+    static assert(Pack!(Erase!(1,
+                real, 3, 1, 4, 1, 5, 9)).
+        equals!(real, 3,    4, 1, 5, 9));
+}
+
+
+/**
+ * Returns a typetuple created from TList with the all occurrences,
+ * if any, of T removed.
+ */
+template EraseAll(T, TList...)
+{
+    alias EraseAll = GenericEraseAll!(T, TList).result;
+}
+
+/// Ditto
+template EraseAll(alias T, TList...)
+{
+    alias EraseAll = GenericEraseAll!(T, TList).result;
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, long, int);
+
+    alias TL = EraseAll!(long, Types);
+    static assert(is(TL == AliasSeq!(int, int)));
+}
+
+// [internal]
+private template GenericEraseAll(args...)
+    if (args.length >= 1)
+{
+    alias e     = OldAlias!(args[0]);
+    alias tuple = args[1 .. $];
+
+    static if (tuple.length)
+    {
+        alias head = OldAlias!(tuple[0]);
+        alias tail = tuple[1 .. $];
+        alias next = GenericEraseAll!(e, tail).result;
+
+        static if (isSame!(e, head))
+            alias result = next;
+        else
+            alias result = AliasSeq!(head, next);
+    }
+    else
+    {
+        alias result = AliasSeq!();
+    }
+}
+
+unittest
+{
+    static assert(Pack!(EraseAll!(int,
+                short, int, int, 4)).
+        equals!(short,           4));
+
+    static assert(Pack!(EraseAll!(1,
+                real, 3, 1, 4, 1, 5, 9)).
+        equals!(real, 3,    4,    5, 9));
+}
+
+
+/**
+ * Returns a typetuple created from TList with the all duplicate
+ * types removed.
+ */
+template NoDuplicates(TList...)
+{
+    static if (TList.length == 0)
+        alias NoDuplicates = TList;
+    else
+        alias NoDuplicates =
+            AliasSeq!(TList[0], NoDuplicates!(EraseAll!(TList[0], TList[1 .. $])));
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, long, int, float);
+
+    alias TL = NoDuplicates!(Types);
+    static assert(is(TL == AliasSeq!(int, long, float)));
+}
+
+unittest
+{
+    static assert(
+        Pack!(
+            NoDuplicates!(1, int, 1, NoDuplicates, int, NoDuplicates, real))
+        .equals!(1, int,    NoDuplicates,                    real));
+}
+
+
+/**
+ * Returns a typetuple created from TList with the first occurrence
+ * of type T, if found, replaced with type U.
+ */
+template Replace(T, U, TList...)
+{
+    alias Replace = GenericReplace!(T, U, TList).result;
+}
+
+/// Ditto
+template Replace(alias T, U, TList...)
+{
+    alias Replace = GenericReplace!(T, U, TList).result;
+}
+
+/// Ditto
+template Replace(T, alias U, TList...)
+{
+    alias Replace = GenericReplace!(T, U, TList).result;
+}
+
+/// Ditto
+template Replace(alias T, alias U, TList...)
+{
+    alias Replace = GenericReplace!(T, U, TList).result;
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, long, int, float);
+
+    alias TL = Replace!(long, char, Types);
+    static assert(is(TL == AliasSeq!(int, char, long, int, float)));
+}
+
+// [internal]
+private template GenericReplace(args...)
+    if (args.length >= 2)
+{
+    alias from  = OldAlias!(args[0]);
+    alias to    = OldAlias!(args[1]);
+    alias tuple = args[2 .. $];
+
+    static if (tuple.length)
+    {
+        alias head = OldAlias!(tuple[0]);
+        alias tail = tuple[1 .. $];
+
+        static if (isSame!(from, head))
+            alias result = AliasSeq!(to, tail);
+        else
+            alias result = AliasSeq!(head,
+                GenericReplace!(from, to, tail).result);
+    }
+    else
+    {
+        alias result = AliasSeq!();
+    }
+ }
+
+unittest
+{
+    static assert(Pack!(Replace!(byte, ubyte,
+                short,  byte, byte, byte)).
+        equals!(short, ubyte, byte, byte));
+
+    static assert(Pack!(Replace!(1111, byte,
+                2222, 1111, 1111, 1111)).
+        equals!(2222, byte, 1111, 1111));
+
+    static assert(Pack!(Replace!(byte, 1111,
+                short, byte, byte, byte)).
+        equals!(short, 1111, byte, byte));
+
+    static assert(Pack!(Replace!(1111, "11",
+                2222, 1111, 1111, 1111)).
+        equals!(2222, "11", 1111, 1111));
+}
+
+/**
+ * Returns a typetuple created from TList with all occurrences
+ * of type T, if found, replaced with type U.
+ */
+template ReplaceAll(T, U, TList...)
+{
+    alias ReplaceAll = GenericReplaceAll!(T, U, TList).result;
+}
+
+/// Ditto
+template ReplaceAll(alias T, U, TList...)
+{
+    alias ReplaceAll = GenericReplaceAll!(T, U, TList).result;
+}
+
+/// Ditto
+template ReplaceAll(T, alias U, TList...)
+{
+    alias ReplaceAll = GenericReplaceAll!(T, U, TList).result;
+}
+
+/// Ditto
+template ReplaceAll(alias T, alias U, TList...)
+{
+    alias ReplaceAll = GenericReplaceAll!(T, U, TList).result;
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, long, int, float);
+
+    alias TL = ReplaceAll!(long, char, Types);
+    static assert(is(TL == AliasSeq!(int, char, char, int, float)));
+}
+
+// [internal]
+private template GenericReplaceAll(args...)
+    if (args.length >= 2)
+{
+    alias from  = OldAlias!(args[0]);
+    alias to    = OldAlias!(args[1]);
+    alias tuple = args[2 .. $];
+
+    static if (tuple.length)
+    {
+        alias head = OldAlias!(tuple[0]);
+        alias tail = tuple[1 .. $];
+        alias next = GenericReplaceAll!(from, to, tail).result;
+
+        static if (isSame!(from, head))
+            alias result = AliasSeq!(to, next);
+        else
+            alias result = AliasSeq!(head, next);
+    }
+    else
+    {
+        alias result = AliasSeq!();
+    }
+}
+
+unittest
+{
+    static assert(Pack!(ReplaceAll!(byte, ubyte,
+                 byte, short,  byte,  byte)).
+        equals!(ubyte, short, ubyte, ubyte));
+
+    static assert(Pack!(ReplaceAll!(1111, byte,
+                1111, 2222, 1111, 1111)).
+        equals!(byte, 2222, byte, byte));
+
+    static assert(Pack!(ReplaceAll!(byte, 1111,
+                byte, short, byte, byte)).
+        equals!(1111, short, 1111, 1111));
+
+    static assert(Pack!(ReplaceAll!(1111, "11",
+                1111, 2222, 1111, 1111)).
+        equals!("11", 2222, "11", "11"));
+}
+
+/**
+ * Returns a typetuple created from TList with the order reversed.
+ */
+template Reverse(TList...)
+{
+    static if (TList.length <= 1)
+    {
+        alias Reverse = TList;
+    }
+    else
+    {
+        alias Reverse =
+            AliasSeq!(
+                Reverse!(TList[$/2 ..  $ ]),
+                Reverse!(TList[ 0  .. $/2]));
+    }
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(int, long, long, int, float);
+
+    alias TL = Reverse!(Types);
+    static assert(is(TL == AliasSeq!(float, int, long, long, int)));
+}
+
+/**
+ * Returns the type from TList that is the most derived from type T.
+ * If none are found, T is returned.
+ */
+template MostDerived(T, TList...)
+{
+    static if (TList.length == 0)
+        alias MostDerived = T;
+    else static if (is(TList[0] : T))
+        alias MostDerived = MostDerived!(TList[0], TList[1 .. $]);
+    else
+        alias MostDerived = MostDerived!(T, TList[1 .. $]);
+}
+
+///
+unittest
+{
+    class A { }
+    class B : A { }
+    class C : B { }
+    alias Types = AliasSeq!(A, C, B);
+
+    MostDerived!(Object, Types) x;  // x is declared as type C
+    static assert(is(typeof(x) == C));
+}
+
+/**
+ * Returns the typetuple TList with the types sorted so that the most
+ * derived types come first.
+ */
+template DerivedToFront(TList...)
+{
+    static if (TList.length == 0)
+        alias DerivedToFront = TList;
+    else
+        alias DerivedToFront =
+            AliasSeq!(MostDerived!(TList[0], TList[1 .. $]),
+                       DerivedToFront!(ReplaceAll!(MostDerived!(TList[0], TList[1 .. $]),
+                                TList[0],
+                                TList[1 .. $])));
+}
+
+///
+unittest
+{
+    class A { }
+    class B : A { }
+    class C : B { }
+    alias Types = AliasSeq!(A, C, B);
+
+    alias TL = DerivedToFront!(Types);
+    static assert(is(TL == AliasSeq!(C, B, A)));
+}
+
+/**
+Evaluates to $(D AliasSeq!(F!(T[0]), F!(T[1]), ..., F!(T[$ - 1]))).
+ */
+template staticMap(alias F, T...)
+{
+    static if (T.length == 0)
+    {
+        alias staticMap = AliasSeq!();
+    }
+    else static if (T.length == 1)
+    {
+        alias staticMap = AliasSeq!(F!(T[0]));
+    }
+    else
+    {
+        alias staticMap =
+            AliasSeq!(
+                staticMap!(F, T[ 0  .. $/2]),
+                staticMap!(F, T[$/2 ..  $ ]));
+    }
+}
+
+///
+unittest
+{
+    import std.traits : Unqual;
+    alias TL = staticMap!(Unqual, int, const int, immutable int);
+    static assert(is(TL == AliasSeq!(int, int, int)));
+}
+
+unittest
+{
+    import std.traits : Unqual;
+
+    // empty
+    alias Empty = staticMap!(Unqual);
+    static assert(Empty.length == 0);
+
+    // single
+    alias Single = staticMap!(Unqual, const int);
+    static assert(is(Single == AliasSeq!int));
+
+    alias T = staticMap!(Unqual, int, const int, immutable int);
+    static assert(is(T == AliasSeq!(int, int, int)));
+}
+
+/**
+Tests whether all given items satisfy a template predicate, i.e. evaluates to
+$(D F!(T[0]) && F!(T[1]) && ... && F!(T[$ - 1])).
+
+Evaluation is $(I not) short-circuited if a false result is encountered; the
+template predicate must be instantiable with all the given items.
+ */
+template allSatisfy(alias F, T...)
+{
+    static if (T.length == 0)
+    {
+        enum allSatisfy = true;
+    }
+    else static if (T.length == 1)
+    {
+        enum allSatisfy = F!(T[0]);
+    }
+    else
+    {
+        enum allSatisfy =
+            allSatisfy!(F, T[ 0  .. $/2]) &&
+            allSatisfy!(F, T[$/2 ..  $ ]);
+    }
+}
+
+///
+unittest
+{
+    import std.traits : isIntegral;
+
+    static assert(!allSatisfy!(isIntegral, int, double));
+    static assert( allSatisfy!(isIntegral, int, long));
+}
+
+/**
+Tests whether any given items satisfy a template predicate, i.e. evaluates to
+$(D F!(T[0]) || F!(T[1]) || ... || F!(T[$ - 1])).
+
+Evaluation is $(I not) short-circuited if a true result is encountered; the
+template predicate must be instantiable with all the given items.
+ */
+template anySatisfy(alias F, T...)
+{
+    static if(T.length == 0)
+    {
+        enum anySatisfy = false;
+    }
+    else static if (T.length == 1)
+    {
+        enum anySatisfy = F!(T[0]);
+    }
+    else
+    {
+        enum anySatisfy =
+            anySatisfy!(F, T[ 0  .. $/2]) ||
+            anySatisfy!(F, T[$/2 ..  $ ]);
+    }
+}
+
+///
+unittest
+{
+    import std.traits : isIntegral;
+
+    static assert(!anySatisfy!(isIntegral, string, double));
+    static assert( anySatisfy!(isIntegral, int, double));
+}
+
+
+/**
+ * Filters an $(D AliasSeq) using a template predicate. Returns a
+ * $(D AliasSeq) of the elements which satisfy the predicate.
+ */
+template Filter(alias pred, TList...)
+{
+    static if (TList.length == 0)
+    {
+        alias Filter = AliasSeq!();
+    }
+    else static if (TList.length == 1)
+    {
+        static if (pred!(TList[0]))
+            alias Filter = AliasSeq!(TList[0]);
+        else
+            alias Filter = AliasSeq!();
+    }
+    else
+    {
+        alias Filter =
+            AliasSeq!(
+                Filter!(pred, TList[ 0  .. $/2]),
+                Filter!(pred, TList[$/2 ..  $ ]));
+    }
+}
+
+///
+unittest
+{
+    import std.traits : isNarrowString, isUnsigned;
+
+    alias Types1 = AliasSeq!(string, wstring, dchar[], char[], dstring, int);
+    alias TL1 = Filter!(isNarrowString, Types1);
+    static assert(is(TL1 == AliasSeq!(string, wstring, char[])));
+
+    alias Types2 = AliasSeq!(int, byte, ubyte, dstring, dchar, uint, ulong);
+    alias TL2 = Filter!(isUnsigned, Types2);
+    static assert(is(TL2 == AliasSeq!(ubyte, uint, ulong)));
+}
+
+unittest
+{
+    import std.traits : isPointer;
+
+    static assert(is(Filter!(isPointer, int, void*, char[], int*) == AliasSeq!(void*, int*)));
+    static assert(is(Filter!isPointer == AliasSeq!()));
+}
+
+
+// Used in template predicate unit tests below.
+private version (unittest)
+{
+    template testAlways(T...)
+    {
+        enum testAlways = true;
+    }
+
+    template testNever(T...)
+    {
+        enum testNever = false;
+    }
+
+    template testError(T...)
+    {
+        static assert(false, "Should never be instantiated.");
+    }
+}
+
+
+/**
+ * Negates the passed template predicate.
+ */
+template templateNot(alias pred)
+{
+    enum templateNot(T...) = !pred!T;
+}
+
+///
+unittest
+{
+    import std.traits : isPointer;
+
+    alias isNoPointer = templateNot!isPointer;
+    static assert(!isNoPointer!(int*));
+    static assert(allSatisfy!(isNoPointer, string, char, float));
+}
+
+unittest
+{
+    foreach (T; AliasSeq!(int, staticMap, 42))
+    {
+        static assert(!Instantiate!(templateNot!testAlways, T));
+        static assert(Instantiate!(templateNot!testNever, T));
+    }
+}
+
+
+/**
+ * Combines several template predicates using logical AND, i.e. constructs a new
+ * predicate which evaluates to true for a given input T if and only if all of
+ * the passed predicates are true for T.
+ *
+ * The predicates are evaluated from left to right, aborting evaluation in a
+ * short-cut manner if a false result is encountered, in which case the latter
+ * instantiations do not need to compile.
+ */
+template templateAnd(Preds...)
+{
+    template templateAnd(T...)
+    {
+        static if (Preds.length == 0)
+        {
+            enum templateAnd = true;
+        }
+        else
+        {
+            static if (Instantiate!(Preds[0], T))
+                alias templateAnd = Instantiate!(.templateAnd!(Preds[1 .. $]), T);
+            else
+                enum templateAnd = false;
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.traits : isNumeric, isUnsigned;
+
+    alias storesNegativeNumbers = templateAnd!(isNumeric, templateNot!isUnsigned);
+    static assert(storesNegativeNumbers!int);
+    static assert(!storesNegativeNumbers!string && !storesNegativeNumbers!uint);
+
+    // An empty list of predicates always yields true.
+    alias alwaysTrue = templateAnd!();
+    static assert(alwaysTrue!int);
+}
+
+unittest
+{
+    foreach (T; AliasSeq!(int, staticMap, 42))
+    {
+        static assert( Instantiate!(templateAnd!(), T));
+        static assert( Instantiate!(templateAnd!(testAlways), T));
+        static assert( Instantiate!(templateAnd!(testAlways, testAlways), T));
+        static assert(!Instantiate!(templateAnd!(testNever), T));
+        static assert(!Instantiate!(templateAnd!(testAlways, testNever), T));
+        static assert(!Instantiate!(templateAnd!(testNever, testAlways), T));
+
+        static assert(!Instantiate!(templateAnd!(testNever, testError), T));
+        static assert(!is(typeof(Instantiate!(templateAnd!(testAlways, testError), T))));
+    }
+}
+
+
+/**
+ * Combines several template predicates using logical OR, i.e. constructs a new
+ * predicate which evaluates to true for a given input T if and only at least
+ * one of the passed predicates is true for T.
+ *
+ * The predicates are evaluated from left to right, aborting evaluation in a
+ * short-cut manner if a true result is encountered, in which case the latter
+ * instantiations do not need to compile.
+ */
+template templateOr(Preds...)
+{
+    template templateOr(T...)
+    {
+        static if (Preds.length == 0)
+        {
+            enum templateOr = false;
+        }
+        else
+        {
+            static if (Instantiate!(Preds[0], T))
+                enum templateOr = true;
+            else
+                alias templateOr = Instantiate!(.templateOr!(Preds[1 .. $]), T);
+        }
+    }
+}
+
+///
+unittest
+{
+    import std.traits : isPointer, isUnsigned;
+
+    alias isPtrOrUnsigned = templateOr!(isPointer, isUnsigned);
+    static assert( isPtrOrUnsigned!uint &&  isPtrOrUnsigned!(short*));
+    static assert(!isPtrOrUnsigned!int  && !isPtrOrUnsigned!(string));
+
+    // An empty list of predicates never yields true.
+    alias alwaysFalse = templateOr!();
+    static assert(!alwaysFalse!int);
+}
+
+unittest
+{
+    foreach (T; AliasSeq!(int, staticMap, 42))
+    {
+        static assert( Instantiate!(templateOr!(testAlways), T));
+        static assert( Instantiate!(templateOr!(testAlways, testAlways), T));
+        static assert( Instantiate!(templateOr!(testAlways, testNever), T));
+        static assert( Instantiate!(templateOr!(testNever, testAlways), T));
+        static assert(!Instantiate!(templateOr!(), T));
+        static assert(!Instantiate!(templateOr!(testNever), T));
+
+        static assert( Instantiate!(templateOr!(testAlways, testError), T));
+        static assert( Instantiate!(templateOr!(testNever, testAlways, testError), T));
+        // DMD @@BUG@@: Assertion fails for int, seems like a error gagging
+        // problem. The bug goes away when removing some of the other template
+        // instantiations in the module.
+        // static assert(!is(typeof(Instantiate!(templateOr!(testNever, testError), T))));
+    }
+}
+
+/**
+ * Converts an input range $(D range) to an alias sequence.
+ */
+template aliasSeqOf(alias range)
+{
+    import std.range : isInputRange;
+    import std.traits : isArray, isNarrowString;
+
+    alias ArrT = typeof(range);
+    static if (isArray!ArrT && !isNarrowString!ArrT)
+    {
+        static if (range.length == 0)
+        {
+            alias aliasSeqOf = AliasSeq!();
+        }
+        else static if (range.length == 1)
+        {
+            alias aliasSeqOf = AliasSeq!(range[0]);
+        }
+        else
+        {
+            alias aliasSeqOf = AliasSeq!(aliasSeqOf!(range[0 .. $/2]), aliasSeqOf!(range[$/2 .. $]));
+        }
+    }
+    else static if (isInputRange!ArrT)
+    {
+        import std.array : array;
+        alias aliasSeqOf = aliasSeqOf!(array(range));
+    }
+    else
+    {
+        import std.string : format;
+        static assert(false, format("Cannot transform %s of type %s into a AliasSeq.", range, ArrT.stringof));
+    }
+}
+
+///
+unittest
+{
+    import std.algorithm : map, sort;
+    import std.string : capitalize;
+
+    struct S
+    {
+        int a;
+        int c;
+        int b;
+    }
+
+    alias capMembers = aliasSeqOf!([__traits(allMembers, S)].sort().map!capitalize());
+    static assert(capMembers[0] == "A");
+    static assert(capMembers[1] == "B");
+    static assert(capMembers[2] == "C");
+}
+
+///
+unittest
+{
+    enum REF = [0, 1, 2, 3];
+    foreach(I, V; aliasSeqOf!([0, 1, 2, 3]))
+    {
+        static assert(V == I);
+        static assert(V == REF[I]);
+    }
+}
+
+unittest
+{
+    import std.range : iota;
+    import std.conv : to, octal;
+    //Testing compile time octal
+    foreach (I2; aliasSeqOf!(iota(0, 8)))
+        foreach (I1; aliasSeqOf!(iota(0, 8)))
+        {
+            enum oct = I2 *  8 + I1;
+            enum dec = I2 * 10 + I1;
+            enum str = to!string(dec);
+            static assert(octal!dec == oct);
+            static assert(octal!str == oct);
+        }
+}
+
+unittest
+{
+    enum REF = "日本語"d;
+    foreach(I, V; aliasSeqOf!"日本語"c)
+    {
+        static assert(V == REF[I]);
+    }
+}
+
+/**
+  * $(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies)
+  * $(D_PARAM Template) by binding its first (left) or last (right) arguments
+  * to $(D_PARAM args).
+  *
+  * Behaves like the identity function when $(D_PARAM args) is empty.
+  * Params:
+  *    Template = template to partially apply
+  *    args     = arguments to bind
+  * Returns:
+  *    _Template with arity smaller than or equal to $(D_PARAM Template)
+  */
+template ApplyLeft(alias Template, args...)
+{
+    static if (args.length)
+    {
+        template ApplyLeft(right...)
+        {
+            static if (is(typeof(Template!(args, right))))
+                enum ApplyLeft = Template!(args, right); // values
+            else
+                alias ApplyLeft = Template!(args, right); // symbols
+        }
+    }
+    else
+        alias ApplyLeft = Template;
+}
+
+/// Ditto
+template ApplyRight(alias Template, args...)
+{
+    static if (args.length)
+    {
+        template ApplyRight(left...)
+        {
+            static if (is(typeof(Template!(left, args))))
+                enum ApplyRight = Template!(left, args); // values
+            else
+                alias ApplyRight = Template!(left, args); // symbols
+        }
+    }
+    else
+        alias ApplyRight = Template;
+}
+
+///
+unittest
+{
+    import std.traits : isImplicitlyConvertible;
+
+    static assert(allSatisfy!(
+        ApplyLeft!(isImplicitlyConvertible, ubyte),
+        short, ushort, int, uint, long, ulong));
+
+    static assert(is(Filter!(ApplyRight!(isImplicitlyConvertible, short),
+        ubyte, string, short, float, int) == AliasSeq!(ubyte, short)));
+}
+
+///
+unittest
+{
+    import std.traits : hasMember, ifTestable;
+
+    struct T1
+    {
+        bool foo;
+    }
+
+    struct T2
+    {
+        struct Test
+        {
+            bool opCast(T : bool)() { return true; }
+        }
+
+        Test foo;
+    }
+
+    static assert(allSatisfy!(ApplyRight!(hasMember, "foo"), T1, T2));
+    static assert(allSatisfy!(ApplyRight!(ifTestable, a => a.foo), T1, T2));
+}
+
+///
+unittest
+{
+    import std.traits : Largest;
+
+    alias Types = AliasSeq!(byte, short, int, long);
+
+    static assert(is(staticMap!(ApplyLeft!(Largest, short), Types) ==
+                AliasSeq!(short, short, int, long)));
+    static assert(is(staticMap!(ApplyLeft!(Largest, int), Types) ==
+                AliasSeq!(int, int, int, long)));
+}
+
+///
+unittest
+{
+    import std.traits : FunctionAttribute, SetFunctionAttributes;
+
+    static void foo() @system;
+    static int bar(int) @system;
+
+    alias SafeFunctions = AliasSeq!(
+        void function() @safe,
+        int function(int) @safe);
+
+    static assert(is(staticMap!(ApplyRight!(
+        SetFunctionAttributes, "D", FunctionAttribute.safe),
+        typeof(&foo), typeof(&bar)) == SafeFunctions));
+}
+
+/**
+ * Creates an `AliasSeq` which repeats a type or an `AliasSeq` exactly `n` times.
+ */
+template Repeat(size_t n, TList...) if (n > 0)
+{
+    static if (n == 1)
+    {
+        alias Repeat = AliasSeq!TList;
+    }
+    else static if (n == 2)
+    {
+        alias Repeat = AliasSeq!(TList, TList);
+    }
+    else
+    {
+        alias R = Repeat!((n - 1) / 2, TList);
+        static if ((n - 1) % 2 == 0)
+        {
+            alias Repeat = AliasSeq!(TList, R, R);
+        }
+        else
+        {
+            alias Repeat = AliasSeq!(TList, TList, R, R);
+        }
+    }
+}
+
+///
+unittest
+{
+    alias ImInt1 = Repeat!(1, immutable(int));
+    static assert(is(ImInt1 == AliasSeq!(immutable(int))));
+
+    alias Real3 = Repeat!(3, real);
+    static assert(is(Real3 == AliasSeq!(real, real, real)));
+
+    alias Real12 = Repeat!(4, Real3);
+    static assert(is(Real12 == AliasSeq!(real, real, real, real, real, real,
+        real, real, real, real, real, real)));
+
+    alias Composite = AliasSeq!(uint, int);
+    alias Composite2 = Repeat!(2, Composite);
+    static assert(is(Composite2 == AliasSeq!(uint, int, uint, int)));
+}
+
+
+///
+unittest
+{
+    auto staticArray(T, size_t n)(Repeat!(n, T) elems)
+    {
+        T[n] a = [elems];
+        return a;
+    }
+
+    auto a = staticArray!(long, 3)(3, 1, 4);
+    assert(is(typeof(a) == long[3]));
+    assert(a == [3, 1, 4]);
+}
+
+/**
+ * Sorts a $(LREF AliasSeq) using $(D cmp).
+ *
+ * Parameters:
+ *     cmp = A template that returns a $(D bool) (if its first argument is less than the second one)
+ *         or an $(D int) (-1 means less than, 0 means equal, 1 means greater than)
+ *
+ *     Seq = The  $(LREF AliasSeq) to sort
+ *
+ * Returns: The sorted alias sequence
+ */
+template staticSort(alias cmp, Seq...)
+{
+    static if (Seq.length < 2)
+    {
+        alias staticSort = Seq;
+    }
+    else
+    {
+        private alias bottom = staticSort!(cmp, Seq[0 .. $ / 2]);
+        private alias top = staticSort!(cmp, Seq[$ / 2 .. $]);
+        alias staticSort = staticMerge!(cmp, Seq.length / 2, bottom, top);
+    }
+}
+
+///
+unittest
+{
+    alias Nums = AliasSeq!(7, 2, 3, 23);
+    enum Comp(int N1, int N2) = N1 < N2;
+    static assert(AliasSeq!(2, 3, 7, 23) == staticSort!(Comp, Nums));
+}
+
+///
+unittest
+{
+    alias Types = AliasSeq!(uint, short, ubyte, long, ulong);
+    enum Comp(T1, T2) = __traits(isUnsigned, T2) - __traits(isUnsigned, T1);
+    static assert(is(AliasSeq!(uint, ubyte, ulong, short, long) == staticSort!(Comp,
+        Types)));
+}
+
+private template staticMerge(alias cmp, int half, Seq...)
+{
+    static if (half == 0 || half == Seq.length)
+    {
+        alias staticMerge = Seq;
+    }
+    else
+    {
+        private enum Result = cmp!(Seq[0], Seq[half]);
+        static if (is(typeof(Result) == bool))
+        {
+            private enum Check = Result;
+        }
+        else static if (is(typeof(Result) : int))
+        {
+            private enum Check = Result <= 0;
+        }
+        else
+        {
+            static assert(0, typeof(Result).stringof ~ " is not a value comparison type");
+        }
+        static if (Check)
+        {
+            alias staticMerge = AliasSeq!(Seq[0], staticMerge!(cmp, half - 1, Seq[1 .. $]));
+        }
+        else
+        {
+            alias staticMerge = AliasSeq!(Seq[half], staticMerge!(cmp, half,
+                Seq[0 .. half], Seq[half + 1 .. $]));
+        }
+    }
+}
+
+// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
+private:
+
+/*
+ * [internal] Returns true if a and b are the same thing, or false if
+ * not. Both a and b can be types, literals, or symbols.
+ *
+ * How:                     When:
+ *      is(a == b)        - both are types
+ *        a == b          - both are literals (true literals, enums)
+ * __traits(isSame, a, b) - other cases (variables, functions,
+ *                          templates, etc.)
+ */
+private template isSame(ab...)
+    if (ab.length == 2)
+{
+    static if (__traits(compiles, expectType!(ab[0]),
+                                  expectType!(ab[1])))
+    {
+        enum isSame = is(ab[0] == ab[1]);
+    }
+    else static if (!__traits(compiles, expectType!(ab[0])) &&
+                    !__traits(compiles, expectType!(ab[1])) &&
+                     __traits(compiles, expectBool!(ab[0] == ab[1])))
+    {
+        static if (!__traits(compiles, &ab[0]) ||
+                   !__traits(compiles, &ab[1]))
+            enum isSame = (ab[0] == ab[1]);
+        else
+            enum isSame = __traits(isSame, ab[0], ab[1]);
+    }
+    else
+    {
+        enum isSame = __traits(isSame, ab[0], ab[1]);
+    }
+}
+private template expectType(T) {}
+private template expectBool(bool b) {}
+
+unittest
+{
+    static assert( isSame!(int, int));
+    static assert(!isSame!(int, short));
+
+    enum a = 1, b = 1, c = 2, s = "a", t = "a";
+    static assert( isSame!(1, 1));
+    static assert( isSame!(a, 1));
+    static assert( isSame!(a, b));
+    static assert(!isSame!(b, c));
+    static assert( isSame!("a", "a"));
+    static assert( isSame!(s, "a"));
+    static assert( isSame!(s, t));
+    static assert(!isSame!(1, "1"));
+    static assert(!isSame!(a, "a"));
+    static assert( isSame!(isSame, isSame));
+    static assert(!isSame!(isSame, a));
+
+    static assert(!isSame!(byte, a));
+    static assert(!isSame!(short, isSame));
+    static assert(!isSame!(a, int));
+    static assert(!isSame!(long, isSame));
+
+    static immutable X = 1, Y = 1, Z = 2;
+    static assert( isSame!(X, X));
+    static assert(!isSame!(X, Y));
+    static assert(!isSame!(Y, Z));
+
+    int  foo();
+    int  bar();
+    real baz(int);
+    static assert( isSame!(foo, foo));
+    static assert(!isSame!(foo, bar));
+    static assert(!isSame!(bar, baz));
+    static assert( isSame!(baz, baz));
+    static assert(!isSame!(foo, 0));
+
+    int  x, y;
+    real z;
+    static assert( isSame!(x, x));
+    static assert(!isSame!(x, y));
+    static assert(!isSame!(y, z));
+    static assert( isSame!(z, z));
+    static assert(!isSame!(x, 0));
+}
+
+/*
+ * [internal] Confines a tuple within a template.
+ */
+private template Pack(T...)
+{
+    alias tuple = T;
+
+    // For convenience
+    template equals(U...)
+    {
+        static if (T.length == U.length)
+        {
+            static if (T.length == 0)
+                enum equals = true;
+            else
+                enum equals = isSame!(T[0], U[0]) &&
+                    Pack!(T[1 .. $]).equals!(U[1 .. $]);
+        }
+        else
+        {
+            enum equals = false;
+        }
+    }
+}
+
+unittest
+{
+    static assert( Pack!(1, int, "abc").equals!(1, int, "abc"));
+    static assert(!Pack!(1, int, "abc").equals!(1, int, "cba"));
+}
+
+/*
+ * Instantiates the given template with the given list of parameters.
+ *
+ * Used to work around syntactic limitations of D with regard to instantiating
+ * a template from an alias sequence (e.g. T[0]!(...) is not valid) or a template
+ * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed).
+ */
+// TODO: Consider publicly exposing this, maybe even if only for better
+// understandability of error messages.
+alias Instantiate(alias Template, Params...) = Template!Params;
diff --git a/libphobos/src/std/mmfile.d b/libphobos/src/std/mmfile.d
new file mode 100644
index 00000000000..aaf368365c6
--- /dev/null
+++ b/libphobos/src/std/mmfile.d
@@ -0,0 +1,697 @@
+// Written in the D programming language.
+
+/**
+ * Read and write memory mapped files.
+ * Macros:
+ *  WIKI=Phobos/StdMmfile
+ *
+ * Copyright: Copyright Digital Mars 2004 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright),
+ *            Matthew Wilson
+ * Source:    $(PHOBOSSRC std/_mmfile.d)
+ */
+/*          Copyright Digital Mars 2004 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.mmfile;
+
+private import std.file;
+private import core.stdc.stdio;
+private import core.stdc.stdlib;
+private import core.stdc.errno;
+private import std.path;
+private import std.string;
+import std.conv, std.exception, std.stdio;
+
+import std.internal.cstring;
+
+//debug = MMFILE;
+
+version (Windows)
+{
+    private import core.sys.windows.windows;
+    private import std.utf;
+    private import std.windows.syserror;
+}
+else version (Posix)
+{
+    private import core.sys.posix.fcntl;
+    private import core.sys.posix.unistd;
+    private import core.sys.posix.sys.mman;
+    private import core.sys.posix.sys.stat;
+}
+else
+{
+    static assert(0);
+}
+
+/**
+ * MmFile objects control the memory mapped file resource.
+ */
+class MmFile
+{
+    /**
+     * The mode the memory mapped file is opened with.
+     */
+    enum Mode
+    {
+        read,            /// Read existing file
+        readWriteNew,    /// Delete existing file, write new file
+        readWrite,       /// Read/Write existing file, create if not existing
+        readCopyOnWrite, /// Read/Write existing file, copy on write
+    }
+
+    /**
+     * Open memory mapped file filename for reading.
+     * File is closed when the object instance is deleted.
+     * Throws:
+     *  std.file.FileException
+     */
+    this(string filename)
+    {
+        this(filename, Mode.read, 0, null);
+    }
+
+    version(linux) this(File file, Mode mode = Mode.read, ulong size = 0,
+            void* address = null, size_t window = 0)
+    {
+        // Save a copy of the File to make sure the fd stays open.
+        this.file = file;
+        this(file.fileno, mode, size, address, window);
+    }
+
+    version(linux) private this(int fildes, Mode mode, ulong size,
+            void* address, size_t window)
+    {
+        int oflag;
+        int fmode;
+
+        switch (mode)
+        {
+        case Mode.read:
+            flags = MAP_SHARED;
+            prot = PROT_READ;
+            oflag = O_RDONLY;
+            fmode = 0;
+            break;
+
+        case Mode.readWriteNew:
+            assert(size != 0);
+            flags = MAP_SHARED;
+            prot = PROT_READ | PROT_WRITE;
+            oflag = O_CREAT | O_RDWR | O_TRUNC;
+            fmode = octal!660;
+            break;
+
+        case Mode.readWrite:
+            flags = MAP_SHARED;
+            prot = PROT_READ | PROT_WRITE;
+            oflag = O_CREAT | O_RDWR;
+            fmode = octal!660;
+            break;
+
+        case Mode.readCopyOnWrite:
+            flags = MAP_PRIVATE;
+            prot = PROT_READ | PROT_WRITE;
+            oflag = O_RDWR;
+            fmode = 0;
+            break;
+
+        default:
+            assert(0);
+        }
+
+        fd = fildes;
+
+        // Adjust size
+        stat_t statbuf = void;
+        errnoEnforce(fstat(fd, &statbuf) == 0);
+        if (prot & PROT_WRITE && size > statbuf.st_size)
+        {
+            // Need to make the file size bytes big
+            lseek(fd, cast(off_t)(size - 1), SEEK_SET);
+            char c = 0;
+            core.sys.posix.unistd.write(fd, &c, 1);
+        }
+        else if (prot & PROT_READ && size == 0)
+            size = statbuf.st_size;
+        this.size = size;
+
+        // Map the file into memory!
+        size_t initial_map = (window && 2*window<size)
+            ? 2*window : cast(size_t)size;
+        auto p = mmap(address, initial_map, prot, flags, fd, 0);
+        if (p == MAP_FAILED) {
+            errnoEnforce(false, "Could not map file into memory");
+        }
+        data = p[0 .. initial_map];
+    }
+
+    /**
+     * Open memory mapped file filename in mode.
+     * File is closed when the object instance is deleted.
+     * Params:
+     *  filename = name of the file.
+     *      If null, an anonymous file mapping is created.
+     *  mode = access mode defined above.
+     *  size =  the size of the file. If 0, it is taken to be the
+     *      size of the existing file.
+     *  address = the preferred address to map the file to,
+     *      although the system is not required to honor it.
+     *      If null, the system selects the most convenient address.
+     *  window = preferred block size of the amount of data to map at one time
+     *      with 0 meaning map the entire file. The window size must be a
+     *      multiple of the memory allocation page size.
+     * Throws:
+     *  std.file.FileException
+     */
+    this(string filename, Mode mode, ulong size, void* address,
+            size_t window = 0)
+    {
+        this.filename = filename;
+        this.mMode = mode;
+        this.window = window;
+        this.address = address;
+
+        version (Windows)
+        {
+            void* p;
+            uint dwDesiredAccess2;
+            uint dwShareMode;
+            uint dwCreationDisposition;
+            uint flProtect;
+
+            switch (mode)
+            {
+            case Mode.read:
+                dwDesiredAccess2 = GENERIC_READ;
+                dwShareMode = FILE_SHARE_READ;
+                dwCreationDisposition = OPEN_EXISTING;
+                flProtect = PAGE_READONLY;
+                dwDesiredAccess = FILE_MAP_READ;
+                break;
+
+            case Mode.readWriteNew:
+                assert(size != 0);
+                dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
+                dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+                dwCreationDisposition = CREATE_ALWAYS;
+                flProtect = PAGE_READWRITE;
+                dwDesiredAccess = FILE_MAP_WRITE;
+                break;
+
+            case Mode.readWrite:
+                dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
+                dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+                dwCreationDisposition = OPEN_ALWAYS;
+                flProtect = PAGE_READWRITE;
+                dwDesiredAccess = FILE_MAP_WRITE;
+                break;
+
+            case Mode.readCopyOnWrite:
+                dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
+                dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+                dwCreationDisposition = OPEN_EXISTING;
+                flProtect = PAGE_WRITECOPY;
+                dwDesiredAccess = FILE_MAP_COPY;
+                break;
+
+            default:
+                assert(0);
+            }
+
+            if (filename != null)
+            {
+                hFile = CreateFileW(filename.tempCStringW(),
+                        dwDesiredAccess2,
+                        dwShareMode,
+                        null,
+                        dwCreationDisposition,
+                        FILE_ATTRIBUTE_NORMAL,
+                        cast(HANDLE)null);
+                wenforce(hFile != INVALID_HANDLE_VALUE, "CreateFileW");
+            }
+            else
+                hFile = INVALID_HANDLE_VALUE;
+
+            scope(failure)
+            {
+                if (hFile != INVALID_HANDLE_VALUE)
+                {
+                    CloseHandle(hFile);
+                    hFile = INVALID_HANDLE_VALUE;
+                }
+            }
+
+            int hi = cast(int)(size>>32);
+            hFileMap = CreateFileMappingW(hFile, null, flProtect,
+                    hi, cast(uint)size, null);
+            wenforce(hFileMap, "CreateFileMapping");
+            scope(failure)
+            {
+                CloseHandle(hFileMap);
+                hFileMap = null;
+            }
+
+            if (size == 0 && filename != null)
+            {
+                uint sizehi;
+                uint sizelow = GetFileSize(hFile, &sizehi);
+                wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS,
+                    "GetFileSize");
+                size = (cast(ulong)sizehi << 32) + sizelow;
+            }
+            this.size = size;
+
+            size_t initial_map = (window && 2*window<size)
+                ? 2*window : cast(size_t)size;
+            p = MapViewOfFileEx(hFileMap, dwDesiredAccess, 0, 0,
+                    initial_map, address);
+            wenforce(p, "MapViewOfFileEx");
+            data = p[0 .. initial_map];
+
+            debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size);
+        }
+        else version (Posix)
+        {
+            void* p;
+            int oflag;
+            int fmode;
+
+            switch (mode)
+            {
+            case Mode.read:
+                flags = MAP_SHARED;
+                prot = PROT_READ;
+                oflag = O_RDONLY;
+                fmode = 0;
+                break;
+
+            case Mode.readWriteNew:
+                assert(size != 0);
+                flags = MAP_SHARED;
+                prot = PROT_READ | PROT_WRITE;
+                oflag = O_CREAT | O_RDWR | O_TRUNC;
+                fmode = octal!660;
+                break;
+
+            case Mode.readWrite:
+                flags = MAP_SHARED;
+                prot = PROT_READ | PROT_WRITE;
+                oflag = O_CREAT | O_RDWR;
+                fmode = octal!660;
+                break;
+
+            case Mode.readCopyOnWrite:
+                flags = MAP_PRIVATE;
+                prot = PROT_READ | PROT_WRITE;
+                oflag = O_RDWR;
+                fmode = 0;
+                break;
+
+            default:
+                assert(0);
+            }
+
+            if (filename.length)
+            {
+                fd = .open(filename.tempCString(), oflag, fmode);
+                errnoEnforce(fd != -1, "Could not open file "~filename);
+
+                stat_t statbuf;
+                if (fstat(fd, &statbuf))
+                {
+                    //printf("\tfstat error, errno = %d\n", errno);
+                    .close(fd);
+                    fd = -1;
+                    errnoEnforce(false, "Could not stat file "~filename);
+                }
+
+                if (prot & PROT_WRITE && size > statbuf.st_size)
+                {
+                    // Need to make the file size bytes big
+                    .lseek(fd, cast(off_t)(size - 1), SEEK_SET);
+                    char c = 0;
+                    core.sys.posix.unistd.write(fd, &c, 1);
+                }
+                else if (prot & PROT_READ && size == 0)
+                    size = statbuf.st_size;
+            }
+            else
+            {
+                fd = -1;
+                version(CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON;
+                flags |= MAP_ANON;
+            }
+            this.size = size;
+            size_t initial_map = (window && 2*window<size)
+                ? 2*window : cast(size_t)size;
+            p = mmap(address, initial_map, prot, flags, fd, 0);
+            if (p == MAP_FAILED)
+            {
+                if (fd != -1)
+                {
+                    .close(fd);
+                    fd = -1;
+                }
+                errnoEnforce(false, "Could not map file "~filename);
+            }
+
+            data = p[0 .. initial_map];
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+
+    /**
+     * Flushes pending output and closes the memory mapped file.
+     */
+    ~this()
+    {
+        debug (MMFILE) printf("MmFile.~this()\n");
+        unmap();
+        data = null;
+        version (Windows)
+        {
+            wenforce(hFileMap == null || CloseHandle(hFileMap) == TRUE,
+                    "Could not close file handle");
+            hFileMap = null;
+
+            wenforce(!hFile || hFile == INVALID_HANDLE_VALUE
+                    || CloseHandle(hFile) == TRUE,
+                    "Could not close handle");
+            hFile = INVALID_HANDLE_VALUE;
+        }
+        else version (Posix)
+        {
+            version (linux)
+            {
+                if (file !is File.init)
+                {
+                    // The File destructor will close the file,
+                    // if it is the only remaining reference.
+                    return;
+                }
+            }
+            errnoEnforce(fd == -1 || fd <= 2
+                    || .close(fd) != -1,
+                    "Could not close handle");
+            fd = -1;
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+
+    /* Flush any pending output.
+     */
+    void flush()
+    {
+        debug (MMFILE) printf("MmFile.flush()\n");
+        version (Windows)
+        {
+            FlushViewOfFile(data.ptr, data.length);
+        }
+        else version (Posix)
+        {
+            int i;
+            i = msync(cast(void*)data, data.length, MS_SYNC);   // sys/mman.h
+            errnoEnforce(i == 0, "msync failed");
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+
+    /**
+     * Gives size in bytes of the memory mapped file.
+     */
+    @property ulong length() const
+    {
+        debug (MMFILE) printf("MmFile.length()\n");
+        return size;
+    }
+
+    /**
+     * Read-only property returning the file mode.
+     */
+    Mode mode()
+    {
+        debug (MMFILE) printf("MmFile.mode()\n");
+        return mMode;
+    }
+
+    /**
+     * Returns entire file contents as an array.
+     */
+    void[] opSlice()
+    {
+        debug (MMFILE) printf("MmFile.opSlice()\n");
+        return opSlice(0,size);
+    }
+
+    /**
+     * Returns slice of file contents as an array.
+     */
+    void[] opSlice(ulong i1, ulong i2)
+    {
+        debug (MMFILE) printf("MmFile.opSlice(%lld, %lld)\n", i1, i2);
+        ensureMapped(i1,i2);
+        size_t off1 = cast(size_t)(i1-start);
+        size_t off2 = cast(size_t)(i2-start);
+        return data[off1 .. off2];
+    }
+
+    /**
+     * Returns byte at index i in file.
+     */
+    ubyte opIndex(ulong i)
+    {
+        debug (MMFILE) printf("MmFile.opIndex(%lld)\n", i);
+        ensureMapped(i);
+        size_t off = cast(size_t)(i-start);
+        return (cast(ubyte[])data)[off];
+    }
+
+    /**
+     * Sets and returns byte at index i in file to value.
+     */
+    ubyte opIndexAssign(ubyte value, ulong i)
+    {
+        debug (MMFILE) printf("MmFile.opIndex(%lld, %d)\n", i, value);
+        ensureMapped(i);
+        size_t off = cast(size_t)(i-start);
+        return (cast(ubyte[])data)[off] = value;
+    }
+
+
+    // return true if the given position is currently mapped
+    private int mapped(ulong i)
+    {
+        debug (MMFILE) printf("MmFile.mapped(%lld, %lld, %d)\n", i,start,
+                data.length);
+        return i >= start && i < start+data.length;
+    }
+
+    // unmap the current range
+    private void unmap()
+    {
+        debug (MMFILE) printf("MmFile.unmap()\n");
+        version(Windows) {
+            wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile");
+        } else {
+            errnoEnforce(!data.ptr || munmap(cast(void*)data, data.length) == 0,
+                    "munmap failed");
+        }
+        data = null;
+    }
+
+    // map range
+    private void map(ulong start, size_t len)
+    {
+        debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len);
+        void* p;
+        if (start+len > size)
+            len = cast(size_t)(size-start);
+        version(Windows) {
+            uint hi = cast(uint)(start>>32);
+            p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint)start, len, address);
+            wenforce(p, "MapViewOfFileEx");
+        } else {
+            p = mmap(address, len, prot, flags, fd, cast(off_t)start);
+            errnoEnforce(p != MAP_FAILED);
+        }
+        data = p[0 .. len];
+        this.start = start;
+    }
+
+    // ensure a given position is mapped
+    private void ensureMapped(ulong i)
+    {
+        debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i);
+        if (!mapped(i)) {
+            unmap();
+            if (window == 0) {
+                map(0,cast(size_t)size);
+            } else {
+                ulong block = i/window;
+                if (block == 0)
+                    map(0,2*window);
+                else
+                    map(window*(block-1),3*window);
+            }
+        }
+    }
+
+    // ensure a given range is mapped
+    private void ensureMapped(ulong i, ulong j)
+    {
+        debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j);
+        if (!mapped(i) || !mapped(j-1)) {
+            unmap();
+            if (window == 0) {
+                map(0,cast(size_t)size);
+            } else {
+                ulong iblock = i/window;
+                ulong jblock = (j-1)/window;
+                if (iblock == 0) {
+                    map(0,cast(size_t)(window*(jblock+2)));
+                } else {
+                    map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3)));
+                }
+            }
+        }
+    }
+
+private:
+    string filename;
+    void[] data;
+    ulong  start;
+    size_t window;
+    ulong  size;
+    Mode   mMode;
+    void*  address;
+    version (linux) File file;
+
+    version (Windows)
+    {
+        HANDLE hFile = INVALID_HANDLE_VALUE;
+        HANDLE hFileMap = null;
+        uint dwDesiredAccess;
+    }
+    else version (Posix)
+    {
+        int fd;
+        int prot;
+        int flags;
+        int fmode;
+    }
+    else
+    {
+        static assert(0);
+    }
+
+    // Report error, where errno gives the error number
+    // void errNo()
+    // {
+    //     version (Windows)
+    //     {
+    //         throw new FileException(filename, GetLastError());
+    //     }
+    //     else version (linux)
+    //     {
+    //         throw new FileException(filename, errno);
+    //     }
+    //     else
+    //     {
+    //         static assert(0);
+    //     }
+    // }
+}
+
+unittest
+{
+    import std.file : deleteme;
+
+    const size_t K = 1024;
+    size_t win = 64*K; // assume the page size is 64K
+    version(Windows) {
+        /+ these aren't defined in core.sys.windows.windows so let's use default
+         SYSTEM_INFO sysinfo;
+         GetSystemInfo(&sysinfo);
+         win = sysinfo.dwAllocationGranularity;
+         +/
+    } else version (linux) {
+        // getpagesize() is not defined in the unix D headers so use the guess
+    }
+    string test_file = std.file.deleteme ~ "-testing.txt";
+    MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,
+            100*K,null,win);
+    ubyte[] str = cast(ubyte[])"1234567890";
+    ubyte[] data = cast(ubyte[])mf[0 .. 10];
+    data[] = str[];
+    assert( mf[0 .. 10] == str );
+    data = cast(ubyte[])mf[50 .. 60];
+    data[] = str[];
+    assert( mf[50 .. 60] == str );
+    ubyte[] data2 = cast(ubyte[])mf[20*K .. 60*K];
+    assert( data2.length == 40*K );
+    assert( data2[$-1] == 0 );
+    mf[100*K-1] = cast(ubyte)'b';
+    data2 = cast(ubyte[])mf[21*K .. 100*K];
+    assert( data2.length == 79*K );
+    assert( data2[$-1] == 'b' );
+    delete mf;
+    std.file.remove(test_file);
+    // Create anonymous mapping
+    auto test = new MmFile(null, MmFile.Mode.readWriteNew, 1024*1024, null);
+}
+
+version(linux)
+unittest // Issue 14868
+{
+    import std.typecons : scoped;
+    import std.file : deleteme;
+
+    // Test retaining ownership of File/fd
+
+    auto fn = std.file.deleteme ~ "-testing.txt";
+    scope(exit) std.file.remove(fn);
+    File(fn, "wb").writeln("Testing!");
+    scoped!MmFile(File(fn));
+
+    // Test that unique ownership of File actually leads to the fd being closed
+
+    auto f = File(fn);
+    auto fd = f.fileno;
+    {
+        auto mf = scoped!MmFile(f);
+        f = File.init;
+    }
+    assert(.close(fd) == -1);
+}
+
+unittest // Issue 14994, 14995
+{
+    import std.file : deleteme;
+    import std.typecons : scoped;
+
+    // Zero-length map may or may not be valid on OSX and NetBSD
+    version (OSX)
+        import std.exception : verifyThrown = collectException;
+    version (NetBSD)
+        import std.exception : verifyThrown = collectException;
+    else
+        import std.exception : verifyThrown = assertThrown;
+
+    auto fn = std.file.deleteme ~ "-testing.txt";
+    scope(exit) std.file.remove(fn);
+    verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null));
+}
diff --git a/libphobos/src/std/net/curl.d b/libphobos/src/std/net/curl.d
new file mode 100644
index 00000000000..9c6af6609b2
--- /dev/null
+++ b/libphobos/src/std/net/curl.d
@@ -0,0 +1,4791 @@
+// Written in the D programming language.
+
+/**
+Networking client functionality as provided by $(WEB _curl.haxx.se/libcurl,
+libcurl). The libcurl library must be installed on the system in order to use
+this module.
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW High level) $(TD $(MYREF download) $(MYREF upload) $(MYREF get)
+$(MYREF post) $(MYREF put) $(MYREF del) $(MYREF options) $(MYREF trace)
+$(MYREF connect) $(MYREF byLine) $(MYREF byChunk)
+$(MYREF byLineAsync) $(MYREF byChunkAsync) )
+)
+$(TR $(TDNW Low level) $(TD $(MYREF HTTP) $(MYREF FTP) $(MYREF
+SMTP) )
+)
+)
+)
+
+Note:
+You may need to link to the $(B curl) library, e.g. by adding $(D "libs": ["curl"])
+to your $(B dub.json) file if you are using $(LINK2 http://code.dlang.org, DUB).
+
+Windows x86 note:
+A DMD compatible libcurl static library can be downloaded from the dlang.org
+$(LINK2 http://dlang.org/download.html, download page).
+
+Compared to using libcurl directly this module allows simpler client code for
+common uses, requires no unsafe operations, and integrates better with the rest
+of the language. Futhermore it provides <a href="std_range.html">$(D range)</a>
+access to protocols supported by libcurl both synchronously and asynchronously.
+
+A high level and a low level API are available. The high level API is built
+entirely on top of the low level one.
+
+The high level API is for commonly used functionality such as HTTP/FTP get. The
+$(LREF byLineAsync) and $(LREF byChunkAsync) provides asynchronous <a
+href="std_range.html">$(D ranges)</a> that performs the request in another
+thread while handling a line/chunk in the current thread.
+
+The low level API allows for streaming and other advanced features.
+
+$(BOOKTABLE Cheat Sheet,
+$(TR $(TH Function Name) $(TH Description)
+)
+$(LEADINGROW High level)
+$(TR $(TDNW $(LREF download)) $(TD $(D
+download("ftp.digitalmars.com/sieve.ds", "/tmp/downloaded-ftp-file"))
+downloads file from URL to file system.)
+)
+$(TR $(TDNW $(LREF upload)) $(TD $(D
+upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds");)
+uploads file from file system to URL.)
+)
+$(TR $(TDNW $(LREF get)) $(TD $(D
+get("dlang.org")) returns a char[] containing the dlang.org web page.)
+)
+$(TR $(TDNW $(LREF put)) $(TD $(D
+put("dlang.org", "Hi")) returns a char[] containing
+the dlang.org web page. after a HTTP PUT of "hi")
+)
+$(TR $(TDNW $(LREF post)) $(TD $(D
+post("dlang.org", "Hi")) returns a char[] containing
+the dlang.org web page. after a HTTP POST of "hi")
+)
+$(TR $(TDNW $(LREF byLine)) $(TD $(D
+byLine("dlang.org")) returns a range of char[] containing the
+dlang.org web page.)
+)
+$(TR $(TDNW $(LREF byChunk)) $(TD $(D
+byChunk("dlang.org", 10)) returns a range of ubyte[10] containing the
+dlang.org web page.)
+)
+$(TR $(TDNW $(LREF byLineAsync)) $(TD $(D
+byLineAsync("dlang.org")) returns a range of char[] containing the dlang.org web
+ page asynchronously.)
+)
+$(TR $(TDNW $(LREF byChunkAsync)) $(TD $(D
+byChunkAsync("dlang.org", 10)) returns a range of ubyte[10] containing the
+dlang.org web page asynchronously.)
+)
+$(LEADINGROW Low level
+)
+$(TR $(TDNW $(LREF HTTP)) $(TD $(D HTTP) struct for advanced usage))
+$(TR $(TDNW $(LREF FTP)) $(TD $(D FTP) struct for advanced usage))
+$(TR $(TDNW $(LREF SMTP)) $(TD $(D SMTP) struct for advanced usage))
+)
+
+
+Example:
+---
+import std.net.curl, std.stdio;
+
+// Return a char[] containing the content specified by an URL
+auto content = get("dlang.org");
+
+// Post data and return a char[] containing the content specified by an URL
+auto content = post("mydomain.com/here.cgi", "post data");
+
+// Get content of file from ftp server
+auto content = get("ftp.digitalmars.com/sieve.ds");
+
+// Post and print out content line by line. The request is done in another thread.
+foreach (line; byLineAsync("dlang.org", "Post data"))
+    writeln(line);
+
+// Get using a line range and proxy settings
+auto client = HTTP();
+client.proxy = "1.2.3.4";
+foreach (line; byLine("dlang.org", client))
+    writeln(line);
+---
+
+For more control than the high level functions provide, use the low level API:
+
+Example:
+---
+import std.net.curl, std.stdio;
+
+// GET with custom data receivers
+auto http = HTTP("dlang.org");
+http.onReceiveHeader =
+    (in char[] key, in char[] value) { writeln(key, ": ", value); };
+http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; };
+http.perform();
+---
+
+First, an instance of the reference-counted HTTP struct is created. Then the
+custom delegates are set. These will be called whenever the HTTP instance
+receives a header and a data buffer, respectively. In this simple example, the
+headers are written to stdout and the data is ignored. If the request should be
+stopped before it has finished then return something less than data.length from
+the onReceive callback. See $(LREF onReceiveHeader)/$(LREF onReceive) for more
+information. Finally the HTTP request is effected by calling perform(), which is
+synchronous.
+
+Source: $(PHOBOSSRC std/net/_curl.d)
+
+Copyright: Copyright Jonas Drewsen 2011-2012
+License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors: Jonas Drewsen. Some of the SMTP code contributed by Jimmy Cao.
+
+Credits: The functionally is based on $(WEB _curl.haxx.se/libcurl, libcurl).
+         LibCurl is licensed under an MIT/X derivative license.
+*/
+/*
+         Copyright Jonas Drewsen 2011 - 2012.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.net.curl;
+
+import core.thread;
+import etc.c.curl;
+import std.algorithm;
+import std.array;
+import std.concurrency;
+import std.conv;
+import std.datetime;
+import std.encoding;
+import std.exception;
+import std.meta;
+import std.regex;
+import std.socket : InternetAddress;
+import std.string;
+import std.traits;
+import std.typecons;
+
+import std.internal.cstring;
+
+public import etc.c.curl : CurlOption;
+
+version(unittest)
+{
+    // Run unit test with the PHOBOS_TEST_ALLOW_NET=1 set in order to
+    // allow net traffic
+    import std.stdio;
+    import std.range;
+    import std.process : environment;
+    import std.file : deleteme;
+    import std.path : buildPath;
+
+    import std.socket : Address, INADDR_LOOPBACK, Socket, TcpSocket;
+
+    private struct TestServer
+    {
+        string addr() { return _addr; }
+
+        void handle(void function(Socket s) dg)
+        {
+            tid.send(dg);
+        }
+
+    private:
+        string _addr;
+        Tid tid;
+
+        static void loop(shared TcpSocket listener)
+        {
+            try while (true)
+            {
+                void function(Socket) handler = void;
+                try
+                    handler = receiveOnly!(typeof(handler));
+                catch (OwnerTerminated)
+                    return;
+                handler((cast()listener).accept);
+            }
+            catch (Throwable e)
+            {
+                import core.stdc.stdlib : exit, EXIT_FAILURE;
+                stderr.writeln(e);
+                exit(EXIT_FAILURE); // Bugzilla 7018
+            }
+        }
+    }
+
+    private TestServer startServer()
+    {
+        auto sock = new TcpSocket;
+        sock.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY));
+        sock.listen(1);
+        auto addr = sock.localAddress.toString();
+        auto tid = spawn(&TestServer.loop, cast(shared)sock);
+        return TestServer(addr, tid);
+    }
+
+    private ref TestServer testServer()
+    {
+        __gshared TestServer server;
+        return initOnce!server(startServer());
+    }
+
+    private struct Request(T)
+    {
+        string hdrs;
+        immutable(T)[] bdy;
+    }
+
+    private Request!T recvReq(T=char)(Socket s)
+    {
+        ubyte[1024] tmp=void;
+        ubyte[] buf;
+
+        while (true)
+        {
+            auto nbytes = s.receive(tmp[]);
+            assert(nbytes >= 0);
+
+            immutable beg = buf.length > 3 ? buf.length - 3 : 0;
+            buf ~= tmp[0 .. nbytes];
+            auto bdy = buf[beg .. $].find(cast(ubyte[])"\r\n\r\n");
+            if (bdy.empty)
+                continue;
+
+            auto hdrs = cast(string)buf[0 .. $ - bdy.length];
+            bdy.popFrontN(4);
+            // no support for chunked transfer-encoding
+            if (auto m = hdrs.matchFirst(ctRegex!(`Content-Length: ([0-9]+)`, "i")))
+            {
+                import std.uni : asUpperCase;
+                if (hdrs.asUpperCase.canFind("EXPECT: 100-CONTINUE"))
+                    s.send(httpContinue);
+
+                size_t remain = m.captures[1].to!size_t - bdy.length;
+                while (remain)
+                {
+                    nbytes = s.receive(tmp[0 .. min(remain, $)]);
+                    assert(nbytes >= 0);
+                    buf ~= tmp[0 .. nbytes];
+                    remain -= nbytes;
+                }
+            }
+            else
+            {
+                assert(bdy.empty);
+            }
+            bdy = buf[hdrs.length + 4 .. $];
+            return typeof(return)(hdrs, cast(immutable(T)[])bdy);
+        }
+    }
+
+    private string httpOK(string msg)
+    {
+        return "HTTP/1.1 200 OK\r\n"~
+            "Content-Type: text/plain\r\n"~
+            "Content-Length: "~msg.length.to!string~"\r\n"
+            "\r\n"~
+            msg;
+    }
+
+    private string httpOK()
+    {
+        return "HTTP/1.1 200 OK\r\n"~
+            "Content-Length: 0\r\n"~
+            "\r\n";
+    }
+
+    private string httpNotFound()
+    {
+        return "HTTP/1.1 404 Not Found\r\n"~
+            "Content-Length: 0\r\n"~
+            "\r\n";
+    }
+
+    private enum httpContinue = "HTTP/1.1 100 Continue\r\n\r\n";
+}
+version(StdDdoc) import std.stdio;
+
+extern (C) void exit(int);
+
+// Default data timeout for Protocols
+private enum _defaultDataTimeout = dur!"minutes"(2);
+
+/**
+Macros:
+
+CALLBACK_PARAMS = $(TABLE ,
+    $(DDOC_PARAM_ROW
+        $(DDOC_PARAM_ID $(DDOC_PARAM dlTotal))
+        $(DDOC_PARAM_DESC total bytes to download)
+        )
+    $(DDOC_PARAM_ROW
+        $(DDOC_PARAM_ID $(DDOC_PARAM dlNow))
+        $(DDOC_PARAM_DESC currently downloaded bytes)
+        )
+    $(DDOC_PARAM_ROW
+        $(DDOC_PARAM_ID $(DDOC_PARAM ulTotal))
+        $(DDOC_PARAM_DESC total bytes to upload)
+        )
+    $(DDOC_PARAM_ROW
+        $(DDOC_PARAM_ID $(DDOC_PARAM ulNow))
+        $(DDOC_PARAM_DESC currently uploaded bytes)
+        )
+)
+*/
+
+/** Connection type used when the URL should be used to auto detect the protocol.
+  *
+  * This struct is used as placeholder for the connection parameter when calling
+  * the high level API and the connection type (HTTP/FTP) should be guessed by
+  * inspecting the URL parameter.
+  *
+  * The rules for guessing the protocol are:
+  * 1, if URL starts with ftp://, ftps:// or ftp. then FTP connection is assumed.
+  * 2, HTTP connection otherwise.
+  *
+  * Example:
+  * ---
+  * import std.net.curl;
+  * // Two requests below will do the same.
+  * string content;
+  *
+  * // Explicit connection provided
+  * content = get!HTTP("dlang.org");
+  *
+  * // Guess connection type by looking at the URL
+  * content = get!AutoProtocol("ftp://foo.com/file");
+  * // and since AutoProtocol is default this is the same as
+  * connect = get("ftp://foo.com/file");
+  * // and will end up detecting FTP from the url and be the same as
+  * connect = get!FTP("ftp://foo.com/file");
+  * ---
+  */
+struct AutoProtocol { }
+
+// Returns true if the url points to an FTP resource
+private bool isFTPUrl(const(char)[] url)
+{
+    return startsWith(url.toLower(), "ftp://", "ftps://", "ftp.") != 0;
+}
+
+// Is true if the Conn type is a valid Curl Connection type.
+private template isCurlConn(Conn)
+{
+    enum auto isCurlConn = is(Conn : HTTP) ||
+        is(Conn : FTP) || is(Conn : AutoProtocol);
+}
+
+/** HTTP/FTP download to local file system.
+ *
+ * Params:
+ * url = resource to download
+ * saveToPath = path to store the downloaded content on local disk
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * download("d-lang.appspot.com/testUrl2", "/tmp/downloaded-http-file");
+ * ----
+ */
+void download(Conn = AutoProtocol)(const(char)[] url, string saveToPath, Conn conn = Conn())
+    if (isCurlConn!Conn)
+{
+    static if (is(Conn : HTTP) || is(Conn : FTP))
+    {
+        import std.stdio : File;
+        conn.url = url;
+        auto f = File(saveToPath, "wb");
+        conn.onReceive = (ubyte[] data) { f.rawWrite(data); return data.length; };
+        conn.perform();
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return download!FTP(url, saveToPath, FTP());
+        else
+            return download!HTTP(url, saveToPath, HTTP());
+    }
+}
+
+unittest
+{
+    static import std.file;
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            assert(s.recvReq.hdrs.canFind("GET /"));
+            s.send(httpOK("Hello world"));
+        });
+        auto fn = std.file.deleteme;
+        scope (exit) std.file.remove(fn);
+        download(host, fn);
+        assert(std.file.readText(fn) == "Hello world");
+    }
+}
+
+/** Upload file from local files system using the HTTP or FTP protocol.
+ *
+ * Params:
+ * loadFromPath = path load data from local disk.
+ * url = resource to upload to
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds");
+ * upload("/tmp/downloaded-http-file", "d-lang.appspot.com/testUrl2");
+ * ----
+ */
+void upload(Conn = AutoProtocol)(string loadFromPath, const(char)[] url, Conn conn = Conn())
+    if (isCurlConn!Conn)
+{
+    static if (is(Conn : HTTP))
+    {
+        conn.url = url;
+        conn.method = HTTP.Method.put;
+    }
+    else static if (is(Conn : FTP))
+    {
+        conn.url = url;
+        conn.handle.set(CurlOption.upload, 1L);
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return upload!FTP(loadFromPath, url, FTP());
+        else
+            return upload!HTTP(loadFromPath, url, HTTP());
+    }
+
+    static if (is(Conn : HTTP) || is(Conn : FTP))
+    {
+        import std.stdio : File;
+        auto f = File(loadFromPath, "rb");
+        conn.onSend = buf => f.rawRead(buf).length;
+        auto sz = f.size;
+        if (sz != ulong.max)
+            conn.contentLength = sz;
+        conn.perform();
+    }
+}
+
+unittest
+{
+    static import std.file;
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        auto fn = std.file.deleteme;
+        scope (exit) std.file.remove(fn);
+        std.file.write(fn, "upload data\n");
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            assert(req.hdrs.canFind("PUT /path"));
+            assert(req.bdy.canFind("upload data"));
+            s.send(httpOK());
+        });
+        upload(fn, host ~ "/path");
+    }
+}
+
+/** HTTP/FTP get content.
+ *
+ * Params:
+ * url = resource to get
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking
+ * for $(D char), content will be converted from the connection character set
+ * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1
+ * by default) to UTF-8.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * auto content = get("d-lang.appspot.com/testUrl2");
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the content of the resource pointed to by the URL.
+ *
+ * Throws:
+ *
+ * $(D CurlException) on error.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] get(Conn = AutoProtocol, T = char)(const(char)[] url, Conn conn = Conn())
+    if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) )
+{
+    static if (is(Conn : HTTP))
+    {
+        conn.method = HTTP.Method.get;
+        return _basicHTTP!(T)(url, "", conn);
+
+    }
+    else static if (is(Conn : FTP))
+    {
+        return _basicFTP!(T)(url, "", conn);
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return get!(FTP,T)(url, FTP());
+        else
+            return get!(HTTP,T)(url, HTTP());
+    }
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            assert(s.recvReq.hdrs.canFind("GET /path"));
+            s.send(httpOK("GETRESPONSE"));
+        });
+        auto res = get(host ~ "/path");
+        assert(res == "GETRESPONSE");
+    }
+}
+
+
+/** HTTP post content.
+ *
+ * Params:
+ * url = resource to post to
+ * postData = data to send as the body of the request. An array
+ *            of an arbitrary type is accepted and will be cast to ubyte[]
+ *            before sending it.
+ * conn = HTTP connection to use
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking
+ * for $(D char), content will be converted from the connection character set
+ * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1
+ * by default) to UTF-8.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * auto content = post("d-lang.appspot.com/testUrl2", [1,2,3,4]);
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the content of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] post(T = char, PostUnit)(const(char)[] url, const(PostUnit)[] postData, HTTP conn = HTTP())
+if (is(T == char) || is(T == ubyte))
+{
+    conn.method = HTTP.Method.post;
+    return _basicHTTP!(T)(url, postData, conn);
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            assert(req.hdrs.canFind("POST /path"));
+            assert(req.bdy.canFind("POSTBODY"));
+            s.send(httpOK("POSTRESPONSE"));
+        });
+        auto res = post(host ~ "/path", "POSTBODY");
+        assert(res == "POSTRESPONSE");
+    }
+}
+
+unittest
+{
+    auto data = new ubyte[](256);
+    foreach (i, ref ub; data)
+        ub = cast(ubyte)i;
+
+    testServer.handle((s) {
+        auto req = s.recvReq!ubyte;
+        assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4]));
+        assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255]));
+        s.send(httpOK(cast(ubyte[])[17, 27, 35, 41]));
+    });
+    auto res = post!ubyte(testServer.addr, data);
+    assert(res == cast(ubyte[])[17, 27, 35, 41]);
+}
+
+
+/** HTTP/FTP put content.
+ *
+ * Params:
+ * url = resource to put
+ * putData = data to send as the body of the request. An array
+ *           of an arbitrary type is accepted and will be cast to ubyte[]
+ *           before sending it.
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking
+ * for $(D char), content will be converted from the connection character set
+ * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1
+ * by default) to UTF-8.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * auto content = put("d-lang.appspot.com/testUrl2",
+ *                      "Putting this data");
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the content of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] put(Conn = AutoProtocol, T = char, PutUnit)(const(char)[] url, const(PutUnit)[] putData,
+                                                  Conn conn = Conn())
+    if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) )
+{
+    static if (is(Conn : HTTP))
+    {
+        conn.method = HTTP.Method.put;
+        return _basicHTTP!(T)(url, putData, conn);
+    }
+    else static if (is(Conn : FTP))
+    {
+        return _basicFTP!(T)(url, putData, conn);
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return put!(FTP,T)(url, putData, FTP());
+        else
+            return put!(HTTP,T)(url, putData, HTTP());
+    }
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            assert(req.hdrs.canFind("PUT /path"));
+            assert(req.bdy.canFind("PUTBODY"));
+            s.send(httpOK("PUTRESPONSE"));
+        });
+        auto res = put(host ~ "/path", "PUTBODY");
+        assert(res == "PUTRESPONSE");
+    }
+}
+
+
+/** HTTP/FTP delete content.
+ *
+ * Params:
+ * url = resource to delete
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * del("d-lang.appspot.com/testUrl2");
+ * ----
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+void del(Conn = AutoProtocol)(const(char)[] url, Conn conn = Conn())
+    if (isCurlConn!Conn)
+{
+    static if (is(Conn : HTTP))
+    {
+        conn.method = HTTP.Method.del;
+        _basicHTTP!char(url, cast(void[]) null, conn);
+    }
+    else static if (is(Conn : FTP))
+    {
+        auto trimmed = url.findSplitAfter("ftp://")[1];
+        auto t = trimmed.findSplitAfter("/");
+        enum minDomainNameLength = 3;
+        enforce!CurlException(t[0].length > minDomainNameLength,
+                                text("Invalid FTP URL for delete ", url));
+        conn.url = t[0];
+
+        enforce!CurlException(!t[1].empty,
+                                text("No filename specified to delete for URL ", url));
+        conn.addCommand("DELE " ~ t[1]);
+        conn.perform();
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return del!FTP(url, FTP());
+        else
+            return del!HTTP(url, HTTP());
+    }
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            assert(req.hdrs.canFind("DELETE /path"));
+            s.send(httpOK());
+        });
+        del(host ~ "/path");
+    }
+}
+
+
+/** HTTP options request.
+ *
+ * Params:
+ * url = resource make a option call to
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]).
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * auto http = HTTP();
+ * options("d-lang.appspot.com/testUrl2", http);
+ * writeln("Allow set to " ~ http.responseHeaders["Allow"]);
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the options of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] options(T = char)(const(char)[] url, HTTP conn = HTTP())
+    if (is(T == char) || is(T == ubyte))
+{
+    conn.method = HTTP.Method.options;
+    return _basicHTTP!(T)(url, null, conn);
+}
+
+// Explicitly undocumented. It will be removed in February 2017. @@@DEPRECATED_2017-02@@@
+deprecated("options does not send any data")
+T[] options(T = char, OptionsUnit)(const(char)[] url,
+                                   const(OptionsUnit)[] optionsData = null,
+                                   HTTP conn = HTTP())
+    if (is(T == char) || is(T == ubyte))
+{
+    return options!T(url, conn);
+}
+
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("OPTIONS /path"));
+        s.send(httpOK("OPTIONSRESPONSE"));
+    });
+    auto res = options(testServer.addr ~ "/path");
+    assert(res == "OPTIONSRESPONSE");
+}
+
+
+/** HTTP trace request.
+ *
+ * Params:
+ * url = resource make a trace call to
+ * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will
+ *        guess connection type and create a new instance for this call only.
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]).
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * trace("d-lang.appspot.com/testUrl1");
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the trace info of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] trace(T = char)(const(char)[] url, HTTP conn = HTTP())
+   if (is(T == char) || is(T == ubyte))
+{
+    conn.method = HTTP.Method.trace;
+    return _basicHTTP!(T)(url, cast(void[]) null, conn);
+}
+
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("TRACE /path"));
+        s.send(httpOK("TRACERESPONSE"));
+    });
+    auto res = trace(testServer.addr ~ "/path");
+    assert(res == "TRACERESPONSE");
+}
+
+
+/** HTTP connect request.
+ *
+ * Params:
+ * url = resource make a connect to
+ * conn = HTTP connection to use
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]).
+ *
+ * Example:
+ * ----
+ * import std.net.curl;
+ * connect("d-lang.appspot.com/testUrl1");
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the connect info of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] connect(T = char)(const(char)[] url, HTTP conn = HTTP())
+   if (is(T == char) || is(T == ubyte))
+{
+    conn.method = HTTP.Method.connect;
+    return _basicHTTP!(T)(url, cast(void[]) null, conn);
+}
+
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("CONNECT /path"));
+        s.send(httpOK("CONNECTRESPONSE"));
+    });
+    auto res = connect(testServer.addr ~ "/path");
+    assert(res == "CONNECTRESPONSE");
+}
+
+
+/** HTTP patch content.
+ *
+ * Params:
+ * url = resource to patch
+ * patchData = data to send as the body of the request. An array
+ *           of an arbitrary type is accepted and will be cast to ubyte[]
+ *           before sending it.
+ * conn = HTTP connection to use
+ *
+ * The template parameter $(D T) specifies the type to return. Possible values
+ * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]).
+ *
+ * Example:
+ * ----
+ * auto http = HTTP();
+ * http.addRequestHeader("Content-Type", "application/json");
+ * auto content = patch("d-lang.appspot.com/testUrl2", `{"title": "Patched Title"}`, http);
+ * ----
+ *
+ * Returns:
+ * A T[] range containing the content of the resource pointed to by the URL.
+ *
+ * See_Also: $(LREF HTTP.Method)
+ */
+T[] patch(T = char, PatchUnit)(const(char)[] url, const(PatchUnit)[] patchData,
+                               HTTP conn = HTTP())
+    if (is(T == char) || is(T == ubyte))
+{
+    conn.method = HTTP.Method.patch;
+    return _basicHTTP!(T)(url, patchData, conn);
+}
+
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("PATCH /path"));
+        assert(req.bdy.canFind("PATCHBODY"));
+        s.send(httpOK("PATCHRESPONSE"));
+    });
+    auto res = patch(testServer.addr ~ "/path", "PATCHBODY");
+    assert(res == "PATCHRESPONSE");
+}
+
+
+/*
+ * Helper function for the high level interface.
+ *
+ * It performs an HTTP request using the client which must have
+ * been setup correctly before calling this function.
+ */
+private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP client)
+{
+    immutable doSend = sendData !is null &&
+        (client.method == HTTP.Method.post ||
+         client.method == HTTP.Method.put ||
+         client.method == HTTP.Method.patch);
+
+    scope (exit)
+    {
+        client.onReceiveHeader = null;
+        client.onReceiveStatusLine = null;
+        client.onReceive = null;
+
+        if (doSend)
+        {
+            client.onSend = null;
+            client.handle.onSeek = null;
+            client.contentLength = 0;
+        }
+    }
+    client.url = url;
+    HTTP.StatusLine statusLine;
+    import std.array : appender;
+    auto content = appender!(ubyte[])();
+    string[string] headers;
+    client.onReceive = (ubyte[] data)
+    {
+        content ~= data;
+        return data.length;
+    };
+
+    if (doSend)
+    {
+        client.contentLength = sendData.length;
+        auto remainingData = sendData;
+        client.onSend = delegate size_t(void[] buf)
+        {
+            size_t minLen = min(buf.length, remainingData.length);
+            if (minLen == 0) return 0;
+            buf[0..minLen] = remainingData[0..minLen];
+            remainingData = remainingData[minLen..$];
+            return minLen;
+        };
+        client.handle.onSeek = delegate(long offset, CurlSeekPos mode)
+        {
+            switch (mode)
+            {
+                case CurlSeekPos.set:
+                    remainingData = sendData[cast(size_t)offset..$];
+                    return CurlSeek.ok;
+                default:
+                    // As of curl 7.18.0, libcurl will not pass
+                    // anything other than CurlSeekPos.set.
+                    return CurlSeek.cantseek;
+            }
+        };
+    }
+
+    client.onReceiveHeader = (in char[] key,
+                              in char[] value)
+    {
+        if (key == "content-length") {
+            import std.conv : to;
+            content.reserve(value.to!size_t);
+        }
+        if (auto v = key in headers)
+        {
+            *v ~= ", ";
+            *v ~= value;
+        }
+        else
+            headers[key] = value.idup;
+    };
+    client.onReceiveStatusLine = (HTTP.StatusLine l) { statusLine = l; };
+    client.perform();
+    enforce!CurlException(statusLine.code / 100 == 2,
+                            format("HTTP request returned status code %d (%s)",
+                                   statusLine.code, statusLine.reason));
+
+    // Default charset defined in HTTP RFC
+    auto charset = "ISO-8859-1";
+    if (auto v = "content-type" in headers)
+    {
+        auto m = match(cast(char[]) (*v), regex("charset=([^;,]*)"));
+        if (!m.empty && m.captures.length > 1)
+        {
+            charset = m.captures[1].idup;
+        }
+    }
+
+    return _decodeContent!T(content.data, charset);
+}
+
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("GET /path"));
+        s.send(httpNotFound());
+    });
+    auto e = collectException!CurlException(get(testServer.addr ~ "/path"));
+    assert(e.msg == "HTTP request returned status code 404 (Not Found)");
+}
+
+// Bugzilla 14760 - content length must be reset after post
+unittest
+{
+    testServer.handle((s) {
+        auto req = s.recvReq;
+        assert(req.hdrs.canFind("POST /"));
+        assert(req.bdy.canFind("POSTBODY"));
+        s.send(httpOK("POSTRESPONSE"));
+
+        req = s.recvReq;
+        assert(req.hdrs.canFind("TRACE /"));
+        assert(req.bdy.empty);
+        s.blocking = false;
+        ubyte[6] buf = void;
+        assert(s.receive(buf[]) < 0);
+        s.send(httpOK("TRACERESPONSE"));
+    });
+    auto http = HTTP();
+    auto res = post(testServer.addr, "POSTBODY", http);
+    assert(res == "POSTRESPONSE");
+    res = trace(testServer.addr, http);
+    assert(res == "TRACERESPONSE");
+}
+
+/*
+ * Helper function for the high level interface.
+ *
+ * It performs an FTP request using the client which must have
+ * been setup correctly before calling this function.
+ */
+private auto _basicFTP(T)(const(char)[] url, const(void)[] sendData, FTP client)
+{
+    scope (exit)
+    {
+        client.onReceive = null;
+        if (!sendData.empty)
+            client.onSend = null;
+    }
+
+    ubyte[] content;
+
+    if (client.encoding.empty)
+        client.encoding = "ISO-8859-1";
+
+    client.url = url;
+    client.onReceive = (ubyte[] data)
+    {
+        content ~= data;
+        return data.length;
+    };
+
+    if (!sendData.empty)
+    {
+        client.handle.set(CurlOption.upload, 1L);
+        client.onSend = delegate size_t(void[] buf)
+        {
+            size_t minLen = min(buf.length, sendData.length);
+            if (minLen == 0) return 0;
+            buf[0..minLen] = sendData[0..minLen];
+            sendData = sendData[minLen..$];
+            return minLen;
+        };
+    }
+
+    client.perform();
+
+    return _decodeContent!T(content, client.encoding);
+}
+
+/* Used by _basicHTTP() and _basicFTP() to decode ubyte[] to
+ * correct string format
+ */
+private auto _decodeContent(T)(ubyte[] content, string encoding)
+{
+    static if (is(T == ubyte))
+    {
+        return content;
+    }
+    else
+    {
+        // Optimally just return the utf8 encoded content
+        if (encoding == "UTF-8")
+            return cast(char[])(content);
+
+        // The content has to be re-encoded to utf8
+        auto scheme = EncodingScheme.create(encoding);
+        enforce!CurlException(scheme !is null,
+                                format("Unknown encoding '%s'", encoding));
+
+        auto strInfo = decodeString(content, scheme);
+        enforce!CurlException(strInfo[0] != size_t.max,
+                                format("Invalid encoding sequence for encoding '%s'",
+                                       encoding));
+
+        return strInfo[1];
+    }
+}
+
+alias KeepTerminator = Flag!"keepTerminator";
+/+
+struct ByLineBuffer(Char)
+{
+    bool linePresent;
+    bool EOF;
+    Char[] buffer;
+    ubyte[] decodeRemainder;
+
+    bool append(const(ubyte)[] data)
+    {
+        byLineBuffer ~= data;
+    }
+
+    @property bool linePresent()
+    {
+        return byLinePresent;
+    }
+
+    Char[] get()
+    {
+        if (!linePresent)
+        {
+            // Decode ubyte[] into Char[] until a Terminator is found.
+            // If not Terminator is found and EOF is false then raise an
+            // exception.
+        }
+        return byLineBuffer;
+    }
+
+}
+++/
+/** HTTP/FTP fetch content as a range of lines.
+ *
+ * A range of lines is returned when the request is complete. If the method or
+ * other request properties is to be customized then set the $(D conn) parameter
+ * with a HTTP/FTP instance that has these properties set.
+ *
+ * Example:
+ * ----
+ * import std.net.curl, std.stdio;
+ * foreach (line; byLine("dlang.org"))
+ *     writeln(line);
+ * ----
+ *
+ * Params:
+ * url = The url to receive content from
+ * keepTerminator = KeepTerminator.yes signals that the line terminator should be
+ *                  returned as part of the lines in the range.
+ * terminator = The character that terminates a line
+ * conn = The connection to use e.g. HTTP or FTP.
+ *
+ * Returns:
+ * A range of Char[] with the content of the resource pointer to by the URL
+ */
+auto byLine(Conn = AutoProtocol, Terminator = char, Char = char)
+           (const(char)[] url, KeepTerminator keepTerminator = KeepTerminator.no,
+            Terminator terminator = '\n', Conn conn = Conn())
+if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator)
+{
+    static struct SyncLineInputRange
+    {
+
+        private Char[] lines;
+        private Char[] current;
+        private bool currentValid;
+        private bool keepTerminator;
+        private Terminator terminator;
+
+        this(Char[] lines, bool kt, Terminator terminator)
+        {
+            this.lines = lines;
+            this.keepTerminator = kt;
+            this.terminator = terminator;
+            currentValid = true;
+            popFront();
+        }
+
+        @property @safe bool empty()
+        {
+            return !currentValid;
+        }
+
+        @property @safe Char[] front()
+        {
+            enforce!CurlException(currentValid, "Cannot call front() on empty range");
+            return current;
+        }
+
+        void popFront()
+        {
+            enforce!CurlException(currentValid, "Cannot call popFront() on empty range");
+            if (lines.empty)
+            {
+                currentValid = false;
+                return;
+            }
+
+            if (keepTerminator)
+            {
+                auto r = findSplitAfter(lines, [ terminator ]);
+                if (r[0].empty)
+                {
+                    current = r[1];
+                    lines = r[0];
+                }
+                else
+                {
+                    current = r[0];
+                    lines = r[1];
+                }
+            }
+            else
+            {
+                auto r = findSplit(lines, [ terminator ]);
+                current = r[0];
+                lines = r[2];
+            }
+        }
+    }
+
+    auto result = _getForRange!Char(url, conn);
+    return SyncLineInputRange(result, keepTerminator == KeepTerminator.yes, terminator);
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            s.send(httpOK("Line1\nLine2\nLine3"));
+        });
+        assert(byLine(host).equal(["Line1", "Line2", "Line3"]));
+    }
+}
+
+/** HTTP/FTP fetch content as a range of chunks.
+ *
+ * A range of chunks is returned when the request is complete. If the method or
+ * other request properties is to be customized then set the $(D conn) parameter
+ * with a HTTP/FTP instance that has these properties set.
+ *
+ * Example:
+ * ----
+ * import std.net.curl, std.stdio;
+ * foreach (chunk; byChunk("dlang.org", 100))
+ *     writeln(chunk); // chunk is ubyte[100]
+ * ----
+ *
+ * Params:
+ * url = The url to receive content from
+ * chunkSize = The size of each chunk
+ * conn = The connection to use e.g. HTTP or FTP.
+ *
+ * Returns:
+ * A range of ubyte[chunkSize] with the content of the resource pointer to by the URL
+ */
+auto byChunk(Conn = AutoProtocol)
+            (const(char)[] url, size_t chunkSize = 1024, Conn conn = Conn())
+    if (isCurlConn!(Conn))
+{
+    static struct SyncChunkInputRange
+    {
+        private size_t chunkSize;
+        private ubyte[] _bytes;
+        private size_t offset;
+
+        this(ubyte[] bytes, size_t chunkSize)
+        {
+            this._bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        @property @safe auto empty()
+        {
+            return offset == _bytes.length;
+        }
+
+        @property ubyte[] front()
+        {
+            size_t nextOffset = offset + chunkSize;
+            if (nextOffset > _bytes.length) nextOffset = _bytes.length;
+            return _bytes[offset..nextOffset];
+        }
+
+        @safe void popFront()
+        {
+            offset += chunkSize;
+            if (offset > _bytes.length) offset = _bytes.length;
+        }
+    }
+
+    auto result = _getForRange!ubyte(url, conn);
+    return SyncChunkInputRange(result, chunkSize);
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5]));
+        });
+        assert(byChunk(host, 2).equal([[0, 1], [2, 3], [4, 5]]));
+    }
+}
+
+private T[] _getForRange(T,Conn)(const(char)[] url, Conn conn)
+{
+    static if (is(Conn : HTTP))
+    {
+        conn.method = conn.method == HTTP.Method.undefined ? HTTP.Method.get : conn.method;
+        return _basicHTTP!(T)(url, null, conn);
+    }
+    else static if (is(Conn : FTP))
+    {
+        return _basicFTP!(T)(url, null, conn);
+    }
+    else
+    {
+        if (isFTPUrl(url))
+            return get!(FTP,T)(url, FTP());
+        else
+            return get!(HTTP,T)(url, HTTP());
+    }
+}
+
+/*
+  Main thread part of the message passing protocol used for all async
+  curl protocols.
+ */
+private mixin template WorkerThreadProtocol(Unit, alias units)
+{
+    @property bool empty()
+    {
+        tryEnsureUnits();
+        return state == State.done;
+    }
+
+    @property Unit[] front()
+    {
+        tryEnsureUnits();
+        assert(state == State.gotUnits,
+               format("Expected %s but got $s",
+                      State.gotUnits, state));
+        return units;
+    }
+
+    void popFront()
+    {
+        tryEnsureUnits();
+        assert(state == State.gotUnits,
+               format("Expected %s but got $s",
+                      State.gotUnits, state));
+        state = State.needUnits;
+        // Send to worker thread for buffer reuse
+        workerTid.send(cast(immutable(Unit)[]) units);
+        units = null;
+    }
+
+    /** Wait for duration or until data is available and return true if data is
+         available
+    */
+    bool wait(Duration d)
+    {
+        if (state == State.gotUnits)
+            return true;
+
+        enum noDur = dur!"hnsecs"(0);
+        StopWatch sw;
+        sw.start();
+        while (state != State.gotUnits && d > noDur)
+        {
+            final switch (state)
+            {
+            case State.needUnits:
+                receiveTimeout(d,
+                        (Tid origin, CurlMessage!(immutable(Unit)[]) _data)
+                        {
+                            if (origin != workerTid)
+                                return false;
+                            units = cast(Unit[]) _data.data;
+                            state = State.gotUnits;
+                            return true;
+                        },
+                        (Tid origin, CurlMessage!bool f)
+                        {
+                            if (origin != workerTid)
+                                return false;
+                            state = state.done;
+                            return true;
+                        }
+                        );
+                break;
+            case State.gotUnits: return true;
+            case State.done:
+                return false;
+            }
+            d -= sw.peek();
+            sw.reset();
+        }
+        return state == State.gotUnits;
+    }
+
+    enum State
+    {
+        needUnits,
+        gotUnits,
+        done
+    }
+    State state;
+
+    void tryEnsureUnits()
+    {
+        while (true)
+        {
+            final switch (state)
+            {
+            case State.needUnits:
+                receive(
+                        (Tid origin, CurlMessage!(immutable(Unit)[]) _data)
+                        {
+                            if (origin != workerTid)
+                                return false;
+                            units = cast(Unit[]) _data.data;
+                            state = State.gotUnits;
+                            return true;
+                        },
+                        (Tid origin, CurlMessage!bool f)
+                        {
+                            if (origin != workerTid)
+                                return false;
+                            state = state.done;
+                            return true;
+                        }
+                        );
+                break;
+            case State.gotUnits: return;
+            case State.done:
+                return;
+            }
+        }
+    }
+}
+
+// Workaround bug #2458
+// It should really be defined inside the byLineAsync method.
+// Do not create instances of this struct since it will be
+// moved when the bug has been fixed.
+// Range that reads one line at a time asynchronously.
+static struct AsyncLineInputRange(Char)
+{
+    private Char[] line;
+    mixin WorkerThreadProtocol!(Char, line);
+
+    private Tid workerTid;
+    private State running;
+
+    private this(Tid tid, size_t transmitBuffers, size_t bufferSize)
+    {
+        workerTid = tid;
+        state = State.needUnits;
+
+        // Send buffers to other thread for it to use.  Since no mechanism is in
+        // place for moving ownership a cast to shared is done here and casted
+        // back to non-shared in the receiving end.
+        foreach (i ; 0..transmitBuffers)
+        {
+            auto arr = new Char[](bufferSize);
+            workerTid.send(cast(immutable(Char[]))arr);
+        }
+    }
+}
+
+
+/** HTTP/FTP fetch content as a range of lines asynchronously.
+ *
+ * A range of lines is returned immediately and the request that fetches the
+ * lines is performed in another thread. If the method or other request
+ * properties is to be customized then set the $(D conn) parameter with a
+ * HTTP/FTP instance that has these properties set.
+ *
+ * If $(D postData) is non-_null the method will be set to $(D post) for HTTP
+ * requests.
+ *
+ * The background thread will buffer up to transmitBuffers number of lines
+ * before it stops receiving data from network. When the main thread reads the
+ * lines from the range it frees up buffers and allows for the background thread
+ * to receive more data from the network.
+ *
+ * If no data is available and the main thread accesses the range it will block
+ * until data becomes available. An exception to this is the $(D wait(Duration)) method on
+ * the $(LREF AsyncLineInputRange). This method will wait at maximum for the
+ * specified duration and return true if data is available.
+ *
+ * Example:
+ * ----
+ * import std.net.curl, std.stdio;
+ * // Get some pages in the background
+ * auto range1 = byLineAsync("www.google.com");
+ * auto range2 = byLineAsync("www.wikipedia.org");
+ * foreach (line; byLineAsync("dlang.org"))
+ *     writeln(line);
+ *
+ * // Lines already fetched in the background and ready
+ * foreach (line; range1) writeln(line);
+ * foreach (line; range2) writeln(line);
+ * ----
+ *
+ * ----
+ * import std.net.curl, std.stdio;
+ * // Get a line in a background thread and wait in
+ * // main thread for 2 seconds for it to arrive.
+ * auto range3 = byLineAsync("dlang.com");
+ * if (range.wait(dur!"seconds"(2)))
+ *     writeln(range.front);
+ * else
+ *     writeln("No line received after 2 seconds!");
+ * ----
+ *
+ * Params:
+ * url = The url to receive content from
+ * postData = Data to HTTP Post
+ * keepTerminator = KeepTerminator.yes signals that the line terminator should be
+ *                  returned as part of the lines in the range.
+ * terminator = The character that terminates a line
+ * transmitBuffers = The number of lines buffered asynchronously
+ * conn = The connection to use e.g. HTTP or FTP.
+ *
+ * Returns:
+ * A range of Char[] with the content of the resource pointer to by the
+ * URL.
+ */
+auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char, PostUnit)
+            (const(char)[] url, const(PostUnit)[] postData,
+             KeepTerminator keepTerminator = KeepTerminator.no,
+             Terminator terminator = '\n',
+             size_t transmitBuffers = 10, Conn conn = Conn())
+    if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator)
+{
+    static if (is(Conn : AutoProtocol))
+    {
+        if (isFTPUrl(url))
+            return byLineAsync(url, postData, keepTerminator,
+                               terminator, transmitBuffers, FTP());
+        else
+            return byLineAsync(url, postData, keepTerminator,
+                               terminator, transmitBuffers, HTTP());
+    }
+    else
+    {
+        // 50 is just an arbitrary number for now
+        setMaxMailboxSize(thisTid, 50, OnCrowding.block);
+        auto tid = spawn(&_spawnAsync!(Conn, Char, Terminator));
+        tid.send(thisTid);
+        tid.send(terminator);
+        tid.send(keepTerminator == KeepTerminator.yes);
+
+        _asyncDuplicateConnection(url, conn, postData, tid);
+
+        return AsyncLineInputRange!Char(tid, transmitBuffers,
+                                        Conn.defaultAsyncStringBufferSize);
+    }
+}
+
+/// ditto
+auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char)
+            (const(char)[] url, KeepTerminator keepTerminator = KeepTerminator.no,
+             Terminator terminator = '\n',
+             size_t transmitBuffers = 10, Conn conn = Conn())
+{
+    static if (is(Conn : AutoProtocol))
+    {
+        if (isFTPUrl(url))
+            return byLineAsync(url, cast(void[])null, keepTerminator,
+                               terminator, transmitBuffers, FTP());
+        else
+            return byLineAsync(url, cast(void[])null, keepTerminator,
+                               terminator, transmitBuffers, HTTP());
+    }
+    else
+    {
+        return byLineAsync(url, cast(void[])null, keepTerminator,
+                           terminator, transmitBuffers, conn);
+    }
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            s.send(httpOK("Line1\nLine2\nLine3"));
+        });
+        assert(byLineAsync(host).equal(["Line1", "Line2", "Line3"]));
+    }
+}
+
+
+// Workaround bug #2458
+// It should really be defined inside the byLineAsync method.
+// Do not create instances of this struct since it will be
+// moved when the bug has been fixed.
+// Range that reads one chunk at a time asynchronously.
+static struct AsyncChunkInputRange
+{
+    private ubyte[] chunk;
+    mixin WorkerThreadProtocol!(ubyte, chunk);
+
+    private Tid workerTid;
+    private State running;
+
+    private this(Tid tid, size_t transmitBuffers, size_t chunkSize)
+    {
+        workerTid = tid;
+        state = State.needUnits;
+
+        // Send buffers to other thread for it to use.  Since no mechanism is in
+        // place for moving ownership a cast to shared is done here and a cast
+        // back to non-shared in the receiving end.
+        foreach (i ; 0..transmitBuffers)
+        {
+            ubyte[] arr = new ubyte[](chunkSize);
+            workerTid.send(cast(immutable(ubyte[]))arr);
+        }
+    }
+}
+
+/** HTTP/FTP fetch content as a range of chunks asynchronously.
+ *
+ * A range of chunks is returned immediately and the request that fetches the
+ * chunks is performed in another thread. If the method or other request
+ * properties is to be customized then set the $(D conn) parameter with a
+ * HTTP/FTP instance that has these properties set.
+ *
+ * If $(D postData) is non-_null the method will be set to $(D post) for HTTP
+ * requests.
+ *
+ * The background thread will buffer up to transmitBuffers number of chunks
+ * before is stops receiving data from network. When the main thread reads the
+ * chunks from the range it frees up buffers and allows for the background
+ * thread to receive more data from the network.
+ *
+ * If no data is available and the main thread access the range it will block
+ * until data becomes available. An exception to this is the $(D wait(Duration))
+ * method on the $(LREF AsyncChunkInputRange). This method will wait at maximum for the specified
+ * duration and return true if data is available.
+ *
+ * Example:
+ * ----
+ * import std.net.curl, std.stdio;
+ * // Get some pages in the background
+ * auto range1 = byChunkAsync("www.google.com", 100);
+ * auto range2 = byChunkAsync("www.wikipedia.org");
+ * foreach (chunk; byChunkAsync("dlang.org"))
+ *     writeln(chunk); // chunk is ubyte[100]
+ *
+ * // Chunks already fetched in the background and ready
+ * foreach (chunk; range1) writeln(chunk);
+ * foreach (chunk; range2) writeln(chunk);
+ * ----
+ *
+ * ----
+ * import std.net.curl, std.stdio;
+ * // Get a line in a background thread and wait in
+ * // main thread for 2 seconds for it to arrive.
+ * auto range3 = byChunkAsync("dlang.com", 10);
+ * if (range.wait(dur!"seconds"(2)))
+ *     writeln(range.front);
+ * else
+ *     writeln("No chunk received after 2 seconds!");
+ * ----
+ *
+ * Params:
+ * url = The url to receive content from
+ * postData = Data to HTTP Post
+ * chunkSize = The size of the chunks
+ * transmitBuffers = The number of chunks buffered asynchronously
+ * conn = The connection to use e.g. HTTP or FTP.
+ *
+ * Returns:
+ * A range of ubyte[chunkSize] with the content of the resource pointer to by
+ * the URL.
+ */
+auto byChunkAsync(Conn = AutoProtocol, PostUnit)
+           (const(char)[] url, const(PostUnit)[] postData,
+            size_t chunkSize = 1024, size_t transmitBuffers = 10,
+            Conn conn = Conn())
+    if (isCurlConn!(Conn))
+{
+    static if (is(Conn : AutoProtocol))
+    {
+        if (isFTPUrl(url))
+            return byChunkAsync(url, postData, chunkSize,
+                                transmitBuffers, FTP());
+        else
+            return byChunkAsync(url, postData, chunkSize,
+                                transmitBuffers, HTTP());
+    }
+    else
+    {
+        // 50 is just an arbitrary number for now
+        setMaxMailboxSize(thisTid, 50, OnCrowding.block);
+        auto tid = spawn(&_spawnAsync!(Conn, ubyte));
+        tid.send(thisTid);
+
+        _asyncDuplicateConnection(url, conn, postData, tid);
+
+        return AsyncChunkInputRange(tid, transmitBuffers, chunkSize);
+    }
+}
+
+/// ditto
+auto byChunkAsync(Conn = AutoProtocol)
+           (const(char)[] url,
+            size_t chunkSize = 1024, size_t transmitBuffers = 10,
+            Conn conn = Conn())
+    if (isCurlConn!(Conn))
+{
+    static if (is(Conn : AutoProtocol))
+    {
+        if (isFTPUrl(url))
+            return byChunkAsync(url, cast(void[])null, chunkSize,
+                                transmitBuffers, FTP());
+        else
+            return byChunkAsync(url, cast(void[])null, chunkSize,
+                                transmitBuffers, HTTP());
+    }
+    else
+    {
+        return byChunkAsync(url, cast(void[])null, chunkSize,
+                            transmitBuffers, conn);
+    }
+}
+
+unittest
+{
+    foreach (host; [testServer.addr, "http://"~testServer.addr])
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5]));
+        });
+        assert(byChunkAsync(host, 2).equal([[0, 1], [2, 3], [4, 5]]));
+    }
+}
+
+
+/* Used by byLineAsync/byChunkAsync to duplicate an existing connection
+ * that can be used exclusively in a spawned thread.
+ */
+private void _asyncDuplicateConnection(Conn, PostData)
+    (const(char)[] url, Conn conn, PostData postData, Tid tid)
+{
+    // no move semantic available in std.concurrency ie. must use casting.
+    auto connDup = conn.dup();
+    connDup.url = url;
+
+    static if ( is(Conn : HTTP) )
+    {
+        connDup.p.headersOut = null;
+        connDup.method = conn.method == HTTP.Method.undefined ?
+            HTTP.Method.get : conn.method;
+        if (postData !is null)
+        {
+            if (connDup.method == HTTP.Method.put)
+            {
+                connDup.handle.set(CurlOption.infilesize_large,
+                                   postData.length);
+            }
+            else
+            {
+                // post
+                connDup.method = HTTP.Method.post;
+                connDup.handle.set(CurlOption.postfieldsize_large,
+                                   postData.length);
+            }
+            connDup.handle.set(CurlOption.copypostfields,
+                               cast(void*) postData.ptr);
+        }
+        tid.send(cast(ulong)connDup.handle.handle);
+        tid.send(connDup.method);
+    }
+    else
+    {
+        enforce!CurlException(postData is null,
+                                "Cannot put ftp data using byLineAsync()");
+        tid.send(cast(ulong)connDup.handle.handle);
+        tid.send(HTTP.Method.undefined);
+    }
+    connDup.p.curl.handle = null; // make sure handle is not freed
+}
+
+/*
+  Mixin template for all supported curl protocols. This is the commom
+  functionallity such as timeouts and network interface settings. This should
+  really be in the HTTP/FTP/SMTP structs but the documentation tool does not
+  support a mixin to put its doc strings where a mixin is done. Therefore docs
+  in this template is copied into each of HTTP/FTP/SMTP below.
+*/
+private mixin template Protocol()
+{
+
+    /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
+    /// pause a request
+    alias requestPause = CurlReadFunc.pause;
+
+    /// Value to return from onSend delegate in order to abort a request
+    alias requestAbort = CurlReadFunc.abort;
+
+    static uint defaultAsyncStringBufferSize = 100;
+
+    /**
+       The curl handle used by this connection.
+    */
+    @property ref Curl handle() return
+    {
+        return p.curl;
+    }
+
+    /**
+       True if the instance is stopped. A stopped instance is not usable.
+    */
+    @property bool isStopped()
+    {
+        return p.curl.stopped;
+    }
+
+    /// Stop and invalidate this instance.
+    void shutdown()
+    {
+        p.curl.shutdown();
+    }
+
+    /** Set verbose.
+        This will print request information to stderr.
+     */
+    @property void verbose(bool on)
+    {
+        p.curl.set(CurlOption.verbose, on ? 1L : 0L);
+    }
+
+    // Connection settings
+
+    /// Set timeout for activity on connection.
+    @property void dataTimeout(Duration d)
+    {
+        p.curl.set(CurlOption.low_speed_limit, 1);
+        p.curl.set(CurlOption.low_speed_time, d.total!"seconds");
+    }
+
+    /** Set maximum time an operation is allowed to take.
+        This includes dns resolution, connecting, data transfer, etc.
+     */
+    @property void operationTimeout(Duration d)
+    {
+        p.curl.set(CurlOption.timeout_ms, d.total!"msecs");
+    }
+
+    /// Set timeout for connecting.
+    @property void connectTimeout(Duration d)
+    {
+        p.curl.set(CurlOption.connecttimeout_ms, d.total!"msecs");
+    }
+
+    // Network settings
+
+    /** Proxy
+     *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
+     */
+    @property void proxy(const(char)[] host)
+    {
+        p.curl.set(CurlOption.proxy, host);
+    }
+
+    /** Proxy port
+     *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
+     */
+    @property void proxyPort(ushort port)
+    {
+        p.curl.set(CurlOption.proxyport, cast(long) port);
+    }
+
+    /// Type of proxy
+    alias CurlProxy = etc.c.curl.CurlProxy;
+
+    /** Proxy type
+     *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
+     */
+    @property void proxyType(CurlProxy type)
+    {
+        p.curl.set(CurlOption.proxytype, cast(long) type);
+    }
+
+    /// DNS lookup timeout.
+    @property void dnsTimeout(Duration d)
+    {
+        p.curl.set(CurlOption.dns_cache_timeout, d.total!"msecs");
+    }
+
+    /**
+     * The network interface to use in form of the the IP of the interface.
+     *
+     * Example:
+     * ----
+     * theprotocol.netInterface = "192.168.1.32";
+     * theprotocol.netInterface = [ 192, 168, 1, 32 ];
+     * ----
+     *
+     * See: $(XREF socket, InternetAddress)
+     */
+    @property void netInterface(const(char)[] i)
+    {
+        p.curl.set(CurlOption.intrface, i);
+    }
+
+    /// ditto
+    @property void netInterface(const(ubyte)[4] i)
+    {
+        auto str = format("%d.%d.%d.%d", i[0], i[1], i[2], i[3]);
+        netInterface = str;
+    }
+
+    /// ditto
+    @property void netInterface(InternetAddress i)
+    {
+        netInterface = i.toAddrString();
+    }
+
+    /**
+       Set the local outgoing port to use.
+       Params:
+       port = the first outgoing port number to try and use
+    */
+    @property void localPort(ushort port)
+    {
+        p.curl.set(CurlOption.localport, cast(long)port);
+    }
+
+    /**
+       Set the local outgoing port range to use.
+       This can be used together with the localPort property.
+       Params:
+       range = if the first port is occupied then try this many
+               port number forwards
+    */
+    @property void localPortRange(ushort range)
+    {
+        p.curl.set(CurlOption.localportrange, cast(long)range);
+    }
+
+    /** Set the tcp no-delay socket option on or off.
+        See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
+    */
+    @property void tcpNoDelay(bool on)
+    {
+        p.curl.set(CurlOption.tcp_nodelay, cast(long) (on ? 1 : 0) );
+    }
+
+    /** Sets whether SSL peer certificates should be verified.
+        See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYPEER, verifypeer)
+    */
+    @property void verifyPeer(bool on)
+    {
+      p.curl.set(CurlOption.ssl_verifypeer, on ? 1 : 0);
+    }
+
+    /** Sets whether the host within an SSL certificate should be verified.
+        See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYHOST, verifypeer)
+    */
+    @property void verifyHost(bool on)
+    {
+      p.curl.set(CurlOption.ssl_verifyhost, on ? 2 : 0);
+    }
+
+    // Authentication settings
+
+    /**
+       Set the user name, password and optionally domain for authentication
+       purposes.
+
+       Some protocols may need authentication in some cases. Use this
+       function to provide credentials.
+
+       Params:
+       username = the username
+       password = the password
+       domain = used for NTLM authentication only and is set to the NTLM domain
+                name
+    */
+    void setAuthentication(const(char)[] username, const(char)[] password,
+                           const(char)[] domain = "")
+    {
+        if (!domain.empty)
+            username = format("%s/%s", domain, username);
+        p.curl.set(CurlOption.userpwd, format("%s:%s", username, password));
+    }
+
+    unittest
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq;
+            assert(req.hdrs.canFind("GET /"));
+            assert(req.hdrs.canFind("Basic dXNlcjpwYXNz"));
+            s.send(httpOK());
+        });
+
+        auto http = HTTP(testServer.addr);
+        http.onReceive = (ubyte[] data) { return data.length; };
+        http.setAuthentication("user", "pass");
+        http.perform();
+    }
+
+    /**
+       Set the user name and password for proxy authentication.
+
+       Params:
+       username = the username
+       password = the password
+    */
+    void setProxyAuthentication(const(char)[] username, const(char)[] password)
+    {
+        p.curl.set(CurlOption.proxyuserpwd,
+            format("%s:%s",
+                username.replace(":", "%3A"),
+                password.replace(":", "%3A"))
+        );
+    }
+
+    /**
+     * The event handler that gets called when data is needed for sending. The
+     * length of the $(D void[]) specifies the maximum number of bytes that can
+     * be sent.
+     *
+     * Returns:
+     * The callback returns the number of elements in the buffer that have been
+     * filled and are ready to send.
+     * The special value $(D .abortRequest) can be returned in order to abort the
+     * current request.
+     * The special value $(D .pauseRequest) can be returned in order to pause the
+     * current request.
+     *
+     * Example:
+     * ----
+     * import std.net.curl;
+     * string msg = "Hello world";
+     * auto client = HTTP("dlang.org");
+     * client.onSend = delegate size_t(void[] data)
+     * {
+     *     auto m = cast(void[])msg;
+     *     size_t length = m.length > data.length ? data.length : m.length;
+     *     if (length == 0) return 0;
+     *     data[0..length] = m[0..length];
+     *     msg = msg[length..$];
+     *     return length;
+     * };
+     * client.perform();
+     * ----
+     */
+    @property void onSend(size_t delegate(void[]) callback)
+    {
+        p.curl.clear(CurlOption.postfields); // cannot specify data when using callback
+        p.curl.onSend = callback;
+    }
+
+    /**
+      * The event handler that receives incoming data. Be sure to copy the
+      * incoming ubyte[] since it is not guaranteed to be valid after the
+      * callback returns.
+      *
+      * Returns:
+      * The callback returns the number of incoming bytes read. If the entire array is
+      * not read the request will abort.
+      * The special value .pauseRequest can be returned in order to pause the
+      * current request.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * auto client = HTTP("dlang.org");
+      * client.onReceive = (ubyte[] data)
+      * {
+      *     writeln("Got data", to!(const(char)[])(data));
+      *     return data.length;
+      * };
+      * client.perform();
+      * ----
+      */
+    @property void onReceive(size_t delegate(ubyte[]) callback)
+    {
+        p.curl.onReceive = callback;
+    }
+
+    /**
+      * The event handler that gets called to inform of upload/download progress.
+      *
+      * Params:
+      * dlTotal = total bytes to download
+      * dlNow = currently downloaded bytes
+      * ulTotal = total bytes to upload
+      * ulNow = currently uploaded bytes
+      *
+      * Returns:
+      * Return 0 from the callback to signal success, return non-zero to abort
+      *          transfer
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * auto client = HTTP("dlang.org");
+      * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult)
+      * {
+      *     writeln("Progress: downloaded ", dln, " of ", dl);
+      *     writeln("Progress: uploaded ", uln, " of ", ul);
+      * };
+      * client.perform();
+      * ----
+      */
+    @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
+                                           size_t ulTotal, size_t ulNow) callback)
+    {
+        p.curl.onProgress = callback;
+    }
+}
+
+/*
+  Decode $(D ubyte[]) array using the provided EncodingScheme up to maxChars
+  Returns: Tuple of ubytes read and the $(D Char[]) characters decoded.
+           Not all ubytes are guaranteed to be read in case of decoding error.
+*/
+private Tuple!(size_t,Char[])
+decodeString(Char = char)(const(ubyte)[] data,
+                          EncodingScheme scheme,
+                          size_t maxChars = size_t.max)
+{
+    Char[] res;
+    auto startLen = data.length;
+    size_t charsDecoded = 0;
+    while (data.length && charsDecoded < maxChars)
+    {
+        dchar dc = scheme.safeDecode(data);
+        if (dc == INVALID_SEQUENCE)
+        {
+            return typeof(return)(size_t.max, cast(Char[])null);
+        }
+        charsDecoded++;
+        res ~= dc;
+    }
+    return typeof(return)(startLen-data.length, res);
+}
+
+/*
+  Decode $(D ubyte[]) array using the provided $(D EncodingScheme) until a the
+  line terminator specified is found. The basesrc parameter is effectively
+  prepended to src as the first thing.
+
+  This function is used for decoding as much of the src buffer as
+  possible until either the terminator is found or decoding fails. If
+  it fails as the last data in the src it may mean that the src buffer
+  were missing some bytes in order to represent a correct code
+  point. Upon the next call to this function more bytes have been
+  received from net and the failing bytes should be given as the
+  basesrc parameter. It is done this way to minimize data copying.
+
+  Returns: true if a terminator was found
+           Not all ubytes are guaranteed to be read in case of decoding error.
+           any decoded chars will be inserted into dst.
+*/
+private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc,
+                                                     ref const(ubyte)[] src,
+                                                     ref Char[] dst,
+                                                     EncodingScheme scheme,
+                                                     Terminator terminator)
+{
+    auto startLen = src.length;
+    size_t charsDecoded = 0;
+    // if there is anything in the basesrc then try to decode that
+    // first.
+    if (basesrc.length != 0)
+    {
+        // Try to ensure 4 entries in the basesrc by copying from src.
+        auto blen = basesrc.length;
+        size_t len = (basesrc.length + src.length) >= 4 ?
+                     4 : basesrc.length + src.length;
+        basesrc.length = len;
+
+        dchar dc = scheme.safeDecode(basesrc);
+        if (dc == INVALID_SEQUENCE)
+        {
+            enforce!CurlException(len != 4, "Invalid code sequence");
+            return false;
+        }
+        dst ~= dc;
+        src = src[len-basesrc.length-blen .. $]; // remove used ubytes from src
+        basesrc.length = 0;
+    }
+
+    while (src.length)
+    {
+        auto lsrc = src;
+        dchar dc = scheme.safeDecode(src);
+        if (dc == INVALID_SEQUENCE)
+        {
+            if (src.empty)
+            {
+                // The invalid sequence was in the end of the src.  Maybe there
+                // just need to be more bytes available so these last bytes are
+                // put back to src for later use.
+                src = lsrc;
+                return false;
+            }
+            dc = '?';
+        }
+        dst ~= dc;
+
+        if (dst.endsWith(terminator))
+            return true;
+    }
+    return false; // no terminator found
+}
+
+/**
+  * HTTP client functionality.
+  *
+  * Example:
+  * ---
+  * import std.net.curl, std.stdio;
+  *
+  * // Get with custom data receivers
+  * auto http = HTTP("dlang.org");
+  * http.onReceiveHeader =
+  *     (in char[] key, in char[] value) { writeln(key ~ ": " ~ value); };
+  * http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; };
+  * http.perform();
+  *
+  * // Put with data senders
+  * auto msg = "Hello world";
+  * http.contentLength = msg.length;
+  * http.onSend = (void[] data)
+  * {
+  *     auto m = cast(void[])msg;
+  *     size_t len = m.length > data.length ? data.length : m.length;
+  *     if (len == 0) return len;
+  *     data[0..len] = m[0..len];
+  *     msg = msg[len..$];
+  *     return len;
+  * };
+  * http.perform();
+  *
+  * // Track progress
+  * http.method = HTTP.Method.get;
+  * http.url = "http://upload.wikimedia.org/wikipedia/commons/"
+  *            "5/53/Wikipedia-logo-en-big.png";
+  * http.onReceive = (ubyte[] data) { return data.length; };
+  * http.onProgress = (size_t dltotal, size_t dlnow,
+  *                    size_t ultotal, size_t ulnow)
+  * {
+  *     writeln("Progress ", dltotal, ", ", dlnow, ", ", ultotal, ", ", ulnow);
+  *     return 0;
+  * };
+  * http.perform();
+  * ---
+  *
+  * See_Also: $(WEB www.ietf.org/rfc/rfc2616.txt, RFC2616)
+  *
+  */
+struct HTTP
+{
+    mixin Protocol;
+
+    /// Authentication method equal to $(ECXREF curl, CurlAuth)
+    alias AuthMethod = CurlAuth;
+
+    static private uint defaultMaxRedirects = 10;
+
+    private struct Impl
+    {
+        ~this()
+        {
+            if (headersOut !is null)
+                Curl.curl.slist_free_all(headersOut);
+            if (curl.handle !is null) // work around RefCounted/emplace bug
+                curl.shutdown();
+        }
+        Curl curl;
+        curl_slist* headersOut;
+        string[string] headersIn;
+        string charset;
+
+        /// The status line of the final sub-request in a request.
+        StatusLine status;
+        private void delegate(StatusLine) onReceiveStatusLine;
+
+        /// The HTTP method to use.
+        Method method = Method.undefined;
+
+        @property void onReceiveHeader(void delegate(in char[] key,
+                                                     in char[] value) callback)
+        {
+            // Wrap incoming callback in order to separate http status line from
+            // http headers.  On redirected requests there may be several such
+            // status lines. The last one is the one recorded.
+            auto dg = (in char[] header)
+            {
+                import std.utf : UTFException;
+                try
+                {
+                    if (header.empty)
+                    {
+                        // header delimiter
+                        return;
+                    }
+                    if (header.startsWith("HTTP/"))
+                    {
+                        string[string] empty;
+                        headersIn = empty; // clear
+
+                        auto m = match(header, regex(r"^HTTP/(\d+)\.(\d+) (\d+) (.*)$"));
+                        if (m.empty)
+                        {
+                            // Invalid status line
+                        }
+                        else
+                        {
+                            status.majorVersion = to!ushort(m.captures[1]);
+                            status.minorVersion = to!ushort(m.captures[2]);
+                            status.code = to!ushort(m.captures[3]);
+                            status.reason = m.captures[4].idup;
+                            if (onReceiveStatusLine != null)
+                                onReceiveStatusLine(status);
+                        }
+                        return;
+                    }
+
+                    // Normal http header
+                    auto m = match(cast(char[]) header, regex("(.*?): (.*)$"));
+
+                    auto fieldName = m.captures[1].toLower().idup;
+                    if (fieldName == "content-type")
+                    {
+                        auto mct = match(cast(char[]) m.captures[2],
+                                         regex("charset=([^;]*)"));
+                        if (!mct.empty && mct.captures.length > 1)
+                            charset = mct.captures[1].idup;
+                    }
+
+                    if (!m.empty && callback !is null)
+                        callback(fieldName, m.captures[2]);
+                    headersIn[fieldName] = m.captures[2].idup;
+                }
+                catch(UTFException e)
+                {
+                    //munch it - a header should be all ASCII, any "wrong UTF" is broken header
+                }
+            };
+
+            curl.onReceiveHeader = dg;
+        }
+    }
+
+    private RefCounted!Impl p;
+
+    /** Time condition enumeration as an alias of $(ECXREF curl, CurlTimeCond)
+
+        $(WEB www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25)
+    */
+    alias TimeCond = CurlTimeCond;
+
+    /**
+       Constructor taking the url as parameter.
+    */
+    static HTTP opCall(const(char)[] url)
+    {
+        HTTP http;
+        http.initialize();
+        http.url = url;
+        return http;
+    }
+
+    static HTTP opCall()
+    {
+        HTTP http;
+        http.initialize();
+        return http;
+    }
+
+    HTTP dup()
+    {
+        HTTP copy;
+        copy.initialize();
+        copy.p.method = p.method;
+        curl_slist* cur = p.headersOut;
+        curl_slist* newlist = null;
+        while (cur)
+        {
+            newlist = Curl.curl.slist_append(newlist, cur.data);
+            cur = cur.next;
+        }
+        copy.p.headersOut = newlist;
+        copy.p.curl.set(CurlOption.httpheader, copy.p.headersOut);
+        copy.p.curl = p.curl.dup();
+        copy.dataTimeout = _defaultDataTimeout;
+        copy.onReceiveHeader = null;
+        return copy;
+    }
+
+    private void initialize()
+    {
+        p.curl.initialize();
+        maxRedirects = HTTP.defaultMaxRedirects;
+        p.charset = "ISO-8859-1"; // Default charset defined in HTTP RFC
+        p.method = Method.undefined;
+        setUserAgent(HTTP.defaultUserAgent);
+        dataTimeout = _defaultDataTimeout;
+        onReceiveHeader = null;
+        verifyPeer = true;
+        verifyHost = true;
+    }
+
+    /**
+       Perform a http request.
+
+       After the HTTP client has been setup and possibly assigned callbacks the
+       $(D perform()) method will start performing the request towards the
+       specified server.
+
+       Params:
+       throwOnError = whether to throw an exception or return a CurlCode on error
+    */
+    CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes)
+    {
+        p.status.reset();
+
+        CurlOption opt;
+        final switch (p.method)
+        {
+        case Method.head:
+            p.curl.set(CurlOption.nobody, 1L);
+            opt = CurlOption.nobody;
+            break;
+        case Method.undefined:
+        case Method.get:
+            p.curl.set(CurlOption.httpget, 1L);
+            opt = CurlOption.httpget;
+            break;
+        case Method.post:
+            p.curl.set(CurlOption.post, 1L);
+            opt = CurlOption.post;
+            break;
+        case Method.put:
+            p.curl.set(CurlOption.upload, 1L);
+            opt = CurlOption.upload;
+            break;
+        case Method.del:
+            p.curl.set(CurlOption.customrequest, "DELETE");
+            opt = CurlOption.customrequest;
+            break;
+        case Method.options:
+            p.curl.set(CurlOption.customrequest, "OPTIONS");
+            opt = CurlOption.customrequest;
+            break;
+        case Method.trace:
+            p.curl.set(CurlOption.customrequest, "TRACE");
+            opt = CurlOption.customrequest;
+            break;
+        case Method.connect:
+            p.curl.set(CurlOption.customrequest, "CONNECT");
+            opt = CurlOption.customrequest;
+            break;
+        case Method.patch:
+            p.curl.set(CurlOption.customrequest, "PATCH");
+            opt = CurlOption.customrequest;
+            break;
+        }
+
+        scope (exit) p.curl.clear(opt);
+        return p.curl.perform(throwOnError);
+    }
+
+    /// The URL to specify the location of the resource.
+    @property void url(const(char)[] url)
+    {
+        if (!startsWith(url.toLower(), "http://", "https://"))
+            url = "http://" ~ url;
+        p.curl.set(CurlOption.url, url);
+    }
+
+    /// Set the CA certificate bundle file to use for SSL peer verification
+    @property void caInfo(const(char)[] caFile)
+    {
+        p.curl.set(CurlOption.cainfo, caFile);
+    }
+
+    // This is a workaround for mixed in content not having its
+    // docs mixed in.
+    version (StdDdoc)
+    {
+        /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
+        /// pause a request
+        alias requestPause = CurlReadFunc.pause;
+
+        /// Value to return from onSend delegate in order to abort a request
+        alias requestAbort = CurlReadFunc.abort;
+
+        /**
+           True if the instance is stopped. A stopped instance is not usable.
+        */
+        @property bool isStopped();
+
+        /// Stop and invalidate this instance.
+        void shutdown();
+
+        /** Set verbose.
+            This will print request information to stderr.
+        */
+        @property void verbose(bool on);
+
+        // Connection settings
+
+        /// Set timeout for activity on connection.
+        @property void dataTimeout(Duration d);
+
+        /** Set maximum time an operation is allowed to take.
+            This includes dns resolution, connecting, data transfer, etc.
+          */
+        @property void operationTimeout(Duration d);
+
+        /// Set timeout for connecting.
+        @property void connectTimeout(Duration d);
+
+        // Network settings
+
+        /** Proxy
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
+         */
+        @property void proxy(const(char)[] host);
+
+        /** Proxy port
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
+         */
+        @property void proxyPort(ushort port);
+
+        /// Type of proxy
+        alias CurlProxy = etc.c.curl.CurlProxy;
+
+        /** Proxy type
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
+         */
+        @property void proxyType(CurlProxy type);
+
+        /// DNS lookup timeout.
+        @property void dnsTimeout(Duration d);
+
+        /**
+         * The network interface to use in form of the the IP of the interface.
+         *
+         * Example:
+         * ----
+         * theprotocol.netInterface = "192.168.1.32";
+         * theprotocol.netInterface = [ 192, 168, 1, 32 ];
+         * ----
+         *
+         * See: $(XREF socket, InternetAddress)
+         */
+        @property void netInterface(const(char)[] i);
+
+        /// ditto
+        @property void netInterface(const(ubyte)[4] i);
+
+        /// ditto
+        @property void netInterface(InternetAddress i);
+
+        /**
+           Set the local outgoing port to use.
+           Params:
+           port = the first outgoing port number to try and use
+        */
+        @property void localPort(ushort port);
+
+        /**
+           Set the local outgoing port range to use.
+           This can be used together with the localPort property.
+           Params:
+           range = if the first port is occupied then try this many
+           port number forwards
+        */
+        @property void localPortRange(ushort range);
+
+        /** Set the tcp no-delay socket option on or off.
+            See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
+        */
+        @property void tcpNoDelay(bool on);
+
+        // Authentication settings
+
+        /**
+           Set the user name, password and optionally domain for authentication
+           purposes.
+
+           Some protocols may need authentication in some cases. Use this
+           function to provide credentials.
+
+           Params:
+           username = the username
+           password = the password
+           domain = used for NTLM authentication only and is set to the NTLM domain
+           name
+        */
+        void setAuthentication(const(char)[] username, const(char)[] password,
+                               const(char)[] domain = "");
+
+        /**
+           Set the user name and password for proxy authentication.
+
+           Params:
+           username = the username
+           password = the password
+        */
+        void setProxyAuthentication(const(char)[] username, const(char)[] password);
+
+        /**
+         * The event handler that gets called when data is needed for sending. The
+         * length of the $(D void[]) specifies the maximum number of bytes that can
+         * be sent.
+         *
+         * Returns:
+         * The callback returns the number of elements in the buffer that have been
+         * filled and are ready to send.
+         * The special value $(D .abortRequest) can be returned in order to abort the
+         * current request.
+         * The special value $(D .pauseRequest) can be returned in order to pause the
+         * current request.
+         *
+         * Example:
+         * ----
+         * import std.net.curl;
+         * string msg = "Hello world";
+         * auto client = HTTP("dlang.org");
+         * client.onSend = delegate size_t(void[] data)
+         * {
+         *     auto m = cast(void[])msg;
+         *     size_t length = m.length > data.length ? data.length : m.length;
+         *     if (length == 0) return 0;
+         *     data[0..length] = m[0..length];
+         *     msg = msg[length..$];
+         *     return length;
+         * };
+         * client.perform();
+         * ----
+         */
+        @property void onSend(size_t delegate(void[]) callback);
+
+        /**
+         * The event handler that receives incoming data. Be sure to copy the
+         * incoming ubyte[] since it is not guaranteed to be valid after the
+         * callback returns.
+         *
+         * Returns:
+         * The callback returns the incoming bytes read. If not the entire array is
+         * the request will abort.
+         * The special value .pauseRequest can be returned in order to pause the
+         * current request.
+         *
+         * Example:
+         * ----
+         * import std.net.curl, std.stdio;
+         * auto client = HTTP("dlang.org");
+         * client.onReceive = (ubyte[] data)
+         * {
+         *     writeln("Got data", to!(const(char)[])(data));
+         *     return data.length;
+         * };
+         * client.perform();
+         * ----
+         */
+        @property void onReceive(size_t delegate(ubyte[]) callback);
+
+        /**
+         * Register an event handler that gets called to inform of
+         * upload/download progress.
+         *
+         * Callback_parameters:
+         * $(CALLBACK_PARAMS)
+         *
+         * Callback_returns: Return 0 to signal success, return non-zero to
+         * abort transfer.
+         *
+         * Example:
+         * ----
+         * import std.net.curl, std.stdio;
+         * auto client = HTTP("dlang.org");
+         * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult)
+         * {
+         *     writeln("Progress: downloaded ", dln, " of ", dl);
+         *     writeln("Progress: uploaded ", uln, " of ", ul);
+         * };
+         * client.perform();
+         * ----
+         */
+        @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
+                                               size_t ulTotal, size_t ulNow) callback);
+    }
+
+    /** Clear all outgoing headers.
+    */
+    void clearRequestHeaders()
+    {
+        if (p.headersOut !is null)
+            Curl.curl.slist_free_all(p.headersOut);
+        p.headersOut = null;
+        p.curl.clear(CurlOption.httpheader);
+    }
+
+    /** Add a header e.g. "X-CustomField: Something is fishy".
+     *
+     * There is no remove header functionality. Do a $(LREF clearRequestHeaders)
+     * and set the needed headers instead.
+     *
+     * Example:
+     * ---
+     * import std.net.curl;
+     * auto client = HTTP();
+     * client.addRequestHeader("X-Custom-ABC", "This is the custom value");
+     * auto content = get("dlang.org", client);
+     * ---
+     */
+    void addRequestHeader(const(char)[] name, const(char)[] value)
+    {
+        if (icmp(name, "User-Agent") == 0)
+            return setUserAgent(value);
+        string nv = format("%s: %s", name, value);
+        p.headersOut = Curl.curl.slist_append(p.headersOut,
+                                              nv.tempCString().buffPtr);
+        p.curl.set(CurlOption.httpheader, p.headersOut);
+    }
+
+    /**
+     * The default "User-Agent" value send with a request.
+     * It has the form "Phobos-std.net.curl/$(I PHOBOS_VERSION) (libcurl/$(I CURL_VERSION))"
+     */
+    static string defaultUserAgent() @property
+    {
+        import std.compiler : version_major, version_minor;
+
+        // http://curl.haxx.se/docs/versions.html
+        enum fmt = "Phobos-std.net.curl/%d.%03d (libcurl/%d.%d.%d)";
+        enum maxLen = fmt.length - "%d%03d%d%d%d".length + 10 + 10 + 3 + 3 + 3;
+
+        static char[maxLen] buf = void;
+        static string userAgent;
+
+        if (!userAgent.length)
+        {
+            auto curlVer = Curl.curl.version_info(CURLVERSION_NOW).version_num;
+            userAgent = cast(immutable)sformat(
+                buf, fmt, version_major, version_minor,
+                curlVer >> 16 & 0xFF, curlVer >> 8 & 0xFF, curlVer & 0xFF);
+        }
+        return userAgent;
+    }
+
+    /** Set the value of the user agent request header field.
+     *
+     * By default a request has it's "User-Agent" field set to $(LREF
+     * defaultUserAgent) even if $(D setUserAgent) was never called.  Pass
+     * an empty string to suppress the "User-Agent" field altogether.
+     */
+    void setUserAgent(const(char)[] userAgent)
+    {
+        p.curl.set(CurlOption.useragent, userAgent);
+    }
+
+    /** The headers read from a successful response.
+     *
+     */
+    @property string[string] responseHeaders()
+    {
+        return p.headersIn;
+    }
+
+    /// HTTP method used.
+    @property void method(Method m)
+    {
+        p.method = m;
+    }
+
+    /// ditto
+    @property Method method()
+    {
+        return p.method;
+    }
+
+    /**
+       HTTP status line of last response. One call to perform may
+       result in several requests because of redirection.
+    */
+    @property StatusLine statusLine()
+    {
+        return p.status;
+    }
+
+    /// Set the active cookie string e.g. "name1=value1;name2=value2"
+    void setCookie(const(char)[] cookie)
+    {
+        p.curl.set(CurlOption.cookie, cookie);
+    }
+
+    /// Set a file path to where a cookie jar should be read/stored.
+    void setCookieJar(const(char)[] path)
+    {
+        p.curl.set(CurlOption.cookiefile, path);
+        if (path.length)
+            p.curl.set(CurlOption.cookiejar, path);
+    }
+
+    /// Flush cookie jar to disk.
+    void flushCookieJar()
+    {
+        p.curl.set(CurlOption.cookielist, "FLUSH");
+    }
+
+    /// Clear session cookies.
+    void clearSessionCookies()
+    {
+        p.curl.set(CurlOption.cookielist, "SESS");
+    }
+
+    /// Clear all cookies.
+    void clearAllCookies()
+    {
+        p.curl.set(CurlOption.cookielist, "ALL");
+    }
+
+    /**
+       Set time condition on the request.
+
+       Params:
+       cond =  $(D CurlTimeCond.{none,ifmodsince,ifunmodsince,lastmod})
+       timestamp = Timestamp for the condition
+
+       $(WEB www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25)
+    */
+    void setTimeCondition(HTTP.TimeCond cond, SysTime timestamp)
+    {
+        p.curl.set(CurlOption.timecondition, cond);
+        p.curl.set(CurlOption.timevalue, timestamp.toUnixTime());
+    }
+
+    /** Specifying data to post when not using the onSend callback.
+      *
+      * The data is NOT copied by the library.  Content-Type will default to
+      * application/octet-stream.  Data is not converted or encoded by this
+      * method.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * auto http = HTTP("http://www.mydomain.com");
+      * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
+      * http.postData = [1,2,3,4,5];
+      * http.perform();
+      * ----
+      */
+    @property void postData(const(void)[] data)
+    {
+        setPostData(data, "application/octet-stream");
+    }
+
+    /** Specifying data to post when not using the onSend callback.
+      *
+      * The data is NOT copied by the library.  Content-Type will default to
+      * text/plain.  Data is not converted or encoded by this method.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * auto http = HTTP("http://www.mydomain.com");
+      * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
+      * http.postData = "The quick....";
+      * http.perform();
+      * ----
+      */
+    @property void postData(const(char)[] data)
+    {
+        setPostData(data, "text/plain");
+    }
+
+    /**
+     * Specify data to post when not using the onSend callback, with
+     * user-specified Content-Type.
+     * Params:
+     *  data = Data to post.
+     *  contentType = MIME type of the data, for example, "text/plain" or
+     *      "application/octet-stream". See also:
+     *      $(LINK2 http://en.wikipedia.org/wiki/Internet_media_type,
+     *      Internet media type) on Wikipedia.
+     * -----
+     * import std.net.curl;
+     * auto http = HTTP("http://onlineform.example.com");
+     * auto data = "app=login&username=bob&password=s00perS3kret";
+     * http.setPostData(data, "application/x-www-form-urlencoded");
+     * http.onReceive = (ubyte[] data) { return data.length; };
+     * http.perform();
+     * -----
+     */
+    void setPostData(const(void)[] data, string contentType)
+    {
+        // cannot use callback when specifying data directly so it is disabled here.
+        p.curl.clear(CurlOption.readfunction);
+        addRequestHeader("Content-Type", contentType);
+        p.curl.set(CurlOption.postfields, cast(void*)data.ptr);
+        p.curl.set(CurlOption.postfieldsize, data.length);
+        if (method == Method.undefined)
+            method = Method.post;
+    }
+
+    unittest
+    {
+        testServer.handle((s) {
+            auto req = s.recvReq!ubyte;
+            assert(req.hdrs.canFind("POST /path"));
+            assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4]));
+            assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255]));
+            s.send(httpOK(cast(ubyte[])[17, 27, 35, 41]));
+        });
+        auto data = new ubyte[](256);
+        foreach (i, ref ub; data)
+            ub = cast(ubyte)i;
+
+        auto http = HTTP(testServer.addr~"/path");
+        http.postData = data;
+        ubyte[] res;
+        http.onReceive = (data) { res ~= data; return data.length; };
+        http.perform();
+        assert(res == cast(ubyte[])[17, 27, 35, 41]);
+    }
+
+    /**
+      * Set the event handler that receives incoming headers.
+      *
+      * The callback will receive a header field key, value as parameter. The
+      * $(D const(char)[]) arrays are not valid after the delegate has returned.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * auto http = HTTP("dlang.org");
+      * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
+      * http.onReceiveHeader = (in char[] key, in char[] value) { writeln(key, " = ", value); };
+      * http.perform();
+      * ----
+      */
+    @property void onReceiveHeader(void delegate(in char[] key,
+                                                 in char[] value) callback)
+    {
+        p.onReceiveHeader = callback;
+    }
+
+    /**
+       Callback for each received StatusLine.
+
+       Notice that several callbacks can be done for each call to
+       $(D perform()) due to redirections.
+
+       See_Also: $(LREF StatusLine)
+     */
+    @property void onReceiveStatusLine(void delegate(StatusLine) callback)
+    {
+        p.onReceiveStatusLine = callback;
+    }
+
+    /**
+       The content length in bytes when using request that has content
+       e.g. POST/PUT and not using chunked transfer. Is set as the
+       "Content-Length" header.  Set to ulong.max to reset to chunked transfer.
+    */
+    @property void contentLength(ulong len)
+    {
+        CurlOption lenOpt;
+
+        // Force post if necessary
+        if (p.method != Method.put && p.method != Method.post &&
+            p.method != Method.patch)
+            p.method = Method.post;
+
+        if (p.method == Method.post || p.method == Method.patch)
+            lenOpt = CurlOption.postfieldsize_large;
+        else
+            lenOpt = CurlOption.infilesize_large;
+
+        if (size_t.max != ulong.max && len == size_t.max)
+            len = ulong.max; // check size_t.max for backwards compat, turn into error
+
+        if (len == ulong.max)
+        {
+            // HTTP 1.1 supports requests with no length header set.
+            addRequestHeader("Transfer-Encoding", "chunked");
+            addRequestHeader("Expect", "100-continue");
+        }
+        else
+        {
+            p.curl.set(lenOpt, to!curl_off_t(len));
+        }
+    }
+
+    /**
+       Authentication method as specified in $(LREF AuthMethod).
+    */
+    @property void authenticationMethod(AuthMethod authMethod)
+    {
+        p.curl.set(CurlOption.httpauth, cast(long) authMethod);
+    }
+
+    /**
+       Set max allowed redirections using the location header.
+       uint.max for infinite.
+    */
+    @property void maxRedirects(uint maxRedirs)
+    {
+        if (maxRedirs == uint.max)
+        {
+            // Disable
+            p.curl.set(CurlOption.followlocation, 0);
+        }
+        else
+        {
+            p.curl.set(CurlOption.followlocation, 1);
+            p.curl.set(CurlOption.maxredirs, maxRedirs);
+        }
+    }
+
+    /** <a name="HTTP.Method"/>The standard HTTP methods :
+     *  $(WEB www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1, _RFC2616 Section 5.1.1)
+     */
+    enum Method
+    {
+        undefined,
+        head, ///
+        get,  ///
+        post, ///
+        put,  ///
+        del,  ///
+        options, ///
+        trace,   ///
+        connect,  ///
+        patch, ///
+    }
+
+    /**
+       HTTP status line ie. the first line returned in an HTTP response.
+
+       If authentication or redirections are done then the status will be for
+       the last response received.
+    */
+    struct StatusLine
+    {
+        ushort majorVersion; /// Major HTTP version ie. 1 in HTTP/1.0.
+        ushort minorVersion; /// Minor HTTP version ie. 0 in HTTP/1.0.
+        ushort code;         /// HTTP status line code e.g. 200.
+        string reason;       /// HTTP status line reason string.
+
+        /// Reset this status line
+        @safe void reset()
+        {
+            majorVersion = 0;
+            minorVersion = 0;
+            code = 0;
+            reason = "";
+        }
+
+        ///
+        string toString()
+        {
+            return format("%s %s (%s.%s)",
+                          code, reason, majorVersion, minorVersion);
+        }
+    }
+
+} // HTTP
+
+/**
+   FTP client functionality.
+
+   See_Also: $(WEB tools.ietf.org/html/rfc959, RFC959)
+*/
+struct FTP
+{
+
+    mixin Protocol;
+
+    private struct Impl
+    {
+        ~this()
+        {
+            if (commands !is null)
+                Curl.curl.slist_free_all(commands);
+            if (curl.handle !is null) // work around RefCounted/emplace bug
+                curl.shutdown();
+        }
+        curl_slist* commands;
+        Curl curl;
+        string encoding;
+    }
+
+    private RefCounted!Impl p;
+
+    /**
+       FTP access to the specified url.
+    */
+    static FTP opCall(const(char)[] url)
+    {
+        FTP ftp;
+        ftp.initialize();
+        ftp.url = url;
+        return ftp;
+    }
+
+    static FTP opCall()
+    {
+        FTP ftp;
+        ftp.initialize();
+        return ftp;
+    }
+
+    FTP dup()
+    {
+        FTP copy = FTP();
+        copy.initialize();
+        copy.p.encoding = p.encoding;
+        copy.p.curl = p.curl.dup();
+        curl_slist* cur = p.commands;
+        curl_slist* newlist = null;
+        while (cur)
+        {
+            newlist = Curl.curl.slist_append(newlist, cur.data);
+            cur = cur.next;
+        }
+        copy.p.commands = newlist;
+        copy.p.curl.set(CurlOption.postquote, copy.p.commands);
+        copy.dataTimeout = _defaultDataTimeout;
+        return copy;
+    }
+
+    private void initialize()
+    {
+        p.curl.initialize();
+        p.encoding = "ISO-8859-1";
+        dataTimeout = _defaultDataTimeout;
+    }
+
+    /**
+       Performs the ftp request as it has been configured.
+
+       After a FTP client has been setup and possibly assigned callbacks the $(D
+       perform()) method will start performing the actual communication with the
+       server.
+
+       Params:
+       throwOnError = whether to throw an exception or return a CurlCode on error
+    */
+    CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes)
+    {
+        return p.curl.perform(throwOnError);
+    }
+
+    /// The URL to specify the location of the resource.
+    @property void url(const(char)[] url)
+    {
+        if (!startsWith(url.toLower(), "ftp://", "ftps://"))
+            url = "ftp://" ~ url;
+        p.curl.set(CurlOption.url, url);
+    }
+
+    // This is a workaround for mixed in content not having its
+    // docs mixed in.
+    version (StdDdoc)
+    {
+        /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
+        /// pause a request
+        alias requestPause = CurlReadFunc.pause;
+
+        /// Value to return from onSend delegate in order to abort a request
+        alias requestAbort = CurlReadFunc.abort;
+
+        /**
+           True if the instance is stopped. A stopped instance is not usable.
+        */
+        @property bool isStopped();
+
+        /// Stop and invalidate this instance.
+        void shutdown();
+
+        /** Set verbose.
+            This will print request information to stderr.
+        */
+        @property void verbose(bool on);
+
+        // Connection settings
+
+        /// Set timeout for activity on connection.
+        @property void dataTimeout(Duration d);
+
+        /** Set maximum time an operation is allowed to take.
+            This includes dns resolution, connecting, data transfer, etc.
+          */
+        @property void operationTimeout(Duration d);
+
+        /// Set timeout for connecting.
+        @property void connectTimeout(Duration d);
+
+        // Network settings
+
+        /** Proxy
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
+         */
+        @property void proxy(const(char)[] host);
+
+        /** Proxy port
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
+         */
+        @property void proxyPort(ushort port);
+
+        /// Type of proxy
+        alias CurlProxy = etc.c.curl.CurlProxy;
+
+        /** Proxy type
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
+         */
+        @property void proxyType(CurlProxy type);
+
+        /// DNS lookup timeout.
+        @property void dnsTimeout(Duration d);
+
+        /**
+         * The network interface to use in form of the the IP of the interface.
+         *
+         * Example:
+         * ----
+         * theprotocol.netInterface = "192.168.1.32";
+         * theprotocol.netInterface = [ 192, 168, 1, 32 ];
+         * ----
+         *
+         * See: $(XREF socket, InternetAddress)
+         */
+        @property void netInterface(const(char)[] i);
+
+        /// ditto
+        @property void netInterface(const(ubyte)[4] i);
+
+        /// ditto
+        @property void netInterface(InternetAddress i);
+
+        /**
+           Set the local outgoing port to use.
+           Params:
+           port = the first outgoing port number to try and use
+        */
+        @property void localPort(ushort port);
+
+        /**
+           Set the local outgoing port range to use.
+           This can be used together with the localPort property.
+           Params:
+           range = if the first port is occupied then try this many
+           port number forwards
+        */
+        @property void localPortRange(ushort range);
+
+        /** Set the tcp no-delay socket option on or off.
+            See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
+        */
+        @property void tcpNoDelay(bool on);
+
+        // Authentication settings
+
+        /**
+           Set the user name, password and optionally domain for authentication
+           purposes.
+
+           Some protocols may need authentication in some cases. Use this
+           function to provide credentials.
+
+           Params:
+           username = the username
+           password = the password
+           domain = used for NTLM authentication only and is set to the NTLM domain
+           name
+        */
+        void setAuthentication(const(char)[] username, const(char)[] password,
+                               const(char)[] domain = "");
+
+        /**
+           Set the user name and password for proxy authentication.
+
+           Params:
+           username = the username
+           password = the password
+        */
+        void setProxyAuthentication(const(char)[] username, const(char)[] password);
+
+        /**
+         * The event handler that gets called when data is needed for sending. The
+         * length of the $(D void[]) specifies the maximum number of bytes that can
+         * be sent.
+         *
+         * Returns:
+         * The callback returns the number of elements in the buffer that have been
+         * filled and are ready to send.
+         * The special value $(D .abortRequest) can be returned in order to abort the
+         * current request.
+         * The special value $(D .pauseRequest) can be returned in order to pause the
+         * current request.
+         *
+         */
+        @property void onSend(size_t delegate(void[]) callback);
+
+        /**
+         * The event handler that receives incoming data. Be sure to copy the
+         * incoming ubyte[] since it is not guaranteed to be valid after the
+         * callback returns.
+         *
+         * Returns:
+         * The callback returns the incoming bytes read. If not the entire array is
+         * the request will abort.
+         * The special value .pauseRequest can be returned in order to pause the
+         * current request.
+         *
+         */
+        @property void onReceive(size_t delegate(ubyte[]) callback);
+
+        /**
+         * The event handler that gets called to inform of upload/download progress.
+         *
+         * Callback_parameters:
+         * $(CALLBACK_PARAMS)
+         *
+         * Callback_returns:
+         * Return 0 from the callback to signal success, return non-zero to
+         * abort transfer.
+         */
+        @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
+                                               size_t ulTotal, size_t ulNow) callback);
+    }
+
+    /** Clear all commands send to ftp server.
+    */
+    void clearCommands()
+    {
+        if (p.commands !is null)
+            Curl.curl.slist_free_all(p.commands);
+        p.commands = null;
+        p.curl.clear(CurlOption.postquote);
+    }
+
+    /** Add a command to send to ftp server.
+     *
+     * There is no remove command functionality. Do a $(LREF clearCommands) and
+     * set the needed commands instead.
+     *
+     * Example:
+     * ---
+     * import std.net.curl;
+     * auto client = FTP();
+     * client.addCommand("RNFR my_file.txt");
+     * client.addCommand("RNTO my_renamed_file.txt");
+     * upload("my_file.txt", "ftp.digitalmars.com", client);
+     * ---
+     */
+    void addCommand(const(char)[] command)
+    {
+        p.commands = Curl.curl.slist_append(p.commands,
+                                            command.tempCString().buffPtr);
+        p.curl.set(CurlOption.postquote, p.commands);
+    }
+
+    /// Connection encoding. Defaults to ISO-8859-1.
+    @property void encoding(string name)
+    {
+        p.encoding = name;
+    }
+
+    /// ditto
+    @property string encoding()
+    {
+        return p.encoding;
+    }
+
+    /**
+       The content length in bytes of the ftp data.
+    */
+    @property void contentLength(ulong len)
+    {
+        p.curl.set(CurlOption.infilesize_large, to!curl_off_t(len));
+    }
+}
+
+/**
+  * Basic SMTP protocol support.
+  *
+  * Example:
+  * ---
+  * import std.net.curl;
+  *
+  * // Send an email with SMTPS
+  * auto smtp = SMTP("smtps://smtp.gmail.com");
+  * smtp.setAuthentication("from.addr@gmail.com", "password");
+  * smtp.mailTo = ["<to.addr@gmail.com>"];
+  * smtp.mailFrom = "<from.addr@gmail.com>";
+  * smtp.message = "Example Message";
+  * smtp.perform();
+  * ---
+  *
+  * See_Also: $(WEB www.ietf.org/rfc/rfc2821.txt, RFC2821)
+  */
+struct SMTP
+{
+    mixin Protocol;
+
+    private struct Impl
+    {
+        ~this()
+        {
+            if (curl.handle !is null) // work around RefCounted/emplace bug
+                curl.shutdown();
+        }
+        Curl curl;
+
+        @property void message(string msg)
+        {
+            auto _message = msg;
+            /**
+                This delegate reads the message text and copies it.
+            */
+            curl.onSend = delegate size_t(void[] data)
+            {
+                if (!msg.length) return 0;
+                auto m = cast(void[])msg;
+                size_t to_copy = min(data.length, _message.length);
+                data[0..to_copy] = (cast(void[])_message)[0..to_copy];
+                _message = _message[to_copy..$];
+                return to_copy;
+            };
+        }
+    }
+
+    private RefCounted!Impl p;
+
+    /**
+        Sets to the URL of the SMTP server.
+    */
+    static SMTP opCall(const(char)[] url)
+    {
+        SMTP smtp;
+        smtp.initialize();
+        smtp.url = url;
+        return smtp;
+    }
+
+    static SMTP opCall()
+    {
+        SMTP smtp;
+        smtp.initialize();
+        return smtp;
+    }
+
+    /+ TODO: The other structs have this function.
+    SMTP dup()
+    {
+        SMTP copy = SMTP();
+        copy.initialize();
+        copy.p.encoding = p.encoding;
+        copy.p.curl = p.curl.dup();
+        curl_slist* cur = p.commands;
+        curl_slist* newlist = null;
+        while (cur)
+        {
+            newlist = Curl.curl.slist_append(newlist, cur.data);
+            cur = cur.next;
+        }
+        copy.p.commands = newlist;
+        copy.p.curl.set(CurlOption.postquote, copy.p.commands);
+        copy.dataTimeout = _defaultDataTimeout;
+        return copy;
+    }
+    +/
+
+    /**
+        Performs the request as configured.
+        Params:
+        throwOnError = whether to throw an exception or return a CurlCode on error
+    */
+    CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes)
+    {
+        return p.curl.perform(throwOnError);
+    }
+
+    /// The URL to specify the location of the resource.
+    @property void url(const(char)[] url)
+    {
+        auto lowered = url.toLower();
+
+        if (lowered.startsWith("smtps://"))
+        {
+            p.curl.set(CurlOption.use_ssl, CurlUseSSL.all);
+        }
+        else
+        {
+            enforce!CurlException(lowered.startsWith("smtp://"),
+                                    "The url must be for the smtp protocol.");
+        }
+        p.curl.set(CurlOption.url, url);
+    }
+
+    private void initialize()
+    {
+        p.curl.initialize();
+        p.curl.set(CurlOption.upload, 1L);
+        dataTimeout = _defaultDataTimeout;
+        verifyPeer = true;
+        verifyHost = true;
+    }
+
+    // This is a workaround for mixed in content not having its
+    // docs mixed in.
+    version (StdDdoc)
+    {
+        /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
+        /// pause a request
+        alias requestPause = CurlReadFunc.pause;
+
+        /// Value to return from onSend delegate in order to abort a request
+        alias requestAbort = CurlReadFunc.abort;
+
+        /**
+           True if the instance is stopped. A stopped instance is not usable.
+        */
+        @property bool isStopped();
+
+        /// Stop and invalidate this instance.
+        void shutdown();
+
+        /** Set verbose.
+            This will print request information to stderr.
+        */
+        @property void verbose(bool on);
+
+        // Connection settings
+
+        /// Set timeout for activity on connection.
+        @property void dataTimeout(Duration d);
+
+        /** Set maximum time an operation is allowed to take.
+            This includes dns resolution, connecting, data transfer, etc.
+          */
+        @property void operationTimeout(Duration d);
+
+        /// Set timeout for connecting.
+        @property void connectTimeout(Duration d);
+
+        // Network settings
+
+        /** Proxy
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
+         */
+        @property void proxy(const(char)[] host);
+
+        /** Proxy port
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
+         */
+        @property void proxyPort(ushort port);
+
+        /// Type of proxy
+        alias CurlProxy = etc.c.curl.CurlProxy;
+
+        /** Proxy type
+         *  See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
+         */
+        @property void proxyType(CurlProxy type);
+
+        /// DNS lookup timeout.
+        @property void dnsTimeout(Duration d);
+
+        /**
+         * The network interface to use in form of the the IP of the interface.
+         *
+         * Example:
+         * ----
+         * theprotocol.netInterface = "192.168.1.32";
+         * theprotocol.netInterface = [ 192, 168, 1, 32 ];
+         * ----
+         *
+         * See: $(XREF socket, InternetAddress)
+         */
+        @property void netInterface(const(char)[] i);
+
+        /// ditto
+        @property void netInterface(const(ubyte)[4] i);
+
+        /// ditto
+        @property void netInterface(InternetAddress i);
+
+        /**
+           Set the local outgoing port to use.
+           Params:
+           port = the first outgoing port number to try and use
+        */
+        @property void localPort(ushort port);
+
+        /**
+           Set the local outgoing port range to use.
+           This can be used together with the localPort property.
+           Params:
+           range = if the first port is occupied then try this many
+           port number forwards
+        */
+        @property void localPortRange(ushort range);
+
+        /** Set the tcp no-delay socket option on or off.
+            See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
+        */
+        @property void tcpNoDelay(bool on);
+
+        // Authentication settings
+
+        /**
+           Set the user name, password and optionally domain for authentication
+           purposes.
+
+           Some protocols may need authentication in some cases. Use this
+           function to provide credentials.
+
+           Params:
+           username = the username
+           password = the password
+           domain = used for NTLM authentication only and is set to the NTLM domain
+           name
+        */
+        void setAuthentication(const(char)[] username, const(char)[] password,
+                               const(char)[] domain = "");
+
+        /**
+           Set the user name and password for proxy authentication.
+
+           Params:
+           username = the username
+           password = the password
+        */
+        void setProxyAuthentication(const(char)[] username, const(char)[] password);
+
+        /**
+         * The event handler that gets called when data is needed for sending. The
+         * length of the $(D void[]) specifies the maximum number of bytes that can
+         * be sent.
+         *
+         * Returns:
+         * The callback returns the number of elements in the buffer that have been
+         * filled and are ready to send.
+         * The special value $(D .abortRequest) can be returned in order to abort the
+         * current request.
+         * The special value $(D .pauseRequest) can be returned in order to pause the
+         * current request.
+         */
+        @property void onSend(size_t delegate(void[]) callback);
+
+        /**
+         * The event handler that receives incoming data. Be sure to copy the
+         * incoming ubyte[] since it is not guaranteed to be valid after the
+         * callback returns.
+         *
+         * Returns:
+         * The callback returns the incoming bytes read. If not the entire array is
+         * the request will abort.
+         * The special value .pauseRequest can be returned in order to pause the
+         * current request.
+         */
+        @property void onReceive(size_t delegate(ubyte[]) callback);
+
+        /**
+         * The event handler that gets called to inform of upload/download progress.
+         *
+         * Callback_parameters:
+         * $(CALLBACK_PARAMS)
+         *
+         * Callback_returns:
+         * Return 0 from the callback to signal success, return non-zero to
+         * abort transfer.
+         */
+        @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
+                                               size_t ulTotal, size_t ulNow) callback);
+    }
+
+    /**
+        Setter for the sender's email address.
+    */
+    @property void mailFrom()(const(char)[] sender)
+    {
+        assert(!sender.empty, "Sender must not be empty");
+        p.curl.set(CurlOption.mail_from, sender);
+    }
+
+    /**
+        Setter for the recipient email addresses.
+    */
+    void mailTo()(const(char)[][] recipients...)
+    {
+        assert(!recipients.empty, "Recipient must not be empty");
+        curl_slist* recipients_list = null;
+        foreach(recipient; recipients)
+        {
+            recipients_list =
+                Curl.curl.slist_append(recipients_list,
+                                  recipient.tempCString().buffPtr);
+        }
+        p.curl.set(CurlOption.mail_rcpt, recipients_list);
+    }
+
+    /**
+        Sets the message body text.
+    */
+
+    @property void message(string msg)
+    {
+        p.message = msg;
+    }
+}
+
+/++
+    Exception thrown on errors in std.net.curl functions.
++/
+class CurlException : Exception
+{
+    /++
+        Params:
+            msg  = The message for the exception.
+            file = The file where the exception occurred.
+            line = The line number where the exception occurred.
+            next = The previous exception in the chain of exceptions, if any.
+      +/
+    @safe pure nothrow
+    this(string msg,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         Throwable next = null)
+    {
+        super(msg, file, line, next);
+    }
+}
+
+/++
+    Exception thrown on timeout errors in std.net.curl functions.
++/
+class CurlTimeoutException : CurlException
+{
+    /++
+        Params:
+            msg  = The message for the exception.
+            file = The file where the exception occurred.
+            line = The line number where the exception occurred.
+            next = The previous exception in the chain of exceptions, if any.
+      +/
+    @safe pure nothrow
+    this(string msg,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         Throwable next = null)
+    {
+        super(msg, file, line, next);
+    }
+}
+
+/// Equal to $(ECXREF curl, CURLcode)
+alias CurlCode = CURLcode;
+
+import std.typecons : Flag;
+/// Flag to specify whether or not an exception is thrown on error.
+alias ThrowOnError = Flag!"throwOnError";
+
+private struct CurlAPI
+{
+    static struct API
+    {
+    extern(C):
+        import core.stdc.config : c_long;
+        CURLcode function(c_long flags) global_init;
+        void function() global_cleanup;
+        curl_version_info_data * function(CURLversion) version_info;
+        CURL* function() easy_init;
+        CURLcode function(CURL *curl, CURLoption option,...) easy_setopt;
+        CURLcode function(CURL *curl) easy_perform;
+        CURL* function(CURL *curl) easy_duphandle;
+        char* function(CURLcode) easy_strerror;
+        CURLcode function(CURL *handle, int bitmask) easy_pause;
+        void function(CURL *curl) easy_cleanup;
+        curl_slist* function(curl_slist *, char *) slist_append;
+        void function(curl_slist *) slist_free_all;
+    }
+    __gshared API _api;
+    __gshared void* _handle;
+
+    static ref API instance() @property
+    {
+        import std.concurrency;
+        initOnce!_handle(loadAPI());
+        return _api;
+    }
+
+    static void* loadAPI()
+    {
+        version (Posix)
+        {
+            import core.sys.posix.dlfcn;
+            alias loadSym = dlsym;
+        }
+        else version (Windows)
+        {
+            import core.sys.windows.windows;
+            alias loadSym = GetProcAddress;
+        }
+        else
+            static assert(0, "unimplemented");
+
+        void* handle;
+        version (Posix)
+            handle = dlopen(null, RTLD_LAZY);
+        else version (Windows)
+            handle = GetModuleHandleA(null);
+        assert(handle !is null);
+
+        // try to load curl from the executable to allow static linking
+        if (loadSym(handle, "curl_global_init") is null)
+        {
+            version (Posix)
+                dlclose(handle);
+
+            version (OSX)
+                static immutable names = ["libcurl.4.dylib"];
+            else version (Posix)
+                static immutable names = ["libcurl.so", "libcurl.so.4", "libcurl-gnutls.so.4", "libcurl-nss.so.4", "libcurl.so.3"];
+            else version (Windows)
+                static immutable names = ["libcurl.dll", "curl.dll"];
+
+            foreach (name; names)
+            {
+                version (Posix)
+                    handle = dlopen(name.ptr, RTLD_LAZY);
+                else version (Windows)
+                    handle = LoadLibraryA(name.ptr);
+                if (handle !is null) break;
+            }
+
+            enforce!CurlException(handle !is null, "Failed to load curl, tried %(%s, %).".format(names));
+        }
+
+        foreach (i, FP; typeof(API.tupleof))
+        {
+            enum name = __traits(identifier, _api.tupleof[i]);
+            auto p = enforce!CurlException(loadSym(handle, "curl_"~name),
+                                           "Couldn't load curl_"~name~" from libcurl.");
+            _api.tupleof[i] = cast(FP) p;
+        }
+
+        enforce!CurlException(!_api.global_init(CurlGlobal.all),
+                              "Failed to initialize libcurl");
+
+        return handle;
+    }
+
+    shared static ~this()
+    {
+        if (_handle is null) return;
+
+        _api.global_cleanup();
+        version (Posix)
+        {
+            import core.sys.posix.dlfcn;
+            dlclose(_handle);
+        }
+        else version (Windows)
+        {
+            import core.sys.windows.windows;
+            FreeLibrary(_handle);
+        }
+        else
+            static assert(0, "unimplemented");
+
+        _api = API.init;
+        _handle = null;
+    }
+}
+
+/**
+  Wrapper to provide a better interface to libcurl than using the plain C API.
+  It is recommended to use the $(D HTTP)/$(D FTP) etc. structs instead unless
+  raw access to libcurl is needed.
+
+  Warning: This struct uses interior pointers for callbacks. Only allocate it
+  on the stack if you never move or copy it. This also means passing by reference
+  when passing Curl to other functions. Otherwise always allocate on
+  the heap.
+*/
+struct Curl
+{
+    alias OutData = void[];
+    alias InData = ubyte[];
+    bool stopped;
+
+    private static auto ref curl() @property { return CurlAPI.instance; }
+
+    // A handle should not be used by two threads simultaneously
+    private CURL* handle;
+
+    // May also return $(D CURL_READFUNC_ABORT) or $(D CURL_READFUNC_PAUSE)
+    private size_t delegate(OutData) _onSend;
+    private size_t delegate(InData) _onReceive;
+    private void delegate(in char[]) _onReceiveHeader;
+    private CurlSeek delegate(long,CurlSeekPos) _onSeek;
+    private int delegate(curl_socket_t,CurlSockType) _onSocketOption;
+    private int delegate(size_t dltotal, size_t dlnow,
+                         size_t ultotal, size_t ulnow) _onProgress;
+
+    alias requestPause = CurlReadFunc.pause;
+    alias requestAbort = CurlReadFunc.abort;
+
+    /**
+       Initialize the instance by creating a working curl handle.
+    */
+    void initialize()
+    {
+        enforce!CurlException(!handle, "Curl instance already initialized");
+        handle = curl.easy_init();
+        enforce!CurlException(handle, "Curl instance couldn't be initialized");
+        stopped = false;
+        set(CurlOption.nosignal, 1);
+    }
+
+    /**
+       Duplicate this handle.
+
+       The new handle will have all options set as the one it was duplicated
+       from. An exception to this is that all options that cannot be shared
+       across threads are reset thereby making it safe to use the duplicate
+       in a new thread.
+    */
+    Curl dup()
+    {
+        Curl copy;
+        copy.handle = curl.easy_duphandle(handle);
+        copy.stopped = false;
+
+        with (CurlOption) {
+            auto tt = AliasSeq!(file, writefunction, writeheader,
+                headerfunction, infile, readfunction, ioctldata, ioctlfunction,
+                seekdata, seekfunction, sockoptdata, sockoptfunction,
+                opensocketdata, opensocketfunction, progressdata,
+                progressfunction, debugdata, debugfunction, interleavedata,
+                interleavefunction, chunk_data, chunk_bgn_function,
+                chunk_end_function, fnmatch_data, fnmatch_function, cookiejar, postfields);
+
+            foreach(option; tt)
+                copy.clear(option);
+        }
+
+        // The options are only supported by libcurl when it has been built
+        // against certain versions of OpenSSL - if your libcurl uses an old
+        // OpenSSL, or uses an entirely different SSL engine, attempting to
+        // clear these normally will raise an exception
+        copy.clearIfSupported(CurlOption.ssl_ctx_function);
+        copy.clearIfSupported(CurlOption.ssh_keydata);
+
+        // Enable for curl version > 7.21.7
+        static if (LIBCURL_VERSION_MAJOR >= 7 &&
+                   LIBCURL_VERSION_MINOR >= 21 &&
+                   LIBCURL_VERSION_PATCH >= 7)
+        {
+            copy.clear(CurlOption.closesocketdata);
+            copy.clear(CurlOption.closesocketfunction);
+        }
+
+        copy.set(CurlOption.nosignal, 1);
+
+        // copy.clear(CurlOption.ssl_ctx_data); Let ssl function be shared
+        // copy.clear(CurlOption.ssh_keyfunction); Let key function be shared
+
+        /*
+          Allow sharing of conv functions
+          copy.clear(CurlOption.conv_to_network_function);
+          copy.clear(CurlOption.conv_from_network_function);
+          copy.clear(CurlOption.conv_from_utf8_function);
+        */
+
+        return copy;
+    }
+
+    private void _check(CurlCode code)
+    {
+        enforce!CurlTimeoutException(code != CurlError.operation_timedout,
+                                       errorString(code));
+
+        enforce!CurlException(code == CurlError.ok,
+                                errorString(code));
+    }
+
+    private string errorString(CurlCode code)
+    {
+        import core.stdc.string : strlen;
+
+        auto msgZ = curl.easy_strerror(code);
+        // doing the following (instead of just using std.conv.to!string) avoids 1 allocation
+        return format("%s on handle %s", msgZ[0 .. strlen(msgZ)], handle);
+    }
+
+    private void throwOnStopped(string message = null)
+    {
+        auto def = "Curl instance called after being cleaned up";
+        enforce!CurlException(!stopped,
+                                message == null ? def : message);
+    }
+
+    /**
+        Stop and invalidate this curl instance.
+        Warning: Do not call this from inside a callback handler e.g. $(D onReceive).
+    */
+    void shutdown()
+    {
+        throwOnStopped();
+        stopped = true;
+        curl.easy_cleanup(this.handle);
+        this.handle = null;
+    }
+
+    /**
+       Pausing and continuing transfers.
+    */
+    void pause(bool sendingPaused, bool receivingPaused)
+    {
+        throwOnStopped();
+        _check(curl.easy_pause(this.handle,
+                               (sendingPaused ? CurlPause.send_cont : CurlPause.send) |
+                               (receivingPaused ? CurlPause.recv_cont : CurlPause.recv)));
+    }
+
+    /**
+       Set a string curl option.
+       Params:
+       option = A $(ECXREF curl, CurlOption) as found in the curl documentation
+       value = The string
+    */
+    void set(CurlOption option, const(char)[] value)
+    {
+        throwOnStopped();
+        _check(curl.easy_setopt(this.handle, option, value.tempCString().buffPtr));
+    }
+
+    /**
+       Set a long curl option.
+       Params:
+       option = A $(ECXREF curl, CurlOption) as found in the curl documentation
+       value = The long
+    */
+    void set(CurlOption option, long value)
+    {
+        throwOnStopped();
+        _check(curl.easy_setopt(this.handle, option, value));
+    }
+
+    /**
+       Set a void* curl option.
+       Params:
+       option = A $(ECXREF curl, CurlOption) as found in the curl documentation
+       value = The pointer
+    */
+    void set(CurlOption option, void* value)
+    {
+        throwOnStopped();
+        _check(curl.easy_setopt(this.handle, option, value));
+    }
+
+    /**
+       Clear a pointer option.
+       Params:
+       option = A $(ECXREF curl, CurlOption) as found in the curl documentation
+    */
+    void clear(CurlOption option)
+    {
+        throwOnStopped();
+        _check(curl.easy_setopt(this.handle, option, null));
+    }
+
+    /**
+       Clear a pointer option. Does not raise an exception if the underlying
+       libcurl does not support the option. Use sparingly.
+       Params:
+       option = A $(ECXREF curl, CurlOption) as found in the curl documentation
+    */
+    void clearIfSupported(CurlOption option)
+    {
+        throwOnStopped();
+        auto rval = curl.easy_setopt(this.handle, option, null);
+        if (rval != CurlError.unknown_option && rval != CurlError.not_built_in)
+            _check(rval);
+    }
+
+    /**
+       perform the curl request by doing the HTTP,FTP etc. as it has
+       been setup beforehand.
+
+       Params:
+       throwOnError = whether to throw an exception or return a CurlCode on error
+    */
+    CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes)
+    {
+        throwOnStopped();
+        CurlCode code = curl.easy_perform(this.handle);
+        if (throwOnError)
+            _check(code);
+        return code;
+    }
+
+    /**
+      * The event handler that receives incoming data.
+      *
+      * Params:
+      * callback = the callback that receives the $(D ubyte[]) data.
+      * Be sure to copy the incoming data and not store
+      * a slice.
+      *
+      * Returns:
+      * The callback returns the incoming bytes read. If not the entire array is
+      * the request will abort.
+      * The special value HTTP.pauseRequest can be returned in order to pause the
+      * current request.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      * curl.onReceive = (ubyte[] data) { writeln("Got data", to!(const(char)[])(data)); return data.length;};
+      * curl.perform();
+      * ----
+      */
+    @property void onReceive(size_t delegate(InData) callback)
+    {
+        _onReceive = (InData id)
+        {
+            throwOnStopped("Receive callback called on cleaned up Curl instance");
+            return callback(id);
+        };
+        set(CurlOption.file, cast(void*) &this);
+        set(CurlOption.writefunction, cast(void*) &Curl._receiveCallback);
+    }
+
+    /**
+      * The event handler that receives incoming headers for protocols
+      * that uses headers.
+      *
+      * Params:
+      * callback = the callback that receives the header string.
+      * Make sure the callback copies the incoming params if
+      * it needs to store it because they are references into
+      * the backend and may very likely change.
+      *
+      * Example:
+      * ----
+      * import std.net.curl, std.stdio;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      * curl.onReceiveHeader = (in char[] header) { writeln(header); };
+      * curl.perform();
+      * ----
+      */
+    @property void onReceiveHeader(void delegate(in char[]) callback)
+    {
+        _onReceiveHeader = (in char[] od)
+        {
+            throwOnStopped("Receive header callback called on "~
+                           "cleaned up Curl instance");
+            callback(od);
+        };
+        set(CurlOption.writeheader, cast(void*) &this);
+        set(CurlOption.headerfunction,
+            cast(void*) &Curl._receiveHeaderCallback);
+    }
+
+    /**
+      * The event handler that gets called when data is needed for sending.
+      *
+      * Params:
+      * callback = the callback that has a $(D void[]) buffer to be filled
+      *
+      * Returns:
+      * The callback returns the number of elements in the buffer that have been
+      * filled and are ready to send.
+      * The special value $(D Curl.abortRequest) can be returned in
+      * order to abort the current request.
+      * The special value $(D Curl.pauseRequest) can be returned in order to
+      * pause the current request.
+      *
+      * Example:
+      * ----
+      * import std.net.curl;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      *
+      * string msg = "Hello world";
+      * curl.onSend = (void[] data)
+      * {
+      *     auto m = cast(void[])msg;
+      *     size_t length = m.length > data.length ? data.length : m.length;
+      *     if (length == 0) return 0;
+      *     data[0..length] = m[0..length];
+      *     msg = msg[length..$];
+      *     return length;
+      * };
+      * curl.perform();
+      * ----
+      */
+    @property void onSend(size_t delegate(OutData) callback)
+    {
+        _onSend = (OutData od)
+        {
+            throwOnStopped("Send callback called on cleaned up Curl instance");
+            return callback(od);
+        };
+        set(CurlOption.infile, cast(void*) &this);
+        set(CurlOption.readfunction, cast(void*) &Curl._sendCallback);
+    }
+
+    /**
+      * The event handler that gets called when the curl backend needs to seek
+      * the data to be sent.
+      *
+      * Params:
+      * callback = the callback that receives a seek offset and a seek position
+      *            $(ECXREF curl, CurlSeekPos)
+      *
+      * Returns:
+      * The callback returns the success state of the seeking
+      * $(ECXREF curl, CurlSeek)
+      *
+      * Example:
+      * ----
+      * import std.net.curl;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      * curl.onSeek = (long p, CurlSeekPos sp)
+      * {
+      *     return CurlSeek.cantseek;
+      * };
+      * curl.perform();
+      * ----
+      */
+    @property void onSeek(CurlSeek delegate(long, CurlSeekPos) callback)
+    {
+        _onSeek = (long ofs, CurlSeekPos sp)
+        {
+            throwOnStopped("Seek callback called on cleaned up Curl instance");
+            return callback(ofs, sp);
+        };
+        set(CurlOption.seekdata, cast(void*) &this);
+        set(CurlOption.seekfunction, cast(void*) &Curl._seekCallback);
+    }
+
+    /**
+      * The event handler that gets called when the net socket has been created
+      * but a $(D connect()) call has not yet been done. This makes it possible to set
+      * misc. socket options.
+      *
+      * Params:
+      * callback = the callback that receives the socket and socket type
+      * $(ECXREF curl, CurlSockType)
+      *
+      * Returns:
+      * Return 0 from the callback to signal success, return 1 to signal error
+      * and make curl close the socket
+      *
+      * Example:
+      * ----
+      * import std.net.curl;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      * curl.onSocketOption = delegate int(curl_socket_t s, CurlSockType t) { /+ do stuff +/ };
+      * curl.perform();
+      * ----
+      */
+    @property void onSocketOption(int delegate(curl_socket_t,
+                                               CurlSockType) callback)
+    {
+        _onSocketOption = (curl_socket_t sock, CurlSockType st)
+        {
+            throwOnStopped("Socket option callback called on "~
+                           "cleaned up Curl instance");
+            return callback(sock, st);
+        };
+        set(CurlOption.sockoptdata, cast(void*) &this);
+        set(CurlOption.sockoptfunction,
+            cast(void*) &Curl._socketOptionCallback);
+    }
+
+    /**
+      * The event handler that gets called to inform of upload/download progress.
+      *
+      * Params:
+      * callback = the callback that receives the (total bytes to download,
+      * currently downloaded bytes, total bytes to upload, currently uploaded
+      * bytes).
+      *
+      * Returns:
+      * Return 0 from the callback to signal success, return non-zero to abort
+      * transfer
+      *
+      * Example:
+      * ----
+      * import std.net.curl;
+      * Curl curl;
+      * curl.initialize();
+      * curl.set(CurlOption.url, "http://dlang.org");
+      * curl.onProgress = delegate int(size_t dltotal, size_t dlnow, size_t ultotal, size_t uln)
+      * {
+      *     writeln("Progress: downloaded bytes ", dlnow, " of ", dltotal);
+      *     writeln("Progress: uploaded bytes ", ulnow, " of ", ultotal);
+      *     curl.perform();
+      * };
+      * ----
+      */
+    @property void onProgress(int delegate(size_t dlTotal,
+                                           size_t dlNow,
+                                           size_t ulTotal,
+                                           size_t ulNow) callback)
+    {
+        _onProgress = (size_t dlt, size_t dln, size_t ult, size_t uln)
+        {
+            throwOnStopped("Progress callback called on cleaned "~
+                           "up Curl instance");
+            return callback(dlt, dln, ult, uln);
+        };
+        set(CurlOption.noprogress, 0);
+        set(CurlOption.progressdata, cast(void*) &this);
+        set(CurlOption.progressfunction, cast(void*) &Curl._progressCallback);
+    }
+
+    // Internal C callbacks to register with libcurl
+    extern (C) private static
+    size_t _receiveCallback(const char* str,
+                            size_t size, size_t nmemb, void* ptr)
+    {
+        auto b = cast(Curl*) ptr;
+        if (b._onReceive != null)
+            return b._onReceive(cast(InData)(str[0..size*nmemb]));
+        return size*nmemb;
+    }
+
+    extern (C) private static
+    size_t _receiveHeaderCallback(const char* str,
+                                  size_t size, size_t nmemb, void* ptr)
+    {
+        auto b = cast(Curl*) ptr;
+        auto s = str[0..size*nmemb].chomp();
+        if (b._onReceiveHeader != null)
+            b._onReceiveHeader(s);
+
+        return size*nmemb;
+    }
+
+    extern (C) private static
+    size_t _sendCallback(char *str, size_t size, size_t nmemb, void *ptr)
+    {
+        Curl* b = cast(Curl*) ptr;
+        auto a = cast(void[]) str[0..size*nmemb];
+        if (b._onSend == null)
+            return 0;
+        return b._onSend(a);
+    }
+
+    extern (C) private static
+    int _seekCallback(void *ptr, curl_off_t offset, int origin)
+    {
+        auto b = cast(Curl*) ptr;
+        if (b._onSeek == null)
+            return CurlSeek.cantseek;
+
+        // origin: CurlSeekPos.set/current/end
+        // return: CurlSeek.ok/fail/cantseek
+        return b._onSeek(cast(long) offset, cast(CurlSeekPos) origin);
+    }
+
+    extern (C) private static
+    int _socketOptionCallback(void *ptr,
+                              curl_socket_t curlfd, curlsocktype purpose)
+    {
+        auto b = cast(Curl*) ptr;
+        if (b._onSocketOption == null)
+            return 0;
+
+        // return: 0 ok, 1 fail
+        return b._onSocketOption(curlfd, cast(CurlSockType) purpose);
+    }
+
+    extern (C) private static
+    int _progressCallback(void *ptr,
+                          double dltotal, double dlnow,
+                          double ultotal, double ulnow)
+    {
+        auto b = cast(Curl*) ptr;
+        if (b._onProgress == null)
+            return 0;
+
+        // return: 0 ok, 1 fail
+        return b._onProgress(cast(size_t)dltotal, cast(size_t)dlnow,
+                             cast(size_t)ultotal, cast(size_t)ulnow);
+    }
+
+}
+
+// Internal messages send between threads.
+// The data is wrapped in this struct in order to ensure that
+// other std.concurrency.receive calls does not pick up our messages
+// by accident.
+private struct CurlMessage(T)
+{
+    public T data;
+}
+
+private static CurlMessage!T curlMessage(T)(T data)
+{
+    return CurlMessage!T(data);
+}
+
+// Pool of to be used for reusing buffers
+private struct Pool(Data)
+{
+    private struct Entry
+    {
+        Data data;
+        Entry* next;
+    }
+    private Entry*  root;
+    private Entry* freeList;
+
+    @safe @property bool empty()
+    {
+        return root == null;
+    }
+
+    @safe nothrow void push(Data d)
+    {
+        if (freeList == null)
+        {
+            // Allocate new Entry since there is no one
+            // available in the freeList
+            freeList = new Entry;
+        }
+        freeList.data = d;
+        Entry* oldroot = root;
+        root = freeList;
+        freeList = freeList.next;
+        root.next = oldroot;
+    }
+
+    @safe Data pop()
+    {
+        enforce!Exception(root != null, "pop() called on empty pool");
+        auto d = root.data;
+        auto n = root.next;
+        root.next = freeList;
+        freeList = root;
+        root = n;
+        return d;
+    }
+}
+
+// Shared function for reading incoming chunks of data and
+// sending the to a parent thread
+private static size_t _receiveAsyncChunks(ubyte[] data, ref ubyte[] outdata,
+                                          Pool!(ubyte[]) freeBuffers,
+                                          ref ubyte[] buffer, Tid fromTid,
+                                          ref bool aborted)
+{
+    immutable datalen = data.length;
+
+    // Copy data to fill active buffer
+    while (!data.empty)
+    {
+
+        // Make sure a buffer is present
+        while ( outdata.empty && freeBuffers.empty)
+        {
+            // Active buffer is invalid and there are no
+            // available buffers in the pool. Wait for buffers
+            // to return from main thread in order to reuse
+            // them.
+            receive((immutable(ubyte)[] buf)
+                    {
+                        buffer = cast(ubyte[])buf;
+                        outdata = buffer[];
+                    },
+                    (bool flag) { aborted = true; }
+                    );
+            if (aborted) return cast(size_t)0;
+        }
+        if (outdata.empty)
+        {
+            buffer = freeBuffers.pop();
+            outdata = buffer[];
+        }
+
+        // Copy data
+        auto copyBytes = outdata.length < data.length ?
+            outdata.length : data.length;
+
+        outdata[0..copyBytes] = data[0..copyBytes];
+        outdata = outdata[copyBytes..$];
+        data = data[copyBytes..$];
+
+        if (outdata.empty)
+            fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer));
+    }
+
+    return datalen;
+}
+
+// ditto
+private static void _finalizeAsyncChunks(ubyte[] outdata, ref ubyte[] buffer,
+                                         Tid fromTid)
+{
+    if (!outdata.empty)
+    {
+        // Resize the last buffer
+        buffer.length = buffer.length - outdata.length;
+        fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer));
+    }
+}
+
+
+// Shared function for reading incoming lines of data and sending the to a
+// parent thread
+private static size_t _receiveAsyncLines(Terminator, Unit)
+    (const(ubyte)[] data, ref EncodingScheme encodingScheme,
+     bool keepTerminator, Terminator terminator,
+     ref const(ubyte)[] leftOverBytes, ref bool bufferValid,
+     ref Pool!(Unit[]) freeBuffers, ref Unit[] buffer,
+     Tid fromTid, ref bool aborted)
+{
+
+    immutable datalen = data.length;
+
+    // Terminator is specified and buffers should be resized as determined by
+    // the terminator
+
+    // Copy data to active buffer until terminator is found.
+
+    // Decode as many lines as possible
+    while (true)
+    {
+
+        // Make sure a buffer is present
+        while (!bufferValid && freeBuffers.empty)
+        {
+            // Active buffer is invalid and there are no available buffers in
+            // the pool. Wait for buffers to return from main thread in order to
+            // reuse them.
+            receive((immutable(Unit)[] buf)
+                    {
+                        buffer = cast(Unit[])buf;
+                        buffer.length = 0;
+                        buffer.assumeSafeAppend();
+                        bufferValid = true;
+                    },
+                    (bool flag) { aborted = true; }
+                    );
+            if (aborted) return cast(size_t)0;
+        }
+        if (!bufferValid)
+        {
+            buffer = freeBuffers.pop();
+            bufferValid = true;
+        }
+
+        // Try to read a line from left over bytes from last onReceive plus the
+        // newly received bytes.
+        try
+        {
+            if (decodeLineInto(leftOverBytes, data, buffer,
+                               encodingScheme, terminator))
+            {
+                if (keepTerminator)
+                {
+                    fromTid.send(thisTid,
+                                 curlMessage(cast(immutable(Unit)[])buffer));
+                }
+                else
+                {
+                    static if (isArray!Terminator)
+                        fromTid.send(thisTid,
+                                     curlMessage(cast(immutable(Unit)[])
+                                             buffer[0..$-terminator.length]));
+                    else
+                        fromTid.send(thisTid,
+                                     curlMessage(cast(immutable(Unit)[])
+                                             buffer[0..$-1]));
+                }
+                bufferValid = false;
+            }
+            else
+            {
+                // Could not decode an entire line. Save
+                // bytes left in data for next call to
+                // onReceive. Can be up to a max of 4 bytes.
+                enforce!CurlException(data.length <= 4,
+                                        format(
+                                        "Too many bytes left not decoded %s"~
+                                        " > 4. Maybe the charset specified in"~
+                                        " headers does not match "~
+                                        "the actual content downloaded?",
+                                        data.length));
+                leftOverBytes ~= data;
+                break;
+            }
+        }
+        catch (CurlException ex)
+        {
+            prioritySend(fromTid, cast(immutable(CurlException))ex);
+            return cast(size_t)0;
+        }
+    }
+    return datalen;
+}
+
+// ditto
+private static
+void _finalizeAsyncLines(Unit)(bool bufferValid, Unit[] buffer, Tid fromTid)
+{
+    if (bufferValid && buffer.length != 0)
+        fromTid.send(thisTid, curlMessage(cast(immutable(Unit)[])buffer[0..$]));
+}
+
+
+// Spawn a thread for handling the reading of incoming data in the
+// background while the delegate is executing.  This will optimize
+// throughput by allowing simultaneous input (this struct) and
+// output (e.g. AsyncHTTPLineOutputRange).
+private static void _spawnAsync(Conn, Unit, Terminator = void)()
+{
+    Tid fromTid = receiveOnly!Tid();
+
+    // Get buffer to read into
+    Pool!(Unit[]) freeBuffers;  // Free list of buffer objects
+
+    // Number of bytes filled into active buffer
+    Unit[] buffer;
+    bool aborted = false;
+
+    EncodingScheme encodingScheme;
+    static if ( !is(Terminator == void))
+    {
+        // Only lines reading will receive a terminator
+        auto terminator = receiveOnly!Terminator();
+        auto keepTerminator = receiveOnly!bool();
+
+        // max number of bytes to carry over from an onReceive
+        // callback. This is 4 because it is the max code units to
+        // decode a code point in the supported encodings.
+        auto leftOverBytes =  new const(ubyte)[4];
+        leftOverBytes.length = 0;
+        auto bufferValid = false;
+    }
+    else
+    {
+        Unit[] outdata;
+    }
+
+    // no move semantic available in std.concurrency ie. must use casting.
+    auto connDup = cast(CURL*)receiveOnly!ulong();
+    auto client = Conn();
+    client.p.curl.handle = connDup;
+
+    // receive a method for both ftp and http but just use it for http
+    auto method = receiveOnly!(HTTP.Method)();
+
+    client.onReceive = (ubyte[] data)
+    {
+        // If no terminator is specified the chunk size is fixed.
+        static if ( is(Terminator == void) )
+            return _receiveAsyncChunks(data, outdata, freeBuffers, buffer,
+                                       fromTid, aborted);
+        else
+            return _receiveAsyncLines(data, encodingScheme,
+                                      keepTerminator, terminator, leftOverBytes,
+                                      bufferValid, freeBuffers, buffer,
+                                      fromTid, aborted);
+    };
+
+    static if ( is(Conn == HTTP) )
+    {
+        client.method = method;
+        // register dummy header handler
+        client.onReceiveHeader = (in char[] key, in char[] value)
+        {
+            if (key == "content-type")
+                encodingScheme = EncodingScheme.create(client.p.charset);
+        };
+    }
+    else
+    {
+        encodingScheme = EncodingScheme.create(client.encoding);
+    }
+
+    // Start the request
+    CurlCode code;
+    try
+    {
+        code = client.perform(ThrowOnError.no);
+    }
+    catch (Exception ex)
+    {
+        prioritySend(fromTid, cast(immutable(Exception)) ex);
+        fromTid.send(thisTid, curlMessage(true)); // signal done
+        return;
+    }
+
+    if (code != CurlError.ok)
+    {
+        if (aborted && (code == CurlError.aborted_by_callback ||
+                        code == CurlError.write_error))
+        {
+            fromTid.send(thisTid, curlMessage(true)); // signal done
+            return;
+        }
+        prioritySend(fromTid, cast(immutable(CurlException))
+                     new CurlException(client.p.curl.errorString(code)));
+
+        fromTid.send(thisTid, curlMessage(true)); // signal done
+        return;
+    }
+
+    // Send remaining data that is not a full chunk size
+    static if ( is(Terminator == void) )
+        _finalizeAsyncChunks(outdata, buffer, fromTid);
+    else
+        _finalizeAsyncLines(bufferValid, buffer, fromTid);
+
+    fromTid.send(thisTid, curlMessage(true)); // signal done
+}
diff --git a/libphobos/src/std/net/isemail.d b/libphobos/src/std/net/isemail.d
new file mode 100644
index 00000000000..9f400148c4a
--- /dev/null
+++ b/libphobos/src/std/net/isemail.d
@@ -0,0 +1,1973 @@
+/**
+ * Validates an email address according to RFCs 5321, 5322 and others.
+ *
+ * Authors: Dominic Sayers $(LT)dominic@sayers.cc$(GT), Jacob Carlborg
+ * Copyright: Dominic Sayers, Jacob Carlborg 2008-.
+ * Test schema documentation: Copyright © 2011, Daniel Marschall
+ * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
+ * Version: 3.0.13 - Version 3.0 of the original PHP implementation: $(LINK http://www.dominicsayers.com/isemail)
+ *
+ * Standards:
+ *         $(UL
+ *             $(LI RFC 5321)
+ *             $(LI RFC 5322)
+ *          )
+ *
+ * References:
+ *         $(UL
+ *             $(LI $(LINK http://www.dominicsayers.com/isemail))
+ *             $(LI $(LINK http://tools.ietf.org/html/rfc5321))
+ *             $(LI $(LINK http://tools.ietf.org/html/rfc5322))
+ *          )
+ *
+ * Source: $(PHOBOSSRC std/net/_isemail.d)
+ */
+module std.net.isemail;
+
+// FIXME
+import std.range.primitives; // : ElementType;
+import std.traits;
+
+/**
+ * Check that an email address conforms to RFCs 5321, 5322 and others.
+ *
+ * As of Version 3.0, we are now distinguishing clearly between a Mailbox as defined
+ * by RFC 5321 and an addr-spec as defined by RFC 5322. Depending on the context,
+ * either can be regarded as a valid email address. The RFC 5321 Mailbox specification
+ * is more restrictive (comments, white space and obsolete forms are not allowed).
+ *
+ * Note: The DNS check is currently not implemented.
+ *
+ * Params:
+ *     email = The email address to check
+ *     checkDNS = If CheckDns.yes then a DNS check for MX records will be made
+ *     errorLevel = Determines the boundary between valid and invalid addresses.
+ *                  Status codes above this number will be returned as-is,
+ *                  status codes below will be returned as EmailStatusCode.valid.
+ *                  Thus the calling program can simply look for EmailStatusCode.valid
+ *                  if it is only interested in whether an address is valid or not. The
+ *                  $(D_PARAM errorLevel) will determine how "picky" isEmail() is about
+ *                  the address.
+ *
+ *                  If omitted or passed as EmailStatusCode.none then isEmail() will
+ *                  not perform any finer grained error checking and an address is
+ *                  either considered valid or not. Email status code will either be
+ *                  EmailStatusCode.valid or EmailStatusCode.error.
+ *
+ * Returns: an EmailStatus, indicating the status of the email address.
+ */
+EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no,
+    EmailStatusCode errorLevel = EmailStatusCode.none) if (isSomeChar!(Char))
+{
+    import std.algorithm : uniq, canFind;
+    import std.exception : enforce;
+    import std.array : array, split;
+    import std.conv : to;
+    import std.regex : match, regex;
+    import std.string : indexOf, lastIndexOf;
+    import std.uni : isNumber;
+
+    alias tstring = const(Char)[];
+
+    enum defaultThreshold = 16;
+    int threshold;
+    bool diagnose;
+
+    if (errorLevel == EmailStatusCode.any || errorLevel == EmailStatusCode.none)
+    {
+        threshold = EmailStatusCode.valid;
+        diagnose = errorLevel == EmailStatusCode.any;
+    }
+
+    else
+    {
+        diagnose = true;
+
+        switch (errorLevel)
+        {
+            case EmailStatusCode.warning: threshold = defaultThreshold; break;
+            case EmailStatusCode.error: threshold = EmailStatusCode.valid; break;
+            default: threshold = errorLevel;
+        }
+    }
+
+    auto returnStatus = [EmailStatusCode.valid];
+    auto context = EmailPart.componentLocalPart;
+    auto contextStack = [context];
+    auto contextPrior = context;
+    tstring token = "";
+    tstring tokenPrior = "";
+    tstring[EmailPart] parseData = [EmailPart.componentLocalPart : "", EmailPart.componentDomain : ""];
+    tstring[][EmailPart] atomList = [EmailPart.componentLocalPart : [""], EmailPart.componentDomain : [""]];
+    auto elementCount = 0;
+    auto elementLength = 0;
+    auto hyphenFlag = false;
+    auto endOrDie = false;
+    auto crlfCount = int.min; // int.min == not defined
+
+    foreach (ref i, e ; email)
+    {
+        token = email.get(i, e);
+
+        switch (context)
+        {
+            case EmailPart.componentLocalPart:
+                switch (token)
+                {
+                    case Token.openParenthesis:
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.comment :
+                                EmailStatusCode.deprecatedComment;
+
+                        else
+                        {
+                            returnStatus ~= EmailStatusCode.comment;
+                            endOrDie = true;
+                        }
+
+                        contextStack ~= context;
+                        context = EmailPart.contextComment;
+                    break;
+
+                    case Token.dot:
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
+                                EmailStatusCode.errorConsecutiveDots;
+
+                        else
+                        {
+                            if (endOrDie)
+                                returnStatus ~= EmailStatusCode.deprecatedLocalPart;
+                        }
+
+                        endOrDie = false;
+                        elementLength = 0;
+                        elementCount++;
+                        parseData[EmailPart.componentLocalPart] ~= token;
+
+                        if (elementCount >= atomList[EmailPart.componentLocalPart].length)
+                            atomList[EmailPart.componentLocalPart] ~= "";
+
+                        else
+                            atomList[EmailPart.componentLocalPart][elementCount] = "";
+                    break;
+
+                    case Token.doubleQuote:
+                        if (elementLength == 0)
+                        {
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.rfc5321QuotedString :
+                                EmailStatusCode.deprecatedLocalPart;
+
+                            parseData[EmailPart.componentLocalPart] ~= token;
+                            atomList[EmailPart.componentLocalPart][elementCount] ~= token;
+                            elementLength++;
+                            endOrDie = true;
+                            contextStack ~= context;
+                            context = EmailPart.contextQuotedString;
+                        }
+
+                        else
+                            returnStatus ~= EmailStatusCode.errorExpectingText;
+                    break;
+
+                    case Token.cr:
+                    case Token.space:
+                    case Token.tab:
+                        if ((token == Token.cr) && ((++i == email.length) || (email.get(i, e) != Token.lf)))
+                        {
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                            break;
+                        }
+
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.foldingWhitespace :
+                                EmailStatusCode.deprecatedFoldingWhitespace;
+
+                        else
+                            endOrDie = true;
+
+                        contextStack ~= context;
+                        context = EmailPart.contextFoldingWhitespace;
+                        tokenPrior = token;
+                    break;
+
+                    case Token.at:
+                        enforce(contextStack.length == 1, "Unexpected item on context stack");
+
+                        if (parseData[EmailPart.componentLocalPart] == "")
+                            returnStatus ~= EmailStatusCode.errorNoLocalPart;
+
+                        else if (elementLength == 0)
+                            returnStatus ~= EmailStatusCode.errorDotEnd;
+
+                        else if (parseData[EmailPart.componentLocalPart].length > 64)
+                            returnStatus ~= EmailStatusCode.rfc5322LocalTooLong;
+
+                        else if (contextPrior == EmailPart.contextComment ||
+                            contextPrior == EmailPart.contextFoldingWhitespace)
+                                returnStatus ~= EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt;
+
+                        context = EmailPart.componentDomain;
+                        contextStack = [context];
+                        elementCount = 0;
+                        elementLength = 0;
+                        endOrDie = false;
+                    break;
+
+                    default:
+                        if (endOrDie)
+                        {
+                            switch (contextPrior)
+                            {
+                                case EmailPart.contextComment:
+                                case EmailPart.contextFoldingWhitespace:
+                                    returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
+                                break;
+
+                                case EmailPart.contextQuotedString:
+                                    returnStatus ~= EmailStatusCode.errorTextAfterQuotedString;
+                                break;
+
+                                default:
+                                    throw new Exception("More text found where none is allowed, but unrecognised prior " ~
+                                                        "context: " ~ to!(string)(contextPrior));
+                            }
+                        }
+
+                        else
+                        {
+                            contextPrior = context;
+                            auto c = token.front;
+
+                            if (c < '!' || c > '~' || c == '\n' || Token.specials.canFind(token))
+                                returnStatus ~= EmailStatusCode.errorExpectingText;
+
+                            parseData[EmailPart.componentLocalPart] ~= token;
+                            atomList[EmailPart.componentLocalPart][elementCount] ~= token;
+                            elementLength++;
+                        }
+                }
+            break;
+
+            case EmailPart.componentDomain:
+                switch (token)
+                {
+                    case Token.openParenthesis:
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
+                                : EmailStatusCode.deprecatedComment;
+
+                        else
+                        {
+                            returnStatus ~= EmailStatusCode.comment;
+                            endOrDie = true;
+                        }
+
+                        contextStack ~= context;
+                        context = EmailPart.contextComment;
+                    break;
+
+                    case Token.dot:
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart :
+                                EmailStatusCode.errorConsecutiveDots;
+
+                        else if (hyphenFlag)
+                            returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
+
+                        else
+                        {
+                            if (elementLength > 63)
+                                returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
+                        }
+
+                        endOrDie = false;
+                        elementLength = 0;
+                        elementCount++;
+
+                        //atomList[EmailPart.componentDomain][elementCount] = "";
+                        atomList[EmailPart.componentDomain] ~= "";
+                        parseData[EmailPart.componentDomain] ~= token;
+                    break;
+
+                    case Token.openBracket:
+                        if (parseData[EmailPart.componentDomain] == "")
+                        {
+                            endOrDie = true;
+                            elementLength++;
+                            contextStack ~= context;
+                            context = EmailPart.componentLiteral;
+                            parseData[EmailPart.componentDomain] ~= token;
+                            atomList[EmailPart.componentDomain][elementCount] ~= token;
+                            parseData[EmailPart.componentLiteral] = "";
+                        }
+
+                        else
+                            returnStatus ~= EmailStatusCode.errorExpectingText;
+                    break;
+
+                    case Token.cr:
+                    case Token.space:
+                    case Token.tab:
+                        if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
+                        {
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                            break;
+                        }
+
+                        if (elementLength == 0)
+                            returnStatus ~= elementCount == 0 ? EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt
+                                : EmailStatusCode.deprecatedFoldingWhitespace;
+
+                        else
+                        {
+                            returnStatus ~= EmailStatusCode.foldingWhitespace;
+                            endOrDie = true;
+                        }
+
+                        contextStack ~= context;
+                        context = EmailPart.contextFoldingWhitespace;
+                        tokenPrior = token;
+                    break;
+
+                    default:
+                        if (endOrDie)
+                        {
+                            switch (contextPrior)
+                            {
+                                case EmailPart.contextComment:
+                                case EmailPart.contextFoldingWhitespace:
+                                    returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace;
+                                break;
+
+                                case EmailPart.componentLiteral:
+                                    returnStatus ~= EmailStatusCode.errorTextAfterDomainLiteral;
+                                break;
+
+                                default:
+                                    throw new Exception("More text found where none is allowed, but unrecognised prior " ~
+                                                        "context: " ~ to!(string)(contextPrior));
+                            }
+
+                        }
+
+                        auto c = token.front;
+                        hyphenFlag = false;
+
+                        if (c < '!' || c > '~' || Token.specials.canFind(token))
+                            returnStatus ~= EmailStatusCode.errorExpectingText;
+
+                        else if (token == Token.hyphen)
+                        {
+                            if (elementLength == 0)
+                                returnStatus ~= EmailStatusCode.errorDomainHyphenStart;
+
+                            hyphenFlag = true;
+                        }
+
+                        else if (!((c > '/' && c < ':') || (c > '@' && c < '[') || (c > '`' && c < '{')))
+                            returnStatus ~= EmailStatusCode.rfc5322Domain;
+
+                        parseData[EmailPart.componentDomain] ~= token;
+                        atomList[EmailPart.componentDomain][elementCount] ~= token;
+                        elementLength++;
+                }
+            break;
+
+            case EmailPart.componentLiteral:
+                switch (token)
+                {
+                    case Token.closeBracket:
+                        if (returnStatus.max() < EmailStatusCode.deprecated_)
+                        {
+                            auto maxGroups = 8;
+                            size_t index = -1;
+                            auto addressLiteral = parseData[EmailPart.componentLiteral];
+                            enum regexStr = `\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}`~
+                                            `(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`;
+                            auto matchesIp = array(addressLiteral.match(regex!tstring(regexStr)).captures);
+
+                            if (!matchesIp.empty)
+                            {
+                                index = addressLiteral.lastIndexOf(matchesIp.front);
+
+                                if (index != 0)
+                                    addressLiteral = addressLiteral.substr(0, index) ~ "0:0";
+                            }
+
+                            if (index == 0)
+                                returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
+
+                            else if (addressLiteral.compareFirstN(Token.ipV6Tag, 5, true))
+                                returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
+
+                            else
+                            {
+                                auto ipV6 = addressLiteral.substr(5);
+                                matchesIp = ipV6.split(Token.colon);
+                                auto groupCount = matchesIp.length;
+                                index = ipV6.indexOf(Token.doubleColon);
+
+                                if (index == -1)
+                                {
+                                    if (groupCount != maxGroups)
+                                        returnStatus ~= EmailStatusCode.rfc5322IpV6GroupCount;
+                                }
+
+                                else
+                                {
+                                    if (index != ipV6.lastIndexOf(Token.doubleColon))
+                                        returnStatus ~= EmailStatusCode.rfc5322IpV6TooManyDoubleColons;
+
+                                    else
+                                    {
+                                        if (index == 0 || index == (ipV6.length - 2))
+                                            maxGroups++;
+
+                                        if (groupCount > maxGroups)
+                                            returnStatus ~= EmailStatusCode.rfc5322IpV6MaxGroups;
+
+                                        else if (groupCount == maxGroups)
+                                            returnStatus ~= EmailStatusCode.rfc5321IpV6Deprecated;
+                                    }
+                                }
+
+                                if (ipV6.substr(0, 1) == Token.colon && ipV6.substr(1, 1) != Token.colon)
+                                    returnStatus ~= EmailStatusCode.rfc5322IpV6ColonStart;
+
+                                else if (ipV6.substr(-1) == Token.colon && ipV6.substr(-2, -1) != Token.colon)
+                                    returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd;
+
+                                else if (!matchesIp.grep(regex!(tstring)(`^[0-9A-Fa-f]{0,4}$`), true).empty)
+                                    returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar;
+
+                                else
+                                    returnStatus ~= EmailStatusCode.rfc5321AddressLiteral;
+                            }
+                        }
+
+                        else
+                            returnStatus ~= EmailStatusCode.rfc5322DomainLiteral;
+
+                        parseData[EmailPart.componentDomain] ~= token;
+                        atomList[EmailPart.componentDomain][elementCount] ~= token;
+                        elementLength++;
+                        contextPrior = context;
+                        context = contextStack.pop();
+                    break;
+
+                    case Token.backslash:
+                        returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
+                        contextStack ~= context;
+                        context = EmailPart.contextQuotedPair;
+                    break;
+
+                    case Token.cr:
+                    case Token.space:
+                    case Token.tab:
+                        if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
+                        {
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                            break;
+                        }
+
+                        returnStatus ~= EmailStatusCode.foldingWhitespace;
+                        contextStack ~= context;
+                        context = EmailPart.contextFoldingWhitespace;
+                        tokenPrior = token;
+                    break;
+
+                    default:
+                        auto c = token.front;
+
+                        if (c > AsciiToken.delete_ || c == '\0' || token == Token.openBracket)
+                        {
+                            returnStatus ~= EmailStatusCode.errorExpectingDomainText;
+                            break;
+                        }
+
+                        else if (c < '!' || c == AsciiToken.delete_ )
+                            returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText;
+
+                        parseData[EmailPart.componentLiteral] ~= token;
+                        parseData[EmailPart.componentDomain] ~= token;
+                        atomList[EmailPart.componentDomain][elementCount] ~= token;
+                        elementLength++;
+                }
+            break;
+
+            case EmailPart.contextQuotedString:
+                switch (token)
+                {
+                    case Token.backslash:
+                        contextStack ~= context;
+                        context = EmailPart.contextQuotedPair;
+                    break;
+
+                    case Token.cr:
+                    case Token.tab:
+                        if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
+                        {
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                            break;
+                        }
+
+                        parseData[EmailPart.componentLocalPart] ~= Token.space;
+                        atomList[EmailPart.componentLocalPart][elementCount] ~= Token.space;
+                        elementLength++;
+
+                        returnStatus ~= EmailStatusCode.foldingWhitespace;
+                        contextStack ~= context;
+                        context = EmailPart.contextFoldingWhitespace;
+                        tokenPrior = token;
+                    break;
+
+                    case Token.doubleQuote:
+                        parseData[EmailPart.componentLocalPart] ~= token;
+                        atomList[EmailPart.componentLocalPart][elementCount] ~= token;
+                        elementLength++;
+                        contextPrior = context;
+                        context = contextStack.pop();
+                    break;
+
+                    default:
+                        auto c = token.front;
+
+                        if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
+                            returnStatus ~= EmailStatusCode.errorExpectingQuotedText;
+
+                        else if (c < ' ' || c == AsciiToken.delete_)
+                            returnStatus ~= EmailStatusCode.deprecatedQuotedText;
+
+                        parseData[EmailPart.componentLocalPart] ~= token;
+                        atomList[EmailPart.componentLocalPart][elementCount] ~= token;
+                        elementLength++;
+                }
+            break;
+
+            case EmailPart.contextQuotedPair:
+                auto c = token.front;
+
+                if (c > AsciiToken.delete_)
+                    returnStatus ~= EmailStatusCode.errorExpectingQuotedPair;
+
+                else if (c < AsciiToken.unitSeparator && c != AsciiToken.horizontalTab || c == AsciiToken.delete_)
+                    returnStatus ~= EmailStatusCode.deprecatedQuotedPair;
+
+                contextPrior = context;
+                context = contextStack.pop();
+                token = Token.backslash ~ token;
+
+                switch (context)
+                {
+                    case EmailPart.contextComment: break;
+
+                    case EmailPart.contextQuotedString:
+                        parseData[EmailPart.componentLocalPart] ~= token;
+                        atomList[EmailPart.componentLocalPart][elementCount] ~= token;
+                        elementLength += 2;
+                    break;
+
+                    case EmailPart.componentLiteral:
+                        parseData[EmailPart.componentDomain] ~= token;
+                        atomList[EmailPart.componentDomain][elementCount] ~= token;
+                        elementLength += 2;
+                    break;
+
+                    default:
+                        throw new Exception("Quoted pair logic invoked in an invalid context: " ~ to!(string)(context));
+                }
+            break;
+
+            case EmailPart.contextComment:
+                switch (token)
+                {
+                    case Token.openParenthesis:
+                        contextStack ~= context;
+                        context = EmailPart.contextComment;
+                    break;
+
+                    case Token.closeParenthesis:
+                        contextPrior = context;
+                        context = contextStack.pop();
+                    break;
+
+                    case Token.backslash:
+                        contextStack ~= context;
+                        context = EmailPart.contextQuotedPair;
+                    break;
+
+                    case Token.cr:
+                    case Token.space:
+                    case Token.tab:
+                        if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf))
+                        {
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                            break;
+                        }
+
+                        returnStatus ~= EmailStatusCode.foldingWhitespace;
+
+                        contextStack ~= context;
+                        context = EmailPart.contextFoldingWhitespace;
+                        tokenPrior = token;
+                    break;
+
+                    default:
+                        auto c = token.front;
+
+                        if (c > AsciiToken.delete_ || c == '\0' || c == '\n')
+                        {
+                            returnStatus ~= EmailStatusCode.errorExpectingCommentText;
+                            break;
+                        }
+
+                        else if (c < ' ' || c == AsciiToken.delete_)
+                            returnStatus ~= EmailStatusCode.deprecatedCommentText;
+                }
+            break;
+
+            case EmailPart.contextFoldingWhitespace:
+                if (tokenPrior == Token.cr)
+                {
+                    if (token == Token.cr)
+                    {
+                        returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrflX2;
+                        break;
+                    }
+
+                    if (crlfCount != int.min) // int.min == not defined
+                    {
+                        if (++crlfCount > 1)
+                            returnStatus ~= EmailStatusCode.deprecatedFoldingWhitespace;
+                    }
+
+                    else
+                        crlfCount = 1;
+                }
+
+                switch (token)
+                {
+                    case Token.cr:
+                        if (++i == email.length || email.get(i, e) != Token.lf)
+                            returnStatus ~= EmailStatusCode.errorCrNoLf;
+                    break;
+
+                    case Token.space:
+                    case Token.tab:
+                    break;
+
+                    default:
+                        if (tokenPrior == Token.cr)
+                        {
+                            returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
+                            break;
+                        }
+
+                        crlfCount = int.min; // int.min == not defined
+                        contextPrior = context;
+                        context = contextStack.pop();
+                        i--;
+                    break;
+                }
+
+                tokenPrior = token;
+            break;
+
+            default:
+                throw new Exception("Unkown context: " ~ to!(string)(context));
+        }
+
+        if (returnStatus.max() > EmailStatusCode.rfc5322)
+            break;
+    }
+
+    if (returnStatus.max() < EmailStatusCode.rfc5322)
+    {
+        if (context == EmailPart.contextQuotedString)
+            returnStatus ~= EmailStatusCode.errorUnclosedQuotedString;
+
+        else if (context == EmailPart.contextQuotedPair)
+            returnStatus ~= EmailStatusCode.errorBackslashEnd;
+
+        else if (context == EmailPart.contextComment)
+            returnStatus ~= EmailStatusCode.errorUnclosedComment;
+
+        else if (context == EmailPart.componentLiteral)
+            returnStatus ~= EmailStatusCode.errorUnclosedDomainLiteral;
+
+        else if (token == Token.cr)
+            returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd;
+
+        else if (parseData[EmailPart.componentDomain] == "")
+            returnStatus ~= EmailStatusCode.errorNoDomain;
+
+        else if (elementLength == 0)
+            returnStatus ~= EmailStatusCode.errorDotEnd;
+
+        else if (hyphenFlag)
+            returnStatus ~= EmailStatusCode.errorDomainHyphenEnd;
+
+        else if (parseData[EmailPart.componentDomain].length > 255)
+            returnStatus ~= EmailStatusCode.rfc5322DomainTooLong;
+
+        else if ((parseData[EmailPart.componentLocalPart] ~ Token.at ~ parseData[EmailPart.componentDomain]).length >
+            254)
+                returnStatus ~= EmailStatusCode.rfc5322TooLong;
+
+        else if (elementLength > 63)
+            returnStatus ~= EmailStatusCode.rfc5322LabelTooLong;
+    }
+
+    auto dnsChecked = false;
+
+    if (checkDNS == CheckDns.yes && returnStatus.max() < EmailStatusCode.dnsWarning)
+    {
+        assert(false, "DNS check is currently not implemented");
+    }
+
+    if (!dnsChecked && returnStatus.max() < EmailStatusCode.dnsWarning)
+    {
+        if (elementCount == 0)
+            returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain;
+
+        if (isNumber(atomList[EmailPart.componentDomain][elementCount].front))
+            returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric;
+    }
+
+    returnStatus = array(uniq(returnStatus));
+    auto finalStatus = returnStatus.max();
+
+    if (returnStatus.length != 1)
+        returnStatus.popFront();
+
+    parseData[EmailPart.status] = to!(tstring)(returnStatus);
+
+    if (finalStatus < threshold)
+        finalStatus = EmailStatusCode.valid;
+
+    if (!diagnose)
+        finalStatus = finalStatus < threshold ? EmailStatusCode.valid : EmailStatusCode.error;
+
+    auto valid = finalStatus == EmailStatusCode.valid;
+    tstring localPart = "";
+    tstring domainPart = "";
+
+    if (auto value = EmailPart.componentLocalPart in parseData)
+        localPart = *value;
+
+    if (auto value = EmailPart.componentDomain in parseData)
+        domainPart = *value;
+
+    return EmailStatus(valid, to!(string)(localPart), to!(string)(domainPart), finalStatus);
+}
+
+unittest
+{
+    assert(``.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
+    assert(`test`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
+    assert(`@`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
+    assert(`test@`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
+
+    // assert(`test@io`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
+    //     `io. currently has an MX-record (Feb 2011). Some DNS setups seem to find it, some don't.`
+    //     ` If you don't see the MX for io. then try setting your DNS server to 8.8.8.8 (the Google DNS server)`);
+
+    assert(`@io`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart,
+        `io. currently has an MX-record (Feb 2011)`);
+
+    assert(`@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart);
+    assert(`test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+    assert(`test@nominet.org.uk`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+    assert(`test@about.museum`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+    assert(`a@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    //assert(`test@e.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
+        // DNS check is currently not implemented
+
+    //assert(`test@iana.a`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
+        // DNS check is currently not implemented
+
+    assert(`test.test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+    assert(`.test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
+    assert(`test.@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
+
+    assert(`test..iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorConsecutiveDots);
+
+    assert(`test_exa-mple.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain);
+    assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    assert(`test\@test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert(`123@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+    assert(`test@123.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    assert(`test@iana.123`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321TopLevelDomainNumeric);
+    assert(`test@255.255.255.255`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321TopLevelDomainNumeric);
+
+    assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong);
+
+    // assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(CheckDns.no,
+    //     EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
+        // DNS check is currently not implemented
+
+    assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LabelTooLong);
+
+    assert(`test@mason-dixon.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    assert(`test@-iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorDomainHyphenStart);
+
+    assert(`test@iana-.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorDomainHyphenEnd);
+
+    assert(`test@g--a.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid);
+
+    //assert(`test@iana.co-uk`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        //EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
+
+    assert(`test@.iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart);
+    assert(`test@iana.org.`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd);
+    assert(`test@iana..com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorConsecutiveDots);
+
+    //assert(`a@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z`
+    //        `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z`
+    //        `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+    //        EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented
+
+    // assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`
+    //         `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`
+    //         `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(CheckDns.no,
+    //         EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord);
+        // DNS check is currently not implemented
+
+    assert((`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`).isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
+
+    assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`).isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong);
+
+    assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`).isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322DomainTooLong);
+
+    assert(`"test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321QuotedString);
+
+    assert(`""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
+    assert(`"""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
+    assert(`"\a"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
+    assert(`"\""@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
+
+    assert(`"\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedQuotedString);
+
+    assert(`"\\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString);
+    assert(`test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
+
+    assert(`"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedQuotedString);
+
+    assert(`"test"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorTextAfterQuotedString);
+
+    assert(`test"text"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert(`"test""test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert(`"test"."test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedLocalPart);
+
+    assert(`"test\ test"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321QuotedString);
+
+    assert(`"test".test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedLocalPart);
+
+    assert("\"test\u0000\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingQuotedText);
+
+    assert("\"test\\\u0000\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedQuotedPair);
+
+    assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
+        `Quotes are still part of the length restriction`);
+
+    assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong,
+        `Quoted pair is still part of the length restriction`);
+
+    assert(`test@[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@a[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert(`test@[255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert(`test@[255.255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert(`test@[255.255.255.256]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322IpV6GroupCount);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode
+        == EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6BadChar);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321IpV6Deprecated);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
+
+    assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322IpV6ColonStart);
+
+    assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
+
+    assert(`test@[IPv6:::]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount);
+
+    assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups);
+
+    assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode
+        == EmailStatusCode.rfc5322IpV6TooManyDoubleColons);
+
+    assert(`test@[IPv6::255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322IpV6ColonStart);
+
+    assert(` test @iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
+
+    assert(`test@ iana .com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
+
+    assert(`test . test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedFoldingWhitespace);
+
+    assert("\u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.foldingWhitespace, `Folding whitespace`);
+
+    assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP`~
+        ` -- only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`);
+
+    assert(`(comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.comment);
+    assert(`((comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedComment);
+
+    assert(`(comment(comment))test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.comment);
+
+    assert(`test@(comment)iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
+
+    assert(`test(comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorTextAfterCommentFoldingWhitespace);
+
+    assert(`test@(comment)[255.255.255.255]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
+
+    assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.comment);
+
+    assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt);
+
+    assert((`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyz`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.`~
+        `abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`).isEmail(CheckDns.no,
+        EmailStatusCode.any).statusCode == EmailStatusCode.comment);
+
+    assert("test@iana.org\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.valid, `A valid IDN from ICANN's <a href="http://idn.icann.org/#The_example.test_names">`~
+        `IDN TLD evaluation gateway</a>`);
+
+    assert(`xn--test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid,
+        `RFC 3490: "unless the email standards are revised to invite the use of IDNA for local parts, a domain label`~
+        ` that holds the local part of an email address SHOULD NOT begin with the ACE prefix, and even if it does,`~
+        ` it is to be interpreted literally as a local part that happens to begin with the ACE prefix"`);
+
+    assert(`test@iana.org-`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorDomainHyphenEnd);
+
+    assert(`"test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedQuotedString);
+
+    assert(`(test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedComment);
+
+    assert(`test@(iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedComment);
+
+    assert(`test@[1.2.3.4`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedDomainLiteral);
+
+    assert(`"test\"@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedQuotedString);
+
+    assert(`(comment\)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedComment);
+
+    assert(`test@iana.org(comment\)`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedComment);
+
+    assert(`test@iana.org(comment\`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorBackslashEnd);
+
+    assert(`test@[RFC-5322-domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert(`test@[RFC-5322]-domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorTextAfterDomainLiteral);
+
+    assert(`test@[RFC-5322-[domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingDomainText);
+
+    assert("test@[RFC-5322-\\\u0007-domain-literal]".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteralObsoleteText, `obs-dtext <strong>and</strong> obs-qp`);
+
+    assert("test@[RFC-5322-\\\u0009-domain-literal]".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteralObsoleteText);
+
+    assert(`test@[RFC-5322-\]-domain-literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteralObsoleteText);
+
+    assert(`test@[RFC-5322-domain-literal\]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorUnclosedDomainLiteral);
+
+    assert(`test@[RFC-5322-domain-literal\`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorBackslashEnd);
+
+    assert(`test@[RFC 5322 domain literal]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral, `Spaces are FWS in a domain literal`);
+
+    assert(`test@[RFC-5322-domain-literal] (comment)`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322DomainLiteral);
+
+    assert("\u007F@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
+    assert("test@\u007F.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText);
+    assert("\"\u007F\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedQuotedText);
+
+    assert("\"\\\u007F\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+            EmailStatusCode.deprecatedQuotedPair);
+
+    assert("(\u007F)test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentText);
+
+    assert("test@iana.org\u000D".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
+        `No LF after the CR`);
+
+    assert("\u000Dtest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
+        `No LF after the CR`);
+
+    assert("\"\u000Dtest\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf
+        ,`No LF after the CR`);
+
+    assert("(\u000D)test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
+        `No LF after the CR`);
+
+    assert("(\u000D".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
+        `No LF after the CR`);
+
+    assert("test@iana.org(\u000D)".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf,
+        `No LF after the CR`);
+
+    assert("\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert("\"\u000A\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingQuotedText);
+
+    assert("\"\\\u000A\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedQuotedPair);
+
+    assert("(\u000A)test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingCommentText);
+
+    assert("\u0007@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert("test@\u0007.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingText);
+
+    assert("\"\u0007\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedQuotedText);
+
+    assert("\"\\\u0007\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedQuotedPair);
+
+    assert("(\u0007)test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedCommentText);
+
+    assert("\u000D\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
+
+    assert("\u000D\u000A \u000D\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
+
+    assert(" \u000D\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
+
+    assert(" \u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.foldingWhitespace, `FWS`);
+
+    assert(" \u000D\u000A \u000D\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
+
+    assert(" \u000D\u000A\u000D\u000Atest@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
+
+    assert(" \u000D\u000A\u000D\u000A test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
+
+    assert("test@iana.org\u000D\u000A ".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.foldingWhitespace, `FWS`);
+
+    assert("test@iana.org\u000D\u000A \u000D\u000A ".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP -- `~
+        `only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`);
+
+    assert("test@iana.org\u000D\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`);
+
+    assert("test@iana.org\u000D\u000A \u000D\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`);
+
+    assert("test@iana.org \u000D\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`);
+
+    assert("test@iana.org \u000D\u000A ".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.foldingWhitespace, `FWS`);
+
+    assert("test@iana.org \u000D\u000A \u000D\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`);
+
+    assert("test@iana.org \u000D\u000A\u000D\u000A".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`);
+
+    assert("test@iana.org \u000D\u000A\u000D\u000A ".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`);
+
+    assert(" test@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace);
+    assert(`test@iana.org `.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace);
+
+    assert(`test@[IPv6:1::2:]`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.rfc5322IpV6ColonEnd);
+
+    assert("\"test\\\u00A9\"@iana.org".isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.errorExpectingQuotedPair);
+
+    assert(`test@iana/icann.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322Domain);
+
+    assert(`test.(comment)test@iana.org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+        EmailStatusCode.deprecatedComment);
+
+    assert(`test@org`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321TopLevelDomain);
+
+    // assert(`test@test.com`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode ==
+            //EmailStatusCode.dnsWarningNoMXRecord, `test.com has an A-record but not an MX-record`);
+            // DNS check is currently not implemented
+    //
+    // assert(`test@nic.no`.isEmail(CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord,
+    //     `nic.no currently has no MX-records or A-records (Feb 2011). If you are seeing an A-record for nic.io then`
+    //       ` try setting your DNS server to 8.8.8.8 (the Google DNS server) - your DNS server may be faking an A-record`
+    //     ` (OpenDNS does this, for instance).`); // DNS check is currently not implemented
+}
+
+/// Enum for indicating if the isEmail function should perform a DNS check or not.
+enum CheckDns
+{
+    /// Does not perform DNS checking
+    no,
+
+    /// Performs DNS checking
+    yes
+}
+
+/// This struct represents the status of an email address
+struct EmailStatus
+{
+    private
+    {
+        bool valid_;
+        string localPart_;
+        string domainPart_;
+        EmailStatusCode statusCode_;
+    }
+
+    ///
+    alias valid this;
+
+    /*
+     * Params:
+     *     valid = indicates if the email address is valid or not
+     *     localPart = the local part of the email address
+     *     domainPart = the domain part of the email address
+     *        statusCode = the status code
+     */
+    private this (bool valid, string localPart, string domainPart, EmailStatusCode statusCode)
+    {
+        this.valid_ = valid;
+        this.localPart_ = localPart;
+        this.domainPart_ = domainPart;
+        this.statusCode_ = statusCode;
+    }
+
+    /// Indicates if the email address is valid or not.
+    @property bool valid () const
+    {
+        return valid_;
+    }
+
+    /// The local part of the email address, that is, the part before the @ sign.
+    @property string localPart () const
+    {
+        return localPart_;
+    }
+
+    /// The domain part of the email address, that is, the part after the @ sign.
+    @property string domainPart () const
+    {
+        return domainPart_;
+    }
+
+    /// The email status code
+    @property EmailStatusCode statusCode () const
+    {
+        return statusCode_;
+    }
+
+    /// Returns a describing string of the status code
+    @property string status () const
+    {
+        return statusCodeDescription(statusCode_);
+    }
+
+    /// Returns a textual representation of the email status
+    string toString () const
+    {
+        import std.format : format;
+        return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid,
+            localPart, domainPart, statusCode);
+    }
+}
+
+/// Returns a describing string of the given status code
+string statusCodeDescription (EmailStatusCode statusCode)
+{
+    final switch (statusCode)
+    {
+        // Categories
+        case EmailStatusCode.validCategory: return "Address is valid";
+        case EmailStatusCode.dnsWarning: return "Address is valid but a DNS check was not successful";
+        case EmailStatusCode.rfc5321: return "Address is valid for SMTP but has unusual elements";
+
+        case EmailStatusCode.cFoldingWhitespace: return "Address is valid within the message but cannot be used"~
+            " unmodified for the envelope";
+
+        case EmailStatusCode.deprecated_: return "Address contains deprecated elements but may still be valid in"~
+            " restricted contexts";
+
+        case EmailStatusCode.rfc5322: return "The address is only valid according to the broad definition of RFC 5322."~
+            " It is otherwise invalid";
+
+        case EmailStatusCode.any: return "";
+        case EmailStatusCode.none: return "";
+        case EmailStatusCode.warning: return "";
+        case EmailStatusCode.error: return "Address is invalid for any purpose";
+
+        // Diagnoses
+        case EmailStatusCode.valid: return "Address is valid";
+
+        // Address is valid but a DNS check was not successful
+        case EmailStatusCode.dnsWarningNoMXRecord: return "Could not find an MX record for this domain but an A-record"~
+            " does exist";
+
+        case EmailStatusCode.dnsWarningNoRecord: return "Could not find an MX record or an A-record for this domain";
+
+        // Address is valid for SMTP but has unusual elements
+        case EmailStatusCode.rfc5321TopLevelDomain: return "Address is valid but at a Top Level Domain";
+
+        case EmailStatusCode.rfc5321TopLevelDomainNumeric: return "Address is valid but the Top Level Domain begins"~
+            " with a number";
+
+        case EmailStatusCode.rfc5321QuotedString: return "Address is valid but contains a quoted string";
+        case EmailStatusCode.rfc5321AddressLiteral: return "Address is valid but at a literal address not a domain";
+
+        case EmailStatusCode.rfc5321IpV6Deprecated: return "Address is valid but contains a :: that only elides one"~
+            " zero group";
+
+
+        // Address is valid within the message but cannot be used unmodified for the envelope
+        case EmailStatusCode.comment: return "Address contains comments";
+        case EmailStatusCode.foldingWhitespace: return "Address contains Folding White Space";
+
+        // Address contains deprecated elements but may still be valid in restricted contexts
+        case EmailStatusCode.deprecatedLocalPart: return "The local part is in a deprecated form";
+
+        case EmailStatusCode.deprecatedFoldingWhitespace: return "Address contains an obsolete form of"~
+            " Folding White Space";
+
+        case EmailStatusCode.deprecatedQuotedText: return "A quoted string contains a deprecated character";
+        case EmailStatusCode.deprecatedQuotedPair: return "A quoted pair contains a deprecated character";
+        case EmailStatusCode.deprecatedComment: return "Address contains a comment in a position that is deprecated";
+        case EmailStatusCode.deprecatedCommentText: return "A comment contains a deprecated character";
+
+        case EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt: return "Address contains a comment or"~
+            " Folding White Space around the @ sign";
+
+        // The address is only valid according to the broad definition of RFC 5322
+        case EmailStatusCode.rfc5322Domain: return "Address is RFC 5322 compliant but contains domain characters that"~
+        " are not allowed by DNS";
+
+        case EmailStatusCode.rfc5322TooLong: return "Address is too long";
+        case EmailStatusCode.rfc5322LocalTooLong: return "The local part of the address is too long";
+        case EmailStatusCode.rfc5322DomainTooLong: return "The domain part is too long";
+        case EmailStatusCode.rfc5322LabelTooLong: return "The domain part contains an element that is too long";
+        case EmailStatusCode.rfc5322DomainLiteral: return "The domain literal is not a valid RFC 5321 address literal";
+
+        case EmailStatusCode.rfc5322DomainLiteralObsoleteText: return "The domain literal is not a valid RFC 5321"~
+            " address literal and it contains obsolete characters";
+
+        case EmailStatusCode.rfc5322IpV6GroupCount:
+            return "The IPv6 literal address contains the wrong number of groups";
+
+        case EmailStatusCode.rfc5322IpV6TooManyDoubleColons:
+            return "The IPv6 literal address contains too many :: sequences";
+
+        case EmailStatusCode.rfc5322IpV6BadChar: return "The IPv6 address contains an illegal group of characters";
+        case EmailStatusCode.rfc5322IpV6MaxGroups: return "The IPv6 address has too many groups";
+        case EmailStatusCode.rfc5322IpV6ColonStart: return "IPv6 address starts with a single colon";
+        case EmailStatusCode.rfc5322IpV6ColonEnd: return "IPv6 address ends with a single colon";
+
+        // Address is invalid for any purpose
+        case EmailStatusCode.errorExpectingDomainText:
+            return "A domain literal contains a character that is not allowed";
+
+        case EmailStatusCode.errorNoLocalPart: return "Address has no local part";
+        case EmailStatusCode.errorNoDomain: return "Address has no domain part";
+        case EmailStatusCode.errorConsecutiveDots: return "The address may not contain consecutive dots";
+
+        case EmailStatusCode.errorTextAfterCommentFoldingWhitespace:
+            return "Address contains text after a comment or Folding White Space";
+
+        case EmailStatusCode.errorTextAfterQuotedString: return "Address contains text after a quoted string";
+
+        case EmailStatusCode.errorTextAfterDomainLiteral: return "Extra characters were found after the end of"~
+            " the domain literal";
+
+        case EmailStatusCode.errorExpectingQuotedPair:
+            return "The address contains a character that is not allowed in a quoted pair";
+
+        case EmailStatusCode.errorExpectingText: return "Address contains a character that is not allowed";
+
+        case EmailStatusCode.errorExpectingQuotedText:
+            return "A quoted string contains a character that is not allowed";
+
+        case EmailStatusCode.errorExpectingCommentText: return "A comment contains a character that is not allowed";
+        case EmailStatusCode.errorBackslashEnd: return "The address cannot end with a backslash";
+        case EmailStatusCode.errorDotStart: return "Neither part of the address may begin with a dot";
+        case EmailStatusCode.errorDotEnd: return "Neither part of the address may end with a dot";
+        case EmailStatusCode.errorDomainHyphenStart: return "A domain or subdomain cannot begin with a hyphen";
+        case EmailStatusCode.errorDomainHyphenEnd: return "A domain or subdomain cannot end with a hyphen";
+        case EmailStatusCode.errorUnclosedQuotedString: return "Unclosed quoted string";
+        case EmailStatusCode.errorUnclosedComment: return "Unclosed comment";
+        case EmailStatusCode.errorUnclosedDomainLiteral: return "Domain literal is missing its closing bracket";
+
+        case EmailStatusCode.errorFoldingWhitespaceCrflX2:
+            return "Folding White Space contains consecutive CRLF sequences";
+
+        case EmailStatusCode.errorFoldingWhitespaceCrLfEnd: return "Folding White Space ends with a CRLF sequence";
+
+        case EmailStatusCode.errorCrNoLf:
+            return "Address contains a carriage return that is not followed by a line feed";
+    }
+}
+
+/**
+ * An email status code, indicating if an email address is valid or not.
+ * If it is invalid it also indicates why.
+ */
+enum EmailStatusCode
+{
+    // Categories
+
+    /// Address is valid
+    validCategory = 1,
+
+    /// Address is valid but a DNS check was not successful
+    dnsWarning = 7,
+
+    /// Address is valid for SMTP but has unusual elements
+    rfc5321 = 15,
+
+    /// Address is valid within the message but cannot be used unmodified for the envelope
+    cFoldingWhitespace = 31,
+
+    /// Address contains deprecated elements but may still be valid in restricted contexts
+    deprecated_ = 63,
+
+    /// The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid
+    rfc5322 = 127,
+
+    /**
+     * All finer grained error checking is turned on. Address containing errors or
+     * warnings is considered invalid. A specific email status code will be
+     * returned indicating the error/warning of the address.
+     */
+    any = 252,
+
+    /**
+     * Address is either considered valid or not, no finer grained error checking
+     * is performed. Returned email status code will be either Error or Valid.
+     */
+    none = 253,
+
+    /**
+     * Address containing warnings is considered valid, that is,
+     * any status code below 16 is considered valid.
+     */
+    warning = 254,
+
+    /// Address is invalid for any purpose
+    error = 255,
+
+
+
+    // Diagnoses
+
+    /// Address is valid
+    valid = 0,
+
+    // Address is valid but a DNS check was not successful
+
+    /// Could not find an MX record for this domain but an A-record does exist
+    dnsWarningNoMXRecord = 5,
+
+    /// Could not find an MX record or an A-record for this domain
+    dnsWarningNoRecord = 6,
+
+
+
+    // Address is valid for SMTP but has unusual elements
+
+    /// Address is valid but at a Top Level Domain
+    rfc5321TopLevelDomain = 9,
+
+    /// Address is valid but the Top Level Domain begins with a number
+    rfc5321TopLevelDomainNumeric = 10,
+
+    /// Address is valid but contains a quoted string
+    rfc5321QuotedString = 11,
+
+    /// Address is valid but at a literal address not a domain
+    rfc5321AddressLiteral = 12,
+
+    /// Address is valid but contains a :: that only elides one zero group
+    rfc5321IpV6Deprecated = 13,
+
+
+
+    // Address is valid within the message but cannot be used unmodified for the envelope
+
+    /// Address contains comments
+    comment = 17,
+
+    /// Address contains Folding White Space
+    foldingWhitespace = 18,
+
+
+
+    // Address contains deprecated elements but may still be valid in restricted contexts
+
+    /// The local part is in a deprecated form
+    deprecatedLocalPart = 33,
+
+    /// Address contains an obsolete form of Folding White Space
+    deprecatedFoldingWhitespace = 34,
+
+    /// A quoted string contains a deprecated character
+    deprecatedQuotedText = 35,
+
+    /// A quoted pair contains a deprecated character
+    deprecatedQuotedPair = 36,
+
+    /// Address contains a comment in a position that is deprecated
+    deprecatedComment = 37,
+
+    /// A comment contains a deprecated character
+    deprecatedCommentText = 38,
+
+    /// Address contains a comment or Folding White Space around the @ sign
+    deprecatedCommentFoldingWhitespaceNearAt = 49,
+
+
+
+    // The address is only valid according to the broad definition of RFC 5322
+
+    /// Address is RFC 5322 compliant but contains domain characters that are not allowed by DNS
+    rfc5322Domain = 65,
+
+    /// Address is too long
+    rfc5322TooLong = 66,
+
+    /// The local part of the address is too long
+    rfc5322LocalTooLong = 67,
+
+    /// The domain part is too long
+    rfc5322DomainTooLong = 68,
+
+    /// The domain part contains an element that is too long
+    rfc5322LabelTooLong = 69,
+
+    /// The domain literal is not a valid RFC 5321 address literal
+    rfc5322DomainLiteral = 70,
+
+    /// The domain literal is not a valid RFC 5321 address literal and it contains obsolete characters
+    rfc5322DomainLiteralObsoleteText = 71,
+
+    /// The IPv6 literal address contains the wrong number of groups
+    rfc5322IpV6GroupCount = 72,
+
+    /// The IPv6 literal address contains too many :: sequences
+    rfc5322IpV6TooManyDoubleColons = 73,
+
+    /// The IPv6 address contains an illegal group of characters
+    rfc5322IpV6BadChar = 74,
+
+    /// The IPv6 address has too many groups
+    rfc5322IpV6MaxGroups = 75,
+
+    /// IPv6 address starts with a single colon
+    rfc5322IpV6ColonStart = 76,
+
+    /// IPv6 address ends with a single colon
+    rfc5322IpV6ColonEnd = 77,
+
+
+
+    // Address is invalid for any purpose
+
+    /// A domain literal contains a character that is not allowed
+    errorExpectingDomainText = 129,
+
+    /// Address has no local part
+    errorNoLocalPart = 130,
+
+    /// Address has no domain part
+    errorNoDomain = 131,
+
+    /// The address may not contain consecutive dots
+    errorConsecutiveDots = 132,
+
+    /// Address contains text after a comment or Folding White Space
+    errorTextAfterCommentFoldingWhitespace = 133,
+
+    /// Address contains text after a quoted string
+    errorTextAfterQuotedString = 134,
+
+    /// Extra characters were found after the end of the domain literal
+    errorTextAfterDomainLiteral = 135,
+
+    /// The address contains a character that is not allowed in a quoted pair
+    errorExpectingQuotedPair = 136,
+
+    /// Address contains a character that is not allowed
+    errorExpectingText = 137,
+
+    /// A quoted string contains a character that is not allowed
+    errorExpectingQuotedText = 138,
+
+    /// A comment contains a character that is not allowed
+    errorExpectingCommentText = 139,
+
+    /// The address cannot end with a backslash
+    errorBackslashEnd = 140,
+
+    /// Neither part of the address may begin with a dot
+    errorDotStart = 141,
+
+    /// Neither part of the address may end with a dot
+    errorDotEnd = 142,
+
+    /// A domain or subdomain cannot begin with a hyphen
+    errorDomainHyphenStart = 143,
+
+    /// A domain or subdomain cannot end with a hyphen
+    errorDomainHyphenEnd = 144,
+
+    /// Unclosed quoted string
+    errorUnclosedQuotedString = 145,
+
+    /// Unclosed comment
+    errorUnclosedComment = 146,
+
+    /// Domain literal is missing its closing bracket
+    errorUnclosedDomainLiteral = 147,
+
+    /// Folding White Space contains consecutive CRLF sequences
+    errorFoldingWhitespaceCrflX2 = 148,
+
+    /// Folding White Space ends with a CRLF sequence
+    errorFoldingWhitespaceCrLfEnd = 149,
+
+    /// Address contains a carriage return that is not followed by a line feed
+    errorCrNoLf = 150,
+}
+
+private:
+
+// Email parts for the isEmail function
+enum EmailPart
+{
+    // The local part of the email address, that is, the part before the @ sign
+    componentLocalPart,
+
+    // The domain part of the email address, that is, the part after the @ sign.
+    componentDomain,
+
+    componentLiteral,
+    contextComment,
+    contextFoldingWhitespace,
+    contextQuotedString,
+    contextQuotedPair,
+    status
+}
+
+// Miscellaneous string constants
+struct Token
+{
+    enum
+    {
+        at = "@",
+        backslash = `\`,
+        dot = ".",
+        doubleQuote = `"`,
+        openParenthesis = "(",
+        closeParenthesis = ")",
+        openBracket = "[",
+        closeBracket = "]",
+        hyphen = "-",
+        colon = ":",
+        doubleColon = "::",
+        space = " ",
+        tab = "\t",
+        cr = "\r",
+        lf = "\n",
+        ipV6Tag = "IPV6:",
+
+        // US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3)
+        specials = `()<>[]:;@\\,."`
+    }
+}
+
+enum AsciiToken
+{
+    horizontalTab = 9,
+    unitSeparator = 31,
+    delete_ = 127
+}
+
+/*
+ * Returns the maximum of the values in the given array.
+ *
+ * Params:
+ *     arr = the array containing the values to return the maximum of
+ *
+ * Returns: the maximum value
+ */
+T max (T) (T[] arr)
+{
+    import std.algorithm/* : max*/;
+
+    auto max = arr.front;
+
+    foreach (i ; 0 .. arr.length - 1)
+        max = std.algorithm.max(max, arr[i + 1]);
+
+    return max;
+}
+
+///
+unittest
+{
+    assert([1, 2, 3, 4].max() == 4);
+    assert([3, 5, 9, 2, 5].max() == 9);
+    assert([7, 13, 9, 12, 0].max() == 13);
+}
+
+/*
+ * Returns the portion of string specified by the $(D_PARAM start) and
+ * $(D_PARAM length) parameters.
+ *
+ * Params:
+ *     str = the input string. Must be one character or longer.
+ *     start = if $(D_PARAM start) is non-negative, the returned string will start at the
+ *             $(D_PARAM start)'th position in $(D_PARAM str), counting from zero.
+ *             For instance, in the string "abcdef", the character at position 0 is 'a',
+ *             the character at position 2 is 'c', and so forth.
+ *
+ *             If $(D_PARAM start) is negative, the returned string will start at the
+ *             $(D_PARAM start)'th character from the end of $(D_PARAM str).
+ *
+ *             If $(D_PARAM str) is less than or equal to $(D_PARAM start) characters long,
+ *             $(D_KEYWORD true) will be returned.
+ *
+ *     length = if $(D_PARAM length) is given and is positive, the string returned will
+ *              contain at most $(D_PARAM length) characters beginning from $(D_PARAM start)
+ *              (depending on the length of string).
+ *
+ *              If $(D_PARAM length) is given and is negative, then that many characters
+ *              will be omitted from the end of string (after the start position has been
+ *              calculated when a $(D_PARAM start) is negative). If $(D_PARAM start)
+ *              denotes the position of this truncation or beyond, $(D_KEYWORD false)
+ *              will be returned.
+ *
+ *              If $(D_PARAM length) is given and is 0, an empty string will be returned.
+ *
+ *              If $(D_PARAM length) is omitted, the substring starting from $(D_PARAM start)
+ *              until the end of the string will be returned.
+ *
+ * Returns: the extracted part of string, or an empty string.
+ */
+T[] substr (T) (T[] str, ptrdiff_t start = 0, ptrdiff_t length = ptrdiff_t.min)
+{
+    ptrdiff_t end = length;
+
+    if (start < 0)
+    {
+        start = str.length + start;
+
+        if (end < 0)
+        {
+            if (end == ptrdiff_t.min)
+                end = 0;
+
+            end = str.length + end;
+        }
+
+        else
+            end = start + end;
+    }
+
+    else
+    {
+        if (end == ptrdiff_t.min)
+            end = str.length;
+
+        if (end < 0)
+            end = str.length + end;
+
+        else
+            end = start + end;
+    }
+
+    if (start > end)
+        end = start;
+
+    if (end > str.length)
+        end = str.length;
+
+    return str[start .. end];
+}
+
+///
+unittest
+{
+    assert("abcdef".substr(-1) == "f");
+    assert("abcdef".substr(-2) == "ef");
+    assert("abcdef".substr(-3, 1) == "d");
+}
+
+unittest
+{
+    assert("abcdef".substr(0, -1) == "abcde");
+    assert("abcdef".substr(2, -1) == "cde");
+    assert("abcdef".substr(4, -4) == []);
+    assert("abcdef".substr(-3, -1) == "de");
+    assert("abcdef".substr(1, 1) == "b");
+    assert("abcdef".substr(-1, -1) == []);
+}
+
+/*
+ * Compare the two given strings lexicographically. An upper limit of the number of
+ * characters, that will be used in the comparison, can be specified. Supports both
+ * case-sensitive and case-insensitive comparison.
+ *
+ * Params:
+ *     s1 = the first string to be compared
+ *     s2 = the second string to be compared
+ *     length = the length of strings to be used in the comparison.
+ *     caseInsensitive = if true, a case-insensitive comparison will be made,
+ *                       otherwise a case-sensitive comparison will be made
+ *
+ * Returns: (for $(D pred = "a < b")):
+ *
+ * $(BOOKTABLE,
+ * $(TR $(TD $(D < 0))  $(TD $(D s1 < s2) ))
+ * $(TR $(TD $(D = 0))  $(TD $(D s1 == s2)))
+ * $(TR $(TD $(D > 0))  $(TD $(D s1 > s2)))
+ * )
+ */
+int compareFirstN (alias pred = "a < b", S1, S2) (S1 s1, S2 s2, size_t length, bool caseInsensitive = false)
+    if (is(Unqual!(ElementType!(S1)) == dchar) && is(Unqual!(ElementType!(S2)) == dchar))
+{
+    import std.uni : icmp;
+    import std.algorithm : cmp;
+    auto s1End = length <= s1.length ? length : s1.length;
+    auto s2End = length <= s2.length ? length : s2.length;
+
+    auto slice1 = s1[0 .. s1End];
+    auto slice2 = s2[0 .. s2End];
+
+    return caseInsensitive ? slice1.icmp(slice2) : slice1.cmp(slice2);
+}
+
+///
+unittest
+{
+    assert("abc".compareFirstN("abcdef", 3) == 0);
+    assert("abc".compareFirstN("Abc", 3, true) == 0);
+    assert("abc".compareFirstN("abcdef", 6) < 0);
+    assert("abcdef".compareFirstN("abc", 6) > 0);
+}
+
+/*
+ * Returns a range consisting of the elements of the $(D_PARAM input) range that
+ * matches the given $(D_PARAM pattern).
+ *
+ * Params:
+ *     input = the input range
+ *     pattern = the regular expression pattern to search for
+ *     invert = if $(D_KEYWORD true), this function returns the elements of the
+ *              input range that do $(B not) match the given $(D_PARAM pattern).
+ *
+ * Returns: a range containing the matched elements
+ */
+auto grep (Range, Regex) (Range input, Regex pattern, bool invert = false)
+{
+    import std.regex : match;
+    import std.algorithm : filter;
+    auto dg = invert ? (ElementType!(Range) e) { return e.match(pattern).empty; } :
+                       (ElementType!(Range) e) { return !e.match(pattern).empty; };
+
+    return filter!(dg)(input);
+}
+
+///
+unittest
+{
+    import std.algorithm : equal;
+    import std.regex;
+    assert(equal(["ab", "0a", "cd", "1b"].grep(regex(`\d\w`)), ["0a", "1b"]));
+    assert(equal(["abc", "0123", "defg", "4567"].grep(regex(`4567`), true), ["abc", "0123", "defg"]));
+}
+
+/*
+ * Pops the last element of the given range and returns the element.
+ *
+ * Params:
+ *     range = the range to pop the element from
+ *
+ * Returns: the popped element
+ */
+ElementType!(A) pop (A) (ref A a) if (isDynamicArray!(A) && !isNarrowString!(A) && isMutable!(A) && !is(A == void[]))
+{
+    auto e = a.back;
+    a.popBack();
+    return e;
+}
+
+///
+unittest
+{
+    auto array = [0, 1, 2, 3];
+    auto result = array.pop();
+
+    assert(array == [0, 1, 2]);
+    assert(result == 3);
+}
+
+/*
+ * Returns the character at the given index as a string. The returned string will be a
+ * slice of the original string.
+ *
+ * Params:
+ *     str = the string to get the character from
+ *     index = the index of the character to get
+ *     c = the character to return, or any other of the same length
+ *
+ * Returns: the character at the given index as a string
+ */
+const(T)[] get (T) (const(T)[] str, size_t index, dchar c)
+{
+    import std.utf : codeLength;
+    return str[index .. index + codeLength!(T)(c)];
+}
+
+unittest
+{
+    assert("abc".get(1, 'b') == "b");
+    assert("löv".get(1, 'ö') == "ö");
+}
+
+unittest
+{
+    assert("abc".get(1, 'b') == "b");
+    assert("löv".get(1, 'ö') == "ö");
+}
diff --git a/libphobos/src/std/numeric.d b/libphobos/src/std/numeric.d
new file mode 100644
index 00000000000..f14faa68b1b
--- /dev/null
+++ b/libphobos/src/std/numeric.d
@@ -0,0 +1,3090 @@
+// Written in the D programming language.
+
+/**
+This module is a port of a growing fragment of the $(D_PARAM numeric)
+header in Alexander Stepanov's $(LINK2 http://sgi.com/tech/stl,
+Standard Template Library), with a few additions.
+
+Macros:
+
+WIKI = Phobos/StdNumeric
+
+Copyright: Copyright Andrei Alexandrescu 2008 - 2009.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu),
+                   Don Clugston, Robert Jacques
+Source:    $(PHOBOSSRC std/_numeric.d)
+*/
+/*
+         Copyright Andrei Alexandrescu 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.numeric;
+
+import std.complex;
+import std.exception;
+import std.math;
+import std.range.primitives;
+import std.traits;
+import std.typecons;
+
+version(unittest)
+{
+    import std.stdio;
+}
+/// Format flags for CustomFloat.
+public enum CustomFloatFlags
+{
+    /// Adds a sign bit to allow for signed numbers.
+    signed = 1,
+
+    /**
+     * Store values in normalized form by default. The actual precision of the
+     * significand is extended by 1 bit by assuming an implicit leading bit of 1
+     * instead of 0. i.e. $(D 1.nnnn) instead of $(D 0.nnnn).
+     * True for all $(LUCKY IEE754) types
+     */
+    storeNormalized = 2,
+
+    /**
+     * Stores the significand in $(LUCKY IEEE754 denormalized) form when the
+     * exponent is 0. Required to express the value 0.
+     */
+    allowDenorm = 4,
+
+    /// Allows the storage of $(LUCKY IEEE754 _infinity) values.
+    infinity = 8,
+
+    /// Allows the storage of $(LUCKY IEEE754 Not a Number) values.
+    nan = 16,
+
+    /**
+     * If set, select an exponent bias such that max_exp = 1.
+     * i.e. so that the maximum value is >= 1.0 and < 2.0.
+     * Ignored if the exponent bias is manually specified.
+     */
+    probability = 32,
+
+    /// If set, unsigned custom floats are assumed to be negative.
+    negativeUnsigned = 64,
+
+    /**If set, 0 is the only allowed $(LUCKY IEEE754 denormalized) number.
+     * Requires allowDenorm and storeNormalized.
+     */
+    allowDenormZeroOnly = 128 | allowDenorm | storeNormalized,
+
+    /// Include _all of the $(LUCKY IEEE754) options.
+    ieee = signed | storeNormalized | allowDenorm | infinity | nan ,
+
+    /// Include none of the above options.
+    none = 0
+}
+
+private template CustomFloatParams(uint bits)
+{
+    enum CustomFloatFlags flags = CustomFloatFlags.ieee
+                ^ ((bits == 80) ? CustomFloatFlags.storeNormalized : CustomFloatFlags.none);
+    static if (bits ==  8) alias CustomFloatParams = CustomFloatParams!( 4,  3, flags);
+    static if (bits == 16) alias CustomFloatParams = CustomFloatParams!(10,  5, flags);
+    static if (bits == 32) alias CustomFloatParams = CustomFloatParams!(23,  8, flags);
+    static if (bits == 64) alias CustomFloatParams = CustomFloatParams!(52, 11, flags);
+    static if (bits == 80) alias CustomFloatParams = CustomFloatParams!(64, 15, flags);
+}
+
+private template CustomFloatParams(uint precision, uint exponentWidth, CustomFloatFlags flags)
+{
+    import std.meta : AliasSeq;
+    alias CustomFloatParams =
+        AliasSeq!(
+            precision,
+            exponentWidth,
+            flags,
+            (1 << (exponentWidth - ((flags & flags.probability) == 0)))
+             - ((flags & (flags.nan | flags.infinity)) != 0) - ((flags & flags.probability) != 0)
+        ); // ((flags & CustomFloatFlags.probability) == 0)
+}
+
+/**
+ * Allows user code to define custom floating-point formats. These formats are
+ * for storage only; all operations on them are performed by first implicitly
+ * extracting them to $(D real) first. After the operation is completed the
+ * result can be stored in a custom floating-point value via assignment.
+ */
+template CustomFloat(uint bits)
+    if (bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 80)
+{
+    alias CustomFloat = CustomFloat!(CustomFloatParams!(bits));
+}
+
+/// ditto
+template CustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags = CustomFloatFlags.ieee)
+    if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + exponentWidth > 0)
+{
+    alias CustomFloat = CustomFloat!(CustomFloatParams!(precision, exponentWidth, flags));
+}
+
+///
+unittest
+{
+    // Define a 16-bit floating point values
+    CustomFloat!16                                x;     // Using the number of bits
+    CustomFloat!(10, 5)                           y;     // Using the precision and exponent width
+    CustomFloat!(10, 5,CustomFloatFlags.ieee)     z;     // Using the precision, exponent width and format flags
+    CustomFloat!(10, 5,CustomFloatFlags.ieee, 15) w;     // Using the precision, exponent width, format flags and exponent offset bias
+
+    // Use the 16-bit floats mostly like normal numbers
+    w = x*y - 1;
+
+    // Functions calls require conversion
+    z = sin(+x)           + cos(+y);                     // Use unary plus to concisely convert to a real
+    z = sin(x.get!float)  + cos(y.get!float);            // Or use get!T
+    z = sin(cast(float)x) + cos(cast(float)y);           // Or use cast(T) to explicitly convert
+
+    // Define a 8-bit custom float for storing probabilities
+    alias Probability = CustomFloat!(4, 4, CustomFloatFlags.ieee^CustomFloatFlags.probability^CustomFloatFlags.signed );
+    auto p = Probability(0.5);
+}
+
+/// ditto
+struct CustomFloat(uint             precision,  // fraction bits (23 for float)
+                   uint             exponentWidth,  // exponent bits (8 for float)  Exponent width
+                   CustomFloatFlags flags,
+                   uint             bias)
+    if (((flags & flags.signed)  + precision + exponentWidth) % 8 == 0 &&
+        precision + exponentWidth > 0)
+{
+    import std.bitmanip;
+    import std.meta;
+private:
+    // get the correct unsigned bitfield type to support > 32 bits
+    template uType(uint bits)
+    {
+        static if(bits <= size_t.sizeof*8)  alias uType = size_t;
+        else                                alias uType = ulong ;
+    }
+
+    // get the correct signed   bitfield type to support > 32 bits
+    template sType(uint bits)
+    {
+        static if(bits <= ptrdiff_t.sizeof*8-1) alias sType = ptrdiff_t;
+        else                                    alias sType = long;
+    }
+
+    alias T_sig = uType!precision;
+    alias T_exp = uType!exponentWidth;
+    alias T_signed_exp = sType!exponentWidth;
+
+    alias Flags = CustomFloatFlags;
+
+    // Facilitate converting numeric types to custom float
+    union ToBinary(F)
+        if (is(typeof(CustomFloatParams!(F.sizeof*8))) || is(F == real))
+    {
+        F set;
+
+        // If on Linux or Mac, where 80-bit reals are padded, ignore the
+        // padding.
+        import std.algorithm : min;
+        CustomFloat!(CustomFloatParams!(min(F.sizeof*8, 80))) get;
+
+        // Convert F to the correct binary type.
+        static typeof(get) opCall(F value)
+        {
+            ToBinary r;
+            r.set = value;
+            return r.get;
+        }
+        alias get this;
+    }
+
+    // Perform IEEE rounding with round to nearest detection
+    void roundedShift(T,U)(ref T sig, U shift)
+    {
+        if (sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1))
+        {
+            // round to even
+            sig >>= shift;
+            sig  += sig & 1;
+        }
+        else
+        {
+            sig >>= shift - 1;
+            sig  += sig & 1;
+            // Perform standard rounding
+            sig >>= 1;
+        }
+    }
+
+    // Convert the current value to signed exponent, normalized form
+    void toNormalized(T,U)(ref T sig, ref U exp)
+    {
+        sig = significand;
+        auto shift = (T.sizeof*8) - precision;
+        exp = exponent;
+        static if (flags&(Flags.infinity|Flags.nan))
+        {
+            // Handle inf or nan
+            if (exp == exponent_max)
+            {
+                exp = exp.max;
+                sig <<= shift;
+                static if (flags&Flags.storeNormalized)
+                {
+                    // Save inf/nan in denormalized format
+                    sig >>= 1;
+                    sig  += cast(T) 1uL << (T.sizeof*8 - 1);
+                }
+                return;
+            }
+        }
+        if ((~flags&Flags.storeNormalized) ||
+            // Convert denormalized form to normalized form
+            ((flags&Flags.allowDenorm) && exp==0))
+        {
+            if(sig > 0)
+            {
+                import core.bitop : bsr;
+                auto shift2 = precision - bsr(sig);
+                exp  -= shift2-1;
+                shift += shift2;
+            }
+            else                                // value = 0.0
+            {
+                exp = exp.min;
+                return;
+            }
+        }
+        sig <<= shift;
+        exp -= bias;
+    }
+
+    // Set the current value from signed exponent, normalized form
+    void fromNormalized(T,U)(ref T sig, ref U exp)
+    {
+        auto shift = (T.sizeof*8) - precision;
+        if (exp == exp.max)
+        {
+            // infinity or nan
+            exp = exponent_max;
+            static if (flags & Flags.storeNormalized)
+                sig <<= 1;
+
+            // convert back to normalized form
+            static if (~flags & Flags.infinity)
+                // No infinity support?
+                enforce(sig != 0, "Infinity floating point value assigned to a "
+                        ~ typeof(this).stringof~" (no infinity support).");
+
+            static if (~flags & Flags.nan)  // No NaN support?
+                enforce(sig == 0,"NaN floating point value assigned to a " ~
+                        typeof(this).stringof~" (no nan support).");
+            sig >>= shift;
+            return;
+        }
+        if (exp == exp.min)     // 0.0
+        {
+             exp = 0;
+             sig = 0;
+             return;
+        }
+
+        exp += bias;
+        if (exp <= 0)
+        {
+            static if ((flags&Flags.allowDenorm) ||
+                       // Convert from normalized form to denormalized
+                       (~flags&Flags.storeNormalized))
+            {
+                shift += -exp;
+                roundedShift(sig,1);
+                sig   += cast(T) 1uL << (T.sizeof*8 - 1);
+                // Add the leading 1
+                exp    = 0;
+            }
+            else
+                enforce((flags&Flags.storeNormalized) && exp == 0,
+                    "Underflow occured assigning to a " ~
+                    typeof(this).stringof ~ " (no denormal support).");
+        }
+        else
+        {
+            static if (~flags&Flags.storeNormalized)
+            {
+                // Convert from normalized form to denormalized
+                roundedShift(sig,1);
+                sig  += cast(T) 1uL << (T.sizeof*8 - 1);
+                // Add the leading 1
+            }
+        }
+
+        if (shift > 0)
+            roundedShift(sig,shift);
+        if (sig > significand_max)
+        {
+            // handle significand overflow (should only be 1 bit)
+            static if (~flags&Flags.storeNormalized)
+            {
+                sig >>= 1;
+            }
+            else
+                sig &= significand_max;
+            exp++;
+        }
+        static if ((flags&Flags.allowDenormZeroOnly)==Flags.allowDenormZeroOnly)
+        {
+            // disallow non-zero denormals
+            if (exp == 0)
+            {
+                sig <<= 1;
+                if (sig > significand_max && (sig&significand_max) > 0)
+                    // Check and round to even
+                    exp++;
+                sig = 0;
+            }
+        }
+
+        if (exp >= exponent_max)
+        {
+            static if (flags&(Flags.infinity|Flags.nan))
+            {
+                sig         = 0;
+                exp         = exponent_max;
+                static if (~flags&(Flags.infinity))
+                    enforce(false, "Overflow occured assigning to a " ~
+                        typeof(this).stringof~" (no infinity support).");
+            }
+            else
+                enforce(exp == exponent_max, "Overflow occured assigning to a "
+                    ~ typeof(this).stringof~" (no infinity support).");
+        }
+    }
+
+public:
+    static if (precision == 64) // CustomFloat!80 support hack
+    {
+        ulong significand;
+        enum ulong significand_max = ulong.max;
+        mixin(bitfields!(
+            T_exp , "exponent", exponentWidth,
+            bool  , "sign"    , flags & flags.signed ));
+    }
+    else
+    {
+        mixin(bitfields!(
+            T_sig, "significand", precision,
+            T_exp, "exponent"   , exponentWidth,
+            bool , "sign"       , flags & flags.signed ));
+    }
+
+    /// Returns: infinity value
+    static if (flags & Flags.infinity)
+        static @property CustomFloat infinity()
+        {
+            CustomFloat value;
+            static if (flags & Flags.signed)
+            value.sign          = 0;
+            value.significand   = 0;
+            value.exponent      = exponent_max;
+            return value;
+        }
+
+    /// Returns: NaN value
+    static if (flags & Flags.nan)
+        static @property CustomFloat nan()
+        {
+            CustomFloat value;
+            static if (flags & Flags.signed)
+            value.sign          = 0;
+            value.significand   = cast(typeof(significand_max)) 1L << (precision-1);
+            value.exponent      = exponent_max;
+            return value;
+        }
+
+    /// Returns: number of decimal digits of precision
+    static @property size_t dig()
+    {
+        auto shiftcnt =  precision - ((flags&Flags.storeNormalized) != 0);
+        auto x = (shiftcnt == 64) ? 0 : 1uL << shiftcnt;
+        return cast(size_t) log10(x);
+    }
+
+    /// Returns: smallest increment to the value 1
+    static @property CustomFloat epsilon()
+    {
+        CustomFloat value;
+        static if (flags & Flags.signed)
+        value.sign       = 0;
+        T_signed_exp exp = -precision;
+        T_sig        sig = 0;
+
+        value.fromNormalized(sig,exp);
+        if (exp == 0 && sig == 0) // underflowed to zero
+        {
+            static if ((flags&Flags.allowDenorm) ||
+                       (~flags&Flags.storeNormalized))
+                sig = 1;
+            else
+                sig = cast(T) 1uL << (precision - 1);
+        }
+        value.exponent     = cast(value.T_exp) exp;
+        value.significand  = cast(value.T_sig) sig;
+        return value;
+    }
+
+    /// the number of bits in mantissa
+    enum mant_dig = precision + ((flags&Flags.storeNormalized) != 0);
+
+    /// Returns: maximum int value such that 10<sup>max_10_exp</sup> is representable
+    static @property int max_10_exp(){ return cast(int) log10( +max ); }
+
+    /// maximum int value such that 2<sup>max_exp-1</sup> is representable
+    enum max_exp = exponent_max-bias+((~flags&(Flags.infinity|flags.nan))!=0);
+
+    /// Returns: minimum int value such that 10<sup>min_10_exp</sup> is representable
+    static @property int min_10_exp(){ return cast(int) log10( +min_normal ); }
+
+    /// minimum int value such that 2<sup>min_exp-1</sup> is representable as a normalized value
+    enum min_exp = cast(T_signed_exp)-bias +1+ ((flags&Flags.allowDenorm)!=0);
+
+    /// Returns: largest representable value that's not infinity
+    static @property CustomFloat max()
+    {
+        CustomFloat value;
+        static if (flags & Flags.signed)
+        value.sign        = 0;
+        value.exponent    = exponent_max - ((flags&(flags.infinity|flags.nan)) != 0);
+        value.significand = significand_max;
+        return value;
+    }
+
+    /// Returns: smallest representable normalized value that's not 0
+    static @property CustomFloat min_normal() {
+        CustomFloat value;
+        static if (flags & Flags.signed)
+        value.sign        = 0;
+        value.exponent    = 1;
+        static if(flags&Flags.storeNormalized)
+            value.significand = 0;
+        else
+            value.significand = cast(T_sig) 1uL << (precision - 1);
+        return value;
+    }
+
+    /// Returns: real part
+    @property CustomFloat re() { return this; }
+
+    /// Returns: imaginary part
+    static @property CustomFloat im() { return CustomFloat(0.0f); }
+
+    /// Initialize from any $(D real) compatible type.
+    this(F)(F input) if (__traits(compiles, cast(real)input ))
+    {
+        this = input;
+    }
+
+    /// Self assignment
+    void opAssign(F:CustomFloat)(F input)
+    {
+        static if (flags & Flags.signed)
+        sign        = input.sign;
+        exponent    = input.exponent;
+        significand = input.significand;
+    }
+
+    /// Assigns from any $(D real) compatible type.
+    void opAssign(F)(F input)
+        if (__traits(compiles, cast(real)input))
+    {
+        import std.conv: text;
+        static if (staticIndexOf!(Unqual!F, float, double, real) >= 0)
+            auto value = ToBinary!(Unqual!F)(input);
+        else
+            auto value = ToBinary!(real    )(input);
+
+        // Assign the sign bit
+        static if (~flags & Flags.signed)
+            enforce( (!value.sign)^((flags&flags.negativeUnsigned)>0) ,
+                "Incorrectly signed floating point value assigned to a " ~
+                typeof(this).stringof~" (no sign support).");
+        else
+            sign = value.sign;
+
+        CommonType!(T_signed_exp ,value.T_signed_exp) exp = value.exponent;
+        CommonType!(T_sig,        value.T_sig       ) sig = value.significand;
+
+        value.toNormalized(sig,exp);
+        fromNormalized(sig,exp);
+
+        assert(exp <= exponent_max,    text(typeof(this).stringof ~
+            " exponent too large: "   ,exp," > ",exponent_max,   "\t",input,"\t",sig));
+        assert(sig <= significand_max, text(typeof(this).stringof ~
+            " significand too large: ",sig," > ",significand_max,
+            "\t",input,"\t",exp," ",exponent_max));
+        exponent    = cast(T_exp) exp;
+        significand = cast(T_sig) sig;
+    }
+
+    /// Fetches the stored value either as a $(D float), $(D double) or $(D real).
+    @property F get(F)()
+        if (staticIndexOf!(Unqual!F, float, double, real) >= 0)
+    {
+        import std.conv: text;
+
+        ToBinary!F result;
+
+        static if (flags&Flags.signed)
+            result.sign = sign;
+        else
+            result.sign = (flags&flags.negativeUnsigned) > 0;
+
+        CommonType!(T_signed_exp ,result.get.T_signed_exp ) exp = exponent; // Assign the exponent and fraction
+        CommonType!(T_sig,        result.get.T_sig        ) sig = significand;
+
+        toNormalized(sig,exp);
+        result.fromNormalized(sig,exp);
+        assert(exp <= result.exponent_max,    text("get exponent too large: "   ,exp," > ",result.exponent_max) );
+        assert(sig <= result.significand_max, text("get significand too large: ",sig," > ",result.significand_max) );
+        result.exponent     = cast(result.get.T_exp) exp;
+        result.significand  = cast(result.get.T_sig) sig;
+        return result.set;
+    }
+
+    ///ditto
+    T opCast(T)() if (__traits(compiles, get!T )) { return get!T; }
+
+    /// Convert the CustomFloat to a real and perform the relavent operator on the result
+    real opUnary(string op)()
+        if (__traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--")
+    {
+        static if (op=="++" || op=="--")
+        {
+            auto result = get!real;
+            this = mixin(op~`result`);
+            return result;
+        }
+        else
+            return mixin(op~`get!real`);
+    }
+
+    /// ditto
+    real opBinary(string op,T)(T b)
+        if (__traits(compiles, mixin(`get!real`~op~`b`)))
+    {
+        return mixin(`get!real`~op~`b`);
+    }
+
+    /// ditto
+    real opBinaryRight(string op,T)(T a)
+        if ( __traits(compiles, mixin(`a`~op~`get!real`)) &&
+            !__traits(compiles, mixin(`get!real`~op~`b`)))
+    {
+        return mixin(`a`~op~`get!real`);
+    }
+
+    /// ditto
+    int opCmp(T)(auto ref T b)
+        if (__traits(compiles, cast(real)b))
+    {
+        auto x = get!real;
+        auto y = cast(real) b;
+        return  (x>=y)-(x<=y);
+    }
+
+    /// ditto
+    void opOpAssign(string op, T)(auto ref T b)
+        if (__traits(compiles, mixin(`get!real`~op~`cast(real)b`)))
+    {
+        return mixin(`this = this `~op~` cast(real)b`);
+    }
+
+    /// ditto
+    template toString()
+    {
+        import std.format : FormatSpec, formatValue;
+        // Needs to be a template because of DMD @@BUG@@ 13737.
+        void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
+        {
+            sink.formatValue(get!real, fmt);
+        }
+    }
+}
+
+unittest
+{
+    import std.meta;
+    alias FPTypes =
+        AliasSeq!(
+            CustomFloat!(5, 10),
+            CustomFloat!(5, 11, CustomFloatFlags.ieee ^ CustomFloatFlags.signed),
+            CustomFloat!(1, 15, CustomFloatFlags.ieee ^ CustomFloatFlags.signed),
+            CustomFloat!(4, 3, CustomFloatFlags.ieee | CustomFloatFlags.probability ^ CustomFloatFlags.signed)
+        );
+
+    foreach (F; FPTypes)
+    {
+        auto x = F(0.125);
+        assert(x.get!float == 0.125F);
+        assert(x.get!double == 0.125);
+
+        x -= 0.0625;
+        assert(x.get!float == 0.0625F);
+        assert(x.get!double == 0.0625);
+
+        x *= 2;
+        assert(x.get!float == 0.125F);
+        assert(x.get!double == 0.125);
+
+        x /= 4;
+        assert(x.get!float == 0.03125);
+        assert(x.get!double == 0.03125);
+
+        x = 0.5;
+        x ^^= 4;
+        assert(x.get!float == 1 / 16.0F);
+        assert(x.get!double == 1 / 16.0);
+    }
+}
+
+unittest
+{
+    import std.conv;
+    CustomFloat!(5, 10) y = CustomFloat!(5, 10)(0.125);
+    assert(y.to!string == "0.125");
+}
+
+/**
+Defines the fastest type to use when storing temporaries of a
+calculation intended to ultimately yield a result of type $(D F)
+(where $(D F) must be one of $(D float), $(D double), or $(D
+real)). When doing a multi-step computation, you may want to store
+intermediate results as $(D FPTemporary!F).
+
+The necessity of $(D FPTemporary) stems from the optimized
+floating-point operations and registers present in virtually all
+processors. When adding numbers in the example above, the addition may
+in fact be done in $(D real) precision internally. In that case,
+storing the intermediate $(D result) in $(D double format) is not only
+less precise, it is also (surprisingly) slower, because a conversion
+from $(D real) to $(D double) is performed every pass through the
+loop. This being a lose-lose situation, $(D FPTemporary!F) has been
+defined as the $(I fastest) type to use for calculations at precision
+$(D F). There is no need to define a type for the $(I most accurate)
+calculations, as that is always $(D real).
+
+Finally, there is no guarantee that using $(D FPTemporary!F) will
+always be fastest, as the speed of floating-point calculations depends
+on very many factors.
+ */
+template FPTemporary(F)
+    if (isFloatingPoint!F)
+{
+    alias FPTemporary = real;
+}
+
+///
+unittest
+{
+    // Average numbers in an array
+    double avg(in double[] a)
+    {
+        if (a.length == 0) return 0;
+        FPTemporary!double result = 0;
+        foreach (e; a) result += e;
+        return result / a.length;
+    }
+
+    auto a = [1.0, 2.0, 3.0];
+    assert(approxEqual(avg(a), 2));
+}
+
+/**
+Implements the $(WEB tinyurl.com/2zb9yr, secant method) for finding a
+root of the function $(D fun) starting from points $(D [xn_1, x_n])
+(ideally close to the root). $(D Num) may be $(D float), $(D double),
+or $(D real).
+*/
+template secantMethod(alias fun)
+{
+    import std.functional : unaryFun;
+    Num secantMethod(Num)(Num xn_1, Num xn)
+    {
+        auto fxn = unaryFun!(fun)(xn_1), d = xn_1 - xn;
+        typeof(fxn) fxn_1;
+
+        xn = xn_1;
+        while (!approxEqual(d, 0) && isFinite(d))
+        {
+            xn_1 = xn;
+            xn -= d;
+            fxn_1 = fxn;
+            fxn = unaryFun!(fun)(xn);
+            d *= -fxn / (fxn - fxn_1);
+        }
+        return xn;
+    }
+}
+
+///
+unittest
+{
+    float f(float x)
+    {
+        return cos(x) - x*x*x;
+    }
+    auto x = secantMethod!(f)(0f, 1f);
+    assert(approxEqual(x, 0.865474));
+}
+
+unittest
+{
+    scope(failure) stderr.writeln("Failure testing secantMethod");
+    float f(float x)
+    {
+        return cos(x) - x*x*x;
+    }
+    immutable x = secantMethod!(f)(0f, 1f);
+    assert(approxEqual(x, 0.865474));
+    auto d = &f;
+    immutable y = secantMethod!(d)(0f, 1f);
+    assert(approxEqual(y, 0.865474));
+}
+
+
+/**
+ * Return true if a and b have opposite sign.
+ */
+private bool oppositeSigns(T1, T2)(T1 a, T2 b)
+{
+    return signbit(a) != signbit(b);
+}
+
+public:
+
+/**  Find a real root of a real function f(x) via bracketing.
+ *
+ * Given a function $(D f) and a range $(D [a..b]) such that $(D f(a))
+ * and $(D f(b)) have opposite signs or at least one of them equals ±0,
+ * returns the value of $(D x) in
+ * the range which is closest to a root of $(D f(x)).  If $(D f(x))
+ * has more than one root in the range, one will be chosen
+ * arbitrarily.  If $(D f(x)) returns NaN, NaN will be returned;
+ * otherwise, this algorithm is guaranteed to succeed.
+ *
+ * Uses an algorithm based on TOMS748, which uses inverse cubic
+ * interpolation whenever possible, otherwise reverting to parabolic
+ * or secant interpolation. Compared to TOMS748, this implementation
+ * improves worst-case performance by a factor of more than 100, and
+ * typical performance by a factor of 2. For 80-bit reals, most
+ * problems require 8 to 15 calls to $(D f(x)) to achieve full machine
+ * precision. The worst-case performance (pathological cases) is
+ * approximately twice the number of bits.
+ *
+ * References: "On Enclosing Simple Roots of Nonlinear Equations",
+ * G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61,
+ * pp733-744 (1993).  Fortran code available from $(WEB
+ * www.netlib.org,www.netlib.org) as algorithm TOMS478.
+ *
+ */
+T findRoot(T, DF, DT)(scope DF f, in T a, in T b,
+    scope DT tolerance) //= (T a, T b) => false)
+    if(
+        isFloatingPoint!T &&
+        is(typeof(tolerance(T.init, T.init)) : bool) &&
+        is(typeof(f(T.init)) == R, R) && isFloatingPoint!R
+    )
+{
+    immutable fa = f(a);
+    if(fa == 0)
+        return a;
+    immutable fb = f(b);
+    if(fb == 0)
+        return b;
+    immutable r = findRoot(f, a, b, fa, fb, tolerance);
+    // Return the first value if it is smaller or NaN
+    return !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1];
+}
+
+///ditto
+T findRoot(T, DF)(scope DF f, in T a, in T b)
+{
+    return findRoot(f, a, b, (T a, T b) => false);
+}
+
+/** Find root of a real function f(x) by bracketing, allowing the
+ * termination condition to be specified.
+ *
+ * Params:
+ *
+ * f = Function to be analyzed
+ *
+ * ax = Left bound of initial range of $(D f) known to contain the
+ * root.
+ *
+ * bx = Right bound of initial range of $(D f) known to contain the
+ * root.
+ *
+ * fax = Value of $(D f(ax)).
+ *
+ * fbx = Value of $(D f(bx)). $(D fax) and $(D fbx) should have opposite signs.
+ * ($(D f(ax)) and $(D f(bx)) are commonly known in advance.)
+ *
+ *
+ * tolerance = Defines an early termination condition. Receives the
+ *             current upper and lower bounds on the root. The
+ *             delegate must return $(D true) when these bounds are
+ *             acceptable. If this function always returns $(D false),
+ *             full machine precision will be achieved.
+ *
+ * Returns:
+ *
+ * A tuple consisting of two ranges. The first two elements are the
+ * range (in $(D x)) of the root, while the second pair of elements
+ * are the corresponding function values at those points. If an exact
+ * root was found, both of the first two elements will contain the
+ * root, and the second pair of elements will be 0.
+ */
+Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, in T ax, in T bx, in R fax, in R fbx,
+    scope DT tolerance) // = (T a, T b) => false)
+    if(
+        isFloatingPoint!T &&
+        is(typeof(tolerance(T.init, T.init)) : bool) &&
+        is(typeof(f(T.init)) == R) && isFloatingPoint!R
+    )
+in
+{
+    assert(!ax.isNaN() && !bx.isNaN(), "Limits must not be NaN");
+    assert(signbit(fax) != signbit(fbx), "Parameters must bracket the root.");
+}
+body
+{
+    // Author: Don Clugston. This code is (heavily) modified from TOMS748
+    // (www.netlib.org).  The changes to improve the worst-cast performance are
+    // entirely original.
+
+    T a, b, d;  // [a..b] is our current bracket. d is the third best guess.
+    R fa, fb, fd; // Values of f at a, b, d.
+    bool done = false; // Has a root been found?
+
+    // Allow ax and bx to be provided in reverse order
+    if (ax <= bx)
+    {
+        a = ax; fa = fax;
+        b = bx; fb = fbx;
+    }
+    else
+    {
+        a = bx; fa = fbx;
+        b = ax; fb = fax;
+    }
+
+    // Test the function at point c; update brackets accordingly
+    void bracket(T c)
+    {
+        R fc = f(c);
+        if (fc == 0 || fc.isNaN()) // Exact solution, or NaN
+        {
+            a = c;
+            fa = fc;
+            d = c;
+            fd = fc;
+            done = true;
+            return;
+        }
+
+        // Determine new enclosing interval
+        if (signbit(fa) != signbit(fc))
+        {
+            d = b;
+            fd = fb;
+            b = c;
+            fb = fc;
+        }
+        else
+        {
+            d = a;
+            fd = fa;
+            a = c;
+            fa = fc;
+        }
+    }
+
+   /* Perform a secant interpolation. If the result would lie on a or b, or if
+     a and b differ so wildly in magnitude that the result would be meaningless,
+     perform a bisection instead.
+    */
+    static T secant_interpolate(T a, T b, R fa, R fb)
+    {
+        if (( ((a - b) == a) && b!=0) || (a!=0 && ((b - a) == b)))
+        {
+            // Catastrophic cancellation
+            if (a == 0)
+                a = copysign(T(0), b);
+            else if (b == 0)
+                b = copysign(T(0), a);
+            else if (signbit(a) != signbit(b))
+                return 0;
+            T c = ieeeMean(a, b);
+            return c;
+        }
+        // avoid overflow
+        if (b - a > T.max)
+            return b / 2 + a / 2;
+        if (fb - fa > R.max)
+            return a - (b - a) / 2;
+        T c = a - (fa / (fb - fa)) * (b - a);
+        if (c == a || c == b)
+            return (a + b) / 2;
+        return c;
+    }
+
+    /* Uses 'numsteps' newton steps to approximate the zero in [a..b] of the
+       quadratic polynomial interpolating f(x) at a, b, and d.
+       Returns:
+         The approximate zero in [a..b] of the quadratic polynomial.
+    */
+    T newtonQuadratic(int numsteps)
+    {
+        // Find the coefficients of the quadratic polynomial.
+        immutable T a0 = fa;
+        immutable T a1 = (fb - fa)/(b - a);
+        immutable T a2 = ((fd - fb)/(d - b) - a1)/(d - a);
+
+        // Determine the starting point of newton steps.
+        T c = oppositeSigns(a2, fa) ? a  : b;
+
+        // start the safeguarded newton steps.
+        foreach (int i; 0..numsteps)
+        {
+            immutable T pc = a0 + (a1 + a2 * (c - b))*(c - a);
+            immutable T pdc = a1 + a2*((2 * c) - (a + b));
+            if (pdc == 0)
+                return a - a0 / a1;
+            else
+                c = c - pc / pdc;
+        }
+        return c;
+    }
+
+    // On the first iteration we take a secant step:
+    if (fa == 0 || fa.isNaN())
+    {
+        done = true;
+        b = a;
+        fb = fa;
+    }
+    else if (fb == 0 || fb.isNaN())
+    {
+        done = true;
+        a = b;
+        fa = fb;
+    }
+    else
+    {
+        bracket(secant_interpolate(a, b, fa, fb));
+    }
+
+    // Starting with the second iteration, higher-order interpolation can
+    // be used.
+    int itnum = 1;   // Iteration number
+    int baditer = 1; // Num bisections to take if an iteration is bad.
+    T c, e;  // e is our fourth best guess
+    R fe;
+
+whileloop:
+    while (!done && (b != nextUp(a)) && !tolerance(a, b))
+    {
+        T a0 = a, b0 = b; // record the brackets
+
+        // Do two higher-order (cubic or parabolic) interpolation steps.
+        foreach (int QQ; 0..2)
+        {
+            // Cubic inverse interpolation requires that
+            // all four function values fa, fb, fd, and fe are distinct;
+            // otherwise use quadratic interpolation.
+            bool distinct = (fa != fb) && (fa != fd) && (fa != fe)
+                         && (fb != fd) && (fb != fe) && (fd != fe);
+            // The first time, cubic interpolation is impossible.
+            if (itnum<2) distinct = false;
+            bool ok = distinct;
+            if (distinct)
+            {
+                // Cubic inverse interpolation of f(x) at a, b, d, and e
+                immutable q11 = (d - e) * fd / (fe - fd);
+                immutable q21 = (b - d) * fb / (fd - fb);
+                immutable q31 = (a - b) * fa / (fb - fa);
+                immutable d21 = (b - d) * fd / (fd - fb);
+                immutable d31 = (a - b) * fb / (fb - fa);
+
+                immutable q22 = (d21 - q11) * fb / (fe - fb);
+                immutable q32 = (d31 - q21) * fa / (fd - fa);
+                immutable d32 = (d31 - q21) * fd / (fd - fa);
+                immutable q33 = (d32 - q22) * fa / (fe - fa);
+                c = a + (q31 + q32 + q33);
+                if (c.isNaN() || (c <= a) || (c >= b))
+                {
+                    // DAC: If the interpolation predicts a or b, it's
+                    // probable that it's the actual root. Only allow this if
+                    // we're already close to the root.
+                    if (c == a && a - b != a)
+                    {
+                        c = nextUp(a);
+                    }
+                    else if (c == b && a - b != -b)
+                    {
+                        c = nextDown(b);
+                    }
+                    else
+                    {
+                        ok = false;
+                    }
+                }
+            }
+            if (!ok)
+            {
+                // DAC: Alefeld doesn't explain why the number of newton steps
+                // should vary.
+                c = newtonQuadratic(distinct ? 3 : 2);
+                if (c.isNaN() || (c <= a) || (c >= b))
+                {
+                    // Failure, try a secant step:
+                    c = secant_interpolate(a, b, fa, fb);
+                }
+            }
+            ++itnum;
+            e = d;
+            fe = fd;
+            bracket(c);
+            if (done || ( b == nextUp(a)) || tolerance(a, b))
+                break whileloop;
+            if (itnum == 2)
+                continue whileloop;
+        }
+
+        // Now we take a double-length secant step:
+        T u;
+        R fu;
+        if (fabs(fa) < fabs(fb))
+        {
+            u = a;
+            fu = fa;
+        }
+        else
+        {
+            u = b;
+            fu = fb;
+        }
+        c = u - 2 * (fu / (fb - fa)) * (b - a);
+
+        // DAC: If the secant predicts a value equal to an endpoint, it's
+        // probably false.
+        if (c==a || c==b || c.isNaN() || fabs(c - u) > (b - a) / 2)
+        {
+            if ((a-b) == a || (b-a) == b)
+            {
+                if ((a>0 && b<0) || (a<0 && b>0))
+                    c = 0;
+                else
+                {
+                    if (a==0)
+                        c = ieeeMean(copysign(T(0), b), b);
+                    else if (b==0)
+                        c = ieeeMean(copysign(T(0), a), a);
+                    else
+                        c = ieeeMean(a, b);
+                }
+            }
+            else
+            {
+                c = a + (b - a) / 2;
+            }
+        }
+        e = d;
+        fe = fd;
+        bracket(c);
+        if (done || (b == nextUp(a)) || tolerance(a, b))
+            break;
+
+        // IMPROVE THE WORST-CASE PERFORMANCE
+        // We must ensure that the bounds reduce by a factor of 2
+        // in binary space! every iteration. If we haven't achieved this
+        // yet, or if we don't yet know what the exponent is,
+        // perform a binary chop.
+
+        if ((a==0 || b==0 ||
+            (fabs(a) >= T(0.5) * fabs(b) && fabs(b) >= T(0.5) * fabs(a)))
+            &&  (b - a) < T(0.25) * (b0 - a0))
+        {
+            baditer = 1;
+            continue;
+        }
+
+        // DAC: If this happens on consecutive iterations, we probably have a
+        // pathological function. Perform a number of bisections equal to the
+        // total number of consecutive bad iterations.
+
+        if ((b - a) < T(0.25) * (b0 - a0))
+            baditer = 1;
+        foreach (int QQ; 0..baditer)
+        {
+            e = d;
+            fe = fd;
+
+            T w;
+            if ((a>0 && b<0) || (a<0 && b>0))
+                w = 0;
+            else
+            {
+                T usea = a;
+                T useb = b;
+                if (a == 0)
+                    usea = copysign(T(0), b);
+                else if (b == 0)
+                    useb = copysign(T(0), a);
+                w = ieeeMean(usea, useb);
+            }
+            bracket(w);
+        }
+        ++baditer;
+    }
+    return Tuple!(T, T, R, R)(a, b, fa, fb);
+}
+
+///ditto
+Tuple!(T, T, R, R) findRoot(T, R, DF)(scope DF f, in T ax, in T bx, in R fax, in R fbx)
+{
+    return findRoot(f, ax, bx, fax, fbx, (T a, T b) => false);
+}
+
+///ditto
+T findRoot(T, R)(scope R delegate(T) f, in T a, in T b,
+    scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false)
+{
+    return findRoot!(T, R delegate(T), bool delegate(T lo, T hi))(f, a, b, tolerance);
+}
+
+nothrow unittest
+{
+    int numProblems = 0;
+    int numCalls;
+
+    void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2) @nogc @safe nothrow pure
+    {
+        //numCalls=0;
+        //++numProblems;
+        assert(!x1.isNaN() && !x2.isNaN());
+        assert(signbit(x1) != signbit(x2));
+        auto result = findRoot(f, x1, x2, f(x1), f(x2),
+          (real lo, real hi) { return false; });
+
+        auto flo = f(result[0]);
+        auto fhi = f(result[1]);
+        if (flo!=0)
+        {
+            assert(oppositeSigns(flo, fhi));
+        }
+    }
+
+    // Test functions
+    real cubicfn(real x) @nogc @safe nothrow pure
+    {
+        //++numCalls;
+        if (x>float.max)
+            x = float.max;
+        if (x<-double.max)
+            x = -double.max;
+        // This has a single real root at -59.286543284815
+        return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2;
+    }
+    // Test a function with more than one root.
+    real multisine(real x) { ++numCalls; return sin(x); }
+    //testFindRoot( &multisine, 6, 90);
+    //testFindRoot(&cubicfn, -100, 100);
+    //testFindRoot( &cubicfn, -double.max, real.max);
+
+
+/* Tests from the paper:
+ * "On Enclosing Simple Roots of Nonlinear Equations", G. Alefeld, F.A. Potra,
+ *   Yixun Shi, Mathematics of Computation 61, pp733-744 (1993).
+ */
+    // Parameters common to many alefeld tests.
+    int n;
+    real ale_a, ale_b;
+
+    int powercalls = 0;
+
+    real power(real x)
+    {
+        ++powercalls;
+        ++numCalls;
+        return pow(x, n) + double.min_normal;
+    }
+    int [] power_nvals = [3, 5, 7, 9, 19, 25];
+    // Alefeld paper states that pow(x,n) is a very poor case, where bisection
+    // outperforms his method, and gives total numcalls =
+    // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit),
+    // 2624 for brent (6.8/bit)
+    // ... but that is for double, not real80.
+    // This poor performance seems mainly due to catastrophic cancellation,
+    // which is avoided here by the use of ieeeMean().
+    // I get: 231 (0.48/bit).
+    // IE this is 10X faster in Alefeld's worst case
+    numProblems=0;
+    foreach (k; power_nvals)
+    {
+        n = k;
+        //testFindRoot(&power, -1, 10);
+    }
+
+    int powerProblems = numProblems;
+
+    // Tests from Alefeld paper
+
+    int [9] alefeldSums;
+    real alefeld0(real x)
+    {
+        ++alefeldSums[0];
+        ++numCalls;
+        real q =  sin(x) - x/2;
+        for (int i=1; i<20; ++i)
+            q+=(2*i-5.0)*(2*i-5.0)/((x-i*i)*(x-i*i)*(x-i*i));
+        return q;
+    }
+    real alefeld1(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[1];
+        return ale_a*x + exp(ale_b * x);
+    }
+    real alefeld2(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[2];
+        return pow(x, n) - ale_a;
+    }
+    real alefeld3(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[3];
+        return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2);
+    }
+    real alefeld4(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[4];
+        return x*x - pow(1-x, n);
+    }
+    real alefeld5(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[5];
+        return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4);
+    }
+    real alefeld6(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[6];
+        return exp(-n*x)*(x-1.01L) + pow(x, n);
+    }
+    real alefeld7(real x)
+    {
+        ++numCalls;
+        ++alefeldSums[7];
+        return (n*x-1)/((n-1)*x);
+    }
+
+    numProblems=0;
+    //testFindRoot(&alefeld0, PI_2, PI);
+    for (n=1; n<=10; ++n)
+    {
+        //testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L);
+    }
+    ale_a = -40; ale_b = -1;
+    //testFindRoot(&alefeld1, -9, 31);
+    ale_a = -100; ale_b = -2;
+    //testFindRoot(&alefeld1, -9, 31);
+    ale_a = -200; ale_b = -3;
+    //testFindRoot(&alefeld1, -9, 31);
+    int [] nvals_3 = [1, 2, 5, 10, 15, 20];
+    int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20];
+    int [] nvals_6 = [1, 5, 10, 15, 20];
+    int [] nvals_7 = [2, 5, 15, 20];
+
+    for (int i=4; i<12; i+=2)
+    {
+       n = i;
+       ale_a = 0.2;
+       //testFindRoot(&alefeld2, 0, 5);
+       ale_a=1;
+       //testFindRoot(&alefeld2, 0.95, 4.05);
+       //testFindRoot(&alefeld2, 0, 1.5);
+    }
+    foreach (i; nvals_3)
+    {
+        n=i;
+        //testFindRoot(&alefeld3, 0, 1);
+    }
+    foreach (i; nvals_3)
+    {
+        n=i;
+        //testFindRoot(&alefeld4, 0, 1);
+    }
+    foreach (i; nvals_5)
+    {
+        n=i;
+        //testFindRoot(&alefeld5, 0, 1);
+    }
+    foreach (i; nvals_6)
+    {
+        n=i;
+        //testFindRoot(&alefeld6, 0, 1);
+    }
+    foreach (i; nvals_7)
+    {
+        n=i;
+        //testFindRoot(&alefeld7, 0.01L, 1);
+    }
+    real worstcase(real x)
+    {
+        ++numCalls;
+        return x<0.3*real.max? -0.999e-3 : 1.0;
+    }
+    //testFindRoot(&worstcase, -real.max, real.max);
+
+    // just check that the double + float cases compile
+    //findRoot((double x){ return 0.0; }, -double.max, double.max);
+    //findRoot((float x){ return 0.0f; }, -float.max, float.max);
+
+/*
+   int grandtotal=0;
+   foreach (calls; alefeldSums)
+   {
+       grandtotal+=calls;
+   }
+   grandtotal-=2*numProblems;
+   printf("\nALEFELD TOTAL = %d avg = %f (alefeld avg=19.3 for double)\n",
+   grandtotal, (1.0*grandtotal)/numProblems);
+   powercalls -= 2*powerProblems;
+   printf("POWER TOTAL = %d avg = %f ", powercalls,
+        (1.0*powercalls)/powerProblems);
+*/
+    //Issue 14231
+    auto xp = findRoot((float x) => x, 0f, 1f);
+    auto xn = findRoot((float x) => x, -1f, -0f);
+}
+
+//regression control
+unittest
+{
+    static assert(__traits(compiles, findRoot((float x)=>cast(real)x, float.init, float.init)));
+    static assert(__traits(compiles, findRoot!real((x)=>cast(double)x, real.init, real.init)));
+    static assert(__traits(compiles, findRoot((real x)=>cast(double)x, real.init, real.init)));
+}
+
+/**
+Computes $(LUCKY Euclidean distance) between input ranges $(D a) and
+$(D b). The two ranges must have the same length. The three-parameter
+version stops computation as soon as the distance is greater than or
+equal to $(D limit) (this is useful to save computation if a small
+distance is sought).
+ */
+CommonType!(ElementType!(Range1), ElementType!(Range2))
+euclideanDistance(Range1, Range2)(Range1 a, Range2 b)
+    if (isInputRange!(Range1) && isInputRange!(Range2))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        auto t = a.front - b.front;
+        result += t * t;
+    }
+    static if (!haveLen) enforce(b.empty);
+    return sqrt(result);
+}
+
+/// Ditto
+CommonType!(ElementType!(Range1), ElementType!(Range2))
+euclideanDistance(Range1, Range2, F)(Range1 a, Range2 b, F limit)
+    if (isInputRange!(Range1) && isInputRange!(Range2))
+{
+    limit *= limit;
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    for (; ; a.popFront(), b.popFront())
+    {
+        if (a.empty)
+        {
+            static if (!haveLen) enforce(b.empty);
+            break;
+        }
+        auto t = a.front - b.front;
+        result += t * t;
+        if (result >= limit) break;
+    }
+    return sqrt(result);
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+    foreach(T; AliasSeq!(double, const double, immutable double))
+    {
+        T[] a = [ 1.0, 2.0, ];
+        T[] b = [ 4.0, 6.0, ];
+        assert(euclideanDistance(a, b) == 5);
+        assert(euclideanDistance(a, b, 5) == 5);
+        assert(euclideanDistance(a, b, 4) == 5);
+        assert(euclideanDistance(a, b, 2) == 3);
+    }
+}
+
+/**
+Computes the $(LUCKY dot product) of input ranges $(D a) and $(D
+b). The two ranges must have the same length. If both ranges define
+length, the check is done once; otherwise, it is done at each
+iteration.
+ */
+CommonType!(ElementType!(Range1), ElementType!(Range2))
+dotProduct(Range1, Range2)(Range1 a, Range2 b)
+    if (isInputRange!(Range1) && isInputRange!(Range2) &&
+        !(isArray!(Range1) && isArray!(Range2)))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        result += a.front * b.front;
+    }
+    static if (!haveLen) enforce(b.empty);
+    return result;
+}
+
+/// Ditto
+CommonType!(F1, F2)
+dotProduct(F1, F2)(in F1[] avector, in F2[] bvector)
+{
+    immutable n = avector.length;
+    assert(n == bvector.length);
+    auto avec = avector.ptr, bvec = bvector.ptr;
+    Unqual!(typeof(return)) sum0 = 0, sum1 = 0;
+
+    const all_endp = avec + n;
+    const smallblock_endp = avec + (n & ~3);
+    const bigblock_endp = avec + (n & ~15);
+
+    for (; avec != bigblock_endp; avec += 16, bvec += 16)
+    {
+        sum0 += avec[0] * bvec[0];
+        sum1 += avec[1] * bvec[1];
+        sum0 += avec[2] * bvec[2];
+        sum1 += avec[3] * bvec[3];
+        sum0 += avec[4] * bvec[4];
+        sum1 += avec[5] * bvec[5];
+        sum0 += avec[6] * bvec[6];
+        sum1 += avec[7] * bvec[7];
+        sum0 += avec[8] * bvec[8];
+        sum1 += avec[9] * bvec[9];
+        sum0 += avec[10] * bvec[10];
+        sum1 += avec[11] * bvec[11];
+        sum0 += avec[12] * bvec[12];
+        sum1 += avec[13] * bvec[13];
+        sum0 += avec[14] * bvec[14];
+        sum1 += avec[15] * bvec[15];
+    }
+
+    for (; avec != smallblock_endp; avec += 4, bvec += 4)
+    {
+        sum0 += avec[0] * bvec[0];
+        sum1 += avec[1] * bvec[1];
+        sum0 += avec[2] * bvec[2];
+        sum1 += avec[3] * bvec[3];
+    }
+
+    sum0 += sum1;
+
+    /* Do trailing portion in naive loop. */
+    while (avec != all_endp)
+    {
+        sum0 += *avec * *bvec;
+        ++avec;
+        ++bvec;
+    }
+
+    return sum0;
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+    foreach(T; AliasSeq!(double, const double, immutable double))
+    {
+        T[] a = [ 1.0, 2.0, ];
+        T[] b = [ 4.0, 6.0, ];
+        assert(dotProduct(a, b) == 16);
+        assert(dotProduct([1, 3, -5], [4, -2, -1]) == 3);
+    }
+
+    // Make sure the unrolled loop codepath gets tested.
+    static const x =
+        [1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
+    static const y =
+        [2.0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
+    assertCTFEable!({ assert(dotProduct(x, y) == 2280); });
+}
+
+/**
+Computes the $(LUCKY cosine similarity) of input ranges $(D a) and $(D
+b). The two ranges must have the same length. If both ranges define
+length, the check is done once; otherwise, it is done at each
+iteration. If either range has all-zero elements, return 0.
+ */
+CommonType!(ElementType!(Range1), ElementType!(Range2))
+cosineSimilarity(Range1, Range2)(Range1 a, Range2 b)
+    if (isInputRange!(Range1) && isInputRange!(Range2))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) norma = 0, normb = 0, dotprod = 0;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        immutable t1 = a.front, t2 = b.front;
+        norma += t1 * t1;
+        normb += t2 * t2;
+        dotprod += t1 * t2;
+    }
+    static if (!haveLen) enforce(b.empty);
+    if (norma == 0 || normb == 0) return 0;
+    return dotprod / sqrt(norma * normb);
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+    foreach(T; AliasSeq!(double, const double, immutable double))
+    {
+        T[] a = [ 1.0, 2.0, ];
+        T[] b = [ 4.0, 3.0, ];
+        assert(approxEqual(
+                    cosineSimilarity(a, b), 10.0 / sqrt(5.0 * 25),
+                    0.01));
+    }
+}
+
+/**
+Normalizes values in $(D range) by multiplying each element with a
+number chosen such that values sum up to $(D sum). If elements in $(D
+range) sum to zero, assigns $(D sum / range.length) to
+all. Normalization makes sense only if all elements in $(D range) are
+positive. $(D normalize) assumes that is the case without checking it.
+
+Returns: $(D true) if normalization completed normally, $(D false) if
+all elements in $(D range) were zero or if $(D range) is empty.
+ */
+bool normalize(R)(R range, ElementType!(R) sum = 1)
+    if (isForwardRange!(R))
+{
+    ElementType!(R) s = 0;
+    // Step 1: Compute sum and length of the range
+    static if (hasLength!(R))
+    {
+        const length = range.length;
+        foreach (e; range)
+        {
+            s += e;
+        }
+    }
+    else
+    {
+        uint length = 0;
+        foreach (e; range)
+        {
+            s += e;
+            ++length;
+        }
+    }
+    // Step 2: perform normalization
+    if (s == 0)
+    {
+        if (length)
+        {
+            auto f = sum / range.length;
+            foreach (ref e; range) e = f;
+        }
+        return false;
+    }
+    // The path most traveled
+    assert(s >= 0);
+    auto f = sum / s;
+    foreach (ref e; range)
+        e *= f;
+    return true;
+}
+
+///
+unittest
+{
+    double[] a = [];
+    assert(!normalize(a));
+    a = [ 1.0, 3.0 ];
+    assert(normalize(a));
+    assert(a == [ 0.25, 0.75 ]);
+    a = [ 0.0, 0.0 ];
+    assert(!normalize(a));
+    assert(a == [ 0.5, 0.5 ]);
+}
+
+/**
+Compute the sum of binary logarithms of the input range $(D r).
+The error of this method is much smaller than with a naive sum of log2.
+ */
+ElementType!Range sumOfLog2s(Range)(Range r)
+    if (isInputRange!Range && isFloatingPoint!(ElementType!Range))
+{
+    long exp = 0;
+    Unqual!(typeof(return)) x = 1;
+    foreach (e; r)
+    {
+        if (e < 0)
+            return typeof(return).nan;
+        int lexp = void;
+        x *= frexp(e, lexp);
+        exp += lexp;
+        if (x < 0.5)
+        {
+            x *= 2;
+            exp--;
+        }
+    }
+    return exp + log2(x);
+}
+
+///
+unittest
+{
+    assert(sumOfLog2s(new double[0]) == 0);
+    assert(sumOfLog2s([0.0L]) == -real.infinity);
+    assert(sumOfLog2s([-0.0L]) == -real.infinity);
+    assert(sumOfLog2s([2.0L]) == 1);
+    assert(sumOfLog2s([-2.0L]).isNaN());
+    assert(sumOfLog2s([real.nan]).isNaN());
+    assert(sumOfLog2s([-real.nan]).isNaN());
+    assert(sumOfLog2s([real.infinity]) == real.infinity);
+    assert(sumOfLog2s([-real.infinity]).isNaN());
+    assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9);
+}
+
+/**
+Computes $(LUCKY _entropy) of input range $(D r) in bits. This
+function assumes (without checking) that the values in $(D r) are all
+in $(D [0, 1]). For the entropy to be meaningful, often $(D r) should
+be normalized too (i.e., its values should sum to 1). The
+two-parameter version stops evaluating as soon as the intermediate
+result is greater than or equal to $(D max).
+ */
+ElementType!Range entropy(Range)(Range r) if (isInputRange!Range)
+{
+    Unqual!(typeof(return)) result = 0.0;
+    foreach (e; r)
+    {
+        if (!e) continue;
+        result -= e * log2(e);
+    }
+    return result;
+}
+
+/// Ditto
+ElementType!Range entropy(Range, F)(Range r, F max)
+if (isInputRange!Range &&
+    !is(CommonType!(ElementType!Range, F) == void))
+{
+    Unqual!(typeof(return)) result = 0.0;
+    foreach (e; r)
+    {
+        if (!e) continue;
+        result -= e * log2(e);
+        if (result >= max) break;
+    }
+    return result;
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+    foreach(T; AliasSeq!(double, const double, immutable double))
+    {
+        T[] p = [ 0.0, 0, 0, 1 ];
+        assert(entropy(p) == 0);
+        p = [ 0.25, 0.25, 0.25, 0.25 ];
+        assert(entropy(p) == 2);
+        assert(entropy(p, 1) == 1);
+    }
+}
+
+/**
+Computes the $(LUCKY Kullback-Leibler divergence) between input ranges
+$(D a) and $(D b), which is the sum $(D ai * log(ai / bi)). The base
+of logarithm is 2. The ranges are assumed to contain elements in $(D
+[0, 1]). Usually the ranges are normalized probability distributions,
+but this is not required or checked by $(D
+kullbackLeiblerDivergence). If any element $(D bi) is zero and the
+corresponding element $(D ai) nonzero, returns infinity. (Otherwise,
+if $(D ai == 0 && bi == 0), the term $(D ai * log(ai / bi)) is
+considered zero.) If the inputs are normalized, the result is
+positive.
+ */
+CommonType!(ElementType!Range1, ElementType!Range2)
+kullbackLeiblerDivergence(Range1, Range2)(Range1 a, Range2 b)
+    if (isInputRange!(Range1) && isInputRange!(Range2))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        immutable t1 = a.front;
+        if (t1 == 0) continue;
+        immutable t2 = b.front;
+        if (t2 == 0) return result.infinity;
+        assert(t1 > 0 && t2 > 0);
+        result += t1 * log2(t1 / t2);
+    }
+    static if (!haveLen) enforce(b.empty);
+    return result;
+}
+
+///
+unittest
+{
+    double[] p = [ 0.0, 0, 0, 1 ];
+    assert(kullbackLeiblerDivergence(p, p) == 0);
+    double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ];
+    assert(kullbackLeiblerDivergence(p1, p1) == 0);
+    assert(kullbackLeiblerDivergence(p, p1) == 2);
+    assert(kullbackLeiblerDivergence(p1, p) == double.infinity);
+    double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ];
+    assert(approxEqual(kullbackLeiblerDivergence(p1, p2), 0.0719281));
+    assert(approxEqual(kullbackLeiblerDivergence(p2, p1), 0.0780719));
+}
+
+/**
+Computes the $(LUCKY Jensen-Shannon divergence) between $(D a) and $(D
+b), which is the sum $(D (ai * log(2 * ai / (ai + bi)) + bi * log(2 *
+bi / (ai + bi))) / 2). The base of logarithm is 2. The ranges are
+assumed to contain elements in $(D [0, 1]). Usually the ranges are
+normalized probability distributions, but this is not required or
+checked by $(D jensenShannonDivergence). If the inputs are normalized,
+the result is bounded within $(D [0, 1]). The three-parameter version
+stops evaluations as soon as the intermediate result is greater than
+or equal to $(D limit).
+ */
+CommonType!(ElementType!Range1, ElementType!Range2)
+jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b)
+    if (isInputRange!Range1 && isInputRange!Range2 &&
+        is(CommonType!(ElementType!Range1, ElementType!Range2)))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        immutable t1 = a.front;
+        immutable t2 = b.front;
+        immutable avg = (t1 + t2) / 2;
+        if (t1 != 0)
+        {
+            result += t1 * log2(t1 / avg);
+        }
+        if (t2 != 0)
+        {
+            result += t2 * log2(t2 / avg);
+        }
+    }
+    static if (!haveLen) enforce(b.empty);
+    return result / 2;
+}
+
+/// Ditto
+CommonType!(ElementType!Range1, ElementType!Range2)
+jensenShannonDivergence(Range1, Range2, F)(Range1 a, Range2 b, F limit)
+   if (isInputRange!Range1 && isInputRange!Range2 &&
+       is(typeof(CommonType!(ElementType!Range1, ElementType!Range2).init
+                           >= F.init) : bool))
+{
+    enum bool haveLen = hasLength!(Range1) && hasLength!(Range2);
+    static if (haveLen) enforce(a.length == b.length);
+    Unqual!(typeof(return)) result = 0;
+    limit *= 2;
+    for (; !a.empty; a.popFront(), b.popFront())
+    {
+        immutable t1 = a.front;
+        immutable t2 = b.front;
+        immutable avg = (t1 + t2) / 2;
+        if (t1 != 0)
+        {
+            result += t1 * log2(t1 / avg);
+        }
+        if (t2 != 0)
+        {
+            result += t2 * log2(t2 / avg);
+        }
+        if (result >= limit) break;
+    }
+    static if (!haveLen) enforce(b.empty);
+    return result / 2;
+}
+
+///
+unittest
+{
+    double[] p = [ 0.0, 0, 0, 1 ];
+    assert(jensenShannonDivergence(p, p) == 0);
+    double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ];
+    assert(jensenShannonDivergence(p1, p1) == 0);
+    assert(approxEqual(jensenShannonDivergence(p1, p), 0.548795));
+    double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ];
+    assert(approxEqual(jensenShannonDivergence(p1, p2), 0.0186218));
+    assert(approxEqual(jensenShannonDivergence(p2, p1), 0.0186218));
+    assert(approxEqual(jensenShannonDivergence(p2, p1, 0.005), 0.00602366));
+}
+
+/**
+The so-called "all-lengths gap-weighted string kernel" computes a
+similarity measure between $(D s) and $(D t) based on all of their
+common subsequences of all lengths. Gapped subsequences are also
+included.
+
+To understand what $(D gapWeightedSimilarity(s, t, lambda)) computes,
+consider first the case $(D lambda = 1) and the strings $(D s =
+["Hello", "brave", "new", "world"]) and $(D t = ["Hello", "new",
+"world"]). In that case, $(D gapWeightedSimilarity) counts the
+following matches:
+
+$(OL $(LI three matches of length 1, namely $(D "Hello"), $(D "new"),
+and $(D "world");) $(LI three matches of length 2, namely ($(D
+"Hello", "new")), ($(D "Hello", "world")), and ($(D "new", "world"));)
+$(LI one match of length 3, namely ($(D "Hello", "new", "world")).))
+
+The call $(D gapWeightedSimilarity(s, t, 1)) simply counts all of
+these matches and adds them up, returning 7.
+
+----
+string[] s = ["Hello", "brave", "new", "world"];
+string[] t = ["Hello", "new", "world"];
+assert(gapWeightedSimilarity(s, t, 1) == 7);
+----
+
+Note how the gaps in matching are simply ignored, for example ($(D
+"Hello", "new")) is deemed as good a match as ($(D "new",
+"world")). This may be too permissive for some applications. To
+eliminate gapped matches entirely, use $(D lambda = 0):
+
+----
+string[] s = ["Hello", "brave", "new", "world"];
+string[] t = ["Hello", "new", "world"];
+assert(gapWeightedSimilarity(s, t, 0) == 4);
+----
+
+The call above eliminated the gapped matches ($(D "Hello", "new")),
+($(D "Hello", "world")), and ($(D "Hello", "new", "world")) from the
+tally. That leaves only 4 matches.
+
+The most interesting case is when gapped matches still participate in
+the result, but not as strongly as ungapped matches. The result will
+be a smooth, fine-grained similarity measure between the input
+strings. This is where values of $(D lambda) between 0 and 1 enter
+into play: gapped matches are $(I exponentially penalized with the
+number of gaps) with base $(D lambda). This means that an ungapped
+match adds 1 to the return value; a match with one gap in either
+string adds $(D lambda) to the return value; ...; a match with a total
+of $(D n) gaps in both strings adds $(D pow(lambda, n)) to the return
+value. In the example above, we have 4 matches without gaps, 2 matches
+with one gap, and 1 match with three gaps. The latter match is ($(D
+"Hello", "world")), which has two gaps in the first string and one gap
+in the second string, totaling to three gaps. Summing these up we get
+$(D 4 + 2 * lambda + pow(lambda, 3)).
+
+----
+string[] s = ["Hello", "brave", "new", "world"];
+string[] t = ["Hello", "new", "world"];
+assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 0.5 * 2 + 0.125);
+----
+
+$(D gapWeightedSimilarity) is useful wherever a smooth similarity
+measure between sequences allowing for approximate matches is
+needed. The examples above are given with words, but any sequences
+with elements comparable for equality are allowed, e.g. characters or
+numbers. $(D gapWeightedSimilarity) uses a highly optimized dynamic
+programming implementation that needs $(D 16 * min(s.length,
+t.length)) extra bytes of memory and $(BIGOH s.length * t.length) time
+to complete.
+ */
+F gapWeightedSimilarity(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda)
+    if (isRandomAccessRange!(R1) && hasLength!(R1) &&
+        isRandomAccessRange!(R2) && hasLength!(R2))
+{
+    import std.functional : binaryFun;
+    import std.algorithm : swap;
+    import core.stdc.stdlib;
+
+    if (s.length < t.length) return gapWeightedSimilarity(t, s, lambda);
+    if (!t.length) return 0;
+
+    immutable tl1 = t.length + 1;
+    auto dpvi = enforce(cast(F*) malloc(F.sizeof * 2 * t.length));
+    auto dpvi1 = dpvi + t.length;
+    scope(exit) free(dpvi < dpvi1 ? dpvi : dpvi1);
+    dpvi[0 .. t.length] = 0;
+    dpvi1[0] = 0;
+    immutable lambda2 = lambda * lambda;
+
+    F result = 0;
+    foreach (i; 0 .. s.length)
+    {
+        const si = s[i];
+        for (size_t j = 0;;)
+        {
+            F dpsij = void;
+            if (binaryFun!(comp)(si, t[j]))
+            {
+                dpsij = 1 + dpvi[j];
+                result += dpsij;
+            }
+            else
+            {
+                dpsij = 0;
+            }
+            immutable j1 = j + 1;
+            if (j1 == t.length) break;
+            dpvi1[j1] = dpsij + lambda * (dpvi1[j] + dpvi[j1]) -
+                        lambda2 * dpvi[j];
+            j = j1;
+        }
+        swap(dpvi, dpvi1);
+    }
+    return result;
+}
+
+unittest
+{
+    string[] s = ["Hello", "brave", "new", "world"];
+    string[] t = ["Hello", "new", "world"];
+    assert(gapWeightedSimilarity(s, t, 1) == 7);
+    assert(gapWeightedSimilarity(s, t, 0) == 4);
+    assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 2 * 0.5 + 0.125);
+}
+
+/**
+The similarity per $(D gapWeightedSimilarity) has an issue in that it
+grows with the lengths of the two strings, even though the strings are
+not actually very similar. For example, the range $(D ["Hello",
+"world"]) is increasingly similar with the range $(D ["Hello",
+"world", "world", "world",...]) as more instances of $(D "world") are
+appended. To prevent that, $(D gapWeightedSimilarityNormalized)
+computes a normalized version of the similarity that is computed as
+$(D gapWeightedSimilarity(s, t, lambda) /
+sqrt(gapWeightedSimilarity(s, t, lambda) * gapWeightedSimilarity(s, t,
+lambda))). The function $(D gapWeightedSimilarityNormalized) (a
+so-called normalized kernel) is bounded in $(D [0, 1]), reaches $(D 0)
+only for ranges that don't match in any position, and $(D 1) only for
+identical ranges.
+
+The optional parameters $(D sSelfSim) and $(D tSelfSim) are meant for
+avoiding duplicate computation. Many applications may have already
+computed $(D gapWeightedSimilarity(s, s, lambda)) and/or $(D
+gapWeightedSimilarity(t, t, lambda)). In that case, they can be passed
+as $(D sSelfSim) and $(D tSelfSim), respectively.
+ */
+Select!(isFloatingPoint!(F), F, double)
+gapWeightedSimilarityNormalized(alias comp = "a == b", R1, R2, F)
+        (R1 s, R2 t, F lambda, F sSelfSim = F.init, F tSelfSim = F.init)
+    if (isRandomAccessRange!(R1) && hasLength!(R1) &&
+        isRandomAccessRange!(R2) && hasLength!(R2))
+{
+    static bool uncomputed(F n)
+    {
+        static if (isFloatingPoint!(F))
+            return isNaN(n);
+        else
+            return n == n.init;
+    }
+    if (uncomputed(sSelfSim))
+        sSelfSim = gapWeightedSimilarity!(comp)(s, s, lambda);
+    if (sSelfSim == 0) return 0;
+    if (uncomputed(tSelfSim))
+        tSelfSim = gapWeightedSimilarity!(comp)(t, t, lambda);
+    if (tSelfSim == 0) return 0;
+
+    return gapWeightedSimilarity!(comp)(s, t, lambda) /
+           sqrt(cast(typeof(return)) sSelfSim * tSelfSim);
+}
+
+///
+unittest
+{
+    string[] s = ["Hello", "brave", "new", "world"];
+    string[] t = ["Hello", "new", "world"];
+    assert(gapWeightedSimilarity(s, s, 1) == 15);
+    assert(gapWeightedSimilarity(t, t, 1) == 7);
+    assert(gapWeightedSimilarity(s, t, 1) == 7);
+    assert(approxEqual(gapWeightedSimilarityNormalized(s, t, 1),
+                    7.0 / sqrt(15.0 * 7), 0.01));
+}
+
+/**
+Similar to $(D gapWeightedSimilarity), just works in an incremental
+manner by first revealing the matches of length 1, then gapped matches
+of length 2, and so on. The memory requirement is $(BIGOH s.length *
+t.length). The time complexity is $(BIGOH s.length * t.length) time
+for computing each step. Continuing on the previous example:
+
+The implementation is based on the pseudocode in Fig. 4 of the paper
+$(WEB jmlr.csail.mit.edu/papers/volume6/rousu05a/rousu05a.pdf,
+"Efﬁcient Computation of Gapped Substring Kernels on Large Alphabets")
+by Rousu et al., with additional algorithmic and systems-level
+optimizations.
+ */
+struct GapWeightedSimilarityIncremental(Range, F = double)
+    if (isRandomAccessRange!(Range) && hasLength!(Range))
+{
+    import core.stdc.stdlib;
+
+private:
+    Range s, t;
+    F currentValue = 0;
+    F* kl;
+    size_t gram = void;
+    F lambda = void, lambda2 = void;
+
+public:
+/**
+Constructs an object given two ranges $(D s) and $(D t) and a penalty
+$(D lambda). Constructor completes in $(BIGOH s.length * t.length)
+time and computes all matches of length 1.
+ */
+    this(Range s, Range t, F lambda)
+    {
+        enforce(lambda > 0);
+        this.gram = 0;
+        this.lambda = lambda;
+        this.lambda2 = lambda * lambda; // for efficiency only
+
+        size_t iMin = size_t.max, jMin = size_t.max,
+            iMax = 0, jMax = 0;
+        /* initialize */
+        Tuple!(size_t, size_t) * k0;
+        size_t k0len;
+        scope(exit) free(k0);
+        currentValue = 0;
+        foreach (i, si; s)
+        {
+            foreach (j; 0 .. t.length)
+            {
+                if (si != t[j]) continue;
+                k0 = cast(typeof(k0)) realloc(k0, ++k0len * (*k0).sizeof);
+                with (k0[k0len - 1])
+                {
+                    field[0] = i;
+                    field[1] = j;
+                }
+                // Maintain the minimum and maximum i and j
+                if (iMin > i) iMin = i;
+                if (iMax < i) iMax = i;
+                if (jMin > j) jMin = j;
+                if (jMax < j) jMax = j;
+            }
+        }
+
+        if (iMin > iMax) return;
+        assert(k0len);
+
+        currentValue = k0len;
+        // Chop strings down to the useful sizes
+        s = s[iMin .. iMax + 1];
+        t = t[jMin .. jMax + 1];
+        this.s = s;
+        this.t = t;
+
+        // Si = errnoEnforce(cast(F *) malloc(t.length * F.sizeof));
+        kl = errnoEnforce(cast(F *) malloc(s.length * t.length * F.sizeof));
+
+        kl[0 .. s.length * t.length] = 0;
+        foreach (pos; 0 .. k0len)
+        {
+            with (k0[pos])
+            {
+                kl[(field[0] - iMin) * t.length + field[1] -jMin] = lambda2;
+            }
+        }
+    }
+
+    /**
+    Returns: $(D this).
+     */
+    ref GapWeightedSimilarityIncremental opSlice()
+    {
+        return this;
+    }
+
+    /**
+    Computes the match of the popFront length. Completes in $(BIGOH s.length *
+    t.length) time.
+     */
+    void popFront()
+    {
+        import std.algorithm : swap;
+
+        // This is a large source of optimization: if similarity at
+        // the gram-1 level was 0, then we can safely assume
+        // similarity at the gram level is 0 as well.
+        if (empty) return;
+
+        // Now attempt to match gapped substrings of length `gram'
+        ++gram;
+        currentValue = 0;
+
+        auto Si = cast(F*) alloca(t.length * F.sizeof);
+        Si[0 .. t.length] = 0;
+        foreach (i; 0 .. s.length)
+        {
+            const si = s[i];
+            F Sij_1 = 0;
+            F Si_1j_1 = 0;
+            auto kli = kl + i * t.length;
+            for (size_t j = 0;;)
+            {
+                const klij = kli[j];
+                const Si_1j = Si[j];
+                const tmp = klij + lambda * (Si_1j + Sij_1) - lambda2 * Si_1j_1;
+                // now update kl and currentValue
+                if (si == t[j])
+                    currentValue += kli[j] = lambda2 * Si_1j_1;
+                else
+                    kli[j] = 0;
+                // commit to Si
+                Si[j] = tmp;
+                if (++j == t.length) break;
+                // get ready for the popFront step; virtually increment j,
+                // so essentially stuffj_1 <-- stuffj
+                Si_1j_1 = Si_1j;
+                Sij_1 = tmp;
+            }
+        }
+        currentValue /= pow(lambda, 2 * (gram + 1));
+
+        version (none)
+        {
+            Si_1[0 .. t.length] = 0;
+            kl[0 .. min(t.length, maxPerimeter + 1)] = 0;
+            foreach (i; 1 .. min(s.length, maxPerimeter + 1))
+            {
+                auto kli = kl + i * t.length;
+                assert(s.length > i);
+                const si = s[i];
+                auto kl_1i_1 = kl_1 + (i - 1) * t.length;
+                kli[0] = 0;
+                F lastS = 0;
+                foreach (j; 1 .. min(maxPerimeter - i + 1, t.length))
+                {
+                    immutable j_1 = j - 1;
+                    immutable tmp = kl_1i_1[j_1]
+                        + lambda * (Si_1[j] + lastS)
+                        - lambda2 * Si_1[j_1];
+                    kl_1i_1[j_1] = float.nan;
+                    Si_1[j_1] = lastS;
+                    lastS = tmp;
+                    if (si == t[j])
+                    {
+                        currentValue += kli[j] = lambda2 * lastS;
+                    }
+                    else
+                    {
+                        kli[j] = 0;
+                    }
+                }
+                Si_1[t.length - 1] = lastS;
+            }
+            currentValue /= pow(lambda, 2 * (gram + 1));
+            // get ready for the popFront computation
+            swap(kl, kl_1);
+        }
+    }
+
+    /**
+    Returns: The gapped similarity at the current match length (initially
+    1, grows with each call to $(D popFront)).
+    */
+    @property F front() { return currentValue; }
+
+    /**
+    Returns: Whether there are more matches.
+     */
+    @property bool empty()
+    {
+        if (currentValue) return false;
+        if (kl)
+        {
+            free(kl);
+            kl = null;
+        }
+        return true;
+    }
+}
+
+/**
+Ditto
+ */
+GapWeightedSimilarityIncremental!(R, F) gapWeightedSimilarityIncremental(R, F)
+(R r1, R r2, F penalty)
+{
+    return typeof(return)(r1, r2, penalty);
+}
+
+///
+unittest
+{
+    string[] s = ["Hello", "brave", "new", "world"];
+    string[] t = ["Hello", "new", "world"];
+    auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0);
+    assert(simIter.front == 3); // three 1-length matches
+    simIter.popFront();
+    assert(simIter.front == 3); // three 2-length matches
+    simIter.popFront();
+    assert(simIter.front == 1); // one 3-length match
+    simIter.popFront();
+    assert(simIter.empty);     // no more match
+}
+
+unittest
+{
+    import std.conv: text;
+    string[] s = ["Hello", "brave", "new", "world"];
+    string[] t = ["Hello", "new", "world"];
+    auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0);
+    //foreach (e; simIter) writeln(e);
+    assert(simIter.front == 3); // three 1-length matches
+    simIter.popFront();
+    assert(simIter.front == 3, text(simIter.front)); // three 2-length matches
+    simIter.popFront();
+    assert(simIter.front == 1); // one 3-length matches
+    simIter.popFront();
+    assert(simIter.empty);     // no more match
+
+    s = ["Hello"];
+    t = ["bye"];
+    simIter = gapWeightedSimilarityIncremental(s, t, 0.5);
+    assert(simIter.empty);
+
+    s = ["Hello"];
+    t = ["Hello"];
+    simIter = gapWeightedSimilarityIncremental(s, t, 0.5);
+    assert(simIter.front == 1); // one match
+    simIter.popFront();
+    assert(simIter.empty);
+
+    s = ["Hello", "world"];
+    t = ["Hello"];
+    simIter = gapWeightedSimilarityIncremental(s, t, 0.5);
+    assert(simIter.front == 1); // one match
+    simIter.popFront();
+    assert(simIter.empty);
+
+    s = ["Hello", "world"];
+    t = ["Hello", "yah", "world"];
+    simIter = gapWeightedSimilarityIncremental(s, t, 0.5);
+    assert(simIter.front == 2); // two 1-gram matches
+    simIter.popFront();
+    assert(simIter.front == 0.5, text(simIter.front)); // one 2-gram match, 1 gap
+}
+
+unittest
+{
+    GapWeightedSimilarityIncremental!(string[]) sim =
+        GapWeightedSimilarityIncremental!(string[])(
+            ["nyuk", "I", "have", "no", "chocolate", "giba"],
+            ["wyda", "I", "have", "I", "have", "have", "I", "have", "hehe"],
+            0.5);
+    double[] witness = [ 7.0, 4.03125, 0, 0 ];
+    foreach (e; sim)
+    {
+        //writeln(e);
+        assert(e == witness.front);
+        witness.popFront();
+    }
+    witness = [ 3.0, 1.3125, 0.25 ];
+    sim = GapWeightedSimilarityIncremental!(string[])(
+        ["I", "have", "no", "chocolate"],
+        ["I", "have", "some", "chocolate"],
+        0.5);
+    foreach (e; sim)
+    {
+        //writeln(e);
+        assert(e == witness.front);
+        witness.popFront();
+    }
+    assert(witness.empty);
+}
+
+/**
+Computes the greatest common divisor of $(D a) and $(D b) by using
+Euclid's algorithm.
+ */
+T gcd(T)(T a, T b)
+{
+    static if (is(T == const) || is(T == immutable))
+    {
+        return gcd!(Unqual!T)(a, b);
+    }
+    else
+    {
+        static if (T.min < 0)
+        {
+            enforce(a >= 0 && b >=0);
+        }
+        while (b)
+        {
+            auto t = b;
+            b = a % b;
+            a = t;
+        }
+        return a;
+    }
+}
+
+///
+unittest
+{
+    assert(gcd(2 * 5 * 7 * 7, 5 * 7 * 11) == 5 * 7);
+    const int a = 5 * 13 * 23 * 23, b = 13 * 59;
+    assert(gcd(a, b) == 13);
+}
+
+// This is to make tweaking the speed/size vs. accuracy tradeoff easy,
+// though floats seem accurate enough for all practical purposes, since
+// they pass the "approxEqual(inverseFft(fft(arr)), arr)" test even for
+// size 2 ^^ 22.
+private alias lookup_t = float;
+
+/**A class for performing fast Fourier transforms of power of two sizes.
+ * This class encapsulates a large amount of state that is reusable when
+ * performing multiple FFTs of sizes smaller than or equal to that specified
+ * in the constructor.  This results in substantial speedups when performing
+ * multiple FFTs with a known maximum size.  However,
+ * a free function API is provided for convenience if you need to perform a
+ * one-off FFT.
+ *
+ * References:
+ * $(WEB en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm)
+ */
+final class Fft
+{
+    import std.algorithm : map;
+    import core.bitop : bsf;
+    import std.array : uninitializedArray;
+
+private:
+    immutable lookup_t[][] negSinLookup;
+
+    void enforceSize(R)(R range) const
+    {
+        import std.conv: text;
+        enforce(range.length <= size, text(
+            "FFT size mismatch.  Expected ", size, ", got ", range.length));
+    }
+
+    void fftImpl(Ret, R)(Stride!R range, Ret buf) const
+    in
+    {
+        assert(range.length >= 4);
+        assert(isPowerOfTwo(range.length));
+    }
+    body
+    {
+        auto recurseRange = range;
+        recurseRange.doubleSteps();
+
+        if(buf.length > 4)
+        {
+            fftImpl(recurseRange, buf[0..$ / 2]);
+            recurseRange.popHalf();
+            fftImpl(recurseRange, buf[$ / 2..$]);
+        }
+        else
+        {
+            // Do this here instead of in another recursion to save on
+            // recursion overhead.
+            slowFourier2(recurseRange, buf[0..$ / 2]);
+            recurseRange.popHalf();
+            slowFourier2(recurseRange, buf[$ / 2..$]);
+        }
+
+        butterfly(buf);
+    }
+
+    // This algorithm works by performing the even and odd parts of our FFT
+    // using the "two for the price of one" method mentioned at
+    // http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM#Head521
+    // by making the odd terms into the imaginary components of our new FFT,
+    // and then using symmetry to recombine them.
+    void fftImplPureReal(Ret, R)(R range, Ret buf) const
+    in
+    {
+        assert(range.length >= 4);
+        assert(isPowerOfTwo(range.length));
+    }
+    body
+    {
+        alias E = ElementType!R;
+
+        // Converts odd indices of range to the imaginary components of
+        // a range half the size.  The even indices become the real components.
+        static if(isArray!R && isFloatingPoint!E)
+        {
+            // Then the memory layout of complex numbers provides a dirt
+            // cheap way to convert.  This is a common case, so take advantage.
+            auto oddsImag = cast(Complex!E[]) range;
+        }
+        else
+        {
+            // General case:  Use a higher order range.  We can assume
+            // source.length is even because it has to be a power of 2.
+            static struct OddToImaginary
+            {
+                R source;
+                alias C = Complex!(CommonType!(E, typeof(buf[0].re)));
+
+                @property
+                {
+                    C front()
+                    {
+                        return C(source[0], source[1]);
+                    }
+
+                    C back()
+                    {
+                        immutable n = source.length;
+                        return C(source[n - 2], source[n - 1]);
+                    }
+
+                    typeof(this) save()
+                    {
+                        return typeof(this)(source.save);
+                    }
+
+                    bool empty()
+                    {
+                        return source.empty;
+                    }
+
+                    size_t length()
+                    {
+                        return source.length / 2;
+                    }
+                }
+
+                void popFront()
+                {
+                    source.popFront();
+                    source.popFront();
+                }
+
+                void popBack()
+                {
+                    source.popBack();
+                    source.popBack();
+                }
+
+                C opIndex(size_t index)
+                {
+                    return C(source[index * 2], source[index * 2 + 1]);
+                }
+
+                typeof(this) opSlice(size_t lower, size_t upper)
+                {
+                    return typeof(this)(source[lower * 2..upper * 2]);
+                }
+            }
+
+            auto oddsImag = OddToImaginary(range);
+        }
+
+        fft(oddsImag, buf[0..$ / 2]);
+        auto evenFft = buf[0..$ / 2];
+        auto oddFft = buf[$ / 2..$];
+        immutable halfN = evenFft.length;
+        oddFft[0].re = buf[0].im;
+        oddFft[0].im = 0;
+        evenFft[0].im = 0;
+        // evenFft[0].re is already right b/c it's aliased with buf[0].re.
+
+        foreach (k; 1..halfN / 2 + 1)
+        {
+            immutable bufk = buf[k];
+            immutable bufnk = buf[buf.length / 2 - k];
+            evenFft[k].re = 0.5 * (bufk.re + bufnk.re);
+            evenFft[halfN - k].re = evenFft[k].re;
+            evenFft[k].im = 0.5 * (bufk.im - bufnk.im);
+            evenFft[halfN - k].im = -evenFft[k].im;
+
+            oddFft[k].re = 0.5 * (bufk.im + bufnk.im);
+            oddFft[halfN - k].re = oddFft[k].re;
+            oddFft[k].im = 0.5 * (bufnk.re - bufk.re);
+            oddFft[halfN - k].im = -oddFft[k].im;
+        }
+
+        butterfly(buf);
+    }
+
+    void butterfly(R)(R buf) const
+    in
+    {
+        assert(isPowerOfTwo(buf.length));
+    }
+    body
+    {
+        immutable n = buf.length;
+        immutable localLookup = negSinLookup[bsf(n)];
+        assert(localLookup.length == n);
+
+        immutable cosMask = n - 1;
+        immutable cosAdd = n / 4 * 3;
+
+        lookup_t negSinFromLookup(size_t index) pure nothrow
+        {
+            return localLookup[index];
+        }
+
+        lookup_t cosFromLookup(size_t index) pure nothrow
+        {
+            // cos is just -sin shifted by PI * 3 / 2.
+            return localLookup[(index + cosAdd) & cosMask];
+        }
+
+        immutable halfLen = n / 2;
+
+        // This loop is unrolled and the two iterations are interleaved
+        // relative to the textbook FFT to increase ILP.  This gives roughly 5%
+        // speedups on DMD.
+        for (size_t k = 0; k < halfLen; k += 2)
+        {
+            immutable cosTwiddle1 = cosFromLookup(k);
+            immutable sinTwiddle1 = negSinFromLookup(k);
+            immutable cosTwiddle2 = cosFromLookup(k + 1);
+            immutable sinTwiddle2 = negSinFromLookup(k + 1);
+
+            immutable realLower1 = buf[k].re;
+            immutable imagLower1 = buf[k].im;
+            immutable realLower2 = buf[k + 1].re;
+            immutable imagLower2 = buf[k + 1].im;
+
+            immutable upperIndex1 = k + halfLen;
+            immutable upperIndex2 = upperIndex1 + 1;
+            immutable realUpper1 = buf[upperIndex1].re;
+            immutable imagUpper1 = buf[upperIndex1].im;
+            immutable realUpper2 = buf[upperIndex2].re;
+            immutable imagUpper2 = buf[upperIndex2].im;
+
+            immutable realAdd1 = cosTwiddle1 * realUpper1
+                               - sinTwiddle1 * imagUpper1;
+            immutable imagAdd1 = sinTwiddle1 * realUpper1
+                               + cosTwiddle1 * imagUpper1;
+            immutable realAdd2 = cosTwiddle2 * realUpper2
+                               - sinTwiddle2 * imagUpper2;
+            immutable imagAdd2 = sinTwiddle2 * realUpper2
+                               + cosTwiddle2 * imagUpper2;
+
+            buf[k].re += realAdd1;
+            buf[k].im += imagAdd1;
+            buf[k + 1].re += realAdd2;
+            buf[k + 1].im += imagAdd2;
+
+            buf[upperIndex1].re = realLower1 - realAdd1;
+            buf[upperIndex1].im = imagLower1 - imagAdd1;
+            buf[upperIndex2].re = realLower2 - realAdd2;
+            buf[upperIndex2].im = imagLower2 - imagAdd2;
+        }
+    }
+
+    // This constructor is used within this module for allocating the
+    // buffer space elsewhere besides the GC heap.  It's definitely **NOT**
+    // part of the public API and definitely **IS** subject to change.
+    //
+    // Also, this is unsafe because the memSpace buffer will be cast
+    // to immutable.
+    public this(lookup_t[] memSpace)  // Public b/c of bug 4636.
+    {
+        immutable size = memSpace.length / 2;
+
+        /* Create a lookup table of all negative sine values at a resolution of
+         * size and all smaller power of two resolutions.  This may seem
+         * inefficient, but having all the lookups be next to each other in
+         * memory at every level of iteration is a huge win performance-wise.
+         */
+        if(size == 0)
+        {
+            return;
+        }
+
+        enforce(isPowerOfTwo(size),
+            "Can only do FFTs on ranges with a size that is a power of two.");
+        auto table = new lookup_t[][bsf(size) + 1];
+
+        table[$ - 1] = memSpace[$ - size..$];
+        memSpace = memSpace[0..size];
+
+        auto lastRow = table[$ - 1];
+        lastRow[0] = 0;  // -sin(0) == 0.
+        foreach (ptrdiff_t i; 1..size)
+        {
+            // The hard coded cases are for improved accuracy and to prevent
+            // annoying non-zeroness when stuff should be zero.
+
+            if (i == size / 4)
+                lastRow[i] = -1;  // -sin(pi / 2) == -1.
+            else if (i == size / 2)
+                lastRow[i] = 0;   // -sin(pi) == 0.
+            else if (i == size * 3 / 4)
+                lastRow[i] = 1;  // -sin(pi * 3 / 2) == 1
+            else
+                lastRow[i] = -sin(i * 2.0L * PI / size);
+        }
+
+        // Fill in all the other rows with strided versions.
+        foreach (i; 1..table.length - 1)
+        {
+            immutable strideLength = size / (2 ^^ i);
+            auto strided = Stride!(lookup_t[])(lastRow, strideLength);
+            table[i] = memSpace[$ - strided.length..$];
+            memSpace = memSpace[0..$ - strided.length];
+
+            size_t copyIndex;
+            foreach (elem; strided)
+            {
+                table[i][copyIndex++] = elem;
+            }
+        }
+
+        negSinLookup = cast(immutable) table;
+    }
+
+public:
+    /**Create an $(D Fft) object for computing fast Fourier transforms of
+     * power of two sizes of $(D size) or smaller.  $(D size) must be a
+     * power of two.
+     */
+    this(size_t size)
+    {
+        // Allocate all twiddle factor buffers in one contiguous block so that,
+        // when one is done being used, the next one is next in cache.
+        auto memSpace = uninitializedArray!(lookup_t[])(2 * size);
+        this(memSpace);
+    }
+
+    @property size_t size() const
+    {
+        return (negSinLookup is null) ? 0 : negSinLookup[$ - 1].length;
+    }
+
+    /**Compute the Fourier transform of range using the $(BIGOH N log N)
+     * Cooley-Tukey Algorithm.  $(D range) must be a random-access range with
+     * slicing and a length equal to $(D size) as provided at the construction of
+     * this object.  The contents of range can be either  numeric types,
+     * which will be interpreted as pure real values, or complex types with
+     * properties or members $(D .re) and $(D .im) that can be read.
+     *
+     * Note:  Pure real FFTs are automatically detected and the relevant
+     *        optimizations are performed.
+     *
+     * Returns:  An array of complex numbers representing the transformed data in
+     *           the frequency domain.
+     *
+     * Conventions: The exponent is negative and the factor is one,
+     *              i.e., output[j] := sum[ exp(-2 PI i j k / N) input[k] ].
+     */
+    Complex!F[] fft(F = double, R)(R range) const
+        if (isFloatingPoint!F && isRandomAccessRange!R)
+    {
+        enforceSize(range);
+        Complex!F[] ret;
+        if (range.length == 0)
+        {
+            return ret;
+        }
+
+        // Don't waste time initializing the memory for ret.
+        ret = uninitializedArray!(Complex!F[])(range.length);
+
+        fft(range,  ret);
+        return ret;
+    }
+
+    /**Same as the overload, but allows for the results to be stored in a user-
+     * provided buffer.  The buffer must be of the same length as range, must be
+     * a random-access range, must have slicing, and must contain elements that are
+     * complex-like.  This means that they must have a .re and a .im member or
+     * property that can be both read and written and are floating point numbers.
+     */
+    void fft(Ret, R)(R range, Ret buf) const
+        if(isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret)
+    {
+        enforce(buf.length == range.length);
+        enforceSize(range);
+
+        if (range.length == 0)
+        {
+            return;
+        }
+        else if (range.length == 1)
+        {
+            buf[0] = range[0];
+            return;
+        }
+        else if (range.length == 2)
+        {
+            slowFourier2(range, buf);
+            return;
+        }
+        else
+        {
+            alias E = ElementType!R;
+            static if (is(E : real))
+            {
+                return fftImplPureReal(range, buf);
+            }
+            else
+            {
+                static if (is(R : Stride!R))
+                    return fftImpl(range, buf);
+                else
+                    return fftImpl(Stride!R(range, 1), buf);
+            }
+        }
+    }
+
+    /**
+     * Computes the inverse Fourier transform of a range.  The range must be a
+     * random access range with slicing, have a length equal to the size
+     * provided at construction of this object, and contain elements that are
+     * either of type std.complex.Complex or have essentially
+     * the same compile-time interface.
+     *
+     * Returns:  The time-domain signal.
+     *
+     * Conventions: The exponent is positive and the factor is 1/N, i.e.,
+     *              output[j] := (1 / N) sum[ exp(+2 PI i j k / N) input[k] ].
+     */
+    Complex!F[] inverseFft(F = double, R)(R range) const
+        if (isRandomAccessRange!R && isComplexLike!(ElementType!R) && isFloatingPoint!F)
+    {
+        enforceSize(range);
+        Complex!F[] ret;
+        if (range.length == 0)
+        {
+            return ret;
+        }
+
+        // Don't waste time initializing the memory for ret.
+        ret = uninitializedArray!(Complex!F[])(range.length);
+
+        inverseFft(range, ret);
+        return ret;
+    }
+
+    /**
+     * Inverse FFT that allows a user-supplied buffer to be provided.  The buffer
+     * must be a random access range with slicing, and its elements
+     * must be some complex-like type.
+     */
+    void inverseFft(Ret, R)(R range, Ret buf) const
+        if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret)
+    {
+        enforceSize(range);
+
+        auto swapped = map!swapRealImag(range);
+        fft(swapped,  buf);
+
+        immutable lenNeg1 = 1.0 / buf.length;
+        foreach (ref elem; buf)
+        {
+            auto temp = elem.re * lenNeg1;
+            elem.re = elem.im * lenNeg1;
+            elem.im = temp;
+        }
+    }
+}
+
+// This mixin creates an Fft object in the scope it's mixed into such that all
+// memory owned by the object is deterministically destroyed at the end of that
+// scope.
+private enum string MakeLocalFft = q{
+    import core.stdc.stdlib;
+    import core.exception : OutOfMemoryError;
+    auto lookupBuf = (cast(lookup_t*) malloc(range.length * 2 * lookup_t.sizeof))
+                     [0..2 * range.length];
+    if (!lookupBuf.ptr)
+    {
+        throw new OutOfMemoryError(__FILE__, __LINE__);
+    }
+    scope(exit) free(cast(void*) lookupBuf.ptr);
+    auto fftObj = scoped!Fft(lookupBuf);
+};
+
+/**Convenience functions that create an $(D Fft) object, run the FFT or inverse
+ * FFT and return the result.  Useful for one-off FFTs.
+ *
+ * Note:  In addition to convenience, these functions are slightly more
+ *        efficient than manually creating an Fft object for a single use,
+ *        as the Fft object is deterministically destroyed before these
+ *        functions return.
+ */
+Complex!F[] fft(F = double, R)(R range)
+{
+    mixin(MakeLocalFft);
+    return fftObj.fft!(F, R)(range);
+}
+
+/// ditto
+void fft(Ret, R)(R range, Ret buf)
+{
+    mixin(MakeLocalFft);
+    return fftObj.fft!(Ret, R)(range, buf);
+}
+
+/// ditto
+Complex!F[] inverseFft(F = double, R)(R range)
+{
+    mixin(MakeLocalFft);
+    return fftObj.inverseFft!(F, R)(range);
+}
+
+/// ditto
+void inverseFft(Ret, R)(R range, Ret buf)
+{
+    mixin(MakeLocalFft);
+    return fftObj.inverseFft!(Ret, R)(range, buf);
+}
+
+unittest
+{
+    import std.algorithm;
+    import std.range;
+    import std.conv;
+    // Test values from R and Octave.
+    auto arr = [1,2,3,4,5,6,7,8];
+    auto fft1 = fft(arr);
+    assert(approxEqual(map!"a.re"(fft1),
+        [36.0, -4, -4, -4, -4, -4, -4, -4]));
+    assert(approxEqual(map!"a.im"(fft1),
+        [0, 9.6568, 4, 1.6568, 0, -1.6568, -4, -9.6568]));
+
+    auto fft1Retro = fft(retro(arr));
+    assert(approxEqual(map!"a.re"(fft1Retro),
+        [36.0, 4, 4, 4, 4, 4, 4, 4]));
+    assert(approxEqual(map!"a.im"(fft1Retro),
+        [0, -9.6568, -4, -1.6568, 0, 1.6568, 4, 9.6568]));
+
+    auto fft1Float = fft(to!(float[])(arr));
+    assert(approxEqual(map!"a.re"(fft1), map!"a.re"(fft1Float)));
+    assert(approxEqual(map!"a.im"(fft1), map!"a.im"(fft1Float)));
+
+    alias C = Complex!float;
+    auto arr2 = [C(1,2), C(3,4), C(5,6), C(7,8), C(9,10),
+        C(11,12), C(13,14), C(15,16)];
+    auto fft2 = fft(arr2);
+    assert(approxEqual(map!"a.re"(fft2),
+        [64.0, -27.3137, -16, -11.3137, -8, -4.6862, 0, 11.3137]));
+    assert(approxEqual(map!"a.im"(fft2),
+        [72, 11.3137, 0, -4.686, -8, -11.3137, -16, -27.3137]));
+
+    auto inv1 = inverseFft(fft1);
+    assert(approxEqual(map!"a.re"(inv1), arr));
+    assert(reduce!max(map!"a.im"(inv1)) < 1e-10);
+
+    auto inv2 = inverseFft(fft2);
+    assert(approxEqual(map!"a.re"(inv2), map!"a.re"(arr2)));
+    assert(approxEqual(map!"a.im"(inv2), map!"a.im"(arr2)));
+
+    // FFTs of size 0, 1 and 2 are handled as special cases.  Test them here.
+    ushort[] empty;
+    assert(fft(empty) == null);
+    assert(inverseFft(fft(empty)) == null);
+
+    real[] oneElem = [4.5L];
+    auto oneFft = fft(oneElem);
+    assert(oneFft.length == 1);
+    assert(oneFft[0].re == 4.5L);
+    assert(oneFft[0].im == 0);
+
+    auto oneInv = inverseFft(oneFft);
+    assert(oneInv.length == 1);
+    assert(approxEqual(oneInv[0].re, 4.5));
+    assert(approxEqual(oneInv[0].im, 0));
+
+    long[2] twoElems = [8, 4];
+    auto twoFft = fft(twoElems[]);
+    assert(twoFft.length == 2);
+    assert(approxEqual(twoFft[0].re, 12));
+    assert(approxEqual(twoFft[0].im, 0));
+    assert(approxEqual(twoFft[1].re, 4));
+    assert(approxEqual(twoFft[1].im, 0));
+    auto twoInv = inverseFft(twoFft);
+    assert(approxEqual(twoInv[0].re, 8));
+    assert(approxEqual(twoInv[0].im, 0));
+    assert(approxEqual(twoInv[1].re, 4));
+    assert(approxEqual(twoInv[1].im, 0));
+}
+
+// Swaps the real and imaginary parts of a complex number.  This is useful
+// for inverse FFTs.
+C swapRealImag(C)(C input)
+{
+    return C(input.im, input.re);
+}
+
+private:
+// The reasons I couldn't use std.algorithm were b/c its stride length isn't
+// modifiable on the fly and because range has grown some performance hacks
+// for powers of 2.
+struct Stride(R)
+{
+    import core.bitop : bsf;
+    Unqual!R range;
+    size_t _nSteps;
+    size_t _length;
+    alias E = ElementType!(R);
+
+    this(R range, size_t nStepsIn)
+    {
+        this.range = range;
+       _nSteps = nStepsIn;
+       _length = (range.length + _nSteps - 1) / nSteps;
+    }
+
+    size_t length() const @property
+    {
+        return _length;
+    }
+
+    typeof(this) save() @property
+    {
+        auto ret = this;
+        ret.range = ret.range.save;
+        return ret;
+    }
+
+    E opIndex(size_t index)
+    {
+        return range[index * _nSteps];
+    }
+
+    E front() @property
+    {
+        return range[0];
+    }
+
+    void popFront()
+    {
+        if (range.length >= _nSteps)
+        {
+            range = range[_nSteps..range.length];
+            _length--;
+        }
+        else
+        {
+            range = range[0..0];
+            _length = 0;
+        }
+    }
+
+    // Pops half the range's stride.
+    void popHalf()
+    {
+        range = range[_nSteps / 2..range.length];
+    }
+
+    bool empty() const @property
+    {
+        return length == 0;
+    }
+
+    size_t nSteps() const @property
+    {
+        return _nSteps;
+    }
+
+    void doubleSteps()
+    {
+        _nSteps *= 2;
+        _length /= 2;
+    }
+
+    size_t nSteps(size_t newVal) @property
+    {
+        _nSteps = newVal;
+
+        // Using >> bsf(nSteps) is a few cycles faster than / nSteps.
+        _length = (range.length + _nSteps - 1)  >> bsf(nSteps);
+        return newVal;
+    }
+}
+
+// Hard-coded base case for FFT of size 2.  This is actually a TON faster than
+// using a generic slow DFT.  This seems to be the best base case.  (Size 1
+// can be coded inline as buf[0] = range[0]).
+void slowFourier2(Ret, R)(R range, Ret buf)
+{
+    assert(range.length == 2);
+    assert(buf.length == 2);
+    buf[0] = range[0] + range[1];
+    buf[1] = range[0] - range[1];
+}
+
+// Hard-coded base case for FFT of size 4.  Doesn't work as well as the size
+// 2 case.
+void slowFourier4(Ret, R)(R range, Ret buf)
+{
+    alias C = ElementType!Ret;
+
+    assert(range.length == 4);
+    assert(buf.length == 4);
+    buf[0] = range[0] + range[1] + range[2] + range[3];
+    buf[1] = range[0] - range[1] * C(0, 1) - range[2] + range[3] * C(0, 1);
+    buf[2] = range[0] - range[1] + range[2] - range[3];
+    buf[3] = range[0] + range[1] * C(0, 1) - range[2] - range[3] * C(0, 1);
+}
+
+bool isPowerOfTwo(N)(N num)
+    if (isScalarType!N && !isFloatingPoint!N)
+{
+    import core.bitop : bsf, bsr;
+    return bsr(num) == bsf(num);
+}
+
+N roundDownToPowerOf2(N)(N num)
+    if (isScalarType!N && !isFloatingPoint!N)
+{
+    import core.bitop : bsr;
+    return num & (cast(N) 1 << bsr(num));
+}
+
+unittest
+{
+    assert(roundDownToPowerOf2(7) == 4);
+    assert(roundDownToPowerOf2(4) == 4);
+}
+
+template isComplexLike(T)
+{
+    enum bool isComplexLike = is(typeof(T.init.re)) &&
+        is(typeof(T.init.im));
+}
+
+unittest
+{
+    static assert(isComplexLike!(Complex!double));
+    static assert(!isComplexLike!(uint));
+}
diff --git a/libphobos/src/std/outbuffer.d b/libphobos/src/std/outbuffer.d
new file mode 100644
index 00000000000..7f988386ad7
--- /dev/null
+++ b/libphobos/src/std/outbuffer.d
@@ -0,0 +1,439 @@
+// Written in the D programming language.
+
+/**
+Serialize data to $(D ubyte) arrays.
+
+ * Macros:
+ *      WIKI = Phobos/StdOutbuffer
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2015.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_outbuffer.d)
+ */
+module std.outbuffer;
+
+private
+{
+    import core.memory;
+    import core.stdc.stdarg;
+    import core.stdc.stdio;
+    import core.stdc.stdlib;
+    import std.algorithm;
+    import std.string;
+}
+
+/*********************************************
+ * OutBuffer provides a way to build up an array of bytes out
+ * of raw data. It is useful for things like preparing an
+ * array of bytes to write out to a file.
+ * OutBuffer's byte order is the format native to the computer.
+ * To control the byte order (endianness), use a class derived
+ * from OutBuffer.
+ * OutBuffer's internal buffer is allocated with the GC. Pointers
+ * stored into the buffer are scanned by the GC, but you have to
+ * ensure proper alignment, e.g. by using alignSize((void*).sizeof).
+ */
+
+class OutBuffer
+{
+    ubyte[] data;
+    size_t offset;
+
+    invariant()
+    {
+        //printf("this = %p, offset = %x, data.length = %u\n", this, offset, data.length);
+        assert(offset <= data.length);
+    }
+
+  pure nothrow @safe
+  {
+    this()
+    {
+        //printf("in OutBuffer constructor\n");
+    }
+
+    /*********************************
+     * Convert to array of bytes.
+     */
+
+    ubyte[] toBytes() { return data[0 .. offset]; }
+
+    /***********************************
+     * Preallocate nbytes more to the size of the internal buffer.
+     *
+     * This is a
+     * speed optimization, a good guess at the maximum size of the resulting
+     * buffer will improve performance by eliminating reallocations and copying.
+     */
+
+
+    void reserve(size_t nbytes) @trusted
+        in
+        {
+            assert(offset + nbytes >= offset);
+        }
+        out
+        {
+            assert(offset + nbytes <= data.length);
+        }
+        body
+        {
+            //c.stdio.printf("OutBuffer.reserve: length = %d, offset = %d, nbytes = %d\n", data.length, offset, nbytes);
+            if (data.length < offset + nbytes)
+            {
+                void[] vdata = data;
+                vdata.length = (offset + nbytes + 7) * 2; // allocates as void[] to not set BlkAttr.NO_SCAN
+                data = cast(ubyte[])vdata;
+            }
+        }
+
+    /**********************************
+     * put enables OutBuffer to be used as an OutputRange.
+     */
+    alias put = write;
+
+    /*************************************
+     * Append data to the internal buffer.
+     */
+
+    void write(const(ubyte)[] bytes)
+        {
+            reserve(bytes.length);
+            data[offset .. offset + bytes.length] = bytes[];
+            offset += bytes.length;
+        }
+
+    void write(in wchar[] chars) @trusted
+        {
+        write(cast(ubyte[]) chars);
+        }
+
+    void write(const(dchar)[] chars) @trusted
+        {
+        write(cast(ubyte[]) chars);
+        }
+
+    void write(ubyte b)         /// ditto
+        {
+            reserve(ubyte.sizeof);
+            this.data[offset] = b;
+            offset += ubyte.sizeof;
+        }
+
+    void write(byte b) { write(cast(ubyte)b); }         /// ditto
+    void write(char c) { write(cast(ubyte)c); }         /// ditto
+    void write(dchar c) { write(cast(uint)c); }         /// ditto
+
+    void write(ushort w) @trusted                /// ditto
+    {
+        reserve(ushort.sizeof);
+        *cast(ushort *)&data[offset] = w;
+        offset += ushort.sizeof;
+    }
+
+    void write(short s) { write(cast(ushort)s); }               /// ditto
+
+    void write(wchar c) @trusted        /// ditto
+    {
+        reserve(wchar.sizeof);
+        *cast(wchar *)&data[offset] = c;
+        offset += wchar.sizeof;
+    }
+
+    void write(uint w) @trusted         /// ditto
+    {
+        reserve(uint.sizeof);
+        *cast(uint *)&data[offset] = w;
+        offset += uint.sizeof;
+    }
+
+    void write(int i) { write(cast(uint)i); }           /// ditto
+
+    void write(ulong l) @trusted         /// ditto
+    {
+        reserve(ulong.sizeof);
+        *cast(ulong *)&data[offset] = l;
+        offset += ulong.sizeof;
+    }
+
+    void write(long l) { write(cast(ulong)l); }         /// ditto
+
+    void write(float f) @trusted         /// ditto
+    {
+        reserve(float.sizeof);
+        *cast(float *)&data[offset] = f;
+        offset += float.sizeof;
+    }
+
+    void write(double f) @trusted               /// ditto
+    {
+        reserve(double.sizeof);
+        *cast(double *)&data[offset] = f;
+        offset += double.sizeof;
+    }
+
+    void write(real f) @trusted         /// ditto
+    {
+        reserve(real.sizeof);
+        *cast(real *)&data[offset] = f;
+        offset += real.sizeof;
+    }
+
+    void write(in char[] s) @trusted             /// ditto
+    {
+        write(cast(ubyte[])s);
+    }
+
+    void write(OutBuffer buf)           /// ditto
+    {
+        write(buf.toBytes());
+    }
+
+    /****************************************
+     * Append nbytes of 0 to the internal buffer.
+     */
+
+    void fill0(size_t nbytes)
+    {
+        reserve(nbytes);
+        data[offset .. offset + nbytes] = 0;
+        offset += nbytes;
+    }
+
+    /**********************************
+     * 0-fill to align on power of 2 boundary.
+     */
+
+    void alignSize(size_t alignsize)
+    in
+    {
+        assert(alignsize && (alignsize & (alignsize - 1)) == 0);
+    }
+    out
+    {
+        assert((offset & (alignsize - 1)) == 0);
+    }
+    body
+    {
+        auto nbytes = offset & (alignsize - 1);
+        if (nbytes)
+            fill0(alignsize - nbytes);
+    }
+
+    /****************************************
+     * Optimize common special case alignSize(2)
+     */
+
+    void align2()
+    {
+        if (offset & 1)
+            write(cast(byte)0);
+    }
+
+    /****************************************
+     * Optimize common special case alignSize(4)
+     */
+
+    void align4()
+    {
+        if (offset & 3)
+        {   auto nbytes = (4 - offset) & 3;
+            fill0(nbytes);
+        }
+    }
+
+    /**************************************
+     * Convert internal buffer to array of chars.
+     */
+
+    override string toString() const
+    {
+        //printf("OutBuffer.toString()\n");
+        return cast(string) data[0 .. offset].idup;
+    }
+  }
+
+    /*****************************************
+     * Append output of C's vprintf() to internal buffer.
+     */
+
+    void vprintf(string format, va_list args) @trusted nothrow
+    {
+        char[128] buffer;
+        int count;
+
+        // Can't use `tempCString()` here as it will result in compilation error:
+        // "cannot mix core.std.stdlib.alloca() and exception handling".
+        auto f = toStringz(format);
+        auto p = buffer.ptr;
+        auto psize = buffer.length;
+        for (;;)
+        {
+            version(Windows)
+            {
+                count = vsnprintf(p,psize,f,args);
+                if (count != -1)
+                    break;
+                psize *= 2;
+                p = cast(char *) alloca(psize); // buffer too small, try again with larger size
+            }
+            else version(Posix)
+            {
+                count = vsnprintf(p,psize,f,args);
+                if (count == -1)
+                    psize *= 2;
+                else if (count >= psize)
+                    psize = count + 1;
+                else
+                    break;
+                /+
+                if (p != buffer)
+                    c.stdlib.free(p);
+                p = (char *) c.stdlib.malloc(psize);    // buffer too small, try again with larger size
+                +/
+                p = cast(char *) alloca(psize); // buffer too small, try again with larger size
+            }
+            else
+            {
+                static assert(0);
+            }
+        }
+        write(cast(ubyte[]) p[0 .. count]);
+        /+
+        version (Posix)
+        {
+            if (p != buffer)
+                c.stdlib.free(p);
+        }
+        +/
+    }
+
+    /*****************************************
+     * Append output of C's printf() to internal buffer.
+     */
+
+    void printf(string format, ...) @trusted
+    {
+        va_list ap;
+        va_start(ap, format);
+        vprintf(format, ap);
+        va_end(ap);
+    }
+
+    /**
+     * Formats and writes its arguments in text format to the OutBuffer.
+     *
+     * Params:
+     *  fmt = format string as described in $(XREF format, formattedWrite)
+     *  args = arguments to be formatted
+     *
+     * See_Also:
+     *  $(XREF stdio, _writef);
+     *  $(XREF format, formattedWrite);
+     */
+    void writef(Char, A...)(in Char[] fmt, A args)
+    {
+        import std.format : formattedWrite;
+        formattedWrite(this, fmt, args);
+    }
+
+    ///
+    unittest
+    {
+        OutBuffer b = new OutBuffer();
+        b.writef("a%sb", 16);
+        assert(b.toString() == "a16b");
+    }
+
+    /**
+     * Formats and writes its arguments in text format to the OutBuffer,
+     * followed by a newline.
+     *
+     * Params:
+     *  fmt = format string as described in $(XREF format, formattedWrite)
+     *  args = arguments to be formatted
+     *
+     * See_Also:
+     *  $(XREF stdio, _writefln);
+     *  $(XREF format, formattedWrite);
+     */
+    void writefln(Char, A...)(in Char[] fmt, A args)
+    {
+        import std.format : formattedWrite;
+        formattedWrite(this, fmt, args);
+        put('\n');
+    }
+
+    ///
+    unittest
+    {
+        OutBuffer b = new OutBuffer();
+        b.writefln("a%sb", 16);
+        assert(b.toString() == "a16b\n");
+    }
+
+    /*****************************************
+     * At offset index into buffer, create nbytes of space by shifting upwards
+     * all data past index.
+     */
+
+    void spread(size_t index, size_t nbytes) pure nothrow @safe
+        in
+        {
+            assert(index <= offset);
+        }
+        body
+        {
+            reserve(nbytes);
+
+            // This is an overlapping copy - should use memmove()
+            for (size_t i = offset; i > index; )
+            {
+                --i;
+                data[i + nbytes] = data[i];
+            }
+            offset += nbytes;
+        }
+}
+
+unittest
+{
+    //printf("Starting OutBuffer test\n");
+
+    OutBuffer buf = new OutBuffer();
+
+    //printf("buf = %p\n", buf);
+    //printf("buf.offset = %x\n", buf.offset);
+    assert(buf.offset == 0);
+    buf.write("hello"[]);
+    buf.write(cast(byte)0x20);
+    buf.write("world"[]);
+    buf.printf(" %d", 6);
+    //auto s = buf.toString();
+    //printf("buf = '%.*s'\n", s.length, s.ptr);
+    assert(cmp(buf.toString(), "hello world 6") == 0);
+}
+
+unittest
+{
+    import std.range;
+    static assert(isOutputRange!(OutBuffer, char));
+
+    import std.algorithm;
+  {
+    OutBuffer buf = new OutBuffer();
+    "hello".copy(buf);
+    assert(buf.toBytes() == "hello");
+  }
+  {
+    OutBuffer buf = new OutBuffer();
+    "hello"w.copy(buf);
+    assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00");
+  }
+  {
+    OutBuffer buf = new OutBuffer();
+    "hello"d.copy(buf);
+    assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00");
+  }
+}
diff --git a/libphobos/src/std/parallelism.d b/libphobos/src/std/parallelism.d
new file mode 100644
index 00000000000..39711c0f1c6
--- /dev/null
+++ b/libphobos/src/std/parallelism.d
@@ -0,0 +1,4561 @@
+/**
+$(D std._parallelism) implements high-level primitives for SMP _parallelism.
+These include parallel foreach, parallel reduce, parallel eager map, pipelining
+and future/promise _parallelism.  $(D std._parallelism) is recommended when the
+same operation is to be executed in parallel on different data, or when a
+function is to be executed in a background thread and its result returned to a
+well-defined main thread.  For communication between arbitrary threads, see
+$(D std.concurrency).
+
+$(D std._parallelism) is based on the concept of a $(D Task).  A $(D Task) is an
+object that represents the fundamental unit of work in this library and may be
+executed in parallel with any other $(D Task).  Using $(D Task)
+directly allows programming with a future/promise paradigm.  All other
+supported _parallelism paradigms (parallel foreach, map, reduce, pipelining)
+represent an additional level of abstraction over $(D Task).  They
+automatically create one or more $(D Task) objects, or closely related types
+that are conceptually identical but not part of the public API.
+
+After creation, a $(D Task) may be executed in a new thread, or submitted
+to a $(D TaskPool) for execution.  A $(D TaskPool) encapsulates a task queue
+and its worker threads.  Its purpose is to efficiently map a large
+number of $(D Task)s onto a smaller number of threads.  A task queue is a
+FIFO queue of $(D Task) objects that have been submitted to the
+$(D TaskPool) and are awaiting execution.  A worker thread is a thread that
+is associated with exactly one task queue.  It executes the $(D Task) at the
+front of its queue when the queue has work available, or sleeps when
+no work is available.  Each task queue is associated with zero or
+more worker threads.  If the result of a $(D Task) is needed before execution
+by a worker thread has begun, the $(D Task) can be removed from the task queue
+and executed immediately in the thread where the result is needed.
+
+Warning:  Unless marked as $(D @trusted) or $(D @safe), artifacts in
+          this module allow implicit data sharing between threads and cannot
+          guarantee that client code is free from low level data races.
+
+Synopsis:
+
+---
+import std.algorithm, std.parallelism, std.range;
+
+void main() {
+    // Parallel reduce can be combined with
+    // std.algorithm.map to interesting effect.
+    // The following example (thanks to Russel Winder)
+    // calculates pi by quadrature  using
+    // std.algorithm.map and TaskPool.reduce.
+    // getTerm is evaluated in parallel as needed by
+    // TaskPool.reduce.
+    //
+    // Timings on an Athlon 64 X2 dual core machine:
+    //
+    // TaskPool.reduce:       12.170 s
+    // std.algorithm.reduce:  24.065 s
+
+    immutable n = 1_000_000_000;
+    immutable delta = 1.0 / n;
+
+    real getTerm(int i)
+    {
+        immutable x = ( i - 0.5 ) * delta;
+        return delta / ( 1.0 + x * x ) ;
+    }
+
+    immutable pi = 4.0 * taskPool.reduce!"a + b"(
+        std.algorithm.map!getTerm(iota(n))
+    );
+}
+---
+
+Source:    $(PHOBOSSRC std/_parallelism.d)
+Author:  David Simcha
+Copyright:  Copyright (c) 2009-2011, David Simcha.
+License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
+*/
+module std.parallelism;
+
+import core.atomic;
+import core.exception;
+import core.memory;
+import core.sync.condition;
+import core.thread;
+
+import std.algorithm;
+import std.conv;
+import std.exception;
+import std.functional;
+import std.math;
+import std.meta;
+import std.range;
+import std.traits;
+import std.typecons;
+
+version(OSX)
+{
+    version = useSysctlbyname;
+}
+else version(FreeBSD)
+{
+    version = useSysctlbyname;
+}
+else version(NetBSD)
+{
+    version = useSysctlbyname;
+}
+
+
+version(Windows)
+{
+    // BUGS:  Only works on Windows 2000 and above.
+    shared static this()
+    {
+        import core.sys.windows.windows;
+
+        SYSTEM_INFO si;
+        GetSystemInfo(&si);
+        totalCPUs = max(1, cast(uint) si.dwNumberOfProcessors);
+    }
+
+}
+else version(linux)
+{
+    import core.sys.posix.unistd;
+
+    shared static this()
+    {
+        totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN);
+    }
+}
+else version(Solaris)
+{
+    import core.sys.posix.unistd;
+
+    shared static this()
+    {
+        totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN);
+    }
+}
+else version(useSysctlbyname)
+{
+    extern(C) int sysctlbyname(
+        const char *, void *, size_t *, void *, size_t
+    );
+
+    shared static this()
+    {
+        version(OSX)
+        {
+            auto nameStr = "machdep.cpu.core_count\0".ptr;
+        }
+        else version(FreeBSD)
+        {
+            auto nameStr = "hw.ncpu\0".ptr;
+        }
+        else version(NetBSD)
+        {
+            auto nameStr = "hw.ncpu\0".ptr;
+        }
+
+        uint ans;
+        size_t len = uint.sizeof;
+        sysctlbyname(nameStr, &ans, &len, null, 0);
+        totalCPUs = ans;
+    }
+
+}
+else
+{
+    static assert(0, "Don't know how to get N CPUs on this OS.");
+}
+
+immutable size_t cacheLineSize;
+shared static this()
+{
+    import core.cpuid : datacache;
+    size_t lineSize = 0;
+    foreach (cachelevel; datacache)
+    {
+        if (cachelevel.lineSize > lineSize && cachelevel.lineSize < uint.max)
+        {
+            lineSize = cachelevel.lineSize;
+        }
+    }
+
+    cacheLineSize = lineSize;
+}
+
+
+/* Atomics code.  These forward to core.atomic, but are written like this
+   for two reasons:
+
+   1.  They used to actually contain ASM code and I don' want to have to change
+       to directly calling core.atomic in a zillion different places.
+
+   2.  core.atomic has some misc. issues that make my use cases difficult
+       without wrapping it.  If I didn't wrap it, casts would be required
+       basically everywhere.
+*/
+private void atomicSetUbyte(T)(ref T stuff, T newVal)
+if (__traits(isIntegral, T) && is(T : ubyte))
+{
+    //core.atomic.cas(cast(shared) &stuff, stuff, newVal);
+    atomicStore(*(cast(shared) &stuff), newVal);
+}
+
+private ubyte atomicReadUbyte(T)(ref T val)
+if (__traits(isIntegral, T) && is(T : ubyte))
+{
+    return atomicLoad(*(cast(shared) &val));
+}
+
+// This gets rid of the need for a lot of annoying casts in other parts of the
+// code, when enums are involved.
+private bool atomicCasUbyte(T)(ref T stuff, T testVal, T newVal)
+if (__traits(isIntegral, T) && is(T : ubyte))
+{
+    return core.atomic.cas(cast(shared) &stuff, testVal, newVal);
+}
+
+/*--------------------- Generic helper functions, etc.------------------------*/
+private template MapType(R, functions...)
+{
+    static assert(functions.length);
+
+    ElementType!R e = void;
+    alias MapType =
+        typeof(adjoin!(staticMap!(unaryFun, functions))(e));
+}
+
+private template ReduceType(alias fun, R, E)
+{
+    alias ReduceType = typeof(binaryFun!fun(E.init, ElementType!R.init));
+}
+
+private template noUnsharedAliasing(T)
+{
+    enum bool noUnsharedAliasing = !hasUnsharedAliasing!T;
+}
+
+// This template tests whether a function may be executed in parallel from
+// @safe code via Task.executeInNewThread().  There is an additional
+// requirement for executing it via a TaskPool.  (See isSafeReturn).
+private template isSafeTask(F)
+{
+    enum bool isSafeTask =
+        (functionAttributes!F & (FunctionAttribute.safe | FunctionAttribute.trusted)) != 0 &&
+        (functionAttributes!F & FunctionAttribute.ref_) == 0 &&
+        (isFunctionPointer!F || !hasUnsharedAliasing!F) &&
+        allSatisfy!(noUnsharedAliasing, Parameters!F);
+}
+
+unittest
+{
+    alias F1 = void function() @safe;
+    alias F2 = void function();
+    alias F3 = void function(uint, string) @trusted;
+    alias F4 = void function(uint, char[]);
+
+    static assert( isSafeTask!F1);
+    static assert(!isSafeTask!F2);
+    static assert( isSafeTask!F3);
+    static assert(!isSafeTask!F4);
+
+    alias F5 = uint[] function(uint, string) pure @trusted;
+    static assert( isSafeTask!F5);
+}
+
+// This function decides whether Tasks that meet all of the other requirements
+// for being executed from @safe code can be executed on a TaskPool.
+// When executing via TaskPool, it's theoretically possible
+// to return a value that is also pointed to by a worker thread's thread local
+// storage.  When executing from executeInNewThread(), the thread that executed
+// the Task is terminated by the time the return value is visible in the calling
+// thread, so this is a non-issue.  It's also a non-issue for pure functions
+// since they can't read global state.
+private template isSafeReturn(T)
+{
+    static if(!hasUnsharedAliasing!(T.ReturnType))
+    {
+        enum isSafeReturn = true;
+    }
+    else static if(T.isPure)
+    {
+        enum isSafeReturn = true;
+    }
+    else
+    {
+        enum isSafeReturn = false;
+    }
+}
+
+private template randAssignable(R)
+{
+    enum randAssignable = isRandomAccessRange!R && hasAssignableElements!R;
+}
+
+private enum TaskStatus : ubyte
+{
+    notStarted,
+    inProgress,
+    done
+}
+
+private template AliasReturn(alias fun, T...)
+{
+    alias AliasReturn = typeof({ T args; return fun(args); });
+}
+
+// Should be private, but std.algorithm.reduce is used in the zero-thread case
+// and won't work w/ private.
+template reduceAdjoin(functions...)
+{
+    static if(functions.length == 1)
+    {
+        alias reduceAdjoin = binaryFun!(functions[0]);
+    }
+    else
+    {
+        T reduceAdjoin(T, U)(T lhs, U rhs)
+        {
+            alias funs = staticMap!(binaryFun, functions);
+
+            foreach(i, Unused; typeof(lhs.expand))
+            {
+                lhs.expand[i] = funs[i](lhs.expand[i], rhs);
+            }
+
+            return lhs;
+        }
+    }
+}
+
+private template reduceFinish(functions...)
+{
+    static if(functions.length == 1)
+    {
+        alias reduceFinish = binaryFun!(functions[0]);
+    }
+    else
+    {
+        T reduceFinish(T)(T lhs, T rhs)
+        {
+            alias funs = staticMap!(binaryFun, functions);
+
+            foreach(i, Unused; typeof(lhs.expand))
+            {
+                lhs.expand[i] = funs[i](lhs.expand[i], rhs.expand[i]);
+            }
+
+            return lhs;
+        }
+    }
+}
+
+private template isRoundRobin(R : RoundRobinBuffer!(C1, C2), C1, C2)
+{
+    enum isRoundRobin = true;
+}
+
+private template isRoundRobin(T)
+{
+    enum isRoundRobin = false;
+}
+
+unittest
+{
+    static assert( isRoundRobin!(RoundRobinBuffer!(void delegate(char[]), bool delegate())));
+    static assert(!isRoundRobin!(uint));
+}
+
+// This is the base "class" for all of the other tasks.  Using C-style
+// polymorphism to allow more direct control over memory allocation, etc.
+private struct AbstractTask
+{
+    AbstractTask* prev;
+    AbstractTask* next;
+
+    // Pointer to a function that executes this task.
+    void function(void*) runTask;
+
+    Throwable exception;
+    ubyte taskStatus = TaskStatus.notStarted;
+
+    bool done() @property
+    {
+        if(atomicReadUbyte(taskStatus) == TaskStatus.done)
+        {
+            if(exception)
+            {
+                throw exception;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    void job()
+    {
+        runTask(&this);
+    }
+}
+
+/**
+$(D Task) represents the fundamental unit of work.  A $(D Task) may be
+executed in parallel with any other $(D Task).  Using this struct directly
+allows future/promise _parallelism.  In this paradigm, a function (or delegate
+or other callable) is executed in a thread other than the one it was called
+from.  The calling thread does not block while the function is being executed.
+A call to $(D workForce), $(D yieldForce), or $(D spinForce) is used to
+ensure that the $(D Task) has finished executing and to obtain the return
+value, if any.  These functions and $(D done) also act as full memory barriers,
+meaning that any memory writes made in the thread that executed the $(D Task)
+are guaranteed to be visible in the calling thread after one of these functions
+returns.
+
+The $(XREF parallelism, task) and $(XREF parallelism, scopedTask) functions can
+be used to create an instance of this struct.  See $(D task) for usage examples.
+
+Function results are returned from $(D yieldForce), $(D spinForce) and
+$(D workForce) by ref.  If $(D fun) returns by ref, the reference will point
+to the returned reference of $(D fun).  Otherwise it will point to a
+field in this struct.
+
+Copying of this struct is disabled, since it would provide no useful semantics.
+If you want to pass this struct around, you should do so by reference or
+pointer.
+
+Bugs:  Changes to $(D ref) and $(D out) arguments are not propagated to the
+       call site, only to $(D args) in this struct.
+*/
+struct Task(alias fun, Args...)
+{
+    AbstractTask base = {runTask : &impl};
+    alias base this;
+
+    private @property AbstractTask* basePtr()
+    {
+        return &base;
+    }
+
+    private static void impl(void* myTask)
+    {
+        import std.algorithm.internal : addressOf;
+
+        Task* myCastedTask = cast(typeof(this)*) myTask;
+        static if(is(ReturnType == void))
+        {
+            fun(myCastedTask._args);
+        }
+        else static if(is(typeof(addressOf(fun(myCastedTask._args)))))
+        {
+            myCastedTask.returnVal = addressOf(fun(myCastedTask._args));
+        }
+        else
+        {
+            myCastedTask.returnVal = fun(myCastedTask._args);
+        }
+    }
+
+    private TaskPool pool;
+    private bool isScoped;  // True if created with scopedTask.
+
+    Args _args;
+
+    /**
+    The arguments the function was called with.  Changes to $(D out) and
+    $(D ref) arguments will be visible here.
+    */
+    static if(__traits(isSame, fun, run))
+    {
+        alias args = _args[1..$];
+    }
+    else
+    {
+        alias args = _args;
+    }
+
+
+    // The purpose of this code is to decide whether functions whose
+    // return values have unshared aliasing can be executed via
+    // TaskPool from @safe code.  See isSafeReturn.
+    static if(__traits(isSame, fun, run))
+    {
+        static if(isFunctionPointer!(_args[0]))
+        {
+            private enum bool isPure =
+            functionAttributes!(Args[0]) & FunctionAttribute.pure_;
+        }
+        else
+        {
+            // BUG:  Should check this for delegates too, but std.traits
+            //       apparently doesn't allow this.  isPure is irrelevant
+            //       for delegates, at least for now since shared delegates
+            //       don't work.
+            private enum bool isPure = false;
+        }
+
+    }
+    else
+    {
+        // We already know that we can't execute aliases in @safe code, so
+        // just put a dummy value here.
+        private enum bool isPure = false;
+    }
+
+
+    /**
+    The return type of the function called by this $(D Task).  This can be
+    $(D void).
+    */
+    alias ReturnType = typeof(fun(_args));
+
+    static if(!is(ReturnType == void))
+    {
+        static if(is(typeof(&fun(_args))))
+        {
+            // Ref return.
+            ReturnType* returnVal;
+
+            ref ReturnType fixRef(ReturnType* val)
+            {
+                return *val;
+            }
+
+        }
+        else
+        {
+            ReturnType returnVal;
+
+            ref ReturnType fixRef(ref ReturnType val)
+            {
+                return val;
+            }
+        }
+    }
+
+    private void enforcePool()
+    {
+        enforce(this.pool !is null, "Job not submitted yet.");
+    }
+
+    static if(Args.length > 0)
+    {
+        private this(Args args)
+        {
+            _args = args;
+        }
+    }
+
+    // Work around DMD bug 6588, allow immutable elements.
+    static if(allSatisfy!(isAssignable, Args))
+    {
+        typeof(this) opAssign(typeof(this) rhs)
+        {
+            foreach(i, Type; typeof(this.tupleof))
+            {
+                this.tupleof[i] = rhs.tupleof[i];
+            }
+            return this;
+        }
+    }
+    else
+    {
+        @disable typeof(this) opAssign(typeof(this) rhs)
+        {
+            assert(0);
+        }
+    }
+
+    /**
+    If the $(D Task) isn't started yet, execute it in the current thread.
+    If it's done, return its return value, if any.  If it's in progress,
+    busy spin until it's done, then return the return value.  If it threw
+    an exception, rethrow that exception.
+
+    This function should be used when you expect the result of the
+    $(D Task) to be available on a timescale shorter than that of an OS
+    context switch.
+     */
+    @property ref ReturnType spinForce() @trusted
+    {
+        enforcePool();
+
+        this.pool.tryDeleteExecute(basePtr);
+
+        while(atomicReadUbyte(this.taskStatus) != TaskStatus.done) {}
+
+        if(exception)
+        {
+            throw exception;
+        }
+
+        static if(!is(ReturnType == void))
+        {
+            return fixRef(this.returnVal);
+        }
+    }
+
+    /**
+    If the $(D Task) isn't started yet, execute it in the current thread.
+    If it's done, return its return value, if any.  If it's in progress,
+    wait on a condition variable.  If it threw an exception, rethrow that
+    exception.
+
+    This function should be used for expensive functions, as waiting on a
+    condition variable introduces latency, but avoids wasted CPU cycles.
+     */
+    @property ref ReturnType yieldForce() @trusted
+    {
+        enforcePool();
+        this.pool.tryDeleteExecute(basePtr);
+
+        if(done)
+        {
+            static if(is(ReturnType == void))
+            {
+                return;
+            }
+            else
+            {
+                return fixRef(this.returnVal);
+            }
+        }
+
+        pool.waiterLock();
+        scope(exit) pool.waiterUnlock();
+
+        while(atomicReadUbyte(this.taskStatus) != TaskStatus.done)
+        {
+            pool.waitUntilCompletion();
+        }
+
+        if(exception)
+        {
+            throw exception;
+        }
+
+        static if(!is(ReturnType == void))
+        {
+            return fixRef(this.returnVal);
+        }
+    }
+
+    /**
+    If this $(D Task) was not started yet, execute it in the current
+    thread.  If it is finished, return its result.  If it is in progress,
+    execute any other $(D Task) from the $(D TaskPool) instance that
+    this $(D Task) was submitted to until this one
+    is finished.  If it threw an exception, rethrow that exception.
+    If no other tasks are available or this $(D Task) was executed using
+    $(D executeInNewThread), wait on a condition variable.
+     */
+    @property ref ReturnType workForce() @trusted
+    {
+        enforcePool();
+        this.pool.tryDeleteExecute(basePtr);
+
+        while(true)
+        {
+            if(done)    // done() implicitly checks for exceptions.
+            {
+                static if(is(ReturnType == void))
+                {
+                    return;
+                }
+                else
+                {
+                    return fixRef(this.returnVal);
+                }
+            }
+
+            AbstractTask* job;
+            {
+                // Locking explicitly and calling popNoSync() because
+                // pop() waits on a condition variable if there are no Tasks
+                // in the queue.
+
+                pool.queueLock();
+                scope(exit) pool.queueUnlock();
+                job = pool.popNoSync();
+            }
+
+
+            if(job !is null)
+            {
+
+                version(verboseUnittest)
+                {
+                    stderr.writeln("Doing workForce work.");
+                }
+
+                pool.doJob(job);
+
+                if(done)
+                {
+                    static if(is(ReturnType == void))
+                    {
+                        return;
+                    }
+                    else
+                    {
+                        return fixRef(this.returnVal);
+                    }
+                }
+            }
+            else
+            {
+                version(verboseUnittest)
+                {
+                    stderr.writeln("Yield from workForce.");
+                }
+
+                return yieldForce;
+            }
+        }
+    }
+
+    /**
+    Returns $(D true) if the $(D Task) is finished executing.
+
+    Throws:  Rethrows any exception thrown during the execution of the
+             $(D Task).
+    */
+    @property bool done() @trusted
+    {
+        // Explicitly forwarded for documentation purposes.
+        return base.done;
+    }
+
+    /**
+    Create a new thread for executing this $(D Task), execute it in the
+    newly created thread, then terminate the thread.  This can be used for
+    future/promise parallelism.  An explicit priority may be given
+    to the $(D Task).  If one is provided, its value is forwarded to
+    $(D core.thread.Thread.priority). See $(XREF parallelism, task) for
+    usage example.
+    */
+    void executeInNewThread() @trusted
+    {
+        pool = new TaskPool(basePtr);
+    }
+
+    /// Ditto
+    void executeInNewThread(int priority) @trusted
+    {
+        pool = new TaskPool(basePtr, priority);
+    }
+
+    @safe ~this()
+    {
+        if(isScoped && pool !is null && taskStatus != TaskStatus.done)
+        {
+            yieldForce;
+        }
+    }
+
+    // When this is uncommented, it somehow gets called on returning from
+    // scopedTask even though the struct shouldn't be getting copied.
+    //@disable this(this) {}
+}
+
+// Calls $(D fpOrDelegate) with $(D args).  This is an
+// adapter that makes $(D Task) work with delegates, function pointers and
+// functors instead of just aliases.
+ReturnType!F run(F, Args...)(F fpOrDelegate, ref Args args)
+{
+    return fpOrDelegate(args);
+}
+
+/**
+Creates a $(D Task) on the GC heap that calls an alias.  This may be executed
+via $(D Task.executeInNewThread) or by submitting to a
+$(XREF parallelism, TaskPool).  A globally accessible instance of
+$(D TaskPool) is provided by $(XREF parallelism, taskPool).
+
+Returns:  A pointer to the $(D Task).
+
+Example:
+---
+// Read two files into memory at the same time.
+import std.file;
+
+void main()
+{
+    // Create and execute a Task for reading
+    // foo.txt.
+    auto file1Task = task!read("foo.txt");
+    file1Task.executeInNewThread();
+
+    // Read bar.txt in parallel.
+    auto file2Data = read("bar.txt");
+
+    // Get the results of reading foo.txt.
+    auto file1Data = file1Task.yieldForce;
+}
+---
+
+---
+// Sorts an array using a parallel quick sort algorithm.
+// The first partition is done serially.  Both recursion
+// branches are then executed in parallel.
+//
+// Timings for sorting an array of 1,000,000 doubles on
+// an Athlon 64 X2 dual core machine:
+//
+// This implementation:               176 milliseconds.
+// Equivalent serial implementation:  280 milliseconds
+void parallelSort(T)(T[] data)
+{
+    // Sort small subarrays serially.
+    if(data.length < 100)
+    {
+         std.algorithm.sort(data);
+         return;
+    }
+
+    // Partition the array.
+    swap(data[$ / 2], data[$ - 1]);
+    auto pivot = data[$ - 1];
+    bool lessThanPivot(T elem) { return elem < pivot; }
+
+    auto greaterEqual = partition!lessThanPivot(data[0..$ - 1]);
+    swap(data[$ - greaterEqual.length - 1], data[$ - 1]);
+
+    auto less = data[0..$ - greaterEqual.length - 1];
+    greaterEqual = data[$ - greaterEqual.length..$];
+
+    // Execute both recursion branches in parallel.
+    auto recurseTask = task!parallelSort(greaterEqual);
+    taskPool.put(recurseTask);
+    parallelSort(less);
+    recurseTask.yieldForce;
+}
+---
+*/
+auto task(alias fun, Args...)(Args args)
+{
+    return new Task!(fun, Args)(args);
+}
+
+/**
+Creates a $(D Task) on the GC heap that calls a function pointer, delegate, or
+class/struct with overloaded opCall.
+
+Example:
+---
+// Read two files in at the same time again,
+// but this time use a function pointer instead
+// of an alias to represent std.file.read.
+import std.file;
+
+void main()
+{
+    // Create and execute a Task for reading
+    // foo.txt.
+    auto file1Task = task(&read, "foo.txt");
+    file1Task.executeInNewThread();
+
+    // Read bar.txt in parallel.
+    auto file2Data = read("bar.txt");
+
+    // Get the results of reading foo.txt.
+    auto file1Data = file1Task.yieldForce;
+}
+---
+
+Notes: This function takes a non-scope delegate, meaning it can be
+       used with closures.  If you can't allocate a closure due to objects
+       on the stack that have scoped destruction, see $(D scopedTask), which
+       takes a scope delegate.
+ */
+auto task(F, Args...)(F delegateOrFp, Args args)
+if(is(typeof(delegateOrFp(args))) && !isSafeTask!F)
+{
+    return new Task!(run, F, Args)(delegateOrFp, args);
+}
+
+/**
+Version of $(D task) usable from $(D @safe) code.  Usage mechanics are
+identical to the non-@safe case, but safety introduces some restrictions:
+
+1.  $(D fun) must be @safe or @trusted.
+
+2.  $(D F) must not have any unshared aliasing as defined by
+    $(XREF traits, hasUnsharedAliasing).  This means it
+    may not be an unshared delegate or a non-shared class or struct
+    with overloaded $(D opCall).  This also precludes accepting template
+    alias parameters.
+
+3.  $(D Args) must not have unshared aliasing.
+
+4.  $(D fun) must not return by reference.
+
+5.  The return type must not have unshared aliasing unless $(D fun) is
+    $(D pure) or the $(D Task) is executed via $(D executeInNewThread) instead
+    of using a $(D TaskPool).
+
+*/
+@trusted auto task(F, Args...)(F fun, Args args)
+if(is(typeof(fun(args))) && isSafeTask!F)
+{
+    return new Task!(run, F, Args)(fun, args);
+}
+
+/**
+These functions allow the creation of $(D Task) objects on the stack rather
+than the GC heap.  The lifetime of a $(D Task) created by $(D scopedTask)
+cannot exceed the lifetime of the scope it was created in.
+
+$(D scopedTask) might be preferred over $(D task):
+
+1.  When a $(D Task) that calls a delegate is being created and a closure
+    cannot be allocated due to objects on the stack that have scoped
+    destruction.  The delegate overload of $(D scopedTask) takes a $(D scope)
+    delegate.
+
+2.  As a micro-optimization, to avoid the heap allocation associated with
+    $(D task) or with the creation of a closure.
+
+Usage is otherwise identical to $(D task).
+
+Notes:  $(D Task) objects created using $(D scopedTask) will automatically
+call $(D Task.yieldForce) in their destructor if necessary to ensure
+the $(D Task) is complete before the stack frame they reside on is destroyed.
+*/
+auto scopedTask(alias fun, Args...)(Args args)
+{
+    auto ret = Task!(fun, Args)(args);
+    ret.isScoped = true;
+    return ret;
+}
+
+/// Ditto
+auto scopedTask(F, Args...)(scope F delegateOrFp, Args args)
+if(is(typeof(delegateOrFp(args))) && !isSafeTask!F)
+{
+    auto ret = Task!(run, F, Args)(delegateOrFp, args);
+    ret.isScoped = true;
+    return ret;
+}
+
+/// Ditto
+@trusted auto scopedTask(F, Args...)(F fun, Args args)
+if(is(typeof(fun(args))) && isSafeTask!F)
+{
+    auto ret = Task!(run, F, Args)(fun, args);
+    ret.isScoped = true;
+    return ret;
+}
+
+/**
+The total number of CPU cores available on the current machine, as reported by
+the operating system.
+*/
+immutable uint totalCPUs;
+
+/*
+This class serves two purposes:
+
+1.  It distinguishes std.parallelism threads from other threads so that
+    the std.parallelism daemon threads can be terminated.
+
+2.  It adds a reference to the pool that the thread is a member of,
+    which is also necessary to allow the daemon threads to be properly
+    terminated.
+*/
+private final class ParallelismThread : Thread
+{
+    this(void delegate() dg)
+    {
+        super(dg);
+    }
+
+    TaskPool pool;
+}
+
+// Kill daemon threads.
+shared static ~this()
+{
+    auto allThreads = Thread.getAll();
+
+    foreach(thread; allThreads)
+    {
+        auto pthread = cast(ParallelismThread) thread;
+        if(pthread is null) continue;
+        auto pool = pthread.pool;
+        if(!pool.isDaemon) continue;
+        pool.stop();
+        pthread.join();
+    }
+}
+
+/**
+This class encapsulates a task queue and a set of worker threads.  Its purpose
+is to efficiently map a large number of $(D Task)s onto a smaller number of
+threads.  A task queue is a FIFO queue of $(D Task) objects that have been
+submitted to the $(D TaskPool) and are awaiting execution.  A worker thread is a
+thread that executes the $(D Task) at the front of the queue when one is
+available and sleeps when the queue is empty.
+
+This class should usually be used via the global instantiation
+available via the $(XREF parallelism, taskPool) property.
+Occasionally it is useful to explicitly instantiate a $(D TaskPool):
+
+1.  When you want $(D TaskPool) instances with multiple priorities, for example
+    a low priority pool and a high priority pool.
+
+2.  When the threads in the global task pool are waiting on a synchronization
+    primitive (for example a mutex), and you want to parallelize the code that
+    needs to run before these threads can be resumed.
+ */
+final class TaskPool
+{
+private:
+
+    // A pool can either be a regular pool or a single-task pool.  A
+    // single-task pool is a dummy pool that's fired up for
+    // Task.executeInNewThread().
+    bool isSingleTask;
+
+    ParallelismThread[] pool;
+    Thread singleTaskThread;
+
+    AbstractTask* head;
+    AbstractTask* tail;
+    PoolState status = PoolState.running;
+    Condition workerCondition;
+    Condition waiterCondition;
+    Mutex queueMutex;
+    Mutex waiterMutex;  // For waiterCondition
+
+    // The instanceStartIndex of the next instance that will be created.
+    __gshared static size_t nextInstanceIndex = 1;
+
+    // The index of the current thread.
+    static size_t threadIndex;
+
+    // The index of the first thread in this instance.
+    immutable size_t instanceStartIndex;
+
+    // The index that the next thread to be initialized in this pool will have.
+    size_t nextThreadIndex;
+
+    enum PoolState : ubyte
+    {
+        running,
+        finishing,
+        stopNow
+    }
+
+    void doJob(AbstractTask* job)
+    {
+        assert(job.taskStatus == TaskStatus.inProgress);
+        assert(job.next is null);
+        assert(job.prev is null);
+
+        scope(exit)
+        {
+            if(!isSingleTask)
+            {
+                waiterLock();
+                scope(exit) waiterUnlock();
+                notifyWaiters();
+            }
+        }
+
+        try
+        {
+            job.job();
+        }
+        catch(Throwable e)
+        {
+            job.exception = e;
+        }
+
+        atomicSetUbyte(job.taskStatus, TaskStatus.done);
+    }
+
+    // This function is used for dummy pools created by Task.executeInNewThread().
+    void doSingleTask()
+    {
+        // No synchronization.  Pool is guaranteed to only have one thread,
+        // and the queue is submitted to before this thread is created.
+        assert(head);
+        auto t = head;
+        t.next = t.prev = head = null;
+        doJob(t);
+    }
+
+    // This function performs initialization for each thread that affects
+    // thread local storage and therefore must be done from within the
+    // worker thread.  It then calls executeWorkLoop().
+    void startWorkLoop()
+    {
+        // Initialize thread index.
+        {
+            queueLock();
+            scope(exit) queueUnlock();
+            threadIndex = nextThreadIndex;
+            nextThreadIndex++;
+        }
+
+        executeWorkLoop();
+    }
+
+    // This is the main work loop that worker threads spend their time in
+    // until they terminate.  It's also entered by non-worker threads when
+    // finish() is called with the blocking variable set to true.
+    void executeWorkLoop()
+    {
+        while(atomicReadUbyte(status) != PoolState.stopNow)
+        {
+            AbstractTask* task = pop();
+            if (task is null)
+            {
+                if(atomicReadUbyte(status) == PoolState.finishing)
+                {
+                    atomicSetUbyte(status, PoolState.stopNow);
+                    return;
+                }
+            }
+            else
+            {
+                doJob(task);
+            }
+        }
+    }
+
+    // Pop a task off the queue.
+    AbstractTask* pop()
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        auto ret = popNoSync();
+        while(ret is null && status == PoolState.running)
+        {
+            wait();
+            ret = popNoSync();
+        }
+        return ret;
+    }
+
+    AbstractTask* popNoSync()
+    out(returned)
+    {
+        /* If task.prev and task.next aren't null, then another thread
+         * can try to delete this task from the pool after it's
+         * alreadly been deleted/popped.
+         */
+        if(returned !is null)
+        {
+            assert(returned.next is null);
+            assert(returned.prev is null);
+        }
+    }
+    body
+    {
+        if(isSingleTask) return null;
+
+        AbstractTask* returned = head;
+        if (head !is null)
+        {
+            head = head.next;
+            returned.prev = null;
+            returned.next = null;
+            returned.taskStatus = TaskStatus.inProgress;
+        }
+        if(head !is null)
+        {
+            head.prev = null;
+        }
+
+        return returned;
+    }
+
+    // Push a task onto the queue.
+    void abstractPut(AbstractTask* task)
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        abstractPutNoSync(task);
+    }
+
+    void abstractPutNoSync(AbstractTask* task)
+    in
+    {
+        assert(task);
+    }
+    out
+    {
+        assert(tail.prev !is tail);
+        assert(tail.next is null, text(tail.prev, '\t', tail.next));
+        if(tail.prev !is null)
+        {
+            assert(tail.prev.next is tail, text(tail.prev, '\t', tail.next));
+        }
+    }
+    body
+    {
+        // Not using enforce() to save on function call overhead since this
+        // is a performance critical function.
+        if(status != PoolState.running)
+        {
+            throw new Error(
+                "Cannot submit a new task to a pool after calling " ~
+                "finish() or stop()."
+            );
+        }
+
+        task.next = null;
+        if (head is null)   //Queue is empty.
+        {
+            head = task;
+            tail = task;
+            tail.prev = null;
+        }
+        else {
+            assert(tail);
+            task.prev = tail;
+            tail.next = task;
+            tail = task;
+        }
+        notify();
+    }
+
+    void abstractPutGroupNoSync(AbstractTask* h, AbstractTask* t)
+    {
+        if(status != PoolState.running)
+        {
+            throw new Error(
+                "Cannot submit a new task to a pool after calling " ~
+                "finish() or stop()."
+            );
+        }
+
+        if(head is null)
+        {
+            head = h;
+            tail = t;
+        }
+        else
+        {
+            h.prev = tail;
+            tail.next = h;
+            tail = t;
+        }
+
+        notifyAll();
+    }
+
+    void tryDeleteExecute(AbstractTask* toExecute)
+    {
+        if(isSingleTask) return;
+
+        if( !deleteItem(toExecute) )
+        {
+            return;
+        }
+
+        try
+        {
+            toExecute.job();
+        }
+        catch(Exception e)
+        {
+            toExecute.exception = e;
+        }
+
+        atomicSetUbyte(toExecute.taskStatus, TaskStatus.done);
+    }
+
+    bool deleteItem(AbstractTask* item)
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        return deleteItemNoSync(item);
+    }
+
+    bool deleteItemNoSync(AbstractTask* item)
+    {
+        if(item.taskStatus != TaskStatus.notStarted)
+        {
+            return false;
+        }
+        item.taskStatus = TaskStatus.inProgress;
+
+        if(item is head)
+        {
+            // Make sure head gets set properly.
+            popNoSync();
+            return true;
+        }
+        if(item is tail)
+        {
+            tail = tail.prev;
+            if(tail !is null)
+            {
+                tail.next = null;
+            }
+            item.next = null;
+            item.prev = null;
+            return true;
+        }
+        if(item.next !is null)
+        {
+            assert(item.next.prev is item);  // Check queue consistency.
+            item.next.prev = item.prev;
+        }
+        if(item.prev !is null)
+        {
+            assert(item.prev.next is item);  // Check queue consistency.
+            item.prev.next = item.next;
+        }
+        item.next = null;
+        item.prev = null;
+        return true;
+    }
+
+    void queueLock()
+    {
+        assert(queueMutex);
+        if(!isSingleTask) queueMutex.lock();
+    }
+
+    void queueUnlock()
+    {
+        assert(queueMutex);
+        if(!isSingleTask) queueMutex.unlock();
+    }
+
+    void waiterLock()
+    {
+        if(!isSingleTask) waiterMutex.lock();
+    }
+
+    void waiterUnlock()
+    {
+        if(!isSingleTask) waiterMutex.unlock();
+    }
+
+    void wait()
+    {
+        if(!isSingleTask) workerCondition.wait();
+    }
+
+    void notify()
+    {
+        if(!isSingleTask) workerCondition.notify();
+    }
+
+    void notifyAll()
+    {
+        if(!isSingleTask) workerCondition.notifyAll();
+    }
+
+    void waitUntilCompletion()
+    {
+        if(isSingleTask)
+        {
+            singleTaskThread.join();
+        }
+        else
+        {
+            waiterCondition.wait();
+        }
+    }
+
+    void notifyWaiters()
+    {
+        if(!isSingleTask) waiterCondition.notifyAll();
+    }
+
+    // Private constructor for creating dummy pools that only have one thread,
+    // only execute one Task, and then terminate.  This is used for
+    // Task.executeInNewThread().
+    this(AbstractTask* task, int priority = int.max)
+    {
+        assert(task);
+
+        // Dummy value, not used.
+        instanceStartIndex = 0;
+
+        this.isSingleTask = true;
+        task.taskStatus = TaskStatus.inProgress;
+        this.head = task;
+        singleTaskThread = new Thread(&doSingleTask);
+        singleTaskThread.start();
+
+        // Disabled until writing code to support
+        // running thread with specified priority
+        // See https://d.puremagic.com/issues/show_bug.cgi?id=8960
+
+        /*if(priority != int.max)
+        {
+            singleTaskThread.priority = priority;
+        }*/
+    }
+
+public:
+    // This is used in parallel_algorithm but is too unstable to document
+    // as public API.
+    size_t defaultWorkUnitSize(size_t rangeLen) const @safe pure nothrow
+    {
+        if(this.size == 0)
+        {
+            return rangeLen;
+        }
+
+        immutable size_t eightSize = 4 * (this.size + 1);
+        auto ret = (rangeLen / eightSize) + ((rangeLen % eightSize == 0) ? 0 : 1);
+        return max(ret, 1);
+    }
+
+    /**
+    Default constructor that initializes a $(D TaskPool) with
+    $(D totalCPUs) - 1 worker threads.  The minus 1 is included because the
+    main thread will also be available to do work.
+
+    Note:  On single-core machines, the primitives provided by $(D TaskPool)
+           operate transparently in single-threaded mode.
+     */
+    this() @trusted
+    {
+        this(totalCPUs - 1);
+    }
+
+    /**
+    Allows for custom number of worker threads.
+    */
+    this(size_t nWorkers) @trusted
+    {
+        synchronized(typeid(TaskPool))
+        {
+            instanceStartIndex = nextInstanceIndex;
+
+            // The first worker thread to be initialized will have this index,
+            // and will increment it.  The second worker to be initialized will
+            // have this index plus 1.
+            nextThreadIndex = instanceStartIndex;
+            nextInstanceIndex += nWorkers;
+        }
+
+        queueMutex = new Mutex(this);
+        waiterMutex = new Mutex();
+        workerCondition = new Condition(queueMutex);
+        waiterCondition = new Condition(waiterMutex);
+
+        pool = new ParallelismThread[nWorkers];
+        foreach(ref poolThread; pool)
+        {
+            poolThread = new ParallelismThread(&startWorkLoop);
+            poolThread.pool = this;
+            poolThread.start();
+        }
+    }
+
+    /**
+    Implements a parallel foreach loop over a range.  This works by implicitly
+    creating and submitting one $(D Task) to the $(D TaskPool) for each worker
+    thread.  A work unit is a set of consecutive elements of $(D range) to
+    be processed by a worker thread between communication with any other
+    thread.  The number of elements processed per work unit is controlled by the
+    $(D workUnitSize) parameter.  Smaller work units provide better load
+    balancing, but larger work units avoid the overhead of communicating
+    with other threads frequently to fetch the next work unit.  Large work
+    units also avoid false sharing in cases where the range is being modified.
+    The less time a single iteration of the loop takes, the larger
+    $(D workUnitSize) should be.  For very expensive loop bodies,
+    $(D workUnitSize) should  be 1.  An overload that chooses a default work
+    unit size is also available.
+
+    Example:
+    ---
+    // Find the logarithm of every number from 1 to
+    // 10_000_000 in parallel.
+    auto logs = new double[10_000_000];
+
+    // Parallel foreach works with or without an index
+    // variable.  It can be iterate by ref if range.front
+    // returns by ref.
+
+    // Iterate over logs using work units of size 100.
+    foreach(i, ref elem; taskPool.parallel(logs, 100))
+    {
+        elem = log(i + 1.0);
+    }
+
+    // Same thing, but use the default work unit size.
+    //
+    // Timings on an Athlon 64 X2 dual core machine:
+    //
+    // Parallel foreach:  388 milliseconds
+    // Regular foreach:   619 milliseconds
+    foreach(i, ref elem; taskPool.parallel(logs))
+    {
+        elem = log(i + 1.0);
+    }
+    ---
+
+    Notes:
+
+    The memory usage of this implementation is guaranteed to be constant
+    in $(D range.length).
+
+    Breaking from a parallel foreach loop via a break, labeled break,
+    labeled continue, return or goto statement throws a
+    $(D ParallelForeachError).
+
+    In the case of non-random access ranges, parallel foreach buffers lazily
+    to an array of size $(D workUnitSize) before executing the parallel portion
+    of the loop.  The exception is that, if a parallel foreach is executed
+    over a range returned by $(D asyncBuf) or $(D map), the copying is elided
+    and the buffers are simply swapped.  In this case $(D workUnitSize) is
+    ignored and the work unit size is set to the  buffer size of $(D range).
+
+    A memory barrier is guaranteed to be executed on exit from the loop,
+    so that results produced by all threads are visible in the calling thread.
+
+    $(B Exception Handling):
+
+    When at least one exception is thrown from inside a parallel foreach loop,
+    the submission of additional $(D Task) objects is terminated as soon as
+    possible, in a non-deterministic manner.  All executing or
+    enqueued work units are allowed to complete.  Then, all exceptions that
+    were thrown by any work unit are chained using $(D Throwable.next) and
+    rethrown.  The order of the exception chaining is non-deterministic.
+    */
+    ParallelForeach!R parallel(R)(R range, size_t workUnitSize)
+    {
+        enforce(workUnitSize > 0, "workUnitSize must be > 0.");
+        alias RetType = ParallelForeach!R;
+        return RetType(this, range, workUnitSize);
+    }
+
+
+    /// Ditto
+    ParallelForeach!R parallel(R)(R range)
+    {
+        static if(hasLength!R)
+        {
+            // Default work unit size is such that we would use 4x as many
+            // slots as are in this thread pool.
+            size_t workUnitSize = defaultWorkUnitSize(range.length);
+            return parallel(range, workUnitSize);
+        }
+        else
+        {
+            // Just use a really, really dumb guess if the user is too lazy to
+            // specify.
+            return parallel(range, 512);
+        }
+    }
+
+    ///
+    template amap(functions...)
+    {
+        /**
+        Eager parallel map.  The eagerness of this function means it has less
+        overhead than the lazily evaluated $(D TaskPool.map) and should be
+        preferred where the memory requirements of eagerness are acceptable.
+        $(D functions) are the functions to be evaluated, passed as template
+        alias parameters in a style similar to
+        $(XREF_PACK algorithm,iteration,map).
+        The first argument must be a random access range. For performance
+        reasons, amap will assume the range elements have not yet been
+        initialized. Elements will be overwritten without calling a destructor
+        nor doing an assignment. As such, the range must not contain meaningful
+        data: either un-initialized objects, or objects in their $(D .init)
+        state.
+
+        ---
+        auto numbers = iota(100_000_000.0);
+
+        // Find the square roots of numbers.
+        //
+        // Timings on an Athlon 64 X2 dual core machine:
+        //
+        // Parallel eager map:                   0.802 s
+        // Equivalent serial implementation:     1.768 s
+        auto squareRoots = taskPool.amap!sqrt(numbers);
+        ---
+
+        Immediately after the range argument, an optional work unit size argument
+        may be provided.  Work units as used by $(D amap) are identical to those
+        defined for parallel foreach.  If no work unit size is provided, the
+        default work unit size is used.
+
+        ---
+        // Same thing, but make work unit size 100.
+        auto squareRoots = taskPool.amap!sqrt(numbers, 100);
+        ---
+
+        An output range for returning the results may be provided as the last
+        argument.  If one is not provided, an array of the proper type will be
+        allocated on the garbage collected heap.  If one is provided, it must be a
+        random access range with assignable elements, must have reference
+        semantics with respect to assignment to its elements, and must have the
+        same length as the input range.  Writing to adjacent elements from
+        different threads must be safe.
+
+        ---
+        // Same thing, but explicitly allocate an array
+        // to return the results in.  The element type
+        // of the array may be either the exact type
+        // returned by functions or an implicit conversion
+        // target.
+        auto squareRoots = new float[numbers.length];
+        taskPool.amap!sqrt(numbers, squareRoots);
+
+        // Multiple functions, explicit output range, and
+        // explicit work unit size.
+        auto results = new Tuple!(float, real)[numbers.length];
+        taskPool.amap!(sqrt, log)(numbers, 100, results);
+        ---
+
+        Note:
+
+        A memory barrier is guaranteed to be executed after all results are written
+        but before returning so that results produced by all threads are visible
+        in the calling thread.
+
+        Tips:
+
+        To perform the mapping operation in place, provide the same range for the
+        input and output range.
+
+        To parallelize the copying of a range with expensive to evaluate elements
+        to an array, pass an identity function (a function that just returns
+        whatever argument is provided to it) to $(D amap).
+
+        $(B Exception Handling):
+
+        When at least one exception is thrown from inside the map functions,
+        the submission of additional $(D Task) objects is terminated as soon as
+        possible, in a non-deterministic manner.  All currently executing or
+        enqueued work units are allowed to complete.  Then, all exceptions that
+        were thrown from any work unit are chained using $(D Throwable.next) and
+        rethrown.  The order of the exception chaining is non-deterministic.
+        */
+        auto amap(Args...)(Args args)
+        if(isRandomAccessRange!(Args[0]))
+        {
+            alias fun = adjoin!(staticMap!(unaryFun, functions));
+
+            alias range = args[0];
+            immutable len = range.length;
+
+            static if(
+                Args.length > 1 &&
+                randAssignable!(Args[$ - 1]) &&
+                is(MapType!(Args[0], functions) : ElementType!(Args[$ - 1]))
+                )
+            {
+                alias buf = args[$ - 1];
+                alias args2 = args[0..$ - 1];
+                alias Args2 = Args[0..$ - 1];
+                enforce(buf.length == len,
+                        text("Can't use a user supplied buffer that's the wrong ",
+                             "size.  (Expected  :", len, " Got:  ", buf.length));
+            }
+            else static if(randAssignable!(Args[$ - 1]) && Args.length > 1)
+            {
+                static assert(0, "Wrong buffer type.");
+            }
+            else
+            {
+                auto buf = uninitializedArray!(MapType!(Args[0], functions)[])(len);
+                alias args2 = args;
+                alias Args2 = Args;
+            }
+
+            if(!len) return buf;
+
+            static if(isIntegral!(Args2[$ - 1]))
+            {
+                static assert(args2.length == 2);
+                auto workUnitSize = cast(size_t) args2[1];
+            }
+            else
+            {
+                static assert(args2.length == 1, Args);
+                auto workUnitSize = defaultWorkUnitSize(range.length);
+            }
+
+            alias R = typeof(range);
+
+            if(workUnitSize > len)
+            {
+                workUnitSize = len;
+            }
+
+            // Handle as a special case:
+            if(size == 0)
+            {
+                size_t index = 0;
+                foreach(elem; range)
+                {
+                    emplaceRef(buf[index++], fun(elem));
+                }
+                return buf;
+            }
+
+            // Effectively -1:  chunkIndex + 1 == 0:
+            shared size_t workUnitIndex = size_t.max;
+            shared bool shouldContinue = true;
+
+            void doIt()
+            {
+                scope(failure)
+                {
+                    // If an exception is thrown, all threads should bail.
+                    atomicStore(shouldContinue, false);
+                }
+
+                while(atomicLoad(shouldContinue))
+                {
+                    immutable myUnitIndex = atomicOp!"+="(workUnitIndex, 1);
+                    immutable start = workUnitSize * myUnitIndex;
+                    if(start >= len)
+                    {
+                        atomicStore(shouldContinue, false);
+                        break;
+                    }
+
+                    immutable end = min(len, start + workUnitSize);
+
+                    static if (hasSlicing!R)
+                    {
+                        auto subrange = range[start..end];
+                        foreach(i; start..end)
+                        {
+                            emplaceRef(buf[i], fun(subrange.front));
+                            subrange.popFront();
+                        }
+                    }
+                    else
+                    {
+                        foreach(i; start..end)
+                        {
+                            emplaceRef(buf[i], fun(range[i]));
+                        }
+                    }
+                }
+            }
+
+            submitAndExecute(this, &doIt);
+            return buf;
+        }
+    }
+
+    ///
+    template map(functions...)
+    {
+        /**
+        A semi-lazy parallel map that can be used for pipelining.  The map
+        functions are evaluated for the first $(D bufSize) elements and stored in a
+        buffer and made available to $(D popFront).  Meanwhile, in the
+        background a second buffer of the same size is filled.  When the first
+        buffer is exhausted, it is swapped with the second buffer and filled while
+        the values from what was originally the second buffer are read.  This
+        implementation allows for elements to be written to the buffer without
+        the need for atomic operations or synchronization for each write, and
+        enables the mapping function to be evaluated efficiently in parallel.
+
+        $(D map) has more overhead than the simpler procedure used by $(D amap)
+        but avoids the need to keep all results in memory simultaneously and works
+        with non-random access ranges.
+
+        Params:
+
+        source = The input range to be mapped.  If $(D source) is not random
+        access it will be lazily buffered to an array of size $(D bufSize) before
+        the map function is evaluated.  (For an exception to this rule, see Notes.)
+
+        bufSize = The size of the buffer to store the evaluated elements.
+
+        workUnitSize = The number of elements to evaluate in a single
+        $(D Task).  Must be less than or equal to $(D bufSize), and
+        should be a fraction of $(D bufSize) such that all worker threads can be
+        used.  If the default of size_t.max is used, workUnitSize will be set to
+        the pool-wide default.
+
+        Returns:  An input range representing the results of the map.  This range
+                  has a length iff $(D source) has a length.
+
+        Notes:
+
+        If a range returned by $(D map) or $(D asyncBuf) is used as an input to
+        $(D map), then as an optimization the copying from the output buffer
+        of the first range to the input buffer of the second range is elided, even
+        though the ranges returned by $(D map) and $(D asyncBuf) are non-random
+        access ranges.  This means that the $(D bufSize) parameter passed to the
+        current call to $(D map) will be ignored and the size of the buffer
+        will be the buffer size of $(D source).
+
+        Example:
+        ---
+        // Pipeline reading a file, converting each line
+        // to a number, taking the logarithms of the numbers,
+        // and performing the additions necessary to find
+        // the sum of the logarithms.
+
+        auto lineRange = File("numberList.txt").byLine();
+        auto dupedLines = std.algorithm.map!"a.idup"(lineRange);
+        auto nums = taskPool.map!(to!double)(dupedLines);
+        auto logs = taskPool.map!log10(nums);
+
+        double sum = 0;
+        foreach(elem; logs)
+        {
+            sum += elem;
+        }
+        ---
+
+        $(B Exception Handling):
+
+        Any exceptions thrown while iterating over $(D source)
+        or computing the map function are re-thrown on a call to $(D popFront) or,
+        if thrown during construction, are simply allowed to propagate to the
+        caller.  In the case of exceptions thrown while computing the map function,
+        the exceptions are chained as in $(D TaskPool.amap).
+        */
+        auto
+        map(S)(S source, size_t bufSize = 100, size_t workUnitSize = size_t.max)
+        if(isInputRange!S)
+        {
+            enforce(workUnitSize == size_t.max || workUnitSize <= bufSize,
+                    "Work unit size must be smaller than buffer size.");
+            alias fun = adjoin!(staticMap!(unaryFun, functions));
+
+            static final class Map
+            {
+                // This is a class because the task needs to be located on the
+                // heap and in the non-random access case source needs to be on
+                // the heap, too.
+
+            private:
+                enum bufferTrick = is(typeof(source.buf1)) &&
+                is(typeof(source.bufPos)) &&
+                is(typeof(source.doBufSwap()));
+
+                alias E = MapType!(S, functions);
+                E[] buf1, buf2;
+                S source;
+                TaskPool pool;
+                Task!(run, E[] delegate(E[]), E[]) nextBufTask;
+                size_t workUnitSize;
+                size_t bufPos;
+                bool lastTaskWaited;
+
+            static if(isRandomAccessRange!S)
+            {
+                alias FromType = S;
+
+                void popSource()
+                {
+                    static if(__traits(compiles, source[0..source.length]))
+                    {
+                        source = source[min(buf1.length, source.length)..source.length];
+                    }
+                    else static if(__traits(compiles, source[0..$]))
+                    {
+                        source = source[min(buf1.length, source.length)..$];
+                    }
+                    else
+                    {
+                        static assert(0, "S must have slicing for Map."
+                                      ~ "  " ~ S.stringof ~ " doesn't.");
+                    }
+                }
+            }
+            else static if(bufferTrick)
+            {
+                // Make sure we don't have the buffer recycling overload of
+                // asyncBuf.
+                static if(
+                    is(typeof(source.source)) &&
+                    isRoundRobin!(typeof(source.source))
+                )
+                {
+                    static assert(0, "Cannot execute a parallel map on " ~
+                                  "the buffer recycling overload of asyncBuf."
+                                 );
+                }
+
+                alias FromType = typeof(source.buf1);
+                FromType from;
+
+                // Just swap our input buffer with source's output buffer.
+                // No need to copy element by element.
+                FromType dumpToFrom()
+                {
+                    assert(source.buf1.length <= from.length);
+                    from.length = source.buf1.length;
+                    swap(source.buf1, from);
+
+                    // Just in case this source has been popped before
+                    // being sent to map:
+                    from = from[source.bufPos..$];
+
+                    static if(is(typeof(source._length)))
+                    {
+                        source._length -= (from.length - source.bufPos);
+                    }
+
+                    source.doBufSwap();
+
+                    return from;
+                }
+            }
+            else
+            {
+                alias FromType = ElementType!S[];
+
+                // The temporary array that data is copied to before being
+                // mapped.
+                FromType from;
+
+                FromType dumpToFrom()
+                {
+                    assert(from !is null);
+
+                    size_t i;
+                    for(; !source.empty && i < from.length; source.popFront())
+                    {
+                        from[i++] = source.front;
+                    }
+
+                    from = from[0..i];
+                    return from;
+                }
+            }
+
+            static if(hasLength!S)
+            {
+                size_t _length;
+
+                public @property size_t length() const @safe pure nothrow
+                {
+                    return _length;
+                }
+            }
+
+                this(S source, size_t bufSize, size_t workUnitSize, TaskPool pool)
+                {
+                    static if(bufferTrick)
+                    {
+                        bufSize = source.buf1.length;
+                    }
+
+                    buf1.length = bufSize;
+                    buf2.length = bufSize;
+
+                    static if(!isRandomAccessRange!S)
+                    {
+                        from.length = bufSize;
+                    }
+
+                    this.workUnitSize = (workUnitSize == size_t.max) ?
+                            pool.defaultWorkUnitSize(bufSize) : workUnitSize;
+                    this.source = source;
+                    this.pool = pool;
+
+                    static if(hasLength!S)
+                    {
+                        _length = source.length;
+                    }
+
+                    buf1 = fillBuf(buf1);
+                    submitBuf2();
+                }
+
+                // The from parameter is a dummy and ignored in the random access
+                // case.
+                E[] fillBuf(E[] buf)
+                {
+                    static if(isRandomAccessRange!S)
+                    {
+                        auto toMap = take(source, buf.length);
+                        scope(success) popSource();
+                    }
+                    else
+                    {
+                        auto toMap = dumpToFrom();
+                    }
+
+                    buf = buf[0..min(buf.length, toMap.length)];
+
+                    // Handle as a special case:
+                    if(pool.size == 0)
+                    {
+                        size_t index = 0;
+                        foreach(elem; toMap)
+                        {
+                            buf[index++] = fun(elem);
+                        }
+                        return buf;
+                    }
+
+                    pool.amap!functions(toMap, workUnitSize, buf);
+
+                    return buf;
+                }
+
+                void submitBuf2()
+                in
+                {
+                    assert(nextBufTask.prev is null);
+                    assert(nextBufTask.next is null);
+                } body
+                {
+                    // Hack to reuse the task object.
+
+                    nextBufTask = typeof(nextBufTask).init;
+                    nextBufTask._args[0] = &fillBuf;
+                    nextBufTask._args[1] = buf2;
+                    pool.put(nextBufTask);
+                }
+
+                void doBufSwap()
+                {
+                    if(lastTaskWaited)
+                    {
+                        // Then the source is empty.  Signal it here.
+                        buf1 = null;
+                        buf2 = null;
+
+                        static if(!isRandomAccessRange!S)
+                        {
+                            from = null;
+                        }
+
+                        return;
+                    }
+
+                    buf2 = buf1;
+                    buf1 = nextBufTask.yieldForce;
+                    bufPos = 0;
+
+                    if(source.empty)
+                    {
+                        lastTaskWaited = true;
+                    }
+                    else
+                    {
+                        submitBuf2();
+                    }
+                }
+
+            public:
+                @property auto front()
+                {
+                    return buf1[bufPos];
+                }
+
+                void popFront()
+                {
+                    static if(hasLength!S)
+                    {
+                        _length--;
+                    }
+
+                    bufPos++;
+                    if(bufPos >= buf1.length)
+                    {
+                        doBufSwap();
+                    }
+                }
+
+                static if(std.range.isInfinite!S)
+                {
+                    enum bool empty = false;
+                }
+                else
+                {
+
+                    bool empty() @property
+                    {
+                        // popFront() sets this when source is empty
+                        return buf1.length == 0;
+                    }
+                }
+            }
+            return new Map(source, bufSize, workUnitSize, this);
+        }
+    }
+
+    /**
+    Given a $(D source) range that is expensive to iterate over, returns an
+    input range that asynchronously buffers the contents of
+    $(D source) into a buffer of $(D bufSize) elements in a worker thread,
+    while making previously buffered elements from a second buffer, also of size
+    $(D bufSize), available via the range interface of the returned
+    object.  The returned range has a length iff $(D hasLength!S).
+    $(D asyncBuf) is useful, for example, when performing expensive operations
+    on the elements of ranges that represent data on a disk or network.
+
+    Example:
+    ---
+    import std.conv, std.stdio;
+
+    void main()
+    {
+        // Fetch lines of a file in a background thread
+        // while processing previously fetched lines,
+        // dealing with byLine's buffer recycling by
+        // eagerly duplicating every line.
+        auto lines = File("foo.txt").byLine();
+        auto duped = std.algorithm.map!"a.idup"(lines);
+
+        // Fetch more lines in the background while we
+        // process the lines already read into memory
+        // into a matrix of doubles.
+        double[][] matrix;
+        auto asyncReader = taskPool.asyncBuf(duped);
+
+        foreach(line; asyncReader)
+        {
+            auto ls = line.split("\t");
+            matrix ~= to!(double[])(ls);
+        }
+    }
+    ---
+
+    $(B Exception Handling):
+
+    Any exceptions thrown while iterating over $(D source) are re-thrown on a
+    call to $(D popFront) or, if thrown during construction, simply
+    allowed to propagate to the caller.
+    */
+    auto asyncBuf(S)(S source, size_t bufSize = 100) if(isInputRange!S)
+    {
+        static final class AsyncBuf
+        {
+            // This is a class because the task and source both need to be on
+            // the heap.
+
+            // The element type of S.
+            alias E = ElementType!S;  // Needs to be here b/c of forward ref bugs.
+
+        private:
+            E[] buf1, buf2;
+            S source;
+            TaskPool pool;
+            Task!(run, E[] delegate(E[]), E[]) nextBufTask;
+            size_t bufPos;
+            bool lastTaskWaited;
+
+            static if(hasLength!S)
+            {
+                size_t _length;
+
+                // Available if hasLength!S.
+                public @property size_t length() const @safe pure nothrow
+                {
+                    return _length;
+                }
+            }
+
+            this(S source, size_t bufSize, TaskPool pool)
+            {
+                buf1.length = bufSize;
+                buf2.length = bufSize;
+
+                this.source = source;
+                this.pool = pool;
+
+                static if(hasLength!S)
+                {
+                    _length = source.length;
+                }
+
+                buf1 = fillBuf(buf1);
+                submitBuf2();
+            }
+
+            E[] fillBuf(E[] buf)
+            {
+                assert(buf !is null);
+
+                size_t i;
+                for(; !source.empty && i < buf.length; source.popFront())
+                {
+                    buf[i++] = source.front;
+                }
+
+                buf = buf[0..i];
+                return buf;
+            }
+
+            void submitBuf2()
+            in
+            {
+                assert(nextBufTask.prev is null);
+                assert(nextBufTask.next is null);
+            } body
+            {
+                // Hack to reuse the task object.
+
+                nextBufTask = typeof(nextBufTask).init;
+                nextBufTask._args[0] = &fillBuf;
+                nextBufTask._args[1] = buf2;
+                pool.put(nextBufTask);
+            }
+
+            void doBufSwap()
+            {
+                if(lastTaskWaited)
+                {
+                    // Then source is empty.  Signal it here.
+                    buf1 = null;
+                    buf2 = null;
+                    return;
+                }
+
+                buf2 = buf1;
+                buf1 = nextBufTask.yieldForce;
+                bufPos = 0;
+
+                if(source.empty)
+                {
+                    lastTaskWaited = true;
+                }
+                else
+                {
+                    submitBuf2();
+                }
+            }
+
+        public:
+            E front() @property
+            {
+                return buf1[bufPos];
+            }
+
+            void popFront()
+            {
+                static if(hasLength!S)
+                {
+                    _length--;
+                }
+
+                bufPos++;
+                if(bufPos >= buf1.length)
+                {
+                    doBufSwap();
+                }
+            }
+
+            static if(std.range.isInfinite!S)
+            {
+                enum bool empty = false;
+            }
+
+            else
+            {
+                ///
+                bool empty() @property
+                {
+                    // popFront() sets this when source is empty:
+                    return buf1.length == 0;
+                }
+            }
+        }
+        return new AsyncBuf(source, bufSize, this);
+    }
+
+    /**
+    Given a callable object $(D next) that writes to a user-provided buffer and
+    a second callable object $(D empty) that determines whether more data is
+    available to write via $(D next), returns an input range that
+    asynchronously calls $(D next) with a set of size $(D nBuffers) of buffers
+    and makes the results available in the order they were obtained via the
+    input range interface of the returned object.  Similarly to the
+    input range overload of $(D asyncBuf), the first half of the buffers
+    are made available via the range interface while the second half are
+    filled and vice-versa.
+
+    Params:
+
+    next = A callable object that takes a single argument that must be an array
+           with mutable elements.  When called, $(D next) writes data to
+           the array provided by the caller.
+
+    empty = A callable object that takes no arguments and returns a type
+            implicitly convertible to $(D bool).  This is used to signify
+            that no more data is available to be obtained by calling $(D next).
+
+    initialBufSize = The initial size of each buffer.  If $(D next) takes its
+                     array by reference, it may resize the buffers.
+
+    nBuffers = The number of buffers to cycle through when calling $(D next).
+
+    Example:
+    ---
+    // Fetch lines of a file in a background
+    // thread while processing previously fetched
+    // lines, without duplicating any lines.
+    auto file = File("foo.txt");
+
+    void next(ref char[] buf)
+    {
+        file.readln(buf);
+    }
+
+    // Fetch more lines in the background while we
+    // process the lines already read into memory
+    // into a matrix of doubles.
+    double[][] matrix;
+    auto asyncReader = taskPool.asyncBuf(&next, &file.eof);
+
+    foreach(line; asyncReader)
+    {
+        auto ls = line.split("\t");
+        matrix ~= to!(double[])(ls);
+    }
+    ---
+
+    $(B Exception Handling):
+
+    Any exceptions thrown while iterating over $(D range) are re-thrown on a
+    call to $(D popFront).
+
+    Warning:
+
+    Using the range returned by this function in a parallel foreach loop
+    will not work because buffers may be overwritten while the task that
+    processes them is in queue.  This is checked for at compile time
+    and will result in a static assertion failure.
+    */
+    auto asyncBuf(C1, C2)(C1 next, C2 empty, size_t initialBufSize = 0, size_t nBuffers = 100)
+    if(is(typeof(C2.init()) : bool) &&
+        Parameters!C1.length == 1 &&
+        Parameters!C2.length == 0 &&
+        isArray!(Parameters!C1[0])
+    ) {
+        auto roundRobin = RoundRobinBuffer!(C1, C2)(next, empty, initialBufSize, nBuffers);
+        return asyncBuf(roundRobin, nBuffers / 2);
+    }
+
+    ///
+    template reduce(functions...)
+    {
+        /**
+        Parallel reduce on a random access range.  Except as otherwise noted,
+        usage is similar to $(XREF_PACK algorithm,iteration,_reduce).  This
+        function works by splitting the range to be reduced into work units,
+        which are slices to be reduced in parallel.  Once the results from all
+        work units are computed, a final serial reduction is performed on these
+        results to compute the final answer. Therefore, care must be taken to
+        choose the seed value appropriately.
+
+        Because the reduction is being performed in parallel, $(D functions)
+        must be associative.  For notational simplicity, let # be an
+        infix operator representing $(D functions).  Then, (a # b) # c must equal
+        a # (b # c).  Floating point addition is not associative
+        even though addition in exact arithmetic is.  Summing floating
+        point numbers using this function may give different results than summing
+        serially.  However, for many practical purposes floating point addition
+        can be treated as associative.
+
+        Note that, since $(D functions) are assumed to be associative,
+        additional optimizations are made to the serial portion of the reduction
+        algorithm. These take advantage of the instruction level parallelism of
+        modern CPUs, in addition to the thread-level parallelism that the rest
+        of this module exploits.  This can lead to better than linear speedups
+        relative to $(XREF_PACK algorithm,iteration,_reduce), especially for
+        fine-grained benchmarks like dot products.
+
+        An explicit seed may be provided as the first argument.  If
+        provided, it is used as the seed for all work units and for the final
+        reduction of results from all work units.  Therefore, if it is not the
+        identity value for the operation being performed, results may differ
+        from those generated by $(XREF_PACK algorithm,iteration,_reduce) or
+        depending on how many work units are used.  The next argument must be
+        the range to be reduced.
+        ---
+        // Find the sum of squares of a range in parallel, using
+        // an explicit seed.
+        //
+        // Timings on an Athlon 64 X2 dual core machine:
+        //
+        // Parallel reduce:                     72 milliseconds
+        // Using std.algorithm.reduce instead:  181 milliseconds
+        auto nums = iota(10_000_000.0f);
+        auto sumSquares = taskPool.reduce!"a + b"(
+            0.0, std.algorithm.map!"a * a"(nums)
+        );
+        ---
+
+        If no explicit seed is provided, the first element of each work unit
+        is used as a seed.  For the final reduction, the result from the first
+        work unit is used as the seed.
+        ---
+        // Find the sum of a range in parallel, using the first
+        // element of each work unit as the seed.
+        auto sum = taskPool.reduce!"a + b"(nums);
+        ---
+
+        An explicit work unit size may be specified as the last argument.
+        Specifying too small a work unit size will effectively serialize the
+        reduction, as the final reduction of the result of each work unit will
+        dominate computation time.  If $(D TaskPool.size) for this instance
+        is zero, this parameter is ignored and one work unit is used.
+        ---
+        // Use a work unit size of 100.
+        auto sum2 = taskPool.reduce!"a + b"(nums, 100);
+
+        // Work unit size of 100 and explicit seed.
+        auto sum3 = taskPool.reduce!"a + b"(0.0, nums, 100);
+        ---
+
+        Parallel reduce supports multiple functions, like
+        $(D std.algorithm.reduce).
+        ---
+        // Find both the min and max of nums.
+        auto minMax = taskPool.reduce!(min, max)(nums);
+        assert(minMax[0] == reduce!min(nums));
+        assert(minMax[1] == reduce!max(nums));
+        ---
+
+        $(B Exception Handling):
+
+        After this function is finished executing, any exceptions thrown
+        are chained together via $(D Throwable.next) and rethrown.  The chaining
+        order is non-deterministic.
+         */
+        auto reduce(Args...)(Args args)
+        {
+            alias fun = reduceAdjoin!functions;
+            alias finishFun = reduceFinish!functions;
+
+            static if(isIntegral!(Args[$ - 1]))
+            {
+                size_t workUnitSize = cast(size_t) args[$ - 1];
+                alias args2 = args[0..$ - 1];
+                alias Args2 = Args[0..$ - 1];
+            }
+            else
+            {
+                alias args2 = args;
+                alias Args2 = Args;
+            }
+
+            auto makeStartValue(Type)(Type e)
+            {
+                static if(functions.length == 1)
+                {
+                    return e;
+                }
+                else
+                {
+                    typeof(adjoin!(staticMap!(binaryFun, functions))(e, e)) seed = void;
+                    foreach (i, T; seed.Types)
+                    {
+                        emplaceRef(seed.expand[i], e);
+                    }
+
+                    return seed;
+                }
+            }
+
+            static if(args2.length == 2)
+            {
+                static assert(isInputRange!(Args2[1]));
+                alias range = args2[1];
+                alias seed = args2[0];
+                enum explicitSeed = true;
+
+                static if(!is(typeof(workUnitSize)))
+                {
+                    size_t workUnitSize = defaultWorkUnitSize(range.length);
+                }
+            }
+            else
+            {
+                static assert(args2.length == 1);
+                alias range = args2[0];
+
+                static if(!is(typeof(workUnitSize)))
+                {
+                    size_t workUnitSize = defaultWorkUnitSize(range.length);
+                }
+
+                enforce(!range.empty,
+                    "Cannot reduce an empty range with first element as start value.");
+
+                auto seed = makeStartValue(range.front);
+                enum explicitSeed = false;
+                range.popFront();
+            }
+
+            alias E = typeof(seed);
+            alias R = typeof(range);
+
+            E reduceOnRange(R range, size_t lowerBound, size_t upperBound)
+            {
+                // This is for exploiting instruction level parallelism by
+                // using multiple accumulator variables within each thread,
+                // since we're assuming functions are associative anyhow.
+
+                // This is so that loops can be unrolled automatically.
+                enum ilpTuple = AliasSeq!(0, 1, 2, 3, 4, 5);
+                enum nILP = ilpTuple.length;
+                immutable subSize = (upperBound - lowerBound) / nILP;
+
+                if(subSize <= 1)
+                {
+                    // Handle as a special case.
+                    static if(explicitSeed)
+                    {
+                        E result = seed;
+                    }
+                    else
+                    {
+                        E result = makeStartValue(range[lowerBound]);
+                        lowerBound++;
+                    }
+
+                    foreach(i; lowerBound..upperBound)
+                    {
+                        result = fun(result, range[i]);
+                    }
+
+                    return result;
+                }
+
+                assert(subSize > 1);
+                E[nILP] results;
+                size_t[nILP] offsets;
+
+                foreach(i; ilpTuple)
+                {
+                    offsets[i] = lowerBound + subSize * i;
+
+                    static if(explicitSeed)
+                    {
+                        results[i] = seed;
+                    }
+                    else
+                    {
+                        results[i] = makeStartValue(range[offsets[i]]);
+                        offsets[i]++;
+                    }
+                }
+
+                immutable nLoop = subSize - (!explicitSeed);
+                foreach(i; 0..nLoop)
+                {
+                    foreach(j; ilpTuple)
+                    {
+                        results[j] = fun(results[j], range[offsets[j]]);
+                        offsets[j]++;
+                    }
+                }
+
+                // Finish the remainder.
+                foreach(i; nILP * subSize + lowerBound..upperBound)
+                {
+                    results[$ - 1] = fun(results[$ - 1], range[i]);
+                }
+
+                foreach(i; ilpTuple[1..$])
+                {
+                    results[0] = finishFun(results[0], results[i]);
+                }
+
+                return results[0];
+            }
+
+            immutable len = range.length;
+            if(len == 0)
+            {
+                return seed;
+            }
+
+            if(this.size == 0)
+            {
+                return finishFun(seed, reduceOnRange(range, 0, len));
+            }
+
+            // Unlike the rest of the functions here, I can't use the Task object
+            // recycling trick here because this has to work on non-commutative
+            // operations.  After all the tasks are done executing, fun() has to
+            // be applied on the results of these to get a final result, but
+            // it can't be evaluated out of order.
+
+            if(workUnitSize > len)
+            {
+                workUnitSize = len;
+            }
+
+            immutable size_t nWorkUnits = (len / workUnitSize) + ((len % workUnitSize == 0) ? 0 : 1);
+            assert(nWorkUnits * workUnitSize >= len);
+
+            alias RTask = Task!(run, typeof(&reduceOnRange), R, size_t, size_t);
+            RTask[] tasks;
+
+            // Can't use alloca() due to Bug 3753.  Use a fixed buffer
+            // backed by malloc().
+            enum maxStack = 2_048;
+            byte[maxStack] buf = void;
+            immutable size_t nBytesNeeded = nWorkUnits * RTask.sizeof;
+
+            import core.stdc.stdlib;
+            if(nBytesNeeded < maxStack)
+            {
+                tasks = (cast(RTask*) buf.ptr)[0..nWorkUnits];
+            }
+            else
+            {
+                auto ptr = cast(RTask*) malloc(nBytesNeeded);
+                if(!ptr)
+                {
+                    throw new OutOfMemoryError(
+                        "Out of memory in std.parallelism."
+                    );
+                }
+
+                tasks = ptr[0..nWorkUnits];
+            }
+
+            scope(exit)
+            {
+                if(nBytesNeeded > maxStack)
+                {
+                    free(tasks.ptr);
+                }
+            }
+
+            foreach (ref t; tasks[])
+                emplaceRef(t, RTask());
+
+            // Hack to take the address of a nested function w/o
+            // making a closure.
+            static auto scopedAddress(D)(scope D del)
+            {
+                auto tmp = del;
+                return tmp;
+            }
+
+            size_t curPos = 0;
+            void useTask(ref RTask task)
+            {
+                task.pool = this;
+                task._args[0] = scopedAddress(&reduceOnRange);
+                task._args[3] = min(len, curPos + workUnitSize);  // upper bound.
+                task._args[1] = range;  // range
+                task._args[2] = curPos; // lower bound.
+
+                curPos += workUnitSize;
+            }
+
+            foreach(ref task; tasks)
+            {
+                useTask(task);
+            }
+
+            foreach(i; 1..tasks.length - 1)
+            {
+                tasks[i].next = tasks[i + 1].basePtr;
+                tasks[i + 1].prev = tasks[i].basePtr;
+            }
+
+            if(tasks.length > 1)
+            {
+                queueLock();
+                scope(exit) queueUnlock();
+
+                abstractPutGroupNoSync(
+                    tasks[1].basePtr,
+                    tasks[$ - 1].basePtr
+                );
+            }
+
+            if(tasks.length > 0)
+            {
+                try
+                {
+                    tasks[0].job();
+                }
+                catch(Throwable e)
+                {
+                    tasks[0].exception = e;
+                }
+                tasks[0].taskStatus = TaskStatus.done;
+
+                // Try to execute each of these in the current thread
+                foreach(ref task; tasks[1..$])
+                {
+                    tryDeleteExecute(task.basePtr);
+                }
+            }
+
+            // Now that we've tried to execute every task, they're all either
+            // done or in progress.  Force all of them.
+            E result = seed;
+
+            Throwable firstException, lastException;
+
+            foreach(ref task; tasks)
+            {
+                try
+                {
+                    task.yieldForce;
+                }
+                catch(Throwable e)
+                {
+                    addToChain(e, firstException, lastException);
+                    continue;
+                }
+
+                if(!firstException) result = finishFun(result, task.returnVal);
+            }
+
+            if(firstException) throw firstException;
+
+            return result;
+        }
+    }
+
+    /**
+    Gets the index of the current thread relative to this $(D TaskPool).  Any
+    thread not in this pool will receive an index of 0.  The worker threads in
+    this pool receive unique indices of 1 through $(D this.size).
+
+    This function is useful for maintaining worker-local resources.
+
+    Example:
+    ---
+    // Execute a loop that computes the greatest common
+    // divisor of every number from 0 through 999 with
+    // 42 in parallel.  Write the results out to
+    // a set of files, one for each thread.  This allows
+    // results to be written out without any synchronization.
+
+    import std.conv, std.range, std.numeric, std.stdio;
+
+    void main()
+    {
+        auto filesHandles = new File[taskPool.size + 1];
+        scope(exit) {
+            foreach(ref handle; fileHandles) {
+                handle.close();
+            }
+        }
+
+        foreach(i, ref handle; fileHandles)
+        {
+            handle = File("workerResults" ~ to!string(i) ~ ".txt");
+        }
+
+        foreach(num; parallel(iota(1_000)))
+        {
+            auto outHandle = fileHandles[taskPool.workerIndex];
+            outHandle.writeln(num, '\t', gcd(num, 42));
+        }
+    }
+    ---
+    */
+    size_t workerIndex() @property @safe const nothrow
+    {
+        immutable rawInd = threadIndex;
+        return (rawInd >= instanceStartIndex && rawInd < instanceStartIndex + size) ?
+                (rawInd - instanceStartIndex + 1) : 0;
+    }
+
+    /**
+    Struct for creating worker-local storage.  Worker-local storage is
+    thread-local storage that exists only for worker threads in a given
+    $(D TaskPool) plus a single thread outside the pool.  It is allocated on the
+    garbage collected heap in a way that avoids _false sharing, and doesn't
+    necessarily have global scope within any thread.  It can be accessed from
+    any worker thread in the $(D TaskPool) that created it, and one thread
+    outside this $(D TaskPool).  All threads outside the pool that created a
+    given instance of worker-local storage share a single slot.
+
+    Since the underlying data for this struct is heap-allocated, this struct
+    has reference semantics when passed between functions.
+
+    The main uses cases for $(D WorkerLocalStorageStorage) are:
+
+    1.  Performing parallel reductions with an imperative, as opposed to
+    functional, programming style.  In this case, it's useful to treat
+    $(D WorkerLocalStorageStorage) as local to each thread for only the parallel
+    portion of an algorithm.
+
+    2.  Recycling temporary buffers across iterations of a parallel foreach loop.
+
+    Example:
+    ---
+    // Calculate pi as in our synopsis example, but
+    // use an imperative instead of a functional style.
+    immutable n = 1_000_000_000;
+    immutable delta = 1.0L / n;
+
+    auto sums = taskPool.workerLocalStorage(0.0L);
+    foreach(i; parallel(iota(n)))
+    {
+        immutable x = ( i - 0.5L ) * delta;
+        immutable toAdd = delta / ( 1.0 + x * x );
+        sums.get += toAdd;
+    }
+
+    // Add up the results from each worker thread.
+    real pi = 0;
+    foreach(threadResult; sums.toRange)
+    {
+        pi += 4.0L * threadResult;
+    }
+    ---
+     */
+    static struct WorkerLocalStorage(T)
+    {
+    private:
+        TaskPool pool;
+        size_t size;
+
+        size_t elemSize;
+        bool* stillThreadLocal;
+
+        static size_t roundToLine(size_t num) pure nothrow
+        {
+            if(num % cacheLineSize == 0)
+            {
+                return num;
+            }
+            else {
+                return ((num / cacheLineSize) + 1) * cacheLineSize;
+            }
+        }
+
+        void* data;
+
+        void initialize(TaskPool pool)
+        {
+            this.pool = pool;
+            size = pool.size + 1;
+            stillThreadLocal = new bool;
+            *stillThreadLocal = true;
+
+            // Determines whether the GC should scan the array.
+            auto blkInfo = (typeid(T).flags & 1) ?
+                           cast(GC.BlkAttr) 0 :
+                           GC.BlkAttr.NO_SCAN;
+
+            immutable nElem = pool.size + 1;
+            elemSize = roundToLine(T.sizeof);
+
+            // The + 3 is to pad one full cache line worth of space on either side
+            // of the data structure to make sure false sharing with completely
+            // unrelated heap data is prevented, and to provide enough padding to
+            // make sure that data is cache line-aligned.
+            data = GC.malloc(elemSize * (nElem + 3), blkInfo) + elemSize;
+
+            // Cache line align data ptr.
+            data = cast(void*) roundToLine(cast(size_t) data);
+
+            foreach(i; 0..nElem)
+            {
+                this.opIndex(i) = T.init;
+            }
+        }
+
+        ref T opIndex(size_t index)
+        {
+            assert(index < size, text(index, '\t', uint.max));
+            return *(cast(T*) (data + elemSize * index));
+        }
+
+        void opIndexAssign(T val, size_t index)
+        {
+            assert(index < size);
+            *(cast(T*) (data + elemSize * index)) = val;
+        }
+
+    public:
+        /**
+        Get the current thread's instance.  Returns by ref.
+        Note that calling $(D get) from any thread
+        outside the $(D TaskPool) that created this instance will return the
+        same reference, so an instance of worker-local storage should only be
+        accessed from one thread outside the pool that created it.  If this
+        rule is violated, undefined behavior will result.
+
+        If assertions are enabled and $(D toRange) has been called, then this
+        WorkerLocalStorage instance is no longer worker-local and an assertion
+        failure will result when calling this method.  This is not checked
+        when assertions are disabled for performance reasons.
+         */
+        ref T get() @property
+        {
+            assert(*stillThreadLocal,
+                "Cannot call get() on this instance of WorkerLocalStorage " ~
+                "because it is no longer worker-local."
+            );
+            return opIndex(pool.workerIndex);
+        }
+
+        /**
+        Assign a value to the current thread's instance.  This function has
+        the same caveats as its overload.
+        */
+        void get(T val) @property
+        {
+            assert(*stillThreadLocal,
+                "Cannot call get() on this instance of WorkerLocalStorage " ~
+                "because it is no longer worker-local."
+            );
+
+            opIndexAssign(val, pool.workerIndex);
+        }
+
+        /**
+        Returns a range view of the values for all threads, which can be used
+        to further process the results of each thread after running the parallel
+        part of your algorithm.  Do not use this method in the parallel portion
+        of your algorithm.
+
+        Calling this function sets a flag indicating that this struct is no
+        longer worker-local, and attempting to use the $(D get) method again
+        will result in an assertion failure if assertions are enabled.
+         */
+        WorkerLocalStorageRange!T toRange() @property
+        {
+            if(*stillThreadLocal)
+            {
+                *stillThreadLocal = false;
+
+                // Make absolutely sure results are visible to all threads.
+                // This is probably not necessary since some other
+                // synchronization primitive will be used to signal that the
+                // parallel part of the algorithm is done, but the
+                // performance impact should be negligible, so it's better
+                // to be safe.
+                ubyte barrierDummy;
+                atomicSetUbyte(barrierDummy, 1);
+            }
+
+            return WorkerLocalStorageRange!T(this);
+        }
+    }
+
+    /**
+    Range primitives for worker-local storage.  The purpose of this is to
+    access results produced by each worker thread from a single thread once you
+    are no longer using the worker-local storage from multiple threads.
+    Do not use this struct in the parallel portion of your algorithm.
+
+    The proper way to instantiate this object is to call
+    $(D WorkerLocalStorage.toRange).  Once instantiated, this object behaves
+    as a finite random-access range with assignable, lvalue elements and
+    a length equal to the number of worker threads in the $(D TaskPool) that
+    created it plus 1.
+     */
+    static struct WorkerLocalStorageRange(T)
+    {
+    private:
+        WorkerLocalStorage!T workerLocalStorage;
+
+        size_t _length;
+        size_t beginOffset;
+
+        this(WorkerLocalStorage!T wl)
+        {
+            this.workerLocalStorage = wl;
+            _length = wl.size;
+        }
+
+    public:
+        ref T front() @property
+        {
+            return this[0];
+        }
+
+        ref T back() @property
+        {
+            return this[_length - 1];
+        }
+
+        void popFront()
+        {
+            if(_length > 0)
+            {
+                beginOffset++;
+                _length--;
+            }
+        }
+
+        void popBack()
+        {
+            if(_length > 0)
+            {
+                _length--;
+            }
+        }
+
+        typeof(this) save() @property
+        {
+            return this;
+        }
+
+        ref T opIndex(size_t index)
+        {
+            assert(index < _length);
+            return workerLocalStorage[index + beginOffset];
+        }
+
+        void opIndexAssign(T val, size_t index)
+        {
+            assert(index < _length);
+            workerLocalStorage[index] = val;
+        }
+
+        typeof(this) opSlice(size_t lower, size_t upper)
+        {
+            assert(upper <= _length);
+            auto newWl = this.workerLocalStorage;
+            newWl.data += lower * newWl.elemSize;
+            newWl.size = upper - lower;
+            return typeof(this)(newWl);
+        }
+
+        bool empty() @property
+        {
+            return length == 0;
+        }
+
+        size_t length() @property
+        {
+            return _length;
+        }
+    }
+
+    /**
+    Creates an instance of worker-local storage, initialized with a given
+    value.  The value is $(D lazy) so that you can, for example, easily
+    create one instance of a class for each worker.  For usage example,
+    see the $(D WorkerLocalStorage) struct.
+     */
+    WorkerLocalStorage!T workerLocalStorage(T)(lazy T initialVal = T.init)
+    {
+        WorkerLocalStorage!T ret;
+        ret.initialize(this);
+        foreach(i; 0..size + 1)
+        {
+            ret[i] = initialVal;
+        }
+
+        // Memory barrier to make absolutely sure that what we wrote is
+        // visible to worker threads.
+        ubyte barrierDummy;
+        atomicSetUbyte(barrierDummy, 0);
+
+        return ret;
+    }
+
+    /**
+    Signals to all worker threads to terminate as soon as they are finished
+    with their current $(D Task), or immediately if they are not executing a
+    $(D Task).  $(D Task)s that were in queue will not be executed unless
+    a call to $(D Task.workForce), $(D Task.yieldForce) or $(D Task.spinForce)
+    causes them to be executed.
+
+    Use only if you have waited on every $(D Task) and therefore know the
+    queue is empty, or if you speculatively executed some tasks and no longer
+    need the results.
+     */
+    void stop() @trusted
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        atomicSetUbyte(status, PoolState.stopNow);
+        notifyAll();
+    }
+
+    /**
+    Signals worker threads to terminate when the queue becomes empty.
+
+    If blocking argument is true, wait for all worker threads to terminate
+    before returning.  This option might be used in applications where
+    task results are never consumed-- e.g. when $(D TaskPool) is employed as a
+    rudimentary scheduler for tasks which communicate by means other than
+    return values.
+
+    Warning:  Calling this function with $(D blocking = true) from a worker
+              thread that is a member of the same $(D TaskPool) that
+              $(D finish) is being called on will result in a deadlock.
+     */
+    void finish(bool blocking = false) @trusted
+    {
+        {
+            queueLock();
+            scope(exit) queueUnlock();
+            atomicCasUbyte(status, PoolState.running, PoolState.finishing);
+            notifyAll();
+        }
+        if (blocking)
+        {
+            // Use this thread as a worker until everything is finished.
+            executeWorkLoop();
+
+            foreach(t; pool)
+            {
+                // Maybe there should be something here to prevent a thread
+                // from calling join() on itself if this function is called
+                // from a worker thread in the same pool, but:
+                //
+                // 1.  Using an if statement to skip join() would result in
+                //     finish() returning without all tasks being finished.
+                //
+                // 2.  If an exception were thrown, it would bubble up to the
+                //     Task from which finish() was called and likely be
+                //     swallowed.
+                t.join();
+            }
+        }
+    }
+
+    /// Returns the number of worker threads in the pool.
+    @property size_t size() @safe const pure nothrow
+    {
+        return pool.length;
+    }
+
+    /**
+    Put a $(D Task) object on the back of the task queue.  The $(D Task)
+    object may be passed by pointer or reference.
+
+    Example:
+    ---
+    import std.file;
+
+    // Create a task.
+    auto t = task!read("foo.txt");
+
+    // Add it to the queue to be executed.
+    taskPool.put(t);
+    ---
+
+    Notes:
+
+    @trusted overloads of this function are called for $(D Task)s if
+    $(XREF traits, hasUnsharedAliasing) is false for the $(D Task)'s
+    return type or the function the $(D Task) executes is $(D pure).
+    $(D Task) objects that meet all other requirements specified in the
+    $(D @trusted) overloads of $(D task) and $(D scopedTask) may be created
+    and executed from $(D @safe) code via $(D Task.executeInNewThread) but
+    not via $(D TaskPool).
+
+    While this function takes the address of variables that may
+    be on the stack, some overloads are marked as @trusted.
+    $(D Task) includes a destructor that waits for the task to complete
+    before destroying the stack frame it is allocated on.  Therefore,
+    it is impossible for the stack frame to be destroyed before the task is
+    complete and no longer referenced by a $(D TaskPool).
+    */
+    void put(alias fun, Args...)(ref Task!(fun, Args) task)
+    if(!isSafeReturn!(typeof(task)))
+    {
+        task.pool = this;
+        abstractPut(task.basePtr);
+    }
+
+    /// Ditto
+    void put(alias fun, Args...)(Task!(fun, Args)* task)
+    if(!isSafeReturn!(typeof(*task)))
+    {
+        enforce(task !is null, "Cannot put a null Task on a TaskPool queue.");
+        put(*task);
+    }
+
+    @trusted void put(alias fun, Args...)(ref Task!(fun, Args) task)
+    if(isSafeReturn!(typeof(task)))
+    {
+        task.pool = this;
+        abstractPut(task.basePtr);
+    }
+
+    @trusted void put(alias fun, Args...)(Task!(fun, Args)* task)
+    if(isSafeReturn!(typeof(*task)))
+    {
+        enforce(task !is null, "Cannot put a null Task on a TaskPool queue.");
+        put(*task);
+    }
+
+    /**
+    These properties control whether the worker threads are daemon threads.
+    A daemon thread is automatically terminated when all non-daemon threads
+    have terminated.  A non-daemon thread will prevent a program from
+    terminating as long as it has not terminated.
+
+    If any $(D TaskPool) with non-daemon threads is active, either $(D stop)
+    or $(D finish) must be called on it before the program can terminate.
+
+    The worker treads in the $(D TaskPool) instance returned by the
+    $(D taskPool) property are daemon by default.  The worker threads of
+    manually instantiated task pools are non-daemon by default.
+
+    Note:  For a size zero pool, the getter arbitrarily returns true and the
+           setter has no effect.
+    */
+    bool isDaemon() @property @trusted
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        return (size == 0) ? true : pool[0].isDaemon;
+    }
+
+    /// Ditto
+    void isDaemon(bool newVal) @property @trusted
+    {
+        queueLock();
+        scope(exit) queueUnlock();
+        foreach(thread; pool)
+        {
+            thread.isDaemon = newVal;
+        }
+    }
+
+    /**
+    These functions allow getting and setting the OS scheduling priority of
+    the worker threads in this $(D TaskPool).  They forward to
+    $(D core.thread.Thread.priority), so a given priority value here means the
+    same thing as an identical priority value in $(D core.thread).
+
+    Note:  For a size zero pool, the getter arbitrarily returns
+           $(D core.thread.Thread.PRIORITY_MIN) and the setter has no effect.
+    */
+    int priority() @property @trusted
+    {
+        return (size == 0) ? core.thread.Thread.PRIORITY_MIN :
+        pool[0].priority;
+    }
+
+    /// Ditto
+    void priority(int newPriority) @property @trusted
+    {
+        if(size > 0)
+        {
+            foreach(t; pool)
+            {
+                t.priority = newPriority;
+            }
+        }
+    }
+}
+
+/**
+Returns a lazily initialized global instantiation of $(D TaskPool).
+This function can safely be called concurrently from multiple non-worker
+threads.  The worker threads in this pool are daemon threads, meaning that it
+is not necessary to call $(D TaskPool.stop) or $(D TaskPool.finish) before
+terminating the main thread.
+*/
+@property TaskPool taskPool() @trusted
+{
+    import std.concurrency : initOnce;
+    __gshared TaskPool pool;
+    return initOnce!pool({
+        auto p = new TaskPool(defaultPoolThreads);
+        p.isDaemon = true;
+        return p;
+    }());
+}
+
+private shared uint _defaultPoolThreads;
+shared static this()
+{
+    atomicStore(_defaultPoolThreads, totalCPUs - 1);
+}
+
+/**
+These properties get and set the number of worker threads in the $(D TaskPool)
+instance returned by $(D taskPool).  The default value is $(D totalCPUs) - 1.
+Calling the setter after the first call to $(D taskPool) does not changes
+number of worker threads in the instance returned by $(D taskPool).
+*/
+@property uint defaultPoolThreads() @trusted
+{
+    return atomicLoad(_defaultPoolThreads);
+}
+
+/// Ditto
+@property void defaultPoolThreads(uint newVal) @trusted
+{
+    atomicStore(_defaultPoolThreads, newVal);
+}
+
+/**
+Convenience functions that forwards to $(D taskPool.parallel).  The
+purpose of these is to make parallel foreach less verbose and more
+readable.
+
+Example:
+---
+// Find the logarithm of every number from
+// 1 to 1_000_000 in parallel, using the
+// default TaskPool instance.
+auto logs = new double[1_000_000];
+
+foreach(i, ref elem; parallel(logs)) {
+    elem = log(i + 1.0);
+}
+---
+
+*/
+ParallelForeach!R parallel(R)(R range)
+{
+    return taskPool.parallel(range);
+}
+
+/// Ditto
+ParallelForeach!R parallel(R)(R range, size_t workUnitSize)
+{
+    return taskPool.parallel(range, workUnitSize);
+}
+
+// Thrown when a parallel foreach loop is broken from.
+class ParallelForeachError : Error
+{
+    this()
+    {
+        super("Cannot break from a parallel foreach loop using break, return, "
+              ~ "labeled break/continue or goto statements.");
+    }
+}
+
+/*------Structs that implement opApply for parallel foreach.------------------*/
+private template randLen(R)
+{
+    enum randLen = isRandomAccessRange!R && hasLength!R;
+}
+
+private void submitAndExecute(
+    TaskPool pool,
+    scope void delegate() doIt
+)
+{
+    immutable nThreads = pool.size + 1;
+
+    alias PTask = typeof(scopedTask(doIt));
+    import core.stdc.stdlib;
+    import core.stdc.string : memcpy;
+
+    // The logical thing to do would be to just use alloca() here, but that
+    // causes problems on Windows for reasons that I don't understand
+    // (tentatively a compiler bug) and definitely doesn't work on Posix due
+    // to Bug 3753.  Therefore, allocate a fixed buffer and fall back to
+    // malloc() if someone's using a ridiculous amount of threads.  Also,
+    // the using a byte array instead of a PTask array as the fixed buffer
+    // is to prevent d'tors from being called on uninitialized excess PTask
+    // instances.
+    enum nBuf = 64;
+    byte[nBuf * PTask.sizeof] buf = void;
+    PTask[] tasks;
+    if(nThreads <= nBuf)
+    {
+        tasks = (cast(PTask*) buf.ptr)[0..nThreads];
+    }
+    else
+    {
+        auto ptr = cast(PTask*) malloc(nThreads * PTask.sizeof);
+        if(!ptr) throw new OutOfMemoryError("Out of memory in std.parallelism.");
+        tasks = ptr[0..nThreads];
+    }
+
+    scope(exit)
+    {
+        if(nThreads > nBuf)
+        {
+            free(tasks.ptr);
+        }
+    }
+
+    foreach(ref t; tasks)
+    {
+        import core.stdc.string : memcpy;
+
+        // This silly looking code is necessary to prevent d'tors from being
+        // called on uninitialized objects.
+        auto temp = scopedTask(doIt);
+        memcpy(&t, &temp, PTask.sizeof);
+
+        // This has to be done to t after copying, not temp before copying.
+        // Otherwise, temp's destructor will sit here and wait for the
+        // task to finish.
+        t.pool = pool;
+    }
+
+    foreach(i; 1..tasks.length - 1)
+    {
+        tasks[i].next = tasks[i + 1].basePtr;
+        tasks[i + 1].prev = tasks[i].basePtr;
+    }
+
+    if(tasks.length > 1)
+    {
+        pool.queueLock();
+        scope(exit) pool.queueUnlock();
+
+        pool.abstractPutGroupNoSync(
+            tasks[1].basePtr,
+            tasks[$ - 1].basePtr
+        );
+    }
+
+    if(tasks.length > 0)
+    {
+        try
+        {
+            tasks[0].job();
+        }
+        catch(Throwable e)
+        {
+            tasks[0].exception = e;
+        }
+        tasks[0].taskStatus = TaskStatus.done;
+
+        // Try to execute each of these in the current thread
+        foreach(ref task; tasks[1..$])
+        {
+            pool.tryDeleteExecute(task.basePtr);
+        }
+    }
+
+    Throwable firstException, lastException;
+
+    foreach(i, ref task; tasks)
+    {
+        try
+        {
+            task.yieldForce;
+        }
+        catch(Throwable e)
+        {
+            addToChain(e, firstException, lastException);
+            continue;
+        }
+    }
+
+    if(firstException) throw firstException;
+}
+
+void foreachErr()
+{
+    throw new ParallelForeachError();
+}
+
+int doSizeZeroCase(R, Delegate)(ref ParallelForeach!R p, Delegate dg)
+{
+    with(p)
+    {
+        int res = 0;
+        size_t index = 0;
+
+        // The explicit ElementType!R in the foreach loops is necessary for
+        // correct behavior when iterating over strings.
+        static if(hasLvalueElements!R)
+        {
+            foreach(ref ElementType!R elem; range)
+            {
+                static if(Parameters!dg.length == 2)
+                {
+                    res = dg(index, elem);
+                }
+                else
+                {
+                    res = dg(elem);
+                }
+                if(res) foreachErr();
+                index++;
+            }
+        }
+        else
+        {
+            foreach(ElementType!R elem; range)
+            {
+                static if(Parameters!dg.length == 2)
+                {
+                    res = dg(index, elem);
+                }
+                else
+                {
+                    res = dg(elem);
+                }
+                if(res) foreachErr();
+                index++;
+            }
+        }
+        return res;
+    }
+}
+
+private enum string parallelApplyMixinRandomAccess = q{
+    // Handle empty thread pool as special case.
+    if(pool.size == 0)
+    {
+        return doSizeZeroCase(this, dg);
+    }
+
+    // Whether iteration is with or without an index variable.
+    enum withIndex = Parameters!(typeof(dg)).length == 2;
+
+    shared size_t workUnitIndex = size_t.max;  // Effectively -1:  chunkIndex + 1 == 0
+    immutable len = range.length;
+    if(!len) return 0;
+
+    shared bool shouldContinue = true;
+
+    void doIt()
+    {
+        scope(failure)
+        {
+            // If an exception is thrown, all threads should bail.
+            atomicStore(shouldContinue, false);
+        }
+
+        while(atomicLoad(shouldContinue))
+        {
+            immutable myUnitIndex = atomicOp!"+="(workUnitIndex, 1);
+            immutable start = workUnitSize * myUnitIndex;
+            if(start >= len)
+            {
+                atomicStore(shouldContinue, false);
+                break;
+            }
+
+            immutable end = min(len, start + workUnitSize);
+
+            foreach(i; start..end)
+            {
+                static if(withIndex)
+                {
+                    if(dg(i, range[i])) foreachErr();
+                }
+                else
+                {
+                    if(dg(range[i])) foreachErr();
+                }
+            }
+        }
+    }
+
+    submitAndExecute(pool, &doIt);
+
+    return 0;
+};
+
+enum string parallelApplyMixinInputRange = q{
+    // Handle empty thread pool as special case.
+    if(pool.size == 0)
+    {
+        return doSizeZeroCase(this, dg);
+    }
+
+    // Whether iteration is with or without an index variable.
+    enum withIndex = Parameters!(typeof(dg)).length == 2;
+
+    // This protects the range while copying it.
+    auto rangeMutex = new Mutex();
+
+    shared bool shouldContinue = true;
+
+    // The total number of elements that have been popped off range.
+    // This is updated only while protected by rangeMutex;
+    size_t nPopped = 0;
+
+    static if(
+        is(typeof(range.buf1)) &&
+        is(typeof(range.bufPos)) &&
+        is(typeof(range.doBufSwap()))
+    )
+    {
+        // Make sure we don't have the buffer recycling overload of
+        // asyncBuf.
+        static if(
+            is(typeof(range.source)) &&
+            isRoundRobin!(typeof(range.source))
+        )
+        {
+            static assert(0, "Cannot execute a parallel foreach loop on " ~
+            "the buffer recycling overload of asyncBuf.");
+        }
+
+        enum bool bufferTrick = true;
+    }
+    else
+    {
+        enum bool bufferTrick = false;
+    }
+
+    void doIt()
+    {
+        scope(failure)
+        {
+            // If an exception is thrown, all threads should bail.
+            atomicStore(shouldContinue, false);
+        }
+
+        static if(hasLvalueElements!R)
+        {
+            alias Temp = ElementType!R*[];
+            Temp temp;
+
+            // Returns:  The previous value of nPopped.
+            size_t makeTemp()
+            {
+                import std.algorithm.internal : addressOf;
+
+                if(temp is null)
+                {
+                    temp = uninitializedArray!Temp(workUnitSize);
+                }
+
+                rangeMutex.lock();
+                scope(exit) rangeMutex.unlock();
+
+                size_t i = 0;
+                for(; i < workUnitSize && !range.empty; range.popFront(), i++)
+                {
+                    temp[i] = addressOf(range.front);
+                }
+
+                temp = temp[0..i];
+                auto ret = nPopped;
+                nPopped += temp.length;
+                return ret;
+            }
+
+        }
+        else
+        {
+
+            alias Temp = ElementType!R[];
+            Temp temp;
+
+            // Returns:  The previous value of nPopped.
+            static if(!bufferTrick) size_t makeTemp()
+            {
+                if(temp is null)
+                {
+                    temp = uninitializedArray!Temp(workUnitSize);
+                }
+
+                rangeMutex.lock();
+                scope(exit) rangeMutex.unlock();
+
+                size_t i = 0;
+                for(; i < workUnitSize && !range.empty; range.popFront(), i++)
+                {
+                    temp[i] = range.front;
+                }
+
+                temp = temp[0..i];
+                auto ret = nPopped;
+                nPopped += temp.length;
+                return ret;
+            }
+
+            static if(bufferTrick) size_t makeTemp()
+            {
+                rangeMutex.lock();
+                scope(exit) rangeMutex.unlock();
+
+                // Elide copying by just swapping buffers.
+                temp.length = range.buf1.length;
+                swap(range.buf1, temp);
+
+                // This is necessary in case popFront() has been called on
+                // range before entering the parallel foreach loop.
+                temp = temp[range.bufPos..$];
+
+                static if(is(typeof(range._length)))
+                {
+                    range._length -= (temp.length - range.bufPos);
+                }
+
+                range.doBufSwap();
+                auto ret = nPopped;
+                nPopped += temp.length;
+                return ret;
+            }
+        }
+
+        while(atomicLoad(shouldContinue))
+        {
+            auto overallIndex = makeTemp();
+            if(temp.empty)
+            {
+                atomicStore(shouldContinue, false);
+                break;
+            }
+
+            foreach(i; 0..temp.length)
+            {
+                scope(success) overallIndex++;
+
+                static if(hasLvalueElements!R)
+                {
+                    static if(withIndex)
+                    {
+                        if(dg(overallIndex, *temp[i])) foreachErr();
+                    }
+                    else
+                    {
+                        if(dg(*temp[i])) foreachErr();
+                    }
+                }
+                else
+                {
+                    static if(withIndex)
+                    {
+                        if(dg(overallIndex, temp[i])) foreachErr();
+                    }
+                    else
+                    {
+                        if(dg(temp[i])) foreachErr();
+                    }
+                }
+            }
+        }
+    }
+
+    submitAndExecute(pool, &doIt);
+
+    return 0;
+};
+
+// Calls e.next until the end of the chain is found.
+private Throwable findLastException(Throwable e) pure nothrow
+{
+    if(e is null) return null;
+
+    while(e.next)
+    {
+        e = e.next;
+    }
+
+    return e;
+}
+
+// Adds e to the exception chain.
+private void addToChain(
+    Throwable e,
+    ref Throwable firstException,
+    ref Throwable lastException
+) pure nothrow
+{
+    if(firstException)
+    {
+        assert(lastException);
+        lastException.next = e;
+        lastException = findLastException(e);
+    }
+    else
+    {
+        firstException = e;
+        lastException = findLastException(e);
+    }
+}
+
+private struct ParallelForeach(R)
+{
+    TaskPool pool;
+    R range;
+    size_t workUnitSize;
+    alias E = ElementType!R;
+
+    static if(hasLvalueElements!R)
+    {
+        alias NoIndexDg = int delegate(ref E);
+        alias IndexDg = int delegate(size_t, ref E);
+    }
+    else
+    {
+        alias NoIndexDg = int delegate(E);
+        alias IndexDg = int delegate(size_t, E);
+    }
+
+    int opApply(scope NoIndexDg dg)
+    {
+        static if(randLen!R)
+        {
+            mixin(parallelApplyMixinRandomAccess);
+        }
+        else
+        {
+            mixin(parallelApplyMixinInputRange);
+        }
+    }
+
+    int opApply(scope IndexDg dg)
+    {
+        static if(randLen!R)
+        {
+            mixin(parallelApplyMixinRandomAccess);
+        }
+        else
+        {
+            mixin(parallelApplyMixinInputRange);
+        }
+    }
+}
+
+/*
+This struct buffers the output of a callable that outputs data into a
+user-supplied buffer into a set of buffers of some fixed size.  It allows these
+buffers to be accessed with an input range interface.  This is used internally
+in the buffer-recycling overload of TaskPool.asyncBuf, which creates an
+instance and forwards it to the input range overload of asyncBuf.
+*/
+private struct RoundRobinBuffer(C1, C2)
+{
+    // No need for constraints because they're already checked for in asyncBuf.
+
+    alias Array = Parameters!(C1.init)[0];
+    alias T = typeof(Array.init[0]);
+
+    T[][] bufs;
+    size_t index;
+    C1 nextDel;
+    C2 emptyDel;
+    bool _empty;
+    bool primed;
+
+    this(
+        C1 nextDel,
+        C2 emptyDel,
+        size_t initialBufSize,
+        size_t nBuffers
+    ) {
+        this.nextDel = nextDel;
+        this.emptyDel = emptyDel;
+        bufs.length = nBuffers;
+
+        foreach(ref buf; bufs)
+        {
+            buf.length = initialBufSize;
+        }
+    }
+
+    void prime()
+    in
+    {
+        assert(!empty);
+    }
+    body
+    {
+        scope(success) primed = true;
+        nextDel(bufs[index]);
+    }
+
+
+    T[] front() @property
+    in
+    {
+        assert(!empty);
+    }
+    body
+    {
+        if(!primed) prime();
+        return bufs[index];
+    }
+
+    void popFront()
+    {
+        if(empty || emptyDel())
+        {
+            _empty = true;
+            return;
+        }
+
+        index = (index + 1) % bufs.length;
+        primed = false;
+    }
+
+    bool empty() @property const @safe pure nothrow
+    {
+        return _empty;
+    }
+}
+
+version(unittest)
+{
+    // This was the only way I could get nested maps to work.
+    __gshared TaskPool poolInstance;
+
+    import std.stdio;
+}
+
+// These test basic functionality but don't stress test for threading bugs.
+// These are the tests that should be run every time Phobos is compiled.
+unittest
+{
+    poolInstance = new TaskPool(2);
+    scope(exit) poolInstance.stop();
+
+    // The only way this can be verified is manually.
+    debug(std_parallelism) stderr.writeln("totalCPUs = ", totalCPUs);
+
+    auto oldPriority = poolInstance.priority;
+    poolInstance.priority = Thread.PRIORITY_MAX;
+    assert(poolInstance.priority == Thread.PRIORITY_MAX);
+
+    poolInstance.priority = Thread.PRIORITY_MIN;
+    assert(poolInstance.priority == Thread.PRIORITY_MIN);
+
+    poolInstance.priority = oldPriority;
+    assert(poolInstance.priority == oldPriority);
+
+    static void refFun(ref uint num)
+    {
+        num++;
+    }
+
+    uint x;
+
+    // Test task().
+    auto t = task!refFun(x);
+    poolInstance.put(t);
+    t.yieldForce;
+    assert(t.args[0] == 1);
+
+    auto t2 = task(&refFun, x);
+    poolInstance.put(t2);
+    t2.yieldForce;
+    assert(t2.args[0] == 1);
+
+    // Test scopedTask().
+    auto st = scopedTask!refFun(x);
+    poolInstance.put(st);
+    st.yieldForce;
+    assert(st.args[0] == 1);
+
+    auto st2 = scopedTask(&refFun, x);
+    poolInstance.put(st2);
+    st2.yieldForce;
+    assert(st2.args[0] == 1);
+
+    // Test executeInNewThread().
+    auto ct = scopedTask!refFun(x);
+    ct.executeInNewThread(Thread.PRIORITY_MAX);
+    ct.yieldForce;
+    assert(ct.args[0] == 1);
+
+    // Test ref return.
+    uint toInc = 0;
+    static ref T makeRef(T)(ref T num)
+    {
+        return num;
+    }
+
+    auto t3 = task!makeRef(toInc);
+    taskPool.put(t3);
+    assert(t3.args[0] == 0);
+    t3.spinForce++;
+    assert(t3.args[0] == 1);
+
+    static void testSafe() @safe {
+        static int bump(int num)
+        {
+            return num + 1;
+        }
+
+        auto safePool = new TaskPool(0);
+        auto t = task(&bump, 1);
+        taskPool.put(t);
+        assert(t.yieldForce == 2);
+
+        auto st = scopedTask(&bump, 1);
+        taskPool.put(st);
+        assert(st.yieldForce == 2);
+        safePool.stop();
+    }
+
+    auto arr = [1,2,3,4,5];
+    auto nums = new uint[5];
+    auto nums2 = new uint[5];
+
+    foreach(i, ref elem; poolInstance.parallel(arr))
+    {
+        elem++;
+        nums[i] = cast(uint) i + 2;
+        nums2[i] = elem;
+    }
+
+    assert(nums == [2,3,4,5,6], text(nums));
+    assert(nums2 == nums, text(nums2));
+    assert(arr == nums, text(arr));
+
+    // Test const/immutable arguments.
+    static int add(int lhs, int rhs)
+    {
+        return lhs + rhs;
+    }
+    immutable addLhs = 1;
+    immutable addRhs = 2;
+    auto addTask = task(&add, addLhs, addRhs);
+    auto addScopedTask = scopedTask(&add, addLhs, addRhs);
+    poolInstance.put(addTask);
+    poolInstance.put(addScopedTask);
+    assert(addTask.yieldForce == 3);
+    assert(addScopedTask.yieldForce == 3);
+
+    // Test parallel foreach with non-random access range.
+    auto range = filter!"a != 666"([0, 1, 2, 3, 4]);
+
+    foreach(i, elem; poolInstance.parallel(range))
+    {
+        nums[i] = cast(uint) i;
+    }
+
+    assert(nums == [0,1,2,3,4]);
+
+    auto logs = new double[1_000_000];
+    foreach(i, ref elem; poolInstance.parallel(logs))
+    {
+        elem = log(i + 1.0);
+    }
+
+    foreach(i, elem; logs)
+    {
+        assert(approxEqual(elem, cast(double) log(i + 1)));
+    }
+
+    assert(poolInstance.amap!"a * a"([1,2,3,4,5]) == [1,4,9,16,25]);
+    assert(poolInstance.amap!"a * a"([1,2,3,4,5], new long[5]) == [1,4,9,16,25]);
+    assert(poolInstance.amap!("a * a", "-a")([1,2,3]) ==
+           [tuple(1, -1), tuple(4, -2), tuple(9, -3)]);
+
+    auto tupleBuf = new Tuple!(int, int)[3];
+    poolInstance.amap!("a * a", "-a")([1,2,3], tupleBuf);
+    assert(tupleBuf == [tuple(1, -1), tuple(4, -2), tuple(9, -3)]);
+    poolInstance.amap!("a * a", "-a")([1,2,3], 5, tupleBuf);
+    assert(tupleBuf == [tuple(1, -1), tuple(4, -2), tuple(9, -3)]);
+
+    // Test amap with a non-array buffer.
+    auto toIndex = new int[5];
+    auto indexed = std.range.indexed(toIndex, [3, 1, 4, 0, 2]);
+    poolInstance.amap!"a * 2"([1, 2, 3, 4, 5], indexed);
+    assert(equal(indexed, [2, 4, 6, 8, 10]));
+    assert(equal(toIndex, [8, 4, 10, 2, 6]));
+    poolInstance.amap!"a / 2"(indexed, indexed);
+    assert(equal(indexed, [1, 2, 3, 4, 5]));
+    assert(equal(toIndex, [4, 2, 5, 1, 3]));
+
+    auto buf = new int[5];
+    poolInstance.amap!"a * a"([1,2,3,4,5], buf);
+    assert(buf == [1,4,9,16,25]);
+    poolInstance.amap!"a * a"([1,2,3,4,5], 4, buf);
+    assert(buf == [1,4,9,16,25]);
+
+    assert(poolInstance.reduce!"a + b"([1]) == 1);
+    assert(poolInstance.reduce!"a + b"([1,2,3,4]) == 10);
+    assert(poolInstance.reduce!"a + b"(0.0, [1,2,3,4]) == 10);
+    assert(poolInstance.reduce!"a + b"(0.0, [1,2,3,4], 1) == 10);
+    assert(poolInstance.reduce!(min, max)([1,2,3,4]) == tuple(1, 4));
+    assert(poolInstance.reduce!("a + b", "a * b")(tuple(0, 1), [1,2,3,4]) ==
+           tuple(10, 24));
+
+    immutable serialAns = std.algorithm.reduce!"a + b"(iota(1000));
+    assert(poolInstance.reduce!"a + b"(0, iota(1000)) == serialAns);
+    assert(poolInstance.reduce!"a + b"(iota(1000)) == serialAns);
+
+    // Test worker-local storage.
+    auto wl = poolInstance.workerLocalStorage(0);
+    foreach(i; poolInstance.parallel(iota(1000), 1))
+    {
+        wl.get = wl.get + i;
+    }
+
+    auto wlRange = wl.toRange;
+    auto parallelSum = poolInstance.reduce!"a + b"(wlRange);
+    assert(parallelSum == 499500);
+    assert(wlRange[0..1][0] == wlRange[0]);
+    assert(wlRange[1..2][0] == wlRange[1]);
+
+    // Test finish()
+    {
+        static void slowFun() { Thread.sleep(dur!"msecs"(1)); }
+
+        auto pool1 = new TaskPool();
+        auto tSlow = task!slowFun();
+        pool1.put(tSlow);
+        pool1.finish();
+        tSlow.yieldForce;
+        // Can't assert that pool1.status == PoolState.stopNow because status
+        // doesn't change until after the "done" flag is set and the waiting
+        // thread is woken up.
+
+        auto pool2 = new TaskPool();
+        auto tSlow2 = task!slowFun();
+        pool2.put(tSlow2);
+        pool2.finish(true); // blocking
+        assert(tSlow2.done);
+
+        // Test fix for Bug 8582 by making pool size zero.
+        auto pool3 = new TaskPool(0);
+        auto tSlow3 = task!slowFun();
+        pool3.put(tSlow3);
+        pool3.finish(true); // blocking
+        assert(tSlow3.done);
+
+        // This is correct because no thread will terminate unless pool2.status
+        // and pool3.status have already been set to stopNow.
+        assert(pool2.status == TaskPool.PoolState.stopNow);
+        assert(pool3.status == TaskPool.PoolState.stopNow);
+    }
+
+    // Test default pool stuff.
+    assert(taskPool.size == totalCPUs - 1);
+
+    nums = new uint[1000];
+    foreach(i; parallel(iota(1000)))
+    {
+        nums[i] = cast(uint) i;
+    }
+    assert(equal(nums, iota(1000)));
+
+    assert(equal(
+               poolInstance.map!"a * a"(iota(30_000_001), 10_000),
+               std.algorithm.map!"a * a"(iota(30_000_001))
+           ));
+
+    // The filter is to kill random access and test the non-random access
+    // branch.
+    assert(equal(
+               poolInstance.map!"a * a"(
+                   filter!"a == a"(iota(30_000_001)
+                                  ), 10_000, 1000),
+               std.algorithm.map!"a * a"(iota(30_000_001))
+           ));
+
+    assert(
+        reduce!"a + b"(0UL,
+                       poolInstance.map!"a * a"(iota(3_000_001), 10_000)
+                      ) ==
+        reduce!"a + b"(0UL,
+                       std.algorithm.map!"a * a"(iota(3_000_001))
+                      )
+    );
+
+    assert(equal(
+               iota(1_000_002),
+               poolInstance.asyncBuf(filter!"a == a"(iota(1_000_002)))
+           ));
+
+    {
+        import std.file : deleteme;
+
+        string temp_file = deleteme ~ "-tempDelMe.txt";
+        auto file = File(temp_file, "wb");
+        scope(exit)
+        {
+            file.close();
+            import std.file;
+            remove(temp_file);
+        }
+
+        auto written = [[1.0, 2, 3], [4.0, 5, 6], [7.0, 8, 9]];
+        foreach(row; written)
+        {
+            file.writeln(join(to!(string[])(row), "\t"));
+        }
+
+        file = File(temp_file);
+
+        void next(ref char[] buf)
+        {
+            file.readln(buf);
+            import std.string : chomp;
+            buf = chomp(buf);
+        }
+
+        double[][] read;
+        auto asyncReader = taskPool.asyncBuf(&next, &file.eof);
+
+        foreach(line; asyncReader)
+        {
+            if(line.length == 0) continue;
+            auto ls = line.split("\t");
+            read ~= to!(double[])(ls);
+        }
+
+        assert(read == written);
+        file.close();
+    }
+
+    // Test Map/AsyncBuf chaining.
+
+    auto abuf = poolInstance.asyncBuf(iota(-1.0, 3_000_000), 100);
+    auto temp = poolInstance.map!sqrt(
+                    abuf, 100, 5
+                );
+    auto lmchain = poolInstance.map!"a * a"(temp, 100, 5);
+    lmchain.popFront();
+
+    int ii;
+    foreach( elem; (lmchain))
+    {
+        if(!approxEqual(elem, ii))
+        {
+            stderr.writeln(ii, '\t', elem);
+        }
+        ii++;
+    }
+
+    // Test buffer trick in parallel foreach.
+    abuf = poolInstance.asyncBuf(iota(-1.0, 1_000_000), 100);
+    abuf.popFront();
+    auto bufTrickTest = new size_t[abuf.length];
+    foreach(i, elem; parallel(abuf))
+    {
+        bufTrickTest[i] = i;
+    }
+
+    assert(equal(iota(1_000_000), bufTrickTest));
+
+    auto myTask = task!(std.math.abs)(-1);
+    taskPool.put(myTask);
+    assert(myTask.spinForce == 1);
+
+    // Test that worker local storage from one pool receives an index of 0
+    // when the index is queried w.r.t. another pool.  The only way to do this
+    // is non-deterministically.
+    foreach(i; parallel(iota(1000), 1))
+    {
+        assert(poolInstance.workerIndex == 0);
+    }
+
+    foreach(i; poolInstance.parallel(iota(1000), 1))
+    {
+        assert(taskPool.workerIndex == 0);
+    }
+
+    // Test exception handling.
+    static void parallelForeachThrow()
+    {
+        foreach(elem; parallel(iota(10)))
+        {
+            throw new Exception("");
+        }
+    }
+
+    assertThrown!Exception(parallelForeachThrow());
+
+    static int reduceException(int a, int b)
+    {
+        throw new Exception("");
+    }
+
+    assertThrown!Exception(
+        poolInstance.reduce!reduceException(iota(3))
+    );
+
+    static int mapException(int a)
+    {
+        throw new Exception("");
+    }
+
+    assertThrown!Exception(
+        poolInstance.amap!mapException(iota(3))
+    );
+
+    static void mapThrow()
+    {
+        auto m = poolInstance.map!mapException(iota(3));
+        m.popFront();
+    }
+
+    assertThrown!Exception(mapThrow());
+
+    struct ThrowingRange
+    {
+        @property int front()
+        {
+            return 1;
+        }
+        void popFront()
+        {
+            throw new Exception("");
+        }
+        enum bool empty = false;
+    }
+
+    assertThrown!Exception(poolInstance.asyncBuf(ThrowingRange.init));
+}
+
+//version = parallelismStressTest;
+
+// These are more like stress tests than real unit tests.  They print out
+// tons of stuff and should not be run every time make unittest is run.
+version(parallelismStressTest)
+{
+    unittest
+    {
+        size_t attempt;
+        for(; attempt < 10; attempt++)
+            foreach(poolSize; [0, 4])
+        {
+
+            poolInstance = new TaskPool(poolSize);
+
+            uint[] numbers = new uint[1_000];
+
+            foreach(i; poolInstance.parallel( iota(0, numbers.length)) )
+            {
+                numbers[i] = cast(uint) i;
+            }
+
+            // Make sure it works.
+            foreach(i; 0..numbers.length)
+            {
+                assert(numbers[i] == i);
+            }
+
+            stderr.writeln("Done creating nums.");
+
+
+            auto myNumbers = filter!"a % 7 > 0"( iota(0, 1000));
+            foreach(num; poolInstance.parallel(myNumbers))
+            {
+                assert(num % 7 > 0 && num < 1000);
+            }
+            stderr.writeln("Done modulus test.");
+
+            uint[] squares = poolInstance.amap!"a * a"(numbers, 100);
+            assert(squares.length == numbers.length);
+            foreach(i, number; numbers)
+            {
+                assert(squares[i] == number * number);
+            }
+            stderr.writeln("Done squares.");
+
+            auto sumFuture = task!( reduce!"a + b" )(numbers);
+            poolInstance.put(sumFuture);
+
+            ulong sumSquares = 0;
+            foreach(elem; numbers)
+            {
+                sumSquares += elem * elem;
+            }
+
+            uint mySum = sumFuture.spinForce();
+            assert(mySum == 999 * 1000 / 2);
+
+            auto mySumParallel = poolInstance.reduce!"a + b"(numbers);
+            assert(mySum == mySumParallel);
+            stderr.writeln("Done sums.");
+
+            auto myTask = task(
+            {
+                synchronized writeln("Our lives are parallel...Our lives are parallel.");
+            });
+            poolInstance.put(myTask);
+
+            auto nestedOuter = "abcd";
+            auto nestedInner =  iota(0, 10, 2);
+
+            foreach(i, letter; poolInstance.parallel(nestedOuter, 1))
+            {
+                foreach(j, number; poolInstance.parallel(nestedInner, 1))
+                {
+                    synchronized writeln(i, ": ", letter, "  ", j, ": ", number);
+                }
+            }
+
+            poolInstance.stop();
+        }
+
+        assert(attempt == 10);
+        writeln("Press enter to go to next round of unittests.");
+        readln();
+    }
+
+    // These unittests are intended more for actual testing and not so much
+    // as examples.
+    unittest
+    {
+        foreach(attempt; 0..10)
+        foreach(poolSize; [0, 4])
+        {
+            poolInstance = new TaskPool(poolSize);
+
+            // Test indexing.
+            stderr.writeln("Creator Raw Index:  ", poolInstance.threadIndex);
+            assert(poolInstance.workerIndex() == 0);
+
+            // Test worker-local storage.
+            auto workerLocalStorage = poolInstance.workerLocalStorage!uint(1);
+            foreach(i; poolInstance.parallel(iota(0U, 1_000_000)))
+            {
+                workerLocalStorage.get++;
+            }
+            assert(reduce!"a + b"(workerLocalStorage.toRange) ==
+            1_000_000 + poolInstance.size + 1);
+
+            // Make sure work is reasonably balanced among threads.  This test is
+            // non-deterministic and is more of a sanity check than something that
+            // has an absolute pass/fail.
+            shared(uint)[void*] nJobsByThread;
+            foreach(thread; poolInstance.pool)
+            {
+                nJobsByThread[cast(void*) thread] = 0;
+            }
+            nJobsByThread[ cast(void*) Thread.getThis()] = 0;
+
+            foreach(i; poolInstance.parallel( iota(0, 1_000_000), 100 ))
+            {
+                atomicOp!"+="( nJobsByThread[ cast(void*) Thread.getThis() ], 1);
+            }
+
+            stderr.writeln("\nCurrent thread is:  ",
+            cast(void*) Thread.getThis());
+            stderr.writeln("Workload distribution:  ");
+            foreach(k, v; nJobsByThread)
+            {
+                stderr.writeln(k, '\t', v);
+            }
+
+            // Test whether amap can be nested.
+            real[][] matrix = new real[][](1000, 1000);
+            foreach(i; poolInstance.parallel( iota(0, matrix.length) ))
+            {
+                foreach(j; poolInstance.parallel( iota(0, matrix[0].length) ))
+                {
+                    matrix[i][j] = i * j;
+                }
+            }
+
+            // Get around weird bugs having to do w/ sqrt being an intrinsic:
+            static real mySqrt(real num)
+            {
+                return sqrt(num);
+            }
+
+            static real[] parallelSqrt(real[] nums)
+            {
+                return poolInstance.amap!mySqrt(nums);
+            }
+
+            real[][] sqrtMatrix = poolInstance.amap!parallelSqrt(matrix);
+
+            foreach(i, row; sqrtMatrix)
+            {
+                foreach(j, elem; row)
+                {
+                    real shouldBe = sqrt( cast(real) i * j);
+                    assert(approxEqual(shouldBe, elem));
+                    sqrtMatrix[i][j] = shouldBe;
+                }
+            }
+
+            auto saySuccess = task(
+            {
+                stderr.writeln(
+                    "Success doing matrix stuff that involves nested pool use.");
+            });
+            poolInstance.put(saySuccess);
+            saySuccess.workForce();
+
+            // A more thorough test of amap, reduce:  Find the sum of the square roots of
+            // matrix.
+
+            static real parallelSum(real[] input)
+            {
+                return poolInstance.reduce!"a + b"(input);
+            }
+
+            auto sumSqrt = poolInstance.reduce!"a + b"(
+                               poolInstance.amap!parallelSum(
+                                   sqrtMatrix
+                               )
+                           );
+
+            assert(approxEqual(sumSqrt, 4.437e8));
+            stderr.writeln("Done sum of square roots.");
+
+            // Test whether tasks work with function pointers.
+            auto nanTask = task(&isNaN, 1.0L);
+            poolInstance.put(nanTask);
+            assert(nanTask.spinForce == false);
+
+            if(poolInstance.size > 0)
+            {
+                // Test work waiting.
+                static void uselessFun()
+                {
+                    foreach(i; 0..1_000_000) {}
+                }
+
+                auto uselessTasks = new typeof(task(&uselessFun))[1000];
+                foreach(ref uselessTask; uselessTasks)
+                {
+                    uselessTask = task(&uselessFun);
+                }
+                foreach(ref uselessTask; uselessTasks)
+                {
+                    poolInstance.put(uselessTask);
+                }
+                foreach(ref uselessTask; uselessTasks)
+                {
+                    uselessTask.workForce();
+                }
+            }
+
+            // Test the case of non-random access + ref returns.
+            int[] nums = [1,2,3,4,5];
+            static struct RemoveRandom
+            {
+                int[] arr;
+
+                ref int front()
+                {
+                    return arr.front;
+                }
+                void popFront()
+                {
+                    arr.popFront();
+                }
+                bool empty()
+                {
+                    return arr.empty;
+                }
+            }
+
+            auto refRange = RemoveRandom(nums);
+            foreach(ref elem; poolInstance.parallel(refRange))
+            {
+                elem++;
+            }
+            assert(nums == [2,3,4,5,6], text(nums));
+            stderr.writeln("Nums:  ", nums);
+
+            poolInstance.stop();
+        }
+    }
+}
+
+version(unittest)
+{
+    struct __S_12733
+    {
+        invariant() { assert(checksum == 1234567890); }
+        this(ulong u){n = u;}
+        void opAssign(__S_12733 s){this.n = s.n;}
+        ulong n;
+        ulong checksum = 1234567890;
+    }
+
+    static auto __genPair_12733(ulong n) { return __S_12733(n); }
+}
+
+unittest
+{
+    immutable ulong[] data = [ 2UL^^59-1, 2UL^^59-1, 2UL^^59-1, 112_272_537_195_293UL ];
+
+    auto result = taskPool.amap!__genPair_12733(data);
+}
diff --git a/libphobos/src/std/path.d b/libphobos/src/std/path.d
new file mode 100644
index 00000000000..60c844fe3e4
--- /dev/null
+++ b/libphobos/src/std/path.d
@@ -0,0 +1,4025 @@
+// Written in the D programming language.
+
+/** This module is used to manipulate _path strings.
+
+    All functions, with the exception of $(LREF expandTilde) (and in some
+    cases $(LREF absolutePath) and $(LREF relativePath)), are pure
+    string manipulation functions; they don't depend on any state outside
+    the program, nor do they perform any actual file system actions.
+    This has the consequence that the module does not make any distinction
+    between a _path that points to a directory and a _path that points to a
+    file, and it does not know whether or not the object pointed to by the
+    _path actually exists in the file system.
+    To differentiate between these cases, use $(XREF file,isDir) and
+    $(XREF file,exists).
+
+    Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`))
+    are in principle valid directory separators.  This module treats them
+    both on equal footing, but in cases where a $(I new) separator is
+    added, a backslash will be used.  Furthermore, the $(LREF buildNormalizedPath)
+    function will replace all slashes with backslashes on that platform.
+
+    In general, the functions in this module assume that the input paths
+    are well-formed.  (That is, they should not contain invalid characters,
+    they should follow the file system's _path format, etc.)  The result
+    of calling a function on an ill-formed _path is undefined.  When there
+    is a chance that a _path or a file name is invalid (for instance, when it
+    has been input by the user), it may sometimes be desirable to use the
+    $(LREF isValidFilename) and $(LREF isValidPath) functions to check
+    this.
+
+    Most functions do not perform any memory allocations, and if a string is
+    returned, it is usually a slice of an input string.  If a function
+    allocates, this is explicitly mentioned in the documentation.
+
+    Upgrading:
+        $(WEB digitalmars.com/d/1.0/phobos/std_path.html#fnmatch) can
+        be replaced with $(D globMatch).
+
+    Authors:
+        Lars Tandle Kyllingstad,
+        $(WEB digitalmars.com, Walter Bright),
+        Grzegorz Adam Hankiewicz,
+        Thomas K$(UUML)hne,
+        $(WEB erdani.org, Andrei Alexandrescu)
+    Copyright:
+        Copyright (c) 2000-2014, the authors. All rights reserved.
+    License:
+        $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
+    Source:
+        $(PHOBOSSRC std/_path.d)
+    Macros:
+        WIKI = Phobos/StdPath
+*/
+module std.path;
+
+
+// FIXME
+import std.file; //: getcwd;
+import std.range.primitives;
+import std.traits;
+static import std.meta;
+
+version (unittest)
+{
+private:
+    struct TestAliasedString
+    {
+        string get() @safe @nogc pure nothrow { return _s; }
+        alias get this;
+        @disable this(this);
+        string _s;
+    }
+
+    bool testAliasedString(alias func, Args...)(string s, Args args)
+    {
+        return func(TestAliasedString(s), args) == func(s, args);
+    }
+}
+
+/** String used to separate directory names in a path.  Under
+    POSIX this is a slash, under Windows a backslash.
+*/
+version(Posix)          enum string dirSeparator = "/";
+else version(Windows)   enum string dirSeparator = "\\";
+else static assert (0, "unsupported platform");
+
+
+
+
+/** Path separator string.  A colon under POSIX, a semicolon
+    under Windows.
+*/
+version(Posix)          enum string pathSeparator = ":";
+else version(Windows)   enum string pathSeparator = ";";
+else static assert (0, "unsupported platform");
+
+
+
+
+/** Determines whether the given character is a directory separator.
+
+    On Windows, this includes both $(D `\`) and $(D `/`).
+    On POSIX, it's just $(D `/`).
+*/
+bool isDirSeparator(dchar c)  @safe pure nothrow @nogc
+{
+    if (c == '/') return true;
+    version(Windows) if (c == '\\') return true;
+    return false;
+}
+
+
+/*  Determines whether the given character is a drive separator.
+
+    On Windows, this is true if c is the ':' character that separates
+    the drive letter from the rest of the path.  On POSIX, this always
+    returns false.
+*/
+private bool isDriveSeparator(dchar c)  @safe pure nothrow @nogc
+{
+    version(Windows) return c == ':';
+    else return false;
+}
+
+
+/*  Combines the isDirSeparator and isDriveSeparator tests. */
+version(Windows) private bool isSeparator(dchar c)  @safe pure nothrow @nogc
+{
+    return isDirSeparator(c) || isDriveSeparator(c);
+}
+version(Posix) private alias isSeparator = isDirSeparator;
+
+
+/*  Helper function that determines the position of the last
+    drive/directory separator in a string.  Returns -1 if none
+    is found.
+*/
+private ptrdiff_t lastSeparator(R)(R path)
+    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+        isNarrowString!R)
+{
+    auto i = (cast(ptrdiff_t) path.length) - 1;
+    while (i >= 0 && !isSeparator(path[i])) --i;
+    return i;
+}
+
+
+version (Windows)
+{
+    private bool isUNC(R)(R path)
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            isNarrowString!R)
+    {
+        return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
+            && !isDirSeparator(path[2]);
+    }
+
+    private ptrdiff_t uncRootLength(R)(R path)
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            isNarrowString!R)
+        in { assert (isUNC(path)); }
+        body
+    {
+        ptrdiff_t i = 3;
+        while (i < path.length && !isDirSeparator(path[i])) ++i;
+        if (i < path.length)
+        {
+            auto j = i;
+            do { ++j; } while (j < path.length && isDirSeparator(path[j]));
+            if (j < path.length)
+            {
+                do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
+                i = j;
+            }
+        }
+        return i;
+    }
+
+    private bool hasDrive(R)(R path)
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            isNarrowString!R)
+    {
+        return path.length >= 2 && isDriveSeparator(path[1]);
+    }
+
+    private bool isDriveRoot(R)(R path)
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            isNarrowString!R)
+    {
+        return path.length >= 3 && isDriveSeparator(path[1])
+            && isDirSeparator(path[2]);
+    }
+}
+
+
+/*  Helper functions that strip leading/trailing slashes and backslashes
+    from a path.
+*/
+private auto ltrimDirSeparators(R)(R path)
+    if (isInputRange!R && isSomeChar!(ElementType!R) ||
+        isNarrowString!R)
+{
+    static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R)
+    {
+        int i = 0;
+        while (i < path.length && isDirSeparator(path[i]))
+            ++i;
+        return path[i .. path.length];
+    }
+    else
+    {
+        while (!path.empty && isDirSeparator(path.front))
+            path.popFront();
+        return path;
+    }
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byDchar;
+
+    assert(ltrimDirSeparators("//abc//").array == "abc//");
+    assert(ltrimDirSeparators("//abc//"d).array == "abc//"d);
+    assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);
+}
+
+private auto rtrimDirSeparators(R)(R path)
+    if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
+        isNarrowString!R)
+{
+    static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R)
+    {
+        auto i = (cast(ptrdiff_t) path.length) - 1;
+        while (i >= 0 && isDirSeparator(path[i]))
+            --i;
+        return path[0 .. i+1];
+    }
+    else
+    {
+        while (!path.empty && isDirSeparator(path.back))
+            path.popBack();
+        return path;
+    }
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byDchar;
+
+    assert(rtrimDirSeparators("//abc//").array == "//abc");
+    assert(rtrimDirSeparators("//abc//"d).array == "//abc"d);
+
+    assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");
+}
+
+private auto trimDirSeparators(R)(R path)
+    if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
+        isNarrowString!R)
+{
+    return ltrimDirSeparators(rtrimDirSeparators(path));
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byDchar;
+
+    assert(trimDirSeparators("//abc//").array == "abc");
+    assert(trimDirSeparators("//abc//"d).array == "abc"d);
+
+    assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");
+}
+
+
+
+
+/** This $(D enum) is used as a template argument to functions which
+    compare file names, and determines whether the comparison is
+    case sensitive or not.
+*/
+enum CaseSensitive : bool
+{
+    /// File names are case insensitive
+    no = false,
+
+    /// File names are case sensitive
+    yes = true,
+
+    /** The default (or most common) setting for the current platform.
+        That is, $(D no) on Windows and Mac OS X, and $(D yes) on all
+        POSIX systems except OS X (Linux, *BSD, etc.).
+    */
+    osDefault = osDefaultCaseSensitivity
+}
+version (Windows)    private enum osDefaultCaseSensitivity = false;
+else version (OSX)   private enum osDefaultCaseSensitivity = false;
+else version (Posix) private enum osDefaultCaseSensitivity = true;
+else static assert (0);
+
+
+
+
+/**
+    Params:
+        cs = Whether or not suffix matching is case-sensitive.
+        path = A path name. It can be a string, or any random-access range of
+            characters.
+        suffix = An optional suffix to be removed from the file name.
+    Returns: The name of the file in the path name, without any leading
+        directory and with an optional suffix chopped off.
+
+    If $(D suffix) is specified, it will be compared to $(D path)
+    using $(D filenameCmp!cs),
+    where $(D cs) is an optional template parameter determining whether
+    the comparison is case sensitive or not.  See the
+    $(LREF filenameCmp) documentation for details.
+
+    Example:
+    ---
+    assert (baseName("dir/file.ext")         == "file.ext");
+    assert (baseName("dir/file.ext", ".ext") == "file");
+    assert (baseName("dir/file.ext", ".xyz") == "file.ext");
+    assert (baseName("dir/filename", "name") == "file");
+    assert (baseName("dir/subdir/")          == "subdir");
+
+    version (Windows)
+    {
+        assert (baseName(`d:file.ext`)      == "file.ext");
+        assert (baseName(`d:\dir\file.ext`) == "file.ext");
+    }
+    ---
+
+    Note:
+    This function $(I only) strips away the specified suffix, which
+    doesn't necessarily have to represent an extension.
+    To remove the extension from a path, regardless of what the extension
+    is, use $(LREF stripExtension).
+    To obtain the filename without leading directories and without
+    an extension, combine the functions like this:
+    ---
+    assert (baseName(stripExtension("dir/file.ext")) == "file");
+    ---
+
+    Standards:
+    This function complies with
+    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
+    the POSIX requirements for the 'basename' shell utility)
+    (with suitable adaptations for Windows paths).
+*/
+auto baseName(R)(R path)
+    if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
+        is(StringTypeOf!R))
+{
+    auto p1 = stripDrive!(BaseOf!R)(path);
+    if (p1.empty)
+    {
+        version (Windows) if (isUNC!(BaseOf!R)(path))
+        {
+            return path[0..1];
+        }
+        static if (is(StringTypeOf!R))
+            return StringTypeOf!R.init[];   // which is null
+        else
+            return p1; // which is empty
+    }
+
+    auto p2 = rtrimDirSeparators(p1);
+    if (p2.empty) return p1[0 .. 1];
+
+    return p2[lastSeparator(p2)+1 .. p2.length];
+}
+
+/// ditto
+inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
+    (inout(C)[] path, in C1[] suffix)
+    @safe pure //TODO: nothrow (because of filenameCmp())
+    if (isSomeChar!C && isSomeChar!C1)
+{
+    auto p = baseName(path);
+    if (p.length > suffix.length
+        && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0)
+    {
+        return p[0 .. $-suffix.length];
+    }
+    else return p;
+}
+
+
+unittest
+{
+    assert (baseName("").empty);
+    assert (baseName("file.ext"w) == "file.ext");
+    assert (baseName("file.ext"d, ".ext") == "file");
+    assert (baseName("file", "file"w.dup) == "file");
+    assert (baseName("dir/file.ext"d.dup) == "file.ext");
+    assert (baseName("dir/file.ext", ".ext"d) == "file");
+    assert (baseName("dir/file"w, "file"d) == "file");
+    assert (baseName("dir///subdir////") == "subdir");
+    assert (baseName("dir/subdir.ext/", ".ext") == "subdir");
+    assert (baseName("dir/subdir/".dup, "subdir") == "subdir");
+    assert (baseName("/"w.dup) == "/");
+    assert (baseName("//"d.dup) == "/");
+    assert (baseName("///") == "/");
+
+    assert (baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
+    assert (baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
+
+    {
+        auto r = MockRange!(immutable(char))(`dir/file.ext`);
+        auto s = r.baseName();
+        foreach (i, c; `file`)
+            assert(s[i] == c);
+    }
+
+    version (Windows)
+    {
+        assert (baseName(`dir\file.ext`) == `file.ext`);
+        assert (baseName(`dir\file.ext`, `.ext`) == `file`);
+        assert (baseName(`dir\file`, `file`) == `file`);
+        assert (baseName(`d:file.ext`) == `file.ext`);
+        assert (baseName(`d:file.ext`, `.ext`) == `file`);
+        assert (baseName(`d:file`, `file`) == `file`);
+        assert (baseName(`dir\\subdir\\\`) == `subdir`);
+        assert (baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
+        assert (baseName(`dir\subdir\`, `subdir`) == `subdir`);
+        assert (baseName(`\`) == `\`);
+        assert (baseName(`\\`) == `\`);
+        assert (baseName(`\\\`) == `\`);
+        assert (baseName(`d:\`) == `\`);
+        assert (baseName(`d:`).empty);
+        assert (baseName(`\\server\share\file`) == `file`);
+        assert (baseName(`\\server\share\`) == `\`);
+        assert (baseName(`\\server\share`) == `\`);
+
+        auto r = MockRange!(immutable(char))(`\\server\share`);
+        auto s = r.baseName();
+        foreach (i, c; `\`)
+            assert(s[i] == c);
+    }
+
+    assert (baseName(stripExtension("dir/file.ext")) == "file");
+
+    static assert (baseName("dir/file.ext") == "file.ext");
+    static assert (baseName("dir/file.ext", ".ext") == "file");
+
+    static struct DirEntry { string s; alias s this; }
+    assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
+}
+
+
+
+
+/** Returns the directory part of a path.  On Windows, this
+    includes the drive letter if present.
+
+    Params:
+        path = A path name.
+
+    Returns:
+        A slice of $(D path) or ".".
+
+    Standards:
+    This function complies with
+    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
+    the POSIX requirements for the 'dirname' shell utility)
+    (with suitable adaptations for Windows paths).
+*/
+auto dirName(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    static auto result(bool dot, typeof(path[0..1]) p)
+    {
+        static if (isSomeString!R)
+            return dot ? "." : p;
+        else
+        {
+            import std.range : choose, only;
+            return choose(dot, only(cast(ElementEncodingType!R)'.'), p);
+        }
+    }
+
+    if (path.empty)
+        return result(true, path[0 .. 0]);
+
+    auto p = rtrimDirSeparators(path);
+    if (p.empty)
+        return result(false, path[0 .. 1]);
+
+    version (Windows)
+    {
+        if (isUNC(p) && uncRootLength(p) == p.length)
+            return result(false, p);
+
+        if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
+            return result(false, path[0 .. 3]);
+    }
+
+    auto i = lastSeparator(p);
+    if (i == -1)
+        return result(true, p);
+    if (i == 0)
+        return result(false, p[0 .. 1]);
+
+    version (Windows)
+    {
+        // If the directory part is either d: or d:\
+        // do not chop off the last symbol.
+        if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
+            return result(false, p[0 .. i+1]);
+    }
+    // Remove any remaining trailing (back)slashes.
+    return result(false, rtrimDirSeparators(p[0 .. i]));
+}
+
+///
+unittest
+{
+    assert (dirName("") == ".");
+    assert (dirName("file"w) == ".");
+    assert (dirName("dir/"d) == ".");
+    assert (dirName("dir///") == ".");
+    assert (dirName("dir/file"w.dup) == "dir");
+    assert (dirName("dir///file"d.dup) == "dir");
+    assert (dirName("dir/subdir/") == "dir");
+    assert (dirName("/dir/file"w) == "/dir");
+    assert (dirName("/file"d) == "/");
+    assert (dirName("/") == "/");
+    assert (dirName("///") == "/");
+
+    version (Windows)
+    {
+        assert (dirName(`dir\`) == `.`);
+        assert (dirName(`dir\\\`) == `.`);
+        assert (dirName(`dir\file`) == `dir`);
+        assert (dirName(`dir\\\file`) == `dir`);
+        assert (dirName(`dir\subdir\`) == `dir`);
+        assert (dirName(`\dir\file`) == `\dir`);
+        assert (dirName(`\file`) == `\`);
+        assert (dirName(`\`) == `\`);
+        assert (dirName(`\\\`) == `\`);
+        assert (dirName(`d:`) == `d:`);
+        assert (dirName(`d:file`) == `d:`);
+        assert (dirName(`d:\`) == `d:\`);
+        assert (dirName(`d:\file`) == `d:\`);
+        assert (dirName(`d:\dir\file`) == `d:\dir`);
+        assert (dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
+        assert (dirName(`\\server\share\file`) == `\\server\share`);
+        assert (dirName(`\\server\share\`) == `\\server\share`);
+        assert (dirName(`\\server\share`) == `\\server\share`);
+    }
+}
+
+auto dirName(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return dirName!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!dirName("file"));
+}
+
+unittest
+{
+    static assert (dirName("dir/file") == "dir");
+
+    import std.array;
+    import std.utf : byChar, byWchar, byDchar;
+
+    assert (dirName("".byChar).array == ".");
+    assert (dirName("file"w.byWchar).array == "."w);
+    assert (dirName("dir/"d.byDchar).array == "."d);
+    assert (dirName("dir///".byChar).array == ".");
+    assert (dirName("dir/subdir/".byChar).array == "dir");
+    assert (dirName("/dir/file"w.byWchar).array == "/dir"w);
+    assert (dirName("/file"d.byDchar).array == "/"d);
+    assert (dirName("/".byChar).array == "/");
+    assert (dirName("///".byChar).array == "/");
+
+    version (Windows)
+    {
+        assert (dirName(`dir\`.byChar).array == `.`);
+        assert (dirName(`dir\\\`.byChar).array == `.`);
+        assert (dirName(`dir\file`.byChar).array == `dir`);
+        assert (dirName(`dir\\\file`.byChar).array == `dir`);
+        assert (dirName(`dir\subdir\`.byChar).array == `dir`);
+        assert (dirName(`\dir\file`.byChar).array == `\dir`);
+        assert (dirName(`\file`.byChar).array == `\`);
+        assert (dirName(`\`.byChar).array == `\`);
+        assert (dirName(`\\\`.byChar).array == `\`);
+        assert (dirName(`d:`.byChar).array == `d:`);
+        assert (dirName(`d:file`.byChar).array == `d:`);
+        assert (dirName(`d:\`.byChar).array == `d:\`);
+        assert (dirName(`d:\file`.byChar).array == `d:\`);
+        assert (dirName(`d:\dir\file`.byChar).array == `d:\dir`);
+        assert (dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`);
+        assert (dirName(`\\server\share\file`) == `\\server\share`);
+        assert (dirName(`\\server\share\`.byChar).array == `\\server\share`);
+        assert (dirName(`\\server\share`.byChar).array == `\\server\share`);
+    }
+
+    //static assert (dirName("dir/file".byChar).array == "dir");
+}
+
+
+
+
+/** Returns the root directory of the specified path, or $(D null) if the
+    path is not rooted.
+
+    Params:
+        path = A path name.
+
+    Returns:
+        A slice of $(D path).
+*/
+auto rootName(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    if (path.empty)
+        goto Lnull;
+
+    version (Posix)
+    {
+        if (isDirSeparator(path[0])) return path[0 .. 1];
+    }
+    else version (Windows)
+    {
+        if (isDirSeparator(path[0]))
+        {
+            if (isUNC(path)) return path[0 .. uncRootLength(path)];
+            else return path[0 .. 1];
+        }
+        else if (path.length >= 3 && isDriveSeparator(path[1]) &&
+            isDirSeparator(path[2]))
+        {
+            return path[0 .. 3];
+        }
+    }
+    else static assert (0, "unsupported platform");
+
+    assert (!isRooted(path));
+Lnull:
+    static if (is(StringTypeOf!R))
+        return null; // legacy code may rely on null return rather than slice
+    else
+        return path[0..0];
+}
+
+///
+unittest
+{
+    assert (rootName("") is null);
+    assert (rootName("foo") is null);
+    assert (rootName("/") == "/");
+    assert (rootName("/foo/bar") == "/");
+
+    version (Windows)
+    {
+        assert (rootName("d:foo") is null);
+        assert (rootName(`d:\foo`) == `d:\`);
+        assert (rootName(`\\server\share\foo`) == `\\server\share`);
+        assert (rootName(`\\server\share`) == `\\server\share`);
+    }
+}
+
+unittest
+{
+    assert (testAliasedString!rootName("/foo/bar"));
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byChar;
+
+    assert (rootName("".byChar).array == "");
+    assert (rootName("foo".byChar).array == "");
+    assert (rootName("/".byChar).array == "/");
+    assert (rootName("/foo/bar".byChar).array == "/");
+
+    version (Windows)
+    {
+        assert (rootName("d:foo".byChar).array == "");
+        assert (rootName(`d:\foo`.byChar).array == `d:\`);
+        assert (rootName(`\\server\share\foo`.byChar).array == `\\server\share`);
+        assert (rootName(`\\server\share`.byChar).array == `\\server\share`);
+    }
+}
+
+auto rootName(R)(R path)
+    if (isConvertibleToString!R)
+{
+    return rootName!(StringTypeOf!R)(path);
+}
+
+
+/**
+    Get the drive portion of a path.
+
+    Params:
+        path = string or range of characters
+
+    Returns:
+        A slice of $(D _path) that is the drive, or an empty range if the drive
+        is not specified.  In the case of UNC paths, the network share
+        is returned.
+
+        Always returns an empty range on POSIX.
+*/
+auto driveName(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    version (Windows)
+    {
+        if (hasDrive(path))
+            return path[0 .. 2];
+        else if (isUNC(path))
+            return path[0 .. uncRootLength(path)];
+    }
+    static if (isSomeString!R)
+        return cast(ElementEncodingType!R[])null; // legacy code may rely on null return rather than slice
+    else
+        return path[0..0];
+}
+
+///
+unittest
+{
+    version (Posix)  assert (driveName("c:/foo").empty);
+    version (Windows)
+    {
+        assert (driveName(`dir\file`).empty);
+        assert (driveName(`d:file`) == "d:");
+        assert (driveName(`d:\file`) == "d:");
+        assert (driveName("d:") == "d:");
+        assert (driveName(`\\server\share\file`) == `\\server\share`);
+        assert (driveName(`\\server\share\`) == `\\server\share`);
+        assert (driveName(`\\server\share`) == `\\server\share`);
+
+        static assert (driveName(`d:\file`) == "d:");
+    }
+}
+
+auto driveName(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return driveName!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!driveName(`d:\file`));
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byChar;
+
+    version (Posix)  assert (driveName("c:/foo".byChar).empty);
+    version (Windows)
+    {
+        assert (driveName(`dir\file`.byChar).empty);
+        assert (driveName(`d:file`.byChar).array == "d:");
+        assert (driveName(`d:\file`.byChar).array == "d:");
+        assert (driveName("d:".byChar).array == "d:");
+        assert (driveName(`\\server\share\file`.byChar).array == `\\server\share`);
+        assert (driveName(`\\server\share\`.byChar).array == `\\server\share`);
+        assert (driveName(`\\server\share`.byChar).array == `\\server\share`);
+
+        static assert (driveName(`d:\file`).array == "d:");
+    }
+}
+
+
+/** Strips the drive from a Windows path.  On POSIX, the path is returned
+    unaltered.
+
+    Params:
+        path = A pathname
+
+    Returns: A slice of path without the drive component.
+*/
+auto stripDrive(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    version(Windows)
+    {
+        if (hasDrive!(BaseOf!R)(path))      return path[2 .. path.length];
+        else if (isUNC!(BaseOf!R)(path))    return path[uncRootLength!(BaseOf!R)(path) .. path.length];
+    }
+    return path;
+}
+
+///
+unittest
+{
+    version (Windows)
+    {
+        assert (stripDrive(`d:\dir\file`) == `\dir\file`);
+        assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
+    }
+}
+
+auto stripDrive(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return stripDrive!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!stripDrive(`d:\dir\file`));
+}
+
+unittest
+{
+    version(Windows)
+    {
+        assert (stripDrive(`d:\dir\file`) == `\dir\file`);
+        assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
+        static assert (stripDrive(`d:\dir\file`) == `\dir\file`);
+
+        auto r = MockRange!(immutable(char))(`d:\dir\file`);
+        auto s = r.stripDrive();
+        foreach (i, c; `\dir\file`)
+            assert(s[i] == c);
+    }
+    version(Posix)
+    {
+        assert (stripDrive(`d:\dir\file`) == `d:\dir\file`);
+
+        auto r = MockRange!(immutable(char))(`d:\dir\file`);
+        auto s = r.stripDrive();
+        foreach (i, c; `d:\dir\file`)
+            assert(s[i] == c);
+    }
+}
+
+
+/*  Helper function that returns the position of the filename/extension
+    separator dot in path.
+
+    Params:
+        path = file spec as string or indexable range
+    Returns:
+        index of extension separator (the dot), or -1 if not found
+*/
+private ptrdiff_t extSeparatorPos(R)(const R path)
+    if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) ||
+        isNarrowString!R)
+{
+    for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); )
+    {
+        if (path[i] == '.' && i > 0 && !isSeparator(path[i-1]))
+            return i;
+    }
+    return -1;
+}
+
+unittest
+{
+    assert (extSeparatorPos("file") == -1);
+    assert (extSeparatorPos("file.ext"w) == 4);
+    assert (extSeparatorPos("file.ext1.ext2"d) == 9);
+    assert (extSeparatorPos(".foo".dup) == -1);
+    assert (extSeparatorPos(".foo.ext"w.dup) == 4);
+}
+
+unittest
+{
+    assert (extSeparatorPos("dir/file"d.dup) == -1);
+    assert (extSeparatorPos("dir/file.ext") == 8);
+    assert (extSeparatorPos("dir/file.ext1.ext2"w) == 13);
+    assert (extSeparatorPos("dir/.foo"d) == -1);
+    assert (extSeparatorPos("dir/.foo.ext".dup) == 8);
+
+    version(Windows)
+    {
+        assert (extSeparatorPos("dir\\file") == -1);
+        assert (extSeparatorPos("dir\\file.ext") == 8);
+        assert (extSeparatorPos("dir\\file.ext1.ext2") == 13);
+        assert (extSeparatorPos("dir\\.foo") == -1);
+        assert (extSeparatorPos("dir\\.foo.ext") == 8);
+
+        assert (extSeparatorPos("d:file") == -1);
+        assert (extSeparatorPos("d:file.ext") == 6);
+        assert (extSeparatorPos("d:file.ext1.ext2") == 11);
+        assert (extSeparatorPos("d:.foo") == -1);
+        assert (extSeparatorPos("d:.foo.ext") == 6);
+    }
+
+    static assert (extSeparatorPos("file") == -1);
+    static assert (extSeparatorPos("file.ext"w) == 4);
+}
+
+
+/**
+    Params: path = A path name.
+    Returns: The _extension part of a file name, including the dot.
+
+    If there is no _extension, $(D null) is returned.
+*/
+auto extension(R)(R path)
+    if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
+        is(StringTypeOf!R))
+{
+    auto i = extSeparatorPos!(BaseOf!R)(path);
+    if (i == -1)
+    {
+        static if (is(StringTypeOf!R))
+            return StringTypeOf!R.init[];   // which is null
+        else
+            return path[0 .. 0];
+    }
+    else return path[i .. path.length];
+}
+
+///
+unittest
+{
+    assert (extension("file").empty);
+    assert (extension("file.") == ".");
+    assert (extension("file.ext"w) == ".ext");
+    assert (extension("file.ext1.ext2"d) == ".ext2");
+    assert (extension(".foo".dup).empty);
+    assert (extension(".foo.ext"w.dup) == ".ext");
+
+    static assert (extension("file").empty);
+    static assert (extension("file.ext") == ".ext");
+}
+
+unittest
+{
+    {
+        auto r = MockRange!(immutable(char))(`file.ext1.ext2`);
+        auto s = r.extension();
+        foreach (i, c; `.ext2`)
+            assert(s[i] == c);
+    }
+
+    static struct DirEntry { string s; alias s this; }
+    assert (extension(DirEntry("file")).empty);
+}
+
+
+/** Remove extension from path.
+
+    Params:
+        path = string or range to be sliced
+
+    Returns:
+        slice of path with the extension (if any) stripped off
+*/
+auto stripExtension(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    auto i = extSeparatorPos(path);
+    return (i == -1) ? path : path[0 .. i];
+}
+
+///
+unittest
+{
+    assert (stripExtension("file")           == "file");
+    assert (stripExtension("file.ext")       == "file");
+    assert (stripExtension("file.ext1.ext2") == "file.ext1");
+    assert (stripExtension("file.")          == "file");
+    assert (stripExtension(".file")          == ".file");
+    assert (stripExtension(".file.ext")      == ".file");
+    assert (stripExtension("dir/file.ext")   == "dir/file");
+}
+
+auto stripExtension(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return stripExtension!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert (testAliasedString!stripExtension("file"));
+}
+
+unittest
+{
+    assert (stripExtension("file.ext"w) == "file");
+    assert (stripExtension("file.ext1.ext2"d) == "file.ext1");
+
+    import std.array;
+    import std.utf : byChar, byWchar, byDchar;
+
+    assert (stripExtension("file".byChar).array == "file");
+    assert (stripExtension("file.ext"w.byWchar).array == "file");
+    assert (stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1");
+}
+
+
+/** Params:
+        path = A path name
+        ext = The new extension
+
+    Returns: A string containing the _path given by $(D path), but where
+    the extension has been set to $(D ext).
+
+    If the filename already has an extension, it is replaced.   If not, the
+    extension is simply appended to the filename.  Including a leading dot
+    in $(D ext) is optional.
+
+    If the extension is empty, this function is equivalent to
+    $(LREF stripExtension).
+
+    This function normally allocates a new string (the possible exception
+    being the case when path is immutable and doesn't already have an
+    extension).
+
+    See_Also:
+        $(LREF withExtension) which does not allocate and returns a lazy range.
+*/
+immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
+    if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
+{
+    try
+    {
+        import std.conv : to;
+        return withExtension(path, ext).to!(typeof(return));
+    }
+    catch (Exception e)
+    {
+        assert(0);
+    }
+}
+
+///ditto
+immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
+    if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
+{
+    if (ext.length == 0)
+        return stripExtension(path);
+
+    try
+    {
+        import std.conv : to;
+        return withExtension(path, ext).to!(typeof(return));
+    }
+    catch (Exception e)
+    {
+        assert(0);
+    }
+}
+
+///
+unittest
+{
+    assert (setExtension("file", "ext") == "file.ext");
+    assert (setExtension("file"w, ".ext"w) == "file.ext");
+    assert (setExtension("file."d, "ext"d) == "file.ext");
+    assert (setExtension("file.", ".ext") == "file.ext");
+    assert (setExtension("file.old"w, "new"w) == "file.new");
+    assert (setExtension("file.old"d, ".new"d) == "file.new");
+}
+
+unittest
+{
+    assert (setExtension("file"w.dup, "ext"w) == "file.ext");
+    assert (setExtension("file"w.dup, ".ext"w) == "file.ext");
+    assert (setExtension("file."w, "ext"w.dup) == "file.ext");
+    assert (setExtension("file."w, ".ext"w.dup) == "file.ext");
+    assert (setExtension("file.old"d.dup, "new"d) == "file.new");
+    assert (setExtension("file.old"d.dup, ".new"d) == "file.new");
+
+    static assert (setExtension("file", "ext") == "file.ext");
+    static assert (setExtension("file.old", "new") == "file.new");
+
+    static assert (setExtension("file"w.dup, "ext"w) == "file.ext");
+    static assert (setExtension("file.old"d.dup, "new"d) == "file.new");
+
+    // Issue 10601
+    assert (setExtension("file", "") == "file");
+    assert (setExtension("file.ext", "") == "file");
+}
+
+/************
+ * Replace existing extension on filespec with new one.
+ *
+ * Params:
+ *      path = string or random access range representing a filespec
+ *      ext = the new extension
+ * Returns:
+ *      Range with $(D path)'s extension (if any) replaced with $(D ext).
+ *      The element encoding type of the returned range will be the same as $(D path)'s.
+ * See_Also:
+ *      $(LREF setExtension)
+ */
+auto withExtension(R, C)(R path, C[] ext)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R &&
+        isSomeChar!C)
+{
+    import std.range : only, chain;
+    import std.utf : byUTF;
+
+    alias CR = Unqual!(ElementEncodingType!R);
+    auto dot = only(CR('.'));
+    if (ext.length == 0 || ext[0] == '.')
+        dot.popFront();                 // so dot is an empty range, too
+    return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR);
+}
+
+///
+unittest
+{
+    import std.array;
+    assert (withExtension("file", "ext").array == "file.ext");
+    assert (withExtension("file"w, ".ext"w).array == "file.ext");
+    assert (withExtension("file.ext"w, ".").array == "file.");
+
+    import std.utf : byChar, byWchar;
+    assert (withExtension("file".byChar, "ext").array == "file.ext");
+    assert (withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w);
+    assert (withExtension("file.ext"w.byWchar, ".").array == "file."w);
+}
+
+auto withExtension(R, C)(auto ref R path, C[] ext)
+    if (isConvertibleToString!R)
+{
+    return withExtension!(StringTypeOf!R)(path, ext);
+}
+
+unittest
+{
+    assert (testAliasedString!withExtension("file", "ext"));
+}
+
+/** Params:
+        path = A path name.
+        ext = The default extension to use.
+
+    Returns: The _path given by $(D path), with the extension given by $(D ext)
+    appended if the path doesn't already have one.
+
+    Including the dot in the extension is optional.
+
+    This function always allocates a new string, except in the case when
+    path is immutable and already has an extension.
+*/
+immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
+    if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
+{
+    import std.conv : to;
+    return withDefaultExtension(path, ext).to!(typeof(return));
+}
+
+///
+unittest
+{
+    assert (defaultExtension("file", "ext") == "file.ext");
+    assert (defaultExtension("file", ".ext") == "file.ext");
+    assert (defaultExtension("file.", "ext")     == "file.");
+    assert (defaultExtension("file.old", "new") == "file.old");
+    assert (defaultExtension("file.old", ".new") == "file.old");
+}
+
+unittest
+{
+    assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
+    assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
+
+    static assert (defaultExtension("file", "ext") == "file.ext");
+    static assert (defaultExtension("file.old", "new") == "file.old");
+
+    static assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
+    static assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
+}
+
+
+/********************************
+ * Set the extension of $(D path) to $(D ext) if $(D path) doesn't have one.
+ *
+ * Params:
+ *      path = filespec as string or range
+ *      ext = extension, may have leading '.'
+ * Returns:
+ *      range with the result
+ */
+auto withDefaultExtension(R, C)(R path, C[] ext)
+    if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R &&
+        isSomeChar!C)
+{
+    import std.range : only, chain;
+    import std.utf : byUTF;
+
+    alias CR = Unqual!(ElementEncodingType!R);
+    auto dot = only(CR('.'));
+    auto i = extSeparatorPos(path);
+    if (i == -1)
+    {
+        if (ext.length > 0 && ext[0] == '.')
+            ext = ext[1 .. $];              // remove any leading . from ext[]
+    }
+    else
+    {
+        // path already has an extension, so make these empty
+        ext = ext[0 .. 0];
+        dot.popFront();
+    }
+    return chain(path.byUTF!CR, dot, ext.byUTF!CR);
+}
+
+///
+unittest
+{
+    import std.array;
+    assert (withDefaultExtension("file", "ext").array == "file.ext");
+    assert (withDefaultExtension("file"w, ".ext").array == "file.ext"w);
+    assert (withDefaultExtension("file.", "ext").array == "file.");
+    assert (withDefaultExtension("file", "").array == "file.");
+
+    import std.utf : byChar, byWchar;
+    assert (withDefaultExtension("file".byChar, "ext").array == "file.ext");
+    assert (withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w);
+    assert (withDefaultExtension("file.".byChar, "ext"d).array == "file.");
+    assert (withDefaultExtension("file".byChar, "").array == "file.");
+}
+
+auto withDefaultExtension(R, C)(auto ref R path, C[] ext)
+    if (isConvertibleToString!R)
+{
+    return withDefaultExtension!(StringTypeOf!R, C)(path, ext);
+}
+
+unittest
+{
+    assert (testAliasedString!withDefaultExtension("file", "ext"));
+}
+
+/** Combines one or more path segments.
+
+    This function takes a set of path segments, given as an input
+    range of string elements or as a set of string arguments,
+    and concatenates them with each other.  Directory separators
+    are inserted between segments if necessary.  If any of the
+    path segments are absolute (as defined by $(LREF isAbsolute)), the
+    preceding segments will be dropped.
+
+    On Windows, if one of the path segments are rooted, but not absolute
+    (e.g. $(D `\foo`)), all preceding path segments down to the previous
+    root will be dropped.  (See below for an example.)
+
+    This function always allocates memory to hold the resulting path.
+    The variadic overload is guaranteed to only perform a single
+    allocation, as is the range version if $(D paths) is a forward
+    range.
+
+    Params:
+        segments = An input range of segments to assemble the path from.
+    Returns: The assembled path.
+*/
+immutable(ElementEncodingType!(ElementType!Range))[]
+    buildPath(Range)(Range segments)
+        if (isInputRange!Range && isSomeString!(ElementType!Range))
+{
+    if (segments.empty) return null;
+
+    // If this is a forward range, we can pre-calculate a maximum length.
+    static if (isForwardRange!Range)
+    {
+        auto segments2 = segments.save;
+        size_t precalc = 0;
+        foreach (segment; segments2) precalc += segment.length + 1;
+    }
+    // Otherwise, just venture a guess and resize later if necessary.
+    else size_t precalc = 255;
+
+    auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
+    size_t pos = 0;
+    foreach (segment; segments)
+    {
+        if (segment.empty) continue;
+        static if (!isForwardRange!Range)
+        {
+            immutable neededLength = pos + segment.length + 1;
+            if (buf.length < neededLength)
+                buf.length = reserve(buf, neededLength + buf.length/2);
+        }
+        auto r = chainPath(buf[0 .. pos], segment);
+        size_t i;
+        foreach (c; r)
+        {
+            buf[i] = c;
+            ++i;
+        }
+        pos = i;
+    }
+    static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
+    return trustedCast!(typeof(return))(buf[0 .. pos]);
+}
+
+/// ditto
+immutable(C)[] buildPath(C)(const(C)[][] paths...)
+    @safe pure nothrow
+    if (isSomeChar!C)
+{
+    return buildPath!(typeof(paths))(paths);
+}
+
+///
+unittest
+{
+    version (Posix)
+    {
+        assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
+        assert (buildPath("/foo/", "bar/baz")  == "/foo/bar/baz");
+        assert (buildPath("/foo", "/bar")      == "/bar");
+    }
+
+    version (Windows)
+    {
+        assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
+        assert (buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
+        assert (buildPath("foo", `d:\bar`)     == `d:\bar`);
+        assert (buildPath("foo", `\bar`)       == `\bar`);
+        assert (buildPath(`c:\foo`, `\bar`)    == `c:\bar`);
+    }
+}
+
+unittest // non-documented
+{
+    import std.range;
+    // ir() wraps an array in a plain (i.e. non-forward) input range, so that
+    // we can test both code paths
+    InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
+    version (Posix)
+    {
+        assert (buildPath("foo") == "foo");
+        assert (buildPath("/foo/") == "/foo/");
+        assert (buildPath("foo", "bar") == "foo/bar");
+        assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
+        assert (buildPath("foo/".dup, "bar") == "foo/bar");
+        assert (buildPath("foo///", "bar".dup) == "foo///bar");
+        assert (buildPath("/foo"w, "bar"w) == "/foo/bar");
+        assert (buildPath("foo"w.dup, "/bar"w) == "/bar");
+        assert (buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
+        assert (buildPath("/"d, "foo"d) == "/foo");
+        assert (buildPath(""d.dup, "foo"d) == "foo");
+        assert (buildPath("foo"d, ""d.dup) == "foo");
+        assert (buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
+        assert (buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
+
+        static assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
+        static assert (buildPath("foo", "/bar", "baz") == "/bar/baz");
+
+        // The following are mostly duplicates of the above, except that the
+        // range version does not accept mixed constness.
+        assert (buildPath(ir("foo")) == "foo");
+        assert (buildPath(ir("/foo/")) == "/foo/");
+        assert (buildPath(ir("foo", "bar")) == "foo/bar");
+        assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
+        assert (buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
+        assert (buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
+        assert (buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
+        assert (buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
+        assert (buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
+        assert (buildPath(ir("/"d, "foo"d)) == "/foo");
+        assert (buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
+        assert (buildPath(ir("foo"d, ""d)) == "foo");
+        assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
+        assert (buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
+    }
+    version (Windows)
+    {
+        assert (buildPath("foo") == "foo");
+        assert (buildPath(`\foo/`) == `\foo/`);
+        assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
+        assert (buildPath("foo", `\bar`) == `\bar`);
+        assert (buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
+        assert (buildPath("foo"w, `d:\bar`w.dup) ==  `d:\bar`);
+        assert (buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
+        assert (buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
+
+        static assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
+        static assert (buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
+
+        assert (buildPath(ir("foo")) == "foo");
+        assert (buildPath(ir(`\foo/`)) == `\foo/`);
+        assert (buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
+        assert (buildPath(ir("foo", `\bar`)) == `\bar`);
+        assert (buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
+        assert (buildPath(ir("foo"w.dup, `d:\bar`w.dup)) ==  `d:\bar`);
+        assert (buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
+        assert (buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
+    }
+
+    // Test that allocation works as it should.
+    auto manyShort = "aaa".repeat(1000).array();
+    auto manyShortCombined = join(manyShort, dirSeparator);
+    assert (buildPath(manyShort) == manyShortCombined);
+    assert (buildPath(ir(manyShort)) == manyShortCombined);
+
+    auto fewLong = 'b'.repeat(500).array().repeat(10).array();
+    auto fewLongCombined = join(fewLong, dirSeparator);
+    assert (buildPath(fewLong) == fewLongCombined);
+    assert (buildPath(ir(fewLong)) == fewLongCombined);
+}
+
+unittest
+{
+    // Test for issue 7397
+    string[] ary = ["a", "b"];
+    version (Posix)
+    {
+        assert (buildPath(ary) == "a/b");
+    }
+    else version (Windows)
+    {
+        assert (buildPath(ary) == `a\b`);
+    }
+}
+
+
+/**
+ * Concatenate path segments together to form one path.
+ *
+ * Params:
+ *      r1 = first segment
+ *      r2 = second segment
+ *      ranges = 0 or more segments
+ * Returns:
+ *      Lazy range which is the concatenation of r1, r2 and ranges with path separators.
+ *      The resulting element type is that of r1.
+ * See_Also:
+ *      $(LREF buildPath)
+ */
+auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges)
+    if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) ||
+         isNarrowString!R1 &&
+        !isConvertibleToString!R1) &&
+        (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) ||
+         isNarrowString!R2 &&
+        !isConvertibleToString!R2) &&
+        (Ranges.length == 0 || is(typeof(chainPath(r2, ranges))))
+       )
+{
+    static if (Ranges.length)
+    {
+        return chainPath(chainPath(r1, r2), ranges);
+    }
+    else
+    {
+        import std.range : only, chain;
+        import std.utf : byUTF;
+
+        alias CR = Unqual!(ElementEncodingType!R1);
+        auto sep = only(CR(dirSeparator[0]));
+        bool usesep = false;
+
+        auto pos = r1.length;
+
+        if (pos)
+        {
+            if (isRooted(r2))
+            {
+                version (Posix)
+                {
+                    pos = 0;
+                }
+                else version (Windows)
+                {
+                    if (isAbsolute(r2))
+                        pos = 0;
+                    else
+                    {
+                        pos = rootName(r1).length;
+                        if (pos > 0 && isDirSeparator(r1[pos - 1]))
+                            --pos;
+                    }
+                }
+                else
+                    static assert(0);
+            }
+            else if (!isDirSeparator(r1[pos - 1]))
+                usesep = true;
+        }
+        if (!usesep)
+            sep.popFront();
+        // Return r1 ~ '/' ~ r2
+        return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR);
+    }
+}
+
+///
+unittest
+{
+    import std.array;
+    version (Posix)
+    {
+        assert (chainPath("foo", "bar", "baz").array == "foo/bar/baz");
+        assert (chainPath("/foo/", "bar/baz").array  == "/foo/bar/baz");
+        assert (chainPath("/foo", "/bar").array      == "/bar");
+    }
+
+    version (Windows)
+    {
+        assert (chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
+        assert (chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`);
+        assert (chainPath("foo", `d:\bar`).array     == `d:\bar`);
+        assert (chainPath("foo", `\bar`).array       == `\bar`);
+        assert (chainPath(`c:\foo`, `\bar`).array    == `c:\bar`);
+    }
+
+    import std.utf : byChar;
+    version (Posix)
+    {
+        assert (chainPath("foo", "bar", "baz").array == "foo/bar/baz");
+        assert (chainPath("/foo/".byChar, "bar/baz").array  == "/foo/bar/baz");
+        assert (chainPath("/foo", "/bar".byChar).array      == "/bar");
+    }
+
+    version (Windows)
+    {
+        assert (chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
+        assert (chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`);
+        assert (chainPath("foo", `d:\bar`).array     == `d:\bar`);
+        assert (chainPath("foo", `\bar`.byChar).array       == `\bar`);
+        assert (chainPath(`c:\foo`, `\bar`w).array    == `c:\bar`);
+    }
+}
+
+auto chainPath(Ranges...)(auto ref Ranges ranges)
+    if (Ranges.length >= 2 &&
+        std.meta.anySatisfy!(isConvertibleToString, Ranges))
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, Ranges);
+    return chainPath!Types(ranges);
+}
+
+unittest
+{
+    assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty);
+    assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty);
+    assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty);
+    static struct S { string s; }
+    static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
+}
+
+/** Performs the same task as $(LREF buildPath),
+    while at the same time resolving current/parent directory
+    symbols ($(D ".") and $(D "..")) and removing superfluous
+    directory separators.
+    It will return "." if the path leads to the starting directory.
+    On Windows, slashes are replaced with backslashes.
+
+    Using buildNormalizedPath on null paths will always return null.
+
+    Note that this function does not resolve symbolic links.
+
+    This function always allocates memory to hold the resulting path.
+    Use $(LREF asNormalizedPath) to not allocate memory.
+
+    Params:
+        paths = An array of paths to assemble.
+
+    Returns: The assembled path.
+*/
+immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
+    @trusted pure nothrow
+    if (isSomeChar!C)
+{
+    import std.array;
+
+    const(C)[] result;
+    foreach (path; paths)
+    {
+        if (result)
+            result = chainPath(result, path).array;
+        else
+            result = path;
+    }
+    result = asNormalizedPath(result).array;
+    return cast(typeof(return)) result;
+}
+
+///
+unittest
+{
+    assert (buildNormalizedPath("foo", "..") == ".");
+
+    version (Posix)
+    {
+        assert (buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
+        assert (buildNormalizedPath("../foo/.") == "../foo");
+        assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
+        assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
+        assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
+        assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
+    }
+
+    version (Windows)
+    {
+        assert (buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
+        assert (buildNormalizedPath(`..\foo\.`) == `..\foo`);
+        assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
+        assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
+                `\\server\share\bar`);
+    }
+}
+
+unittest
+{
+    assert (buildNormalizedPath(".", ".") == ".");
+    assert (buildNormalizedPath("foo", "..") == ".");
+    assert (buildNormalizedPath("", "") is null);
+    assert (buildNormalizedPath("", ".") == ".");
+    assert (buildNormalizedPath(".", "") == ".");
+    assert (buildNormalizedPath(null, "foo") == "foo");
+    assert (buildNormalizedPath("", "foo") == "foo");
+    assert (buildNormalizedPath("", "") == "");
+    assert (buildNormalizedPath("", null) == "");
+    assert (buildNormalizedPath(null, "") == "");
+    assert (buildNormalizedPath!(char)(null, null) == "");
+
+    version (Posix)
+    {
+        assert (buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
+        assert (buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
+        assert (buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
+        assert (buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
+        assert (buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
+        assert (buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
+        assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
+        assert (buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
+        assert (buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
+        assert (buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
+        assert (buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
+        assert (buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
+        assert (buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
+        static assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
+    }
+    else version (Windows)
+    {
+        assert (buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
+        assert (buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
+        assert (buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
+        assert (buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
+        assert (buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
+        assert (buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
+        assert (buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
+        assert (buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
+        assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
+        assert (buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
+        assert (buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
+        assert (buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
+
+        assert (buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
+        assert (buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
+        assert (buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
+        assert (buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
+        assert (buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
+        assert (buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
+        assert (buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
+
+        assert (buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
+        assert (buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
+        assert (buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
+        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
+
+        static assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
+    }
+    else static assert (0);
+}
+
+unittest
+{
+    // Test for issue 7397
+    string[] ary = ["a", "b"];
+    version (Posix)
+    {
+        assert (buildNormalizedPath(ary) == "a/b");
+    }
+    else version (Windows)
+    {
+        assert (buildNormalizedPath(ary) == `a\b`);
+    }
+}
+
+
+/** Normalize a path by resolving current/parent directory
+    symbols ($(D ".") and $(D "..")) and removing superfluous
+    directory separators.
+    It will return "." if the path leads to the starting directory.
+    On Windows, slashes are replaced with backslashes.
+
+    Using asNormalizedPath on empty paths will always return an empty path.
+
+    Does not resolve symbolic links.
+
+    This function always allocates memory to hold the resulting path.
+    Use $(LREF buildNormalizedPath) to allocate memory and return a string.
+
+    Params:
+        path = string or random access range representing the _path to normalize
+
+    Returns:
+        normalized path as a forward range
+*/
+
+auto asNormalizedPath(R)(R path)
+    if (isSomeChar!(ElementEncodingType!R) &&
+        (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    alias C = Unqual!(ElementEncodingType!R);
+    alias S = typeof(path[0..0]);
+
+    static struct Result
+    {
+        @property bool empty()
+        {
+            return c == c.init;
+        }
+
+        @property C front()
+        {
+            return c;
+        }
+
+        void popFront()
+        {
+            C lastc = c;
+            c = c.init;
+            if (!element.empty)
+            {
+                c = getElement0();
+                return;
+            }
+          L1:
+            while (1)
+            {
+                if (elements.empty)
+                {
+                    element = element[0..0];
+                    return;
+                }
+                element = elements.front;
+                elements.popFront();
+                if (isDot(element) || (rooted && isDotDot(element)))
+                    continue;
+
+                if (rooted || !isDotDot(element))
+                {
+                    int n = 1;
+                    auto elements2 = elements.save;
+                    while (!elements2.empty)
+                    {
+                        auto e = elements2.front;
+                        elements2.popFront();
+                        if (isDot(e))
+                            continue;
+                        if (isDotDot(e))
+                        {
+                            --n;
+                            if (n == 0)
+                            {
+                                elements = elements2;
+                                element = element[0..0];
+                                continue L1;
+                            }
+                        }
+                        else
+                            ++n;
+                    }
+                }
+                break;
+            }
+
+            static assert(dirSeparator.length == 1);
+            if (lastc == dirSeparator[0] || lastc == lastc.init)
+                c = getElement0();
+            else
+                c = dirSeparator[0];
+        }
+
+        static if (isForwardRange!R)
+        {
+            @property auto save()
+            {
+                auto result = this;
+                result.element = element.save;
+                result.elements = elements.save;
+                return result;
+            }
+        }
+
+      private:
+        this(R path)
+        {
+            element = rootName(path);
+            auto i = element.length;
+            while (i < path.length && isDirSeparator(path[i]))
+                ++i;
+            rooted = i > 0;
+            elements = pathSplitter(path[i .. $]);
+            popFront();
+            if (c == c.init && path.length)
+                c = C('.');
+        }
+
+        C getElement0()
+        {
+            static if (isNarrowString!S)  // avoid autodecode
+            {
+                C c = element[0];
+                element = element[1 .. $];
+            }
+            else
+            {
+                C c = element.front;
+                element.popFront();
+            }
+            version (Windows)
+            {
+                if (c == '/')   // can appear in root element
+                    c = '\\';   // use native Windows directory separator
+            }
+            return c;
+        }
+
+        // See if elem is "."
+        static bool isDot(S elem)
+        {
+            return elem.length == 1 && elem[0] == '.';
+        }
+
+        // See if elem is ".."
+        static bool isDotDot(S elem)
+        {
+            return elem.length == 2 && elem[0] == '.' && elem[1] == '.';
+        }
+
+        bool rooted;    // the path starts with a root directory
+        C c;
+        S element;
+        typeof(pathSplitter(path[0..0])) elements;
+    }
+
+    return Result(path);
+}
+
+///
+unittest
+{
+    import std.array;
+    assert (asNormalizedPath("foo/..").array == ".");
+
+    version (Posix)
+    {
+        assert (asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz");
+        assert (asNormalizedPath("../foo/.").array == "../foo");
+        assert (asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz");
+        assert (asNormalizedPath("/foo/./bar/../../baz").array == "/baz");
+    }
+
+    version (Windows)
+    {
+        assert (asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`);
+        assert (asNormalizedPath(`..\foo\.`).array == `..\foo`);
+        assert (asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`);
+        assert (asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`);
+        assert (asNormalizedPath(`\\server\share\foo\..\bar`).array ==
+                `\\server\share\bar`);
+    }
+}
+
+auto asNormalizedPath(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return asNormalizedPath!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!asNormalizedPath(null));
+}
+
+unittest
+{
+    import std.array;
+    import std.utf : byChar;
+
+    assert (asNormalizedPath("").array is null);
+    assert (asNormalizedPath("foo").array == "foo");
+    assert (asNormalizedPath(".").array == ".");
+    assert (asNormalizedPath("./.").array == ".");
+    assert (asNormalizedPath("foo/..").array == ".");
+
+    auto save = asNormalizedPath("fob").save;
+    save.popFront();
+    assert(save.front == 'o');
+
+    version (Posix)
+    {
+        assert (asNormalizedPath("/foo/bar").array == "/foo/bar");
+        assert (asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
+        assert (asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
+        assert (asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz");
+        assert (asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz");
+        assert (asNormalizedPath("/foo/../bar/baz").array == "/bar/baz");
+        assert (asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz");
+        assert (asNormalizedPath("/foo/bar/../baz").array == "/foo/baz");
+        assert (asNormalizedPath("/foo/bar/../../baz").array == "/baz");
+        assert (asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee");
+        assert (asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee");
+
+        assert (asNormalizedPath("foo//bar").array == "foo/bar");
+        assert (asNormalizedPath("foo/bar").array == "foo/bar");
+
+        //Curent dir path
+        assert (asNormalizedPath("./").array == ".");
+        assert (asNormalizedPath("././").array == ".");
+        assert (asNormalizedPath("./foo/..").array == ".");
+        assert (asNormalizedPath("foo/..").array == ".");
+    }
+    else version (Windows)
+    {
+        assert (asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
+        assert (asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
+        assert (asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
+        assert (asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`);
+        assert (asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`);
+        assert (asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`);
+        assert (asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`);
+        assert (asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
+
+        assert (asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`);
+        assert (asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`);
+        assert (asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`);
+
+        assert (asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
+        assert (asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
+        assert (asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
+        assert (asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`);
+        assert (asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`);
+
+        assert (asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`);
+        assert (asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`);
+        assert (asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`);
+        assert (asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`);
+        assert (asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`);
+        assert (asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`);
+        assert (asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`);
+        assert (asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`);
+        assert (asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`);
+        assert (asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`);
+
+        static assert (asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
+
+        assert (asNormalizedPath("foo//bar").array == `foo\bar`);
+
+        //Curent dir path
+        assert (asNormalizedPath(`.\`).array == ".");
+        assert (asNormalizedPath(`.\.\`).array == ".");
+        assert (asNormalizedPath(`.\foo\..`).array == ".");
+        assert (asNormalizedPath(`foo\..`).array == ".");
+    }
+    else static assert (0);
+}
+
+unittest
+{
+    import std.array;
+
+    version (Posix)
+    {
+        // Trivial
+        assert (asNormalizedPath("").empty);
+        assert (asNormalizedPath("foo/bar").array == "foo/bar");
+
+        // Correct handling of leading slashes
+        assert (asNormalizedPath("/").array == "/");
+        assert (asNormalizedPath("///").array == "/");
+        assert (asNormalizedPath("////").array == "/");
+        assert (asNormalizedPath("/foo/bar").array == "/foo/bar");
+        assert (asNormalizedPath("//foo/bar").array == "/foo/bar");
+        assert (asNormalizedPath("///foo/bar").array == "/foo/bar");
+        assert (asNormalizedPath("////foo/bar").array == "/foo/bar");
+
+        // Correct handling of single-dot symbol (current directory)
+        assert (asNormalizedPath("/./foo").array == "/foo");
+        assert (asNormalizedPath("/foo/./bar").array == "/foo/bar");
+
+        assert (asNormalizedPath("./foo").array == "foo");
+        assert (asNormalizedPath("././foo").array == "foo");
+        assert (asNormalizedPath("foo/././bar").array == "foo/bar");
+
+        // Correct handling of double-dot symbol (previous directory)
+        assert (asNormalizedPath("/foo/../bar").array == "/bar");
+        assert (asNormalizedPath("/foo/../../bar").array == "/bar");
+        assert (asNormalizedPath("/../foo").array == "/foo");
+        assert (asNormalizedPath("/../../foo").array == "/foo");
+        assert (asNormalizedPath("/foo/..").array == "/");
+        assert (asNormalizedPath("/foo/../..").array == "/");
+
+        assert (asNormalizedPath("foo/../bar").array == "bar");
+        assert (asNormalizedPath("foo/../../bar").array == "../bar");
+        assert (asNormalizedPath("../foo").array == "../foo");
+        assert (asNormalizedPath("../../foo").array == "../../foo");
+        assert (asNormalizedPath("../foo/../bar").array == "../bar");
+        assert (asNormalizedPath(".././../foo").array == "../../foo");
+        assert (asNormalizedPath("foo/bar/..").array == "foo");
+        assert (asNormalizedPath("/foo/../..").array == "/");
+
+        // The ultimate path
+        assert (asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
+        static assert (asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
+    }
+    else version (Windows)
+    {
+        // Trivial
+        assert (asNormalizedPath("").empty);
+        assert (asNormalizedPath(`foo\bar`).array == `foo\bar`);
+        assert (asNormalizedPath("foo/bar").array == `foo\bar`);
+
+        // Correct handling of absolute paths
+        assert (asNormalizedPath("/").array == `\`);
+        assert (asNormalizedPath(`\`).array == `\`);
+        assert (asNormalizedPath(`\\\`).array == `\`);
+        assert (asNormalizedPath(`\\\\`).array == `\`);
+        assert (asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
+        assert (asNormalizedPath(`\\foo`).array == `\\foo`);
+        assert (asNormalizedPath(`\\foo\\`).array == `\\foo`);
+        assert (asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`);
+        assert (asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`);
+        assert (asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`);
+        assert (asNormalizedPath(`c:\`).array == `c:\`);
+        assert (asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
+        assert (asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`);
+
+        // Correct handling of single-dot symbol (current directory)
+        assert (asNormalizedPath(`\./foo`).array == `\foo`);
+        assert (asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`);
+
+        assert (asNormalizedPath(`.\foo`).array == `foo`);
+        assert (asNormalizedPath(`./.\foo`).array == `foo`);
+        assert (asNormalizedPath(`foo\.\./bar`).array == `foo\bar`);
+
+        // Correct handling of double-dot symbol (previous directory)
+        assert (asNormalizedPath(`\foo\..\bar`).array == `\bar`);
+        assert (asNormalizedPath(`\foo\../..\bar`).array == `\bar`);
+        assert (asNormalizedPath(`\..\foo`).array == `\foo`);
+        assert (asNormalizedPath(`\..\..\foo`).array == `\foo`);
+        assert (asNormalizedPath(`\foo\..`).array == `\`);
+        assert (asNormalizedPath(`\foo\../..`).array == `\`);
+
+        assert (asNormalizedPath(`foo\..\bar`).array == `bar`);
+        assert (asNormalizedPath(`foo\..\../bar`).array == `..\bar`);
+
+        assert (asNormalizedPath(`..\foo`).array == `..\foo`);
+        assert (asNormalizedPath(`..\..\foo`).array == `..\..\foo`);
+        assert (asNormalizedPath(`..\foo\..\bar`).array == `..\bar`);
+        assert (asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`);
+        assert (asNormalizedPath(`foo\bar\..`).array == `foo`);
+        assert (asNormalizedPath(`\foo\..\..`).array == `\`);
+        assert (asNormalizedPath(`c:\foo\..\..`).array == `c:\`);
+
+        // Correct handling of non-root path with drive specifier
+        assert (asNormalizedPath(`c:foo`).array == `c:foo`);
+        assert (asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`);
+
+        // The ultimate path
+        assert (asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
+        static assert (asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
+    }
+    else static assert (false);
+}
+
+/** Slice up a path into its elements.
+
+    Params:
+        path = string or slicable random access range
+
+    Returns:
+        bidirectional range of slices of `path`
+*/
+auto pathSplitter(R)(R path)
+    if ((isRandomAccessRange!R && hasSlicing!R ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    static struct PathSplitter
+    {
+        @property bool empty() const { return pe == 0; }
+
+        @property R front()
+        {
+            assert(!empty);
+            return _path[fs .. fe];
+        }
+
+        void popFront()
+        {
+            assert(!empty);
+            if (ps == pe)
+            {
+                if (fs == bs && fe == be)
+                {
+                    pe = 0;
+                }
+                else
+                {
+                    fs = bs;
+                    fe = be;
+                }
+            }
+            else
+            {
+                fs = ps;
+                fe = fs;
+                while (fe < pe && !isDirSeparator(_path[fe]))
+                    ++fe;
+                ps = ltrim(fe, pe);
+            }
+        }
+
+        @property R back()
+        {
+            assert(!empty);
+            return _path[bs .. be];
+        }
+
+        void popBack()
+        {
+            assert(!empty);
+            if (ps == pe)
+            {
+                if (fs == bs && fe == be)
+                {
+                    pe = 0;
+                }
+                else
+                {
+                    bs = fs;
+                    be = fe;
+                }
+            }
+            else
+            {
+                bs = pe;
+                be = bs;
+                while (bs > ps && !isDirSeparator(_path[bs - 1]))
+                    --bs;
+                pe = rtrim(ps, bs);
+            }
+        }
+        @property auto save() { return this; }
+
+
+    private:
+        R _path;
+        size_t ps, pe;
+        size_t fs, fe;
+        size_t bs, be;
+
+        this(R p)
+        {
+            if (p.empty)
+            {
+                pe = 0;
+                return;
+            }
+            _path = p;
+
+            ps = 0;
+            pe = _path.length;
+
+            // If path is rooted, first element is special
+            version (Windows)
+            {
+                if (isUNC(_path))
+                {
+                    auto i = uncRootLength(_path);
+                    fs = 0;
+                    fe = i;
+                    ps = ltrim(fe, pe);
+                }
+                else if (isDriveRoot(_path))
+                {
+                    fs = 0;
+                    fe = 3;
+                    ps = ltrim(fe, pe);
+                }
+                else if (_path.length >= 1 && isDirSeparator(_path[0]))
+                {
+                    fs = 0;
+                    fe = 1;
+                    ps = ltrim(fe, pe);
+                }
+                else
+                {
+                    assert (!isRooted(_path));
+                    popFront();
+                }
+            }
+            else version (Posix)
+            {
+                if (_path.length >= 1 && isDirSeparator(_path[0]))
+                {
+                    fs = 0;
+                    fe = 1;
+                    ps = ltrim(fe, pe);
+                }
+                else
+                {
+                    popFront();
+                }
+            }
+            else static assert (0);
+
+            if (ps == pe)
+            {
+                bs = fs;
+                be = fe;
+            }
+            else
+            {
+                pe = rtrim(ps, pe);
+                popBack();
+            }
+        }
+
+        size_t ltrim(size_t s, size_t e)
+        {
+            while (s < e && isDirSeparator(_path[s]))
+                ++s;
+            return s;
+        }
+
+        size_t rtrim(size_t s, size_t e)
+        {
+            while (s < e && isDirSeparator(_path[e - 1]))
+                --e;
+            return e;
+        }
+    }
+
+    return PathSplitter(path);
+}
+
+///
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.conv : to;
+
+    assert (equal(pathSplitter("/"), ["/"]));
+    assert (equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
+    assert (equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
+
+    version (Posix)
+    {
+        assert (equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
+    }
+
+    version (Windows)
+    {
+        assert (equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
+        assert (equal(pathSplitter("c:"), ["c:"]));
+        assert (equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
+        assert (equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
+    }
+}
+
+auto pathSplitter(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return pathSplitter!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    assert (testAliasedString!pathSplitter("/"));
+}
+
+unittest
+{
+    // equal2 verifies that the range is the same both ways, i.e.
+    // through front/popFront and back/popBack.
+    import std.range;
+    import std.algorithm;
+    bool equal2(R1, R2)(R1 r1, R2 r2)
+    {
+        static assert (isBidirectionalRange!R1);
+        return equal(r1, r2) && equal(retro(r1), retro(r2));
+    }
+
+    assert (pathSplitter("").empty);
+
+    // Root directories
+    assert (equal2(pathSplitter("/"), ["/"]));
+    assert (equal2(pathSplitter("//"), ["/"]));
+    assert (equal2(pathSplitter("///"w), ["/"w]));
+
+    // Absolute paths
+    assert (equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
+
+    // General
+    assert (equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d]));
+    assert (equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
+    assert (equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w]));
+    assert (equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d]));
+
+    // save()
+    auto ps1 = pathSplitter("foo/bar/baz");
+    auto ps2 = ps1.save;
+    ps1.popFront();
+    assert (equal2(ps1, ["bar", "baz"]));
+    assert (equal2(ps2, ["foo", "bar", "baz"]));
+
+    // Platform specific
+    version (Posix)
+    {
+        assert (equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w]));
+    }
+    version (Windows)
+    {
+        assert (equal2(pathSplitter(`\`), [`\`]));
+        assert (equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
+        assert (equal2(pathSplitter("c:"), ["c:"]));
+        assert (equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
+        assert (equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
+        assert (equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
+        assert (equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
+        assert (equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
+    }
+
+    import std.exception;
+    assertCTFEable!(
+    {
+        assert (equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
+    });
+
+    static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[]));
+
+    import std.utf : byDchar;
+    assert (equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d]));
+}
+
+
+
+
+/** Determines whether a path starts at a root directory.
+
+    Params: path = A path name.
+    Returns: Whether a path starts at a root directory.
+
+    On POSIX, this function returns true if and only if the path starts
+    with a slash (/).
+    ---
+    version (Posix)
+    {
+        assert (isRooted("/"));
+        assert (isRooted("/foo"));
+        assert (!isRooted("foo"));
+        assert (!isRooted("../foo"));
+    }
+    ---
+
+    On Windows, this function returns true if the path starts at
+    the root directory of the current drive, of some other drive,
+    or of a network drive.
+    ---
+    version (Windows)
+    {
+        assert (isRooted(`\`));
+        assert (isRooted(`\foo`));
+        assert (isRooted(`d:\foo`));
+        assert (isRooted(`\\foo\bar`));
+        assert (!isRooted("foo"));
+        assert (!isRooted("d:foo"));
+    }
+    ---
+*/
+bool isRooted(R)(R path)
+    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+        is(StringTypeOf!R))
+{
+    if (path.length >= 1 && isDirSeparator(path[0])) return true;
+    version (Posix)         return false;
+    else version (Windows)  return isAbsolute!(BaseOf!R)(path);
+}
+
+
+unittest
+{
+    assert (isRooted("/"));
+    assert (isRooted("/foo"));
+    assert (!isRooted("foo"));
+    assert (!isRooted("../foo"));
+
+    version (Windows)
+    {
+    assert (isRooted(`\`));
+    assert (isRooted(`\foo`));
+    assert (isRooted(`d:\foo`));
+    assert (isRooted(`\\foo\bar`));
+    assert (!isRooted("foo"));
+    assert (!isRooted("d:foo"));
+    }
+
+    static assert (isRooted("/foo"));
+    static assert (!isRooted("foo"));
+
+    static struct DirEntry { string s; alias s this; }
+    assert (!isRooted(DirEntry("foo")));
+}
+
+
+
+
+/** Determines whether a path is absolute or not.
+
+    Params: path = A path name.
+
+    Returns: Whether a path is absolute or not.
+
+    Example:
+    On POSIX, an absolute path starts at the root directory.
+    (In fact, $(D _isAbsolute) is just an alias for $(LREF isRooted).)
+    ---
+    version (Posix)
+    {
+        assert (isAbsolute("/"));
+        assert (isAbsolute("/foo"));
+        assert (!isAbsolute("foo"));
+        assert (!isAbsolute("../foo"));
+    }
+    ---
+
+    On Windows, an absolute path starts at the root directory of
+    a specific drive.  Hence, it must start with $(D `d:\`) or $(D `d:/`),
+    where $(D d) is the drive letter.  Alternatively, it may be a
+    network path, i.e. a path starting with a double (back)slash.
+    ---
+    version (Windows)
+    {
+        assert (isAbsolute(`d:\`));
+        assert (isAbsolute(`d:\foo`));
+        assert (isAbsolute(`\\foo\bar`));
+        assert (!isAbsolute(`\`));
+        assert (!isAbsolute(`\foo`));
+        assert (!isAbsolute("d:foo"));
+    }
+    ---
+*/
+version (StdDdoc)
+{
+    bool isAbsolute(R)(R path) pure nothrow @safe
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            is(StringTypeOf!R));
+}
+else version (Windows)
+{
+    bool isAbsolute(R)(R path)
+        if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+            is(StringTypeOf!R))
+    {
+        return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path);
+    }
+}
+else version (Posix)
+{
+    alias isAbsolute = isRooted;
+}
+
+
+unittest
+{
+    assert (!isAbsolute("foo"));
+    assert (!isAbsolute("../foo"w));
+    static assert (!isAbsolute("foo"));
+
+    version (Posix)
+    {
+    assert (isAbsolute("/"d));
+    assert (isAbsolute("/foo".dup));
+    static assert (isAbsolute("/foo"));
+    }
+
+    version (Windows)
+    {
+    assert (isAbsolute("d:\\"w));
+    assert (isAbsolute("d:\\foo"d));
+    assert (isAbsolute("\\\\foo\\bar"));
+    assert (!isAbsolute("\\"w.dup));
+    assert (!isAbsolute("\\foo"d.dup));
+    assert (!isAbsolute("d:"));
+    assert (!isAbsolute("d:foo"));
+    static assert (isAbsolute(`d:\foo`));
+    }
+
+    {
+        auto r = MockRange!(immutable(char))(`../foo`);
+        assert(!r.isAbsolute());
+    }
+
+    static struct DirEntry { string s; alias s this; }
+    assert(!isAbsolute(DirEntry("foo")));
+}
+
+
+
+
+/** Tranforms $(D path) into an absolute _path.
+
+    The following algorithm is used:
+    $(OL
+        $(LI If $(D path) is empty, return $(D null).)
+        $(LI If $(D path) is already absolute, return it.)
+        $(LI Otherwise, append $(D path) to $(D base) and return
+            the result. If $(D base) is not specified, the current
+            working directory is used.)
+    )
+    The function allocates memory if and only if it gets to the third stage
+    of this algorithm.
+
+    Params:
+        path = the relative path to transform
+        base = the base directory of the relative path
+
+    Returns:
+        string of transformed path
+
+    Throws:
+    $(D Exception) if the specified _base directory is not absolute.
+
+    See_Also:
+        $(LREF asAbsolutePath) which does not allocate
+*/
+string absolutePath(string path, lazy string base = getcwd())
+    @safe pure
+{
+    if (path.empty)  return null;
+    if (isAbsolute(path))  return path;
+    auto baseVar = base;
+    if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute");
+    import std.array;
+    return chainPath(baseVar, path).array;
+}
+
+///
+unittest
+{
+    version (Posix)
+    {
+        assert (absolutePath("some/file", "/foo/bar")  == "/foo/bar/some/file");
+        assert (absolutePath("../file", "/foo/bar")    == "/foo/bar/../file");
+        assert (absolutePath("/some/file", "/foo/bar") == "/some/file");
+    }
+
+    version (Windows)
+    {
+        assert (absolutePath(`some\file`, `c:\foo\bar`)    == `c:\foo\bar\some\file`);
+        assert (absolutePath(`..\file`, `c:\foo\bar`)      == `c:\foo\bar\..\file`);
+        assert (absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
+        assert (absolutePath(`\`, `c:\`)                   == `c:\`);
+        assert (absolutePath(`\some\file`, `c:\foo\bar`)   == `c:\some\file`);
+    }
+}
+
+unittest
+{
+    version (Posix)
+    {
+        static assert (absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
+    }
+
+    version (Windows)
+    {
+        static assert (absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
+    }
+
+    import std.exception;
+    assertThrown(absolutePath("bar", "foo"));
+}
+
+/** Tranforms $(D path) into an absolute _path.
+
+    The following algorithm is used:
+    $(OL
+        $(LI If $(D path) is empty, return $(D null).)
+        $(LI If $(D path) is already absolute, return it.)
+        $(LI Otherwise, append $(D path) to the current working directory,
+        which allocates memory.)
+    )
+
+    Params:
+        path = the relative path to transform
+
+    Returns:
+        the transformed path as a lazy range
+
+    See_Also:
+        $(LREF absolutePath) which returns an allocated string
+*/
+auto asAbsolutePath(R)(R path)
+    if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
+         isNarrowString!R) &&
+        !isConvertibleToString!R)
+{
+    import std.file : getcwd;
+    string base = null;
+    if (!path.empty && !isAbsolute(path))
+        base = getcwd();
+    return chainPath(base, path);
+}
+
+///
+unittest
+{
+    import std.array;
+    assert(asAbsolutePath(cast(string)null).array == "");
+    version (Posix)
+    {
+        assert(asAbsolutePath("/foo").array == "/foo");
+    }
+    version (Windows)
+    {
+        assert(asAbsolutePath("c:/foo").array == "c:/foo");
+    }
+    asAbsolutePath("foo");
+}
+
+auto asAbsolutePath(R)(auto ref R path)
+    if (isConvertibleToString!R)
+{
+    return asAbsolutePath!(StringTypeOf!R)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!asAbsolutePath(null));
+}
+
+/** Translates $(D path) into a relative _path.
+
+    The returned _path is relative to $(D base), which is by default
+    taken to be the current working directory.  If specified,
+    $(D base) must be an absolute _path, and it is always assumed
+    to refer to a directory.  If $(D path) and $(D base) refer to
+    the same directory, the function returns $(D `.`).
+
+    The following algorithm is used:
+    $(OL
+        $(LI If $(D path) is a relative directory, return it unaltered.)
+        $(LI Find a common root between $(D path) and $(D base).
+            If there is no common root, return $(D path) unaltered.)
+        $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
+            necessary to reach the common root from base path.)
+        $(LI Append the remaining segments of $(D path) to the string
+            and return.)
+    )
+
+    In the second step, path components are compared using $(D filenameCmp!cs),
+    where $(D cs) is an optional template parameter determining whether
+    the comparison is case sensitive or not.  See the
+    $(LREF filenameCmp) documentation for details.
+
+    This function allocates memory.
+
+    Params:
+        cs = Whether matching path name components against the base path should
+            be case-sensitive or not.
+        path = A path name.
+        base = The base path to construct the relative path from.
+
+    Returns: The relative path.
+
+    See_Also:
+        $(LREF asRelativePath) which does not allocate memory
+
+    Throws:
+    $(D Exception) if the specified _base directory is not absolute.
+*/
+string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
+    (string path, lazy string base = getcwd())
+{
+    if (!isAbsolute(path))
+        return path;
+    auto baseVar = base;
+    if (!isAbsolute(baseVar))
+        throw new Exception("Base directory must be absolute");
+
+    import std.conv : to;
+    return asRelativePath!cs(path, baseVar).to!string;
+}
+
+///
+unittest
+{
+    assert (relativePath("foo") == "foo");
+
+    version (Posix)
+    {
+        assert (relativePath("foo", "/bar") == "foo");
+        assert (relativePath("/foo/bar", "/foo/bar") == ".");
+        assert (relativePath("/foo/bar", "/foo/baz") == "../bar");
+        assert (relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
+        assert (relativePath("/foo/bar/baz", "/foo/bar") == "baz");
+    }
+    version (Windows)
+    {
+        assert (relativePath("foo", `c:\bar`) == "foo");
+        assert (relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
+        assert (relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
+        assert (relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
+        assert (relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
+        assert (relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
+    }
+}
+
+unittest
+{
+    import std.exception;
+    assert (relativePath("foo") == "foo");
+    version (Posix)
+    {
+        relativePath("/foo");
+        assert (relativePath("/foo/bar", "/foo/baz") == "../bar");
+        assertThrown(relativePath("/foo", "bar"));
+    }
+    else version (Windows)
+    {
+        relativePath(`\foo`);
+        assert (relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
+        assertThrown(relativePath(`c:\foo`, "bar"));
+    }
+    else static assert (0);
+}
+
+/** Transforms `path` into a _path relative to `base`.
+
+    The returned _path is relative to `base`, which is usually
+    the current working directory.
+    `base` must be an absolute _path, and it is always assumed
+    to refer to a directory.  If `path` and `base` refer to
+    the same directory, the function returns `'.'`.
+
+    The following algorithm is used:
+    $(OL
+        $(LI If `path` is a relative directory, return it unaltered.)
+        $(LI Find a common root between `path` and `base`.
+            If there is no common root, return `path` unaltered.)
+        $(LI Prepare a string with as many `../` or `..\` as
+            necessary to reach the common root from base path.)
+        $(LI Append the remaining segments of `path` to the string
+            and return.)
+    )
+
+    In the second step, path components are compared using `filenameCmp!cs`,
+    where `cs` is an optional template parameter determining whether
+    the comparison is case sensitive or not.  See the
+    $(LREF filenameCmp) documentation for details.
+
+    Params:
+        path = _path to transform
+        base = absolute path
+        cs = whether filespec comparisons are sensitive or not; defaults to
+         `CaseSensitive.osDefault`
+
+    Returns:
+        a random access range of the transformed _path
+
+    See_Also:
+        $(LREF relativePath)
+*/
+auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
+    (R1 path, R2 base)
+    if ((isNarrowString!R1 ||
+         (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) &&
+        !isConvertibleToString!R1) &&
+        (isNarrowString!R2 ||
+         (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) &&
+         !isConvertibleToString!R2))
+{
+    bool choosePath = !isAbsolute(path);
+
+    // Find common root with current working directory
+
+    auto basePS = pathSplitter(base);
+    auto pathPS = pathSplitter(path);
+    choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0;
+
+    basePS.popFront();
+    pathPS.popFront();
+
+    import std.range.primitives : walkLength;
+    import std.range : repeat, chain, choose;
+    import std.algorithm : mismatch, joiner;
+    import std.array;
+    import std.utf : byCodeUnit, byChar;
+
+    // Remove matching prefix from basePS and pathPS
+    auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS);
+    basePS = tup[0];
+    pathPS = tup[1];
+
+    string sep;
+    if (basePS.empty && pathPS.empty)
+        sep = ".";              // if base == path, this is the return
+    else if (!basePS.empty && !pathPS.empty)
+        sep = dirSeparator;
+
+    // Append as many "../" as necessary to reach common base from path
+    auto r1 = ".."
+        .byChar
+        .repeat(basePS.walkLength())
+        .joiner(dirSeparator.byChar);
+
+    auto r2 = pathPS
+        .joiner(dirSeparator.byChar)
+        .byChar;
+
+    // Return (r1 ~ sep ~ r2)
+    return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2));
+}
+
+///
+unittest
+{
+    import std.array;
+    version (Posix)
+    {
+        assert (asRelativePath("foo", "/bar").array == "foo");
+        assert (asRelativePath("/foo/bar", "/foo/bar").array == ".");
+        assert (asRelativePath("/foo/bar", "/foo/baz").array == "../bar");
+        assert (asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz");
+        assert (asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz");
+    }
+    else version (Windows)
+    {
+        assert (asRelativePath("foo", `c:\bar`).array == "foo");
+        assert (asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == ".");
+        assert (asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`);
+        assert (asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
+        assert (asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
+        assert (asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz");
+        assert (asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`);
+        assert (asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`);
+    }
+    else
+        static assert(0);
+}
+
+auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
+    (auto ref R1 path, auto ref R2 base)
+    if (isConvertibleToString!R1 || isConvertibleToString!R2)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, R1, R2);
+    return asRelativePath!(cs, Types)(path, base);
+}
+
+unittest
+{
+    import std.array;
+    version (Posix)
+        assert (asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo");
+    else version (Windows)
+        assert (asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo");
+    assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo");
+    assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo");
+    assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo");
+    import std.utf : byDchar;
+    assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo");
+}
+
+unittest
+{
+    import std.array, std.utf : bCU=byCodeUnit;
+    version (Posix)
+    {
+        assert (asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz");
+        assert (asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w);
+        assert (asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d);
+    }
+    else version (Windows)
+    {
+        assert (asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`);
+        assert (asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w);
+        assert (asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d);
+    }
+}
+
+/** Compares filename characters.
+
+    This function can perform a case-sensitive or a case-insensitive
+    comparison.  This is controlled through the $(D cs) template parameter
+    which, if not specified, is given by $(LREF CaseSensitive)$(D .osDefault).
+
+    On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
+    are considered equal.
+
+    Params:
+        cs = Case-sensitivity of the comparison.
+        a = A filename character.
+        b = A filename character.
+
+    Returns:
+        $(D < 0) if $(D a < b),
+        $(D 0) if $(D a == b), and
+        $(D > 0) if $(D a > b).
+*/
+int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
+    @safe pure nothrow
+{
+    if (isDirSeparator(a) && isDirSeparator(b)) return 0;
+    static if (!cs)
+    {
+        import std.uni;
+        a = toLower(a);
+        b = toLower(b);
+    }
+    return cast(int)(a - b);
+}
+
+///
+unittest
+{
+    assert (filenameCharCmp('a', 'a') == 0);
+    assert (filenameCharCmp('a', 'b') < 0);
+    assert (filenameCharCmp('b', 'a') > 0);
+
+    version (linux)
+    {
+        // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
+        assert (filenameCharCmp('A', 'a') < 0);
+        assert (filenameCharCmp('a', 'A') > 0);
+    }
+    version (Windows)
+    {
+        // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
+        assert (filenameCharCmp('a', 'A') == 0);
+        assert (filenameCharCmp('a', 'B') < 0);
+        assert (filenameCharCmp('A', 'b') < 0);
+    }
+}
+
+unittest
+{
+    assert (filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0);
+    assert (filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0);
+
+    assert (filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0);
+    assert (filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0);
+
+    version (Posix)   assert (filenameCharCmp('\\', '/') != 0);
+    version (Windows) assert (filenameCharCmp('\\', '/') == 0);
+}
+
+
+/** Compares file names and returns
+
+    Individual characters are compared using $(D filenameCharCmp!cs),
+    where $(D cs) is an optional template parameter determining whether
+    the comparison is case sensitive or not.
+
+    Treatment of invalid UTF encodings is implementation defined.
+
+    Params:
+        cs = case sensitivity
+        filename1 = range for first file name
+        filename2 = range for second file name
+
+    Returns:
+        $(D < 0) if $(D filename1 < filename2),
+        $(D 0) if $(D filename1 == filename2) and
+        $(D > 0) if $(D filename1 > filename2).
+
+    See_Also:
+        $(LREF filenameCharCmp)
+*/
+int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
+    (Range1 filename1, Range2 filename2)
+    if (isInputRange!Range1 && isSomeChar!(ElementEncodingType!Range1) && !isConvertibleToString!Range1 &&
+        isInputRange!Range2 && isSomeChar!(ElementEncodingType!Range2) && !isConvertibleToString!Range2)
+{
+    alias C1 = Unqual!(ElementEncodingType!Range1);
+    alias C2 = Unqual!(ElementEncodingType!Range2);
+
+    static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) ||
+               C1.sizeof != C2.sizeof)
+    {
+        // Case insensitive - decode so case is checkable
+        // Different char sizes - decode to bring to common type
+        import std.utf : byDchar;
+        return filenameCmp!cs(filename1.byDchar, filename2.byDchar);
+    }
+    else static if (isSomeString!Range1 && C1.sizeof < 4 ||
+                    isSomeString!Range2 && C2.sizeof < 4)
+    {
+        // Avoid autodecoding
+        import std.utf : byCodeUnit;
+        return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit);
+    }
+    else
+    {
+        for (;;)
+        {
+            if (filename1.empty) return -(cast(int) !filename2.empty);
+            if (filename2.empty) return  1;
+            const c = filenameCharCmp!cs(filename1.front, filename2.front);
+            if (c != 0) return c;
+            filename1.popFront();
+            filename2.popFront();
+        }
+    }
+}
+
+///
+unittest
+{
+    assert (filenameCmp("abc", "abc") == 0);
+    assert (filenameCmp("abc", "abd") < 0);
+    assert (filenameCmp("abc", "abb") > 0);
+    assert (filenameCmp("abc", "abcd") < 0);
+    assert (filenameCmp("abcd", "abc") > 0);
+
+    version (linux)
+    {
+        // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
+        assert (filenameCmp("Abc", "abc") < 0);
+        assert (filenameCmp("abc", "Abc") > 0);
+    }
+    version (Windows)
+    {
+        // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
+        assert (filenameCmp("Abc", "abc") == 0);
+        assert (filenameCmp("abc", "Abc") == 0);
+        assert (filenameCmp("Abc", "abD") < 0);
+        assert (filenameCmp("abc", "AbB") > 0);
+    }
+}
+
+int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
+    (auto ref Range1 filename1, auto ref Range2 filename2)
+    if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
+{
+    import std.meta : staticMap;
+    alias Types = staticMap!(convertToString, Range1, Range2);
+    return filenameCmp!(cs, Types)(filename1, filename2);
+}
+
+unittest
+{
+    assert (filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0);
+    assert (filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0);
+    assert (filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
+}
+
+unittest
+{
+    assert (filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0);
+    assert (filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0);
+
+    assert (filenameCmp!(CaseSensitive.no)("abc", "abc") == 0);
+    assert (filenameCmp!(CaseSensitive.no)("abc", "abd") < 0);
+    assert (filenameCmp!(CaseSensitive.no)("abc", "abb") > 0);
+    assert (filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0);
+    assert (filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0);
+    assert (filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0);
+    assert (filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0);
+    assert (filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0);
+    assert (filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0);
+
+    version (Posix)   assert (filenameCmp(`abc\def`, `abc/def`) != 0);
+    version (Windows) assert (filenameCmp(`abc\def`, `abc/def`) == 0);
+}
+
+/** Matches a pattern against a path.
+
+    Some characters of pattern have a special meaning (they are
+    $(I meta-characters)) and can't be escaped. These are:
+
+    $(BOOKTABLE,
+    $(TR $(TD $(D *))
+         $(TD Matches 0 or more instances of any character.))
+    $(TR $(TD $(D ?))
+         $(TD Matches exactly one instance of any character.))
+    $(TR $(TD $(D [)$(I chars)$(D ]))
+         $(TD Matches one instance of any character that appears
+              between the brackets.))
+    $(TR $(TD $(D [!)$(I chars)$(D ]))
+         $(TD Matches one instance of any character that does not
+              appear between the brackets after the exclamation mark.))
+    $(TR $(TD $(D {)$(I string1)$(D ,)$(I string2)$(D ,)&hellip;$(D }))
+         $(TD Matches either of the specified strings.))
+    )
+
+    Individual characters are compared using $(D filenameCharCmp!cs),
+    where $(D cs) is an optional template parameter determining whether
+    the comparison is case sensitive or not.  See the
+    $(LREF filenameCharCmp) documentation for details.
+
+    Note that directory
+    separators and dots don't stop a meta-character from matching
+    further portions of the path.
+
+    Params:
+        cs = Whether the matching should be case-sensitive
+        path = The path to be matched against
+        pattern = The glob pattern
+
+    Returns:
+    $(D true) if pattern matches path, $(D false) otherwise.
+
+    See_also:
+    $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
+ */
+bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
+    (Range path, const(C)[] pattern)
+    @safe pure nothrow
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range &&
+        isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range)))
+in
+{
+    // Verify that pattern[] is valid
+    import std.algorithm : balancedParens;
+    assert(balancedParens(pattern, '[', ']', 0));
+    assert(balancedParens(pattern, '{', '}', 0));
+}
+body
+{
+    alias RC = Unqual!(ElementEncodingType!Range);
+
+    static if (RC.sizeof == 1 && isSomeString!Range)
+    {
+        import std.utf : byChar;
+        return globMatch!cs(path.byChar, pattern);
+    }
+    else static if (RC.sizeof == 2 && isSomeString!Range)
+    {
+        import std.utf : byWchar;
+        return globMatch!cs(path.byWchar, pattern);
+    }
+    else
+    {
+        C[] pattmp;
+        foreach (ref pi; 0 .. pattern.length)
+        {
+            const pc = pattern[pi];
+            switch (pc)
+            {
+                case '*':
+                    if (pi + 1 == pattern.length)
+                        return true;
+                    for (; !path.empty; path.popFront())
+                    {
+                        auto p = path.save;
+                        if (globMatch!(cs, C)(p,
+                                        pattern[pi + 1 .. pattern.length]))
+                            return true;
+                    }
+                    return false;
+
+                case '?':
+                    if (path.empty)
+                        return false;
+                    path.popFront();
+                    break;
+
+                case '[':
+                    if (path.empty)
+                        return false;
+                    auto nc = path.front;
+                    path.popFront();
+                    auto not = false;
+                    ++pi;
+                    if (pattern[pi] == '!')
+                    {
+                        not = true;
+                        ++pi;
+                    }
+                    auto anymatch = false;
+                    while (1)
+                    {
+                        const pc2 = pattern[pi];
+                        if (pc2 == ']')
+                            break;
+                        if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0))
+                            anymatch = true;
+                        ++pi;
+                    }
+                    if (anymatch == not)
+                        return false;
+                    break;
+
+                case '{':
+                    // find end of {} section
+                    auto piRemain = pi;
+                    for (; piRemain < pattern.length
+                             && pattern[piRemain] != '}'; ++piRemain)
+                    {   }
+
+                    if (piRemain < pattern.length)
+                        ++piRemain;
+                    ++pi;
+
+                    while (pi < pattern.length)
+                    {
+                        const pi0 = pi;
+                        C pc3 = pattern[pi];
+                        // find end of current alternative
+                        for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi)
+                        {
+                            pc3 = pattern[pi];
+                        }
+
+                        auto p = path.save;
+                        if (pi0 == pi)
+                        {
+                            if (globMatch!(cs, C)(p, pattern[piRemain..$]))
+                            {
+                                return true;
+                            }
+                            ++pi;
+                        }
+                        else
+                        {
+                            /* Match for:
+                             *   pattern[pi0..pi-1] ~ pattern[piRemain..$]
+                             */
+                            if (pattmp.ptr == null)
+                                // Allocate this only once per function invocation.
+                                // Should do it with malloc/free, but that would make it impure.
+                                pattmp = new C[pattern.length];
+
+                            const len1 = pi - 1 - pi0;
+                            pattmp[0 .. len1] = pattern[pi0 .. pi - 1];
+
+                            const len2 = pattern.length - piRemain;
+                            pattmp[len1 .. len1 + len2] = pattern[piRemain .. $];
+
+                            if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2]))
+                            {
+                                return true;
+                            }
+                        }
+                        if (pc3 == '}')
+                        {
+                            break;
+                        }
+                    }
+                    return false;
+
+                default:
+                    if (path.empty)
+                        return false;
+                    if (filenameCharCmp!cs(pc, path.front) != 0)
+                        return false;
+                    path.popFront();
+                    break;
+            }
+        }
+        return path.empty;
+    }
+}
+
+///
+unittest
+{
+    assert (globMatch("foo.bar", "*"));
+    assert (globMatch("foo.bar", "*.*"));
+    assert (globMatch(`foo/foo\bar`, "f*b*r"));
+    assert (globMatch("foo.bar", "f???bar"));
+    assert (globMatch("foo.bar", "[fg]???bar"));
+    assert (globMatch("foo.bar", "[!gh]*bar"));
+    assert (globMatch("bar.fooz", "bar.{foo,bif}z"));
+    assert (globMatch("bar.bifz", "bar.{foo,bif}z"));
+
+    version (Windows)
+    {
+        // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
+        assert (globMatch("foo", "Foo"));
+        assert (globMatch("Goo.bar", "[fg]???bar"));
+    }
+    version (linux)
+    {
+        // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
+        assert (!globMatch("foo", "Foo"));
+        assert (!globMatch("Goo.bar", "[fg]???bar"));
+    }
+}
+
+bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
+    (auto ref Range path, const(C)[] pattern)
+    @safe pure nothrow
+    if (isConvertibleToString!Range)
+{
+    return globMatch!(cs, C, StringTypeOf!Range)(path, pattern);
+}
+
+unittest
+{
+    assert (testAliasedString!globMatch("foo.bar", "*"));
+}
+
+unittest
+{
+    assert (globMatch!(CaseSensitive.no)("foo", "Foo"));
+    assert (!globMatch!(CaseSensitive.yes)("foo", "Foo"));
+
+    assert(globMatch("foo", "*"));
+    assert(globMatch("foo.bar"w, "*"w));
+    assert(globMatch("foo.bar"d, "*.*"d));
+    assert(globMatch("foo.bar", "foo*"));
+    assert(globMatch("foo.bar"w, "f*bar"w));
+    assert(globMatch("foo.bar"d, "f*b*r"d));
+    assert(globMatch("foo.bar", "f???bar"));
+    assert(globMatch("foo.bar"w, "[fg]???bar"w));
+    assert(globMatch("foo.bar"d, "[!gh]*bar"d));
+
+    assert(!globMatch("foo", "bar"));
+    assert(!globMatch("foo"w, "*.*"w));
+    assert(!globMatch("foo.bar"d, "f*baz"d));
+    assert(!globMatch("foo.bar", "f*b*x"));
+    assert(!globMatch("foo.bar", "[gh]???bar"));
+    assert(!globMatch("foo.bar"w, "[!fg]*bar"w));
+    assert(!globMatch("foo.bar"d, "[fg]???baz"d));
+    assert(!globMatch("foo.di", "*.d")); // test issue 6634: triggered bad assertion
+
+    assert(globMatch("foo.bar", "{foo,bif}.bar"));
+    assert(globMatch("bif.bar"w, "{foo,bif}.bar"w));
+
+    assert(globMatch("bar.foo"d, "bar.{foo,bif}"d));
+    assert(globMatch("bar.bif", "bar.{foo,bif}"));
+
+    assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w));
+    assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d));
+
+    assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
+    assert(globMatch("bar.foo"w, "bar.{biz,}foo"w));
+    assert(globMatch("bar.foo"d, "bar.{,biz}foo"d));
+    assert(globMatch("bar.foo", "bar.{}foo"));
+
+    assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w));
+    assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d));
+    assert(globMatch("bar.o", "bar.{,ar,fo}o"));
+
+    assert(!globMatch("foo", "foo?"));
+    assert(!globMatch("foo", "foo[]"));
+    assert(!globMatch("foo", "foob"));
+    assert(!globMatch("foo", "foo{b}"));
+
+
+    static assert(globMatch("foo.bar", "[!gh]*bar"));
+}
+
+
+
+
+/** Checks that the given file or directory name is valid.
+
+    The maximum length of $(D filename) is given by the constant
+    $(D core.stdc.stdio.FILENAME_MAX).  (On Windows, this number is
+    defined as the maximum number of UTF-16 code points, and the
+    test will therefore only yield strictly correct results when
+    $(D filename) is a string of $(D wchar)s.)
+
+    On Windows, the following criteria must be satisfied
+    ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
+    $(UL
+        $(LI $(D filename) must not contain any characters whose integer
+            representation is in the range 0-31.)
+        $(LI $(D filename) must not contain any of the following $(I reserved
+            characters): <>:"/\|?*)
+        $(LI $(D filename) may not end with a space ($(D ' ')) or a period
+            ($(D '.')).)
+    )
+
+    On POSIX, $(D filename) may not contain a forward slash ($(D '/')) or
+    the null character ($(D '\0')).
+
+    Params:
+        filename = string to check
+
+    Returns:
+        $(D true) if and only if $(D filename) is not
+        empty, not too long, and does not contain invalid characters.
+
+*/
+bool isValidFilename(Range)(Range filename)
+    if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range)
+{
+    import core.stdc.stdio : FILENAME_MAX;
+    if (filename.length == 0 || filename.length >= FILENAME_MAX) return false;
+    foreach (c; filename)
+    {
+        version (Windows)
+        {
+            switch (c)
+            {
+                case 0:
+                ..
+                case 31:
+                case '<':
+                case '>':
+                case ':':
+                case '"':
+                case '/':
+                case '\\':
+                case '|':
+                case '?':
+                case '*':
+                    return false;
+
+                default:
+                    break;
+            }
+        }
+        else version (Posix)
+        {
+            if (c == 0 || c == '/') return false;
+        }
+        else static assert (0);
+    }
+    version (Windows)
+    {
+        auto last = filename[filename.length - 1];
+        if (last == '.' || last == ' ') return false;
+    }
+
+    // All criteria passed
+    return true;
+}
+
+///
+@safe pure @nogc nothrow
+unittest
+{
+    import std.utf : byCodeUnit;
+
+    assert(isValidFilename("hello.exe".byCodeUnit));
+}
+
+bool isValidFilename(Range)(auto ref Range filename)
+    if (isConvertibleToString!Range)
+{
+    return isValidFilename!(StringTypeOf!Range)(filename);
+}
+
+unittest
+{
+    assert(testAliasedString!isValidFilename("hello.exe"));
+}
+
+@safe pure
+unittest
+{
+    import std.conv;
+    auto valid = ["foo"];
+    auto invalid = ["", "foo\0bar", "foo/bar"];
+    auto pfdep = [`foo\bar`, "*.txt"];
+    version (Windows) invalid ~= pfdep;
+    else version (Posix) valid ~= pfdep;
+    else static assert (0);
+
+    import std.meta : AliasSeq;
+    foreach (T; AliasSeq!(char[], const(char)[], string, wchar[],
+        const(wchar)[], wstring, dchar[], const(dchar)[], dstring))
+    {
+        foreach (fn; valid)
+            assert (isValidFilename(to!T(fn)));
+        foreach (fn; invalid)
+            assert (!isValidFilename(to!T(fn)));
+    }
+
+    {
+        auto r = MockRange!(immutable(char))(`dir/file.d`);
+        assert(!isValidFilename(r));
+    }
+
+    static struct DirEntry { string s; alias s this; }
+    assert(isValidFilename(DirEntry("file.ext")));
+
+    version (Windows)
+    {
+        immutable string cases = "<>:\"/\\|?*";
+        foreach (i; 0 .. 31 + cases.length)
+        {
+            char[3] buf;
+            buf[0] = 'a';
+            buf[1] = i <= 31 ? cast(char)i : cases[i - 32];
+            buf[2] = 'b';
+            assert(!isValidFilename(buf[]));
+        }
+    }
+}
+
+
+
+/** Checks whether $(D path) is a valid _path.
+
+    Generally, this function checks that $(D path) is not empty, and that
+    each component of the path either satisfies $(LREF isValidFilename)
+    or is equal to $(D ".") or $(D "..").
+    It does $(I not) check whether the _path points to an existing file
+    or directory; use $(XREF file,exists) for this purpose.
+
+    On Windows, some special rules apply:
+    $(UL
+        $(LI If the second character of $(D path) is a colon ($(D ':')),
+            the first character is interpreted as a drive letter, and
+            must be in the range A-Z (case insensitive).)
+        $(LI If $(D path) is on the form $(D `\\$(I server)\$(I share)\...`)
+            (UNC path), $(LREF isValidFilename) is applied to $(I server)
+            and $(I share) as well.)
+        $(LI If $(D path) starts with $(D `\\?\`) (long UNC path), the
+            only requirement for the rest of the string is that it does
+            not contain the null character.)
+        $(LI If $(D path) starts with $(D `\\.\`) (Win32 device namespace)
+            this function returns $(D false); such paths are beyond the scope
+            of this module.)
+    )
+
+    Params:
+        path = string or Range of characters to check
+
+    Returns:
+        true if $(D path) is a valid _path.
+*/
+bool isValidPath(Range)(Range path)
+    if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range)
+{
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    if (path.empty) return false;
+
+    // Check whether component is "." or "..", or whether it satisfies
+    // isValidFilename.
+    bool isValidComponent(Range component)
+    {
+        assert (component.length > 0);
+        if (component[0] == '.')
+        {
+            if (component.length == 1) return true;
+            else if (component.length == 2 && component[1] == '.') return true;
+        }
+        return isValidFilename(component);
+    }
+
+    if (path.length == 1)
+        return isDirSeparator(path[0]) || isValidComponent(path);
+
+    Range remainder;
+    version (Windows)
+    {
+        if (isDirSeparator(path[0]) && isDirSeparator(path[1]))
+        {
+            // Some kind of UNC path
+            if (path.length < 5)
+            {
+                // All valid UNC paths must have at least 5 characters
+                return false;
+            }
+            else if (path[2] == '?')
+            {
+                // Long UNC path
+                if (!isDirSeparator(path[3])) return false;
+                foreach (c; path[4 .. $])
+                {
+                    if (c == '\0') return false;
+                }
+                return true;
+            }
+            else if (path[2] == '.')
+            {
+                // Win32 device namespace not supported
+                return false;
+            }
+            else
+            {
+                // Normal UNC path, i.e. \\server\share\...
+                size_t i = 2;
+                while (i < path.length && !isDirSeparator(path[i])) ++i;
+                if (i == path.length || !isValidFilename(path[2 .. i]))
+                    return false;
+                ++i; // Skip a single dir separator
+                size_t j = i;
+                while (j < path.length && !isDirSeparator(path[j])) ++j;
+                if (!isValidFilename(path[i .. j])) return false;
+                remainder = path[j .. $];
+            }
+        }
+        else if (isDriveSeparator(path[1]))
+        {
+            import std.ascii;
+            if (!isAlpha(path[0])) return false;
+            remainder = path[2 .. $];
+        }
+        else
+        {
+            remainder = path;
+        }
+    }
+    else version (Posix)
+    {
+        remainder = path;
+    }
+    else static assert (0);
+    remainder = ltrimDirSeparators(remainder);
+
+    // Check that each component satisfies isValidComponent.
+    while (!remainder.empty)
+    {
+        size_t i = 0;
+        while (i < remainder.length && !isDirSeparator(remainder[i])) ++i;
+        assert (i > 0);
+        if (!isValidComponent(remainder[0 .. i])) return false;
+        remainder = ltrimDirSeparators(remainder[i .. $]);
+    }
+
+    // All criteria passed
+    return true;
+}
+
+///
+@safe pure @nogc nothrow
+unittest
+{
+    assert (isValidPath("/foo/bar"));
+    assert (!isValidPath("/foo\0/bar"));
+    assert (isValidPath("/"));
+    assert (isValidPath("a"));
+
+    version (Windows)
+    {
+        assert (isValidPath(`c:\`));
+        assert (isValidPath(`c:\foo`));
+        assert (isValidPath(`c:\foo\.\bar\\\..\`));
+        assert (!isValidPath(`!:\foo`));
+        assert (!isValidPath(`c::\foo`));
+        assert (!isValidPath(`c:\foo?`));
+        assert (!isValidPath(`c:\foo.`));
+
+        assert (isValidPath(`\\server\share`));
+        assert (isValidPath(`\\server\share\foo`));
+        assert (isValidPath(`\\server\share\\foo`));
+        assert (!isValidPath(`\\\server\share\foo`));
+        assert (!isValidPath(`\\server\\share\foo`));
+        assert (!isValidPath(`\\ser*er\share\foo`));
+        assert (!isValidPath(`\\server\sha?e\foo`));
+        assert (!isValidPath(`\\server\share\|oo`));
+
+        assert (isValidPath(`\\?\<>:"?*|/\..\.`));
+        assert (!isValidPath("\\\\?\\foo\0bar"));
+
+        assert (!isValidPath(`\\.\PhysicalDisk1`));
+        assert (!isValidPath(`\\`));
+    }
+
+    import std.utf : byCodeUnit;
+    assert (isValidPath("/foo/bar".byCodeUnit));
+}
+
+bool isValidPath(Range)(auto ref Range path)
+    if (isConvertibleToString!Range)
+{
+    return isValidPath!(StringTypeOf!Range)(path);
+}
+
+unittest
+{
+    assert(testAliasedString!isValidPath("/foo/bar"));
+}
+
+/** Performs tilde expansion in paths on POSIX systems.
+    On Windows, this function does nothing.
+
+    There are two ways of using tilde expansion in a path. One
+    involves using the tilde alone or followed by a path separator. In
+    this case, the tilde will be expanded with the value of the
+    environment variable $(D HOME).  The second way is putting
+    a username after the tilde (i.e. $(D ~john/Mail)). Here,
+    the username will be searched for in the user database
+    (i.e. $(D /etc/passwd) on Unix systems) and will expand to
+    whatever path is stored there.  The username is considered the
+    string after the tilde ending at the first instance of a path
+    separator.
+
+    Note that using the $(D ~user) syntax may give different
+    values from just $(D ~) if the environment variable doesn't
+    match the value stored in the user database.
+
+    When the environment variable version is used, the path won't
+    be modified if the environment variable doesn't exist or it
+    is empty. When the database version is used, the path won't be
+    modified if the user doesn't exist in the database or there is
+    not enough memory to perform the query.
+
+    This function performs several memory allocations.
+
+    Params:
+        inputPath = The path name to expand.
+
+    Returns:
+    $(D inputPath) with the tilde expanded, or just $(D inputPath)
+    if it could not be expanded.
+    For Windows, $(D expandTilde) merely returns its argument $(D inputPath).
+
+    Example:
+    -----
+    void processFile(string path)
+    {
+        // Allow calling this function with paths such as ~/foo
+        auto fullPath = expandTilde(path);
+        ...
+    }
+    -----
+*/
+string expandTilde(string inputPath) nothrow
+{
+    version(Posix)
+    {
+        import core.stdc.stdlib : malloc, free, realloc;
+        import core.exception : onOutOfMemoryError;
+        import core.stdc.errno : errno, ERANGE;
+
+        /*  Joins a path from a C string to the remainder of path.
+
+            The last path separator from c_path is discarded. The result
+            is joined to path[char_pos .. length] if char_pos is smaller
+            than length, otherwise path is not appended to c_path.
+        */
+        static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) nothrow
+        {
+            import core.stdc.string : strlen;
+
+            assert(c_path != null);
+            assert(path.length > 0);
+            assert(char_pos >= 0);
+
+            // Search end of C string
+            size_t end = strlen(c_path);
+
+            // Remove trailing path separator, if any
+            if (end && isDirSeparator(c_path[end - 1]))
+                end--;
+
+            // (this is the only GC allocation done in expandTilde())
+            string cp;
+            if (char_pos < path.length)
+                // Append something from path
+                cp = cast(string)(c_path[0 .. end] ~ path[char_pos .. $]);
+            else
+                // Create our own copy, as lifetime of c_path is undocumented
+                cp = c_path[0 .. end].idup;
+
+            return cp;
+        }
+
+        // Replaces the tilde from path with the environment variable HOME.
+        static string expandFromEnvironment(string path) nothrow
+        {
+            import core.stdc.stdlib : getenv;
+
+            assert(path.length >= 1);
+            assert(path[0] == '~');
+
+            // Get HOME and use that to replace the tilde.
+            auto home = getenv("HOME");
+            if (home == null)
+                return path;
+
+            return combineCPathWithDPath(home, path, 1);
+        }
+
+        // Replaces the tilde from path with the path from the user database.
+        static string expandFromDatabase(string path) nothrow
+        {
+            // bionic doesn't really support this, as getpwnam_r
+            // isn't provided and getpwnam is basically just a stub
+            version(CRuntime_Bionic)
+            {
+                return path;
+            }
+            else
+            {
+                import core.sys.posix.pwd : passwd, getpwnam_r;
+                import std.string : indexOf;
+
+                assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1])));
+                assert(path[0] == '~');
+
+                // Extract username, searching for path separator.
+                auto last_char = indexOf(path, dirSeparator[0]);
+
+                size_t username_len = (last_char == -1) ? path.length : last_char;
+                char* username = cast(char*)malloc(username_len * char.sizeof);
+                if (!username)
+                    onOutOfMemoryError();
+                scope(exit) free(username);
+
+                if (last_char == -1)
+                {
+                    username[0 .. username_len - 1] = path[1 .. $];
+                    last_char = path.length + 1;
+                }
+                else
+                {
+                    username[0 .. username_len - 1] = path[1 .. last_char];
+                }
+                username[username_len - 1] = 0;
+
+                assert(last_char > 1);
+
+                // Reserve C memory for the getpwnam_r() function.
+                int extra_memory_size = 5 * 1024;
+                char* extra_memory;
+                scope(exit) free(extra_memory);
+
+                passwd result;
+                while (1)
+                {
+                    extra_memory = cast(char*)realloc(extra_memory, extra_memory_size * char.sizeof);
+                    if (extra_memory == null)
+                        onOutOfMemoryError();
+
+                    // Obtain info from database.
+                    passwd *verify;
+                    errno = 0;
+                    if (getpwnam_r(username, &result, extra_memory, extra_memory_size,
+                            &verify) == 0)
+                    {
+                        // Succeeded if verify points at result
+                        if (verify == &result)
+                            // username is found
+                            path = combineCPathWithDPath(result.pw_dir, path, last_char);
+                        break;
+                    }
+
+                    if (errno != ERANGE)
+                        onOutOfMemoryError();
+
+                    // extra_memory isn't large enough
+                    extra_memory_size *= 2;
+                }
+                return path;
+            }
+        }
+
+        // Return early if there is no tilde in path.
+        if (inputPath.length < 1 || inputPath[0] != '~')
+            return inputPath;
+
+        if (inputPath.length == 1 || isDirSeparator(inputPath[1]))
+            return expandFromEnvironment(inputPath);
+        else
+            return expandFromDatabase(inputPath);
+    }
+    else version(Windows)
+    {
+        // Put here real windows implementation.
+        return inputPath;
+    }
+    else
+    {
+        static assert(0); // Guard. Implement on other platforms.
+    }
+}
+
+
+version(unittest) import std.process: environment;
+unittest
+{
+    version (Posix)
+    {
+        // Retrieve the current home variable.
+        auto oldHome = environment.get("HOME");
+
+        // Testing when there is no environment variable.
+        environment.remove("HOME");
+        assert(expandTilde("~/") == "~/");
+        assert(expandTilde("~") == "~");
+
+        // Testing when an environment variable is set.
+        environment["HOME"] = "dmd/test";
+        assert(expandTilde("~/") == "dmd/test/");
+        assert(expandTilde("~") == "dmd/test");
+
+        // The same, but with a variable ending in a slash.
+        environment["HOME"] = "dmd/test/";
+        assert(expandTilde("~/") == "dmd/test/");
+        assert(expandTilde("~") == "dmd/test");
+
+        // Recover original HOME variable before continuing.
+        if (oldHome !is null) environment["HOME"] = oldHome;
+        else environment.remove("HOME");
+
+        // Test user expansion for root, no /root on Android
+        version (OSX)
+        {
+            assert(expandTilde("~root") == "/var/root", expandTilde("~root"));
+            assert(expandTilde("~root/") == "/var/root/", expandTilde("~root/"));
+        }
+        else version (Android)
+        {
+        }
+        else
+        {
+            assert(expandTilde("~root") == "/root", expandTilde("~root"));
+            assert(expandTilde("~root/") == "/root/", expandTilde("~root/"));
+        }
+        assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
+    }
+}
+
+version (unittest)
+{
+    /* Define a mock RandomAccessRange to use for unittesting.
+     */
+
+    struct MockRange(C)
+    {
+        this(C[] array) { this.array = array; }
+      const
+      {
+        @property size_t length() { return array.length; }
+        @property bool empty() { return array.length == 0; }
+        @property C front() { return array[0]; }
+        @property C back()  { return array[$ - 1]; }
+        @property size_t opDollar() { return length; }
+        C opIndex(size_t i) { return array[i]; }
+      }
+        void popFront() { array = array[1 .. $]; }
+        void popBack()  { array = array[0 .. $-1]; }
+        MockRange!C opSlice( size_t lwr, size_t upr) const
+        {
+            return MockRange!C(array[lwr .. upr]);
+        }
+        @property MockRange save() { return this; }
+      private:
+        C[] array;
+    }
+
+    static assert( isRandomAccessRange!(MockRange!(const(char))) );
+}
+
+version (unittest)
+{
+    /* Define a mock BidirectionalRange to use for unittesting.
+     */
+
+    struct MockBiRange(C)
+    {
+        this(const(C)[] array) { this.array = array; }
+        const
+        {
+            @property bool empty() { return array.length == 0; }
+            @property C front() { return array[0]; }
+            @property C back()  { return array[$ - 1]; }
+            @property size_t opDollar() { return array.length; }
+        }
+        void popFront() { array = array[1 .. $]; }
+        void popBack()  { array = array[0 .. $-1]; }
+        @property MockBiRange save() { return this; }
+      private:
+        const(C)[] array;
+    }
+
+    static assert( isBidirectionalRange!(MockBiRange!(const(char))) );
+}
+
+private template BaseOf(R)
+{
+    static if (isRandomAccessRange!R && isSomeChar!(ElementType!R))
+        alias BaseOf = R;
+    else
+        alias BaseOf = StringTypeOf!R;
+}
diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d
new file mode 100644
index 00000000000..4bead1ea2dc
--- /dev/null
+++ b/libphobos/src/std/process.d
@@ -0,0 +1,3775 @@
+// Written in the D programming language.
+
+/**
+Functions for starting and interacting with other processes, and for
+working with the current _process' execution environment.
+
+Process_handling:
+$(UL $(LI
+    $(LREF spawnProcess) spawns a new _process, optionally assigning it an
+    arbitrary set of standard input, output, and error streams.
+    The function returns immediately, leaving the child _process to execute
+    in parallel with its parent.  All other functions in this module that
+    spawn processes are built around $(D spawnProcess).)
+$(LI
+    $(LREF wait) makes the parent _process wait for a child _process to
+    terminate.  In general one should always do this, to avoid
+    child processes becoming "zombies" when the parent _process exits.
+    Scope guards are perfect for this – see the $(LREF spawnProcess)
+    documentation for examples.  $(LREF tryWait) is similar to $(D wait),
+    but does not block if the _process has not yet terminated.)
+$(LI
+    $(LREF pipeProcess) also spawns a child _process which runs
+    in parallel with its parent.  However, instead of taking
+    arbitrary streams, it automatically creates a set of
+    pipes that allow the parent to communicate with the child
+    through the child's standard input, output, and/or error streams.
+    This function corresponds roughly to C's $(D popen) function.)
+$(LI
+    $(LREF execute) starts a new _process and waits for it
+    to complete before returning.  Additionally, it captures
+    the _process' standard output and error streams and returns
+    the output of these as a string.)
+$(LI
+    $(LREF spawnShell), $(LREF pipeShell) and $(LREF executeShell) work like
+    $(D spawnProcess), $(D pipeProcess) and $(D execute), respectively,
+    except that they take a single command string and run it through
+    the current user's default command interpreter.
+    $(D executeShell) corresponds roughly to C's $(D system) function.)
+$(LI
+    $(LREF kill) attempts to terminate a running _process.)
+)
+
+The following table compactly summarises the different _process creation
+functions and how they relate to each other:
+$(BOOKTABLE,
+    $(TR $(TH )
+         $(TH Runs program directly)
+         $(TH Runs shell command))
+    $(TR $(TD Low-level _process creation)
+         $(TD $(LREF spawnProcess))
+         $(TD $(LREF spawnShell)))
+    $(TR $(TD Automatic input/output redirection using pipes)
+         $(TD $(LREF pipeProcess))
+         $(TD $(LREF pipeShell)))
+    $(TR $(TD Execute and wait for completion, collect output)
+         $(TD $(LREF execute))
+         $(TD $(LREF executeShell)))
+)
+
+Other_functionality:
+$(UL
+$(LI
+    $(LREF pipe) is used to create unidirectional pipes.)
+$(LI
+    $(LREF environment) is an interface through which the current _process'
+    environment variables can be read and manipulated.)
+$(LI
+    $(LREF escapeShellCommand) and $(LREF escapeShellFileName) are useful
+    for constructing shell command lines in a portable way.)
+)
+
+Authors:
+    $(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad),
+    $(LINK2 https://github.com/schveiguy, Steven Schveighoffer),
+    $(WEB thecybershadow.net, Vladimir Panteleev)
+Copyright:
+    Copyright (c) 2013, the authors. All rights reserved.
+License:
+   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Source:
+    $(PHOBOSSRC std/_process.d)
+Macros:
+    WIKI=Phobos/StdProcess
+    OBJECTREF=$(D $(LINK2 object.html#$0,$0))
+    LREF=$(D $(LINK2 #.$0,$0))
+*/
+module std.process;
+
+version (Posix)
+{
+    import core.stdc.errno;
+    import core.stdc.string;
+    import core.sys.posix.stdio;
+    import core.sys.posix.unistd;
+    import core.sys.posix.sys.wait;
+}
+version (Windows)
+{
+    import core.stdc.stdio;
+    import core.sys.windows.windows;
+    import std.utf;
+    import std.windows.syserror;
+}
+
+import std.range.primitives;
+import std.conv;
+import std.exception;
+import std.path;
+import std.stdio;
+import std.internal.processinit;
+import std.internal.cstring;
+
+
+// When the DMC runtime is used, we have to use some custom functions
+// to convert between Windows file handles and FILE*s.
+version (Win32) version (CRuntime_DigitalMars) version = DMC_RUNTIME;
+
+
+// Some of the following should be moved to druntime.
+private
+{
+
+// Microsoft Visual C Runtime (MSVCRT) declarations.
+version (Windows)
+{
+    version (DMC_RUNTIME) { } else
+    {
+        import core.stdc.stdint;
+        enum
+        {
+            STDIN_FILENO  = 0,
+            STDOUT_FILENO = 1,
+            STDERR_FILENO = 2,
+        }
+    }
+}
+
+// POSIX API declarations.
+version (Posix)
+{
+    version (OSX)
+    {
+        extern(C) char*** _NSGetEnviron() nothrow;
+        private __gshared const(char**)* environPtr;
+        extern(C) void std_process_shared_static_this() { environPtr = _NSGetEnviron(); }
+        const(char**) environ() @property @trusted nothrow { return *environPtr; }
+    }
+    else
+    {
+        // Made available by the C runtime:
+        extern(C) extern __gshared const char** environ;
+    }
+
+    unittest
+    {
+        new Thread({assert(environ !is null);}).start();
+    }
+}
+
+
+} // private
+
+
+// =============================================================================
+// Functions and classes for process management.
+// =============================================================================
+
+
+/**
+Spawns a new _process, optionally assigning it an arbitrary set of standard
+input, output, and error streams.
+
+The function returns immediately, leaving the child _process to execute
+in parallel with its parent.  It is recommended to always call $(LREF wait)
+on the returned $(LREF Pid), as detailed in the documentation for $(D wait).
+
+Command_line:
+There are four overloads of this function.  The first two take an array
+of strings, $(D args), which should contain the program name as the
+zeroth element and any command-line arguments in subsequent elements.
+The third and fourth versions are included for convenience, and may be
+used when there are no command-line arguments.  They take a single string,
+$(D program), which specifies the program name.
+
+Unless a directory is specified in $(D args[0]) or $(D program),
+$(D spawnProcess) will search for the program in a platform-dependent
+manner.  On POSIX systems, it will look for the executable in the
+directories listed in the PATH environment variable, in the order
+they are listed.  On Windows, it will search for the executable in
+the following sequence:
+$(OL
+    $(LI The directory from which the application loaded.)
+    $(LI The current directory for the parent process.)
+    $(LI The 32-bit Windows system directory.)
+    $(LI The 16-bit Windows system directory.)
+    $(LI The Windows directory.)
+    $(LI The directories listed in the PATH environment variable.)
+)
+---
+// Run an executable called "prog" located in the current working
+// directory:
+auto pid = spawnProcess("./prog");
+scope(exit) wait(pid);
+// We can do something else while the program runs.  The scope guard
+// ensures that the process is waited for at the end of the scope.
+...
+
+// Run DMD on the file "myprog.d", specifying a few compiler switches:
+auto dmdPid = spawnProcess(["dmd", "-O", "-release", "-inline", "myprog.d" ]);
+if (wait(dmdPid) != 0)
+    writeln("Compilation failed!");
+---
+
+Environment_variables:
+By default, the child process inherits the environment of the parent
+process, along with any additional variables specified in the $(D env)
+parameter.  If the same variable exists in both the parent's environment
+and in $(D env), the latter takes precedence.
+
+If the $(LREF Config.newEnv) flag is set in $(D config), the child
+process will $(I not) inherit the parent's environment.  Its entire
+environment will then be determined by $(D env).
+---
+wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv));
+---
+
+Standard_streams:
+The optional arguments $(D stdin), $(D stdout) and $(D stderr) may
+be used to assign arbitrary $(XREF stdio,File) objects as the standard
+input, output and error streams, respectively, of the child process.  The
+former must be opened for reading, while the latter two must be opened for
+writing.  The default is for the child process to inherit the standard
+streams of its parent.
+---
+// Run DMD on the file myprog.d, logging any error messages to a
+// file named errors.log.
+auto logFile = File("errors.log", "w");
+auto pid = spawnProcess(["dmd", "myprog.d"],
+                        std.stdio.stdin,
+                        std.stdio.stdout,
+                        logFile);
+if (wait(pid) != 0)
+    writeln("Compilation failed. See errors.log for details.");
+---
+
+Note that if you pass a $(D File) object that is $(I not)
+one of the standard input/output/error streams of the parent process,
+that stream will by default be $(I closed) in the parent process when
+this function returns.  See the $(LREF Config) documentation below for
+information about how to disable this behaviour.
+
+Beware of buffering issues when passing $(D File) objects to
+$(D spawnProcess).  The child process will inherit the low-level raw
+read/write offset associated with the underlying file descriptor, but
+it will not be aware of any buffered data.  In cases where this matters
+(e.g. when a file should be aligned before being passed on to the
+child process), it may be a good idea to use unbuffered streams, or at
+least ensure all relevant buffers are flushed.
+
+Params:
+args    = An array which contains the program name as the zeroth element
+          and any command-line arguments in the following elements.
+stdin   = The standard input stream of the child process.
+          This can be any $(XREF stdio,File) that is opened for reading.
+          By default the child process inherits the parent's input
+          stream.
+stdout  = The standard output stream of the child process.
+          This can be any $(XREF stdio,File) that is opened for writing.
+          By default the child process inherits the parent's output stream.
+stderr  = The standard error stream of the child process.
+          This can be any $(XREF stdio,File) that is opened for writing.
+          By default the child process inherits the parent's error stream.
+env     = Additional environment variables for the child process.
+config  = Flags that control process creation. See $(LREF Config)
+          for an overview of available flags.
+workDir = The working directory for the new process.
+          By default the child process inherits the parent's working
+          directory.
+
+Returns:
+A $(LREF Pid) object that corresponds to the spawned process.
+
+Throws:
+$(LREF ProcessException) on failure to start the process.$(BR)
+$(XREF stdio,StdioException) on failure to pass one of the streams
+    to the child process (Windows only).$(BR)
+$(CXREF exception,RangeError) if $(D args) is empty.
+*/
+Pid spawnProcess(in char[][] args,
+                 File stdin = std.stdio.stdin,
+                 File stdout = std.stdio.stdout,
+                 File stderr = std.stdio.stderr,
+                 const string[string] env = null,
+                 Config config = Config.none,
+                 in char[] workDir = null)
+    @trusted // TODO: Should be @safe
+{
+    version (Windows)    auto  args2 = escapeShellArguments(args);
+    else version (Posix) alias args2 = args;
+    return spawnProcessImpl(args2, stdin, stdout, stderr, env, config, workDir);
+}
+
+/// ditto
+Pid spawnProcess(in char[][] args,
+                 const string[string] env,
+                 Config config = Config.none,
+                 in char[] workDir = null)
+    @trusted // TODO: Should be @safe
+{
+    return spawnProcess(args,
+                        std.stdio.stdin,
+                        std.stdio.stdout,
+                        std.stdio.stderr,
+                        env,
+                        config,
+                        workDir);
+}
+
+/// ditto
+Pid spawnProcess(in char[] program,
+                 File stdin = std.stdio.stdin,
+                 File stdout = std.stdio.stdout,
+                 File stderr = std.stdio.stderr,
+                 const string[string] env = null,
+                 Config config = Config.none,
+                 in char[] workDir = null)
+    @trusted
+{
+    return spawnProcess((&program)[0 .. 1],
+                        stdin, stdout, stderr, env, config, workDir);
+}
+
+/// ditto
+Pid spawnProcess(in char[] program,
+                 const string[string] env,
+                 Config config = Config.none,
+                 in char[] workDir = null)
+    @trusted
+{
+    return spawnProcess((&program)[0 .. 1], env, config, workDir);
+}
+
+/*
+Implementation of spawnProcess() for POSIX.
+
+envz should be a zero-terminated array of zero-terminated strings
+on the form "var=value".
+*/
+version (Posix)
+private Pid spawnProcessImpl(in char[][] args,
+                             File stdin,
+                             File stdout,
+                             File stderr,
+                             const string[string] env,
+                             Config config,
+                             in char[] workDir)
+    @trusted // TODO: Should be @safe
+{
+    import core.exception: RangeError;
+    import std.path : isDirSeparator;
+    import std.algorithm : any;
+    import std.string : toStringz;
+
+    if (args.empty) throw new RangeError();
+    const(char)[] name = args[0];
+    if (any!isDirSeparator(name))
+    {
+        if (!isExecutable(name))
+            throw new ProcessException(text("Not an executable file: ", name));
+    }
+    else
+    {
+        name = searchPathFor(name);
+        if (name is null)
+            throw new ProcessException(text("Executable file not found: ", args[0]));
+    }
+
+    // Convert program name and arguments to C-style strings.
+    auto argz = new const(char)*[args.length+1];
+    argz[0] = toStringz(name);
+    foreach (i; 1 .. args.length) argz[i] = toStringz(args[i]);
+    argz[$-1] = null;
+
+    // Prepare environment.
+    auto envz = createEnv(env, !(config & Config.newEnv));
+
+    // Open the working directory.
+    // We use open in the parent and fchdir in the child
+    // so that most errors (directory doesn't exist, not a directory)
+    // can be propagated as exceptions before forking.
+    int workDirFD = -1;
+    scope(exit) if (workDirFD >= 0) close(workDirFD);
+    if (workDir.length)
+    {
+        import core.sys.posix.fcntl;
+        workDirFD = open(workDir.tempCString(), O_RDONLY);
+        if (workDirFD < 0)
+            throw ProcessException.newFromErrno("Failed to open working directory");
+        stat_t s;
+        if (fstat(workDirFD, &s) < 0)
+            throw ProcessException.newFromErrno("Failed to stat working directory");
+        if (!S_ISDIR(s.st_mode))
+            throw new ProcessException("Not a directory: " ~ cast(string)workDir);
+    }
+
+    int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); }
+
+    // Get the file descriptors of the streams.
+    // These could potentially be invalid, but that is OK.  If so, later calls
+    // to dup2() and close() will just silently fail without causing any harm.
+    auto stdinFD  = getFD(stdin);
+    auto stdoutFD = getFD(stdout);
+    auto stderrFD = getFD(stderr);
+
+    auto id = fork();
+    if (id < 0)
+        throw ProcessException.newFromErrno("Failed to spawn new process");
+    if (id == 0)
+    {
+        // Child process
+
+        // Set the working directory.
+        if (workDirFD >= 0)
+        {
+            if (fchdir(workDirFD) < 0)
+            {
+                // Fail. It is dangerous to run a program
+                // in an unexpected working directory.
+                core.sys.posix.stdio.perror("spawnProcess(): " ~
+                    "Failed to set working directory");
+                core.sys.posix.unistd._exit(1);
+                assert(0);
+            }
+            close(workDirFD);
+        }
+
+        // Redirect streams and close the old file descriptors.
+        // In the case that stderr is redirected to stdout, we need
+        // to backup the file descriptor since stdout may be redirected
+        // as well.
+        if (stderrFD == STDOUT_FILENO)  stderrFD = dup(stderrFD);
+        dup2(stdinFD,  STDIN_FILENO);
+        dup2(stdoutFD, STDOUT_FILENO);
+        dup2(stderrFD, STDERR_FILENO);
+
+        // Ensure that the standard streams aren't closed on execute, and
+        // optionally close all other file descriptors.
+        setCLOEXEC(STDIN_FILENO, false);
+        setCLOEXEC(STDOUT_FILENO, false);
+        setCLOEXEC(STDERR_FILENO, false);
+        if (!(config & Config.inheritFDs))
+        {
+            import core.sys.posix.sys.resource;
+            rlimit r;
+            getrlimit(RLIMIT_NOFILE, &r);
+            foreach (i; 3 .. cast(int) r.rlim_cur) close(i);
+        }
+
+        // Close the old file descriptors, unless they are
+        // either of the standard streams.
+        if (stdinFD  > STDERR_FILENO)  close(stdinFD);
+        if (stdoutFD > STDERR_FILENO)  close(stdoutFD);
+        if (stderrFD > STDERR_FILENO)  close(stderrFD);
+
+        // Execute program.
+        core.sys.posix.unistd.execve(argz[0], argz.ptr, envz);
+
+        // If execution fails, exit as quickly as possible.
+        core.sys.posix.stdio.perror("spawnProcess(): Failed to execute program");
+        core.sys.posix.unistd._exit(1);
+        assert (0);
+    }
+    else
+    {
+        // Parent process:  Close streams and return.
+        if (!(config & Config.retainStdin ) && stdinFD  > STDERR_FILENO
+                                            && stdinFD  != getFD(std.stdio.stdin ))
+            stdin.close();
+        if (!(config & Config.retainStdout) && stdoutFD > STDERR_FILENO
+                                            && stdoutFD != getFD(std.stdio.stdout))
+            stdout.close();
+        if (!(config & Config.retainStderr) && stderrFD > STDERR_FILENO
+                                            && stderrFD != getFD(std.stdio.stderr))
+            stderr.close();
+        return new Pid(id);
+    }
+}
+
+/*
+Implementation of spawnProcess() for Windows.
+
+commandLine must contain the entire command line, properly
+quoted/escaped as required by CreateProcessW().
+
+envz must be a pointer to a block of UTF-16 characters on the form
+"var1=value1\0var2=value2\0...varN=valueN\0\0".
+*/
+version (Windows)
+private Pid spawnProcessImpl(in char[] commandLine,
+                             File stdin,
+                             File stdout,
+                             File stderr,
+                             const string[string] env,
+                             Config config,
+                             in char[] workDir)
+    @trusted
+{
+    import core.exception: RangeError;
+
+    if (commandLine.empty) throw new RangeError("Command line is empty");
+
+    // Prepare environment.
+    auto envz = createEnv(env, !(config & Config.newEnv));
+
+    // Startup info for CreateProcessW().
+    STARTUPINFO_W startinfo;
+    startinfo.cb = startinfo.sizeof;
+    static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; }
+
+    // Extract file descriptors and HANDLEs from the streams and make the
+    // handles inheritable.
+    static void prepareStream(ref File file, DWORD stdHandle, string which,
+                              out int fileDescriptor, out HANDLE handle)
+    {
+        fileDescriptor = getFD(file);
+        handle = null;
+        if (fileDescriptor >= 0)
+            handle = file.windowsHandle;
+        // Windows GUI applications have a fd but not a valid Windows HANDLE.
+        if (handle is null || handle == INVALID_HANDLE_VALUE)
+            handle = GetStdHandle(stdHandle);
+
+        DWORD dwFlags;
+        if (GetHandleInformation(handle, &dwFlags))
+        {
+            if (!(dwFlags & HANDLE_FLAG_INHERIT))
+            {
+                if (!SetHandleInformation(handle,
+                                          HANDLE_FLAG_INHERIT,
+                                          HANDLE_FLAG_INHERIT))
+                {
+                    throw new StdioException(
+                        "Failed to make "~which~" stream inheritable by child process ("
+                        ~sysErrorString(GetLastError()) ~ ')',
+                        0);
+                }
+            }
+        }
+    }
+    int stdinFD = -1, stdoutFD = -1, stderrFD = -1;
+    prepareStream(stdin,  STD_INPUT_HANDLE,  "stdin" , stdinFD,  startinfo.hStdInput );
+    prepareStream(stdout, STD_OUTPUT_HANDLE, "stdout", stdoutFD, startinfo.hStdOutput);
+    prepareStream(stderr, STD_ERROR_HANDLE,  "stderr", stderrFD, startinfo.hStdError );
+
+    if ((startinfo.hStdInput  != null && startinfo.hStdInput  != INVALID_HANDLE_VALUE)
+     || (startinfo.hStdOutput != null && startinfo.hStdOutput != INVALID_HANDLE_VALUE)
+     || (startinfo.hStdError  != null && startinfo.hStdError  != INVALID_HANDLE_VALUE))
+        startinfo.dwFlags = STARTF_USESTDHANDLES;
+
+    // Create process.
+    PROCESS_INFORMATION pi;
+    DWORD dwCreationFlags =
+        CREATE_UNICODE_ENVIRONMENT |
+        ((config & Config.suppressConsole) ? CREATE_NO_WINDOW : 0);
+    auto pworkDir = workDir.tempCStringW();     // workaround until Bugzilla 14696 is fixed
+    if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, null, null, true, dwCreationFlags,
+                        envz, workDir.length ? pworkDir : null, &startinfo, &pi))
+        throw ProcessException.newFromLastError("Failed to spawn new process");
+
+    // figure out if we should close any of the streams
+    if (!(config & Config.retainStdin ) && stdinFD  > STDERR_FILENO
+                                        && stdinFD  != getFD(std.stdio.stdin ))
+        stdin.close();
+    if (!(config & Config.retainStdout) && stdoutFD > STDERR_FILENO
+                                        && stdoutFD != getFD(std.stdio.stdout))
+        stdout.close();
+    if (!(config & Config.retainStderr) && stderrFD > STDERR_FILENO
+                                        && stderrFD != getFD(std.stdio.stderr))
+        stderr.close();
+
+    // close the thread handle in the process info structure
+    CloseHandle(pi.hThread);
+
+    return new Pid(pi.dwProcessId, pi.hProcess);
+}
+
+// Converts childEnv to a zero-terminated array of zero-terminated strings
+// on the form "name=value", optionally adding those of the current process'
+// environment strings that are not present in childEnv.  If the parent's
+// environment should be inherited without modification, this function
+// returns environ directly.
+version (Posix)
+private const(char*)* createEnv(const string[string] childEnv,
+                                bool mergeWithParentEnv)
+{
+    // Determine the number of strings in the parent's environment.
+    int parentEnvLength = 0;
+    if (mergeWithParentEnv)
+    {
+        if (childEnv.length == 0) return environ;
+        while (environ[parentEnvLength] != null) ++parentEnvLength;
+    }
+
+    // Convert the "new" variables to C-style strings.
+    auto envz = new const(char)*[parentEnvLength + childEnv.length + 1];
+    int pos = 0;
+    foreach (var, val; childEnv)
+        envz[pos++] = (var~'='~val~'\0').ptr;
+
+    // Add the parent's environment.
+    foreach (environStr; environ[0 .. parentEnvLength])
+    {
+        int eqPos = 0;
+        while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') ++eqPos;
+        if (environStr[eqPos] != '=') continue;
+        auto var = environStr[0 .. eqPos];
+        if (var in childEnv) continue;
+        envz[pos++] = environStr;
+    }
+    envz[pos] = null;
+    return envz.ptr;
+}
+
+version (Posix) unittest
+{
+    auto e1 = createEnv(null, false);
+    assert (e1 != null && *e1 == null);
+
+    auto e2 = createEnv(null, true);
+    assert (e2 != null);
+    int i = 0;
+    for (; environ[i] != null; ++i)
+    {
+        assert (e2[i] != null);
+        import core.stdc.string;
+        assert (strcmp(e2[i], environ[i]) == 0);
+    }
+    assert (e2[i] == null);
+
+    auto e3 = createEnv(["foo" : "bar", "hello" : "world"], false);
+    assert (e3 != null && e3[0] != null && e3[1] != null && e3[2] == null);
+    assert ((e3[0][0 .. 8] == "foo=bar\0" && e3[1][0 .. 12] == "hello=world\0")
+         || (e3[0][0 .. 12] == "hello=world\0" && e3[1][0 .. 8] == "foo=bar\0"));
+}
+
+
+// Converts childEnv to a Windows environment block, which is on the form
+// "name1=value1\0name2=value2\0...nameN=valueN\0\0", optionally adding
+// those of the current process' environment strings that are not present
+// in childEnv.  Returns null if the parent's environment should be
+// inherited without modification, as this is what is expected by
+// CreateProcess().
+version (Windows)
+private LPVOID createEnv(const string[string] childEnv,
+                         bool mergeWithParentEnv)
+{
+    if (mergeWithParentEnv && childEnv.length == 0) return null;
+    import std.array : appender;
+    import std.uni : toUpper;
+    auto envz = appender!(wchar[])();
+    void put(string var, string val)
+    {
+        envz.put(var);
+        envz.put('=');
+        envz.put(val);
+        envz.put(cast(wchar) '\0');
+    }
+
+    // Add the variables in childEnv, removing them from parentEnv
+    // if they exist there too.
+    auto parentEnv = mergeWithParentEnv ? environment.toAA() : null;
+    foreach (k, v; childEnv)
+    {
+        auto uk = toUpper(k);
+        put(uk, v);
+        if (uk in parentEnv) parentEnv.remove(uk);
+    }
+
+    // Add remaining parent environment variables.
+    foreach (k, v; parentEnv) put(k, v);
+
+    // Two final zeros are needed in case there aren't any environment vars,
+    // and the last one does no harm when there are.
+    envz.put("\0\0"w);
+    return envz.data.ptr;
+}
+
+version (Windows) unittest
+{
+    assert (createEnv(null, true) == null);
+    assert ((cast(wchar*) createEnv(null, false))[0 .. 2] == "\0\0"w);
+    auto e1 = (cast(wchar*) createEnv(["foo":"bar", "ab":"c"], false))[0 .. 14];
+    assert (e1 == "FOO=bar\0AB=c\0\0"w || e1 == "AB=c\0FOO=bar\0\0"w);
+}
+
+// Searches the PATH variable for the given executable file,
+// (checking that it is in fact executable).
+version (Posix)
+private string searchPathFor(in char[] executable)
+    @trusted //TODO: @safe nothrow
+{
+    import std.algorithm : splitter;
+
+    auto pathz = core.stdc.stdlib.getenv("PATH");
+    if (pathz == null)  return null;
+
+    foreach (dir; splitter(to!string(pathz), ':'))
+    {
+        auto execPath = buildPath(dir, executable);
+        if (isExecutable(execPath))  return execPath;
+    }
+
+    return null;
+}
+
+// Checks whether the file exists and can be executed by the
+// current user.
+version (Posix)
+private bool isExecutable(in char[] path) @trusted nothrow @nogc //TODO: @safe
+{
+    return (access(path.tempCString(), X_OK) == 0);
+}
+
+version (Posix) unittest
+{
+    import std.algorithm;
+    auto lsPath = searchPathFor("ls");
+    assert (!lsPath.empty);
+    assert (lsPath[0] == '/');
+    assert (lsPath.endsWith("ls"));
+    auto unlikely = searchPathFor("lkmqwpoialhggyaofijadsohufoiqezm");
+    assert (unlikely is null, "Are you kidding me?");
+}
+
+// Sets or unsets the FD_CLOEXEC flag on the given file descriptor.
+version (Posix)
+private void setCLOEXEC(int fd, bool on)
+{
+    import core.sys.posix.fcntl;
+    auto flags = fcntl(fd, F_GETFD);
+    if (flags >= 0)
+    {
+        if (on) flags |= FD_CLOEXEC;
+        else    flags &= ~(cast(typeof(flags)) FD_CLOEXEC);
+        flags = fcntl(fd, F_SETFD, flags);
+    }
+    assert (flags != -1 || .errno == EBADF);
+}
+
+unittest // Command line arguments in spawnProcess().
+{
+    version (Windows) TestScript prog =
+       "if not [%~1]==[foo] ( exit 1 )
+        if not [%~2]==[bar] ( exit 2 )
+        exit 0";
+    else version (Posix) TestScript prog =
+       `if test "$1" != "foo"; then exit 1; fi
+        if test "$2" != "bar"; then exit 2; fi
+        exit 0`;
+    assert (wait(spawnProcess(prog.path)) == 1);
+    assert (wait(spawnProcess([prog.path])) == 1);
+    assert (wait(spawnProcess([prog.path, "foo"])) == 2);
+    assert (wait(spawnProcess([prog.path, "foo", "baz"])) == 2);
+    assert (wait(spawnProcess([prog.path, "foo", "bar"])) == 0);
+}
+
+unittest // Environment variables in spawnProcess().
+{
+    // We really should use set /a on Windows, but Wine doesn't support it.
+    version (Windows) TestScript envProg =
+       `if [%STD_PROCESS_UNITTEST1%] == [1] (
+            if [%STD_PROCESS_UNITTEST2%] == [2] (exit 3)
+            exit 1
+        )
+        if [%STD_PROCESS_UNITTEST1%] == [4] (
+            if [%STD_PROCESS_UNITTEST2%] == [2] (exit 6)
+            exit 4
+        )
+        if [%STD_PROCESS_UNITTEST2%] == [2] (exit 2)
+        exit 0`;
+    version (Posix) TestScript envProg =
+       `if test "$std_process_unittest1" = ""; then
+            std_process_unittest1=0
+        fi
+        if test "$std_process_unittest2" = ""; then
+            std_process_unittest2=0
+        fi
+        exit $(($std_process_unittest1+$std_process_unittest2))`;
+
+    environment.remove("std_process_unittest1"); // Just in case.
+    environment.remove("std_process_unittest2");
+    assert (wait(spawnProcess(envProg.path)) == 0);
+    assert (wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0);
+
+    environment["std_process_unittest1"] = "1";
+    assert (wait(spawnProcess(envProg.path)) == 1);
+    assert (wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0);
+
+    auto env = ["std_process_unittest2" : "2"];
+    assert (wait(spawnProcess(envProg.path, env)) == 3);
+    assert (wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2);
+
+    env["std_process_unittest1"] = "4";
+    assert (wait(spawnProcess(envProg.path, env)) == 6);
+    assert (wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6);
+
+    environment.remove("std_process_unittest1");
+    assert (wait(spawnProcess(envProg.path, env)) == 6);
+    assert (wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6);
+}
+
+unittest // Stream redirection in spawnProcess().
+{
+    import std.string;
+    version (Windows) TestScript prog =
+       "set /p INPUT=
+        echo %INPUT% output %~1
+        echo %INPUT% error %~2 1>&2";
+    else version (Posix) TestScript prog =
+       "read INPUT
+        echo $INPUT output $1
+        echo $INPUT error $2 >&2";
+
+    // Pipes
+    auto pipei = pipe();
+    auto pipeo = pipe();
+    auto pipee = pipe();
+    auto pid = spawnProcess([prog.path, "foo", "bar"],
+                             pipei.readEnd, pipeo.writeEnd, pipee.writeEnd);
+    pipei.writeEnd.writeln("input");
+    pipei.writeEnd.flush();
+    assert (pipeo.readEnd.readln().chomp() == "input output foo");
+    assert (pipee.readEnd.readln().chomp().stripRight() == "input error bar");
+    wait(pid);
+
+    // Files
+    import std.ascii, std.file, std.uuid;
+    auto pathi = buildPath(tempDir(), randomUUID().toString());
+    auto patho = buildPath(tempDir(), randomUUID().toString());
+    auto pathe = buildPath(tempDir(), randomUUID().toString());
+    std.file.write(pathi, "INPUT"~std.ascii.newline);
+    auto filei = File(pathi, "r");
+    auto fileo = File(patho, "w");
+    auto filee = File(pathe, "w");
+    pid = spawnProcess([prog.path, "bar", "baz" ], filei, fileo, filee);
+    wait(pid);
+    assert (readText(patho).chomp() == "INPUT output bar");
+    assert (readText(pathe).chomp().stripRight() == "INPUT error baz");
+    remove(pathi);
+    remove(patho);
+    remove(pathe);
+}
+
+unittest // Error handling in spawnProcess()
+{
+    assertThrown!ProcessException(spawnProcess("ewrgiuhrifuheiohnmnvqweoijwf"));
+    assertThrown!ProcessException(spawnProcess("./rgiuhrifuheiohnmnvqweoijwf"));
+}
+
+unittest // Specifying a working directory.
+{
+    TestScript prog = "echo foo>bar";
+
+    auto directory = uniqueTempPath();
+    mkdir(directory);
+    scope(exit) rmdirRecurse(directory);
+
+    auto pid = spawnProcess([prog.path], null, Config.none, directory);
+    wait(pid);
+    assert(exists(buildPath(directory, "bar")));
+}
+
+unittest // Specifying a bad working directory.
+{
+    TestScript prog = "echo";
+
+    auto directory = uniqueTempPath();
+    assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory));
+
+    std.file.write(directory, "foo");
+    scope(exit) remove(directory);
+    assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory));
+}
+
+unittest // Specifying empty working directory.
+{
+    TestScript prog = "";
+
+    string directory = "";
+    assert(directory.ptr && !directory.length);
+    spawnProcess([prog.path], null, Config.none, directory).wait();
+}
+
+unittest // Reopening the standard streams (issue 13258)
+{
+    import std.string;
+    void fun()
+    {
+        spawnShell("echo foo").wait();
+        spawnShell("echo bar").wait();
+    }
+
+    auto tmpFile = uniqueTempPath();
+    scope(exit) if (exists(tmpFile)) remove(tmpFile);
+
+    {
+        auto oldOut = std.stdio.stdout;
+        scope(exit) std.stdio.stdout = oldOut;
+
+        std.stdio.stdout = File(tmpFile, "w");
+        fun();
+        std.stdio.stdout.close();
+    }
+
+    auto lines = readText(tmpFile).splitLines();
+    assert(lines == ["foo", "bar"]);
+}
+
+version (Windows)
+unittest // MSVCRT workaround (issue 14422)
+{
+    auto fn = uniqueTempPath();
+    std.file.write(fn, "AAAAAAAAAA");
+
+    auto f = File(fn, "a");
+    spawnProcess(["cmd", "/c", "echo BBBBB"], std.stdio.stdin, f).wait();
+
+    auto data = readText(fn);
+    assert(data == "AAAAAAAAAABBBBB\r\n", data);
+}
+
+/**
+A variation on $(LREF spawnProcess) that runs the given _command through
+the current user's preferred _command interpreter (aka. shell).
+
+The string $(D command) is passed verbatim to the shell, and is therefore
+subject to its rules about _command structure, argument/filename quoting
+and escaping of special characters.
+The path to the shell executable defaults to $(LREF nativeShell).
+
+In all other respects this function works just like $(D spawnProcess).
+Please refer to the $(LREF spawnProcess) documentation for descriptions
+of the other function parameters, the return value and any exceptions
+that may be thrown.
+---
+// Run the command/program "foo" on the file named "my file.txt", and
+// redirect its output into foo.log.
+auto pid = spawnShell(`foo "my file.txt" > foo.log`);
+wait(pid);
+---
+
+See_also:
+$(LREF escapeShellCommand), which may be helpful in constructing a
+properly quoted and escaped shell _command line for the current platform.
+*/
+Pid spawnShell(in char[] command,
+               File stdin = std.stdio.stdin,
+               File stdout = std.stdio.stdout,
+               File stderr = std.stdio.stderr,
+               const string[string] env = null,
+               Config config = Config.none,
+               in char[] workDir = null,
+               string shellPath = nativeShell)
+    @trusted // TODO: Should be @safe
+{
+    version (Windows)
+    {
+        // CMD does not parse its arguments like other programs.
+        // It does not use CommandLineToArgvW.
+        // Instead, it treats the first and last quote specially.
+        // See CMD.EXE /? for details.
+        auto args = escapeShellFileName(shellPath)
+                    ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`;
+    }
+    else version (Posix)
+    {
+        const(char)[][3] args;
+        args[0] = shellPath;
+        args[1] = shellSwitch;
+        args[2] = command;
+    }
+    return spawnProcessImpl(args, stdin, stdout, stderr, env, config, workDir);
+}
+
+/// ditto
+Pid spawnShell(in char[] command,
+               const string[string] env,
+               Config config = Config.none,
+               in char[] workDir = null,
+               string shellPath = nativeShell)
+    @trusted // TODO: Should be @safe
+{
+    return spawnShell(command,
+                      std.stdio.stdin,
+                      std.stdio.stdout,
+                      std.stdio.stderr,
+                      env,
+                      config,
+                      workDir,
+                      shellPath);
+}
+
+unittest
+{
+    version (Windows)
+        auto cmd = "echo %FOO%";
+    else version (Posix)
+        auto cmd = "echo $foo";
+    import std.file;
+    auto tmpFile = uniqueTempPath();
+    scope(exit) if (exists(tmpFile)) remove(tmpFile);
+    auto redir = "> \""~tmpFile~'"';
+    auto env = ["foo" : "bar"];
+    assert (wait(spawnShell(cmd~redir, env)) == 0);
+    auto f = File(tmpFile, "a");
+    version(CRuntime_Microsoft) f.seek(0, SEEK_END); // MSVCRT probably seeks to the end when writing, not before
+    assert (wait(spawnShell(cmd, std.stdio.stdin, f, std.stdio.stderr, env)) == 0);
+    f.close();
+    auto output = std.file.readText(tmpFile);
+    assert (output == "bar\nbar\n" || output == "bar\r\nbar\r\n");
+}
+
+version (Windows)
+unittest
+{
+    import std.string;
+    TestScript prog = "echo %0 %*";
+    auto outputFn = uniqueTempPath();
+    scope(exit) if (exists(outputFn)) remove(outputFn);
+    auto args = [`a b c`, `a\b\c\`, `a"b"c"`];
+    auto result = executeShell(
+        escapeShellCommand([prog.path] ~ args)
+        ~ " > " ~
+        escapeShellFileName(outputFn));
+    assert(result.status == 0);
+    auto args2 = outputFn.readText().strip().parseCommandLine()[1..$];
+    assert(args == args2, text(args2));
+}
+
+
+/**
+Flags that control the behaviour of $(LREF spawnProcess) and
+$(LREF spawnShell).
+
+Use bitwise OR to combine flags.
+
+Example:
+---
+auto logFile = File("myapp_error.log", "w");
+
+// Start program, suppressing the console window (Windows only),
+// redirect its error stream to logFile, and leave logFile open
+// in the parent process as well.
+auto pid = spawnProcess("myapp", stdin, stdout, logFile,
+                        Config.retainStderr | Config.suppressConsole);
+scope(exit)
+{
+    auto exitCode = wait(pid);
+    logFile.writeln("myapp exited with code ", exitCode);
+    logFile.close();
+}
+---
+*/
+enum Config
+{
+    none = 0,
+
+    /**
+    By default, the child process inherits the parent's environment,
+    and any environment variables passed to $(LREF spawnProcess) will
+    be added to it.  If this flag is set, the only variables in the
+    child process' environment will be those given to spawnProcess.
+    */
+    newEnv = 1,
+
+    /**
+    Unless the child process inherits the standard input/output/error
+    streams of its parent, one almost always wants the streams closed
+    in the parent when $(LREF spawnProcess) returns.  Therefore, by
+    default, this is done.  If this is not desirable, pass any of these
+    options to spawnProcess.
+    */
+    retainStdin  = 2,
+    retainStdout = 4,                                  /// ditto
+    retainStderr = 8,                                  /// ditto
+
+    /**
+    On Windows, if the child process is a console application, this
+    flag will prevent the creation of a console window.  Otherwise,
+    it will be ignored. On POSIX, $(D suppressConsole) has no effect.
+    */
+    suppressConsole = 16,
+
+    /**
+    On POSIX, open $(LINK2 http://en.wikipedia.org/wiki/File_descriptor,file descriptors)
+    are by default inherited by the child process.  As this may lead
+    to subtle bugs when pipes or multiple threads are involved,
+    $(LREF spawnProcess) ensures that all file descriptors except the
+    ones that correspond to standard input/output/error are closed
+    in the child process when it starts.  Use $(D inheritFDs) to prevent
+    this.
+
+    On Windows, this option has no effect, and any handles which have been
+    explicitly marked as inheritable will always be inherited by the child
+    process.
+    */
+    inheritFDs = 32,
+}
+
+
+/// A handle that corresponds to a spawned process.
+final class Pid
+{
+    /**
+    The process ID number.
+
+    This is a number that uniquely identifies the process on the operating
+    system, for at least as long as the process is running.  Once $(LREF wait)
+    has been called on the $(LREF Pid), this method will return an
+    invalid (negative) process ID.
+    */
+    @property int processID() const @safe pure nothrow
+    {
+        return _processID;
+    }
+
+    /**
+    An operating system handle to the process.
+
+    This handle is used to specify the process in OS-specific APIs.
+    On POSIX, this function returns a $(D core.sys.posix.sys.types.pid_t)
+    with the same value as $(LREF Pid.processID), while on Windows it returns
+    a $(D core.sys.windows.windows.HANDLE).
+
+    Once $(LREF wait) has been called on the $(LREF Pid), this method
+    will return an invalid handle.
+    */
+    // Note: Since HANDLE is a reference, this function cannot be const.
+    version (Windows)
+    @property HANDLE osHandle() @safe pure nothrow
+    {
+        return _handle;
+    }
+    else version (Posix)
+    @property pid_t osHandle() @safe pure nothrow
+    {
+        return _processID;
+    }
+
+private:
+    /*
+    Pid.performWait() does the dirty work for wait() and nonBlockingWait().
+
+    If block == true, this function blocks until the process terminates,
+    sets _processID to terminated, and returns the exit code or terminating
+    signal as described in the wait() documentation.
+
+    If block == false, this function returns immediately, regardless
+    of the status of the process.  If the process has terminated, the
+    function has the exact same effect as the blocking version.  If not,
+    it returns 0 and does not modify _processID.
+    */
+    version (Posix)
+    int performWait(bool block) @trusted
+    {
+        if (_processID == terminated) return _exitCode;
+        int exitCode;
+        while(true)
+        {
+            int status;
+            auto check = waitpid(_processID, &status, block ? 0 : WNOHANG);
+            if (check == -1)
+            {
+                if (errno == ECHILD)
+                {
+                    throw new ProcessException(
+                        "Process does not exist or is not a child process.");
+                }
+                else
+                {
+                    // waitpid() was interrupted by a signal.  We simply
+                    // restart it.
+                    assert (errno == EINTR);
+                    continue;
+                }
+            }
+            if (!block && check == 0) return 0;
+            if (WIFEXITED(status))
+            {
+                exitCode = WEXITSTATUS(status);
+                break;
+            }
+            else if (WIFSIGNALED(status))
+            {
+                exitCode = -WTERMSIG(status);
+                break;
+            }
+            // We check again whether the call should be blocking,
+            // since we don't care about other status changes besides
+            // "exited" and "terminated by signal".
+            if (!block) return 0;
+
+            // Process has stopped, but not terminated, so we continue waiting.
+        }
+        // Mark Pid as terminated, and cache and return exit code.
+        _processID = terminated;
+        _exitCode = exitCode;
+        return exitCode;
+    }
+    else version (Windows)
+    {
+        int performWait(bool block) @trusted
+        {
+            if (_processID == terminated) return _exitCode;
+            assert (_handle != INVALID_HANDLE_VALUE);
+            if (block)
+            {
+                auto result = WaitForSingleObject(_handle, INFINITE);
+                if (result != WAIT_OBJECT_0)
+                    throw ProcessException.newFromLastError("Wait failed.");
+            }
+            if (!GetExitCodeProcess(_handle, cast(LPDWORD)&_exitCode))
+                throw ProcessException.newFromLastError();
+            if (!block && _exitCode == STILL_ACTIVE) return 0;
+            CloseHandle(_handle);
+            _handle = INVALID_HANDLE_VALUE;
+            _processID = terminated;
+            return _exitCode;
+        }
+
+        ~this()
+        {
+            if(_handle != INVALID_HANDLE_VALUE)
+            {
+                CloseHandle(_handle);
+                _handle = INVALID_HANDLE_VALUE;
+            }
+        }
+    }
+
+    // Special values for _processID.
+    enum invalid = -1, terminated = -2;
+
+    // OS process ID number.  Only nonnegative IDs correspond to
+    // running processes.
+    int _processID = invalid;
+
+    // Exit code cached by wait().  This is only expected to hold a
+    // sensible value if _processID == terminated.
+    int _exitCode;
+
+    // Pids are only meant to be constructed inside this module, so
+    // we make the constructor private.
+    version (Windows)
+    {
+        HANDLE _handle = INVALID_HANDLE_VALUE;
+        this(int pid, HANDLE handle) @safe pure nothrow
+        {
+            _processID = pid;
+            _handle = handle;
+        }
+    }
+    else
+    {
+        this(int id) @safe pure nothrow
+        {
+            _processID = id;
+        }
+    }
+}
+
+
+/**
+Waits for the process associated with $(D pid) to terminate, and returns
+its exit status.
+
+In general one should always _wait for child processes to terminate
+before exiting the parent process.  Otherwise, they may become
+"$(WEB en.wikipedia.org/wiki/Zombie_process,zombies)" – processes
+that are defunct, yet still occupy a slot in the OS process table.
+
+If the process has already terminated, this function returns directly.
+The exit code is cached, so that if wait() is called multiple times on
+the same $(LREF Pid) it will always return the same value.
+
+POSIX_specific:
+If the process is terminated by a signal, this function returns a
+negative number whose absolute value is the signal number.
+Since POSIX restricts normal exit codes to the range 0-255, a
+negative return value will always indicate termination by signal.
+Signal codes are defined in the $(D core.sys.posix.signal) module
+(which corresponds to the $(D signal.h) POSIX header).
+
+Throws:
+$(LREF ProcessException) on failure.
+
+Example:
+See the $(LREF spawnProcess) documentation.
+
+See_also:
+$(LREF tryWait), for a non-blocking function.
+*/
+int wait(Pid pid) @safe
+{
+    assert(pid !is null, "Called wait on a null Pid.");
+    return pid.performWait(true);
+}
+
+
+unittest // Pid and wait()
+{
+    version (Windows)    TestScript prog = "exit %~1";
+    else version (Posix) TestScript prog = "exit $1";
+    assert (wait(spawnProcess([prog.path, "0"])) == 0);
+    assert (wait(spawnProcess([prog.path, "123"])) == 123);
+    auto pid = spawnProcess([prog.path, "10"]);
+    assert (pid.processID > 0);
+    version (Windows)    assert (pid.osHandle != INVALID_HANDLE_VALUE);
+    else version (Posix) assert (pid.osHandle == pid.processID);
+    assert (wait(pid) == 10);
+    assert (wait(pid) == 10); // cached exit code
+    assert (pid.processID < 0);
+    version (Windows)    assert (pid.osHandle == INVALID_HANDLE_VALUE);
+    else version (Posix) assert (pid.osHandle < 0);
+}
+
+
+/**
+A non-blocking version of $(LREF wait).
+
+If the process associated with $(D pid) has already terminated,
+$(D tryWait) has the exact same effect as $(D wait).
+In this case, it returns a tuple where the $(D terminated) field
+is set to $(D true) and the $(D status) field has the same
+interpretation as the return value of $(D wait).
+
+If the process has $(I not) yet terminated, this function differs
+from $(D wait) in that does not wait for this to happen, but instead
+returns immediately.  The $(D terminated) field of the returned
+tuple will then be set to $(D false), while the $(D status) field
+will always be 0 (zero).  $(D wait) or $(D tryWait) should then be
+called again on the same $(D Pid) at some later time; not only to
+get the exit code, but also to avoid the process becoming a "zombie"
+when it finally terminates.  (See $(LREF wait) for details).
+
+Returns:
+An $(D std.typecons.Tuple!(bool, "terminated", int, "status")).
+
+Throws:
+$(LREF ProcessException) on failure.
+
+Example:
+---
+auto pid = spawnProcess("dmd myapp.d");
+scope(exit) wait(pid);
+...
+auto dmd = tryWait(pid);
+if (dmd.terminated)
+{
+    if (dmd.status == 0) writeln("Compilation succeeded!");
+    else writeln("Compilation failed");
+}
+else writeln("Still compiling...");
+...
+---
+Note that in this example, the first $(D wait) call will have no
+effect if the process has already terminated by the time $(D tryWait)
+is called.  In the opposite case, however, the $(D scope) statement
+ensures that we always wait for the process if it hasn't terminated
+by the time we reach the end of the scope.
+*/
+auto tryWait(Pid pid) @safe
+{
+    import std.typecons : Tuple;
+    assert(pid !is null, "Called tryWait on a null Pid.");
+    auto code = pid.performWait(false);
+    return Tuple!(bool, "terminated", int, "status")(pid._processID == Pid.terminated, code);
+}
+// unittest: This function is tested together with kill() below.
+
+
+/**
+Attempts to terminate the process associated with $(D pid).
+
+The effect of this function, as well as the meaning of $(D codeOrSignal),
+is highly platform dependent.  Details are given below.  Common to all
+platforms is that this function only $(I initiates) termination of the process,
+and returns immediately.  It does not wait for the process to end,
+nor does it guarantee that the process does in fact get terminated.
+
+Always call $(LREF wait) to wait for a process to complete, even if $(D kill)
+has been called on it.
+
+Windows_specific:
+The process will be
+$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714%28v=vs.100%29.aspx,
+forcefully and abruptly terminated).  If $(D codeOrSignal) is specified, it
+must be a nonnegative number which will be used as the exit code of the process.
+If not, the process wil exit with code 1.  Do not use $(D codeOrSignal = 259),
+as this is a special value (aka. $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189.aspx,STILL_ACTIVE))
+used by Windows to signal that a process has in fact $(I not) terminated yet.
+---
+auto pid = spawnProcess("some_app");
+kill(pid, 10);
+assert (wait(pid) == 10);
+---
+
+POSIX_specific:
+A $(LINK2 http://en.wikipedia.org/wiki/Unix_signal,signal) will be sent to
+the process, whose value is given by $(D codeOrSignal).  Depending on the
+signal sent, this may or may not terminate the process.  Symbolic constants
+for various $(LINK2 http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals,
+POSIX signals) are defined in $(D core.sys.posix.signal), which corresponds to the
+$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html,
+$(D signal.h) POSIX header).  If $(D codeOrSignal) is omitted, the
+$(D SIGTERM) signal will be sent.  (This matches the behaviour of the
+$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html,
+$(D _kill)) shell command.)
+---
+import core.sys.posix.signal: SIGKILL;
+auto pid = spawnProcess("some_app");
+kill(pid, SIGKILL);
+assert (wait(pid) == -SIGKILL); // Negative return value on POSIX!
+---
+
+Throws:
+$(LREF ProcessException) on error (e.g. if codeOrSignal is invalid).
+    Note that failure to terminate the process is considered a "normal"
+    outcome, not an error.$(BR)
+*/
+void kill(Pid pid)
+{
+    version (Windows) kill(pid, 1);
+    else version (Posix)
+    {
+        import core.sys.posix.signal: SIGTERM;
+        kill(pid, SIGTERM);
+    }
+}
+
+/// ditto
+void kill(Pid pid, int codeOrSignal)
+{
+    version (Windows)
+    {
+        if (codeOrSignal < 0) throw new ProcessException("Invalid exit code");
+        // On Windows, TerminateProcess() appears to terminate the
+        // *current* process if it is passed an invalid handle...
+        if (pid.osHandle == INVALID_HANDLE_VALUE)
+            throw new ProcessException("Invalid process handle");
+        if (!TerminateProcess(pid.osHandle, codeOrSignal))
+            throw ProcessException.newFromLastError();
+    }
+    else version (Posix)
+    {
+        import core.sys.posix.signal : kill;
+        if (kill(pid.osHandle, codeOrSignal) == -1)
+            throw ProcessException.newFromErrno();
+    }
+}
+
+unittest // tryWait() and kill()
+{
+    import core.thread;
+    // The test script goes into an infinite loop.
+    version (Windows)
+    {
+        TestScript prog = ":loop
+                           goto loop";
+    }
+    else version (Posix)
+    {
+        import core.sys.posix.signal: SIGTERM, SIGKILL;
+        TestScript prog = "while true; do sleep 1; done";
+    }
+    auto pid = spawnProcess(prog.path);
+    // Android appears to automatically kill sleeping processes very quickly,
+    // so shorten the wait before killing here.
+    version (Android)
+        Thread.sleep(dur!"msecs"(5));
+    else
+        Thread.sleep(dur!"seconds"(1));
+    kill(pid);
+    version (Windows)    assert (wait(pid) == 1);
+    else version (Posix) assert (wait(pid) == -SIGTERM);
+
+    pid = spawnProcess(prog.path);
+    Thread.sleep(dur!"seconds"(1));
+    auto s = tryWait(pid);
+    assert (!s.terminated && s.status == 0);
+    assertThrown!ProcessException(kill(pid, -123)); // Negative code not allowed.
+    version (Windows)    kill(pid, 123);
+    else version (Posix) kill(pid, SIGKILL);
+    do { s = tryWait(pid); } while (!s.terminated);
+    version (Windows)    assert (s.status == 123);
+    else version (Posix) assert (s.status == -SIGKILL);
+    assertThrown!ProcessException(kill(pid));
+}
+
+
+/**
+Creates a unidirectional _pipe.
+
+Data is written to one end of the _pipe and read from the other.
+---
+auto p = pipe();
+p.writeEnd.writeln("Hello World");
+p.writeEnd.flush();
+assert (p.readEnd.readln().chomp() == "Hello World");
+---
+Pipes can, for example, be used for interprocess communication
+by spawning a new process and passing one end of the _pipe to
+the child, while the parent uses the other end.
+(See also $(LREF pipeProcess) and $(LREF pipeShell) for an easier
+way of doing this.)
+---
+// Use cURL to download the dlang.org front page, pipe its
+// output to grep to extract a list of links to ZIP files,
+// and write the list to the file "D downloads.txt":
+auto p = pipe();
+auto outFile = File("D downloads.txt", "w");
+auto cpid = spawnProcess(["curl", "http://dlang.org/download.html"],
+                         std.stdio.stdin, p.writeEnd);
+scope(exit) wait(cpid);
+auto gpid = spawnProcess(["grep", "-o", `http://\S*\.zip`],
+                         p.readEnd, outFile);
+scope(exit) wait(gpid);
+---
+
+Returns:
+A $(LREF Pipe) object that corresponds to the created _pipe.
+
+Throws:
+$(XREF stdio,StdioException) on failure.
+*/
+version (Posix)
+Pipe pipe() @trusted //TODO: @safe
+{
+    int[2] fds;
+    if (core.sys.posix.unistd.pipe(fds) != 0)
+        throw new StdioException("Unable to create pipe");
+    Pipe p;
+    auto readFP = fdopen(fds[0], "r");
+    if (readFP == null)
+        throw new StdioException("Cannot open read end of pipe");
+    p._read = File(readFP, null);
+    auto writeFP = fdopen(fds[1], "w");
+    if (writeFP == null)
+        throw new StdioException("Cannot open write end of pipe");
+    p._write = File(writeFP, null);
+    return p;
+}
+else version (Windows)
+Pipe pipe() @trusted //TODO: @safe
+{
+    // use CreatePipe to create an anonymous pipe
+    HANDLE readHandle;
+    HANDLE writeHandle;
+    if (!CreatePipe(&readHandle, &writeHandle, null, 0))
+    {
+        throw new StdioException(
+            "Error creating pipe (" ~ sysErrorString(GetLastError()) ~ ')',
+            0);
+    }
+
+    scope(failure)
+    {
+        CloseHandle(readHandle);
+        CloseHandle(writeHandle);
+    }
+
+    try
+    {
+        Pipe p;
+        p._read .windowsHandleOpen(readHandle , "r");
+        p._write.windowsHandleOpen(writeHandle, "a");
+        return p;
+    }
+    catch (Exception e)
+    {
+        throw new StdioException("Error attaching pipe (" ~ e.msg ~ ")",
+            0);
+    }
+}
+
+
+/// An interface to a pipe created by the $(LREF pipe) function.
+struct Pipe
+{
+    /// The read end of the pipe.
+    @property File readEnd() @safe nothrow { return _read; }
+
+
+    /// The write end of the pipe.
+    @property File writeEnd() @safe nothrow { return _write; }
+
+
+    /**
+    Closes both ends of the pipe.
+
+    Normally it is not necessary to do this manually, as $(XREF stdio,File)
+    objects are automatically closed when there are no more references
+    to them.
+
+    Note that if either end of the pipe has been passed to a child process,
+    it will only be closed in the parent process.  (What happens in the
+    child process is platform dependent.)
+
+    Throws:
+    $(XREF exception,ErrnoException) if an error occurs.
+    */
+    void close() @safe
+    {
+        _read.close();
+        _write.close();
+    }
+
+private:
+    File _read, _write;
+}
+
+unittest
+{
+    import std.string;
+    auto p = pipe();
+    p.writeEnd.writeln("Hello World");
+    p.writeEnd.flush();
+    assert (p.readEnd.readln().chomp() == "Hello World");
+    p.close();
+    assert (!p.readEnd.isOpen);
+    assert (!p.writeEnd.isOpen);
+}
+
+
+/**
+Starts a new process, creating pipes to redirect its standard
+input, output and/or error streams.
+
+$(D pipeProcess) and $(D pipeShell) are convenient wrappers around
+$(LREF spawnProcess) and $(LREF spawnShell), respectively, and
+automate the task of redirecting one or more of the child process'
+standard streams through pipes.  Like the functions they wrap,
+these functions return immediately, leaving the child process to
+execute in parallel with the invoking process.  It is recommended
+to always call $(LREF wait) on the returned $(LREF ProcessPipes.pid),
+as detailed in the documentation for $(D wait).
+
+The $(D args)/$(D program)/$(D command), $(D env) and $(D config)
+parameters are forwarded straight to the underlying spawn functions,
+and we refer to their documentation for details.
+
+Params:
+args      = An array which contains the program name as the zeroth element
+            and any command-line arguments in the following elements.
+            (See $(LREF spawnProcess) for details.)
+program   = The program name, $(I without) command-line arguments.
+            (See $(LREF spawnProcess) for details.)
+command   = A shell command which is passed verbatim to the command
+            interpreter.  (See $(LREF spawnShell) for details.)
+redirect  = Flags that determine which streams are redirected, and
+            how.  See $(LREF Redirect) for an overview of available
+            flags.
+env       = Additional environment variables for the child process.
+            (See $(LREF spawnProcess) for details.)
+config    = Flags that control process creation. See $(LREF Config)
+            for an overview of available flags, and note that the
+            $(D retainStd...) flags have no effect in this function.
+workDir   = The working directory for the new process.
+            By default the child process inherits the parent's working
+            directory.
+shellPath = The path to the shell to use to run the specified program.
+            By default this is $(LREF nativeShell).
+
+Returns:
+A $(LREF ProcessPipes) object which contains $(XREF stdio,File)
+handles that communicate with the redirected streams of the child
+process, along with a $(LREF Pid) object that corresponds to the
+spawned process.
+
+Throws:
+$(LREF ProcessException) on failure to start the process.$(BR)
+$(XREF stdio,StdioException) on failure to redirect any of the streams.$(BR)
+
+Example:
+---
+// my_application writes to stdout and might write to stderr
+auto pipes = pipeProcess("my_application", Redirect.stdout | Redirect.stderr);
+scope(exit) wait(pipes.pid);
+
+// Store lines of output.
+string[] output;
+foreach (line; pipes.stdout.byLine) output ~= line.idup;
+
+// Store lines of errors.
+string[] errors;
+foreach (line; pipes.stderr.byLine) errors ~= line.idup;
+
+
+// sendmail expects to read from stdin
+pipes = pipeProcess(["/usr/bin/sendmail", "-t"], Redirect.stdin);
+pipes.stdin.writeln("To: you");
+pipes.stdin.writeln("From: me");
+pipes.stdin.writeln("Subject: dlang");
+pipes.stdin.writeln("");
+pipes.stdin.writeln(message);
+
+// a single period tells sendmail we are finished
+pipes.stdin.writeln(".");
+
+// but at this point sendmail might not see it, we need to flush
+pipes.stdin.flush();
+
+// sendmail happens to exit on ".", but some you have to close the file:
+pipes.stdin.close();
+
+// otherwise this wait will wait forever
+wait(pipes.pid);
+
+---
+*/
+ProcessPipes pipeProcess(in char[][] args,
+                         Redirect redirect = Redirect.all,
+                         const string[string] env = null,
+                         Config config = Config.none,
+                         in char[] workDir = null)
+    @safe
+{
+    return pipeProcessImpl!spawnProcess(args, redirect, env, config, workDir);
+}
+
+/// ditto
+ProcessPipes pipeProcess(in char[] program,
+                         Redirect redirect = Redirect.all,
+                         const string[string] env = null,
+                         Config config = Config.none,
+                         in char[] workDir = null)
+    @safe
+{
+    return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir);
+}
+
+/// ditto
+ProcessPipes pipeShell(in char[] command,
+                       Redirect redirect = Redirect.all,
+                       const string[string] env = null,
+                       Config config = Config.none,
+                       in char[] workDir = null,
+                       string shellPath = nativeShell)
+    @safe
+{
+    return pipeProcessImpl!spawnShell(command,
+                                      redirect,
+                                      env,
+                                      config,
+                                      workDir,
+                                      shellPath);
+}
+
+// Implementation of the pipeProcess() family of functions.
+private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...)
+                                    (Cmd command,
+                                     Redirect redirectFlags,
+                                     const string[string] env = null,
+                                     Config config = Config.none,
+                                     in char[] workDir = null,
+                                     ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init)
+    @trusted //TODO: @safe
+{
+    File childStdin, childStdout, childStderr;
+    ProcessPipes pipes;
+    pipes._redirectFlags = redirectFlags;
+
+    if (redirectFlags & Redirect.stdin)
+    {
+        auto p = pipe();
+        childStdin = p.readEnd;
+        pipes._stdin = p.writeEnd;
+    }
+    else
+    {
+        childStdin = std.stdio.stdin;
+    }
+
+    if (redirectFlags & Redirect.stdout)
+    {
+        if ((redirectFlags & Redirect.stdoutToStderr) != 0)
+            throw new StdioException("Cannot create pipe for stdout AND "
+                                     ~"redirect it to stderr", 0);
+        auto p = pipe();
+        childStdout = p.writeEnd;
+        pipes._stdout = p.readEnd;
+    }
+    else
+    {
+        childStdout = std.stdio.stdout;
+    }
+
+    if (redirectFlags & Redirect.stderr)
+    {
+        if ((redirectFlags & Redirect.stderrToStdout) != 0)
+            throw new StdioException("Cannot create pipe for stderr AND "
+                                     ~"redirect it to stdout", 0);
+        auto p = pipe();
+        childStderr = p.writeEnd;
+        pipes._stderr = p.readEnd;
+    }
+    else
+    {
+        childStderr = std.stdio.stderr;
+    }
+
+    if (redirectFlags & Redirect.stdoutToStderr)
+    {
+        if (redirectFlags & Redirect.stderrToStdout)
+        {
+            // We know that neither of the other options have been
+            // set, so we assign the std.stdio.std* streams directly.
+            childStdout = std.stdio.stderr;
+            childStderr = std.stdio.stdout;
+        }
+        else
+        {
+            childStdout = childStderr;
+        }
+    }
+    else if (redirectFlags & Redirect.stderrToStdout)
+    {
+        childStderr = childStdout;
+    }
+
+    config &= ~(Config.retainStdin | Config.retainStdout | Config.retainStderr);
+    pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr,
+                           env, config, workDir, extraArgs);
+    return pipes;
+}
+
+
+/**
+Flags that can be passed to $(LREF pipeProcess) and $(LREF pipeShell)
+to specify which of the child process' standard streams are redirected.
+Use bitwise OR to combine flags.
+*/
+enum Redirect
+{
+    /// Redirect the standard input, output or error streams, respectively.
+    stdin = 1,
+    stdout = 2,                             /// ditto
+    stderr = 4,                             /// ditto
+
+    /**
+    Redirect _all three streams.  This is equivalent to
+    $(D Redirect.stdin | Redirect.stdout | Redirect.stderr).
+    */
+    all = stdin | stdout | stderr,
+
+    /**
+    Redirect the standard error stream into the standard output stream.
+    This can not be combined with $(D Redirect.stderr).
+    */
+    stderrToStdout = 8,
+
+    /**
+    Redirect the standard output stream into the standard error stream.
+    This can not be combined with $(D Redirect.stdout).
+    */
+    stdoutToStderr = 16,
+}
+
+unittest
+{
+    import std.string;
+    version (Windows) TestScript prog =
+       "call :sub %~1 %~2 0
+        call :sub %~1 %~2 1
+        call :sub %~1 %~2 2
+        call :sub %~1 %~2 3
+        exit 3
+
+        :sub
+        set /p INPUT=
+        if -%INPUT%-==-stop- ( exit %~3 )
+        echo %INPUT% %~1
+        echo %INPUT% %~2 1>&2";
+    else version (Posix) TestScript prog =
+       `for EXITCODE in 0 1 2 3; do
+            read INPUT
+            if test "$INPUT" = stop; then break; fi
+            echo "$INPUT $1"
+            echo "$INPUT $2" >&2
+        done
+        exit $EXITCODE`;
+    auto pp = pipeProcess([prog.path, "bar", "baz"]);
+    pp.stdin.writeln("foo");
+    pp.stdin.flush();
+    assert (pp.stdout.readln().chomp() == "foo bar");
+    assert (pp.stderr.readln().chomp().stripRight() == "foo baz");
+    pp.stdin.writeln("1234567890");
+    pp.stdin.flush();
+    assert (pp.stdout.readln().chomp() == "1234567890 bar");
+    assert (pp.stderr.readln().chomp().stripRight() == "1234567890 baz");
+    pp.stdin.writeln("stop");
+    pp.stdin.flush();
+    assert (wait(pp.pid) == 2);
+
+    pp = pipeProcess([prog.path, "12345", "67890"],
+                     Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout);
+    pp.stdin.writeln("xyz");
+    pp.stdin.flush();
+    assert (pp.stdout.readln().chomp() == "xyz 12345");
+    assert (pp.stdout.readln().chomp().stripRight() == "xyz 67890");
+    pp.stdin.writeln("stop");
+    pp.stdin.flush();
+    assert (wait(pp.pid) == 1);
+
+    pp = pipeShell(escapeShellCommand(prog.path, "AAAAA", "BBB"),
+                   Redirect.stdin | Redirect.stdoutToStderr | Redirect.stderr);
+    pp.stdin.writeln("ab");
+    pp.stdin.flush();
+    assert (pp.stderr.readln().chomp() == "ab AAAAA");
+    assert (pp.stderr.readln().chomp().stripRight() == "ab BBB");
+    pp.stdin.writeln("stop");
+    pp.stdin.flush();
+    assert (wait(pp.pid) == 1);
+}
+
+unittest
+{
+    TestScript prog = "exit 0";
+    assertThrown!StdioException(pipeProcess(
+        prog.path,
+        Redirect.stdout | Redirect.stdoutToStderr));
+    assertThrown!StdioException(pipeProcess(
+        prog.path,
+        Redirect.stderr | Redirect.stderrToStdout));
+    auto p = pipeProcess(prog.path, Redirect.stdin);
+    assertThrown!Error(p.stdout);
+    assertThrown!Error(p.stderr);
+    wait(p.pid);
+    p = pipeProcess(prog.path, Redirect.stderr);
+    assertThrown!Error(p.stdin);
+    assertThrown!Error(p.stdout);
+    wait(p.pid);
+}
+
+/**
+Object which contains $(XREF stdio,File) handles that allow communication
+with a child process through its standard streams.
+*/
+struct ProcessPipes
+{
+    /// The $(LREF Pid) of the child process.
+    @property Pid pid() @safe nothrow
+    {
+        assert(_pid !is null);
+        return _pid;
+    }
+
+    /**
+    An $(XREF stdio,File) that allows writing to the child process'
+    standard input stream.
+
+    Throws:
+    $(OBJECTREF Error) if the child process' standard input stream hasn't
+    been redirected.
+    */
+    @property File stdin() @safe nothrow
+    {
+        if ((_redirectFlags & Redirect.stdin) == 0)
+            throw new Error("Child process' standard input stream hasn't "
+                            ~"been redirected.");
+        return _stdin;
+    }
+
+    /**
+    An $(XREF stdio,File) that allows reading from the child process'
+    standard output stream.
+
+    Throws:
+    $(OBJECTREF Error) if the child process' standard output stream hasn't
+    been redirected.
+    */
+    @property File stdout() @safe nothrow
+    {
+        if ((_redirectFlags & Redirect.stdout) == 0)
+            throw new Error("Child process' standard output stream hasn't "
+                            ~"been redirected.");
+        return _stdout;
+    }
+
+    /**
+    An $(XREF stdio,File) that allows reading from the child process'
+    standard error stream.
+
+    Throws:
+    $(OBJECTREF Error) if the child process' standard error stream hasn't
+    been redirected.
+    */
+    @property File stderr() @safe nothrow
+    {
+        if ((_redirectFlags & Redirect.stderr) == 0)
+            throw new Error("Child process' standard error stream hasn't "
+                            ~"been redirected.");
+        return _stderr;
+    }
+
+private:
+    Redirect _redirectFlags;
+    Pid _pid;
+    File _stdin, _stdout, _stderr;
+}
+
+
+
+/**
+Executes the given program or shell command and returns its exit
+code and output.
+
+$(D execute) and $(D executeShell) start a new process using
+$(LREF spawnProcess) and $(LREF spawnShell), respectively, and wait
+for the process to complete before returning.  The functions capture
+what the child process prints to both its standard output and
+standard error streams, and return this together with its exit code.
+---
+auto dmd = execute(["dmd", "myapp.d"]);
+if (dmd.status != 0) writeln("Compilation failed:\n", dmd.output);
+
+auto ls = executeShell("ls -l");
+if (ls.status != 0) writeln("Failed to retrieve file listing");
+else writeln(ls.output);
+---
+
+The $(D args)/$(D program)/$(D command), $(D env) and $(D config)
+parameters are forwarded straight to the underlying spawn functions,
+and we refer to their documentation for details.
+
+Params:
+args      = An array which contains the program name as the zeroth element
+            and any command-line arguments in the following elements.
+            (See $(LREF spawnProcess) for details.)
+program   = The program name, $(I without) command-line arguments.
+            (See $(LREF spawnProcess) for details.)
+command   = A shell command which is passed verbatim to the command
+            interpreter.  (See $(LREF spawnShell) for details.)
+env       = Additional environment variables for the child process.
+            (See $(LREF spawnProcess) for details.)
+config    = Flags that control process creation. See $(LREF Config)
+            for an overview of available flags, and note that the
+            $(D retainStd...) flags have no effect in this function.
+maxOutput = The maximum number of bytes of output that should be
+            captured.
+workDir   = The working directory for the new process.
+            By default the child process inherits the parent's working
+            directory.
+shellPath = The path to the shell to use to run the specified program.
+            By default this is $(LREF nativeShell).
+
+
+Returns:
+An $(D std.typecons.Tuple!(int, "status", string, "output")).
+
+POSIX_specific:
+If the process is terminated by a signal, the $(D status) field of
+the return value will contain a negative number whose absolute
+value is the signal number.  (See $(LREF wait) for details.)
+
+Throws:
+$(LREF ProcessException) on failure to start the process.$(BR)
+$(XREF stdio,StdioException) on failure to capture output.
+*/
+auto execute(in char[][] args,
+             const string[string] env = null,
+             Config config = Config.none,
+             size_t maxOutput = size_t.max,
+             in char[] workDir = null)
+    @trusted //TODO: @safe
+{
+    return executeImpl!pipeProcess(args, env, config, maxOutput, workDir);
+}
+
+/// ditto
+auto execute(in char[] program,
+             const string[string] env = null,
+             Config config = Config.none,
+             size_t maxOutput = size_t.max,
+             in char[] workDir = null)
+    @trusted //TODO: @safe
+{
+    return executeImpl!pipeProcess(program, env, config, maxOutput, workDir);
+}
+
+/// ditto
+auto executeShell(in char[] command,
+                  const string[string] env = null,
+                  Config config = Config.none,
+                  size_t maxOutput = size_t.max,
+                  in char[] workDir = null,
+                  string shellPath = nativeShell)
+    @trusted //TODO: @safe
+{
+    return executeImpl!pipeShell(command,
+                                 env,
+                                 config,
+                                 maxOutput,
+                                 workDir,
+                                 shellPath);
+}
+
+// Does the actual work for execute() and executeShell().
+private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)(
+    Cmd commandLine,
+    const string[string] env = null,
+    Config config = Config.none,
+    size_t maxOutput = size_t.max,
+    in char[] workDir = null,
+    ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init)
+{
+    import std.string;
+    import std.typecons : Tuple;
+    import std.array : appender;
+    import std.algorithm : min;
+
+    auto p = pipeFunc(commandLine, Redirect.stdout | Redirect.stderrToStdout,
+                      env, config, workDir, extraArgs);
+
+    auto a = appender!(ubyte[])();
+    enum size_t defaultChunkSize = 4096;
+    immutable chunkSize = min(maxOutput, defaultChunkSize);
+
+    // Store up to maxOutput bytes in a.
+    foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize))
+    {
+        immutable size_t remain = maxOutput - a.data.length;
+
+        if (chunk.length < remain) a.put(chunk);
+        else
+        {
+            a.put(chunk[0 .. remain]);
+            break;
+        }
+    }
+    // Exhaust the stream, if necessary.
+    foreach (ubyte[] chunk; p.stdout.byChunk(defaultChunkSize)) { }
+
+    return Tuple!(int, "status", string, "output")(wait(p.pid), cast(string) a.data);
+}
+
+unittest
+{
+    import std.string;
+    // To avoid printing the newline characters, we use the echo|set trick on
+    // Windows, and printf on POSIX (neither echo -n nor echo \c are portable).
+    version (Windows) TestScript prog =
+       "echo|set /p=%~1
+        echo|set /p=%~2 1>&2
+        exit 123";
+    else version (Android) TestScript prog =
+       `echo -n $1
+        echo -n $2 >&2
+        exit 123`;
+    else version (Posix) TestScript prog =
+       `printf '%s' $1
+        printf '%s' $2 >&2
+        exit 123`;
+    auto r = execute([prog.path, "foo", "bar"]);
+    assert (r.status == 123);
+    assert (r.output.stripRight() == "foobar");
+    auto s = execute([prog.path, "Hello", "World"]);
+    assert (s.status == 123);
+    assert (s.output.stripRight() == "HelloWorld");
+}
+
+unittest
+{
+    import std.string;
+    auto r1 = executeShell("echo foo");
+    assert (r1.status == 0);
+    assert (r1.output.chomp() == "foo");
+    auto r2 = executeShell("echo bar 1>&2");
+    assert (r2.status == 0);
+    assert (r2.output.chomp().stripRight() == "bar");
+    auto r3 = executeShell("exit 123");
+    assert (r3.status == 123);
+    assert (r3.output.empty);
+}
+
+unittest
+{
+    import std.typecons : Tuple;
+    void foo() //Just test the compilation
+    {
+        auto ret1 = execute(["dummy", "arg"]);
+        auto ret2 = executeShell("dummy arg");
+        static assert(is(typeof(ret1) == typeof(ret2)));
+
+        Tuple!(int, string) ret3 = execute(["dummy", "arg"]);
+    }
+}
+
+/// An exception that signals a problem with starting or waiting for a process.
+class ProcessException : Exception
+{
+    mixin basicExceptionCtors;
+
+    // Creates a new ProcessException based on errno.
+    static ProcessException newFromErrno(string customMsg = null,
+                                         string file = __FILE__,
+                                         size_t line = __LINE__)
+    {
+        import core.stdc.errno;
+        import core.stdc.string;
+        version (CRuntime_Glibc)
+        {
+            char[1024] buf;
+            auto errnoMsg = to!string(
+                core.stdc.string.strerror_r(errno, buf.ptr, buf.length));
+        }
+        else
+        {
+            auto errnoMsg = to!string(core.stdc.string.strerror(errno));
+        }
+        auto msg = customMsg.empty ? errnoMsg
+                                   : customMsg ~ " (" ~ errnoMsg ~ ')';
+        return new ProcessException(msg, file, line);
+    }
+
+    // Creates a new ProcessException based on GetLastError() (Windows only).
+    version (Windows)
+    static ProcessException newFromLastError(string customMsg = null,
+                                             string file = __FILE__,
+                                             size_t line = __LINE__)
+    {
+        auto lastMsg = sysErrorString(GetLastError());
+        auto msg = customMsg.empty ? lastMsg
+                                   : customMsg ~ " (" ~ lastMsg ~ ')';
+        return new ProcessException(msg, file, line);
+    }
+}
+
+
+/**
+Determines the path to the current user's preferred command interpreter.
+
+On Windows, this function returns the contents of the COMSPEC environment
+variable, if it exists.  Otherwise, it returns the result of $(LREF nativeShell).
+
+On POSIX, $(D userShell) returns the contents of the SHELL environment
+variable, if it exists and is non-empty.  Otherwise, it returns the result of
+$(LREF nativeShell).
+*/
+@property string userShell() @safe
+{
+    version (Windows)      return environment.get("COMSPEC", nativeShell);
+    else version (Posix)   return environment.get("SHELL", nativeShell);
+}
+
+/**
+The platform-specific native shell path.
+
+This function returns $(D "cmd.exe") on Windows, $(D "/bin/sh") on POSIX, and
+$(D "/system/bin/sh") on Android.
+*/
+@property string nativeShell() @safe @nogc pure nothrow
+{
+    version (Windows)      return "cmd.exe";
+    else version (Android) return "/system/bin/sh";
+    else version (Posix)   return "/bin/sh";
+}
+
+// A command-line switch that indicates to the shell that it should
+// interpret the following argument as a command to be executed.
+version (Posix)   private immutable string shellSwitch = "-c";
+version (Windows) private immutable string shellSwitch = "/C";
+
+
+/**
+ * Returns the process ID of the current process,
+ * which is guaranteed to be unique on the system.
+ *
+ * Example:
+ * ---
+ * writefln("Current process ID: %d", thisProcessID);
+ * ---
+ */
+@property int thisProcessID() @trusted nothrow //TODO: @safe
+{
+    version (Windows)    return GetCurrentProcessId();
+    else version (Posix) return core.sys.posix.unistd.getpid();
+}
+
+
+/**
+ * Returns the process ID of the current thread,
+ * which is guaranteed to be unique within the current process.
+ *
+ * Returns:
+ * A $(CXREF thread, ThreadID) value for the calling thread.
+ *
+ * Example:
+ * ---
+ * writefln("Current thread ID: %s", thisThreadID);
+ * ---
+ */
+@property ThreadID thisThreadID() @trusted nothrow //TODO: @safe
+{
+    version (Windows)
+        return GetCurrentThreadId();
+    else
+    version (Posix)
+    {
+        import core.sys.posix.pthread;
+        return pthread_self();
+    }
+}
+
+
+unittest
+{
+    int pidA, pidB;
+    ThreadID tidA, tidB;
+    pidA = thisProcessID;
+    tidA = thisThreadID;
+
+    import core.thread;
+    auto t = new Thread({
+        pidB = thisProcessID;
+        tidB = thisThreadID;
+    });
+    t.start();
+    t.join();
+
+    assert(pidA == pidB);
+    assert(tidA != tidB);
+}
+
+
+// Unittest support code:  TestScript takes a string that contains a
+// shell script for the current platform, and writes it to a temporary
+// file. On Windows the file name gets a .cmd extension, while on
+// POSIX its executable permission bit is set.  The file is
+// automatically deleted when the object goes out of scope.
+version (unittest)
+private struct TestScript
+{
+    this(string code)
+    {
+        import std.ascii, std.file;
+        version (Windows)
+        {
+            auto ext = ".cmd";
+            auto firstLine = "@echo off";
+        }
+        else version (Posix)
+        {
+            auto ext = "";
+            auto firstLine = "#!" ~ nativeShell;
+        }
+        path = uniqueTempPath()~ext;
+        std.file.write(path, firstLine~std.ascii.newline~code~std.ascii.newline);
+        version (Posix)
+        {
+            import core.sys.posix.sys.stat;
+            chmod(path.tempCString(), octal!777);
+        }
+    }
+
+    ~this()
+    {
+        import std.file;
+        if (!path.empty && exists(path))
+        {
+            try { remove(path); }
+            catch (Exception e)
+            {
+                debug std.stdio.stderr.writeln(e.msg);
+            }
+        }
+    }
+
+    string path;
+}
+
+version (unittest)
+private string uniqueTempPath()
+{
+    import std.file, std.uuid;
+    // Path should contain spaces to test escaping whitespace
+    return buildPath(tempDir(), "std.process temporary file " ~
+        randomUUID().toString());
+}
+
+
+// =============================================================================
+// Functions for shell command quoting/escaping.
+// =============================================================================
+
+
+/*
+    Command line arguments exist in three forms:
+    1) string or char* array, as received by main.
+       Also used internally on POSIX systems.
+    2) Command line string, as used in Windows'
+       CreateProcess and CommandLineToArgvW functions.
+       A specific quoting and escaping algorithm is used
+       to distinguish individual arguments.
+    3) Shell command string, as written at a shell prompt
+       or passed to cmd /C - this one may contain shell
+       control characters, e.g. > or | for redirection /
+       piping - thus, yet another layer of escaping is
+       used to distinguish them from program arguments.
+
+    Except for escapeWindowsArgument, the intermediary
+    format (2) is hidden away from the user in this module.
+*/
+
+/**
+Escapes an argv-style argument array to be used with $(LREF spawnShell),
+$(LREF pipeShell) or $(LREF executeShell).
+---
+string url = "http://dlang.org/";
+executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html"));
+---
+
+Concatenate multiple $(D escapeShellCommand) and
+$(LREF escapeShellFileName) results to use shell redirection or
+piping operators.
+---
+executeShell(
+    escapeShellCommand("curl", "http://dlang.org/download.html") ~
+    "|" ~
+    escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~
+    ">" ~
+    escapeShellFileName("D download links.txt"));
+---
+
+Throws:
+$(OBJECTREF Exception) if any part of the command line contains unescapable
+characters (NUL on all platforms, as well as CR and LF on Windows).
+*/
+string escapeShellCommand(in char[][] args...) @safe pure
+{
+    if (args.empty)
+        return null;
+    version (Windows)
+    {
+        // Do not ^-escape the first argument (the program path),
+        // as the shell parses it differently from parameters.
+        // ^-escaping a program path that contains spaces will fail.
+        string result = escapeShellFileName(args[0]);
+        if (args.length > 1)
+        {
+            result ~= " " ~ escapeShellCommandString(
+                escapeShellArguments(args[1..$]));
+        }
+        return result;
+    }
+    version (Posix)
+    {
+        return escapeShellCommandString(escapeShellArguments(args));
+    }
+}
+
+unittest
+{
+    // This is a simple unit test without any special requirements,
+    // in addition to the unittest_burnin one below which requires
+    // special preparation.
+
+    struct TestVector { string[] args; string windows, posix; }
+    TestVector[] tests =
+    [
+        {
+            args    : ["foo bar"],
+            windows : `"foo bar"`,
+            posix   : `'foo bar'`
+        },
+        {
+            args    : ["foo bar", "hello"],
+            windows : `"foo bar" hello`,
+            posix   : `'foo bar' 'hello'`
+        },
+        {
+            args    : ["foo bar", "hello world"],
+            windows : `"foo bar" ^"hello world^"`,
+            posix   : `'foo bar' 'hello world'`
+        },
+        {
+            args    : ["foo bar", "hello", "world"],
+            windows : `"foo bar" hello world`,
+            posix   : `'foo bar' 'hello' 'world'`
+        },
+        {
+            args    : ["foo bar", `'"^\`],
+            windows : `"foo bar" ^"'\^"^^\\^"`,
+            posix   : `'foo bar' ''\''"^\'`
+        },
+    ];
+
+    foreach (test; tests)
+        version (Windows)
+            assert(escapeShellCommand(test.args) == test.windows);
+        else
+            assert(escapeShellCommand(test.args) == test.posix  );
+}
+
+private string escapeShellCommandString(string command) @safe pure
+{
+    version (Windows)
+        return escapeWindowsShellCommand(command);
+    else
+        return command;
+}
+
+private string escapeWindowsShellCommand(in char[] command) @safe pure
+{
+    import std.array : appender;
+    auto result = appender!string();
+    result.reserve(command.length);
+
+    foreach (c; command)
+        switch (c)
+        {
+            case '\0':
+                throw new Exception("Cannot put NUL in command line");
+            case '\r':
+            case '\n':
+                throw new Exception("CR/LF are not escapable");
+            case '\x01': .. case '\x09':
+            case '\x0B': .. case '\x0C':
+            case '\x0E': .. case '\x1F':
+            case '"':
+            case '^':
+            case '&':
+            case '<':
+            case '>':
+            case '|':
+                result.put('^');
+                goto default;
+            default:
+                result.put(c);
+        }
+    return result.data;
+}
+
+private string escapeShellArguments(in char[][] args...)
+    @trusted pure nothrow
+{
+    char[] buf;
+
+    @safe nothrow
+    char[] allocator(size_t size)
+    {
+        if (buf.length == 0)
+            return buf = new char[size];
+        else
+        {
+            auto p = buf.length;
+            buf.length = buf.length + 1 + size;
+            buf[p++] = ' ';
+            return buf[p..p+size];
+        }
+    }
+
+    foreach (arg; args)
+        escapeShellArgument!allocator(arg);
+    return assumeUnique(buf);
+}
+
+private auto escapeShellArgument(alias allocator)(in char[] arg) @safe nothrow
+{
+    // The unittest for this function requires special
+    // preparation - see below.
+
+    version (Windows)
+        return escapeWindowsArgumentImpl!allocator(arg);
+    else
+        return escapePosixArgumentImpl!allocator(arg);
+}
+
+/**
+Quotes a command-line argument in a manner conforming to the behavior of
+$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx,
+CommandLineToArgvW).
+*/
+string escapeWindowsArgument(in char[] arg) @trusted pure nothrow
+{
+    // Rationale for leaving this function as public:
+    // this algorithm of escaping paths is also used in other software,
+    // e.g. DMD's response files.
+
+    auto buf = escapeWindowsArgumentImpl!charAllocator(arg);
+    return assumeUnique(buf);
+}
+
+
+private char[] charAllocator(size_t size) @safe pure nothrow
+{
+    return new char[size];
+}
+
+
+private char[] escapeWindowsArgumentImpl(alias allocator)(in char[] arg)
+    @safe nothrow
+    if (is(typeof(allocator(size_t.init)[0] = char.init)))
+{
+    // References:
+    // * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
+    // * http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
+
+    // Check if the string needs to be escaped,
+    // and calculate the total string size.
+
+    // Trailing backslashes must be escaped
+    bool escaping = true;
+    bool needEscape = false;
+    // Result size = input size + 2 for surrounding quotes + 1 for the
+    // backslash for each escaped character.
+    size_t size = 1 + arg.length + 1;
+
+    foreach_reverse (char c; arg)
+    {
+        if (c == '"')
+        {
+            needEscape = true;
+            escaping = true;
+            size++;
+        }
+        else
+        if (c == '\\')
+        {
+            if (escaping)
+                size++;
+        }
+        else
+        {
+            if (c == ' ' || c == '\t')
+                needEscape = true;
+            escaping = false;
+        }
+    }
+
+    import std.ascii : isDigit;
+    // Empty arguments need to be specified as ""
+    if (!arg.length)
+        needEscape = true;
+    else
+    // Arguments ending with digits need to be escaped,
+    // to disambiguate with 1>file redirection syntax
+    if (isDigit(arg[$-1]))
+        needEscape = true;
+
+    if (!needEscape)
+        return allocator(arg.length)[] = arg;
+
+    // Construct result string.
+
+    auto buf = allocator(size);
+    size_t p = size;
+    buf[--p] = '"';
+    escaping = true;
+    foreach_reverse (char c; arg)
+    {
+        if (c == '"')
+            escaping = true;
+        else
+        if (c != '\\')
+            escaping = false;
+
+        buf[--p] = c;
+        if (escaping)
+            buf[--p] = '\\';
+    }
+    buf[--p] = '"';
+    assert(p == 0);
+
+    return buf;
+}
+
+version(Windows) version(unittest)
+{
+    import core.sys.windows.shellapi : CommandLineToArgvW;
+    import core.sys.windows.windows;
+    import core.stdc.stddef;
+    import core.stdc.wchar_ : wcslen;
+    import std.array;
+
+    string[] parseCommandLine(string line)
+    {
+        import std.algorithm : map;
+        import std.array : array;
+        LPWSTR lpCommandLine = (to!(wchar[])(line) ~ "\0"w).ptr;
+        int numArgs;
+        LPWSTR* args = CommandLineToArgvW(lpCommandLine, &numArgs);
+        scope(exit) LocalFree(args);
+        return args[0..numArgs]
+            .map!(arg => to!string(arg[0..wcslen(arg)]))
+            .array();
+    }
+
+    unittest
+    {
+        string[] testStrings = [
+            `Hello`,
+            `Hello, world`,
+            `Hello, "world"`,
+            `C:\`,
+            `C:\dmd`,
+            `C:\Program Files\`,
+        ];
+
+        enum CHARS = `_x\" *&^` ~ "\t"; // _ is placeholder for nothing
+        foreach (c1; CHARS)
+        foreach (c2; CHARS)
+        foreach (c3; CHARS)
+        foreach (c4; CHARS)
+            testStrings ~= [c1, c2, c3, c4].replace("_", "");
+
+        foreach (s; testStrings)
+        {
+            auto q = escapeWindowsArgument(s);
+            auto args = parseCommandLine("Dummy.exe " ~ q);
+            assert(args.length==2, s ~ " => " ~ q ~ " #" ~ text(args.length-1));
+            assert(args[1] == s, s ~ " => " ~ q ~ " => " ~ args[1]);
+        }
+    }
+}
+
+private string escapePosixArgument(in char[] arg) @trusted pure nothrow
+{
+    auto buf = escapePosixArgumentImpl!charAllocator(arg);
+    return assumeUnique(buf);
+}
+
+private char[] escapePosixArgumentImpl(alias allocator)(in char[] arg)
+    @safe nothrow
+    if (is(typeof(allocator(size_t.init)[0] = char.init)))
+{
+    // '\'' means: close quoted part of argument, append an escaped
+    // single quote, and reopen quotes
+
+    // Below code is equivalent to:
+    // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`;
+
+    size_t size = 1 + arg.length + 1;
+    foreach (char c; arg)
+        if (c == '\'')
+            size += 3;
+
+    auto buf = allocator(size);
+    size_t p = 0;
+    buf[p++] = '\'';
+    foreach (char c; arg)
+        if (c == '\'')
+        {
+            buf[p..p+4] = `'\''`;
+            p += 4;
+        }
+        else
+            buf[p++] = c;
+    buf[p++] = '\'';
+    assert(p == size);
+
+    return buf;
+}
+
+/**
+Escapes a filename to be used for shell redirection with $(LREF spawnShell),
+$(LREF pipeShell) or $(LREF executeShell).
+*/
+string escapeShellFileName(in char[] fileName) @trusted pure nothrow
+{
+    // The unittest for this function requires special
+    // preparation - see below.
+
+    version (Windows)
+    {
+        // If a file starts with &, it can cause cmd.exe to misinterpret
+        // the file name as the stream redirection syntax:
+        //     command > "&foo.txt"
+        // gets interpreted as
+        //     command >&foo.txt
+        // Prepend .\ to disambiguate.
+
+        if (fileName.length && fileName[0] == '&')
+            return cast(string)(`".\` ~ fileName ~ '"');
+
+        return cast(string)('"' ~ fileName ~ '"');
+    }
+    else
+        return escapePosixArgument(fileName);
+}
+
+// Loop generating strings with random characters
+//version = unittest_burnin;
+
+version(unittest_burnin)
+unittest
+{
+    // There are no readily-available commands on all platforms suitable
+    // for properly testing command escaping. The behavior of CMD's "echo"
+    // built-in differs from the POSIX program, and Windows ports of POSIX
+    // environments (Cygwin, msys, gnuwin32) may interfere with their own
+    // "echo" ports.
+
+    // To run this unit test, create std_process_unittest_helper.d with the
+    // following content and compile it:
+    // import std.stdio, std.array; void main(string[] args) { write(args.join("\0")); }
+    // Then, test this module with:
+    // rdmd --main -unittest -version=unittest_burnin process.d
+
+    auto helper = absolutePath("std_process_unittest_helper");
+    assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction");
+
+    void test(string[] s, string fn)
+    {
+        string e;
+        string[] g;
+
+        e = escapeShellCommand(helper ~ s);
+        {
+            scope(failure) writefln("executeShell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]);
+            auto result = executeShell(e);
+            assert(result.status == 0, "std_process_unittest_helper failed");
+            g = result.output.split("\0")[1..$];
+        }
+        assert(s == g, format("executeShell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e]));
+
+        e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn);
+        {
+            scope(failure) writefln("executeShell() with redirect failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]);
+            auto result = executeShell(e);
+            assert(result.status == 0, "std_process_unittest_helper failed");
+            assert(!result.output.length, "No output expected, got:\n" ~ result.output);
+            g = readText(fn).split("\0")[1..$];
+        }
+        remove(fn);
+        assert(s == g, format("executeShell() with redirect test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e]));
+    }
+
+    while (true)
+    {
+        string[] args;
+        foreach (n; 0..uniform(1, 4))
+        {
+            string arg;
+            foreach (l; 0..uniform(0, 10))
+            {
+                dchar c;
+                while (true)
+                {
+                    version (Windows)
+                    {
+                        // As long as DMD's system() uses CreateProcessA,
+                        // we can't reliably pass Unicode
+                        c = uniform(0, 128);
+                    }
+                    else
+                        c = uniform!ubyte();
+
+                    if (c == 0)
+                        continue; // argv-strings are zero-terminated
+                    version (Windows)
+                        if (c == '\r' || c == '\n')
+                            continue; // newlines are unescapable on Windows
+                    break;
+                }
+                arg ~= c;
+            }
+            args ~= arg;
+        }
+
+        // generate filename
+        string fn;
+        foreach (l; 0..uniform(1, 10))
+        {
+            dchar c;
+            while (true)
+            {
+                version (Windows)
+                    c = uniform(0, 128); // as above
+                else
+                    c = uniform!ubyte();
+
+                if (c == 0 || c == '/')
+                    continue; // NUL and / are the only characters
+                              // forbidden in POSIX filenames
+                version (Windows)
+                    if (c < '\x20' || c == '<' || c == '>' || c == ':' ||
+                        c == '"' || c == '\\' || c == '|' || c == '?' || c == '*')
+                        continue; // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+                break;
+            }
+
+            fn ~= c;
+        }
+        fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$];
+
+        test(args, fn);
+    }
+}
+
+
+// =============================================================================
+// Environment variable manipulation.
+// =============================================================================
+
+
+/**
+Manipulates _environment variables using an associative-array-like
+interface.
+
+This class contains only static methods, and cannot be instantiated.
+See below for examples of use.
+*/
+abstract final class environment
+{
+static:
+    /**
+    Retrieves the value of the environment variable with the given $(D name).
+    ---
+    auto path = environment["PATH"];
+    ---
+
+    Throws:
+    $(OBJECTREF Exception) if the environment variable does not exist,
+    or $(XREF utf,UTFException) if the variable contains invalid UTF-16
+    characters (Windows only).
+
+    See_also:
+    $(LREF environment.get), which doesn't throw on failure.
+    */
+    string opIndex(in char[] name) @safe
+    {
+        string value;
+        enforce(getImpl(name, value), "Environment variable not found: "~name);
+        return value;
+    }
+
+    /**
+    Retrieves the value of the environment variable with the given $(D name),
+    or a default value if the variable doesn't exist.
+
+    Unlike $(LREF environment.opIndex), this function never throws.
+    ---
+    auto sh = environment.get("SHELL", "/bin/sh");
+    ---
+    This function is also useful in checking for the existence of an
+    environment variable.
+    ---
+    auto myVar = environment.get("MYVAR");
+    if (myVar is null)
+    {
+        // Environment variable doesn't exist.
+        // Note that we have to use 'is' for the comparison, since
+        // myVar == null is also true if the variable exists but is
+        // empty.
+    }
+    ---
+
+    Throws:
+    $(XREF utf,UTFException) if the variable contains invalid UTF-16
+    characters (Windows only).
+    */
+    string get(in char[] name, string defaultValue = null) @safe
+    {
+        string value;
+        auto found = getImpl(name, value);
+        return found ? value : defaultValue;
+    }
+
+    /**
+    Assigns the given $(D value) to the environment variable with the given
+    $(D name).
+
+    If the variable does not exist, it will be created. If it already exists,
+    it will be overwritten.
+    ---
+    environment["foo"] = "bar";
+    ---
+
+    Throws:
+    $(OBJECTREF Exception) if the environment variable could not be added
+        (e.g. if the name is invalid).
+    */
+    inout(char)[] opIndexAssign(inout char[] value, in char[] name) @trusted
+    {
+        version (Posix)
+        {
+            if (core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), 1) != -1)
+            {
+                return value;
+            }
+            // The default errno error message is very uninformative
+            // in the most common case, so we handle it manually.
+            enforce(errno != EINVAL,
+                "Invalid environment variable name: '"~name~"'");
+            errnoEnforce(false,
+                "Failed to add environment variable");
+            assert(0);
+        }
+        else version (Windows)
+        {
+            enforce(
+                SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()),
+                sysErrorString(GetLastError())
+            );
+            return value;
+        }
+        else static assert(0);
+    }
+
+    /**
+    Removes the environment variable with the given $(D name).
+
+    If the variable isn't in the environment, this function returns
+    successfully without doing anything.
+    */
+    void remove(in char[] name) @trusted nothrow @nogc // TODO: @safe
+    {
+        version (Windows)    SetEnvironmentVariableW(name.tempCStringW(), null);
+        else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString());
+        else static assert(0);
+    }
+
+    /**
+    Copies all environment variables into an associative array.
+
+    Windows_specific:
+    While Windows environment variable names are case insensitive, D's
+    built-in associative arrays are not.  This function will store all
+    variable names in uppercase (e.g. $(D PATH)).
+
+    Throws:
+    $(OBJECTREF Exception) if the environment variables could not
+        be retrieved (Windows only).
+    */
+    string[string] toAA() @trusted
+    {
+        string[string] aa;
+        version (Posix)
+        {
+            for (int i=0; environ[i] != null; ++i)
+            {
+                import std.string : indexOf;
+
+                immutable varDef = to!string(environ[i]);
+                immutable eq = indexOf(varDef, '=');
+                assert (eq >= 0);
+
+                immutable name = varDef[0 .. eq];
+                immutable value = varDef[eq+1 .. $];
+
+                // In POSIX, environment variables may be defined more
+                // than once.  This is a security issue, which we avoid
+                // by checking whether the key already exists in the array.
+                // For more info:
+                // http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html
+                if (name !in aa)  aa[name] = value;
+            }
+        }
+        else version (Windows)
+        {
+            import std.uni : toUpper;
+            auto envBlock = GetEnvironmentStringsW();
+            enforce(envBlock, "Failed to retrieve environment variables.");
+            scope(exit) FreeEnvironmentStringsW(envBlock);
+
+            for (int i=0; envBlock[i] != '\0'; ++i)
+            {
+                auto start = i;
+                while (envBlock[i] != '=') ++i;
+                immutable name = toUTF8(toUpper(envBlock[start .. i]));
+
+                start = i+1;
+                while (envBlock[i] != '\0') ++i;
+
+                // Ignore variables with empty names. These are used internally
+                // by Windows to keep track of each drive's individual current
+                // directory.
+                if (!name.length)
+                    continue;
+
+                // Just like in POSIX systems, environment variables may be
+                // defined more than once in an environment block on Windows,
+                // and it is just as much of a security issue there.  Moreso,
+                // in fact, due to the case insensensitivity of variable names,
+                // which is not handled correctly by all programs.
+                if (name !in aa) aa[name] = toUTF8(envBlock[start .. i]);
+            }
+        }
+        else static assert(0);
+        return aa;
+    }
+
+private:
+    // Returns the length of an environment variable (in number of
+    // wchars, including the null terminator), or 0 if it doesn't exist.
+    version (Windows)
+    int varLength(LPCWSTR namez) @trusted nothrow
+    {
+        return GetEnvironmentVariableW(namez, null, 0);
+    }
+
+    // Retrieves the environment variable, returns false on failure.
+    bool getImpl(in char[] name, out string value) @trusted
+    {
+        version (Windows)
+        {
+            const namezTmp = name.tempCStringW();
+            immutable len = varLength(namezTmp);
+            if (len == 0) return false;
+            if (len == 1)
+            {
+                value = "";
+                return true;
+            }
+
+            auto buf = new WCHAR[len];
+            GetEnvironmentVariableW(namezTmp, buf.ptr, to!DWORD(buf.length));
+            value = toUTF8(buf[0 .. $-1]);
+            return true;
+        }
+        else version (Posix)
+        {
+            const vz = core.sys.posix.stdlib.getenv(name.tempCString());
+            if (vz == null) return false;
+            auto v = vz[0 .. strlen(vz)];
+
+            // Cache the last call's result.
+            static string lastResult;
+            if (v != lastResult) lastResult = v.idup;
+            value = lastResult;
+            return true;
+        }
+        else static assert(0);
+    }
+}
+
+unittest
+{
+    // New variable
+    environment["std_process"] = "foo";
+    assert (environment["std_process"] == "foo");
+
+    // Set variable again
+    environment["std_process"] = "bar";
+    assert (environment["std_process"] == "bar");
+
+    // Remove variable
+    environment.remove("std_process");
+
+    // Remove again, should succeed
+    environment.remove("std_process");
+
+    // Throw on not found.
+    assertThrown(environment["std_process"]);
+
+    // get() without default value
+    assert (environment.get("std_process") == null);
+
+    // get() with default value
+    assert (environment.get("std_process", "baz") == "baz");
+
+    // Convert to associative array
+    auto aa = environment.toAA();
+    assert (aa.length > 0);
+    foreach (n, v; aa)
+    {
+        // Wine has some bugs related to environment variables:
+        //  - Wine allows the existence of an env. variable with the name
+        //    "\0", but GetEnvironmentVariable refuses to retrieve it.
+        //    As of 2.067 we filter these out anyway (see comment in toAA).
+        //  - If an env. variable has zero length, i.e. is "\0",
+        //    GetEnvironmentVariable should return 1.  Instead it returns
+        //    0, indicating the variable doesn't exist.
+        version (Windows) if (v.length == 0) continue;
+
+        assert (v == environment[n]);
+    }
+
+    // ... and back again.
+    foreach (n, v; aa)
+        environment[n] = v;
+
+    // Complete the roundtrip
+    auto aa2 = environment.toAA();
+    assert(aa == aa2);
+}
+
+
+
+
+// =============================================================================
+// Everything below this line was part of the old std.process, and most of
+// it will be deprecated and removed.
+// =============================================================================
+
+
+/*
+Macros:
+
+WIKI=Phobos/StdProcess
+
+Copyright: Copyright Digital Mars 2007 - 2009.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB digitalmars.com, Walter Bright),
+           $(WEB erdani.org, Andrei Alexandrescu),
+           $(WEB thecybershadow.net, Vladimir Panteleev)
+Source:    $(PHOBOSSRC std/_process.d)
+*/
+/*
+         Copyright Digital Mars 2007 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+
+import core.stdc.stdlib;
+import core.stdc.errno;
+import core.thread;
+import core.stdc.string;
+
+version (Windows)
+{
+    import std.format, std.random, std.file;
+}
+version (Posix)
+{
+    import core.sys.posix.stdlib;
+}
+version (unittest)
+{
+    import std.file, std.conv, std.random;
+}
+
+
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use wait(spawnShell(command)) or executeShell(command) instead")
+int system(string command)
+{
+    if (!command.ptr) return core.stdc.stdlib.system(null);
+    immutable status = core.stdc.stdlib.system(command.tempCString());
+    if (status == -1) return status;
+    version (Posix)
+    {
+        if (exited(status))
+            return exitstatus(status);
+
+        // Abnormal termination, return -1.
+        return -1;
+    }
+    else version (Windows)
+        return status;
+    else
+        static assert(0, "system not implemented for this OS.");
+}
+
+private void toAStringz(in string[] a, const(char)**az)
+{
+    import std.string : toStringz;
+    foreach(string s; a)
+    {
+        *az++ = toStringz(s);
+    }
+    *az = null;
+}
+
+
+/* ========================================================== */
+
+//version (Windows)
+//{
+//    int spawnvp(int mode, string pathname, string[] argv)
+//    {
+//      char** argv_ = cast(char**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+//      scope(exit) core.stdc.stdlib.free(argv_);
+//
+//      toAStringz(argv, argv_);
+//
+//      return spawnvp(mode, pathname.tempCString(), argv_);
+//    }
+//}
+
+// Incorporating idea (for spawnvp() on Posix) from Dave Fladebo
+
+enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY };
+version(Windows) extern(C) int spawnvp(int, in char *, in char **);
+alias P_WAIT = _P_WAIT;
+alias P_NOWAIT = _P_NOWAIT;
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use spawnProcess instead")
+int spawnvp(int mode, string pathname, string[] argv)
+{
+    auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+    scope(exit) core.stdc.stdlib.free(argv_);
+
+    toAStringz(argv, argv_);
+
+    version (Posix)
+    {
+        return _spawnvp(mode, pathname.tempCString(), argv_);
+    }
+    else version (Windows)
+    {
+        return spawnvp(mode, pathname.tempCString(), argv_);
+    }
+    else
+        static assert(0, "spawnvp not implemented for this OS.");
+}
+
+version (Posix)
+{
+private import core.sys.posix.unistd;
+private import core.sys.posix.sys.wait;
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use spawnProcess instead")
+int _spawnvp(int mode, in char *pathname, in char **argv)
+{
+    int retval = 0;
+    pid_t pid = fork();
+
+    if(!pid)
+    {   // child
+        core.sys.posix.unistd.execvp(pathname, argv);
+        goto Lerror;
+    }
+    else if(pid > 0)
+    {   // parent
+        if(mode == _P_NOWAIT)
+        {
+            retval = pid; // caller waits
+        }
+        else
+        {
+            while(1)
+            {
+                int status;
+                pid_t wpid = waitpid(pid, &status, 0);
+                if(exited(status))
+                {
+                    retval = exitstatus(status);
+                    break;
+                }
+                else if(signaled(status))
+                {
+                    retval = -termsig(status);
+                    break;
+                }
+                else if(stopped(status)) // ptrace support
+                    continue;
+                else
+                    goto Lerror;
+            }
+        }
+
+        return retval;
+    }
+
+Lerror:
+    retval = errno;
+    char[80] buf = void;
+    throw new Exception(
+        "Cannot spawn " ~ to!string(pathname) ~ "; "
+        ~ to!string(strerror_r(retval, buf.ptr, buf.length))
+        ~ " [errno " ~ to!string(retval) ~ "]");
+}   // _spawnvp
+private
+{
+    alias stopped = WIFSTOPPED;
+    alias signaled = WIFSIGNALED;
+    alias termsig = WTERMSIG;
+    alias exited = WIFEXITED;
+    alias exitstatus = WEXITSTATUS;
+}   // private
+}   // version (Posix)
+
+/* ========================================================== */
+
+version (StdDdoc)
+{
+    /**
+    Replaces the current process by executing a command, $(D pathname), with
+    the arguments in $(D argv).
+
+    $(BLUE This functions is Posix-Only.)
+
+    Typically, the first element of $(D argv) is
+    the command being executed, i.e. $(D argv[0] == pathname). The 'p'
+    versions of $(D exec) search the PATH environment variable for $(D
+    pathname). The 'e' versions additionally take the new process'
+    environment variables as an array of strings of the form key=value.
+
+    Does not return on success (the current process will have been
+    replaced). Returns -1 on failure with no indication of the
+    underlying error.
+
+    Windows_specific:
+    These functions are only supported on POSIX platforms, as the Windows
+    operating systems do not provide the ability to overwrite the current
+    process image with another. In single-threaded programs it is possible
+    to approximate the effect of $(D execv*) by using $(LREF spawnProcess)
+    and terminating the current process once the child process has returned.
+    For example:
+    ---
+    auto commandLine = [ "program", "arg1", "arg2" ];
+    version (Posix)
+    {
+        execv(commandLine[0], commandLine);
+        throw new Exception("Failed to execute program");
+    }
+    else version (Windows)
+    {
+        import core.stdc.stdlib: _exit;
+        _exit(wait(spawnProcess(commandLine)));
+    }
+    ---
+    This is, however, NOT equivalent to POSIX' $(D execv*).  For one thing, the
+    executed program is started as a separate process, with all this entails.
+    Secondly, in a multithreaded program, other threads will continue to do
+    work while the current thread is waiting for the child process to complete.
+
+    A better option may sometimes be to terminate the current program immediately
+    after spawning the child process.  This is the behaviour exhibited by the
+    $(LINK2 http://msdn.microsoft.com/en-us/library/431x4c1w.aspx,$(D __exec))
+    functions in Microsoft's C runtime library, and it is how D's now-deprecated
+    Windows $(D execv*) functions work. Example:
+    ---
+    auto commandLine = [ "program", "arg1", "arg2" ];
+    version (Posix)
+    {
+        execv(commandLine[0], commandLine);
+        throw new Exception("Failed to execute program");
+    }
+    else version (Windows)
+    {
+        spawnProcess(commandLine);
+        import core.stdc.stdlib: _exit;
+        _exit(0);
+    }
+    ---
+    */
+    int execv(in string pathname, in string[] argv);
+    ///ditto
+    int execve(in string pathname, in string[] argv, in string[] envp);
+    /// ditto
+    int execvp(in string pathname, in string[] argv);
+    /// ditto
+    int execvpe(in string pathname, in string[] argv, in string[] envp);
+}
+else
+{
+    private enum execvForwarderDefs = q{
+        int execv(in string pathname, in string[] argv)
+        {
+            return execv_(pathname, argv);
+        }
+        int execve(in string pathname, in string[] argv, in string[] envp)
+        {
+            return execve_(pathname, argv, envp);
+        }
+        int execvp(in string pathname, in string[] argv)
+        {
+            return execvp_(pathname, argv);
+        }
+        int execvpe(in string pathname, in string[] argv, in string[] envp)
+        {
+            return execvpe_(pathname, argv, envp);
+        }
+    };
+    version (Posix)
+    {
+        mixin (execvForwarderDefs);
+    }
+    else version (Windows)
+    {
+        // Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+        private enum execvDeprecationMsg =
+            "Please consult the API documentation for more information: "
+            ~"http://dlang.org/phobos/std_process.html#.execv";
+        mixin (`deprecated ("`~execvDeprecationMsg~`") {` ~ execvForwarderDefs ~ `}`);
+    }
+    else static assert (false, "Unsupported platform");
+}
+
+// Move these C declarations to druntime if we decide to keep the D wrappers
+extern(C)
+{
+    int execv(in char *, in char **);
+    int execve(in char *, in char **, in char **);
+    int execvp(in char *, in char **);
+    version(Windows) int execvpe(in char *, in char **, in char **);
+}
+
+private int execv_(in string pathname, in string[] argv)
+{
+    auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+    scope(exit) core.stdc.stdlib.free(argv_);
+
+    toAStringz(argv, argv_);
+
+    return execv(pathname.tempCString(), argv_);
+}
+
+private int execve_(in string pathname, in string[] argv, in string[] envp)
+{
+    auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+    scope(exit) core.stdc.stdlib.free(argv_);
+    auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length));
+    scope(exit) core.stdc.stdlib.free(envp_);
+
+    toAStringz(argv, argv_);
+    toAStringz(envp, envp_);
+
+    return execve(pathname.tempCString(), argv_, envp_);
+}
+
+private int execvp_(in string pathname, in string[] argv)
+{
+    auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+    scope(exit) core.stdc.stdlib.free(argv_);
+
+    toAStringz(argv, argv_);
+
+    return execvp(pathname.tempCString(), argv_);
+}
+
+private int execvpe_(in string pathname, in string[] argv, in string[] envp)
+{
+version(Posix)
+{
+    import std.array : split;
+    // Is pathname rooted?
+    if(pathname[0] == '/')
+    {
+        // Yes, so just call execve()
+        return execve(pathname, argv, envp);
+    }
+    else
+    {
+        // No, so must traverse PATHs, looking for first match
+        string[]    envPaths    =   split(
+            to!string(core.stdc.stdlib.getenv("PATH")), ":");
+        int         iRet        =   0;
+
+        // Note: if any call to execve() succeeds, this process will cease
+        // execution, so there's no need to check the execve() result through
+        // the loop.
+
+        foreach(string pathDir; envPaths)
+        {
+            string  composite   =  cast(string) (pathDir ~ "/" ~ pathname);
+
+            iRet = execve(composite, argv, envp);
+        }
+        if(0 != iRet)
+        {
+            iRet = execve(pathname, argv, envp);
+        }
+
+        return iRet;
+    }
+}
+else version(Windows)
+{
+    auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length));
+    scope(exit) core.stdc.stdlib.free(argv_);
+    auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length));
+    scope(exit) core.stdc.stdlib.free(envp_);
+
+    toAStringz(argv, argv_);
+    toAStringz(envp, envp_);
+
+    return execvpe(pathname.tempCString(), argv_, envp_);
+}
+else
+{
+    static assert(0);
+} // version
+}
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use thisProcessID instead")
+alias getpid = core.thread.getpid;
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use executeShell instead")
+string shell(string cmd)
+{
+    version(Windows)
+    {
+        import std.array : appender;
+        // Generate a random filename
+        auto a = appender!string();
+        foreach (ref e; 0 .. 8)
+        {
+            formattedWrite(a, "%x", rndGen.front);
+            rndGen.popFront();
+        }
+        auto filename = a.data;
+        scope(exit) if (exists(filename)) remove(filename);
+        // We can't use escapeShellCommands here because we don't know
+        // if cmd is escaped (wrapped in quotes) or not, without relying
+        // on shady heuristics. The current code shouldn't cause much
+        // trouble unless filename contained spaces (it won't).
+        errnoEnforce(system(cmd ~ "> " ~ filename) == 0);
+        return readText(filename);
+    }
+    else version(Posix)
+    {
+        File f;
+        f.popen(cmd, "r");
+        char[] line;
+        string result;
+        while (f.readln(line))
+        {
+            result ~= line;
+        }
+        f.close();
+        return result;
+    }
+    else
+        static assert(0, "shell not implemented for this OS.");
+}
+
+deprecated unittest
+{
+    auto x = shell("echo wyda");
+    // @@@ This fails on wine
+    //assert(x == "wyda" ~ newline, text(x.length));
+
+    import std.exception;  // Issue 9444
+    version(windows)
+        string cmd = "98c10ec7e253a11cdff45f807b984a81 2>NUL";
+    else
+        string cmd = "98c10ec7e253a11cdff45f807b984a81 2>/dev/null";
+    assertThrown!ErrnoException(shell(cmd));
+}
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+deprecated("Please use environment.opIndex or environment.get instead")
+string getenv(in char[] name) nothrow
+{
+    // Cache the last call's result
+    static string lastResult;
+    auto p = core.stdc.stdlib.getenv(name.tempCString());
+    if (!p) return null;
+    auto value = p[0 .. strlen(p)];
+    if (value == lastResult) return lastResult;
+    return lastResult = value.idup;
+}
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+version(StdDdoc) deprecated void setenv(in char[] name, in char[] value, bool overwrite);
+else version(Posix)
+    deprecated("Please use environment.opIndexAssign instead.")
+    void setenv(in char[] name, in char[] value, bool overwrite)
+{
+    errnoEnforce(
+        core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), overwrite) == 0);
+}
+
+// Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
+version(StdDdoc) deprecated void unsetenv(in char[] name);
+else version(Posix)
+    deprecated("Please use environment.remove instead")
+    void unsetenv(in char[] name)
+{
+    errnoEnforce(core.sys.posix.stdlib.unsetenv(name.tempCString()) == 0);
+}
+
+version (Posix) deprecated unittest
+{
+    setenv("wyda", "geeba", true);
+    assert(getenv("wyda") == "geeba");
+    // Get again to make sure caching works
+    assert(getenv("wyda") == "geeba");
+    unsetenv("wyda");
+    assert(getenv("wyda") is null);
+}
+
+
+version(StdDdoc)
+{
+    /****************************************
+     * Start up the browser and set it to viewing the page at url.
+     */
+    void browse(string url);
+}
+else
+version (Windows)
+{
+    import core.sys.windows.windows;
+
+    version (GNU) {}
+    else pragma(lib,"shell32.lib");
+
+    void browse(string url)
+    {
+        ShellExecuteW(null, "open", url.tempCStringW(), null, null, SW_SHOWNORMAL);
+    }
+}
+else version (OSX)
+{
+    import core.stdc.stdio;
+    import core.stdc.string;
+    import core.sys.posix.unistd;
+
+    void browse(string url)
+    {
+        const(char)*[5] args;
+
+        auto curl = url.tempCString();
+        const(char)* browser = core.stdc.stdlib.getenv("BROWSER");
+        if (browser)
+        {   browser = strdup(browser);
+            args[0] = browser;
+            args[1] = curl;
+            args[2] = null;
+        }
+        else
+        {
+            args[0] = "open".ptr;
+            args[1] = curl;
+            args[2] = null;
+        }
+
+        auto childpid = fork();
+        if (childpid == 0)
+        {
+            core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr);
+            perror(args[0]);                // failed to execute
+            return;
+        }
+        if (browser)
+            free(cast(void*)browser);
+    }
+}
+else version (Posix)
+{
+    import core.stdc.stdio;
+    import core.stdc.string;
+    import core.sys.posix.unistd;
+
+    void browse(string url)
+    {
+        const(char)*[3] args;
+
+        const(char)* browser = core.stdc.stdlib.getenv("BROWSER");
+        if (browser)
+        {   browser = strdup(browser);
+            args[0] = browser;
+        }
+        else
+            //args[0] = "x-www-browser".ptr;  // doesn't work on some systems
+            args[0] = "xdg-open".ptr;
+
+        args[1] = url.tempCString();
+        args[2] = null;
+
+        auto childpid = fork();
+        if (childpid == 0)
+        {
+            core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr);
+            perror(args[0]);                // failed to execute
+            return;
+        }
+        if (browser)
+            free(cast(void*)browser);
+    }
+}
+else
+    static assert(0, "os not supported");
diff --git a/libphobos/src/std/random.d b/libphobos/src/std/random.d
new file mode 100644
index 00000000000..2ba68fb1a8c
--- /dev/null
+++ b/libphobos/src/std/random.d
@@ -0,0 +1,3061 @@
+// Written in the D programming language.
+
+/**
+Facilities for random number generation.
+
+The new-style generator objects hold their own state so they are
+immune of threading issues. The generators feature a number of
+well-known and well-documented methods of generating random
+numbers. An overall fast and reliable means to generate random numbers
+is the $(D_PARAM Mt19937) generator, which derives its name from
+"$(LUCKY Mersenne Twister) with a period of 2 to the power of
+19937". In memory-constrained situations, $(LUCKY linear congruential)
+generators such as $(D MinstdRand0) and $(D MinstdRand) might be
+useful. The standard library provides an alias $(D_PARAM Random) for
+whichever generator it considers the most fit for the target
+environment.
+
+Example:
+
+----
+// Generate a uniformly-distributed integer in the range [0, 14]
+auto i = uniform(0, 15);
+// Generate a uniformly-distributed real in the range [0, 100)
+// using a specific random generator
+Random gen;
+auto r = uniform(0.0L, 100.0L, gen);
+----
+
+In addition to random number generators, this module features
+distributions, which skew a generator's output statistical
+distribution in various ways. So far the uniform distribution for
+integers and real numbers have been implemented.
+
+Upgrading:
+        $(WEB digitalmars.com/d/1.0/phobos/std_random.html#rand, Phobos D1 $(D rand())) can
+        be replaced with $(D uniform!uint()).
+
+Source:    $(PHOBOSSRC std/_random.d)
+
+Macros:
+
+WIKI = Phobos/StdRandom
+
+
+Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Joseph Rushton Wakeling 2012.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu)
+           Masahiro Nakagawa (Xorshift random generator)
+           $(WEB braingam.es, Joseph Rushton Wakeling) (Algorithm D for random sampling)
+Credits:   The entire random number library architecture is derived from the
+           excellent $(WEB open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf, C++0X)
+           random number facility proposed by Jens Maurer and contributed to by
+           researchers at the Fermi laboratory (excluding Xorshift).
+*/
+/*
+         Copyright Andrei Alexandrescu 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.random;
+
+
+import std.range.primitives;
+import std.traits;
+
+version(unittest)
+{
+    static import std.meta;
+    package alias PseudoRngTypes = std.meta.AliasSeq!(MinstdRand0, MinstdRand, Mt19937, Xorshift32, Xorshift64,
+                                                      Xorshift96, Xorshift128, Xorshift160, Xorshift192);
+}
+
+// Segments of the code in this file Copyright (c) 1997 by Rick Booth
+// From "Inner Loops" by Rick Booth, Addison-Wesley
+
+// Work derived from:
+
+/*
+   A C-program for MT19937, with initialization improved 2002/1/26.
+   Coded by Takuji Nishimura and Makoto Matsumoto.
+
+   Before using, initialize the state by using init_genrand(seed)
+   or init_by_array(init_key, key_length).
+
+   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+
+     3. The names of its contributors may not be used to endorse or promote
+        products derived from this software without specific prior written
+        permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+   Any feedback is very welcome.
+   http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+*/
+
+/**
+ * Test if Rng is a random-number generator. The overload
+ * taking a ElementType also makes sure that the Rng generates
+ * values of that type.
+ *
+ * A random-number generator has at least the following features:
+ * $(UL
+ *   $(LI it's an InputRange)
+ *   $(LI it has a 'bool isUniformRandom' field readable in CTFE)
+ * )
+ */
+template isUniformRNG(Rng, ElementType)
+{
+    enum bool isUniformRNG = isInputRange!Rng &&
+        is(typeof(Rng.front) == ElementType) &&
+        is(typeof(
+        {
+            static assert(Rng.isUniformRandom); //tag
+        }));
+}
+
+/**
+ * ditto
+ */
+template isUniformRNG(Rng)
+{
+    enum bool isUniformRNG = isInputRange!Rng &&
+        is(typeof(
+        {
+            static assert(Rng.isUniformRandom); //tag
+        }));
+}
+
+/**
+ * Test if Rng is seedable. The overload
+ * taking a SeedType also makes sure that the Rng can be seeded with SeedType.
+ *
+ * A seedable random-number generator has the following additional features:
+ * $(UL
+ *   $(LI it has a 'seed(ElementType)' function)
+ * )
+ */
+template isSeedable(Rng, SeedType)
+{
+    enum bool isSeedable = isUniformRNG!(Rng) &&
+        is(typeof(
+        {
+            Rng r = void;              // can define a Rng object
+            r.seed(SeedType.init);     // can seed a Rng
+        }));
+}
+
+///ditto
+template isSeedable(Rng)
+{
+    enum bool isSeedable = isUniformRNG!Rng &&
+        is(typeof(
+        {
+            Rng r = void;                     // can define a Rng object
+            r.seed(typeof(r.front).init);     // can seed a Rng
+        }));
+}
+
+@safe pure nothrow unittest
+{
+    struct NoRng
+    {
+        @property uint front() {return 0;}
+        @property bool empty() {return false;}
+        void popFront() {}
+    }
+    static assert(!isUniformRNG!(NoRng, uint));
+    static assert(!isUniformRNG!(NoRng));
+    static assert(!isSeedable!(NoRng, uint));
+    static assert(!isSeedable!(NoRng));
+
+    struct NoRng2
+    {
+        @property uint front() {return 0;}
+        @property bool empty() {return false;}
+        void popFront() {}
+
+        enum isUniformRandom = false;
+    }
+    static assert(!isUniformRNG!(NoRng2, uint));
+    static assert(!isUniformRNG!(NoRng2));
+    static assert(!isSeedable!(NoRng2, uint));
+    static assert(!isSeedable!(NoRng2));
+
+    struct NoRng3
+    {
+        @property bool empty() {return false;}
+        void popFront() {}
+
+        enum isUniformRandom = true;
+    }
+    static assert(!isUniformRNG!(NoRng3, uint));
+    static assert(!isUniformRNG!(NoRng3));
+    static assert(!isSeedable!(NoRng3, uint));
+    static assert(!isSeedable!(NoRng3));
+
+    struct validRng
+    {
+        @property uint front() {return 0;}
+        @property bool empty() {return false;}
+        void popFront() {}
+
+        enum isUniformRandom = true;
+    }
+    static assert(isUniformRNG!(validRng, uint));
+    static assert(isUniformRNG!(validRng));
+    static assert(!isSeedable!(validRng, uint));
+    static assert(!isSeedable!(validRng));
+
+    struct seedRng
+    {
+        @property uint front() {return 0;}
+        @property bool empty() {return false;}
+        void popFront() {}
+        void seed(uint val){}
+        enum isUniformRandom = true;
+    }
+    static assert(isUniformRNG!(seedRng, uint));
+    static assert(isUniformRNG!(seedRng));
+    static assert(isSeedable!(seedRng, uint));
+    static assert(isSeedable!(seedRng));
+}
+
+/**
+Linear Congruential generator.
+ */
+struct LinearCongruentialEngine(UIntType, UIntType a, UIntType c, UIntType m)
+    if(isUnsigned!UIntType)
+{
+    ///Mark this as a Rng
+    enum bool isUniformRandom = true;
+    /// Does this generator have a fixed range? ($(D_PARAM true)).
+    enum bool hasFixedRange = true;
+    /// Lowest generated value ($(D 1) if $(D c == 0), $(D 0) otherwise).
+    enum UIntType min = ( c == 0 ? 1 : 0 );
+    /// Highest generated value ($(D modulus - 1)).
+    enum UIntType max = m - 1;
+/**
+The parameters of this distribution. The random number is $(D_PARAM x
+= (x * multipler + increment) % modulus).
+ */
+    enum UIntType multiplier = a;
+    ///ditto
+    enum UIntType increment = c;
+    ///ditto
+    enum UIntType modulus = m;
+
+    static assert(isIntegral!(UIntType));
+    static assert(m == 0 || a < m);
+    static assert(m == 0 || c < m);
+    static assert(m == 0 ||
+            (cast(ulong)a * (m-1) + c) % m == (c < a ? c - a + m : c - a));
+
+    // Check for maximum range
+    private static ulong gcd(ulong a, ulong b) @safe pure nothrow
+    {
+        while (b)
+        {
+            auto t = b;
+            b = a % b;
+            a = t;
+        }
+        return a;
+    }
+
+    private static ulong primeFactorsOnly(ulong n) @safe pure nothrow
+    {
+        ulong result = 1;
+        ulong iter = 2;
+        for (; n >= iter * iter; iter += 2 - (iter == 2))
+        {
+            if (n % iter) continue;
+            result *= iter;
+            do
+            {
+                n /= iter;
+            } while (n % iter == 0);
+        }
+        return result * n;
+    }
+
+    @safe pure nothrow unittest
+    {
+        static assert(primeFactorsOnly(100) == 10);
+        //writeln(primeFactorsOnly(11));
+        static assert(primeFactorsOnly(11) == 11);
+        static assert(primeFactorsOnly(7 * 7 * 7 * 11 * 15 * 11) == 7 * 11 * 15);
+        static assert(primeFactorsOnly(129 * 2) == 129 * 2);
+        // enum x = primeFactorsOnly(7 * 7 * 7 * 11 * 15);
+        // static assert(x == 7 * 11 * 15);
+    }
+
+    private static bool properLinearCongruentialParameters(ulong m,
+            ulong a, ulong c) @safe pure nothrow
+    {
+        if (m == 0)
+        {
+            static if (is(UIntType == uint))
+            {
+                // Assume m is uint.max + 1
+                m = (1uL << 32);
+            }
+            else
+            {
+                return false;
+            }
+        }
+        // Bounds checking
+        if (a == 0 || a >= m || c >= m) return false;
+        // c and m are relatively prime
+        if (c > 0 && gcd(c, m) != 1) return false;
+        // a - 1 is divisible by all prime factors of m
+        if ((a - 1) % primeFactorsOnly(m)) return false;
+        // if a - 1 is multiple of 4, then m is a  multiple of 4 too.
+        if ((a - 1) % 4 == 0 && m % 4) return false;
+        // Passed all tests
+        return true;
+    }
+
+    // check here
+    static assert(c == 0 || properLinearCongruentialParameters(m, a, c),
+            "Incorrect instantiation of LinearCongruentialEngine");
+
+/**
+Constructs a $(D_PARAM LinearCongruentialEngine) generator seeded with
+$(D x0).
+ */
+    this(UIntType x0) @safe pure
+    {
+        seed(x0);
+    }
+
+/**
+   (Re)seeds the generator.
+*/
+    void seed(UIntType x0 = 1) @safe pure
+    {
+        static if (c == 0)
+        {
+            import std.exception : enforce;
+            enforce(x0, "Invalid (zero) seed for "
+                    ~ LinearCongruentialEngine.stringof);
+        }
+        _x = modulus ? (x0 % modulus) : x0;
+        popFront();
+    }
+
+/**
+   Advances the random sequence.
+*/
+    void popFront() @safe pure nothrow
+    {
+        static if (m)
+        {
+            static if (is(UIntType == uint) && m == uint.max)
+            {
+                immutable ulong
+                    x = (cast(ulong) a * _x + c),
+                    v = x >> 32,
+                    w = x & uint.max;
+                immutable y = cast(uint)(v + w);
+                _x = (y < v || y == uint.max) ? (y + 1) : y;
+            }
+            else static if (is(UIntType == uint) && m == int.max)
+            {
+                immutable ulong
+                    x = (cast(ulong) a * _x + c),
+                    v = x >> 31,
+                    w = x & int.max;
+                immutable uint y = cast(uint)(v + w);
+                _x = (y >= int.max) ? (y - int.max) : y;
+            }
+            else
+            {
+                _x = cast(UIntType) ((cast(ulong) a * _x + c) % m);
+            }
+        }
+        else
+        {
+            _x = a * _x + c;
+        }
+    }
+
+/**
+   Returns the current number in the random sequence.
+*/
+    @property UIntType front() const @safe pure nothrow
+    {
+        return _x;
+    }
+
+///
+    @property typeof(this) save() @safe pure nothrow
+    {
+        return this;
+    }
+
+/**
+Always $(D false) (random generators are infinite ranges).
+ */
+    enum bool empty = false;
+
+/**
+   Compares against $(D_PARAM rhs) for equality.
+ */
+    bool opEquals(ref const LinearCongruentialEngine rhs) const @safe pure nothrow
+    {
+        return _x == rhs._x;
+    }
+
+    private UIntType _x = m ? (a + c) % m : (a + c);
+}
+
+/**
+Define $(D_PARAM LinearCongruentialEngine) generators with well-chosen
+parameters. $(D MinstdRand0) implements Park and Miller's "minimal
+standard" $(WEB
+wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator,
+generator) that uses 16807 for the multiplier. $(D MinstdRand)
+implements a variant that has slightly better spectral behavior by
+using the multiplier 48271. Both generators are rather simplistic.
+ */
+alias MinstdRand0 = LinearCongruentialEngine!(uint, 16807, 0, 2147483647);
+/// ditto
+alias MinstdRand = LinearCongruentialEngine!(uint, 48271, 0, 2147483647);
+
+///
+unittest
+{
+    // seed with a constant
+    auto rnd0 = MinstdRand0(1);
+    auto n = rnd0.front; // same for each run
+    // Seed with an unpredictable value
+    rnd0.seed(unpredictableSeed);
+    n = rnd0.front; // different across runs
+}
+
+unittest
+{
+    import std.range;
+    static assert(isForwardRange!MinstdRand);
+    static assert(isUniformRNG!MinstdRand);
+    static assert(isUniformRNG!MinstdRand0);
+    static assert(isUniformRNG!(MinstdRand, uint));
+    static assert(isUniformRNG!(MinstdRand0, uint));
+    static assert(isSeedable!MinstdRand);
+    static assert(isSeedable!MinstdRand0);
+    static assert(isSeedable!(MinstdRand, uint));
+    static assert(isSeedable!(MinstdRand0, uint));
+
+    // The correct numbers are taken from The Database of Integer Sequences
+    // http://www.research.att.com/~njas/sequences/eisBTfry00128.txt
+    auto checking0 = [
+        16807UL,282475249,1622650073,984943658,1144108930,470211272,
+        101027544,1457850878,1458777923,2007237709,823564440,1115438165,
+        1784484492,74243042,114807987,1137522503,1441282327,16531729,
+        823378840,143542612 ];
+    //auto rnd0 = MinstdRand0(1);
+    MinstdRand0 rnd0;
+
+    foreach (e; checking0)
+    {
+        assert(rnd0.front == e);
+        rnd0.popFront();
+    }
+    // Test the 10000th invocation
+    // Correct value taken from:
+    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf
+    rnd0.seed();
+    popFrontN(rnd0, 9999);
+    assert(rnd0.front == 1043618065);
+
+    // Test MinstdRand
+    auto checking = [48271UL,182605794,1291394886,1914720637,2078669041,
+                     407355683];
+    //auto rnd = MinstdRand(1);
+    MinstdRand rnd;
+    foreach (e; checking)
+    {
+        assert(rnd.front == e);
+        rnd.popFront();
+    }
+
+    // Test the 10000th invocation
+    // Correct value taken from:
+    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf
+    rnd.seed();
+    popFrontN(rnd, 9999);
+    assert(rnd.front == 399268537);
+
+    // Check .save works
+    foreach (Type; std.meta.AliasSeq!(MinstdRand0, MinstdRand))
+    {
+        auto rnd1 = Type(unpredictableSeed);
+        auto rnd2 = rnd1.save;
+        assert(rnd1 == rnd2);
+        // Enable next test when RNGs are reference types
+        version(none) { assert(rnd1 !is rnd2); }
+        assert(rnd1.take(100).array() == rnd2.take(100).array());
+    }
+}
+
+/**
+The $(LUCKY Mersenne Twister) generator.
+ */
+struct MersenneTwisterEngine(UIntType, size_t w, size_t n, size_t m, size_t r,
+                             UIntType a, size_t u, size_t s,
+                             UIntType b, size_t t,
+                             UIntType c, size_t l)
+    if(isUnsigned!UIntType)
+{
+    static assert(0 < w && w <= UIntType.sizeof * 8);
+    static assert(1 <= m && m <= n);
+    static assert(0 <= r && 0 <= u && 0 <= s && 0 <= t && 0 <= l);
+    static assert(r <= w && u <= w && s <= w && t <= w && l <= w);
+    static assert(0 <= a && 0 <= b && 0 <= c);
+
+    ///Mark this as a Rng
+    enum bool isUniformRandom = true;
+
+/**
+Parameters for the generator.
+*/
+    enum size_t   wordSize   = w;
+    enum size_t   stateSize  = n; /// ditto
+    enum size_t   shiftSize  = m; /// ditto
+    enum size_t   maskBits   = r; /// ditto
+    enum UIntType xorMask    = a; /// ditto
+    enum UIntType temperingU = u; /// ditto
+    enum size_t   temperingS = s; /// ditto
+    enum UIntType temperingB = b; /// ditto
+    enum size_t   temperingT = t; /// ditto
+    enum UIntType temperingC = c; /// ditto
+    enum size_t   temperingL = l; /// ditto
+
+    /// Smallest generated value (0).
+    enum UIntType min = 0;
+    /// Largest generated value.
+    enum UIntType max = UIntType.max >> (UIntType.sizeof * 8u - w);
+    static assert(a <= max && b <= max && c <= max);
+    /// The default seed value.
+    enum UIntType defaultSeed = 5489u;
+
+/**
+   Constructs a MersenneTwisterEngine object.
+*/
+    this(UIntType value) @safe pure nothrow
+    {
+        seed(value);
+    }
+
+/**
+   Seeds a MersenneTwisterEngine object.
+   Note:
+   This seed function gives 2^32 starting points. To allow the RNG to be started in any one of its
+   internal states use the seed overload taking an InputRange.
+*/
+    void seed()(UIntType value = defaultSeed) @safe pure nothrow
+    {
+        static if (w == UIntType.sizeof * 8)
+        {
+            mt[0] = value;
+        }
+        else
+        {
+            static assert(max + 1 > 0);
+            mt[0] = value % (max + 1);
+        }
+        for (mti = 1; mti < n; ++mti)
+        {
+            mt[mti] =
+                cast(UIntType)
+                (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> (w - 2))) + mti);
+            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+            /* In the previous versions, MSBs of the seed affect   */
+            /* only MSBs of the array mt[].                        */
+            /* 2002/01/09 modified by Makoto Matsumoto             */
+            //mt[mti] &= ResultType.max;
+            /* for >32 bit machines */
+        }
+        popFront();
+    }
+
+/**
+   Seeds a MersenneTwisterEngine object using an InputRange.
+
+   Throws:
+   $(D Exception) if the InputRange didn't provide enough elements to seed the generator.
+   The number of elements required is the 'n' template parameter of the MersenneTwisterEngine struct.
+ */
+    void seed(T)(T range) if(isInputRange!T && is(Unqual!(ElementType!T) == UIntType))
+    {
+        size_t j;
+        for (j = 0; j < n && !range.empty; ++j, range.popFront())
+        {
+            mt[j] = range.front;
+        }
+
+        mti = n;
+        if (range.empty && j < n)
+        {
+            import core.internal.string;
+
+            UnsignedStringBuf buf = void;
+            string s = "MersenneTwisterEngine.seed: Input range didn't provide enough elements: Need ";
+            s ~= unsignedToTempString(n, buf, 10) ~ " elements.";
+            throw new Exception(s);
+        }
+
+        popFront();
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm.iteration : map;
+        import std.range : repeat;
+
+        Mt19937 gen;
+        gen.seed(map!((a) => unpredictableSeed)(repeat(0)));
+    }
+
+/**
+   Advances the generator.
+*/
+    void popFront() @safe pure nothrow
+    {
+        if (mti == size_t.max) seed();
+        enum UIntType
+            upperMask = ~((cast(UIntType) 1u <<
+                           (UIntType.sizeof * 8 - (w - r))) - 1),
+            lowerMask = (cast(UIntType) 1u << r) - 1;
+        static immutable UIntType[2] mag01 = [0x0UL, a];
+
+        ulong y = void;
+
+        if (mti >= n)
+        {
+            /* generate N words at one time */
+
+            int kk = 0;
+            const limit1 = n - m;
+            for (; kk < limit1; ++kk)
+            {
+                y = (mt[kk] & upperMask)|(mt[kk + 1] & lowerMask);
+                mt[kk] = cast(UIntType) (mt[kk + m] ^ (y >> 1)
+                        ^ mag01[cast(UIntType) y & 0x1U]);
+            }
+            const limit2 = n - 1;
+            for (; kk < limit2; ++kk)
+            {
+                y = (mt[kk] & upperMask)|(mt[kk + 1] & lowerMask);
+                mt[kk] = cast(UIntType) (mt[kk + (m -n)] ^ (y >> 1)
+                                         ^ mag01[cast(UIntType) y & 0x1U]);
+            }
+            y = (mt[n -1] & upperMask)|(mt[0] & lowerMask);
+            mt[n - 1] = cast(UIntType) (mt[m - 1] ^ (y >> 1)
+                                        ^ mag01[cast(UIntType) y & 0x1U]);
+
+            mti = 0;
+        }
+
+        y = mt[mti++];
+
+        /* Tempering */
+        y ^= (y >> temperingU);
+        y ^= (y << temperingS) & temperingB;
+        y ^= (y << temperingT) & temperingC;
+        y ^= (y >> temperingL);
+
+        _y = cast(UIntType) y;
+    }
+
+/**
+   Returns the current random value.
+ */
+    @property UIntType front() @safe pure nothrow
+    {
+        if (mti == size_t.max) seed();
+        return _y;
+    }
+
+///
+    @property typeof(this) save() @safe pure nothrow
+    {
+        return this;
+    }
+
+/**
+Always $(D false).
+ */
+    enum bool empty = false;
+
+    private UIntType[n] mt;
+    private size_t mti = size_t.max; /* means mt is not initialized */
+    UIntType _y = UIntType.max;
+}
+
+/**
+A $(D MersenneTwisterEngine) instantiated with the parameters of the
+original engine $(WEB math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html,
+MT19937), generating uniformly-distributed 32-bit numbers with a
+period of 2 to the power of 19937. Recommended for random number
+generation unless memory is severely restricted, in which case a $(D
+LinearCongruentialEngine) would be the generator of choice.
+ */
+alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31,
+                                       0x9908b0df, 11, 7,
+                                       0x9d2c5680, 15,
+                                       0xefc60000, 18);
+
+///
+unittest
+{
+    // seed with a constant
+    Mt19937 gen;
+    auto n = gen.front; // same for each run
+    // Seed with an unpredictable value
+    gen.seed(unpredictableSeed);
+    n = gen.front; // different across runs
+}
+
+nothrow unittest
+{
+    import std.algorithm;
+    import std.range;
+    static assert(isUniformRNG!Mt19937);
+    static assert(isUniformRNG!(Mt19937, uint));
+    static assert(isSeedable!Mt19937);
+    static assert(isSeedable!(Mt19937, uint));
+    static assert(isSeedable!(Mt19937, typeof(map!((a) => unpredictableSeed)(repeat(0)))));
+    Mt19937 gen;
+    popFrontN(gen, 9999);
+    assert(gen.front == 4123659995);
+}
+
+unittest
+{
+    import std.exception;
+    import std.range;
+    import std.algorithm;
+
+    Mt19937 gen;
+
+    assertThrown(gen.seed(map!((a) => unpredictableSeed)(repeat(0, 623))));
+
+    gen.seed(map!((a) => unpredictableSeed)(repeat(0, 624)));
+    //infinite Range
+    gen.seed(map!((a) => unpredictableSeed)(repeat(0)));
+}
+
+@safe pure nothrow unittest
+{
+    uint a, b;
+    {
+        Mt19937 gen;
+        a = gen.front;
+    }
+    {
+        Mt19937 gen;
+        gen.popFront();
+        //popFrontN(gen, 1);  // skip 1 element
+        b = gen.front;
+    }
+    assert(a != b);
+}
+
+unittest
+{
+    import std.range;
+    // Check .save works
+    foreach(Type; std.meta.AliasSeq!(Mt19937))
+    {
+        auto gen1 = Type(unpredictableSeed);
+        auto gen2 = gen1.save;
+        assert(gen1 == gen2);  // Danger, Will Robinson -- no opEquals for MT
+        // Enable next test when RNGs are reference types
+        version(none) { assert(gen1 !is gen2); }
+        assert(gen1.take(100).array() == gen2.take(100).array());
+    }
+}
+
+@safe pure nothrow unittest //11690
+{
+    alias MT(UIntType, uint w) = MersenneTwisterEngine!(UIntType, w, 624, 397, 31,
+                                                        0x9908b0df, 11, 7,
+                                                        0x9d2c5680, 15,
+                                                        0xefc60000, 18);
+
+    foreach (R; std.meta.AliasSeq!(MT!(uint, 32), MT!(ulong, 32), MT!(ulong, 48), MT!(ulong, 64)))
+        auto a = R();
+}
+
+
+/**
+ * Xorshift generator using 32bit algorithm.
+ *
+ * Implemented according to $(WEB www.jstatsoft.org/v08/i14/paper, Xorshift RNGs).
+ *
+ * $(BOOKTABLE $(TEXTWITHCOMMAS Supporting bits are below, $(D bits) means second parameter of XorshiftEngine.),
+ *  $(TR $(TH bits) $(TH period))
+ *  $(TR $(TD 32)   $(TD 2^32 - 1))
+ *  $(TR $(TD 64)   $(TD 2^64 - 1))
+ *  $(TR $(TD 96)   $(TD 2^96 - 1))
+ *  $(TR $(TD 128)  $(TD 2^128 - 1))
+ *  $(TR $(TD 160)  $(TD 2^160 - 1))
+ *  $(TR $(TD 192)  $(TD 2^192 - 2^32))
+ * )
+ */
+struct XorshiftEngine(UIntType, UIntType bits, UIntType a, UIntType b, UIntType c)
+    if(isUnsigned!UIntType)
+{
+    static assert(bits == 32 || bits == 64 || bits == 96 || bits == 128 || bits == 160 || bits == 192,
+                  "Xorshift supports only 32, 64, 96, 128, 160 and 192 bit versions. "
+                  ~ to!string(bits) ~ " is not supported.");
+
+  public:
+    ///Mark this as a Rng
+    enum bool isUniformRandom = true;
+    /// Always $(D false) (random generators are infinite ranges).
+    enum empty = false;
+    /// Smallest generated value.
+    enum UIntType min = 0;
+    /// Largest generated value.
+    enum UIntType max = UIntType.max;
+
+
+  private:
+    enum size = bits / 32;
+
+    static if (bits == 32)
+        UIntType[size] seeds_ = [2463534242];
+    else static if (bits == 64)
+        UIntType[size] seeds_ = [123456789, 362436069];
+    else static if (bits == 96)
+        UIntType[size] seeds_ = [123456789, 362436069, 521288629];
+    else static if (bits == 128)
+        UIntType[size] seeds_ = [123456789, 362436069, 521288629, 88675123];
+    else static if (bits == 160)
+        UIntType[size] seeds_ = [123456789, 362436069, 521288629, 88675123, 5783321];
+    else static if (bits == 192)
+    {
+        UIntType[size] seeds_ = [123456789, 362436069, 521288629, 88675123, 5783321, 6615241];
+        UIntType       value_;
+    }
+    else
+    {
+        static assert(false, "Phobos Error: Xorshift has no instantiation rule for "
+                             ~ to!string(bits) ~ " bits.");
+    }
+
+
+  public:
+    /**
+     * Constructs a $(D XorshiftEngine) generator seeded with $(D_PARAM x0).
+     */
+    @safe
+    nothrow this(UIntType x0) pure
+    {
+        seed(x0);
+    }
+
+
+    /**
+     * (Re)seeds the generator.
+     */
+    @safe
+    nothrow void seed(UIntType x0) pure
+    {
+        // Initialization routine from MersenneTwisterEngine.
+        foreach (i, e; seeds_)
+            seeds_[i] = x0 = cast(UIntType)(1812433253U * (x0 ^ (x0 >> 30)) + i + 1);
+
+        // All seeds must not be 0.
+        sanitizeSeeds(seeds_);
+
+        popFront();
+    }
+
+
+    /**
+     * Returns the current number in the random sequence.
+     */
+    @property @safe
+    nothrow UIntType front() const pure
+    {
+        static if (bits == 192)
+            return value_;
+        else
+            return seeds_[size - 1];
+    }
+
+
+    /**
+     * Advances the random sequence.
+     */
+    @safe
+    nothrow void popFront() pure
+    {
+        UIntType temp;
+
+        static if (bits == 32)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] << a);
+            temp      = temp ^ (temp >> b);
+            seeds_[0] = temp ^ (temp << c);
+        }
+        else static if (bits == 64)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] << a);
+            seeds_[0] = seeds_[1];
+            seeds_[1] = seeds_[1] ^ (seeds_[1] >> c) ^ temp ^ (temp >> b);
+        }
+        else static if (bits == 96)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] << a);
+            seeds_[0] = seeds_[1];
+            seeds_[1] = seeds_[2];
+            seeds_[2] = seeds_[2] ^ (seeds_[2] >> c) ^ temp ^ (temp >> b);
+        }
+        else static if (bits == 128)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] << a);
+            seeds_[0] = seeds_[1];
+            seeds_[1] = seeds_[2];
+            seeds_[2] = seeds_[3];
+            seeds_[3] = seeds_[3] ^ (seeds_[3] >> c) ^ temp ^ (temp >> b);
+        }
+        else static if (bits == 160)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] << a);
+            seeds_[0] = seeds_[1];
+            seeds_[1] = seeds_[2];
+            seeds_[2] = seeds_[3];
+            seeds_[3] = seeds_[4];
+            seeds_[4] = seeds_[4] ^ (seeds_[4] >> c) ^ temp ^ (temp >> b);
+        }
+        else static if (bits == 192)
+        {
+            temp      = seeds_[0] ^ (seeds_[0] >> a);
+            seeds_[0] = seeds_[1];
+            seeds_[1] = seeds_[2];
+            seeds_[2] = seeds_[3];
+            seeds_[3] = seeds_[4];
+            seeds_[4] = seeds_[4] ^ (seeds_[4] << c) ^ temp ^ (temp << b);
+            value_    = seeds_[4] + (seeds_[5] += 362437);
+        }
+        else
+        {
+            static assert(false, "Phobos Error: Xorshift has no popFront() update for "
+                                 ~ to!string(bits) ~ " bits.");
+        }
+    }
+
+
+    /**
+     * Captures a range state.
+     */
+    @property @safe
+    nothrow typeof(this) save() pure
+    {
+        return this;
+    }
+
+
+    /**
+     * Compares against $(D_PARAM rhs) for equality.
+     */
+    @safe
+    nothrow bool opEquals(ref const XorshiftEngine rhs) const pure
+    {
+        return seeds_ == rhs.seeds_;
+    }
+
+
+  private:
+    @safe
+    static nothrow void sanitizeSeeds(ref UIntType[size] seeds) pure
+    {
+        for (uint i; i < seeds.length; i++)
+        {
+            if (seeds[i] == 0)
+                seeds[i] = i + 1;
+        }
+    }
+
+
+    @safe pure nothrow unittest
+    {
+        static if (size  ==  4)  // Other bits too
+        {
+            UIntType[size] seeds = [1, 0, 0, 4];
+
+            sanitizeSeeds(seeds);
+
+            assert(seeds == [1, 2, 3, 4]);
+        }
+    }
+}
+
+
+/**
+ * Define $(D XorshiftEngine) generators with well-chosen parameters. See each bits examples of "Xorshift RNGs".
+ * $(D Xorshift) is a Xorshift128's alias because 128bits implementation is mostly used.
+ */
+alias Xorshift32  = XorshiftEngine!(uint, 32,  13, 17, 15) ;
+alias Xorshift64  = XorshiftEngine!(uint, 64,  10, 13, 10); /// ditto
+alias Xorshift96  = XorshiftEngine!(uint, 96,  10, 5,  26); /// ditto
+alias Xorshift128 = XorshiftEngine!(uint, 128, 11, 8,  19); /// ditto
+alias Xorshift160 = XorshiftEngine!(uint, 160, 2,  1,  4);  /// ditto
+alias Xorshift192 = XorshiftEngine!(uint, 192, 2,  1,  4);  /// ditto
+alias Xorshift    = Xorshift128;                            /// ditto
+
+///
+unittest
+{
+    // Seed with a constant
+    auto rnd = Xorshift(1);
+    auto num = rnd.front;  // same for each run
+
+    // Seed with an unpredictable value
+    rnd.seed(unpredictableSeed);
+    num = rnd.front; // different across rnd
+}
+
+unittest
+{
+    import std.range;
+    static assert(isForwardRange!Xorshift);
+    static assert(isUniformRNG!Xorshift);
+    static assert(isUniformRNG!(Xorshift, uint));
+    static assert(isSeedable!Xorshift);
+    static assert(isSeedable!(Xorshift, uint));
+
+    // Result from reference implementation.
+    auto checking = [
+        [2463534242UL, 901999875, 3371835698, 2675058524, 1053936272, 3811264849, 472493137, 3856898176, 2131710969, 2312157505],
+        [362436069UL, 2113136921, 19051112, 3010520417, 951284840, 1213972223, 3173832558, 2611145638, 2515869689, 2245824891],
+        [521288629UL, 1950277231, 185954712, 1582725458, 3580567609, 2303633688, 2394948066, 4108622809, 1116800180, 3357585673],
+        [88675123UL, 3701687786, 458299110, 2500872618, 3633119408, 516391518, 2377269574, 2599949379, 717229868, 137866584],
+        [5783321UL, 393427209, 1947109840, 565829276, 1006220149, 971147905, 1436324242, 2800460115, 1484058076, 3823330032],
+        [0UL, 246875399, 3690007200, 1264581005, 3906711041, 1866187943, 2481925219, 2464530826, 1604040631, 3653403911]
+    ];
+
+    alias XorshiftTypes = std.meta.AliasSeq!(Xorshift32, Xorshift64, Xorshift96, Xorshift128, Xorshift160, Xorshift192);
+
+    foreach (I, Type; XorshiftTypes)
+    {
+        Type rnd;
+
+        foreach (e; checking[I])
+        {
+            assert(rnd.front == e);
+            rnd.popFront();
+        }
+    }
+
+    // Check .save works
+    foreach (Type; XorshiftTypes)
+    {
+        auto rnd1 = Type(unpredictableSeed);
+        auto rnd2 = rnd1.save;
+        assert(rnd1 == rnd2);
+        // Enable next test when RNGs are reference types
+        version(none) { assert(rnd1 !is rnd2); }
+        assert(rnd1.take(100).array() == rnd2.take(100).array());
+    }
+}
+
+
+/* A complete list of all pseudo-random number generators implemented in
+ * std.random.  This can be used to confirm that a given function or
+ * object is compatible with all the pseudo-random number generators
+ * available.  It is enabled only in unittest mode.
+ */
+unittest
+{
+    foreach(Rng; PseudoRngTypes)
+    {
+        static assert(isUniformRNG!Rng);
+        auto rng = Rng(unpredictableSeed);
+    }
+}
+
+
+/**
+A "good" seed for initializing random number engines. Initializing
+with $(D_PARAM unpredictableSeed) makes engines generate different
+random number sequences every run.
+
+Returns:
+A single unsigned integer seed value, different on each successive call
+*/
+@property uint unpredictableSeed() @trusted
+{
+    import core.thread : Thread, getpid, MonoTime;
+    static bool seeded;
+    static MinstdRand0 rand;
+    if (!seeded)
+    {
+        uint threadID = cast(uint) cast(void*) Thread.getThis();
+        rand.seed((getpid() + threadID) ^ cast(uint) MonoTime.currTime.ticks);
+        seeded = true;
+    }
+    rand.popFront();
+    return cast(uint) (MonoTime.currTime.ticks ^ rand.front);
+}
+
+///
+@safe unittest
+{
+    auto rnd = Random(unpredictableSeed);
+    auto n = rnd.front;
+    static assert(is(typeof(n) == uint));
+}
+
+/**
+The "default", "favorite", "suggested" random number generator type on
+the current platform. It is an alias for one of the previously-defined
+generators. You may want to use it if (1) you need to generate some
+nice random numbers, and (2) you don't care for the minutiae of the
+method being used.
+ */
+
+alias Random = Mt19937;
+
+unittest
+{
+    static assert(isUniformRNG!Random);
+    static assert(isUniformRNG!(Random, uint));
+    static assert(isSeedable!Random);
+    static assert(isSeedable!(Random, uint));
+}
+
+/**
+Global random number generator used by various functions in this
+module whenever no generator is specified. It is allocated per-thread
+and initialized to an unpredictable value for each thread.
+
+Returns:
+A singleton instance of the default random number generator
+ */
+@property ref Random rndGen() @safe
+{
+    import std.algorithm : map;
+    import std.range : repeat;
+
+    static Random result;
+    static bool initialized;
+    if (!initialized)
+    {
+        static if(isSeedable!(Random, typeof(map!((a) => unpredictableSeed)(repeat(0)))))
+            result.seed(map!((a) => unpredictableSeed)(repeat(0)));
+        else
+            result = Random(unpredictableSeed);
+        initialized = true;
+    }
+    return result;
+}
+
+/**
+Generates a number between $(D a) and $(D b). The $(D boundaries)
+parameter controls the shape of the interval (open vs. closed on
+either side). Valid values for $(D boundaries) are $(D "[]"), $(D
+"$(LPAREN)]"), $(D "[$(RPAREN)"), and $(D "()"). The default interval
+is closed to the left and open to the right. The version that does not
+take $(D urng) uses the default generator $(D rndGen).
+
+Params:
+    a = lower bound of the _uniform distribution
+    b = upper bound of the _uniform distribution
+    urng = (optional) random number generator to use;
+           if not specified, defaults to $(D rndGen)
+
+Returns:
+    A single random variate drawn from the _uniform distribution
+    between $(D a) and $(D b), whose type is the common type of
+    these parameters
+ */
+auto uniform(string boundaries = "[)", T1, T2)
+(T1 a, T2 b)  if (!is(CommonType!(T1, T2) == void))
+{
+    return uniform!(boundaries, T1, T2, Random)(a, b, rndGen);
+}
+
+///
+unittest
+{
+    auto gen = Random(unpredictableSeed);
+    // Generate an integer in [0, 1023]
+    auto a = uniform(0, 1024, gen);
+    // Generate a float in [0, 1)
+    auto b = uniform(0.0f, 1.0f, gen);
+}
+
+@safe unittest
+{
+    MinstdRand0 gen;
+    foreach (i; 0 .. 20)
+    {
+        auto x = uniform(0.0, 15.0, gen);
+        assert(0 <= x && x < 15);
+    }
+    foreach (i; 0 .. 20)
+    {
+        auto x = uniform!"[]"('a', 'z', gen);
+        assert('a' <= x && x <= 'z');
+    }
+
+    foreach (i; 0 .. 20)
+    {
+        auto x = uniform('a', 'z', gen);
+        assert('a' <= x && x < 'z');
+    }
+
+    foreach(i; 0 .. 20)
+    {
+        immutable ubyte a = 0;
+            immutable ubyte b = 15;
+        auto x = uniform(a, b, gen);
+            assert(a <= x && x < b);
+    }
+}
+
+// Implementation of uniform for floating-point types
+/// ditto
+auto uniform(string boundaries = "[)",
+        T1, T2, UniformRandomNumberGenerator)
+(T1 a, T2 b, ref UniformRandomNumberGenerator urng)
+if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGenerator)
+{
+    import std.exception : enforce;
+    import std.conv : text;
+    alias NumberType = Unqual!(CommonType!(T1, T2));
+    static if (boundaries[0] == '(')
+    {
+        import std.math : nextafter;
+        NumberType _a = nextafter(cast(NumberType) a, NumberType.infinity);
+    }
+    else
+    {
+        NumberType _a = a;
+    }
+    static if (boundaries[1] == ')')
+    {
+        import std.math : nextafter;
+        NumberType _b = nextafter(cast(NumberType) b, -NumberType.infinity);
+    }
+    else
+    {
+        NumberType _b = b;
+    }
+    enforce(_a <= _b,
+            text("std.random.uniform(): invalid bounding interval ",
+                    boundaries[0], a, ", ", b, boundaries[1]));
+    NumberType result =
+        _a + (_b - _a) * cast(NumberType) (urng.front - urng.min)
+        / (urng.max - urng.min);
+    urng.popFront();
+    return result;
+}
+
+// Implementation of uniform for integral types
+/+ Description of algorithm and suggestion of correctness:
+
+The modulus operator maps an integer to a small, finite space. For instance, `x
+% 3` will map whatever x is into the range [0 .. 3). 0 maps to 0, 1 maps to 1, 2
+maps to 2, 3 maps to 0, and so on infinitely. As long as the integer is
+uniformly chosen from the infinite space of all non-negative integers then `x %
+3` will uniformly fall into that range.
+
+(Non-negative is important in this case because some definitions of modulus,
+namely the one used in computers generally, map negative numbers differently to
+(-3 .. 0]. `uniform` does not use negative number modulus, thus we can safely
+ignore that fact.)
+
+The issue with computers is that integers have a finite space they must fit in,
+and our uniformly chosen random number is picked in that finite space. So, that
+method is not sufficient. You can look at it as the integer space being divided
+into "buckets" and every bucket after the first bucket maps directly into that
+first bucket. `[0, 1, 2]`, `[3, 4, 5]`, ... When integers are finite, then the
+last bucket has the chance to be "incomplete": `[uint.max - 3, uint.max - 2,
+uint.max - 1]`, `[uint.max]` ... (the last bucket only has 1!). The issue here
+is that _every_ bucket maps _completely_ to the first bucket except for that
+last one. The last one doesn't have corresponding mappings to 1 or 2, in this
+case, which makes it unfair.
+
+So, the answer is to simply "reroll" if you're in that last bucket, since it's
+the only unfair one. Eventually you'll roll into a fair bucket. Simply, instead
+of the meaning of the last bucket being "maps to `[0]`", it changes to "maps to
+`[0, 1, 2]`", which is precisely what we want.
+
+To generalize, `upperDist` represents the size of our buckets (and, thus, the
+exclusive upper bound for our desired uniform number). `rnum` is a uniformly
+random number picked from the space of integers that a computer can hold (we'll
+say `UpperType` represents that type).
+
+We'll first try to do the mapping into the first bucket by doing `offset = rnum
+% upperDist`. We can figure out the position of the front of the bucket we're in
+by `bucketFront = rnum - offset`.
+
+If we start at `UpperType.max` and walk backwards `upperDist - 1` spaces, then
+the space we land on is the last acceptable position where a full bucket can
+fit:
+
+```
+   bucketFront     UpperType.max
+      v                 v
+[..., 0, 1, 2, ..., upperDist - 1]
+      ^~~ upperDist - 1 ~~^
+```
+
+If the bucket starts any later, then it must have lost at least one number and
+at least that number won't be represented fairly.
+
+```
+                bucketFront     UpperType.max
+                     v                v
+[..., upperDist - 1, 0, 1, 2, ..., upperDist - 2]
+          ^~~~~~~~ upperDist - 1 ~~~~~~~^
+```
+
+Hence, our condition to reroll is
+`bucketFront > (UpperType.max - (upperDist - 1))`
++/
+auto uniform(string boundaries = "[)", T1, T2, RandomGen)
+(T1 a, T2 b, ref RandomGen rng)
+if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) &&
+     isUniformRNG!RandomGen)
+{
+    import std.exception : enforce;
+    import std.conv : text, unsigned;
+    alias ResultType = Unqual!(CommonType!(T1, T2));
+    static if (boundaries[0] == '(')
+    {
+        enforce(a < ResultType.max,
+                text("std.random.uniform(): invalid left bound ", a));
+        ResultType lower = cast(ResultType) (a + 1);
+    }
+    else
+    {
+        ResultType lower = a;
+    }
+
+    static if (boundaries[1] == ']')
+    {
+        enforce(lower <= b,
+                text("std.random.uniform(): invalid bounding interval ",
+                        boundaries[0], a, ", ", b, boundaries[1]));
+        /* Cannot use this next optimization with dchar, as dchar
+         * only partially uses its full bit range
+         */
+        static if (!is(ResultType == dchar))
+        {
+            if (b == ResultType.max && lower == ResultType.min)
+            {
+                // Special case - all bits are occupied
+                return std.random.uniform!ResultType(rng);
+            }
+        }
+        auto upperDist = unsigned(b - lower) + 1u;
+    }
+    else
+    {
+        enforce(lower < b,
+                text("std.random.uniform(): invalid bounding interval ",
+                        boundaries[0], a, ", ", b, boundaries[1]));
+        auto upperDist = unsigned(b - lower);
+    }
+
+    assert(upperDist != 0);
+
+    alias UpperType = typeof(upperDist);
+    static assert(UpperType.min == 0);
+
+    UpperType offset, rnum, bucketFront;
+    do
+    {
+        rnum = uniform!UpperType(rng);
+        offset = rnum % upperDist;
+        bucketFront = rnum - offset;
+    } // while we're in an unfair bucket...
+    while (bucketFront > (UpperType.max - (upperDist - 1)));
+
+    return cast(ResultType)(lower + offset);
+}
+
+@safe unittest
+{
+    import std.conv : to;
+    auto gen = Mt19937(unpredictableSeed);
+    static assert(isForwardRange!(typeof(gen)));
+
+    auto a = uniform(0, 1024, gen);
+    assert(0 <= a && a <= 1024);
+    auto b = uniform(0.0f, 1.0f, gen);
+    assert(0 <= b && b < 1, to!string(b));
+    auto c = uniform(0.0, 1.0);
+    assert(0 <= c && c < 1);
+
+    foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort,
+                          int, uint, long, ulong, float, double, real))
+    {
+        T lo = 0, hi = 100;
+
+        // Try tests with each of the possible bounds
+        {
+            T init = uniform(lo, hi);
+            size_t i = 50;
+            while (--i && uniform(lo, hi) == init) {}
+            assert(i > 0);
+        }
+        {
+            T init = uniform!"[)"(lo, hi);
+            size_t i = 50;
+            while (--i && uniform(lo, hi) == init) {}
+            assert(i > 0);
+        }
+        {
+            T init = uniform!"(]"(lo, hi);
+            size_t i = 50;
+            while (--i && uniform(lo, hi) == init) {}
+            assert(i > 0);
+        }
+        {
+            T init = uniform!"()"(lo, hi);
+            size_t i = 50;
+            while (--i && uniform(lo, hi) == init) {}
+            assert(i > 0);
+        }
+        {
+            T init = uniform!"[]"(lo, hi);
+            size_t i = 50;
+            while (--i && uniform(lo, hi) == init) {}
+            assert(i > 0);
+        }
+
+        /* Test case with closed boundaries covering whole range
+         * of integral type
+         */
+        static if (isIntegral!T || isSomeChar!T)
+        {
+            foreach (immutable _; 0 .. 100)
+            {
+                auto u = uniform!"[]"(T.min, T.max);
+                static assert(is(typeof(u) == T));
+                assert(T.min <= u, "Lower bound violation for uniform!\"[]\" with " ~ T.stringof);
+                assert(u <= T.max, "Upper bound violation for uniform!\"[]\" with " ~ T.stringof);
+            }
+        }
+    }
+
+    auto reproRng = Xorshift(239842);
+
+    foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short,
+                          ushort, int, uint, long, ulong))
+    {
+        T lo = T.min + 10, hi = T.max - 10;
+        T init = uniform(lo, hi, reproRng);
+        size_t i = 50;
+        while (--i && uniform(lo, hi, reproRng) == init) {}
+        assert(i > 0);
+    }
+
+    {
+        bool sawLB = false, sawUB = false;
+        foreach (i; 0 .. 50)
+        {
+            auto x = uniform!"[]"('a', 'd', reproRng);
+            if (x == 'a') sawLB = true;
+            if (x == 'd') sawUB = true;
+            assert('a' <= x && x <= 'd');
+        }
+        assert(sawLB && sawUB);
+    }
+
+    {
+        bool sawLB = false, sawUB = false;
+        foreach (i; 0 .. 50)
+        {
+            auto x = uniform('a', 'd', reproRng);
+            if (x == 'a') sawLB = true;
+            if (x == 'c') sawUB = true;
+            assert('a' <= x && x < 'd');
+        }
+        assert(sawLB && sawUB);
+    }
+
+    {
+        bool sawLB = false, sawUB = false;
+        foreach (i; 0 .. 50)
+        {
+            immutable int lo = -2, hi = 2;
+            auto x = uniform!"()"(lo, hi, reproRng);
+            if (x == (lo+1)) sawLB = true;
+            if (x == (hi-1)) sawUB = true;
+            assert(lo < x && x < hi);
+        }
+        assert(sawLB && sawUB);
+    }
+
+    {
+        bool sawLB = false, sawUB = false;
+        foreach (i; 0 .. 50)
+        {
+            immutable ubyte lo = 0, hi = 5;
+            auto x = uniform(lo, hi, reproRng);
+            if (x == lo) sawLB = true;
+            if (x == (hi-1)) sawUB = true;
+            assert(lo <= x && x < hi);
+        }
+        assert(sawLB && sawUB);
+    }
+
+    {
+        foreach (i; 0 .. 30)
+        {
+            assert(i == uniform(i, i+1, reproRng));
+        }
+    }
+}
+
+/**
+Generates a uniformly-distributed number in the range $(D [T.min,
+T.max]) for any integral or character type $(D T). If no random
+number generator is passed, uses the default $(D rndGen).
+
+Params:
+    urng = (optional) random number generator to use;
+           if not specified, defaults to $(D rndGen)
+
+Returns:
+    Random variate drawn from the _uniform distribution across all
+    possible values of the integral or character type $(D T).
+ */
+auto uniform(T, UniformRandomNumberGenerator)
+(ref UniformRandomNumberGenerator urng)
+if (!is(T == enum) && (isIntegral!T || isSomeChar!T) && isUniformRNG!UniformRandomNumberGenerator)
+{
+    /* dchar does not use its full bit range, so we must
+     * revert to the uniform with specified bounds
+     */
+    static if (is(T == dchar))
+    {
+        return uniform!"[]"(T.min, T.max);
+    }
+    else
+    {
+        auto r = urng.front;
+        urng.popFront();
+        static if (T.sizeof <= r.sizeof)
+        {
+            return cast(T) r;
+        }
+        else
+        {
+            static assert(T.sizeof == 8 && r.sizeof == 4);
+            T r1 = urng.front | (cast(T)r << 32);
+            urng.popFront();
+            return r1;
+        }
+    }
+}
+
+/// Ditto
+auto uniform(T)()
+if (!is(T == enum) && (isIntegral!T || isSomeChar!T))
+{
+    return uniform!T(rndGen);
+}
+
+@safe unittest
+{
+    foreach(T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort,
+                          int, uint, long, ulong))
+    {
+        T init = uniform!T();
+        size_t i = 50;
+        while (--i && uniform!T() == init) {}
+        assert(i > 0);
+
+        foreach (immutable _; 0 .. 100)
+        {
+            auto u = uniform!T();
+            static assert(is(typeof(u) == T));
+            assert(T.min <= u, "Lower bound violation for uniform!" ~ T.stringof);
+            assert(u <= T.max, "Upper bound violation for uniform!" ~ T.stringof);
+        }
+    }
+}
+
+/**
+Returns a uniformly selected member of enum $(D E). If no random number
+generator is passed, uses the default $(D rndGen).
+
+Params:
+    urng = (optional) random number generator to use;
+           if not specified, defaults to $(D rndGen)
+
+Returns:
+    Random variate drawn with equal probability from any
+    of the possible values of the enum $(D E).
+ */
+auto uniform(E, UniformRandomNumberGenerator)
+(ref UniformRandomNumberGenerator urng)
+if (is(E == enum) && isUniformRNG!UniformRandomNumberGenerator)
+{
+    static immutable E[EnumMembers!E.length] members = [EnumMembers!E];
+    return members[std.random.uniform(0, members.length, urng)];
+}
+
+/// Ditto
+auto uniform(E)()
+if (is(E == enum))
+{
+    return uniform!E(rndGen);
+}
+
+///
+@safe unittest
+{
+    enum Fruit { apple, mango, pear }
+    auto randFruit = uniform!Fruit();
+}
+
+@safe unittest
+{
+    enum Fruit { Apple = 12, Mango = 29, Pear = 72 }
+    foreach (_; 0 .. 100)
+    {
+        foreach(f; [uniform!Fruit(), rndGen.uniform!Fruit()])
+        {
+            assert(f == Fruit.Apple || f == Fruit.Mango || f == Fruit.Pear);
+        }
+    }
+}
+
+/**
+ * Generates a uniformly-distributed floating point number of type
+ * $(D T) in the range [0, 1$(RPAREN).  If no random number generator is
+ * specified, the default RNG $(D rndGen) will be used as the source
+ * of randomness.
+ *
+ * $(D uniform01) offers a faster generation of random variates than
+ * the equivalent $(D uniform!"[$(RPAREN)"(0.0, 1.0)) and so may be preferred
+ * for some applications.
+ *
+ * Params:
+ *     rng = (optional) random number generator to use;
+ *           if not specified, defaults to $(D rndGen)
+ *
+ * Returns:
+ *     Floating-point random variate of type $(D T) drawn from the _uniform
+ *     distribution across the half-open interval [0, 1$(RPAREN).
+ *
+ */
+T uniform01(T = double)()
+    if (isFloatingPoint!T)
+{
+    return uniform01!T(rndGen);
+}
+
+/// ditto
+T uniform01(T = double, UniformRNG)(ref UniformRNG rng)
+    if (isFloatingPoint!T && isUniformRNG!UniformRNG)
+out (result)
+{
+    assert(0 <= result);
+    assert(result < 1);
+}
+body
+{
+    alias R = typeof(rng.front);
+    static if (isIntegral!R)
+    {
+        enum T factor = 1 / (T(1) + rng.max - rng.min);
+    }
+    else static if (isFloatingPoint!R)
+    {
+        enum T factor = 1 / (rng.max - rng.min);
+    }
+    else
+    {
+        static assert(false);
+    }
+
+    while (true)
+    {
+        immutable T u = (rng.front - rng.min) * factor;
+        rng.popFront();
+
+        import core.stdc.limits : CHAR_BIT;  // CHAR_BIT is always 8
+        static if (isIntegral!R && T.mant_dig >= (CHAR_BIT * R.sizeof))
+        {
+            /* If RNG variates are integral and T has enough precision to hold
+             * R without loss, we're guaranteed by the definition of factor
+             * that precisely u < 1.
+             */
+            return u;
+        }
+        else
+        {
+            /* Otherwise we have to check whether u is beyond the assumed range
+             * because of the loss of precision, or for another reason, a
+             * floating-point RNG can return a variate that is exactly equal to
+             * its maximum.
+             */
+            if (u < 1)
+            {
+                return u;
+            }
+        }
+    }
+
+    // Shouldn't ever get here.
+    assert(false);
+}
+
+@safe unittest
+{
+    import std.meta;
+    foreach (UniformRNG; PseudoRngTypes)
+    {
+
+        foreach (T; std.meta.AliasSeq!(float, double, real))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            UniformRNG rng = UniformRNG(unpredictableSeed);
+
+            auto a = uniform01();
+            assert(is(typeof(a) == double));
+            assert(0 <= a && a < 1);
+
+            auto b = uniform01(rng);
+            assert(is(typeof(a) == double));
+            assert(0 <= b && b < 1);
+
+            auto c = uniform01!T();
+            assert(is(typeof(c) == T));
+            assert(0 <= c && c < 1);
+
+            auto d = uniform01!T(rng);
+            assert(is(typeof(d) == T));
+            assert(0 <= d && d < 1);
+
+            T init = uniform01!T(rng);
+            size_t i = 50;
+            while (--i && uniform01!T(rng) == init) {}
+            assert(i > 0);
+            assert(i < 50);
+        }();
+    }
+}
+
+/**
+Generates a uniform probability distribution of size $(D n), i.e., an
+array of size $(D n) of positive numbers of type $(D F) that sum to
+$(D 1). If $(D useThis) is provided, it is used as storage.
+ */
+F[] uniformDistribution(F = double)(size_t n, F[] useThis = null)
+    if(isFloatingPoint!F)
+{
+    import std.numeric : normalize;
+    useThis.length = n;
+    foreach (ref e; useThis)
+    {
+        e = uniform(0.0, 1);
+    }
+    normalize(useThis);
+    return useThis;
+}
+
+@safe unittest
+{
+    import std.math;
+    import std.algorithm;
+    static assert(is(CommonType!(double, int) == double));
+    auto a = uniformDistribution(5);
+    assert(a.length == 5);
+    assert(approxEqual(reduce!"a + b"(a), 1));
+    a = uniformDistribution(10, a);
+    assert(a.length == 10);
+    assert(approxEqual(reduce!"a + b"(a), 1));
+}
+
+/**
+Shuffles elements of $(D r) using $(D gen) as a shuffler. $(D r) must be
+a random-access range with length.  If no RNG is specified, $(D rndGen)
+will be used.
+
+Params:
+    r = random-access range whose elements are to be shuffled
+    gen = (optional) random number generator to use; if not
+          specified, defaults to $(D rndGen)
+ */
+
+void randomShuffle(Range, RandomGen)(Range r, ref RandomGen gen)
+    if(isRandomAccessRange!Range && isUniformRNG!RandomGen)
+{
+    return partialShuffle!(Range, RandomGen)(r, r.length, gen);
+}
+
+/// ditto
+void randomShuffle(Range)(Range r)
+    if(isRandomAccessRange!Range)
+{
+    return randomShuffle(r, rndGen);
+}
+
+unittest
+{
+    import std.algorithm;
+    foreach(RandomGen; PseudoRngTypes)
+    {
+        // Also tests partialShuffle indirectly.
+        auto a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+        auto b = a.dup;
+        auto gen = RandomGen(unpredictableSeed);
+        randomShuffle(a, gen);
+        sort(a);
+        assert(a == b);
+        randomShuffle(a);
+        sort(a);
+        assert(a == b);
+    }
+}
+
+/**
+Partially shuffles the elements of $(D r) such that upon returning $(D r[0..n])
+is a random subset of $(D r) and is randomly ordered.  $(D r[n..r.length])
+will contain the elements not in $(D r[0..n]).  These will be in an undefined
+order, but will not be random in the sense that their order after
+$(D partialShuffle) returns will not be independent of their order before
+$(D partialShuffle) was called.
+
+$(D r) must be a random-access range with length.  $(D n) must be less than
+or equal to $(D r.length).  If no RNG is specified, $(D rndGen) will be used.
+
+Params:
+    r = random-access range whose elements are to be shuffled
+    n = number of elements of $(D r) to shuffle (counting from the beginning);
+        must be less than $(D r.length)
+    gen = (optional) random number generator to use; if not
+          specified, defaults to $(D rndGen)
+*/
+void partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen)
+    if(isRandomAccessRange!Range && isUniformRNG!RandomGen)
+{
+    import std.exception : enforce;
+    import std.algorithm : swapAt;
+    enforce(n <= r.length, "n must be <= r.length for partialShuffle.");
+    foreach (i; 0 .. n)
+    {
+        swapAt(r, i, uniform(i, r.length, gen));
+    }
+}
+
+/// ditto
+void partialShuffle(Range)(Range r, in size_t n)
+    if(isRandomAccessRange!Range)
+{
+    return partialShuffle(r, n, rndGen);
+}
+
+unittest
+{
+    import std.algorithm;
+    foreach(RandomGen; PseudoRngTypes)
+    {
+        auto a = [0, 1, 1, 2, 3];
+        auto b = a.dup;
+
+        // Pick a fixed seed so that the outcome of the statistical
+        // test below is deterministic.
+        auto gen = RandomGen(12345);
+
+        // NUM times, pick LEN elements from the array at random.
+        immutable int LEN = 2;
+        immutable int NUM = 750;
+        int[][] chk;
+        foreach(step; 0..NUM)
+        {
+            partialShuffle(a, LEN, gen);
+            chk ~= a[0..LEN].dup;
+        }
+
+        // Check that each possible a[0..LEN] was produced at least once.
+        // For a perfectly random RandomGen, the probability that each
+        // particular combination failed to appear would be at most
+        // 0.95 ^^ NUM which is approximately 1,962e-17.
+        // As long as hardware failure (e.g. bit flip) probability
+        // is higher, we are fine with this unittest.
+        sort(chk);
+        assert(equal(uniq(chk), [       [0,1], [0,2], [0,3],
+                                 [1,0], [1,1], [1,2], [1,3],
+                                 [2,0], [2,1],        [2,3],
+                                 [3,0], [3,1], [3,2],      ]));
+
+        // Check that all the elements are still there.
+        sort(a);
+        assert(equal(a, b));
+    }
+}
+
+/**
+Rolls a dice with relative probabilities stored in $(D
+proportions). Returns the index in $(D proportions) that was chosen.
+
+Params:
+    rnd = (optional) random number generator to use; if not
+          specified, defaults to $(D rndGen)
+    proportions = forward range or list of individual values
+                  whose elements correspond to the probabilities
+                  with which to choose the corresponding index
+                  value
+
+Returns:
+    Random variate drawn from the index values
+    [0, ... $(D proportions.length) - 1], with the probability
+    of getting an individual index value $(D i) being proportional to
+    $(D proportions[i]).
+*/
+size_t dice(Rng, Num)(ref Rng rnd, Num[] proportions...)
+if (isNumeric!Num && isForwardRange!Rng)
+{
+    return diceImpl(rnd, proportions);
+}
+
+/// Ditto
+size_t dice(R, Range)(ref R rnd, Range proportions)
+if (isForwardRange!Range && isNumeric!(ElementType!Range) && !isArray!Range)
+{
+    return diceImpl(rnd, proportions);
+}
+
+/// Ditto
+size_t dice(Range)(Range proportions)
+if (isForwardRange!Range && isNumeric!(ElementType!Range) && !isArray!Range)
+{
+    return diceImpl(rndGen, proportions);
+}
+
+/// Ditto
+size_t dice(Num)(Num[] proportions...)
+if (isNumeric!Num)
+{
+    return diceImpl(rndGen, proportions);
+}
+
+///
+unittest
+{
+    auto x = dice(0.5, 0.5);   // x is 0 or 1 in equal proportions
+    auto y = dice(50, 50);     // y is 0 or 1 in equal proportions
+    auto z = dice(70, 20, 10); // z is 0 70% of the time, 1 20% of the time,
+                               // and 2 10% of the time
+}
+
+private size_t diceImpl(Rng, Range)(ref Rng rng, Range proportions)
+    if (isForwardRange!Range && isNumeric!(ElementType!Range) && isForwardRange!Rng)
+in
+{
+    import std.algorithm : all;
+    assert(proportions.save.all!"a >= 0");
+}
+body
+{
+    import std.exception : enforce;
+    import std.algorithm : reduce;
+    double sum = reduce!"a + b"(0.0, proportions.save);
+    enforce(sum > 0, "Proportions in a dice cannot sum to zero");
+    immutable point = uniform(0.0, sum, rng);
+    assert(point < sum);
+    auto mass = 0.0;
+
+    size_t i = 0;
+    foreach (e; proportions)
+    {
+        mass += e;
+        if (point < mass) return i;
+        i++;
+    }
+    // this point should not be reached
+    assert(false);
+}
+
+unittest
+{
+    auto rnd = Random(unpredictableSeed);
+    auto i = dice(rnd, 0.0, 100.0);
+    assert(i == 1);
+    i = dice(rnd, 100.0, 0.0);
+    assert(i == 0);
+
+    i = dice(100U, 0U);
+    assert(i == 0);
+}
+
+/**
+Covers a given range $(D r) in a random manner, i.e. goes through each
+element of $(D r) once and only once, just in a random order. $(D r)
+must be a random-access range with length.
+
+If no random number generator is passed to $(D randomCover), the
+thread-global RNG rndGen will be used internally.
+
+Params:
+    r = random-access range to cover
+    rng = (optional) random number generator to use;
+          if not specified, defaults to $(D rndGen)
+
+Returns:
+    Range whose elements consist of the elements of $(D r),
+    in random order.  Will be a forward range if both $(D r) and
+    $(D rng) are forward ranges, an input range otherwise.
+
+Example:
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
+foreach (e; randomCover(a))
+{
+    writeln(e);
+}
+----
+
+$(B WARNING:) If an alternative RNG is desired, it is essential for this
+to be a $(I new) RNG seeded in an unpredictable manner. Passing it a RNG
+used elsewhere in the program will result in unintended correlations,
+due to the current implementation of RNGs as value types.
+
+Example:
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
+foreach (e; randomCover(a, Random(unpredictableSeed)))  // correct!
+{
+    writeln(e);
+}
+
+foreach (e; randomCover(a, rndGen))  // DANGEROUS!! rndGen gets copied by value
+{
+    writeln(e);
+}
+
+foreach (e; randomCover(a, rndGen))  // ... so this second random cover
+{                                    // will output the same sequence as
+    writeln(e);                      // the previous one.
+}
+----
+ */
+struct RandomCover(Range, UniformRNG = void)
+    if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void)))
+{
+    private Range _input;
+    private bool[] _chosen;
+    private size_t _current;
+    private size_t _alreadyChosen = 0;
+
+    static if (is(UniformRNG == void))
+    {
+        this(Range input)
+        {
+            _input = input;
+            _chosen.length = _input.length;
+            if (_chosen.length == 0)
+            {
+                _alreadyChosen = 1;
+            }
+        }
+    }
+    else
+    {
+        private UniformRNG _rng;
+
+        this(Range input, ref UniformRNG rng)
+        {
+            _input = input;
+            _rng = rng;
+            _chosen.length = _input.length;
+            if (_chosen.length == 0)
+            {
+                _alreadyChosen = 1;
+            }
+        }
+
+        this(Range input, UniformRNG rng)
+        {
+            this(input, rng);
+        }
+    }
+
+    static if (hasLength!Range)
+    {
+        @property size_t length()
+        {
+            if (_alreadyChosen == 0)
+            {
+                return _input.length;
+            }
+            else
+            {
+                return (1 + _input.length) - _alreadyChosen;
+            }
+        }
+    }
+
+    @property auto ref front()
+    {
+        if (_alreadyChosen == 0)
+        {
+            popFront();
+        }
+        return _input[_current];
+    }
+
+    void popFront()
+    {
+        if (_alreadyChosen >= _input.length)
+        {
+            // No more elements
+            ++_alreadyChosen; // means we're done
+            return;
+        }
+        size_t k = _input.length - _alreadyChosen;
+        size_t i;
+        foreach (e; _input)
+        {
+            if (_chosen[i]) { ++i; continue; }
+            // Roll a dice with k faces
+            static if (is(UniformRNG == void))
+            {
+                auto chooseMe = uniform(0, k) == 0;
+            }
+            else
+            {
+                auto chooseMe = uniform(0, k, _rng) == 0;
+            }
+            assert(k > 1 || chooseMe);
+            if (chooseMe)
+            {
+                _chosen[i] = true;
+                _current = i;
+                ++_alreadyChosen;
+                return;
+            }
+            --k;
+            ++i;
+        }
+    }
+
+    static if (isForwardRange!UniformRNG)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            ret._rng = _rng.save;
+            return ret;
+        }
+    }
+
+    @property bool empty() { return _alreadyChosen > _input.length; }
+}
+
+/// Ditto
+auto randomCover(Range, UniformRNG)(Range r, auto ref UniformRNG rng)
+    if (isRandomAccessRange!Range && isUniformRNG!UniformRNG)
+{
+    return RandomCover!(Range, UniformRNG)(r, rng);
+}
+
+/// Ditto
+auto randomCover(Range)(Range r)
+    if (isRandomAccessRange!Range)
+{
+    return RandomCover!(Range, void)(r);
+}
+
+unittest
+{
+    import std.algorithm;
+    import std.conv;
+    int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
+    foreach (UniformRNG; std.meta.AliasSeq!(void, PseudoRngTypes))
+    {
+        static if (is(UniformRNG == void))
+        {
+            auto rc = randomCover(a);
+            static assert(isInputRange!(typeof(rc)));
+            static assert(!isForwardRange!(typeof(rc)));
+        }
+        else
+        {
+            auto rng = UniformRNG(unpredictableSeed);
+            auto rc = randomCover(a, rng);
+            static assert(isForwardRange!(typeof(rc)));
+            // check for constructor passed a value-type RNG
+            auto rc2 = RandomCover!(int[], UniformRNG)(a, UniformRNG(unpredictableSeed));
+            static assert(isForwardRange!(typeof(rc2)));
+        }
+
+        int[] b = new int[9];
+        uint i;
+        foreach (e; rc)
+        {
+            //writeln(e);
+            b[i++] = e;
+        }
+        sort(b);
+        assert(a == b, text(b));
+    }
+}
+
+unittest
+{
+    // Bugzilla 12589
+    int[] r = [];
+    auto rc = randomCover(r);
+    assert(rc.length == 0);
+    assert(rc.empty);
+}
+
+// RandomSample
+/**
+Selects a random subsample out of $(D r), containing exactly $(D n)
+elements. The order of elements is the same as in the original
+range. The total length of $(D r) must be known. If $(D total) is
+passed in, the total number of sample is considered to be $(D
+total). Otherwise, $(D RandomSample) uses $(D r.length).
+
+Params:
+    r = range to sample from
+    n = number of elements to include in the sample;
+        must be less than or equal to the total number
+        of elements in $(D r) and/or the parameter
+        $(D total) (if provided)
+    total = (semi-optional) number of elements of $(D r)
+            from which to select the sample (counting from
+            the beginning); must be less than or equal to
+            the total number of elements in $(D r) itself.
+            May be omitted if $(D r) has the $(D .length)
+            property and the sample is to be drawn from
+            all elements of $(D r).
+    rng = (optional) random number generator to use;
+          if not specified, defaults to $(D rndGen)
+
+Returns:
+    Range whose elements consist of a randomly selected subset of
+    the elements of $(D r), in the same order as these elements
+    appear in $(D r) itself.  Will be a forward range if both $(D r)
+    and $(D rng) are forward ranges, an input range otherwise.
+
+$(D RandomSample) implements Jeffrey Scott Vitter's Algorithm D
+(see Vitter $(WEB dx.doi.org/10.1145/358105.893, 1984), $(WEB
+dx.doi.org/10.1145/23002.23003, 1987)), which selects a sample
+of size $(D n) in O(n) steps and requiring O(n) random variates,
+regardless of the size of the data being sampled.  The exception
+to this is if traversing k elements on the input range is itself
+an O(k) operation (e.g. when sampling lines from an input file),
+in which case the sampling calculation will inevitably be of
+O(total).
+
+RandomSample will throw an exception if $(D total) is verifiably
+less than the total number of elements available in the input,
+or if $(D n > total).
+
+If no random number generator is passed to $(D randomSample), the
+thread-global RNG rndGen will be used internally.
+
+Example:
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
+// Print 5 random elements picked off from a
+foreach (e; randomSample(a, 5))
+{
+    writeln(e);
+}
+----
+
+$(B WARNING:) If an alternative RNG is desired, it is essential for this
+to be a $(I new) RNG seeded in an unpredictable manner. Passing it a RNG
+used elsewhere in the program will result in unintended correlations,
+due to the current implementation of RNGs as value types.
+
+Example:
+----
+int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
+foreach (e; randomSample(a, 5, Random(unpredictableSeed)))  // correct!
+{
+    writeln(e);
+}
+
+foreach (e; randomSample(a, 5, rndGen))  // DANGEROUS!! rndGen gets
+{                                        // copied by value
+    writeln(e);
+}
+
+foreach (e; randomSample(a, 5, rndGen))  // ... so this second random
+{                                        // sample will select the same
+    writeln(e);                          // values as the previous one.
+}
+----
+*/
+struct RandomSample(Range, UniformRNG = void)
+    if (isInputRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void)))
+{
+    private size_t _available, _toSelect;
+    private enum ushort _alphaInverse = 13; // Vitter's recommended value.
+    private double _Vprime;
+    private Range _input;
+    private size_t _index;
+    private enum Skip { None, A, D }
+    private Skip _skip = Skip.None;
+
+    // If we're using the default thread-local random number generator then
+    // we shouldn't store a copy of it here.  UniformRNG == void is a sentinel
+    // for this.  If we're using a user-specified generator then we have no
+    // choice but to store a copy.
+    static if (is(UniformRNG == void))
+    {
+        static if (hasLength!Range)
+        {
+            this(Range input, size_t howMany)
+            {
+                _input = input;
+                initialize(howMany, input.length);
+            }
+        }
+
+        this(Range input, size_t howMany, size_t total)
+        {
+            _input = input;
+            initialize(howMany, total);
+        }
+    }
+    else
+    {
+        UniformRNG _rng;
+
+        static if (hasLength!Range)
+        {
+            this(Range input, size_t howMany, ref UniformRNG rng)
+            {
+                _rng = rng;
+                _input = input;
+                initialize(howMany, input.length);
+            }
+
+            this(Range input, size_t howMany, UniformRNG rng)
+            {
+                this(input, howMany, rng);
+            }
+        }
+
+        this(Range input, size_t howMany, size_t total, ref UniformRNG rng)
+        {
+            _rng = rng;
+            _input = input;
+            initialize(howMany, total);
+        }
+
+        this(Range input, size_t howMany, size_t total, UniformRNG rng)
+        {
+            this(input, howMany, total, rng);
+        }
+    }
+
+    private void initialize(size_t howMany, size_t total)
+    {
+        import std.exception : enforce;
+        import std.conv : text;
+        _available = total;
+        _toSelect = howMany;
+        enforce(_toSelect <= _available,
+                text("RandomSample: cannot sample ", _toSelect,
+                     " items when only ", _available, " are available"));
+        static if (hasLength!Range)
+        {
+            enforce(_available <= _input.length,
+                    text("RandomSample: specified ", _available,
+                         " items as available when input contains only ",
+                         _input.length));
+        }
+    }
+
+    private void initializeFront()
+    {
+        assert(_skip == Skip.None);
+        // We can save ourselves a random variate by checking right
+        // at the beginning if we should use Algorithm A.
+        if ((_alphaInverse * _toSelect) > _available)
+        {
+            _skip = Skip.A;
+        }
+        else
+        {
+            _skip = Skip.D;
+            _Vprime = newVprime(_toSelect);
+        }
+        prime();
+    }
+
+/**
+   Range primitives.
+*/
+    @property bool empty() const
+    {
+        return _toSelect == 0;
+    }
+
+/// Ditto
+    @property auto ref front()
+    {
+        assert(!empty);
+        // The first sample point must be determined here to avoid
+        // having it always correspond to the first element of the
+        // input.  The rest of the sample points are determined each
+        // time we call popFront().
+        if (_skip == Skip.None)
+        {
+            initializeFront();
+        }
+        return _input.front;
+    }
+
+/// Ditto
+    void popFront()
+    {
+        // First we need to check if the sample has
+        // been initialized in the first place.
+        if (_skip == Skip.None)
+        {
+            initializeFront();
+        }
+
+        _input.popFront();
+        --_available;
+        --_toSelect;
+        ++_index;
+        prime();
+    }
+
+/// Ditto
+    static if (isForwardRange!Range && isForwardRange!UniformRNG)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            ret._rng = _rng.save;
+            return ret;
+        }
+    }
+
+/// Ditto
+    @property size_t length()
+    {
+        return _toSelect;
+    }
+
+/**
+Returns the index of the visited record.
+ */
+    @property size_t index()
+    {
+        if (_skip == Skip.None)
+        {
+            initializeFront();
+        }
+        return _index;
+    }
+
+    private size_t skip()
+    {
+        assert(_skip != Skip.None);
+
+        // Step D1: if the number of points still to select is greater
+        // than a certain proportion of the remaining data points, i.e.
+        // if n >= alpha * N where alpha = 1/13, we carry out the
+        // sampling with Algorithm A.
+        if (_skip == Skip.A)
+        {
+            return skipA();
+        }
+        else if ((_alphaInverse * _toSelect) > _available)
+        {
+            // We shouldn't get here unless the current selected
+            // algorithm is D.
+            assert(_skip == Skip.D);
+            _skip = Skip.A;
+            return skipA();
+        }
+        else
+        {
+            assert(_skip == Skip.D);
+            return skipD();
+        }
+    }
+
+/*
+Vitter's Algorithm A, used when the ratio of needed sample values
+to remaining data values is sufficiently large.
+*/
+    private size_t skipA()
+    {
+        size_t s;
+        double v, quot, top;
+
+        if (_toSelect==1)
+        {
+            static if (is(UniformRNG == void))
+            {
+                s = uniform(0, _available);
+            }
+            else
+            {
+                s = uniform(0, _available, _rng);
+            }
+        }
+        else
+        {
+            v = 0;
+            top = _available - _toSelect;
+            quot = top / _available;
+
+            static if (is(UniformRNG == void))
+            {
+                v = uniform!"()"(0.0, 1.0);
+            }
+            else
+            {
+                v = uniform!"()"(0.0, 1.0, _rng);
+            }
+
+            while (quot > v)
+            {
+                ++s;
+                quot *= (top - s) / (_available - s);
+            }
+        }
+
+        return s;
+    }
+
+/*
+Randomly reset the value of _Vprime.
+*/
+    private double newVprime(size_t remaining)
+    {
+        static if (is(UniformRNG == void))
+        {
+            double r = uniform!"()"(0.0, 1.0);
+        }
+        else
+        {
+            double r = uniform!"()"(0.0, 1.0, _rng);
+        }
+
+        return r ^^ (1.0 / remaining);
+    }
+
+/*
+Vitter's Algorithm D.  For an extensive description of the algorithm
+and its rationale, see:
+
+  * Vitter, J.S. (1984), "Faster methods for random sampling",
+    Commun. ACM 27(7): 703--718
+
+  * Vitter, J.S. (1987) "An efficient algorithm for sequential random
+    sampling", ACM Trans. Math. Softw. 13(1): 58-67.
+
+Variable names are chosen to match those in Vitter's paper.
+*/
+    private size_t skipD()
+    {
+        import std.math : isNaN, trunc;
+        // Confirm that the check in Step D1 is valid and we
+        // haven't been sent here by mistake
+        assert((_alphaInverse * _toSelect) <= _available);
+
+        // Now it's safe to use the standard Algorithm D mechanism.
+        if (_toSelect > 1)
+        {
+            size_t s;
+            size_t qu1 = 1 + _available - _toSelect;
+            double x, y1;
+
+            assert(!_Vprime.isNaN());
+
+            while (true)
+            {
+                // Step D2: set values of x and u.
+                while(1)
+                {
+                    x = _available * (1-_Vprime);
+                    s = cast(size_t) trunc(x);
+                    if (s < qu1)
+                        break;
+                    _Vprime = newVprime(_toSelect);
+                }
+
+                static if (is(UniformRNG == void))
+                {
+                    double u = uniform!"()"(0.0, 1.0);
+                }
+                else
+                {
+                    double u = uniform!"()"(0.0, 1.0, _rng);
+                }
+
+                y1 = (u * (cast(double) _available) / qu1) ^^ (1.0/(_toSelect - 1));
+
+                _Vprime = y1 * ((-x/_available)+1.0) * ( qu1/( (cast(double) qu1) - s ) );
+
+                // Step D3: if _Vprime <= 1.0 our work is done and we return S.
+                // Otherwise ...
+                if (_Vprime > 1.0)
+                {
+                    size_t top = _available - 1, limit;
+                    double y2 = 1.0, bottom;
+
+                    if (_toSelect > (s+1))
+                    {
+                        bottom = _available - _toSelect;
+                        limit = _available - s;
+                    }
+                    else
+                    {
+                        bottom = _available - (s+1);
+                        limit = qu1;
+                    }
+
+                    foreach (size_t t; limit .. _available)
+                    {
+                        y2 *= top/bottom;
+                        top--;
+                        bottom--;
+                    }
+
+                    // Step D4: decide whether or not to accept the current value of S.
+                    if (_available/(_available-x) < y1 * (y2 ^^ (1.0/(_toSelect-1))))
+                    {
+                        // If it's not acceptable, we generate a new value of _Vprime
+                        // and go back to the start of the for (;;) loop.
+                        _Vprime = newVprime(_toSelect);
+                    }
+                    else
+                    {
+                        // If it's acceptable we generate a new value of _Vprime
+                        // based on the remaining number of sample points needed,
+                        // and return S.
+                        _Vprime = newVprime(_toSelect-1);
+                        return s;
+                    }
+                }
+                else
+                {
+                    // Return if condition D3 satisfied.
+                    return s;
+                }
+            }
+        }
+        else
+        {
+            // If only one sample point remains to be taken ...
+            return cast(size_t) trunc(_available * _Vprime);
+        }
+    }
+
+    private void prime()
+    {
+        if (empty)
+        {
+            return;
+        }
+        assert(_available && _available >= _toSelect);
+        immutable size_t s = skip();
+        assert(s + _toSelect <= _available);
+        static if (hasLength!Range)
+        {
+            assert(s + _toSelect <= _input.length);
+        }
+        assert(!_input.empty);
+        _input.popFrontExactly(s);
+        _index += s;
+        _available -= s;
+        assert(_available > 0);
+    }
+}
+
+/// Ditto
+auto randomSample(Range)(Range r, size_t n, size_t total)
+    if (isInputRange!Range)
+{
+    return RandomSample!(Range, void)(r, n, total);
+}
+
+/// Ditto
+auto randomSample(Range)(Range r, size_t n)
+    if (isInputRange!Range && hasLength!Range)
+{
+    return RandomSample!(Range, void)(r, n, r.length);
+}
+
+/// Ditto
+auto randomSample(Range, UniformRNG)(Range r, size_t n, size_t total, auto ref UniformRNG rng)
+    if (isInputRange!Range && isUniformRNG!UniformRNG)
+{
+    return RandomSample!(Range, UniformRNG)(r, n, total, rng);
+}
+
+/// Ditto
+auto randomSample(Range, UniformRNG)(Range r, size_t n, auto ref UniformRNG rng)
+    if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG)
+{
+    return RandomSample!(Range, UniformRNG)(r, n, r.length, rng);
+}
+
+unittest
+{
+    import std.exception;
+    import std.range;
+    import std.conv : text;
+    // For test purposes, an infinite input range
+    struct TestInputRange
+    {
+        private auto r = recurrence!"a[n-1] + 1"(0);
+        bool empty() @property const pure nothrow { return r.empty; }
+        auto front() @property pure nothrow { return r.front; }
+        void popFront() pure nothrow { r.popFront(); }
+    }
+    static assert(isInputRange!TestInputRange);
+    static assert(!isForwardRange!TestInputRange);
+
+    int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
+
+    foreach (UniformRNG; PseudoRngTypes)
+    {
+        auto rng = UniformRNG(1234);
+        /* First test the most general case: randomSample of input range, with and
+         * without a specified random number generator.
+         */
+        static assert(isInputRange!(typeof(randomSample(TestInputRange(), 5, 10))));
+        static assert(isInputRange!(typeof(randomSample(TestInputRange(), 5, 10, rng))));
+        static assert(!isForwardRange!(typeof(randomSample(TestInputRange(), 5, 10))));
+        static assert(!isForwardRange!(typeof(randomSample(TestInputRange(), 5, 10, rng))));
+        // test case with range initialized by direct call to struct
+        {
+            auto sample =
+                RandomSample!(TestInputRange, UniformRNG)
+                             (TestInputRange(), 5, 10, UniformRNG(unpredictableSeed));
+            static assert(isInputRange!(typeof(sample)));
+            static assert(!isForwardRange!(typeof(sample)));
+        }
+
+        /* Now test the case of an input range with length.  We ignore the cases
+         * already covered by the previous tests.
+         */
+        static assert(isInputRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5))));
+        static assert(isInputRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5, rng))));
+        static assert(!isForwardRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5))));
+        static assert(!isForwardRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5, rng))));
+        // test case with range initialized by direct call to struct
+        {
+            auto sample =
+                RandomSample!(typeof(TestInputRange().takeExactly(10)), UniformRNG)
+                             (TestInputRange().takeExactly(10), 5, 10, UniformRNG(unpredictableSeed));
+            static assert(isInputRange!(typeof(sample)));
+            static assert(!isForwardRange!(typeof(sample)));
+        }
+
+        // Now test the case of providing a forward range as input.
+        static assert(!isForwardRange!(typeof(randomSample(a, 5))));
+        static if (isForwardRange!UniformRNG)
+        {
+            static assert(isForwardRange!(typeof(randomSample(a, 5, rng))));
+            // ... and test with range initialized directly
+            {
+                auto sample =
+                    RandomSample!(int[], UniformRNG)
+                                 (a, 5, UniformRNG(unpredictableSeed));
+                static assert(isForwardRange!(typeof(sample)));
+            }
+        }
+        else
+        {
+            static assert(isInputRange!(typeof(randomSample(a, 5, rng))));
+            static assert(!isForwardRange!(typeof(randomSample(a, 5, rng))));
+            // ... and test with range initialized directly
+            {
+                auto sample =
+                    RandomSample!(int[], UniformRNG)
+                                 (a, 5, UniformRNG(unpredictableSeed));
+                static assert(isInputRange!(typeof(sample)));
+                static assert(!isForwardRange!(typeof(sample)));
+            }
+        }
+
+        /* Check that randomSample will throw an error if we claim more
+         * items are available than there actually are, or if we try to
+         * sample more items than are available. */
+        assert(collectExceptionMsg(randomSample(a, 5, 15)) == "RandomSample: specified 15 items as available when input contains only 10");
+        assert(collectExceptionMsg(randomSample(a, 15)) == "RandomSample: cannot sample 15 items when only 10 are available");
+        assert(collectExceptionMsg(randomSample(a, 9, 8)) == "RandomSample: cannot sample 9 items when only 8 are available");
+        assert(collectExceptionMsg(randomSample(TestInputRange(), 12, 11)) == "RandomSample: cannot sample 12 items when only 11 are available");
+
+        /* Check that sampling algorithm never accidentally overruns the end of
+         * the input range.  If input is an InputRange without .length, this
+         * relies on the user specifying the total number of available items
+         * correctly.
+         */
+        {
+            uint i = 0;
+            foreach (e; randomSample(a, a.length))
+            {
+                assert(e == i);
+                ++i;
+            }
+            assert(i == a.length);
+
+            i = 0;
+            foreach (e; randomSample(TestInputRange(), 17, 17))
+            {
+                assert(e == i);
+                ++i;
+            }
+            assert(i == 17);
+        }
+
+
+        // Check length properties of random samples.
+        assert(randomSample(a, 5).length == 5);
+        assert(randomSample(a, 5, 10).length == 5);
+        assert(randomSample(a, 5, rng).length == 5);
+        assert(randomSample(a, 5, 10, rng).length == 5);
+        assert(randomSample(TestInputRange(), 5, 10).length == 5);
+        assert(randomSample(TestInputRange(), 5, 10, rng).length == 5);
+
+        // ... and emptiness!
+        assert(randomSample(a, 0).empty);
+        assert(randomSample(a, 0, 5).empty);
+        assert(randomSample(a, 0, rng).empty);
+        assert(randomSample(a, 0, 5, rng).empty);
+        assert(randomSample(TestInputRange(), 0, 10).empty);
+        assert(randomSample(TestInputRange(), 0, 10, rng).empty);
+
+        /* Test that the (lazy) evaluation of random samples works correctly.
+         *
+         * We cover 2 different cases: a sample where the ratio of sample points
+         * to total points is greater than the threshold for using Algorithm, and
+         * one where the ratio is small enough (< 1/13) for Algorithm D to be used.
+         *
+         * For each, we also cover the case with and without a specified RNG.
+         */
+        {
+            // Small sample/source ratio, no specified RNG.
+            uint i = 0;
+            foreach (e; randomSample(randomCover(a), 5))
+            {
+                ++i;
+            }
+            assert(i == 5);
+
+            // Small sample/source ratio, specified RNG.
+            i = 0;
+            foreach (e; randomSample(randomCover(a), 5, rng))
+            {
+                ++i;
+            }
+            assert(i == 5);
+
+            // Large sample/source ratio, no specified RNG.
+            i = 0;
+            foreach (e; randomSample(TestInputRange(), 123, 123_456))
+            {
+                ++i;
+            }
+            assert(i == 123);
+
+            // Large sample/source ratio, specified RNG.
+            i = 0;
+            foreach (e; randomSample(TestInputRange(), 123, 123_456, rng))
+            {
+                ++i;
+            }
+            assert(i == 123);
+
+            /* Sample/source ratio large enough to start with Algorithm D,
+             * small enough to switch to Algorithm A.
+             */
+            i = 0;
+            foreach (e; randomSample(TestInputRange(), 10, 131))
+            {
+                ++i;
+            }
+            assert(i == 10);
+        }
+
+        // Test that the .index property works correctly
+        {
+            auto sample1 = randomSample(TestInputRange(), 654, 654_321);
+            for (; !sample1.empty; sample1.popFront())
+            {
+                assert(sample1.front == sample1.index);
+            }
+
+            auto sample2 = randomSample(TestInputRange(), 654, 654_321, rng);
+            for (; !sample2.empty; sample2.popFront())
+            {
+                assert(sample2.front == sample2.index);
+            }
+
+            /* Check that it also works if .index is called before .front.
+             * See: http://d.puremagic.com/issues/show_bug.cgi?id=10322
+             */
+            auto sample3 = randomSample(TestInputRange(), 654, 654_321);
+            for (; !sample3.empty; sample3.popFront())
+            {
+                assert(sample3.index == sample3.front);
+            }
+
+            auto sample4 = randomSample(TestInputRange(), 654, 654_321, rng);
+            for (; !sample4.empty; sample4.popFront())
+            {
+                assert(sample4.index == sample4.front);
+            }
+        }
+
+        /* Test behaviour if .popFront() is called before sample is read.
+         * This is a rough-and-ready check that the statistical properties
+         * are in the ballpark -- not a proper validation of statistical
+         * quality!  This incidentally also checks for reference-type
+         * initialization bugs, as the foreach() loop will operate on a
+         * copy of the popFronted (and hence initialized) sample.
+         */
+        {
+            size_t count0, count1, count99;
+            foreach(_; 0 .. 100_000)
+            {
+                auto sample = randomSample(iota(100), 5, &rng);
+                sample.popFront();
+                foreach(s; sample)
+                {
+                    if (s == 0)
+                    {
+                        ++count0;
+                    }
+                    else if (s == 1)
+                    {
+                        ++count1;
+                    }
+                    else if (s == 99)
+                    {
+                        ++count99;
+                    }
+                }
+            }
+            /* Statistical assumptions here: this is a sequential sampling process
+             * so (i) 0 can only be the first sample point, so _can't_ be in the
+             * remainder of the sample after .popFront() is called. (ii) By similar
+             * token, 1 can only be in the remainder if it's the 2nd point of the
+             * whole sample, and hence if 0 was the first; probability of 0 being
+             * first and 1 second is 5/100 * 4/99 (thank you, Algorithm S:-) and
+             * so the mean count of 1 should be about 202.  Finally, 99 can only
+             * be the _last_ sample point to be picked, so its probability of
+             * inclusion should be independent of the .popFront() and it should
+             * occur with frequency 5/100, hence its count should be about 5000.
+             * Unfortunately we have to set quite a high tolerance because with
+             * sample size small enough for unittests to run in reasonable time,
+             * the variance can be quite high.
+             */
+            assert(count0 == 0);
+            assert(count1 < 300, text("1: ", count1, " > 300."));
+            assert(4_700 < count99, text("99: ", count99, " < 4700."));
+            assert(count99 < 5_300, text("99: ", count99, " > 5300."));
+        }
+
+        /* Odd corner-cases: RandomSample has 2 constructors that are not called
+         * by the randomSample() helper functions, but that can be used if the
+         * constructor is called directly.  These cover the case of the user
+         * specifying input but not input length.
+         */
+        {
+            auto input1 = TestInputRange().takeExactly(456_789);
+            static assert(hasLength!(typeof(input1)));
+            auto sample1 = RandomSample!(typeof(input1), void)(input1, 789);
+            static assert(isInputRange!(typeof(sample1)));
+            static assert(!isForwardRange!(typeof(sample1)));
+            assert(sample1.length == 789);
+            assert(sample1._available == 456_789);
+            uint i = 0;
+            for (; !sample1.empty; sample1.popFront())
+            {
+                assert(sample1.front == sample1.index);
+                ++i;
+            }
+            assert(i == 789);
+
+            auto input2 = TestInputRange().takeExactly(456_789);
+            static assert(hasLength!(typeof(input2)));
+            auto sample2 = RandomSample!(typeof(input2), typeof(rng))(input2, 789, rng);
+            static assert(isInputRange!(typeof(sample2)));
+            static assert(!isForwardRange!(typeof(sample2)));
+            assert(sample2.length == 789);
+            assert(sample2._available == 456_789);
+            i = 0;
+            for (; !sample2.empty; sample2.popFront())
+            {
+                assert(sample2.front == sample2.index);
+                ++i;
+            }
+            assert(i == 789);
+        }
+
+        /* Test that the save property works where input is a forward range,
+         * and RandomSample is using a (forward range) random number generator
+         * that is not rndGen.
+         */
+        static if (isForwardRange!UniformRNG)
+        {
+            auto sample1 = randomSample(a, 5, rng);
+            auto sample2 = sample1.save;
+            assert(sample1.array() == sample2.array());
+        }
+
+        // Bugzilla 8314
+        {
+            auto sample(RandomGen)(uint seed) { return randomSample(a, 1, RandomGen(seed)).front; }
+
+            // Start from 1 because not all RNGs accept 0 as seed.
+            immutable fst = sample!UniformRNG(1);
+            uint n = 1;
+            while (sample!UniformRNG(++n) == fst && n < n.max) {}
+            assert(n < n.max);
+        }
+    }
+}
diff --git a/libphobos/src/std/range/interfaces.d b/libphobos/src/std/range/interfaces.d
new file mode 100644
index 00000000000..734dc72a59f
--- /dev/null
+++ b/libphobos/src/std/range/interfaces.d
@@ -0,0 +1,509 @@
+/**
+This module is a submodule of $(LINK2 std_range.html, std.range).
+
+The main $(D std.range) module provides template-based tools for working with
+ranges, but sometimes an object-based interface for ranges is needed, such as
+when runtime polymorphism is required. For this purpose, this submodule
+provides a number of object and $(D interface) definitions that can be used to
+wrap around _range objects created by the $(D std.range) templates.
+
+$(BOOKTABLE ,
+    $(TR $(TD $(D $(LREF InputRange)))
+        $(TD Wrapper for input ranges.
+    ))
+    $(TR $(TD $(D $(LREF InputAssignable)))
+        $(TD Wrapper for input ranges with assignable elements.
+    ))
+    $(TR $(TD $(D $(LREF ForwardRange)))
+        $(TD Wrapper for forward ranges.
+    ))
+    $(TR $(TD $(D $(LREF ForwardAssignable)))
+        $(TD Wrapper for forward ranges with assignable elements.
+    ))
+    $(TR $(TD $(D $(LREF BidirectionalRange)))
+        $(TD Wrapper for bidirectional ranges.
+    ))
+    $(TR $(TD $(D $(LREF BidirectionalAssignable)))
+        $(TD Wrapper for bidirectional ranges with assignable elements.
+    ))
+    $(TR $(TD $(D $(LREF RandomAccessFinite)))
+        $(TD Wrapper for finite random-access ranges.
+    ))
+    $(TR $(TD $(D $(LREF RandomAccessAssignable)))
+        $(TD Wrapper for finite random-access ranges with assignable elements.
+    ))
+    $(TR $(TD $(D $(LREF RandomAccessInfinite)))
+        $(TD Wrapper for infinite random-access ranges.
+    ))
+    $(TR $(TD $(D $(LREF OutputRange)))
+        $(TD Wrapper for output ranges.
+    ))
+    $(TR $(TD $(D $(LREF OutputRangeObject)))
+        $(TD Class that implements the $(D OutputRange) interface and wraps the
+        $(D put) methods in virtual functions.
+    ))
+    $(TR $(TD $(D $(LREF InputRangeObject)))
+        $(TD Class that implements the $(D InputRange) interface and wraps the
+        input _range methods in virtual functions.
+    ))
+    $(TR $(TD $(D $(LREF RefRange)))
+        $(TD Wrapper around a forward _range that gives it reference semantics.
+    ))
+)
+
+
+Source: $(PHOBOSSRC std/range/_interfaces.d)
+
+Macros:
+
+WIKI = Phobos/StdRange
+
+Copyright: Copyright by authors 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha,
+and Jonathan M Davis. Credit for some of the ideas in building this module goes
+to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi).
+*/
+module std.range.interfaces;
+
+import std.meta;
+import std.range.primitives;
+import std.traits;
+
+/**These interfaces are intended to provide virtual function-based wrappers
+ * around input ranges with element type E.  This is useful where a well-defined
+ * binary interface is required, such as when a DLL function or virtual function
+ * needs to accept a generic range as a parameter. Note that
+ * $(LINK2 std_range_primitives.html#isInputRange, isInputRange) and friends check for conformance to structural
+ * interfaces, not for implementation of these $(D interface) types.
+ *
+ * Limitations:
+ *
+ * These interfaces are not capable of forwarding $(D ref) access to elements.
+ *
+ * Infiniteness of the wrapped range is not propagated.
+ *
+ * Length is not propagated in the case of non-random access ranges.
+ *
+ * See_Also:
+ * $(LREF inputRangeObject)
+ */
+interface InputRange(E) {
+    ///
+    @property E front();
+
+    ///
+    E moveFront();
+
+    ///
+    void popFront();
+
+    ///
+    @property bool empty();
+
+    /* Measurements of the benefits of using opApply instead of range primitives
+     * for foreach, using timings for iterating over an iota(100_000_000) range
+     * with an empty loop body, using the same hardware in each case:
+     *
+     * Bare Iota struct, range primitives:  278 milliseconds
+     * InputRangeObject, opApply:           436 milliseconds  (1.57x penalty)
+     * InputRangeObject, range primitives:  877 milliseconds  (3.15x penalty)
+     */
+
+    /**$(D foreach) iteration uses opApply, since one delegate call per loop
+     * iteration is faster than three virtual function calls.
+     */
+    int opApply(int delegate(E));
+
+    /// Ditto
+    int opApply(int delegate(size_t, E));
+
+}
+
+///
+unittest
+{
+    import std.algorithm : map;
+    import std.range : iota;
+
+    void useRange(InputRange!int range) {
+        // Function body.
+    }
+
+    // Create a range type.
+    auto squares = map!"a * a"(iota(10));
+
+    // Wrap it in an interface.
+    auto squaresWrapped = inputRangeObject(squares);
+
+    // Use it.
+    useRange(squaresWrapped);
+}
+
+/**Interface for a forward range of type $(D E).*/
+interface ForwardRange(E) : InputRange!E {
+    ///
+    @property ForwardRange!E save();
+}
+
+/**Interface for a bidirectional range of type $(D E).*/
+interface BidirectionalRange(E) : ForwardRange!(E) {
+    ///
+    @property BidirectionalRange!E save();
+
+    ///
+    @property E back();
+
+    ///
+    E moveBack();
+
+    ///
+    void popBack();
+}
+
+/**Interface for a finite random access range of type $(D E).*/
+interface RandomAccessFinite(E) : BidirectionalRange!(E) {
+    ///
+    @property RandomAccessFinite!E save();
+
+    ///
+    E opIndex(size_t);
+
+    ///
+    E moveAt(size_t);
+
+    ///
+    @property size_t length();
+
+    ///
+    alias opDollar = length;
+
+    // Can't support slicing until issues with requiring slicing for all
+    // finite random access ranges are fully resolved.
+    version(none) {
+        ///
+        RandomAccessFinite!E opSlice(size_t, size_t);
+    }
+}
+
+/**Interface for an infinite random access range of type $(D E).*/
+interface RandomAccessInfinite(E) : ForwardRange!E {
+    ///
+    E moveAt(size_t);
+
+    ///
+    @property RandomAccessInfinite!E save();
+
+    ///
+    E opIndex(size_t);
+}
+
+/**Adds assignable elements to InputRange.*/
+interface InputAssignable(E) : InputRange!E {
+    ///
+    @property void front(E newVal);
+}
+
+/**Adds assignable elements to ForwardRange.*/
+interface ForwardAssignable(E) : InputAssignable!E, ForwardRange!E {
+    ///
+    @property ForwardAssignable!E save();
+}
+
+/**Adds assignable elements to BidirectionalRange.*/
+interface BidirectionalAssignable(E) : ForwardAssignable!E, BidirectionalRange!E {
+    ///
+    @property BidirectionalAssignable!E save();
+
+    ///
+    @property void back(E newVal);
+}
+
+/**Adds assignable elements to RandomAccessFinite.*/
+interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssignable!E {
+    ///
+    @property RandomFiniteAssignable!E save();
+
+    ///
+    void opIndexAssign(E val, size_t index);
+}
+
+/**Interface for an output range of type $(D E).  Usage is similar to the
+ * $(D InputRange) interface and descendants.*/
+interface OutputRange(E) {
+    ///
+    void put(E);
+}
+
+@safe unittest
+{
+    // 6973
+    static assert(isOutputRange!(OutputRange!int, int));
+}
+
+
+// CTFE function that generates mixin code for one put() method for each
+// type E.
+private string putMethods(E...)()
+{
+    import std.conv : to;
+
+    string ret;
+
+    foreach (ti, Unused; E)
+    {
+        ret ~= "void put(E[" ~ to!string(ti) ~ "] e) { .put(_range, e); }";
+    }
+
+    return ret;
+}
+
+/**Implements the $(D OutputRange) interface for all types E and wraps the
+ * $(D put) method for each type $(D E) in a virtual function.
+ */
+class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) {
+    // @BUG 4689:  There should be constraints on this template class, but
+    // DMD won't let me put them in.
+    private R _range;
+
+    this(R range) {
+        this._range = range;
+    }
+
+    mixin(putMethods!E());
+}
+
+
+/**Returns the interface type that best matches $(D R).*/
+template MostDerivedInputRange(R) if (isInputRange!(Unqual!R)) {
+    private alias E = ElementType!R;
+
+    static if (isRandomAccessRange!R) {
+        static if (isInfinite!R) {
+            alias MostDerivedInputRange = RandomAccessInfinite!E;
+        } else static if (hasAssignableElements!R) {
+            alias MostDerivedInputRange = RandomFiniteAssignable!E;
+        } else {
+            alias MostDerivedInputRange = RandomAccessFinite!E;
+        }
+    } else static if (isBidirectionalRange!R) {
+        static if (hasAssignableElements!R) {
+            alias MostDerivedInputRange = BidirectionalAssignable!E;
+        } else {
+            alias MostDerivedInputRange = BidirectionalRange!E;
+        }
+    } else static if (isForwardRange!R) {
+        static if (hasAssignableElements!R) {
+            alias MostDerivedInputRange = ForwardAssignable!E;
+        } else {
+            alias MostDerivedInputRange = ForwardRange!E;
+        }
+    } else {
+        static if (hasAssignableElements!R) {
+            alias MostDerivedInputRange = InputAssignable!E;
+        } else {
+            alias MostDerivedInputRange = InputRange!E;
+        }
+    }
+}
+
+/**Implements the most derived interface that $(D R) works with and wraps
+ * all relevant range primitives in virtual functions.  If $(D R) is already
+ * derived from the $(D InputRange) interface, aliases itself away.
+ */
+template InputRangeObject(R) if (isInputRange!(Unqual!R)) {
+    static if (is(R : InputRange!(ElementType!R))) {
+        alias InputRangeObject = R;
+    } else static if (!is(Unqual!R == R)) {
+        alias InputRangeObject = InputRangeObject!(Unqual!R);
+    } else {
+
+        ///
+        class InputRangeObject : MostDerivedInputRange!(R) {
+            private R _range;
+            private alias E = ElementType!R;
+
+            this(R range) {
+                this._range = range;
+            }
+
+            @property E front() { return _range.front; }
+
+            E moveFront() {
+                return .moveFront(_range);
+            }
+
+            void popFront() { _range.popFront(); }
+            @property bool empty() { return _range.empty; }
+
+            static if (isForwardRange!R) {
+                @property typeof(this) save() {
+                    return new typeof(this)(_range.save);
+                }
+            }
+
+            static if (hasAssignableElements!R) {
+                @property void front(E newVal) {
+                    _range.front = newVal;
+                }
+            }
+
+            static if (isBidirectionalRange!R) {
+                @property E back() { return _range.back; }
+
+                E moveBack() {
+                    return .moveBack(_range);
+                }
+
+                void popBack() { return _range.popBack(); }
+
+                static if (hasAssignableElements!R) {
+                    @property void back(E newVal) {
+                        _range.back = newVal;
+                    }
+                }
+            }
+
+            static if (isRandomAccessRange!R) {
+                E opIndex(size_t index) {
+                    return _range[index];
+                }
+
+                E moveAt(size_t index) {
+                    return .moveAt(_range, index);
+                }
+
+                static if (hasAssignableElements!R) {
+                    void opIndexAssign(E val, size_t index) {
+                        _range[index] = val;
+                    }
+                }
+
+                static if (!isInfinite!R) {
+                    @property size_t length() {
+                        return _range.length;
+                    }
+
+                    alias opDollar = length;
+
+                    // Can't support slicing until all the issues with
+                    // requiring slicing support for finite random access
+                    // ranges are resolved.
+                    version(none) {
+                        typeof(this) opSlice(size_t lower, size_t upper) {
+                            return new typeof(this)(_range[lower..upper]);
+                        }
+                    }
+                }
+            }
+
+            // Optimization:  One delegate call is faster than three virtual
+            // function calls.  Use opApply for foreach syntax.
+            int opApply(int delegate(E) dg) {
+                int res;
+
+                for(auto r = _range; !r.empty; r.popFront()) {
+                    res = dg(r.front);
+                    if (res) break;
+                }
+
+                return res;
+            }
+
+            int opApply(int delegate(size_t, E) dg) {
+                int res;
+
+                size_t i = 0;
+                for(auto r = _range; !r.empty; r.popFront()) {
+                    res = dg(i, r.front);
+                    if (res) break;
+                    i++;
+                }
+
+                return res;
+            }
+        }
+    }
+}
+
+/**Convenience function for creating an $(D InputRangeObject) of the proper type.
+ * See $(LREF InputRange) for an example.
+ */
+InputRangeObject!R inputRangeObject(R)(R range) if (isInputRange!R) {
+    static if (is(R : InputRange!(ElementType!R))) {
+        return range;
+    } else {
+        return new InputRangeObject!R(range);
+    }
+}
+
+/**Convenience function for creating an $(D OutputRangeObject) with a base range
+ * of type $(D R) that accepts types $(D E).
+*/
+template outputRangeObject(E...) {
+
+    ///
+    OutputRangeObject!(R, E) outputRangeObject(R)(R range) {
+        return new OutputRangeObject!(R, E)(range);
+    }
+}
+
+///
+unittest
+{
+     import std.array;
+     auto app = appender!(uint[])();
+     auto appWrapped = outputRangeObject!(uint, uint[])(app);
+     static assert(is(typeof(appWrapped) : OutputRange!(uint[])));
+     static assert(is(typeof(appWrapped) : OutputRange!(uint)));
+}
+
+unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+    import std.array;
+
+    static void testEquality(R)(iInputRange r1, R r2) {
+        assert(equal(r1, r2));
+    }
+
+    auto arr = [1,2,3,4];
+    RandomFiniteAssignable!int arrWrapped = inputRangeObject(arr);
+    static assert(isRandomAccessRange!(typeof(arrWrapped)));
+    //    static assert(hasSlicing!(typeof(arrWrapped)));
+    static assert(hasLength!(typeof(arrWrapped)));
+    arrWrapped[0] = 0;
+    assert(arr[0] == 0);
+    assert(arr.moveFront() == 0);
+    assert(arr.moveBack() == 4);
+    assert(arr.moveAt(1) == 2);
+
+    foreach(elem; arrWrapped) {}
+    foreach(i, elem; arrWrapped) {}
+
+    assert(inputRangeObject(arrWrapped) is arrWrapped);
+
+    foreach(DummyType; AllDummyRanges) {
+        auto d = DummyType.init;
+        static assert(propagatesRangeType!(DummyType,
+                        typeof(inputRangeObject(d))));
+        static assert(propagatesRangeType!(DummyType,
+                        MostDerivedInputRange!DummyType));
+        InputRange!uint wrapped = inputRangeObject(d);
+        assert(equal(wrapped, d));
+    }
+
+    // Test output range stuff.
+    auto app = appender!(uint[])();
+    auto appWrapped = outputRangeObject!(uint, uint[])(app);
+    static assert(is(typeof(appWrapped) : OutputRange!(uint[])));
+    static assert(is(typeof(appWrapped) : OutputRange!(uint)));
+
+    appWrapped.put(1);
+    appWrapped.put([2, 3]);
+    assert(app.data.length == 3);
+    assert(equal(app.data, [1,2,3]));
+}
diff --git a/libphobos/src/std/range/package.d b/libphobos/src/std/range/package.d
new file mode 100644
index 00000000000..2ca25e53284
--- /dev/null
+++ b/libphobos/src/std/range/package.d
@@ -0,0 +1,9362 @@
+// Written in the D programming language.
+
+/**
+This module defines the notion of a range. Ranges generalize the concept of
+arrays, lists, or anything that involves sequential access. This abstraction
+enables the same set of algorithms (see $(LINK2 std_algorithm.html,
+std.algorithm)) to be used with a vast variety of different concrete types. For
+example, a linear search algorithm such as $(LINK2 std_algorithm.html#find,
+std.algorithm.find) works not just for arrays, but for linked-lists, input
+files, incoming network data, etc. See also Ali Çehreli's
+$(WEB ddili.org/ders/d.en/ranges.html, tutorial on ranges) for the basics
+of working with and creating range-based code.
+
+For more detailed information about the conceptual aspect of ranges and the
+motivation behind them, see Andrei Alexandrescu's article
+$(LINK2 http://www.informit.com/articles/printerfriendly.aspx?p=1407357$(AMP)rll=1,
+$(I On Iteration)).
+
+Submodules:
+
+This module has two submodules:
+
+The $(LINK2 std_range_primitives.html, $(D std._range.primitives)) submodule
+provides basic _range functionality. It defines several templates for testing
+whether a given object is a _range, what kind of _range it is, and provides
+some common _range operations.
+
+The $(LINK2 std_range_interfaces.html, $(D std._range.interfaces)) submodule
+provides object-based interfaces for working with ranges via runtime
+polymorphism.
+
+The remainder of this module provides a rich set of _range creation and
+composition templates that let you construct new ranges out of existing ranges:
+
+$(BOOKTABLE ,
+    $(TR $(TD $(D $(LREF chain)))
+        $(TD Concatenates several ranges into a single _range.
+    ))
+    $(TR $(TD $(D $(LREF choose)))
+        $(TD Chooses one of two ranges at runtime based on a boolean condition.
+    ))
+    $(TR $(TD $(D $(LREF chooseAmong)))
+        $(TD Chooses one of several ranges at runtime based on an index.
+    ))
+    $(TR $(TD $(D $(LREF chunks)))
+        $(TD Creates a _range that returns fixed-size chunks of the original
+        _range.
+    ))
+    $(TR $(TD $(D $(LREF cycle)))
+        $(TD Creates an infinite _range that repeats the given forward _range
+        indefinitely. Good for implementing circular buffers.
+    ))
+    $(TR $(TD $(D $(LREF drop)))
+        $(TD Creates the _range that results from discarding the first $(I n)
+        elements from the given _range.
+    ))
+    $(TR $(TD $(D $(LREF dropExactly)))
+        $(TD Creates the _range that results from discarding exactly $(I n)
+        of the first elements from the given _range.
+    ))
+    $(TR $(TD $(D $(LREF dropOne)))
+        $(TD Creates the _range that results from discarding
+        the first elements from the given _range.
+    ))
+    $(TR $(TD $(D $(LREF enumerate)))
+        $(TD Iterates a _range with an attached index variable.
+    ))
+    $(TR $(TD $(D $(LREF evenChunks)))
+        $(TD Creates a _range that returns a number of chunks of
+        approximately equal length from the original _range.
+    ))
+    $(TR $(TD $(D $(LREF frontTransversal)))
+        $(TD Creates a _range that iterates over the first elements of the
+        given ranges.
+    ))
+    $(TR $(TD $(D $(LREF indexed)))
+        $(TD Creates a _range that offers a view of a given _range as though
+        its elements were reordered according to a given _range of indices.
+    ))
+    $(TR $(TD $(D $(LREF iota)))
+        $(TD Creates a _range consisting of numbers between a starting point
+        and ending point, spaced apart by a given interval.
+    ))
+    $(TR $(TD $(D $(LREF lockstep)))
+        $(TD Iterates $(I n) _ranges in lockstep, for use in a $(D foreach)
+        loop. Similar to $(D zip), except that $(D lockstep) is designed
+        especially for $(D foreach) loops.
+    ))
+    $(TR $(TD $(D $(LREF NullSink)))
+        $(TD An output _range that discards the data it receives.
+    ))
+    $(TR $(TD $(D $(LREF only)))
+        $(TD Creates a _range that iterates over the given arguments.
+    ))
+    $(TR $(TD $(D $(LREF radial)))
+        $(TD Given a random-access _range and a starting point, creates a
+        _range that alternately returns the next left and next right element to
+        the starting point.
+    ))
+    $(TR $(TD $(D $(LREF recurrence)))
+        $(TD Creates a forward _range whose values are defined by a
+        mathematical recurrence relation.
+    ))
+    $(TR $(TD $(D $(LREF repeat)))
+        $(TD Creates a _range that consists of a single element repeated $(I n)
+        times, or an infinite _range repeating that element indefinitely.
+    ))
+    $(TR $(TD $(D $(LREF retro)))
+        $(TD Iterates a bidirectional _range backwards.
+    ))
+    $(TR $(TD $(D $(LREF roundRobin)))
+        $(TD Given $(I n) ranges, creates a new _range that return the $(I n)
+        first elements of each _range, in turn, then the second element of each
+        _range, and so on, in a round-robin fashion.
+    ))
+    $(TR $(TD $(D $(LREF sequence)))
+        $(TD Similar to $(D recurrence), except that a random-access _range is
+        created.
+    ))
+    $(TR $(TD $(D $(LREF stride)))
+        $(TD Iterates a _range with stride $(I n).
+    ))
+    $(TR $(TD $(D $(LREF tail)))
+        $(TD Return a _range advanced to within $(D n) elements of the end of
+        the given _range.
+    ))
+    $(TR $(TD $(D $(LREF take)))
+        $(TD Creates a sub-_range consisting of only up to the first $(I n)
+        elements of the given _range.
+    ))
+    $(TR $(TD $(D $(LREF takeExactly)))
+        $(TD Like $(D take), but assumes the given _range actually has $(I n)
+        elements, and therefore also defines the $(D length) property.
+    ))
+    $(TR $(TD $(D $(LREF takeNone)))
+        $(TD Creates a random-access _range consisting of zero elements of the
+        given _range.
+    ))
+    $(TR $(TD $(D $(LREF takeOne)))
+        $(TD Creates a random-access _range consisting of exactly the first
+        element of the given _range.
+    ))
+    $(TR $(TD $(D $(LREF tee)))
+        $(TD Creates a _range that wraps a given _range, forwarding along
+        its elements while also calling a provided function with each element.
+    ))
+    $(TR $(TD $(D $(LREF transposed)))
+        $(TD Transposes a _range of ranges.
+    ))
+    $(TR $(TD $(D $(LREF transversal)))
+        $(TD Creates a _range that iterates over the $(I n)'th elements of the
+        given random-access ranges.
+    ))
+    $(TR $(TD $(D $(LREF zip)))
+        $(TD Given $(I n) _ranges, creates a _range that successively returns a
+        tuple of all the first elements, a tuple of all the second elements,
+        etc.
+    ))
+)
+
+Ranges whose elements are sorted afford better efficiency with certain
+operations. For this, the $(D $(LREF assumeSorted)) function can be used to
+construct a $(D $(LREF SortedRange)) from a pre-sorted _range. The $(LINK2
+std_algorithm.html#sort, $(D std.algorithm.sort)) function also conveniently
+returns a $(D SortedRange). $(D SortedRange) objects provide some additional
+_range operations that take advantage of the fact that the _range is sorted.
+
+Source: $(PHOBOSSRC std/_range/_package.d)
+
+Macros:
+
+WIKI = Phobos/StdRange
+
+Copyright: Copyright by authors 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha,
+and Jonathan M Davis. Credit for some of the ideas in building this module goes
+to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi).
+ */
+module std.range;
+
+public import std.range.primitives;
+public import std.range.interfaces;
+public import std.array;
+public import std.typecons : Flag, Yes, No;
+
+import std.meta;
+import std.traits;
+
+
+/**
+Iterates a bidirectional range backwards. The original range can be
+accessed by using the $(D source) property. Applying retro twice to
+the same range yields the original range.
+ */
+auto retro(Range)(Range r)
+if (isBidirectionalRange!(Unqual!Range))
+{
+    // Check for retro(retro(r)) and just return r in that case
+    static if (is(typeof(retro(r.source)) == Range))
+    {
+        return r.source;
+    }
+    else
+    {
+        static struct Result()
+        {
+            private alias R = Unqual!Range;
+
+            // User code can get and set source, too
+            R source;
+
+            static if (hasLength!R)
+            {
+                private alias IndexType = CommonType!(size_t, typeof(source.length));
+
+                IndexType retroIndex(IndexType n)
+                {
+                    return source.length - n - 1;
+                }
+            }
+
+        public:
+            alias Source = R;
+
+            @property bool empty() { return source.empty; }
+            @property auto save()
+            {
+                return Result(source.save);
+            }
+            @property auto ref front() { return source.back; }
+            void popFront() { source.popBack(); }
+            @property auto ref back() { return source.front; }
+            void popBack() { source.popFront(); }
+
+            static if(is(typeof(.moveBack(source))))
+            {
+                ElementType!R moveFront()
+                {
+                    return .moveBack(source);
+                }
+            }
+
+            static if(is(typeof(.moveFront(source))))
+            {
+                ElementType!R moveBack()
+                {
+                    return .moveFront(source);
+                }
+            }
+
+            static if (hasAssignableElements!R)
+            {
+                @property auto front(ElementType!R val)
+                {
+                    source.back = val;
+                }
+
+                @property auto back(ElementType!R val)
+                {
+                    source.front = val;
+                }
+            }
+
+            static if (isRandomAccessRange!(R) && hasLength!(R))
+            {
+                auto ref opIndex(IndexType n) { return source[retroIndex(n)]; }
+
+                static if (hasAssignableElements!R)
+                {
+                    void opIndexAssign(ElementType!R val, IndexType n)
+                    {
+                        source[retroIndex(n)] = val;
+                    }
+                }
+
+                static if (is(typeof(.moveAt(source, 0))))
+                {
+                    ElementType!R moveAt(IndexType index)
+                    {
+                        return .moveAt(source, retroIndex(index));
+                    }
+                }
+
+                static if (hasSlicing!R)
+                    typeof(this) opSlice(IndexType a, IndexType b)
+                    {
+                        return typeof(this)(source[source.length - b .. source.length - a]);
+                    }
+            }
+
+            static if (hasLength!R)
+            {
+                @property auto length()
+                {
+                    return source.length;
+                }
+
+                alias opDollar = length;
+            }
+        }
+
+        return Result!()(r);
+    }
+}
+
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    assert(equal(retro(a), [ 5, 4, 3, 2, 1 ][]));
+    assert(retro(a).source is a);
+    assert(retro(retro(a)) is a);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    static assert(isBidirectionalRange!(typeof(retro("hello"))));
+    int[] a;
+    static assert(is(typeof(a) == typeof(retro(retro(a)))));
+    assert(retro(retro(a)) is a);
+    static assert(isRandomAccessRange!(typeof(retro([1, 2, 3]))));
+    void test(int[] input, int[] witness)
+    {
+        auto r = retro(input);
+        assert(r.front == witness.front);
+        assert(r.back == witness.back);
+        assert(equal(r, witness));
+    }
+    test([ 1 ], [ 1 ]);
+    test([ 1, 2 ], [ 2, 1 ]);
+    test([ 1, 2, 3 ], [ 3, 2, 1 ]);
+    test([ 1, 2, 3, 4 ], [ 4, 3, 2, 1 ]);
+    test([ 1, 2, 3, 4, 5 ], [ 5, 4, 3, 2, 1 ]);
+    test([ 1, 2, 3, 4, 5, 6 ], [ 6, 5, 4, 3, 2, 1 ]);
+
+   immutable foo = [1,2,3].idup;
+   auto r = retro(foo);
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+
+    foreach(DummyType; AllDummyRanges) {
+        static if (!isBidirectionalRange!DummyType) {
+            static assert(!__traits(compiles, Retro!DummyType));
+        } else {
+            DummyType dummyRange;
+            dummyRange.reinit();
+
+            auto myRetro = retro(dummyRange);
+            static assert(propagatesRangeType!(typeof(myRetro), DummyType));
+            assert(myRetro.front == 10);
+            assert(myRetro.back == 1);
+            assert(myRetro.moveFront() == 10);
+            assert(myRetro.moveBack() == 1);
+
+            static if (isRandomAccessRange!DummyType && hasLength!DummyType) {
+                assert(myRetro[0] == myRetro.front);
+                assert(myRetro.moveAt(2) == 8);
+
+                static if (DummyType.r == ReturnBy.Reference) {
+                    {
+                        myRetro[9]++;
+                        scope(exit) myRetro[9]--;
+                        assert(dummyRange[0] == 2);
+                        myRetro.front++;
+                        scope(exit) myRetro.front--;
+                        assert(myRetro.front == 11);
+                        myRetro.back++;
+                        scope(exit) myRetro.back--;
+                        assert(myRetro.back == 3);
+                    }
+
+                    {
+                        myRetro.front = 0xFF;
+                        scope(exit) myRetro.front = 10;
+                        assert(dummyRange.back == 0xFF);
+
+                        myRetro.back = 0xBB;
+                        scope(exit) myRetro.back = 1;
+                        assert(dummyRange.front == 0xBB);
+
+                        myRetro[1] = 11;
+                        scope(exit) myRetro[1] = 8;
+                        assert(dummyRange[8] == 11);
+                    }
+                }
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    auto LL = iota(1L, 4L);
+    auto r = retro(LL);
+    assert(equal(r, [3L, 2L, 1L]));
+}
+
+// Issue 12662
+@safe @nogc unittest
+{
+    int[3] src = [1,2,3];
+    int[] data = src[];
+    foreach_reverse (x; data) {}
+    foreach (x; data.retro) {}
+}
+
+
+/**
+Iterates range $(D r) with stride $(D n). If the range is a
+random-access range, moves by indexing into the range; otherwise,
+moves by successive calls to $(D popFront). Applying stride twice to
+the same range results in a stride with a step that is the
+product of the two applications. It is an error for $(D n) to be 0.
+ */
+auto stride(Range)(Range r, size_t n)
+if (isInputRange!(Unqual!Range))
+in
+{
+    assert(n != 0, "stride cannot have step zero.");
+}
+body
+{
+    import std.algorithm : min;
+
+    static if (is(typeof(stride(r.source, n)) == Range))
+    {
+        // stride(stride(r, n1), n2) is stride(r, n1 * n2)
+        return stride(r.source, r._n * n);
+    }
+    else
+    {
+        static struct Result
+        {
+            private alias R = Unqual!Range;
+            public R source;
+            private size_t _n;
+
+            // Chop off the slack elements at the end
+            static if (hasLength!R &&
+                    (isRandomAccessRange!R && hasSlicing!R
+                            || isBidirectionalRange!R))
+                private void eliminateSlackElements()
+                {
+                    auto slack = source.length % _n;
+
+                    if (slack)
+                    {
+                        slack--;
+                    }
+                    else if (!source.empty)
+                    {
+                        slack = min(_n, source.length) - 1;
+                    }
+                    else
+                    {
+                        slack = 0;
+                    }
+                    if (!slack) return;
+                    static if (isRandomAccessRange!R && hasSlicing!R)
+                    {
+                        source = source[0 .. source.length - slack];
+                    }
+                    else static if (isBidirectionalRange!R)
+                    {
+                        foreach (i; 0 .. slack)
+                        {
+                            source.popBack();
+                        }
+                    }
+                }
+
+            static if (isForwardRange!R)
+            {
+                @property auto save()
+                {
+                    return Result(source.save, _n);
+                }
+            }
+
+            static if (isInfinite!R)
+            {
+                enum bool empty = false;
+            }
+            else
+            {
+                @property bool empty()
+                {
+                    return source.empty;
+                }
+            }
+
+            @property auto ref front()
+            {
+                return source.front;
+            }
+
+            static if (is(typeof(.moveFront(source))))
+            {
+                ElementType!R moveFront()
+                {
+                    return .moveFront(source);
+                }
+            }
+
+            static if (hasAssignableElements!R)
+            {
+                @property auto front(ElementType!R val)
+                {
+                    source.front = val;
+                }
+            }
+
+            void popFront()
+            {
+                static if (isRandomAccessRange!R && hasLength!R && hasSlicing!R)
+                {
+                    source = source[min(_n, source.length) .. source.length];
+                }
+                else
+                {
+                    static if (hasLength!R)
+                    {
+                        foreach (i; 0 .. min(source.length, _n))
+                        {
+                            source.popFront();
+                        }
+                    }
+                    else
+                    {
+                        foreach (i; 0 .. _n)
+                        {
+                            source.popFront();
+                            if (source.empty) break;
+                        }
+                    }
+                }
+            }
+
+            static if (isBidirectionalRange!R && hasLength!R)
+            {
+                void popBack()
+                {
+                    popBackN(source, _n);
+                }
+
+                @property auto ref back()
+                {
+                    eliminateSlackElements();
+                    return source.back;
+                }
+
+                static if (is(typeof(.moveBack(source))))
+                {
+                    ElementType!R moveBack()
+                    {
+                        eliminateSlackElements();
+                        return .moveBack(source);
+                    }
+                }
+
+                static if (hasAssignableElements!R)
+                {
+                    @property auto back(ElementType!R val)
+                    {
+                        eliminateSlackElements();
+                        source.back = val;
+                    }
+                }
+            }
+
+            static if (isRandomAccessRange!R && hasLength!R)
+            {
+                auto ref opIndex(size_t n)
+                {
+                    return source[_n * n];
+                }
+
+                /**
+                   Forwards to $(D moveAt(source, n)).
+                */
+                static if (is(typeof(.moveAt(source, 0))))
+                {
+                    ElementType!R moveAt(size_t n)
+                    {
+                        return .moveAt(source, _n * n);
+                    }
+                }
+
+                static if (hasAssignableElements!R)
+                {
+                    void opIndexAssign(ElementType!R val, size_t n)
+                    {
+                        source[_n * n] = val;
+                    }
+                }
+            }
+
+            static if (hasSlicing!R && hasLength!R)
+                typeof(this) opSlice(size_t lower, size_t upper)
+                {
+                    assert(upper >= lower && upper <= length);
+                    immutable translatedUpper = (upper == 0) ? 0 :
+                        (upper * _n - (_n - 1));
+                    immutable translatedLower = min(lower * _n, translatedUpper);
+
+                    assert(translatedLower <= translatedUpper);
+
+                    return typeof(this)(source[translatedLower..translatedUpper], _n);
+                }
+
+            static if (hasLength!R)
+            {
+                @property auto length()
+                {
+                    return (source.length + _n - 1) / _n;
+                }
+
+                alias opDollar = length;
+            }
+        }
+        return Result(r, n);
+    }
+}
+
+///
+unittest
+{
+    import std.algorithm : equal;
+
+    int[] a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ];
+    assert(equal(stride(a, 3), [ 1, 4, 7, 10 ][]));
+    assert(stride(stride(a, 2), 3) == stride(a, 6));
+}
+
+nothrow @nogc unittest
+{
+    int[4] testArr = [1,2,3,4];
+    //just checking it compiles
+    auto s = testArr[].stride(2);
+}
+
+debug unittest
+{//check the contract
+    int[4] testArr = [1,2,3,4];
+    import std.exception : assertThrown;
+    import core.exception : AssertError;
+    assertThrown!AssertError(testArr[].stride(0));
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+
+    static assert(isRandomAccessRange!(typeof(stride([1, 2, 3], 2))));
+    void test(size_t n, int[] input, int[] witness)
+    {
+        assert(equal(stride(input, n), witness));
+    }
+    test(1, [], []);
+    int[] arr = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    assert(stride(stride(arr, 2), 3) is stride(arr, 6));
+    test(1, arr, arr);
+    test(2, arr, [1, 3, 5, 7, 9]);
+    test(3, arr, [1, 4, 7, 10]);
+    test(4, arr, [1, 5, 9]);
+
+    // Test slicing.
+    auto s1 = stride(arr, 1);
+    assert(equal(s1[1..4], [2, 3, 4]));
+    assert(s1[1..4].length == 3);
+    assert(equal(s1[1..5], [2, 3, 4, 5]));
+    assert(s1[1..5].length == 4);
+    assert(s1[0..0].empty);
+    assert(s1[3..3].empty);
+    // assert(s1[$ .. $].empty);
+    assert(s1[s1.opDollar .. s1.opDollar].empty);
+
+    auto s2 = stride(arr, 2);
+    assert(equal(s2[0..2], [1,3]));
+    assert(s2[0..2].length == 2);
+    assert(equal(s2[1..5], [3, 5, 7, 9]));
+    assert(s2[1..5].length == 4);
+    assert(s2[0..0].empty);
+    assert(s2[3..3].empty);
+    // assert(s2[$ .. $].empty);
+    assert(s2[s2.opDollar .. s2.opDollar].empty);
+
+    // Test fix for Bug 5035
+    auto m = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]; // 3 rows, 4 columns
+    auto col = stride(m, 4);
+    assert(equal(col, [1, 1, 1]));
+    assert(equal(retro(col), [1, 1, 1]));
+
+    immutable int[] immi = [ 1, 2, 3 ];
+    static assert(isRandomAccessRange!(typeof(stride(immi, 1))));
+
+    // Check for infiniteness propagation.
+    static assert(isInfinite!(typeof(stride(repeat(1), 3))));
+
+    foreach(DummyType; AllDummyRanges) {
+        DummyType dummyRange;
+        dummyRange.reinit();
+
+        auto myStride = stride(dummyRange, 4);
+
+        // Should fail if no length and bidirectional b/c there's no way
+        // to know how much slack we have.
+        static if (hasLength!DummyType || !isBidirectionalRange!DummyType) {
+            static assert(propagatesRangeType!(typeof(myStride), DummyType));
+        }
+        assert(myStride.front == 1);
+        assert(myStride.moveFront() == 1);
+        assert(equal(myStride, [1, 5, 9]));
+
+        static if (hasLength!DummyType) {
+            assert(myStride.length == 3);
+        }
+
+        static if (isBidirectionalRange!DummyType && hasLength!DummyType) {
+            assert(myStride.back == 9);
+            assert(myStride.moveBack() == 9);
+        }
+
+        static if (isRandomAccessRange!DummyType && hasLength!DummyType) {
+            assert(myStride[0] == 1);
+            assert(myStride[1] == 5);
+            assert(myStride.moveAt(1) == 5);
+            assert(myStride[2] == 9);
+
+            static assert(hasSlicing!(typeof(myStride)));
+        }
+
+        static if (DummyType.r == ReturnBy.Reference) {
+            // Make sure reference is propagated.
+
+            {
+                myStride.front++;
+                scope(exit) myStride.front--;
+                assert(dummyRange.front == 2);
+            }
+            {
+                myStride.front = 4;
+                scope(exit) myStride.front = 1;
+                assert(dummyRange.front == 4);
+            }
+
+            static if (isBidirectionalRange!DummyType && hasLength!DummyType) {
+                {
+                    myStride.back++;
+                    scope(exit) myStride.back--;
+                    assert(myStride.back == 10);
+                }
+                {
+                    myStride.back = 111;
+                    scope(exit) myStride.back = 9;
+                    assert(myStride.back == 111);
+                }
+
+                static if (isRandomAccessRange!DummyType) {
+                    {
+                        myStride[1]++;
+                        scope(exit) myStride[1]--;
+                        assert(dummyRange[4] == 6);
+                    }
+                    {
+                        myStride[1] = 55;
+                        scope(exit) myStride[1] = 5;
+                        assert(dummyRange[4] == 55);
+                    }
+                }
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto LL = iota(1L, 10L);
+    auto s = stride(LL, 3);
+    assert(equal(s, [1L, 4L, 7L]));
+}
+
+/**
+Spans multiple ranges in sequence. The function $(D chain) takes any
+number of ranges and returns a $(D Chain!(R1, R2,...)) object. The
+ranges may be different, but they must have the same element type. The
+result is a range that offers the $(D front), $(D popFront), and $(D
+empty) primitives. If all input ranges offer random access and $(D
+length), $(D Chain) offers them as well.
+
+If only one range is offered to $(D Chain) or $(D chain), the $(D
+Chain) type exits the picture by aliasing itself directly to that
+range's type.
+ */
+auto chain(Ranges...)(Ranges rs)
+if (Ranges.length > 0 &&
+    allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) &&
+    !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Ranges))) == void))
+{
+    static if (Ranges.length == 1)
+    {
+        return rs[0];
+    }
+    else
+    {
+        static struct Result
+        {
+        private:
+            alias R = staticMap!(Unqual, Ranges);
+            alias RvalueElementType = CommonType!(staticMap!(.ElementType, R));
+            private template sameET(A)
+            {
+                enum sameET = is(.ElementType!A == RvalueElementType);
+            }
+
+            enum bool allSameType = allSatisfy!(sameET, R);
+
+            // This doesn't work yet
+            static if (allSameType)
+            {
+                alias ref RvalueElementType ElementType;
+            }
+            else
+            {
+                alias ElementType = RvalueElementType;
+            }
+            static if (allSameType && allSatisfy!(hasLvalueElements, R))
+            {
+                static ref RvalueElementType fixRef(ref RvalueElementType val)
+                {
+                    return val;
+                }
+            }
+            else
+            {
+                static RvalueElementType fixRef(RvalueElementType val)
+                {
+                    return val;
+                }
+            }
+
+            // This is the entire state
+            R source;
+            // TODO: use a vtable (or more) instead of linear iteration
+
+        public:
+            this(R input)
+            {
+                foreach (i, v; input)
+                {
+                    source[i] = v;
+                }
+            }
+
+            import std.meta : anySatisfy;
+
+            static if (anySatisfy!(isInfinite, R))
+            {
+                // Propagate infiniteness.
+                enum bool empty = false;
+            }
+            else
+            {
+                @property bool empty()
+                {
+                    foreach (i, Unused; R)
+                    {
+                        if (!source[i].empty) return false;
+                    }
+                    return true;
+                }
+            }
+
+            static if (allSatisfy!(isForwardRange, R))
+                @property auto save()
+                {
+                    typeof(this) result = this;
+                    foreach (i, Unused; R)
+                    {
+                        result.source[i] = result.source[i].save;
+                    }
+                    return result;
+                }
+
+            void popFront()
+            {
+                foreach (i, Unused; R)
+                {
+                    if (source[i].empty) continue;
+                    source[i].popFront();
+                    return;
+                }
+            }
+
+            @property auto ref front()
+            {
+                foreach (i, Unused; R)
+                {
+                    if (source[i].empty) continue;
+                    return fixRef(source[i].front);
+                }
+                assert(false);
+            }
+
+            static if (allSameType && allSatisfy!(hasAssignableElements, R))
+            {
+                // @@@BUG@@@
+                //@property void front(T)(T v) if (is(T : RvalueElementType))
+
+                @property void front(RvalueElementType v)
+                {
+                    foreach (i, Unused; R)
+                    {
+                        if (source[i].empty) continue;
+                        source[i].front = v;
+                        return;
+                    }
+                    assert(false);
+                }
+            }
+
+            static if (allSatisfy!(hasMobileElements, R))
+            {
+                RvalueElementType moveFront()
+                {
+                    foreach (i, Unused; R)
+                    {
+                        if (source[i].empty) continue;
+                        return .moveFront(source[i]);
+                    }
+                    assert(false);
+                }
+            }
+
+            static if (allSatisfy!(isBidirectionalRange, R))
+            {
+                @property auto ref back()
+                {
+                    foreach_reverse (i, Unused; R)
+                    {
+                        if (source[i].empty) continue;
+                        return fixRef(source[i].back);
+                    }
+                    assert(false);
+                }
+
+                void popBack()
+                {
+                    foreach_reverse (i, Unused; R)
+                    {
+                        if (source[i].empty) continue;
+                        source[i].popBack();
+                        return;
+                    }
+                }
+
+                static if (allSatisfy!(hasMobileElements, R))
+                {
+                    RvalueElementType moveBack()
+                    {
+                        foreach_reverse (i, Unused; R)
+                        {
+                            if (source[i].empty) continue;
+                            return .moveBack(source[i]);
+                        }
+                        assert(false);
+                    }
+                }
+
+                static if (allSameType && allSatisfy!(hasAssignableElements, R))
+                {
+                    @property void back(RvalueElementType v)
+                    {
+                        foreach_reverse (i, Unused; R)
+                        {
+                            if (source[i].empty) continue;
+                            source[i].back = v;
+                            return;
+                        }
+                        assert(false);
+                    }
+                }
+            }
+
+            static if (allSatisfy!(hasLength, R))
+            {
+                @property size_t length()
+                {
+                    size_t result;
+                    foreach (i, Unused; R)
+                    {
+                        result += source[i].length;
+                    }
+                    return result;
+                }
+
+                alias opDollar = length;
+            }
+
+            static if (allSatisfy!(isRandomAccessRange, R))
+            {
+                auto ref opIndex(size_t index)
+                {
+                    foreach (i, Range; R)
+                    {
+                        static if (isInfinite!(Range))
+                        {
+                            return source[i][index];
+                        }
+                        else
+                        {
+                            immutable length = source[i].length;
+                            if (index < length) return fixRef(source[i][index]);
+                            index -= length;
+                        }
+                    }
+                    assert(false);
+                }
+
+                static if (allSatisfy!(hasMobileElements, R))
+                {
+                    RvalueElementType moveAt(size_t index)
+                    {
+                        foreach (i, Range; R)
+                        {
+                            static if (isInfinite!(Range))
+                            {
+                                return .moveAt(source[i], index);
+                            }
+                            else
+                            {
+                                immutable length = source[i].length;
+                                if (index < length) return .moveAt(source[i], index);
+                                index -= length;
+                            }
+                        }
+                        assert(false);
+                    }
+                }
+
+                static if (allSameType && allSatisfy!(hasAssignableElements, R))
+                    void opIndexAssign(ElementType v, size_t index)
+                    {
+                        foreach (i, Range; R)
+                        {
+                            static if (isInfinite!(Range))
+                            {
+                                source[i][index] = v;
+                            }
+                            else
+                            {
+                                immutable length = source[i].length;
+                                if (index < length)
+                                {
+                                    source[i][index] = v;
+                                    return;
+                                }
+                                index -= length;
+                            }
+                        }
+                        assert(false);
+                    }
+            }
+
+            static if (allSatisfy!(hasLength, R) && allSatisfy!(hasSlicing, R))
+                auto opSlice(size_t begin, size_t end)
+                {
+                    auto result = this;
+                    foreach (i, Unused; R)
+                    {
+                        immutable len = result.source[i].length;
+                        if (len < begin)
+                        {
+                            result.source[i] = result.source[i]
+                                [len .. len];
+                            begin -= len;
+                        }
+                        else
+                        {
+                            result.source[i] = result.source[i]
+                                [begin .. len];
+                            break;
+                        }
+                    }
+                    auto cut = length;
+                    cut = cut <= end ? 0 : cut - end;
+                    foreach_reverse (i, Unused; R)
+                    {
+                        immutable len = result.source[i].length;
+                        if (cut > len)
+                        {
+                            result.source[i] = result.source[i]
+                                [0 .. 0];
+                            cut -= len;
+                        }
+                        else
+                        {
+                            result.source[i] = result.source[i]
+                                [0 .. len - cut];
+                            break;
+                        }
+                    }
+                    return result;
+                }
+        }
+        return Result(rs);
+    }
+}
+
+///
+unittest
+{
+    import std.algorithm : equal;
+
+    int[] arr1 = [ 1, 2, 3, 4 ];
+    int[] arr2 = [ 5, 6 ];
+    int[] arr3 = [ 7 ];
+    auto s = chain(arr1, arr2, arr3);
+    assert(s.length == 7);
+    assert(s[5] == 6);
+    assert(equal(s, [1, 2, 3, 4, 5, 6, 7][]));
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+
+    {
+        int[] arr1 = [ 1, 2, 3, 4 ];
+        int[] arr2 = [ 5, 6 ];
+        int[] arr3 = [ 7 ];
+        int[] witness = [ 1, 2, 3, 4, 5, 6, 7 ];
+        auto s1 = chain(arr1);
+        static assert(isRandomAccessRange!(typeof(s1)));
+        auto s2 = chain(arr1, arr2);
+        static assert(isBidirectionalRange!(typeof(s2)));
+        static assert(isRandomAccessRange!(typeof(s2)));
+        s2.front = 1;
+        auto s = chain(arr1, arr2, arr3);
+        assert(s[5] == 6);
+        assert(equal(s, witness));
+        assert(s[5] == 6);
+    }
+    {
+        int[] arr1 = [ 1, 2, 3, 4 ];
+        int[] witness = [ 1, 2, 3, 4 ];
+        assert(equal(chain(arr1), witness));
+    }
+    {
+        uint[] foo = [1,2,3,4,5];
+        uint[] bar = [1,2,3,4,5];
+        auto c = chain(foo, bar);
+        c[3] = 42;
+        assert(c[3] == 42);
+        assert(c.moveFront() == 1);
+        assert(c.moveBack() == 5);
+        assert(c.moveAt(4) == 5);
+        assert(c.moveAt(5) == 1);
+    }
+
+    // Make sure bug 3311 is fixed.  ChainImpl should compile even if not all
+    // elements are mutable.
+    auto c = chain( iota(0, 10), iota(0, 10) );
+
+    // Test the case where infinite ranges are present.
+    auto inf = chain([0,1,2][], cycle([4,5,6][]), [7,8,9][]); // infinite range
+    assert(inf[0] == 0);
+    assert(inf[3] == 4);
+    assert(inf[6] == 4);
+    assert(inf[7] == 5);
+    static assert(isInfinite!(typeof(inf)));
+
+    immutable int[] immi = [ 1, 2, 3 ];
+    immutable float[] immf = [ 1, 2, 3 ];
+    static assert(is(typeof(chain(immi, immf))));
+
+    // Check that chain at least instantiates and compiles with every possible
+    // pair of DummyRange types, in either order.
+
+    foreach(DummyType1; AllDummyRanges) {
+        DummyType1 dummy1;
+        foreach(DummyType2; AllDummyRanges) {
+            DummyType2 dummy2;
+            auto myChain = chain(dummy1, dummy2);
+
+            static assert(
+                propagatesRangeType!(typeof(myChain), DummyType1, DummyType2)
+            );
+
+            assert(myChain.front == 1);
+            foreach(i; 0..dummyLength) {
+                myChain.popFront();
+            }
+            assert(myChain.front == 1);
+
+            static if (isBidirectionalRange!DummyType1 &&
+                      isBidirectionalRange!DummyType2) {
+                assert(myChain.back == 10);
+            }
+
+            static if (isRandomAccessRange!DummyType1 &&
+                      isRandomAccessRange!DummyType2) {
+                assert(myChain[0] == 1);
+            }
+
+            static if (hasLvalueElements!DummyType1 && hasLvalueElements!DummyType2)
+            {
+                static assert(hasLvalueElements!(typeof(myChain)));
+            }
+            else
+            {
+                static assert(!hasLvalueElements!(typeof(myChain)));
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    class Foo{}
+    immutable(Foo)[] a;
+    immutable(Foo)[] b;
+    auto c = chain(a, b);
+}
+
+/**
+Choose one of two ranges at runtime depending on a Boolean condition.
+
+The ranges may be different, but they must have compatible element types (i.e.
+$(D CommonType) must exist for the two element types). The result is a range
+that offers the weakest capabilities of the two (e.g. $(D ForwardRange) if $(D
+R1) is a random-access range and $(D R2) is a forward range).
+
+Params:
+    condition = which range to choose: $(D r1) if $(D true), $(D r2) otherwise
+    r1 = the "true" range
+    r2 = the "false" range
+
+Returns:
+    A range type dependent on $(D R1) and $(D R2).
+
+Bugs:
+    $(BUGZILLA 14660)
+ */
+auto choose(R1, R2)(bool condition, R1 r1, R2 r2)
+if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
+    !is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void))
+{
+    static struct Result
+    {
+        import std.algorithm : max;
+        import std.algorithm.internal : addressOf;
+
+        private union
+        {
+            void[max(R1.sizeof, R2.sizeof)] buffer = void;
+            void* forAlignmentOnly = void;
+        }
+        private bool condition;
+        private @property ref R1 r1()
+        {
+            assert(condition);
+            return *cast(R1*) buffer.ptr;
+        }
+        private @property ref R2 r2()
+        {
+            assert(!condition);
+            return *cast(R2*) buffer.ptr;
+        }
+
+        this(bool condition, R1 r1, R2 r2)
+        {
+            this.condition = condition;
+            import std.conv : emplace;
+            if (condition) emplace(addressOf(this.r1), r1);
+            else emplace(addressOf(this.r2), r2);
+        }
+
+        // Carefully defined postblit to postblit the appropriate range
+        static if (hasElaborateCopyConstructor!R1
+            || hasElaborateCopyConstructor!R2)
+        this(this)
+        {
+            if (condition)
+            {
+                static if (hasElaborateCopyConstructor!R1) r1.__postblit();
+            }
+            else
+            {
+                static if (hasElaborateCopyConstructor!R2) r2.__postblit();
+            }
+        }
+
+        static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2)
+        ~this()
+        {
+            if (condition) destroy(r1);
+            else destroy(r2);
+        }
+
+        static if (isInfinite!R1 && isInfinite!R2)
+            // Propagate infiniteness.
+            enum bool empty = false;
+        else
+            @property bool empty()
+            {
+                return condition ? r1.empty : r2.empty;
+            }
+
+        @property auto ref front()
+        {
+            return condition ? r1.front : r2.front;
+        }
+
+        void popFront()
+        {
+            return condition ? r1.popFront : r2.popFront;
+        }
+
+        static if (isForwardRange!R1 && isForwardRange!R2)
+            @property auto save()
+            {
+                auto result = this;
+                if (condition) r1 = r1.save;
+                else r2 = r2.save;
+                return result;
+            }
+
+        @property void front(T)(T v)
+        if (is(typeof({ r1.front = v; r2.front = v; })))
+        {
+            if (condition) r1.front = v; else r2.front = v;
+        }
+
+        static if (hasMobileElements!R1 && hasMobileElements!R2)
+            auto moveFront()
+            {
+                return condition ? r1.moveFront : r2.moveFront;
+            }
+
+        static if (isBidirectionalRange!R1 && isBidirectionalRange!R2)
+        {
+            @property auto ref back()
+            {
+                return condition ? r1.back : r2.back;
+            }
+
+            void popBack()
+            {
+                return condition ? r1.popBack : r2.popBack;
+            }
+
+            static if (hasMobileElements!R1 && hasMobileElements!R2)
+                auto moveBack()
+                {
+                    return condition ? r1.moveBack : r2.moveBack;
+                }
+
+            @property void back(T)(T v)
+            if (is(typeof({ r1.back = v; r2.back = v; })))
+            {
+                if (condition) r1.back = v; else r2.back = v;
+            }
+        }
+
+        static if (hasLength!R1 && hasLength!R2)
+        {
+            @property size_t length()
+            {
+                return condition ? r1.length : r2.length;
+            }
+            alias opDollar = length;
+        }
+
+        static if (isRandomAccessRange!R1 && isRandomAccessRange!R2)
+        {
+            auto ref opIndex(size_t index)
+            {
+                return condition ? r1[index] : r2[index];
+            }
+
+            static if (hasMobileElements!R1 && hasMobileElements!R2)
+                auto moveAt(size_t index)
+                {
+                    return condition ? r1.moveAt(index) : r2.moveAt(index);
+                }
+
+            void opIndexAssign(T)(T v, size_t index)
+            if (is(typeof({ r1[1] = v; r2[1] = v; })))
+            {
+                if (condition) r1[index] = v; else r2[index] = v;
+            }
+        }
+
+        // BUG: this should work for infinite ranges, too
+        static if (hasSlicing!R1 && hasSlicing!R2 &&
+                !isInfinite!R2 && !isInfinite!R2)
+            auto opSlice(size_t begin, size_t end)
+            {
+                auto result = this;
+                if (condition) result.r1 = result.r1[begin .. end];
+                else result.r2 = result.r2[begin .. end];
+                return result;
+            }
+    }
+    return Result(condition, r1, r2);
+}
+
+///
+unittest
+{
+    import std.algorithm.iteration : filter, map;
+    import std.algorithm.comparison : equal;
+
+    auto data1 = [ 1, 2, 3, 4 ].filter!(a => a != 3);
+    auto data2 = [ 5, 6, 7, 8 ].map!(a => a + 1);
+
+    // choose() is primarily useful when you need to select one of two ranges
+    // with different types at runtime.
+    static assert(!is(typeof(data1) == typeof(data2)));
+
+    auto chooseRange(bool pickFirst)
+    {
+        // The returned range is a common wrapper type that can be used for
+        // returning or storing either range without running into a type error.
+        return choose(pickFirst, data1, data2);
+
+        // Simply returning the chosen range without using choose() does not
+        // work, because map() and filter() return different types.
+        //return pickFirst ? data1 : data2; // does not compile
+    }
+
+    auto result = chooseRange(true);
+    assert(result.equal([ 1, 2, 4 ]));
+
+    result = chooseRange(false);
+    assert(result.equal([ 6, 7, 8, 9 ]));
+}
+
+/**
+Choose one of multiple ranges at runtime.
+
+The ranges may be different, but they must have compatible element types. The
+result is a range that offers the weakest capabilities of all $(D Ranges).
+
+Params:
+    index = which range to choose, must be less than the number of ranges
+    rs = two or more ranges
+
+Returns:
+    The indexed range. If rs consists of only one range, the return type is an
+    alias of that range's type.
+ */
+auto chooseAmong(Ranges...)(size_t index, Ranges rs)
+if (Ranges.length > 2
+        && is(typeof(choose(true, rs[0], rs[1])))
+        && is(typeof(chooseAmong(0, rs[1 .. $]))))
+{
+    return choose(index == 0, rs[0], chooseAmong(index - 1, rs[1 .. $]));
+}
+
+/// ditto
+auto chooseAmong(Ranges...)(size_t index, Ranges rs)
+if (Ranges.length == 2 && is(typeof(choose(true, rs[0], rs[1]))))
+{
+    return choose(index == 0, rs[0], rs[1]);
+}
+
+///
+unittest
+{
+    import std.algorithm : equal;
+
+    int[] arr1 = [ 1, 2, 3, 4 ];
+    int[] arr2 = [ 5, 6 ];
+    int[] arr3 = [ 7 ];
+
+    {
+        auto s = chooseAmong(0, arr1, arr2, arr3);
+        auto t = s.save;
+        assert(s.length == 4);
+        assert(s[2] == 3);
+        s.popFront();
+        assert(equal(t, [1, 2, 3, 4][]));
+    }
+    {
+        auto s = chooseAmong(1, arr1, arr2, arr3);
+        assert(s.length == 2);
+        s.front = 8;
+        assert(equal(s, [8, 6][]));
+    }
+    {
+        auto s = chooseAmong(1, arr1, arr2, arr3);
+        assert(s.length == 2);
+        s[1] = 9;
+        assert(equal(s, [8, 9][]));
+    }
+    {
+        auto s = chooseAmong(1, arr2, arr1, arr3)[1..3];
+        assert(s.length == 2);
+        assert(equal(s, [2, 3][]));
+    }
+    {
+        auto s = chooseAmong(0, arr1, arr2, arr3);
+        assert(s.length == 4);
+        assert(s.back == 4);
+        s.popBack();
+        s.back = 5;
+        assert(equal(s, [1, 2, 5][]));
+        s.back = 3;
+        assert(equal(s, [1, 2, 3][]));
+    }
+    {
+        uint[] foo = [1,2,3,4,5];
+        uint[] bar = [6,7,8,9,10];
+        auto c = chooseAmong(1,foo, bar);
+        assert(c[3] == 9);
+        c[3] = 42;
+        assert(c[3] == 42);
+        assert(c.moveFront() == 6);
+        assert(c.moveBack() == 10);
+        assert(c.moveAt(4) == 10);
+    }
+    {
+        import std.range : cycle;
+        auto s = chooseAmong(1, cycle(arr2), cycle(arr3));
+        assert(isInfinite!(typeof(s)));
+        assert(!s.empty);
+        assert(s[100] == 7);
+    }
+}
+
+unittest
+{
+    int[] a = [1, 2, 3];
+    long[] b = [4, 5, 6];
+    auto c = chooseAmong(0, a, b);
+    c[0] = 42;
+    assert(c[0] == 42);
+}
+
+
+/**
+$(D roundRobin(r1, r2, r3)) yields $(D r1.front), then $(D r2.front),
+then $(D r3.front), after which it pops off one element from each and
+continues again from $(D r1). For example, if two ranges are involved,
+it alternately yields elements off the two ranges. $(D roundRobin)
+stops after it has consumed all ranges (skipping over the ones that
+finish early).
+ */
+auto roundRobin(Rs...)(Rs rs)
+if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs)))
+{
+    struct Result
+    {
+        import std.conv : to;
+
+        public Rs source;
+        private size_t _current = size_t.max;
+
+        @property bool empty()
+        {
+            foreach (i, Unused; Rs)
+            {
+                if (!source[i].empty) return false;
+            }
+            return true;
+        }
+
+        @property auto ref front()
+        {
+            final switch (_current)
+            {
+                foreach (i, R; Rs)
+                {
+                    case i:
+                        assert(!source[i].empty);
+                        return source[i].front;
+                }
+            }
+            assert(0);
+        }
+
+        void popFront()
+        {
+            final switch (_current)
+            {
+                foreach (i, R; Rs)
+                {
+                    case i:
+                        source[i].popFront();
+                        break;
+                }
+            }
+
+            auto next = _current == (Rs.length - 1) ? 0 : (_current + 1);
+            final switch (next)
+            {
+                foreach (i, R; Rs)
+                {
+                    case i:
+                        if (!source[i].empty)
+                        {
+                            _current = i;
+                            return;
+                        }
+                        if (i == _current)
+                        {
+                            _current = _current.max;
+                            return;
+                        }
+                        goto case (i + 1) % Rs.length;
+                }
+            }
+        }
+
+        static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs)))
+            @property auto save()
+            {
+                Result result = this;
+                foreach (i, Unused; Rs)
+                {
+                    result.source[i] = result.source[i].save;
+                }
+                return result;
+            }
+
+        static if (allSatisfy!(hasLength, Rs))
+        {
+            @property size_t length()
+            {
+                size_t result;
+                foreach (i, R; Rs)
+                {
+                    result += source[i].length;
+                }
+                return result;
+            }
+
+            alias opDollar = length;
+        }
+    }
+
+    return Result(rs, 0);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    int[] a = [ 1, 2, 3 ];
+    int[] b = [ 10, 20, 30, 40 ];
+    auto r = roundRobin(a, b);
+    assert(equal(r, [ 1, 10, 2, 20, 3, 30, 40 ]));
+}
+
+/**
+Iterates a random-access range starting from a given point and
+progressively extending left and right from that point. If no initial
+point is given, iteration starts from the middle of the
+range. Iteration spans the entire range.
+ */
+auto radial(Range, I)(Range r, I startingIndex)
+if (isRandomAccessRange!(Unqual!Range) && hasLength!(Unqual!Range) && isIntegral!I)
+{
+    if (!r.empty) ++startingIndex;
+    return roundRobin(retro(r[0 .. startingIndex]), r[startingIndex .. r.length]);
+}
+
+/// Ditto
+auto radial(R)(R r)
+if (isRandomAccessRange!(Unqual!R) && hasLength!(Unqual!R))
+{
+    return .radial(r, (r.length - !r.empty) / 2);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    assert(equal(radial(a), [ 3, 4, 2, 5, 1 ]));
+    a = [ 1, 2, 3, 4 ];
+    assert(equal(radial(a), [ 2, 3, 1, 4 ]));
+
+    // If the left end is reached first, the remaining elements on the right
+    // are concatenated in order:
+    a = [ 0, 1, 2, 3, 4, 5 ];
+    assert(equal(radial(a, 1), [ 1, 2, 0, 3, 4, 5 ]));
+
+    // If the right end is reached first, the remaining elements on the left
+    // are concatenated in reverse order:
+    assert(equal(radial(a, 4), [ 4, 5, 3, 2, 1, 0 ]));
+}
+
+@safe unittest
+{
+    import std.conv : text;
+    import std.exception : enforce;
+    import std.algorithm : equal;
+    import std.internal.test.dummyrange;
+
+    void test(int[] input, int[] witness)
+    {
+        enforce(equal(radial(input), witness),
+                text(radial(input), " vs. ", witness));
+    }
+    test([], []);
+    test([ 1 ], [ 1 ]);
+    test([ 1, 2 ], [ 1, 2 ]);
+    test([ 1, 2, 3 ], [ 2, 3, 1 ]);
+    test([ 1, 2, 3, 4 ], [ 2, 3, 1, 4 ]);
+    test([ 1, 2, 3, 4, 5 ], [ 3, 4, 2, 5, 1 ]);
+    test([ 1, 2, 3, 4, 5, 6 ], [ 3, 4, 2, 5, 1, 6 ]);
+
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    assert(equal(radial(a, 1), [ 2, 3, 1, 4, 5 ][]));
+    static assert(isForwardRange!(typeof(radial(a, 1))));
+
+    auto r = radial([1,2,3,4,5]);
+    for(auto rr = r.save; !rr.empty; rr.popFront())
+    {
+        assert(rr.front == moveFront(rr));
+    }
+    r.front = 5;
+    assert(r.front == 5);
+
+    // Test instantiation without lvalue elements.
+    DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random) dummy;
+    assert(equal(radial(dummy, 4), [5, 6, 4, 7, 3, 8, 2, 9, 1, 10]));
+
+    // immutable int[] immi = [ 1, 2 ];
+    // static assert(is(typeof(radial(immi))));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto LL = iota(1L, 6L);
+    auto r = radial(LL);
+    assert(equal(r, [3L, 4L, 2L, 5L, 1L]));
+}
+
+/**
+Lazily takes only up to $(D n) elements of a range. This is
+particularly useful when using with infinite ranges. If the range
+offers random access and $(D length), $(D Take) offers them as well.
+ */
+struct Take(Range)
+if (isInputRange!(Unqual!Range) &&
+    //take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses
+    //take for slicing infinite ranges.
+    !((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == Take!T)))
+{
+    private alias R = Unqual!Range;
+
+    // User accessible in read and write
+    public R source;
+
+    private size_t _maxAvailable;
+
+    alias Source = R;
+
+    @property bool empty()
+    {
+        return _maxAvailable == 0 || source.empty;
+    }
+
+    @property auto ref front()
+    {
+        assert(!empty,
+            "Attempting to fetch the front of an empty "
+            ~ Take.stringof);
+        return source.front;
+    }
+
+    void popFront()
+    {
+        assert(!empty,
+            "Attempting to popFront() past the end of a "
+            ~ Take.stringof);
+        source.popFront();
+        --_maxAvailable;
+    }
+
+    static if (isForwardRange!R)
+        @property Take save()
+        {
+            return Take(source.save, _maxAvailable);
+        }
+
+    static if (hasAssignableElements!R)
+        @property auto front(ElementType!R v)
+        {
+            assert(!empty,
+                "Attempting to assign to the front of an empty "
+                ~ Take.stringof);
+            // This has to return auto instead of void because of Bug 4706.
+            source.front = v;
+        }
+
+    static if (hasMobileElements!R)
+    {
+        auto moveFront()
+        {
+            assert(!empty,
+                "Attempting to move the front of an empty "
+                ~ Take.stringof);
+            return .moveFront(source);
+        }
+    }
+
+    static if (isInfinite!R)
+    {
+        @property size_t length() const
+        {
+            return _maxAvailable;
+        }
+
+        alias opDollar = length;
+
+        //Note: Due to Take/hasSlicing circular dependency,
+        //This needs to be a restrained template.
+        auto opSlice()(size_t i, size_t j)
+        if (hasSlicing!R)
+        {
+            assert(i <= j, "Invalid slice bounds");
+            assert(j - i <= length, "Attempting to slice past the end of a "
+                ~ Take.stringof);
+            return source[i .. j - i];
+        }
+    }
+    else static if (hasLength!R)
+    {
+        @property size_t length()
+        {
+            import std.algorithm : min;
+            return min(_maxAvailable, source.length);
+        }
+
+        alias opDollar = length;
+    }
+
+    static if (isRandomAccessRange!R)
+    {
+        void popBack()
+        {
+            assert(!empty,
+                "Attempting to popBack() past the beginning of a "
+                ~ Take.stringof);
+            --_maxAvailable;
+        }
+
+        @property auto ref back()
+        {
+            assert(!empty,
+                "Attempting to fetch the back of an empty "
+                ~ Take.stringof);
+            return source[this.length - 1];
+        }
+
+        auto ref opIndex(size_t index)
+        {
+            assert(index < length,
+                "Attempting to index out of the bounds of a "
+                ~ Take.stringof);
+            return source[index];
+        }
+
+        static if (hasAssignableElements!R)
+        {
+            @property auto back(ElementType!R v)
+            {
+                // This has to return auto instead of void because of Bug 4706.
+                assert(!empty,
+                    "Attempting to assign to the back of an empty "
+                    ~ Take.stringof);
+                source[this.length - 1] = v;
+            }
+
+            void opIndexAssign(ElementType!R v, size_t index)
+            {
+                assert(index < length,
+                    "Attempting to index out of the bounds of a "
+                    ~ Take.stringof);
+                source[index] = v;
+            }
+        }
+
+        static if (hasMobileElements!R)
+        {
+            auto moveBack()
+            {
+                assert(!empty,
+                    "Attempting to move the back of an empty "
+                    ~ Take.stringof);
+                return .moveAt(source, this.length - 1);
+            }
+
+            auto moveAt(size_t index)
+            {
+                assert(index < length,
+                    "Attempting to index out of the bounds of a "
+                    ~ Take.stringof);
+                return .moveAt(source, index);
+            }
+        }
+    }
+
+    // Nonstandard
+    @property size_t maxLength() const
+    {
+        return _maxAvailable;
+    }
+}
+
+// This template simply aliases itself to R and is useful for consistency in
+// generic code.
+template Take(R)
+if (isInputRange!(Unqual!R) &&
+    ((!isInfinite!(Unqual!R) && hasSlicing!(Unqual!R)) || is(R T == Take!T)))
+{
+    alias Take = R;
+}
+
+// take for finite ranges with slicing
+/// ditto
+Take!R take(R)(R input, size_t n)
+if (isInputRange!(Unqual!R) && !isInfinite!(Unqual!R) && hasSlicing!(Unqual!R) &&
+    !is(R T == Take!T))
+{
+    // @@@BUG@@@
+    //return input[0 .. min(n, $)];
+    import std.algorithm : min;
+    return input[0 .. min(n, input.length)];
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    auto s = take(arr1, 5);
+    assert(s.length == 5);
+    assert(s[4] == 5);
+    assert(equal(s, [ 1, 2, 3, 4, 5 ][]));
+}
+
+/**
+ * If the range runs out before `n` elements, `take` simply returns the entire
+ * range (unlike $(LREF takeExactly), which will cause an assertion failure if
+ * the range ends prematurely):
+ */
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    int[] arr2 = [ 1, 2, 3 ];
+    auto t = take(arr2, 5);
+    assert(t.length == 3);
+    assert(equal(t, [ 1, 2, 3 ]));
+}
+
+// take(take(r, n1), n2)
+Take!R take(R)(R input, size_t n)
+if (is(R T == Take!T))
+{
+    import std.algorithm : min;
+    return R(input.source, min(n, input._maxAvailable));
+}
+
+// Regular take for input ranges
+Take!(R) take(R)(R input, size_t n)
+if (isInputRange!(Unqual!R) && (isInfinite!(Unqual!R) || !hasSlicing!(Unqual!R) && !is(R T == Take!T)))
+{
+    return Take!R(input, n);
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+
+    int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
+    auto s = take(arr1, 5);
+    assert(s.length == 5);
+    assert(s[4] == 5);
+    assert(equal(s, [ 1, 2, 3, 4, 5 ][]));
+    assert(equal(retro(s), [ 5, 4, 3, 2, 1 ][]));
+
+    // Test fix for bug 4464.
+    static assert(is(typeof(s) == Take!(int[])));
+    static assert(is(typeof(s) == int[]));
+
+    // Test using narrow strings.
+    auto myStr = "This is a string.";
+    auto takeMyStr = take(myStr, 7);
+    assert(equal(takeMyStr, "This is"));
+
+    // Test fix for bug 5052.
+    auto takeMyStrAgain = take(takeMyStr, 4);
+    assert(equal(takeMyStrAgain, "This"));
+    static assert (is (typeof(takeMyStrAgain) == typeof(takeMyStr)));
+    takeMyStrAgain = take(takeMyStr, 10);
+    assert(equal(takeMyStrAgain, "This is"));
+
+    foreach(DummyType; AllDummyRanges) {
+        DummyType dummy;
+        auto t = take(dummy, 5);
+        alias T = typeof(t);
+
+        static if (isRandomAccessRange!DummyType) {
+            static assert(isRandomAccessRange!T);
+            assert(t[4] == 5);
+
+            assert(moveAt(t, 1) == t[1]);
+            assert(t.back == moveBack(t));
+        } else static if (isForwardRange!DummyType) {
+            static assert(isForwardRange!T);
+        }
+
+        for(auto tt = t; !tt.empty; tt.popFront())
+        {
+            assert(tt.front == moveFront(tt));
+        }
+
+        // Bidirectional ranges can't be propagated properly if they don't
+        // also have random access.
+
+        assert(equal(t, [1,2,3,4,5]));
+
+        //Test that take doesn't wrap the result of take.
+        assert(take(t, 4) == take(dummy, 4));
+    }
+
+    immutable myRepeat = repeat(1);
+    static assert(is(Take!(typeof(myRepeat))));
+}
+
+@safe unittest
+{
+    // Check that one can declare variables of all Take types,
+    // and that they match the return type of the corresponding
+    // take().  (See issue 4464.)
+    int[] r1;
+    Take!(int[]) t1;
+    t1 = take(r1, 1);
+
+    string r2;
+    Take!string t2;
+    t2 = take(r2, 1);
+
+    Take!(Take!string) t3;
+    t3 = take(t2, 1);
+}
+
+@safe unittest
+{
+    alias R1 = typeof(repeat(1));
+    alias R2 = typeof(cycle([1]));
+    alias TR1 = Take!R1;
+    alias TR2 = Take!R2;
+    static assert(isBidirectionalRange!TR1);
+    static assert(isBidirectionalRange!TR2);
+}
+
+@safe unittest //12731
+{
+    auto a = repeat(1);
+    auto s = a[1 .. 5];
+    s = s[1 .. 3];
+}
+
+@safe unittest //13151
+{
+    import std.algorithm : equal;
+
+    auto r = take(repeat(1, 4), 3);
+    assert(r.take(2).equal(repeat(1, 2)));
+}
+
+
+/**
+Similar to $(LREF take), but assumes that $(D range) has at least $(D
+n) elements. Consequently, the result of $(D takeExactly(range, n))
+always defines the $(D length) property (and initializes it to $(D n))
+even when $(D range) itself does not define $(D length).
+
+The result of $(D takeExactly) is identical to that of $(LREF take) in
+cases where the original range defines $(D length) or is infinite.
+
+Unlike $(LREF take), however, it is illegal to pass a range with less than
+$(D n) elements to $(D takeExactly); this will cause an assertion failure.
+ */
+auto takeExactly(R)(R range, size_t n)
+if (isInputRange!R)
+{
+    static if (is(typeof(takeExactly(range._input, n)) == R))
+    {
+        assert(n <= range._n,
+               "Attempted to take more than the length of the range with takeExactly.");
+        // takeExactly(takeExactly(r, n1), n2) has the same type as
+        // takeExactly(r, n1) and simply returns takeExactly(r, n2)
+        range._n = n;
+        return range;
+    }
+    //Also covers hasSlicing!R for finite ranges.
+    else static if (hasLength!R)
+    {
+        assert(n <= range.length,
+               "Attempted to take more than the length of the range with takeExactly.");
+        return take(range, n);
+    }
+    else static if (isInfinite!R)
+        return Take!R(range, n);
+    else
+    {
+        static struct Result
+        {
+            R _input;
+            private size_t _n;
+
+            @property bool empty() const { return !_n; }
+            @property auto ref front()
+            {
+                assert(_n > 0, "front() on an empty " ~ Result.stringof);
+                return _input.front;
+            }
+            void popFront() { _input.popFront(); --_n; }
+            @property size_t length() const { return _n; }
+            alias opDollar = length;
+
+            @property Take!R _takeExactly_Result_asTake()
+            {
+                return typeof(return)(_input, _n);
+            }
+
+            alias _takeExactly_Result_asTake this;
+
+            static if (isForwardRange!R)
+                @property auto save()
+                {
+                    return Result(_input.save, _n);
+                }
+
+            static if (hasMobileElements!R)
+            {
+                auto moveFront()
+                {
+                    assert(!empty,
+                        "Attempting to move the front of an empty "
+                        ~ typeof(this).stringof);
+                    return .moveFront(_input);
+                }
+            }
+
+            static if (hasAssignableElements!R)
+            {
+                @property auto ref front(ElementType!R v)
+                {
+                    assert(!empty,
+                        "Attempting to assign to the front of an empty "
+                        ~ typeof(this).stringof);
+                    return _input.front = v;
+                }
+            }
+        }
+
+        return Result(range, n);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto a = [ 1, 2, 3, 4, 5 ];
+
+    auto b = takeExactly(a, 3);
+    assert(equal(b, [1, 2, 3]));
+    static assert(is(typeof(b.length) == size_t));
+    assert(b.length == 3);
+    assert(b.front == 1);
+    assert(b.back == 3);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal, filter;
+
+    auto a = [ 1, 2, 3, 4, 5 ];
+    auto b = takeExactly(a, 3);
+    auto c = takeExactly(b, 2);
+
+    auto d = filter!"a > 0"(a);
+    auto e = takeExactly(d, 3);
+    assert(equal(e, [1, 2, 3]));
+    static assert(is(typeof(e.length) == size_t));
+    assert(e.length == 3);
+    assert(e.front == 1);
+
+    assert(equal(takeExactly(e, 3), [1, 2, 3]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.internal.test.dummyrange;
+
+    auto a = [ 1, 2, 3, 4, 5 ];
+    //Test that take and takeExactly are the same for ranges which define length
+    //but aren't sliceable.
+    struct L
+    {
+        @property auto front() { return _arr[0]; }
+        @property bool empty() { return _arr.empty; }
+        void popFront() { _arr.popFront(); }
+        @property size_t length() { return _arr.length; }
+        int[] _arr;
+    }
+    static assert(is(typeof(take(L(a), 3)) == typeof(takeExactly(L(a), 3))));
+    assert(take(L(a), 3) == takeExactly(L(a), 3));
+
+    //Test that take and takeExactly are the same for ranges which are sliceable.
+    static assert(is(typeof(take(a, 3)) == typeof(takeExactly(a, 3))));
+    assert(take(a, 3) == takeExactly(a, 3));
+
+    //Test that take and takeExactly are the same for infinite ranges.
+    auto inf = repeat(1);
+    static assert(is(typeof(take(inf, 5)) == Take!(typeof(inf))));
+    assert(take(inf, 5) == takeExactly(inf, 5));
+
+    //Test that take and takeExactly are _not_ the same for ranges which don't
+    //define length.
+    static assert(!is(typeof(take(filter!"true"(a), 3)) == typeof(takeExactly(filter!"true"(a), 3))));
+
+    foreach(DummyType; AllDummyRanges)
+    {
+        {
+            DummyType dummy;
+            auto t = takeExactly(dummy, 5);
+
+            //Test that takeExactly doesn't wrap the result of takeExactly.
+            assert(takeExactly(t, 4) == takeExactly(dummy, 4));
+        }
+
+        static if(hasMobileElements!DummyType)
+        {
+            {
+                auto t = takeExactly(DummyType.init, 4);
+                assert(t.moveFront() == 1);
+                assert(equal(t, [1, 2, 3, 4]));
+            }
+        }
+
+        static if(hasAssignableElements!DummyType)
+        {
+            {
+                auto t = takeExactly(DummyType.init, 4);
+                t.front = 9;
+                assert(equal(t, [9, 2, 3, 4]));
+            }
+        }
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.internal.test.dummyrange;
+
+    alias DummyType = DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward);
+    auto te = takeExactly(DummyType(), 5);
+    Take!DummyType t = te;
+    assert(equal(t, [1, 2, 3, 4, 5]));
+    assert(equal(t, te));
+}
+
+/**
+Returns a range with at most one element; for example, $(D
+takeOne([42, 43, 44])) returns a range consisting of the integer $(D
+42). Calling $(D popFront()) off that range renders it empty.
+
+In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) but in
+certain interfaces it is important to know statically that the range may only
+have at most one element.
+
+The type returned by $(D takeOne) is a random-access range with length
+regardless of $(D R)'s capabilities (another feature that distinguishes
+$(D takeOne) from $(D take)).
+ */
+auto takeOne(R)(R source) if (isInputRange!R)
+{
+    static if (hasSlicing!R)
+    {
+        return source[0 .. !source.empty];
+    }
+    else
+    {
+        static struct Result
+        {
+            private R _source;
+            private bool _empty = true;
+            @property bool empty() const { return _empty; }
+            @property auto ref front() { assert(!empty); return _source.front; }
+            void popFront() { assert(!empty); _empty = true; }
+            void popBack() { assert(!empty); _empty = true; }
+            static if (isForwardRange!(Unqual!R))
+            {
+                @property auto save() { return Result(_source.save, empty); }
+            }
+            @property auto ref back() { assert(!empty); return _source.front; }
+            @property size_t length() const { return !empty; }
+            alias opDollar = length;
+            auto ref opIndex(size_t n) { assert(n < length); return _source.front; }
+            auto opSlice(size_t m, size_t n)
+            {
+                assert(m <= n && n < length);
+                return n > m ? this : Result(_source, false);
+            }
+            // Non-standard property
+            @property R source() { return _source; }
+        }
+
+        return Result(source, source.empty);
+    }
+}
+
+///
+@safe unittest
+{
+    auto s = takeOne([42, 43, 44]);
+    static assert(isRandomAccessRange!(typeof(s)));
+    assert(s.length == 1);
+    assert(!s.empty);
+    assert(s.front == 42);
+    s.front = 43;
+    assert(s.front == 43);
+    assert(s.back == 43);
+    assert(s[0] == 43);
+    s.popFront();
+    assert(s.length == 0);
+    assert(s.empty);
+}
+
+unittest
+{
+    struct NonForwardRange
+    {
+        enum empty = false;
+        int front() { return 42; }
+        void popFront() {}
+    }
+
+    static assert(!isForwardRange!NonForwardRange);
+
+    auto s = takeOne(NonForwardRange());
+    assert(s.front == 42);
+}
+
+/++
+    Returns an empty range which is statically known to be empty and is
+    guaranteed to have $(D length) and be random access regardless of $(D R)'s
+    capabilities.
+  +/
+auto takeNone(R)()
+    if(isInputRange!R)
+{
+    return typeof(takeOne(R.init)).init;
+}
+
+///
+@safe unittest
+{
+    auto range = takeNone!(int[])();
+    assert(range.length == 0);
+    assert(range.empty);
+}
+
+@safe unittest
+{
+    enum ctfe = takeNone!(int[])();
+    static assert(ctfe.length == 0);
+    static assert(ctfe.empty);
+}
+
+
+/++
+    Creates an empty range from the given range in $(BIGOH 1). If it can, it
+    will return the same range type. If not, it will return
+    $(D takeExactly(range, 0)).
+  +/
+auto takeNone(R)(R range)
+    if(isInputRange!R)
+{
+    //Makes it so that calls to takeNone which don't use UFCS still work with a
+    //member version if it's defined.
+    static if(is(typeof(R.takeNone)))
+        auto retval = range.takeNone();
+    //@@@BUG@@@ 8339
+    else static if(isDynamicArray!R)/+ ||
+                   (is(R == struct) && __traits(compiles, {auto r = R.init;}) && R.init.empty))+/
+    {
+        auto retval = R.init;
+    }
+    //An infinite range sliced at [0 .. 0] would likely still not be empty...
+    else static if(hasSlicing!R && !isInfinite!R)
+        auto retval = range[0 .. 0];
+    else
+        auto retval = takeExactly(range, 0);
+
+    //@@@BUG@@@ 7892 prevents this from being done in an out block.
+    assert(retval.empty);
+    return retval;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : filter;
+    assert(takeNone([42, 27, 19]).empty);
+    assert(takeNone("dlang.org").empty);
+    assert(takeNone(filter!"true"([42, 27, 19])).empty);
+}
+
+@safe unittest
+{
+    import std.algorithm : filter;
+
+    struct Dummy
+    {
+        mixin template genInput()
+        {
+            @property bool empty() { return _arr.empty; }
+            @property auto front() { return _arr.front; }
+            void popFront() { _arr.popFront(); }
+            static assert(isInputRange!(typeof(this)));
+        }
+    }
+    alias genInput = Dummy.genInput;
+
+    static struct NormalStruct
+    {
+        //Disabled to make sure that the takeExactly version is used.
+        @disable this();
+        this(int[] arr) { _arr = arr; }
+        mixin genInput;
+        int[] _arr;
+    }
+
+    static struct SliceStruct
+    {
+        @disable this();
+        this(int[] arr) { _arr = arr; }
+        mixin genInput;
+        @property auto save() { return this; }
+        auto opSlice(size_t i, size_t j) { return typeof(this)(_arr[i .. j]); }
+        @property size_t length() { return _arr.length; }
+        int[] _arr;
+    }
+
+    static struct InitStruct
+    {
+        mixin genInput;
+        int[] _arr;
+    }
+
+    static struct TakeNoneStruct
+    {
+        this(int[] arr) { _arr = arr; }
+        @disable this();
+        mixin genInput;
+        auto takeNone() { return typeof(this)(null); }
+        int[] _arr;
+    }
+
+    static class NormalClass
+    {
+        this(int[] arr) {_arr = arr;}
+        mixin genInput;
+        int[] _arr;
+    }
+
+    static class SliceClass
+    {
+        this(int[] arr) { _arr = arr; }
+        mixin genInput;
+        @property auto save() { return new typeof(this)(_arr); }
+        auto opSlice(size_t i, size_t j) { return new typeof(this)(_arr[i .. j]); }
+        @property size_t length() { return _arr.length; }
+        int[] _arr;
+    }
+
+    static class TakeNoneClass
+    {
+        this(int[] arr) { _arr = arr; }
+        mixin genInput;
+        auto takeNone() { return new typeof(this)(null); }
+        int[] _arr;
+    }
+
+    import std.format : format;
+
+    foreach(range; AliasSeq!([1, 2, 3, 4, 5],
+                             "hello world",
+                             "hello world"w,
+                             "hello world"d,
+                             SliceStruct([1, 2, 3]),
+                             //@@@BUG@@@ 8339 forces this to be takeExactly
+                             //`InitStruct([1, 2, 3]),
+                             TakeNoneStruct([1, 2, 3])))
+    {
+        static assert(takeNone(range).empty, typeof(range).stringof);
+        assert(takeNone(range).empty);
+        static assert(is(typeof(range) == typeof(takeNone(range))), typeof(range).stringof);
+    }
+
+    foreach(range; AliasSeq!(NormalStruct([1, 2, 3]),
+                             InitStruct([1, 2, 3])))
+    {
+        static assert(takeNone(range).empty, typeof(range).stringof);
+        assert(takeNone(range).empty);
+        static assert(is(typeof(takeExactly(range, 0)) == typeof(takeNone(range))), typeof(range).stringof);
+    }
+
+    //Don't work in CTFE.
+    auto normal = new NormalClass([1, 2, 3]);
+    assert(takeNone(normal).empty);
+    static assert(is(typeof(takeExactly(normal, 0)) == typeof(takeNone(normal))), typeof(normal).stringof);
+
+    auto slice = new SliceClass([1, 2, 3]);
+    assert(takeNone(slice).empty);
+    static assert(is(SliceClass == typeof(takeNone(slice))), typeof(slice).stringof);
+
+    auto taken = new TakeNoneClass([1, 2, 3]);
+    assert(takeNone(taken).empty);
+    static assert(is(TakeNoneClass == typeof(takeNone(taken))), typeof(taken).stringof);
+
+    auto filtered = filter!"true"([1, 2, 3, 4, 5]);
+    assert(takeNone(filtered).empty);
+    //@@@BUG@@@ 8339 and 5941 force this to be takeExactly
+    //static assert(is(typeof(filtered) == typeof(takeNone(filtered))), typeof(filtered).stringof);
+}
+
+/++
+ + Return a _range advanced to within $(D _n) elements of the end of
+ + $(D _range).
+ +
+ + Intended as the _range equivalent of the Unix
+ + $(WEB en.wikipedia.org/wiki/Tail_%28Unix%29, _tail) utility. When the length
+ + of $(D _range) is less than or equal to $(D _n), $(D _range) is returned
+ + as-is.
+ +
+ + Completes in $(BIGOH 1) steps for ranges that support slicing and have
+ + length. Completes in $(BIGOH _range.length) time for all other ranges.
+ +
+ + Params:
+ +    range = _range to get _tail of
+ +    n = maximum number of elements to include in _tail
+ +
+ + Returns:
+ +    Returns the _tail of $(D _range) augmented with length information
+ +/
+auto tail(Range)(Range range, size_t n)
+    if (isInputRange!Range && !isInfinite!Range &&
+        (hasLength!Range || isForwardRange!Range))
+{
+    static if (hasLength!Range)
+    {
+        immutable length = range.length;
+        if (n >= length)
+            return range.takeExactly(length);
+        else
+            return range.drop(length - n).takeExactly(n);
+    }
+    else
+    {
+        Range scout = range.save;
+        foreach (immutable i; 0 .. n)
+        {
+            if (scout.empty)
+                return range.takeExactly(i);
+            scout.popFront();
+        }
+
+        auto tail = range.save;
+        while (!scout.empty)
+        {
+            assert(!tail.empty);
+            scout.popFront();
+            tail.popFront();
+        }
+
+        return tail.takeExactly(n);
+    }
+}
+
+///
+pure @safe unittest
+{
+    // tail -c n
+    assert([1, 2, 3].tail(1) == [3]);
+    assert([1, 2, 3].tail(2) == [2, 3]);
+    assert([1, 2, 3].tail(3) == [1, 2, 3]);
+    assert([1, 2, 3].tail(4) == [1, 2, 3]);
+    assert([1, 2, 3].tail(0).length == 0);
+
+    // tail --lines=n
+    import std.algorithm.comparison : equal;
+    import std.algorithm.iteration : joiner;
+    import std.string : lineSplitter;
+
+    assert("one\ntwo\nthree"
+            .lineSplitter
+            .tail(2)
+            .joiner("\n")
+            .equal("two\nthree"));
+}
+
+// @nogc prevented by @@@BUG@@@ 15408
+pure nothrow @safe /+@nogc+/ unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.internal.test.dummyrange;
+
+    static immutable cheatsheet = [6, 7, 8, 9, 10];
+
+    foreach (R; AllDummyRanges)
+    {
+        static if (isInputRange!R && !isInfinite!R &&
+                   (hasLength!R || isForwardRange!R))
+        {
+            assert(R.init.tail(5).equal(cheatsheet));
+            static assert(R.init.tail(5).equal(cheatsheet));
+
+            assert(R.init.tail(0).length == 0);
+            assert(R.init.tail(10).equal(R.init));
+            assert(R.init.tail(11).equal(R.init));
+        }
+    }
+
+    // Infinite ranges are not supported
+    static assert(!__traits(compiles, repeat(0).tail(0)));
+
+    // Neither are non-forward ranges without length
+    static assert(!__traits(compiles, DummyRange!(ReturnBy.Value, Length.No,
+        RangeType.Input).init.tail(5)));
+}
+
+@nogc unittest
+{
+    static immutable input = [1, 2, 3];
+    static immutable expectedOutput = [2, 3];
+    assert(input.tail(2) == expectedOutput);
+}
+
+/++
+    Convenience function which calls
+    $(D range.$(LREF popFrontN)(n)) and returns $(D range). $(D drop)
+    makes it easier to pop elements from a range
+    and then pass it to another function within a single expression,
+    whereas $(D popFrontN) would require multiple statements.
+
+    $(D dropBack) provides the same functionality but instead calls
+    $(D range.popBackN(n)).
+
+    Note: $(D drop) and $(D dropBack) will only pop $(I up to)
+    $(D n) elements but will stop if the range is empty first.
+
+  +/
+R drop(R)(R range, size_t n)
+    if(isInputRange!R)
+{
+    range.popFrontN(n);
+    return range;
+}
+/// ditto
+R dropBack(R)(R range, size_t n)
+    if(isBidirectionalRange!R)
+{
+    range.popBackN(n);
+    return range;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    assert([0, 2, 1, 5, 0, 3].drop(3) == [5, 0, 3]);
+    assert("hello world".drop(6) == "world");
+    assert("hello world".drop(50).empty);
+    assert("hello world".take(6).drop(3).equal("lo "));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    assert([0, 2, 1, 5, 0, 3].dropBack(3) == [0, 2, 1]);
+    assert("hello world".dropBack(6) == "hello");
+    assert("hello world".dropBack(50).empty);
+    assert("hello world".drop(4).dropBack(4).equal("o w"));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.container.dlist;
+
+    //Remove all but the first two elements
+    auto a = DList!int(0, 1, 9, 9, 9, 9);
+    a.remove(a[].drop(2));
+    assert(a[].equal(a[].take(2)));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal, filter;
+
+    assert(drop("", 5).empty);
+    assert(equal(drop(filter!"true"([0, 2, 1, 5, 0, 3]), 3), [5, 0, 3]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.container.dlist;
+
+    //insert before the last two elements
+    auto a = DList!int(0, 1, 2, 5, 6);
+    a.insertAfter(a[].dropBack(2), [3, 4]);
+    assert(a[].equal(iota(0, 7)));
+}
+
+/++
+    Similar to $(LREF drop) and $(D dropBack) but they call
+    $(D range.$(LREF popFrontExactly)(n)) and $(D range.popBackExactly(n))
+    instead.
+
+    Note: Unlike $(D drop), $(D dropExactly) will assume that the
+    range holds at least $(D n) elements. This makes $(D dropExactly)
+    faster than $(D drop), but it also means that if $(D range) does
+    not contain at least $(D n) elements, it will attempt to call $(D popFront)
+    on an empty range, which is undefined behavior. So, only use
+    $(D popFrontExactly) when it is guaranteed that $(D range) holds at least
+    $(D n) elements.
++/
+R dropExactly(R)(R range, size_t n)
+    if(isInputRange!R)
+{
+    popFrontExactly(range, n);
+    return range;
+}
+/// ditto
+R dropBackExactly(R)(R range, size_t n)
+    if(isBidirectionalRange!R)
+{
+    popBackExactly(range, n);
+    return range;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal, filterBidirectional;
+
+    auto a = [1, 2, 3];
+    assert(a.dropExactly(2) == [3]);
+    assert(a.dropBackExactly(2) == [1]);
+
+    string s = "日本語";
+    assert(s.dropExactly(2) == "語");
+    assert(s.dropBackExactly(2) == "日");
+
+    auto bd = filterBidirectional!"true"([1, 2, 3]);
+    assert(bd.dropExactly(2).equal([3]));
+    assert(bd.dropBackExactly(2).equal([1]));
+}
+
+/++
+    Convenience function which calls
+    $(D range.popFront()) and returns $(D range). $(D dropOne)
+    makes it easier to pop an element from a range
+    and then pass it to another function within a single expression,
+    whereas $(D popFront) would require multiple statements.
+
+    $(D dropBackOne) provides the same functionality but instead calls
+    $(D range.popBack()).
++/
+R dropOne(R)(R range)
+    if (isInputRange!R)
+{
+    range.popFront();
+    return range;
+}
+/// ditto
+R dropBackOne(R)(R range)
+    if (isBidirectionalRange!R)
+{
+    range.popBack();
+    return range;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal, filterBidirectional;
+
+    import std.container.dlist;
+
+    auto dl = DList!int(9, 1, 2, 3, 9);
+    assert(dl[].dropOne().dropBackOne().equal([1, 2, 3]));
+
+    auto a = [1, 2, 3];
+    assert(a.dropOne() == [2, 3]);
+    assert(a.dropBackOne() == [1, 2]);
+
+    string s = "日本語";
+    assert(s.dropOne() == "本語");
+    assert(s.dropBackOne() == "日本");
+
+    auto bd = filterBidirectional!"true"([1, 2, 3]);
+    assert(bd.dropOne().equal([2, 3]));
+    assert(bd.dropBackOne().equal([1, 2]));
+}
+
+/**
+Repeats one value forever.
+
+Models an infinite bidirectional and random access range, with slicing.
+*/
+struct Repeat(T)
+{
+private:
+    //Store a non-qualified T when possible: This is to make Repeat assignable
+    static if ((is(T == class) || is(T == interface)) && (is(T == const) || is(T == immutable)))
+    {
+        import std.typecons;
+        alias UT = Rebindable!T;
+    }
+    else static if (is(T : Unqual!T) && is(Unqual!T : T))
+        alias UT = Unqual!T;
+    else
+        alias UT = T;
+    UT _value;
+
+public:
+    @property inout(T) front() inout { return _value; }
+    @property inout(T) back() inout { return _value; }
+    enum bool empty = false;
+    void popFront() {}
+    void popBack() {}
+    @property auto save() inout { return this; }
+    inout(T) opIndex(size_t) inout { return _value; }
+    auto opSlice(size_t i, size_t j)
+    in
+    {
+        assert(i <= j);
+    }
+    body
+    {
+        return this.takeExactly(j - i);
+    }
+    private static struct DollarToken {}
+    enum opDollar = DollarToken.init;
+    auto opSlice(size_t, DollarToken) inout { return this; }
+}
+
+/// Ditto
+Repeat!T repeat(T)(T value) { return Repeat!T(value); }
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto  r = repeat(5);
+    alias R = typeof(r);
+    static assert(isBidirectionalRange!R);
+    static assert(isForwardRange!R);
+    static assert(isInfinite!R);
+    static assert(hasSlicing!R);
+
+    assert(r.back == 5);
+    assert(r.front == 5);
+    assert(r.take(4).equal([ 5, 5, 5, 5 ]));
+    assert(r[0 .. 4].equal([ 5, 5, 5, 5 ]));
+
+    R r2 = r[5 .. $];
+}
+
+/**
+   Repeats $(D value) exactly $(D n) times. Equivalent to $(D
+   take(repeat(value), n)).
+*/
+Take!(Repeat!T) repeat(T)(T value, size_t n)
+{
+    return take(repeat(value), n);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    assert(equal(5.repeat(4), 5.repeat().take(4)));
+}
+
+@safe unittest //12007
+{
+    static class C{}
+    Repeat!(immutable int) ri;
+    ri = ri.save;
+    Repeat!(immutable C) rc;
+    rc = rc.save;
+
+    import std.algorithm;
+    immutable int[] A = [1,2,3];
+    immutable int[] B = [4,5,6];
+
+    auto AB = cartesianProduct(A,B);
+}
+
+/**
+Given callable ($(XREF traits, isCallable)) $(D fun), create as a range
+whose front is defined by successive calls to $(D fun()).
+This is especially useful to call function with global side effects (random
+functions), or to create ranges expressed as a single delegate, rather than
+an entire $(D front)/$(D popFront)/$(D empty) structure.
+$(D fun) maybe be passed either a template alias parameter (existing
+function, delegate, struct type defining static $(D opCall)... ) or
+a run-time value argument (delegate, function object... ).
+The result range models an InputRange
+($(XREF_PACK range,primitives,isInputRange)).
+The resulting range will call $(D fun()) on every call to $(D front),
+and only when $(D front) is called, regardless of how the range is
+iterated.
+It is advised to compose generate with either
+$(XREF_PACK algorithm,iteration,cache) or $(XREF array,array), or to use it in a
+foreach loop.
+A by-value foreach loop means that the loop value is not $(D ref).
+
+Params:
+    fun = is the $(D isCallable) that gets called on every call to front.
+
+Returns: an $(D inputRange) that returns a new value generated by $(D Fun) on
+    any call to $(D front).
+*/
+auto generate(Fun)(Fun fun)
+    if (isCallable!fun)
+{
+    return Generator!(Fun)(fun);
+}
+
+/// ditto
+auto generate(alias fun)()
+    if (isCallable!fun)
+{
+    return Generator!(fun)();
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm : equal, map;
+
+    int i = 1;
+    auto powersOfTwo = generate!(() => i *= 2)().take(10);
+    assert(equal(powersOfTwo, iota(1, 11).map!"2^^a"()));
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm : equal;
+
+    //Returns a run-time delegate
+    auto infiniteIota(T)(T low, T high)
+    {
+        T i = high;
+        return (){if (i == high) i = low; return i++;};
+    }
+    //adapted as a range.
+    assert(equal(generate(infiniteIota(1, 4)).take(10), [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]));
+}
+
+///
+unittest
+{
+    import std.format, std.random;
+
+    auto r = generate!(() => uniform(0, 6)).take(10);
+    format("%(%s %)", r);
+}
+
+//private struct Generator(bool onPopFront, bool runtime, Fun...)
+private struct Generator(Fun...)
+{
+    static assert(Fun.length == 1);
+    static assert(isInputRange!Generator);
+
+private:
+    static if (is(Fun[0]))
+        Fun[0] fun;
+    else
+        alias fun = Fun[0];
+
+public:
+    enum empty = false;
+
+    auto ref front() @property
+    {
+        return fun();
+    }
+
+    void popFront() { }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    struct StaticOpCall
+    {
+        static ubyte opCall() { return 5 ; }
+    }
+
+    assert(equal(generate!StaticOpCall().take(10), repeat(5).take(10)));
+}
+
+@safe pure unittest
+{
+    import std.algorithm : equal;
+
+    struct OpCall
+    {
+        ubyte opCall() @safe pure { return 5 ; }
+    }
+
+    OpCall op;
+    assert(equal(generate(op).take(10), repeat(5).take(10)));
+}
+
+/**
+Repeats the given forward range ad infinitum. If the original range is
+infinite (fact that would make $(D Cycle) the identity application),
+$(D Cycle) detects that and aliases itself to the range type
+itself. If the original range has random access, $(D Cycle) offers
+random access and also offers a constructor taking an initial position
+$(D index). $(D Cycle) works with static arrays in addition to ranges,
+mostly for performance reasons.
+
+Note: The input range must not be empty.
+
+Tip: This is a great way to implement simple circular buffers.
+*/
+struct Cycle(R)
+    if (isForwardRange!R && !isInfinite!R)
+{
+    static if (isRandomAccessRange!R && hasLength!R)
+    {
+        private R _original;
+        private size_t _index;
+
+        this(R input, size_t index = 0)
+        {
+            _original = input;
+            _index = index % _original.length;
+        }
+
+        @property auto ref front()
+        {
+            return _original[_index];
+        }
+
+        static if (is(typeof((cast(const R)_original)[_index])))
+        {
+            @property auto ref front() const
+            {
+                return _original[_index];
+            }
+        }
+
+        static if (hasAssignableElements!R)
+        {
+            @property auto front(ElementType!R val)
+            {
+                _original[_index] = val;
+            }
+        }
+
+        enum bool empty = false;
+
+        void popFront()
+        {
+            ++_index;
+            if (_index >= _original.length)
+                _index = 0;
+        }
+
+        auto ref opIndex(size_t n)
+        {
+            return _original[(n + _index) % _original.length];
+        }
+
+        static if (is(typeof((cast(const R)_original)[_index])) &&
+                   is(typeof((cast(const R)_original).length)))
+        {
+            auto ref opIndex(size_t n) const
+            {
+                return _original[(n + _index) % _original.length];
+            }
+        }
+
+        static if (hasAssignableElements!R)
+        {
+            auto opIndexAssign(ElementType!R val, size_t n)
+            {
+                _original[(n + _index) % _original.length] = val;
+            }
+        }
+
+        @property Cycle save()
+        {
+            //No need to call _original.save, because Cycle never actually modifies _original
+            return Cycle(_original, _index);
+        }
+
+        private static struct DollarToken {}
+        enum opDollar = DollarToken.init;
+
+        auto opSlice(size_t i, size_t j)
+        in
+        {
+            assert(i <= j);
+        }
+        body
+        {
+            return this[i .. $].takeExactly(j - i);
+        }
+
+        auto opSlice(size_t i, DollarToken)
+        {
+            return typeof(this)(_original, _index + i);
+        }
+    }
+    else
+    {
+        private R _original;
+        private R _current;
+
+        this(R input)
+        {
+            _original = input;
+            _current = input.save;
+        }
+
+        @property auto ref front()
+        {
+            return _current.front;
+        }
+
+        static if (is(typeof((cast(const R)_current).front)))
+        {
+            @property auto ref front() const
+            {
+                return _current.front;
+            }
+        }
+
+        static if (hasAssignableElements!R)
+        {
+            @property auto front(ElementType!R val)
+            {
+                return _current.front = val;
+            }
+        }
+
+        enum bool empty = false;
+
+        void popFront()
+        {
+            _current.popFront();
+            if (_current.empty)
+                _current = _original.save;
+        }
+
+        @property Cycle save()
+        {
+            //No need to call _original.save, because Cycle never actually modifies _original
+            Cycle ret = this;
+            ret._original = _original;
+            ret._current =  _current.save;
+            return ret;
+        }
+    }
+}
+
+template Cycle(R)
+    if (isInfinite!R)
+{
+    alias Cycle = R;
+}
+
+struct Cycle(R)
+    if (isStaticArray!R)
+{
+    private alias ElementType = typeof(R.init[0]);
+    private ElementType* _ptr;
+    private size_t _index;
+
+nothrow:
+    this(ref R input, size_t index = 0) @system
+    {
+        _ptr = input.ptr;
+        _index = index % R.length;
+    }
+
+    @property ref inout(ElementType) front() inout @safe
+    {
+        static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted
+        {
+            return p[idx];
+        }
+        return trustedPtrIdx(_ptr, _index);
+    }
+
+    enum bool empty = false;
+
+    void popFront() @safe
+    {
+        ++_index;
+        if (_index >= R.length)
+            _index = 0;
+    }
+
+    ref inout(ElementType) opIndex(size_t n) inout @safe
+    {
+        static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted
+        {
+            return p[idx % R.length];
+        }
+        return trustedPtrIdx(_ptr, n + _index);
+    }
+
+    @property inout(Cycle) save() inout @safe
+    {
+        return this;
+    }
+
+    private static struct DollarToken {}
+    enum opDollar = DollarToken.init;
+
+    auto opSlice(size_t i, size_t j) @safe
+    in
+    {
+        assert(i <= j);
+    }
+    body
+    {
+        return this[i .. $].takeExactly(j - i);
+    }
+
+    inout(typeof(this)) opSlice(size_t i, DollarToken) inout @safe
+    {
+        static auto trustedCtor(typeof(_ptr) p, size_t idx) @trusted
+        {
+            return cast(inout)Cycle(*cast(R*)(p), idx);
+        }
+        return trustedCtor(_ptr, _index + i);
+    }
+}
+
+/// Ditto
+Cycle!R cycle(R)(R input)
+    if (isForwardRange!R && !isInfinite!R)
+{
+    assert(!input.empty);
+    return Cycle!R(input);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.range : cycle, take;
+
+    // Here we create an infinitive cyclic sequence from [1, 2]
+    // (i.e. get here [1, 2, 1, 2, 1, 2 and so on]) then
+    // take 5 elements of this sequence (so we have [1, 2, 1, 2, 1])
+    // and compare them with the expected values for equality.
+    assert(cycle([1, 2]).take(5).equal([ 1, 2, 1, 2, 1 ]));
+}
+
+/// Ditto
+Cycle!R cycle(R)(R input, size_t index = 0)
+    if (isRandomAccessRange!R && !isInfinite!R)
+{
+    assert(!input.empty);
+    return Cycle!R(input, index);
+}
+
+Cycle!R cycle(R)(R input)
+    if (isInfinite!R)
+{
+    return input;
+}
+
+Cycle!R cycle(R)(ref R input, size_t index = 0) @system
+    if (isStaticArray!R)
+{
+    return Cycle!R(input, index);
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+
+    static assert(isForwardRange!(Cycle!(uint[])));
+
+    // Make sure ref is getting propagated properly.
+    int[] nums = [1,2,3];
+    auto c2 = cycle(nums);
+    c2[3]++;
+    assert(nums[0] == 2);
+
+    immutable int[] immarr = [1, 2, 3];
+    auto cycleimm = cycle(immarr);
+
+    foreach(DummyType; AllDummyRanges)
+    {
+        static if (isForwardRange!DummyType)
+        {
+            DummyType dummy;
+            auto cy = cycle(dummy);
+            static assert(isForwardRange!(typeof(cy)));
+            auto t = take(cy, 20);
+            assert(equal(t, [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10]));
+
+            const cRange = cy;
+            assert(cRange.front == 1);
+
+            static if (hasAssignableElements!DummyType)
+            {
+                {
+                    cy.front = 66;
+                    scope(exit) cy.front = 1;
+                    assert(dummy.front == 66);
+                }
+
+                static if (isRandomAccessRange!DummyType)
+                {
+                    {
+                        cy[10] = 66;
+                        scope(exit) cy[10] = 1;
+                        assert(dummy.front == 66);
+                    }
+
+                    assert(cRange[10] == 1);
+                }
+            }
+
+            static if(hasSlicing!DummyType)
+            {
+                auto slice = cy[5 .. 15];
+                assert(equal(slice, [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]));
+                static assert(is(typeof(slice) == typeof(takeExactly(cy, 5))));
+
+                auto infSlice = cy[7 .. $];
+                assert(equal(take(infSlice, 5), [8, 9, 10, 1, 2]));
+                static assert(isInfinite!(typeof(infSlice)));
+            }
+        }
+    }
+}
+
+@system unittest // For static arrays.
+{
+    import std.algorithm : equal;
+
+    int[3] a = [ 1, 2, 3 ];
+    static assert(isStaticArray!(typeof(a)));
+    auto c = cycle(a);
+    assert(a.ptr == c._ptr);
+    assert(equal(take(cycle(a), 5), [ 1, 2, 3, 1, 2 ][]));
+    static assert(isForwardRange!(typeof(c)));
+
+    // Test qualifiers on slicing.
+    alias C = typeof(c);
+    static assert(is(typeof(c[1 .. $]) == C));
+    const cConst = c;
+    static assert(is(typeof(cConst[1 .. $]) == const(C)));
+}
+
+@safe unittest // For infinite ranges
+{
+    struct InfRange
+    {
+        void popFront() { }
+        @property int front() { return 0; }
+        enum empty = false;
+    }
+
+    InfRange i;
+    auto c = cycle(i);
+    assert (c == i);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    int[5] arr = [0, 1, 2, 3, 4];
+    auto cleD = cycle(arr[]); //Dynamic
+    assert(equal(cleD[5 .. 10], arr[]));
+
+    //n is a multiple of 5 worth about 3/4 of size_t.max
+    auto n = size_t.max/4 + size_t.max/2;
+    n -= n % 5;
+
+    //Test index overflow
+    foreach (_ ; 0 .. 10)
+    {
+        cleD = cleD[n .. $];
+        assert(equal(cleD[5 .. 10], arr[]));
+    }
+}
+
+@system unittest
+{
+    import std.algorithm : equal;
+
+    int[5] arr = [0, 1, 2, 3, 4];
+    auto cleS = cycle(arr);   //Static
+    assert(equal(cleS[5 .. 10], arr[]));
+
+    //n is a multiple of 5 worth about 3/4 of size_t.max
+    auto n = size_t.max/4 + size_t.max/2;
+    n -= n % 5;
+
+    //Test index overflow
+    foreach (_ ; 0 .. 10)
+    {
+        cleS = cleS[n .. $];
+        assert(equal(cleS[5 .. 10], arr[]));
+    }
+}
+
+@system unittest
+{
+    import std.algorithm : equal;
+
+    int[1] arr = [0];
+    auto cleS = cycle(arr);
+    cleS = cleS[10 .. $];
+    assert(equal(cleS[5 .. 10], 0.repeat(5)));
+    assert(cleS.front == 0);
+}
+
+unittest //10845
+{
+    import std.algorithm : equal, filter;
+
+    auto a = inputRangeObject(iota(3).filter!"true");
+    assert(equal(cycle(a).take(10), [0, 1, 2, 0, 1, 2, 0, 1, 2, 0]));
+}
+
+@safe unittest // 12177
+{
+    auto a = recurrence!q{a[n - 1] ~ a[n - 2]}("1", "0");
+}
+
+// Issue 13390
+unittest
+{
+    import std.exception;
+    import core.exception : AssertError;
+    assertThrown!AssertError(cycle([0, 1, 2][0..0]));
+}
+
+private alias lengthType(R) = typeof(R.init.length.init);
+
+/**
+   Iterate several ranges in lockstep. The element type is a proxy tuple
+   that allows accessing the current element in the $(D n)th range by
+   using $(D e[n]).
+
+   `zip` is similar to $(LREF lockstep), but `lockstep` doesn't
+   bundle its elements and uses the `opApply` protocol.
+   `lockstep` allows reference access to the elements in
+   `foreach` iterations.
+
+   $(D Zip) offers the lowest range facilities of all components, e.g. it
+   offers random access iff all ranges offer random access, and also
+   offers mutation and swapping if all ranges offer it. Due to this, $(D
+   Zip) is extremely powerful because it allows manipulating several
+   ranges in lockstep.
+*/
+struct Zip(Ranges...)
+    if (Ranges.length && allSatisfy!(isInputRange, Ranges))
+{
+    import std.format : format; //for generic mixins
+    import std.typecons : Tuple;
+
+    alias R = Ranges;
+    R ranges;
+    alias ElementType = Tuple!(staticMap!(.ElementType, R));
+    StoppingPolicy stoppingPolicy = StoppingPolicy.shortest;
+
+/**
+   Builds an object. Usually this is invoked indirectly by using the
+   $(LREF zip) function.
+ */
+    this(R rs, StoppingPolicy s = StoppingPolicy.shortest)
+    {
+        ranges[] = rs[];
+        stoppingPolicy = s;
+    }
+
+/**
+   Returns $(D true) if the range is at end. The test depends on the
+   stopping policy.
+*/
+    static if (allSatisfy!(isInfinite, R))
+    {
+        // BUG:  Doesn't propagate infiniteness if only some ranges are infinite
+        //       and s == StoppingPolicy.longest.  This isn't fixable in the
+        //       current design since StoppingPolicy is known only at runtime.
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty()
+        {
+            import std.exception : enforce;
+
+            final switch (stoppingPolicy)
+            {
+            case StoppingPolicy.shortest:
+                foreach (i, Unused; R)
+                {
+                    if (ranges[i].empty) return true;
+                }
+                return false;
+            case StoppingPolicy.longest:
+                static if (anySatisfy!(isInfinite, R))
+                {
+                    return false;
+                }
+                else
+                {
+                    foreach (i, Unused; R)
+                    {
+                        if (!ranges[i].empty) return false;
+                    }
+                    return true;
+                }
+            case StoppingPolicy.requireSameLength:
+                foreach (i, Unused; R[1 .. $])
+                {
+                    enforce(ranges[0].empty ==
+                            ranges[i + 1].empty,
+                            "Inequal-length ranges passed to Zip");
+                }
+                return ranges[0].empty;
+            }
+            assert(false);
+        }
+    }
+
+    static if (allSatisfy!(isForwardRange, R))
+    {
+        @property Zip save()
+        {
+            //Zip(ranges[0].save, ranges[1].save, ..., stoppingPolicy)
+            return mixin (q{Zip(%(ranges[%s]%|, %), stoppingPolicy)}.format(iota(0, R.length)));
+        }
+    }
+
+    private .ElementType!(R[i]) tryGetInit(size_t i)()
+    {
+        alias E = .ElementType!(R[i]);
+        static if (!is(typeof({static E i;})))
+            throw new Exception("Range with non-default constructable elements exhausted.");
+        else
+            return E.init;
+    }
+
+/**
+   Returns the current iterated element.
+*/
+    @property ElementType front()
+    {
+        @property tryGetFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].front;}
+        //ElementType(tryGetFront!0, tryGetFront!1, ...)
+        return mixin(q{ElementType(%(tryGetFront!%s, %))}.format(iota(0, R.length)));
+    }
+
+/**
+   Sets the front of all iterated ranges.
+*/
+    static if (allSatisfy!(hasAssignableElements, R))
+    {
+        @property void front(ElementType v)
+        {
+            foreach (i, Unused; R)
+            {
+                if (!ranges[i].empty)
+                {
+                    ranges[i].front = v[i];
+                }
+            }
+        }
+    }
+
+/**
+   Moves out the front.
+*/
+    static if (allSatisfy!(hasMobileElements, R))
+    {
+        ElementType moveFront()
+        {
+            @property tryMoveFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : .moveFront(ranges[i]);}
+            //ElementType(tryMoveFront!0, tryMoveFront!1, ...)
+            return mixin(q{ElementType(%(tryMoveFront!%s, %))}.format(iota(0, R.length)));
+        }
+    }
+
+/**
+   Returns the rightmost element.
+*/
+    static if (allSatisfy!(isBidirectionalRange, R))
+    {
+        @property ElementType back()
+        {
+            //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness
+
+            @property tryGetBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].back;}
+            //ElementType(tryGetBack!0, tryGetBack!1, ...)
+            return mixin(q{ElementType(%(tryGetBack!%s, %))}.format(iota(0, R.length)));
+        }
+
+/**
+   Moves out the back.
+*/
+        static if (allSatisfy!(hasMobileElements, R))
+        {
+            ElementType moveBack()
+            {
+                //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness
+
+                @property tryMoveBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : .moveFront(ranges[i]);}
+                //ElementType(tryMoveBack!0, tryMoveBack!1, ...)
+                return mixin(q{ElementType(%(tryMoveBack!%s, %))}.format(iota(0, R.length)));
+            }
+        }
+
+/**
+   Returns the current iterated element.
+*/
+        static if (allSatisfy!(hasAssignableElements, R))
+        {
+            @property void back(ElementType v)
+            {
+                //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness.
+                //Not sure the call is even legal for StoppingPolicy.longest
+
+                foreach (i, Unused; R)
+                {
+                    if (!ranges[i].empty)
+                    {
+                        ranges[i].back = v[i];
+                    }
+                }
+            }
+        }
+    }
+
+/**
+   Advances to the next element in all controlled ranges.
+*/
+    void popFront()
+    {
+        import std.exception : enforce;
+
+        final switch (stoppingPolicy)
+        {
+        case StoppingPolicy.shortest:
+            foreach (i, Unused; R)
+            {
+                assert(!ranges[i].empty);
+                ranges[i].popFront();
+            }
+            break;
+        case StoppingPolicy.longest:
+            foreach (i, Unused; R)
+            {
+                if (!ranges[i].empty) ranges[i].popFront();
+            }
+            break;
+        case StoppingPolicy.requireSameLength:
+            foreach (i, Unused; R)
+            {
+                enforce(!ranges[i].empty, "Invalid Zip object");
+                ranges[i].popFront();
+            }
+            break;
+        }
+    }
+
+/**
+   Calls $(D popBack) for all controlled ranges.
+*/
+    static if (allSatisfy!(isBidirectionalRange, R))
+    {
+        void popBack()
+        {
+            //TODO: Fixme! In case of jaggedness, this is wrong.
+            import std.exception : enforce;
+
+            final switch (stoppingPolicy)
+            {
+            case StoppingPolicy.shortest:
+                foreach (i, Unused; R)
+                {
+                    assert(!ranges[i].empty);
+                    ranges[i].popBack();
+                }
+                break;
+            case StoppingPolicy.longest:
+                foreach (i, Unused; R)
+                {
+                    if (!ranges[i].empty) ranges[i].popBack();
+                }
+                break;
+            case StoppingPolicy.requireSameLength:
+                foreach (i, Unused; R)
+                {
+                    enforce(!ranges[i].empty, "Invalid Zip object");
+                    ranges[i].popBack();
+                }
+                break;
+            }
+        }
+    }
+
+/**
+   Returns the length of this range. Defined only if all ranges define
+   $(D length).
+*/
+    static if (allSatisfy!(hasLength, R))
+    {
+        @property auto length()
+        {
+            static if (Ranges.length == 1)
+                return ranges[0].length;
+            else
+            {
+                if (stoppingPolicy == StoppingPolicy.requireSameLength)
+                    return ranges[0].length;
+
+                //[min|max](ranges[0].length, ranges[1].length, ...)
+                import std.algorithm : min, max;
+                if (stoppingPolicy == StoppingPolicy.shortest)
+                    return mixin(q{min(%(ranges[%s].length%|, %))}.format(iota(0, R.length)));
+                else
+                    return mixin(q{max(%(ranges[%s].length%|, %))}.format(iota(0, R.length)));
+            }
+        }
+
+        alias opDollar = length;
+    }
+
+/**
+   Returns a slice of the range. Defined only if all range define
+   slicing.
+*/
+    static if (allSatisfy!(hasSlicing, R))
+    {
+        auto opSlice(size_t from, size_t to)
+        {
+            //Slicing an infinite range yields the type Take!R
+            //For finite ranges, the type Take!R aliases to R
+            alias ZipResult = Zip!(staticMap!(Take, R));
+
+            //ZipResult(ranges[0][from .. to], ranges[1][from .. to], ..., stoppingPolicy)
+            return mixin (q{ZipResult(%(ranges[%s][from .. to]%|, %), stoppingPolicy)}.format(iota(0, R.length)));
+        }
+    }
+
+/**
+   Returns the $(D n)th element in the composite range. Defined if all
+   ranges offer random access.
+*/
+    static if (allSatisfy!(isRandomAccessRange, R))
+    {
+        ElementType opIndex(size_t n)
+        {
+            //TODO: Fixme! This may create an out of bounds access
+            //for StoppingPolicy.longest
+
+            //ElementType(ranges[0][n], ranges[1][n], ...)
+            return mixin (q{ElementType(%(ranges[%s][n]%|, %))}.format(iota(0, R.length)));
+        }
+
+/**
+   Assigns to the $(D n)th element in the composite range. Defined if
+   all ranges offer random access.
+*/
+        static if (allSatisfy!(hasAssignableElements, R))
+        {
+            void opIndexAssign(ElementType v, size_t n)
+            {
+                //TODO: Fixme! Not sure the call is even legal for StoppingPolicy.longest
+                foreach (i, Range; R)
+                {
+                    ranges[i][n] = v[i];
+                }
+            }
+        }
+
+/**
+   Destructively reads the $(D n)th element in the composite
+   range. Defined if all ranges offer random access.
+*/
+        static if (allSatisfy!(hasMobileElements, R))
+        {
+            ElementType moveAt(size_t n)
+            {
+                //TODO: Fixme! This may create an out of bounds access
+                //for StoppingPolicy.longest
+
+                //ElementType(.moveAt(ranges[0], n), .moveAt(ranges[1], n), ..., )
+                return mixin (q{ElementType(%(.moveAt(ranges[%s], n)%|, %))}.format(iota(0, R.length)));
+            }
+        }
+    }
+}
+
+/// Ditto
+auto zip(Ranges...)(Ranges ranges)
+    if (Ranges.length && allSatisfy!(isInputRange, Ranges))
+{
+    return Zip!Ranges(ranges);
+}
+
+///
+pure unittest
+{
+    import std.algorithm : equal, map;
+
+    // pairwise sum
+    auto arr = [0, 1, 2];
+    assert(zip(arr, arr.dropOne).map!"a[0] + a[1]".equal([1, 3]));
+}
+
+///
+pure unittest
+{
+    import std.conv: to;
+
+    int[] a = [ 1, 2, 3 ];
+    string[] b = [ "a", "b", "c" ];
+    string[] result;
+
+    foreach (tup; zip(a, b))
+    {
+        result ~= tup[0].to!string ~ tup[1];
+    }
+
+    assert(result == [ "1a", "2b", "3c" ]);
+
+    size_t idx = 0;
+    // unpacking tuple elements with foreach
+    foreach (e1, e2; zip(a, b))
+    {
+        assert(e1 == a[idx]);
+        assert(e2 == b[idx]);
+        ++idx;
+    }
+}
+
+/// $(D zip) is powerful - the following code sorts two arrays in parallel:
+pure unittest
+{
+    import std.algorithm : sort;
+
+    int[] a = [ 1, 2, 3 ];
+    string[] b = [ "a", "c", "b" ];
+    zip(a, b).sort!((t1, t2) => t1[0] > t2[0]);
+
+    assert(a == [ 3, 2, 1 ]);
+    // b is sorted according to a's sorting
+    assert(b == [ "b", "c", "a" ]);
+}
+
+/// Ditto
+auto zip(Ranges...)(StoppingPolicy sp, Ranges ranges)
+    if (Ranges.length && allSatisfy!(isInputRange, Ranges))
+{
+    return Zip!Ranges(ranges, sp);
+}
+
+/**
+   Dictates how iteration in a $(D Zip) should stop. By default stop at
+   the end of the shortest of all ranges.
+*/
+enum StoppingPolicy
+{
+    /// Stop when the shortest range is exhausted
+    shortest,
+    /// Stop when the longest range is exhausted
+    longest,
+    /// Require that all ranges are equal
+    requireSameLength,
+}
+
+unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : swap, sort, filter, equal, map;
+
+    import std.exception : assertThrown, assertNotThrown;
+    import std.typecons : tuple;
+
+    int[] a = [ 1, 2, 3 ];
+    float[] b = [ 1.0, 2.0, 3.0 ];
+    foreach (e; zip(a, b))
+    {
+        assert(e[0] == e[1]);
+    }
+
+    swap(a[0], a[1]);
+    auto z = zip(a, b);
+    //swap(z.front(), z.back());
+    sort!("a[0] < b[0]")(zip(a, b));
+    assert(a == [1, 2, 3]);
+    assert(b == [2.0, 1.0, 3.0]);
+
+    z = zip(StoppingPolicy.requireSameLength, a, b);
+    assertNotThrown(z.popBack());
+    assertNotThrown(z.popBack());
+    assertNotThrown(z.popBack());
+    assert(z.empty);
+    assertThrown(z.popBack());
+
+    a = [ 1, 2, 3 ];
+    b = [ 1.0, 2.0, 3.0 ];
+    sort!("a[0] > b[0]")(zip(StoppingPolicy.requireSameLength, a, b));
+    assert(a == [3, 2, 1]);
+    assert(b == [3.0, 2.0, 1.0]);
+
+    a = [];
+    b = [];
+    assert(zip(StoppingPolicy.requireSameLength, a, b).empty);
+
+    // Test infiniteness propagation.
+    static assert(isInfinite!(typeof(zip(repeat(1), repeat(1)))));
+
+    // Test stopping policies with both value and reference.
+    auto a1 = [1, 2];
+    auto a2 = [1, 2, 3];
+    auto stuff = tuple(tuple(a1, a2),
+            tuple(filter!"a"(a1), filter!"a"(a2)));
+
+    alias FOO = Zip!(immutable(int)[], immutable(float)[]);
+
+    foreach(t; stuff.expand) {
+        auto arr1 = t[0];
+        auto arr2 = t[1];
+        auto zShortest = zip(arr1, arr2);
+        assert(equal(map!"a[0]"(zShortest), [1, 2]));
+        assert(equal(map!"a[1]"(zShortest), [1, 2]));
+
+        try {
+            auto zSame = zip(StoppingPolicy.requireSameLength, arr1, arr2);
+            foreach(elem; zSame) {}
+            assert(0);
+        } catch (Throwable) { /* It's supposed to throw.*/ }
+
+        auto zLongest = zip(StoppingPolicy.longest, arr1, arr2);
+        assert(!zLongest.ranges[0].empty);
+        assert(!zLongest.ranges[1].empty);
+
+        zLongest.popFront();
+        zLongest.popFront();
+        assert(!zLongest.empty);
+        assert(zLongest.ranges[0].empty);
+        assert(!zLongest.ranges[1].empty);
+
+        zLongest.popFront();
+        assert(zLongest.empty);
+    }
+
+    // BUG 8900
+    assert(zip([1, 2], repeat('a')).array == [tuple(1, 'a'), tuple(2, 'a')]);
+    assert(zip(repeat('a'), [1, 2]).array == [tuple('a', 1), tuple('a', 2)]);
+
+    // Doesn't work yet.  Issues w/ emplace.
+    // static assert(is(Zip!(immutable int[], immutable float[])));
+
+
+    // These unittests pass, but make the compiler consume an absurd amount
+    // of RAM and time.  Therefore, they should only be run if explicitly
+    // uncommented when making changes to Zip.  Also, running them using
+    // make -fwin32.mak unittest makes the compiler completely run out of RAM.
+    // You need to test just this module.
+    /+
+     foreach(DummyType1; AllDummyRanges) {
+         DummyType1 d1;
+         foreach(DummyType2; AllDummyRanges) {
+             DummyType2 d2;
+             auto r = zip(d1, d2);
+             assert(equal(map!"a[0]"(r), [1,2,3,4,5,6,7,8,9,10]));
+             assert(equal(map!"a[1]"(r), [1,2,3,4,5,6,7,8,9,10]));
+
+             static if (isForwardRange!DummyType1 && isForwardRange!DummyType2) {
+                 static assert(isForwardRange!(typeof(r)));
+             }
+
+             static if (isBidirectionalRange!DummyType1 &&
+                     isBidirectionalRange!DummyType2) {
+                 static assert(isBidirectionalRange!(typeof(r)));
+             }
+             static if (isRandomAccessRange!DummyType1 &&
+                     isRandomAccessRange!DummyType2) {
+                 static assert(isRandomAccessRange!(typeof(r)));
+             }
+         }
+     }
+    +/
+}
+
+pure unittest
+{
+    import std.algorithm : sort;
+
+    auto a = [5,4,3,2,1];
+    auto b = [3,1,2,5,6];
+    auto z = zip(a, b);
+
+    sort!"a[0] < b[0]"(z);
+
+    assert(a == [1, 2, 3, 4, 5]);
+    assert(b == [6, 5, 2, 1, 3]);
+}
+
+@safe pure unittest
+{
+    import std.typecons : tuple;
+    import std.algorithm : equal;
+
+    auto LL = iota(1L, 1000L);
+    auto z = zip(LL, [4]);
+
+    assert(equal(z, [tuple(1L,4)]));
+
+    auto LL2 = iota(0L, 500L);
+    auto z2 = zip([7], LL2);
+    assert(equal(z2, [tuple(7, 0L)]));
+}
+
+// Text for Issue 11196
+@safe pure unittest
+{
+    import std.exception : assertThrown;
+
+    static struct S { @disable this(); }
+    assert(zip((S[5]).init[]).length == 5);
+    assert(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).length == 1);
+    assertThrown(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).front);
+}
+
+@safe pure unittest //12007
+{
+    static struct R
+    {
+        enum empty = false;
+        void popFront(){}
+        int front(){return 1;} @property
+        R save(){return this;} @property
+        void opAssign(R) @disable;
+    }
+    R r;
+    auto z = zip(r, r);
+    auto zz = z.save;
+}
+
+/*
+    Generate lockstep's opApply function as a mixin string.
+    If withIndex is true prepend a size_t index to the delegate.
+*/
+private string lockstepMixin(Ranges...)(bool withIndex)
+{
+    import std.format : format;
+
+    string[] params;
+    string[] emptyChecks;
+    string[] dgArgs;
+    string[] popFronts;
+
+    if (withIndex)
+    {
+        params ~= "size_t";
+        dgArgs ~= "index";
+    }
+
+    foreach (idx, Range; Ranges)
+    {
+        params ~= format("%sElementType!(Ranges[%s])", hasLvalueElements!Range ? "ref " : "", idx);
+        emptyChecks ~= format("!ranges[%s].empty", idx);
+        dgArgs ~= format("ranges[%s].front", idx);
+        popFronts ~= format("ranges[%s].popFront();", idx);
+    }
+
+    return format(
+    q{
+        int opApply(scope int delegate(%s) dg)
+        {
+            import std.exception : enforce;
+
+            auto ranges = _ranges;
+            int res;
+            %s
+
+            while (%s)
+            {
+                res = dg(%s);
+                if (res) break;
+                %s
+                %s
+            }
+
+            if (_stoppingPolicy == StoppingPolicy.requireSameLength)
+            {
+                foreach(range; ranges)
+                    enforce(range.empty);
+            }
+            return res;
+        }
+    }, params.join(", "), withIndex ? "size_t index = 0;" : "",
+       emptyChecks.join(" && "), dgArgs.join(", "),
+       popFronts.join("\n                "),
+       withIndex ? "index++;" : "");
+}
+
+/**
+   Iterate multiple ranges in lockstep using a $(D foreach) loop. In contrast to
+   $(LREF zip) it allows reference access to its elements. If only a single
+   range is passed in, the $(D Lockstep) aliases itself away.  If the
+   ranges are of different lengths and $(D s) == $(D StoppingPolicy.shortest)
+   stop after the shortest range is empty.  If the ranges are of different
+   lengths and $(D s) == $(D StoppingPolicy.requireSameLength), throw an
+   exception.  $(D s) may not be $(D StoppingPolicy.longest), and passing this
+   will throw an exception.
+
+   By default $(D StoppingPolicy) is set to $(D StoppingPolicy.shortest).
+
+   See_Also: $(LREF zip)
+
+       `lockstep` is similar to $(LREF zip), but `zip` bundles its
+       elements and returns a range.
+       `lockstep` also supports reference access.
+       Use `zip` if you want to pass the result to a range function.
+*/
+struct Lockstep(Ranges...)
+    if (Ranges.length > 1 && allSatisfy!(isInputRange, Ranges))
+{
+    this(R ranges, StoppingPolicy sp = StoppingPolicy.shortest)
+    {
+        import std.exception : enforce;
+
+        _ranges = ranges;
+        enforce(sp != StoppingPolicy.longest,
+                "Can't use StoppingPolicy.Longest on Lockstep.");
+        _stoppingPolicy = sp;
+    }
+
+    mixin(lockstepMixin!Ranges(false));
+    mixin(lockstepMixin!Ranges(true));
+
+private:
+    alias R = Ranges;
+    R _ranges;
+    StoppingPolicy _stoppingPolicy;
+}
+
+// For generic programming, make sure Lockstep!(Range) is well defined for a
+// single range.
+template Lockstep(Range)
+{
+    alias Lockstep = Range;
+}
+
+/// Ditto
+Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges)
+    if (allSatisfy!(isInputRange, Ranges))
+{
+    return Lockstep!(Ranges)(ranges);
+}
+/// Ditto
+Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges, StoppingPolicy s)
+    if (allSatisfy!(isInputRange, Ranges))
+{
+    static if (Ranges.length > 1)
+        return Lockstep!Ranges(ranges, s);
+    else
+        return ranges[0];
+}
+
+///
+unittest
+{
+   auto arr1 = [1,2,3,4,5,100];
+   auto arr2 = [6,7,8,9,10];
+
+   foreach (ref a, b; lockstep(arr1, arr2))
+   {
+       a += b;
+   }
+
+   assert(arr1 == [7,9,11,13,15,100]);
+
+   /// Lockstep also supports iterating with an index variable:
+   foreach (index, a, b; lockstep(arr1, arr2))
+   {
+       assert(arr1[index] == a);
+       assert(arr2[index] == b);
+   }
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : filter;
+
+    // The filters are to make these the lowest common forward denominator ranges,
+    // i.e. w/o ref return, random access, length, etc.
+    auto foo = filter!"a"([1,2,3,4,5]);
+    immutable bar = [6f,7f,8f,9f,10f].idup;
+    auto l = lockstep(foo, bar);
+
+    // Should work twice.  These are forward ranges with implicit save.
+    foreach(i; 0..2)
+    {
+        uint[] res1;
+        float[] res2;
+
+        foreach(a, ref b; l) {
+            res1 ~= a;
+            res2 ~= b;
+        }
+
+        assert(res1 == [1,2,3,4,5]);
+        assert(res2 == [6,7,8,9,10]);
+        assert(bar == [6f,7f,8f,9f,10f]);
+    }
+
+    // Doc example.
+    auto arr1 = [1,2,3,4,5];
+    auto arr2 = [6,7,8,9,10];
+
+    foreach(ref a, ref b; lockstep(arr1, arr2))
+    {
+        a += b;
+    }
+
+    assert(arr1 == [7,9,11,13,15]);
+
+    // Make sure StoppingPolicy.requireSameLength doesn't throw.
+    auto ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength);
+
+    foreach(a, b; ls) {}
+
+    // Make sure StoppingPolicy.requireSameLength throws.
+    arr2.popBack();
+    ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength);
+
+    try {
+        foreach(a, b; ls) {}
+        assert(0);
+    } catch (Exception) {}
+
+    // Just make sure 1-range case instantiates.  This hangs the compiler
+    // when no explicit stopping policy is specified due to Bug 4652.
+    auto stuff = lockstep([1,2,3,4,5], StoppingPolicy.shortest);
+
+    // Test with indexing.
+    uint[] res1;
+    float[] res2;
+    size_t[] indices;
+    foreach(i, a, b; lockstep(foo, bar))
+    {
+        indices ~= i;
+        res1 ~= a;
+        res2 ~= b;
+    }
+
+    assert(indices == to!(size_t[])([0, 1, 2, 3, 4]));
+    assert(res1 == [1,2,3,4,5]);
+    assert(res2 == [6f,7f,8f,9f,10f]);
+
+    // Make sure we've worked around the relevant compiler bugs and this at least
+    // compiles w/ >2 ranges.
+    lockstep(foo, foo, foo);
+
+    // Make sure it works with const.
+    const(int[])[] foo2 = [[1, 2, 3]];
+    const(int[])[] bar2 = [[4, 5, 6]];
+    auto c = chain(foo2, bar2);
+
+    foreach(f, b; lockstep(c, c)) {}
+
+    // Regression 10468
+    foreach (x, y; lockstep(iota(0, 10), iota(0, 10))) { }
+}
+
+unittest
+{
+    struct RvalueRange
+    {
+        int[] impl;
+        @property bool empty() { return impl.empty; }
+        @property int front() { return impl[0]; } // N.B. non-ref
+        void popFront() { impl.popFront(); }
+    }
+    auto data1 = [ 1, 2, 3, 4 ];
+    auto data2 = [ 5, 6, 7, 8 ];
+    auto r1 = RvalueRange(data1);
+    auto r2 = data2;
+    foreach (a, ref b; lockstep(r1, r2))
+    {
+        a++;
+        b++;
+    }
+    assert(data1 == [ 1, 2, 3, 4 ]); // changes to a do not propagate to data
+    assert(data2 == [ 6, 7, 8, 9 ]); // but changes to b do.
+
+    // Since r1 is by-value only, the compiler should reject attempts to
+    // foreach over it with ref.
+    static assert(!__traits(compiles, {
+        foreach (ref a, ref b; lockstep(r1, r2)) { a++; }
+    }));
+}
+
+/**
+Creates a mathematical sequence given the initial values and a
+recurrence function that computes the next value from the existing
+values. The sequence comes in the form of an infinite forward
+range. The type $(D Recurrence) itself is seldom used directly; most
+often, recurrences are obtained by calling the function $(D
+recurrence).
+
+When calling $(D recurrence), the function that computes the next
+value is specified as a template argument, and the initial values in
+the recurrence are passed as regular arguments. For example, in a
+Fibonacci sequence, there are two initial values (and therefore a
+state size of 2) because computing the next Fibonacci value needs the
+past two values.
+
+The signature of this function should be:
+----
+auto fun(R)(R state, size_t n)
+----
+where $(D n) will be the index of the current value, and $(D state) will be an
+opaque state vector that can be indexed with array-indexing notation
+$(D state[i]), where valid values of $(D i) range from $(D (n - 1)) to
+$(D (n - State.length)).
+
+If the function is passed in string form, the state has name $(D "a")
+and the zero-based index in the recurrence has name $(D "n"). The
+given string must return the desired value for $(D a[n]) given $(D a[n
+- 1]), $(D a[n - 2]), $(D a[n - 3]),..., $(D a[n - stateSize]). The
+state size is dictated by the number of arguments passed to the call
+to $(D recurrence). The $(D Recurrence) struct itself takes care of
+managing the recurrence's state and shifting it appropriately.
+ */
+struct Recurrence(alias fun, StateType, size_t stateSize)
+{
+    private import std.functional : binaryFun;
+
+    StateType[stateSize] _state;
+    size_t _n;
+
+    this(StateType[stateSize] initial) { _state = initial; }
+
+    void popFront()
+    {
+        static auto trustedCycle(ref typeof(_state) s) @trusted
+        {
+            return cycle(s);
+        }
+        // The cast here is reasonable because fun may cause integer
+        // promotion, but needs to return a StateType to make its operation
+        // closed.  Therefore, we have no other choice.
+        _state[_n % stateSize] = cast(StateType) binaryFun!(fun, "a", "n")(
+            trustedCycle(_state), _n + stateSize);
+        ++_n;
+    }
+
+    @property StateType front()
+    {
+        return _state[_n % stateSize];
+    }
+
+    @property typeof(this) save()
+    {
+        return this;
+    }
+
+    enum bool empty = false;
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    // The Fibonacci numbers, using function in string form:
+    // a[0] = 1, a[1] = 1, and compute a[n+1] = a[n-1] + a[n]
+    auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1);
+    assert(fib.take(10).equal([1, 1, 2, 3, 5, 8, 13, 21, 34, 55]));
+
+    // The factorials, using function in lambda form:
+    auto fac = recurrence!((a,n) => a[n-1] * n)(1);
+    assert(take(fac, 10).equal([
+        1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880
+    ]));
+
+    // The triangular numbers, using function in explicit form:
+    static size_t genTriangular(R)(R state, size_t n)
+    {
+        return state[n-1] + n;
+    }
+    auto tri = recurrence!genTriangular(0);
+    assert(take(tri, 10).equal([0, 1, 3, 6, 10, 15, 21, 28, 36, 45]));
+}
+
+/// Ditto
+Recurrence!(fun, CommonType!(State), State.length)
+recurrence(alias fun, State...)(State initial)
+{
+    CommonType!(State)[State.length] state;
+    foreach (i, Unused; State)
+    {
+        state[i] = initial[i];
+    }
+    return typeof(return)(state);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1);
+    static assert(isForwardRange!(typeof(fib)));
+
+    int[] witness = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ];
+    assert(equal(take(fib, 10), witness));
+    foreach (e; take(fib, 10)) {}
+    auto fact = recurrence!("n * a[n-1]")(1);
+    assert( equal(take(fact, 10), [1, 1, 2, 2*3, 2*3*4, 2*3*4*5, 2*3*4*5*6,
+                            2*3*4*5*6*7, 2*3*4*5*6*7*8, 2*3*4*5*6*7*8*9][]) );
+    auto piapprox = recurrence!("a[n] + (n & 1 ? 4.0 : -4.0) / (2 * n + 3)")(4.0);
+    foreach (e; take(piapprox, 20)) {}
+    // Thanks to yebblies for this test and the associated fix
+    auto r = recurrence!"a[n-2]"(1, 2);
+    witness = [1, 2, 1, 2, 1];
+    assert(equal(take(r, 5), witness));
+}
+
+/**
+   $(D Sequence) is similar to $(D Recurrence) except that iteration is
+   presented in the so-called $(WEB en.wikipedia.org/wiki/Closed_form,
+   closed form). This means that the $(D n)th element in the series is
+   computable directly from the initial values and $(D n) itself. This
+   implies that the interface offered by $(D Sequence) is a random-access
+   range, as opposed to the regular $(D Recurrence), which only offers
+   forward iteration.
+
+   The state of the sequence is stored as a $(D Tuple) so it can be
+   heterogeneous.
+*/
+struct Sequence(alias fun, State)
+{
+private:
+    import std.functional : binaryFun;
+
+    alias compute = binaryFun!(fun, "a", "n");
+    alias ElementType = typeof(compute(State.init, cast(size_t) 1));
+    State _state;
+    size_t _n;
+
+    static struct DollarToken{}
+
+public:
+    this(State initial, size_t n = 0)
+    {
+        _state = initial;
+        _n = n;
+    }
+
+    @property ElementType front()
+    {
+        return compute(_state, _n);
+    }
+
+    void popFront()
+    {
+        ++_n;
+    }
+
+    enum opDollar = DollarToken();
+
+    auto opSlice(size_t lower, size_t upper)
+    in
+    {
+        assert(upper >= lower);
+    }
+    body
+    {
+        return typeof(this)(_state, _n + lower).take(upper - lower);
+    }
+
+    auto opSlice(size_t lower, DollarToken)
+    {
+        return typeof(this)(_state, _n + lower);
+    }
+
+    ElementType opIndex(size_t n)
+    {
+        return compute(_state, n + _n);
+    }
+
+    enum bool empty = false;
+
+    @property Sequence save() { return this; }
+}
+
+/// Ditto
+auto sequence(alias fun, State...)(State args)
+{
+    import std.typecons : Tuple, tuple;
+    alias Return = Sequence!(fun, Tuple!State);
+    return Return(tuple(args));
+}
+
+/// Odd numbers, using function in string form:
+@safe unittest
+{
+    auto odds = sequence!("a[0] + n * a[1]")(1, 2);
+    assert(odds.front == 1);
+    odds.popFront();
+    assert(odds.front == 3);
+    odds.popFront();
+    assert(odds.front == 5);
+}
+
+/// Triangular numbers, using function in lambda form:
+@safe unittest
+{
+    auto tri = sequence!((a,n) => n*(n+1)/2)();
+
+    // Note random access
+    assert(tri[0] == 0);
+    assert(tri[3] == 6);
+    assert(tri[1] == 1);
+    assert(tri[4] == 10);
+    assert(tri[2] == 3);
+}
+
+/// Fibonacci numbers, using function in explicit form:
+@safe unittest
+{
+    import std.math : pow, round, sqrt;
+    static ulong computeFib(S)(S state, size_t n)
+    {
+        // Binet's formula
+        return cast(ulong)(round((pow(state[0], n+1) - pow(state[1], n+1)) /
+                                 state[2]));
+    }
+    auto fib = sequence!computeFib(
+        (1.0 + sqrt(5.0)) / 2.0,    // Golden Ratio
+        (1.0 - sqrt(5.0)) / 2.0,    // Conjugate of Golden Ratio
+        sqrt(5.0));
+
+    // Note random access with [] operator
+    assert(fib[1] == 1);
+    assert(fib[4] == 5);
+    assert(fib[3] == 3);
+    assert(fib[2] == 2);
+    assert(fib[9] == 55);
+}
+
+@safe unittest
+{
+    import std.typecons : Tuple, tuple;
+    auto y = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(tuple(0, 4));
+    static assert(isForwardRange!(typeof(y)));
+
+    //@@BUG
+    //auto y = sequence!("a[0] + n * a[1]")(0, 4);
+    //foreach (e; take(y, 15))
+    {}                                 //writeln(e);
+
+    auto odds = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(
+        tuple(1, 2));
+    for(int currentOdd = 1; currentOdd <= 21; currentOdd += 2) {
+        assert(odds.front == odds[0]);
+        assert(odds[0] == currentOdd);
+        odds.popFront();
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto odds = sequence!("a[0] + n * a[1]")(1, 2);
+    static assert(hasSlicing!(typeof(odds)));
+
+    //Note: don't use drop or take as the target of an equal,
+    //since they'll both just forward to opSlice, making the tests irrelevant
+
+    // static slicing tests
+    assert(equal(odds[0 .. 5], [1,  3,  5,  7,  9]));
+    assert(equal(odds[3 .. 7], [7,  9, 11, 13]));
+
+    // relative slicing test, testing slicing is NOT agnostic of state
+    auto odds_less5 = odds.drop(5); //this should actually call odds[5 .. $]
+    assert(equal(odds_less5[0 ..  3], [11, 13, 15]));
+    assert(equal(odds_less5[0 .. 10], odds[5 .. 15]));
+
+    //Infinite slicing tests
+    odds = odds[10 .. $];
+    assert(equal(odds.take(3), [21, 23, 25]));
+}
+
+// Issue 5036
+unittest
+{
+    auto s = sequence!((a, n) => new int)(0);
+    assert(s.front != s.front);  // no caching
+}
+
+/**
+   Construct a range of values that span the given starting and stopping
+   values.
+
+   Params:
+   begin = The starting value.
+   end = The value that serves as the stopping criterion. This value is not
+        included in the range.
+   step = The value to add to the current value at each iteration.
+
+   Returns:
+   A range that goes through the numbers $(D begin), $(D begin + step),
+   $(D begin + 2 * step), $(D ...), up to and excluding $(D end).
+
+   The two-argument overloads have $(D step = 1). If $(D begin < end && step <
+   0) or $(D begin > end && step > 0) or $(D begin == end), then an empty range
+   is returned. If $(D step == 0) then $(D begin == end) is an error.
+
+   For built-in types, the range returned is a random access range. For
+   user-defined types that support $(D ++), the range is an input
+   range.
+
+    Example:
+    ---
+    void main()
+    {
+        import std.stdio;
+
+        // The following groups all produce the same output of:
+        // 0 1 2 3 4
+
+        foreach (i; 0..5)
+            writef("%s ", i);
+        writeln();
+
+        import std.range : iota;
+        foreach (i; iota(0, 5))
+            writef("%s ", i);
+        writeln();
+
+        writefln("%(%s %|%)", iota(0, 5));
+
+        import std.algorithm : map, copy;
+        import std.format;
+        iota(0, 5).map!(i => format("%s ", i)).copy(stdout.lockingTextWriter());
+        writeln();
+    }
+    ---
+*/
+auto iota(B, E, S)(B begin, E end, S step)
+if ((isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E)))
+        && isIntegral!S)
+in
+{
+    assert(!(step == 0 && begin != end));
+}
+body
+{
+    import std.conv : unsigned;
+
+    alias Value = CommonType!(Unqual!B, Unqual!E);
+    alias StepType = Unqual!S;
+    alias IndexType = typeof(unsigned((end - begin) / step));
+
+    static struct Result
+    {
+        private Value current, pastLast;
+        private StepType step;
+
+        this(Value current, Value pastLast, StepType step)
+        {
+            if ((current < pastLast && step >= 0) ||
+                    (current > pastLast && step <= 0))
+            {
+                this.step = step;
+                this.current = current;
+                if (step > 0)
+                {
+                    this.pastLast = pastLast - 1;
+                    this.pastLast -= (this.pastLast - current) % step;
+                }
+                else
+                {
+                    this.pastLast = pastLast + 1;
+                    this.pastLast += (current - this.pastLast) % -step;
+                }
+                this.pastLast += step;
+            }
+            else
+            {
+                // Initialize an empty range
+                this.current = this.pastLast = current;
+                this.step = 1;
+            }
+        }
+
+        @property bool empty() const { return current == pastLast; }
+        @property inout(Value) front() inout { assert(!empty); return current; }
+        void popFront() { assert(!empty); current += step; }
+
+        @property inout(Value) back() inout { assert(!empty); return pastLast - step; }
+        void popBack() { assert(!empty); pastLast -= step; }
+
+        @property auto save() { return this; }
+
+        inout(Value) opIndex(ulong n) inout
+        {
+            assert(n < this.length);
+
+            // Just cast to Value here because doing so gives overflow behavior
+            // consistent with calling popFront() n times.
+            return cast(inout Value) (current + step * n);
+        }
+        inout(Result) opSlice() inout { return this; }
+        inout(Result) opSlice(ulong lower, ulong upper) inout
+        {
+            assert(upper >= lower && upper <= this.length);
+
+            return cast(inout Result)Result(cast(Value)(current + lower * step),
+                                            cast(Value)(pastLast - (length - upper) * step),
+                                            step);
+        }
+        @property IndexType length() const
+        {
+            if (step > 0)
+            {
+                return unsigned((pastLast - current) / step);
+            }
+            else
+            {
+                return unsigned((current - pastLast) / -step);
+            }
+        }
+
+        alias opDollar = length;
+    }
+
+    return Result(begin, end, step);
+}
+
+/// Ditto
+auto iota(B, E)(B begin, E end)
+    if (isFloatingPoint!(CommonType!(B, E)))
+{
+    return iota(begin, end, CommonType!(B, E)(1));
+}
+
+/// Ditto
+auto iota(B, E)(B begin, E end)
+if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E)))
+{
+    import std.conv : unsigned;
+
+    alias Value = CommonType!(Unqual!B, Unqual!E);
+    alias IndexType = typeof(unsigned(end - begin));
+
+    static struct Result
+    {
+        private Value current, pastLast;
+
+        this(Value current, Value pastLast)
+        {
+            if (current < pastLast)
+            {
+                this.current = current;
+                this.pastLast = pastLast;
+            }
+            else
+            {
+                // Initialize an empty range
+                this.current = this.pastLast = current;
+            }
+        }
+
+        @property bool empty() const { return current == pastLast; }
+        @property inout(Value) front() inout { assert(!empty); return current; }
+        void popFront() { assert(!empty); ++current; }
+
+        @property inout(Value) back() inout { assert(!empty); return cast(inout(Value))(pastLast - 1); }
+        void popBack() { assert(!empty); --pastLast; }
+
+        @property auto save() { return this; }
+
+        inout(Value) opIndex(ulong n) inout
+        {
+            assert(n < this.length);
+
+            // Just cast to Value here because doing so gives overflow behavior
+            // consistent with calling popFront() n times.
+            return cast(inout Value) (current + n);
+        }
+        inout(Result) opSlice() inout { return this; }
+        inout(Result) opSlice(ulong lower, ulong upper) inout
+        {
+            assert(upper >= lower && upper <= this.length);
+
+            return cast(inout Result)Result(cast(Value)(current + lower),
+                                            cast(Value)(pastLast - (length - upper)));
+        }
+        @property IndexType length() const
+        {
+            return unsigned(pastLast - current);
+        }
+
+        alias opDollar = length;
+    }
+
+    return Result(begin, end);
+}
+
+/// Ditto
+auto iota(E)(E end)
+{
+    E begin = 0;
+    return iota(begin, end);
+}
+
+/// Ditto
+// Specialization for floating-point types
+auto iota(B, E, S)(B begin, E end, S step)
+if (isFloatingPoint!(CommonType!(B, E, S)))
+in
+{
+    assert(step != 0, "iota: step must not be 0");
+    assert((end - begin) / step >= 0, "iota: incorrect startup parameters");
+}
+body
+{
+    alias Value = Unqual!(CommonType!(B, E, S));
+    static struct Result
+    {
+        private Value start, step;
+        private size_t index, count;
+
+        this(Value start, Value end, Value step)
+        {
+            import std.conv : to;
+
+            this.start = start;
+            this.step = step;
+            immutable fcount = (end - start) / step;
+            count = to!size_t(fcount);
+            auto pastEnd = start + count * step;
+            if (step > 0)
+            {
+                if (pastEnd < end) ++count;
+                assert(start + count * step >= end);
+            }
+            else
+            {
+                if (pastEnd > end) ++count;
+                assert(start + count * step <= end);
+            }
+        }
+
+        @property bool empty() const { return index == count; }
+        @property Value front() const { assert(!empty); return start + step * index; }
+        void popFront()
+        {
+            assert(!empty);
+            ++index;
+        }
+        @property Value back() const
+        {
+            assert(!empty);
+            return start + step * (count - 1);
+        }
+        void popBack()
+        {
+            assert(!empty);
+            --count;
+        }
+
+        @property auto save() { return this; }
+
+        Value opIndex(size_t n) const
+        {
+            assert(n < count);
+            return start + step * (n + index);
+        }
+        inout(Result) opSlice() inout
+        {
+            return this;
+        }
+        inout(Result) opSlice(size_t lower, size_t upper) inout
+        {
+            assert(upper >= lower && upper <= count);
+
+            Result ret = this;
+            ret.index += lower;
+            ret.count = upper - lower + ret.index;
+            return cast(inout Result)ret;
+        }
+        @property size_t length() const
+        {
+            return count - index;
+        }
+
+        alias opDollar = length;
+    }
+
+    return Result(begin, end, step);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.math : approxEqual;
+
+    auto r = iota(0, 10, 1);
+    assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][]));
+    r = iota(0, 11, 3);
+    assert(equal(r, [0, 3, 6, 9][]));
+    assert(r[2] == 6);
+    auto rf = iota(0.0, 0.5, 0.1);
+    assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4]));
+}
+
+nothrow @nogc unittest
+{
+    auto t0 = iota(0, 10);
+    auto t1 = iota(0, 10, 2);
+    auto t2 = iota(1, 1, 0);
+    //float overloads use std.conv.to so can't be @nogc or nothrow
+}
+
+debug unittest
+{//check the contracts
+    import std.exception : assertThrown;
+    import core.exception : AssertError;
+    assertThrown!AssertError(iota(1,2,0));
+    assertThrown!AssertError(iota(0f,1f,0f));
+    assertThrown!AssertError(iota(1f,0f,0.1f));
+    assertThrown!AssertError(iota(0f,1f,-0.1f));
+}
+
+unittest
+{
+    int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+    auto r1 = iota(a.ptr, a.ptr + a.length, 1);
+    assert(r1.front == a.ptr);
+    assert(r1.back == a.ptr + a.length - 1);
+}
+
+@safe unittest
+{
+    import std.math : approxEqual, nextUp, nextDown;
+    import std.algorithm : count, equal;
+
+    static assert(is(ElementType!(typeof(iota(0f))) == float));
+
+    static assert(hasLength!(typeof(iota(0, 2))));
+    auto r = iota(0, 10, 1);
+    assert(r[$ - 1] == 9);
+    assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][]));
+
+    auto rSlice = r[2..8];
+    assert(equal(rSlice, [2, 3, 4, 5, 6, 7]));
+
+    rSlice.popFront();
+    assert(rSlice[0] == rSlice.front);
+    assert(rSlice.front == 3);
+
+    rSlice.popBack();
+    assert(rSlice[rSlice.length - 1] == rSlice.back);
+    assert(rSlice.back == 6);
+
+    rSlice = r[0..4];
+    assert(equal(rSlice, [0, 1, 2, 3]));
+
+    auto rr = iota(10);
+    assert(equal(rr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][]));
+
+    r = iota(0, -10, -1);
+    assert(equal(r, [0, -1, -2, -3, -4, -5, -6, -7, -8, -9][]));
+    rSlice = r[3..9];
+    assert(equal(rSlice, [-3, -4, -5, -6, -7, -8]));
+
+    r = iota(0, -6, -3);
+    assert(equal(r, [0, -3][]));
+    rSlice = r[1..2];
+    assert(equal(rSlice, [-3]));
+
+    r = iota(0, -7, -3);
+    assert(equal(r, [0, -3, -6][]));
+    rSlice = r[1..3];
+    assert(equal(rSlice, [-3, -6]));
+
+    r = iota(0, 11, 3);
+    assert(equal(r, [0, 3, 6, 9][]));
+    assert(r[2] == 6);
+    rSlice = r[1..3];
+    assert(equal(rSlice, [3, 6]));
+
+    auto rf = iota(0.0, 0.5, 0.1);
+    assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4][]));
+    assert(rf.length == 5);
+
+    rf.popFront();
+    assert(rf.length == 4);
+
+    auto rfSlice = rf[1..4];
+    assert(rfSlice.length == 3);
+    assert(approxEqual(rfSlice, [0.2, 0.3, 0.4]));
+
+    rfSlice.popFront();
+    assert(approxEqual(rfSlice[0], 0.3));
+
+    rf.popFront();
+    assert(rf.length == 3);
+
+    rfSlice = rf[1..3];
+    assert(rfSlice.length == 2);
+    assert(approxEqual(rfSlice, [0.3, 0.4]));
+    assert(approxEqual(rfSlice[0], 0.3));
+
+    // With something just above 0.5
+    rf = iota(0.0, nextUp(0.5), 0.1);
+    assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4, 0.5][]));
+    rf.popBack();
+    assert(rf[rf.length - 1] == rf.back);
+    assert(approxEqual(rf.back, 0.4));
+    assert(rf.length == 5);
+
+    // going down
+    rf = iota(0.0, -0.5, -0.1);
+    assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4][]));
+    rfSlice = rf[2..5];
+    assert(approxEqual(rfSlice, [-0.2, -0.3, -0.4]));
+
+    rf = iota(0.0, nextDown(-0.5), -0.1);
+    assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4, -0.5][]));
+
+    // iota of longs
+    auto rl = iota(5_000_000L);
+    assert(rl.length == 5_000_000L);
+
+    // iota of longs with steps
+    auto iota_of_longs_with_steps = iota(50L, 101L, 10);
+    assert(iota_of_longs_with_steps.length == 6);
+    assert(equal(iota_of_longs_with_steps, [50L, 60L, 70L, 80L, 90L, 100L]));
+
+    // iota of unsigned zero length (issue 6222, actually trying to consume it
+    // is the only way to find something is wrong because the public
+    // properties are all correct)
+    auto iota_zero_unsigned = iota(0, 0u, 3);
+    assert(count(iota_zero_unsigned) == 0);
+
+    // unsigned reverse iota can be buggy if .length doesn't take them into
+    // account (issue 7982).
+    assert(iota(10u, 0u, -1).length == 10);
+    assert(iota(10u, 0u, -2).length == 5);
+    assert(iota(uint.max, uint.max-10, -1).length == 10);
+    assert(iota(uint.max, uint.max-10, -2).length == 5);
+    assert(iota(uint.max, 0u, -1).length == uint.max);
+
+    // Issue 8920
+    foreach (Type; AliasSeq!(byte, ubyte, short, ushort,
+        int, uint, long, ulong))
+    {
+        Type val;
+        foreach (i; iota(cast(Type)0, cast(Type)10)) { val++; }
+        assert(val == 10);
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : copy;
+    auto idx = new size_t[100];
+    copy(iota(0, idx.length), idx);
+}
+
+@safe unittest
+{
+    foreach(range; AliasSeq!(iota(2, 27, 4),
+                             iota(3, 9),
+                             iota(2.7, 12.3, .1),
+                             iota(3.2, 9.7)))
+    {
+        const cRange = range;
+        const e = cRange.empty;
+        const f = cRange.front;
+        const b = cRange.back;
+        const i = cRange[2];
+        const s1 = cRange[];
+        const s2 = cRange[0 .. 3];
+        const l = cRange.length;
+    }
+}
+
+unittest
+{
+    //The ptr stuff can't be done at compile time, so we unfortunately end
+    //up with some code duplication here.
+    auto arr = [0, 5, 3, 5, 5, 7, 9, 2, 0, 42, 7, 6];
+
+    {
+        const cRange = iota(arr.ptr, arr.ptr + arr.length, 3);
+        const e = cRange.empty;
+        const f = cRange.front;
+        const b = cRange.back;
+        const i = cRange[2];
+        const s1 = cRange[];
+        const s2 = cRange[0 .. 3];
+        const l = cRange.length;
+    }
+
+    {
+        const cRange = iota(arr.ptr, arr.ptr + arr.length);
+        const e = cRange.empty;
+        const f = cRange.front;
+        const b = cRange.back;
+        const i = cRange[2];
+        const s1 = cRange[];
+        const s2 = cRange[0 .. 3];
+        const l = cRange.length;
+    }
+}
+
+/* Generic overload that handles arbitrary types that support arithmetic
+ * operations.
+ */
+/// ditto
+auto iota(B, E)(B begin, E end)
+    if (!isIntegral!(CommonType!(B, E)) &&
+        !isFloatingPoint!(CommonType!(B, E)) &&
+        !isPointer!(CommonType!(B, E)) &&
+        is(typeof((ref B b) { ++b; })) &&
+        (is(typeof(B.init < E.init)) || is(typeof(B.init == E.init))) )
+{
+    static struct Result
+    {
+        B current;
+        E end;
+
+        @property bool empty()
+        {
+            static if (is(typeof(B.init < E.init)))
+                return !(current < end);
+            else static if (is(typeof(B.init != E.init)))
+                return current == end;
+            else
+                static assert(0);
+        }
+        @property auto front() { return current; }
+        void popFront()
+        {
+            assert(!empty);
+            ++current;
+        }
+    }
+    return Result(begin, end);
+}
+
+/**
+User-defined types such as $(XREF bigint, BigInt) are also supported, as long
+as they can be incremented with $(D ++) and compared with $(D <) or $(D ==).
+*/
+// Issue 6447
+unittest
+{
+    import std.algorithm.comparison : equal;
+    import std.bigint;
+
+    auto s = BigInt(1_000_000_000_000);
+    auto e = BigInt(1_000_000_000_003);
+    auto r = iota(s, e);
+    assert(r.equal([
+        BigInt(1_000_000_000_000),
+        BigInt(1_000_000_000_001),
+        BigInt(1_000_000_000_002)
+    ]));
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+
+    // Test iota() for a type that only supports ++ and != but does not have
+    // '<'-ordering.
+    struct Cyclic(int wrapAround)
+    {
+        int current;
+
+        this(int start) { current = start % wrapAround; }
+
+        bool opEquals(Cyclic c) { return current == c.current; }
+        bool opEquals(int i) { return current == i; }
+        void opUnary(string op)() if (op == "++")
+        {
+            current = (current + 1) % wrapAround;
+        }
+    }
+    alias Cycle5 = Cyclic!5;
+
+    // Easy case
+    auto i1 = iota(Cycle5(1), Cycle5(4));
+    assert(i1.equal([1, 2, 3]));
+
+    // Wraparound case
+    auto i2 = iota(Cycle5(3), Cycle5(2));
+    assert(i2.equal([3, 4, 0, 1 ]));
+}
+
+/**
+   Options for the $(LREF FrontTransversal) and $(LREF Transversal) ranges
+   (below).
+*/
+enum TransverseOptions
+{
+/**
+   When transversed, the elements of a range of ranges are assumed to
+   have different lengths (e.g. a jagged array).
+*/
+    assumeJagged,                      //default
+    /**
+       The transversal enforces that the elements of a range of ranges have
+       all the same length (e.g. an array of arrays, all having the same
+       length). Checking is done once upon construction of the transversal
+       range.
+    */
+        enforceNotJagged,
+    /**
+       The transversal assumes, without verifying, that the elements of a
+       range of ranges have all the same length. This option is useful if
+       checking was already done from the outside of the range.
+    */
+        assumeNotJagged,
+        }
+
+/**
+   Given a range of ranges, iterate transversally through the first
+   elements of each of the enclosed ranges.
+*/
+struct FrontTransversal(Ror,
+        TransverseOptions opt = TransverseOptions.assumeJagged)
+{
+    alias RangeOfRanges = Unqual!(Ror);
+    alias RangeType     = .ElementType!RangeOfRanges;
+    alias ElementType   = .ElementType!RangeType;
+
+    private void prime()
+    {
+        static if (opt == TransverseOptions.assumeJagged)
+        {
+            while (!_input.empty && _input.front.empty)
+            {
+                _input.popFront();
+            }
+            static if (isBidirectionalRange!RangeOfRanges)
+            {
+                while (!_input.empty && _input.back.empty)
+                {
+                    _input.popBack();
+                }
+            }
+        }
+    }
+
+/**
+   Construction from an input.
+*/
+    this(RangeOfRanges input)
+    {
+        _input = input;
+        prime();
+        static if (opt == TransverseOptions.enforceNotJagged)
+            // (isRandomAccessRange!RangeOfRanges
+            //     && hasLength!RangeType)
+        {
+            import std.exception : enforce;
+
+            if (empty) return;
+            immutable commonLength = _input.front.length;
+            foreach (e; _input)
+            {
+                enforce(e.length == commonLength);
+            }
+        }
+    }
+
+/**
+   Forward range primitives.
+*/
+    static if (isInfinite!RangeOfRanges)
+    {
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return _input.empty;
+        }
+    }
+
+    /// Ditto
+    @property auto ref front()
+    {
+        assert(!empty);
+        return _input.front.front;
+    }
+
+    /// Ditto
+    static if (hasMobileElements!RangeType)
+    {
+        ElementType moveFront()
+        {
+            return .moveFront(_input.front);
+        }
+    }
+
+    static if (hasAssignableElements!RangeType)
+    {
+        @property auto front(ElementType val)
+        {
+            _input.front.front = val;
+        }
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        assert(!empty);
+        _input.popFront();
+        prime();
+    }
+
+/**
+   Duplicates this $(D frontTransversal). Note that only the encapsulating
+   range of range will be duplicated. Underlying ranges will not be
+   duplicated.
+*/
+    static if (isForwardRange!RangeOfRanges)
+    {
+        @property FrontTransversal save()
+        {
+            return FrontTransversal(_input.save);
+        }
+    }
+
+    static if (isBidirectionalRange!RangeOfRanges)
+    {
+/**
+   Bidirectional primitives. They are offered if $(D
+   isBidirectionalRange!RangeOfRanges).
+*/
+        @property auto ref back()
+        {
+            assert(!empty);
+            return _input.back.front;
+        }
+        /// Ditto
+        void popBack()
+        {
+            assert(!empty);
+            _input.popBack();
+            prime();
+        }
+
+        /// Ditto
+        static if (hasMobileElements!RangeType)
+        {
+            ElementType moveBack()
+            {
+                return .moveFront(_input.back);
+            }
+        }
+
+        static if (hasAssignableElements!RangeType)
+        {
+            @property auto back(ElementType val)
+            {
+                _input.back.front = val;
+            }
+        }
+    }
+
+    static if (isRandomAccessRange!RangeOfRanges &&
+            (opt == TransverseOptions.assumeNotJagged ||
+                    opt == TransverseOptions.enforceNotJagged))
+    {
+/**
+   Random-access primitive. It is offered if $(D
+   isRandomAccessRange!RangeOfRanges && (opt ==
+   TransverseOptions.assumeNotJagged || opt ==
+   TransverseOptions.enforceNotJagged)).
+*/
+        auto ref opIndex(size_t n)
+        {
+            return _input[n].front;
+        }
+
+        /// Ditto
+        static if (hasMobileElements!RangeType)
+        {
+            ElementType moveAt(size_t n)
+            {
+                return .moveFront(_input[n]);
+            }
+        }
+        /// Ditto
+        static if (hasAssignableElements!RangeType)
+        {
+            void opIndexAssign(ElementType val, size_t n)
+            {
+                _input[n].front = val;
+            }
+        }
+
+/**
+   Slicing if offered if $(D RangeOfRanges) supports slicing and all the
+   conditions for supporting indexing are met.
+*/
+        static if (hasSlicing!RangeOfRanges)
+        {
+            typeof(this) opSlice(size_t lower, size_t upper)
+            {
+                return typeof(this)(_input[lower..upper]);
+            }
+        }
+    }
+
+    auto opSlice() { return this; }
+
+private:
+    RangeOfRanges _input;
+}
+
+/// Ditto
+FrontTransversal!(RangeOfRanges, opt) frontTransversal(
+    TransverseOptions opt = TransverseOptions.assumeJagged,
+    RangeOfRanges)
+(RangeOfRanges rr)
+{
+    return typeof(return)(rr);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[][] x = new int[][2];
+    x[0] = [1, 2];
+    x[1] = [3, 4];
+    auto ror = frontTransversal(x);
+    assert(equal(ror, [ 1, 3 ][]));
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+    import std.algorithm : equal;
+
+    static assert(is(FrontTransversal!(immutable int[][])));
+
+    foreach(DummyType; AllDummyRanges) {
+        auto dummies =
+            [DummyType.init, DummyType.init, DummyType.init, DummyType.init];
+
+        foreach(i, ref elem; dummies) {
+            // Just violate the DummyRange abstraction to get what I want.
+            elem.arr = elem.arr[i..$ - (3 - i)];
+        }
+
+        auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(dummies);
+        static if (isForwardRange!DummyType) {
+            static assert(isForwardRange!(typeof(ft)));
+        }
+
+        assert(equal(ft, [1, 2, 3, 4]));
+
+        // Test slicing.
+        assert(equal(ft[0..2], [1, 2]));
+        assert(equal(ft[1..3], [2, 3]));
+
+        assert(ft.front == ft.moveFront());
+        assert(ft.back == ft.moveBack());
+        assert(ft.moveAt(1) == ft[1]);
+
+
+        // Test infiniteness propagation.
+        static assert(isInfinite!(typeof(frontTransversal(repeat("foo")))));
+
+        static if (DummyType.r == ReturnBy.Reference) {
+            {
+                ft.front++;
+                scope(exit) ft.front--;
+                assert(dummies.front.front == 2);
+            }
+
+            {
+                ft.front = 5;
+                scope(exit) ft.front = 1;
+                assert(dummies[0].front == 5);
+            }
+
+            {
+                ft.back = 88;
+                scope(exit) ft.back = 4;
+                assert(dummies.back.front == 88);
+            }
+
+            {
+                ft[1] = 99;
+                scope(exit) ft[1] = 2;
+                assert(dummies[1].front == 99);
+            }
+        }
+    }
+}
+
+/**
+   Given a range of ranges, iterate transversally through the the $(D
+   n)th element of each of the enclosed ranges. All elements of the
+   enclosing range must offer random access.
+*/
+struct Transversal(Ror,
+        TransverseOptions opt = TransverseOptions.assumeJagged)
+{
+    private alias RangeOfRanges = Unqual!Ror;
+    private alias InnerRange = ElementType!RangeOfRanges;
+    private alias E = ElementType!InnerRange;
+
+    private void prime()
+    {
+        static if (opt == TransverseOptions.assumeJagged)
+        {
+            while (!_input.empty && _input.front.length <= _n)
+            {
+                _input.popFront();
+            }
+            static if (isBidirectionalRange!RangeOfRanges)
+            {
+                while (!_input.empty && _input.back.length <= _n)
+                {
+                    _input.popBack();
+                }
+            }
+        }
+    }
+
+/**
+   Construction from an input and an index.
+*/
+    this(RangeOfRanges input, size_t n)
+    {
+        _input = input;
+        _n = n;
+        prime();
+        static if (opt == TransverseOptions.enforceNotJagged)
+        {
+            import std.exception : enforce;
+
+            if (empty) return;
+            immutable commonLength = _input.front.length;
+            foreach (e; _input)
+            {
+                enforce(e.length == commonLength);
+            }
+        }
+    }
+
+/**
+   Forward range primitives.
+*/
+    static if (isInfinite!(RangeOfRanges))
+    {
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return _input.empty;
+        }
+    }
+
+    /// Ditto
+    @property auto ref front()
+    {
+        assert(!empty);
+        return _input.front[_n];
+    }
+
+    /// Ditto
+    static if (hasMobileElements!InnerRange)
+    {
+        E moveFront()
+        {
+            return .moveAt(_input.front, _n);
+        }
+    }
+
+    /// Ditto
+    static if (hasAssignableElements!InnerRange)
+    {
+        @property auto front(E val)
+        {
+            _input.front[_n] = val;
+        }
+    }
+
+
+    /// Ditto
+    void popFront()
+    {
+        assert(!empty);
+        _input.popFront();
+        prime();
+    }
+
+    /// Ditto
+    static if (isForwardRange!RangeOfRanges)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            return ret;
+        }
+    }
+
+    static if (isBidirectionalRange!RangeOfRanges)
+    {
+/**
+   Bidirectional primitives. They are offered if $(D
+   isBidirectionalRange!RangeOfRanges).
+*/
+        @property auto ref back()
+        {
+            return _input.back[_n];
+        }
+
+        /// Ditto
+        void popBack()
+        {
+            assert(!empty);
+            _input.popBack();
+            prime();
+        }
+
+        /// Ditto
+        static if (hasMobileElements!InnerRange)
+        {
+            E moveBack()
+            {
+                return .moveAt(_input.back, _n);
+            }
+        }
+
+        /// Ditto
+        static if (hasAssignableElements!InnerRange)
+        {
+            @property auto back(E val)
+            {
+                _input.back[_n] = val;
+            }
+        }
+
+    }
+
+    static if (isRandomAccessRange!RangeOfRanges &&
+            (opt == TransverseOptions.assumeNotJagged ||
+                    opt == TransverseOptions.enforceNotJagged))
+    {
+/**
+   Random-access primitive. It is offered if $(D
+   isRandomAccessRange!RangeOfRanges && (opt ==
+   TransverseOptions.assumeNotJagged || opt ==
+   TransverseOptions.enforceNotJagged)).
+*/
+        auto ref opIndex(size_t n)
+        {
+            return _input[n][_n];
+        }
+
+        /// Ditto
+        static if (hasMobileElements!InnerRange)
+        {
+            E moveAt(size_t n)
+            {
+                return .moveAt(_input[n], _n);
+            }
+        }
+
+        /// Ditto
+        static if (hasAssignableElements!InnerRange)
+        {
+            void opIndexAssign(E val, size_t n)
+            {
+                _input[n][_n] = val;
+            }
+        }
+
+        /// Ditto
+        static if(hasLength!RangeOfRanges)
+        {
+            @property size_t length()
+            {
+                return _input.length;
+            }
+
+            alias opDollar = length;
+        }
+
+/**
+   Slicing if offered if $(D RangeOfRanges) supports slicing and all the
+   conditions for supporting indexing are met.
+*/
+        static if (hasSlicing!RangeOfRanges)
+        {
+            typeof(this) opSlice(size_t lower, size_t upper)
+            {
+                return typeof(this)(_input[lower..upper], _n);
+            }
+        }
+    }
+
+    auto opSlice() { return this; }
+
+private:
+    RangeOfRanges _input;
+    size_t _n;
+}
+
+/// Ditto
+Transversal!(RangeOfRanges, opt) transversal
+(TransverseOptions opt = TransverseOptions.assumeJagged, RangeOfRanges)
+(RangeOfRanges rr, size_t n)
+{
+    return typeof(return)(rr, n);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[][] x = new int[][2];
+    x[0] = [1, 2];
+    x[1] = [3, 4];
+    auto ror = transversal(x, 1);
+    assert(equal(ror, [ 2, 4 ][]));
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+
+    int[][] x = new int[][2];
+    x[0] = [ 1, 2 ];
+    x[1] = [3, 4];
+    auto ror = transversal!(TransverseOptions.assumeNotJagged)(x, 1);
+    auto witness = [ 2, 4 ];
+    uint i;
+    foreach (e; ror) assert(e == witness[i++]);
+    assert(i == 2);
+    assert(ror.length == 2);
+
+    static assert(is(Transversal!(immutable int[][])));
+
+    // Make sure ref, assign is being propagated.
+    {
+        ror.front++;
+        scope(exit) ror.front--;
+        assert(x[0][1] == 3);
+    }
+    {
+        ror.front = 5;
+        scope(exit) ror.front = 2;
+        assert(x[0][1] == 5);
+        assert(ror.moveFront() == 5);
+    }
+    {
+        ror.back = 999;
+        scope(exit) ror.back = 4;
+        assert(x[1][1] == 999);
+        assert(ror.moveBack() == 999);
+    }
+    {
+        ror[0] = 999;
+        scope(exit) ror[0] = 2;
+        assert(x[0][1] == 999);
+        assert(ror.moveAt(0) == 999);
+    }
+
+    // Test w/o ref return.
+    alias D = DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random);
+    auto drs = [D.init, D.init];
+    foreach(num; 0..10) {
+        auto t = transversal!(TransverseOptions.enforceNotJagged)(drs, num);
+        assert(t[0] == t[1]);
+        assert(t[1] == num + 1);
+    }
+
+    static assert(isInfinite!(typeof(transversal(repeat([1,2,3]), 1))));
+
+    // Test slicing.
+    auto mat = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]];
+    auto mat1 = transversal!(TransverseOptions.assumeNotJagged)(mat, 1)[1..3];
+    assert(mat1[0] == 6);
+    assert(mat1[1] == 10);
+}
+
+struct Transposed(RangeOfRanges)
+    if (isForwardRange!RangeOfRanges &&
+        isInputRange!(ElementType!RangeOfRanges) &&
+        hasAssignableElements!RangeOfRanges)
+{
+    //alias ElementType = typeof(map!"a.front"(RangeOfRanges.init));
+
+    this(RangeOfRanges input)
+    {
+        this._input = input;
+    }
+
+    @property auto front()
+    {
+        import std.algorithm : filter, map;
+        return _input.save
+                     .filter!(a => !a.empty)
+                     .map!(a => a.front);
+    }
+
+    void popFront()
+    {
+        // Advance the position of each subrange.
+        auto r = _input.save;
+        while (!r.empty)
+        {
+            auto e = r.front;
+            if (!e.empty)
+            {
+                e.popFront();
+                r.front = e;
+            }
+
+            r.popFront();
+        }
+    }
+
+    // ElementType opIndex(size_t n)
+    // {
+    //     return _input[n].front;
+    // }
+
+    @property bool empty()
+    {
+        if (_input.empty) return true;
+        foreach (e; _input.save)
+        {
+            if (!e.empty) return false;
+        }
+        return true;
+    }
+
+    @property Transposed save()
+    {
+        return Transposed(_input.save);
+    }
+
+    auto opSlice() { return this; }
+
+private:
+    RangeOfRanges _input;
+}
+
+@safe unittest
+{
+    // Boundary case: transpose of empty range should be empty
+    int[][] ror = [];
+    assert(transposed(ror).empty);
+}
+
+// Issue 9507
+unittest
+{
+    import std.algorithm : equal;
+
+    auto r = [[1,2], [3], [4,5], [], [6]];
+    assert(r.transposed.equal!equal([
+        [1, 3, 4, 6],
+        [2, 5]
+    ]));
+}
+
+/**
+Given a range of ranges, returns a range of ranges where the $(I i)'th subrange
+contains the $(I i)'th elements of the original subranges.
+ */
+Transposed!RangeOfRanges transposed(RangeOfRanges)(RangeOfRanges rr)
+    if (isForwardRange!RangeOfRanges &&
+        isInputRange!(ElementType!RangeOfRanges) &&
+        hasAssignableElements!RangeOfRanges)
+{
+    return Transposed!RangeOfRanges(rr);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[][] ror = [
+        [1, 2, 3],
+        [4, 5, 6]
+    ];
+    auto xp = transposed(ror);
+    assert(equal!"a.equal(b)"(xp, [
+        [1, 4],
+        [2, 5],
+        [3, 6]
+    ]));
+}
+
+///
+@safe unittest
+{
+    int[][] x = new int[][2];
+    x[0] = [1, 2];
+    x[1] = [3, 4];
+    auto tr = transposed(x);
+    int[][] witness = [ [ 1, 3 ], [ 2, 4 ] ];
+    uint i;
+
+    foreach (e; tr)
+    {
+        assert(array(e) == witness[i++]);
+    }
+}
+
+// Issue 8764
+@safe unittest
+{
+    import std.algorithm : equal;
+    ulong[1] t0 = [ 123 ];
+
+    assert(!hasAssignableElements!(typeof(t0[].chunks(1))));
+    assert(!is(typeof(transposed(t0[].chunks(1)))));
+    assert(is(typeof(transposed(t0[].chunks(1).array()))));
+
+    auto t1 = transposed(t0[].chunks(1).array());
+    assert(equal!"a.equal(b)"(t1, [[123]]));
+}
+
+/**
+This struct takes two ranges, $(D source) and $(D indices), and creates a view
+of $(D source) as if its elements were reordered according to $(D indices).
+$(D indices) may include only a subset of the elements of $(D source) and
+may also repeat elements.
+
+$(D Source) must be a random access range.  The returned range will be
+bidirectional or random-access if $(D Indices) is bidirectional or
+random-access, respectively.
+*/
+struct Indexed(Source, Indices)
+    if(isRandomAccessRange!Source && isInputRange!Indices &&
+        is(typeof(Source.init[ElementType!(Indices).init])))
+{
+    this(Source source, Indices indices)
+    {
+        this._source = source;
+        this._indices = indices;
+    }
+
+    /// Range primitives
+    @property auto ref front()
+    {
+        assert(!empty);
+        return _source[_indices.front];
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        assert(!empty);
+        _indices.popFront();
+    }
+
+    static if(isInfinite!Indices)
+    {
+        enum bool empty = false;
+    }
+    else
+    {
+        /// Ditto
+        @property bool empty()
+        {
+            return _indices.empty;
+        }
+    }
+
+    static if(isForwardRange!Indices)
+    {
+        /// Ditto
+        @property typeof(this) save()
+        {
+            // Don't need to save _source because it's never consumed.
+            return typeof(this)(_source, _indices.save);
+        }
+    }
+
+    /// Ditto
+    static if(hasAssignableElements!Source)
+    {
+        @property auto ref front(ElementType!Source newVal)
+        {
+            assert(!empty);
+            return _source[_indices.front] = newVal;
+        }
+    }
+
+
+    static if(hasMobileElements!Source)
+    {
+        /// Ditto
+        auto moveFront()
+        {
+            assert(!empty);
+            return .moveAt(_source, _indices.front);
+        }
+    }
+
+    static if(isBidirectionalRange!Indices)
+    {
+        /// Ditto
+        @property auto ref back()
+        {
+            assert(!empty);
+            return _source[_indices.back];
+        }
+
+        /// Ditto
+        void popBack()
+        {
+           assert(!empty);
+           _indices.popBack();
+        }
+
+        /// Ditto
+        static if(hasAssignableElements!Source)
+        {
+            @property auto ref back(ElementType!Source newVal)
+            {
+                assert(!empty);
+                return _source[_indices.back] = newVal;
+            }
+        }
+
+
+        static if(hasMobileElements!Source)
+        {
+            /// Ditto
+            auto moveBack()
+            {
+                assert(!empty);
+                return .moveAt(_source, _indices.back);
+            }
+        }
+    }
+
+    static if(hasLength!Indices)
+    {
+        /// Ditto
+         @property size_t length()
+        {
+            return _indices.length;
+        }
+
+        alias opDollar = length;
+    }
+
+    static if(isRandomAccessRange!Indices)
+    {
+        /// Ditto
+        auto ref opIndex(size_t index)
+        {
+            return _source[_indices[index]];
+        }
+
+        /// Ditto
+        typeof(this) opSlice(size_t a, size_t b)
+        {
+            return typeof(this)(_source, _indices[a..b]);
+        }
+
+
+        static if(hasAssignableElements!Source)
+        {
+            /// Ditto
+            auto opIndexAssign(ElementType!Source newVal, size_t index)
+            {
+                return _source[_indices[index]] = newVal;
+            }
+        }
+
+
+        static if(hasMobileElements!Source)
+        {
+            /// Ditto
+            auto moveAt(size_t index)
+            {
+                return .moveAt(_source, _indices[index]);
+            }
+        }
+    }
+
+    // All this stuff is useful if someone wants to index an Indexed
+    // without adding a layer of indirection.
+
+    /**
+    Returns the source range.
+    */
+    @property Source source()
+    {
+        return _source;
+    }
+
+    /**
+    Returns the indices range.
+    */
+     @property Indices indices()
+    {
+        return _indices;
+    }
+
+    static if(isRandomAccessRange!Indices)
+    {
+        /**
+        Returns the physical index into the source range corresponding to a
+        given logical index.  This is useful, for example, when indexing
+        an $(D Indexed) without adding another layer of indirection.
+        */
+        size_t physicalIndex(size_t logicalIndex)
+        {
+            return _indices[logicalIndex];
+        }
+
+        ///
+        unittest
+        {
+            auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]);
+            assert(ind.physicalIndex(0) == 1);
+        }
+    }
+
+private:
+    Source _source;
+    Indices _indices;
+
+}
+
+/// Ditto
+Indexed!(Source, Indices) indexed(Source, Indices)(Source source, Indices indices)
+{
+    return typeof(return)(source, indices);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    auto source = [1, 2, 3, 4, 5];
+    auto indices = [4, 3, 1, 2, 0, 4];
+    auto ind = indexed(source, indices);
+    assert(equal(ind, [5, 4, 2, 3, 1, 5]));
+    assert(equal(retro(ind), [5, 1, 3, 2, 4, 5]));
+}
+
+@safe unittest
+{
+    {
+        auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]);
+        assert(ind.physicalIndex(0) == 1);
+    }
+
+    auto source = [1, 2, 3, 4, 5];
+    auto indices = [4, 3, 1, 2, 0, 4];
+    auto ind = indexed(source, indices);
+
+    // When elements of indices are duplicated and Source has lvalue elements,
+    // these are aliased in ind.
+    ind[0]++;
+    assert(ind[0] == 6);
+    assert(ind[5] == 6);
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+
+    foreach(DummyType; AllDummyRanges)
+    {
+        auto d = DummyType.init;
+        auto r = indexed([1, 2, 3, 4, 5], d);
+        static assert(propagatesRangeType!(DummyType, typeof(r)));
+        static assert(propagatesLength!(DummyType, typeof(r)));
+    }
+}
+
+/**
+This range iterates over fixed-sized chunks of size $(D chunkSize) of a
+$(D source) range. $(D Source) must be a forward range. $(D chunkSize) must be
+greater than zero.
+
+If $(D !isInfinite!Source) and $(D source.walkLength) is not evenly
+divisible by $(D chunkSize), the back element of this range will contain
+fewer than $(D chunkSize) elements.
+*/
+struct Chunks(Source)
+    if (isForwardRange!Source)
+{
+    /// Standard constructor
+    this(Source source, size_t chunkSize)
+    {
+        assert(chunkSize != 0, "Cannot create a Chunk with an empty chunkSize");
+        _source = source;
+        _chunkSize = chunkSize;
+    }
+
+    /// Forward range primitives. Always present.
+    @property auto front()
+    {
+        assert(!empty);
+        return _source.save.take(_chunkSize);
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        assert(!empty);
+        _source.popFrontN(_chunkSize);
+    }
+
+    static if (!isInfinite!Source)
+        /// Ditto
+        @property bool empty()
+        {
+            return _source.empty;
+        }
+    else
+        // undocumented
+        enum empty = false;
+
+    /// Ditto
+    @property typeof(this) save()
+    {
+        return typeof(this)(_source.save, _chunkSize);
+    }
+
+    static if (hasLength!Source)
+    {
+        /// Length. Only if $(D hasLength!Source) is $(D true)
+        @property size_t length()
+        {
+            // Note: _source.length + _chunkSize may actually overflow.
+            // We cast to ulong to mitigate the problem on x86 machines.
+            // For x64 machines, we just suppose we'll never overflow.
+            // The "safe" code would require either an extra branch, or a
+            //   modulo operation, which is too expensive for such a rare case
+            return cast(size_t)((cast(ulong)(_source.length) + _chunkSize - 1) / _chunkSize);
+        }
+        //Note: No point in defining opDollar here without slicing.
+        //opDollar is defined below in the hasSlicing!Source section
+    }
+
+    static if (hasSlicing!Source)
+    {
+        //Used for various purposes
+        private enum hasSliceToEnd = is(typeof(Source.init[_chunkSize .. $]) == Source);
+
+        /**
+        Indexing and slicing operations. Provided only if
+        $(D hasSlicing!Source) is $(D true).
+         */
+        auto opIndex(size_t index)
+        {
+            immutable start = index * _chunkSize;
+            immutable end   = start + _chunkSize;
+
+            static if (isInfinite!Source)
+                return _source[start .. end];
+            else
+            {
+                import std.algorithm : min;
+                immutable len = _source.length;
+                assert(start < len, "chunks index out of bounds");
+                return _source[start .. min(end, len)];
+            }
+        }
+
+        /// Ditto
+        static if (hasLength!Source)
+            typeof(this) opSlice(size_t lower, size_t upper)
+            {
+                import std.algorithm : min;
+                assert(lower <= upper && upper <= length, "chunks slicing index out of bounds");
+                immutable len = _source.length;
+                return chunks(_source[min(lower * _chunkSize, len) .. min(upper * _chunkSize, len)], _chunkSize);
+            }
+        else static if (hasSliceToEnd)
+            //For slicing an infinite chunk, we need to slice the source to the end.
+            typeof(takeExactly(this, 0)) opSlice(size_t lower, size_t upper)
+            {
+                assert(lower <= upper, "chunks slicing index out of bounds");
+                return chunks(_source[lower * _chunkSize .. $], _chunkSize).takeExactly(upper - lower);
+            }
+
+        static if (isInfinite!Source)
+        {
+            static if (hasSliceToEnd)
+            {
+                private static struct DollarToken{}
+                DollarToken opDollar()
+                {
+                    return DollarToken();
+                }
+                //Slice to dollar
+                typeof(this) opSlice(size_t lower, DollarToken)
+                {
+                    return typeof(this)(_source[lower * _chunkSize .. $], _chunkSize);
+                }
+            }
+        }
+        else
+        {
+            //Dollar token carries a static type, with no extra information.
+            //It can lazily transform into _source.length on algorithmic
+            //operations such as : chunks[$/2, $-1];
+            private static struct DollarToken
+            {
+                Chunks!Source* mom;
+                @property size_t momLength()
+                {
+                    return mom.length;
+                }
+                alias momLength this;
+            }
+            DollarToken opDollar()
+            {
+                return DollarToken(&this);
+            }
+
+            //Slice overloads optimized for using dollar. Without this, to slice to end, we would...
+            //1. Evaluate chunks.length
+            //2. Multiply by _chunksSize
+            //3. To finally just compare it (with min) to the original length of source (!)
+            //These overloads avoid that.
+            typeof(this) opSlice(DollarToken, DollarToken)
+            {
+                static if (hasSliceToEnd)
+                    return chunks(_source[$ .. $], _chunkSize);
+                else
+                {
+                    immutable len = _source.length;
+                    return chunks(_source[len .. len], _chunkSize);
+                }
+            }
+            typeof(this) opSlice(size_t lower, DollarToken)
+            {
+                import std.algorithm : min;
+                assert(lower <= length, "chunks slicing index out of bounds");
+                static if (hasSliceToEnd)
+                    return chunks(_source[min(lower * _chunkSize, _source.length) .. $], _chunkSize);
+                else
+                {
+                    immutable len = _source.length;
+                    return chunks(_source[min(lower * _chunkSize, len) .. len], _chunkSize);
+                }
+            }
+            typeof(this) opSlice(DollarToken, size_t upper)
+            {
+                assert(upper == length, "chunks slicing index out of bounds");
+                return this[$ .. $];
+            }
+        }
+    }
+
+    //Bidirectional range primitives
+    static if (hasSlicing!Source && hasLength!Source)
+    {
+        /**
+        Bidirectional range primitives. Provided only if both
+        $(D hasSlicing!Source) and $(D hasLength!Source) are $(D true).
+         */
+        @property auto back()
+        {
+            assert(!empty, "back called on empty chunks");
+            immutable len = _source.length;
+            immutable start = (len - 1) / _chunkSize * _chunkSize;
+            return _source[start .. len];
+        }
+
+        /// Ditto
+        void popBack()
+        {
+            assert(!empty, "popBack() called on empty chunks");
+            immutable end = (_source.length - 1) / _chunkSize * _chunkSize;
+            _source = _source[0 .. end];
+        }
+    }
+
+private:
+    Source _source;
+    size_t _chunkSize;
+}
+
+/// Ditto
+Chunks!Source chunks(Source)(Source source, size_t chunkSize)
+if (isForwardRange!Source)
+{
+    return typeof(return)(source, chunkSize);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    auto chunks = chunks(source, 4);
+    assert(chunks[0] == [1, 2, 3, 4]);
+    assert(chunks[1] == [5, 6, 7, 8]);
+    assert(chunks[2] == [9, 10]);
+    assert(chunks.back == chunks[2]);
+    assert(chunks.front == chunks[0]);
+    assert(chunks.length == 3);
+    assert(equal(retro(array(chunks)), array(retro(chunks))));
+}
+
+@safe unittest
+{
+    auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    auto chunks = chunks(source, 4);
+    auto chunks2 = chunks.save;
+    chunks.popFront();
+    assert(chunks[0] == [5, 6, 7, 8]);
+    assert(chunks[1] == [9, 10]);
+    chunks2.popBack();
+    assert(chunks2[1] == [5, 6, 7, 8]);
+    assert(chunks2.length == 2);
+
+    static assert(isRandomAccessRange!(typeof(chunks)));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    //Extra toying with slicing and indexing.
+    auto chunks1 = [0, 0, 1, 1, 2, 2, 3, 3, 4].chunks(2);
+    auto chunks2 = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4].chunks(2);
+
+    assert (chunks1.length == 5);
+    assert (chunks2.length == 5);
+    assert (chunks1[4] == [4]);
+    assert (chunks2[4] == [4, 4]);
+    assert (chunks1.back == [4]);
+    assert (chunks2.back == [4, 4]);
+
+    assert (chunks1[0 .. 1].equal([[0, 0]]));
+    assert (chunks1[0 .. 2].equal([[0, 0], [1, 1]]));
+    assert (chunks1[4 .. 5].equal([[4]]));
+    assert (chunks2[4 .. 5].equal([[4, 4]]));
+
+    assert (chunks1[0 .. 0].equal((int[][]).init));
+    assert (chunks1[5 .. 5].equal((int[][]).init));
+    assert (chunks2[5 .. 5].equal((int[][]).init));
+
+    //Fun with opDollar
+    assert (chunks1[$ .. $].equal((int[][]).init)); //Quick
+    assert (chunks2[$ .. $].equal((int[][]).init)); //Quick
+    assert (chunks1[$ - 1 .. $].equal([[4]]));      //Semiquick
+    assert (chunks2[$ - 1 .. $].equal([[4, 4]]));   //Semiquick
+    assert (chunks1[$ .. 5].equal((int[][]).init)); //Semiquick
+    assert (chunks2[$ .. 5].equal((int[][]).init)); //Semiquick
+
+    assert (chunks1[$ / 2 .. $ - 1].equal([[2, 2], [3, 3]])); //Slow
+}
+
+unittest
+{
+    import std.algorithm : equal, filter;
+
+    //ForwardRange
+    auto r = filter!"true"([1, 2, 3, 4, 5]).chunks(2);
+    assert(equal!"equal(a, b)"(r, [[1, 2], [3, 4], [5]]));
+
+    //InfiniteRange w/o RA
+    auto fibsByPairs = recurrence!"a[n-1] + a[n-2]"(1, 1).chunks(2);
+    assert(equal!`equal(a, b)`(fibsByPairs.take(2),         [[ 1,  1], [ 2,  3]]));
+
+    //InfiniteRange w/ RA and slicing
+    auto odds = sequence!("a[0] + n * a[1]")(1, 2);
+    auto oddsByPairs = odds.chunks(2);
+    assert(equal!`equal(a, b)`(oddsByPairs.take(2),         [[ 1,  3], [ 5,  7]]));
+
+    //Requires phobos#991 for Sequence to have slice to end
+    static assert(hasSlicing!(typeof(odds)));
+    assert(equal!`equal(a, b)`(oddsByPairs[3 .. 5],         [[13, 15], [17, 19]]));
+    assert(equal!`equal(a, b)`(oddsByPairs[3 .. $].take(2), [[13, 15], [17, 19]]));
+}
+
+
+
+/**
+This range splits a $(D source) range into $(D chunkCount) chunks of
+approximately equal length. $(D Source) must be a forward range with
+known length.
+
+Unlike $(LREF chunks), $(D evenChunks) takes a chunk count (not size).
+The returned range will contain zero or more $(D source.length /
+chunkCount + 1) elements followed by $(D source.length / chunkCount)
+elements. If $(D source.length < chunkCount), some chunks will be empty.
+
+$(D chunkCount) must not be zero, unless $(D source) is also empty.
+*/
+struct EvenChunks(Source)
+    if (isForwardRange!Source && hasLength!Source)
+{
+    /// Standard constructor
+    this(Source source, size_t chunkCount)
+    {
+        assert(chunkCount != 0 || source.empty, "Cannot create EvenChunks with a zero chunkCount");
+        _source = source;
+        _chunkCount = chunkCount;
+    }
+
+    /// Forward range primitives. Always present.
+    @property auto front()
+    {
+        assert(!empty);
+        return _source.save.take(_chunkPos(1));
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        assert(!empty);
+        _source.popFrontN(_chunkPos(1));
+        _chunkCount--;
+    }
+
+    /// Ditto
+    @property bool empty()
+    {
+        return _source.empty;
+    }
+
+    /// Ditto
+    @property typeof(this) save()
+    {
+        return typeof(this)(_source.save, _chunkCount);
+    }
+
+    /// Length
+    @property size_t length()
+    {
+        return _chunkCount;
+    }
+    //Note: No point in defining opDollar here without slicing.
+    //opDollar is defined below in the hasSlicing!Source section
+
+    static if (hasSlicing!Source)
+    {
+        /**
+        Indexing, slicing and bidirectional operations and range primitives.
+        Provided only if $(D hasSlicing!Source) is $(D true).
+         */
+        auto opIndex(size_t index)
+        {
+            assert(index < _chunkCount, "evenChunks index out of bounds");
+            return _source[_chunkPos(index) .. _chunkPos(index+1)];
+        }
+
+        /// Ditto
+        typeof(this) opSlice(size_t lower, size_t upper)
+        {
+            assert(lower <= upper && upper <= length, "evenChunks slicing index out of bounds");
+            return evenChunks(_source[_chunkPos(lower) .. _chunkPos(upper)], upper - lower);
+        }
+
+        /// Ditto
+        @property auto back()
+        {
+            assert(!empty, "back called on empty evenChunks");
+            return _source[_chunkPos(_chunkCount - 1) .. $];
+        }
+
+        /// Ditto
+        void popBack()
+        {
+            assert(!empty, "popBack() called on empty evenChunks");
+            _source = _source[0 .. _chunkPos(_chunkCount - 1)];
+            _chunkCount--;
+        }
+    }
+
+private:
+    Source _source;
+    size_t _chunkCount;
+
+    size_t _chunkPos(size_t i)
+    {
+        /*
+            _chunkCount = 5, _source.length = 13:
+
+               chunk0
+                 |   chunk3
+                 |     |
+                 v     v
+                +-+-+-+-+-+   ^
+                |0|3|.| | |   |
+                +-+-+-+-+-+   | div
+                |1|4|.| | |   |
+                +-+-+-+-+-+   v
+                |2|5|.|
+                +-+-+-+
+
+                <----->
+                  mod
+
+                <--------->
+                _chunkCount
+
+            One column is one chunk.
+            popFront and popBack pop the left-most
+            and right-most column, respectively.
+        */
+
+        auto div = _source.length / _chunkCount;
+        auto mod = _source.length % _chunkCount;
+        auto pos = i <= mod
+            ? i   * (div+1)
+            : mod * (div+1) + (i-mod) * div
+        ;
+        //auto len = i < mod
+        //    ? div+1
+        //    : div
+        //;
+        return pos;
+    }
+}
+
+/// Ditto
+EvenChunks!Source evenChunks(Source)(Source source, size_t chunkCount)
+if (isForwardRange!Source && hasLength!Source)
+{
+    return typeof(return)(source, chunkCount);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    auto chunks = evenChunks(source, 3);
+    assert(chunks[0] == [1, 2, 3, 4]);
+    assert(chunks[1] == [5, 6, 7]);
+    assert(chunks[2] == [8, 9, 10]);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    auto chunks = evenChunks(source, 3);
+    assert(chunks.back == chunks[2]);
+    assert(chunks.front == chunks[0]);
+    assert(chunks.length == 3);
+    assert(equal(retro(array(chunks)), array(retro(chunks))));
+
+    auto chunks2 = chunks.save;
+    chunks.popFront();
+    assert(chunks[0] == [5, 6, 7]);
+    assert(chunks[1] == [8, 9, 10]);
+    chunks2.popBack();
+    assert(chunks2[1] == [5, 6, 7]);
+    assert(chunks2.length == 2);
+
+    static assert(isRandomAccessRange!(typeof(chunks)));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    int[] source = [];
+    auto chunks = source.evenChunks(0);
+    assert(chunks.length == 0);
+    chunks = source.evenChunks(3);
+    assert(equal(chunks, [[], [], []]));
+    chunks = [1, 2, 3].evenChunks(5);
+    assert(equal(chunks, [[1], [2], [3], [], []]));
+}
+
+
+private struct OnlyResult(T, size_t arity)
+{
+    private this(Values...)(auto ref Values values)
+    {
+        this.data = [values];
+        this.backIndex = arity;
+    }
+
+    bool empty() @property
+    {
+        return frontIndex >= backIndex;
+    }
+
+    T front() @property
+    {
+        assert(!empty);
+        return data[frontIndex];
+    }
+
+    void popFront()
+    {
+        assert(!empty);
+        ++frontIndex;
+    }
+
+    T back() @property
+    {
+        assert(!empty);
+        return data[backIndex - 1];
+    }
+
+    void popBack()
+    {
+        assert(!empty);
+        --backIndex;
+    }
+
+    OnlyResult save() @property
+    {
+        return this;
+    }
+
+    size_t length() @property
+    {
+        return backIndex - frontIndex;
+    }
+
+    alias opDollar = length;
+
+    T opIndex(size_t idx)
+    {
+        // when i + idx points to elements popped
+        // with popBack
+        assert(idx < length);
+        return data[frontIndex + idx];
+    }
+
+    OnlyResult opSlice()
+    {
+        return this;
+    }
+
+    OnlyResult opSlice(size_t from, size_t to)
+    {
+        OnlyResult result = this;
+        result.frontIndex += from;
+        result.backIndex = this.frontIndex + to;
+        assert(from <= to && to <= length);
+        return result;
+    }
+
+    private size_t frontIndex = 0;
+    private size_t backIndex = 0;
+
+    // @@@BUG@@@ 10643
+    version(none)
+    {
+        static if(hasElaborateAssign!T)
+            private T[arity] data;
+        else
+            private T[arity] data = void;
+    }
+    else
+        private T[arity] data;
+}
+
+// Specialize for single-element results
+private struct OnlyResult(T, size_t arity : 1)
+{
+    @property T front() { assert(!_empty); return _value; }
+    @property T back() { assert(!_empty); return _value; }
+    @property bool empty() const { return _empty; }
+    @property size_t length() const { return !_empty; }
+    @property auto save() { return this; }
+    void popFront() { assert(!_empty); _empty = true; }
+    void popBack() { assert(!_empty); _empty = true; }
+    alias opDollar = length;
+
+    private this()(auto ref T value)
+    {
+        this._value = value;
+        this._empty = false;
+    }
+
+    T opIndex(size_t i)
+    {
+        assert(!_empty && i == 0);
+        return _value;
+    }
+
+    OnlyResult opSlice()
+    {
+        return this;
+    }
+
+    OnlyResult opSlice(size_t from, size_t to)
+    {
+        assert(from <= to && to <= length);
+        OnlyResult copy = this;
+        copy._empty = _empty || from == to;
+        return copy;
+    }
+
+    private Unqual!T _value;
+    private bool _empty = true;
+}
+
+// Specialize for the empty range
+private struct OnlyResult(T, size_t arity : 0)
+{
+    private static struct EmptyElementType {}
+
+    bool empty() @property { return true; }
+    size_t length() @property { return 0; }
+    alias opDollar = length;
+    EmptyElementType front() @property { assert(false); }
+    void popFront() { assert(false); }
+    EmptyElementType back() @property { assert(false); }
+    void popBack() { assert(false); }
+    OnlyResult save() @property { return this; }
+
+    EmptyElementType opIndex(size_t i)
+    {
+        assert(false);
+    }
+
+    OnlyResult opSlice() { return this; }
+
+    OnlyResult opSlice(size_t from, size_t to)
+    {
+        assert(from == 0 && to == 0);
+        return this;
+    }
+}
+
+/**
+Assemble $(D values) into a range that carries all its
+elements in-situ.
+
+Useful when a single value or multiple disconnected values
+must be passed to an algorithm expecting a range, without
+having to perform dynamic memory allocation.
+
+As copying the range means copying all elements, it can be
+safely returned from functions. For the same reason, copying
+the returned range may be expensive for a large number of arguments.
+ */
+auto only(Values...)(auto ref Values values)
+    if(!is(CommonType!Values == void) || Values.length == 0)
+{
+    return OnlyResult!(CommonType!Values, Values.length)(values);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm;
+    import std.uni;
+
+    assert(equal(only('♡'), "♡"));
+    assert([1, 2, 3, 4].findSplitBefore(only(3))[0] == [1, 2]);
+
+    assert(only("one", "two", "three").joiner(" ").equal("one two three"));
+
+    string title = "The D Programming Language";
+    assert(filter!isUpper(title).map!only().join(".") == "T.D.P.L");
+}
+
+unittest
+{
+    // Verify that the same common type and same arity
+    // results in the same template instantiation
+    static assert(is(typeof(only(byte.init, int.init)) ==
+        typeof(only(int.init, byte.init))));
+
+    static assert(is(typeof(only((const(char)[]).init, string.init)) ==
+        typeof(only((const(char)[]).init, (const(char)[]).init))));
+}
+
+// Tests the zero-element result
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto emptyRange = only();
+
+    alias EmptyRange = typeof(emptyRange);
+    static assert(isInputRange!EmptyRange);
+    static assert(isForwardRange!EmptyRange);
+    static assert(isBidirectionalRange!EmptyRange);
+    static assert(isRandomAccessRange!EmptyRange);
+    static assert(hasLength!EmptyRange);
+    static assert(hasSlicing!EmptyRange);
+
+    assert(emptyRange.empty);
+    assert(emptyRange.length == 0);
+    assert(emptyRange.equal(emptyRange[]));
+    assert(emptyRange.equal(emptyRange.save));
+    assert(emptyRange[0 .. 0].equal(emptyRange));
+}
+
+// Tests the single-element result
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.typecons : tuple;
+    foreach (x; tuple(1, '1', 1.0, "1", [1]))
+    {
+        auto a = only(x);
+        typeof(x)[] e = [];
+        assert(a.front == x);
+        assert(a.back == x);
+        assert(!a.empty);
+        assert(a.length == 1);
+        assert(equal(a, a[]));
+        assert(equal(a, a[0..1]));
+        assert(equal(a[0..0], e));
+        assert(equal(a[1..1], e));
+        assert(a[0] == x);
+
+        auto b = a.save;
+        assert(equal(a, b));
+        a.popFront();
+        assert(a.empty && a.length == 0 && a[].empty);
+        b.popBack();
+        assert(b.empty && b.length == 0 && b[].empty);
+
+        alias A = typeof(a);
+        static assert(isInputRange!A);
+        static assert(isForwardRange!A);
+        static assert(isBidirectionalRange!A);
+        static assert(isRandomAccessRange!A);
+        static assert(hasLength!A);
+        static assert(hasSlicing!A);
+    }
+
+    auto imm = only!(immutable int)(1);
+    immutable int[] imme = [];
+    assert(imm.front == 1);
+    assert(imm.back == 1);
+    assert(!imm.empty);
+    assert(imm.init.empty); // Issue 13441
+    assert(imm.length == 1);
+    assert(equal(imm, imm[]));
+    assert(equal(imm, imm[0..1]));
+    assert(equal(imm[0..0], imme));
+    assert(equal(imm[1..1], imme));
+    assert(imm[0] == 1);
+}
+
+// Tests multiple-element results
+@safe unittest
+{
+    import std.algorithm : equal, joiner;
+    static assert(!__traits(compiles, only(1, "1")));
+
+    auto nums = only!(byte, uint, long)(1, 2, 3);
+    static assert(is(ElementType!(typeof(nums)) == long));
+    assert(nums.length == 3);
+
+    foreach(i; 0 .. 3)
+        assert(nums[i] == i + 1);
+
+    auto saved = nums.save;
+
+    foreach(i; 1 .. 4)
+    {
+        assert(nums.front == nums[0]);
+        assert(nums.front == i);
+        nums.popFront();
+        assert(nums.length == 3 - i);
+    }
+
+    assert(nums.empty);
+
+    assert(saved.equal(only(1, 2, 3)));
+    assert(saved.equal(saved[]));
+    assert(saved[0 .. 1].equal(only(1)));
+    assert(saved[0 .. 2].equal(only(1, 2)));
+    assert(saved[0 .. 3].equal(saved));
+    assert(saved[1 .. 3].equal(only(2, 3)));
+    assert(saved[2 .. 3].equal(only(3)));
+    assert(saved[0 .. 0].empty);
+    assert(saved[3 .. 3].empty);
+
+    alias data = AliasSeq!("one", "two", "three", "four");
+    static joined =
+        ["one two", "one two three", "one two three four"];
+    string[] joinedRange = joined;
+
+    foreach(argCount; AliasSeq!(2, 3, 4))
+    {
+        auto values = only(data[0 .. argCount]);
+        alias Values = typeof(values);
+        static assert(is(ElementType!Values == string));
+        static assert(isInputRange!Values);
+        static assert(isForwardRange!Values);
+        static assert(isBidirectionalRange!Values);
+        static assert(isRandomAccessRange!Values);
+        static assert(hasSlicing!Values);
+        static assert(hasLength!Values);
+
+        assert(values.length == argCount);
+        assert(values[0 .. $].equal(values[0 .. values.length]));
+        assert(values.joiner(" ").equal(joinedRange.front));
+        joinedRange.popFront();
+    }
+
+    assert(saved.retro.equal(only(3, 2, 1)));
+    assert(saved.length == 3);
+
+    assert(saved.back == 3);
+    saved.popBack();
+    assert(saved.length == 2);
+    assert(saved.back == 2);
+
+    assert(saved.front == 1);
+    saved.popFront();
+    assert(saved.length == 1);
+    assert(saved.front == 2);
+
+    saved.popBack();
+    assert(saved.empty);
+
+    auto imm = only!(immutable int, immutable int)(42, 24);
+    alias Imm = typeof(imm);
+    static assert(is(ElementType!Imm == immutable(int)));
+    assert(!imm.empty);
+    assert(imm.init.empty); // Issue 13441
+    assert(imm.front == 42);
+    imm.popFront();
+    assert(imm.front == 24);
+    imm.popFront();
+    assert(imm.empty);
+
+    static struct Test { int* a; }
+    immutable(Test) test;
+    cast(void)only(test, test); // Works with mutable indirection
+}
+
+/**
+Iterate over $(D range) with an attached index variable.
+
+Each element is a $(XREF typecons, Tuple) containing the index
+and the element, in that order, where the index member is named $(D index)
+and the element member is named $(D value).
+
+The index starts at $(D start) and is incremented by one on every iteration.
+
+Bidirectionality is propagated only if $(D range) has length.
+
+Overflow:
+If $(D range) has length, then it is an error to pass a value for $(D start)
+so that $(D start + range.length) is bigger than $(D Enumerator.max), thus it is
+ensured that overflow cannot happen.
+
+If $(D range) does not have length, and $(D popFront) is called when
+$(D front.index == Enumerator.max), the index will overflow and
+continue from $(D Enumerator.min).
+
+Example:
+Useful for using $(D foreach) with an index loop variable:
+----
+    import std.stdio : stdin, stdout;
+    import std.range : enumerate;
+
+    foreach (lineNum, line; stdin.byLine().enumerate(1))
+        stdout.writefln("line #%s: %s", lineNum, line);
+----
+*/
+auto enumerate(Enumerator = size_t, Range)(Range range, Enumerator start = 0)
+    if (isIntegral!Enumerator && isInputRange!Range)
+in
+{
+    static if (hasLength!Range)
+    {
+        // TODO: core.checkedint supports mixed signedness yet?
+        import core.checkedint : adds, addu;
+        import std.conv : ConvException, to;
+
+        alias LengthType = typeof(range.length);
+        bool overflow;
+        static if(isSigned!Enumerator && isSigned!LengthType)
+            auto result = adds(start, range.length, overflow);
+        else static if(isSigned!Enumerator)
+        {
+            Largest!(Enumerator, Signed!LengthType) signedLength;
+            try signedLength = to!(typeof(signedLength))(range.length);
+            catch(ConvException)
+                overflow = true;
+            catch(Exception)
+                assert(false);
+
+            auto result = adds(start, signedLength, overflow);
+        }
+        else
+        {
+            static if(isSigned!LengthType)
+                assert(range.length >= 0);
+            auto result = addu(start, range.length, overflow);
+        }
+
+        assert(!overflow && result <= Enumerator.max);
+    }
+}
+body
+{
+    // TODO: Relax isIntegral!Enumerator to allow user-defined integral types
+    static struct Result
+    {
+        import std.typecons : Tuple;
+
+        private:
+        alias ElemType = Tuple!(Enumerator, "index", ElementType!Range, "value");
+        Range range;
+        Enumerator index;
+
+        public:
+        ElemType front() @property
+        {
+            assert(!range.empty);
+            return typeof(return)(index, range.front);
+        }
+
+        static if (isInfinite!Range)
+            enum bool empty = false;
+        else
+        {
+            bool empty() @property
+            {
+                return range.empty;
+            }
+        }
+
+        void popFront()
+        {
+            assert(!range.empty);
+            range.popFront();
+            ++index; // When !hasLength!Range, overflow is expected
+        }
+
+        static if (isForwardRange!Range)
+        {
+            Result save() @property
+            {
+                return typeof(return)(range.save, index);
+            }
+        }
+
+        static if (hasLength!Range)
+        {
+            size_t length() @property
+            {
+                return range.length;
+            }
+
+            alias opDollar = length;
+
+            static if (isBidirectionalRange!Range)
+            {
+                ElemType back() @property
+                {
+                    assert(!range.empty);
+                    return typeof(return)(cast(Enumerator)(index + range.length - 1), range.back);
+                }
+
+                void popBack()
+                {
+                    assert(!range.empty);
+                    range.popBack();
+                }
+            }
+        }
+
+        static if (isRandomAccessRange!Range)
+        {
+             ElemType opIndex(size_t i)
+             {
+                return typeof(return)(cast(Enumerator)(index + i), range[i]);
+             }
+        }
+
+        static if (hasSlicing!Range)
+        {
+            static if (hasLength!Range)
+            {
+                Result opSlice(size_t i, size_t j)
+                {
+                    return typeof(return)(range[i .. j], cast(Enumerator)(index + i));
+                }
+            }
+            else
+            {
+                static struct DollarToken {}
+                enum opDollar = DollarToken.init;
+
+                Result opSlice(size_t i, DollarToken)
+                {
+                    return typeof(return)(range[i .. $], cast(Enumerator)(index + i));
+                }
+
+                auto opSlice(size_t i, size_t j)
+                {
+                    return this[i .. $].takeExactly(j - 1);
+                }
+            }
+        }
+    }
+
+    return Result(range, start);
+}
+
+/// Can start enumeration from a negative position:
+pure @safe nothrow unittest
+{
+    import std.array : assocArray;
+    import std.range : enumerate;
+
+    bool[int] aa = true.repeat(3).enumerate(-1).assocArray();
+    assert(aa[-1]);
+    assert(aa[0]);
+    assert(aa[1]);
+}
+
+pure @safe nothrow unittest
+{
+    import std.internal.test.dummyrange;
+
+    import std.typecons : tuple;
+
+    static struct HasSlicing
+    {
+        typeof(this) front() @property { return typeof(this).init; }
+        bool empty() @property { return true; }
+        void popFront() {}
+
+        typeof(this) opSlice(size_t, size_t)
+        {
+            return typeof(this)();
+        }
+    }
+
+    foreach (DummyType; AliasSeq!(AllDummyRanges, HasSlicing))
+    {
+        alias R = typeof(enumerate(DummyType.init));
+        static assert(isInputRange!R);
+        static assert(isForwardRange!R == isForwardRange!DummyType);
+        static assert(isRandomAccessRange!R == isRandomAccessRange!DummyType);
+        static assert(!hasAssignableElements!R);
+
+        static if (hasLength!DummyType)
+        {
+            static assert(hasLength!R);
+            static assert(isBidirectionalRange!R ==
+                isBidirectionalRange!DummyType);
+        }
+
+        static assert(hasSlicing!R == hasSlicing!DummyType);
+    }
+
+    static immutable values = ["zero", "one", "two", "three"];
+    auto enumerated = values[].enumerate();
+    assert(!enumerated.empty);
+    assert(enumerated.front == tuple(0, "zero"));
+    assert(enumerated.back == tuple(3, "three"));
+
+    typeof(enumerated) saved = enumerated.save;
+    saved.popFront();
+    assert(enumerated.front == tuple(0, "zero"));
+    assert(saved.front == tuple(1, "one"));
+    assert(saved.length == enumerated.length - 1);
+    saved.popBack();
+    assert(enumerated.back == tuple(3, "three"));
+    assert(saved.back == tuple(2, "two"));
+    saved.popFront();
+    assert(saved.front == tuple(2, "two"));
+    assert(saved.back == tuple(2, "two"));
+    saved.popFront();
+    assert(saved.empty);
+
+    size_t control = 0;
+    foreach (i, v; enumerated)
+    {
+        static assert(is(typeof(i) == size_t));
+        static assert(is(typeof(v) == typeof(values[0])));
+        assert(i == control);
+        assert(v == values[i]);
+        assert(tuple(i, v) == enumerated[i]);
+        ++control;
+    }
+
+    assert(enumerated[0 .. $].front == tuple(0, "zero"));
+    assert(enumerated[$ - 1 .. $].front == tuple(3, "three"));
+
+    foreach(i; 0 .. 10)
+    {
+        auto shifted = values[0 .. 2].enumerate(i);
+        assert(shifted.front == tuple(i, "zero"));
+        assert(shifted[0] == shifted.front);
+
+        auto next = tuple(i + 1, "one");
+        assert(shifted[1] == next);
+        shifted.popFront();
+        assert(shifted.front == next);
+        shifted.popFront();
+        assert(shifted.empty);
+    }
+
+    foreach(T; AliasSeq!(ubyte, byte, uint, int))
+    {
+        auto inf = 42.repeat().enumerate(T.max);
+        alias Inf = typeof(inf);
+        static assert(isInfinite!Inf);
+        static assert(hasSlicing!Inf);
+
+        // test overflow
+        assert(inf.front == tuple(T.max, 42));
+        inf.popFront();
+        assert(inf.front == tuple(T.min, 42));
+
+        // test slicing
+        inf = inf[42 .. $];
+        assert(inf.front == tuple(T.min + 42, 42));
+        auto window = inf[0 .. 2];
+        assert(window.length == 1);
+        assert(window.front == inf.front);
+        window.popFront();
+        assert(window.empty);
+    }
+}
+
+pure @safe unittest
+{
+    import std.algorithm : equal;
+    static immutable int[] values = [0, 1, 2, 3, 4];
+    foreach(T; AliasSeq!(ubyte, ushort, uint, ulong))
+    {
+        auto enumerated = values.enumerate!T();
+        static assert(is(typeof(enumerated.front.index) == T));
+        assert(enumerated.equal(values[].zip(values)));
+
+        foreach(T i; 0 .. 5)
+        {
+            auto subset = values[cast(size_t)i .. $];
+            auto offsetEnumerated = subset.enumerate(i);
+            static assert(is(typeof(enumerated.front.index) == T));
+            assert(offsetEnumerated.equal(subset.zip(subset)));
+        }
+    }
+}
+
+version(none) // @@@BUG@@@ 10939
+{
+    // Re-enable (or remove) if 10939 is resolved.
+    /+pure+/ unittest // Impure because of std.conv.to
+    {
+        import std.exception : assertNotThrown, assertThrown;
+        import core.exception : RangeError;
+
+        static immutable values = [42];
+
+        static struct SignedLengthRange
+        {
+            immutable(int)[] _values = values;
+
+            int front() @property { assert(false); }
+            bool empty() @property { assert(false); }
+            void popFront() { assert(false); }
+
+            int length() @property
+            {
+                return cast(int)_values.length;
+            }
+        }
+
+        SignedLengthRange svalues;
+        foreach(Enumerator; AliasSeq!(ubyte, byte, ushort, short, uint, int, ulong, long))
+        {
+            assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max));
+            assertNotThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length));
+            assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length + 1));
+
+            assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max));
+            assertNotThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length));
+            assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length + 1));
+        }
+
+        foreach(Enumerator; AliasSeq!(byte, short, int))
+        {
+            assertThrown!RangeError(repeat(0, uint.max).enumerate!Enumerator());
+        }
+
+        assertNotThrown!RangeError(repeat(0, uint.max).enumerate!long());
+    }
+}
+
+/**
+  Returns true if $(D fn) accepts variables of type T1 and T2 in any order.
+  The following code should compile:
+  ---
+  T1 foo();
+  T2 bar();
+
+  fn(foo(), bar());
+  fn(bar(), foo());
+  ---
+*/
+template isTwoWayCompatible(alias fn, T1, T2)
+{
+    enum isTwoWayCompatible = is(typeof( (){
+            T1 foo();
+            T2 bar();
+
+            fn(foo(), bar());
+            fn(bar(), foo());
+        }
+    ));
+}
+
+
+/**
+   Policy used with the searching primitives $(D lowerBound), $(D
+   upperBound), and $(D equalRange) of $(LREF SortedRange) below.
+ */
+enum SearchPolicy
+{
+    /**
+       Searches in a linear fashion.
+    */
+    linear,
+
+    /**
+       Searches with a step that is grows linearly (1, 2, 3,...)
+       leading to a quadratic search schedule (indexes tried are 0, 1,
+       3, 6, 10, 15, 21, 28,...) Once the search overshoots its target,
+       the remaining interval is searched using binary search. The
+       search is completed in $(BIGOH sqrt(n)) time. Use it when you
+       are reasonably confident that the value is around the beginning
+       of the range.
+    */
+    trot,
+
+    /**
+       Performs a $(LUCKY galloping search algorithm), i.e. searches
+       with a step that doubles every time, (1, 2, 4, 8, ...)  leading
+       to an exponential search schedule (indexes tried are 0, 1, 3,
+       7, 15, 31, 63,...) Once the search overshoots its target, the
+       remaining interval is searched using binary search. A value is
+       found in $(BIGOH log(n)) time.
+    */
+        gallop,
+
+    /**
+       Searches using a classic interval halving policy. The search
+       starts in the middle of the range, and each search step cuts
+       the range in half. This policy finds a value in $(BIGOH log(n))
+       time but is less cache friendly than $(D gallop) for large
+       ranges. The $(D binarySearch) policy is used as the last step
+       of $(D trot), $(D gallop), $(D trotBackwards), and $(D
+       gallopBackwards) strategies.
+    */
+        binarySearch,
+
+    /**
+       Similar to $(D trot) but starts backwards. Use it when
+       confident that the value is around the end of the range.
+    */
+        trotBackwards,
+
+    /**
+       Similar to $(D gallop) but starts backwards. Use it when
+       confident that the value is around the end of the range.
+    */
+        gallopBackwards
+        }
+
+/**
+Represents a sorted range. In addition to the regular range
+primitives, supports additional operations that take advantage of the
+ordering, such as merge and binary search. To obtain a $(D
+SortedRange) from an unsorted range $(D r), use
+$(XREF_PACK algorithm,sorting,sort) which sorts $(D r) in place and returns the
+corresponding $(D SortedRange). To construct a $(D SortedRange) from a range
+$(D r) that is known to be already sorted, use $(LREF assumeSorted) described
+below.
+*/
+struct SortedRange(Range, alias pred = "a < b")
+if (isInputRange!Range)
+{
+    private import std.functional : binaryFun;
+
+    private alias predFun = binaryFun!pred;
+    private bool geq(L, R)(L lhs, R rhs)
+    {
+        return !predFun(lhs, rhs);
+    }
+    private bool gt(L, R)(L lhs, R rhs)
+    {
+        return predFun(rhs, lhs);
+    }
+    private Range _input;
+
+    // Undocummented because a clearer way to invoke is by calling
+    // assumeSorted.
+    this(Range input)
+    out
+    {
+        // moved out of the body as a workaround for Issue 12661
+        dbgVerifySorted();
+    }
+    body
+    {
+        this._input = input;
+    }
+
+    // Assertion only.
+    private void dbgVerifySorted()
+    {
+        if(!__ctfe)
+        debug
+        {
+            static if (isRandomAccessRange!Range)
+            {
+                import core.bitop : bsr;
+                import std.algorithm : isSorted;
+
+                // Check the sortedness of the input
+                if (this._input.length < 2) return;
+
+                immutable size_t msb = bsr(this._input.length) + 1;
+                assert(msb > 0 && msb <= this._input.length);
+                immutable step = this._input.length / msb;
+                auto st = stride(this._input, step);
+
+                assert(isSorted!pred(st), "Range is not sorted");
+            }
+        }
+    }
+
+    /// Range primitives.
+    @property bool empty()             //const
+    {
+        return this._input.empty;
+    }
+
+    /// Ditto
+    static if (isForwardRange!Range)
+    @property auto save()
+    {
+        // Avoid the constructor
+        typeof(this) result = this;
+        result._input = _input.save;
+        return result;
+    }
+
+    /// Ditto
+    @property auto ref front()
+    {
+        return _input.front;
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        _input.popFront();
+    }
+
+    /// Ditto
+    static if (isBidirectionalRange!Range)
+    {
+        @property auto ref back()
+        {
+            return _input.back;
+        }
+
+        /// Ditto
+        void popBack()
+        {
+            _input.popBack();
+        }
+    }
+
+    /// Ditto
+    static if (isRandomAccessRange!Range)
+        auto ref opIndex(size_t i)
+        {
+            return _input[i];
+        }
+
+    /// Ditto
+    static if (hasSlicing!Range)
+        auto opSlice(size_t a, size_t b)
+        {
+            assert(a <= b);
+            typeof(this) result = this;
+            result._input = _input[a .. b];// skip checking
+            return result;
+        }
+
+    /// Ditto
+    static if (hasLength!Range)
+    {
+        @property size_t length()          //const
+        {
+            return _input.length;
+        }
+        alias opDollar = length;
+    }
+
+/**
+   Releases the controlled range and returns it.
+*/
+    auto release()
+    {
+        import std.algorithm : move;
+        return move(_input);
+    }
+
+    // Assuming a predicate "test" that returns 0 for a left portion
+    // of the range and then 1 for the rest, returns the index at
+    // which the first 1 appears. Used internally by the search routines.
+    private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
+    if (sp == SearchPolicy.binarySearch && isRandomAccessRange!Range)
+    {
+        size_t first = 0, count = _input.length;
+        while (count > 0)
+        {
+            immutable step = count / 2, it = first + step;
+            if (!test(_input[it], v))
+            {
+                first = it + 1;
+                count -= step + 1;
+            }
+            else
+            {
+                count = step;
+            }
+        }
+        return first;
+    }
+
+    // Specialization for trot and gallop
+    private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
+    if ((sp == SearchPolicy.trot || sp == SearchPolicy.gallop)
+        && isRandomAccessRange!Range)
+    {
+        if (empty || test(front, v)) return 0;
+        immutable count = length;
+        if (count == 1) return 1;
+        size_t below = 0, above = 1, step = 2;
+        while (!test(_input[above], v))
+        {
+            // Still too small, update below and increase gait
+            below = above;
+            immutable next = above + step;
+            if (next >= count)
+            {
+                // Overshot - the next step took us beyond the end. So
+                // now adjust next and simply exit the loop to do the
+                // binary search thingie.
+                above = count;
+                break;
+            }
+            // Still in business, increase step and continue
+            above = next;
+            static if (sp == SearchPolicy.trot)
+                ++step;
+            else
+                step <<= 1;
+        }
+        return below + this[below .. above].getTransitionIndex!(
+            SearchPolicy.binarySearch, test, V)(v);
+    }
+
+    // Specialization for trotBackwards and gallopBackwards
+    private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
+    if ((sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards)
+        && isRandomAccessRange!Range)
+    {
+        immutable count = length;
+        if (empty || !test(back, v)) return count;
+        if (count == 1) return 0;
+        size_t below = count - 2, above = count - 1, step = 2;
+        while (test(_input[below], v))
+        {
+            // Still too large, update above and increase gait
+            above = below;
+            if (below < step)
+            {
+                // Overshot - the next step took us beyond the end. So
+                // now adjust next and simply fall through to do the
+                // binary search thingie.
+                below = 0;
+                break;
+            }
+            // Still in business, increase step and continue
+            below -= step;
+            static if (sp == SearchPolicy.trot)
+                ++step;
+            else
+                step <<= 1;
+        }
+        return below + this[below .. above].getTransitionIndex!(
+            SearchPolicy.binarySearch, test, V)(v);
+    }
+
+// lowerBound
+/**
+   This function uses a search with policy $(D sp) to find the
+   largest left subrange on which $(D pred(x, value)) is $(D true) for
+   all $(D x) (e.g., if $(D pred) is "less than", returns the portion of
+   the range with elements strictly smaller than $(D value)). The search
+   schedule and its complexity are documented in
+   $(LREF SearchPolicy).  See also STL's
+   $(WEB sgi.com/tech/stl/lower_bound.html, lower_bound).
+*/
+    auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
+    if (isTwoWayCompatible!(predFun, ElementType!Range, V)
+         && hasSlicing!Range)
+    {
+        return this[0 .. getTransitionIndex!(sp, geq)(value)];
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm: equal;
+        auto a = assumeSorted([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]);
+        auto p = a.lowerBound(4);
+        assert(equal(p, [ 0, 1, 2, 3 ]));
+    }
+
+// upperBound
+/**
+This function searches with policy $(D sp) to find the largest right
+subrange on which $(D pred(value, x)) is $(D true) for all $(D x)
+(e.g., if $(D pred) is "less than", returns the portion of the range
+with elements strictly greater than $(D value)). The search schedule
+and its complexity are documented in $(LREF SearchPolicy).
+
+For ranges that do not offer random access, $(D SearchPolicy.linear)
+is the only policy allowed (and it must be specified explicitly lest it exposes
+user code to unexpected inefficiencies). For random-access searches, all
+policies are allowed, and $(D SearchPolicy.binarySearch) is the default.
+
+See_Also: STL's $(WEB sgi.com/tech/stl/lower_bound.html,upper_bound).
+*/
+    auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
+    if (isTwoWayCompatible!(predFun, ElementType!Range, V))
+    {
+        static assert(hasSlicing!Range || sp == SearchPolicy.linear,
+            "Specify SearchPolicy.linear explicitly for "
+            ~ typeof(this).stringof);
+        static if (sp == SearchPolicy.linear)
+        {
+            for (; !_input.empty && !predFun(value, _input.front);
+                 _input.popFront())
+            {
+            }
+            return this;
+        }
+        else
+        {
+            return this[getTransitionIndex!(sp, gt)(value) .. length];
+        }
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm: equal;
+        auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]);
+        auto p = a.upperBound(3);
+        assert(equal(p, [4, 4, 5, 6]));
+    }
+
+
+// equalRange
+/**
+   Returns the subrange containing all elements $(D e) for which both $(D
+   pred(e, value)) and $(D pred(value, e)) evaluate to $(D false) (e.g.,
+   if $(D pred) is "less than", returns the portion of the range with
+   elements equal to $(D value)). Uses a classic binary search with
+   interval halving until it finds a value that satisfies the condition,
+   then uses $(D SearchPolicy.gallopBackwards) to find the left boundary
+   and $(D SearchPolicy.gallop) to find the right boundary. These
+   policies are justified by the fact that the two boundaries are likely
+   to be near the first found value (i.e., equal ranges are relatively
+   small). Completes the entire search in $(BIGOH log(n)) time. See also
+   STL's $(WEB sgi.com/tech/stl/equal_range.html, equal_range).
+*/
+    auto equalRange(V)(V value)
+    if (isTwoWayCompatible!(predFun, ElementType!Range, V)
+        && isRandomAccessRange!Range)
+    {
+        size_t first = 0, count = _input.length;
+        while (count > 0)
+        {
+            immutable step = count / 2;
+            auto it = first + step;
+            if (predFun(_input[it], value))
+            {
+                // Less than value, bump left bound up
+                first = it + 1;
+                count -= step + 1;
+            }
+            else if (predFun(value, _input[it]))
+            {
+                // Greater than value, chop count
+                count = step;
+            }
+            else
+            {
+                // Equal to value, do binary searches in the
+                // leftover portions
+                // Gallop towards the left end as it's likely nearby
+                immutable left = first
+                    + this[first .. it]
+                    .lowerBound!(SearchPolicy.gallopBackwards)(value).length;
+                first += count;
+                // Gallop towards the right end as it's likely nearby
+                immutable right = first
+                    - this[it + 1 .. first]
+                    .upperBound!(SearchPolicy.gallop)(value).length;
+                return this[left .. right];
+            }
+        }
+        return this.init;
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm: equal;
+        auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ];
+        auto r = a.assumeSorted.equalRange(3);
+        assert(equal(r, [ 3, 3, 3 ]));
+    }
+
+// trisect
+/**
+Returns a tuple $(D r) such that $(D r[0]) is the same as the result
+of $(D lowerBound(value)), $(D r[1]) is the same as the result of $(D
+equalRange(value)), and $(D r[2]) is the same as the result of $(D
+upperBound(value)). The call is faster than computing all three
+separately. Uses a search schedule similar to $(D
+equalRange). Completes the entire search in $(BIGOH log(n)) time.
+*/
+    auto trisect(V)(V value)
+    if (isTwoWayCompatible!(predFun, ElementType!Range, V)
+        && isRandomAccessRange!Range)
+    {
+        import std.typecons : tuple;
+        size_t first = 0, count = _input.length;
+        while (count > 0)
+        {
+            immutable step = count / 2;
+            auto it = first + step;
+            if (predFun(_input[it], value))
+            {
+                // Less than value, bump left bound up
+                first = it + 1;
+                count -= step + 1;
+            }
+            else if (predFun(value, _input[it]))
+            {
+                // Greater than value, chop count
+                count = step;
+            }
+            else
+            {
+                // Equal to value, do binary searches in the
+                // leftover portions
+                // Gallop towards the left end as it's likely nearby
+                immutable left = first
+                    + this[first .. it]
+                    .lowerBound!(SearchPolicy.gallopBackwards)(value).length;
+                first += count;
+                // Gallop towards the right end as it's likely nearby
+                immutable right = first
+                    - this[it + 1 .. first]
+                    .upperBound!(SearchPolicy.gallop)(value).length;
+                return tuple(this[0 .. left], this[left .. right],
+                        this[right .. length]);
+            }
+        }
+        // No equal element was found
+        return tuple(this[0 .. first], this.init, this[first .. length]);
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm: equal;
+        auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ];
+        auto r = assumeSorted(a).trisect(3);
+        assert(equal(r[0], [ 1, 2 ]));
+        assert(equal(r[1], [ 3, 3, 3 ]));
+        assert(equal(r[2], [ 4, 4, 5, 6 ]));
+    }
+
+// contains
+/**
+Returns $(D true) if and only if $(D value) can be found in $(D
+range), which is assumed to be sorted. Performs $(BIGOH log(r.length))
+evaluations of $(D pred). See also STL's $(WEB
+sgi.com/tech/stl/binary_search.html, binary_search).
+ */
+
+    bool contains(V)(V value)
+    if (isRandomAccessRange!Range)
+    {
+        if(empty) return false;
+        immutable i = getTransitionIndex!(SearchPolicy.binarySearch, geq)(value);
+        if(i >= length) return false;
+        return !predFun(value, _input[i]);
+    }
+
+// groupBy
+/**
+Returns a range of subranges of elements that are equivalent according to the
+sorting relation.
+ */
+    auto groupBy()()
+    {
+        import std.algorithm.iteration : chunkBy;
+        return _input.chunkBy!((a, b) => !predFun(a, b) && !predFun(b, a));
+    }
+}
+
+///
+unittest
+{
+    import std.algorithm : sort;
+    auto a = [ 1, 2, 3, 42, 52, 64 ];
+    auto r = assumeSorted(a);
+    assert(r.contains(3));
+    assert(!r.contains(32));
+    auto r1 = sort!"a > b"(a);
+    assert(r1.contains(3));
+    assert(!r1.contains(32));
+    assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]);
+}
+
+/**
+$(D SortedRange) could accept ranges weaker than random-access, but it
+is unable to provide interesting functionality for them. Therefore,
+$(D SortedRange) is currently restricted to random-access ranges.
+
+No copy of the original range is ever made. If the underlying range is
+changed concurrently with its corresponding $(D SortedRange) in ways
+that break its sortedness, $(D SortedRange) will work erratically.
+*/
+@safe unittest
+{
+    import std.algorithm : swap;
+    auto a = [ 1, 2, 3, 42, 52, 64 ];
+    auto r = assumeSorted(a);
+    assert(r.contains(42));
+    swap(a[3], a[5]);         // illegal to break sortedness of original range
+    assert(!r.contains(42));  // passes although it shouldn't
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    auto a = [ 10, 20, 30, 30, 30, 40, 40, 50, 60 ];
+    auto r = assumeSorted(a).trisect(30);
+    assert(equal(r[0], [ 10, 20 ]));
+    assert(equal(r[1], [ 30, 30, 30 ]));
+    assert(equal(r[2], [ 40, 40, 50, 60 ]));
+
+    r = assumeSorted(a).trisect(35);
+    assert(equal(r[0], [ 10, 20, 30, 30, 30 ]));
+    assert(r[1].empty);
+    assert(equal(r[2], [ 40, 40, 50, 60 ]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    auto a = [ "A", "AG", "B", "E", "F" ];
+    auto r = assumeSorted!"cmp(a,b) < 0"(a).trisect("B"w);
+    assert(equal(r[0], [ "A", "AG" ]));
+    assert(equal(r[1], [ "B" ]));
+    assert(equal(r[2], [ "E", "F" ]));
+    r = assumeSorted!"cmp(a,b) < 0"(a).trisect("A"d);
+    assert(r[0].empty);
+    assert(equal(r[1], [ "A" ]));
+    assert(equal(r[2], [ "AG", "B", "E", "F" ]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    static void test(SearchPolicy pol)()
+    {
+        auto a = [ 1, 2, 3, 42, 52, 64 ];
+        auto r = assumeSorted(a);
+        assert(equal(r.lowerBound(42), [1, 2, 3]));
+
+        assert(equal(r.lowerBound!(pol)(42), [1, 2, 3]));
+        assert(equal(r.lowerBound!(pol)(41), [1, 2, 3]));
+        assert(equal(r.lowerBound!(pol)(43), [1, 2, 3, 42]));
+        assert(equal(r.lowerBound!(pol)(51), [1, 2, 3, 42]));
+        assert(equal(r.lowerBound!(pol)(3), [1, 2]));
+        assert(equal(r.lowerBound!(pol)(55), [1, 2, 3, 42, 52]));
+        assert(equal(r.lowerBound!(pol)(420), a));
+        assert(equal(r.lowerBound!(pol)(0), a[0 .. 0]));
+
+        assert(equal(r.upperBound!(pol)(42), [52, 64]));
+        assert(equal(r.upperBound!(pol)(41), [42, 52, 64]));
+        assert(equal(r.upperBound!(pol)(43), [52, 64]));
+        assert(equal(r.upperBound!(pol)(51), [52, 64]));
+        assert(equal(r.upperBound!(pol)(53), [64]));
+        assert(equal(r.upperBound!(pol)(55), [64]));
+        assert(equal(r.upperBound!(pol)(420), a[0 .. 0]));
+        assert(equal(r.upperBound!(pol)(0), a));
+    }
+
+    test!(SearchPolicy.trot)();
+    test!(SearchPolicy.gallop)();
+    test!(SearchPolicy.trotBackwards)();
+    test!(SearchPolicy.gallopBackwards)();
+    test!(SearchPolicy.binarySearch)();
+}
+
+@safe unittest
+{
+    // Check for small arrays
+    int[] a;
+    auto r = assumeSorted(a);
+    a = [ 1 ];
+    r = assumeSorted(a);
+    a = [ 1, 2 ];
+    r = assumeSorted(a);
+    a = [ 1, 2, 3 ];
+    r = assumeSorted(a);
+}
+
+@safe unittest
+{
+    import std.algorithm : swap;
+    auto a = [ 1, 2, 3, 42, 52, 64 ];
+    auto r = assumeSorted(a);
+    assert(r.contains(42));
+    swap(a[3], a[5]);                  // illegal to break sortedness of original range
+    assert(!r.contains(42));            // passes although it shouldn't
+}
+
+@safe unittest
+{
+    immutable(int)[] arr = [ 1, 2, 3 ];
+    auto s = assumeSorted(arr);
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    int[] arr = [100, 101, 102, 200, 201, 300];
+    auto s = assumeSorted!((a, b) => a / 100 < b / 100)(arr);
+    assert(s.groupBy.equal!equal([[100, 101, 102], [200, 201], [300]]));
+}
+
+// Test on an input range
+unittest
+{
+    import std.stdio, std.file, std.path, std.conv, std.uuid;
+    auto name = buildPath(tempDir(), "test.std.range.line-" ~ text(__LINE__) ~
+                          "." ~ randomUUID().toString());
+    auto f = File(name, "w");
+    scope(exit) if (exists(name)) remove(name);
+    // write a sorted range of lines to the file
+    f.write("abc\ndef\nghi\njkl");
+    f.close();
+    f.open(name, "r");
+    auto r = assumeSorted(f.byLine());
+    auto r1 = r.upperBound!(SearchPolicy.linear)("def");
+    assert(r1.front == "ghi", r1.front);
+    f.close();
+}
+
+/**
+Assumes $(D r) is sorted by predicate $(D pred) and returns the
+corresponding $(D SortedRange!(pred, R)) having $(D r) as support. To
+keep the checking costs low, the cost is $(BIGOH 1) in release mode
+(no checks for sortedness are performed). In debug mode, a few random
+elements of $(D r) are checked for sortedness. The size of the sample
+is proportional $(BIGOH log(r.length)). That way, checking has no
+effect on the complexity of subsequent operations specific to sorted
+ranges (such as binary search). The probability of an arbitrary
+unsorted range failing the test is very high (however, an
+almost-sorted range is likely to pass it). To check for sortedness at
+cost $(BIGOH n), use $(XREF_PACK algorithm,sorting,isSorted).
+ */
+auto assumeSorted(alias pred = "a < b", R)(R r)
+if (isInputRange!(Unqual!R))
+{
+    return SortedRange!(Unqual!R, pred)(r);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    static assert(isRandomAccessRange!(SortedRange!(int[])));
+    int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
+    auto p = assumeSorted(a).lowerBound(4);
+    assert(equal(p, [0, 1, 2, 3]));
+    p = assumeSorted(a).lowerBound(5);
+    assert(equal(p, [0, 1, 2, 3, 4]));
+    p = assumeSorted(a).lowerBound(6);
+    assert(equal(p, [ 0, 1, 2, 3, 4, 5]));
+    p = assumeSorted(a).lowerBound(6.9);
+    assert(equal(p, [ 0, 1, 2, 3, 4, 5, 6]));
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+    int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ];
+    auto p = assumeSorted(a).upperBound(3);
+    assert(equal(p, [4, 4, 5, 6 ]));
+    p = assumeSorted(a).upperBound(4.2);
+    assert(equal(p, [ 5, 6 ]));
+}
+
+@safe unittest
+{
+    import std.conv : text;
+    import std.algorithm : equal;
+
+    int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ];
+    auto p = assumeSorted(a).equalRange(3);
+    assert(equal(p, [ 3, 3, 3 ]), text(p));
+    p = assumeSorted(a).equalRange(4);
+    assert(equal(p, [ 4, 4 ]), text(p));
+    p = assumeSorted(a).equalRange(2);
+    assert(equal(p, [ 2 ]));
+    p = assumeSorted(a).equalRange(0);
+    assert(p.empty);
+    p = assumeSorted(a).equalRange(7);
+    assert(p.empty);
+    p = assumeSorted(a).equalRange(3.0);
+    assert(equal(p, [ 3, 3, 3]));
+}
+
+@safe unittest
+{
+    int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ];
+    if (a.length)
+    {
+        auto b = a[a.length / 2];
+        //auto r = sort(a);
+        //assert(r.contains(b));
+    }
+}
+
+@safe unittest
+{
+    auto a = [ 5, 7, 34, 345, 677 ];
+    auto r = assumeSorted(a);
+    a = null;
+    r = assumeSorted(a);
+    a = [ 1 ];
+    r = assumeSorted(a);
+}
+
+unittest
+{
+    bool ok = true;
+    try
+    {
+        auto r2 = assumeSorted([ 677, 345, 34, 7, 5 ]);
+        debug ok = false;
+    }
+    catch (Throwable)
+    {
+    }
+    assert(ok);
+}
+
+// issue 15003
+@nogc unittest
+{
+    static immutable a = [1, 2, 3, 4];
+    auto r = a.assumeSorted;
+}
+
+/++
+    Wrapper which effectively makes it possible to pass a range by reference.
+    Both the original range and the RefRange will always have the exact same
+    elements. Any operation done on one will affect the other. So, for instance,
+    if it's passed to a function which would implicitly copy the original range
+    if it were passed to it, the original range is $(I not) copied but is
+    consumed as if it were a reference type.
+
+    Note that $(D save) works as normal and operates on a new range, so if
+    $(D save) is ever called on the RefRange, then no operations on the saved
+    range will affect the original.
+
+  +/
+struct RefRange(R)
+    if(isInputRange!R)
+{
+public:
+
+    /++ +/
+    this(R* range) @safe pure nothrow
+    {
+        _range = range;
+    }
+
+
+    /++
+        This does not assign the pointer of $(D rhs) to this $(D RefRange).
+        Rather it assigns the range pointed to by $(D rhs) to the range pointed
+        to by this $(D RefRange). This is because $(I any) operation on a
+        $(D RefRange) is the same is if it occurred to the original range. The
+        one exception is when a $(D RefRange) is assigned $(D null) either
+        directly or because $(D rhs) is $(D null). In that case, $(D RefRange)
+        no longer refers to the original range but is $(D null).
+      +/
+    auto opAssign(RefRange rhs)
+    {
+        if(_range && rhs._range)
+            *_range = *rhs._range;
+        else
+            _range = rhs._range;
+
+        return this;
+    }
+
+    /++ +/
+    auto opAssign(typeof(null) rhs)
+    {
+        _range = null;
+    }
+
+
+    /++
+        A pointer to the wrapped range.
+      +/
+    @property inout(R*) ptr() @safe inout pure nothrow
+    {
+        return _range;
+    }
+
+
+    version(StdDdoc)
+    {
+        /++ +/
+        @property auto front() {assert(0);}
+        /++ Ditto +/
+        @property auto front() const {assert(0);}
+        /++ Ditto +/
+        @property auto front(ElementType!R value) {assert(0);}
+    }
+    else
+    {
+        @property auto front()
+        {
+            return (*_range).front;
+        }
+
+        static if(is(typeof((*(cast(const R*)_range)).front))) @property auto front() const
+        {
+            return (*_range).front;
+        }
+
+        static if(is(typeof((*_range).front = (*_range).front))) @property auto front(ElementType!R value)
+        {
+            return (*_range).front = value;
+        }
+    }
+
+
+    version(StdDdoc)
+    {
+        @property bool empty(); ///
+        @property bool empty() const; ///Ditto
+    }
+    else static if(isInfinite!R)
+        enum empty = false;
+    else
+    {
+        @property bool empty()
+        {
+            return (*_range).empty;
+        }
+
+        static if(is(typeof((*cast(const R*)_range).empty))) @property bool empty() const
+        {
+            return (*_range).empty;
+        }
+    }
+
+
+    /++ +/
+    void popFront()
+    {
+        return (*_range).popFront();
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            Only defined if $(D isForwardRange!R) is $(D true).
+          +/
+        @property auto save() {assert(0);}
+        /++ Ditto +/
+        @property auto save() const {assert(0);}
+        /++ Ditto +/
+        auto opSlice() {assert(0);}
+        /++ Ditto +/
+        auto opSlice() const {assert(0);}
+    }
+    else static if(isForwardRange!R)
+    {
+        static if(isSafe!((R* r) => (*r).save))
+        {
+            @property auto save() @trusted
+            {
+                mixin(_genSave());
+            }
+
+            static if(is(typeof((*cast(const R*)_range).save))) @property auto save() @trusted const
+            {
+                mixin(_genSave());
+            }
+        }
+        else
+        {
+            @property auto save()
+            {
+                mixin(_genSave());
+            }
+
+            static if(is(typeof((*cast(const R*)_range).save))) @property auto save() const
+            {
+                mixin(_genSave());
+            }
+        }
+
+        auto opSlice()()
+        {
+            return save;
+        }
+
+        auto opSlice()() const
+        {
+            return save;
+        }
+
+        private static string _genSave() @safe pure nothrow
+        {
+            return `import std.conv;` ~
+                   `alias S = typeof((*_range).save);` ~
+                   `static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");` ~
+                   `auto mem = new void[S.sizeof];` ~
+                   `emplace!S(mem, cast(S)(*_range).save);` ~
+                   `return RefRange!S(cast(S*)mem.ptr);`;
+        }
+
+        static assert(isForwardRange!RefRange);
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            Only defined if $(D isBidirectionalRange!R) is $(D true).
+          +/
+        @property auto back() {assert(0);}
+        /++ Ditto +/
+        @property auto back() const {assert(0);}
+        /++ Ditto +/
+        @property auto back(ElementType!R value) {assert(0);}
+    }
+    else static if(isBidirectionalRange!R)
+    {
+        @property auto back()
+        {
+            return (*_range).back;
+        }
+
+        static if(is(typeof((*(cast(const R*)_range)).back))) @property auto back() const
+        {
+            return (*_range).back;
+        }
+
+        static if(is(typeof((*_range).back = (*_range).back))) @property auto back(ElementType!R value)
+        {
+            return (*_range).back = value;
+        }
+    }
+
+
+    /++ Ditto +/
+    static if(isBidirectionalRange!R) void popBack()
+    {
+        return (*_range).popBack();
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            Only defined if $(D isRandomAccesRange!R) is $(D true).
+          +/
+        auto ref opIndex(IndexType)(IndexType index) {assert(0);}
+
+        /++ Ditto +/
+        auto ref opIndex(IndexType)(IndexType index) const {assert(0);}
+    }
+    else static if(isRandomAccessRange!R)
+    {
+        auto ref opIndex(IndexType)(IndexType index)
+            if(is(typeof((*_range)[index])))
+        {
+            return (*_range)[index];
+        }
+
+        auto ref opIndex(IndexType)(IndexType index) const
+            if(is(typeof((*cast(const R*)_range)[index])))
+        {
+            return (*_range)[index];
+        }
+    }
+
+
+    /++
+        Only defined if $(D hasMobileElements!R) and $(D isForwardRange!R) are
+        $(D true).
+      +/
+    static if(hasMobileElements!R && isForwardRange!R) auto moveFront()
+    {
+        return (*_range).moveFront();
+    }
+
+
+    /++
+        Only defined if $(D hasMobileElements!R) and $(D isBidirectionalRange!R)
+        are $(D true).
+      +/
+    static if(hasMobileElements!R && isBidirectionalRange!R) auto moveBack()
+    {
+        return (*_range).moveBack();
+    }
+
+
+    /++
+        Only defined if $(D hasMobileElements!R) and $(D isRandomAccessRange!R)
+        are $(D true).
+      +/
+    static if(hasMobileElements!R && isRandomAccessRange!R) auto moveAt(IndexType)(IndexType index)
+        if(is(typeof((*_range).moveAt(index))))
+    {
+        return (*_range).moveAt(index);
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            Only defined if $(D hasLength!R) is $(D true).
+          +/
+        @property auto length() {assert(0);}
+
+        /++ Ditto +/
+        @property auto length() const {assert(0);}
+    }
+    else static if(hasLength!R)
+    {
+        @property auto length()
+        {
+            return (*_range).length;
+        }
+
+        static if(is(typeof((*cast(const R*)_range).length))) @property auto length() const
+        {
+            return (*_range).length;
+        }
+    }
+
+
+    version(StdDdoc)
+    {
+        /++
+            Only defined if $(D hasSlicing!R) is $(D true).
+          +/
+        auto opSlice(IndexType1, IndexType2)
+                    (IndexType1 begin, IndexType2 end) {assert(0);}
+
+        /++ Ditto +/
+        auto opSlice(IndexType1, IndexType2)
+                    (IndexType1 begin, IndexType2 end) const {assert(0);}
+    }
+    else static if(hasSlicing!R)
+    {
+        auto opSlice(IndexType1, IndexType2)
+                    (IndexType1 begin, IndexType2 end)
+            if(is(typeof((*_range)[begin .. end])))
+        {
+            mixin(_genOpSlice());
+        }
+
+        auto opSlice(IndexType1, IndexType2)
+                    (IndexType1 begin, IndexType2 end) const
+            if(is(typeof((*cast(const R*)_range)[begin .. end])))
+        {
+            mixin(_genOpSlice());
+        }
+
+        private static string _genOpSlice() @safe pure nothrow
+        {
+            return `import std.conv;` ~
+                   `alias S = typeof((*_range)[begin .. end]);` ~
+                   `static assert(hasSlicing!S, S.stringof ~ " is not sliceable.");` ~
+                   `auto mem = new void[S.sizeof];` ~
+                   `emplace!S(mem, cast(S)(*_range)[begin .. end]);` ~
+                   `return RefRange!S(cast(S*)mem.ptr);`;
+        }
+    }
+
+
+private:
+
+    R* _range;
+}
+
+/// Basic Example
+unittest
+{
+    import std.algorithm;
+    ubyte[] buffer = [1, 9, 45, 12, 22];
+    auto found1 = find(buffer, 45);
+    assert(found1 == [45, 12, 22]);
+    assert(buffer == [1, 9, 45, 12, 22]);
+
+    auto wrapped1 = refRange(&buffer);
+    auto found2 = find(wrapped1, 45);
+    assert(*found2.ptr == [45, 12, 22]);
+    assert(buffer == [45, 12, 22]);
+
+    auto found3 = find(wrapped1.save, 22);
+    assert(*found3.ptr == [22]);
+    assert(buffer == [45, 12, 22]);
+
+    string str = "hello world";
+    auto wrappedStr = refRange(&str);
+    assert(str.front == 'h');
+    str.popFrontN(5);
+    assert(str == " world");
+    assert(wrappedStr.front == ' ');
+    assert(*wrappedStr.ptr == " world");
+}
+
+/// opAssign Example.
+unittest
+{
+    ubyte[] buffer1 = [1, 2, 3, 4, 5];
+    ubyte[] buffer2 = [6, 7, 8, 9, 10];
+    auto wrapped1 = refRange(&buffer1);
+    auto wrapped2 = refRange(&buffer2);
+    assert(wrapped1.ptr is &buffer1);
+    assert(wrapped2.ptr is &buffer2);
+    assert(wrapped1.ptr !is wrapped2.ptr);
+    assert(buffer1 != buffer2);
+
+    wrapped1 = wrapped2;
+
+    //Everything points to the same stuff as before.
+    assert(wrapped1.ptr is &buffer1);
+    assert(wrapped2.ptr is &buffer2);
+    assert(wrapped1.ptr !is wrapped2.ptr);
+
+    //But buffer1 has changed due to the assignment.
+    assert(buffer1 == [6, 7, 8, 9, 10]);
+    assert(buffer2 == [6, 7, 8, 9, 10]);
+
+    buffer2 = [11, 12, 13, 14, 15];
+
+    //Everything points to the same stuff as before.
+    assert(wrapped1.ptr is &buffer1);
+    assert(wrapped2.ptr is &buffer2);
+    assert(wrapped1.ptr !is wrapped2.ptr);
+
+    //But buffer2 has changed due to the assignment.
+    assert(buffer1 == [6, 7, 8, 9, 10]);
+    assert(buffer2 == [11, 12, 13, 14, 15]);
+
+    wrapped2 = null;
+
+    //The pointer changed for wrapped2 but not wrapped1.
+    assert(wrapped1.ptr is &buffer1);
+    assert(wrapped2.ptr is null);
+    assert(wrapped1.ptr !is wrapped2.ptr);
+
+    //buffer2 is not affected by the assignment.
+    assert(buffer1 == [6, 7, 8, 9, 10]);
+    assert(buffer2 == [11, 12, 13, 14, 15]);
+}
+
+unittest
+{
+    import std.algorithm;
+    {
+        ubyte[] buffer = [1, 2, 3, 4, 5];
+        auto wrapper = refRange(&buffer);
+        auto p = wrapper.ptr;
+        auto f = wrapper.front;
+        wrapper.front = f;
+        auto e = wrapper.empty;
+        wrapper.popFront();
+        auto s = wrapper.save;
+        auto b = wrapper.back;
+        wrapper.back = b;
+        wrapper.popBack();
+        auto i = wrapper[0];
+        wrapper.moveFront();
+        wrapper.moveBack();
+        wrapper.moveAt(0);
+        auto l = wrapper.length;
+        auto sl = wrapper[0 .. 1];
+    }
+
+    {
+        ubyte[] buffer = [1, 2, 3, 4, 5];
+        const wrapper = refRange(&buffer);
+        const p = wrapper.ptr;
+        const f = wrapper.front;
+        const e = wrapper.empty;
+        const s = wrapper.save;
+        const b = wrapper.back;
+        const i = wrapper[0];
+        const l = wrapper.length;
+        const sl = wrapper[0 .. 1];
+    }
+
+    {
+        ubyte[] buffer = [1, 2, 3, 4, 5];
+        auto filtered = filter!"true"(buffer);
+        auto wrapper = refRange(&filtered);
+        auto p = wrapper.ptr;
+        auto f = wrapper.front;
+        wrapper.front = f;
+        auto e = wrapper.empty;
+        wrapper.popFront();
+        auto s = wrapper.save;
+        wrapper.moveFront();
+    }
+
+    {
+        ubyte[] buffer = [1, 2, 3, 4, 5];
+        auto filtered = filter!"true"(buffer);
+        const wrapper = refRange(&filtered);
+        const p = wrapper.ptr;
+
+        //Cannot currently be const. filter needs to be updated to handle const.
+        /+
+        const f = wrapper.front;
+        const e = wrapper.empty;
+        const s = wrapper.save;
+        +/
+    }
+
+    {
+        string str = "hello world";
+        auto wrapper = refRange(&str);
+        auto p = wrapper.ptr;
+        auto f = wrapper.front;
+        auto e = wrapper.empty;
+        wrapper.popFront();
+        auto s = wrapper.save;
+        auto b = wrapper.back;
+        wrapper.popBack();
+    }
+}
+
+//Test assignment.
+unittest
+{
+    ubyte[] buffer1 = [1, 2, 3, 4, 5];
+    ubyte[] buffer2 = [6, 7, 8, 9, 10];
+    RefRange!(ubyte[]) wrapper1;
+    RefRange!(ubyte[]) wrapper2 = refRange(&buffer2);
+    assert(wrapper1.ptr is null);
+    assert(wrapper2.ptr is &buffer2);
+
+    wrapper1 = refRange(&buffer1);
+    assert(wrapper1.ptr is &buffer1);
+
+    wrapper1 = wrapper2;
+    assert(wrapper1.ptr is &buffer1);
+    assert(buffer1 == buffer2);
+
+    wrapper1 = RefRange!(ubyte[]).init;
+    assert(wrapper1.ptr is null);
+    assert(wrapper2.ptr is &buffer2);
+    assert(buffer1 == buffer2);
+    assert(buffer1 == [6, 7, 8, 9, 10]);
+
+    wrapper2 = null;
+    assert(wrapper2.ptr is null);
+    assert(buffer2 == [6, 7, 8, 9, 10]);
+}
+
+unittest
+{
+    import std.algorithm;
+
+    //Test that ranges are properly consumed.
+    {
+        int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+        auto wrapper = refRange(&arr);
+
+        assert(*find(wrapper, 41).ptr == [41, 3, 40, 4, 42, 9]);
+        assert(arr == [41, 3, 40, 4, 42, 9]);
+
+        assert(*drop(wrapper, 2).ptr == [40, 4, 42, 9]);
+        assert(arr == [40, 4, 42, 9]);
+
+        assert(equal(until(wrapper, 42), [40, 4]));
+        assert(arr == [42, 9]);
+
+        assert(find(wrapper, 12).empty);
+        assert(arr.empty);
+    }
+
+    {
+        string str = "Hello, world-like object.";
+        auto wrapper = refRange(&str);
+
+        assert(*find(wrapper, "l").ptr == "llo, world-like object.");
+        assert(str == "llo, world-like object.");
+
+        assert(equal(take(wrapper, 5), "llo, "));
+        assert(str == "world-like object.");
+    }
+
+    //Test that operating on saved ranges does not consume the original.
+    {
+        int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+        auto wrapper = refRange(&arr);
+        auto saved = wrapper.save;
+        saved.popFrontN(3);
+        assert(*saved.ptr == [41, 3, 40, 4, 42, 9]);
+        assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+    }
+
+    {
+        string str = "Hello, world-like object.";
+        auto wrapper = refRange(&str);
+        auto saved = wrapper.save;
+        saved.popFrontN(13);
+        assert(*saved.ptr == "like object.");
+        assert(str == "Hello, world-like object.");
+    }
+
+    //Test that functions which use save work properly.
+    {
+        int[] arr = [1, 42];
+        auto wrapper = refRange(&arr);
+        assert(equal(commonPrefix(wrapper, [1, 27]), [1]));
+    }
+
+    {
+        int[] arr = [4, 5, 6, 7, 1, 2, 3];
+        auto wrapper = refRange(&arr);
+        assert(bringToFront(wrapper[0 .. 4], wrapper[4 .. arr.length]) == 3);
+        assert(arr == [1, 2, 3, 4, 5, 6, 7]);
+    }
+
+    //Test bidirectional functions.
+    {
+        int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+        auto wrapper = refRange(&arr);
+
+        assert(wrapper.back == 9);
+        assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+
+        wrapper.popBack();
+        assert(arr == [1, 42, 2, 41, 3, 40, 4, 42]);
+    }
+
+    {
+        string str = "Hello, world-like object.";
+        auto wrapper = refRange(&str);
+
+        assert(wrapper.back == '.');
+        assert(str == "Hello, world-like object.");
+
+        wrapper.popBack();
+        assert(str == "Hello, world-like object");
+    }
+
+    //Test random access functions.
+    {
+        int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+        auto wrapper = refRange(&arr);
+
+        assert(wrapper[2] == 2);
+        assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+
+        assert(*wrapper[3 .. 6].ptr != null, [41, 3, 40]);
+        assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+    }
+
+    //Test move functions.
+    {
+        int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+        auto wrapper = refRange(&arr);
+
+        auto t1 = wrapper.moveFront();
+        auto t2 = wrapper.moveBack();
+        wrapper.front = t2;
+        wrapper.back = t1;
+        assert(arr == [9, 42, 2, 41, 3, 40, 4, 42, 1]);
+
+        sort(wrapper.save);
+        assert(arr == [1, 2, 3, 4, 9, 40, 41, 42, 42]);
+    }
+}
+
+unittest
+{
+    struct S
+    {
+        @property int front() @safe const pure nothrow { return 0; }
+        enum bool empty = false;
+        void popFront() @safe pure nothrow { }
+        @property auto save() @safe pure nothrow { return this; }
+    }
+
+    S s;
+    auto wrapper = refRange(&s);
+    static assert(isInfinite!(typeof(wrapper)));
+}
+
+unittest
+{
+    class C
+    {
+        @property int front() @safe const pure nothrow { return 0; }
+        @property bool empty() @safe const pure nothrow { return false; }
+        void popFront() @safe pure nothrow { }
+        @property auto save() @safe pure nothrow { return this; }
+    }
+    static assert(isForwardRange!C);
+
+    auto c = new C;
+    auto cWrapper = refRange(&c);
+    static assert(is(typeof(cWrapper) == C));
+    assert(cWrapper is c);
+}
+
+unittest // issue 14373
+{
+    static struct R
+    {
+        @property int front() {return 0;}
+        void popFront() {empty = true;}
+        bool empty = false;
+    }
+    R r;
+    refRange(&r).popFront();
+    assert(r.empty);
+}
+
+unittest // issue 14575
+{
+    struct R
+    {
+        Object front;
+        alias back = front;
+        bool empty = false;
+        void popFront() {empty = true;}
+        alias popBack = popFront;
+        @property R save() {return this;}
+    }
+    static assert(isBidirectionalRange!R);
+    R r;
+    auto rr = refRange(&r);
+
+    struct R2
+    {
+        @property Object front() {return null;}
+        @property const(Object) front() const {return null;}
+        alias back = front;
+        bool empty = false;
+        void popFront() {empty = true;}
+        alias popBack = popFront;
+        @property R2 save() {return this;}
+    }
+    static assert(isBidirectionalRange!R2);
+    R2 r2;
+    auto rr2 = refRange(&r2);
+}
+
+/++
+    Helper function for constructing a $(LREF RefRange).
+
+    If the given range is a class type (and thus is already a reference type),
+    then the original range is returned rather than a $(LREF RefRange).
+  +/
+auto refRange(R)(R* range)
+    if(isInputRange!R && !is(R == class))
+{
+    return RefRange!R(range);
+}
+
+/// ditto
+auto refRange(R)(R* range)
+    if(isInputRange!R && is(R == class))
+{
+    return *range;
+}
+
+/*****************************************************************************/
+
+@safe unittest    // bug 9060
+{
+    import std.algorithm : map, joiner, group, until;
+    // fix for std.algorithm
+    auto r = map!(x => 0)([1]);
+    chain(r, r);
+    zip(r, r);
+    roundRobin(r, r);
+
+    struct NRAR {
+        typeof(r) input;
+        @property empty() { return input.empty; }
+        @property front() { return input.front; }
+        void popFront()   { input.popFront(); }
+        @property save()  { return NRAR(input.save); }
+    }
+    auto n1 = NRAR(r);
+    cycle(n1);  // non random access range version
+
+    assumeSorted(r);
+
+    // fix for std.range
+    joiner([r], [9]);
+
+    struct NRAR2 {
+        NRAR input;
+        @property empty() { return true; }
+        @property front() { return input; }
+        void popFront() { }
+        @property save()  { return NRAR2(input.save); }
+    }
+    auto n2 = NRAR2(n1);
+    joiner(n2);
+
+    group(r);
+
+    until(r, 7);
+    static void foo(R)(R r) { until!(x => x > 7)(r); }
+    foo(r);
+}
+
+
+/*********************************
+ * An OutputRange that discards the data it receives.
+ */
+struct NullSink
+{
+    void put(E)(E){}
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : map, copy;
+    [4, 5, 6].map!(x => x * 2).copy(NullSink()); // data is discarded
+}
+
+
+/++
+
+  Implements a "tee" style pipe, wrapping an input range so that elements of the
+  range can be passed to a provided function or $(LREF OutputRange) as they are
+  iterated over. This is useful for printing out intermediate values in a long
+  chain of range code, performing some operation with side-effects on each call
+  to $(D front) or $(D popFront), or diverting the elements of a range into an
+  auxiliary $(LREF OutputRange).
+
+  It is important to note that as the resultant range is evaluated lazily,
+  in the case of the version of $(D tee) that takes a function, the function
+  will not actually be executed until the range is "walked" using functions
+  that evaluate ranges, such as $(XREF array,array) or
+  $(XREF_PACK algorithm,iteration,fold).
+
+  Params:
+  pipeOnPop = If `Yes.pipeOnPop`, simply iterating the range without ever
+  calling `front` is enough to have `tee` mirror elements to `outputRange` (or,
+  respectively, `fun`). If `No.pipeOnPop`, only elements for which `front` does
+  get called will be also sent to `outputRange`/`fun`.
+  inputRange = The input range beeing passed through.
+  outputRange = This range will receive elements of `inputRange` progressively
+  as iteration proceeds.
+  fun = This function will be called with elements of `inputRange`
+  progressively as iteration proceeds.
+
+  Returns:
+  An input range that offers the elements of `inputRange`. Regardless of
+  whether `inputRange` is a more powerful range (forward, bidirectional etc),
+  the result is always an input range. Reading this causes `inputRange` to be
+  iterated and returns its elements in turn. In addition, the same elements
+  will be passed to `outputRange` or `fun` as well.
+
+  See_Also: $(XREF_PACK algorithm,iteration,each)
++/
+auto tee(Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1, R2)(R1 inputRange, R2 outputRange)
+if (isInputRange!R1 && isOutputRange!(R2, ElementType!R1))
+{
+    static struct Result
+    {
+        private R1 _input;
+        private R2 _output;
+        static if (!pipeOnPop)
+        {
+            private bool _frontAccessed;
+        }
+
+        static if (hasLength!R1)
+        {
+            @property length()
+            {
+                return _input.length;
+            }
+        }
+
+        static if (isInfinite!R1)
+        {
+            enum bool empty = false;
+        }
+        else
+        {
+            @property bool empty() { return _input.empty; }
+        }
+
+        void popFront()
+        {
+            assert(!_input.empty);
+            static if (pipeOnPop)
+            {
+                put(_output, _input.front);
+            }
+            else
+            {
+                _frontAccessed = false;
+            }
+            _input.popFront();
+        }
+
+        @property auto ref front()
+        {
+            static if (!pipeOnPop)
+            {
+                if (!_frontAccessed)
+                {
+                    _frontAccessed = true;
+                    put(_output, _input.front);
+                }
+            }
+            return _input.front;
+        }
+    }
+
+    return Result(inputRange, outputRange);
+}
+
+/// Ditto
+auto tee(alias fun, Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1)(R1 inputRange)
+if (is(typeof(fun) == void) || isSomeFunction!fun)
+{
+    /*
+        Distinguish between function literals and template lambdas
+        when using either as an $(LREF OutputRange). Since a template
+        has no type, typeof(template) will always return void.
+        If it's a template lambda, it's first necessary to instantiate
+        it with $(D ElementType!R1).
+    */
+    static if (is(typeof(fun) == void))
+        alias _fun = fun!(ElementType!R1);
+    else
+        alias _fun = fun;
+
+    static if (isFunctionPointer!_fun || isDelegate!_fun)
+    {
+        return tee!pipeOnPop(inputRange, _fun);
+    }
+    else
+    {
+        return tee!pipeOnPop(inputRange, &_fun);
+    }
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal, filter, map;
+
+    // Sum values while copying
+    int[] values = [1, 4, 9, 16, 25];
+    int sum = 0;
+    auto newValues = values.tee!(a => sum += a).array;
+    assert(equal(newValues, values));
+    assert(sum == 1 + 4 + 9 + 16 + 25);
+
+    // Count values that pass the first filter
+    int count = 0;
+    auto newValues4 = values.filter!(a => a < 10)
+                            .tee!(a => count++)
+                            .map!(a => a + 1)
+                            .filter!(a => a < 10);
+
+    //Fine, equal also evaluates any lazy ranges passed to it.
+    //count is not 3 until equal evaluates newValues4
+    assert(equal(newValues4, [2, 5]));
+    assert(count == 3);
+}
+
+//
+@safe unittest
+{
+    import std.algorithm : equal, filter, map;
+
+    int[] values = [1, 4, 9, 16, 25];
+
+    int count = 0;
+    auto newValues = values.filter!(a => a < 10)
+        .tee!(a => count++, No.pipeOnPop)
+        .map!(a => a + 1)
+        .filter!(a => a < 10);
+
+    auto val = newValues.front;
+    assert(count == 1);
+    //front is only evaluated once per element
+    val = newValues.front;
+    assert(count == 1);
+
+    //popFront() called, fun will be called
+    //again on the next access to front
+    newValues.popFront();
+    newValues.front;
+    assert(count == 2);
+
+    int[] preMap = new int[](3), postMap = [];
+    auto mappedValues = values.filter!(a => a < 10)
+        //Note the two different ways of using tee
+        .tee(preMap)
+        .map!(a => a + 1)
+        .tee!(a => postMap ~= a)
+        .filter!(a => a < 10);
+    assert(equal(mappedValues, [2, 5]));
+    assert(equal(preMap, [1, 4, 9]));
+    assert(equal(postMap, [2, 5, 10]));
+}
+
+//
+@safe unittest
+{
+    import std.algorithm : filter, equal, map;
+
+    char[] txt = "Line one, Line 2".dup;
+
+    bool isVowel(dchar c)
+    {
+        import std.string : indexOf;
+        return "AaEeIiOoUu".indexOf(c) != -1;
+    }
+
+    int vowelCount = 0;
+    int shiftedCount = 0;
+    auto removeVowels = txt.tee!(c => isVowel(c) ? vowelCount++ : 0)
+                                .filter!(c => !isVowel(c))
+                                .map!(c => (c == ' ') ? c : c + 1)
+                                .tee!(c => isVowel(c) ? shiftedCount++ : 0);
+    assert(equal(removeVowels, "Mo o- Mo 3"));
+    assert(vowelCount == 6);
+    assert(shiftedCount == 3);
+}
+
+@safe unittest
+{
+    // Manually stride to test different pipe behavior.
+    void testRange(Range)(Range r)
+    {
+        const int strideLen = 3;
+        int i = 0;
+        ElementType!Range elem1;
+        ElementType!Range elem2;
+        while (!r.empty)
+        {
+            if (i % strideLen == 0)
+            {
+                //Make sure front is only
+                //evaluated once per item
+                elem1 = r.front;
+                elem2 = r.front;
+                assert(elem1 == elem2);
+            }
+            r.popFront();
+            i++;
+        }
+    }
+
+    string txt = "abcdefghijklmnopqrstuvwxyz";
+
+    int popCount = 0;
+    auto pipeOnPop = txt.tee!(a => popCount++);
+    testRange(pipeOnPop);
+    assert(popCount == 26);
+
+    int frontCount = 0;
+    auto pipeOnFront = txt.tee!(a => frontCount++, No.pipeOnPop);
+    testRange(pipeOnFront);
+    assert(frontCount == 9);
+}
+
+@safe unittest
+{
+    import std.algorithm : equal;
+
+    //Test diverting elements to an OutputRange
+    string txt = "abcdefghijklmnopqrstuvwxyz";
+
+    dchar[] asink1 = [];
+    auto fsink = (dchar c) { asink1 ~= c; };
+    auto result1 = txt.tee(fsink).array;
+    assert(equal(txt, result1) && (equal(result1, asink1)));
+
+    dchar[] _asink1 = [];
+    auto _result1 = txt.tee!((dchar c) { _asink1 ~= c; })().array;
+    assert(equal(txt, _result1) && (equal(_result1, _asink1)));
+
+    dchar[] asink2 = new dchar[](txt.length);
+    void fsink2(dchar c) { static int i = 0; asink2[i] = c; i++; }
+    auto result2 = txt.tee(&fsink2).array;
+    assert(equal(txt, result2) && equal(result2, asink2));
+
+    dchar[] asink3 = new dchar[](txt.length);
+    auto result3 = txt.tee(asink3).array;
+    assert(equal(txt, result3) && equal(result3, asink3));
+
+    foreach (CharType; AliasSeq!(char, wchar, dchar))
+    {
+        auto appSink = appender!(CharType[])();
+        auto appResult = txt.tee(appSink).array;
+        assert(equal(txt, appResult) && equal(appResult, appSink.data));
+    }
+
+    foreach (StringType; AliasSeq!(string, wstring, dstring))
+    {
+        auto appSink = appender!StringType();
+        auto appResult = txt.tee(appSink).array;
+        assert(equal(txt, appResult) && equal(appResult, appSink.data));
+    }
+}
+
+@safe unittest
+{
+    // Issue 13483
+    static void func1(T)(T x) {}
+    void func2(int x) {}
+
+    auto r = [1, 2, 3, 4].tee!func1.tee!func2;
+}
diff --git a/libphobos/src/std/range/primitives.d b/libphobos/src/std/range/primitives.d
new file mode 100644
index 00000000000..48558edf2cb
--- /dev/null
+++ b/libphobos/src/std/range/primitives.d
@@ -0,0 +1,2281 @@
+/**
+This module is a submodule of $(LINK2 std_range.html, std.range).
+
+It provides basic range functionality by defining several templates for testing
+whether a given object is a _range, and what kind of _range it is:
+
+$(BOOKTABLE ,
+    $(TR $(TD $(D $(LREF isInputRange)))
+        $(TD Tests if something is an $(I input _range), defined to be
+        something from which one can sequentially read data using the
+        primitives $(D front), $(D popFront), and $(D empty).
+    ))
+    $(TR $(TD $(D $(LREF isOutputRange)))
+        $(TD Tests if something is an $(I output _range), defined to be
+        something to which one can sequentially write data using the
+        $(D $(LREF put)) primitive.
+    ))
+    $(TR $(TD $(D $(LREF isForwardRange)))
+        $(TD Tests if something is a $(I forward _range), defined to be an
+        input _range with the additional capability that one can save one's
+        current position with the $(D save) primitive, thus allowing one to
+        iterate over the same _range multiple times.
+    ))
+    $(TR $(TD $(D $(LREF isBidirectionalRange)))
+        $(TD Tests if something is a $(I bidirectional _range), that is, a
+        forward _range that allows reverse traversal using the primitives $(D
+        back) and $(D popBack).
+    ))
+    $(TR $(TD $(D $(LREF isRandomAccessRange)))
+        $(TD Tests if something is a $(I random access _range), which is a
+        bidirectional _range that also supports the array subscripting
+        operation via the primitive $(D opIndex).
+    ))
+)
+
+It also provides number of templates that test for various _range capabilities:
+
+$(BOOKTABLE ,
+    $(TR $(TD $(D $(LREF hasMobileElements)))
+        $(TD Tests if a given _range's elements can be moved around using the
+        primitives $(D moveFront), $(D moveBack), or $(D moveAt).
+    ))
+    $(TR $(TD $(D $(LREF ElementType)))
+        $(TD Returns the element type of a given _range.
+    ))
+    $(TR $(TD $(D $(LREF ElementEncodingType)))
+        $(TD Returns the encoding element type of a given _range.
+    ))
+    $(TR $(TD $(D $(LREF hasSwappableElements)))
+        $(TD Tests if a _range is a forward _range with swappable elements.
+    ))
+    $(TR $(TD $(D $(LREF hasAssignableElements)))
+        $(TD Tests if a _range is a forward _range with mutable elements.
+    ))
+    $(TR $(TD $(D $(LREF hasLvalueElements)))
+        $(TD Tests if a _range is a forward _range with elements that can be
+        passed by reference and have their address taken.
+    ))
+    $(TR $(TD $(D $(LREF hasLength)))
+        $(TD Tests if a given _range has the $(D length) attribute.
+    ))
+    $(TR $(TD $(D $(LREF isInfinite)))
+        $(TD Tests if a given _range is an $(I infinite _range).
+    ))
+    $(TR $(TD $(D $(LREF hasSlicing)))
+        $(TD Tests if a given _range supports the array slicing operation $(D
+        R[x..y]).
+    ))
+)
+
+Finally, it includes some convenience functions for manipulating ranges:
+
+$(BOOKTABLE ,
+    $(TR $(TD $(D $(LREF popFrontN)))
+        $(TD Advances a given _range by up to $(I n) elements.
+    ))
+    $(TR $(TD $(D $(LREF popBackN)))
+        $(TD Advances a given bidirectional _range from the right by up to
+        $(I n) elements.
+    ))
+    $(TR $(TD $(D $(LREF popFrontExactly)))
+        $(TD Advances a given _range by up exactly $(I n) elements.
+    ))
+    $(TR $(TD $(D $(LREF popBackExactly)))
+        $(TD Advances a given bidirectional _range from the right by exactly
+        $(I n) elements.
+    ))
+    $(TR $(TD $(D $(LREF moveFront)))
+        $(TD Removes the front element of a _range.
+    ))
+    $(TR $(TD $(D $(LREF moveBack)))
+        $(TD Removes the back element of a bidirectional _range.
+    ))
+    $(TR $(TD $(D $(LREF moveAt)))
+        $(TD Removes the $(I i)'th element of a random-access _range.
+    ))
+    $(TR $(TD $(D $(LREF walkLength)))
+        $(TD Computes the length of any _range in O(n) time.
+    ))
+)
+
+Source: $(PHOBOSSRC std/range/_primitives.d)
+
+Macros:
+
+WIKI = Phobos/StdRange
+
+Copyright: Copyright by authors 2008-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha,
+and Jonathan M Davis. Credit for some of the ideas in building this module goes
+to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi).
+*/
+module std.range.primitives;
+
+import std.traits;
+
+/**
+Returns $(D true) if $(D R) is an input range. An input range must
+define the primitives $(D empty), $(D popFront), and $(D front). The
+following code should compile for any input range.
+
+----
+R r;              // can define a range object
+if (r.empty) {}   // can test for empty
+r.popFront();     // can invoke popFront()
+auto h = r.front; // can get the front of the range of non-void type
+----
+
+The semantics of an input range (not checkable during compilation) are
+assumed to be the following ($(D r) is an object of type $(D R)):
+
+$(UL $(LI $(D r.empty) returns $(D false) iff there is more data
+available in the range.)  $(LI $(D r.front) returns the current
+element in the range. It may return by value or by reference. Calling
+$(D r.front) is allowed only if calling $(D r.empty) has, or would
+have, returned $(D false).) $(LI $(D r.popFront) advances to the next
+element in the range. Calling $(D r.popFront) is allowed only if
+calling $(D r.empty) has, or would have, returned $(D false).))
+
+Params:
+    R = type to be tested
+
+Returns:
+    true if R is an InputRange, false if not
+ */
+template isInputRange(R)
+{
+    enum bool isInputRange = is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;     // can define a range object
+        if (r.empty) {}   // can test for empty
+        r.popFront();     // can invoke popFront()
+        auto h = r.front; // can get the front of the range
+    }));
+}
+
+///
+@safe unittest
+{
+    struct A {}
+    struct B
+    {
+        void popFront();
+        @property bool empty();
+        @property int front();
+    }
+    static assert(!isInputRange!A);
+    static assert( isInputRange!B);
+    static assert( isInputRange!(int[]));
+    static assert( isInputRange!(char[]));
+    static assert(!isInputRange!(char[4]));
+    static assert( isInputRange!(inout(int)[]));
+}
+
+/+
+puts the whole raw element $(D e) into $(D r). doPut will not attempt to
+iterate, slice or transcode $(D e) in any way shape or form. It will $(B only)
+call the correct primitive ($(D r.put(e)),  $(D r.front = e) or
+$(D r(0)) once.
+
+This can be important when $(D e) needs to be placed in $(D r) unchanged.
+Furthermore, it can be useful when working with $(D InputRange)s, as doPut
+guarantees that no more than a single element will be placed.
++/
+private void doPut(R, E)(ref R r, auto ref E e)
+{
+    static if(is(PointerTarget!R == struct))
+        enum usingPut = hasMember!(PointerTarget!R, "put");
+    else
+        enum usingPut = hasMember!(R, "put");
+
+    static if (usingPut)
+    {
+        static assert(is(typeof(r.put(e))),
+            "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
+        r.put(e);
+    }
+    else static if (isInputRange!R)
+    {
+        static assert(is(typeof(r.front = e)),
+            "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
+        r.front = e;
+        r.popFront();
+    }
+    else static if (is(typeof(r(e))))
+    {
+        r(e);
+    }
+    else
+    {
+        static assert (false,
+            "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
+    }
+}
+
+@safe unittest
+{
+    static assert (!isNativeOutputRange!(int,     int));
+    static assert ( isNativeOutputRange!(int[],   int));
+    static assert (!isNativeOutputRange!(int[][], int));
+
+    static assert (!isNativeOutputRange!(int,     int[]));
+    static assert (!isNativeOutputRange!(int[],   int[]));
+    static assert ( isNativeOutputRange!(int[][], int[]));
+
+    static assert (!isNativeOutputRange!(int,     int[][]));
+    static assert (!isNativeOutputRange!(int[],   int[][]));
+    static assert (!isNativeOutputRange!(int[][], int[][]));
+
+    static assert (!isNativeOutputRange!(int[4],   int));
+    static assert ( isNativeOutputRange!(int[4][], int)); //Scary!
+    static assert ( isNativeOutputRange!(int[4][], int[4]));
+
+    static assert (!isNativeOutputRange!( char[],   char));
+    static assert (!isNativeOutputRange!( char[],  dchar));
+    static assert ( isNativeOutputRange!(dchar[],   char));
+    static assert ( isNativeOutputRange!(dchar[],  dchar));
+
+}
+
+/++
+Outputs $(D e) to $(D r). The exact effect is dependent upon the two
+types. Several cases are accepted, as described below. The code snippets
+are attempted in order, and the first to compile "wins" and gets
+evaluated.
+
+In this table "doPut" is a method that places $(D e) into $(D r), using the
+correct primitive: $(D r.put(e)) if $(D R) defines $(D put), $(D r.front = e)
+if $(D r) is an input range (followed by $(D r.popFront())), or $(D r(e))
+otherwise.
+
+$(BOOKTABLE ,
+    $(TR
+        $(TH Code Snippet)
+        $(TH Scenario)
+    )
+    $(TR
+        $(TD $(D r.doPut(e);))
+        $(TD $(D R) specifically accepts an $(D E).)
+    )
+    $(TR
+        $(TD $(D r.doPut([ e ]);))
+        $(TD $(D R) specifically accepts an $(D E[]).)
+    )
+    $(TR
+        $(TD $(D r.putChar(e);))
+        $(TD $(D R) accepts some form of string or character. put will
+            transcode the character $(D e) accordingly.)
+    )
+    $(TR
+        $(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);))
+        $(TD Copying range $(D E) into $(D R).)
+    )
+)
+
+Tip: $(D put) should $(I not) be used "UFCS-style", e.g. $(D r.put(e)).
+Doing this may call $(D R.put) directly, by-passing any transformation
+feature provided by $(D Range.put). $(D put(r, e)) is prefered.
+ +/
+void put(R, E)(ref R r, E e)
+{
+    //First level: simply straight up put.
+    static if (is(typeof(doPut(r, e))))
+    {
+        doPut(r, e);
+    }
+    //Optional optimization block for straight up array to array copy.
+    else static if (isDynamicArray!R && !isNarrowString!R && isDynamicArray!E && is(typeof(r[] = e[])))
+    {
+        immutable len = e.length;
+        r[0 .. len] = e[];
+        r = r[len .. $];
+    }
+    //Accepts E[] ?
+    else static if (is(typeof(doPut(r, [e]))) && !isDynamicArray!R)
+    {
+        if (__ctfe)
+        {
+            E[1] arr = [e];
+            doPut(r, arr[]);
+        }
+        else
+            doPut(r, (ref e) @trusted { return (&e)[0..1]; }(e));
+    }
+    //special case for char to string.
+    else static if (isSomeChar!E && is(typeof(putChar(r, e))))
+    {
+        putChar(r, e);
+    }
+    //Extract each element from the range
+    //We can use "put" here, so we can recursively test a RoR of E.
+    else static if (isInputRange!E && is(typeof(put(r, e.front))))
+    {
+        //Special optimization: If E is a narrow string, and r accepts characters no-wider than the string's
+        //Then simply feed the characters 1 by 1.
+        static if (isNarrowString!E && (
+            (is(E : const  char[]) && is(typeof(doPut(r,  char.max))) && !is(typeof(doPut(r, dchar.max))) && !is(typeof(doPut(r, wchar.max)))) ||
+            (is(E : const wchar[]) && is(typeof(doPut(r, wchar.max))) && !is(typeof(doPut(r, dchar.max)))) ) )
+        {
+            foreach(c; e)
+                doPut(r, c);
+        }
+        else
+        {
+            for (; !e.empty; e.popFront())
+                put(r, e.front);
+        }
+    }
+    else
+    {
+        static assert (false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
+    }
+}
+
+@safe pure nothrow @nogc unittest
+{
+    static struct R() { void put(in char[]) {} }
+    R!() r;
+    put(r, 'a');
+}
+
+//Helper function to handle chars as quickly and as elegantly as possible
+//Assumes r.put(e)/r(e) has already been tested
+private void putChar(R, E)(ref R r, E e)
+if (isSomeChar!E)
+{
+    ////@@@9186@@@: Can't use (E[]).init
+    ref const( char)[] cstringInit();
+    ref const(wchar)[] wstringInit();
+    ref const(dchar)[] dstringInit();
+
+    enum csCond = !isDynamicArray!R && is(typeof(doPut(r, cstringInit())));
+    enum wsCond = !isDynamicArray!R && is(typeof(doPut(r, wstringInit())));
+    enum dsCond = !isDynamicArray!R && is(typeof(doPut(r, dstringInit())));
+
+    //Use "max" to avoid static type demotion
+    enum ccCond = is(typeof(doPut(r,  char.max)));
+    enum wcCond = is(typeof(doPut(r, wchar.max)));
+    //enum dcCond = is(typeof(doPut(r, dchar.max)));
+
+    //Fast transform a narrow char into a wider string
+    static if ((wsCond && E.sizeof < wchar.sizeof) || (dsCond && E.sizeof < dchar.sizeof))
+    {
+        enum w = wsCond && E.sizeof < wchar.sizeof;
+        Select!(w, wchar, dchar) c = e;
+        typeof(c)[1] arr = [c];
+        doPut(r, arr[]);
+    }
+    //Encode a wide char into a narrower string
+    else static if (wsCond || csCond)
+    {
+        import std.utf : encode;
+        /+static+/ Select!(wsCond, wchar[2], char[4]) buf; //static prevents purity.
+        doPut(r, buf[0 .. encode(buf, e)]);
+    }
+    //Slowly encode a wide char into a series of narrower chars
+    else static if (wcCond || ccCond)
+    {
+        import std.encoding : encode;
+        alias C = Select!(wcCond, wchar, char);
+        encode!(C, R)(e, r);
+    }
+    else
+    {
+        static assert (false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ ".");
+    }
+}
+
+pure unittest
+{
+    auto f = delegate (const(char)[]) {};
+    putChar(f, cast(dchar)'a');
+}
+
+
+@safe pure unittest
+{
+    static struct R() { void put(in char[]) {} }
+    R!() r;
+    putChar(r, 'a');
+}
+
+unittest
+{
+    struct A {}
+    static assert(!isInputRange!(A));
+    struct B
+    {
+        void put(int) {}
+    }
+    B b;
+    put(b, 5);
+}
+
+unittest
+{
+    int[] a = [1, 2, 3], b = [10, 20];
+    auto c = a;
+    put(a, b);
+    assert(c == [10, 20, 3]);
+    assert(a == [3]);
+}
+
+unittest
+{
+    int[] a = new int[10];
+    int b;
+    static assert(isInputRange!(typeof(a)));
+    put(a, b);
+}
+
+unittest
+{
+    void myprint(in char[] s) { }
+    auto r = &myprint;
+    put(r, 'a');
+}
+
+unittest
+{
+    int[] a = new int[10];
+    static assert(!__traits(compiles, put(a, 1.0L)));
+    put(a, 1);
+    assert(a.length == 9);
+    /*
+     * a[0] = 65;       // OK
+     * a[0] = 'A';      // OK
+     * a[0] = "ABC"[0]; // OK
+     * put(a, "ABC");   // OK
+     */
+    put(a, "ABC");
+    assert(a.length == 6);
+}
+
+unittest
+{
+    char[] a = new char[10];
+    static assert(!__traits(compiles, put(a, 1.0L)));
+    static assert(!__traits(compiles, put(a, 1)));
+    // char[] is NOT output range.
+    static assert(!__traits(compiles, put(a, 'a')));
+    static assert(!__traits(compiles, put(a, "ABC")));
+}
+
+unittest
+{
+    int[][] a = new int[][10];
+    int[]   b = new int[10];
+    int     c;
+    put(b, c);
+    assert(b.length == 9);
+    put(a, b);
+    assert(a.length == 9);
+    static assert(!__traits(compiles, put(a, c)));
+}
+
+unittest
+{
+    int[][] a = new int[][](3);
+    int[]   b = [1];
+    auto aa = a;
+    put(aa, b);
+    assert(aa == [[], []]);
+    assert(a  == [[1], [], []]);
+    int[][3] c = [2];
+    aa = a;
+    put(aa, c[]);
+    assert(aa.empty);
+    assert(a == [[2], [2], [2]]);
+}
+
+unittest
+{
+    // Test fix for bug 7476.
+    struct LockingTextWriter
+    {
+        void put(dchar c){}
+    }
+    struct RetroResult
+    {
+        bool end = false;
+        @property bool empty() const { return end; }
+        @property dchar front(){ return 'a'; }
+        void popFront(){ end = true; }
+    }
+    LockingTextWriter w;
+    RetroResult r;
+    put(w, r);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.meta : AliasSeq;
+    import std.typecons : tuple;
+
+    static struct PutC(C)
+    {
+        string result;
+        void put(const(C) c) { result ~= to!string((&c)[0..1]); }
+    }
+    static struct PutS(C)
+    {
+        string result;
+        void put(const(C)[] s) { result ~= to!string(s); }
+    }
+    static struct PutSS(C)
+    {
+        string result;
+        void put(const(C)[][] ss)
+        {
+            foreach(s; ss)
+                result ~= to!string(s);
+        }
+    }
+
+    PutS!char p;
+    putChar(p, cast(dchar)'a');
+
+    //Source Char
+    foreach (SC; AliasSeq!(char, wchar, dchar))
+    {
+        SC ch = 'I';
+        dchar dh = '♥';
+        immutable(SC)[] s = "日本語！";
+        immutable(SC)[][] ss = ["日本語", "が", "好き", "ですか", "？"];
+
+        //Target Char
+        foreach (TC; AliasSeq!(char, wchar, dchar))
+        {
+            //Testing PutC and PutS
+            foreach (Type; AliasSeq!(PutC!TC, PutS!TC))
+            (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+                Type type;
+                auto sink = new Type();
+
+                //Testing put and sink
+                foreach (value ; tuple(type, sink))
+                {
+                    put(value, ch);
+                    assert(value.result == "I");
+                    put(value, dh);
+                    assert(value.result == "I♥");
+                    put(value, s);
+                    assert(value.result == "I♥日本語！");
+                    put(value, ss);
+                    assert(value.result == "I♥日本語！日本語が好きですか？");
+                }
+            }();
+        }
+    }
+}
+
+unittest
+{
+    static struct CharRange
+    {
+        char c;
+        enum empty = false;
+        void popFront(){};
+        ref char front() return @property
+        {
+            return c;
+        }
+    }
+    CharRange c;
+    put(c, cast(dchar)'H');
+    put(c, "hello"d);
+}
+
+unittest
+{
+    // issue 9823
+    const(char)[] r;
+    void delegate(const(char)[]) dg = (s) { r = s; };
+    put(dg, ["ABC"]);
+    assert(r == "ABC");
+}
+
+unittest
+{
+    // issue 10571
+    import std.format;
+    string buf;
+    formattedWrite((in char[] s) { buf ~= s; }, "%s", "hello");
+    assert(buf == "hello");
+}
+
+unittest
+{
+    import std.format;
+    import std.meta : AliasSeq;
+    struct PutC(C)
+    {
+        void put(C){}
+    }
+    struct PutS(C)
+    {
+        void put(const(C)[]){}
+    }
+    struct CallC(C)
+    {
+        void opCall(C){}
+    }
+    struct CallS(C)
+    {
+        void opCall(const(C)[]){}
+    }
+    struct FrontC(C)
+    {
+        enum empty = false;
+        auto front()@property{return C.init;}
+        void front(C)@property{}
+        void popFront(){}
+    }
+    struct FrontS(C)
+    {
+        enum empty = false;
+        auto front()@property{return C[].init;}
+        void front(const(C)[])@property{}
+        void popFront(){}
+    }
+    void foo()
+    {
+        foreach(C; AliasSeq!(char, wchar, dchar))
+        {
+            formattedWrite((C c){},        "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite((const(C)[]){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite(PutC!C(),       "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite(PutS!C(),       "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            CallC!C callC;
+            CallS!C callS;
+            formattedWrite(callC,          "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite(callS,          "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite(FrontC!C(),     "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+            formattedWrite(FrontS!C(),     "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+        }
+        formattedWrite((dchar[]).init,     "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d);
+    }
+}
+
+/+
+Returns $(D true) if $(D R) is a native output range for elements of type
+$(D E). An output range is defined functionally as a range that
+supports the operation $(D doPut(r, e)) as defined above. if $(D doPut(r, e))
+is valid, then $(D put(r,e)) will have the same behavior.
+
+The two guarantees isNativeOutputRange gives over the larger $(D isOutputRange)
+are:
+1: $(D e) is $(B exactly) what will be placed (not $(D [e]), for example).
+2: if $(D E) is a non $(empty) $(D InputRange), then placing $(D e) is
+guaranteed to not overflow the range.
+ +/
+package template isNativeOutputRange(R, E)
+{
+    enum bool isNativeOutputRange = is(typeof(
+    (inout int = 0)
+    {
+        R r = void;
+        E e;
+        doPut(r, e);
+    }));
+}
+
+///
+@safe unittest
+{
+    int[] r = new int[](4);
+    static assert(isInputRange!(int[]));
+    static assert( isNativeOutputRange!(int[], int));
+    static assert(!isNativeOutputRange!(int[], int[]));
+    static assert( isOutputRange!(int[], int[]));
+
+    if (!r.empty)
+        put(r, 1); //guaranteed to succeed
+    if (!r.empty)
+        put(r, [1, 2]); //May actually error out.
+}
+/++
+Returns $(D true) if $(D R) is an output range for elements of type
+$(D E). An output range is defined functionally as a range that
+supports the operation $(D put(r, e)) as defined above.
+ +/
+template isOutputRange(R, E)
+{
+    enum bool isOutputRange = is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+        E e = E.init;
+        put(r, e);
+    }));
+}
+
+///
+@safe unittest
+{
+    void myprint(in char[] s) { }
+    static assert(isOutputRange!(typeof(&myprint), char));
+
+    static assert(!isOutputRange!(char[], char));
+    static assert( isOutputRange!(dchar[], wchar));
+    static assert( isOutputRange!(dchar[], dchar));
+}
+
+@safe unittest
+{
+    import std.array;
+    import std.stdio : writeln;
+
+    auto app = appender!string();
+    string s;
+    static assert( isOutputRange!(Appender!string, string));
+    static assert( isOutputRange!(Appender!string*, string));
+    static assert(!isOutputRange!(Appender!string, int));
+    static assert(!isOutputRange!(wchar[], wchar));
+    static assert( isOutputRange!(dchar[], char));
+    static assert( isOutputRange!(dchar[], string));
+    static assert( isOutputRange!(dchar[], wstring));
+    static assert( isOutputRange!(dchar[], dstring));
+
+    static assert(!isOutputRange!(const(int)[], int));
+    static assert(!isOutputRange!(inout(int)[], int));
+}
+
+
+/**
+Returns $(D true) if $(D R) is a forward range. A forward range is an
+input range $(D r) that can save "checkpoints" by saving $(D r.save)
+to another value of type $(D R). Notable examples of input ranges that
+are $(I not) forward ranges are file/socket ranges; copying such a
+range will not save the position in the stream, and they most likely
+reuse an internal buffer as the entire stream does not sit in
+memory. Subsequently, advancing either the original or the copy will
+advance the stream, so the copies are not independent.
+
+The following code should compile for any forward range.
+
+----
+static assert(isInputRange!R);
+R r1;
+auto s1 = r1.save;
+static assert (is(typeof(s1) == R));
+----
+
+Saving a range is not duplicating it; in the example above, $(D r1)
+and $(D r2) still refer to the same underlying data. They just
+navigate that data independently.
+
+The semantics of a forward range (not checkable during compilation)
+are the same as for an input range, with the additional requirement
+that backtracking must be possible by saving a copy of the range
+object with $(D save) and using it later.
+ */
+template isForwardRange(R)
+{
+    enum bool isForwardRange = isInputRange!R && is(typeof(
+    (inout int = 0)
+    {
+        R r1 = R.init;
+        // NOTE: we cannot check typeof(r1.save) directly
+        // because typeof may not check the right type there, and
+        // because we want to ensure the range can be copied.
+        auto s1 = r1.save;
+        static assert (is(typeof(s1) == R));
+    }));
+}
+
+///
+@safe unittest
+{
+    static assert(!isForwardRange!(int));
+    static assert( isForwardRange!(int[]));
+    static assert( isForwardRange!(inout(int)[]));
+}
+
+@safe unittest
+{
+    // BUG 14544
+    struct R14544
+    {
+        int front() { return 0;}
+        void popFront() {}
+        bool empty() { return false; }
+        R14544 save() {return this;}
+    }
+
+    static assert( isForwardRange!R14544 );
+}
+
+/**
+Returns $(D true) if $(D R) is a bidirectional range. A bidirectional
+range is a forward range that also offers the primitives $(D back) and
+$(D popBack). The following code should compile for any bidirectional
+range.
+
+The semantics of a bidirectional range (not checkable during
+compilation) are assumed to be the following ($(D r) is an object of
+type $(D R)):
+
+$(UL $(LI $(D r.back) returns (possibly a reference to) the last
+element in the range. Calling $(D r.back) is allowed only if calling
+$(D r.empty) has, or would have, returned $(D false).))
+ */
+template isBidirectionalRange(R)
+{
+    enum bool isBidirectionalRange = isForwardRange!R && is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+        r.popBack();
+        auto t = r.back;
+        auto w = r.front;
+        static assert(is(typeof(t) == typeof(w)));
+    }));
+}
+
+///
+unittest
+{
+    alias R = int[];
+    R r = [0,1];
+    static assert(isForwardRange!R);           // is forward range
+    r.popBack();                               // can invoke popBack
+    auto t = r.back;                           // can get the back of the range
+    auto w = r.front;
+    static assert(is(typeof(t) == typeof(w))); // same type for front and back
+}
+
+@safe unittest
+{
+    struct A {}
+    struct B
+    {
+        void popFront();
+        @property bool empty();
+        @property int front();
+    }
+    struct C
+    {
+        @property bool empty();
+        @property C save();
+        void popFront();
+        @property int front();
+        void popBack();
+        @property int back();
+    }
+    static assert(!isBidirectionalRange!(A));
+    static assert(!isBidirectionalRange!(B));
+    static assert( isBidirectionalRange!(C));
+    static assert( isBidirectionalRange!(int[]));
+    static assert( isBidirectionalRange!(char[]));
+    static assert( isBidirectionalRange!(inout(int)[]));
+}
+
+/**
+Returns $(D true) if $(D R) is a random-access range. A random-access
+range is a bidirectional range that also offers the primitive $(D
+opIndex), OR an infinite forward range that offers $(D opIndex). In
+either case, the range must either offer $(D length) or be
+infinite. The following code should compile for any random-access
+range.
+
+The semantics of a random-access range (not checkable during
+compilation) are assumed to be the following ($(D r) is an object of
+type $(D R)): $(UL $(LI $(D r.opIndex(n)) returns a reference to the
+$(D n)th element in the range.))
+
+Although $(D char[]) and $(D wchar[]) (as well as their qualified
+versions including $(D string) and $(D wstring)) are arrays, $(D
+isRandomAccessRange) yields $(D false) for them because they use
+variable-length encodings (UTF-8 and UTF-16 respectively). These types
+are bidirectional ranges only.
+ */
+template isRandomAccessRange(R)
+{
+    enum bool isRandomAccessRange = is(typeof(
+    (inout int = 0)
+    {
+        static assert(isBidirectionalRange!R ||
+                      isForwardRange!R && isInfinite!R);
+        R r = R.init;
+        auto e = r[1];
+        auto f = r.front;
+        static assert(is(typeof(e) == typeof(f)));
+        static assert(!isNarrowString!R);
+        static assert(hasLength!R || isInfinite!R);
+
+        static if(is(typeof(r[$])))
+        {
+            static assert(is(typeof(f) == typeof(r[$])));
+
+            static if(!isInfinite!R)
+                static assert(is(typeof(f) == typeof(r[$ - 1])));
+        }
+    }));
+}
+
+///
+unittest
+{
+    alias R = int[];
+
+    // range is finite and bidirectional or infinite and forward.
+    static assert(isBidirectionalRange!R ||
+                  isForwardRange!R && isInfinite!R);
+
+    R r = [0,1];
+    auto e = r[1]; // can index
+    auto f = r.front;
+    static assert(is(typeof(e) == typeof(f))); // same type for indexed and front
+    static assert(!isNarrowString!R); // narrow strings cannot be indexed as ranges
+    static assert(hasLength!R || isInfinite!R); // must have length or be infinite
+
+    // $ must work as it does with arrays if opIndex works with $
+    static if(is(typeof(r[$])))
+    {
+        static assert(is(typeof(f) == typeof(r[$])));
+
+        // $ - 1 doesn't make sense with infinite ranges but needs to work
+        // with finite ones.
+        static if(!isInfinite!R)
+            static assert(is(typeof(f) == typeof(r[$ - 1])));
+    }
+}
+
+@safe unittest
+{
+    struct A {}
+    struct B
+    {
+        void popFront();
+        @property bool empty();
+        @property int front();
+    }
+    struct C
+    {
+        void popFront();
+        @property bool empty();
+        @property int front();
+        void popBack();
+        @property int back();
+    }
+    struct D
+    {
+        @property bool empty();
+        @property D save();
+        @property int front();
+        void popFront();
+        @property int back();
+        void popBack();
+        ref int opIndex(uint);
+        @property size_t length();
+        alias opDollar = length;
+        //int opSlice(uint, uint);
+    }
+    struct E
+    {
+        bool empty();
+        E save();
+        int front();
+        void popFront();
+        int back();
+        void popBack();
+        ref int opIndex(uint);
+        size_t length();
+        alias opDollar = length;
+        //int opSlice(uint, uint);
+    }
+    static assert(!isRandomAccessRange!(A));
+    static assert(!isRandomAccessRange!(B));
+    static assert(!isRandomAccessRange!(C));
+    static assert( isRandomAccessRange!(D));
+    static assert( isRandomAccessRange!(E));
+    static assert( isRandomAccessRange!(int[]));
+    static assert( isRandomAccessRange!(inout(int)[]));
+}
+
+@safe unittest
+{
+    // Test fix for bug 6935.
+    struct R
+    {
+        @disable this();
+
+        @property bool empty() const { return false; }
+        @property int front() const { return 0; }
+        void popFront() {}
+
+        @property R save() { return this; }
+
+        @property int back() const { return 0; }
+        void popBack(){}
+
+        int opIndex(size_t n) const { return 0; }
+        @property size_t length() const { return 0; }
+        alias opDollar = length;
+
+        void put(int e){  }
+    }
+    static assert(isInputRange!R);
+    static assert(isForwardRange!R);
+    static assert(isBidirectionalRange!R);
+    static assert(isRandomAccessRange!R);
+    static assert(isOutputRange!(R, int));
+}
+
+/**
+Returns $(D true) iff $(D R) is an input range that supports the
+$(D moveFront) primitive, as well as $(D moveBack) and $(D moveAt) if it's a
+bidirectional or random access range. These may be explicitly implemented, or
+may work via the default behavior of the module level functions $(D moveFront)
+and friends. The following code should compile for any range
+with mobile elements.
+
+----
+alias E = ElementType!R;
+R r;
+static assert(isInputRange!R);
+static assert(is(typeof(moveFront(r)) == E));
+static if (isBidirectionalRange!R)
+    static assert(is(typeof(moveBack(r)) == E));
+static if (isRandomAccessRange!R)
+    static assert(is(typeof(moveAt(r, 0)) == E));
+----
+ */
+template hasMobileElements(R)
+{
+    enum bool hasMobileElements = isInputRange!R && is(typeof(
+    (inout int = 0)
+    {
+        alias E = ElementType!R;
+        R r = R.init;
+        static assert(is(typeof(moveFront(r)) == E));
+        static if (isBidirectionalRange!R)
+            static assert(is(typeof(moveBack(r)) == E));
+        static if (isRandomAccessRange!R)
+            static assert(is(typeof(moveAt(r, 0)) == E));
+    }));
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : map;
+    import std.range : iota, repeat;
+
+    static struct HasPostblit
+    {
+        this(this) {}
+    }
+
+    auto nonMobile = map!"a"(repeat(HasPostblit.init));
+    static assert(!hasMobileElements!(typeof(nonMobile)));
+    static assert( hasMobileElements!(int[]));
+    static assert( hasMobileElements!(inout(int)[]));
+    static assert( hasMobileElements!(typeof(iota(1000))));
+
+    static assert( hasMobileElements!( string));
+    static assert( hasMobileElements!(dstring));
+    static assert( hasMobileElements!( char[]));
+    static assert( hasMobileElements!(dchar[]));
+}
+
+/**
+The element type of $(D R). $(D R) does not have to be a range. The
+element type is determined as the type yielded by $(D r.front) for an
+object $(D r) of type $(D R). For example, $(D ElementType!(T[])) is
+$(D T) if $(D T[]) isn't a narrow string; if it is, the element type is
+$(D dchar). If $(D R) doesn't have $(D front), $(D ElementType!R) is
+$(D void).
+ */
+template ElementType(R)
+{
+    static if (is(typeof(R.init.front.init) T))
+        alias ElementType = T;
+    else
+        alias ElementType = void;
+}
+
+///
+@safe unittest
+{
+    import std.range : iota;
+
+    // Standard arrays: returns the type of the elements of the array
+    static assert(is(ElementType!(int[]) == int));
+
+    // Accessing .front retrieves the decoded dchar
+    static assert(is(ElementType!(char[])  == dchar)); // rvalue
+    static assert(is(ElementType!(dchar[]) == dchar)); // lvalue
+
+    // Ditto
+    static assert(is(ElementType!(string) == dchar));
+    static assert(is(ElementType!(dstring) == immutable(dchar)));
+
+    // For ranges it gets the type of .front.
+    auto range = iota(0, 10);
+    static assert(is(ElementType!(typeof(range)) == int));
+}
+
+@safe unittest
+{
+    static assert(is(ElementType!(byte[]) == byte));
+    static assert(is(ElementType!(wchar[]) == dchar)); // rvalue
+    static assert(is(ElementType!(wstring) == dchar));
+}
+
+@safe unittest
+{
+    enum XYZ : string { a = "foo" }
+    auto x = XYZ.a.front;
+    immutable char[3] a = "abc";
+    int[] i;
+    void[] buf;
+    static assert(is(ElementType!(XYZ) == dchar));
+    static assert(is(ElementType!(typeof(a)) == dchar));
+    static assert(is(ElementType!(typeof(i)) == int));
+    static assert(is(ElementType!(typeof(buf)) == void));
+    static assert(is(ElementType!(inout(int)[]) == inout(int)));
+    static assert(is(ElementType!(inout(int[])) == inout(int)));
+}
+
+@safe unittest
+{
+    static assert(is(ElementType!(int[5]) == int));
+    static assert(is(ElementType!(int[0]) == int));
+    static assert(is(ElementType!(char[5]) == dchar));
+    static assert(is(ElementType!(char[0]) == dchar));
+}
+
+@safe unittest //11336
+{
+    static struct S
+    {
+        this(this) @disable;
+    }
+    static assert(is(ElementType!(S[]) == S));
+}
+
+@safe unittest // 11401
+{
+    // ElementType should also work for non-@propety 'front'
+    struct E { ushort id; }
+    struct R
+    {
+        E front() { return E.init; }
+    }
+    static assert(is(ElementType!R == E));
+}
+
+/**
+The encoding element type of $(D R). For narrow strings ($(D char[]),
+$(D wchar[]) and their qualified variants including $(D string) and
+$(D wstring)), $(D ElementEncodingType) is the character type of the
+string. For all other types, $(D ElementEncodingType) is the same as
+$(D ElementType).
+ */
+template ElementEncodingType(R)
+{
+    static if (is(StringTypeOf!R) && is(R : E[], E))
+        alias ElementEncodingType = E;
+    else
+        alias ElementEncodingType = ElementType!R;
+}
+
+///
+@safe unittest
+{
+    import std.range : iota;
+    // internally the range stores the encoded type
+    static assert(is(ElementEncodingType!(char[])  == char));
+
+    static assert(is(ElementEncodingType!(wstring) == immutable(wchar)));
+
+    static assert(is(ElementEncodingType!(byte[]) == byte));
+
+    auto range = iota(0, 10);
+    static assert(is(ElementEncodingType!(typeof(range)) == int));
+}
+
+@safe unittest
+{
+    static assert(is(ElementEncodingType!(wchar[]) == wchar));
+    static assert(is(ElementEncodingType!(dchar[]) == dchar));
+    static assert(is(ElementEncodingType!(string)  == immutable(char)));
+    static assert(is(ElementEncodingType!(dstring) == immutable(dchar)));
+    static assert(is(ElementEncodingType!(int[])  == int));
+}
+
+@safe unittest
+{
+    enum XYZ : string { a = "foo" }
+    auto x = XYZ.a.front;
+    immutable char[3] a = "abc";
+    int[] i;
+    void[] buf;
+    static assert(is(ElementType!(XYZ) : dchar));
+    static assert(is(ElementEncodingType!(char[]) == char));
+    static assert(is(ElementEncodingType!(string) == immutable char));
+    static assert(is(ElementType!(typeof(a)) : dchar));
+    static assert(is(ElementType!(typeof(i)) == int));
+    static assert(is(ElementEncodingType!(typeof(i)) == int));
+    static assert(is(ElementType!(typeof(buf)) : void));
+
+    static assert(is(ElementEncodingType!(inout char[]) : inout(char)));
+}
+
+@safe unittest
+{
+    static assert(is(ElementEncodingType!(int[5]) == int));
+    static assert(is(ElementEncodingType!(int[0]) == int));
+    static assert(is(ElementEncodingType!(char[5]) == char));
+    static assert(is(ElementEncodingType!(char[0]) == char));
+}
+
+/**
+Returns $(D true) if $(D R) is an input range and has swappable
+elements. The following code should compile for any range
+with swappable elements.
+
+----
+R r;
+static assert(isInputRange!R);
+swap(r.front, r.front);
+static if (isBidirectionalRange!R) swap(r.back, r.front);
+static if (isRandomAccessRange!R) swap(r[], r.front);
+----
+ */
+template hasSwappableElements(R)
+{
+    import std.algorithm : swap;
+    enum bool hasSwappableElements = isInputRange!R && is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+        swap(r.front, r.front);
+        static if (isBidirectionalRange!R) swap(r.back, r.front);
+        static if (isRandomAccessRange!R) swap(r[0], r.front);
+    }));
+}
+
+///
+@safe unittest
+{
+    static assert(!hasSwappableElements!(const int[]));
+    static assert(!hasSwappableElements!(const(int)[]));
+    static assert(!hasSwappableElements!(inout(int)[]));
+    static assert( hasSwappableElements!(int[]));
+
+    static assert(!hasSwappableElements!( string));
+    static assert(!hasSwappableElements!(dstring));
+    static assert(!hasSwappableElements!( char[]));
+    static assert( hasSwappableElements!(dchar[]));
+}
+
+/**
+Returns $(D true) if $(D R) is an input range and has mutable
+elements. The following code should compile for any range
+with assignable elements.
+
+----
+R r;
+static assert(isInputRange!R);
+r.front = r.front;
+static if (isBidirectionalRange!R) r.back = r.front;
+static if (isRandomAccessRange!R) r[0] = r.front;
+----
+ */
+template hasAssignableElements(R)
+{
+    enum bool hasAssignableElements = isInputRange!R && is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+        r.front = r.front;
+        static if (isBidirectionalRange!R) r.back = r.front;
+        static if (isRandomAccessRange!R) r[0] = r.front;
+    }));
+}
+
+///
+@safe unittest
+{
+    static assert(!hasAssignableElements!(const int[]));
+    static assert(!hasAssignableElements!(const(int)[]));
+    static assert( hasAssignableElements!(int[]));
+    static assert(!hasAssignableElements!(inout(int)[]));
+
+    static assert(!hasAssignableElements!( string));
+    static assert(!hasAssignableElements!(dstring));
+    static assert(!hasAssignableElements!( char[]));
+    static assert( hasAssignableElements!(dchar[]));
+}
+
+/**
+Tests whether the range $(D R) has lvalue elements. These are defined as
+elements that can be passed by reference and have their address taken.
+The following code should compile for any range with lvalue elements.
+----
+void passByRef(ref ElementType!R stuff);
+...
+static assert(isInputRange!R);
+passByRef(r.front);
+static if (isBidirectionalRange!R) passByRef(r.back);
+static if (isRandomAccessRange!R) passByRef(r[0]);
+----
+*/
+template hasLvalueElements(R)
+{
+    enum bool hasLvalueElements = isInputRange!R && is(typeof(
+    (inout int = 0)
+    {
+        void checkRef(ref ElementType!R stuff);
+        R r = R.init;
+
+        checkRef(r.front);
+        static if (isBidirectionalRange!R) checkRef(r.back);
+        static if (isRandomAccessRange!R) checkRef(r[0]);
+    }));
+}
+
+///
+@safe unittest
+{
+    import std.range : iota, chain;
+
+    static assert( hasLvalueElements!(int[]));
+    static assert( hasLvalueElements!(const(int)[]));
+    static assert( hasLvalueElements!(inout(int)[]));
+    static assert( hasLvalueElements!(immutable(int)[]));
+    static assert(!hasLvalueElements!(typeof(iota(3))));
+
+    static assert(!hasLvalueElements!( string));
+    static assert( hasLvalueElements!(dstring));
+    static assert(!hasLvalueElements!( char[]));
+    static assert( hasLvalueElements!(dchar[]));
+
+    auto c = chain([1, 2, 3], [4, 5, 6]);
+    static assert( hasLvalueElements!(typeof(c)));
+}
+
+@safe unittest
+{
+    // bugfix 6336
+    struct S { immutable int value; }
+    static assert( isInputRange!(S[]));
+    static assert( hasLvalueElements!(S[]));
+}
+
+/**
+Returns $(D true) if $(D R) has a $(D length) member that returns an
+integral type. $(D R) does not have to be a range. Note that $(D
+length) is an optional primitive as no range must implement it. Some
+ranges do not store their length explicitly, some cannot compute it
+without actually exhausting the range (e.g. socket streams), and some
+other ranges may be infinite.
+
+Although narrow string types ($(D char[]), $(D wchar[]), and their
+qualified derivatives) do define a $(D length) property, $(D
+hasLength) yields $(D false) for them. This is because a narrow
+string's length does not reflect the number of characters, but instead
+the number of encoding units, and as such is not useful with
+range-oriented algorithms.
+ */
+template hasLength(R)
+{
+    enum bool hasLength = !isNarrowString!R && is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+        ulong l = r.length;
+    }));
+}
+
+///
+@safe unittest
+{
+    static assert(!hasLength!(char[]));
+    static assert( hasLength!(int[]));
+    static assert( hasLength!(inout(int)[]));
+
+    struct A { ulong length; }
+    struct B { size_t length() { return 0; } }
+    struct C { @property size_t length() { return 0; } }
+    static assert( hasLength!(A));
+    static assert( hasLength!(B));
+    static assert( hasLength!(C));
+}
+
+/**
+Returns $(D true) if $(D R) is an infinite input range. An
+infinite input range is an input range that has a statically-defined
+enumerated member called $(D empty) that is always $(D false),
+for example:
+
+----
+struct MyInfiniteRange
+{
+    enum bool empty = false;
+    ...
+}
+----
+ */
+
+template isInfinite(R)
+{
+    static if (isInputRange!R && __traits(compiles, { enum e = R.empty; }))
+        enum bool isInfinite = !R.empty;
+    else
+        enum bool isInfinite = false;
+}
+
+///
+@safe unittest
+{
+    import std.range : Repeat;
+    static assert(!isInfinite!(int[]));
+    static assert( isInfinite!(Repeat!(int)));
+}
+
+/**
+Returns $(D true) if $(D R) offers a slicing operator with integral boundaries
+that returns a forward range type.
+
+For finite ranges, the result of $(D opSlice) must be of the same type as the
+original range type. If the range defines $(D opDollar), then it must support
+subtraction.
+
+For infinite ranges, when $(I not) using $(D opDollar), the result of
+$(D opSlice) must be the result of $(LREF take) or $(LREF takeExactly) on the
+original range (they both return the same type for infinite ranges). However,
+when using $(D opDollar), the result of $(D opSlice) must be that of the
+original range type.
+
+The following code must compile for $(D hasSlicing) to be $(D true):
+
+----
+R r = void;
+
+static if(isInfinite!R)
+    typeof(take(r, 1)) s = r[1 .. 2];
+else
+{
+    static assert(is(typeof(r[1 .. 2]) == R));
+    R s = r[1 .. 2];
+}
+
+s = r[1 .. 2];
+
+static if(is(typeof(r[0 .. $])))
+{
+    static assert(is(typeof(r[0 .. $]) == R));
+    R t = r[0 .. $];
+    t = r[0 .. $];
+
+    static if(!isInfinite!R)
+    {
+        static assert(is(typeof(r[0 .. $ - 1]) == R));
+        R u = r[0 .. $ - 1];
+        u = r[0 .. $ - 1];
+    }
+}
+
+static assert(isForwardRange!(typeof(r[1 .. 2])));
+static assert(hasLength!(typeof(r[1 .. 2])));
+----
+ */
+template hasSlicing(R)
+{
+    enum bool hasSlicing = isForwardRange!R && !isNarrowString!R && is(typeof(
+    (inout int = 0)
+    {
+        R r = R.init;
+
+        static if(isInfinite!R)
+        {
+            typeof(r[1 .. 1]) s = r[1 .. 2];
+        }
+        else
+        {
+            static assert(is(typeof(r[1 .. 2]) == R));
+            R s = r[1 .. 2];
+        }
+
+        s = r[1 .. 2];
+
+        static if(is(typeof(r[0 .. $])))
+        {
+            static assert(is(typeof(r[0 .. $]) == R));
+            R t = r[0 .. $];
+            t = r[0 .. $];
+
+            static if(!isInfinite!R)
+            {
+                static assert(is(typeof(r[0 .. $ - 1]) == R));
+                R u = r[0 .. $ - 1];
+                u = r[0 .. $ - 1];
+            }
+        }
+
+        static assert(isForwardRange!(typeof(r[1 .. 2])));
+        static assert(hasLength!(typeof(r[1 .. 2])));
+    }));
+}
+
+///
+@safe unittest
+{
+    import std.range : takeExactly;
+    static assert( hasSlicing!(int[]));
+    static assert( hasSlicing!(const(int)[]));
+    static assert(!hasSlicing!(const int[]));
+    static assert( hasSlicing!(inout(int)[]));
+    static assert(!hasSlicing!(inout int []));
+    static assert( hasSlicing!(immutable(int)[]));
+    static assert(!hasSlicing!(immutable int[]));
+    static assert(!hasSlicing!string);
+    static assert( hasSlicing!dstring);
+
+    enum rangeFuncs = "@property int front();" ~
+                      "void popFront();" ~
+                      "@property bool empty();" ~
+                      "@property auto save() { return this; }" ~
+                      "@property size_t length();";
+
+    struct A { mixin(rangeFuncs); int opSlice(size_t, size_t); }
+    struct B { mixin(rangeFuncs); B opSlice(size_t, size_t); }
+    struct C { mixin(rangeFuncs); @disable this(); C opSlice(size_t, size_t); }
+    struct D { mixin(rangeFuncs); int[] opSlice(size_t, size_t); }
+    static assert(!hasSlicing!(A));
+    static assert( hasSlicing!(B));
+    static assert( hasSlicing!(C));
+    static assert(!hasSlicing!(D));
+
+    struct InfOnes
+    {
+        enum empty = false;
+        void popFront() {}
+        @property int front() { return 1; }
+        @property InfOnes save() { return this; }
+        auto opSlice(size_t i, size_t j) { return takeExactly(this, j - i); }
+        auto opSlice(size_t i, Dollar d) { return this; }
+
+        struct Dollar {}
+        Dollar opDollar() const { return Dollar.init; }
+    }
+
+    static assert(hasSlicing!InfOnes);
+}
+
+/**
+This is a best-effort implementation of $(D length) for any kind of
+range.
+
+If $(D hasLength!Range), simply returns $(D range.length) without
+checking $(D upTo) (when specified).
+
+Otherwise, walks the range through its length and returns the number
+of elements seen. Performes $(BIGOH n) evaluations of $(D range.empty)
+and $(D range.popFront()), where $(D n) is the effective length of $(D
+range).
+
+The $(D upTo) parameter is useful to "cut the losses" in case
+the interest is in seeing whether the range has at least some number
+of elements. If the parameter $(D upTo) is specified, stops if $(D
+upTo) steps have been taken and returns $(D upTo).
+
+Infinite ranges are compatible, provided the parameter $(D upTo) is
+specified, in which case the implementation simply returns upTo.
+ */
+auto walkLength(Range)(Range range)
+    if (isInputRange!Range && !isInfinite!Range)
+{
+    static if (hasLength!Range)
+        return range.length;
+    else
+    {
+        size_t result;
+        for ( ; !range.empty ; range.popFront() )
+            ++result;
+        return result;
+    }
+}
+/// ditto
+auto walkLength(Range)(Range range, const size_t upTo)
+    if (isInputRange!Range)
+{
+    static if (hasLength!Range)
+        return range.length;
+    else static if (isInfinite!Range)
+        return upTo;
+    else
+    {
+        size_t result;
+        for ( ; result < upTo && !range.empty ; range.popFront() )
+            ++result;
+        return result;
+    }
+}
+
+@safe unittest
+{
+    import std.algorithm : filter;
+    import std.range : recurrence, take;
+
+    //hasLength Range
+    int[] a = [ 1, 2, 3 ];
+    assert(walkLength(a) == 3);
+    assert(walkLength(a, 0) == 3);
+    assert(walkLength(a, 2) == 3);
+    assert(walkLength(a, 4) == 3);
+
+    //Forward Range
+    auto b = filter!"true"([1, 2, 3, 4]);
+    assert(b.walkLength() == 4);
+    assert(b.walkLength(0) == 0);
+    assert(b.walkLength(2) == 2);
+    assert(b.walkLength(4) == 4);
+    assert(b.walkLength(6) == 4);
+
+    //Infinite Range
+    auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1);
+    assert(!__traits(compiles, fibs.walkLength()));
+    assert(fibs.take(10).walkLength() == 10);
+    assert(fibs.walkLength(55) == 55);
+}
+
+/**
+    Eagerly advances $(D r) itself (not a copy) up to $(D n) times (by
+    calling $(D r.popFront)). $(D popFrontN) takes $(D r) by $(D ref),
+    so it mutates the original range. Completes in $(BIGOH 1) steps for ranges
+    that support slicing and have length.
+    Completes in $(BIGOH n) time for all other ranges.
+
+    Returns:
+    How much $(D r) was actually advanced, which may be less than $(D n) if
+    $(D r) did not have at least $(D n) elements.
+
+    $(D popBackN) will behave the same but instead removes elements from
+    the back of the (bidirectional) range instead of the front.
+*/
+size_t popFrontN(Range)(ref Range r, size_t n)
+    if (isInputRange!Range)
+{
+    static if (hasLength!Range)
+    {
+        n = cast(size_t) (n < r.length ? n : r.length);
+    }
+
+    static if (hasSlicing!Range && is(typeof(r = r[n .. $])))
+    {
+        r = r[n .. $];
+    }
+    else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
+    {
+        r = r[n .. r.length];
+    }
+    else
+    {
+        static if (hasLength!Range)
+        {
+            foreach (i; 0 .. n)
+                r.popFront();
+        }
+        else
+        {
+            foreach (i; 0 .. n)
+            {
+                if (r.empty) return i;
+                r.popFront();
+            }
+        }
+    }
+    return n;
+}
+
+/// ditto
+size_t popBackN(Range)(ref Range r, size_t n)
+    if (isBidirectionalRange!Range)
+{
+    static if (hasLength!Range)
+    {
+        n = cast(size_t) (n < r.length ? n : r.length);
+    }
+
+    static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n])))
+    {
+        r = r[0 .. $ - n];
+    }
+    else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
+    {
+        r = r[0 .. r.length - n];
+    }
+    else
+    {
+        static if (hasLength!Range)
+        {
+            foreach (i; 0 .. n)
+                r.popBack();
+        }
+        else
+        {
+            foreach (i; 0 .. n)
+            {
+                if (r.empty) return i;
+                r.popBack();
+            }
+        }
+    }
+    return n;
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    a.popFrontN(2);
+    assert(a == [ 3, 4, 5 ]);
+    a.popFrontN(7);
+    assert(a == [ ]);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.range : iota;
+    auto LL = iota(1L, 7L);
+    auto r = popFrontN(LL, 2);
+    assert(equal(LL, [3L, 4L, 5L, 6L]));
+    assert(r == 2);
+}
+
+///
+@safe unittest
+{
+    int[] a = [ 1, 2, 3, 4, 5 ];
+    a.popBackN(2);
+    assert(a == [ 1, 2, 3 ]);
+    a.popBackN(7);
+    assert(a == [ ]);
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : equal;
+    import std.range : iota;
+    auto LL = iota(1L, 7L);
+    auto r = popBackN(LL, 2);
+    assert(equal(LL, [1L, 2L, 3L, 4L]));
+    assert(r == 2);
+}
+
+/**
+    Eagerly advances $(D r) itself (not a copy) exactly $(D n) times (by
+    calling $(D r.popFront)). $(D popFrontExactly) takes $(D r) by $(D ref),
+    so it mutates the original range. Completes in $(BIGOH 1) steps for ranges
+    that support slicing, and have either length or are infinite.
+    Completes in $(BIGOH n) time for all other ranges.
+
+    Note: Unlike $(LREF popFrontN), $(D popFrontExactly) will assume that the
+    range holds at least $(D n) elements. This makes $(D popFrontExactly)
+    faster than $(D popFrontN), but it also means that if $(D range) does
+    not contain at least $(D n) elements, it will attempt to call $(D popFront)
+    on an empty range, which is undefined behavior. So, only use
+    $(D popFrontExactly) when it is guaranteed that $(D range) holds at least
+    $(D n) elements.
+
+    $(D popBackExactly) will behave the same but instead removes elements from
+    the back of the (bidirectional) range instead of the front.
+*/
+void popFrontExactly(Range)(ref Range r, size_t n)
+    if (isInputRange!Range)
+{
+    static if (hasLength!Range)
+        assert(n <= r.length, "range is smaller than amount of items to pop");
+
+    static if (hasSlicing!Range && is(typeof(r = r[n .. $])))
+        r = r[n .. $];
+    else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
+        r = r[n .. r.length];
+    else
+        foreach (i; 0 .. n)
+            r.popFront();
+}
+
+/// ditto
+void popBackExactly(Range)(ref Range r, size_t n)
+    if (isBidirectionalRange!Range)
+{
+    static if (hasLength!Range)
+        assert(n <= r.length, "range is smaller than amount of items to pop");
+
+    static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n])))
+        r = r[0 .. $ - n];
+    else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar.
+        r = r[0 .. r.length - n];
+    else
+        foreach (i; 0 .. n)
+            r.popBack();
+}
+
+///
+@safe unittest
+{
+    import std.algorithm : filterBidirectional, equal;
+
+    auto a = [1, 2, 3];
+    a.popFrontExactly(1);
+    assert(a == [2, 3]);
+    a.popBackExactly(1);
+    assert(a == [2]);
+
+    string s = "日本語";
+    s.popFrontExactly(1);
+    assert(s == "本語");
+    s.popBackExactly(1);
+    assert(s == "本");
+
+    auto bd = filterBidirectional!"true"([1, 2, 3]);
+    bd.popFrontExactly(1);
+    assert(bd.equal([2, 3]));
+    bd.popBackExactly(1);
+    assert(bd.equal([2]));
+}
+
+/**
+   Moves the front of $(D r) out and returns it. Leaves $(D r.front) in a
+   destroyable state that does not allocate any resources (usually equal
+   to its $(D .init) value).
+*/
+ElementType!R moveFront(R)(R r)
+{
+    static if (is(typeof(&r.moveFront))) {
+        return r.moveFront();
+    } else static if (!hasElaborateCopyConstructor!(ElementType!R)) {
+        return r.front;
+    } else static if (is(typeof(&(r.front())) == ElementType!R*)) {
+        import std.algorithm : move;
+        return move(r.front);
+    } else {
+        static assert(0,
+                "Cannot move front of a range with a postblit and an rvalue front.");
+    }
+}
+
+///
+@safe unittest
+{
+    auto a = [ 1, 2, 3 ];
+    assert(moveFront(a) == 1);
+
+    // define a perfunctory input range
+    struct InputRange
+    {
+        @property bool empty() { return false; }
+        @property int front() { return 42; }
+        void popFront() {}
+        int moveFront() { return 43; }
+    }
+    InputRange r;
+    assert(moveFront(r) == 43);
+}
+
+@safe unittest
+{
+    struct R
+    {
+        @property ref int front() { static int x = 42; return x; }
+        this(this){}
+    }
+    R r;
+    assert(moveFront(r) == 42);
+}
+
+/**
+   Moves the back of $(D r) out and returns it. Leaves $(D r.back) in a
+   destroyable state that does not allocate any resources (usually equal
+   to its $(D .init) value).
+*/
+ElementType!R moveBack(R)(R r)
+{
+    static if (is(typeof(&r.moveBack))) {
+        return r.moveBack();
+    } else static if (!hasElaborateCopyConstructor!(ElementType!R)) {
+        return r.back;
+    } else static if (is(typeof(&(r.back())) == ElementType!R*)) {
+        import std.algorithm : move;
+        return move(r.back);
+    } else {
+        static assert(0,
+                "Cannot move back of a range with a postblit and an rvalue back.");
+    }
+}
+
+///
+@safe unittest
+{
+    struct TestRange
+    {
+        int payload = 5;
+        @property bool empty() { return false; }
+        @property TestRange save() { return this; }
+        @property ref int front() return { return payload; }
+        @property ref int back() return { return payload; }
+        void popFront() { }
+        void popBack() { }
+    }
+    static assert(isBidirectionalRange!TestRange);
+    TestRange r;
+    auto x = moveBack(r);
+    assert(x == 5);
+}
+
+/**
+   Moves element at index $(D i) of $(D r) out and returns it. Leaves $(D
+   r.front) in a destroyable state that does not allocate any resources
+   (usually equal to its $(D .init) value).
+*/
+ElementType!R moveAt(R, I)(R r, I i) if (isIntegral!I)
+{
+    static if (is(typeof(&r.moveAt))) {
+        return r.moveAt(i);
+    } else static if (!hasElaborateCopyConstructor!(ElementType!(R))) {
+        return r[i];
+    } else static if (is(typeof(&r[i]) == ElementType!R*)) {
+        import std.algorithm : move;
+        return move(r[i]);
+    } else {
+        static assert(0,
+                "Cannot move element of a range with a postblit and rvalue elements.");
+    }
+}
+
+///
+@safe unittest
+{
+    auto a = [1,2,3,4];
+    foreach(idx, it; a)
+    {
+        assert(it == moveAt(a, idx));
+    }
+}
+
+@safe unittest
+{
+    import std.internal.test.dummyrange;
+
+    foreach(DummyType; AllDummyRanges) {
+        auto d = DummyType.init;
+        assert(moveFront(d) == 1);
+
+        static if (isBidirectionalRange!DummyType) {
+            assert(moveBack(d) == 10);
+        }
+
+        static if (isRandomAccessRange!DummyType) {
+            assert(moveAt(d, 2) == 3);
+        }
+    }
+}
+
+/**
+Implements the range interface primitive $(D empty) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.empty) is
+equivalent to $(D empty(array)).
+ */
+
+@property bool empty(T)(in T[] a) @safe pure nothrow @nogc
+{
+    return !a.length;
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [ 1, 2, 3 ];
+    assert(!a.empty);
+    assert(a[3 .. $].empty);
+}
+
+/**
+Implements the range interface primitive $(D save) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.save) is
+equivalent to $(D save(array)). The function does not duplicate the
+content of the array, it simply returns its argument.
+ */
+
+@property T[] save(T)(T[] a) @safe pure nothrow @nogc
+{
+    return a;
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [ 1, 2, 3 ];
+    auto b = a.save;
+    assert(b is a);
+}
+
+/**
+Implements the range interface primitive $(D popFront) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.popFront) is
+equivalent to $(D popFront(array)). For $(GLOSSARY narrow strings),
+$(D popFront) automatically advances to the next $(GLOSSARY code
+point).
+*/
+
+void popFront(T)(ref T[] a) @safe pure nothrow @nogc
+if (!isNarrowString!(T[]) && !is(T[] == void[]))
+{
+    assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof);
+    a = a[1 .. $];
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [ 1, 2, 3 ];
+    a.popFront();
+    assert(a == [ 2, 3 ]);
+}
+
+version(unittest)
+{
+    static assert(!is(typeof({          int[4] a; popFront(a); })));
+    static assert(!is(typeof({ immutable int[] a; popFront(a); })));
+    static assert(!is(typeof({          void[] a; popFront(a); })));
+}
+
+// Specialization for narrow strings. The necessity of
+void popFront(C)(ref C[] str) @trusted pure nothrow
+if (isNarrowString!(C[]))
+{
+    assert(str.length, "Attempting to popFront() past the end of an array of " ~ C.stringof);
+
+    static if(is(Unqual!C == char))
+    {
+        immutable c = str[0];
+        if(c < 0x80)
+        {
+            //ptr is used to avoid unnnecessary bounds checking.
+            str = str.ptr[1 .. str.length];
+        }
+        else
+        {
+             import core.bitop : bsr;
+             auto msbs = 7 - bsr(~c);
+             if((msbs < 2) | (msbs > 6))
+             {
+                 //Invalid UTF-8
+                 msbs = 1;
+             }
+             str = str[msbs .. $];
+        }
+    }
+    else static if(is(Unqual!C == wchar))
+    {
+        immutable u = str[0];
+        str = str[1 + (u >= 0xD800 && u <= 0xDBFF) .. $];
+    }
+    else static assert(0, "Bad template constraint.");
+}
+
+@safe pure unittest
+{
+    import std.meta : AliasSeq;
+
+    foreach(S; AliasSeq!(string, wstring, dstring))
+    {
+        S s = "\xC2\xA9hello";
+        s.popFront();
+        assert(s == "hello");
+
+        S str = "hello\U00010143\u0100\U00010143";
+        foreach(dchar c; ['h', 'e', 'l', 'l', 'o', '\U00010143', '\u0100', '\U00010143'])
+        {
+            assert(str.front == c);
+            str.popFront();
+        }
+        assert(str.empty);
+
+        static assert(!is(typeof({          immutable S a; popFront(a); })));
+        static assert(!is(typeof({ typeof(S.init[0])[4] a; popFront(a); })));
+    }
+
+    C[] _eatString(C)(C[] str)
+    {
+        while(!str.empty)
+            str.popFront();
+
+        return str;
+    }
+    enum checkCTFE = _eatString("ウェブサイト@La_Verité.com");
+    static assert(checkCTFE.empty);
+    enum checkCTFEW = _eatString("ウェブサイト@La_Verité.com"w);
+    static assert(checkCTFEW.empty);
+}
+
+/**
+Implements the range interface primitive $(D popBack) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.popBack) is
+equivalent to $(D popBack(array)). For $(GLOSSARY narrow strings), $(D
+popFront) automatically eliminates the last $(GLOSSARY code point).
+*/
+
+void popBack(T)(ref T[] a) @safe pure nothrow @nogc
+if (!isNarrowString!(T[]) && !is(T[] == void[]))
+{
+    assert(a.length);
+    a = a[0 .. $ - 1];
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto a = [ 1, 2, 3 ];
+    a.popBack();
+    assert(a == [ 1, 2 ]);
+}
+
+version(unittest)
+{
+    static assert(!is(typeof({ immutable int[] a; popBack(a); })));
+    static assert(!is(typeof({          int[4] a; popBack(a); })));
+    static assert(!is(typeof({          void[] a; popBack(a); })));
+}
+
+// Specialization for arrays of char
+void popBack(T)(ref T[] a) @safe pure
+if (isNarrowString!(T[]))
+{
+    import std.utf: strideBack;
+    assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof);
+    a = a[0 .. $ - strideBack(a, $)];
+}
+
+@safe pure unittest
+{
+    import std.meta : AliasSeq;
+
+    foreach(S; AliasSeq!(string, wstring, dstring))
+    {
+        S s = "hello\xE2\x89\xA0";
+        s.popBack();
+        assert(s == "hello");
+        S s3 = "\xE2\x89\xA0";
+        auto c = s3.back;
+        assert(c == cast(dchar)'\u2260');
+        s3.popBack();
+        assert(s3 == "");
+
+        S str = "\U00010143\u0100\U00010143hello";
+        foreach(dchar ch; ['o', 'l', 'l', 'e', 'h', '\U00010143', '\u0100', '\U00010143'])
+        {
+            assert(str.back == ch);
+            str.popBack();
+        }
+        assert(str.empty);
+
+        static assert(!is(typeof({          immutable S a; popBack(a); })));
+        static assert(!is(typeof({ typeof(S.init[0])[4] a; popBack(a); })));
+    }
+}
+
+/**
+Implements the range interface primitive $(D front) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.front) is
+equivalent to $(D front(array)). For $(GLOSSARY narrow strings), $(D
+front) automatically returns the first $(GLOSSARY code point) as a $(D
+dchar).
+*/
+@property ref T front(T)(T[] a) @safe pure nothrow @nogc
+if (!isNarrowString!(T[]) && !is(T[] == void[]))
+{
+    assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof);
+    return a[0];
+}
+
+///
+@safe pure nothrow unittest
+{
+    int[] a = [ 1, 2, 3 ];
+    assert(a.front == 1);
+}
+
+@safe pure nothrow unittest
+{
+    auto a = [ 1, 2 ];
+    a.front = 4;
+    assert(a.front == 4);
+    assert(a == [ 4, 2 ]);
+
+    immutable b = [ 1, 2 ];
+    assert(b.front == 1);
+
+    int[2] c = [ 1, 2 ];
+    assert(c.front == 1);
+}
+
+@property dchar front(T)(T[] a) @safe pure if (isNarrowString!(T[]))
+{
+    import std.utf : decode;
+    assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof);
+    size_t i = 0;
+    return decode(a, i);
+}
+
+/**
+Implements the range interface primitive $(D back) for built-in
+arrays. Due to the fact that nonmember functions can be called with
+the first argument using the dot notation, $(D array.back) is
+equivalent to $(D back(array)). For $(GLOSSARY narrow strings), $(D
+back) automatically returns the last $(GLOSSARY code point) as a $(D
+dchar).
+*/
+@property ref T back(T)(T[] a) @safe pure nothrow @nogc
+if (!isNarrowString!(T[]))
+{
+    assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof);
+    return a[$ - 1];
+}
+
+///
+@safe pure nothrow unittest
+{
+    int[] a = [ 1, 2, 3 ];
+    assert(a.back == 3);
+    a.back += 4;
+    assert(a.back == 7);
+}
+
+@safe pure nothrow unittest
+{
+    immutable b = [ 1, 2, 3 ];
+    assert(b.back == 3);
+
+    int[3] c = [ 1, 2, 3 ];
+    assert(c.back == 3);
+}
+
+// Specialization for strings
+@property dchar back(T)(T[] a) @safe pure if (isNarrowString!(T[]))
+{
+    import std.utf : decode, strideBack;
+    assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof);
+    size_t i = a.length - strideBack(a, a.length);
+    return decode(a, i);
+}
diff --git a/libphobos/src/std/regex/internal/backtracking.d b/libphobos/src/std/regex/internal/backtracking.d
new file mode 100644
index 00000000000..7527787f159
--- /dev/null
+++ b/libphobos/src/std/regex/internal/backtracking.d
@@ -0,0 +1,1406 @@
+/*
+    Implementation of backtracking std.regex engine.
+    Contains both compile-time and run-time versions.
+*/
+module std.regex.internal.backtracking;
+
+package(std.regex):
+
+import std.regex.internal.ir;
+import std.range, std.typecons, std.traits, core.stdc.stdlib;
+
+/+
+    BacktrackingMatcher implements backtracking scheme of matching
+    regular expressions.
++/
+template BacktrackingMatcher(bool CTregex)
+{
+    @trusted struct BacktrackingMatcher(Char, Stream = Input!Char)
+        if(is(Char : dchar))
+    {
+        alias DataIndex = Stream.DataIndex;
+        struct State
+        {//top bit in pc is set if saved along with matches
+            DataIndex index;
+            uint pc, counter, infiniteNesting;
+        }
+        static assert(State.sizeof % size_t.sizeof == 0);
+        enum stateSize = State.sizeof / size_t.sizeof;
+        enum initialStack = 1<<11; // items in a block of segmented stack
+        alias String = const(Char)[];
+        alias RegEx = Regex!Char;
+        alias MatchFn = bool function (ref BacktrackingMatcher!(Char, Stream));
+        RegEx re;      //regex program
+        static if(CTregex)
+            MatchFn nativeFn; //native code for that program
+        //Stream state
+        Stream s;
+        DataIndex index;
+        dchar front;
+        bool exhausted;
+        //backtracking machine state
+        uint pc, counter;
+        DataIndex lastState = 0;    //top of state stack
+        DataIndex[] trackers;
+        static if(!CTregex)
+            uint infiniteNesting;
+        size_t[] memory;
+        //local slice of matches, global for backref
+        Group!DataIndex[] matches, backrefed;
+
+        static if(__traits(hasMember,Stream, "search"))
+        {
+            enum kicked = true;
+        }
+        else
+            enum kicked = false;
+
+        static size_t initialMemory(const ref RegEx re)
+        {
+            return (re.ngroup+1)*DataIndex.sizeof //trackers
+                + stackSize(re)*size_t.sizeof;
+        }
+
+        static size_t stackSize(const ref RegEx re)
+        {
+            return initialStack*(stateSize + re.ngroup*(Group!DataIndex).sizeof/size_t.sizeof)+1;
+        }
+
+        @property bool atStart(){ return index == 0; }
+
+        @property bool atEnd(){ return index == s.lastIndex && s.atEnd; }
+
+        void next()
+        {
+            if(!s.nextChar(front, index))
+                index = s.lastIndex;
+        }
+
+        void search()
+        {
+            static if(kicked)
+            {
+                if(!s.search(re.kickstart, front, index))
+                {
+                    index = s.lastIndex;
+                }
+            }
+            else
+                next();
+        }
+
+        //
+        void newStack()
+        {
+            auto chunk = mallocArray!(size_t)(stackSize(re));
+            chunk[0] = cast(size_t)(memory.ptr);
+            memory = chunk[1..$];
+        }
+
+        void initExternalMemory(void[] memBlock)
+        {
+            trackers = arrayInChunk!(DataIndex)(re.ngroup+1, memBlock);
+            memory = cast(size_t[])memBlock;
+            memory[0] = 0; //hidden pointer
+            memory = memory[1..$];
+        }
+
+        void initialize(ref RegEx program, Stream stream, void[] memBlock)
+        {
+            re = program;
+            s = stream;
+            exhausted = false;
+            initExternalMemory(memBlock);
+            backrefed = null;
+        }
+
+        auto dupTo(void[] memory)
+        {
+            typeof(this) tmp = this;
+            tmp.initExternalMemory(memory);
+            return tmp;
+        }
+
+        this(ref RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx)
+        {
+            initialize(program, stream, memBlock);
+            front = ch;
+            index = idx;
+        }
+
+        this(ref RegEx program, Stream stream, void[] memBlock)
+        {
+            initialize(program, stream, memBlock);
+            next();
+        }
+
+        auto fwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock)
+        {
+            alias BackMatcherTempl = .BacktrackingMatcher!(CTregex);
+            alias BackMatcher = BackMatcherTempl!(Char, Stream);
+            auto fwdMatcher = BackMatcher(matcher.re, s, memBlock, front, index);
+            return fwdMatcher;
+        }
+
+        auto bwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock)
+        {
+            alias BackMatcherTempl = .BacktrackingMatcher!(CTregex);
+            alias BackMatcher = BackMatcherTempl!(Char, typeof(s.loopBack(index)));
+            auto fwdMatcher =
+                BackMatcher(matcher.re, s.loopBack(index), memBlock);
+            return fwdMatcher;
+        }
+
+        //
+        bool matchFinalize()
+        {
+            size_t start = index;
+            if(matchImpl())
+            {//stream is updated here
+                matches[0].begin = start;
+                matches[0].end = index;
+                if(!(re.flags & RegexOption.global) || atEnd)
+                    exhausted = true;
+                if(start == index)//empty match advances input
+                    next();
+                return true;
+            }
+            else
+                return false;
+        }
+
+        //lookup next match, fill matches with indices into input
+        bool match(Group!DataIndex[] matches)
+        {
+            debug(std_regex_matcher)
+            {
+                writeln("------------------------------------------");
+            }
+            if(exhausted) //all matches collected
+                return false;
+            this.matches = matches;
+            if(re.flags & RegexInfo.oneShot)
+            {
+                exhausted = true;
+                DataIndex start = index;
+                auto m = matchImpl();
+                if(m)
+                {
+                    matches[0].begin = start;
+                    matches[0].end = index;
+                }
+                return m;
+            }
+            static if(kicked)
+            {
+                if(!re.kickstart.empty)
+                {
+                    for(;;)
+                    {
+
+                        if(matchFinalize())
+                            return true;
+                        else
+                        {
+                            if(atEnd)
+                                break;
+                            search();
+                            if(atEnd)
+                            {
+                                exhausted = true;
+                                return matchFinalize();
+                            }
+                        }
+                    }
+                    exhausted = true;
+                    return false; //early return
+                }
+            }
+            //no search available - skip a char at a time
+            for(;;)
+            {
+                if(matchFinalize())
+                    return true;
+                else
+                {
+                    if(atEnd)
+                        break;
+                    next();
+                    if(atEnd)
+                    {
+                        exhausted = true;
+                        return matchFinalize();
+                    }
+                }
+            }
+            exhausted = true;
+            return false;
+        }
+
+        /+
+            match subexpression against input,
+            results are stored in matches
+        +/
+        bool matchImpl()
+        {
+            static if(CTregex && is(typeof(nativeFn(this))))
+            {
+                    debug(std_regex_ctr) writeln("using C-T matcher");
+                return nativeFn(this);
+            }
+            else
+            {
+                pc = 0;
+                counter = 0;
+                lastState = 0;
+                infiniteNesting = -1;//intentional
+                auto start = s._index;
+                debug(std_regex_matcher)
+                    writeln("Try match starting at ", s[index..s.lastIndex]);
+                for(;;)
+                {
+                    debug(std_regex_matcher)
+                        writefln("PC: %s\tCNT: %s\t%s \tfront: %s src: %s",
+                            pc, counter, disassemble(re.ir, pc, re.dict),
+                            front, s._index);
+                    switch(re.ir[pc].code)
+                    {
+                    case IR.OrChar://assumes IRL!(OrChar) == 1
+                        if(atEnd)
+                            goto L_backtrack;
+                        uint len = re.ir[pc].sequence;
+                        uint end = pc + len;
+                        if(re.ir[pc].data != front && re.ir[pc+1].data != front)
+                        {
+                            for(pc = pc+2; pc < end; pc++)
+                                if(re.ir[pc].data == front)
+                                    break;
+                            if(pc == end)
+                                goto L_backtrack;
+                        }
+                        pc = end;
+                        next();
+                        break;
+                    case IR.Char:
+                        if(atEnd || front != re.ir[pc].data)
+                            goto L_backtrack;
+                        pc += IRL!(IR.Char);
+                        next();
+                    break;
+                    case IR.Any:
+                        if(atEnd || (!(re.flags & RegexOption.singleline)
+                                && (front == '\r' || front == '\n')))
+                            goto L_backtrack;
+                        pc += IRL!(IR.Any);
+                        next();
+                        break;
+                    case IR.CodepointSet:
+                        if(atEnd || !re.charsets[re.ir[pc].data].scanFor(front))
+                            goto L_backtrack;
+                        next();
+                        pc += IRL!(IR.CodepointSet);
+                        break;
+                    case IR.Trie:
+                        if(atEnd || !re.tries[re.ir[pc].data][front])
+                            goto L_backtrack;
+                        next();
+                        pc += IRL!(IR.Trie);
+                        break;
+                    case IR.Wordboundary:
+                        dchar back;
+                        DataIndex bi;
+                        //at start & end of input
+                        if(atStart && wordTrie[front])
+                        {
+                            pc += IRL!(IR.Wordboundary);
+                            break;
+                        }
+                        else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                                && wordTrie[back])
+                        {
+                            pc += IRL!(IR.Wordboundary);
+                            break;
+                        }
+                        else if(s.loopBack(index).nextChar(back, bi))
+                        {
+                            bool af = wordTrie[front];
+                            bool ab = wordTrie[back];
+                            if(af ^ ab)
+                            {
+                                pc += IRL!(IR.Wordboundary);
+                                break;
+                            }
+                        }
+                        goto L_backtrack;
+                    case IR.Notwordboundary:
+                        dchar back;
+                        DataIndex bi;
+                        //at start & end of input
+                        if(atStart && wordTrie[front])
+                            goto L_backtrack;
+                        else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                                && wordTrie[back])
+                            goto L_backtrack;
+                        else if(s.loopBack(index).nextChar(back, bi))
+                        {
+                            bool af = wordTrie[front];
+                            bool ab = wordTrie[back];
+                            if(af ^ ab)
+                                goto L_backtrack;
+                        }
+                        pc += IRL!(IR.Wordboundary);
+                        break;
+                    case IR.Bol:
+                        dchar back;
+                        DataIndex bi;
+                        if(atStart)
+                            pc += IRL!(IR.Bol);
+                        else if((re.flags & RegexOption.multiline)
+                            && s.loopBack(index).nextChar(back,bi)
+                            && endOfLine(back, front == '\n'))
+                        {
+                            pc += IRL!(IR.Bol);
+                        }
+                        else
+                            goto L_backtrack;
+                        break;
+                    case IR.Eol:
+                        dchar back;
+                        DataIndex bi;
+                        debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]);
+                        //no matching inside \r\n
+                        if(atEnd || ((re.flags & RegexOption.multiline)
+                            && endOfLine(front, s.loopBack(index).nextChar(back,bi)
+                                && back == '\r')))
+                        {
+                            pc += IRL!(IR.Eol);
+                        }
+                        else
+                            goto L_backtrack;
+                        break;
+                    case IR.InfiniteStart, IR.InfiniteQStart:
+                        trackers[infiniteNesting+1] = index;
+                        pc += re.ir[pc].data + IRL!(IR.InfiniteStart);
+                        //now pc is at end IR.Infininite(Q)End
+                        uint len = re.ir[pc].data;
+                        int test;
+                        if(re.ir[pc].code == IR.InfiniteEnd)
+                        {
+                            test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re);
+                            if(test >= 0)
+                                pushState(pc+IRL!(IR.InfiniteEnd), counter);
+                            infiniteNesting++;
+                            pc -= len;
+                        }
+                        else
+                        {
+                            test = quickTestFwd(pc - len, front, re);
+                            if(test >= 0)
+                            {
+                                infiniteNesting++;
+                                pushState(pc - len, counter);
+                                infiniteNesting--;
+                            }
+                            pc += IRL!(IR.InfiniteEnd);
+                        }
+                        break;
+                    case IR.RepeatStart, IR.RepeatQStart:
+                        pc += re.ir[pc].data + IRL!(IR.RepeatStart);
+                        break;
+                    case IR.RepeatEnd:
+                    case IR.RepeatQEnd:
+                        //len, step, min, max
+                        uint len = re.ir[pc].data;
+                        uint step =  re.ir[pc+2].raw;
+                        uint min = re.ir[pc+3].raw;
+                        uint max = re.ir[pc+4].raw;
+                        if(counter < min)
+                        {
+                            counter += step;
+                            pc -= len;
+                        }
+                        else if(counter < max)
+                        {
+                            if(re.ir[pc].code == IR.RepeatEnd)
+                            {
+                                pushState(pc + IRL!(IR.RepeatEnd), counter%step);
+                                counter += step;
+                                pc -= len;
+                            }
+                            else
+                            {
+                                pushState(pc - len, counter + step);
+                                counter = counter%step;
+                                pc += IRL!(IR.RepeatEnd);
+                            }
+                        }
+                        else
+                        {
+                            counter = counter%step;
+                            pc += IRL!(IR.RepeatEnd);
+                        }
+                        break;
+                    case IR.InfiniteEnd:
+                    case IR.InfiniteQEnd:
+                        uint len = re.ir[pc].data;
+                        debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting);
+                        assert(infiniteNesting < trackers.length);
+
+                        if(trackers[infiniteNesting] == index)
+                        {//source not consumed
+                            pc += IRL!(IR.InfiniteEnd);
+                            infiniteNesting--;
+                            break;
+                        }
+                        else
+                            trackers[infiniteNesting] = index;
+                        int test;
+                        if(re.ir[pc].code == IR.InfiniteEnd)
+                        {
+                            test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re);
+                            if(test >= 0)
+                            {
+                                infiniteNesting--;
+                                pushState(pc + IRL!(IR.InfiniteEnd), counter);
+                                infiniteNesting++;
+                            }
+                            pc -= len;
+                        }
+                        else
+                        {
+                            test = quickTestFwd(pc-len, front, re);
+                            if(test >= 0)
+                                pushState(pc-len, counter);
+                            pc += IRL!(IR.InfiniteEnd);
+                            infiniteNesting--;
+                        }
+                        break;
+                    case IR.OrEnd:
+                        pc += IRL!(IR.OrEnd);
+                        break;
+                    case IR.OrStart:
+                        pc += IRL!(IR.OrStart);
+                        goto case;
+                    case IR.Option:
+                        uint len = re.ir[pc].data;
+                        if(re.ir[pc+len].code == IR.GotoEndOr)//not a last one
+                        {
+                            pushState(pc + len + IRL!(IR.Option), counter); //remember 2nd branch
+                        }
+                        pc += IRL!(IR.Option);
+                        break;
+                    case IR.GotoEndOr:
+                        pc = pc + re.ir[pc].data + IRL!(IR.GotoEndOr);
+                        break;
+                    case IR.GroupStart:
+                        uint n = re.ir[pc].data;
+                        matches[n].begin = index;
+                        debug(std_regex_matcher)  writefln("IR group #%u starts at %u", n, index);
+                        pc += IRL!(IR.GroupStart);
+                        break;
+                    case IR.GroupEnd:
+                        uint n = re.ir[pc].data;
+                        matches[n].end = index;
+                        debug(std_regex_matcher) writefln("IR group #%u ends at %u", n, index);
+                        pc += IRL!(IR.GroupEnd);
+                        break;
+                    case IR.LookaheadStart:
+                    case IR.NeglookaheadStart:
+                        uint len = re.ir[pc].data;
+                        auto save = index;
+                        uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw;
+                        auto mem = malloc(initialMemory(re))[0..initialMemory(re)];
+                        scope(exit) free(mem.ptr);
+                        static if(Stream.isLoopback)
+                        {
+                            auto matcher = bwdMatcher(this, mem);
+                        }
+                        else
+                        {
+                            auto matcher = fwdMatcher(this, mem);
+                        }
+                        matcher.matches = matches[ms .. me];
+                        matcher.backrefed = backrefed.empty ? matches : backrefed;
+                        matcher.re.ir = re.ir[pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd)];
+                        bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookaheadStart);
+                        s.reset(save);
+                        next();
+                        if(!match)
+                            goto L_backtrack;
+                        else
+                        {
+                            pc += IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd);
+                        }
+                        break;
+                    case IR.LookbehindStart:
+                    case IR.NeglookbehindStart:
+                        uint len = re.ir[pc].data;
+                        uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw;
+                        auto mem = malloc(initialMemory(re))[0..initialMemory(re)];
+                        scope(exit) free(mem.ptr);
+                        static if(Stream.isLoopback)
+                        {
+                            alias Matcher = BacktrackingMatcher!(Char, Stream);
+                            auto matcher = Matcher(re, s, mem, front, index);
+                        }
+                        else
+                        {
+                            alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index)));
+                            auto matcher = Matcher(re, s.loopBack(index), mem);
+                        }
+                        matcher.matches = matches[ms .. me];
+                        matcher.re.ir = re.ir[pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd)];
+                        matcher.backrefed  = backrefed.empty ? matches : backrefed;
+                        bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookbehindStart);
+                        if(!match)
+                            goto L_backtrack;
+                        else
+                        {
+                            pc += IRL!(IR.LookbehindStart)+len+IRL!(IR.LookbehindEnd);
+                        }
+                        break;
+                    case IR.Backref:
+                        uint n = re.ir[pc].data;
+                        auto referenced = re.ir[pc].localRef
+                                ? s[matches[n].begin .. matches[n].end]
+                                : s[backrefed[n].begin .. backrefed[n].end];
+                        while(!atEnd && !referenced.empty && front == referenced.front)
+                        {
+                            next();
+                            referenced.popFront();
+                        }
+                        if(referenced.empty)
+                            pc++;
+                        else
+                            goto L_backtrack;
+                        break;
+                        case IR.Nop:
+                        pc += IRL!(IR.Nop);
+                        break;
+                    case IR.LookaheadEnd:
+                    case IR.NeglookaheadEnd:
+                    case IR.LookbehindEnd:
+                    case IR.NeglookbehindEnd:
+                    case IR.End:
+                        return true;
+                    default:
+                        debug printBytecode(re.ir[0..$]);
+                        assert(0);
+                    L_backtrack:
+                        if(!popState())
+                        {
+                            s.reset(start);
+                            return false;
+                        }
+                    }
+                }
+            }
+            assert(0);
+        }
+
+        @property size_t stackAvail()
+        {
+            return memory.length - lastState;
+        }
+
+        bool prevStack()
+        {
+            import core.stdc.stdlib;
+            size_t* prev = memory.ptr-1;
+            prev = cast(size_t*)*prev;//take out hidden pointer
+            if(!prev)
+                return false;
+            free(memory.ptr);//last segment is freed in RegexMatch
+            immutable size = initialStack*(stateSize + 2*re.ngroup);
+            memory = prev[0..size];
+            lastState = size;
+            return true;
+        }
+
+        void stackPush(T)(T val)
+            if(!isDynamicArray!T)
+        {
+            *cast(T*)&memory[lastState] = val;
+            enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof;
+            lastState += delta;
+            debug(std_regex_matcher) writeln("push element SP= ", lastState);
+        }
+
+        void stackPush(T)(T[] val)
+        {
+            static assert(T.sizeof % size_t.sizeof == 0);
+            (cast(T*)&memory[lastState])[0..val.length]
+                = val[0..$];
+            lastState += val.length*(T.sizeof/size_t.sizeof);
+            debug(std_regex_matcher) writeln("push array SP= ", lastState);
+        }
+
+        void stackPop(T)(ref T val)
+            if(!isDynamicArray!T)
+        {
+            enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof;
+            lastState -= delta;
+            val = *cast(T*)&memory[lastState];
+            debug(std_regex_matcher) writeln("pop element SP= ", lastState);
+        }
+
+        void stackPop(T)(T[] val)
+        {
+            stackPop(val);  // call ref version
+        }
+        void stackPop(T)(ref T[] val)
+        {
+            lastState -= val.length*(T.sizeof/size_t.sizeof);
+            val[0..$] = (cast(T*)&memory[lastState])[0..val.length];
+            debug(std_regex_matcher) writeln("pop array SP= ", lastState);
+        }
+
+        static if(!CTregex)
+        {
+            //helper function, saves engine state
+            void pushState(uint pc, uint counter)
+            {
+                if(stateSize + trackers.length + matches.length > stackAvail)
+                {
+                    newStack();
+                    lastState = 0;
+                }
+                *cast(State*)&memory[lastState] =
+                    State(index, pc, counter, infiniteNesting);
+                lastState += stateSize;
+                memory[lastState .. lastState + 2 * matches.length] = (cast(size_t[])matches)[];
+                lastState += 2*matches.length;
+                if(trackers.length)
+                {
+                    memory[lastState .. lastState + trackers.length] = trackers[];
+                    lastState += trackers.length;
+                }
+                debug(std_regex_matcher)
+                    writefln("Saved(pc=%s) front: %s src: %s",
+                        pc, front, s[index..s.lastIndex]);
+            }
+
+            //helper function, restores engine state
+            bool popState()
+            {
+                if(!lastState)
+                    return prevStack();
+                if (trackers.length)
+                {
+                    lastState -= trackers.length;
+                    trackers[] = memory[lastState .. lastState + trackers.length];
+                }
+                lastState -= 2*matches.length;
+                auto pm = cast(size_t[])matches;
+                pm[] = memory[lastState .. lastState + 2 * matches.length];
+                lastState -= stateSize;
+                State* state = cast(State*)&memory[lastState];
+                index = state.index;
+                pc = state.pc;
+                counter = state.counter;
+                infiniteNesting = state.infiniteNesting;
+                debug(std_regex_matcher)
+                {
+                    writefln("Restored matches", front, s[index .. s.lastIndex]);
+                    foreach(i, m; matches)
+                        writefln("Sub(%d) : %s..%s", i, m.begin, m.end);
+                }
+                s.reset(index);
+                next();
+                debug(std_regex_matcher)
+                    writefln("Backtracked (pc=%s) front: %s src: %s",
+                        pc, front, s[index..s.lastIndex]);
+                return true;
+            }
+        }
+    }
+}
+
+//very shitty string formatter, $$ replaced with next argument converted to string
+@trusted string ctSub( U...)(string format, U args)
+{
+    import std.conv;
+    bool seenDollar;
+    foreach(i, ch; format)
+    {
+        if(ch == '$')
+        {
+            if(seenDollar)
+            {
+                static if(args.length > 0)
+                {
+                    return  format[0 .. i - 1] ~ to!string(args[0])
+                        ~ ctSub(format[i + 1 .. $], args[1 .. $]);
+                }
+                else
+                    assert(0);
+            }
+            else
+                seenDollar = true;
+        }
+        else
+            seenDollar = false;
+
+    }
+    return format;
+}
+
+alias Sequence(int B, int E) = staticIota!(B, E);
+
+struct CtContext
+{
+    import std.conv;
+    //dirty flags
+    bool counter, infNesting;
+    // to make a unique advancement counter per nesting level of loops
+    int curInfLoop, nInfLoops;
+    //to mark the portion of matches to save
+    int match, total_matches;
+    int reserved;
+
+
+    //state of codegenerator
+    struct CtState
+    {
+        string code;
+        int addr;
+    }
+
+    this(Char)(Regex!Char re)
+    {
+        match = 1;
+        reserved = 1; //first match is skipped
+        total_matches = re.ngroup;
+    }
+
+    CtContext lookaround(uint s, uint e)
+    {
+        CtContext ct;
+        ct.total_matches = e - s;
+        ct.match = 1;
+        return ct;
+    }
+
+    //restore state having current context
+    string restoreCode()
+    {
+        string text;
+        //stack is checked in L_backtrack
+        text ~= counter
+            ? "
+                    stackPop(counter);"
+            : "
+                    counter = 0;";
+        if(infNesting)
+        {
+            text ~= ctSub(`
+                    stackPop(trackers[0..$$]);
+                    `, curInfLoop + 1);
+        }
+        if(match < total_matches)
+        {
+            text ~= ctSub("
+                    stackPop(matches[$$..$$]);", reserved, match);
+            text ~= ctSub("
+                    matches[$$..$] = typeof(matches[0]).init;", match);
+        }
+        else
+            text ~= ctSub("
+                    stackPop(matches[$$..$]);", reserved);
+        return text;
+    }
+
+    //save state having current context
+    string saveCode(uint pc, string count_expr="counter")
+    {
+        string text = ctSub("
+                    if(stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + trackers.length + $$)
+                    {
+                        newStack();
+                        lastState = 0;
+                    }", match - reserved, cast(int)counter + 2);
+        if(match < total_matches)
+            text ~= ctSub("
+                    stackPush(matches[$$..$$]);", reserved, match);
+        else
+            text ~= ctSub("
+                    stackPush(matches[$$..$]);", reserved);
+        if(infNesting)
+        {
+            text ~= ctSub(`
+                    stackPush(trackers[0..$$]);
+                    `, curInfLoop + 1);
+        }
+        text ~= counter ? ctSub("
+                    stackPush($$);", count_expr) : "";
+        text ~= ctSub("
+                    stackPush(index); stackPush($$); \n", pc);
+        return text;
+    }
+
+    //
+    CtState ctGenBlock(Bytecode[] ir, int addr)
+    {
+        CtState result;
+        result.addr = addr;
+        while(!ir.empty)
+        {
+            auto n = ctGenGroup(ir, result.addr);
+            result.code ~= n.code;
+            result.addr = n.addr;
+        }
+        return result;
+    }
+
+    //
+    CtState ctGenGroup(ref Bytecode[] ir, int addr)
+    {
+        import std.algorithm : max;
+        auto bailOut = "goto L_backtrack;";
+        auto nextInstr = ctSub("goto case $$;", addr+1);
+        CtState r;
+        assert(!ir.empty);
+        switch(ir[0].code)
+        {
+        case IR.InfiniteStart, IR.InfiniteQStart, IR.RepeatStart, IR.RepeatQStart:
+            bool infLoop =
+                ir[0].code == IR.InfiniteStart || ir[0].code == IR.InfiniteQStart;
+            infNesting = infNesting || infLoop;
+            if(infLoop)
+            {
+                curInfLoop++;
+                nInfLoops = max(nInfLoops, curInfLoop+1);
+            }
+            counter = counter ||
+                ir[0].code == IR.RepeatStart || ir[0].code == IR.RepeatQStart;
+            uint len = ir[0].data;
+            auto nir = ir[ir[0].length .. ir[0].length+len];
+            r = ctGenBlock(nir, addr+1);
+            if(infLoop)
+                curInfLoop--;
+            //start/end codegen
+            //r.addr is at last test+ jump of loop, addr+1 is body of loop
+            nir = ir[ir[0].length + len .. $];
+            r.code = ctGenFixupCode(ir[0..ir[0].length], addr, r.addr) ~ r.code;
+            r.code ~= ctGenFixupCode(nir, r.addr, addr+1);
+            r.addr += 2;   //account end instruction + restore state
+            ir = nir;
+            break;
+        case IR.OrStart:
+            uint len = ir[0].data;
+            auto nir = ir[ir[0].length .. ir[0].length+len];
+            r = ctGenAlternation(nir, addr);
+            ir = ir[ir[0].length + len .. $];
+            assert(ir[0].code == IR.OrEnd);
+            ir = ir[ir[0].length..$];
+            break;
+        case IR.LookaheadStart:
+        case IR.NeglookaheadStart:
+        case IR.LookbehindStart:
+        case IR.NeglookbehindStart:
+            uint len = ir[0].data;
+            bool behind = ir[0].code == IR.LookbehindStart || ir[0].code == IR.NeglookbehindStart;
+            bool negative = ir[0].code == IR.NeglookaheadStart || ir[0].code == IR.NeglookbehindStart;
+            string fwdType = "typeof(fwdMatcher(matcher, []))";
+            string bwdType = "typeof(bwdMatcher(matcher, []))";
+            string fwdCreate = "fwdMatcher(matcher, mem)";
+            string bwdCreate = "bwdMatcher(matcher, mem)";
+            uint start = IRL!(IR.LookbehindStart);
+            uint end = IRL!(IR.LookbehindStart)+len+IRL!(IR.LookaheadEnd);
+            CtContext context = lookaround(ir[1].raw, ir[2].raw); //split off new context
+            auto slice = ir[start .. end];
+            r.code ~= ctSub(`
+            case $$: //fake lookaround "atom"
+                    static if(typeof(matcher.s).isLoopback)
+                        alias Lookaround = $$;
+                    else
+                        alias Lookaround = $$;
+                    static bool matcher_$$(ref Lookaround matcher) @trusted
+                    {
+                        //(neg)lookaround piece start
+                        $$
+                        //(neg)lookaround piece ends
+                    }
+                    auto save = index;
+                    auto mem = malloc(initialMemory(re))[0..initialMemory(re)];
+                    scope(exit) free(mem.ptr);
+                    static if(typeof(matcher.s).isLoopback)
+                        auto lookaround = $$;
+                    else
+                        auto lookaround = $$;
+                    lookaround.matches = matches[$$..$$];
+                    lookaround.backrefed = backrefed.empty ? matches : backrefed;
+                    lookaround.nativeFn = &matcher_$$; //hookup closure's binary code
+                    bool match = $$;
+                    s.reset(save);
+                    next();
+                    if(match)
+                        $$
+                    else
+                        $$`, addr,
+                        behind ? fwdType : bwdType, behind ? bwdType : fwdType,
+                        addr, context.ctGenRegEx(slice),
+                        behind ? fwdCreate : bwdCreate, behind ? bwdCreate : fwdCreate,
+                        ir[1].raw, ir[2].raw, //start - end of matches slice
+                        addr,
+                        negative ? "!lookaround.matchImpl()" : "lookaround.matchImpl()",
+                        nextInstr, bailOut);
+            ir = ir[end .. $];
+            r.addr = addr + 1;
+            break;
+        case IR.LookaheadEnd: case IR.NeglookaheadEnd:
+        case IR.LookbehindEnd: case IR.NeglookbehindEnd:
+            ir = ir[IRL!(IR.LookaheadEnd) .. $];
+            r.addr = addr;
+            break;
+        default:
+            assert(ir[0].isAtom,  text(ir[0].mnemonic));
+            r = ctGenAtom(ir, addr);
+        }
+        return r;
+    }
+
+    //generate source for bytecode contained  in OrStart ... OrEnd
+    CtState ctGenAlternation(Bytecode[] ir, int addr)
+    {
+        CtState[] pieces;
+        CtState r;
+        enum optL = IRL!(IR.Option);
+        for(;;)
+        {
+            assert(ir[0].code == IR.Option);
+            auto len = ir[0].data;
+            if(optL+len < ir.length  && ir[optL+len].code == IR.Option)//not a last option
+            {
+                auto nir = ir[optL .. optL+len-IRL!(IR.GotoEndOr)];
+                r = ctGenBlock(nir, addr+2);//space for Option + restore state
+                //r.addr+1 to account GotoEndOr  at end of branch
+                r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr+1) ~ r.code;
+                addr = r.addr+1;//leave space for GotoEndOr
+                pieces ~= r;
+                ir = ir[optL + len .. $];
+            }
+            else
+            {
+                pieces ~= ctGenBlock(ir[optL..$], addr);
+                addr = pieces[$-1].addr;
+                break;
+            }
+        }
+        r = pieces[0];
+        for(uint i = 1; i < pieces.length; i++)
+        {
+            r.code ~= ctSub(`
+                case $$:
+                    goto case $$; `, pieces[i-1].addr, addr);
+            r.code ~= pieces[i].code;
+        }
+        r.addr = addr;
+        return r;
+    }
+
+    // generate fixup code for instruction in ir,
+    // fixup means it has an alternative way for control flow
+    string ctGenFixupCode(Bytecode[] ir, int addr, int fixup)
+    {
+        return ctGenFixupCode(ir, addr, fixup); // call ref Bytecode[] version
+    }
+    string ctGenFixupCode(ref Bytecode[] ir, int addr, int fixup)
+    {
+        string r;
+        string testCode;
+        r = ctSub(`
+                case $$: debug(std_regex_matcher) writeln("#$$");`,
+                    addr, addr);
+        switch(ir[0].code)
+        {
+        case IR.InfiniteStart, IR.InfiniteQStart:
+            r ~= ctSub( `
+                    trackers[$$] = DataIndex.max;
+                    goto case $$;`, curInfLoop, fixup);
+            ir = ir[ir[0].length..$];
+            break;
+        case IR.InfiniteEnd:
+            testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1);
+            r ~= ctSub( `
+                    if(trackers[$$] == index)
+                    {//source not consumed
+                        goto case $$;
+                    }
+                    trackers[$$] = index;
+
+                    $$
+                    {
+                        $$
+                    }
+                    goto case $$;
+                case $$: //restore state and go out of loop
+                    $$
+                    goto case;`, curInfLoop, addr+2,
+                    curInfLoop, testCode, saveCode(addr+1),
+                    fixup, addr+1, restoreCode());
+            ir = ir[ir[0].length..$];
+            break;
+        case IR.InfiniteQEnd:
+            testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1);
+            auto altCode = testCode.length ? ctSub("else goto case $$;", fixup) : "";
+            r ~= ctSub( `
+                    if(trackers[$$] == index)
+                    {//source not consumed
+                        goto case $$;
+                    }
+                    trackers[$$] = index;
+
+                    $$
+                    {
+                        $$
+                        goto case $$;
+                    }
+                    $$
+                case $$://restore state and go inside loop
+                    $$
+                    goto case $$;`, curInfLoop, addr+2,
+                    curInfLoop, testCode, saveCode(addr+1),
+                    addr+2, altCode, addr+1, restoreCode(), fixup);
+            ir = ir[ir[0].length..$];
+            break;
+        case IR.RepeatStart, IR.RepeatQStart:
+            r ~= ctSub( `
+                    goto case $$;`, fixup);
+            ir = ir[ir[0].length..$];
+            break;
+         case IR.RepeatEnd, IR.RepeatQEnd:
+            //len, step, min, max
+            uint len = ir[0].data;
+            uint step = ir[2].raw;
+            uint min = ir[3].raw;
+            uint max = ir[4].raw;
+            r ~= ctSub(`
+                    if(counter < $$)
+                    {
+                        debug(std_regex_matcher) writeln("RepeatEnd min case pc=", $$);
+                        counter += $$;
+                        goto case $$;
+                    }`,  min, addr, step, fixup);
+            if(ir[0].code == IR.RepeatEnd)
+            {
+                string counter_expr = ctSub("counter % $$", step);
+                r ~= ctSub(`
+                    else if(counter < $$)
+                    {
+                            $$
+                            counter += $$;
+                            goto case $$;
+                    }`, max, saveCode(addr+1, counter_expr), step, fixup);
+            }
+            else
+            {
+                string counter_expr = ctSub("counter % $$", step);
+                r ~= ctSub(`
+                    else if(counter < $$)
+                    {
+                        $$
+                        counter = counter % $$;
+                        goto case $$;
+                    }`, max, saveCode(addr+1,counter_expr), step, addr+2);
+            }
+            r ~= ctSub(`
+                    else
+                    {
+                        counter = counter % $$;
+                        goto case $$;
+                    }
+                case $$: //restore state
+                    $$
+                    goto case $$;`, step, addr+2, addr+1, restoreCode(),
+                    ir[0].code == IR.RepeatEnd ? addr+2 : fixup );
+            ir = ir[ir[0].length..$];
+            break;
+        case IR.Option:
+            r ~= ctSub( `
+                {
+                    $$
+                }
+                goto case $$;
+            case $$://restore thunk to go to the next group
+                $$
+                goto case $$;`, saveCode(addr+1), addr+2,
+                    addr+1, restoreCode(), fixup);
+                ir = ir[ir[0].length..$];
+            break;
+        default:
+            assert(0, text(ir[0].mnemonic));
+        }
+        return r;
+    }
+
+
+    string ctQuickTest(Bytecode[] ir, int id)
+    {
+        uint pc = 0;
+        while(pc < ir.length && ir[pc].isAtom)
+        {
+            if(ir[pc].code == IR.GroupStart || ir[pc].code == IR.GroupEnd)
+            {
+                pc++;
+            }
+            else if(ir[pc].code == IR.Backref)
+                break;
+            else
+            {
+                auto code = ctAtomCode(ir[pc..$], -1);
+                return ctSub(`
+                    int test_$$()
+                    {
+                        $$ //$$
+                    }
+                    if(test_$$() >= 0)`, id, code.ptr ? code : "return 0;",
+                        ir[pc].mnemonic, id);
+            }
+        }
+        return "";
+    }
+
+    //process & generate source for simple bytecodes at front of ir using address addr
+    CtState ctGenAtom(ref Bytecode[] ir, int addr)
+    {
+        CtState result;
+        result.code = ctAtomCode(ir, addr);
+        ir.popFrontN(ir[0].code == IR.OrChar ? ir[0].sequence : ir[0].length);
+        result.addr = addr + 1;
+        return result;
+    }
+
+    //D code for atom at ir using address addr, addr < 0 means quickTest
+    string ctAtomCode(Bytecode[] ir, int addr)
+    {
+        string code;
+        string bailOut, nextInstr;
+        if(addr < 0)
+        {
+            bailOut = "return -1;";
+            nextInstr = "return 0;";
+        }
+        else
+        {
+            bailOut = "goto L_backtrack;";
+            nextInstr = ctSub("goto case $$;", addr+1);
+            code ~=  ctSub( `
+                 case $$: debug(std_regex_matcher) writeln("#$$");
+                    `, addr, addr);
+        }
+        switch(ir[0].code)
+        {
+        case IR.OrChar://assumes IRL!(OrChar) == 1
+            code ~=  ctSub(`
+                    if(atEnd)
+                        $$`, bailOut);
+            uint len = ir[0].sequence;
+            for(uint i = 0; i < len; i++)
+            {
+                code ~= ctSub( `
+                    if(front == $$)
+                    {
+                        $$
+                        $$
+                    }`,   ir[i].data, addr >= 0 ? "next();" :"", nextInstr);
+            }
+            code ~= ctSub( `
+                $$`, bailOut);
+            break;
+        case IR.Char:
+            code ~= ctSub( `
+                    if(atEnd || front != $$)
+                        $$
+                    $$
+                    $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
+            break;
+        case IR.Any:
+            code ~= ctSub( `
+                    if(atEnd || (!(re.flags & RegexOption.singleline)
+                                && (front == '\r' || front == '\n')))
+                        $$
+                    $$
+                    $$`, bailOut, addr >= 0 ? "next();" :"",nextInstr);
+            break;
+        case IR.CodepointSet:
+            code ~= ctSub( `
+                    if(atEnd || !re.charsets[$$].scanFor(front))
+                        $$
+                    $$
+                $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
+            break;
+        case IR.Trie:
+            code ~= ctSub( `
+                    if(atEnd || !re.tries[$$][front])
+                        $$
+                    $$
+                $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
+            break;
+        case IR.Wordboundary:
+            code ~= ctSub( `
+                    dchar back;
+                    DataIndex bi;
+                    if(atStart && wordTrie[front])
+                    {
+                        $$
+                    }
+                    else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                            && wordTrie[back])
+                    {
+                        $$
+                    }
+                    else if(s.loopBack(index).nextChar(back, bi))
+                    {
+                        bool af = wordTrie[front];
+                        bool ab = wordTrie[back];
+                        if(af ^ ab)
+                        {
+                            $$
+                        }
+                    }
+                    $$`, nextInstr, nextInstr, nextInstr, bailOut);
+            break;
+        case IR.Notwordboundary:
+            code ~= ctSub( `
+                    dchar back;
+                    DataIndex bi;
+                    //at start & end of input
+                    if(atStart && wordTrie[front])
+                        $$
+                    else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                            && wordTrie[back])
+                        $$
+                    else if(s.loopBack(index).nextChar(back, bi))
+                    {
+                        bool af = wordTrie[front];
+                        bool ab = wordTrie[back];
+                        if(af ^ ab)
+                            $$
+                    }
+                    $$`, bailOut, bailOut, bailOut, nextInstr);
+
+            break;
+        case IR.Bol:
+            code ~= ctSub(`
+                    dchar back;
+                    DataIndex bi;
+                    if(atStart || ((re.flags & RegexOption.multiline)
+                        && s.loopBack(index).nextChar(back,bi)
+                        && endOfLine(back, front == '\n')))
+                    {
+                        debug(std_regex_matcher) writeln("BOL matched");
+                        $$
+                    }
+                    else
+                        $$`, nextInstr, bailOut);
+
+            break;
+        case IR.Eol:
+            code ~= ctSub(`
+                    dchar back;
+                    DataIndex bi;
+                    debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]);
+                    //no matching inside \r\n
+                    if(atEnd || ((re.flags & RegexOption.multiline)
+                            && endOfLine(front, s.loopBack(index).nextChar(back,bi)
+                             && back == '\r')))
+                    {
+                        debug(std_regex_matcher) writeln("EOL matched");
+                        $$
+                    }
+                    else
+                        $$`, nextInstr, bailOut);
+
+            break;
+        case IR.GroupStart:
+            code ~= ctSub(`
+                    matches[$$].begin = index;
+                    $$`, ir[0].data, nextInstr);
+            match = ir[0].data+1;
+            break;
+        case IR.GroupEnd:
+            code ~= ctSub(`
+                    matches[$$].end = index;
+                    $$`, ir[0].data, nextInstr);
+            break;
+        case IR.Backref:
+            string mStr = "auto referenced = ";
+            mStr ~= ir[0].localRef
+                ? ctSub("s[matches[$$].begin .. matches[$$].end];",
+                    ir[0].data, ir[0].data)
+                : ctSub("s[backrefed[$$].begin .. backrefed[$$].end];",
+                    ir[0].data, ir[0].data);
+            code ~= ctSub( `
+                    $$
+                    while(!atEnd && !referenced.empty && front == referenced.front)
+                    {
+                        next();
+                        referenced.popFront();
+                    }
+                    if(referenced.empty)
+                        $$
+                    else
+                        $$`, mStr, nextInstr, bailOut);
+            break;
+        case IR.Nop:
+        case IR.End:
+            break;
+        default:
+            assert(0, text(ir[0].mnemonic, " is not supported yet"));
+        }
+        return code;
+    }
+
+    //generate D code for the whole regex
+    public string ctGenRegEx(Bytecode[] ir)
+    {
+        auto bdy = ctGenBlock(ir, 0);
+        auto r = `
+            import core.stdc.stdlib;
+            with(matcher)
+            {
+            pc = 0;
+            counter = 0;
+            lastState = 0;
+            auto start = s._index;`;
+        r ~= `
+            goto StartLoop;
+            debug(std_regex_matcher) writeln("Try CT matching  starting at ",s[index..s.lastIndex]);
+        L_backtrack:
+            if(lastState || prevStack())
+            {
+                stackPop(pc);
+                stackPop(index);
+                s.reset(index);
+                next();
+            }
+            else
+            {
+                s.reset(start);
+                return false;
+            }
+        StartLoop:
+            switch(pc)
+            {
+        `;
+        r ~= bdy.code;
+        r ~= ctSub(`
+                case $$: break;`,bdy.addr);
+        r ~= `
+            default:
+                assert(0);
+            }
+            return true;
+            }
+        `;
+        return r;
+    }
+
+}
+
+string ctGenRegExCode(Char)(Regex!Char re)
+{
+    auto context = CtContext(re);
+    return context.ctGenRegEx(re.ir);
+}
diff --git a/libphobos/src/std/regex/internal/generator.d b/libphobos/src/std/regex/internal/generator.d
new file mode 100644
index 00000000000..541acc3c268
--- /dev/null
+++ b/libphobos/src/std/regex/internal/generator.d
@@ -0,0 +1,185 @@
+/*
+    Generators - components that generate strings for a given regex pattern.
+
+    For the moment undocumented, and is subject to change.
+*/
+module std.regex.internal.generator;
+
+/*
+    Useful utility for self-testing, an infinite range of string samples
+    that _have_ to match given compiled regex.
+    Caveats: supports only a simple subset of bytecode.
+*/
+@trusted private struct SampleGenerator(Char)
+{
+    import std.regex.internal.ir;
+    import std.array, std.format, std.utf, std.random;
+    Regex!Char re;
+    Appender!(char[]) app;
+    uint limit, seed;
+    Xorshift gen;
+    //generator for pattern r, with soft maximum of threshold elements
+    //and a given random seed
+    this(ref Regex!Char r, uint threshold, uint randomSeed)
+    {
+        re = r;
+        limit = threshold;
+        seed = randomSeed;
+        app = appender!(Char[])();
+        compose();
+    }
+
+    uint rand(uint x)
+    {
+        uint r = gen.front % x;
+        gen.popFront();
+        return r;
+    }
+
+    void compose()
+    {
+        uint pc = 0, counter = 0, dataLenOld = uint.max;
+        for(;;)
+        {
+            switch(re.ir[pc].code)
+            {
+            case IR.Char:
+                    formattedWrite(app,"%s", cast(dchar)re.ir[pc].data);
+                    pc += IRL!(IR.Char);
+                    break;
+                case IR.OrChar:
+                    uint len = re.ir[pc].sequence;
+                    formattedWrite(app, "%s", cast(dchar)re.ir[pc + rand(len)].data);
+                    pc += len;
+                    break;
+                case IR.CodepointSet:
+                case IR.Trie:
+                    auto set = re.charsets[re.ir[pc].data];
+                    auto x = rand(cast(uint)set.byInterval.length);
+                    auto y = rand(set.byInterval[x].b - set.byInterval[x].a);
+                    formattedWrite(app, "%s", cast(dchar)(set.byInterval[x].a+y));
+                    pc += IRL!(IR.CodepointSet);
+                    break;
+                case IR.Any:
+                    uint x;
+                    do
+                    {
+                        x = rand(0x11_000);
+                    }while(x == '\r' || x == '\n' || !isValidDchar(x));
+                    formattedWrite(app, "%s", cast(dchar)x);
+                    pc += IRL!(IR.Any);
+                    break;
+                case IR.GotoEndOr:
+                    pc += IRL!(IR.GotoEndOr)+re.ir[pc].data;
+                    assert(re.ir[pc].code == IR.OrEnd);
+                    goto case;
+                case IR.OrEnd:
+                    pc += IRL!(IR.OrEnd);
+                    break;
+                case IR.OrStart:
+                    pc += IRL!(IR.OrStart);
+                    goto case;
+                case IR.Option:
+                    uint next = pc + re.ir[pc].data + IRL!(IR.Option);
+                    uint nOpt = 0;
+                    //queue next Option
+                    while(re.ir[next].code == IR.Option)
+                    {
+                        nOpt++;
+                        next += re.ir[next].data + IRL!(IR.Option);
+                    }
+                    nOpt++;
+                    nOpt = rand(nOpt);
+                    for(;nOpt; nOpt--)
+                    {
+                        pc += re.ir[pc].data + IRL!(IR.Option);
+                    }
+                    assert(re.ir[pc].code == IR.Option);
+                    pc += IRL!(IR.Option);
+                    break;
+                case IR.RepeatStart:case IR.RepeatQStart:
+                    pc += IRL!(IR.RepeatStart)+re.ir[pc].data;
+                    goto case IR.RepeatEnd;
+                case IR.RepeatEnd:
+                case IR.RepeatQEnd:
+                    uint len = re.ir[pc].data;
+                    uint step = re.ir[pc+2].raw;
+                    uint min = re.ir[pc+3].raw;
+                    if(counter < min)
+                    {
+                        counter += step;
+                        pc -= len;
+                        break;
+                    }
+                    uint max = re.ir[pc+4].raw;
+                    if(counter < max)
+                    {
+                        if(app.data.length < limit && rand(3) > 0)
+                        {
+                            pc -= len;
+                            counter += step;
+                        }
+                        else
+                        {
+                            counter = counter%step;
+                            pc += IRL!(IR.RepeatEnd);
+                        }
+                    }
+                    else
+                    {
+                        counter = counter%step;
+                        pc += IRL!(IR.RepeatEnd);
+                    }
+                    break;
+                case IR.InfiniteStart, IR.InfiniteQStart:
+                    pc += re.ir[pc].data + IRL!(IR.InfiniteStart);
+                    goto case IR.InfiniteEnd; //both Q and non-Q
+                case IR.InfiniteEnd:
+                case IR.InfiniteQEnd:
+                    uint len = re.ir[pc].data;
+                    if(app.data.length == dataLenOld)
+                    {
+                        pc += IRL!(IR.InfiniteEnd);
+                        break;
+                    }
+                    dataLenOld = cast(uint)app.data.length;
+                    if(app.data.length < limit && rand(3) > 0)
+                        pc = pc - len;
+                    else
+                        pc = pc + IRL!(IR.InfiniteEnd);
+                    break;
+                case IR.GroupStart, IR.GroupEnd:
+                    pc += IRL!(IR.GroupStart);
+                    break;
+                case IR.Bol, IR.Wordboundary, IR.Notwordboundary:
+                case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+                default:
+                    return;
+            }
+        }
+    }
+
+    @property Char[] front()
+    {
+        return app.data;
+    }
+
+    @property empty(){  return false; }
+
+    void popFront()
+    {
+        app.shrinkTo(0);
+        compose();
+    }
+}
+
+unittest
+{
+    import std.range, std.regex;
+    auto re = regex(`P[a-z]{3,}q`);
+    auto gen = SampleGenerator!char(re, 20, 3141592);
+    static assert(isInputRange!(typeof(gen)));
+    //@@@BUG@@@ somehow gen.take(1_000) doesn't work
+    foreach(v; take(gen, 1_000))
+        assert(v.match(re));
+}
diff --git a/libphobos/src/std/regex/internal/ir.d b/libphobos/src/std/regex/internal/ir.d
new file mode 100644
index 00000000000..548c1e3c859
--- /dev/null
+++ b/libphobos/src/std/regex/internal/ir.d
@@ -0,0 +1,745 @@
+/*
+    Implementation of std.regex IR, an intermediate representation
+    of a regular expression pattern.
+
+    This is a common ground between frontend regex component (parser)
+    and backend components - generators, matchers and other "filters".
+*/
+module std.regex.internal.ir;
+
+package(std.regex):
+
+import std.exception, std.uni, std.meta, std.traits, std.range;
+
+// just a common trait, may be moved elsewhere
+alias BasicElementOf(Range) = Unqual!(ElementEncodingType!Range);
+
+// heuristic value determines maximum CodepointSet length suitable for linear search
+enum maxCharsetUsed = 6;
+
+// another variable to tweak behavior of caching generated Tries for character classes
+enum maxCachedTries = 8;
+
+alias Trie = CodepointSetTrie!(13, 8);
+alias makeTrie = codepointSetTrie!(13, 8);
+
+Trie[CodepointSet] trieCache;
+
+//accessor with caching
+@trusted Trie getTrie(CodepointSet set)
+{// @@@BUG@@@ 6357 almost all properties of AA are not @safe
+    if(__ctfe || maxCachedTries == 0)
+        return makeTrie(set);
+    else
+    {
+        auto p = set in trieCache;
+        if(p)
+            return *p;
+        if(trieCache.length == maxCachedTries)
+        {
+            // flush entries in trieCache
+            trieCache = null;
+        }
+        return (trieCache[set] = makeTrie(set));
+    }
+}
+
+@trusted auto memoizeExpr(string expr)()
+{
+    if(__ctfe)
+        return mixin(expr);
+    alias T = typeof(mixin(expr));
+    static T slot;
+    static bool initialized;
+    if(!initialized)
+    {
+        slot =  mixin(expr);
+        initialized = true;
+    }
+    return slot;
+}
+
+//property for \w character class
+@property CodepointSet wordCharacter()
+{
+    return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc
+        | unicode.Me | unicode.Nd | unicode.Pc")();
+}
+
+@property Trie wordTrie()
+{
+    return memoizeExpr!("makeTrie(wordCharacter)")();
+}
+
+// some special Unicode white space characters
+private enum NEL = '\u0085', LS = '\u2028', PS = '\u2029';
+
+//Regular expression engine/parser options:
+// global - search  all nonoverlapping matches in input
+// casefold - case insensitive matching, do casefolding on match in unicode mode
+// freeform - ignore whitespace in pattern, to match space use [ ] or \s
+// multiline - switch  ^, $ detect start and end of linesinstead of just start and end of input
+enum RegexOption: uint {
+    global = 0x1,
+    casefold = 0x2,
+    freeform = 0x4,
+    nonunicode = 0x8,
+    multiline = 0x10,
+    singleline = 0x20
+}
+//do not reorder this list
+alias RegexOptionNames = AliasSeq!('g', 'i', 'x', 'U', 'm', 's');
+static assert( RegexOption.max < 0x80);
+// flags that allow guide execution of engine
+enum RegexInfo : uint { oneShot = 0x80 }
+
+// IR bit pattern: 0b1_xxxxx_yy
+// where yy indicates class of instruction, xxxxx for actual operation code
+//     00: atom, a normal instruction
+//     01: open, opening of a group, has length of contained IR in the low bits
+//     10: close, closing of a group, has length of contained IR in the low bits
+//     11 unused
+//
+// Loops with Q (non-greedy, with ? mark) must have the same size / other properties as non Q version
+// Possible changes:
+//* merge group, option, infinite/repeat start (to never copy during parsing of (a|b){1,2})
+//* reorganize groups to make n args easier to find, or simplify the check for groups of similar ops
+//  (like lookaround), or make it easier to identify hotspots.
+
+enum IR:uint {
+    Char              = 0b1_00000_00, //a character
+    Any               = 0b1_00001_00, //any character
+    CodepointSet      = 0b1_00010_00, //a most generic CodepointSet [...]
+    Trie              = 0b1_00011_00, //CodepointSet implemented as Trie
+    //match with any of a consecutive OrChar's in this sequence
+    //(used for case insensitive match)
+    //OrChar holds in upper two bits of data total number of OrChars in this _sequence_
+    //the drawback of this representation is that it is difficult
+    // to detect a jump in the middle of it
+    OrChar            = 0b1_00100_00,
+    Nop               = 0b1_00101_00, //no operation (padding)
+    End               = 0b1_00110_00, //end of program
+    Bol               = 0b1_00111_00, //beginning of a string ^
+    Eol               = 0b1_01000_00, //end of a string $
+    Wordboundary      = 0b1_01001_00, //boundary of a word
+    Notwordboundary   = 0b1_01010_00, //not a word boundary
+    Backref           = 0b1_01011_00, //backreference to a group (that has to be pinned, i.e. locally unique) (group index)
+    GroupStart        = 0b1_01100_00, //start of a group (x) (groupIndex+groupPinning(1bit))
+    GroupEnd          = 0b1_01101_00, //end of a group (x) (groupIndex+groupPinning(1bit))
+    Option            = 0b1_01110_00, //start of an option within an alternation x | y (length)
+    GotoEndOr         = 0b1_01111_00, //end of an option (length of the rest)
+    //... any additional atoms here
+
+    OrStart           = 0b1_00000_01, //start of alternation group  (length)
+    OrEnd             = 0b1_00000_10, //end of the or group (length,mergeIndex)
+    //with this instruction order
+    //bit mask 0b1_00001_00 could be used to test/set greediness
+    InfiniteStart     = 0b1_00001_01, //start of an infinite repetition x* (length)
+    InfiniteEnd       = 0b1_00001_10, //end of infinite repetition x* (length,mergeIndex)
+    InfiniteQStart    = 0b1_00010_01, //start of a non eager infinite repetition x*? (length)
+    InfiniteQEnd      = 0b1_00010_10, //end of non eager infinite repetition x*? (length,mergeIndex)
+    RepeatStart       = 0b1_00011_01, //start of a {n,m} repetition (length)
+    RepeatEnd         = 0b1_00011_10, //end of x{n,m} repetition (length,step,minRep,maxRep)
+    RepeatQStart      = 0b1_00100_01, //start of a non eager x{n,m}? repetition (length)
+    RepeatQEnd        = 0b1_00100_10, //end of non eager x{n,m}? repetition (length,step,minRep,maxRep)
+    //
+    LookaheadStart    = 0b1_00101_01, //begin of the lookahead group (length)
+    LookaheadEnd      = 0b1_00101_10, //end of a lookahead group (length)
+    NeglookaheadStart = 0b1_00110_01, //start of a negative lookahead (length)
+    NeglookaheadEnd   = 0b1_00110_10, //end of a negative lookahead (length)
+    LookbehindStart   = 0b1_00111_01, //start of a lookbehind (length)
+    LookbehindEnd     = 0b1_00111_10, //end of a lookbehind (length)
+    NeglookbehindStart= 0b1_01000_01, //start of a negative lookbehind (length)
+    NeglookbehindEnd  = 0b1_01000_10, //end of negative lookbehind (length)
+}
+
+//a shorthand for IR length - full length of specific opcode evaluated at compile time
+template IRL(IR code)
+{
+    enum uint IRL =  lengthOfIR(code);
+}
+static assert (IRL!(IR.LookaheadStart) == 3);
+
+//how many parameters follow the IR, should be optimized fixing some IR bits
+int immediateParamsIR(IR i){
+    switch (i){
+    case IR.OrEnd,IR.InfiniteEnd,IR.InfiniteQEnd:
+        return 1;
+    case IR.RepeatEnd, IR.RepeatQEnd:
+        return 4;
+    case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+        return 2;
+    default:
+        return 0;
+    }
+}
+
+//full length of IR instruction inlcuding all parameters that might follow it
+int lengthOfIR(IR i)
+{
+    return 1 + immediateParamsIR(i);
+}
+
+//full length of the paired IR instruction inlcuding all parameters that might follow it
+int lengthOfPairedIR(IR i)
+{
+    return 1 + immediateParamsIR(pairedIR(i));
+}
+
+//if the operation has a merge point (this relies on the order of the ops)
+bool hasMerge(IR i)
+{
+    return (i&0b11)==0b10 && i <= IR.RepeatQEnd;
+}
+
+//is an IR that opens a "group"
+bool isStartIR(IR i)
+{
+    return (i&0b11)==0b01;
+}
+
+//is an IR that ends a "group"
+bool isEndIR(IR i)
+{
+    return (i&0b11)==0b10;
+}
+
+//is a standalone IR
+bool isAtomIR(IR i)
+{
+    return (i&0b11)==0b00;
+}
+
+//makes respective pair out of IR i, swapping start/end bits of instruction
+IR pairedIR(IR i)
+{
+    assert(isStartIR(i) || isEndIR(i));
+    return cast(IR)(i ^ 0b11);
+}
+
+//encoded IR instruction
+struct Bytecode
+{
+    uint raw;
+    //natural constraints
+    enum maxSequence = 2+4;
+    enum maxData = 1<<22;
+    enum maxRaw = 1<<31;
+
+    this(IR code, uint data)
+    {
+        assert(data < (1<<22) && code < 256);
+        raw = code<<24 | data;
+    }
+
+    this(IR code, uint data, uint seq)
+    {
+        assert(data < (1<<22) && code < 256 );
+        assert(seq >= 2 && seq < maxSequence);
+        raw = code << 24 | (seq - 2)<<22 | data;
+    }
+
+    //store raw data
+    static Bytecode fromRaw(uint data)
+    {
+        Bytecode t;
+        t.raw = data;
+        return t;
+    }
+
+    //bit twiddling helpers
+    //0-arg template due to @@@BUG@@@ 10985
+    @property uint data()() const { return raw & 0x003f_ffff; }
+
+    //ditto
+    //0-arg template due to @@@BUG@@@ 10985
+    @property uint sequence()() const { return 2 + (raw >> 22 & 0x3); }
+
+    //ditto
+    //0-arg template due to @@@BUG@@@ 10985
+    @property IR code()() const { return cast(IR)(raw>>24); }
+
+    //ditto
+    @property bool hotspot() const { return hasMerge(code); }
+
+    //test the class of this instruction
+    @property bool isAtom() const { return isAtomIR(code); }
+
+    //ditto
+    @property bool isStart() const { return isStartIR(code); }
+
+    //ditto
+    @property bool isEnd() const { return isEndIR(code); }
+
+    //number of arguments for this instruction
+    @property int args() const { return immediateParamsIR(code); }
+
+    //mark this GroupStart or GroupEnd as referenced in backreference
+    void setBackrefence()
+    {
+        assert(code == IR.GroupStart || code == IR.GroupEnd);
+        raw = raw | 1 << 23;
+    }
+
+    //is referenced
+    @property bool backreference() const
+    {
+        assert(code == IR.GroupStart || code == IR.GroupEnd);
+        return cast(bool)(raw & 1 << 23);
+    }
+
+    //mark as local reference (for backrefs in lookarounds)
+    void setLocalRef()
+    {
+        assert(code == IR.Backref);
+        raw = raw | 1 << 23;
+    }
+
+    //is a local ref
+    @property bool localRef() const
+    {
+        assert(code == IR.Backref);
+        return cast(bool)(raw & 1 << 23);
+    }
+
+    //human readable name of instruction
+    @trusted @property string mnemonic()() const
+    {//@@@BUG@@@ to is @system
+        import std.conv;
+        return to!string(code);
+    }
+
+    //full length of instruction
+    @property uint length() const
+    {
+        return lengthOfIR(code);
+    }
+
+    //full length of respective start/end of this instruction
+    @property uint pairedLength() const
+    {
+        return lengthOfPairedIR(code);
+    }
+
+    //returns bytecode of paired instruction (assuming this one is start or end)
+    @property Bytecode paired() const
+    {//depends on bit and struct layout order
+        assert(isStart || isEnd);
+        return Bytecode.fromRaw(raw ^ 0b11 << 24);
+    }
+
+    //gets an index into IR block of the respective pair
+    uint indexOfPair(uint pc) const
+    {
+        assert(isStart || isEnd);
+        return isStart ? pc + data + length  : pc - data - lengthOfPairedIR(code);
+    }
+}
+
+static assert(Bytecode.sizeof == 4);
+
+
+//index entry structure for name --> number of submatch
+struct NamedGroup
+{
+    string name;
+    uint group;
+}
+
+//holds pair of start-end markers for a submatch
+struct Group(DataIndex)
+{
+    DataIndex begin, end;
+    @trusted string toString()() const
+    {
+        import std.format;
+        auto a = appender!string();
+        formattedWrite(a, "%s..%s", begin, end);
+        return a.data;
+    }
+}
+
+//debugging tool, prints out instruction along with opcodes
+@trusted string disassemble(in Bytecode[] irb, uint pc, in NamedGroup[] dict=[])
+{
+    import std.array, std.format;
+    auto output = appender!string();
+    formattedWrite(output,"%s", irb[pc].mnemonic);
+    switch(irb[pc].code)
+    {
+    case IR.Char:
+        formattedWrite(output, " %s (0x%x)",cast(dchar)irb[pc].data, irb[pc].data);
+        break;
+    case IR.OrChar:
+        formattedWrite(output, " %s (0x%x) seq=%d", cast(dchar)irb[pc].data, irb[pc].data, irb[pc].sequence);
+        break;
+    case IR.RepeatStart, IR.InfiniteStart, IR.Option, IR.GotoEndOr, IR.OrStart:
+        //forward-jump instructions
+        uint len = irb[pc].data;
+        formattedWrite(output, " pc=>%u", pc+len+IRL!(IR.RepeatStart));
+        break;
+    case IR.RepeatEnd, IR.RepeatQEnd: //backward-jump instructions
+        uint len = irb[pc].data;
+        formattedWrite(output, " pc=>%u min=%u max=%u step=%u",
+            pc - len, irb[pc + 3].raw, irb[pc + 4].raw, irb[pc + 2].raw);
+        break;
+    case IR.InfiniteEnd, IR.InfiniteQEnd, IR.OrEnd: //ditto
+        uint len = irb[pc].data;
+        formattedWrite(output, " pc=>%u", pc-len);
+        break;
+    case  IR.LookaheadEnd, IR.NeglookaheadEnd: //ditto
+        uint len = irb[pc].data;
+        formattedWrite(output, " pc=>%u", pc-len);
+        break;
+    case IR.GroupStart, IR.GroupEnd:
+        uint n = irb[pc].data;
+        string name;
+        foreach(v;dict)
+            if(v.group == n)
+            {
+                name = "'"~v.name~"'";
+                break;
+            }
+        formattedWrite(output, " %s #%u " ~ (irb[pc].backreference ? "referenced" : ""),
+                name, n);
+        break;
+    case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+        uint len = irb[pc].data;
+        uint start = irb[pc+1].raw, end = irb[pc+2].raw;
+        formattedWrite(output, " pc=>%u [%u..%u]", pc + len + IRL!(IR.LookaheadStart), start, end);
+        break;
+    case IR.Backref: case IR.CodepointSet: case IR.Trie:
+        uint n = irb[pc].data;
+        formattedWrite(output, " %u",  n);
+        if(irb[pc].code == IR.Backref)
+            formattedWrite(output, " %s", irb[pc].localRef ? "local" : "global");
+        break;
+    default://all data-free instructions
+    }
+    if(irb[pc].hotspot)
+        formattedWrite(output, " Hotspot %u", irb[pc+1].raw);
+    return output.data;
+}
+
+//disassemble the whole chunk
+@trusted void printBytecode()(in Bytecode[] slice, in NamedGroup[] dict=[])
+{
+    import std.stdio;
+    for(uint pc=0; pc<slice.length; pc += slice[pc].length)
+        writeln("\t", disassemble(slice, pc, dict));
+}
+
+/++
+    $(D Regex) object holds regular expression pattern in compiled form.
+    Instances of this object are constructed via calls to $(D regex).
+    This is an intended form for caching and storage of frequently
+    used regular expressions.
++/
+struct Regex(Char)
+{
+    //temporary workaround for identifier lookup
+    CodepointSet[] charsets; //
+    Bytecode[] ir;      //compiled bytecode of pattern
+
+
+    @safe @property bool empty() const nothrow {  return ir is null; }
+
+    @safe @property auto namedCaptures()
+    {
+        static struct NamedGroupRange
+        {
+        private:
+            NamedGroup[] groups;
+            size_t start;
+            size_t end;
+        public:
+            this(NamedGroup[] g, size_t s, size_t e)
+            {
+                assert(s <= e);
+                assert(e <= g.length);
+                groups = g;
+                start = s;
+                end = e;
+            }
+
+            @property string front() { return groups[start].name; }
+            @property string back() { return groups[end-1].name; }
+            @property bool empty() { return start >= end; }
+            @property size_t length() { return end - start; }
+            alias opDollar = length;
+            @property NamedGroupRange save()
+            {
+                return NamedGroupRange(groups, start, end);
+            }
+            void popFront() { assert(!empty); start++; }
+            void popBack() { assert(!empty); end--; }
+            string opIndex()(size_t i)
+            {
+                assert(start + i < end,
+                       "Requested named group is out of range.");
+                return groups[start+i].name;
+            }
+            NamedGroupRange opSlice(size_t low, size_t high) {
+                assert(low <= high);
+                assert(start + high <= end);
+                return NamedGroupRange(groups, start + low, start + high);
+            }
+            NamedGroupRange opSlice() { return this.save; }
+        }
+        return NamedGroupRange(dict, 0, dict.length);
+    }
+
+package(std.regex):
+    import std.regex.internal.kickstart : Kickstart; //TODO: get rid of this dependency
+    NamedGroup[] dict;  //maps name -> user group number
+    uint ngroup;        //number of internal groups
+    uint maxCounterDepth; //max depth of nested {n,m} repetitions
+    uint hotspotTableSize; //number of entries in merge table
+    uint threadCount;
+    uint flags;         //global regex flags
+    public const(Trie)[]  tries; //
+    uint[] backrefed; //bit array of backreferenced submatches
+    Kickstart!Char kickstart;
+
+    //bit access helper
+    uint isBackref(uint n)
+    {
+        if(n/32 >= backrefed.length)
+            return 0;
+        return backrefed[n / 32] & (1 << (n & 31));
+    }
+
+    //check if searching is not needed
+    void checkIfOneShot()
+    {
+        if(flags & RegexOption.multiline)
+            return;
+    L_CheckLoop:
+        for(uint i = 0; i < ir.length; i += ir[i].length)
+        {
+            switch(ir[i].code)
+            {
+                case IR.Bol:
+                    flags |= RegexInfo.oneShot;
+                    break L_CheckLoop;
+                case IR.GroupStart, IR.GroupEnd, IR.Eol, IR.Wordboundary, IR.Notwordboundary:
+                    break;
+                default:
+                    break L_CheckLoop;
+            }
+        }
+    }
+
+    //print out disassembly a program's IR
+    @trusted debug(std_regex_parser) void print() const
+    {//@@@BUG@@@ write is system
+        for(uint i = 0; i < ir.length; i += ir[i].length)
+        {
+            writefln("%d\t%s ", i, disassemble(ir, i, dict));
+        }
+        writeln("Total merge table size: ", hotspotTableSize);
+        writeln("Max counter nesting depth: ", maxCounterDepth);
+    }
+
+}
+
+//@@@BUG@@@ (unreduced) - public makes it inaccessible in std.regex.package (!)
+/*public*/ struct StaticRegex(Char)
+{
+package(std.regex):
+    import std.regex.internal.backtracking;
+    alias Matcher = BacktrackingMatcher!(true);
+    alias MatchFn = bool function(ref Matcher!Char) @trusted;
+    MatchFn nativeFn;
+public:
+    Regex!Char _regex;
+    alias _regex this;
+    this(Regex!Char re, MatchFn fn)
+    {
+        _regex = re;
+        nativeFn = fn;
+
+    }
+
+}
+
+// The stuff below this point is temporarrily part of IR module
+// but may need better place in the future (all internals)
+package(std.regex):
+
+//Simple UTF-string abstraction compatible with stream interface
+struct Input(Char)
+    if(is(Char :dchar))
+{
+    import std.utf;
+    alias DataIndex = size_t;
+    enum { isLoopback = false };
+    alias String = const(Char)[];
+    String _origin;
+    size_t _index;
+
+    //constructs Input object out of plain string
+    this(String input, size_t idx = 0)
+    {
+        _origin = input;
+        _index = idx;
+    }
+
+    //codepoint at current stream position
+    bool nextChar(ref dchar res, ref size_t pos)
+    {
+        pos = _index;
+        if(_index == _origin.length)
+            return false;
+        res = std.utf.decode(_origin, _index);
+        return true;
+    }
+    @property bool atEnd(){
+        return _index == _origin.length;
+    }
+    bool search(Kickstart)(ref Kickstart kick, ref dchar res, ref size_t pos)
+    {
+        size_t idx = kick.search(_origin, _index);
+        _index = idx;
+        return nextChar(res, pos);
+    }
+
+    //index of at End position
+    @property size_t lastIndex(){   return _origin.length; }
+
+    //support for backtracker engine, might not be present
+    void reset(size_t index){   _index = index;  }
+
+    String opSlice(size_t start, size_t end){   return _origin[start..end]; }
+
+    struct BackLooper
+    {
+        alias DataIndex = size_t;
+        enum { isLoopback = true };
+        String _origin;
+        size_t _index;
+        this(Input input, size_t index)
+        {
+            _origin = input._origin;
+            _index = index;
+        }
+        @trusted bool nextChar(ref dchar res,ref size_t pos)
+        {
+            pos = _index;
+            if(_index == 0)
+                return false;
+
+            res = _origin[0.._index].back;
+            _index -= std.utf.strideBack(_origin, _index);
+
+            return true;
+        }
+        @property atEnd(){ return _index == 0 || _index == std.utf.strideBack(_origin, _index); }
+        auto loopBack(size_t index){   return Input(_origin, index); }
+
+        //support for backtracker engine, might not be present
+        //void reset(size_t index){   _index = index ? index-std.utf.strideBack(_origin, index) : 0;  }
+        void reset(size_t index){   _index = index;  }
+
+        String opSlice(size_t start, size_t end){   return _origin[end..start]; }
+        //index of at End position
+        @property size_t lastIndex(){   return 0; }
+    }
+    auto loopBack(size_t index){   return BackLooper(this, index); }
+}
+
+
+//both helpers below are internal, on its own are quite "explosive"
+//unsafe, no initialization of elements
+@system T[] mallocArray(T)(size_t len)
+{
+    import core.stdc.stdlib;
+    return (cast(T*)malloc(len * T.sizeof))[0 .. len];
+}
+
+//very unsafe, no initialization
+@system T[] arrayInChunk(T)(size_t len, ref void[] chunk)
+{
+    auto ret = (cast(T*)chunk.ptr)[0..len];
+    chunk = chunk[len * T.sizeof .. $];
+    return ret;
+}
+
+//
+@trusted uint lookupNamedGroup(String)(NamedGroup[] dict, String name)
+{//equal is @system?
+    import std.conv;
+    import std.algorithm : map, equal;
+
+    auto fnd = assumeSorted!"cmp(a,b) < 0"(map!"a.name"(dict)).lowerBound(name).length;
+    enforce(fnd < dict.length && equal(dict[fnd].name, name),
+        text("no submatch named ", name));
+    return dict[fnd].group;
+}
+
+//whether ch is one of unicode newline sequences
+//0-arg template due to @@@BUG@@@ 10985
+bool endOfLine()(dchar front, bool seenCr)
+{
+    return ((front == '\n') ^ seenCr) || front == '\r'
+    || front == NEL || front == LS || front == PS;
+}
+
+//
+//0-arg template due to @@@BUG@@@ 10985
+bool startOfLine()(dchar back, bool seenNl)
+{
+    return ((back == '\r') ^ seenNl) || back == '\n'
+    || back == NEL || back == LS || back == PS;
+}
+
+//Test if bytecode starting at pc in program 're' can match given codepoint
+//Returns: 0 - can't tell, -1 if doesn't match
+int quickTestFwd(RegEx)(uint pc, dchar front, const ref RegEx re)
+{
+    static assert(IRL!(IR.OrChar) == 1);//used in code processing IR.OrChar
+    for(;;)
+        switch(re.ir[pc].code)
+        {
+        case IR.OrChar:
+            uint len = re.ir[pc].sequence;
+            uint end = pc + len;
+            if(re.ir[pc].data != front && re.ir[pc+1].data != front)
+            {
+                for(pc = pc+2; pc < end; pc++)
+                    if(re.ir[pc].data == front)
+                        break;
+                if(pc == end)
+                    return -1;
+            }
+            return 0;
+        case IR.Char:
+            if(front == re.ir[pc].data)
+                return 0;
+            else
+                return -1;
+        case IR.Any:
+            return 0;
+        case IR.CodepointSet:
+            if(re.charsets[re.ir[pc].data].scanFor(front))
+                return 0;
+            else
+                return -1;
+        case IR.GroupStart, IR.GroupEnd:
+            pc += IRL!(IR.GroupStart);
+            break;
+        case IR.Trie:
+            if(re.tries[re.ir[pc].data][front])
+                return 0;
+            else
+                return -1;
+        default:
+            return 0;
+        }
+}
+
+///Exception object thrown in case of errors during regex compilation.
+public class RegexException : Exception
+{
+    mixin basicExceptionCtors;
+}
diff --git a/libphobos/src/std/regex/internal/kickstart.d b/libphobos/src/std/regex/internal/kickstart.d
new file mode 100644
index 00000000000..86a38f42397
--- /dev/null
+++ b/libphobos/src/std/regex/internal/kickstart.d
@@ -0,0 +1,546 @@
+/*
+    Kickstart is a coarse-grained "filter" engine that finds likely matches
+    to be verified by full-blown matcher.
+*/
+module std.regex.internal.kickstart;
+
+package(std.regex):
+
+import std.regex.internal.ir;
+import std.algorithm, std.range, std.utf;
+
+//utility for shiftOr, returns a minimum number of bytes to test in a Char
+uint effectiveSize(Char)()
+{
+    static if(is(Char == char))
+        return 1;
+    else static if(is(Char == wchar))
+        return 2;
+    else static if(is(Char == dchar))
+        return 3;
+    else
+        static assert(0);
+}
+
+/*
+    Kickstart engine using ShiftOr algorithm,
+    a bit parallel technique for inexact string searching.
+*/
+struct ShiftOr(Char)
+{
+private:
+    uint[] table;
+    uint fChar;
+    uint n_length;
+    enum charSize =  effectiveSize!Char();
+    //maximum number of chars in CodepointSet to process
+    enum uint charsetThreshold = 32_000;
+    static struct ShiftThread
+    {
+        uint[] tab;
+        uint mask;
+        uint idx;
+        uint pc, counter, hops;
+        this(uint newPc, uint newCounter, uint[] table)
+        {
+            pc = newPc;
+            counter = newCounter;
+            mask = 1;
+            idx = 0;
+            hops = 0;
+            tab = table;
+        }
+
+        void setMask(uint idx, uint mask)
+        {
+            tab[idx] |= mask;
+        }
+
+        void setInvMask(uint idx, uint mask)
+        {
+            tab[idx] &= ~mask;
+        }
+
+        void set(alias setBits = setInvMask)(dchar ch)
+        {
+            static if(charSize == 3)
+            {
+                uint val = ch, tmask = mask;
+                setBits(val&0xFF, tmask);
+                tmask <<= 1;
+                val >>= 8;
+                setBits(val&0xFF, tmask);
+                tmask <<= 1;
+                val >>= 8;
+                assert(val <= 0x10);
+                setBits(val, tmask);
+                tmask <<= 1;
+            }
+            else
+            {
+                Char[dchar.sizeof/Char.sizeof] buf;
+                uint tmask = mask;
+                size_t total = encode(buf, ch);
+                for(size_t i = 0; i < total; i++, tmask<<=1)
+                {
+                    static if(charSize == 1)
+                        setBits(buf[i], tmask);
+                    else static if(charSize == 2)
+                    {
+                        setBits(buf[i]&0xFF, tmask);
+                        tmask <<= 1;
+                        setBits(buf[i]>>8, tmask);
+                    }
+                }
+            }
+        }
+        void add(dchar ch){ return set!setInvMask(ch); }
+        void advance(uint s)
+        {
+            mask <<= s;
+            idx += s;
+        }
+        @property bool full(){    return !mask; }
+    }
+
+    static ShiftThread fork(ShiftThread t, uint newPc, uint newCounter)
+    {
+        ShiftThread nt = t;
+        nt.pc = newPc;
+        nt.counter = newCounter;
+        return nt;
+    }
+
+    @trusted static ShiftThread fetch(ref ShiftThread[] worklist)
+    {
+        auto t = worklist[$-1];
+        worklist.length -= 1;
+        if(!__ctfe)
+            cast(void)worklist.assumeSafeAppend();
+        return t;
+    }
+
+    static uint charLen(uint ch)
+    {
+        assert(ch <= 0x10FFFF);
+        return codeLength!Char(cast(dchar)ch)*charSize;
+    }
+
+public:
+    @trusted this(ref Regex!Char re, uint[] memory)
+    {
+        import std.conv;
+        assert(memory.length == 256);
+        fChar = uint.max;
+    L_FindChar:
+        for(size_t i = 0;;)
+        {
+            switch(re.ir[i].code)
+            {
+                case IR.Char:
+                    fChar = re.ir[i].data;
+                    static if(charSize != 3)
+                    {
+                        Char[dchar.sizeof/Char.sizeof] buf;
+                        encode(buf, fChar);
+                        fChar = buf[0];
+                    }
+                    fChar = fChar & 0xFF;
+                    break L_FindChar;
+                case IR.GroupStart, IR.GroupEnd:
+                    i += IRL!(IR.GroupStart);
+                    break;
+                case IR.Bol, IR.Wordboundary, IR.Notwordboundary:
+                    i += IRL!(IR.Bol);
+                    break;
+                default:
+                    break L_FindChar;
+            }
+        }
+        table = memory;
+        table[] =  uint.max;
+        ShiftThread[] trs;
+        ShiftThread t = ShiftThread(0, 0, table);
+        //locate first fixed char if any
+        n_length = 32;
+        for(;;)
+        {
+        L_Eval_Thread:
+            for(;;)
+            {
+                switch(re.ir[t.pc].code)
+                {
+                case IR.Char:
+                    uint s = charLen(re.ir[t.pc].data);
+                    if(t.idx+s > n_length)
+                        goto L_StopThread;
+                    t.add(re.ir[t.pc].data);
+                    t.advance(s);
+                    t.pc += IRL!(IR.Char);
+                    break;
+                case IR.OrChar://assumes IRL!(OrChar) == 1
+                    uint len = re.ir[t.pc].sequence;
+                    uint end = t.pc + len;
+                    uint[Bytecode.maxSequence] s;
+                    uint numS;
+                    for(uint i = 0; i < len; i++)
+                    {
+                        auto x = charLen(re.ir[t.pc+i].data);
+                        if(countUntil(s[0..numS], x) < 0)
+                           s[numS++] = x;
+                    }
+                    for(uint i = t.pc; i < end; i++)
+                    {
+                        t.add(re.ir[i].data);
+                    }
+                    for(uint i = 0; i < numS; i++)
+                    {
+                        auto tx = fork(t, t.pc + len, t.counter);
+                        if(tx.idx + s[i] <= n_length)
+                        {
+                            tx.advance(s[i]);
+                            trs ~= tx;
+                        }
+                    }
+                    if(!trs.empty)
+                        t = fetch(trs);
+                    else
+                        goto L_StopThread;
+                    break;
+                case IR.CodepointSet:
+                case IR.Trie:
+                    auto set = re.charsets[re.ir[t.pc].data];
+                    uint[4] s;
+                    uint numS;
+                    static if(charSize == 3)
+                    {
+                        s[0] = charSize;
+                        numS = 1;
+                    }
+                    else
+                    {
+
+                        static if(charSize == 1)
+                            static immutable codeBounds = [0x0, 0x7F, 0x80, 0x7FF, 0x800, 0xFFFF, 0x10000, 0x10FFFF];
+                        else //== 2
+                            static immutable codeBounds = [0x0, 0xFFFF, 0x10000, 0x10FFFF];
+                        uint[] arr = new uint[set.byInterval.length * 2];
+                        size_t ofs = 0;
+                        foreach(ival; set.byInterval)
+                        {
+                            arr[ofs++] = ival.a;
+                            arr[ofs++] = ival.b;
+                        }
+                        auto srange = assumeSorted!"a <= b"(arr);
+                        for(uint i = 0; i < codeBounds.length/2; i++)
+                        {
+                            auto start = srange.lowerBound(codeBounds[2*i]).length;
+                            auto end = srange.lowerBound(codeBounds[2*i+1]).length;
+                            if(end > start || (end == start && (end & 1)))
+                               s[numS++] = (i+1)*charSize;
+                        }
+                    }
+                    if(numS == 0 || t.idx + s[numS-1] > n_length)
+                        goto L_StopThread;
+                    auto  chars = set.length;
+                    if(chars > charsetThreshold)
+                        goto L_StopThread;
+                    foreach(ch; set.byCodepoint)
+                    {
+                        //avoid surrogate pairs
+                        if(0xD800 <= ch && ch <= 0xDFFF)
+                            continue;
+                        t.add(ch);
+                    }
+                    for(uint i = 0; i < numS; i++)
+                    {
+                        auto tx =  fork(t, t.pc + IRL!(IR.CodepointSet), t.counter);
+                        tx.advance(s[i]);
+                        trs ~= tx;
+                    }
+                    if(!trs.empty)
+                        t = fetch(trs);
+                    else
+                        goto L_StopThread;
+                    break;
+                case IR.Any:
+                    goto L_StopThread;
+
+                case IR.GotoEndOr:
+                    t.pc += IRL!(IR.GotoEndOr)+re.ir[t.pc].data;
+                    assert(re.ir[t.pc].code == IR.OrEnd);
+                    goto case;
+                case IR.OrEnd:
+                    t.pc += IRL!(IR.OrEnd);
+                    break;
+                case IR.OrStart:
+                    t.pc += IRL!(IR.OrStart);
+                    goto case;
+                case IR.Option:
+                    uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option);
+                    //queue next Option
+                    if(re.ir[next].code == IR.Option)
+                    {
+                        trs ~= fork(t, next, t.counter);
+                    }
+                    t.pc += IRL!(IR.Option);
+                    break;
+                case IR.RepeatStart:case IR.RepeatQStart:
+                    t.pc += IRL!(IR.RepeatStart)+re.ir[t.pc].data;
+                    goto case IR.RepeatEnd;
+                case IR.RepeatEnd:
+                case IR.RepeatQEnd:
+                    uint len = re.ir[t.pc].data;
+                    uint step = re.ir[t.pc+2].raw;
+                    uint min = re.ir[t.pc+3].raw;
+                    if(t.counter < min)
+                    {
+                        t.counter += step;
+                        t.pc -= len;
+                        break;
+                    }
+                    uint max = re.ir[t.pc+4].raw;
+                    if(t.counter < max)
+                    {
+                        trs ~= fork(t, t.pc - len, t.counter + step);
+                        t.counter = t.counter%step;
+                        t.pc += IRL!(IR.RepeatEnd);
+                    }
+                    else
+                    {
+                        t.counter = t.counter%step;
+                        t.pc += IRL!(IR.RepeatEnd);
+                    }
+                    break;
+                case IR.InfiniteStart, IR.InfiniteQStart:
+                    t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart);
+                    goto case IR.InfiniteEnd; //both Q and non-Q
+                case IR.InfiniteEnd:
+                case IR.InfiniteQEnd:
+                    uint len = re.ir[t.pc].data;
+                    uint pc1, pc2; //branches to take in priority order
+                    if(++t.hops == 32)
+                        goto L_StopThread;
+                    pc1 = t.pc + IRL!(IR.InfiniteEnd);
+                    pc2 = t.pc - len;
+                    trs ~= fork(t, pc2, t.counter);
+                    t.pc = pc1;
+                    break;
+                case IR.GroupStart, IR.GroupEnd:
+                    t.pc += IRL!(IR.GroupStart);
+                    break;
+                case IR.Bol, IR.Wordboundary, IR.Notwordboundary:
+                    t.pc += IRL!(IR.Bol);
+                    break;
+                case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+                    t.pc += IRL!(IR.LookaheadStart) + IRL!(IR.LookaheadEnd) + re.ir[t.pc].data;
+                    break;
+                default:
+                L_StopThread:
+                    assert(re.ir[t.pc].code >= 0x80, text(re.ir[t.pc].code));
+                    debug (fred_search) writeln("ShiftOr stumbled on ",re.ir[t.pc].mnemonic);
+                    n_length = min(t.idx, n_length);
+                    break L_Eval_Thread;
+                }
+            }
+            if(trs.empty)
+                break;
+            t = fetch(trs);
+        }
+        debug(std_regex_search)
+        {
+            writeln("Min length: ", n_length);
+        }
+    }
+
+    @property bool empty() const {  return n_length == 0; }
+
+    @property uint length() const{ return n_length/charSize; }
+
+    // lookup compatible bit pattern in haystack, return starting index
+    // has a useful trait: if supplied with valid UTF indexes,
+    // returns only valid UTF indexes
+    // (that given the haystack in question is valid UTF string)
+    @trusted size_t search(const(Char)[] haystack, size_t idx)
+    {//@BUG: apparently assumes little endian machines
+        import std.conv, core.stdc.string;
+        assert(!empty);
+        auto p = cast(const(ubyte)*)(haystack.ptr+idx);
+        uint state = uint.max;
+        uint limit = 1u<<(n_length - 1u);
+        debug(std_regex_search) writefln("Limit: %32b",limit);
+        if(fChar != uint.max)
+        {
+            const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length);
+            const orginalAlign = cast(size_t)p & (Char.sizeof-1);
+            while(p != end)
+            {
+                if(!~state)
+                {//speed up seeking first matching place
+                    for(;;)
+                    {
+                        assert(p <= end, text(p," vs ", end));
+                        p = cast(ubyte*)memchr(p, fChar, end - p);
+                        if(!p)
+                            return haystack.length;
+                        if((cast(size_t)p & (Char.sizeof-1)) == orginalAlign)
+                            break;
+                        if(++p == end)
+                            return haystack.length;
+                    }
+                    state = ~1u;
+                    assert((cast(size_t)p & (Char.sizeof-1)) == orginalAlign);
+                    static if(charSize == 3)
+                    {
+                        state = (state<<1) | table[p[1]];
+                        state = (state<<1) | table[p[2]];
+                        p += 4;
+                    }
+                    else
+                        p++;
+                    //first char is tested, see if that's all
+                    if(!(state & limit))
+                        return (p-cast(ubyte*)haystack.ptr)/Char.sizeof
+                            -length;
+                }
+                else
+                {//have some bits/states for possible matches,
+                 //use the usual shift-or cycle
+                    static if(charSize == 3)
+                    {
+                        state = (state<<1) | table[p[0]];
+                        state = (state<<1) | table[p[1]];
+                        state = (state<<1) | table[p[2]];
+                        p += 4;
+                    }
+                    else
+                    {
+                        state = (state<<1) | table[p[0]];
+                        p++;
+                    }
+                    if(!(state & limit))
+                        return (p-cast(ubyte*)haystack.ptr)/Char.sizeof
+                            -length;
+                }
+                debug(std_regex_search) writefln("State: %32b", state);
+            }
+        }
+        else
+        {
+            //normal path, partially unrolled for char/wchar
+            static if(charSize == 3)
+            {
+                const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length);
+                while(p != end)
+                {
+                    state = (state<<1) | table[p[0]];
+                    state = (state<<1) | table[p[1]];
+                    state = (state<<1) | table[p[2]];
+                    p += 4;
+                    if(!(state & limit))//division rounds down for dchar
+                        return (p-cast(ubyte*)haystack.ptr)/Char.sizeof
+                        -length;
+                }
+            }
+            else
+            {
+                auto len = cast(ubyte*)(haystack.ptr + haystack.length) - p;
+                size_t i  = 0;
+                if(len & 1)
+                {
+                    state = (state<<1) | table[p[i++]];
+                    if(!(state & limit))
+                        return idx+i/Char.sizeof-length;
+                }
+                while(i < len)
+                {
+                    state = (state<<1) | table[p[i++]];
+                    if(!(state & limit))
+                        return idx+i/Char.sizeof
+                            -length;
+                    state = (state<<1) | table[p[i++]];
+                    if(!(state & limit))
+                        return idx+i/Char.sizeof
+                            -length;
+                    debug(std_regex_search) writefln("State: %32b", state);
+                }
+            }
+        }
+        return haystack.length;
+    }
+
+    @system debug static void dump(uint[] table)
+    {//@@@BUG@@@ writef(ln) is @system
+        import std.stdio;
+        for(size_t i = 0; i < table.length; i += 4)
+        {
+            writefln("%32b %32b %32b %32b",table[i], table[i+1], table[i+2], table[i+3]);
+        }
+    }
+}
+
+unittest
+{
+    import std.conv, std.regex;
+    @trusted void test_fixed(alias Kick)()
+    {
+        foreach(i, v; AliasSeq!(char, wchar, dchar))
+        {
+            alias Char = v;
+            alias String = immutable(v)[];
+            auto r = regex(to!String(`abc$`));
+            auto kick = Kick!Char(r, new uint[256]);
+            assert(kick.length == 3, text(Kick.stringof," ",v.stringof, " == ", kick.length));
+            auto r2 = regex(to!String(`(abc){2}a+`));
+            kick = Kick!Char(r2, new uint[256]);
+            assert(kick.length == 7, text(Kick.stringof,v.stringof," == ", kick.length));
+            auto r3 = regex(to!String(`\b(a{2}b{3}){2,4}`));
+            kick = Kick!Char(r3, new uint[256]);
+            assert(kick.length == 10, text(Kick.stringof,v.stringof," == ", kick.length));
+            auto r4 = regex(to!String(`\ba{2}c\bxyz`));
+            kick = Kick!Char(r4, new uint[256]);
+            assert(kick.length == 6, text(Kick.stringof,v.stringof, " == ", kick.length));
+            auto r5 = regex(to!String(`\ba{2}c\b`));
+            kick = Kick!Char(r5, new uint[256]);
+            size_t x = kick.search("aabaacaa", 0);
+            assert(x == 3, text(Kick.stringof,v.stringof," == ", kick.length));
+            x = kick.search("aabaacaa", x+1);
+            assert(x == 8, text(Kick.stringof,v.stringof," == ", kick.length));
+        }
+    }
+    @trusted void test_flex(alias Kick)()
+    {
+        foreach(i, v; AliasSeq!(char, wchar, dchar))
+        {
+            alias Char = v;
+            alias String = immutable(v)[];
+            auto r = regex(to!String(`abc[a-z]`));
+            auto kick = Kick!Char(r, new uint[256]);
+            auto x = kick.search(to!String("abbabca"), 0);
+            assert(x == 3, text("real x is ", x, " ",v.stringof));
+
+            auto r2 = regex(to!String(`(ax|bd|cdy)`));
+            String s2 = to!String("abdcdyabax");
+            kick = Kick!Char(r2, new uint[256]);
+            x = kick.search(s2, 0);
+            assert(x == 1, text("real x is ", x));
+            x = kick.search(s2, x+1);
+            assert(x == 3, text("real x is ", x));
+            x = kick.search(s2, x+1);
+            assert(x == 8, text("real x is ", x));
+            auto rdot = regex(to!String(`...`));
+            kick = Kick!Char(rdot, new uint[256]);
+            assert(kick.length == 0);
+            auto rN = regex(to!String(`a(b+|c+)x`));
+            kick = Kick!Char(rN, new uint[256]);
+            assert(kick.length == 3);
+            assert(kick.search("ababx",0) == 2);
+            assert(kick.search("abaacba",0) == 3);//expected inexact
+
+        }
+    }
+    test_fixed!(ShiftOr)();
+    test_flex!(ShiftOr)();
+}
+
+alias Kickstart = ShiftOr;
diff --git a/libphobos/src/std/regex/internal/parser.d b/libphobos/src/std/regex/internal/parser.d
new file mode 100644
index 00000000000..685204d2e97
--- /dev/null
+++ b/libphobos/src/std/regex/internal/parser.d
@@ -0,0 +1,1499 @@
+//Written in the D programming language
+/*
+    Regular expression pattern parser.
+*/
+module std.regex.internal.parser;
+
+import std.regex.internal.ir;
+import std.algorithm, std.range, std.uni, std.meta,
+    std.traits, std.typecons, std.exception;
+static import std.ascii;
+
+// package relevant info from parser into a regex object
+auto makeRegex(S)(Parser!S p)
+{
+    Regex!(BasicElementOf!S) re;
+    with(re)
+    {
+        ir = p.ir;
+        dict = p.dict;
+        ngroup = p.groupStack.top;
+        maxCounterDepth = p.counterDepth;
+        flags = p.re_flags;
+        charsets = p.charsets;
+        tries = p.tries;
+        backrefed = p.backrefed;
+        re.lightPostprocess();
+        debug(std_regex_parser)
+        {
+            print();
+        }
+        //@@@BUG@@@ (not reduced)
+        //somehow just using validate _collides_ with std.utf.validate (!)
+        version(assert) re.validateRe();
+    }
+    return re;
+}
+
+// helper for unittest
+auto makeRegex(S)(S arg)
+    if(isSomeString!S)
+{
+    return makeRegex(Parser!S(arg, ""));
+}
+
+unittest
+{
+    auto re = makeRegex(`(?P<name>\w+) = (?P<var>\d+)`);
+    auto nc = re.namedCaptures;
+    static assert(isRandomAccessRange!(typeof(nc)));
+    assert(!nc.empty);
+    assert(nc.length == 2);
+    assert(nc.equal(["name", "var"]));
+    assert(nc[0] == "name");
+    assert(nc[1..$].equal(["var"]));
+
+    re = makeRegex(`(\w+) (?P<named>\w+) (\w+)`);
+    nc = re.namedCaptures;
+    assert(nc.length == 1);
+    assert(nc[0] == "named");
+    assert(nc.front == "named");
+    assert(nc.back == "named");
+
+    re = makeRegex(`(\w+) (\w+)`);
+    nc = re.namedCaptures;
+    assert(nc.empty);
+
+    re = makeRegex(`(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/`);
+    nc = re.namedCaptures;
+    auto cp = nc.save;
+    assert(nc.equal(cp));
+    nc.popFront();
+    assert(nc.equal(cp[1..$]));
+    nc.popBack();
+    assert(nc.equal(cp[1 .. $ - 1]));
+}
+
+
+@trusted void reverseBytecode()(Bytecode[] code)
+{
+    Bytecode[] rev = new Bytecode[code.length];
+    uint revPc = cast(uint)rev.length;
+    Stack!(Tuple!(uint, uint, uint)) stack;
+    uint start = 0;
+    uint end = cast(uint)code.length;
+    for(;;)
+    {
+        for(uint pc = start; pc < end; )
+        {
+            uint len = code[pc].length;
+            if(code[pc].code == IR.GotoEndOr)
+                break; //pick next alternation branch
+            if(code[pc].isAtom)
+            {
+                rev[revPc - len .. revPc] = code[pc .. pc + len];
+                revPc -= len;
+                pc += len;
+            }
+            else if(code[pc].isStart || code[pc].isEnd)
+            {
+                //skip over other embedded lookbehinds they are reversed
+                if(code[pc].code == IR.LookbehindStart
+                    || code[pc].code == IR.NeglookbehindStart)
+                {
+                    uint blockLen = len + code[pc].data
+                         + code[pc].pairedLength;
+                    rev[revPc - blockLen .. revPc] = code[pc .. pc + blockLen];
+                    pc += blockLen;
+                    revPc -= blockLen;
+                    continue;
+                }
+                uint second = code[pc].indexOfPair(pc);
+                uint secLen = code[second].length;
+                rev[revPc - secLen .. revPc] = code[second .. second + secLen];
+                revPc -= secLen;
+                if(code[pc].code == IR.OrStart)
+                {
+                    //we pass len bytes forward, but secLen in reverse
+                    uint revStart = revPc - (second + len - secLen - pc);
+                    uint r = revStart;
+                    uint i = pc + IRL!(IR.OrStart);
+                    while(code[i].code == IR.Option)
+                    {
+                        if(code[i - 1].code != IR.OrStart)
+                        {
+                            assert(code[i - 1].code == IR.GotoEndOr);
+                            rev[r - 1] = code[i - 1];
+                        }
+                        rev[r] = code[i];
+                        auto newStart = i + IRL!(IR.Option);
+                        auto newEnd = newStart + code[i].data;
+                        auto newRpc = r + code[i].data + IRL!(IR.Option);
+                        if(code[newEnd].code != IR.OrEnd)
+                        {
+                            newRpc--;
+                        }
+                        stack.push(tuple(newStart, newEnd, newRpc));
+                        r += code[i].data + IRL!(IR.Option);
+                        i += code[i].data + IRL!(IR.Option);
+                    }
+                    pc = i;
+                    revPc = revStart;
+                    assert(code[pc].code == IR.OrEnd);
+                }
+                else
+                    pc += len;
+            }
+        }
+        if(stack.empty)
+            break;
+        start = stack.top[0];
+        end = stack.top[1];
+        revPc = stack.top[2];
+        stack.pop();
+    }
+    code[] = rev[];
+}
+
+
+alias Escapables = AliasSeq!('[', ']', '\\', '^', '$', '.', '|', '?', ',', '-',
+    ';', ':', '#', '&', '%', '/', '<', '>', '`',  '*', '+', '(', ')', '{', '}',  '~');
+
+//test if a given string starts with hex number of maxDigit that's a valid codepoint
+//returns it's value and skips these maxDigit chars on success, throws on failure
+dchar parseUniHex(Char)(ref Char[] str, size_t maxDigit)
+{
+    //std.conv.parse is both @system and bogus
+    enforce(str.length >= maxDigit,"incomplete escape sequence");
+    uint val;
+    for(int k = 0; k < maxDigit; k++)
+    {
+        auto current = str[k];//accepts ascii only, so it's OK to index directly
+        if('0' <= current && current <= '9')
+            val = val * 16 + current - '0';
+        else if('a' <= current && current <= 'f')
+            val = val * 16 + current -'a' + 10;
+        else if('A' <= current && current <= 'F')
+            val = val * 16 + current - 'A' + 10;
+        else
+            throw new Exception("invalid escape sequence");
+    }
+    enforce(val <= 0x10FFFF, "invalid codepoint");
+    str = str[maxDigit..$];
+    return val;
+}
+
+@system unittest //BUG canFind is system
+{
+    string[] non_hex = [ "000j", "000z", "FffG", "0Z"];
+    string[] hex = [ "01", "ff", "00af", "10FFFF" ];
+    int[] value = [ 1, 0xFF, 0xAF, 0x10FFFF ];
+    foreach(v; non_hex)
+        assert(collectException(parseUniHex(v, v.length)).msg
+          .canFind("invalid escape sequence"));
+    foreach(i, v; hex)
+        assert(parseUniHex(v, v.length) == value[i]);
+    string over = "0011FFFF";
+    assert(collectException(parseUniHex(over, over.length)).msg
+      .canFind("invalid codepoint"));
+}
+
+//heuristic value determines maximum CodepointSet length suitable for linear search
+enum maxCharsetUsed = 6;
+
+enum maxCachedTries = 8;
+
+alias Trie = CodepointSetTrie!(13, 8);
+alias makeTrie = codepointSetTrie!(13, 8);
+
+Trie[CodepointSet] trieCache;
+
+//accessor with caching
+@trusted Trie getTrie(CodepointSet set)
+{// @@@BUG@@@ 6357 almost all properties of AA are not @safe
+    if(__ctfe || maxCachedTries == 0)
+        return makeTrie(set);
+    else
+    {
+        auto p = set in trieCache;
+        if(p)
+            return *p;
+        if(trieCache.length == maxCachedTries)
+        {
+            // flush entries in trieCache
+            trieCache = null;
+        }
+        return (trieCache[set] = makeTrie(set));
+    }
+}
+
+
+auto caseEnclose(CodepointSet set)
+{
+    auto cased = set & unicode.LC;
+    foreach (dchar ch; cased.byCodepoint)
+    {
+        foreach(c; simpleCaseFoldings(ch))
+            set |= c;
+    }
+    return set;
+}
+
+/+
+    fetch codepoint set corresponding to a name (InBlock or binary property)
++/
+@trusted CodepointSet getUnicodeSet(in char[] name, bool negated,  bool casefold)
+{
+    CodepointSet s = unicode(name);
+    //FIXME: caseEnclose for new uni as Set | CaseEnclose(SET && LC)
+    if(casefold)
+       s = caseEnclose(s);
+    if(negated)
+        s = s.inverted;
+    return s;
+}
+
+//basic stack, just in case it gets used anywhere else then Parser
+@trusted struct Stack(T)
+{
+    T[] data;
+    @property bool empty(){ return data.empty; }
+
+    @property size_t length(){ return data.length; }
+
+    void push(T val){ data ~= val;  }
+
+    T pop()
+    {
+        assert(!empty);
+        auto val = data[$ - 1];
+        data = data[0 .. $ - 1];
+        if(!__ctfe)
+            cast(void)data.assumeSafeAppend();
+        return val;
+    }
+
+    @property ref T top()
+    {
+        assert(!empty);
+        return data[$ - 1];
+    }
+}
+
+//safety limits
+enum maxGroupNumber = 2^^19;
+enum maxLookaroundDepth = 16;
+// *Bytecode.sizeof, i.e. 1Mb of bytecode alone
+enum maxCompiledLength = 2^^18;
+//amounts to up to 4 Mb of auxilary table for matching
+enum maxCumulativeRepetitionLength = 2^^20;
+
+struct Parser(R)
+    if (isForwardRange!R && is(ElementType!R : dchar))
+{
+    enum infinite = ~0u;
+    dchar _current;
+    bool empty;
+    R pat, origin;       //keep full pattern for pretty printing error messages
+    Bytecode[] ir;       //resulting bytecode
+    uint re_flags = 0;   //global flags e.g. multiline + internal ones
+    Stack!(uint) fixupStack;  //stack of opened start instructions
+    NamedGroup[] dict;   //maps name -> user group number
+    //current num of group, group nesting level and repetitions step
+    Stack!(uint) groupStack;
+    uint nesting = 0;
+    uint lookaroundNest = 0;
+    uint counterDepth = 0; //current depth of nested counted repetitions
+    CodepointSet[] charsets;  //
+    const(Trie)[] tries; //
+    uint[] backrefed; //bitarray for groups
+
+    @trusted this(S)(R pattern, S flags)
+        if(isSomeString!S)
+    {
+        pat = origin = pattern;
+        //reserve slightly more then avg as sampled from unittests
+        if(!__ctfe)
+            ir.reserve((pat.length*5+2)/4);
+        parseFlags(flags);
+        _current = ' ';//a safe default for freeform parsing
+        next();
+        try
+        {
+            parseRegex();
+        }
+        catch(Exception e)
+        {
+            error(e.msg);//also adds pattern location
+        }
+        put(Bytecode(IR.End, 0));
+    }
+
+    //mark referenced groups for latter processing
+    void markBackref(uint n)
+    {
+        if(n/32 >= backrefed.length)
+            backrefed.length = n/32 + 1;
+        backrefed[n / 32] |= 1 << (n & 31);
+    }
+
+    bool isOpenGroup(uint n)
+    {
+        // walk the fixup stack and see if there are groups labeled 'n'
+        // fixup '0' is reserved for alternations
+        return fixupStack.data[1..$].
+            canFind!(fix => ir[fix].code == IR.GroupStart && ir[fix].data == n)();
+    }
+
+    @property dchar current(){ return _current; }
+
+    bool _next()
+    {
+        if(pat.empty)
+        {
+            empty =  true;
+            return false;
+        }
+        _current = pat.front;
+        pat.popFront();
+        return true;
+    }
+
+    void skipSpace()
+    {
+        while(isWhite(current) && _next()){ }
+    }
+
+    bool next()
+    {
+        if(re_flags & RegexOption.freeform)
+        {
+            bool r = _next();
+            skipSpace();
+            return r;
+        }
+        else
+            return _next();
+    }
+
+    void put(Bytecode code)
+    {
+        enforce(ir.length < maxCompiledLength,
+            "maximum compiled pattern length is exceeded");
+        ir ~= code;
+    }
+
+    void putRaw(uint number)
+    {
+        enforce(ir.length < maxCompiledLength,
+            "maximum compiled pattern length is exceeded");
+        ir ~= Bytecode.fromRaw(number);
+    }
+
+    //parsing number with basic overflow check
+    uint parseDecimal()
+    {
+        uint r = 0;
+        while(std.ascii.isDigit(current))
+        {
+            if(r >= (uint.max/10))
+                error("Overflow in decimal number");
+            r = 10*r + cast(uint)(current-'0');
+            if(!next())
+                break;
+        }
+        return r;
+    }
+
+    //parse control code of form \cXXX, c assumed to be the current symbol
+    dchar parseControlCode()
+    {
+        enforce(next(), "Unfinished escape sequence");
+        enforce(('a' <= current && current <= 'z') || ('A' <= current && current <= 'Z'),
+            "Only letters are allowed after \\c");
+        return current & 0x1f;
+    }
+
+    //
+    @trusted void parseFlags(S)(S flags)
+    {//@@@BUG@@@ text is @system
+        import std.conv;
+        foreach(ch; flags)//flags are ASCII anyway
+        {
+        L_FlagSwitch:
+            switch(ch)
+            {
+
+                foreach(i, op; __traits(allMembers, RegexOption))
+                {
+                    case RegexOptionNames[i]:
+                            if(re_flags & mixin("RegexOption."~op))
+                                throw new RegexException(text("redundant flag specified: ",ch));
+                            re_flags |= mixin("RegexOption."~op);
+                            break L_FlagSwitch;
+                }
+                default:
+                    throw new RegexException(text("unknown regex flag '",ch,"'"));
+            }
+        }
+    }
+
+    //parse and store IR for regex pattern
+    @trusted void parseRegex()
+    {
+        fixupStack.push(0);
+        groupStack.push(1);//0 - whole match
+        auto maxCounterDepth = counterDepth;
+        uint fix;//fixup pointer
+
+        while(!empty)
+        {
+            debug(std_regex_parser)
+                writeln("*LR*\nSource: ", pat, "\nStack: ",fixupStack.stack.data);
+            switch(current)
+            {
+            case '(':
+                next();
+                nesting++;
+                uint nglob;
+                fixupStack.push(cast(uint)ir.length);
+                if(current == '?')
+                {
+                    next();
+                    switch(current)
+                    {
+                    case ':':
+                        put(Bytecode(IR.Nop, 0));
+                        next();
+                        break;
+                    case '=':
+                        genLookaround(IR.LookaheadStart);
+                        next();
+                        break;
+                    case '!':
+                        genLookaround(IR.NeglookaheadStart);
+                        next();
+                        break;
+                    case 'P':
+                        next();
+                        if(current != '<')
+                            error("Expected '<' in named group");
+                        string name;
+                        if(!next() || !(isAlpha(current) || current == '_'))
+                            error("Expected alpha starting a named group");
+                        name ~= current;
+                        while(next() && (isAlpha(current) ||
+                            current == '_' || std.ascii.isDigit(current)))
+                        {
+                            name ~= current;
+                        }
+                        if(current != '>')
+                            error("Expected '>' closing named group");
+                        next();
+                        nglob = groupStack.top++;
+                        enforce(groupStack.top <= maxGroupNumber, "limit on submatches is exceeded");
+                        auto t = NamedGroup(name, nglob);
+                        auto d = assumeSorted!"a.name < b.name"(dict);
+                        auto ind = d.lowerBound(t).length;
+                        insertInPlace(dict, ind, t);
+                        put(Bytecode(IR.GroupStart, nglob));
+                        break;
+                    case '<':
+                        next();
+                        if(current == '=')
+                            genLookaround(IR.LookbehindStart);
+                        else if(current == '!')
+                            genLookaround(IR.NeglookbehindStart);
+                        else
+                            error("'!' or '=' expected after '<'");
+                        next();
+                        break;
+                    default:
+                        error(" ':', '=', '<', 'P' or '!' expected after '(?' ");
+                    }
+                }
+                else
+                {
+                    nglob = groupStack.top++;
+                    enforce(groupStack.top <= maxGroupNumber, "limit on number of submatches is exceeded");
+                    put(Bytecode(IR.GroupStart, nglob));
+                }
+                break;
+            case ')':
+                enforce(nesting, "Unmatched ')'");
+                nesting--;
+                next();
+                fix = fixupStack.pop();
+                switch(ir[fix].code)
+                {
+                case IR.GroupStart:
+                    put(Bytecode(IR.GroupEnd,ir[fix].data));
+                    parseQuantifier(fix);
+                    break;
+                case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+                    assert(lookaroundNest);
+                    fixLookaround(fix);
+                    lookaroundNest--;
+                    break;
+                case IR.Option: //| xxx )
+                    //two fixups: last option + full OR
+                    finishAlternation(fix);
+                    fix = fixupStack.top;
+                    switch(ir[fix].code)
+                    {
+                    case IR.GroupStart:
+                        fixupStack.pop();
+                        put(Bytecode(IR.GroupEnd,ir[fix].data));
+                        parseQuantifier(fix);
+                        break;
+                    case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart:
+                        assert(lookaroundNest);
+                        lookaroundNest--;
+                        fix = fixupStack.pop();
+                        fixLookaround(fix);
+                        break;
+                    default://(?:xxx)
+                        fixupStack.pop();
+                        parseQuantifier(fix);
+                    }
+                    break;
+                default://(?:xxx)
+                    parseQuantifier(fix);
+                }
+                break;
+            case '|':
+                next();
+                fix = fixupStack.top;
+                if(ir.length > fix && ir[fix].code == IR.Option)
+                {
+                    ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix);
+                    put(Bytecode(IR.GotoEndOr, 0));
+                    fixupStack.top = cast(uint)ir.length; //replace latest fixup for Option
+                    put(Bytecode(IR.Option, 0));
+                    break;
+                }
+                uint len, orStart;
+                //start a new option
+                if(fixupStack.length == 1)
+                {//only root entry, effectively no fixup
+                    len = cast(uint)ir.length + IRL!(IR.GotoEndOr);
+                    orStart = 0;
+                }
+                else
+                {//IR.lookahead, etc. fixups that have length > 1, thus check ir[x].length
+                    len = cast(uint)ir.length - fix - (ir[fix].length - 1);
+                    orStart = fix + ir[fix].length;
+                }
+                insertInPlace(ir, orStart, Bytecode(IR.OrStart, 0), Bytecode(IR.Option, len));
+                assert(ir[orStart].code == IR.OrStart);
+                put(Bytecode(IR.GotoEndOr, 0));
+                fixupStack.push(orStart); //fixup for StartOR
+                fixupStack.push(cast(uint)ir.length); //for second Option
+                put(Bytecode(IR.Option, 0));
+                break;
+            default://no groups or whatever
+                uint start = cast(uint)ir.length;
+                parseAtom();
+                parseQuantifier(start);
+            }
+        }
+
+        if(fixupStack.length != 1)
+        {
+            fix = fixupStack.pop();
+            enforce(ir[fix].code == IR.Option, "no matching ')'");
+            finishAlternation(fix);
+            enforce(fixupStack.length == 1, "no matching ')'");
+        }
+    }
+
+    //helper function, finalizes IR.Option, fix points to the first option of sequence
+    void finishAlternation(uint fix)
+    {
+        enforce(ir[fix].code == IR.Option, "no matching ')'");
+        ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix - IRL!(IR.OrStart));
+        fix = fixupStack.pop();
+        enforce(ir[fix].code == IR.OrStart, "no matching ')'");
+        ir[fix] = Bytecode(IR.OrStart, cast(uint)ir.length - fix - IRL!(IR.OrStart));
+        put(Bytecode(IR.OrEnd, cast(uint)ir.length - fix - IRL!(IR.OrStart)));
+        uint pc = fix + IRL!(IR.OrStart);
+        while(ir[pc].code == IR.Option)
+        {
+            pc = pc + ir[pc].data;
+            if(ir[pc].code != IR.GotoEndOr)
+                break;
+            ir[pc] = Bytecode(IR.GotoEndOr, cast(uint)(ir.length - pc - IRL!(IR.OrEnd)));
+            pc += IRL!(IR.GotoEndOr);
+        }
+        put(Bytecode.fromRaw(0));
+    }
+
+    //parse and store IR for atom-quantifier pair
+    @trusted void parseQuantifier(uint offset)
+    {//copy is @system
+        uint replace = ir[offset].code == IR.Nop;
+        if(empty && !replace)
+            return;
+        uint min, max;
+        switch(current)
+        {
+        case '*':
+            min = 0;
+            max = infinite;
+            break;
+        case '?':
+            min = 0;
+            max = 1;
+            break;
+        case '+':
+            min = 1;
+            max = infinite;
+            break;
+        case '{':
+            enforce(next(), "Unexpected end of regex pattern");
+            enforce(std.ascii.isDigit(current), "First number required in repetition");
+            min = parseDecimal();
+            if(current == '}')
+                max = min;
+            else if(current == ',')
+            {
+                next();
+                if(std.ascii.isDigit(current))
+                    max = parseDecimal();
+                else if(current == '}')
+                    max = infinite;
+                else
+                    error("Unexpected symbol in regex pattern");
+                skipSpace();
+                if(current != '}')
+                    error("Unmatched '{' in regex pattern");
+            }
+            else
+                error("Unexpected symbol in regex pattern");
+            if(min > max)
+                error("Illegal {n,m} quantifier");
+            break;
+        default:
+            if(replace)
+            {
+                copy(ir[offset + 1 .. $], ir[offset .. $ - 1]);
+                ir.length -= 1;
+            }
+            return;
+        }
+        uint len = cast(uint)ir.length - offset - replace;
+        bool greedy = true;
+        //check only if we managed to get new symbol
+        if(next() && current == '?')
+        {
+            greedy = false;
+            next();
+        }
+        if(max != infinite)
+        {
+            if(min != 1 || max != 1)
+            {
+                Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len);
+                if(replace)
+                    ir[offset] = op;
+                else
+                    insertInPlace(ir, offset, op);
+                put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len));
+                put(Bytecode.init); //hotspot
+                putRaw(1);
+                putRaw(min);
+                putRaw(max);
+                counterDepth = std.algorithm.max(counterDepth, nesting+1);
+            }
+        }
+        else if(min) //&& max is infinite
+        {
+            if(min != 1)
+            {
+                Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len);
+                if(replace)
+                    ir[offset] = op;
+                else
+                    insertInPlace(ir, offset, op);
+                offset += 1;//so it still points to the repeated block
+                put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len));
+                put(Bytecode.init); //hotspot
+                putRaw(1);
+                putRaw(min);
+                putRaw(min);
+                counterDepth = std.algorithm.max(counterDepth, nesting+1);
+            }
+            else if(replace)
+            {
+                copy(ir[offset+1 .. $], ir[offset .. $-1]);
+                ir.length -= 1;
+            }
+            put(Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len));
+            enforce(ir.length + len < maxCompiledLength,  "maximum compiled pattern length is exceeded");
+            ir ~= ir[offset .. offset+len];
+            //IR.InfinteX is always a hotspot
+            put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len));
+            put(Bytecode.init); //merge index
+        }
+        else//vanila {0,inf}
+        {
+            Bytecode op = Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len);
+            if(replace)
+                ir[offset] = op;
+            else
+                insertInPlace(ir, offset, op);
+            //IR.InfinteX is always a hotspot
+            put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len));
+            put(Bytecode.init); //merge index
+
+        }
+    }
+
+    //parse and store IR for atom
+    void parseAtom()
+    {
+        if(empty)
+            return;
+        switch(current)
+        {
+        case '*', '?', '+', '|', '{', '}':
+            error("'*', '+', '?', '{', '}' not allowed in atom");
+            break;
+        case '.':
+            put(Bytecode(IR.Any, 0));
+            next();
+            break;
+        case '[':
+            parseCharset();
+            break;
+        case '\\':
+            enforce(_next(), "Unfinished escape sequence");
+            parseEscape();
+            break;
+        case '^':
+            put(Bytecode(IR.Bol, 0));
+            next();
+            break;
+        case '$':
+            put(Bytecode(IR.Eol, 0));
+            next();
+            break;
+        default:
+            //FIXME: getCommonCasing in new std uni
+            if(re_flags & RegexOption.casefold)
+            {
+                auto range = simpleCaseFoldings(current);
+                assert(range.length <= 5);
+                if(range.length == 1)
+                    put(Bytecode(IR.Char, range.front));
+                else
+                    foreach(v; range)
+                        put(Bytecode(IR.OrChar, v, cast(uint)range.length));
+            }
+            else
+                put(Bytecode(IR.Char, current));
+            next();
+        }
+    }
+
+    //generate code for start of lookaround: (?= (?! (?<= (?<!
+    void genLookaround(IR opcode)
+    {
+        put(Bytecode(opcode, 0));
+        put(Bytecode.fromRaw(0));
+        put(Bytecode.fromRaw(0));
+        groupStack.push(0);
+        lookaroundNest++;
+        enforce(lookaroundNest <= maxLookaroundDepth,
+            "maximum lookaround depth is exceeded");
+    }
+
+    //fixup lookaround with start at offset fix and append a proper *-End opcode
+    void fixLookaround(uint fix)
+    {
+        ir[fix] = Bytecode(ir[fix].code,
+            cast(uint)ir.length - fix - IRL!(IR.LookaheadStart));
+        auto g = groupStack.pop();
+        assert(!groupStack.empty);
+        ir[fix+1] = Bytecode.fromRaw(groupStack.top);
+        //groups are cumulative across lookarounds
+        ir[fix+2] = Bytecode.fromRaw(groupStack.top+g);
+        groupStack.top += g;
+        if(ir[fix].code == IR.LookbehindStart || ir[fix].code == IR.NeglookbehindStart)
+        {
+            reverseBytecode(ir[fix + IRL!(IR.LookbehindStart) .. $]);
+    }
+        put(ir[fix].paired);
+    }
+
+    //CodepointSet operations relatively in order of priority
+    enum Operator:uint {
+        Open = 0, Negate,  Difference, SymDifference, Intersection, Union, None
+    }
+
+    //parse unit of CodepointSet spec, most notably escape sequences and char ranges
+    //also fetches next set operation
+    Tuple!(CodepointSet,Operator) parseCharTerm()
+    {
+        enum State{ Start, Char, Escape, CharDash, CharDashEscape,
+            PotentialTwinSymbolOperator }
+        Operator op = Operator.None;
+        dchar last;
+        CodepointSet set;
+        State state = State.Start;
+
+        static void addWithFlags(ref CodepointSet set, uint ch, uint re_flags)
+        {
+            if(re_flags & RegexOption.casefold)
+            {
+                auto range = simpleCaseFoldings(ch);
+                foreach(v; range)
+                    set |= v;
+            }
+            else
+                set |= ch;
+        }
+
+        static Operator twinSymbolOperator(dchar symbol)
+        {
+            switch(symbol)
+            {
+            case '|':
+                return Operator.Union;
+            case '-':
+                return Operator.Difference;
+            case '~':
+                return Operator.SymDifference;
+            case '&':
+                return Operator.Intersection;
+            default:
+                assert(false);
+            }
+        }
+
+        L_CharTermLoop:
+        for(;;)
+        {
+            final switch(state)
+            {
+            case State.Start:
+                switch(current)
+                {
+                case '|':
+                case '-':
+                case '~':
+                case '&':
+                    state = State.PotentialTwinSymbolOperator;
+                    last = current;
+                    break;
+                case '[':
+                    op = Operator.Union;
+                    goto case;
+                case ']':
+                    break L_CharTermLoop;
+                case '\\':
+                    state = State.Escape;
+                    break;
+                default:
+                    state = State.Char;
+                    last = current;
+                }
+                break;
+            case State.Char:
+                // xxx last current xxx
+                switch(current)
+                {
+                case '|':
+                case '~':
+                case '&':
+                    // then last is treated as normal char and added as implicit union
+                    state = State.PotentialTwinSymbolOperator;
+                    addWithFlags(set, last, re_flags);
+                    last = current;
+                    break;
+                case '-': // still need more info
+                    state = State.CharDash;
+                    break;
+                case '\\':
+                    set |= last;
+                    state = State.Escape;
+                    break;
+                case '[':
+                    op = Operator.Union;
+                    goto case;
+                case ']':
+                    addWithFlags(set, last, re_flags);
+                    break L_CharTermLoop;
+                default:
+                    state = State.Char;
+                    addWithFlags(set, last, re_flags);
+                    last = current;
+                }
+                break;
+            case State.PotentialTwinSymbolOperator:
+                // xxx last current xxxx
+                // where last = [|-&~]
+                if(current == last)
+                {
+                    op = twinSymbolOperator(last);
+                    next();//skip second twin char
+                    break L_CharTermLoop;
+                }
+                goto case State.Char;
+            case State.Escape:
+                // xxx \ current xxx
+                switch(current)
+                {
+                case 'f':
+                    last = '\f';
+                    state = State.Char;
+                    break;
+                case 'n':
+                    last = '\n';
+                    state = State.Char;
+                    break;
+                case 'r':
+                    last = '\r';
+                    state = State.Char;
+                    break;
+                case 't':
+                    last = '\t';
+                    state = State.Char;
+                    break;
+                case 'v':
+                    last = '\v';
+                    state = State.Char;
+                    break;
+                case 'c':
+                    last = parseControlCode();
+                    state = State.Char;
+                    break;
+                foreach(val; Escapables)
+                {
+                case val:
+                }
+                    last = current;
+                    state = State.Char;
+                    break;
+                case 'p':
+                    set.add(parseUnicodePropertySpec(false));
+                    state = State.Start;
+                    continue L_CharTermLoop; //next char already fetched
+                case 'P':
+                    set.add(parseUnicodePropertySpec(true));
+                    state = State.Start;
+                    continue L_CharTermLoop; //next char already fetched
+                case 'x':
+                    last = parseUniHex(pat, 2);
+                    state = State.Char;
+                    break;
+                case 'u':
+                    last = parseUniHex(pat, 4);
+                    state = State.Char;
+                    break;
+                case 'U':
+                    last = parseUniHex(pat, 8);
+                    state = State.Char;
+                    break;
+                case 'd':
+                    set.add(unicode.Nd);
+                    state = State.Start;
+                    break;
+                case 'D':
+                    set.add(unicode.Nd.inverted);
+                    state = State.Start;
+                    break;
+                case 's':
+                    set.add(unicode.White_Space);
+                    state = State.Start;
+                    break;
+                case 'S':
+                    set.add(unicode.White_Space.inverted);
+                    state = State.Start;
+                    break;
+                case 'w':
+                    set.add(wordCharacter);
+                    state = State.Start;
+                    break;
+                case 'W':
+                    set.add(wordCharacter.inverted);
+                    state = State.Start;
+                    break;
+                default:
+                    enforce(false, "invalid escape sequence");
+                }
+                break;
+            case State.CharDash:
+                // xxx last - current xxx
+                switch(current)
+                {
+                case '[':
+                    op = Operator.Union;
+                    goto case;
+                case ']':
+                    //means dash is a single char not an interval specifier
+                    addWithFlags(set, last, re_flags);
+                    addWithFlags(set, '-', re_flags);
+                    break L_CharTermLoop;
+                 case '-'://set Difference again
+                    addWithFlags(set, last, re_flags);
+                    op = Operator.Difference;
+                    next();//skip '-'
+                    break L_CharTermLoop;
+                case '\\':
+                    state = State.CharDashEscape;
+                    break;
+                default:
+                    enforce(last <= current, "inverted range");
+                    if(re_flags & RegexOption.casefold)
+                    {
+                        for(uint ch = last; ch <= current; ch++)
+                            addWithFlags(set, ch, re_flags);
+                    }
+                    else
+                        set.add(last, current + 1);
+                    state = State.Start;
+                }
+                break;
+            case State.CharDashEscape:
+            //xxx last - \ current xxx
+                uint end;
+                switch(current)
+                {
+                case 'f':
+                    end = '\f';
+                    break;
+                case 'n':
+                    end = '\n';
+                    break;
+                case 'r':
+                    end = '\r';
+                    break;
+                case 't':
+                    end = '\t';
+                    break;
+                case 'v':
+                    end = '\v';
+                    break;
+                foreach(val; Escapables)
+                {
+                case val:
+                }
+                    end = current;
+                    break;
+                case 'c':
+                    end = parseControlCode();
+                    break;
+                case 'x':
+                    end = parseUniHex(pat, 2);
+                    break;
+                case 'u':
+                    end = parseUniHex(pat, 4);
+                    break;
+                case 'U':
+                    end = parseUniHex(pat, 8);
+                    break;
+                default:
+                    error("invalid escape sequence");
+                }
+                enforce(last <= end,"inverted range");
+                set.add(last, end + 1);
+                state = State.Start;
+                break;
+            }
+            enforce(next(), "unexpected end of CodepointSet");
+        }
+        return tuple(set, op);
+    }
+
+    alias ValStack = Stack!(CodepointSet);
+    alias OpStack = Stack!(Operator);
+
+    //parse and store IR for CodepointSet
+    void parseCharset()
+    {
+        ValStack vstack;
+        OpStack opstack;
+        import std.functional : unaryFun;
+        //
+        static bool apply(Operator op, ref ValStack stack)
+        {
+            switch(op)
+            {
+            case Operator.Negate:
+                stack.top = stack.top.inverted;
+                break;
+            case Operator.Union:
+                auto s = stack.pop();//2nd operand
+                enforce(!stack.empty, "no operand for '||'");
+                stack.top.add(s);
+                break;
+            case Operator.Difference:
+                auto s = stack.pop();//2nd operand
+                enforce(!stack.empty, "no operand for '--'");
+                stack.top.sub(s);
+                break;
+            case Operator.SymDifference:
+                auto s = stack.pop();//2nd operand
+                enforce(!stack.empty, "no operand for '~~'");
+                stack.top ~= s;
+                break;
+            case Operator.Intersection:
+                auto s = stack.pop();//2nd operand
+                enforce(!stack.empty, "no operand for '&&'");
+                stack.top.intersect(s);
+                break;
+            default:
+                return false;
+            }
+            return true;
+        }
+        static bool unrollWhile(alias cond)(ref ValStack vstack, ref OpStack opstack)
+        {
+            while(cond(opstack.top))
+            {
+                if(!apply(opstack.pop(),vstack))
+                    return false;//syntax error
+                if(opstack.empty)
+                    return false;
+            }
+            return true;
+        }
+
+        L_CharsetLoop:
+        do
+        {
+            switch(current)
+            {
+            case '[':
+                opstack.push(Operator.Open);
+                enforce(next(), "unexpected end of character class");
+                if(current == '^')
+                {
+                    opstack.push(Operator.Negate);
+                    enforce(next(), "unexpected end of character class");
+                }
+                //[] is prohibited
+                enforce(current != ']', "wrong character class");
+                goto default;
+            case ']':
+                enforce(unrollWhile!(unaryFun!"a != a.Open")(vstack, opstack),
+                    "character class syntax error");
+                enforce(!opstack.empty, "unmatched ']'");
+                opstack.pop();
+                next();
+                if(opstack.empty)
+                    break L_CharsetLoop;
+                auto pair  = parseCharTerm();
+                if(!pair[0].empty)//not only operator e.g. -- or ~~
+                {
+                    vstack.top.add(pair[0]);//apply union
+                }
+                if(pair[1] != Operator.None)
+                {
+                    if(opstack.top == Operator.Union)
+                        unrollWhile!(unaryFun!"a == a.Union")(vstack, opstack);
+                    opstack.push(pair[1]);
+                }
+                break;
+            //
+            default://yet another pair of term(op)?
+                auto pair = parseCharTerm();
+                if(pair[1] != Operator.None)
+                {
+                    if(opstack.top == Operator.Union)
+                        unrollWhile!(unaryFun!"a == a.Union")(vstack, opstack);
+                    opstack.push(pair[1]);
+                }
+                vstack.push(pair[0]);
+            }
+
+        }while(!empty || !opstack.empty);
+        while(!opstack.empty)
+            apply(opstack.pop(),vstack);
+        assert(vstack.length == 1);
+        charsetToIr(vstack.top);
+    }
+    //try to generate optimal IR code for this CodepointSet
+    @trusted void charsetToIr(CodepointSet set)
+    {//@@@BUG@@@ writeln is @system
+        uint chars = cast(uint)set.length;
+        if(chars < Bytecode.maxSequence)
+        {
+            switch(chars)
+            {
+                case 1:
+                    put(Bytecode(IR.Char, set.byCodepoint.front));
+                    break;
+                case 0:
+                    error("empty CodepointSet not allowed");
+                    break;
+                default:
+                    foreach(ch; set.byCodepoint)
+                        put(Bytecode(IR.OrChar, ch, chars));
+            }
+        }
+        else
+        {
+            import std.algorithm : countUntil;
+            auto ivals = set.byInterval;
+            auto n = charsets.countUntil(set);
+            if(n >= 0)
+            {
+                if(ivals.length*2 > maxCharsetUsed)
+                    put(Bytecode(IR.Trie, cast(uint)n));
+                else
+                    put(Bytecode(IR.CodepointSet, cast(uint)n));
+                return;
+            }
+            if(ivals.length*2 > maxCharsetUsed)
+            {
+                auto t  = getTrie(set);
+                put(Bytecode(IR.Trie, cast(uint)tries.length));
+                tries ~= t;
+                debug(std_regex_allocation) writeln("Trie generated");
+            }
+            else
+            {
+                put(Bytecode(IR.CodepointSet, cast(uint)charsets.length));
+                tries ~= Trie.init;
+            }
+            charsets ~= set;
+            assert(charsets.length == tries.length);
+        }
+    }
+
+    //parse and generate IR for escape stand alone escape sequence
+    @trusted void parseEscape()
+    {//accesses array of appender
+
+        switch(current)
+        {
+        case 'f':   next(); put(Bytecode(IR.Char, '\f')); break;
+        case 'n':   next(); put(Bytecode(IR.Char, '\n')); break;
+        case 'r':   next(); put(Bytecode(IR.Char, '\r')); break;
+        case 't':   next(); put(Bytecode(IR.Char, '\t')); break;
+        case 'v':   next(); put(Bytecode(IR.Char, '\v')); break;
+
+        case 'd':
+            next();
+            charsetToIr(unicode.Nd);
+            break;
+        case 'D':
+            next();
+            charsetToIr(unicode.Nd.inverted);
+            break;
+        case 'b':   next(); put(Bytecode(IR.Wordboundary, 0)); break;
+        case 'B':   next(); put(Bytecode(IR.Notwordboundary, 0)); break;
+        case 's':
+            next();
+            charsetToIr(unicode.White_Space);
+            break;
+        case 'S':
+            next();
+            charsetToIr(unicode.White_Space.inverted);
+            break;
+        case 'w':
+            next();
+            charsetToIr(wordCharacter);
+            break;
+        case 'W':
+            next();
+            charsetToIr(wordCharacter.inverted);
+            break;
+        case 'p': case 'P':
+            auto CodepointSet = parseUnicodePropertySpec(current == 'P');
+            charsetToIr(CodepointSet);
+            break;
+        case 'x':
+            uint code = parseUniHex(pat, 2);
+            next();
+            put(Bytecode(IR.Char,code));
+            break;
+        case 'u': case 'U':
+            uint code = parseUniHex(pat, current == 'u' ? 4 : 8);
+            next();
+            put(Bytecode(IR.Char, code));
+            break;
+        case 'c': //control codes
+            Bytecode code = Bytecode(IR.Char, parseControlCode());
+            next();
+            put(code);
+            break;
+        case '0':
+            next();
+            put(Bytecode(IR.Char, 0));//NUL character
+            break;
+        case '1': .. case '9':
+            uint nref = cast(uint)current - '0';
+            uint maxBackref = sum(groupStack.data);
+            enforce(nref < maxBackref, "Backref to unseen group");
+            //perl's disambiguation rule i.e.
+            //get next digit only if there is such group number
+            while(nref < maxBackref && next() && std.ascii.isDigit(current))
+            {
+                nref = nref * 10 + current - '0';
+            }
+            if(nref >= maxBackref)
+                nref /= 10;
+            enforce(!isOpenGroup(nref), "Backref to open group");
+            uint localLimit = maxBackref - groupStack.top;
+            if(nref >= localLimit)
+            {
+                put(Bytecode(IR.Backref, nref-localLimit));
+                ir[$-1].setLocalRef();
+            }
+            else
+                put(Bytecode(IR.Backref, nref));
+            markBackref(nref);
+            break;
+        default:
+            auto op = Bytecode(IR.Char, current);
+            next();
+            put(op);
+        }
+    }
+
+    //parse and return a CodepointSet for \p{...Property...} and \P{...Property..},
+    //\ - assumed to be processed, p - is current
+    CodepointSet parseUnicodePropertySpec(bool negated)
+    {
+        enum MAX_PROPERTY = 128;
+        char[MAX_PROPERTY] result;
+        uint k = 0;
+        enforce(next(), "eof parsing unicode property spec");
+        if(current == '{')
+        {
+            while(k < MAX_PROPERTY && next() && current !='}' && current !=':')
+                if(current != '-' && current != ' ' && current != '_')
+                    result[k++] = cast(char)std.ascii.toLower(current);
+            enforce(k != MAX_PROPERTY, "invalid property name");
+            enforce(current == '}', "} expected ");
+        }
+        else
+        {//single char properties e.g.: \pL, \pN ...
+            enforce(current < 0x80, "invalid property name");
+            result[k++] = cast(char)current;
+        }
+        auto s = getUnicodeSet(result[0..k], negated,
+            cast(bool)(re_flags & RegexOption.casefold));
+        enforce(!s.empty, "unrecognized unicode property spec");
+        next();
+        return s;
+    }
+
+    //
+    @trusted void error(string msg)
+    {
+        import std.format;
+        auto app = appender!string();
+        ir = null;
+        formattedWrite(app, "%s\nPattern with error: `%s` <--HERE-- `%s`",
+                       msg, origin[0..$-pat.length], pat);
+        throw new RegexException(app.data);
+    }
+
+    alias Char = BasicElementOf!R;
+
+    @property program()
+    {
+        return makeRegex(this);
+    }
+}
+
+/+
+    lightweight post process step,
+    only essentials
++/
+@trusted void lightPostprocess(Char)(ref Regex!Char zis)
+{//@@@BUG@@@ write is @system
+    with(zis)
+    {
+        struct FixedStack(T)
+        {
+            T[] arr;
+            uint _top;
+            //this(T[] storage){   arr = storage; _top = -1; }
+            @property ref T top(){  assert(!empty); return arr[_top]; }
+            void push(T x){  arr[++_top] = x; }
+            T pop() { assert(!empty);   return arr[_top--]; }
+            @property bool empty(){   return _top == -1; }
+        }
+        auto counterRange = FixedStack!uint(new uint[maxCounterDepth+1], -1);
+        counterRange.push(1);
+        ulong cumRange = 0;
+        for(uint i = 0; i < ir.length; i += ir[i].length)
+        {
+            if(ir[i].hotspot)
+            {
+                assert(i + 1 < ir.length,
+                    "unexpected end of IR while looking for hotspot");
+                ir[i+1] = Bytecode.fromRaw(hotspotTableSize);
+                hotspotTableSize += counterRange.top;
+            }
+            switch(ir[i].code)
+            {
+            case IR.RepeatStart, IR.RepeatQStart:
+                uint repEnd = cast(uint)(i + ir[i].data + IRL!(IR.RepeatStart));
+                assert(ir[repEnd].code == ir[i].paired.code);
+                uint max = ir[repEnd + 4].raw;
+                ir[repEnd+2].raw = counterRange.top;
+                ir[repEnd+3].raw *= counterRange.top;
+                ir[repEnd+4].raw *= counterRange.top;
+                ulong cntRange = cast(ulong)(max)*counterRange.top;
+                cumRange += cntRange;
+                enforce(cumRange < maxCumulativeRepetitionLength,
+                    "repetition length limit is exceeded");
+                counterRange.push(cast(uint)cntRange + counterRange.top);
+                threadCount += counterRange.top;
+                break;
+            case IR.RepeatEnd, IR.RepeatQEnd:
+                threadCount += counterRange.top;
+                counterRange.pop();
+                break;
+            case IR.GroupStart:
+                if(isBackref(ir[i].data))
+                    ir[i].setBackrefence();
+                threadCount += counterRange.top;
+                break;
+            case IR.GroupEnd:
+                if(isBackref(ir[i].data))
+                    ir[i].setBackrefence();
+                threadCount += counterRange.top;
+                break;
+            default:
+                threadCount += counterRange.top;
+            }
+        }
+        checkIfOneShot();
+        if(!(flags & RegexInfo.oneShot))
+            kickstart = Kickstart!Char(zis, new uint[](256));
+        debug(std_regex_allocation) writefln("IR processed, max threads: %d", threadCount);
+    }
+}
+
+//IR code validator - proper nesting, illegal instructions, etc.
+@trusted void validateRe(Char)(ref Regex!Char zis)
+{//@@@BUG@@@ text is @system
+    import std.conv;
+    with(zis)
+    {
+        for(uint pc = 0; pc < ir.length; pc += ir[pc].length)
+        {
+            if(ir[pc].isStart || ir[pc].isEnd)
+            {
+                uint dest = ir[pc].indexOfPair(pc);
+                assert(dest < ir.length, text("Wrong length in opcode at pc=",
+                    pc, " ", dest, " vs ", ir.length));
+                assert(ir[dest].paired ==  ir[pc],
+                    text("Wrong pairing of opcodes at pc=", pc, "and pc=", dest));
+            }
+            else if(ir[pc].isAtom)
+            {
+
+            }
+            else
+               assert(0, text("Unknown type of instruction at pc=", pc));
+        }
+    }
+}
diff --git a/libphobos/src/std/regex/internal/tests.d b/libphobos/src/std/regex/internal/tests.d
new file mode 100644
index 00000000000..e5d61300003
--- /dev/null
+++ b/libphobos/src/std/regex/internal/tests.d
@@ -0,0 +1,970 @@
+/*
+    Regualar expressions package test suite.
+*/
+module std.regex.internal.tests;
+
+package(std.regex):
+
+import std.algorithm, std.conv, std.exception, std.meta, std.range,
+    std.typecons, std.regex;
+
+import std.regex.internal.parser : Escapables; // characters that need escaping
+
+alias Sequence(int B, int E) = staticIota!(B, E);
+
+unittest
+{//sanity checks
+    regex("(a|b)*");
+    regex(`(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*(.*)\s*#`);
+    regex("abc|edf|ighrg");
+    auto r1 = regex("abc");
+    auto r2 = regex("(gylba)");
+    assert(match("abcdef", r1).hit == "abc");
+    assert(!match("wida",r2));
+    assert(bmatch("abcdef", r1).hit == "abc");
+    assert(!bmatch("wida", r2));
+    assert(match("abc", "abc".dup));
+    assert(bmatch("abc", "abc".dup));
+    Regex!char rc;
+    assert(rc.empty);
+    rc = regex("test");
+    assert(!rc.empty);
+}
+
+/* The test vectors in this file are altered from Henry Spencer's regexp
+   test code. His copyright notice is:
+
+        Copyright (c) 1986 by University of Toronto.
+        Written by Henry Spencer.  Not derived from licensed software.
+
+        Permission is granted to anyone to use this software for any
+        purpose on any computer system, and to redistribute it freely,
+        subject to the following restrictions:
+
+        1. The author is not responsible for the consequences of use of
+                this software, no matter how awful, even if they arise
+                from defects in it.
+
+        2. The origin of this software must not be misrepresented, either
+                by explicit claim or by omission.
+
+        3. Altered versions must be plainly marked as such, and must not
+                be misrepresented as being the original software.
+
+
+ */
+
+unittest
+{
+    struct TestVectors
+    {
+        string pattern;
+        string input;
+        string result;
+        string format;
+        string replace;
+        string flags;
+    }
+
+    static immutable TestVectors[] tv = [
+        TestVectors(  "a\\b",       "a",  "y",    "$&",    "a" ),
+        TestVectors(  "(a)b\\1",   "abaab","y",    "$&",    "aba" ),
+        TestVectors(  "()b\\1",     "aaab", "y",    "$&",    "b" ),
+        TestVectors(  "abc",       "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "abc",       "xbc",  "n",    "-",    "-" ),
+        TestVectors(  "abc",       "axc",  "n",    "-",    "-" ),
+        TestVectors(  "abc",       "abx",  "n",    "-",    "-" ),
+        TestVectors(  "abc",       "xabcy","y",    "$&",    "abc" ),
+        TestVectors(  "abc",       "ababc","y",    "$&",    "abc" ),
+        TestVectors(  "ab*c",      "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "ab*bc",     "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "ab*bc",     "abbc", "y",    "$&",    "abbc" ),
+        TestVectors(  "ab*bc",     "abbbbc","y",   "$&",    "abbbbc" ),
+        TestVectors(  "ab+bc",     "abbc", "y",    "$&",    "abbc" ),
+        TestVectors(  "ab+bc",     "abc",  "n",    "-",    "-" ),
+        TestVectors(  "ab+bc",     "abq",  "n",    "-",    "-" ),
+        TestVectors(  "ab+bc",     "abbbbc","y",   "$&",    "abbbbc" ),
+        TestVectors(  "ab?bc",     "abbc", "y",    "$&",    "abbc" ),
+        TestVectors(  "ab?bc",     "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "ab?bc",     "abbbbc","n",   "-",    "-" ),
+        TestVectors(  "ab?c",      "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "^abc$",     "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "^abc$",     "abcc", "n",    "-",    "-" ),
+        TestVectors(  "^abc",      "abcc", "y",    "$&",    "abc" ),
+        TestVectors(  "^abc$",     "aabc", "n",    "-",    "-" ),
+        TestVectors(  "abc$",      "aabc", "y",    "$&",    "abc" ),
+        TestVectors(  "^",         "abc",  "y",    "$&",    "" ),
+        TestVectors(  "$",         "abc",  "y",    "$&",    "" ),
+        TestVectors(  "a.c",       "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "a.c",       "axc",  "y",    "$&",    "axc" ),
+        TestVectors(  "a.*c",      "axyzc","y",    "$&",    "axyzc" ),
+        TestVectors(  "a.*c",      "axyzd","n",    "-",    "-" ),
+        TestVectors(  "a[bc]d",    "abc",  "n",    "-",    "-" ),
+        TestVectors(  "a[bc]d",    "abd",  "y",    "$&",    "abd" ),
+        TestVectors(  "a[b-d]e",   "abd",  "n",    "-",    "-" ),
+        TestVectors(  "a[b-d]e",   "ace",  "y",    "$&",    "ace" ),
+        TestVectors(  "a[b-d]",    "aac",  "y",    "$&",    "ac" ),
+        TestVectors(  "a[-b]",     "a-",   "y",    "$&",    "a-" ),
+        TestVectors(  "a[b-]",     "a-",   "y",    "$&",    "a-" ),
+        TestVectors(  "a[b-a]",    "-",    "c",    "-",    "-" ),
+        TestVectors(  "a[]b",      "-",    "c",    "-",    "-" ),
+        TestVectors(  "a[",        "-",    "c",    "-",    "-" ),
+        TestVectors(  "a]",        "a]",   "y",    "$&",    "a]" ),
+        TestVectors(  "a[\\]]b",     "a]b",  "y",  "$&",    "a]b" ),
+        TestVectors(  "a[^bc]d",   "aed",  "y",    "$&",    "aed" ),
+        TestVectors(  "a[^bc]d",   "abd",  "n",    "-",    "-" ),
+        TestVectors(  "a[^-b]c",   "adc",  "y",    "$&",    "adc" ),
+        TestVectors(  "a[^-b]c",   "a-c",  "n",    "-",    "-" ),
+        TestVectors(  "a[^\\]b]c",   "adc",  "y",  "$&",    "adc" ),
+        TestVectors(  "ab|cd",     "abc",  "y",    "$&",    "ab" ),
+        TestVectors(  "ab|cd",     "abcd", "y",    "$&",    "ab" ),
+        TestVectors(  "()ef",      "def",  "y",    "$&-$1",        "ef-" ),
+        TestVectors(  "()*",       "-",    "y",    "-",    "-" ),
+        TestVectors(  "*a",        "-",    "c",    "-",    "-" ),
+        TestVectors(  "^*",        "-",    "y",    "-",    "-" ),
+        TestVectors(  "$*",        "-",    "y",    "-",    "-" ),
+        TestVectors(  "(*)b",      "-",    "c",    "-",    "-" ),
+        TestVectors(  "$b",        "b",    "n",    "-",    "-" ),
+        TestVectors(  "a\\",       "-",    "c",    "-",    "-" ),
+        TestVectors(  "a\\(b",     "a(b",  "y",    "$&-$1",        "a(b-" ),
+        TestVectors(  "a\\(*b",    "ab",   "y",    "$&",    "ab" ),
+        TestVectors(  "a\\(*b",    "a((b", "y",    "$&",    "a((b" ),
+        TestVectors(  "a\\\\b",    "a\\b", "y",    "$&",    "a\\b" ),
+        TestVectors(  "abc)",      "-",    "c",    "-",    "-" ),
+        TestVectors(  "(abc",      "-",    "c",    "-",    "-" ),
+        TestVectors(  "((a))",     "abc",  "y",    "$&-$1-$2",    "a-a-a" ),
+        TestVectors(  "(a)b(c)",   "abc",  "y",    "$&-$1-$2",    "abc-a-c" ),
+        TestVectors(  "a+b+c",     "aabbabc","y",  "$&",    "abc" ),
+        TestVectors(  "a**",       "-",    "c",    "-",    "-" ),
+        TestVectors(  "a*?a",      "aa",   "y",    "$&",    "a" ),
+        TestVectors(  "(a*)*",     "aaa",  "y",    "-",    "-" ),
+        TestVectors(  "(a*)+",     "aaa",  "y",    "-",    "-" ),
+        TestVectors(  "(a|)*",     "-",    "y",    "-",    "-" ),
+        TestVectors(  "(a*|b)*",   "aabb", "y",    "-",    "-" ),
+        TestVectors(  "(a|b)*",    "ab",   "y",    "$&-$1",        "ab-b" ),
+        TestVectors(  "(a+|b)*",   "ab",   "y",    "$&-$1",        "ab-b" ),
+        TestVectors(  "(a+|b)+",   "ab",   "y",    "$&-$1",        "ab-b" ),
+        TestVectors(  "(a+|b)?",   "ab",   "y",    "$&-$1",        "a-a" ),
+        TestVectors(  "[^ab]*",    "cde",  "y",    "$&",    "cde" ),
+        TestVectors(  "(^)*",      "-",    "y",    "-",    "-" ),
+        TestVectors(  "(ab|)*",    "-",    "y",    "-",    "-" ),
+        TestVectors(  ")(",        "-",    "c",    "-",    "-" ),
+        TestVectors(  "",  "abc",  "y",    "$&",    "" ),
+        TestVectors(  "abc",       "",     "n",    "-",    "-" ),
+        TestVectors(  "a*",        "",     "y",    "$&",    "" ),
+        TestVectors(  "([abc])*d", "abbbcd",       "y",    "$&-$1",        "abbbcd-c" ),
+        TestVectors(  "([abc])*bcd", "abcd",       "y",    "$&-$1",        "abcd-a" ),
+        TestVectors(  "a|b|c|d|e", "e",    "y",    "$&",    "e" ),
+        TestVectors(  "(a|b|c|d|e)f", "ef",        "y",    "$&-$1",        "ef-e" ),
+        TestVectors(  "((a*|b))*", "aabb", "y",    "-",    "-" ),
+        TestVectors(  "abcd*efg",  "abcdefg",      "y",    "$&",    "abcdefg" ),
+        TestVectors(  "ab*",       "xabyabbbz",    "y",    "$&",    "ab" ),
+        TestVectors(  "ab*",       "xayabbbz",     "y",    "$&",    "a" ),
+        TestVectors(  "(ab|cd)e",  "abcde",        "y",    "$&-$1",        "cde-cd" ),
+        TestVectors(  "[abhgefdc]ij",      "hij",  "y",    "$&",    "hij" ),
+        TestVectors(  "^(ab|cd)e", "abcde",        "n",    "x$1y",        "xy" ),
+        TestVectors(  "(abc|)ef",  "abcdef",       "y",    "$&-$1",        "ef-" ),
+        TestVectors(  "(a|b)c*d",  "abcd",         "y",    "$&-$1",        "bcd-b" ),
+        TestVectors(  "(ab|ab*)bc",        "abc",  "y",    "$&-$1",        "abc-a" ),
+        TestVectors(  "a([bc]*)c*",        "abc",  "y",    "$&-$1",        "abc-bc" ),
+        TestVectors(  "a([bc]*)(c*d)",     "abcd", "y",    "$&-$1-$2",    "abcd-bc-d" ),
+        TestVectors(  "a([bc]+)(c*d)",     "abcd", "y",    "$&-$1-$2",    "abcd-bc-d" ),
+        TestVectors(  "a([bc]*)(c+d)",     "abcd", "y",    "$&-$1-$2",    "abcd-b-cd" ),
+        TestVectors(  "a[bcd]*dcdcde",     "adcdcde",      "y",    "$&",    "adcdcde" ),
+        TestVectors(  "a[bcd]+dcdcde",     "adcdcde",      "n",    "-",    "-" ),
+        TestVectors(  "(ab|a)b*c", "abc",           "y",    "$&-$1",        "abc-ab" ),
+        TestVectors(  "((a)(b)c)(d)",      "abcd",  "y",    "$1-$2-$3-$4",      "abc-a-b-d" ),
+        TestVectors(  "[a-zA-Z_][a-zA-Z0-9_]*",    "alpha",        "y",    "$&",    "alpha" ),
+        TestVectors(  "^a(bc+|b[eh])g|.h$",        "abh",  "y",    "$&-$1",        "bh-" ),
+        TestVectors(  "(bc+d$|ef*g.|h?i(j|k))",    "effgz",        "y",    "$&-$1-$2",    "effgz-effgz-" ),
+        TestVectors(  "(bc+d$|ef*g.|h?i(j|k))",    "ij",   "y",    "$&-$1-$2",    "ij-ij-j" ),
+        TestVectors(  "(bc+d$|ef*g.|h?i(j|k))",    "effg", "n",    "-",    "-" ),
+        TestVectors(  "(bc+d$|ef*g.|h?i(j|k))",    "bcdd", "n",    "-",    "-" ),
+        TestVectors(  "(bc+d$|ef*g.|h?i(j|k))",    "reffgz",       "y",    "$&-$1-$2",    "effgz-effgz-" ),
+        TestVectors(  "(((((((((a)))))))))",       "a",    "y",    "$&",    "a" ),
+        TestVectors(  "multiple words of text",    "uh-uh",        "n",    "-",    "-" ),
+        TestVectors(  "multiple words",    "multiple words, yeah", "y",    "$&",    "multiple words" ),
+        TestVectors(  "(.*)c(.*)", "abcde",                "y",    "$&-$1-$2",    "abcde-ab-de" ),
+        TestVectors(  "\\((.*), (.*)\\)",  "(a, b)",       "y",    "($2, $1)",   "(b, a)" ),
+        TestVectors(  "abcd",      "abcd",                   "y",    "$&-&-$$$&",  "abcd-&-$abcd" ),
+        TestVectors(  "a(bc)d",    "abcd",                 "y",    "$1-$$1-$$$1",    "bc-$1-$bc" ),
+        TestVectors(  "[k]",                       "ab",   "n",    "-",    "-" ),
+        TestVectors(  "[ -~]*",                    "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~]*",                 "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~ -~]*",              "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~ -~ -~]*",           "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~ -~ -~ -~]*",        "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~ -~ -~ -~ -~]*",     "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "[ -~ -~ -~ -~ -~ -~ -~]*",  "abc",  "y",    "$&",    "abc" ),
+        TestVectors(  "a{2}",      "candy",                "n",    "",     "" ),
+        TestVectors(  "a{2}",      "caandy",               "y",    "$&",    "aa" ),
+        TestVectors(  "a{2}",      "caaandy",              "y",    "$&",    "aa" ),
+        TestVectors(  "a{2,}",     "candy",                "n",    "",     "" ),
+        TestVectors(  "a{2,}",     "caandy",               "y",    "$&",    "aa" ),
+        TestVectors(  "a{2,}",     "caaaaaandy",           "y",    "$&",    "aaaaaa" ),
+        TestVectors(  "a{1,3}",    "cndy",                 "n",    "",     "" ),
+        TestVectors(  "a{1,3}",    "candy",                "y",    "$&",    "a" ),
+        TestVectors(  "a{1,3}",    "caandy",               "y",    "$&",    "aa" ),
+        TestVectors(  "a{1,3}",    "caaaaaandy",           "y",    "$&",    "aaa" ),
+        TestVectors(  "e?le?",     "angel",                "y",    "$&",    "el" ),
+        TestVectors(  "e?le?",     "angle",                "y",    "$&",    "le" ),
+        TestVectors(  "\\bn\\w",   "noonday",              "y",    "$&",    "no" ),
+        TestVectors(  "\\wy\\b",   "possibly yesterday",   "y",    "$&",    "ly" ),
+        TestVectors(  "\\w\\Bn",   "noonday",              "y",    "$&",    "on" ),
+        TestVectors(  "y\\B\\w",   "possibly yesterday",   "y",    "$&",    "ye" ),
+        TestVectors(  "\\cJ",      "abc\ndef",             "y",    "$&",    "\n" ),
+        TestVectors(  "\\d",       "B2 is",                "y",    "$&",    "2" ),
+        TestVectors(  "\\D",       "B2 is",                "y",    "$&",    "B" ),
+        TestVectors(  "\\s\\w*",   "foo bar",              "y",    "$&",    " bar" ),
+        TestVectors(  "\\S\\w*",   "foo bar",              "y",    "$&",    "foo" ),
+        TestVectors(  "abc",       "ababc",                "y",    "$&",    "abc" ),
+        TestVectors(  "apple(,)\\sorange\\1",      "apple, orange, cherry, peach", "y", "$&", "apple, orange," ),
+        TestVectors(  "(\\w+)\\s(\\w+)",           "John Smith", "y", "$2, $1", "Smith, John" ),
+        TestVectors(  "\\n\\f\\r\\t\\v",           "abc\n\f\r\t\vdef", "y", "$&", "\n\f\r\t\v" ),
+        TestVectors(  ".*c",       "abcde",                        "y",    "$&",    "abc" ),
+        TestVectors(  "^\\w+((;|=)\\w+)+$", "some=host=tld",    "y", "$&-$1-$2", "some=host=tld-=tld-=" ),
+        TestVectors(  "^\\w+((\\.|-)\\w+)+$", "some.host.tld",    "y", "$&-$1-$2", "some.host.tld-.tld-." ),
+        TestVectors(  "q(a|b)*q",  "xxqababqyy",                "y",    "$&-$1",        "qababq-b" ),
+        TestVectors(  "^(a)(b){0,1}(c*)",   "abcc", "y", "$1 $2 $3", "a b cc" ),
+        TestVectors(  "^(a)((b){0,1})(c*)", "abcc", "y", "$1 $2 $3", "a b b" ),
+        TestVectors(  "^(a)(b)?(c*)",       "abcc", "y", "$1 $2 $3", "a b cc" ),
+        TestVectors(  "^(a)((b)?)(c*)",     "abcc", "y", "$1 $2 $3", "a b b" ),
+        TestVectors(  "^(a)(b){0,1}(c*)",   "acc",  "y", "$1 $2 $3", "a  cc" ),
+        TestVectors(  "^(a)((b){0,1})(c*)", "acc",  "y", "$1 $2 $3", "a  " ),
+        TestVectors(  "^(a)(b)?(c*)",       "acc",  "y", "$1 $2 $3", "a  cc" ),
+        TestVectors(  "^(a)((b)?)(c*)",     "acc",  "y", "$1 $2 $3", "a  " ),
+        TestVectors(  "(?:ab){3}",       "_abababc","y", "$&-$1",    "ababab-" ),
+        TestVectors(  "(?:a(?:x)?)+",    "aaxaxx",  "y", "$&-$1-$2", "aaxax--" ),
+        TestVectors(  `\W\w\W`,         "aa b!ca",  "y", "$&",       " b!"),
+//more repetitions:
+        TestVectors(  "(?:a{2,4}b{1,3}){1,2}",  "aaabaaaabbb", "y", "$&", "aaabaaaabbb" ),
+        TestVectors(  "(?:a{2,4}b{1,3}){1,2}?", "aaabaaaabbb", "y", "$&", "aaab" ),
+//groups:
+        TestVectors(  "(abc)|(edf)|(xyz)",     "xyz",             "y",   "$1-$2-$3","--xyz"),
+        TestVectors(  "(?P<q>\\d+)/(?P<d>\\d+)",     "2/3",       "y",     "${d}/${q}",    "3/2"),
+//set operations:
+        TestVectors(  "[a-z--d-f]",                  " dfa",      "y",   "$&",     "a"),
+        TestVectors(  "[abc[pq--acq]]{2}",           "bqpaca",    "y",   "$&",     "pa"),
+        TestVectors(  "[a-z9&&abc0-9]{3}",           "z90a0abc",  "y",   "$&",     "abc"),
+        TestVectors(  "[0-9a-f~~0-5a-z]{2}",         "g0a58x",    "y",   "$&",     "8x"),
+        TestVectors(  "[abc[pq]xyz[rs]]{4}",         "cqxr",      "y",   "$&",     "cqxr"),
+        TestVectors(  "[abcdf--[ab&&[bcd]][acd]]",   "abcdefgh",  "y",   "$&",     "f"),
+        TestVectors(  "[a-c||d-f]+",    "abcdef", "y", "$&", "abcdef"),
+        TestVectors(  "[a-f--a-c]+",    "abcdef", "y", "$&", "def"),
+        TestVectors(  "[a-c&&b-f]+",    "abcdef", "y", "$&", "bc"),
+        TestVectors(  "[a-c~~b-f]+",    "abcdef", "y", "$&", "a"),
+//unicode blocks & properties:
+        TestVectors(  `\P{Inlatin1suppl ement}`, "\u00c2!", "y", "$&", "!"),
+        TestVectors(  `\p{InLatin-1 Supplement}\p{in-mathematical-operators}\P{Inlatin1suppl ement}`, "\u00c2\u2200\u00c3\u2203.", "y", "$&", "\u00c3\u2203."),
+        TestVectors(  `[-+*/\p{in-mathematical-operators}]{2}`,    "a+\u2212",    "y",    "$&",    "+\u2212"),
+        TestVectors(  `\p{Ll}+`,                      "XabcD",    "y",  "$&",      "abc"),
+        TestVectors(  `\p{Lu}+`,                      "абвГДЕ",   "y",  "$&",      "ГДЕ"),
+        TestVectors(  `^\p{Currency Symbol}\p{Sc}`,   "$₤",       "y",  "$&",      "$₤"),
+        TestVectors(  `\p{Common}\p{Thai}`,           "!ฆ",       "y",  "$&",      "!ฆ"),
+        TestVectors(  `[\d\s]*\D`,  "12 \t3\U00001680\u0F20_2",   "y",  "$&", "12 \t3\U00001680\u0F20_"),
+        TestVectors(  `[c-wф]фф`, "ффф", "y", "$&", "ффф"),
+//case insensitive:
+        TestVectors(   `^abcdEf$`,           "AbCdEF",              "y",   "$&", "AbCdEF",      "i"),
+        TestVectors(   `Русский язык`,    "рУсскИй ЯзЫк",           "y",   "$&", "рУсскИй ЯзЫк",     "i"),
+        TestVectors(   `ⒶⒷⓒ` ,        "ⓐⓑⒸ",                   "y",   "$&", "ⓐⓑⒸ",      "i"),
+        TestVectors(   "\U00010400{2}",  "\U00010428\U00010400 ",   "y",   "$&", "\U00010428\U00010400", "i"),
+        TestVectors(   `[adzУ-Я]{4}`,    "DzюЯ",                   "y",   "$&", "DzюЯ", "i"),
+        TestVectors(   `\p{L}\p{Lu}{10}`, "абвгдеЖЗИКЛ",            "y",   "$&", "абвгдеЖЗИКЛ", "i"),
+        TestVectors(   `(?:Dåb){3}`,  "DåbDÅBdÅb",                  "y",   "$&", "DåbDÅBdÅb", "i"),
+//escapes:
+        TestVectors(    `\u0041\u005a\U00000065\u0001`,         "AZe\u0001",       "y",   "$&", "AZe\u0001"),
+        TestVectors(    `\u`,               "",   "c",   "-",  "-"),
+        TestVectors(    `\U`,               "",   "c",   "-",  "-"),
+        TestVectors(    `\u003`,            "",   "c",   "-",  "-"),
+        TestVectors(    `[\x00-\x7f]{4}`,        "\x00\x09ab",   "y", "$&", "\x00\x09ab"),
+        TestVectors(    `[\cJ\cK\cA-\cD]{3}\cQ`, "\x01\x0B\x0A\x11", "y", "$&", "\x01\x0B\x0A\x11"),
+        TestVectors(    `\r\n\v\t\f\\`,     "\r\n\v\t\f\\",   "y",   "$&", "\r\n\v\t\f\\"),
+        TestVectors(    `[\u0003\u0001]{2}`,  "\u0001\u0003",         "y",   "$&", "\u0001\u0003"),
+        TestVectors(    `^[\u0020-\u0080\u0001\n-\r]{8}`,  "abc\u0001\v\f\r\n",  "y",   "$&", "abc\u0001\v\f\r\n"),
+        TestVectors(    `\w+\S\w+`, "ab7!44c",  "y", "$&", "ab7!44c"),
+        TestVectors(    `\b\w+\b`,  " abde4 ",  "y", "$&", "abde4"),
+        TestVectors(    `\b\w+\b`,  " abde4",   "y", "$&", "abde4"),
+        TestVectors(    `\b\w+\b`,  "abde4 ",   "y", "$&", "abde4"),
+        TestVectors(    `\pL\pS`,   "a\u02DA",  "y", "$&", "a\u02DA"),
+        TestVectors(    `\pX`,      "",         "c", "-",  "-"),
+// ^, $, \b, \B, multiline :
+        TestVectors(    `\r.*?$`,    "abc\r\nxy", "y", "$&", "\r\nxy", "sm"),
+        TestVectors(    `^a$^b$`,    "a\r\nb\n",  "n", "$&", "-", "m"),
+        TestVectors(    `^a$\r\n^b$`,"a\r\nb\n",  "y", "$&", "a\r\nb", "m"),
+        TestVectors(    `^$`,        "\r\n",      "y", "$&", "", "m"),
+        TestVectors(    `^a$\nx$`,   "a\nx\u2028","y", "$&", "a\nx", "m"),
+        TestVectors(    `^a$\nx$`,   "a\nx\u2029","y", "$&", "a\nx", "m"),
+        TestVectors(    `^a$\nx$`,   "a\nx\u0085","y", "$&", "a\nx","m"),
+        TestVectors(    `^x$`,       "\u2028x",   "y", "$&", "x", "m"),
+        TestVectors(    `^x$`,       "\u2029x",   "y", "$&", "x", "m"),
+        TestVectors(    `^x$`,       "\u0085x",   "y", "$&", "x", "m"),
+        TestVectors(    `\b^.`,      "ab",        "y", "$&", "a"),
+        TestVectors(    `\B^.`,      "ab",        "n", "-",  "-"),
+        TestVectors(    `^ab\Bc\B`,  "\r\nabcd",  "y", "$&", "abc", "m"),
+        TestVectors(    `^.*$`,      "12345678",  "y", "$&", "12345678"),
+
+// luckily obtained regression on incremental matching in backtracker
+        TestVectors(  `^(?:(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*([^ ]*)\s*#|# (?:\w|_)+=((?:\w|_)+))`,
+            "0020  ; White_Space # ", "y", "$1-$2-$3", "--0020"),
+//lookahead
+        TestVectors(    "(foo.)(?=(bar))",     "foobar foodbar", "y", "$&-$1-$2", "food-food-bar" ),
+        TestVectors(    `\b(\d+)[a-z](?=\1)`,  "123a123",        "y", "$&-$1", "123a-123" ),
+        TestVectors(    `\$(?!\d{3})\w+`,      "$123 $abc",      "y", "$&", "$abc"),
+        TestVectors(    `(abc)(?=(ed(f))\3)`,    "abcedff",      "y", "-", "-"),
+        TestVectors(    `\b[A-Za-z0-9.]+(?=(@(?!gmail)))`, "a@gmail,x@com",  "y", "$&-$1", "x-@"),
+        TestVectors(    `x()(abc)(?=(d)(e)(f)\2)`,   "xabcdefabc", "y", "$&", "xabc"),
+        TestVectors(    `x()(abc)(?=(d)(e)(f)()\3\4\5)`,   "xabcdefdef", "y", "$&", "xabc"),
+//lookback
+        TestVectors(    `(?<=(ab))\d`,    "12ba3ab4",    "y",   "$&-$1", "4-ab",  "i"),
+        TestVectors(    `\w(?<!\d)\w`,   "123ab24",  "y",   "$&", "ab"),
+        TestVectors(    `(?<=Dåb)x\w`,  "DåbDÅBxdÅb",  "y",   "$&", "xd", "i"),
+        TestVectors(    `(?<=(ab*c))x`,   "abbbbcxac",  "y",   "$&-$1", "x-abbbbc"),
+        TestVectors(    `(?<=(ab*?c))x`,   "abbbbcxac",  "y",   "$&-$1", "x-abbbbc"),
+        TestVectors(    `(?<=(a.*?c))x`,   "ababbcxac",  "y",   "$&-$1", "x-abbc"),
+        TestVectors(    `(?<=(a{2,4}b{1,3}))x`,   "yyaaaabx",  "y",   "$&-$1", "x-aaaab"),
+        TestVectors(    `(?<=((?:a{2,4}b{1,3}){1,2}))x`,   "aabbbaaaabx",  "y",   "$&-$1", "x-aabbbaaaab"),
+        TestVectors(    `(?<=((?:a{2,4}b{1,3}){1,2}?))x`,   "aabbbaaaabx",  "y",   "$&-$1", "x-aaaab"),
+        TestVectors(    `(?<=(abc|def|aef))x`,    "abcx", "y",        "$&-$1",  "x-abc"),
+        TestVectors(    `(?<=(abc|def|aef))x`,    "aefx", "y",        "$&-$1",  "x-aef"),
+        TestVectors(    `(?<=(abc|dabc))(x)`,    "dabcx", "y",        "$&-$1-$2",  "x-abc-x"),
+        TestVectors(    `(?<=(|abc))x`,        "dabcx", "y",        "$&-$1",  "x-"),
+        TestVectors(    `(?<=((ab|da)*))x`,    "abdaabx", "y",        "$&-$2-$1",  "x-ab-abdaab"),
+        TestVectors(    `a(?<=(ba(?<=(aba)(?<=aaba))))`, "aabaa", "y", "$&-$1-$2", "a-ba-aba"),
+        TestVectors(    `.(?<!b).`,   "bax",  "y", "$&", "ax"),
+        TestVectors(    `(?<=b(?<!ab)).`,   "abbx",  "y",  "$&", "x"),
+        TestVectors(    `(?<=\.|[!?]+)X`,   "Hey?!X", "y", "$&", "X"),
+        TestVectors(    `(?<=\.|[!?]+)a{3}`,   ".Nope.aaaX", "y", "$&", "aaa"),
+//mixed lookaround
+        TestVectors(   `a(?<=a(?=b))b`,    "ab", "y",      "$&", "ab"),
+        TestVectors(   `a(?<=a(?!b))c`,    "ac", "y",      "$&", "ac"),
+        ];
+    string produceExpected(M,String)(auto ref M m, String fmt)
+    {
+        auto app = appender!(String)();
+        replaceFmt(fmt, m.captures, app, true);
+        return app.data;
+    }
+    void run_tests(alias matchFn)()
+    {
+        int i;
+        foreach(Char; AliasSeq!( char, wchar, dchar))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            alias String = immutable(Char)[];
+            String produceExpected(M,Range)(auto ref M m, Range fmt)
+            {
+                auto app = appender!(String)();
+                replaceFmt(fmt, m.captures, app, true);
+                return app.data;
+            }
+            Regex!(Char) r;
+            foreach(a, tvd; tv)
+            {
+                uint c = tvd.result[0];
+                debug(std_regex_test) writeln(" Test #", a, " pattern: ", tvd.pattern, " with Char = ", Char.stringof);
+                try
+                {
+                    i = 1;
+                    r = regex(to!(String)(tvd.pattern), tvd.flags);
+                }
+                catch (RegexException e)
+                {
+                    i = 0;
+                    debug(std_regex_test) writeln(e.msg);
+                }
+
+                assert((c == 'c') ? !i : i, "failed to compile pattern "~tvd.pattern);
+
+                if(c != 'c')
+                {
+                    auto m = matchFn(to!(String)(tvd.input), r);
+                    i = !m.empty;
+                    assert((c == 'y') ? i : !i, text(matchFn.stringof ~": failed to match pattern #", a ,": ", tvd.pattern));
+                    if(c == 'y')
+                    {
+                        auto result = produceExpected(m, to!(String)(tvd.format));
+                        assert(result == to!String(tvd.replace),
+                            text(matchFn.stringof ~": mismatch pattern #", a, ": ", tvd.pattern," expected: ",
+                                    tvd.replace, " vs ", result));
+                    }
+                }
+            }
+        }();
+        debug(std_regex_test) writeln("!!! FReD bulk test done "~matchFn.stringof~" !!!");
+    }
+
+
+    void ct_tests()
+    {
+        version(std_regex_ct1)
+        {
+            pragma(msg, "Testing 1st part of ctRegex");
+            alias Tests = Sequence!(0, 155);
+        }
+        else version(std_regex_ct2)
+        {
+            pragma(msg, "Testing 2nd part of ctRegex");
+            alias Tests = Sequence!(155, 174);
+        }
+        //FIXME: #174-178 contains CTFE parser bug
+        else version(std_regex_ct3)
+        {
+            pragma(msg, "Testing 3rd part of ctRegex");
+            alias Tests = Sequence!(178, 220);
+        }
+        else version(std_regex_ct4)
+        {
+            pragma(msg, "Testing 4th part of ctRegex");
+            alias Tests = Sequence!(220, tv.length);
+        }
+        else
+            alias Tests = AliasSeq!(Sequence!(0, 30), Sequence!(235, tv.length-5));
+        foreach(a, v; Tests)
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            enum tvd = tv[v];
+            static if(tvd.result == "c")
+            {
+                static assert(!__traits(compiles, (){
+                    enum r = regex(tvd.pattern, tvd.flags);
+                }), "errornously compiles regex pattern: " ~ tvd.pattern);
+            }
+            else
+            {
+                //BUG: tv[v] is fine but tvd is not known at compile time?!
+                auto r = ctRegex!(tv[v].pattern, tv[v].flags);
+                auto nr = regex(tvd.pattern, tvd.flags);
+                assert(equal(r.ir, nr.ir),
+                    text("!C-T regex! failed to compile pattern #", a ,": ", tvd.pattern));
+                auto m = match(tvd.input, r);
+                auto c = tvd.result[0];
+                bool ok = (c == 'y') ^ m.empty;
+                assert(ok, text("ctRegex: failed to match pattern #",
+                    a ,": ", tvd.pattern));
+                if(c == 'y')
+                {
+                    import std.stdio;
+                    auto result = produceExpected(m, tvd.format);
+                    if(result != tvd.replace)
+                        writeln("ctRegex mismatch pattern #", a, ": ", tvd.pattern," expected: ",
+                                tvd.replace, " vs ", result);
+                }
+            }
+        }();
+        debug(std_regex_test) writeln("!!! FReD C-T test done !!!");
+    }
+
+    ct_tests();
+    run_tests!bmatch(); //backtracker
+    run_tests!match(); //thompson VM
+}
+
+unittest
+{
+    auto cr = ctRegex!("abc");
+    assert(bmatch("abc",cr).hit == "abc");
+    auto cr2 = ctRegex!("ab*c");
+    assert(bmatch("abbbbc",cr2).hit == "abbbbc");
+}
+unittest
+{
+    auto cr3 = ctRegex!("^abc$");
+    assert(bmatch("abc",cr3).hit == "abc");
+    auto cr4 = ctRegex!(`\b(a\B[a-z]b)\b`);
+    assert(array(match("azb",cr4).captures) == ["azb", "azb"]);
+}
+
+unittest
+{
+    auto cr5 = ctRegex!("(?:a{2,4}b{1,3}){1,2}");
+    assert(bmatch("aaabaaaabbb", cr5).hit == "aaabaaaabbb");
+    auto cr6 = ctRegex!("(?:a{2,4}b{1,3}){1,2}?"w);
+    assert(bmatch("aaabaaaabbb"w,  cr6).hit == "aaab"w);
+}
+
+unittest
+{
+    auto cr7 = ctRegex!(`\r.*?$`,"sm");
+    assert(bmatch("abc\r\nxy",  cr7).hit == "\r\nxy");
+    auto greed =  ctRegex!("<packet.*?/packet>");
+    assert(bmatch("<packet>text</packet><packet>text</packet>", greed).hit
+            == "<packet>text</packet>");
+}
+
+unittest
+{
+    auto cr8 = ctRegex!("^(a)(b)?(c*)");
+    auto m8 = bmatch("abcc",cr8);
+    assert(m8);
+    assert(m8.captures[1] == "a");
+    assert(m8.captures[2] == "b");
+    assert(m8.captures[3] == "cc");
+    auto cr9 = ctRegex!("q(a|b)*q");
+    auto m9 = match("xxqababqyy",cr9);
+    assert(m9);
+    assert(equal(bmatch("xxqababqyy",cr9).captures, ["qababq", "b"]));
+}
+
+unittest
+{
+    auto rtr = regex("a|b|c");
+    enum ctr = regex("a|b|c");
+    assert(equal(rtr.ir,ctr.ir));
+    //CTFE parser BUG is triggered by group
+    //in the middle of alternation (at least not first and not last)
+    enum testCT = regex(`abc|(edf)|xyz`);
+    auto testRT = regex(`abc|(edf)|xyz`);
+    assert(equal(testCT.ir,testRT.ir));
+}
+
+unittest
+{
+    enum cx = ctRegex!"(A|B|C)";
+    auto mx = match("B",cx);
+    assert(mx);
+    assert(equal(mx.captures, [ "B", "B"]));
+    enum cx2 = ctRegex!"(A|B)*";
+    assert(match("BAAA",cx2));
+
+    enum cx3 = ctRegex!("a{3,4}","i");
+    auto mx3 = match("AaA",cx3);
+    assert(mx3);
+    assert(mx3.captures[0] == "AaA");
+    enum cx4 = ctRegex!(`^a{3,4}?[a-zA-Z0-9~]{1,2}`,"i");
+    auto mx4 = match("aaaabc", cx4);
+    assert(mx4);
+    assert(mx4.captures[0] == "aaaab");
+    auto cr8 = ctRegex!("(a)(b)?(c*)");
+    auto m8 = bmatch("abcc",cr8);
+    assert(m8);
+    assert(m8.captures[1] == "a");
+    assert(m8.captures[2] == "b");
+    assert(m8.captures[3] == "cc");
+    auto cr9 = ctRegex!(".*$", "gm");
+    auto m9 = match("First\rSecond", cr9);
+    assert(m9);
+    assert(equal(map!"a.hit"(m9), ["First", "", "Second"]));
+}
+
+unittest
+{
+//global matching
+    void test_body(alias matchFn)()
+    {
+        string s = "a quick brown fox jumps over a lazy dog";
+        auto r1 = regex("\\b[a-z]+\\b","g");
+        string[] test;
+        foreach(m; matchFn(s, r1))
+            test ~= m.hit;
+        assert(equal(test, [ "a", "quick", "brown", "fox", "jumps", "over", "a", "lazy", "dog"]));
+        auto free_reg = regex(`
+
+            abc
+            \s+
+            "
+            (
+                    [^"]+
+                |   \\ "
+            )+
+            "
+            z
+        `, "x");
+        auto m = match(`abc  "quoted string with \" inside"z`,free_reg);
+        assert(m);
+        string mails = " hey@you.com no@spam.net ";
+        auto rm = regex(`@(?<=\S+@)\S+`,"g");
+        assert(equal(map!"a[0]"(matchFn(mails, rm)), ["@you.com", "@spam.net"]));
+        auto m2 = matchFn("First line\nSecond line",regex(".*$","gm"));
+        assert(equal(map!"a[0]"(m2), ["First line", "", "Second line"]));
+        auto m2a = matchFn("First line\nSecond line",regex(".+$","gm"));
+        assert(equal(map!"a[0]"(m2a), ["First line", "Second line"]));
+        auto m2b = matchFn("First line\nSecond line",regex(".+?$","gm"));
+        assert(equal(map!"a[0]"(m2b), ["First line", "Second line"]));
+        debug(std_regex_test) writeln("!!! FReD FLAGS test done "~matchFn.stringof~" !!!");
+    }
+    test_body!bmatch();
+    test_body!match();
+}
+
+//tests for accumulated std.regex issues and other regressions
+unittest
+{
+    void test_body(alias matchFn)()
+    {
+        //issue 5857
+        //matching goes out of control if ... in (...){x} has .*/.+
+        auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures;
+        assert(c[0] == "axxxzayyyyyzd");
+        assert(c[1] == "ayyyyyz");
+        auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures;
+        assert(c2[0] == "axxxayyyyyd");
+        assert(c2[1] == "ayyyyy");
+        //issue 2108
+        //greedy vs non-greedy
+        auto nogreed = regex("<packet.*?/packet>");
+        assert(matchFn("<packet>text</packet><packet>text</packet>", nogreed).hit
+               == "<packet>text</packet>");
+        auto greed =  regex("<packet.*/packet>");
+        assert(matchFn("<packet>text</packet><packet>text</packet>", greed).hit
+               == "<packet>text</packet><packet>text</packet>");
+        //issue 4574
+        //empty successful match still advances the input
+        string[] pres, posts, hits;
+        foreach(m; matchFn("abcabc", regex("","g"))) {
+            pres ~= m.pre;
+            posts ~= m.post;
+            assert(m.hit.empty);
+
+        }
+        auto heads = [
+            "abcabc",
+            "abcab",
+            "abca",
+            "abc",
+            "ab",
+            "a",
+            ""
+        ];
+        auto tails = [
+            "abcabc",
+             "bcabc",
+              "cabc",
+               "abc",
+                "bc",
+                 "c",
+                  ""
+        ];
+        assert(pres == array(retro(heads)));
+        assert(posts == tails);
+        //issue 6076
+        //regression on .*
+        auto re = regex("c.*|d");
+        auto m = matchFn("mm", re);
+        assert(!m);
+        debug(std_regex_test) writeln("!!! FReD REGRESSION test done "~matchFn.stringof~" !!!");
+        auto rprealloc = regex(`((.){5}.{1,10}){5}`);
+        auto arr = array(repeat('0',100));
+        auto m2 = matchFn(arr, rprealloc);
+        assert(m2);
+        assert(collectException(
+                regex(r"^(import|file|binary|config)\s+([^\(]+)\(?([^\)]*)\)?\s*$")
+                ) is null);
+        foreach(ch; [Escapables])
+        {
+            assert(match(to!string(ch),regex(`[\`~ch~`]`)));
+            assert(!match(to!string(ch),regex(`[^\`~ch~`]`)));
+            assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`)));
+        }
+        //bugzilla 7718
+        string strcmd = "./myApp.rb -os OSX -path \"/GIT/Ruby Apps/sec\" -conf 'notimer'";
+        auto reStrCmd = regex (`(".*")|('.*')`, "g");
+        assert(equal(map!"a[0]"(matchFn(strcmd, reStrCmd)),
+                     [`"/GIT/Ruby Apps/sec"`, `'notimer'`]));
+    }
+    test_body!bmatch();
+    test_body!match();
+}
+
+// tests for replace
+unittest
+{
+    void test(alias matchFn)()
+    {
+        import std.uni : toUpper;
+
+        foreach(i, v; AliasSeq!(string, wstring, dstring))
+        {
+            auto baz(Cap)(Cap m)
+            if (is(Cap == Captures!(Cap.String)))
+            {
+                return toUpper(m.hit);
+            }
+            alias String = v;
+            assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r")), to!String("c"))
+                   == to!String("ack rapacity"));
+            assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r"), "g"), to!String("c"))
+                   == to!String("ack capacity"));
+            assert(std.regex.replace!(matchFn)(to!String("noon"), regex(to!String("^n")), to!String("[$&]"))
+                   == to!String("[n]oon"));
+            assert(std.regex.replace!(matchFn)(to!String("test1 test2"), regex(to!String(`\w+`),"g"), to!String("$`:$'"))
+                   == to!String(": test2 test1 :"));
+            auto s = std.regex.replace!(baz!(Captures!(String)))(to!String("Strap a rocket engine on a chicken."),
+                    regex(to!String("[ar]"), "g"));
+            assert(s == "StRAp A Rocket engine on A chicken.");
+        }
+        debug(std_regex_test) writeln("!!! Replace test done "~matchFn.stringof~"  !!!");
+    }
+    test!(bmatch)();
+    test!(match)();
+}
+
+// tests for splitter
+unittest
+{
+    auto s1 = ", abc, de,     fg, hi, ";
+    auto sp1 = splitter(s1, regex(", *"));
+    auto w1 = ["", "abc", "de", "fg", "hi", ""];
+    assert(equal(sp1, w1));
+
+    auto s2 = ", abc, de,  fg, hi";
+    auto sp2 = splitter(s2, regex(", *"));
+    auto w2 = ["", "abc", "de", "fg", "hi"];
+
+    uint cnt;
+    foreach(e; sp2) {
+        assert(w2[cnt++] == e);
+    }
+    assert(equal(sp2, w2));
+}
+
+unittest
+{
+    char[] s1 = ", abc, de,  fg, hi, ".dup;
+    auto sp2 = splitter(s1, regex(", *"));
+}
+
+unittest
+{
+    auto s1 = ", abc, de,  fg, hi, ";
+    auto w1 = ["", "abc", "de", "fg", "hi", ""];
+    assert(equal(split(s1, regex(", *")), w1[]));
+}
+
+unittest
+{ // bugzilla 7141
+    string pattern = `[a\--b]`;
+    assert(match("-", pattern));
+    assert(match("b", pattern));
+    string pattern2 = `[&-z]`;
+    assert(match("b", pattern2));
+}
+unittest
+{//bugzilla 7111
+    assert(match("", regex("^")));
+}
+unittest
+{//bugzilla 7300
+    assert(!match("a"d, "aa"d));
+}
+
+unittest
+{//bugzilla 7674
+    assert("1234".replace(regex("^"), "$$") == "$1234");
+    assert("hello?".replace(regex(r"\?", "g"), r"\?") == r"hello\?");
+    assert("hello?".replace(regex(r"\?", "g"), r"\\?") != r"hello\?");
+}
+unittest
+{// bugzilla 7679
+    foreach(S; AliasSeq!(string, wstring, dstring))
+    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+        enum re = ctRegex!(to!S(r"\."));
+        auto str = to!S("a.b");
+        assert(equal(std.regex.splitter(str, re), [to!S("a"), to!S("b")]));
+        assert(split(str, re) == [to!S("a"), to!S("b")]);
+    }();
+}
+unittest
+{//bugzilla 8203
+    string data = "
+    NAME   = XPAW01_STA:STATION
+    NAME   = XPAW01_STA
+    ";
+    auto uniFileOld = data;
+    auto r = regex(
+       r"^NAME   = (?P<comp>[a-zA-Z0-9_]+):*(?P<blk>[a-zA-Z0-9_]*)","gm");
+    auto uniCapturesNew = match(uniFileOld, r);
+    for(int i = 0; i < 20; i++)
+        foreach (matchNew; uniCapturesNew) {}
+    //a second issue with same symptoms
+    auto r2 = regex(`([а-яА-Я\-_]+\s*)+(?<=[\s\.,\^])`);
+    match("аллея Театральная", r2);
+}
+unittest
+{// bugzilla 8637 purity of enforce
+    auto m = match("hello world", regex("world"));
+    enforce(m);
+}
+
+// bugzilla 8725
+unittest
+{
+  static italic = regex( r"\*
+                (?!\s+)
+                (.*?)
+                (?!\s+)
+                \*", "gx" );
+  string input = "this * is* interesting, *very* interesting";
+  assert(replace(input, italic, "<i>$1</i>") ==
+      "this * is* interesting, <i>very</i> interesting");
+}
+
+// bugzilla 8349
+unittest
+{
+    enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)</a>";
+    enum peakRegex = ctRegex!(peakRegexStr);
+    //note that the regex pattern itself is probably bogus
+    assert(match(r"\>wgEncode-blah-Tfbs.narrow</a>", peakRegex));
+}
+
+// bugzilla 9211
+unittest
+{
+    auto rx_1 =  regex(r"^(\w)*(\d)");
+    auto m = match("1234", rx_1);
+    assert(equal(m.front, ["1234", "3", "4"]));
+    auto rx_2 = regex(r"^([0-9])*(\d)");
+    auto m2 = match("1234", rx_2);
+    assert(equal(m2.front, ["1234", "3", "4"]));
+}
+
+// bugzilla 9280
+unittest
+{
+    string tomatch = "a!b@c";
+    static r = regex(r"^(?P<nick>.*?)!(?P<ident>.*?)@(?P<host>.*?)$");
+    auto nm = match(tomatch, r);
+    assert(nm);
+    auto c = nm.captures;
+    assert(c[1] == "a");
+    assert(c["nick"] == "a");
+}
+
+
+// bugzilla 9579
+unittest
+{
+    char[] input = ['a', 'b', 'c'];
+    string format = "($1)";
+    // used to give a compile error:
+    auto re = regex(`(a)`, "g");
+    auto r = replace(input, re, format);
+    assert(r == "(a)bc");
+}
+
+// bugzilla 9634
+unittest
+{
+    auto re = ctRegex!"(?:a+)";
+    assert(match("aaaa", re).hit == "aaaa");
+}
+
+//bugzilla 10798
+unittest
+{
+    auto cr = ctRegex!("[abcd--c]*");
+    auto m  = "abc".match(cr);
+    assert(m);
+    assert(m.hit == "ab");
+}
+
+// bugzilla 10913
+unittest
+{
+    @system static string foo(const(char)[] s)
+    {
+        return s.dup;
+    }
+    @safe static string bar(const(char)[] s)
+    {
+        return s.dup;
+    }
+    () @system {
+        replace!((a) => foo(a.hit))("blah", regex(`a`));
+    }();
+    () @safe {
+        replace!((a) => bar(a.hit))("blah", regex(`a`));
+    }();
+}
+
+// bugzilla 11262
+unittest
+{
+    enum reg = ctRegex!(r",", "g");
+    auto str = "This,List";
+    str = str.replace(reg, "-");
+    assert(str == "This-List");
+}
+
+// bugzilla 11775
+unittest
+{
+    assert(collectException(regex("a{1,0}")));
+}
+
+// bugzilla 11839
+unittest
+{
+    assert(regex(`(?P<var1>\w+)`).namedCaptures.equal(["var1"]));
+    assert(collectException(regex(`(?P<1>\w+)`)));
+    assert(regex(`(?P<v1>\w+)`).namedCaptures.equal(["v1"]));
+    assert(regex(`(?P<__>\w+)`).namedCaptures.equal(["__"]));
+    assert(regex(`(?P<я>\w+)`).namedCaptures.equal(["я"]));
+}
+
+// bugzilla 12076
+unittest
+{
+    auto RE = ctRegex!(r"(?<!x[a-z]+)\s([a-z]+)");
+    string s = "one two";
+    auto m = match(s, RE);
+}
+
+// bugzilla 12105
+unittest
+{
+    auto r = ctRegex!`.*?(?!a)`;
+    assert("aaab".matchFirst(r).hit == "aaa");
+    auto r2 = ctRegex!`.*(?!a)`;
+    assert("aaab".matchFirst(r2).hit == "aaab");
+}
+
+//bugzilla 11784
+unittest
+{
+    assert("abcdefghijklmnopqrstuvwxyz"
+        .matchFirst("[a-z&&[^aeiuo]]").hit == "b");
+}
+
+//bugzilla 12366
+unittest
+{
+     auto re = ctRegex!(`^((?=(xx+?)\2+$)((?=\2+$)(?=(x+)(\4+$))\5){2})*x?$`);
+     assert("xxxxxxxx".match(re).empty);
+     assert(!"xxxx".match(re).empty);
+}
+
+// bugzilla 12582
+unittest
+{
+    auto r = regex(`(?P<a>abc)`);
+    assert(collectException("abc".matchFirst(r)["b"]));
+}
+
+// bugzilla 12691
+unittest
+{
+    assert(bmatch("e@", "^([a-z]|)*$").empty);
+    assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty);
+}
+
+//bugzilla  12713
+unittest
+{
+    assertThrown(regex("[[a-z]([a-z]|(([[a-z])))"));
+}
+
+//bugzilla 12747
+unittest
+{
+    assertThrown(regex(`^x(\1)`));
+    assertThrown(regex(`^(x(\1))`));
+    assertThrown(regex(`^((x)(?=\1))`));
+}
+
+// bugzilla 14529
+unittest
+{
+    auto ctPat2 = regex(r"^[CDF]$", "i");
+    foreach(v; ["C", "c", "D", "d", "F", "f"])
+        assert(matchAll(v, ctPat2).front.hit == v);
+}
+
diff --git a/libphobos/src/std/regex/internal/thompson.d b/libphobos/src/std/regex/internal/thompson.d
new file mode 100644
index 00000000000..5b7eacb0fbb
--- /dev/null
+++ b/libphobos/src/std/regex/internal/thompson.d
@@ -0,0 +1,942 @@
+//Written in the D programming language
+/*
+    Implementation of Thompson NFA std.regex engine.
+    Key point is evaluation of all possible threads (state) at each step
+    in a breadth-first manner, thereby geting some nice properties:
+        - looking at each character only once
+        - merging of equivalent threads, that gives matching process linear time complexity
+*/
+module std.regex.internal.thompson;
+
+package(std.regex):
+
+import std.regex.internal.ir;
+import std.range;
+
+//State of VM thread
+struct Thread(DataIndex)
+{
+    Thread* next;    //intrusive linked list
+    uint pc;
+    uint counter;    //loop counter
+    uint uopCounter; //counts micro operations inside one macro instruction (e.g. BackRef)
+    Group!DataIndex[1] matches;
+}
+
+//head-tail singly-linked list
+struct ThreadList(DataIndex)
+{
+    Thread!DataIndex* tip = null, toe = null;
+    //add new thread to the start of list
+    void insertFront(Thread!DataIndex* t)
+    {
+        if(tip)
+        {
+            t.next = tip;
+            tip = t;
+        }
+        else
+        {
+            t.next = null;
+            tip = toe = t;
+        }
+    }
+    //add new thread to the end of list
+    void insertBack(Thread!DataIndex* t)
+    {
+        if(toe)
+        {
+            toe.next = t;
+            toe = t;
+        }
+        else
+            tip = toe = t;
+        toe.next = null;
+    }
+    //move head element out of list
+    Thread!DataIndex* fetch()
+    {
+        auto t = tip;
+        if(tip == toe)
+            tip = toe = null;
+        else
+            tip = tip.next;
+        return t;
+    }
+    //non-destructive iteration of ThreadList
+    struct ThreadRange
+    {
+        const(Thread!DataIndex)* ct;
+        this(ThreadList tlist){ ct = tlist.tip; }
+        @property bool empty(){ return ct is null; }
+        @property const(Thread!DataIndex)* front(){ return ct; }
+        @property popFront()
+        {
+            assert(ct);
+            ct = ct.next;
+        }
+    }
+    @property bool empty()
+    {
+        return tip == null;
+    }
+    ThreadRange opSlice()
+    {
+        return ThreadRange(this);
+    }
+}
+
+/+
+   Thomspon matcher does all matching in lockstep,
+   never looking at the same char twice
++/
+@trusted struct ThompsonMatcher(Char, Stream = Input!Char)
+    if(is(Char : dchar))
+{
+    alias DataIndex = Stream.DataIndex;
+    Thread!DataIndex* freelist;
+    ThreadList!DataIndex clist, nlist;
+    DataIndex[] merge;
+    Group!DataIndex[] backrefed;
+    Regex!Char re;           //regex program
+    Stream s;
+    dchar front;
+    DataIndex index;
+    DataIndex genCounter;    //merge trace counter, goes up on every dchar
+    size_t[size_t] subCounters; //a table of gen counter per sub-engine: PC -> counter
+    size_t threadSize;
+    bool matched;
+    bool exhausted;
+    static if(__traits(hasMember,Stream, "search"))
+    {
+        enum kicked = true;
+    }
+    else
+        enum kicked = false;
+
+    static size_t getThreadSize(const ref Regex!Char re)
+    {
+        return re.ngroup
+            ? (Thread!DataIndex).sizeof + (re.ngroup-1)*(Group!DataIndex).sizeof
+            : (Thread!DataIndex).sizeof - (Group!DataIndex).sizeof;
+    }
+
+    static size_t initialMemory(const ref Regex!Char re)
+    {
+        return getThreadSize(re)*re.threadCount + re.hotspotTableSize*size_t.sizeof;
+    }
+
+    //true if it's start of input
+    @property bool atStart(){   return index == 0; }
+
+    //true if it's end of input
+    @property bool atEnd(){  return index == s.lastIndex && s.atEnd; }
+
+    bool next()
+    {
+        if(!s.nextChar(front, index))
+        {
+            index =  s.lastIndex;
+            return false;
+        }
+        return true;
+    }
+
+    static if(kicked)
+    {
+        bool search()
+        {
+
+            if(!s.search(re.kickstart, front, index))
+            {
+                index = s.lastIndex;
+                return false;
+            }
+            return true;
+        }
+    }
+
+    void initExternalMemory(void[] memory)
+    {
+        threadSize = getThreadSize(re);
+        prepareFreeList(re.threadCount, memory);
+        if(re.hotspotTableSize)
+        {
+            merge = arrayInChunk!(DataIndex)(re.hotspotTableSize, memory);
+            merge[] = 0;
+        }
+    }
+
+    this()(Regex!Char program, Stream stream, void[] memory)
+    {
+        re = program;
+        s = stream;
+        initExternalMemory(memory);
+        genCounter = 0;
+    }
+
+    this(S)(ref ThompsonMatcher!(Char,S) matcher, Bytecode[] piece, Stream stream)
+    {
+        s = stream;
+        re = matcher.re;
+        re.ir = piece;
+        threadSize = matcher.threadSize;
+        merge = matcher.merge;
+        freelist = matcher.freelist;
+        front = matcher.front;
+        index = matcher.index;
+    }
+
+    auto fwdMatcher()(Bytecode[] piece, size_t counter)
+    {
+        auto m = ThompsonMatcher!(Char, Stream)(this, piece, s);
+        m.genCounter = counter;
+        return m;
+    }
+
+    auto bwdMatcher()(Bytecode[] piece, size_t counter)
+    {
+        alias BackLooper = typeof(s.loopBack(index));
+        auto m = ThompsonMatcher!(Char, BackLooper)(this, piece, s.loopBack(index));
+        m.genCounter = counter;
+        m.next();
+        return m;
+    }
+
+    auto dupTo(void[] memory)
+    {
+        typeof(this) tmp = this;//bitblit
+        tmp.initExternalMemory(memory);
+        tmp.genCounter = 0;
+        return tmp;
+    }
+
+    enum MatchResult{
+        NoMatch,
+        PartialMatch,
+        Match,
+    }
+
+    bool match(Group!DataIndex[] matches)
+    {
+        debug(std_regex_matcher)
+            writeln("------------------------------------------");
+        if(exhausted)
+        {
+            return false;
+        }
+        if(re.flags & RegexInfo.oneShot)
+        {
+            next();
+            exhausted = true;
+            return matchOneShot(matches)==MatchResult.Match;
+        }
+        static if(kicked)
+            if(!re.kickstart.empty)
+                return matchImpl!(true)(matches);
+        return matchImpl!(false)(matches);
+    }
+
+    //match the input and fill matches
+    bool matchImpl(bool withSearch)(Group!DataIndex[] matches)
+    {
+        if(!matched && clist.empty)
+        {
+           static if(withSearch)
+                search();
+           else
+                next();
+        }
+        else//char in question is  fetched in prev call to match
+        {
+            matched = false;
+        }
+
+        if(!atEnd)//if no char
+            for(;;)
+            {
+                genCounter++;
+                debug(std_regex_matcher)
+                {
+                    writefln("Threaded matching threads at  %s", s[index..s.lastIndex]);
+                    foreach(t; clist[])
+                    {
+                        assert(t);
+                        writef("pc=%s ",t.pc);
+                        write(t.matches);
+                        writeln();
+                    }
+                }
+                for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch())
+                {
+                    eval!true(t, matches);
+                }
+                if(!matched)//if we already have match no need to push the engine
+                    eval!true(createStart(index), matches);//new thread staring at this position
+                else if(nlist.empty)
+                {
+                    debug(std_regex_matcher) writeln("Stopped  matching before consuming full input");
+                    break;//not a partial match for sure
+                }
+                clist = nlist;
+                nlist = (ThreadList!DataIndex).init;
+                if(clist.tip is null)
+                {
+                    static if(withSearch)
+                    {
+                        if(!search())
+                            break;
+                    }
+                    else
+                    {
+                        if(!next())
+                            break;
+                    }
+                }
+                else if(!next())
+                {
+                    if (!atEnd) return false;
+                    exhausted = true;
+                    break;
+                }
+            }
+
+        genCounter++; //increment also on each end
+        debug(std_regex_matcher) writefln("Threaded matching threads at end");
+        //try out all zero-width posibilities
+        for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch())
+        {
+            eval!false(t, matches);
+        }
+        if(!matched)
+            eval!false(createStart(index), matches);//new thread starting at end of input
+        if(matched)
+        {//in case NFA found match along the way
+         //and last possible longer alternative ultimately failed
+            s.reset(matches[0].end);//reset to last successful match
+            next();//and reload front character
+            //--- here the exact state of stream was restored ---
+            exhausted = atEnd || !(re.flags & RegexOption.global);
+            //+ empty match advances the input
+            if(!exhausted && matches[0].begin == matches[0].end)
+                next();
+        }
+        return matched;
+    }
+
+    /+
+        handle succesful threads
+    +/
+    void finish(const(Thread!DataIndex)* t, Group!DataIndex[] matches)
+    {
+        matches.ptr[0..re.ngroup] = t.matches.ptr[0..re.ngroup];
+        debug(std_regex_matcher)
+        {
+            writef("FOUND pc=%s prog_len=%s",
+                    t.pc, re.ir.length);
+            if(!matches.empty)
+                writefln(": %s..%s", matches[0].begin, matches[0].end);
+            foreach(v; matches)
+                writefln("%d .. %d", v.begin, v.end);
+        }
+        matched = true;
+    }
+
+    /+
+        match thread against codepoint, cutting trough all 0-width instructions
+        and taking care of control flow, then add it to nlist
+    +/
+    void eval(bool withInput)(Thread!DataIndex* t, Group!DataIndex[] matches)
+    {
+        ThreadList!DataIndex worklist;
+        debug(std_regex_matcher) writeln("---- Evaluating thread");
+        for(;;)
+        {
+            debug(std_regex_matcher)
+            {
+                writef("\tpc=%s [", t.pc);
+                foreach(x; worklist[])
+                    writef(" %s ", x.pc);
+                writeln("]");
+            }
+            switch(re.ir[t.pc].code)
+            {
+            case IR.End:
+                finish(t, matches);
+                matches[0].end = index; //fix endpoint of the whole match
+                recycle(t);
+                //cut off low priority threads
+                recycle(clist);
+                recycle(worklist);
+                debug(std_regex_matcher) writeln("Finished thread ", matches);
+                return;
+            case IR.Wordboundary:
+                dchar back;
+                DataIndex bi;
+                //at start & end of input
+                if(atStart && wordTrie[front])
+                {
+                    t.pc += IRL!(IR.Wordboundary);
+                    break;
+                }
+                else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                        && wordTrie[back])
+                {
+                    t.pc += IRL!(IR.Wordboundary);
+                    break;
+                }
+                else if(s.loopBack(index).nextChar(back, bi))
+                {
+                    bool af = wordTrie[front];
+                    bool ab = wordTrie[back];
+                    if(af ^ ab)
+                    {
+                        t.pc += IRL!(IR.Wordboundary);
+                        break;
+                    }
+                }
+                recycle(t);
+                t = worklist.fetch();
+                if(!t)
+                    return;
+                break;
+            case IR.Notwordboundary:
+                dchar back;
+                DataIndex bi;
+                //at start & end of input
+                if(atStart && wordTrie[front])
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                else if(atEnd && s.loopBack(index).nextChar(back, bi)
+                        && wordTrie[back])
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                else if(s.loopBack(index).nextChar(back, bi))
+                {
+                    bool af = wordTrie[front];
+                    bool ab = wordTrie[back]  != 0;
+                    if(af ^ ab)
+                    {
+                        recycle(t);
+                        t = worklist.fetch();
+                        if(!t)
+                            return;
+                        break;
+                    }
+                }
+                t.pc += IRL!(IR.Wordboundary);
+                break;
+            case IR.Bol:
+                dchar back;
+                DataIndex bi;
+                if(atStart
+                    ||( (re.flags & RegexOption.multiline)
+                    && s.loopBack(index).nextChar(back,bi)
+                    && startOfLine(back, front == '\n')))
+                {
+                    t.pc += IRL!(IR.Bol);
+                }
+                else
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                }
+                break;
+            case IR.Eol:
+                debug(std_regex_matcher) writefln("EOL (front 0x%x) %s",  front, s[index..s.lastIndex]);
+                dchar back;
+                DataIndex bi;
+                //no matching inside \r\n
+                if(atEnd || ((re.flags & RegexOption.multiline)
+                    && endOfLine(front, s.loopBack(index).nextChar(back, bi)
+                        && back == '\r')))
+                {
+                    t.pc += IRL!(IR.Eol);
+                }
+                else
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                }
+                break;
+            case IR.InfiniteStart, IR.InfiniteQStart:
+                t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart);
+                goto case IR.InfiniteEnd; //both Q and non-Q
+            case IR.RepeatStart, IR.RepeatQStart:
+                t.pc += re.ir[t.pc].data + IRL!(IR.RepeatStart);
+                goto case IR.RepeatEnd; //both Q and non-Q
+            case IR.RepeatEnd:
+            case IR.RepeatQEnd:
+                //len, step, min, max
+                uint len = re.ir[t.pc].data;
+                uint step =  re.ir[t.pc+2].raw;
+                uint min = re.ir[t.pc+3].raw;
+                if(t.counter < min)
+                {
+                    t.counter += step;
+                    t.pc -= len;
+                    break;
+                }
+                if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter)
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] );
+                    merge[re.ir[t.pc + 1].raw+t.counter] = genCounter;
+                }
+                else
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] );
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                uint max = re.ir[t.pc+4].raw;
+                if(t.counter < max)
+                {
+                    if(re.ir[t.pc].code == IR.RepeatEnd)
+                    {
+                        //queue out-of-loop thread
+                        worklist.insertFront(fork(t, t.pc + IRL!(IR.RepeatEnd),  t.counter % step));
+                        t.counter += step;
+                        t.pc -= len;
+                    }
+                    else
+                    {
+                        //queue into-loop thread
+                        worklist.insertFront(fork(t, t.pc - len,  t.counter + step));
+                        t.counter %= step;
+                        t.pc += IRL!(IR.RepeatEnd);
+                    }
+                }
+                else
+                {
+                    t.counter %= step;
+                    t.pc += IRL!(IR.RepeatEnd);
+                }
+                break;
+            case IR.InfiniteEnd:
+            case IR.InfiniteQEnd:
+                if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter)
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] );
+                    merge[re.ir[t.pc + 1].raw+t.counter] = genCounter;
+                }
+                else
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] );
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                uint len = re.ir[t.pc].data;
+                uint pc1, pc2; //branches to take in priority order
+                if(re.ir[t.pc].code == IR.InfiniteEnd)
+                {
+                    pc1 = t.pc - len;
+                    pc2 = t.pc + IRL!(IR.InfiniteEnd);
+                }
+                else
+                {
+                    pc1 = t.pc + IRL!(IR.InfiniteEnd);
+                    pc2 = t.pc - len;
+                }
+                static if(withInput)
+                {
+                    worklist.insertFront(fork(t, pc2, t.counter));
+                    t.pc = pc1;
+                }
+                else
+                {
+                    worklist.insertFront(fork(t, pc2, t.counter));
+                    t.pc = pc1;
+                }
+                break;
+            case IR.OrEnd:
+                if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter)
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] );
+                    merge[re.ir[t.pc + 1].raw+t.counter] = genCounter;
+                    t.pc += IRL!(IR.OrEnd);
+                }
+                else
+                {
+                    debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s",
+                                    t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] );
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                }
+                break;
+            case IR.OrStart:
+                t.pc += IRL!(IR.OrStart);
+                goto case;
+            case IR.Option:
+                uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option);
+                //queue next Option
+                if(re.ir[next].code == IR.Option)
+                {
+                    worklist.insertFront(fork(t, next, t.counter));
+                }
+                t.pc += IRL!(IR.Option);
+                break;
+            case IR.GotoEndOr:
+                t.pc = t.pc + re.ir[t.pc].data + IRL!(IR.GotoEndOr);
+                goto case IR.OrEnd;
+            case IR.GroupStart:
+                uint n = re.ir[t.pc].data;
+                t.matches.ptr[n].begin = index;
+                t.pc += IRL!(IR.GroupStart);
+                break;
+            case IR.GroupEnd:
+                uint n = re.ir[t.pc].data;
+                t.matches.ptr[n].end = index;
+                t.pc += IRL!(IR.GroupEnd);
+                break;
+            case IR.Backref:
+                uint n = re.ir[t.pc].data;
+                Group!DataIndex* source = re.ir[t.pc].localRef ? t.matches.ptr : backrefed.ptr;
+                assert(source);
+                if(source[n].begin == source[n].end)//zero-width Backref!
+                {
+                    t.pc += IRL!(IR.Backref);
+                }
+                else static if(withInput)
+                {
+                    size_t idx = source[n].begin + t.uopCounter;
+                    size_t end = source[n].end;
+                    if(s[idx..end].front == front)
+                    {
+                        import std.utf : stride;
+
+                        t.uopCounter += stride(s[idx..end], 0);
+                        if(t.uopCounter + source[n].begin == source[n].end)
+                        {//last codepoint
+                            t.pc += IRL!(IR.Backref);
+                            t.uopCounter = 0;
+                        }
+                        nlist.insertBack(t);
+                    }
+                    else
+                        recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                else
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                break;
+            case IR.LookbehindStart:
+            case IR.NeglookbehindStart:
+                uint len = re.ir[t.pc].data;
+                uint ms = re.ir[t.pc + 1].raw, me = re.ir[t.pc + 2].raw;
+                uint end = t.pc + len + IRL!(IR.LookbehindEnd) + IRL!(IR.LookbehindStart);
+                bool positive = re.ir[t.pc].code == IR.LookbehindStart;
+                static if(Stream.isLoopback)
+                    auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0));
+                else
+                    auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0));
+                matcher.re.ngroup = me - ms;
+                matcher.backrefed = backrefed.empty ? t.matches : backrefed;
+                //backMatch
+                auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookbehindStart));
+                freelist = matcher.freelist;
+                subCounters[t.pc] = matcher.genCounter;
+                if((mRes == MatchResult.Match) ^ positive)
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                else
+                    t.pc = end;
+                break;
+            case IR.LookaheadStart:
+            case IR.NeglookaheadStart:
+                auto save = index;
+                uint len = re.ir[t.pc].data;
+                uint ms = re.ir[t.pc+1].raw, me = re.ir[t.pc+2].raw;
+                uint end = t.pc+len+IRL!(IR.LookaheadEnd)+IRL!(IR.LookaheadStart);
+                bool positive = re.ir[t.pc].code == IR.LookaheadStart;
+                static if(Stream.isLoopback)
+                    auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0));
+                else
+                    auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0));
+                matcher.re.ngroup = me - ms;
+                matcher.backrefed = backrefed.empty ? t.matches : backrefed;
+                auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookaheadStart));
+                freelist = matcher.freelist;
+                subCounters[t.pc] = matcher.genCounter;
+                s.reset(index);
+                next();
+                if((mRes == MatchResult.Match) ^ positive)
+                {
+                    recycle(t);
+                    t = worklist.fetch();
+                    if(!t)
+                        return;
+                    break;
+                }
+                else
+                    t.pc = end;
+                break;
+            case IR.LookaheadEnd:
+            case IR.NeglookaheadEnd:
+            case IR.LookbehindEnd:
+            case IR.NeglookbehindEnd:
+                finish(t, matches.ptr[0 .. re.ngroup]);
+                recycle(t);
+                //cut off low priority threads
+                recycle(clist);
+                recycle(worklist);
+                return;
+            case IR.Nop:
+                t.pc += IRL!(IR.Nop);
+                break;
+
+                static if(withInput)
+                {
+            case IR.OrChar:
+                      uint len = re.ir[t.pc].sequence;
+                      uint end = t.pc + len;
+                      static assert(IRL!(IR.OrChar) == 1);
+                      for(; t.pc < end; t.pc++)
+                          if(re.ir[t.pc].data == front)
+                              break;
+                      if(t.pc != end)
+                      {
+                          t.pc = end;
+                          nlist.insertBack(t);
+                      }
+                      else
+                          recycle(t);
+                      t = worklist.fetch();
+                      if(!t)
+                          return;
+                      break;
+            case IR.Char:
+                      if(front == re.ir[t.pc].data)
+                      {
+                          t.pc += IRL!(IR.Char);
+                          nlist.insertBack(t);
+                      }
+                      else
+                          recycle(t);
+                      t = worklist.fetch();
+                      if(!t)
+                          return;
+                      break;
+            case IR.Any:
+                      t.pc += IRL!(IR.Any);
+                      if(!(re.flags & RegexOption.singleline)
+                              && (front == '\r' || front == '\n'))
+                          recycle(t);
+                      else
+                          nlist.insertBack(t);
+                      t = worklist.fetch();
+                      if(!t)
+                          return;
+                      break;
+            case IR.CodepointSet:
+                      if(re.charsets[re.ir[t.pc].data].scanFor(front))
+                      {
+                          t.pc += IRL!(IR.CodepointSet);
+                          nlist.insertBack(t);
+                      }
+                      else
+                      {
+                          recycle(t);
+                      }
+                      t = worklist.fetch();
+                      if(!t)
+                          return;
+                      break;
+            case IR.Trie:
+                      if(re.tries[re.ir[t.pc].data][front])
+                      {
+                          t.pc += IRL!(IR.Trie);
+                          nlist.insertBack(t);
+                      }
+                      else
+                      {
+                          recycle(t);
+                      }
+                      t = worklist.fetch();
+                      if(!t)
+                          return;
+                      break;
+                  default:
+                      assert(0, "Unrecognized instruction " ~ re.ir[t.pc].mnemonic);
+                }
+                else
+                {
+                    default:
+                        recycle(t);
+                        t = worklist.fetch();
+                        if(!t)
+                            return;
+                }
+            }
+        }
+
+    }
+    enum uint RestartPc = uint.max;
+    //match the input, evaluating IR without searching
+    MatchResult matchOneShot(Group!DataIndex[] matches, uint startPc = 0)
+    {
+        debug(std_regex_matcher)
+        {
+            writefln("---------------single shot match ----------------- ");
+        }
+        alias evalFn = eval;
+        assert(clist == (ThreadList!DataIndex).init || startPc == RestartPc); // incorrect after a partial match
+        assert(nlist == (ThreadList!DataIndex).init || startPc == RestartPc);
+        if(!atEnd)//if no char
+        {
+            debug(std_regex_matcher)
+            {
+                writefln("-- Threaded matching threads at  %s",  s[index..s.lastIndex]);
+            }
+            if(startPc!=RestartPc)
+            {
+                auto startT = createStart(index, startPc);
+                genCounter++;
+                evalFn!true(startT, matches);
+            }
+            for(;;)
+            {
+                debug(std_regex_matcher) writeln("\n-- Started iteration of main cycle");
+                genCounter++;
+                debug(std_regex_matcher)
+                {
+                    foreach(t; clist[])
+                    {
+                        assert(t);
+                    }
+                }
+                for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch())
+                {
+                    evalFn!true(t, matches);
+                }
+                if(nlist.empty)
+                {
+                    debug(std_regex_matcher) writeln("Stopped  matching before consuming full input");
+                    break;//not a partial match for sure
+                }
+                clist = nlist;
+                nlist = (ThreadList!DataIndex).init;
+                if(!next())
+                {
+                    if (!atEnd) return MatchResult.PartialMatch;
+                    break;
+                }
+                debug(std_regex_matcher) writeln("-- Ended iteration of main cycle\n");
+            }
+        }
+        genCounter++; //increment also on each end
+        debug(std_regex_matcher) writefln("-- Matching threads at end");
+        //try out all zero-width posibilities
+        for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch())
+        {
+            evalFn!false(t, matches);
+        }
+        if(!matched)
+            evalFn!false(createStart(index, startPc), matches);
+
+        return (matched?MatchResult.Match:MatchResult.NoMatch);
+    }
+
+    //get a dirty recycled Thread
+    Thread!DataIndex* allocate()
+    {
+        assert(freelist, "not enough preallocated memory");
+        Thread!DataIndex* t = freelist;
+        freelist = freelist.next;
+        return t;
+    }
+
+    //link memory into a free list of Threads
+    void prepareFreeList(size_t size, ref void[] memory)
+    {
+        void[] mem = memory[0 .. threadSize*size];
+        memory = memory[threadSize * size .. $];
+        freelist = cast(Thread!DataIndex*)&mem[0];
+        size_t i;
+        for(i = threadSize; i < threadSize*size; i += threadSize)
+            (cast(Thread!DataIndex*)&mem[i-threadSize]).next = cast(Thread!DataIndex*)&mem[i];
+        (cast(Thread!DataIndex*)&mem[i-threadSize]).next = null;
+    }
+
+    //dispose a thread
+    void recycle(Thread!DataIndex* t)
+    {
+        t.next = freelist;
+        freelist = t;
+    }
+
+    //dispose list of threads
+    void recycle(ref ThreadList!DataIndex list)
+    {
+        if(list.tip)
+        {
+            // just put this head-tail list in front of freelist
+            list.toe.next = freelist;
+            freelist = list.tip;
+            list = list.init;
+        }
+    }
+
+    //creates a copy of master thread with given pc
+    Thread!DataIndex* fork(Thread!DataIndex* master, uint pc, uint counter)
+    {
+        auto t = allocate();
+        t.matches.ptr[0..re.ngroup] = master.matches.ptr[0..re.ngroup];
+        t.pc = pc;
+        t.counter = counter;
+        t.uopCounter = 0;
+        return t;
+    }
+
+    //creates a start thread
+    Thread!DataIndex* createStart(DataIndex index, uint pc = 0)
+    {
+        auto t = allocate();
+        t.matches.ptr[0..re.ngroup] = (Group!DataIndex).init;
+        t.matches[0].begin = index;
+        t.pc = pc;
+        t.counter = 0;
+        t.uopCounter = 0;
+        return t;
+    }
+}
diff --git a/libphobos/src/std/regex/package.d b/libphobos/src/std/regex/package.d
new file mode 100644
index 00000000000..22c31854fe0
--- /dev/null
+++ b/libphobos/src/std/regex/package.d
@@ -0,0 +1,1428 @@
+/++
+  $(LUCKY Regular expressions) are a commonly used method of pattern matching
+  on strings, with $(I regex) being a catchy word for a pattern in this domain
+  specific language. Typical problems usually solved by regular expressions
+  include validation of user input and the ubiquitous find $(AMP) replace
+  in text processing utilities.
+
+  $(SECTION Synopsis)
+  ---
+  import std.regex;
+  import std.stdio;
+  void main()
+  {
+      // Print out all possible dd/mm/yy(yy) dates found in user input.
+      auto r = regex(r"\b[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9](?:[0-9][0-9])?\b");
+      foreach(line; stdin.byLine)
+      {
+        // matchAll() returns a range that can be iterated
+        // to get all subsequent matches.
+        foreach(c; matchAll(line, r))
+            writeln(c.hit);
+      }
+  }
+  ...
+
+  // Create a static regex at compile-time, which contains fast native code.
+  auto ctr = ctRegex!(`^.*/([^/]+)/?$`);
+
+  // It works just like a normal regex:
+  auto c2 = matchFirst("foo/bar", ctr);   // First match found here, if any
+  assert(!c2.empty);   // Be sure to check if there is a match before examining contents!
+  assert(c2[1] == "bar");   // Captures is a range of submatches: 0 = full match.
+
+  ...
+
+  // The result of the $(D matchAll) is directly testable with if/assert/while.
+  // e.g. test if a string consists of letters:
+  assert(matchFirst("Letter", `^\p{L}+$`));
+
+
+  ---
+  $(SECTION Syntax and general information)
+  The general usage guideline is to keep regex complexity on the side of simplicity,
+  as its capabilities reside in purely character-level manipulation.
+  As such it's ill-suited for tasks involving higher level invariants
+  like matching an integer number $(U bounded) in an [a,b] interval.
+  Checks of this sort of are better addressed by additional post-processing.
+
+  The basic syntax shouldn't surprise experienced users of regular expressions.
+  For an introduction to $(D std.regex) see a
+  $(WEB dlang.org/regular-expression.html, short tour) of the module API
+  and its abilities.
+
+  There are other web resources on regular expressions to help newcomers,
+  and a good $(WEB www.regular-expressions.info, reference with tutorial)
+  can easily be found.
+
+  This library uses a remarkably common ECMAScript syntax flavor
+  with the following extensions:
+  $(UL
+    $(LI Named subexpressions, with Python syntax. )
+    $(LI Unicode properties such as Scripts, Blocks and common binary properties e.g Alphabetic, White_Space, Hex_Digit etc.)
+    $(LI Arbitrary length and complexity lookbehind, including lookahead in lookbehind and vise-versa.)
+  )
+
+  $(REG_START Pattern syntax )
+  $(I std.regex operates on codepoint level,
+    'character' in this table denotes a single Unicode codepoint.)
+  $(REG_TABLE
+    $(REG_TITLE Pattern element, Semantics )
+    $(REG_TITLE Atoms, Match single characters )
+    $(REG_ROW any character except [{|*+?()^$, Matches the character itself. )
+    $(REG_ROW ., In single line mode matches any character.
+      Otherwise it matches any character except '\n' and '\r'. )
+    $(REG_ROW [class], Matches a single character
+      that belongs to this character class. )
+    $(REG_ROW [^class], Matches a single character that
+      does $(U not) belong to this character class.)
+    $(REG_ROW \cC, Matches the control character corresponding to letter C)
+    $(REG_ROW \xXX, Matches a character with hexadecimal value of XX. )
+    $(REG_ROW \uXXXX, Matches a character  with hexadecimal value of XXXX. )
+    $(REG_ROW \U00YYYYYY, Matches a character with hexadecimal value of YYYYYY. )
+    $(REG_ROW \f, Matches a formfeed character. )
+    $(REG_ROW \n, Matches a linefeed character. )
+    $(REG_ROW \r, Matches a carriage return character. )
+    $(REG_ROW \t, Matches a tab character. )
+    $(REG_ROW \v, Matches a vertical tab character. )
+    $(REG_ROW \d, Matches any Unicode digit. )
+    $(REG_ROW \D, Matches any character except Unicode digits. )
+    $(REG_ROW \w, Matches any word character (note: this includes numbers).)
+    $(REG_ROW \W, Matches any non-word character.)
+    $(REG_ROW \s, Matches whitespace, same as \p{White_Space}.)
+    $(REG_ROW \S, Matches any character except those recognized as $(I \s ). )
+    $(REG_ROW \\, Matches \ character. )
+    $(REG_ROW \c where c is one of [|*+?(), Matches the character c itself. )
+    $(REG_ROW \p{PropertyName}, Matches a character that belongs
+        to the Unicode PropertyName set.
+      Single letter abbreviations can be used without surrounding {,}. )
+    $(REG_ROW  \P{PropertyName}, Matches a character that does not belong
+        to the Unicode PropertyName set.
+      Single letter abbreviations can be used without surrounding {,}. )
+    $(REG_ROW \p{InBasicLatin}, Matches any character that is part of
+          the BasicLatin Unicode $(U block).)
+    $(REG_ROW \P{InBasicLatin}, Matches any character except ones in
+          the BasicLatin Unicode $(U block).)
+    $(REG_ROW \p{Cyrillic}, Matches any character that is part of
+        Cyrillic $(U script).)
+    $(REG_ROW \P{Cyrillic}, Matches any character except ones in
+        Cyrillic $(U script).)
+    $(REG_TITLE Quantifiers, Specify repetition of other elements)
+    $(REG_ROW *, Matches previous character/subexpression 0 or more times.
+      Greedy version - tries as many times as possible.)
+    $(REG_ROW *?, Matches previous character/subexpression 0 or more times.
+      Lazy version  - stops as early as possible.)
+    $(REG_ROW +, Matches previous character/subexpression 1 or more times.
+      Greedy version - tries as many times as possible.)
+    $(REG_ROW +?, Matches previous character/subexpression 1 or more times.
+      Lazy version  - stops as early as possible.)
+    $(REG_ROW {n}, Matches previous character/subexpression exactly n times. )
+    $(REG_ROW {n$(COMMA)}, Matches previous character/subexpression n times or more.
+      Greedy version - tries as many times as possible. )
+    $(REG_ROW {n$(COMMA)}?, Matches previous character/subexpression n times or more.
+      Lazy version - stops as early as possible.)
+    $(REG_ROW {n$(COMMA)m}, Matches previous character/subexpression n to m times.
+      Greedy version - tries as many times as possible, but no more than m times. )
+    $(REG_ROW {n$(COMMA)m}?, Matches previous character/subexpression n to m times.
+      Lazy version - stops as early as possible, but no less then n times.)
+    $(REG_TITLE Other, Subexpressions $(AMP) alternations )
+    $(REG_ROW (regex),  Matches subexpression regex,
+      saving matched portion of text for later retrieval. )
+    $(REG_ROW (?:regex), Matches subexpression regex,
+      $(U not) saving matched portion of text. Useful to speed up matching. )
+    $(REG_ROW A|B, Matches subexpression A, or failing that, matches B. )
+    $(REG_ROW (?P$(LT)name$(GT)regex), Matches named subexpression
+        regex labeling it with name 'name'.
+        When referring to a matched portion of text,
+        names work like aliases in addition to direct numbers.
+     )
+    $(REG_TITLE Assertions, Match position rather than character )
+    $(REG_ROW ^, Matches at the begining of input or line (in multiline mode).)
+    $(REG_ROW $, Matches at the end of input or line (in multiline mode). )
+    $(REG_ROW \b, Matches at word boundary. )
+    $(REG_ROW \B, Matches when $(U not) at word boundary. )
+    $(REG_ROW (?=regex), Zero-width lookahead assertion.
+        Matches at a point where the subexpression
+        regex could be matched starting from the current position.
+      )
+    $(REG_ROW (?!regex), Zero-width negative lookahead assertion.
+        Matches at a point where the subexpression
+        regex could $(U not) be matched starting from the current position.
+      )
+    $(REG_ROW (?<=regex), Zero-width lookbehind assertion. Matches at a point
+        where the subexpression regex could be matched ending
+        at the current position (matching goes backwards).
+      )
+    $(REG_ROW  (?<!regex), Zero-width negative lookbehind assertion.
+      Matches at a point where the subexpression regex could $(U not)
+      be matched ending at the current position (matching goes backwards).
+     )
+  )
+
+  $(REG_START Character classes )
+  $(REG_TABLE
+    $(REG_TITLE Pattern element, Semantics )
+    $(REG_ROW Any atom, Has the same meaning as outside of a character class.)
+    $(REG_ROW a-z, Includes characters a, b, c, ..., z. )
+    $(REG_ROW [a||b]$(COMMA) [a--b]$(COMMA) [a~~b]$(COMMA) [a$(AMP)$(AMP)b],
+     Where a, b are arbitrary classes, means union, set difference,
+     symmetric set difference, and intersection respectively.
+     $(I Any sequence of character class elements implicitly forms a union.) )
+  )
+
+  $(REG_START Regex flags )
+  $(REG_TABLE
+    $(REG_TITLE Flag, Semantics )
+    $(REG_ROW g, Global regex, repeat over the whole input. )
+    $(REG_ROW i, Case insensitive matching. )
+    $(REG_ROW m, Multi-line mode, match ^, $ on start and end line separators
+       as well as start and end of input.)
+    $(REG_ROW s, Single-line mode, makes . match '\n' and '\r' as well. )
+    $(REG_ROW x, Free-form syntax, ignores whitespace in pattern,
+      useful for formatting complex regular expressions. )
+  )
+
+  $(SECTION Unicode support)
+
+  This library provides full Level 1 support* according to
+    $(WEB unicode.org/reports/tr18/, UTS 18). Specifically:
+  $(UL
+    $(LI 1.1 Hex notation via any of \uxxxx, \U00YYYYYY, \xZZ.)
+    $(LI 1.2 Unicode properties.)
+    $(LI 1.3 Character classes with set operations.)
+    $(LI 1.4 Word boundaries use the full set of "word" characters.)
+    $(LI 1.5 Using simple casefolding to match case
+        insensitively across the full range of codepoints.)
+    $(LI 1.6 Respecting line breaks as any of
+        \u000A | \u000B | \u000C | \u000D | \u0085 | \u2028 | \u2029 | \u000D\u000A.)
+    $(LI 1.7 Operating on codepoint level.)
+  )
+  *With exception of point 1.1.1, as of yet, normalization of input
+    is expected to be enforced by user.
+
+    $(SECTION Replace format string)
+
+    A set of functions in this module that do the substitution rely
+    on a simple format to guide the process. In particular the table below
+    applies to the $(D format) argument of
+    $(LREF replaceFirst) and $(LREF replaceAll).
+
+    The format string can reference parts of match using the following notation.
+    $(REG_TABLE
+        $(REG_TITLE Format specifier, Replaced by )
+        $(REG_ROW $$(AMP), the whole match. )
+        $(REG_ROW $(DOLLAR)$(BACKTICK), part of input $(I preceding) the match. )
+        $(REG_ROW $', part of input $(I following) the match. )
+        $(REG_ROW $$, '$' character. )
+        $(REG_ROW \c $(COMMA) where c is any character, the character c itself. )
+        $(REG_ROW \\, '\' character. )
+        $(REG_ROW $(DOLLAR)1 .. $(DOLLAR)99, submatch number 1 to 99 respectively. )
+    )
+
+  $(SECTION Slicing and zero memory allocations orientation)
+
+  All matches returned by pattern matching functionality in this library
+    are slices of the original input. The notable exception is the $(D replace)
+    family of functions  that generate a new string from the input.
+
+    In cases where producing the replacement is the ultimate goal
+    $(LREF replaceFirstInto) and $(LREF replaceAllInto) could come in handy
+    as functions that  avoid allocations even for replacement.
+
+    Copyright: Copyright Dmitry Olshansky, 2011-
+
+  License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+  Authors: Dmitry Olshansky,
+
+    API and utility constructs are modeled after the original $(D std.regex)
+  by Walter Bright and Andrei Alexandrescu.
+
+  Source: $(PHOBOSSRC std/_regex/_package.d)
+
+Macros:
+    REG_ROW = $(TR $(TD $(I $1 )) $(TD $+) )
+    REG_TITLE = $(TR $(TD $(B $1)) $(TD $(B $2)) )
+    REG_TABLE = <table border="1" cellspacing="0" cellpadding="5" > $0 </table>
+    REG_START = <h3><div align="center"> $0 </div></h3>
+    SECTION = <h3><a id="$1">$0</a></h3>
+    S_LINK = <a href="#$1">$+</a>
+ +/
+module std.regex;
+
+import std.regex.internal.ir;
+import std.regex.internal.thompson; //TODO: get rid of this dependency
+import std.exception, std.traits, std.range;
+
+/++
+    $(D Regex) object holds regular expression pattern in compiled form.
+
+    Instances of this object are constructed via calls to $(D regex).
+    This is an intended form for caching and storage of frequently
+    used regular expressions.
+
+    Example:
+
+    Test if this object doesn't contain any compiled pattern.
+    ---
+    Regex!char r;
+    assert(r.empty);
+    r = regex(""); // Note: "" is a valid regex pattern.
+    assert(!r.empty);
+    ---
+
+    Getting a range of all the named captures in the regex.
+    ----
+    import std.range;
+    import std.algorithm;
+
+    auto re = regex(`(?P<name>\w+) = (?P<var>\d+)`);
+    auto nc = re.namedCaptures;
+    static assert(isRandomAccessRange!(typeof(nc)));
+    assert(!nc.empty);
+    assert(nc.length == 2);
+    assert(nc.equal(["name", "var"]));
+    assert(nc[0] == "name");
+    assert(nc[1..$].equal(["var"]));
+    ----
++/
+public alias Regex(Char) = std.regex.internal.ir.Regex!(Char);
+
+/++
+    A $(D StaticRegex) is $(D Regex) object that contains D code specially
+    generated at compile-time to speed up matching.
+
+    Implicitly convertible to normal $(D Regex),
+    however doing so will result in losing this additional capability.
++/
+public alias StaticRegex(Char) = std.regex.internal.ir.StaticRegex!(Char);
+
+/++
+    Compile regular expression pattern for the later execution.
+    Returns: $(D Regex) object that works on inputs having
+    the same character width as $(D pattern).
+
+    Params:
+    pattern = Regular expression
+    flags = The _attributes (g, i, m and x accepted)
+
+    Throws: $(D RegexException) if there were any errors during compilation.
++/
+@trusted public auto regex(S)(S pattern, const(char)[] flags="")
+    if(isSomeString!(S))
+{
+    import std.functional;
+    enum cacheSize = 8; //TODO: invent nice interface to control regex caching
+    if(__ctfe)
+        return regexImpl(pattern, flags);
+    return memoize!(regexImpl!S, cacheSize)(pattern, flags);
+}
+
+public auto regexImpl(S)(S pattern, const(char)[] flags="")
+    if(isSomeString!(S))
+{
+    import std.regex.internal.parser;
+    auto parser = Parser!(Unqual!(typeof(pattern)))(pattern, flags);
+    auto r = parser.program;
+    return r;
+}
+
+
+template ctRegexImpl(alias pattern, string flags=[])
+{
+    import std.regex.internal.parser, std.regex.internal.backtracking;
+    enum r = regex(pattern, flags);
+    alias Char = BasicElementOf!(typeof(pattern));
+    enum source = ctGenRegExCode(r);
+    alias Matcher = BacktrackingMatcher!(true);
+    @trusted bool func(ref Matcher!Char matcher)
+    {
+        debug(std_regex_ctr) pragma(msg, source);
+        mixin(source);
+    }
+    enum nr = StaticRegex!Char(r, &func);
+}
+
+/++
+    Compile regular expression using CTFE
+    and generate optimized native machine code for matching it.
+
+    Returns: StaticRegex object for faster matching.
+
+    Params:
+    pattern = Regular expression
+    flags = The _attributes (g, i, m and x accepted)
++/
+public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).nr;
+
+enum isRegexFor(RegEx, R) = is(RegEx == Regex!(BasicElementOf!R))
+     || is(RegEx == StaticRegex!(BasicElementOf!R));
+
+
+/++
+    $(D Captures) object contains submatches captured during a call
+    to $(D match) or iteration over $(D RegexMatch) range.
+
+    First element of range is the whole match.
++/
+@trusted public struct Captures(R, DIndex = size_t)
+    if(isSomeString!R)
+{//@trusted because of union inside
+    alias DataIndex = DIndex;
+    alias String = R;
+private:
+    import std.conv;
+    R _input;
+    bool _empty;
+    enum smallString = 3;
+    union
+    {
+        Group!DataIndex[] big_matches;
+        Group!DataIndex[smallString] small_matches;
+    }
+    uint _f, _b;
+    uint _ngroup;
+    NamedGroup[] _names;
+
+    this()(R input, uint ngroups, NamedGroup[] named)
+    {
+        _input = input;
+        _ngroup = ngroups;
+        _names = named;
+        newMatches();
+        _b = _ngroup;
+        _f = 0;
+    }
+
+    this(alias Engine)(ref RegexMatch!(R,Engine) rmatch)
+    {
+        _input = rmatch._input;
+        _ngroup = rmatch._engine.re.ngroup;
+        _names = rmatch._engine.re.dict;
+        newMatches();
+        _b = _ngroup;
+        _f = 0;
+    }
+
+    @property Group!DataIndex[] matches()
+    {
+       return _ngroup > smallString ? big_matches : small_matches[0 .. _ngroup];
+    }
+
+    void newMatches()
+    {
+        if(_ngroup > smallString)
+            big_matches = new Group!DataIndex[_ngroup];
+    }
+
+public:
+    ///Slice of input prior to the match.
+    @property R pre()
+    {
+        return _empty ? _input[] : _input[0 .. matches[0].begin];
+    }
+
+    ///Slice of input immediately after the match.
+    @property R post()
+    {
+        return _empty ? _input[] : _input[matches[0].end .. $];
+    }
+
+    ///Slice of matched portion of input.
+    @property R hit()
+    {
+        assert(!_empty);
+        return _input[matches[0].begin .. matches[0].end];
+    }
+
+    ///Range interface.
+    @property R front()
+    {
+        assert(!empty);
+        return _input[matches[_f].begin .. matches[_f].end];
+    }
+
+    ///ditto
+    @property R back()
+    {
+        assert(!empty);
+        return _input[matches[_b - 1].begin .. matches[_b - 1].end];
+    }
+
+    ///ditto
+    void popFront()
+    {
+        assert(!empty);
+        ++_f;
+    }
+
+    ///ditto
+    void popBack()
+    {
+        assert(!empty);
+        --_b;
+    }
+
+    ///ditto
+    @property bool empty() const { return _empty || _f >= _b; }
+
+    ///ditto
+    R opIndex()(size_t i) /*const*/ //@@@BUG@@@
+    {
+        assert(_f + i < _b,text("requested submatch number ", i," is out of range"));
+        assert(matches[_f + i].begin <= matches[_f + i].end,
+            text("wrong match: ", matches[_f + i].begin, "..", matches[_f + i].end));
+        return _input[matches[_f + i].begin .. matches[_f + i].end];
+    }
+
+    /++
+        Explicit cast to bool.
+        Useful as a shorthand for !(x.empty) in if and assert statements.
+
+        ---
+        import std.regex;
+
+        assert(!matchFirst("nothing", "something"));
+        ---
+    +/
+
+    @safe bool opCast(T:bool)() const nothrow { return !empty; }
+
+    /++
+        Lookup named submatch.
+
+        ---
+        import std.regex;
+        import std.range;
+
+        auto c = matchFirst("a = 42;", regex(`(?P<var>\w+)\s*=\s*(?P<value>\d+);`));
+        assert(c["var"] == "a");
+        assert(c["value"] == "42");
+        popFrontN(c, 2);
+        //named groups are unaffected by range primitives
+        assert(c["var"] =="a");
+        assert(c.front == "42");
+        ----
+    +/
+    R opIndex(String)(String i) /*const*/ //@@@BUG@@@
+        if(isSomeString!String)
+    {
+        size_t index = lookupNamedGroup(_names, i);
+        return _input[matches[index].begin .. matches[index].end];
+    }
+
+    ///Number of matches in this object.
+    @property size_t length() const { return _empty ? 0 : _b - _f;  }
+
+    ///A hook for compatibility with original std.regex.
+    @property ref captures(){ return this; }
+}
+
+///
+unittest
+{
+    auto c = matchFirst("@abc#", regex(`(\w)(\w)(\w)`));
+    assert(c.pre == "@"); // Part of input preceding match
+    assert(c.post == "#"); // Immediately after match
+    assert(c.hit == c[0] && c.hit == "abc"); // The whole match
+    assert(c[2] == "b");
+    assert(c.front == "abc");
+    c.popFront();
+    assert(c.front == "a");
+    assert(c.back == "c");
+    c.popBack();
+    assert(c.back == "b");
+    popFrontN(c, 2);
+    assert(c.empty);
+
+    assert(!matchFirst("nothing", "something"));
+}
+
+/++
+    A regex engine state, as returned by $(D match) family of functions.
+
+    Effectively it's a forward range of Captures!R, produced
+    by lazily searching for matches in a given input.
+
+    $(D alias Engine) specifies an engine type to use during matching,
+    and is automatically deduced in a call to $(D match)/$(D bmatch).
++/
+@trusted public struct RegexMatch(R, alias Engine = ThompsonMatcher)
+    if(isSomeString!R)
+{
+private:
+    import core.stdc.stdlib;
+    alias Char = BasicElementOf!R;
+    alias EngineType = Engine!Char;
+    EngineType _engine;
+    R _input;
+    Captures!(R,EngineType.DataIndex) _captures;
+    void[] _memory;//is ref-counted
+
+    this(RegEx)(R input, RegEx prog)
+    {
+        _input = input;
+        immutable size = EngineType.initialMemory(prog)+size_t.sizeof;
+        _memory = (enforce(malloc(size), "malloc failed")[0..size]);
+        scope(failure) free(_memory.ptr);
+        *cast(size_t*)_memory.ptr = 1;
+        _engine = EngineType(prog, Input!Char(input), _memory[size_t.sizeof..$]);
+        static if(is(RegEx == StaticRegex!(BasicElementOf!R)))
+            _engine.nativeFn = prog.nativeFn;
+        _captures = Captures!(R,EngineType.DataIndex)(this);
+        _captures._empty = !_engine.match(_captures.matches);
+        debug(std_regex_allocation) writefln("RefCount (ctor): %x %d", _memory.ptr, counter);
+    }
+
+    @property ref size_t counter(){ return *cast(size_t*)_memory.ptr; }
+public:
+    this(this)
+    {
+        if(_memory.ptr)
+        {
+            ++counter;
+            debug(std_regex_allocation) writefln("RefCount (postblit): %x %d",
+                _memory.ptr, *cast(size_t*)_memory.ptr);
+        }
+    }
+
+    ~this()
+    {
+        if(_memory.ptr && --*cast(size_t*)_memory.ptr == 0)
+        {
+            debug(std_regex_allocation) writefln("RefCount (dtor): %x %d",
+                _memory.ptr, *cast(size_t*)_memory.ptr);
+            free(cast(void*)_memory.ptr);
+        }
+    }
+
+    ///Shorthands for front.pre, front.post, front.hit.
+    @property R pre()
+    {
+        return _captures.pre;
+    }
+
+    ///ditto
+    @property R post()
+    {
+        return _captures.post;
+    }
+
+    ///ditto
+    @property R hit()
+    {
+        return _captures.hit;
+    }
+
+    /++
+        Functionality for processing subsequent matches of global regexes via range interface:
+        ---
+        import std.regex;
+        auto m = matchAll("Hello, world!", regex(`\w+`));
+        assert(m.front.hit == "Hello");
+        m.popFront();
+        assert(m.front.hit == "world");
+        m.popFront();
+        assert(m.empty);
+        ---
+    +/
+    @property auto front()
+    {
+        return _captures;
+    }
+
+    ///ditto
+    void popFront()
+    {
+
+        if(counter != 1)
+        {//do cow magic first
+            counter--;//we abandon this reference
+            immutable size = EngineType.initialMemory(_engine.re)+size_t.sizeof;
+            _memory = (enforce(malloc(size), "malloc failed")[0..size]);
+            _engine = _engine.dupTo(_memory[size_t.sizeof..size]);
+            counter = 1;//points to new chunk
+        }
+        //previous _captures can have escaped references from Capture object
+        _captures.newMatches();
+        _captures._empty = !_engine.match(_captures.matches);
+    }
+
+    ///ditto
+    auto save(){ return this; }
+
+    ///Test if this match object is empty.
+    @property bool empty(){ return _captures._empty; }
+
+    ///Same as !(x.empty), provided for its convenience  in conditional statements.
+    T opCast(T:bool)(){ return !empty; }
+
+    /// Same as .front, provided for compatibility with original std.regex.
+    @property auto captures(){ return _captures; }
+
+}
+
+private @trusted auto matchOnce(alias Engine, RegEx, R)(R input, RegEx re)
+{
+    import core.stdc.stdlib;
+    alias Char = BasicElementOf!R;
+    alias EngineType = Engine!Char;
+
+    size_t size = EngineType.initialMemory(re);
+    void[] memory = enforce(malloc(size), "malloc failed")[0..size];
+    scope(exit) free(memory.ptr);
+    auto captures = Captures!(R, EngineType.DataIndex)(input, re.ngroup, re.dict);
+    auto engine = EngineType(re, Input!Char(input), memory);
+    static if(is(RegEx == StaticRegex!(BasicElementOf!R)))
+        engine.nativeFn = re.nativeFn;
+    captures._empty = !engine.match(captures.matches);
+    return captures;
+}
+
+private auto matchMany(alias Engine, RegEx, R)(R input, RegEx re)
+{
+    re.flags |= RegexOption.global;
+    return RegexMatch!(R, Engine)(input, re);
+}
+
+unittest
+{
+    //sanity checks for new API
+    auto re = regex("abc");
+    assert(!"abc".matchOnce!(ThompsonMatcher)(re).empty);
+    assert("abc".matchOnce!(ThompsonMatcher)(re)[0] == "abc");
+}
+
+
+private enum isReplaceFunctor(alias fun, R) =
+    __traits(compiles, (Captures!R c) { fun(c); });
+
+// the lowest level - just stuff replacements into the sink
+private @trusted void replaceCapturesInto(alias output, Sink, R, T)
+        (ref Sink sink, R input, T captures)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R)
+{
+    sink.put(captures.pre);
+    // a hack to get around bogus errors, should be simply output(captures, sink)
+    // "is a nested function and cannot be accessed from"
+    static if(isReplaceFunctor!(output, R))
+        sink.put(output(captures)); //"mutator" type of function
+    else
+        output(captures, sink); //"output" type of function
+    sink.put(captures.post);
+}
+
+// ditto for a range of captures
+private void replaceMatchesInto(alias output, Sink, R, T)
+        (ref Sink sink, R input, T matches)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R)
+{
+    size_t offset = 0;
+    foreach(cap; matches)
+    {
+        sink.put(cap.pre[offset .. $]);
+        // same hack, see replaceCapturesInto
+        static if(isReplaceFunctor!(output, R))
+            sink.put(output(cap)); //"mutator" type of function
+        else
+            output(cap, sink); //"output" type of function
+        offset = cap.pre.length + cap.hit.length;
+    }
+    sink.put(input[offset .. $]);
+}
+
+//  a general skeleton of replaceFirst
+private R replaceFirstWith(alias output, R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    auto data = matchFirst(input, re);
+    if(data.empty)
+        return input;
+    auto app = appender!(R)();
+    replaceCapturesInto!output(app, input, data);
+    return app.data;
+}
+
+// ditto for replaceAll
+// the method parameter allows old API to ride on the back of the new one
+private R replaceAllWith(alias output,
+        alias method=matchAll, R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    auto matches = method(input, re); //inout(C)[] fails
+    if(matches.empty)
+        return input;
+    auto app = appender!(R)();
+    replaceMatchesInto!output(app, input, matches);
+    return app.data;
+}
+
+
+/++
+    Start matching $(D input) to regex pattern $(D re),
+    using Thompson NFA matching scheme.
+
+    The use of this function is $(RED discouraged) - use either of
+    $(LREF matchAll) or $(LREF matchFirst).
+
+    Delegating  the kind of operation
+    to "g" flag is soon to be phased out along with the
+    ability to choose the exact matching scheme. The choice of
+    matching scheme to use depends highly on the pattern kind and
+    can done automatically on case by case basis.
+
+    Returns: a $(D RegexMatch) object holding engine state after first match.
++/
+
+public auto match(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R)))
+{
+    import std.regex.internal.thompson;
+    return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, re);
+}
+
+///ditto
+public auto match(R, String)(R input, String re)
+    if(isSomeString!R && isSomeString!String)
+{
+    import std.regex.internal.thompson;
+    return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, regex(re));
+}
+
+public auto match(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R)))
+{
+    import std.regex.internal.backtracking;
+    return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re);
+}
+
+/++
+    Find the first (leftmost) slice of the $(D input) that
+    matches the pattern $(D re). This function picks the most suitable
+    regular expression engine depending on the pattern properties.
+
+    $(D re) parameter can be one of three types:
+    $(UL
+      $(LI Plain string, in which case it's compiled to bytecode before matching. )
+      $(LI Regex!char (wchar/dchar) that contains a pattern in the form of
+        compiled  bytecode. )
+      $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of
+        compiled native machine code. )
+    )
+
+    Returns:
+    $(LREF Captures) containing the extent of a match together with all submatches
+    if there was a match, otherwise an empty $(LREF Captures) object.
++/
+public auto matchFirst(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R)))
+{
+    import std.regex.internal.thompson;
+    return matchOnce!ThompsonMatcher(input, re);
+}
+
+///ditto
+public auto matchFirst(R, String)(R input, String re)
+    if(isSomeString!R && isSomeString!String)
+{
+    import std.regex.internal.thompson;
+    return matchOnce!ThompsonMatcher(input, regex(re));
+}
+
+public auto matchFirst(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R)))
+{
+    import std.regex.internal.backtracking;
+    return matchOnce!(BacktrackingMatcher!true)(input, re);
+}
+
+/++
+    Initiate a search for all non-overlapping matches to the pattern $(D re)
+    in the given $(D input). The result is a lazy range of matches generated
+    as they are encountered in the input going left to right.
+
+    This function picks the most suitable regular expression engine
+    depending on the pattern properties.
+
+    $(D re) parameter can be one of three types:
+    $(UL
+      $(LI Plain string, in which case it's compiled to bytecode before matching. )
+      $(LI Regex!char (wchar/dchar) that contains a pattern in the form of
+        compiled  bytecode. )
+      $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of
+        compiled native machine code. )
+    )
+
+    Returns:
+    $(LREF RegexMatch) object that represents matcher state
+    after the first match was found or an empty one if not present.
++/
+public auto matchAll(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R)))
+{
+    import std.regex.internal.thompson;
+    return matchMany!ThompsonMatcher(input, re);
+}
+
+///ditto
+public auto matchAll(R, String)(R input, String re)
+    if(isSomeString!R && isSomeString!String)
+{
+    import std.regex.internal.thompson;
+    return matchMany!ThompsonMatcher(input, regex(re));
+}
+
+public auto matchAll(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R)))
+{
+    import std.regex.internal.backtracking;
+    return matchMany!(BacktrackingMatcher!true)(input, re);
+}
+
+// another set of tests just to cover the new API
+@system unittest
+{
+    import std.conv : to;
+    import std.algorithm : map, equal;
+
+    foreach(String; AliasSeq!(string, wstring, const(dchar)[]))
+    {
+        auto str1 = "blah-bleh".to!String();
+        auto pat1 = "bl[ae]h".to!String();
+        auto mf = matchFirst(str1, pat1);
+        assert(mf.equal(["blah".to!String()]));
+        auto mAll = matchAll(str1, pat1);
+        assert(mAll.equal!((a,b) => a.equal(b))
+            ([["blah".to!String()], ["bleh".to!String()]]));
+
+        auto str2 = "1/03/12 - 3/03/12".to!String();
+        auto pat2 = regex(r"(\d+)/(\d+)/(\d+)".to!String());
+        auto mf2 = matchFirst(str2, pat2);
+        assert(mf2.equal(["1/03/12", "1", "03", "12"].map!(to!String)()));
+        auto mAll2 = matchAll(str2, pat2);
+        assert(mAll2.front.equal(mf2));
+        mAll2.popFront();
+        assert(mAll2.front.equal(["3/03/12", "3", "03", "12"].map!(to!String)()));
+        mf2.popFrontN(3);
+        assert(mf2.equal(["12".to!String()]));
+
+        auto ctPat = ctRegex!(`(?P<Quot>\d+)/(?P<Denom>\d+)`.to!String());
+        auto str = "2 + 34/56 - 6/1".to!String();
+        auto cmf = matchFirst(str, ctPat);
+        assert(cmf.equal(["34/56", "34", "56"].map!(to!String)()));
+        assert(cmf["Quot"] == "34".to!String());
+        assert(cmf["Denom"] == "56".to!String());
+
+        auto cmAll = matchAll(str, ctPat);
+        assert(cmAll.front.equal(cmf));
+        cmAll.popFront();
+        assert(cmAll.front.equal(["6/1", "6", "1"].map!(to!String)()));
+    }
+}
+
+/++
+    Start matching of $(D input) to regex pattern $(D re),
+    using traditional $(LUCKY backtracking) matching scheme.
+
+    The use of this function is $(RED discouraged) - use either of
+    $(LREF matchAll) or $(LREF matchFirst).
+
+    Delegating  the kind of operation
+    to "g" flag is soon to be phased out along with the
+    ability to choose the exact matching scheme. The choice of
+    matching scheme to use depends highly on the pattern kind and
+    can done automatically on case by case basis.
+
+    Returns: a $(D RegexMatch) object holding engine
+    state after first match.
+
++/
+public auto bmatch(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R)))
+{
+    import std.regex.internal.backtracking;
+    return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, re);
+}
+
+///ditto
+public auto bmatch(R, String)(R input, String re)
+    if(isSomeString!R && isSomeString!String)
+{
+    import std.regex.internal.backtracking;
+    return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, regex(re));
+}
+
+public auto bmatch(R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R)))
+{
+    import std.regex.internal.backtracking;
+    return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re);
+}
+
+// produces replacement string from format using captures for substitution
+package void replaceFmt(R, Capt, OutR)
+    (R format, Capt captures, OutR sink, bool ignoreBadSubs = false)
+    if(isOutputRange!(OutR, ElementEncodingType!R[]) &&
+        isOutputRange!(OutR, ElementEncodingType!(Capt.String)[]))
+{
+    import std.algorithm, std.conv;
+    import std.ascii: isDigit, isAlpha;
+    enum State { Normal, Dollar }
+    auto state = State.Normal;
+    size_t offset;
+L_Replace_Loop:
+    while(!format.empty)
+        final switch(state)
+        {
+        case State.Normal:
+            for(offset = 0; offset < format.length; offset++)//no decoding
+            {
+                if(format[offset] == '$')
+                {
+                    state = State.Dollar;
+                    sink.put(format[0 .. offset]);
+                    format = format[offset+1 .. $];//ditto
+                    continue L_Replace_Loop;
+                }
+            }
+            sink.put(format[0 .. offset]);
+            format = format[offset .. $];
+            break;
+        case State.Dollar:
+            if(isDigit(format[0]))
+            {
+                uint digit = parse!uint(format);
+                enforce(ignoreBadSubs || digit < captures.length, text("invalid submatch number ", digit));
+                if(digit < captures.length)
+                    sink.put(captures[digit]);
+            }
+            else if(format[0] == '{')
+            {
+                auto x = find!(a => !isAlpha(a))(format[1..$]);
+                enforce(!x.empty && x[0] == '}', "no matching '}' in replacement format");
+                auto name = format[1 .. $ - x.length];
+                format = x[1..$];
+                enforce(!name.empty, "invalid name in ${...} replacement format");
+                sink.put(captures[name]);
+            }
+            else if(format[0] == '&')
+            {
+                sink.put(captures[0]);
+                format = format[1 .. $];
+            }
+            else if(format[0] == '`')
+            {
+                sink.put(captures.pre);
+                format = format[1 .. $];
+            }
+            else if(format[0] == '\'')
+            {
+                sink.put(captures.post);
+                format = format[1 .. $];
+            }
+            else if(format[0] == '$')
+            {
+                sink.put(format[0 .. 1]);
+                format = format[1 .. $];
+            }
+            state = State.Normal;
+            break;
+        }
+    enforce(state == State.Normal, "invalid format string in regex replace");
+}
+
+/++
+    Construct a new string from $(D input) by replacing the first match with
+    a string generated from it according to the $(D format) specifier.
+
+    To replace all matches use $(LREF replaceAll).
+
+    Params:
+    input = string to search
+    re = compiled regular expression to use
+    format = format string to generate replacements from,
+    see $(S_LINK Replace format string, the format string).
+
+    Returns:
+    A string of the same type with the first match (if any) replaced.
+    If no match is found returns the input string itself.
++/
+public R replaceFirst(R, C, RegEx)(R input, RegEx re, const(C)[] format)
+    if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R))
+{
+    return replaceFirstWith!((m, sink) => replaceFmt(format, m, sink))(input, re);
+}
+
+///
+unittest
+{
+    assert(replaceFirst("noon", regex("n"), "[$&]") == "[n]oon");
+}
+
+/++
+    This is a general replacement tool that construct a new string by replacing
+    matches of pattern $(D re) in the $(D input). Unlike the other overload
+    there is no format string instead captures are passed to
+    to a user-defined functor $(D fun) that returns a new string
+    to use as replacement.
+
+    This version replaces the first match in $(D input),
+    see $(LREF replaceAll) to replace the all of the matches.
+
+    Returns:
+    A new string of the same type as $(D input) with all matches
+    replaced by return values of $(D fun). If no matches found
+    returns the $(D input) itself.
++/
+public R replaceFirst(alias fun, R, RegEx)(R input, RegEx re)
+  if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    return replaceFirstWith!((m, sink) => sink.put(fun(m)))(input, re);
+}
+
+///
+unittest
+{
+    import std.conv: to;
+    string list = "#21 out of 46";
+    string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1))
+        (list, regex(`[0-9]+`));
+    assert(newList == "#22 out of 46");
+}
+
+/++
+    A variation on $(LREF replaceFirst) that instead of allocating a new string
+    on each call outputs the result piece-wise to the $(D sink). In particular
+    this enables efficient construction of a final output incrementally.
+
+    Like in $(LREF replaceFirst) family of functions there is an overload
+    for the substitution guided by the $(D format) string
+    and the one with the user defined callback.
++/
+public @trusted void replaceFirstInto(Sink, R, C, RegEx)
+        (ref Sink sink, R input, RegEx re, const(C)[] format)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R
+        && is(C : dchar) && isRegexFor!(RegEx, R))
+    {
+    replaceCapturesInto!((m, sink) => replaceFmt(format, m, sink))
+        (sink, input, matchFirst(input, re));
+    }
+
+///ditto
+public @trusted void replaceFirstInto(alias fun, Sink, R, RegEx)
+    (Sink sink, R input, RegEx re)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R))
+{
+    replaceCapturesInto!fun(sink, input, matchFirst(input, re));
+}
+
+///
+unittest
+{
+    import std.array;
+    string m1 = "first message\n";
+    string m2 = "second message\n";
+    auto result = appender!string();
+    replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1");
+    //equivalent of the above with user-defined callback
+    replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`));
+    assert(result.data == "first\nsecond\n");
+}
+
+//examples for replaceFirst
+@system unittest
+{
+    import std.conv;
+    string list = "#21 out of 46";
+    string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1))
+        (list, regex(`[0-9]+`));
+    assert(newList == "#22 out of 46");
+    import std.array;
+    string m1 = "first message\n";
+    string m2 = "second message\n";
+    auto result = appender!string();
+    replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1");
+    //equivalent of the above with user-defined callback
+    replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`));
+    assert(result.data == "first\nsecond\n");
+}
+
+/++
+    Construct a new string from $(D input) by replacing all of the
+    fragments that match a pattern $(D re) with a string generated
+    from the match according to the $(D format) specifier.
+
+    To replace only the first match use $(LREF replaceFirst).
+
+    Params:
+    input = string to search
+    re = compiled regular expression to use
+    format = format string to generate replacements from,
+    see $(S_LINK Replace format string, the format string).
+
+    Returns:
+    A string of the same type as $(D input) with the all
+    of the matches (if any) replaced.
+    If no match is found returns the input string itself.
++/
+public @trusted R replaceAll(R, C, RegEx)(R input, RegEx re, const(C)[] format)
+    if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R))
+{
+    return replaceAllWith!((m, sink) => replaceFmt(format, m, sink))(input, re);
+}
+
+///
+unittest
+{
+    // insert comma as thousands delimiter
+    auto re = regex(r"(?<=\d)(?=(\d\d\d)+\b)","g");
+    assert(replaceAll("12000 + 42100 = 54100", re, ",") == "12,000 + 42,100 = 54,100");
+}
+
+/++
+    This is a general replacement tool that construct a new string by replacing
+    matches of pattern $(D re) in the $(D input). Unlike the other overload
+    there is no format string instead captures are passed to
+    to a user-defined functor $(D fun) that returns a new string
+    to use as replacement.
+
+    This version replaces all of the matches found in $(D input),
+    see $(LREF replaceFirst) to replace the first match only.
+
+    Returns:
+    A new string of the same type as $(D input) with all matches
+    replaced by return values of $(D fun). If no matches found
+    returns the $(D input) itself.
+
+    Params:
+    input = string to search
+    re = compiled regular expression
+    fun = delegate to use
++/
+public @trusted R replaceAll(alias fun, R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    return replaceAllWith!((m, sink) => sink.put(fun(m)))(input, re);
+}
+
+///
+unittest
+{
+    string baz(Captures!(string) m)
+    {
+        return std.string.toUpper(m.hit);
+    }
+    // Capitalize the letters 'a' and 'r':
+    auto s = replaceAll!(baz)("Strap a rocket engine on a chicken.",
+            regex("[ar]"));
+    assert(s == "StRAp A Rocket engine on A chicken.");
+}
+
+/++
+    A variation on $(LREF replaceAll) that instead of allocating a new string
+    on each call outputs the result piece-wise to the $(D sink). In particular
+    this enables efficient construction of a final output incrementally.
+
+    As with $(LREF replaceAll) there are 2 overloads - one with a format string,
+    the other one with a user defined functor.
++/
+public @trusted void replaceAllInto(Sink, R, C, RegEx)
+        (Sink sink, R input, RegEx re, const(C)[] format)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R
+        && is(C : dchar) && isRegexFor!(RegEx, R))
+    {
+    replaceMatchesInto!((m, sink) => replaceFmt(format, m, sink))
+        (sink, input, matchAll(input, re));
+    }
+
+///ditto
+public @trusted void replaceAllInto(alias fun, Sink, R, RegEx)
+        (Sink sink, R input, RegEx re)
+    if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R))
+{
+    replaceMatchesInto!fun(sink, input, matchAll(input, re));
+}
+
+///
+@system unittest
+{
+    // insert comma as thousands delimiter in fifty randomly produced big numbers
+    import std.array, std.random, std.conv, std.range;
+    static re = regex(`(?<=\d)(?=(\d\d\d)+\b)`, "g");
+    auto sink = appender!(char [])();
+    enum ulong min = 10UL ^^ 10, max = 10UL ^^ 19;
+    foreach (i; 0 .. 50)
+    {
+        sink.clear();
+        replaceAllInto(sink, text(uniform(min, max)), re, ",");
+        foreach (pos; iota(sink.data.length - 4, 0, -4))
+            assert(sink.data[pos] == ',');
+    }
+}
+
+// exercise all of the replace APIs
+@system unittest
+{
+    import std.conv;
+    // try and check first/all simple substitution
+    foreach(S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+    {
+        S s1 = "curt trial".to!S();
+        S s2 = "round dome".to!S();
+        S t1F = "court trial".to!S();
+        S t2F = "hound dome".to!S();
+        S t1A = "court trial".to!S();
+        S t2A = "hound home".to!S();
+        auto re1 = regex("curt".to!S());
+        auto re2 = regex("[dr]o".to!S());
+
+        assert(replaceFirst(s1, re1, "court") == t1F);
+        assert(replaceFirst(s2, re2, "ho") == t2F);
+        assert(replaceAll(s1, re1, "court") == t1A);
+        assert(replaceAll(s2, re2, "ho") == t2A);
+
+        auto rep1 = replaceFirst!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1);
+        assert(rep1 == t1F);
+        assert(replaceFirst!(cap => "ho".to!S())(s2, re2) == t2F);
+        auto rep1A = replaceAll!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1);
+        assert(rep1A == t1A);
+        assert(replaceAll!(cap => "ho".to!S())(s2, re2) == t2A);
+
+        auto sink = appender!S();
+        replaceFirstInto(sink, s1, re1, "court");
+        assert(sink.data == t1F);
+        replaceFirstInto(sink, s2, re2, "ho");
+        assert(sink.data == t1F~t2F);
+        replaceAllInto(sink, s1, re1, "court");
+        assert(sink.data == t1F~t2F~t1A);
+        replaceAllInto(sink, s2, re2, "ho");
+        assert(sink.data == t1F~t2F~t1A~t2A);
+    }
+}
+
+/++
+    Old API for replacement, operation depends on flags of pattern $(D re).
+    With "g" flag it performs the equivalent of $(LREF replaceAll) otherwise it
+    works the same as $(LREF replaceFirst).
+
+    The use of this function is $(RED discouraged), please use $(LREF replaceAll)
+    or $(LREF replaceFirst) explicitly.
++/
+public R replace(alias scheme = match, R, C, RegEx)(R input, RegEx re, const(C)[] format)
+    if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    return replaceAllWith!((m, sink) => replaceFmt(format, m, sink), match)(input, re);
+}
+
+///ditto
+public R replace(alias fun, R, RegEx)(R input, RegEx re)
+    if(isSomeString!R && isRegexFor!(RegEx, R))
+{
+    return replaceAllWith!(fun, match)(input, re);
+}
+
+/++
+Range that splits a string using a regular expression as a
+separator.
++/
+public struct Splitter(Range, alias RegEx = Regex)
+    if(isSomeString!Range && isRegexFor!(RegEx, Range))
+{
+private:
+    Range _input;
+    size_t _offset;
+    alias Rx = typeof(match(Range.init,RegEx.init));
+    Rx _match;
+
+    @trusted this(Range input, RegEx separator)
+    {//@@@BUG@@@ generated opAssign of RegexMatch is not @trusted
+        _input = input;
+        separator.flags |= RegexOption.global;
+        if (_input.empty)
+        {
+            //there is nothing to match at all, make _offset > 0
+            _offset = 1;
+        }
+        else
+        {
+            _match = Rx(_input, separator);
+        }
+    }
+
+public:
+    auto ref opSlice()
+    {
+        return this.save;
+    }
+
+    ///Forward range primitives.
+    @property Range front()
+    {
+        import std.algorithm : min;
+
+        assert(!empty && _offset <= _match.pre.length
+                && _match.pre.length <= _input.length);
+        return _input[_offset .. min($, _match.pre.length)];
+    }
+
+    ///ditto
+    @property bool empty()
+    {
+        return _offset > _input.length;
+    }
+
+    ///ditto
+    void popFront()
+    {
+        assert(!empty);
+        if (_match.empty)
+        {
+            //No more separators, work is done here
+            _offset = _input.length + 1;
+        }
+        else
+        {
+            //skip past the separator
+            _offset = _match.pre.length + _match.hit.length;
+            _match.popFront();
+        }
+    }
+
+    ///ditto
+    @property auto save()
+    {
+        return this;
+    }
+}
+
+/**
+    A helper function, creates a $(D Splitter) on range $(D r) separated by regex $(D pat).
+    Captured subexpressions have no effect on the resulting range.
+*/
+public Splitter!(Range, RegEx) splitter(Range, RegEx)(Range r, RegEx pat)
+    if(is(BasicElementOf!Range : dchar) && isRegexFor!(RegEx, Range))
+{
+    return Splitter!(Range, RegEx)(r, pat);
+}
+
+///
+unittest
+{
+    import std.algorithm: equal;
+    auto s1 = ", abc, de,  fg, hi, ";
+    assert(equal(splitter(s1, regex(", *")),
+        ["", "abc", "de", "fg", "hi", ""]));
+}
+
+///An eager version of $(D splitter) that creates an array with splitted slices of $(D input).
+public @trusted String[] split(String, RegEx)(String input, RegEx rx)
+    if(isSomeString!String  && isRegexFor!(RegEx, String))
+{
+    auto a = appender!(String[])();
+    foreach(e; splitter(input, rx))
+        a.put(e);
+    return a.data;
+}
+
+///Exception object thrown in case of errors during regex compilation.
+public alias RegexException = std.regex.internal.ir.RegexException;
diff --git a/libphobos/src/std/signals.d b/libphobos/src/std/signals.d
new file mode 100644
index 00000000000..e850763930d
--- /dev/null
+++ b/libphobos/src/std/signals.d
@@ -0,0 +1,545 @@
+// Written in the D programming language.
+
+/**
+ * Signals and Slots are an implementation of the Observer Pattern.
+ * Essentially, when a Signal is emitted, a list of connected Observers
+ * (called slots) are called.
+ *
+ * There have been several D implementations of Signals and Slots.
+ * This version makes use of several new features in D, which make
+ * using it simpler and less error prone. In particular, it is no
+ * longer necessary to instrument the slots.
+ *
+ * References:
+ *      $(LUCKY A Deeper Look at Signals and Slots)$(BR)
+ *      $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
+ *      $(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR)
+ *      $(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR)
+ *      $(LINK2 http://qt-project.org/doc/qt-5/signalsandslots.html, Qt)$(BR)
+ *
+ *      There has been a great deal of discussion in the D newsgroups
+ *      over this, and several implementations:
+ *
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals &amp; Slots for Walter)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties$(COMMA) signals and slots$(COMMA) etc))$(BR)
+ *      $(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR)
+ *
+ * Bugs:
+ *      Slots can only be delegates formed from class objects or
+ *      interfaces to class objects. If a delegate to something else
+ *      is passed to connect(), such as a struct member function,
+ *      a nested function or a COM interface, undefined behavior
+ *      will result.
+ *
+ *      Not safe for multiple threads operating on the same signals
+ *      or slots.
+ * Macros:
+ *      WIKI = Phobos/StdSignals
+ *      SIGNALS=signals
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_signals.d)
+ */
+/*          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.signals;
+
+import std.stdio;
+import core.stdc.stdlib : calloc, realloc, free;
+import core.exception : onOutOfMemoryError;
+
+// Special function for internal use only.
+// Use of this is where the slot had better be a delegate
+// to an object or an interface that is part of an object.
+extern (C) Object _d_toObject(void* p);
+
+// Used in place of Object.notifyRegister and Object.notifyUnRegister.
+alias DisposeEvt = void delegate(Object);
+extern (C) void  rt_attachDisposeEvent( Object obj, DisposeEvt evt );
+extern (C) void  rt_detachDisposeEvent( Object obj, DisposeEvt evt );
+//debug=signal;
+
+/************************
+ * Mixin to create a signal within a class object.
+ *
+ * Different signals can be added to a class by naming the mixins.
+ */
+
+mixin template Signal(T1...)
+{
+    static import core.stdc.stdlib;
+    static import core.exception;
+    /***
+     * A slot is implemented as a delegate.
+     * The slot_t is the type of the delegate.
+     * The delegate must be to an instance of a class or an interface
+     * to a class instance.
+     * Delegates to struct instances or nested functions must not be
+     * used as slots.
+     */
+    alias slot_t = void delegate(T1);
+
+    /***
+     * Call each of the connected slots, passing the argument(s) i to them.
+     */
+    final void emit( T1 i )
+    {
+        foreach (slot; slots[0 .. slots_idx])
+        {   if (slot)
+                slot(i);
+        }
+    }
+
+    /***
+     * Add a slot to the list of slots to be called when emit() is called.
+     */
+    final void connect(slot_t slot)
+    {
+        /* Do this:
+         *    slots ~= slot;
+         * but use malloc() and friends instead
+         */
+        auto len = slots.length;
+        if (slots_idx == len)
+        {
+            if (slots.length == 0)
+            {
+                len = 4;
+                auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len);
+                if (!p)
+                    core.exception.onOutOfMemoryError();
+                slots = (cast(slot_t*)p)[0 .. len];
+            }
+            else
+            {
+                len = len * 2 + 4;
+                auto p = core.stdc.stdlib.realloc(slots.ptr, slot_t.sizeof * len);
+                if (!p)
+                    core.exception.onOutOfMemoryError();
+                slots = (cast(slot_t*)p)[0 .. len];
+                slots[slots_idx + 1 .. $] = null;
+            }
+        }
+        slots[slots_idx++] = slot;
+
+     L1:
+        Object o = _d_toObject(slot.ptr);
+        rt_attachDisposeEvent(o, &unhook);
+    }
+
+    /***
+     * Remove a slot from the list of slots to be called when emit() is called.
+     */
+    final void disconnect(slot_t slot)
+    {
+        debug (signal) writefln("Signal.disconnect(slot)");
+        for (size_t i = 0; i < slots_idx; )
+        {
+            if (slots[i] == slot)
+            {   slots_idx--;
+                slots[i] = slots[slots_idx];
+                slots[slots_idx] = null;        // not strictly necessary
+
+                Object o = _d_toObject(slot.ptr);
+                rt_detachDisposeEvent(o, &unhook);
+            }
+            else
+                i++;
+        }
+    }
+
+    /* **
+     * Special function called when o is destroyed.
+     * It causes any slots dependent on o to be removed from the list
+     * of slots to be called by emit().
+     */
+    final void unhook(Object o)
+    {
+        debug (signal) writefln("Signal.unhook(o = %s)", cast(void*)o);
+        for (size_t i = 0; i < slots_idx; )
+        {
+            if (_d_toObject(slots[i].ptr) is o)
+            {   slots_idx--;
+                slots[i] = slots[slots_idx];
+                slots[slots_idx] = null;        // not strictly necessary
+            }
+            else
+                i++;
+        }
+    }
+
+    /* **
+     * There can be multiple destructors inserted by mixins.
+     */
+    ~this()
+    {
+        /* **
+         * When this object is destroyed, need to let every slot
+         * know that this object is destroyed so they are not left
+         * with dangling references to it.
+         */
+        if (slots.length)
+        {
+            foreach (slot; slots[0 .. slots_idx])
+            {
+                if (slot)
+                {   Object o = _d_toObject(slot.ptr);
+                    rt_detachDisposeEvent(o, &unhook);
+                }
+            }
+            core.stdc.stdlib.free(slots.ptr);
+            slots = null;
+        }
+    }
+
+  private:
+    slot_t[] slots;             // the slots to call from emit()
+    size_t slots_idx;           // used length of slots[]
+}
+
+///
+unittest
+{
+    import std.signals;
+
+    int observedMessageCounter = 0;
+
+    class Observer
+    {   // our slot
+        void watch(string msg, int value)
+        {
+            switch(observedMessageCounter++)
+            {
+                case 0:
+                    assert(msg == "setting new value");
+                    assert(value == 4);
+                    break;
+                case 1:
+                    assert(msg == "setting new value");
+                    assert(value == 6);
+                    break;
+                default:
+                    assert(0, "Unknown observation");
+            }
+        }
+    }
+
+    class Foo
+    {
+        int value() { return _value; }
+
+        int value(int v)
+        {
+            if (v != _value)
+            {   _value = v;
+                // call all the connected slots with the two parameters
+                emit("setting new value", v);
+            }
+            return v;
+        }
+
+        // Mix in all the code we need to make Foo into a signal
+        mixin Signal!(string, int);
+
+      private :
+        int _value;
+    }
+
+    Foo a = new Foo;
+    Observer o = new Observer;
+
+    a.value = 3;                // should not call o.watch()
+    a.connect(&o.watch);        // o.watch is the slot
+    a.value = 4;                // should call o.watch()
+    a.disconnect(&o.watch);     // o.watch is no longer a slot
+    a.value = 5;                // so should not call o.watch()
+    a.connect(&o.watch);        // connect again
+    a.value = 6;                // should call o.watch()
+    destroy(o);                 // destroying o should automatically disconnect it
+    a.value = 7;                // should not call o.watch()
+
+    assert(observedMessageCounter == 2);
+}
+
+// A function whose sole purpose is to get this module linked in
+// so the unittest will run.
+void linkin() { }
+
+unittest
+{
+    class Observer
+    {
+        void watch(string msg, int i)
+        {
+            //writefln("Observed msg '%s' and value %s", msg, i);
+            captured_value = i;
+            captured_msg   = msg;
+        }
+
+        int    captured_value;
+        string captured_msg;
+    }
+
+    class Foo
+    {
+        @property int value() { return _value; }
+
+        @property int value(int v)
+        {
+            if (v != _value)
+            {   _value = v;
+                emit("setting new value", v);
+            }
+            return v;
+        }
+
+        mixin Signal!(string, int);
+
+      private:
+        int _value;
+    }
+
+    Foo a = new Foo;
+    Observer o = new Observer;
+
+    // check initial condition
+    assert(o.captured_value == 0);
+    assert(o.captured_msg == "");
+
+    // set a value while no observation is in place
+    a.value = 3;
+    assert(o.captured_value == 0);
+    assert(o.captured_msg == "");
+
+    // connect the watcher and trigger it
+    a.connect(&o.watch);
+    a.value = 4;
+    assert(o.captured_value == 4);
+    assert(o.captured_msg == "setting new value");
+
+    // disconnect the watcher and make sure it doesn't trigger
+    a.disconnect(&o.watch);
+    a.value = 5;
+    assert(o.captured_value == 4);
+    assert(o.captured_msg == "setting new value");
+
+    // reconnect the watcher and make sure it triggers
+    a.connect(&o.watch);
+    a.value = 6;
+    assert(o.captured_value == 6);
+    assert(o.captured_msg == "setting new value");
+
+    // destroy the underlying object and make sure it doesn't cause
+    // a crash or other problems
+    destroy(o);
+    a.value = 7;
+}
+
+unittest {
+    class Observer
+    {
+        int    i;
+        long   l;
+        string str;
+
+        void watchInt(string str, int i)
+        {
+            this.str = str;
+            this.i = i;
+        }
+
+        void watchLong(string str, long l)
+        {
+            this.str = str;
+            this.l = l;
+        }
+    }
+
+    class Bar
+    {
+        @property void value1(int v)  { s1.emit("str1", v); }
+        @property void value2(int v)  { s2.emit("str2", v); }
+        @property void value3(long v) { s3.emit("str3", v); }
+
+        mixin Signal!(string, int)  s1;
+        mixin Signal!(string, int)  s2;
+        mixin Signal!(string, long) s3;
+    }
+
+    void test(T)(T a) {
+        auto o1 = new Observer;
+        auto o2 = new Observer;
+        auto o3 = new Observer;
+
+        // connect the watcher and trigger it
+        a.s1.connect(&o1.watchInt);
+        a.s2.connect(&o2.watchInt);
+        a.s3.connect(&o3.watchLong);
+
+        assert(!o1.i && !o1.l && o1.str == null);
+        assert(!o2.i && !o2.l && o2.str == null);
+        assert(!o3.i && !o3.l && o3.str == null);
+
+        a.value1 = 11;
+        assert(o1.i == 11 && !o1.l && o1.str == "str1");
+        assert(!o2.i && !o2.l && o2.str == null);
+        assert(!o3.i && !o3.l && o3.str == null);
+        o1.i = -11; o1.str = "x1";
+
+        a.value2 = 12;
+        assert(o1.i == -11 && !o1.l && o1.str == "x1");
+        assert(o2.i == 12 && !o2.l && o2.str == "str2");
+        assert(!o3.i && !o3.l && o3.str == null);
+        o2.i = -12; o2.str = "x2";
+
+        a.value3 = 13;
+        assert(o1.i == -11 && !o1.l && o1.str == "x1");
+        assert(o2.i == -12 && !o1.l && o2.str == "x2");
+        assert(!o3.i && o3.l == 13 && o3.str == "str3");
+        o3.l = -13; o3.str = "x3";
+
+        // disconnect the watchers and make sure it doesn't trigger
+        a.s1.disconnect(&o1.watchInt);
+        a.s2.disconnect(&o2.watchInt);
+        a.s3.disconnect(&o3.watchLong);
+
+        a.value1 = 21;
+        a.value2 = 22;
+        a.value3 = 23;
+        assert(o1.i == -11 && !o1.l && o1.str == "x1");
+        assert(o2.i == -12 && !o1.l && o2.str == "x2");
+        assert(!o3.i && o3.l == -13 && o3.str == "x3");
+
+        // reconnect the watcher and make sure it triggers
+        a.s1.connect(&o1.watchInt);
+        a.s2.connect(&o2.watchInt);
+        a.s3.connect(&o3.watchLong);
+
+        a.value1 = 31;
+        a.value2 = 32;
+        a.value3 = 33;
+        assert(o1.i == 31 && !o1.l && o1.str == "str1");
+        assert(o2.i == 32 && !o1.l && o2.str == "str2");
+        assert(!o3.i && o3.l == 33 && o3.str == "str3");
+
+        // destroy observers
+        destroy(o1);
+        destroy(o2);
+        destroy(o3);
+        a.value1 = 41;
+        a.value2 = 42;
+        a.value3 = 43;
+    }
+
+    test(new Bar);
+
+    class BarDerived: Bar
+    {
+        @property void value4(int v)  { s4.emit("str4", v); }
+        @property void value5(int v)  { s5.emit("str5", v); }
+        @property void value6(long v) { s6.emit("str6", v); }
+
+        mixin Signal!(string, int)  s4;
+        mixin Signal!(string, int)  s5;
+        mixin Signal!(string, long) s6;
+    }
+
+    auto a = new BarDerived;
+
+    test!Bar(a);
+    test!BarDerived(a);
+
+    auto o4 = new Observer;
+    auto o5 = new Observer;
+    auto o6 = new Observer;
+
+    // connect the watcher and trigger it
+    a.s4.connect(&o4.watchInt);
+    a.s5.connect(&o5.watchInt);
+    a.s6.connect(&o6.watchLong);
+
+    assert(!o4.i && !o4.l && o4.str == null);
+    assert(!o5.i && !o5.l && o5.str == null);
+    assert(!o6.i && !o6.l && o6.str == null);
+
+    a.value4 = 44;
+    assert(o4.i == 44 && !o4.l && o4.str == "str4");
+    assert(!o5.i && !o5.l && o5.str == null);
+    assert(!o6.i && !o6.l && o6.str == null);
+    o4.i = -44; o4.str = "x4";
+
+    a.value5 = 45;
+    assert(o4.i == -44 && !o4.l && o4.str == "x4");
+    assert(o5.i == 45 && !o5.l && o5.str == "str5");
+    assert(!o6.i && !o6.l && o6.str == null);
+    o5.i = -45; o5.str = "x5";
+
+    a.value6 = 46;
+    assert(o4.i == -44 && !o4.l && o4.str == "x4");
+    assert(o5.i == -45 && !o4.l && o5.str == "x5");
+    assert(!o6.i && o6.l == 46 && o6.str == "str6");
+    o6.l = -46; o6.str = "x6";
+
+    // disconnect the watchers and make sure it doesn't trigger
+    a.s4.disconnect(&o4.watchInt);
+    a.s5.disconnect(&o5.watchInt);
+    a.s6.disconnect(&o6.watchLong);
+
+    a.value4 = 54;
+    a.value5 = 55;
+    a.value6 = 56;
+    assert(o4.i == -44 && !o4.l && o4.str == "x4");
+    assert(o5.i == -45 && !o4.l && o5.str == "x5");
+    assert(!o6.i && o6.l == -46 && o6.str == "x6");
+
+    // reconnect the watcher and make sure it triggers
+    a.s4.connect(&o4.watchInt);
+    a.s5.connect(&o5.watchInt);
+    a.s6.connect(&o6.watchLong);
+
+    a.value4 = 64;
+    a.value5 = 65;
+    a.value6 = 66;
+    assert(o4.i == 64 && !o4.l && o4.str == "str4");
+    assert(o5.i == 65 && !o4.l && o5.str == "str5");
+    assert(!o6.i && o6.l == 66 && o6.str == "str6");
+
+    // destroy observers
+    destroy(o4);
+    destroy(o5);
+    destroy(o6);
+    a.value4 = 44;
+    a.value5 = 45;
+    a.value6 = 46;
+}
+
+version(none) // Disabled because of dmd @@@BUG5028@@@
+unittest
+{
+    class A
+    {
+        mixin Signal!(string, int) s1;
+    }
+
+    class B : A
+    {
+        mixin Signal!(string, int) s2;
+    }
+}
diff --git a/libphobos/src/std/socket.d b/libphobos/src/std/socket.d
new file mode 100644
index 00000000000..34873d1c556
--- /dev/null
+++ b/libphobos/src/std/socket.d
@@ -0,0 +1,3551 @@
+// Written in the D programming language
+
+/*
+        Copyright (C) 2004-2011 Christopher E. Miller
+
+        Boost Software License - Version 1.0 - August 17th, 2003
+
+        Permission is hereby granted, free of charge, to any person or organization
+        obtaining a copy of the software and accompanying documentation covered by
+        this license (the "Software") to use, reproduce, display, distribute,
+        execute, and transmit the Software, and to prepare derivative works of the
+        Software, and to permit third-parties to whom the Software is furnished to
+        do so, all subject to the following:
+
+        The copyright notices in the Software and this entire statement, including
+        the above license grant, this restriction and the following disclaimer,
+        must be included in all copies of the Software, in whole or in part, and
+        all derivative works of the Software, unless such copies or derivative
+        works are solely in the form of machine-executable object code generated by
+        a source language processor.
+
+        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+        FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+        SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+        FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+        ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+        DEALINGS IN THE SOFTWARE.
+
+        socket.d 1.4
+        Jan 2011
+
+        Thanks to Benjamin Herr for his assistance.
+ */
+
+/**
+ * Socket primitives.
+ * Example: See $(SAMPLESRC listener.d) and $(SAMPLESRC htmlget.d)
+ * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors: Christopher E. Miller, $(WEB klickverbot.at, David Nadlinger),
+ *      $(WEB thecybershadow.net, Vladimir Panteleev)
+ * Source:  $(PHOBOSSRC std/_socket.d)
+ * Macros:
+ *      WIKI=Phobos/StdSocket
+ */
+
+module std.socket;
+
+import core.stdc.stdint, core.stdc.string, std.string, core.stdc.stdlib, std.conv;
+
+import core.stdc.config;
+import core.time : dur, Duration;
+import std.exception;
+
+import std.internal.cstring;
+
+
+@safe:
+
+version(Windows)
+{
+    version(GNU) {}
+    else
+    {
+        pragma (lib, "ws2_32.lib");
+        pragma (lib, "wsock32.lib");
+    }
+
+    public import core.sys.windows.winsock2;
+    private import core.sys.windows.windows, std.windows.syserror;
+    private alias _ctimeval = core.sys.windows.winsock2.timeval;
+    private alias _clinger = core.sys.windows.winsock2.linger;
+
+    enum socket_t : SOCKET { INVALID_SOCKET }
+    private const int _SOCKET_ERROR = SOCKET_ERROR;
+
+
+    private int _lasterr() nothrow @nogc
+    {
+        return WSAGetLastError();
+    }
+}
+else version(Posix)
+{
+    version(linux)
+    {
+        enum : int
+        {
+            TCP_KEEPIDLE  = 4,
+            TCP_KEEPINTVL = 5
+        }
+    }
+
+    import core.sys.posix.netdb;
+    import core.sys.posix.sys.un : sockaddr_un;
+    private import core.sys.posix.fcntl;
+    private import core.sys.posix.unistd;
+    private import core.sys.posix.arpa.inet;
+    private import core.sys.posix.netinet.tcp;
+    private import core.sys.posix.netinet.in_;
+    private import core.sys.posix.sys.time;
+    private import core.sys.posix.sys.select;
+    private import core.sys.posix.sys.socket;
+    private alias _ctimeval = core.sys.posix.sys.time.timeval;
+    private alias _clinger = core.sys.posix.sys.socket.linger;
+
+    private import core.stdc.errno;
+
+    enum socket_t : int32_t { init = -1 }
+    private const int _SOCKET_ERROR = -1;
+
+    private enum : int
+    {
+        SD_RECEIVE = SHUT_RD,
+        SD_SEND    = SHUT_WR,
+        SD_BOTH    = SHUT_RDWR
+    }
+
+    private int _lasterr() nothrow @nogc
+    {
+        return errno;
+    }
+}
+else
+{
+    static assert(0);     // No socket support yet.
+}
+
+version(unittest)
+{
+    static assert(is(uint32_t == uint));
+    static assert(is(uint16_t == ushort));
+
+    private import std.stdio : writefln;
+
+    // Print a message on exception instead of failing the unittest.
+    private void softUnittest(void delegate() @safe test, int line = __LINE__) @trusted
+    {
+        try
+            test();
+        catch (Throwable e)
+        {
+            writefln(" --- std.socket(%d) test fails depending on environment ---", line);
+            writefln(" (%s)", e);
+        }
+    }
+}
+
+/// Base exception thrown by $(D std.socket).
+class SocketException: Exception
+{
+    mixin basicExceptionCtors;
+}
+
+
+// Needs to be public so that SocketOSException can be thrown outside of
+// std.socket (since it uses it as a default argument), but it probably doesn't
+// need to actually show up in the docs, since there's not really any public
+// need for it outside of being a default argument.
+string formatSocketError(int err) @trusted
+{
+    version(Posix)
+    {
+        char[80] buf;
+        const(char)* cs;
+        version (CRuntime_Glibc)
+        {
+            cs = strerror_r(err, buf.ptr, buf.length);
+        }
+        else version (OSX)
+        {
+            auto errs = strerror_r(err, buf.ptr, buf.length);
+            if (errs == 0)
+                cs = buf.ptr;
+            else
+                return "Socket error " ~ to!string(err);
+        }
+        else version (FreeBSD)
+        {
+            auto errs = strerror_r(err, buf.ptr, buf.length);
+            if (errs == 0)
+                cs = buf.ptr;
+            else
+                return "Socket error " ~ to!string(err);
+        }
+        else version (NetBSD)
+        {
+            auto errs = strerror_r(err, buf.ptr, buf.length);
+            if (errs == 0)
+                cs = buf.ptr;
+            else
+                return "Socket error " ~ to!string(err);
+        }
+        else version (Solaris)
+        {
+            auto errs = strerror_r(err, buf.ptr, buf.length);
+            if (errs == 0)
+                cs = buf.ptr;
+            else
+                return "Socket error " ~ to!string(err);
+        }
+        else version (CRuntime_Bionic)
+        {
+            auto errs = strerror_r(err, buf.ptr, buf.length);
+            if (errs == 0)
+                cs = buf.ptr;
+            else
+                return "Socket error " ~ to!string(err);
+        }
+        else
+            static assert(0);
+
+        auto len = strlen(cs);
+
+        if(cs[len - 1] == '\n')
+            len--;
+        if(cs[len - 1] == '\r')
+            len--;
+        return cs[0 .. len].idup;
+    }
+    else
+    version(Windows)
+    {
+        return sysErrorString(err);
+    }
+    else
+        return "Socket error " ~ to!string(err);
+}
+
+/// Retrieve the error message for the most recently encountered network error.
+@property string lastSocketError()
+{
+    return formatSocketError(_lasterr());
+}
+
+/// Socket exceptions representing network errors reported by the operating
+/// system.
+class SocketOSException: SocketException
+{
+    int errorCode;     /// Platform-specific error code.
+
+    ///
+    this(string msg,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         Throwable next = null,
+         int err = _lasterr(),
+         string function(int) @trusted errorFormatter = &formatSocketError)
+    {
+        errorCode = err;
+
+        if (msg.length)
+            super(msg ~ ": " ~ errorFormatter(err), file, line, next);
+        else
+            super(errorFormatter(err), file, line, next);
+    }
+
+    ///
+    this(string msg,
+         Throwable next,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         int err = _lasterr(),
+         string function(int) @trusted errorFormatter = &formatSocketError)
+    {
+        this(msg, file, line, next, err, errorFormatter);
+    }
+
+    ///
+    this(string msg,
+         int err,
+         string function(int) @trusted errorFormatter = &formatSocketError,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         Throwable next = null)
+    {
+        this(msg, file, line, next, err, errorFormatter);
+    }
+}
+
+/// Socket exceptions representing invalid parameters specified by user code.
+class SocketParameterException: SocketException
+{
+    mixin basicExceptionCtors;
+}
+
+/// Socket exceptions representing attempts to use network capabilities not
+/// available on the current system.
+class SocketFeatureException: SocketException
+{
+    mixin basicExceptionCtors;
+}
+
+
+/// Return $(D true) if the last socket operation failed because the socket
+/// was in non-blocking mode and the operation would have blocked.
+bool wouldHaveBlocked() nothrow @nogc
+{
+    version(Windows)
+        return _lasterr() == WSAEWOULDBLOCK;
+    else version(Posix)
+        return _lasterr() == EAGAIN;
+    else
+        static assert(0);
+}
+
+
+private immutable
+{
+    typeof(&getnameinfo) getnameinfoPointer;
+    typeof(&getaddrinfo) getaddrinfoPointer;
+    typeof(&freeaddrinfo) freeaddrinfoPointer;
+}
+
+shared static this() @system
+{
+    version(Windows)
+    {
+        WSADATA wd;
+
+        // Winsock will still load if an older version is present.
+        // The version is just a request.
+        int val;
+        val = WSAStartup(0x2020, &wd);
+        if(val)         // Request Winsock 2.2 for IPv6.
+            throw new SocketOSException("Unable to initialize socket library", val);
+
+        // These functions may not be present on older Windows versions.
+        // See the comment in InternetAddress.toHostNameString() for details.
+        auto ws2Lib = GetModuleHandleA("ws2_32.dll");
+        if (ws2Lib)
+        {
+            getnameinfoPointer = cast(typeof(getnameinfoPointer))
+                                 GetProcAddress(ws2Lib, "getnameinfo");
+            getaddrinfoPointer = cast(typeof(getaddrinfoPointer))
+                                 GetProcAddress(ws2Lib, "getaddrinfo");
+            freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))
+                                 GetProcAddress(ws2Lib, "freeaddrinfo");
+        }
+    }
+    else version(Posix)
+    {
+        getnameinfoPointer = &getnameinfo;
+        getaddrinfoPointer = &getaddrinfo;
+        freeaddrinfoPointer = &freeaddrinfo;
+    }
+}
+
+
+shared static ~this() @system nothrow @nogc
+{
+    version(Windows)
+    {
+        WSACleanup();
+    }
+}
+
+/**
+ * The communication domain used to resolve an address.
+ */
+enum AddressFamily: int
+{
+    UNSPEC =     AF_UNSPEC,     /// Unspecified address family
+    UNIX =       AF_UNIX,       /// Local communication
+    INET =       AF_INET,       /// Internet Protocol version 4
+    IPX =        AF_IPX,        /// Novell IPX
+    APPLETALK =  AF_APPLETALK,  /// AppleTalk
+    INET6 =      AF_INET6,      /// Internet Protocol version 6
+}
+
+
+/**
+ * Communication semantics
+ */
+enum SocketType: int
+{
+    STREAM =     SOCK_STREAM,           /// Sequenced, reliable, two-way communication-based byte streams
+    DGRAM =      SOCK_DGRAM,            /// Connectionless, unreliable datagrams with a fixed maximum length; data may be lost or arrive out of order
+    RAW =        SOCK_RAW,              /// Raw protocol access
+    RDM =        SOCK_RDM,              /// Reliably-delivered message datagrams
+    SEQPACKET =  SOCK_SEQPACKET,        /// Sequenced, reliable, two-way connection-based datagrams with a fixed maximum length
+}
+
+
+/**
+ * Protocol
+ */
+enum ProtocolType: int
+{
+    IP =    IPPROTO_IP,         /// Internet Protocol version 4
+    ICMP =  IPPROTO_ICMP,       /// Internet Control Message Protocol
+    IGMP =  IPPROTO_IGMP,       /// Internet Group Management Protocol
+    GGP =   IPPROTO_GGP,        /// Gateway to Gateway Protocol
+    TCP =   IPPROTO_TCP,        /// Transmission Control Protocol
+    PUP =   IPPROTO_PUP,        /// PARC Universal Packet Protocol
+    UDP =   IPPROTO_UDP,        /// User Datagram Protocol
+    IDP =   IPPROTO_IDP,        /// Xerox NS protocol
+    RAW =   IPPROTO_RAW,        /// Raw IP packets
+    IPV6 =  IPPROTO_IPV6,       /// Internet Protocol version 6
+}
+
+
+/**
+ * $(D Protocol) is a class for retrieving protocol information.
+ *
+ * Example:
+ * ---
+ * auto proto = new Protocol;
+ * writeln("About protocol TCP:");
+ * if (proto.getProtocolByType(ProtocolType.TCP))
+ * {
+ *     writefln("  Name: %s", proto.name);
+ *     foreach(string s; proto.aliases)
+ *          writefln("  Alias: %s", s);
+ * }
+ * else
+ *     writeln("  No information found");
+ * ---
+ */
+class Protocol
+{
+    /// These members are populated when one of the following functions are called successfully:
+    ProtocolType type;
+    string name;                /// ditto
+    string[] aliases;           /// ditto
+
+
+    void populate(protoent* proto) @system pure nothrow
+    {
+        type = cast(ProtocolType)proto.p_proto;
+        name = to!string(proto.p_name);
+
+        int i;
+        for(i = 0;; i++)
+        {
+            if(!proto.p_aliases[i])
+                break;
+        }
+
+        if(i)
+        {
+            aliases = new string[i];
+            for(i = 0; i != aliases.length; i++)
+            {
+                aliases[i] =
+                    to!string(proto.p_aliases[i]);
+            }
+        }
+        else
+        {
+            aliases = null;
+        }
+    }
+
+    /** Returns: false on failure */
+    bool getProtocolByName(in char[] name) @trusted nothrow
+    {
+        protoent* proto;
+        proto = getprotobyname(name.tempCString());
+        if(!proto)
+            return false;
+        populate(proto);
+        return true;
+    }
+
+
+    /** Returns: false on failure */
+    // Same as getprotobynumber().
+    bool getProtocolByType(ProtocolType type) @trusted nothrow
+    {
+        protoent* proto;
+        proto = getprotobynumber(type);
+        if(!proto)
+            return false;
+        populate(proto);
+        return true;
+    }
+}
+
+
+// Skip this test on Android because getprotobyname/number are
+// unimplemented in bionic.
+version(CRuntime_Bionic) {} else
+unittest
+{
+    softUnittest({
+        Protocol proto = new Protocol;
+        assert(proto.getProtocolByType(ProtocolType.TCP));
+        //writeln("About protocol TCP:");
+        //writefln("\tName: %s", proto.name);
+        // foreach(string s; proto.aliases)
+        // {
+        //      writefln("\tAlias: %s", s);
+        // }
+        assert(proto.name == "tcp");
+        assert(proto.aliases.length == 1 && proto.aliases[0] == "TCP");
+    });
+}
+
+
+/**
+ * $(D Service) is a class for retrieving service information.
+ *
+ * Example:
+ * ---
+ * auto serv = new Service;
+ * writeln("About service epmap:");
+ * if (serv.getServiceByName("epmap", "tcp"))
+ * {
+ *     writefln("  Service: %s", serv.name);
+ *     writefln("  Port: %d", serv.port);
+ *     writefln("  Protocol: %s", serv.protocolName);
+ *     foreach (string s; serv.aliases)
+ *          writefln("  Alias: %s", s);
+ * }
+ * else
+ *     writefln("  No service for epmap.");
+ * ---
+ */
+class Service
+{
+    /// These members are populated when one of the following functions are called successfully:
+    string name;
+    string[] aliases;           /// ditto
+    ushort port;                /// ditto
+    string protocolName;        /// ditto
+
+
+    void populate(servent* serv) @system pure nothrow
+    {
+        name = to!string(serv.s_name);
+        port = ntohs(cast(ushort)serv.s_port);
+        protocolName = to!string(serv.s_proto);
+
+        int i;
+        for(i = 0;; i++)
+        {
+            if(!serv.s_aliases[i])
+                break;
+        }
+
+        if(i)
+        {
+            aliases = new string[i];
+            for(i = 0; i != aliases.length; i++)
+            {
+                aliases[i] =
+                    to!string(serv.s_aliases[i]);
+            }
+        }
+        else
+        {
+            aliases = null;
+        }
+    }
+
+    /**
+     * If a protocol name is omitted, any protocol will be matched.
+     * Returns: false on failure.
+     */
+    bool getServiceByName(in char[] name, in char[] protocolName = null) @trusted nothrow
+    {
+        servent* serv;
+        serv = getservbyname(name.tempCString(), protocolName.tempCString());
+        if(!serv)
+            return false;
+        populate(serv);
+        return true;
+    }
+
+
+    /// ditto
+    bool getServiceByPort(ushort port, in char[] protocolName = null) @trusted nothrow
+    {
+        servent* serv;
+        serv = getservbyport(port, protocolName.tempCString());
+        if(!serv)
+            return false;
+        populate(serv);
+        return true;
+    }
+}
+
+
+unittest
+{
+    softUnittest({
+        Service serv = new Service;
+        if(serv.getServiceByName("epmap", "tcp"))
+        {
+            // writefln("About service epmap:");
+            // writefln("\tService: %s", serv.name);
+            // writefln("\tPort: %d", serv.port);
+            // writefln("\tProtocol: %s", serv.protocolName);
+            // foreach(string s; serv.aliases)
+            // {
+            //      writefln("\tAlias: %s", s);
+            // }
+            // For reasons unknown this is loc-srv on Wine and epmap on Windows
+            assert(serv.name == "loc-srv" || serv.name == "epmap", serv.name);
+            assert(serv.port == 135);
+            assert(serv.protocolName == "tcp");
+        }
+        else
+        {
+            writefln("No service for epmap.");
+        }
+    });
+}
+
+
+private mixin template socketOSExceptionCtors()
+{
+    ///
+    this(string msg, string file = __FILE__, size_t line = __LINE__,
+         Throwable next = null, int err = _lasterr())
+    {
+        super(msg, file, line, next, err);
+    }
+
+    ///
+    this(string msg, Throwable next, string file = __FILE__,
+         size_t line = __LINE__, int err = _lasterr())
+    {
+        super(msg, next, file, line, err);
+    }
+
+    ///
+    this(string msg, int err, string file = __FILE__, size_t line = __LINE__,
+         Throwable next = null)
+    {
+        super(msg, next, file, line, err);
+    }
+}
+
+
+/**
+ * Class for exceptions thrown from an $(D InternetHost).
+ */
+class HostException: SocketOSException
+{
+    mixin socketOSExceptionCtors;
+}
+
+/**
+ * $(D InternetHost) is a class for resolving IPv4 addresses.
+ *
+ * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods
+ * instead of using this class directly.
+ *
+ * Example:
+ * ---
+ * auto ih = new InternetHost;
+ *
+ * // Forward lookup
+ * writeln("About www.digitalmars.com:");
+ * if (ih.getHostByName("www.digitalmars.com"))
+ * {
+ *     writefln("  Name: %s", ih.name);
+ *     auto ip = InternetAddress.addrToString(ih.addrList[0]);
+ *     writefln("  IP address: %s", ip);
+ *     foreach (string s; ih.aliases)
+ *          writefln("  Alias: %s", s);
+ *     writeln("---");
+ *
+ *     // Reverse lookup
+ *     writefln("About IP %s:", ip);
+ *     if (ih.getHostByAddr(ih.addrList[0]))
+ *     {
+ *         writefln("  Name: %s", ih.name);
+ *         foreach (string s; ih.aliases)
+ *              writefln("  Alias: %s", s);
+ *     }
+ *     else
+ *         writeln("  Reverse lookup failed");
+ * }
+ * else
+ *     writeln("  Can't resolve www.digitalmars.com");
+ * ---
+ */
+class InternetHost
+{
+    /// These members are populated when one of the following functions are called successfully:
+    string name;
+    string[] aliases;           /// ditto
+    uint[] addrList;            /// ditto
+
+
+    void validHostent(in hostent* he)
+    {
+        if(he.h_addrtype != cast(int)AddressFamily.INET || he.h_length != 4)
+            throw new HostException("Address family mismatch");
+    }
+
+
+    void populate(hostent* he) @system pure nothrow
+    {
+        int i;
+        char* p;
+
+        name = to!string(he.h_name);
+
+        for(i = 0;; i++)
+        {
+            p = he.h_aliases[i];
+            if(!p)
+                break;
+        }
+
+        if(i)
+        {
+            aliases = new string[i];
+            for(i = 0; i != aliases.length; i++)
+            {
+                aliases[i] =
+                    to!string(he.h_aliases[i]);
+            }
+        }
+        else
+        {
+            aliases = null;
+        }
+
+        for(i = 0;; i++)
+        {
+            p = he.h_addr_list[i];
+            if(!p)
+                break;
+        }
+
+        if(i)
+        {
+            addrList = new uint[i];
+            for(i = 0; i != addrList.length; i++)
+            {
+                addrList[i] = ntohl(*(cast(uint*)he.h_addr_list[i]));
+            }
+        }
+        else
+        {
+            addrList = null;
+        }
+    }
+
+    private bool getHostNoSync(string opMixin, T)(T param) @system
+    {
+        mixin(opMixin);
+        if (!he)
+            return false;
+        validHostent(he);
+        populate(he);
+        return true;
+    }
+
+    version(Windows)
+        alias getHost = getHostNoSync;
+    else
+    {
+        // posix systems use global state for return value, so we
+        // must synchronize across all threads
+        private bool getHost(string opMixin, T)(T param) @system
+        {
+            synchronized(this.classinfo)
+                return getHostNoSync!(opMixin, T)(param);
+        }
+    }
+
+    /**
+     * Resolve host name.
+     * Returns: false if unable to resolve.
+     */
+    bool getHostByName(in char[] name) @trusted
+    {
+        static if (is(typeof(gethostbyname_r)))
+        {
+            return getHostNoSync!q{
+                hostent he_v;
+                hostent* he;
+                ubyte[256] buffer_v = void;
+                auto buffer = buffer_v[];
+                auto param_zTmp = param.tempCString();
+                while (true)
+                {
+                    he = &he_v;
+                    int errno;
+                    if (gethostbyname_r(param_zTmp, he, buffer.ptr, buffer.length, &he, &errno) == ERANGE)
+                        buffer.length = buffer.length * 2;
+                    else
+                        break;
+                }
+            }(name);
+        }
+        else
+        {
+            return getHost!q{
+                auto he = gethostbyname(param.tempCString());
+            }(name);
+        }
+    }
+
+    /**
+     * Resolve IPv4 address number.
+     *
+     * Params:
+     *   addr = The IPv4 address to resolve, in host byte order.
+     * Returns:
+     *   false if unable to resolve.
+     */
+    bool getHostByAddr(uint addr) @trusted
+    {
+        return getHost!q{
+            auto x = htonl(param);
+            auto he = gethostbyaddr(&x, 4, cast(int)AddressFamily.INET);
+        }(addr);
+    }
+
+    /**
+     * Same as previous, but addr is an IPv4 address string in the
+     * dotted-decimal form $(I a.b.c.d).
+     * Returns: false if unable to resolve.
+     */
+    bool getHostByAddr(in char[] addr) @trusted
+    {
+        return getHost!q{
+            auto x = inet_addr(param.tempCString());
+            enforce(x != INADDR_NONE,
+                new SocketParameterException("Invalid IPv4 address"));
+            auto he = gethostbyaddr(&x, 4, cast(int)AddressFamily.INET);
+        }(addr);
+    }
+}
+
+
+unittest
+{
+    InternetHost ih = new InternetHost;
+
+    ih.getHostByAddr(0x7F_00_00_01);
+    assert(ih.addrList[0] == 0x7F_00_00_01);
+    ih.getHostByAddr("127.0.0.1");
+    assert(ih.addrList[0] == 0x7F_00_00_01);
+
+    softUnittest({
+        if (!ih.getHostByName("www.digitalmars.com"))
+            return;             // don't fail if not connected to internet
+        //writefln("addrList.length = %d", ih.addrList.length);
+        assert(ih.addrList.length);
+        InternetAddress ia = new InternetAddress(ih.addrList[0], InternetAddress.PORT_ANY);
+        assert(ih.name == "www.digitalmars.com" || ih.name == "digitalmars.com",
+               ih.name);
+        // writefln("IP address = %s", ia.toAddrString());
+        // writefln("name = %s", ih.name);
+        // foreach(int i, string s; ih.aliases)
+        // {
+        //      writefln("aliases[%d] = %s", i, s);
+        // }
+        // writefln("---");
+
+        //assert(ih.getHostByAddr(ih.addrList[0]));
+        // writefln("name = %s", ih.name);
+        // foreach(int i, string s; ih.aliases)
+        // {
+        //      writefln("aliases[%d] = %s", i, s);
+        // }
+    });
+}
+
+
+/// Holds information about a socket _address retrieved by $(D getAddressInfo).
+struct AddressInfo
+{
+    AddressFamily family;   /// Address _family
+    SocketType type;        /// Socket _type
+    ProtocolType protocol;  /// Protocol
+    Address address;        /// Socket _address
+    string canonicalName;   /// Canonical name, when $(D AddressInfoFlags.CANONNAME) is used.
+}
+
+// A subset of flags supported on all platforms with getaddrinfo.
+/// Specifies option flags for $(D getAddressInfo).
+enum AddressInfoFlags: int
+{
+    /// The resulting addresses will be used in a call to $(D Socket.bind).
+    PASSIVE = AI_PASSIVE,
+
+    /// The canonical name is returned in $(D canonicalName) member in the first $(D AddressInfo).
+    CANONNAME = AI_CANONNAME,
+
+    /// The $(D node) parameter passed to $(D getAddressInfo) must be a numeric string.
+    /// This will suppress any potentially lengthy network host address lookups.
+    NUMERICHOST = AI_NUMERICHOST,
+}
+
+
+/// On POSIX, getaddrinfo uses its own error codes, and thus has its own
+/// formatting function.
+private string formatGaiError(int err) @trusted
+{
+    version(Windows)
+    {
+        return sysErrorString(err);
+    }
+    else
+    {
+        synchronized
+            return to!string(gai_strerror(err));
+    }
+}
+
+/**
+ * Provides _protocol-independent translation from host names to socket
+ * addresses. If advanced functionality is not required, consider using
+ * $(D getAddress) for compatibility with older systems.
+ *
+ * Returns: Array with one $(D AddressInfo) per socket address.
+ *
+ * Throws: $(D SocketOSException) on failure, or $(D SocketFeatureException)
+ * if this functionality is not available on the current system.
+ *
+ * Params:
+ *  node     = string containing host name or numeric address
+ *  options  = optional additional parameters, identified by type:
+ *             $(UL $(LI $(D string) - service name or port number)
+ *                  $(LI $(D AddressInfoFlags) - option flags)
+ *                  $(LI $(D AddressFamily) - address family to filter by)
+ *                  $(LI $(D SocketType) - socket type to filter by)
+ *                  $(LI $(D ProtocolType) - protocol to filter by))
+ *
+ * Example:
+ * ---
+ * // Roundtrip DNS resolution
+ * auto results = getAddressInfo("www.digitalmars.com");
+ * assert(results[0].address.toHostNameString() ==
+ *     "digitalmars.com");
+ *
+ * // Canonical name
+ * results = getAddressInfo("www.digitalmars.com",
+ *     AddressInfoFlags.CANONNAME);
+ * assert(results[0].canonicalName == "digitalmars.com");
+ *
+ * // IPv6 resolution
+ * results = getAddressInfo("ipv6.google.com");
+ * assert(results[0].family == AddressFamily.INET6);
+ *
+ * // Multihomed resolution
+ * results = getAddressInfo("google.com");
+ * assert(results.length > 1);
+ *
+ * // Parsing IPv4
+ * results = getAddressInfo("127.0.0.1",
+ *     AddressInfoFlags.NUMERICHOST);
+ * assert(results.length && results[0].family ==
+ *     AddressFamily.INET);
+ *
+ * // Parsing IPv6
+ * results = getAddressInfo("::1",
+ *     AddressInfoFlags.NUMERICHOST);
+ * assert(results.length && results[0].family ==
+ *     AddressFamily.INET6);
+ * ---
+ */
+AddressInfo[] getAddressInfo(T...)(in char[] node, T options) @trusted
+{
+    const(char)[] service = null;
+    addrinfo hints;
+    hints.ai_family = AF_UNSPEC;
+
+    foreach (option; options)
+    {
+        static if (is(typeof(option) : const(char)[]))
+            service = option;
+        else
+        static if (is(typeof(option) == AddressInfoFlags))
+            hints.ai_flags |= option;
+        else
+        static if (is(typeof(option) == AddressFamily))
+            hints.ai_family = option;
+        else
+        static if (is(typeof(option) == SocketType))
+            hints.ai_socktype = option;
+        else
+        static if (is(typeof(option) == ProtocolType))
+            hints.ai_protocol = option;
+        else
+            static assert(0, "Unknown getAddressInfo option type: " ~ typeof(option).stringof);
+    }
+
+    return getAddressInfoImpl(node, service, &hints);
+}
+
+private AddressInfo[] getAddressInfoImpl(in char[] node, in char[] service, addrinfo* hints) @system
+{
+        import std.array : appender;
+
+    if (getaddrinfoPointer && freeaddrinfoPointer)
+    {
+        addrinfo* ai_res;
+
+        int ret = getaddrinfoPointer(
+            node.tempCString(),
+            service.tempCString(),
+            hints, &ai_res);
+        enforce(ret == 0, new SocketOSException("getaddrinfo error", ret, &formatGaiError));
+        scope(exit) freeaddrinfoPointer(ai_res);
+
+        auto result = appender!(AddressInfo[])();
+
+        // Use const to force UnknownAddressReference to copy the sockaddr.
+        for (const(addrinfo)* ai = ai_res; ai; ai = ai.ai_next)
+            result ~= AddressInfo(
+                cast(AddressFamily) ai.ai_family,
+                cast(SocketType   ) ai.ai_socktype,
+                cast(ProtocolType ) ai.ai_protocol,
+                new UnknownAddressReference(ai.ai_addr, cast(socklen_t) ai.ai_addrlen),
+                ai.ai_canonname ? to!string(ai.ai_canonname) : null);
+
+        assert(result.data.length > 0);
+        return result.data;
+    }
+
+    throw new SocketFeatureException("Address info lookup is not available " ~
+        "on this system.");
+}
+
+
+unittest
+{
+    softUnittest({
+        if (getaddrinfoPointer)
+        {
+            // Roundtrip DNS resolution
+            auto results = getAddressInfo("www.digitalmars.com");
+            assert(results[0].address.toHostNameString() == "digitalmars.com");
+
+            // Canonical name
+            results = getAddressInfo("www.digitalmars.com",
+                AddressInfoFlags.CANONNAME);
+            assert(results[0].canonicalName == "digitalmars.com");
+
+            // IPv6 resolution
+            //results = getAddressInfo("ipv6.google.com");
+            //assert(results[0].family == AddressFamily.INET6);
+
+            // Multihomed resolution
+            //results = getAddressInfo("google.com");
+            //assert(results.length > 1);
+
+            // Parsing IPv4
+            results = getAddressInfo("127.0.0.1", AddressInfoFlags.NUMERICHOST);
+            assert(results.length && results[0].family == AddressFamily.INET);
+
+            // Parsing IPv6
+            results = getAddressInfo("::1", AddressInfoFlags.NUMERICHOST);
+            assert(results.length && results[0].family == AddressFamily.INET6);
+        }
+    });
+
+    if (getaddrinfoPointer)
+    {
+        auto results = getAddressInfo(null, "1234", AddressInfoFlags.PASSIVE, SocketType.STREAM, ProtocolType.TCP, AddressFamily.INET);
+        assert(results.length == 1 && results[0].address.toString() == "0.0.0.0:1234");
+    }
+}
+
+
+private ushort serviceToPort(in char[] service)
+{
+    if (service == "")
+        return InternetAddress.PORT_ANY;
+    else
+    if (isNumeric(service))
+        return to!ushort(service);
+    else
+    {
+        auto s = new Service();
+        s.getServiceByName(service);
+        return s.port;
+    }
+}
+
+/**
+ * Provides _protocol-independent translation from host names to socket
+ * addresses. Uses $(D getAddressInfo) if the current system supports it,
+ * and $(D InternetHost) otherwise.
+ *
+ * Returns: Array with one $(D Address) instance per socket address.
+ *
+ * Throws: $(D SocketOSException) on failure.
+ *
+ * Example:
+ * ---
+ * writeln("Resolving www.digitalmars.com:");
+ * try
+ * {
+ *     auto addresses = getAddress("www.digitalmars.com");
+ *     foreach (address; addresses)
+ *         writefln("  IP: %s", address.toAddrString());
+ * }
+ * catch (SocketException e)
+ *     writefln("  Lookup failed: %s", e.msg);
+ * ---
+ */
+Address[] getAddress(in char[] hostname, in char[] service = null)
+{
+    if (getaddrinfoPointer && freeaddrinfoPointer)
+    {
+        // use getAddressInfo
+        auto infos = getAddressInfo(hostname, service);
+        Address[] results;
+        results.length = infos.length;
+        foreach (i, ref result; results)
+            result = infos[i].address;
+        return results;
+    }
+    else
+        return getAddress(hostname, serviceToPort(service));
+}
+
+/// ditto
+Address[] getAddress(in char[] hostname, ushort port)
+{
+    if (getaddrinfoPointer && freeaddrinfoPointer)
+        return getAddress(hostname, to!string(port));
+    else
+    {
+        // use getHostByName
+        auto ih = new InternetHost;
+        if (!ih.getHostByName(hostname))
+            throw new AddressException(
+                        text("Unable to resolve host '", hostname, "'"));
+
+        Address[] results;
+        foreach (uint addr; ih.addrList)
+            results ~= new InternetAddress(addr, port);
+        return results;
+    }
+}
+
+
+unittest
+{
+    softUnittest({
+        auto addresses = getAddress("63.105.9.61");
+        assert(addresses.length && addresses[0].toAddrString() == "63.105.9.61");
+
+        if (getaddrinfoPointer)
+        {
+            // test via gethostbyname
+            auto getaddrinfoPointerBackup = getaddrinfoPointer;
+            cast() getaddrinfoPointer = null;
+            scope(exit) cast() getaddrinfoPointer = getaddrinfoPointerBackup;
+
+            addresses = getAddress("63.105.9.61");
+            assert(addresses.length && addresses[0].toAddrString() == "63.105.9.61");
+        }
+    });
+}
+
+
+/**
+ * Provides _protocol-independent parsing of network addresses. Does not
+ * attempt name resolution. Uses $(D getAddressInfo) with
+ * $(D AddressInfoFlags.NUMERICHOST) if the current system supports it, and
+ * $(D InternetAddress) otherwise.
+ *
+ * Returns: An $(D Address) instance representing specified address.
+ *
+ * Throws: $(D SocketException) on failure.
+ *
+ * Example:
+ * ---
+ * writeln("Enter IP address:");
+ * string ip = readln().chomp();
+ * try
+ * {
+ *     Address address = parseAddress(ip);
+ *     writefln("Looking up reverse of %s:",
+ *         address.toAddrString());
+ *     try
+ *     {
+ *         string reverse = address.toHostNameString();
+ *         if (reverse)
+ *             writefln("  Reverse name: %s", reverse);
+ *         else
+ *             writeln("  Reverse hostname not found.");
+ *     }
+ *     catch (SocketException e)
+ *         writefln("  Lookup error: %s", e.msg);
+ * }
+ * catch (SocketException e)
+ * {
+ *     writefln("  %s is not a valid IP address: %s",
+ *         ip, e.msg);
+ * }
+ * ---
+ */
+Address parseAddress(in char[] hostaddr, in char[] service = null)
+{
+    if (getaddrinfoPointer && freeaddrinfoPointer)
+        return getAddressInfo(hostaddr, service, AddressInfoFlags.NUMERICHOST)[0].address;
+    else
+        return parseAddress(hostaddr, serviceToPort(service));
+}
+
+/// ditto
+Address parseAddress(in char[] hostaddr, ushort port)
+{
+    if (getaddrinfoPointer && freeaddrinfoPointer)
+        return parseAddress(hostaddr, to!string(port));
+    else
+    {
+        auto in4_addr = InternetAddress.parse(hostaddr);
+        enforce(in4_addr != InternetAddress.ADDR_NONE,
+            new SocketParameterException("Invalid IP address"));
+        return new InternetAddress(in4_addr, port);
+    }
+}
+
+
+unittest
+{
+    softUnittest({
+        auto address = parseAddress("63.105.9.61");
+        assert(address.toAddrString() == "63.105.9.61");
+
+        if (getaddrinfoPointer)
+        {
+            // test via inet_addr
+            auto getaddrinfoPointerBackup = getaddrinfoPointer;
+            cast() getaddrinfoPointer = null;
+            scope(exit) cast() getaddrinfoPointer = getaddrinfoPointerBackup;
+
+            address = parseAddress("63.105.9.61");
+            assert(address.toAddrString() == "63.105.9.61");
+        }
+
+        assert(collectException!SocketException(parseAddress("Invalid IP address")));
+    });
+}
+
+
+/**
+ * Class for exceptions thrown from an $(D Address).
+ */
+class AddressException: SocketOSException
+{
+    mixin socketOSExceptionCtors;
+}
+
+
+/**
+ * $(D Address) is an abstract class for representing a socket addresses.
+ *
+ * Example:
+ * ---
+ * writeln("About www.google.com port 80:");
+ * try
+ * {
+ *     Address[] addresses = getAddress("www.google.com", 80);
+ *     writefln("  %d addresses found.", addresses.length);
+ *     foreach (int i, Address a; addresses)
+ *     {
+ *         writefln("  Address %d:", i+1);
+ *         writefln("    IP address: %s", a.toAddrString());
+ *         writefln("    Hostname: %s", a.toHostNameString());
+ *         writefln("    Port: %s", a.toPortString());
+ *         writefln("    Service name: %s",
+ *             a.toServiceNameString());
+ *     }
+ * }
+ * catch (SocketException e)
+ *     writefln("  Lookup error: %s", e.msg);
+ * ---
+ */
+abstract class Address
+{
+    /// Returns pointer to underlying $(D sockaddr) structure.
+    abstract @property sockaddr* name() pure nothrow @nogc;
+    abstract @property const(sockaddr)* name() const pure nothrow @nogc; /// ditto
+
+    /// Returns actual size of underlying $(D sockaddr) structure.
+    abstract @property socklen_t nameLen() const pure nothrow @nogc;
+
+    /// Family of this address.
+    @property AddressFamily addressFamily() const pure nothrow @nogc
+    {
+        return cast(AddressFamily) name.sa_family;
+    }
+
+    // Common code for toAddrString and toHostNameString
+    private final string toHostString(bool numeric) @trusted const
+    {
+        // getnameinfo() is the recommended way to perform a reverse (name)
+        // lookup on both Posix and Windows. However, it is only available
+        // on Windows XP and above, and not included with the WinSock import
+        // libraries shipped with DMD. Thus, we check for getnameinfo at
+        // runtime in the shared module constructor, and use it if it's
+        // available in the base class method. Classes for specific network
+        // families (e.g. InternetHost) override this method and use a
+        // deprecated, albeit commonly-available method when getnameinfo()
+        // is not available.
+        // http://technet.microsoft.com/en-us/library/aa450403.aspx
+        if (getnameinfoPointer)
+        {
+            auto buf = new char[NI_MAXHOST];
+            auto ret = getnameinfoPointer(
+                        name, nameLen,
+                        buf.ptr, cast(uint)buf.length,
+                        null, 0,
+                        numeric ? NI_NUMERICHOST : NI_NAMEREQD);
+
+            if (!numeric)
+            {
+                if (ret==EAI_NONAME)
+                    return null;
+                version(Windows)
+                    if (ret==WSANO_DATA)
+                        return null;
+            }
+
+            enforce(ret == 0, new AddressException("Could not get " ~
+                        (numeric ? "host address" : "host name")));
+            return assumeUnique(buf[0 .. strlen(buf.ptr)]);
+        }
+
+        throw new SocketFeatureException((numeric ? "Host address" : "Host name") ~
+            " lookup for this address family is not available on this system.");
+    }
+
+    // Common code for toPortString and toServiceNameString
+    private final string toServiceString(bool numeric) @trusted const
+    {
+        // See toHostNameString() for details about getnameinfo().
+        if (getnameinfoPointer)
+        {
+            auto buf = new char[NI_MAXSERV];
+            enforce(getnameinfoPointer(
+                        name, nameLen,
+                        null, 0,
+                        buf.ptr, cast(uint)buf.length,
+                        numeric ? NI_NUMERICSERV : NI_NAMEREQD
+                    ) == 0, new AddressException("Could not get " ~
+                        (numeric ? "port number" : "service name")));
+            return assumeUnique(buf[0 .. strlen(buf.ptr)]);
+        }
+
+        throw new SocketFeatureException((numeric ? "Port number" : "Service name") ~
+            " lookup for this address family is not available on this system.");
+    }
+
+    /**
+     * Attempts to retrieve the host address as a human-readable string.
+     *
+     * Throws: $(D AddressException) on failure, or $(D SocketFeatureException)
+     * if address retrieval for this address family is not available on the
+     * current system.
+     */
+    string toAddrString() const
+    {
+        return toHostString(true);
+    }
+
+    /**
+     * Attempts to retrieve the host name as a fully qualified domain name.
+     *
+     * Returns: The FQDN corresponding to this $(D Address), or $(D null) if
+     * the host name did not resolve.
+     *
+     * Throws: $(D AddressException) on error, or $(D SocketFeatureException)
+     * if host name lookup for this address family is not available on the
+     * current system.
+     */
+    string toHostNameString() const
+    {
+        return toHostString(false);
+    }
+
+    /**
+     * Attempts to retrieve the numeric port number as a string.
+     *
+     * Throws: $(D AddressException) on failure, or $(D SocketFeatureException)
+     * if port number retrieval for this address family is not available on the
+     * current system.
+     */
+    string toPortString() const
+    {
+        return toServiceString(true);
+    }
+
+    /**
+     * Attempts to retrieve the service name as a string.
+     *
+     * Throws: $(D AddressException) on failure, or $(D SocketFeatureException)
+     * if service name lookup for this address family is not available on the
+     * current system.
+     */
+    string toServiceNameString() const
+    {
+        return toServiceString(false);
+    }
+
+    /// Human readable string representing this address.
+    override string toString() const
+    {
+        try
+        {
+            string host = toAddrString();
+            string port = toPortString();
+            if (host.indexOf(':') >= 0)
+                return "[" ~ host ~ "]:" ~ port;
+            else
+                return host ~ ":" ~ port;
+        }
+        catch (SocketException)
+            return "Unknown";
+    }
+}
+
+/**
+ * $(D UnknownAddress) encapsulates an unknown socket address.
+ */
+class UnknownAddress: Address
+{
+protected:
+    sockaddr sa;
+
+
+public:
+    override @property sockaddr* name()
+    {
+        return &sa;
+    }
+
+    override @property const(sockaddr)* name() const
+    {
+        return &sa;
+    }
+
+
+    override @property socklen_t nameLen() const
+    {
+        return cast(socklen_t) sa.sizeof;
+    }
+
+}
+
+
+/**
+ * $(D UnknownAddressReference) encapsulates a reference to an arbitrary
+ * socket address.
+ */
+class UnknownAddressReference: Address
+{
+protected:
+    sockaddr* sa;
+    socklen_t len;
+
+public:
+    /// Constructs an $(D Address) with a reference to the specified $(D sockaddr).
+    this(sockaddr* sa, socklen_t len) pure nothrow @nogc
+    {
+        this.sa  = sa;
+        this.len = len;
+    }
+
+    /// Constructs an $(D Address) with a copy of the specified $(D sockaddr).
+    this(const(sockaddr)* sa, socklen_t len) @system pure nothrow
+    {
+        this.sa = cast(sockaddr*) (cast(ubyte*)sa)[0..len].dup.ptr;
+        this.len = len;
+    }
+
+    override @property sockaddr* name()
+    {
+        return sa;
+    }
+
+    override @property const(sockaddr)* name() const
+    {
+        return sa;
+    }
+
+
+    override @property socklen_t nameLen() const
+    {
+        return cast(socklen_t) len;
+    }
+}
+
+
+/**
+ * $(D InternetAddress) encapsulates an IPv4 (Internet Protocol version 4)
+ * socket address.
+ *
+ * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods
+ * instead of using this class directly.
+ */
+class InternetAddress: Address
+{
+protected:
+    sockaddr_in sin;
+
+
+    this() pure nothrow @nogc
+    {
+    }
+
+
+public:
+    override @property sockaddr* name()
+    {
+        return cast(sockaddr*)&sin;
+    }
+
+    override @property const(sockaddr)* name() const
+    {
+        return cast(const(sockaddr)*)&sin;
+    }
+
+
+    override @property socklen_t nameLen() const
+    {
+        return cast(socklen_t) sin.sizeof;
+    }
+
+
+    enum uint ADDR_ANY = INADDR_ANY;         /// Any IPv4 host address.
+    enum uint ADDR_NONE = INADDR_NONE;       /// An invalid IPv4 host address.
+    enum ushort PORT_ANY = 0;                /// Any IPv4 port number.
+
+    /// Returns the IPv4 _port number (in host byte order).
+    @property ushort port() const pure nothrow @nogc
+    {
+        return ntohs(sin.sin_port);
+    }
+
+    /// Returns the IPv4 address number (in host byte order).
+    @property uint addr() const pure nothrow @nogc
+    {
+        return ntohl(sin.sin_addr.s_addr);
+    }
+
+    /**
+     * Construct a new $(D InternetAddress).
+     * Params:
+     *   addr = an IPv4 address string in the dotted-decimal form a.b.c.d,
+     *          or a host name which will be resolved using an $(D InternetHost)
+     *          object.
+     *   port = port number, may be $(D PORT_ANY).
+     */
+    this(in char[] addr, ushort port)
+    {
+        uint uiaddr = parse(addr);
+        if(ADDR_NONE == uiaddr)
+        {
+            InternetHost ih = new InternetHost;
+            if(!ih.getHostByName(addr))
+                //throw new AddressException("Invalid internet address");
+                throw new AddressException(
+                          text("Unable to resolve host '", addr, "'"));
+            uiaddr = ih.addrList[0];
+        }
+        sin.sin_family = AddressFamily.INET;
+        sin.sin_addr.s_addr = htonl(uiaddr);
+        sin.sin_port = htons(port);
+    }
+
+    /**
+     * Construct a new $(D InternetAddress).
+     * Params:
+     *   addr = (optional) an IPv4 address in host byte order, may be $(D ADDR_ANY).
+     *   port = port number, may be $(D PORT_ANY).
+     */
+    this(uint addr, ushort port) pure nothrow @nogc
+    {
+        sin.sin_family = AddressFamily.INET;
+        sin.sin_addr.s_addr = htonl(addr);
+        sin.sin_port = htons(port);
+    }
+
+    /// ditto
+    this(ushort port) pure nothrow @nogc
+    {
+        sin.sin_family = AddressFamily.INET;
+        sin.sin_addr.s_addr = ADDR_ANY;
+        sin.sin_port = htons(port);
+    }
+
+    /**
+     * Construct a new $(D InternetAddress).
+     * Params:
+     *   addr = A sockaddr_in as obtained from lower-level API calls such as getifaddrs.
+     */
+    this(sockaddr_in addr) pure nothrow @nogc
+    {
+        assert(addr.sin_family == AddressFamily.INET);
+        sin = addr;
+    }
+
+    /// Human readable string representing the IPv4 address in dotted-decimal form.
+    override string toAddrString() @trusted const
+    {
+        return to!string(inet_ntoa(sin.sin_addr));
+    }
+
+    /// Human readable string representing the IPv4 port.
+    override string toPortString() const
+    {
+        return std.conv.to!string(port);
+    }
+
+    /**
+     * Attempts to retrieve the host name as a fully qualified domain name.
+     *
+     * Returns: The FQDN corresponding to this $(D InternetAddress), or
+     * $(D null) if the host name did not resolve.
+     *
+     * Throws: $(D AddressException) on error.
+     */
+    override string toHostNameString() const
+    {
+        // getnameinfo() is the recommended way to perform a reverse (name)
+        // lookup on both Posix and Windows. However, it is only available
+        // on Windows XP and above, and not included with the WinSock import
+        // libraries shipped with DMD. Thus, we check for getnameinfo at
+        // runtime in the shared module constructor, and fall back to the
+        // deprecated getHostByAddr() if it could not be found. See also:
+        // http://technet.microsoft.com/en-us/library/aa450403.aspx
+
+        if (getnameinfoPointer)
+            return super.toHostNameString();
+        else
+        {
+            auto host = new InternetHost();
+            if (!host.getHostByAddr(ntohl(sin.sin_addr.s_addr)))
+                return null;
+            return host.name;
+        }
+    }
+
+    /**
+     * Compares with another InternetAddress of same type for equality
+     * Returns: true if the InternetAddresses share the same address and
+     * port number.
+     */
+    override bool opEquals(Object o) const
+    {
+        auto other = cast(InternetAddress)o;
+        return other && this.sin.sin_addr.s_addr == other.sin.sin_addr.s_addr &&
+            this.sin.sin_port == other.sin.sin_port;
+    }
+
+    ///
+    @system unittest
+    {
+        auto addr1 = new InternetAddress("127.0.0.1", 80);
+        auto addr2 = new InternetAddress("127.0.0.2", 80);
+
+        assert(addr1 == addr1);
+        assert(addr1 != addr2);
+    }
+
+    /**
+     * Parse an IPv4 address string in the dotted-decimal form $(I a.b.c.d)
+     * and return the number.
+     * Returns: If the string is not a legitimate IPv4 address,
+     * $(D ADDR_NONE) is returned.
+     */
+    static uint parse(in char[] addr) @trusted nothrow
+    {
+        return ntohl(inet_addr(addr.tempCString()));
+    }
+
+    /**
+     * Convert an IPv4 address number in host byte order to a human readable
+     * string representing the IPv4 address in dotted-decimal form.
+     */
+    static string addrToString(uint addr) @trusted nothrow
+    {
+        in_addr sin_addr;
+        sin_addr.s_addr = htonl(addr);
+        return to!string(inet_ntoa(sin_addr));
+    }
+}
+
+
+unittest
+{
+    softUnittest({
+        const InternetAddress ia = new InternetAddress("63.105.9.61", 80);
+        assert(ia.toString() == "63.105.9.61:80");
+    });
+
+    softUnittest({
+        // test construction from a sockaddr_in
+        sockaddr_in sin;
+
+        sin.sin_addr.s_addr = htonl(0x7F_00_00_01);  // 127.0.0.1
+        sin.sin_family = AddressFamily.INET;
+        sin.sin_port = htons(80);
+
+        const InternetAddress ia = new InternetAddress(sin);
+        assert(ia.toString() == "127.0.0.1:80");
+    });
+
+    softUnittest({
+        // test reverse lookup
+        auto ih = new InternetHost;
+        if (ih.getHostByName("digitalmars.com"))
+        {
+            const ia = new InternetAddress(ih.addrList[0], 80);
+            assert(ia.toHostNameString() == "digitalmars.com");
+
+            if (getnameinfoPointer)
+            {
+                // test reverse lookup, via gethostbyaddr
+                auto getnameinfoPointerBackup = getnameinfoPointer;
+                cast() getnameinfoPointer = null;
+                scope(exit) cast() getnameinfoPointer = getnameinfoPointerBackup;
+
+                assert(ia.toHostNameString() == "digitalmars.com");
+            }
+        }
+    });
+
+    version (SlowTests)
+    softUnittest({
+        // test failing reverse lookup
+        const InternetAddress ia = new InternetAddress("127.114.111.120", 80);
+        assert(ia.toHostNameString() is null);
+
+        if (getnameinfoPointer)
+        {
+            // test failing reverse lookup, via gethostbyaddr
+            auto getnameinfoPointerBackup = getnameinfoPointer;
+            getnameinfoPointer = null;
+            scope(exit) getnameinfoPointer = getnameinfoPointerBackup;
+
+            assert(ia.toHostNameString() is null);
+        }
+    });
+}
+
+
+/**
+ * $(D Internet6Address) encapsulates an IPv6 (Internet Protocol version 6)
+ * socket address.
+ *
+ * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods
+ * instead of using this class directly.
+ */
+class Internet6Address: Address
+{
+protected:
+    sockaddr_in6 sin6;
+
+
+    this() pure nothrow @nogc
+    {
+    }
+
+
+public:
+    override @property sockaddr* name()
+    {
+        return cast(sockaddr*)&sin6;
+    }
+
+    override @property const(sockaddr)* name() const
+    {
+        return cast(const(sockaddr)*)&sin6;
+    }
+
+
+    override @property socklen_t nameLen() const
+    {
+        return cast(socklen_t) sin6.sizeof;
+    }
+
+
+    /// Any IPv6 host address.
+    static @property ref const(ubyte)[16] ADDR_ANY() pure nothrow @nogc
+    {
+        const(ubyte)[16]* addr;
+        static if (is(typeof(IN6ADDR_ANY)))
+        {
+            addr = &IN6ADDR_ANY.s6_addr;
+            return *addr;
+        }
+        else static if (is(typeof(in6addr_any)))
+        {
+            addr = &in6addr_any.s6_addr;
+            return *addr;
+        }
+        else
+            static assert(0);
+    }
+
+    /// Any IPv6 port number.
+    enum ushort PORT_ANY = 0;
+
+    /// Returns the IPv6 port number.
+    @property ushort port() const pure nothrow @nogc
+    {
+        return ntohs(sin6.sin6_port);
+    }
+
+    /// Returns the IPv6 address.
+    @property ubyte[16] addr() const pure nothrow @nogc
+    {
+        return sin6.sin6_addr.s6_addr;
+    }
+
+    /**
+     * Construct a new $(D Internet6Address).
+     * Params:
+     *   addr    = an IPv6 host address string in the form described in RFC 2373,
+     *             or a host name which will be resolved using $(D getAddressInfo).
+     *   service = (optional) service name.
+     */
+    this(in char[] addr, in char[] service = null) @trusted
+    {
+        auto results = getAddressInfo(addr, service, AddressFamily.INET6);
+        assert(results.length && results[0].family == AddressFamily.INET6);
+        sin6 = *cast(sockaddr_in6*)results[0].address.name;
+    }
+
+    /**
+     * Construct a new $(D Internet6Address).
+     * Params:
+     *   addr = an IPv6 host address string in the form described in RFC 2373,
+     *          or a host name which will be resolved using $(D getAddressInfo).
+     *   port = port number, may be $(D PORT_ANY).
+     */
+    this(in char[] addr, ushort port)
+    {
+        if (port == PORT_ANY)
+            this(addr);
+        else
+            this(addr, to!string(port));
+    }
+
+    /**
+     * Construct a new $(D Internet6Address).
+     * Params:
+     *   addr = (optional) an IPv6 host address in host byte order, or
+     *          $(D ADDR_ANY).
+     *   port = port number, may be $(D PORT_ANY).
+     */
+    this(ubyte[16] addr, ushort port) pure nothrow @nogc
+    {
+        sin6.sin6_family = AddressFamily.INET6;
+        sin6.sin6_addr.s6_addr = addr;
+        sin6.sin6_port = htons(port);
+    }
+
+    /// ditto
+    this(ushort port) pure nothrow @nogc
+    {
+        sin6.sin6_family = AddressFamily.INET6;
+        sin6.sin6_addr.s6_addr = ADDR_ANY;
+        sin6.sin6_port = htons(port);
+    }
+
+     /**
+     * Construct a new $(D Internet6Address).
+     * Params:
+     *   addr = A sockaddr_in6 as obtained from lower-level API calls such as getifaddrs.
+     */
+    this(sockaddr_in6 addr) pure nothrow @nogc
+    {
+        assert(addr.sin6_family == AddressFamily.INET6);
+        sin6 = addr;
+    }
+
+   /**
+     * Parse an IPv6 host address string as described in RFC 2373, and return the
+     * address.
+     * Throws: $(D SocketException) on error.
+     */
+    static ubyte[16] parse(in char[] addr) @trusted
+    {
+        // Although we could use inet_pton here, it's only available on Windows
+        // versions starting with Vista, so use getAddressInfo with NUMERICHOST
+        // instead.
+        auto results = getAddressInfo(addr, AddressInfoFlags.NUMERICHOST);
+        if (results.length && results[0].family == AddressFamily.INET6)
+            return (cast(sockaddr_in6*)results[0].address.name).sin6_addr.s6_addr;
+        throw new AddressException("Not an IPv6 address", 0);
+    }
+}
+
+
+unittest
+{
+    softUnittest({
+        const Internet6Address ia = new Internet6Address("::1", 80);
+        assert(ia.toString() == "[::1]:80");
+    });
+
+    softUnittest({
+        // test construction from a sockaddr_in6
+        sockaddr_in6 sin;
+
+        sin.sin6_addr.s6_addr = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];  // [::1]
+        sin.sin6_family = AddressFamily.INET6;
+        sin.sin6_port = htons(80);
+
+        const Internet6Address ia = new Internet6Address(sin);
+        assert(ia.toString() == "[::1]:80");
+    });
+}
+
+
+version(StdDdoc)
+{
+    static if (!is(sockaddr_un))
+    {
+        // This exists only to allow the constructor taking
+        // a sockaddr_un to be compilable for documentation
+        // on platforms that don't supply a sockaddr_un.
+        struct sockaddr_un
+        {
+        }
+    }
+
+    /**
+     * $(D UnixAddress) encapsulates an address for a Unix domain socket
+     * ($(D AF_UNIX)). Available only on supported systems.
+     */
+    class UnixAddress: Address
+    {
+        private this() pure nothrow @nogc {}
+
+        /// Construct a new $(D UnixAddress) from the specified path.
+        this(in char[] path) { }
+
+        /**
+         * Construct a new $(D UnixAddress).
+         * Params:
+         *   addr = A sockaddr_un as obtained from lower-level API calls.
+         */
+        this(sockaddr_un addr) pure nothrow @nogc { }
+
+        /// Get the underlying _path.
+        @property string path() const { return null; }
+
+        /// ditto
+        override string toString() const { return null; }
+
+        override @property sockaddr* name() { return null; }
+        override @property const(sockaddr)* name() const { return null; }
+        override @property socklen_t nameLen() const { return 0; }
+    }
+}
+else
+static if (is(sockaddr_un))
+{
+    class UnixAddress: Address
+    {
+    protected:
+        struct
+        {
+        align (1):
+            sockaddr_un sun;
+            char unused = '\0'; // placeholder for a terminating '\0'
+        }
+
+        this() pure nothrow @nogc
+        {
+            sun.sun_family = AddressFamily.UNIX;
+            sun.sun_path = '?';
+        }
+
+    public:
+        override @property sockaddr* name()
+        {
+            return cast(sockaddr*)&sun;
+        }
+
+        override @property const(sockaddr)* name() const
+        {
+            return cast(const(sockaddr)*)&sun;
+        }
+
+        override @property socklen_t nameLen() @trusted const
+        {
+            return cast(socklen_t) (sockaddr_un.init.sun_path.offsetof +
+                strlen(cast(const(char*)) sun.sun_path.ptr) + 1);
+        }
+
+        this(in char[] path) @trusted pure
+        {
+            enforce(path.length <= sun.sun_path.sizeof, new SocketParameterException("Path too long"));
+            sun.sun_family = AddressFamily.UNIX;
+            sun.sun_path.ptr[0..path.length] = (cast(byte[]) path)[];
+            sun.sun_path.ptr[path.length] = 0;
+        }
+
+        this(sockaddr_un addr) pure nothrow @nogc
+        {
+            assert(addr.sun_family == AddressFamily.UNIX);
+            sun = addr;
+        }
+
+        @property string path() @trusted const pure
+        {
+            return to!string(cast(const(char)*)sun.sun_path.ptr);
+        }
+
+        override string toString() const pure
+        {
+            return path;
+        }
+    }
+
+    unittest
+    {
+        import core.stdc.stdio : remove;
+        import std.file: deleteme;
+
+        immutable ubyte[] data = [1, 2, 3, 4];
+        Socket[2] pair;
+
+        auto name = deleteme ~ "-unix-socket";
+        auto address = new UnixAddress(name);
+
+        auto listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
+        scope(exit) listener.close();
+
+        listener.bind(address);
+        scope(exit) () @trusted { remove(name.tempCString()); } ();
+        assert(listener.localAddress.toString == name);
+
+        listener.listen(1);
+
+        pair[0] = new Socket(AddressFamily.UNIX, SocketType.STREAM);
+        scope(exit) listener.close();
+
+        pair[0].connect(address);
+        scope(exit) pair[0].close();
+
+        pair[1] = listener.accept();
+        scope(exit) pair[1].close();
+
+        pair[0].send(data);
+
+        auto buf = new ubyte[data.length];
+        pair[1].receive(buf);
+        assert(buf == data);
+    }
+}
+
+
+/**
+ * Class for exceptions thrown by $(D Socket.accept).
+ */
+class SocketAcceptException: SocketOSException
+{
+    mixin socketOSExceptionCtors;
+}
+
+/// How a socket is shutdown:
+enum SocketShutdown: int
+{
+    RECEIVE =  SD_RECEIVE,      /// socket receives are disallowed
+    SEND =     SD_SEND,         /// socket sends are disallowed
+    BOTH =     SD_BOTH,         /// both RECEIVE and SEND
+}
+
+
+/// Flags may be OR'ed together:
+enum SocketFlags: int
+{
+    NONE =       0,                 /// no flags specified
+
+    OOB =        MSG_OOB,           /// out-of-band stream data
+    PEEK =       MSG_PEEK,          /// peek at incoming data without removing it from the queue, only for receiving
+    DONTROUTE =  MSG_DONTROUTE,     /// data should not be subject to routing; this flag may be ignored. Only for sending
+}
+
+
+private mixin template FieldProxy(string target, string field)
+{
+    mixin(`
+        @property typeof(`~target~`) `~field~`() const pure nothrow @nogc
+        {
+            return `~target~`;
+        }
+
+        /// ditto
+        @property typeof(`~target~`) `~field~`(typeof(`~target~`) value) pure nothrow @nogc
+        {
+            return `~target~` = value;
+        }
+    `);
+}
+
+
+/// Duration timeout value.
+struct TimeVal
+{
+    _ctimeval ctimeval;
+    alias tv_sec_t = typeof(ctimeval.tv_sec);
+    alias tv_usec_t = typeof(ctimeval.tv_usec);
+
+    version (StdDdoc) // no DDoc for string mixins, can't forward individual fields
+    {
+        tv_sec_t seconds;           /// Number of _seconds.
+        tv_usec_t microseconds;     /// Number of additional _microseconds.
+    }
+    else
+    {
+        // D interface
+        mixin FieldProxy!(`ctimeval.tv_sec`, `seconds`);
+        mixin FieldProxy!(`ctimeval.tv_usec`, `microseconds`);
+    }
+}
+
+
+/**
+ * A collection of sockets for use with $(D Socket.select).
+ *
+ * $(D SocketSet) wraps the platform $(D fd_set) type. However, unlike
+ * $(D fd_set), $(D SocketSet) is not statically limited to $(D FD_SETSIZE)
+ * or any other limit, and grows as needed.
+ */
+class SocketSet
+{
+private:
+    version (Windows)
+    {
+        // On Windows, fd_set is an array of socket handles,
+        // following a word containing the fd_set instance size.
+        // We use one dynamic array for everything, and use its first
+        // element(s) for the count.
+
+        alias fd_set_count_type = typeof(fd_set.init.fd_count);
+        alias fd_set_type = typeof(fd_set.init.fd_array[0]);
+        static assert(fd_set_type.sizeof == socket_t.sizeof);
+
+        // Number of fd_set_type elements at the start of our array that are
+        // used for the socket count and alignment
+
+        enum FD_SET_OFFSET = fd_set.fd_array.offsetof / fd_set_type.sizeof;
+        static assert(FD_SET_OFFSET);
+        static assert(fd_set.fd_count.offsetof % fd_set_type.sizeof == 0);
+
+        fd_set_type[] set;
+
+        final void resize(size_t size) pure nothrow
+        {
+            set.length = FD_SET_OFFSET + size;
+        }
+
+        final ref inout(fd_set_count_type) count() @trusted @property inout pure nothrow @nogc
+        {
+            assert(set.length);
+            return *cast(inout(fd_set_count_type)*)set.ptr;
+        }
+
+        final size_t capacity() @property const pure nothrow @nogc
+        {
+            return set.length - FD_SET_OFFSET;
+        }
+
+        final inout(socket_t)[] fds() @trusted inout @property pure nothrow @nogc
+        {
+            return cast(inout(socket_t)[])set[FD_SET_OFFSET..FD_SET_OFFSET+count];
+        }
+    }
+    else
+    version (Posix)
+    {
+        // On Posix, fd_set is a bit array. We assume that the fd_set
+        // type (declared in core.sys.posix.sys.select) is a structure
+        // containing a single field, a static array.
+
+        static assert(fd_set.tupleof.length==1);
+
+        // This is the type used in the fd_set array.
+        // Using the type of the correct size is important for big-endian
+        // architectures.
+
+        alias fd_set_type = typeof(fd_set.init.tupleof[0][0]);
+
+        // Number of file descriptors represented by one fd_set_type
+
+        enum FD_NFDBITS = 8 * fd_set_type.sizeof;
+
+        static fd_set_type mask(uint n) pure nothrow @nogc
+        {
+            return (cast(fd_set_type)1) << (n % FD_NFDBITS);
+        }
+
+        // Array size to fit that many sockets
+
+        static size_t lengthFor(size_t size) pure nothrow @nogc
+        {
+            return (size + (FD_NFDBITS-1)) / FD_NFDBITS;
+        }
+
+        fd_set_type[] set;
+
+        final void resize(size_t size) pure nothrow
+        {
+            set.length = lengthFor(size);
+        }
+
+        // Make sure we can fit that many sockets
+
+        final void setMinCapacity(size_t size) pure nothrow
+        {
+            auto length = lengthFor(size);
+            if (set.length < length)
+                set.length = length;
+        }
+
+        final size_t capacity() @property const pure nothrow @nogc
+        {
+            return set.length * FD_NFDBITS;
+        }
+
+        int maxfd;
+    }
+    else
+        static assert(false, "Unknown platform");
+
+public:
+
+    /**
+     * Create a SocketSet with a specific initial capacity (defaults to
+     * $(D FD_SETSIZE), the system's default capacity).
+     */
+    this(size_t size = FD_SETSIZE) pure nothrow
+    {
+        resize(size);
+        reset();
+    }
+
+    /// Reset the $(D SocketSet) so that there are 0 $(D Socket)s in the collection.
+    void reset() pure nothrow @nogc
+    {
+        version (Windows)
+            count = 0;
+        else
+        {
+            set[] = 0;
+            maxfd = -1;
+        }
+    }
+
+
+    void add(socket_t s) @trusted pure nothrow
+    {
+        version (Windows)
+        {
+            if (count == capacity)
+            {
+                set.length *= 2;
+                set.length = set.capacity;
+            }
+            ++count;
+            fds[$-1] = s;
+        }
+        else
+        {
+            auto index = s / FD_NFDBITS;
+            auto length = set.length;
+            if (index >= length)
+            {
+                while (index >= length)
+                    length *= 2;
+                set.length = length;
+                set.length = set.capacity;
+            }
+            set[index] |= mask(s);
+            if (maxfd < s)
+                maxfd = s;
+        }
+    }
+
+    /// Add a $(D Socket) to the collection.
+    /// The socket must not already be in the collection.
+    void add(Socket s) pure nothrow
+    {
+        add(s.sock);
+    }
+
+    void remove(socket_t s) pure nothrow
+    {
+        version (Windows)
+        {
+            import std.algorithm : countUntil;
+            auto fds = fds;
+            auto p = fds.countUntil(s);
+            if (p >= 0)
+                fds[p] = fds[--count];
+        }
+        else
+        {
+            auto index = s / FD_NFDBITS;
+            if (index >= set.length)
+                return;
+            set[index] &= ~mask(s);
+            // note: adjusting maxfd would require scanning the set, not worth it
+        }
+    }
+
+
+    /// Remove this $(D Socket) from the collection.
+    /// Does nothing if the socket is not in the collection already.
+    void remove(Socket s) pure nothrow
+    {
+        remove(s.sock);
+    }
+
+    int isSet(socket_t s) const pure nothrow @nogc
+    {
+        version (Windows)
+        {
+            import std.algorithm;
+            return fds.canFind(s) ? 1 : 0;
+        }
+        else
+        {
+            if (s > maxfd)
+                return 0;
+            auto index = s / FD_NFDBITS;
+            return (set[index] & mask(s)) ? 1 : 0;
+        }
+    }
+
+
+    /// Return nonzero if this $(D Socket) is in the collection.
+    int isSet(Socket s) const pure nothrow @nogc
+    {
+        return isSet(s.sock);
+    }
+
+
+    /// Return the current capacity of this $(D SocketSet). The exact
+    /// meaning of the return value varies from platform to platform.
+    ///
+    /// Note that since D 2.065, this value does not indicate a
+    /// restriction, and $(D SocketSet) will grow its capacity as
+    /// needed automatically.
+    @property uint max() const pure nothrow @nogc
+    {
+        return cast(uint)capacity;
+    }
+
+
+    fd_set* toFd_set() @trusted pure nothrow @nogc
+    {
+        return cast(fd_set*)set.ptr;
+    }
+
+
+    int selectn() const pure nothrow @nogc
+    {
+        version (Windows)
+        {
+            return count;
+        }
+        else version (Posix)
+        {
+            return maxfd + 1;
+        }
+    }
+}
+
+unittest
+{
+    auto fds = cast(socket_t[])
+        [cast(socket_t)1, 2, 0, 1024, 17, 42, 1234, 77, 77+32, 77+64];
+    auto set = new SocketSet();
+    foreach (fd; fds) assert(!set.isSet(fd));
+    foreach (fd; fds) set.add(fd);
+    foreach (fd; fds) assert(set.isSet(fd));
+
+    // Make sure SocketSet reimplements fd_set correctly
+    auto fdset = set.toFd_set();
+    foreach (fd; fds[0]..cast(socket_t)(fds[$-1]+1))
+        assert(cast(bool)set.isSet(fd) == cast(bool)(() @trusted => FD_ISSET(fd, fdset))());
+
+    foreach (fd; fds)
+    {
+        assert(set.isSet(fd));
+        set.remove(fd);
+        assert(!set.isSet(fd));
+    }
+}
+
+unittest
+{
+    softUnittest({
+        enum PAIRS = 768;
+        version(Posix)
+        () @trusted
+        {
+            enum LIMIT = 2048;
+            static assert(LIMIT > PAIRS*2);
+            import core.sys.posix.sys.resource;
+            rlimit fileLimit;
+            getrlimit(RLIMIT_NOFILE, &fileLimit);
+            assert(fileLimit.rlim_max > LIMIT, "Open file hard limit too low");
+            fileLimit.rlim_cur = LIMIT;
+            setrlimit(RLIMIT_NOFILE, &fileLimit);
+        } ();
+
+        Socket[2][PAIRS] pairs;
+        foreach (ref pair; pairs)
+            pair = socketPair();
+        scope(exit)
+        {
+            foreach (pair; pairs)
+            {
+                pair[0].close();
+                pair[1].close();
+            }
+        }
+
+        import std.random;
+        auto rng = Xorshift(42);
+        pairs[].randomShuffle(rng);
+
+        auto readSet = new SocketSet();
+        auto writeSet = new SocketSet();
+        auto errorSet = new SocketSet();
+
+        foreach (testPair; pairs)
+        {
+            void fillSets()
+            {
+                readSet.reset();
+                writeSet.reset();
+                errorSet.reset();
+                foreach (ref pair; pairs)
+                    foreach (s; pair[])
+                    {
+                        readSet.add(s);
+                        writeSet.add(s);
+                        errorSet.add(s);
+                    }
+            }
+
+            fillSets();
+            auto n = Socket.select(readSet, writeSet, errorSet);
+            assert(n == PAIRS*2); // All in writeSet
+            assert(writeSet.isSet(testPair[0]));
+            assert(writeSet.isSet(testPair[1]));
+            assert(!readSet.isSet(testPair[0]));
+            assert(!readSet.isSet(testPair[1]));
+            assert(!errorSet.isSet(testPair[0]));
+            assert(!errorSet.isSet(testPair[1]));
+
+            ubyte[1] b;
+            testPair[0].send(b[]);
+            fillSets();
+            n = Socket.select(readSet, null, null);
+            assert(n == 1); // testPair[1]
+            assert(readSet.isSet(testPair[1]));
+            assert(!readSet.isSet(testPair[0]));
+            testPair[1].receive(b[]);
+        }
+    });
+}
+
+unittest // Issue 14012, 14013
+{
+    auto set = new SocketSet(1);
+    assert(set.max >= 0);
+
+    enum LIMIT = 4096;
+    foreach (n; 0..LIMIT)
+        set.add(cast(socket_t)n);
+    assert(set.max >= LIMIT);
+}
+
+/// The level at which a socket option is defined:
+enum SocketOptionLevel: int
+{
+    SOCKET =  SOL_SOCKET,               /// Socket level
+    IP =      ProtocolType.IP,          /// Internet Protocol version 4 level
+    ICMP =    ProtocolType.ICMP,        /// Internet Control Message Protocol level
+    IGMP =    ProtocolType.IGMP,        /// Internet Group Management Protocol level
+    GGP =     ProtocolType.GGP,         /// Gateway to Gateway Protocol level
+    TCP =     ProtocolType.TCP,         /// Transmission Control Protocol level
+    PUP =     ProtocolType.PUP,         /// PARC Universal Packet Protocol level
+    UDP =     ProtocolType.UDP,         /// User Datagram Protocol level
+    IDP =     ProtocolType.IDP,         /// Xerox NS protocol level
+    RAW =     ProtocolType.RAW,         /// Raw IP packet level
+    IPV6 =    ProtocolType.IPV6,        /// Internet Protocol version 6 level
+}
+
+/// _Linger information for use with SocketOption.LINGER.
+struct Linger
+{
+    _clinger clinger;
+
+    version (StdDdoc) // no DDoc for string mixins, can't forward individual fields
+    {
+        private alias l_onoff_t = typeof(_clinger.init.l_onoff );
+        private alias l_linger_t = typeof(_clinger.init.l_linger);
+        l_onoff_t  on;   /// Nonzero for _on.
+        l_linger_t time; /// Linger _time.
+    }
+    else
+    {
+        // D interface
+        mixin FieldProxy!(`clinger.l_onoff`, `on`);
+        mixin FieldProxy!(`clinger.l_linger`, `time`);
+    }
+}
+
+/// Specifies a socket option:
+enum SocketOption: int
+{
+    DEBUG =                SO_DEBUG,            /// Record debugging information
+    BROADCAST =            SO_BROADCAST,        /// Allow transmission of broadcast messages
+    REUSEADDR =            SO_REUSEADDR,        /// Allow local reuse of address
+    LINGER =               SO_LINGER,           /// Linger on close if unsent data is present
+    OOBINLINE =            SO_OOBINLINE,        /// Receive out-of-band data in band
+    SNDBUF =               SO_SNDBUF,           /// Send buffer size
+    RCVBUF =               SO_RCVBUF,           /// Receive buffer size
+    DONTROUTE =            SO_DONTROUTE,        /// Do not route
+    SNDTIMEO =             SO_SNDTIMEO,         /// Send timeout
+    RCVTIMEO =             SO_RCVTIMEO,         /// Receive timeout
+    ERROR =                SO_ERROR,            /// Retrieve and clear error status
+    KEEPALIVE =            SO_KEEPALIVE,        /// Enable keep-alive packets
+    ACCEPTCONN =           SO_ACCEPTCONN,       /// Listen
+    RCVLOWAT =             SO_RCVLOWAT,         /// Minimum number of input bytes to process
+    SNDLOWAT =             SO_SNDLOWAT,         /// Minimum number of output bytes to process
+    TYPE =                 SO_TYPE,             /// Socket type
+
+    // SocketOptionLevel.TCP:
+    TCP_NODELAY =          .TCP_NODELAY,        /// Disable the Nagle algorithm for send coalescing
+
+    // SocketOptionLevel.IPV6:
+    IPV6_UNICAST_HOPS =    .IPV6_UNICAST_HOPS,          /// IP unicast hop limit
+    IPV6_MULTICAST_IF =    .IPV6_MULTICAST_IF,          /// IP multicast interface
+    IPV6_MULTICAST_LOOP =  .IPV6_MULTICAST_LOOP,        /// IP multicast loopback
+    IPV6_MULTICAST_HOPS =  .IPV6_MULTICAST_HOPS,        /// IP multicast hops
+    IPV6_JOIN_GROUP =      .IPV6_JOIN_GROUP,            /// Add an IP group membership
+    IPV6_LEAVE_GROUP =     .IPV6_LEAVE_GROUP,           /// Drop an IP group membership
+    IPV6_V6ONLY =          .IPV6_V6ONLY,                /// Treat wildcard bind as AF_INET6-only
+}
+
+
+/**
+ * $(D Socket) is a class that creates a network communication endpoint using
+ * the Berkeley sockets interface.
+ */
+class Socket
+{
+private:
+    socket_t sock;
+    AddressFamily _family;
+
+    version(Windows)
+        bool _blocking = false;         /// Property to get or set whether the socket is blocking or nonblocking.
+
+    // The WinSock timeouts seem to be effectively skewed by a constant
+    // offset of about half a second (value in milliseconds). This has
+    // been confirmed on updated (as of Jun 2011) Windows XP, Windows 7
+    // and Windows Server 2008 R2 boxes. The unittest below tests this
+    // behavior.
+    enum WINSOCK_TIMEOUT_SKEW = 500;
+
+    unittest
+    {
+        version(SlowTests)
+        softUnittest({
+            import std.datetime;
+
+            enum msecs = 1000;
+            auto pair = socketPair();
+            auto sock = pair[0];
+            sock.setOption(SocketOptionLevel.SOCKET,
+                SocketOption.RCVTIMEO, dur!"msecs"(msecs));
+
+            auto sw = StopWatch(AutoStart.yes);
+            ubyte[1] buf;
+            sock.receive(buf);
+            sw.stop();
+
+            Duration readBack = void;
+            sock.getOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, readBack);
+
+            assert(readBack.total!"msecs" == msecs);
+            assert(sw.peek().msecs > msecs-100 && sw.peek().msecs < msecs+100);
+        });
+    }
+
+    void setSock(socket_t handle)
+    {
+        assert(handle != socket_t.init);
+        sock = handle;
+
+        // Set the option to disable SIGPIPE on send() if the platform
+        // has it (e.g. on OS X).
+        static if (is(typeof(SO_NOSIGPIPE)))
+        {
+            setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);
+        }
+    }
+
+
+    // For use with accepting().
+    protected this() pure nothrow @nogc
+    {
+    }
+
+
+public:
+
+    /**
+     * Create a blocking socket. If a single protocol type exists to support
+     * this socket type within the address family, the $(D ProtocolType) may be
+     * omitted.
+     */
+    this(AddressFamily af, SocketType type, ProtocolType protocol) @trusted
+    {
+        _family = af;
+        auto handle = cast(socket_t) socket(af, type, protocol);
+        if(handle == socket_t.init)
+            throw new SocketOSException("Unable to create socket");
+        setSock(handle);
+    }
+
+
+    // A single protocol exists to support this socket type within the
+    // protocol family, so the ProtocolType is assumed.
+    /// ditto
+    this(AddressFamily af, SocketType type)
+    {
+        this(af, type, cast(ProtocolType)0);         // Pseudo protocol number.
+    }
+
+
+    /// ditto
+    this(AddressFamily af, SocketType type, in char[] protocolName) @trusted
+    {
+        protoent* proto;
+        proto = getprotobyname(protocolName.tempCString());
+        if(!proto)
+            throw new SocketOSException("Unable to find the protocol");
+        this(af, type, cast(ProtocolType)proto.p_proto);
+    }
+
+
+    /**
+     * Create a blocking socket using the parameters from the specified
+     * $(D AddressInfo) structure.
+     */
+    this(in AddressInfo info)
+    {
+        this(info.family, info.type, info.protocol);
+    }
+
+    /// Use an existing socket handle.
+    this(socket_t sock, AddressFamily af) pure nothrow @nogc
+    {
+        assert(sock != socket_t.init);
+        this.sock = sock;
+        this._family = af;
+    }
+
+
+    ~this() nothrow @nogc
+    {
+        close();
+    }
+
+
+    /// Get underlying socket handle.
+    @property socket_t handle() const pure nothrow @nogc
+    {
+        return sock;
+    }
+
+    /**
+     * Get/set socket's blocking flag.
+     *
+     * When a socket is blocking, calls to receive(), accept(), and send()
+     * will block and wait for data/action.
+     * A non-blocking socket will immediately return instead of blocking.
+     */
+    @property bool blocking() @trusted const nothrow @nogc
+    {
+        version(Windows)
+        {
+            return _blocking;
+        }
+        else version(Posix)
+        {
+            return !(fcntl(handle, F_GETFL, 0) & O_NONBLOCK);
+        }
+    }
+
+    /// ditto
+    @property void blocking(bool byes) @trusted
+    {
+        version(Windows)
+        {
+            uint num = !byes;
+            if(_SOCKET_ERROR == ioctlsocket(sock, FIONBIO, &num))
+                goto err;
+            _blocking = byes;
+        }
+        else version(Posix)
+        {
+            int x = fcntl(sock, F_GETFL, 0);
+            if(-1 == x)
+                goto err;
+            if(byes)
+                x &= ~O_NONBLOCK;
+            else
+                x |= O_NONBLOCK;
+            if(-1 == fcntl(sock, F_SETFL, x))
+                goto err;
+        }
+        return;         // Success.
+
+ err:
+        throw new SocketOSException("Unable to set socket blocking");
+    }
+
+
+    /// Get the socket's address family.
+    @property AddressFamily addressFamily()
+    {
+        return _family;
+    }
+
+    /// Property that indicates if this is a valid, alive socket.
+    @property bool isAlive() @trusted const
+    {
+        int type;
+        socklen_t typesize = cast(socklen_t) type.sizeof;
+        return !getsockopt(sock, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);
+    }
+
+    /// Associate a local address with this socket.
+    void bind(Address addr) @trusted
+    {
+        if(_SOCKET_ERROR == .bind(sock, addr.name, addr.nameLen))
+            throw new SocketOSException("Unable to bind socket");
+    }
+
+    /**
+     * Establish a connection. If the socket is blocking, connect waits for
+     * the connection to be made. If the socket is nonblocking, connect
+     * returns immediately and the connection attempt is still in progress.
+     */
+    void connect(Address to) @trusted
+    {
+        if(_SOCKET_ERROR == .connect(sock, to.name, to.nameLen))
+        {
+            int err;
+            err = _lasterr();
+
+            if(!blocking)
+            {
+                version(Windows)
+                {
+                    if(WSAEWOULDBLOCK == err)
+                        return;
+                }
+                else version(Posix)
+                {
+                    if(EINPROGRESS == err)
+                        return;
+                }
+                else
+                {
+                    static assert(0);
+                }
+            }
+            throw new SocketOSException("Unable to connect socket", err);
+        }
+    }
+
+    /**
+     * Listen for an incoming connection. $(D bind) must be called before you
+     * can $(D listen). The $(D backlog) is a request of how many pending
+     * incoming connections are queued until $(D accept)ed.
+     */
+    void listen(int backlog) @trusted
+    {
+        if(_SOCKET_ERROR == .listen(sock, backlog))
+            throw new SocketOSException("Unable to listen on socket");
+    }
+
+    /**
+     * Called by $(D accept) when a new $(D Socket) must be created for a new
+     * connection. To use a derived class, override this method and return an
+     * instance of your class. The returned $(D Socket)'s handle must not be
+     * set; $(D Socket) has a protected constructor $(D this()) to use in this
+     * situation.
+     */
+    // Override to use a derived class.
+    // The returned socket's handle must not be set.
+    protected Socket accepting() pure nothrow
+    {
+        return new Socket;
+    }
+
+    /**
+     * Accept an incoming connection. If the socket is blocking, $(D accept)
+     * waits for a connection request. Throws $(D SocketAcceptException) if
+     * unable to _accept. See $(D accepting) for use with derived classes.
+     */
+    Socket accept() @trusted
+    {
+        auto newsock = cast(socket_t).accept(sock, null, null);
+        if(socket_t.init == newsock)
+            throw new SocketAcceptException("Unable to accept socket connection");
+
+        Socket newSocket;
+        try
+        {
+            newSocket = accepting();
+            assert(newSocket.sock == socket_t.init);
+
+            newSocket.setSock(newsock);
+            version(Windows)
+                newSocket._blocking = _blocking;                 //inherits blocking mode
+            newSocket._family = _family;             //same family
+        }
+        catch(Throwable o)
+        {
+            _close(newsock);
+            throw o;
+        }
+
+        return newSocket;
+    }
+
+    /// Disables sends and/or receives.
+    void shutdown(SocketShutdown how) @trusted nothrow @nogc
+    {
+        .shutdown(sock, cast(int)how);
+    }
+
+
+    private static void _close(socket_t sock) @system nothrow @nogc
+    {
+        version(Windows)
+        {
+            .closesocket(sock);
+        }
+        else version(Posix)
+        {
+            .close(sock);
+        }
+    }
+
+
+    /**
+     * Immediately drop any connections and release socket resources.
+     * Calling $(D shutdown) before $(D close) is recommended for
+     * connection-oriented sockets. The $(D Socket) object is no longer
+     * usable after $(D close).
+     */
+    //calling shutdown() before this is recommended
+    //for connection-oriented sockets
+    void close() @trusted nothrow @nogc
+    {
+        _close(sock);
+        sock = socket_t.init;
+    }
+
+
+    /// Returns the local machine's host name.
+    // Idea from mango.
+    static @property string hostName() @trusted     // getter
+    {
+        char[256] result;         // Host names are limited to 255 chars.
+        if(_SOCKET_ERROR == .gethostname(result.ptr, result.length))
+            throw new SocketOSException("Unable to obtain host name");
+        return to!string(result.ptr);
+    }
+
+    /// Remote endpoint $(D Address).
+    @property Address remoteAddress() @trusted
+    {
+        Address addr = createAddress();
+        socklen_t nameLen = addr.nameLen;
+        if(_SOCKET_ERROR == .getpeername(sock, addr.name, &nameLen))
+            throw new SocketOSException("Unable to obtain remote socket address");
+        if(nameLen > addr.nameLen)
+            throw new SocketParameterException("Not enough socket address storage");
+        assert(addr.addressFamily == _family);
+        return addr;
+    }
+
+    /// Local endpoint $(D Address).
+    @property Address localAddress() @trusted
+    {
+        Address addr = createAddress();
+        socklen_t nameLen = addr.nameLen;
+        if(_SOCKET_ERROR == .getsockname(sock, addr.name, &nameLen))
+            throw new SocketOSException("Unable to obtain local socket address");
+        if(nameLen > addr.nameLen)
+            throw new SocketParameterException("Not enough socket address storage");
+        assert(addr.addressFamily == _family);
+        return addr;
+    }
+
+    /**
+     * Send or receive error code. See $(D wouldHaveBlocked),
+     * $(D lastSocketError) and $(D Socket.getErrorText) for obtaining more
+     * information about the error.
+     */
+    enum int ERROR = _SOCKET_ERROR;
+
+    /**
+     * Send data on the connection. If the socket is blocking and there is no
+     * buffer space left, $(D send) waits.
+     * Returns: The number of bytes actually sent, or $(D Socket.ERROR) on
+     * failure.
+     */
+    //returns number of bytes actually sent, or -1 on error
+    ptrdiff_t send(const(void)[] buf, SocketFlags flags) @trusted
+    {
+        static if (is(typeof(MSG_NOSIGNAL)))
+        {
+            flags = cast(SocketFlags)(flags | MSG_NOSIGNAL);
+        }
+        version( Windows )
+            auto sent = .send(sock, buf.ptr, to!int(buf.length), cast(int)flags);
+        else
+            auto sent = .send(sock, buf.ptr, buf.length, cast(int)flags);
+        return sent;
+    }
+
+    /// ditto
+    ptrdiff_t send(const(void)[] buf)
+    {
+        return send(buf, SocketFlags.NONE);
+    }
+
+    /**
+     * Send data to a specific destination Address. If the destination address is
+     * not specified, a connection must have been made and that address is used.
+     * If the socket is blocking and there is no buffer space left, $(D sendTo) waits.
+     * Returns: The number of bytes actually sent, or $(D Socket.ERROR) on
+     * failure.
+     */
+    ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags, Address to) @trusted
+    {
+        static if (is(typeof(MSG_NOSIGNAL)))
+        {
+            flags = cast(SocketFlags)(flags | MSG_NOSIGNAL);
+        }
+        version( Windows )
+            return .sendto(
+                       sock, buf.ptr, std.conv.to!int(buf.length),
+                       cast(int)flags, to.name, to.nameLen
+                       );
+        else
+            return .sendto(sock, buf.ptr, buf.length, cast(int)flags, to.name, to.nameLen);
+    }
+
+    /// ditto
+    ptrdiff_t sendTo(const(void)[] buf, Address to)
+    {
+        return sendTo(buf, SocketFlags.NONE, to);
+    }
+
+
+    //assumes you connect()ed
+    /// ditto
+    ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags) @trusted
+    {
+        static if (is(typeof(MSG_NOSIGNAL)))
+        {
+            flags = cast(SocketFlags)(flags | MSG_NOSIGNAL);
+        }
+        version(Windows)
+            return .sendto(sock, buf.ptr, to!int(buf.length), cast(int)flags, null, 0);
+        else
+            return .sendto(sock, buf.ptr, buf.length, cast(int)flags, null, 0);
+    }
+
+
+    //assumes you connect()ed
+    /// ditto
+    ptrdiff_t sendTo(const(void)[] buf)
+    {
+        return sendTo(buf, SocketFlags.NONE);
+    }
+
+
+    /**
+     * Receive data on the connection. If the socket is blocking, $(D receive)
+     * waits until there is data to be received.
+     * Returns: The number of bytes actually received, $(D 0) if the remote side
+     * has closed the connection, or $(D Socket.ERROR) on failure.
+     */
+    //returns number of bytes actually received, 0 on connection closure, or -1 on error
+    ptrdiff_t receive(void[] buf, SocketFlags flags) @trusted
+    {
+        version(Windows)         // Does not use size_t
+        {
+            return buf.length
+                   ? .recv(sock, buf.ptr, to!int(buf.length), cast(int)flags)
+                   : 0;
+        } else {
+            return buf.length
+                   ? .recv(sock, buf.ptr, buf.length, cast(int)flags)
+                   : 0;
+        }
+    }
+
+    /// ditto
+    ptrdiff_t receive(void[] buf)
+    {
+        return receive(buf, SocketFlags.NONE);
+    }
+
+    /**
+     * Receive data and get the remote endpoint $(D Address).
+     * If the socket is blocking, $(D receiveFrom) waits until there is data to
+     * be received.
+     * Returns: The number of bytes actually received, $(D 0) if the remote side
+     * has closed the connection, or $(D Socket.ERROR) on failure.
+     */
+    ptrdiff_t receiveFrom(void[] buf, SocketFlags flags, ref Address from) @trusted
+    {
+        if(!buf.length)         //return 0 and don't think the connection closed
+            return 0;
+        if (from is null || from.addressFamily != _family)
+            from = createAddress();
+        socklen_t nameLen = from.nameLen;
+        version(Windows)
+        {
+            auto read = .recvfrom(sock, buf.ptr, to!int(buf.length), cast(int)flags, from.name, &nameLen);
+            assert(from.addressFamily == _family);
+            // if(!read) //connection closed
+            return read;
+        } else {
+            auto read = .recvfrom(sock, buf.ptr, buf.length, cast(int)flags, from.name, &nameLen);
+            assert(from.addressFamily == _family);
+            // if(!read) //connection closed
+            return read;
+        }
+    }
+
+
+    /// ditto
+    ptrdiff_t receiveFrom(void[] buf, ref Address from)
+    {
+        return receiveFrom(buf, SocketFlags.NONE, from);
+    }
+
+
+    //assumes you connect()ed
+    /// ditto
+    ptrdiff_t receiveFrom(void[] buf, SocketFlags flags) @trusted
+    {
+        if(!buf.length)         //return 0 and don't think the connection closed
+            return 0;
+        version(Windows)
+        {
+            auto read = .recvfrom(sock, buf.ptr, to!int(buf.length), cast(int)flags, null, null);
+            // if(!read) //connection closed
+            return read;
+        } else {
+            auto read = .recvfrom(sock, buf.ptr, buf.length, cast(int)flags, null, null);
+            // if(!read) //connection closed
+            return read;
+        }
+    }
+
+
+    //assumes you connect()ed
+    /// ditto
+    ptrdiff_t receiveFrom(void[] buf)
+    {
+        return receiveFrom(buf, SocketFlags.NONE);
+    }
+
+
+    /// Get a socket option.
+    /// Returns: The number of bytes written to $(D result).
+    //returns the length, in bytes, of the actual result - very different from getsockopt()
+    int getOption(SocketOptionLevel level, SocketOption option, void[] result) @trusted
+    {
+        socklen_t len = cast(socklen_t) result.length;
+        if(_SOCKET_ERROR == .getsockopt(sock, cast(int)level, cast(int)option, result.ptr, &len))
+            throw new SocketOSException("Unable to get socket option");
+        return len;
+    }
+
+
+    /// Common case of getting integer and boolean options.
+    int getOption(SocketOptionLevel level, SocketOption option, out int32_t result) @trusted
+    {
+        return getOption(level, option, (&result)[0 .. 1]);
+    }
+
+
+    /// Get the linger option.
+    int getOption(SocketOptionLevel level, SocketOption option, out Linger result) @trusted
+    {
+        //return getOption(cast(SocketOptionLevel)SocketOptionLevel.SOCKET, SocketOption.LINGER, (&result)[0 .. 1]);
+        return getOption(level, option, (&result.clinger)[0 .. 1]);
+    }
+
+    /// Get a timeout (duration) option.
+    void getOption(SocketOptionLevel level, SocketOption option, out Duration result) @trusted
+    {
+        enforce(option == SocketOption.SNDTIMEO || option == SocketOption.RCVTIMEO,
+                new SocketParameterException("Not a valid timeout option: " ~ to!string(option)));
+        // WinSock returns the timeout values as a milliseconds DWORD,
+        // while Linux and BSD return a timeval struct.
+        version (Windows)
+        {
+            int msecs;
+            getOption(level, option, (&msecs)[0 .. 1]);
+            if (option == SocketOption.RCVTIMEO)
+                msecs += WINSOCK_TIMEOUT_SKEW;
+            result = dur!"msecs"(msecs);
+        }
+        else version (Posix)
+        {
+            TimeVal tv;
+            getOption(level, option, (&tv.ctimeval)[0..1]);
+            result = dur!"seconds"(tv.seconds) + dur!"usecs"(tv.microseconds);
+        }
+        else static assert(false);
+    }
+
+    /// Set a socket option.
+    void setOption(SocketOptionLevel level, SocketOption option, void[] value) @trusted
+    {
+        if(_SOCKET_ERROR == .setsockopt(sock, cast(int)level,
+                                        cast(int)option, value.ptr, cast(uint) value.length))
+            throw new SocketOSException("Unable to set socket option");
+    }
+
+
+    /// Common case for setting integer and boolean options.
+    void setOption(SocketOptionLevel level, SocketOption option, int32_t value) @trusted
+    {
+        setOption(level, option, (&value)[0 .. 1]);
+    }
+
+
+    /// Set the linger option.
+    void setOption(SocketOptionLevel level, SocketOption option, Linger value) @trusted
+    {
+        //setOption(cast(SocketOptionLevel)SocketOptionLevel.SOCKET, SocketOption.LINGER, (&value)[0 .. 1]);
+        setOption(level, option, (&value.clinger)[0 .. 1]);
+    }
+
+    /**
+     * Sets a timeout (duration) option, i.e. $(D SocketOption.SNDTIMEO) or
+     * $(D RCVTIMEO). Zero indicates no timeout.
+     *
+     * In a typical application, you might also want to consider using
+     * a non-blocking socket instead of setting a timeout on a blocking one.
+     *
+     * Note: While the receive timeout setting is generally quite accurate
+     * on *nix systems even for smaller durations, there are two issues to
+     * be aware of on Windows: First, although undocumented, the effective
+     * timeout duration seems to be the one set on the socket plus half
+     * a second. $(D setOption()) tries to compensate for that, but still,
+     * timeouts under 500ms are not possible on Windows. Second, be aware
+     * that the actual amount of time spent until a blocking call returns
+     * randomly varies on the order of 10ms.
+     *
+     * Params:
+     *   level  = The level at which a socket option is defined.
+     *   option = Either $(D SocketOption.SNDTIMEO) or $(D SocketOption.RCVTIMEO).
+     *   value  = The timeout duration to set. Must not be negative.
+     *
+     * Throws: $(D SocketException) if setting the options fails.
+     *
+     * Example:
+     * ---
+     * import std.datetime;
+     * auto pair = socketPair();
+     * scope(exit) foreach (s; pair) s.close();
+     *
+     * // Set a receive timeout, and then wait at one end of
+     * // the socket pair, knowing that no data will arrive.
+     * pair[0].setOption(SocketOptionLevel.SOCKET,
+     *     SocketOption.RCVTIMEO, dur!"seconds"(1));
+     *
+     * auto sw = StopWatch(AutoStart.yes);
+     * ubyte[1] buffer;
+     * pair[0].receive(buffer);
+     * writefln("Waited %s ms until the socket timed out.",
+     *     sw.peek.msecs);
+     * ---
+     */
+    void setOption(SocketOptionLevel level, SocketOption option, Duration value) @trusted
+    {
+        enforce(option == SocketOption.SNDTIMEO || option == SocketOption.RCVTIMEO,
+                new SocketParameterException("Not a valid timeout option: " ~ to!string(option)));
+
+        enforce(value >= dur!"hnsecs"(0), new SocketParameterException(
+                    "Timeout duration must not be negative."));
+
+        version (Windows)
+        {
+            import std.algorithm.comparison : max;
+
+            auto msecs = to!int(value.total!"msecs");
+            if (msecs != 0 && option == SocketOption.RCVTIMEO)
+                msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);
+            setOption(level, option, msecs);
+        }
+        else version (Posix)
+        {
+            _ctimeval tv;
+            value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
+            setOption(level, option, (&tv)[0 .. 1]);
+        }
+        else static assert(false);
+    }
+
+    /// Get a text description of this socket's error status, and clear the
+    /// socket's error status.
+    string getErrorText()
+    {
+        int32_t error;
+        getOption(SocketOptionLevel.SOCKET, SocketOption.ERROR, error);
+        return formatSocketError(error);
+    }
+
+    /**
+     * Enables TCP keep-alive with the specified parameters.
+     *
+     * Params:
+     *   time     = Number of seconds with no activity until the first
+     *              keep-alive packet is sent.
+     *   interval = Number of seconds between when successive keep-alive
+     *              packets are sent if no acknowledgement is received.
+     *
+     * Throws: $(D SocketOSException) if setting the options fails, or
+     * $(D SocketFeatureException) if setting keep-alive parameters is
+     * unsupported on the current platform.
+     */
+    void setKeepAlive(int time, int interval) @trusted
+    {
+        version(Windows)
+        {
+            tcp_keepalive options;
+            options.onoff = 1;
+            options.keepalivetime = time * 1000;
+            options.keepaliveinterval = interval * 1000;
+            uint cbBytesReturned;
+            enforce(WSAIoctl(sock, SIO_KEEPALIVE_VALS,
+                             &options, options.sizeof,
+                             null, 0,
+                             &cbBytesReturned, null, null) == 0,
+                    new SocketOSException("Error setting keep-alive"));
+        }
+        else
+        static if (is(typeof(TCP_KEEPIDLE)) && is(typeof(TCP_KEEPINTVL)))
+        {
+            setOption(SocketOptionLevel.TCP, cast(SocketOption)TCP_KEEPIDLE, time);
+            setOption(SocketOptionLevel.TCP, cast(SocketOption)TCP_KEEPINTVL, interval);
+            setOption(SocketOptionLevel.SOCKET, SocketOption.KEEPALIVE, true);
+        }
+        else
+            throw new SocketFeatureException("Setting keep-alive options " ~
+                "is not supported on this platform");
+    }
+
+    /**
+     * Wait for a socket to change status. A wait timeout of $(Duration) or
+     * $(D TimeVal), may be specified; if a timeout is not specified or the
+     * $(D TimeVal) is $(D null), the maximum timeout is used. The $(D TimeVal)
+     * timeout has an unspecified value when $(D select) returns.
+     * Returns: The number of sockets with status changes, $(D 0) on timeout,
+     * or $(D -1) on interruption. If the return value is greater than $(D 0),
+     * the $(D SocketSets) are updated to only contain the sockets having status
+     * changes. For a connecting socket, a write status change means the
+     * connection is established and it's able to send. For a listening socket,
+     * a read status change means there is an incoming connection request and
+     * it's able to accept.
+     */
+    //SocketSet's updated to include only those sockets which an event occured
+    //returns the number of events, 0 on timeout, or -1 on interruption
+    //for a connect()ing socket, writeability means connected
+    //for a listen()ing socket, readability means listening
+    //Winsock: possibly internally limited to 64 sockets per set
+    static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError, Duration timeout) @trusted
+    {
+        auto vals = timeout.split!("seconds", "usecs")();
+        TimeVal tv;
+        tv.seconds      = cast(tv.tv_sec_t )vals.seconds;
+        tv.microseconds = cast(tv.tv_usec_t)vals.usecs;
+        return select(checkRead, checkWrite, checkError, &tv);
+    }
+
+    /// ditto
+    //maximum timeout
+    static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError)
+    {
+        return select(checkRead, checkWrite, checkError, null);
+    }
+
+    /// Ditto
+    static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError, TimeVal* timeout) @trusted
+    in
+    {
+        //make sure none of the SocketSet's are the same object
+        if(checkRead)
+        {
+            assert(checkRead !is checkWrite);
+            assert(checkRead !is checkError);
+        }
+        if(checkWrite)
+        {
+            assert(checkWrite !is checkError);
+        }
+    }
+    body
+    {
+        fd_set* fr, fw, fe;
+        int n = 0;
+
+        version(Windows)
+        {
+            // Windows has a problem with empty fd_set`s that aren't null.
+            fr = checkRead  && checkRead.count  ? checkRead.toFd_set()  : null;
+            fw = checkWrite && checkWrite.count ? checkWrite.toFd_set() : null;
+            fe = checkError && checkError.count ? checkError.toFd_set() : null;
+        }
+        else
+        {
+            if(checkRead)
+            {
+                fr = checkRead.toFd_set();
+                n = checkRead.selectn();
+            }
+            else
+            {
+                fr = null;
+            }
+
+            if(checkWrite)
+            {
+                fw = checkWrite.toFd_set();
+                int _n;
+                _n = checkWrite.selectn();
+                if(_n > n)
+                    n = _n;
+            }
+            else
+            {
+                fw = null;
+            }
+
+            if(checkError)
+            {
+                fe = checkError.toFd_set();
+                int _n;
+                _n = checkError.selectn();
+                if(_n > n)
+                    n = _n;
+            }
+            else
+            {
+                fe = null;
+            }
+
+            // Make sure the sets' capacity matches, to avoid select reading
+            // out of bounds just because one set was bigger than another
+            if (checkRead ) checkRead .setMinCapacity(n);
+            if (checkWrite) checkWrite.setMinCapacity(n);
+            if (checkError) checkError.setMinCapacity(n);
+        }
+
+        int result = .select(n, fr, fw, fe, &timeout.ctimeval);
+
+        version(Windows)
+        {
+            if(_SOCKET_ERROR == result && WSAGetLastError() == WSAEINTR)
+                return -1;
+        }
+        else version(Posix)
+        {
+            if(_SOCKET_ERROR == result && errno == EINTR)
+                return -1;
+        }
+        else
+        {
+            static assert(0);
+        }
+
+        if(_SOCKET_ERROR == result)
+            throw new SocketOSException("Socket select error");
+
+        return result;
+    }
+
+
+    /// Returns a new Address object for the current address family.
+    /// Can be overridden to support other addresses.
+    protected Address createAddress() pure nothrow
+    {
+        Address result;
+        switch(_family)
+        {
+        static if (is(sockaddr_un))
+        {
+            case AddressFamily.UNIX:
+                result = new UnixAddress;
+                break;
+        }
+
+        case AddressFamily.INET:
+            result = new InternetAddress;
+            break;
+
+        case AddressFamily.INET6:
+            result = new Internet6Address;
+            break;
+
+        default:
+            result = new UnknownAddress;
+        }
+        return result;
+    }
+
+}
+
+
+/// $(D TcpSocket) is a shortcut class for a TCP Socket.
+class TcpSocket: Socket
+{
+    /// Constructs a blocking TCP Socket.
+    this(AddressFamily family)
+    {
+        super(family, SocketType.STREAM, ProtocolType.TCP);
+    }
+
+    /// Constructs a blocking IPv4 TCP Socket.
+    this()
+    {
+        this(AddressFamily.INET);
+    }
+
+
+    //shortcut
+    /// Constructs a blocking TCP Socket and connects to an $(D Address).
+    this(Address connectTo)
+    {
+        this(connectTo.addressFamily);
+        connect(connectTo);
+    }
+}
+
+
+/// $(D UdpSocket) is a shortcut class for a UDP Socket.
+class UdpSocket: Socket
+{
+    /// Constructs a blocking UDP Socket.
+    this(AddressFamily family)
+    {
+        super(family, SocketType.DGRAM, ProtocolType.UDP);
+    }
+
+
+    /// Constructs a blocking IPv4 UDP Socket.
+    this()
+    {
+        this(AddressFamily.INET);
+    }
+}
+
+/**
+ * Creates a pair of connected sockets.
+ *
+ * The two sockets are indistinguishable.
+ *
+ * Throws: $(D SocketException) if creation of the sockets fails.
+ *
+ * Example:
+ * ---
+ * immutable ubyte[] data = [1, 2, 3, 4];
+ * auto pair = socketPair();
+ * scope(exit) foreach (s; pair) s.close();
+ *
+ * pair[0].send(data);
+ *
+ * auto buf = new ubyte[data.length];
+ * pair[1].receive(buf);
+ * assert(buf == data);
+ * ---
+ */
+Socket[2] socketPair() @trusted
+{
+    version(Posix)
+    {
+        int[2] socks;
+        if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1)
+            throw new SocketOSException("Unable to create socket pair");
+
+        Socket toSocket(size_t id)
+        {
+            auto s = new Socket;
+            s.setSock(cast(socket_t)socks[id]);
+            s._family = AddressFamily.UNIX;
+            return s;
+        }
+
+        return [toSocket(0), toSocket(1)];
+    }
+    else version(Windows)
+    {
+        // We do not have socketpair() on Windows, just manually create a
+        // pair of sockets connected over some localhost port.
+        Socket[2] result;
+
+        auto listener = new TcpSocket();
+        listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
+        listener.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY));
+        auto addr = listener.localAddress;
+        listener.listen(1);
+
+        result[0] = new TcpSocket(addr);
+        result[1] = listener.accept();
+
+        listener.close();
+        return result;
+    }
+    else
+        static assert(false);
+}
+
+unittest
+{
+    immutable ubyte[] data = [1, 2, 3, 4];
+    auto pair = socketPair();
+    scope(exit) foreach (s; pair) s.close();
+
+    pair[0].send(data);
+
+    auto buf = new ubyte[data.length];
+    pair[1].receive(buf);
+    assert(buf == data);
+}
diff --git a/libphobos/src/std/socketstream.d b/libphobos/src/std/socketstream.d
new file mode 100644
index 00000000000..59648618c99
--- /dev/null
+++ b/libphobos/src/std/socketstream.d
@@ -0,0 +1,148 @@
+// Written in the D programming language
+
+/*
+        Copyright (C) 2004 Christopher E. Miller
+
+        This software is provided 'as-is', without any express or implied
+        warranty.  In no event will the authors be held liable for any damages
+        arising from the use of this software.
+
+        Permission is granted to anyone to use this software for any purpose,
+        including commercial applications, and to alter it and redistribute it
+        freely, subject to the following restrictions:
+
+        1. The origin of this software must not be misrepresented; you must not
+           claim that you wrote the original software. If you use this software
+           in a product, an acknowledgment in the product documentation would be
+           appreciated but is not required.
+        2. Altered source versions must be plainly marked as such, and must not be
+           misrepresented as being the original software.
+        3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**************
+ * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
+ *       current standards. It will be remove in October 2016.)
+ *
+ * $(D SocketStream) is a stream for a blocking,
+ * connected $(D Socket).
+ *
+ * Example:
+ *      See $(SAMPLESRC htmlget.d)
+ * Authors: Christopher E. Miller
+ * References:
+ *      $(LINK2 std_stream.html, std.stream)
+ * Source:    $(PHOBOSSRC std/_socketstream.d)
+ * Macros: WIKI=Phobos/StdSocketstream
+ */
+deprecated("It will be removed from Phobos in October 2016. If you still need it, go to https://github.com/DigitalMars/undeaD") module std.socketstream;
+// @@@DEPRECATED_2016-10@@@
+
+private import std.stream;
+private import std.socket;
+
+/**************
+ * $(D SocketStream) is a stream for a blocking,
+ * connected $(D Socket).
+ */
+class SocketStream: Stream
+{
+    private:
+        Socket sock;
+
+    public:
+
+        /**
+         * Constructs a SocketStream with the specified Socket and FileMode flags.
+         */
+        this(Socket sock, FileMode mode)
+        {
+            if(mode & FileMode.In)
+                readable = true;
+            if(mode & FileMode.Out)
+                writeable = true;
+
+            this.sock = sock;
+        }
+
+        /**
+         * Uses mode $(D FileMode.In | FileMode.Out).
+         */
+        this(Socket sock)
+        {
+            writeable = readable = true;
+            this.sock = sock;
+        }
+
+        /**
+         * Property to get the $(D Socket) that is being streamed.
+         */
+        Socket socket()
+        {
+            return sock;
+        }
+
+        /**
+         * Attempts to read the entire block, waiting if necessary.
+         */
+        override size_t readBlock(void* _buffer, size_t size)
+        {
+            ubyte* buffer = cast(ubyte*)_buffer;
+            assertReadable();
+
+            if (size == 0)
+                return size;
+
+            auto len = sock.receive(buffer[0 .. size]);
+            readEOF = cast(bool)(len == 0);
+            if (len == sock.ERROR)
+                len = 0;
+            return len;
+        }
+
+        /**
+         * Attempts to write the entire block, waiting if necessary.
+         */
+        override size_t writeBlock(const void* _buffer, size_t size)
+        {
+            ubyte* buffer = cast(ubyte*)_buffer;
+            assertWriteable();
+
+            if (size == 0)
+                return size;
+
+            auto len = sock.send(buffer[0 .. size]);
+            readEOF = cast(bool)(len == 0);
+            if (len == sock.ERROR)
+                len = 0;
+            return len;
+        }
+
+        /**
+         * Socket streams do not support seeking. This disabled method throws
+         * a $(D SeekException).
+         */
+        @disable override ulong seek(long offset, SeekPos whence)
+        {
+            throw new SeekException("Cannot seek a socket.");
+        }
+
+        /**
+         * Does not return the entire stream because that would
+         * require the remote connection to be closed.
+         */
+        override string toString()
+        {
+            return sock.toString();
+        }
+
+        /**
+         * Close the $(D Socket).
+         */
+        override void close()
+        {
+            sock.close();
+            super.close();
+        }
+}
+
diff --git a/libphobos/src/std/stdint.d b/libphobos/src/std/stdint.d
new file mode 100644
index 00000000000..f0740f04e7d
--- /dev/null
+++ b/libphobos/src/std/stdint.d
@@ -0,0 +1,131 @@
+// Written in the D programming language.
+
+/**
+ *
+    D constrains integral types to specific sizes. But efficiency
+    of different sizes varies from machine to machine,
+    pointer sizes vary, and the maximum integer size varies.
+    <b>stdint</b> offers a portable way of trading off size
+    vs efficiency, in a manner compatible with the <tt>stdint.h</tt>
+    definitions in C.
+
+    The exact aliases are types of exactly the specified number of bits.
+    The at least aliases are at least the specified number of bits
+    large, and can be larger.
+    The fast aliases are the fastest integral type supported by the
+    processor that is at least as wide as the specified number of bits.
+
+    The aliases are:
+
+    $(ATABLE $(TR
+    $(TH Exact Alias)
+    $(TH Description)
+    $(TH At Least Alias)
+    $(TH Description)
+    $(TH Fast Alias)
+    $(TH Description)
+    )$(TR
+    $(TD int8_t)
+    $(TD exactly 8 bits signed)
+    $(TD int_least8_t)
+    $(TD at least 8 bits signed)
+    $(TD int_fast8_t)
+    $(TD fast 8 bits signed)
+    )$(TR
+    $(TD uint8_t)
+    $(TD exactly 8 bits unsigned)
+    $(TD uint_least8_t)
+    $(TD at least 8 bits unsigned)
+    $(TD uint_fast8_t)
+    $(TD fast 8 bits unsigned)
+
+    )$(TR
+    $(TD int16_t)
+    $(TD exactly 16 bits signed)
+    $(TD int_least16_t)
+    $(TD at least 16 bits signed)
+    $(TD int_fast16_t)
+    $(TD fast 16 bits signed)
+    )$(TR
+    $(TD uint16_t)
+    $(TD exactly 16 bits unsigned)
+    $(TD uint_least16_t)
+    $(TD at least 16 bits unsigned)
+    $(TD uint_fast16_t)
+    $(TD fast 16 bits unsigned)
+
+    )$(TR
+    $(TD int32_t)
+    $(TD exactly 32 bits signed)
+    $(TD int_least32_t)
+    $(TD at least 32 bits signed)
+    $(TD int_fast32_t)
+    $(TD fast 32 bits signed)
+    )$(TR
+    $(TD uint32_t)
+    $(TD exactly 32 bits unsigned)
+    $(TD uint_least32_t)
+    $(TD at least 32 bits unsigned)
+    $(TD uint_fast32_t)
+    $(TD fast 32 bits unsigned)
+
+    )$(TR
+    $(TD int64_t)
+    $(TD exactly 64 bits signed)
+    $(TD int_least64_t)
+    $(TD at least 64 bits signed)
+    $(TD int_fast64_t)
+    $(TD fast 64 bits signed)
+    )$(TR
+    $(TD uint64_t)
+    $(TD exactly 64 bits unsigned)
+    $(TD uint_least64_t)
+    $(TD at least 64 bits unsigned)
+    $(TD uint_fast64_t)
+    $(TD fast 64 bits unsigned)
+    ))
+
+    The ptr aliases are integral types guaranteed to be large enough
+    to hold a pointer without losing bits:
+
+    $(ATABLE $(TR
+    $(TH Alias)
+    $(TH Description)
+    )$(TR
+    $(TD intptr_t)
+    $(TD signed integral type large enough to hold a pointer)
+    )$(TR
+    $(TD uintptr_t)
+    $(TD unsigned integral type large enough to hold a pointer)
+    ))
+
+    The max aliases are the largest integral types:
+
+    $(ATABLE $(TR
+    $(TH Alias)
+    $(TH Description)
+    )$(TR
+    $(TD intmax_t)
+    $(TD the largest signed integral type)
+    )$(TR
+    $(TD uintmax_t)
+    $(TD the largest unsigned integral type)
+    ))
+
+ * Macros:
+ *  WIKI=Phobos/StdStdint
+ *  ATABLE=<table border="1" cellspacing="0" cellpadding="5">$0</table>
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_stdint.d)
+ */
+/*          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.stdint;
+
+public import core.stdc.stdint;
diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d
new file mode 100644
index 00000000000..85b5250a0b3
--- /dev/null
+++ b/libphobos/src/std/stdio.d
@@ -0,0 +1,4611 @@
+﻿// Written in the D programming language.
+
+/**
+Standard I/O functions that extend $(B core.stdc.stdio).  $(B core.stdc.stdio)
+is $(D_PARAM public)ally imported when importing $(B std.stdio).
+
+Source: $(PHOBOSSRC std/_stdio.d)
+Macros:
+WIKI=Phobos/StdStdio
+
+Copyright: Copyright Digital Mars 2007-.
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB digitalmars.com, Walter Bright),
+           $(WEB erdani.org, Andrei Alexandrescu),
+           Alex Rønne Petersen
+ */
+module std.stdio;
+
+public import core.stdc.stdio;
+import std.typecons;// Flag
+import std.stdiobase;
+import core.stdc.stddef;// wchar_t
+import std.range.primitives;// empty, front, isBidirectionalRange
+import std.traits;// Unqual, isSomeChar, isSomeString
+
+
+/++
+If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter
+is included in the strings returned.
++/
+alias KeepTerminator = Flag!"keepTerminator";
+
+version (CRuntime_Microsoft)
+{
+    version = MICROSOFT_STDIO;
+}
+else version (CRuntime_DigitalMars)
+{
+    // Specific to the way Digital Mars C does stdio
+    version = DIGITAL_MARS_STDIO;
+}
+
+version (CRuntime_Glibc)
+{
+    // Specific to the way Gnu C does stdio
+    version = GCC_IO;
+    version = HAS_GETDELIM;
+}
+
+version (OSX)
+{
+    version = GENERIC_IO;
+    version = HAS_GETDELIM;
+}
+
+version (FreeBSD)
+{
+    version = GENERIC_IO;
+    version = HAS_GETDELIM;
+}
+
+version (NetBSD)
+{
+    version = GENERIC_IO;
+    version = HAS_GETDELIM;
+}
+
+version (Solaris)
+{
+    version = GENERIC_IO;
+    version = NO_GETDELIM;
+}
+
+version (CRuntime_Bionic)
+{
+    version = GENERIC_IO;
+    version = NO_GETDELIM;
+}
+
+// Character type used for operating system filesystem APIs
+version (Windows)
+{
+    private alias FSChar = wchar;
+}
+else version (Posix)
+{
+    private alias FSChar = char;
+}
+else
+    static assert(0);
+
+version(Windows)
+{
+    // core.stdc.stdio.fopen expects file names to be
+    // encoded in CP_ACP on Windows instead of UTF-8.
+    /+ Waiting for druntime pull 299
+    +/
+    extern (C) nothrow @nogc FILE* _wfopen(in wchar* filename, in wchar* mode);
+
+    import core.sys.windows.windows : HANDLE;
+}
+
+version (DIGITAL_MARS_STDIO)
+{
+    extern (C)
+    {
+        /* **
+         * Digital Mars under-the-hood C I/O functions.
+         * Use _iobuf* for the unshared version of FILE*,
+         * usable when the FILE is locked.
+         */
+      nothrow:
+      @nogc:
+        int _fputc_nlock(int, _iobuf*);
+        int _fputwc_nlock(int, _iobuf*);
+        int _fgetc_nlock(_iobuf*);
+        int _fgetwc_nlock(_iobuf*);
+        int __fp_lock(FILE*);
+        void __fp_unlock(FILE*);
+
+        int setmode(int, int);
+    }
+    alias FPUTC = _fputc_nlock;
+    alias FPUTWC = _fputwc_nlock;
+    alias FGETC = _fgetc_nlock;
+    alias FGETWC = _fgetwc_nlock;
+
+    alias FLOCK = __fp_lock;
+    alias FUNLOCK = __fp_unlock;
+
+    alias _setmode = setmode;
+    enum _O_BINARY = 0x8000;
+    int _fileno(FILE* f) { return f._file; }
+    alias fileno = _fileno;
+}
+else version (MICROSOFT_STDIO)
+{
+    extern (C)
+    {
+        /* **
+         * Microsoft under-the-hood C I/O functions
+         */
+      nothrow:
+      @nogc:
+        int _fputc_nolock(int, _iobuf*);
+        int _fputwc_nolock(int, _iobuf*);
+        int _fgetc_nolock(_iobuf*);
+        int _fgetwc_nolock(_iobuf*);
+        void _lock_file(FILE*);
+        void _unlock_file(FILE*);
+        int _setmode(int, int);
+        int _fileno(FILE*);
+        FILE* _fdopen(int, const (char)*);
+        int _fseeki64(FILE*, long, int);
+        long _ftelli64(FILE*);
+    }
+    alias FPUTC = _fputc_nolock;
+    alias FPUTWC = _fputwc_nolock;
+    alias FGETC = _fgetc_nolock;
+    alias FGETWC = _fgetwc_nolock;
+
+    alias FLOCK = _lock_file;
+    alias FUNLOCK = _unlock_file;
+
+    enum
+    {
+        _O_RDONLY = 0x0000,
+        _O_APPEND = 0x0004,
+        _O_TEXT   = 0x4000,
+        _O_BINARY = 0x8000,
+    }
+}
+else version (GCC_IO)
+{
+    /* **
+     * Gnu under-the-hood C I/O functions; see
+     * http://gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html
+     */
+    extern (C)
+    {
+      nothrow:
+      @nogc:
+        int fputc_unlocked(int, _iobuf*);
+        int fputwc_unlocked(wchar_t, _iobuf*);
+        int fgetc_unlocked(_iobuf*);
+        int fgetwc_unlocked(_iobuf*);
+        void flockfile(FILE*);
+        void funlockfile(FILE*);
+
+        private size_t fwrite_unlocked(const(void)* ptr,
+                size_t size, size_t n, _iobuf *stream);
+    }
+
+    alias FPUTC = fputc_unlocked;
+    alias FPUTWC = fputwc_unlocked;
+    alias FGETC = fgetc_unlocked;
+    alias FGETWC = fgetwc_unlocked;
+
+    alias FLOCK = flockfile;
+    alias FUNLOCK = funlockfile;
+}
+else version (GENERIC_IO)
+{
+    nothrow:
+    @nogc:
+
+    extern (C)
+    {
+        void flockfile(FILE*);
+        void funlockfile(FILE*);
+    }
+
+    int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); }
+    int fputwc_unlocked(wchar_t c, _iobuf* fp)
+    {
+        import core.stdc.wchar_ : fputwc;
+        return fputwc(c, cast(shared) fp);
+    }
+    int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); }
+    int fgetwc_unlocked(_iobuf* fp)
+    {
+        import core.stdc.wchar_ : fgetwc;
+        return fgetwc(cast(shared) fp);
+    }
+
+    alias FPUTC = fputc_unlocked;
+    alias FPUTWC = fputwc_unlocked;
+    alias FGETC = fgetc_unlocked;
+    alias FGETWC = fgetwc_unlocked;
+
+    alias FLOCK = flockfile;
+    alias FUNLOCK = funlockfile;
+}
+else
+{
+    static assert(0, "unsupported C I/O system");
+}
+
+version(HAS_GETDELIM) extern(C) nothrow @nogc
+{
+    ptrdiff_t getdelim(char**, size_t*, int, FILE*);
+    // getline() always comes together with getdelim()
+    ptrdiff_t getline(char**, size_t*, FILE*);
+}
+
+//------------------------------------------------------------------------------
+struct ByRecord(Fields...)
+{
+private:
+    import std.typecons : Tuple;
+
+    File file;
+    char[] line;
+    Tuple!(Fields) current;
+    string format;
+
+public:
+    this(File f, string format)
+    {
+        assert(f.isOpen);
+        file = f;
+        this.format = format;
+        popFront(); // prime the range
+    }
+
+    /// Range primitive implementations.
+    @property bool empty()
+    {
+        return !file.isOpen;
+    }
+
+    /// Ditto
+    @property ref Tuple!(Fields) front()
+    {
+        return current;
+    }
+
+    /// Ditto
+    void popFront()
+    {
+        import std.conv : text;
+        import std.exception : enforce;
+        import std.format : formattedRead;
+        import std.string : chomp;
+
+        enforce(file.isOpen, "ByRecord: File must be open");
+        file.readln(line);
+        if (!line.length)
+        {
+            file.detach();
+        }
+        else
+        {
+            line = chomp(line);
+            formattedRead(line, format, &current);
+            enforce(line.empty, text("Leftover characters in record: `",
+                            line, "'"));
+        }
+    }
+}
+
+template byRecord(Fields...)
+{
+    ByRecord!(Fields) byRecord(File f, string format)
+    {
+        return typeof(return)(f, format);
+    }
+}
+
+/**
+Encapsulates a $(D FILE*). Generally D does not attempt to provide
+thin wrappers over equivalent functions in the C standard library, but
+manipulating $(D FILE*) values directly is unsafe and error-prone in
+many ways. The $(D File) type ensures safe manipulation, automatic
+file closing, and a lot of convenience.
+
+The underlying $(D FILE*) handle is maintained in a reference-counted
+manner, such that as soon as the last $(D File) variable bound to a
+given $(D FILE*) goes out of scope, the underlying $(D FILE*) is
+automatically closed.
+
+Example:
+----
+// test.d
+void main(string[] args)
+{
+    auto f = File("test.txt", "w"); // open for writing
+    f.write("Hello");
+    if (args.length > 1)
+    {
+        auto g = f; // now g and f write to the same file
+                    // internal reference count is 2
+        g.write(", ", args[1]);
+        // g exits scope, reference count decreases to 1
+    }
+    f.writeln("!");
+    // f exits scope, reference count falls to zero,
+    // underlying $(D FILE*) is closed.
+}
+----
+$(CONSOLE
+% rdmd test.d Jimmy
+% cat test.txt
+Hello, Jimmy!
+% __
+)
+ */
+struct File
+{
+    import std.traits : isScalarType, isArray;
+    import std.range.primitives : ElementEncodingType;
+    enum Orientation { unknown, narrow, wide }
+
+    private struct Impl
+    {
+        FILE * handle = null; // Is null iff this Impl is closed by another File
+        uint refs = uint.max / 2;
+        bool isPopened; // true iff the stream has been created by popen()
+        Orientation orientation;
+    }
+    private Impl* _p;
+    private string _name;
+
+    package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted
+    {
+        import core.stdc.stdlib : malloc;
+        import std.exception : enforce;
+
+        assert(!_p);
+        _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory");
+        _p.handle = handle;
+        _p.refs = refs;
+        _p.isPopened = isPopened;
+        _p.orientation = Orientation.unknown;
+        _name = name;
+    }
+
+/**
+Constructor taking the name of the file to open and the open mode.
+
+Copying one $(D File) object to another results in the two $(D File)
+objects referring to the same underlying file.
+
+The destructor automatically closes the file as soon as no $(D File)
+object refers to it anymore.
+
+Params:
+    name = range or string representing the file _name
+    stdioOpenmode = range or string represting the open mode
+        (with the same semantics as in the C standard library
+        $(WEB cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen)
+        function)
+
+Throws: $(D ErrnoException) if the file could not be opened.
+ */
+    this(string name, in char[] stdioOpenmode = "rb") @safe
+    {
+        import std.conv : text;
+        import std.exception : errnoEnforce;
+
+        this(errnoEnforce(.fopen(name, stdioOpenmode),
+                        text("Cannot open file `", name, "' in mode `",
+                                stdioOpenmode, "'")),
+                name);
+
+        // MSVCRT workaround (issue 14422)
+        version (MICROSOFT_STDIO)
+        {
+            bool append, update;
+            foreach (c; stdioOpenmode)
+                if (c == 'a')
+                    append = true;
+                else
+                if (c == '+')
+                    update = true;
+            if (append && !update)
+                seek(size);
+        }
+    }
+
+    /// ditto
+    this(R1, R2)(R1 name)
+        if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1))
+    {
+        import std.conv : to;
+        this(name.to!string, "rb");
+    }
+
+    /// ditto
+    this(R1, R2)(R1 name, R2 mode)
+        if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) &&
+            isInputRange!R2 && isSomeChar!(ElementEncodingType!R2))
+    {
+        import std.conv : to;
+        this(name.to!string, mode.to!string);
+    }
+
+    @safe unittest
+    {
+        static import std.file;
+        import std.utf : byChar;
+        auto deleteme = testFilename();
+        auto f = File(deleteme.byChar, "w".byChar);
+        f.close();
+        std.file.remove(deleteme);
+    }
+
+    ~this() @safe
+    {
+        detach();
+    }
+
+    this(this) @safe nothrow
+    {
+        if (!_p) return;
+        assert(_p.refs);
+        ++_p.refs;
+    }
+
+/**
+Assigns a file to another. The target of the assignment gets detached
+from whatever file it was attached to, and attaches itself to the new
+file.
+ */
+    void opAssign(File rhs) @safe
+    {
+        import std.algorithm : swap;
+
+        swap(this, rhs);
+    }
+
+/**
+First calls $(D detach) (throwing on failure), and then attempts to
+_open file $(D name) with mode $(D stdioOpenmode). The mode has the
+same semantics as in the C standard library $(WEB
+cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function.
+
+Throws: $(D ErrnoException) in case of error.
+ */
+    void open(string name, in char[] stdioOpenmode = "rb") @safe
+    {
+        detach();
+        this = File(name, stdioOpenmode);
+    }
+
+/**
+First calls $(D detach) (throwing on failure), and then runs a command
+by calling the C standard library function $(WEB
+opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen).
+
+Throws: $(D ErrnoException) in case of error.
+ */
+    version(Posix) void popen(string command, in char[] stdioOpenmode = "r") @safe
+    {
+        import std.exception : errnoEnforce;
+
+        detach();
+        this = File(errnoEnforce(.popen(command, stdioOpenmode),
+                        "Cannot run command `"~command~"'"),
+                command, 1, true);
+    }
+
+/**
+First calls $(D detach) (throwing on failure), and then attempts to
+associate the given file descriptor with the $(D File). The mode must
+be compatible with the mode of the file descriptor.
+
+Throws: $(D ErrnoException) in case of error.
+ */
+    void fdopen(int fd, in char[] stdioOpenmode = "rb") @safe
+    {
+        fdopen(fd, stdioOpenmode, null);
+    }
+
+    package void fdopen(int fd, in char[] stdioOpenmode, string name) @trusted
+    {
+        import std.internal.cstring : tempCString;
+        import std.exception : errnoEnforce;
+
+        auto modez = stdioOpenmode.tempCString();
+        detach();
+
+        version (DIGITAL_MARS_STDIO)
+        {
+            // This is a re-implementation of DMC's fdopen, but without the
+            // mucking with the file descriptor.  POSIX standard requires the
+            // new fdopen'd file to retain the given file descriptor's
+            // position.
+            import core.stdc.stdio : fopen;
+            auto fp = fopen("NUL", modez);
+            errnoEnforce(fp, "Cannot open placeholder NUL stream");
+            FLOCK(fp);
+            auto iob = cast(_iobuf*)fp;
+            .close(iob._file);
+            iob._file = fd;
+            iob._flag &= ~_IOTRAN;
+            FUNLOCK(fp);
+        }
+        else
+        {
+            version (Windows) // MSVCRT
+                auto fp = _fdopen(fd, modez);
+            else version (Posix)
+            {
+                import core.sys.posix.stdio : fdopen;
+                auto fp = fdopen(fd, modez);
+            }
+            errnoEnforce(fp);
+        }
+        this = File(fp, name);
+    }
+
+    // Declare a dummy HANDLE to allow generating documentation
+    // for Windows-only methods.
+    version(StdDdoc) { version(Windows) {} else alias HANDLE = int; }
+
+/**
+First calls $(D detach) (throwing on failure), and then attempts to
+associate the given Windows $(D HANDLE) with the $(D File). The mode must
+be compatible with the access attributes of the handle. Windows only.
+
+Throws: $(D ErrnoException) in case of error.
+*/
+    version(StdDdoc)
+    void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode);
+
+    version(Windows)
+    void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode)
+    {
+        import std.exception : errnoEnforce;
+        import std.format : format;
+
+        // Create file descriptors from the handles
+        version (DIGITAL_MARS_STDIO)
+            auto fd = _handleToFD(handle, FHND_DEVICE);
+        else // MSVCRT
+        {
+            int mode;
+            modeLoop:
+            foreach (c; stdioOpenmode)
+                switch (c)
+                {
+                    case 'r': mode |= _O_RDONLY; break;
+                    case '+': mode &=~_O_RDONLY; break;
+                    case 'a': mode |= _O_APPEND; break;
+                    case 'b': mode |= _O_BINARY; break;
+                    case 't': mode |= _O_TEXT;   break;
+                    case ',': break modeLoop;
+                    default: break;
+                }
+
+            auto fd = _open_osfhandle(cast(intptr_t)handle, mode);
+        }
+
+        errnoEnforce(fd >= 0, "Cannot open Windows HANDLE");
+        fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle));
+    }
+
+
+/** Returns $(D true) if the file is opened. */
+    @property bool isOpen() const @safe pure nothrow
+    {
+        return _p !is null && _p.handle;
+    }
+
+/**
+Returns $(D true) if the file is at end (see $(WEB
+cplusplus.com/reference/clibrary/cstdio/feof.html, feof)).
+
+Throws: $(D Exception) if the file is not opened.
+ */
+    @property bool eof() const @trusted pure
+    {
+        import std.exception : enforce;
+
+        enforce(_p && _p.handle, "Calling eof() against an unopened file.");
+        return .feof(cast(FILE*) _p.handle) != 0;
+    }
+
+/** Returns the name of the last opened file, if any.
+If a $(D File) was created with $(LREF tmpfile) and $(LREF wrapFile)
+it has no name.*/
+    @property string name() const @safe pure nothrow
+    {
+        return _name;
+    }
+
+/**
+If the file is not opened, returns $(D true). Otherwise, returns
+$(WEB cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for
+the file handle.
+ */
+    @property bool error() const @trusted pure nothrow
+    {
+        return !isOpen || .ferror(cast(FILE*) _p.handle);
+    }
+
+    @safe unittest
+    {
+        // Issue 12349
+        static import std.file;
+        auto deleteme = testFilename();
+        auto f = File(deleteme, "w");
+        scope(exit) std.file.remove(deleteme);
+
+        f.close();
+        assert(f.error);
+    }
+
+/**
+Detaches from the underlying file. If the sole owner, calls $(D close).
+
+Throws: $(D ErrnoException) on failure if closing the file.
+  */
+    void detach() @safe
+    {
+        if (!_p) return;
+        if (_p.refs == 1)
+            close();
+        else
+        {
+            assert(_p.refs);
+            --_p.refs;
+            _p = null;
+        }
+    }
+
+    @safe unittest
+    {
+        static import std.file;
+
+        auto deleteme = testFilename();
+        scope(exit) std.file.remove(deleteme);
+        auto f = File(deleteme, "w");
+        {
+            auto f2 = f;
+            f2.detach();
+        }
+        assert(f._p.refs == 1);
+        f.close();
+    }
+
+/**
+If the file was unopened, succeeds vacuously. Otherwise closes the
+file (by calling $(WEB
+cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)),
+throwing on error. Even if an exception is thrown, afterwards the $(D
+File) object is empty. This is different from $(D detach) in that it
+always closes the file; consequently, all other $(D File) objects
+referring to the same handle will see a closed file henceforth.
+
+Throws: $(D ErrnoException) on error.
+ */
+    void close() @trusted
+    {
+        import core.stdc.stdlib : free;
+        import std.exception : errnoEnforce;
+
+        if (!_p) return; // succeed vacuously
+        scope(exit)
+        {
+            assert(_p.refs);
+            if(!--_p.refs)
+                free(_p);
+            _p = null; // start a new life
+        }
+        if (!_p.handle) return; // Impl is closed by another File
+
+        scope(exit) _p.handle = null; // nullify the handle anyway
+        version (Posix)
+        {
+            import std.format : format;
+            import core.sys.posix.stdio : pclose;
+
+            if (_p.isPopened)
+            {
+                auto res = pclose(_p.handle);
+                errnoEnforce(res != -1,
+                        "Could not close pipe `"~_name~"'");
+                errnoEnforce(res == 0, format("Command returned %d", res));
+                return;
+            }
+        }
+        //fprintf(core.stdc.stdio.stderr, ("Closing file `"~name~"`.\n\0").ptr);
+        errnoEnforce(.fclose(_p.handle) == 0,
+                "Could not close file `"~_name~"'");
+    }
+
+/**
+If the file is not opened, succeeds vacuously. Otherwise, returns
+$(WEB cplusplus.com/reference/clibrary/cstdio/_clearerr.html,
+_clearerr) for the file handle.
+ */
+    void clearerr() @safe pure nothrow
+    {
+        _p is null || _p.handle is null ||
+        .clearerr(_p.handle);
+    }
+
+/**
+Flushes the C $(D FILE) buffers.
+
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush)
+for the file handle.
+
+Throws: $(D Exception) if the file is not opened or if the call to $(D fflush) fails.
+ */
+    void flush() @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to flush() in an unopened file");
+        errnoEnforce(.fflush(_p.handle) == 0);
+    }
+
+    @safe unittest
+    {
+        // Issue 12349
+        static import std.file;
+        import std.exception: assertThrown;
+
+        auto deleteme = testFilename();
+        auto f = File(deleteme, "w");
+        scope(exit) std.file.remove(deleteme);
+
+        f.close();
+        assertThrown(f.flush());
+    }
+
+/**
+Forces any data buffered by the OS to be written to disk.
+Call $(LREF flush) before calling this function to flush the C $(D FILE) buffers first.
+
+This function calls
+$(WEB msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx,
+$(D FlushFileBuffers)) on Windows and
+$(WEB pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html,
+$(D fsync)) on POSIX for the file handle.
+
+Throws: $(D Exception) if the file is not opened or if the OS call fails.
+ */
+    void sync() @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to sync() an unopened file");
+
+        version (Windows)
+            wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed");
+        else
+        {
+            import core.sys.posix.unistd : fsync;
+            errnoEnforce(fsync(fileno) == 0, "fsync failed");
+        }
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the
+file handle. The number of items to read and the size of
+each item is inferred from the size and type of the input array, respectively.
+
+Returns: The slice of $(D buffer) containing the data that was actually read.
+This will be shorter than $(D buffer) if EOF was reached before the buffer
+could be filled.
+
+Throws: $(D Exception) if $(D buffer) is empty.
+        $(D ErrnoException) if the file is not opened or the call to $(D fread) fails.
+
+$(D rawRead) always reads in binary mode on Windows.
+ */
+    T[] rawRead(T)(T[] buffer)
+    {
+        import std.exception : errnoEnforce;
+
+        if (!buffer.length)
+            throw new Exception("rawRead must take a non-empty buffer");
+        version(Windows)
+        {
+            immutable fd = ._fileno(_p.handle);
+            immutable mode = ._setmode(fd, _O_BINARY);
+            scope(exit) ._setmode(fd, mode);
+            version(DIGITAL_MARS_STDIO)
+            {
+                import core.atomic;
+
+                // @@@BUG@@@ 4243
+                immutable info = __fhnd_info[fd];
+                atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
+                scope(exit) __fhnd_info[fd] = info;
+            }
+        }
+        immutable freadResult =
+            fread(buffer.ptr, T.sizeof, buffer.length, _p.handle);
+        assert (freadResult <= buffer.length); // fread return guarantee
+        if (freadResult != buffer.length) // error or eof
+        {
+            errnoEnforce(!error);
+            return buffer[0 .. freadResult];
+        }
+        return buffer;
+    }
+
+    ///
+    unittest
+    {
+        static import std.file;
+
+        auto testFile = testFilename();
+        std.file.write(testFile, "\r\n\n\r\n");
+        scope(exit) std.file.remove(testFile);
+
+        auto f = File(testFile, "r");
+        auto buf = f.rawRead(new char[5]);
+        f.close();
+        assert(buf == "\r\n\n\r\n");
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file
+handle. The number of items to write and the size of each
+item is inferred from the size and type of the input array, respectively. An
+error is thrown if the buffer could not be written in its entirety.
+
+$(D rawWrite) always writes in binary mode on Windows.
+
+Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwrite) fails.
+ */
+    void rawWrite(T)(in T[] buffer)
+    {
+        import std.conv : text;
+        import std.exception : errnoEnforce;
+
+        version(Windows)
+        {
+            flush(); // before changing translation mode
+            immutable fd = ._fileno(_p.handle);
+            immutable mode = ._setmode(fd, _O_BINARY);
+            scope(exit) ._setmode(fd, mode);
+            version(DIGITAL_MARS_STDIO)
+            {
+                import core.atomic;
+
+                // @@@BUG@@@ 4243
+                immutable info = __fhnd_info[fd];
+                atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
+                scope(exit) __fhnd_info[fd] = info;
+            }
+            scope(exit) flush(); // before restoring translation mode
+        }
+        auto result =
+            .fwrite(buffer.ptr, T.sizeof, buffer.length, _p.handle);
+        if (result == result.max) result = 0;
+        errnoEnforce(result == buffer.length,
+                text("Wrote ", result, " instead of ", buffer.length,
+                        " objects of type ", T.stringof, " to file `",
+                        _name, "'"));
+    }
+
+    ///
+    unittest
+    {
+        static import std.file;
+
+        auto testFile = testFilename();
+        auto f = File(testFile, "w");
+        scope(exit) std.file.remove(testFile);
+
+        f.rawWrite("\r\n\n\r\n");
+        f.close();
+        assert(std.file.read(testFile) == "\r\n\n\r\n");
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek)
+for the file handle.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) if the call to $(D fseek) fails.
+ */
+    void seek(long offset, int origin = SEEK_SET) @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+        import std.conv : to, text;
+
+        enforce(isOpen, "Attempting to seek() in an unopened file");
+        version (Windows)
+        {
+            version (CRuntime_Microsoft)
+            {
+                alias fseekFun = _fseeki64;
+                alias off_t = long;
+            }
+            else
+            {
+                alias fseekFun = fseek;
+                alias off_t = int;
+            }
+        }
+        else version (Posix)
+        {
+            import core.sys.posix.stdio : fseeko, off_t;
+            alias fseekFun = fseeko;
+        }
+        errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0,
+                "Could not seek in file `"~_name~"'");
+    }
+
+    unittest
+    {
+        static import std.file;
+
+        auto deleteme = testFilename();
+        auto f = File(deleteme, "w+");
+        scope(exit) { f.close(); std.file.remove(deleteme); }
+        f.rawWrite("abcdefghijklmnopqrstuvwxyz");
+        f.seek(7);
+        assert(f.readln() == "hijklmnopqrstuvwxyz");
+
+        import std.conv : text;
+
+        version (CRuntime_DigitalMars)
+            auto bigOffset = int.max - 100;
+        else
+        version (CRuntime_Bionic)
+            auto bigOffset = int.max - 100;
+        else
+            auto bigOffset = cast(ulong) int.max + 100;
+        f.seek(bigOffset);
+        assert(f.tell == bigOffset, text(f.tell));
+        // Uncomment the tests below only if you want to wait for
+        // a long time
+        // f.rawWrite("abcdefghijklmnopqrstuvwxyz");
+        // f.seek(-3, SEEK_END);
+        // assert(f.readln() == "xyz");
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the
+managed file handle.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) if the call to $(D ftell) fails.
+ */
+    @property ulong tell() const @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to tell() in an unopened file");
+        version (Windows)
+        {
+            version (CRuntime_Microsoft)
+                immutable result = _ftelli64(cast(FILE*) _p.handle);
+            else
+                immutable result = ftell(cast(FILE*) _p.handle);
+        }
+        else version (Posix)
+        {
+            import core.sys.posix.stdio : ftello;
+            immutable result = ftello(cast(FILE*) _p.handle);
+        }
+        errnoEnforce(result != -1,
+                "Query ftell() failed for file `"~_name~"'");
+        return result;
+    }
+
+    ///
+    unittest
+    {
+        static import std.file;
+        import std.conv : text;
+
+        auto testFile = testFilename();
+        std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz");
+        scope(exit) { std.file.remove(testFile); }
+
+        auto f = File(testFile);
+        auto a = new ubyte[4];
+        f.rawRead(a);
+        assert(f.tell == 4, text(f.tell));
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind)
+for the file handle.
+
+Throws: $(D Exception) if the file is not opened.
+ */
+    void rewind() @safe
+    {
+        import std.exception : enforce;
+
+        enforce(isOpen, "Attempting to rewind() an unopened file");
+        .rewind(_p.handle);
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for
+the file handle.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) if the call to $(D setvbuf) fails.
+ */
+    void setvbuf(size_t size, int mode = _IOFBF) @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to call setvbuf() on an unopened file");
+        errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0,
+                "Could not set buffering for file `"~_name~"'");
+    }
+
+/**
+Calls $(WEB cplusplus.com/reference/clibrary/cstdio/_setvbuf.html,
+_setvbuf) for the file handle.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) if the call to $(D setvbuf) fails.
+*/
+    void setvbuf(void[] buf, int mode = _IOFBF) @trusted
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to call setvbuf() on an unopened file");
+        errnoEnforce(.setvbuf(_p.handle,
+                        cast(char*) buf.ptr, mode, buf.length) == 0,
+                "Could not set buffering for file `"~_name~"'");
+    }
+
+
+    version(Windows)
+    {
+        import core.sys.windows.windows;
+
+        private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length,
+            Flags flags)
+        {
+            if (!start && !length)
+                length = ulong.max;
+            ULARGE_INTEGER liStart = void, liLength = void;
+            liStart.QuadPart = start;
+            liLength.QuadPart = length;
+            OVERLAPPED overlapped;
+            overlapped.Offset = liStart.LowPart;
+            overlapped.OffsetHigh = liStart.HighPart;
+            overlapped.hEvent = null;
+            return F(windowsHandle, flags, 0, liLength.LowPart,
+                liLength.HighPart, &overlapped);
+        }
+
+        private static T wenforce(T)(T cond, string str)
+        {
+            import std.windows.syserror;
+
+            if (cond) return cond;
+            throw new Exception(str ~ ": " ~ sysErrorString(GetLastError()));
+        }
+    }
+    version(Posix)
+    {
+        private int lockImpl(int operation, short l_type,
+            ulong start, ulong length)
+        {
+            import std.conv : to;
+            import core.sys.posix.fcntl : fcntl, flock, off_t;
+            import core.sys.posix.unistd : getpid;
+
+            flock fl = void;
+            fl.l_type   = l_type;
+            fl.l_whence = SEEK_SET;
+            fl.l_start  = to!off_t(start);
+            fl.l_len    = to!off_t(length);
+            fl.l_pid    = getpid();
+            return fcntl(fileno, operation, &fl);
+        }
+    }
+
+/**
+Locks the specified file segment. If the file segment is already locked
+by another process, waits until the existing lock is released.
+If both $(D start) and $(D length) are zero, the entire file is locked.
+
+Locks created using $(D lock) and $(D tryLock) have the following properties:
+$(UL
+ $(LI All locks are automatically released when the process terminates.)
+ $(LI Locks are not inherited by child processes.)
+ $(LI Closing a file will release all locks associated with the file. On POSIX,
+      even locks acquired via a different $(D File) will be released as well.)
+ $(LI Not all NFS implementations correctly implement file locking.)
+)
+ */
+    void lock(LockType lockType = LockType.readWrite,
+        ulong start = 0, ulong length = 0)
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to call lock() on an unopened file");
+        version (Posix)
+        {
+            import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK;
+            immutable short type = lockType == LockType.readWrite
+                ? F_WRLCK : F_RDLCK;
+            errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1,
+                    "Could not set lock for file `"~_name~"'");
+        }
+        else
+        version(Windows)
+        {
+            immutable type = lockType == LockType.readWrite ?
+                LOCKFILE_EXCLUSIVE_LOCK : 0;
+            wenforce(lockImpl!LockFileEx(start, length, type),
+                    "Could not set lock for file `"~_name~"'");
+        }
+        else
+            static assert(false);
+    }
+
+/**
+Attempts to lock the specified file segment.
+If both $(D start) and $(D length) are zero, the entire file is locked.
+Returns: $(D true) if the lock was successful, and $(D false) if the
+specified file segment was already locked.
+ */
+    bool tryLock(LockType lockType = LockType.readWrite,
+        ulong start = 0, ulong length = 0)
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to call tryLock() on an unopened file");
+        version (Posix)
+        {
+            import core.stdc.errno : EACCES, EAGAIN, errno;
+            import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK;
+            immutable short type = lockType == LockType.readWrite
+                ? F_WRLCK : F_RDLCK;
+            immutable res = lockImpl(F_SETLK, type, start, length);
+            if (res == -1 && (errno == EACCES || errno == EAGAIN))
+                return false;
+            errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'");
+            return true;
+        }
+        else
+        version(Windows)
+        {
+            immutable type = lockType == LockType.readWrite
+                ? LOCKFILE_EXCLUSIVE_LOCK : 0;
+            immutable res = lockImpl!LockFileEx(start, length,
+                type | LOCKFILE_FAIL_IMMEDIATELY);
+            if (!res && (GetLastError() == ERROR_IO_PENDING
+                || GetLastError() == ERROR_LOCK_VIOLATION))
+                return false;
+            wenforce(res, "Could not set lock for file `"~_name~"'");
+            return true;
+        }
+        else
+            static assert(false);
+    }
+
+/**
+Removes the lock over the specified file segment.
+ */
+    void unlock(ulong start = 0, ulong length = 0)
+    {
+        import std.exception : enforce, errnoEnforce;
+
+        enforce(isOpen, "Attempting to call unlock() on an unopened file");
+        version (Posix)
+        {
+            import core.sys.posix.fcntl : F_SETLK, F_UNLCK;
+            errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1,
+                    "Could not remove lock for file `"~_name~"'");
+        }
+        else
+        version(Windows)
+        {
+            wenforce(lockImpl!UnlockFileEx(start, length),
+                "Could not remove lock for file `"~_name~"'");
+        }
+        else
+            static assert(false);
+    }
+
+    version(Windows)
+    unittest
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        scope(exit) std.file.remove(deleteme);
+        auto f = File(deleteme, "wb");
+        assert(f.tryLock());
+        auto g = File(deleteme, "wb");
+        assert(!g.tryLock());
+        assert(!g.tryLock(LockType.read));
+        f.unlock();
+        f.lock(LockType.read);
+        assert(!g.tryLock());
+        assert(g.tryLock(LockType.read));
+        f.unlock();
+        g.unlock();
+    }
+
+    version(Posix)
+    unittest
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        scope(exit) std.file.remove(deleteme);
+
+        // Since locks are per-process, we cannot test lock failures within
+        // the same process. fork() is used to create a second process.
+        static void runForked(void delegate() code)
+        {
+            import core.stdc.stdlib : exit;
+            import core.sys.posix.unistd;
+            import core.sys.posix.sys.wait;
+            int child, status;
+            if ((child = fork()) == 0)
+            {
+                code();
+                exit(0);
+            }
+            else
+            {
+                assert(wait(&status) != -1);
+                assert(status == 0, "Fork crashed");
+            }
+        }
+
+        auto f = File(deleteme, "w+b");
+
+        runForked
+        ({
+            auto g = File(deleteme, "a+b");
+            assert(g.tryLock());
+            g.unlock();
+            assert(g.tryLock(LockType.read));
+        });
+
+        assert(f.tryLock());
+        runForked
+        ({
+            auto g = File(deleteme, "a+b");
+            assert(!g.tryLock());
+            assert(!g.tryLock(LockType.read));
+        });
+        f.unlock();
+
+        f.lock(LockType.read);
+        runForked
+        ({
+            auto g = File(deleteme, "a+b");
+            assert(!g.tryLock());
+            assert(g.tryLock(LockType.read));
+            g.unlock();
+        });
+        f.unlock();
+    }
+
+
+/**
+Writes its arguments in text format to the file.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) on an error writing to the file.
+*/
+    void write(S...)(S args)
+    {
+        import std.traits : isBoolean, isIntegral, isAggregateType;
+        auto w = lockingTextWriter();
+        foreach (arg; args)
+        {
+            alias A = typeof(arg);
+            static if (isAggregateType!A || is(A == enum))
+            {
+                import std.format : formattedWrite;
+
+                formattedWrite(w, "%s", arg);
+            }
+            else static if (isSomeString!A)
+            {
+                import std.range.primitives : put;
+                put(w, arg);
+            }
+            else static if (isIntegral!A)
+            {
+                import std.conv : toTextRange;
+
+                toTextRange(arg, w);
+            }
+            else static if (isBoolean!A)
+            {
+                put(w, arg ? "true" : "false");
+            }
+            else static if (isSomeChar!A)
+            {
+                import std.range.primitives : put;
+                put(w, arg);
+            }
+            else
+            {
+                import std.format : formattedWrite;
+
+                // Most general case
+                formattedWrite(w, "%s", arg);
+            }
+        }
+    }
+
+/**
+Writes its arguments in text format to the file, followed by a newline.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) on an error writing to the file.
+*/
+    void writeln(S...)(S args)
+    {
+        write(args, '\n');
+    }
+
+/**
+Writes its arguments in text format to the file, according to the
+format in the first argument.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) on an error writing to the file.
+*/
+    void writef(Char, A...)(in Char[] fmt, A args)
+    {
+        import std.format : formattedWrite;
+
+        formattedWrite(lockingTextWriter(), fmt, args);
+    }
+
+/**
+Writes its arguments in text format to the file, according to the
+format in the first argument, followed by a newline.
+
+Throws: $(D Exception) if the file is not opened.
+        $(D ErrnoException) on an error writing to the file.
+*/
+    void writefln(Char, A...)(in Char[] fmt, A args)
+    {
+        import std.format : formattedWrite;
+
+        auto w = lockingTextWriter();
+        formattedWrite(w, fmt, args);
+        w.put('\n');
+    }
+
+/**
+Read line from the file handle and return it as a specified type.
+
+This version manages its own read buffer, which means one memory allocation per call. If you are not
+retaining a reference to the read data, consider the $(D File.readln(buf)) version, which may offer
+better performance as it can reuse its read buffer.
+
+Params:
+    S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string).
+    terminator = Line terminator (by default, $(D '\n')).
+
+Note:
+    String terminators are not supported due to ambiguity with readln(buf) below.
+
+Returns:
+    The line that was read, including the line terminator character.
+
+Throws:
+    $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error.
+
+Example:
+---
+// Reads $(D stdin) and writes it to $(D stdout).
+import std.stdio;
+
+void main()
+{
+    string line;
+    while ((line = stdin.readln()) !is null)
+        write(line);
+}
+---
+*/
+    S readln(S = string)(dchar terminator = '\n')
+    if (isSomeString!S)
+    {
+        Unqual!(ElementEncodingType!S)[] buf;
+        readln(buf, terminator);
+        return cast(S)buf;
+    }
+
+    unittest
+    {
+        static import std.file;
+        import std.algorithm : equal;
+        import std.meta : AliasSeq;
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "hello\nworld\n");
+        scope(exit) std.file.remove(deleteme);
+        foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
+        {
+            auto witness = [ "hello\n", "world\n" ];
+            auto f = File(deleteme);
+            uint i = 0;
+            String buf;
+            while ((buf = f.readln!String()).length)
+            {
+                assert(i < witness.length);
+                assert(equal(buf, witness[i++]));
+            }
+            assert(i == witness.length);
+        }
+    }
+
+    unittest
+    {
+        static import std.file;
+        import std.typecons : Tuple;
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "cześć \U0002000D");
+        scope(exit) std.file.remove(deleteme);
+        uint[] lengths = [12,8,7];
+        foreach (uint i, C; Tuple!(char, wchar, dchar).Types)
+        {
+            immutable(C)[] witness = "cześć \U0002000D";
+            auto buf = File(deleteme).readln!(immutable(C)[])();
+            assert(buf.length == lengths[i]);
+            assert(buf == witness);
+        }
+    }
+
+/**
+Read line from the file handle and write it to $(D buf[]), including
+terminating character.
+
+This can be faster than $(D line = File.readln()) because you can reuse
+the buffer for each call. Note that reusing the buffer means that you
+must copy the previous contents if you wish to retain them.
+
+Params:
+buf = Buffer used to store the resulting line data. buf is
+resized as necessary.
+terminator = Line terminator (by default, $(D '\n')). Use
+$(XREF ascii, newline) for portability (unless the file was opened in
+text mode).
+
+Returns:
+0 for end of file, otherwise number of characters read
+
+Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode
+conversion error.
+
+Example:
+---
+// Read lines from $(D stdin) into a string
+// Ignore lines starting with '#'
+// Write the string to $(D stdout)
+
+void main()
+{
+    string output;
+    char[] buf;
+
+    while (stdin.readln(buf))
+    {
+        if (buf[0] == '#')
+            continue;
+
+        output ~= buf;
+    }
+
+    write(output);
+}
+---
+
+This method can be more efficient than the one in the previous example
+because $(D stdin.readln(buf)) reuses (if possible) memory allocated
+for $(D buf), whereas $(D line = stdin.readln()) makes a new memory allocation
+for every line.
+
+For even better performance you can help $(D readln) by passing in a
+large buffer to avoid memory reallocations. This can be done by reusing the
+largest buffer returned by $(D readln):
+
+Example:
+---
+// Read lines from $(D stdin) and count words
+
+void main()
+{
+    char[] buf;
+    size_t words = 0;
+
+    while (!stdin.eof)
+    {
+        char[] line = buf;
+        stdin.readln(line);
+        if (line.length > buf.length)
+            buf = line;
+
+        words += line.split.length;
+    }
+
+    writeln(words);
+}
+---
+This is actually what $(LREF byLine) does internally, so its usage
+is recommended if you want to process a complete file.
+*/
+    size_t readln(C)(ref C[] buf, dchar terminator = '\n')
+    if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum))
+    {
+        import std.exception : enforce;
+
+        static if (is(C == char))
+        {
+            enforce(_p && _p.handle, "Attempt to read from an unopened file.");
+            if (_p.orientation == Orientation.unknown)
+            {
+                import core.stdc.wchar_ : fwide;
+                auto w = fwide(_p.handle, 0);
+                if (w < 0) _p.orientation = Orientation.narrow;
+                else if (w > 0) _p.orientation = Orientation.wide;
+            }
+            return readlnImpl(_p.handle, buf, terminator, _p.orientation);
+        }
+        else
+        {
+            // TODO: optimize this
+            string s = readln(terminator);
+            buf.length = 0;
+            if (!s.length) return 0;
+            foreach (C c; s)
+            {
+                buf ~= c;
+            }
+            return buf.length;
+        }
+    }
+
+    unittest
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "123\n456789");
+        scope(exit) std.file.remove(deleteme);
+
+        auto file = File(deleteme);
+        char[] buffer = new char[10];
+        char[] line = buffer;
+        file.readln(line);
+        auto beyond = line.length;
+        buffer[beyond] = 'a';
+        file.readln(line); // should not write buffer beyond line
+        assert(buffer[beyond] == 'a');
+    }
+
+    unittest // bugzilla 15293
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "a\n\naa");
+        scope(exit) std.file.remove(deleteme);
+
+        auto file = File(deleteme);
+        char[] buffer;
+        char[] line;
+
+        file.readln(buffer, '\n');
+
+        line = buffer;
+        file.readln(line, '\n');
+
+        line = buffer;
+        file.readln(line, '\n');
+
+        assert(line[0 .. 1].capacity == 0);
+    }
+
+/** ditto */
+    size_t readln(C, R)(ref C[] buf, R terminator)
+    if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
+        isBidirectionalRange!R && is(typeof(terminator.front == dchar.init)))
+    {
+        import std.algorithm : endsWith, swap;
+        import std.range.primitives : back;
+
+        auto last = terminator.back;
+        C[] buf2;
+        swap(buf, buf2);
+        for (;;) {
+            if (!readln(buf2, last) || endsWith(buf2, terminator)) {
+                if (buf.empty) {
+                    buf = buf2;
+                } else {
+                    buf ~= buf2;
+                }
+                break;
+            }
+            buf ~= buf2;
+        }
+        return buf.length;
+    }
+
+    unittest
+    {
+        static import std.file;
+        import std.typecons : Tuple;
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya");
+        scope(exit) std.file.remove(deleteme);
+        foreach (C; Tuple!(char, wchar, dchar).Types)
+        {
+            immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ];
+            auto f = File(deleteme);
+            uint i = 0;
+            C[] buf;
+            while (f.readln(buf, "\n\r"))
+            {
+                assert(i < witness.length);
+                assert(buf == witness[i++]);
+            }
+            assert(buf.length==0);
+        }
+    }
+
+    /**
+     * Read data from the file according to the specified
+     * $(LINK2 std_format.html#_format-string, format specifier) using
+     * $(XREF _format,formattedRead).
+     */
+    uint readf(Data...)(in char[] format, Data data)
+    {
+        import std.format : formattedRead;
+
+        assert(isOpen);
+        auto input = LockingTextReader(this);
+        return formattedRead(input, format, data);
+    }
+
+    ///
+    unittest
+    {
+        static import std.file;
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
+        scope(exit) std.file.remove(deleteme);
+        string s;
+        auto f = File(deleteme);
+        f.readf("%s\n", &s);
+        assert(s == "hello", "["~s~"]");
+        f.readf("%s\n", &s);
+        assert(s == "world", "["~s~"]");
+
+        // Issue 11698
+        bool b1, b2;
+        f.readf("%s\n%s\n", &b1, &b2);
+        assert(b1 == true && b2 == false);
+    }
+
+/**
+ Returns a temporary file by calling $(WEB
+ cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile).
+ Note that the created file has no $(LREF name).*/
+    static File tmpfile() @safe
+    {
+        import std.exception : errnoEnforce;
+
+        return File(errnoEnforce(.tmpfile(),
+                "Could not create temporary file with tmpfile()"),
+            null);
+    }
+
+/**
+Unsafe function that wraps an existing $(D FILE*). The resulting $(D
+File) never takes the initiative in closing the file.
+Note that the created file has no $(LREF name)*/
+    /*private*/ static File wrapFile(FILE* f) @safe
+    {
+        import std.exception : enforce;
+
+        return File(enforce(f, "Could not wrap null FILE*"),
+            null, /*uint.max / 2*/ 9999);
+    }
+
+/**
+Returns the $(D FILE*) corresponding to this object.
+ */
+    FILE* getFP() @safe pure
+    {
+        import std.exception : enforce;
+
+        enforce(_p && _p.handle,
+                "Attempting to call getFP() on an unopened file");
+        return _p.handle;
+    }
+
+    unittest
+    {
+        static import core.stdc.stdio;
+        assert(stdout.getFP() == core.stdc.stdio.stdout);
+    }
+
+/**
+Returns the file number corresponding to this object.
+ */
+    /*version(Posix) */@property int fileno() const @trusted
+    {
+        import std.exception : enforce;
+
+        enforce(isOpen, "Attempting to call fileno() on an unopened file");
+        return .fileno(cast(FILE*) _p.handle);
+    }
+
+/**
+Returns the underlying operating system $(D HANDLE) (Windows only).
+*/
+    version(StdDdoc)
+    @property HANDLE windowsHandle();
+
+    version(Windows)
+    @property HANDLE windowsHandle()
+    {
+        version (DIGITAL_MARS_STDIO)
+            return _fdToHandle(fileno);
+        else
+            return cast(HANDLE)_get_osfhandle(fileno);
+    }
+
+
+// Note: This was documented until 2013/08
+/*
+Range that reads one line at a time.  Returned by $(LREF byLine).
+
+Allows to directly use range operations on lines of a file.
+*/
+    struct ByLine(Char, Terminator)
+    {
+    private:
+        import std.typecons;
+
+        /* Ref-counting stops the source range's Impl
+         * from getting out of sync after the range is copied, e.g.
+         * when accessing range.front, then using std.range.take,
+         * then accessing range.front again. */
+        alias PImpl = RefCounted!(Impl, RefCountedAutoInitialize.no);
+        PImpl impl;
+
+        static if (isScalarType!Terminator)
+            enum defTerm = '\n';
+        else
+            enum defTerm = cast(Terminator)"\n";
+
+    public:
+        this(File f, KeepTerminator kt = KeepTerminator.no,
+                Terminator terminator = defTerm)
+        {
+            impl = PImpl(f, kt, terminator);
+        }
+
+        @property bool empty()
+        {
+            return impl.refCountedPayload.empty;
+        }
+
+        @property Char[] front()
+        {
+            return impl.refCountedPayload.front;
+        }
+
+        void popFront()
+        {
+            impl.refCountedPayload.popFront();
+        }
+
+    private:
+        struct Impl
+        {
+        private:
+            File file;
+            Char[] line;
+            Char[] buffer;
+            Terminator terminator;
+            KeepTerminator keepTerminator;
+
+        public:
+            this(File f, KeepTerminator kt, Terminator terminator)
+            {
+                file = f;
+                this.terminator = terminator;
+                keepTerminator = kt;
+                popFront();
+            }
+
+            // Range primitive implementations.
+            @property bool empty()
+            {
+                return line is null;
+            }
+
+            @property Char[] front()
+            {
+                return line;
+            }
+
+            void popFront()
+            {
+                import std.algorithm : endsWith;
+                assert(file.isOpen);
+                line = buffer;
+                file.readln(line, terminator);
+                if (line.length > buffer.length)
+                {
+                    buffer = line;
+                }
+                if (line.empty)
+                {
+                    file.detach();
+                    line = null;
+                }
+                else if (keepTerminator == KeepTerminator.no
+                        && endsWith(line, terminator))
+                {
+                    static if (isScalarType!Terminator)
+                        enum tlen = 1;
+                    else static if (isArray!Terminator)
+                    {
+                        static assert(
+                            is(Unqual!(ElementEncodingType!Terminator) == Char));
+                        const tlen = terminator.length;
+                    }
+                    else
+                        static assert(false);
+                    line = line.ptr[0 .. line.length - tlen];
+                }
+            }
+        }
+    }
+
+/**
+Returns an input range set up to read from the file handle one line
+at a time.
+
+The element type for the range will be $(D Char[]). Range primitives
+may throw $(D StdioException) on I/O error.
+
+Note:
+Each $(D front) will not persist after $(D
+popFront) is called, so the caller must copy its contents (e.g. by
+calling $(D to!string)) when retention is needed. If the caller needs
+to retain a copy of every line, use the $(LREF byLineCopy) function
+instead.
+
+Params:
+Char = Character type for each line, defaulting to $(D char).
+keepTerminator = Use $(D KeepTerminator.yes) to include the
+terminator at the end of each line.
+terminator = Line separator ($(D '\n') by default). Use
+$(XREF ascii, newline) for portability (unless the file was opened in
+text mode).
+
+Example:
+----
+import std.algorithm, std.stdio, std.string;
+// Count words in a file using ranges.
+void main()
+{
+    auto file = File("file.txt"); // Open for reading
+    const wordCount = file.byLine()            // Read lines
+                          .map!split           // Split into words
+                          .map!(a => a.length) // Count words per line
+                          .sum();              // Total word count
+    writeln(wordCount);
+}
+----
+
+Example:
+----
+import std.range, std.stdio;
+// Read lines using foreach.
+void main()
+{
+    auto file = File("file.txt"); // Open for reading
+    auto range = file.byLine();
+    // Print first three lines
+    foreach (line; range.take(3))
+        writeln(line);
+    // Print remaining lines beginning with '#'
+    foreach (line; range)
+    {
+        if (!line.empty && line[0] == '#')
+            writeln(line);
+    }
+}
+----
+Notice that neither example accesses the line data returned by
+$(D front) after the corresponding $(D popFront) call is made (because
+the contents may well have changed).
+*/
+    auto byLine(Terminator = char, Char = char)
+            (KeepTerminator keepTerminator = KeepTerminator.no,
+            Terminator terminator = '\n')
+    if (isScalarType!Terminator)
+    {
+        return ByLine!(Char, Terminator)(this, keepTerminator, terminator);
+    }
+
+/// ditto
+    auto byLine(Terminator, Char = char)
+            (KeepTerminator keepTerminator, Terminator terminator)
+    if (is(Unqual!(ElementEncodingType!Terminator) == Char))
+    {
+        return ByLine!(Char, Terminator)(this, keepTerminator, terminator);
+    }
+
+    unittest
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "hi");
+        scope(success) std.file.remove(deleteme);
+
+        import std.meta : AliasSeq;
+        foreach (T; AliasSeq!(char, wchar, dchar))
+        {
+            auto blc = File(deleteme).byLine!(T, T);
+            assert(blc.front == "hi");
+            // check front is cached
+            assert(blc.front is blc.front);
+        }
+    }
+
+    private struct ByLineCopy(Char, Terminator)
+    {
+    private:
+        import std.typecons;
+
+        /* Ref-counting stops the source range's ByLineCopyImpl
+         * from getting out of sync after the range is copied, e.g.
+         * when accessing range.front, then using std.range.take,
+         * then accessing range.front again. */
+        alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator),
+            RefCountedAutoInitialize.no);
+        Impl impl;
+
+    public:
+        this(File f, KeepTerminator kt, Terminator terminator)
+        {
+            impl = Impl(f, kt, terminator);
+        }
+
+        @property bool empty()
+        {
+            return impl.refCountedPayload.empty;
+        }
+
+        @property Char[] front()
+        {
+            return impl.refCountedPayload.front;
+        }
+
+        void popFront()
+        {
+            impl.refCountedPayload.popFront();
+        }
+    }
+
+    private struct ByLineCopyImpl(Char, Terminator)
+    {
+        ByLine!(Unqual!Char, Terminator).Impl impl;
+        bool gotFront;
+        Char[] line;
+
+    public:
+        this(File f, KeepTerminator kt, Terminator terminator)
+        {
+            impl = ByLine!(Unqual!Char, Terminator).Impl(f, kt, terminator);
+        }
+
+        @property bool empty()
+        {
+            return impl.empty;
+        }
+
+        @property front()
+        {
+            if (!gotFront)
+            {
+                line = impl.front.dup;
+                gotFront = true;
+            }
+            return line;
+        }
+
+        void popFront()
+        {
+            impl.popFront();
+            gotFront = false;
+        }
+    }
+
+/**
+Returns an input range set up to read from the file handle one line
+at a time. Each line will be newly allocated. $(D front) will cache
+its value to allow repeated calls without unnecessary allocations.
+
+Note: Due to caching byLineCopy can be more memory-efficient than
+$(D File.byLine.map!idup).
+
+The element type for the range will be $(D Char[]). Range
+primitives may throw $(D StdioException) on I/O error.
+
+Params:
+Char = Character type for each line, defaulting to $(D immutable char).
+keepTerminator = Use $(D KeepTerminator.yes) to include the
+terminator at the end of each line.
+terminator = Line separator ($(D '\n') by default). Use
+$(XREF ascii, newline) for portability (unless the file was opened in
+text mode).
+
+Example:
+----
+import std.algorithm, std.array, std.stdio;
+// Print sorted lines of a file.
+void main()
+{
+    auto sortedLines = File("file.txt")   // Open for reading
+                       .byLineCopy()      // Read persistent lines
+                       .array()           // into an array
+                       .sort();           // then sort them
+    foreach (line; sortedLines)
+        writeln(line);
+}
+----
+See_Also:
+$(XREF file,readText)
+*/
+    auto byLineCopy(Terminator = char, Char = immutable char)
+            (KeepTerminator keepTerminator = KeepTerminator.no,
+            Terminator terminator = '\n')
+    if (isScalarType!Terminator)
+    {
+        return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator);
+    }
+
+/// ditto
+    auto byLineCopy(Terminator, Char = immutable char)
+            (KeepTerminator keepTerminator, Terminator terminator)
+    if (is(Unqual!(ElementEncodingType!Terminator) == Unqual!Char))
+    {
+        return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator);
+    }
+
+    unittest
+    {
+        static assert(is(typeof(File("").byLine.front) == char[]));
+        static assert(is(typeof(File("").byLineCopy.front) == string));
+        static assert(
+            is(typeof(File("").byLineCopy!(char, char).front) == char[]));
+    }
+
+    unittest
+    {
+        static import std.file;
+        import std.algorithm : equal;
+        import std.range;
+
+        //printf("Entering test at line %d\n", __LINE__);
+        scope(failure) printf("Failed test at line %d\n", __LINE__);
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "");
+        scope(success) std.file.remove(deleteme);
+
+        // Test empty file
+        auto f = File(deleteme);
+        foreach (line; f.byLine())
+        {
+            assert(false);
+        }
+        f.detach();
+        assert(!f.isOpen);
+
+        void test(Terminator)(string txt, in string[] witness,
+                KeepTerminator kt, Terminator term, bool popFirstLine = false)
+        {
+            import std.conv : text;
+            import std.algorithm : sort;
+            import std.range.primitives : walkLength;
+
+            uint i;
+            std.file.write(deleteme, txt);
+            auto f = File(deleteme);
+            scope(exit)
+            {
+                f.close();
+                assert(!f.isOpen);
+            }
+            auto lines = f.byLine(kt, term);
+            if (popFirstLine)
+            {
+                lines.popFront();
+                i = 1;
+            }
+            assert(lines.empty || lines.front is lines.front);
+            foreach (line; lines)
+            {
+                assert(line == witness[i++]);
+            }
+            assert(i == witness.length, text(i, " != ", witness.length));
+
+            // Issue 11830
+            auto walkedLength = File(deleteme).byLine(kt, term).walkLength;
+            assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length));
+
+            // test persistent lines
+            assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort());
+        }
+
+        KeepTerminator kt = KeepTerminator.no;
+        test("", null, kt, '\n');
+        test("\n", [ "" ], kt, '\n');
+        test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n');
+        test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true);
+        test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n');
+        test("foo", [ "foo" ], kt, '\n', true);
+        test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"],
+            kt, "\r\n");
+        test("sue\r", ["sue"], kt, '\r');
+
+        kt = KeepTerminator.yes;
+        test("", null, kt, '\n');
+        test("\n", [ "\n" ], kt, '\n');
+        test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n');
+        test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n');
+        test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true);
+        test("foo", [ "foo" ], kt, '\n');
+        test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"],
+            kt, "\r\n");
+        test("sue\r", ["sue\r"], kt, '\r');
+    }
+
+    unittest
+    {
+        import std.algorithm : equal;
+        import std.range;
+
+        version(Win64)
+        {
+            static import std.file;
+
+            /* the C function tmpfile doesn't seem to work, even when called from C */
+            auto deleteme = testFilename();
+            auto file = File(deleteme, "w+");
+            scope(success) std.file.remove(deleteme);
+        }
+        else version(CRuntime_Bionic)
+        {
+            static import std.file;
+
+            /* the C function tmpfile doesn't work when called from a shared
+               library apk:
+               https://code.google.com/p/android/issues/detail?id=66815 */
+            auto deleteme = testFilename();
+            auto file = File(deleteme, "w+");
+            scope(success) std.file.remove(deleteme);
+        }
+        else
+            auto file = File.tmpfile();
+        file.write("1\n2\n3\n");
+
+        // bug 9599
+        file.rewind();
+        File.ByLine!(char, char) fbl = file.byLine();
+        auto fbl2 = fbl;
+        assert(fbl.front == "1");
+        assert(fbl.front is fbl2.front);
+        assert(fbl.take(1).equal(["1"]));
+        assert(fbl.equal(["2", "3"]));
+        assert(fbl.empty);
+        assert(file.isOpen); // we still have a valid reference
+
+        file.rewind();
+        fbl = file.byLine();
+        assert(!fbl.drop(2).empty);
+        assert(fbl.equal(["3"]));
+        assert(fbl.empty);
+        assert(file.isOpen);
+
+        file.detach();
+        assert(!file.isOpen);
+    }
+
+    unittest
+    {
+        static import std.file;
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "hi");
+        scope(success) std.file.remove(deleteme);
+
+        auto blc = File(deleteme).byLineCopy;
+        assert(!blc.empty);
+        // check front is cached
+        assert(blc.front is blc.front);
+    }
+
+    template byRecord(Fields...)
+    {
+        ByRecord!(Fields) byRecord(string format)
+        {
+            return typeof(return)(this, format);
+        }
+    }
+
+    unittest
+    {
+        // static import std.file;
+        //
+        // auto deleteme = testFilename();
+        // rndGen.popFront();
+        // scope(failure) printf("Failed test at line %d\n", __LINE__);
+        // std.file.write(deleteme, "1 2\n4 1\n5 100");
+        // scope(exit) std.file.remove(deleteme);
+        // File f = File(deleteme);
+        // scope(exit) f.close();
+        // auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ];
+        // uint i;
+        // foreach (e; f.byRecord!(int, int)("%s %s"))
+        // {
+        //     //.writeln(e);
+        //     assert(e == t[i++]);
+        // }
+    }
+
+    // Note: This was documented until 2013/08
+    /*
+     * Range that reads a chunk at a time.
+     */
+    struct ByChunk
+    {
+    private:
+        File    file_;
+        ubyte[] chunk_;
+
+        void prime()
+        {
+            chunk_ = file_.rawRead(chunk_);
+            if (chunk_.length == 0)
+                file_.detach();
+        }
+
+    public:
+        this(File file, size_t size)
+        {
+            this(file, new ubyte[](size));
+        }
+
+        this(File file, ubyte[] buffer)
+        {
+            import std.exception;
+            enforce(buffer.length, "size must be larger than 0");
+            file_ = file;
+            chunk_ = buffer;
+            prime();
+        }
+
+        // $(D ByChunk)'s input range primitive operations.
+        @property nothrow
+        bool empty() const
+        {
+            return !file_.isOpen;
+        }
+
+        /// Ditto
+        @property nothrow
+        ubyte[] front()
+        {
+            version(assert)
+            {
+                import core.exception : RangeError;
+                if (empty)
+                    throw new RangeError();
+            }
+            return chunk_;
+        }
+
+        /// Ditto
+        void popFront()
+        {
+            version(assert)
+            {
+                import core.exception : RangeError;
+                if (empty)
+                    throw new RangeError();
+            }
+            prime();
+        }
+    }
+
+/**
+Returns an input range set up to read from the file handle a chunk at a
+time.
+
+The element type for the range will be $(D ubyte[]). Range primitives
+may throw $(D StdioException) on I/O error.
+
+Example:
+---------
+void main()
+{
+    // Read standard input 4KB at a time
+    foreach (ubyte[] buffer; stdin.byChunk(4096))
+    {
+        ... use buffer ...
+    }
+}
+---------
+
+The parameter may be a number (as shown in the example above) dictating the
+size of each chunk. Alternatively, $(D byChunk) accepts a
+user-provided buffer that it uses directly.
+
+Example:
+---------
+void main()
+{
+    // Read standard input 4KB at a time
+    foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096]))
+    {
+        ... use buffer ...
+    }
+}
+---------
+
+In either case, the content of the buffer is reused across calls. That means
+$(D front) will not persist after $(D popFront) is called, so if retention is
+needed, the caller must copy its contents (e.g. by calling $(D buffer.dup)).
+
+In the  example above, $(D buffer.length) is 4096 for all iterations, except
+for the last one, in which case $(D buffer.length) may be less than 4096 (but
+always greater than zero).
+
+With the mentioned limitations, $(D byChunks) works with any algorithm
+compatible with input ranges.
+
+Example:
+---
+// Efficient file copy, 1MB at a time.
+import std.algorithm, std.stdio;
+void main()
+{
+    stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter());
+}
+---
+
+$(XREF_PACK algorithm,iteration,joiner) can be used to join chunks together into
+a single range lazily.
+Example:
+---
+import std.algorithm, std.stdio;
+void main()
+{
+    //Range of ranges
+    static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[]));
+    //Range of elements
+    static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte));
+}
+---
+
+Returns: A call to $(D byChunk) returns a range initialized with the $(D File)
+object and the appropriate buffer.
+
+Throws: If the user-provided size is zero or the user-provided buffer
+is empty, throws an $(D Exception). In case of an I/O error throws
+$(D StdioException).
+ */
+    auto byChunk(size_t chunkSize)
+    {
+        return ByChunk(this, chunkSize);
+    }
+/// Ditto
+    ByChunk byChunk(ubyte[] buffer)
+    {
+        return ByChunk(this, buffer);
+    }
+
+    unittest
+    {
+        static import std.file;
+
+        scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "asd\ndef\nasdf");
+
+        auto witness = ["asd\n", "def\n", "asdf" ];
+        auto f = File(deleteme);
+        scope(exit)
+        {
+            f.close();
+            assert(!f.isOpen);
+            std.file.remove(deleteme);
+        }
+
+        uint i;
+        foreach (chunk; f.byChunk(4))
+            assert(chunk == cast(ubyte[])witness[i++]);
+
+        assert(i == witness.length);
+    }
+
+    unittest
+    {
+        static import std.file;
+
+        scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+        auto deleteme = testFilename();
+        std.file.write(deleteme, "asd\ndef\nasdf");
+
+        auto witness = ["asd\n", "def\n", "asdf" ];
+        auto f = File(deleteme);
+        scope(exit)
+        {
+            f.close();
+            assert(!f.isOpen);
+            std.file.remove(deleteme);
+        }
+
+        uint i;
+        foreach (chunk; f.byChunk(new ubyte[4]))
+            assert(chunk == cast(ubyte[])witness[i++]);
+
+        assert(i == witness.length);
+    }
+
+    // Note: This was documented until 2013/08
+/*
+$(D Range) that locks the file and allows fast writing to it.
+ */
+    struct LockingTextWriter
+    {
+    private:
+        import std.range.primitives : ElementType, isInfinite, isInputRange;
+        FILE* fps_;          // the shared file handle
+        _iobuf* handle_;     // the unshared version of fps
+        int orientation_;
+    public:
+
+        this(ref File f) @trusted
+        {
+            import core.stdc.wchar_ : fwide;
+            import std.exception : enforce;
+
+            enforce(f._p && f._p.handle, "Attempting to write to closed File");
+            fps_ = f._p.handle;
+            orientation_ = fwide(fps_, 0);
+            FLOCK(fps_);
+            handle_ = cast(_iobuf*)fps_;
+        }
+
+        ~this() @trusted
+        {
+            if(fps_)
+            {
+                FUNLOCK(fps_);
+                fps_ = null;
+                handle_ = null;
+            }
+        }
+
+        this(this) @trusted
+        {
+            if(fps_)
+            {
+                FLOCK(fps_);
+            }
+        }
+
+        /// Range primitive implementations.
+        void put(A)(A writeme)
+            if (is(ElementType!A : const(dchar)) &&
+                isInputRange!A &&
+                !isInfinite!A)
+        {
+            import std.exception : errnoEnforce;
+
+            alias C = ElementEncodingType!A;
+            static assert(!is(C == void));
+            static if (isSomeString!A && C.sizeof == 1)
+            {
+                if (orientation_ <= 0)
+                {
+                    //file.write(writeme); causes infinite recursion!!!
+                    //file.rawWrite(writeme);
+                    static auto trustedFwrite(in void* ptr, size_t size, size_t nmemb, FILE* stream) @trusted
+                    {
+                        return .fwrite(ptr, size, nmemb, stream);
+                    }
+                    auto result =
+                        trustedFwrite(writeme.ptr, C.sizeof, writeme.length, fps_);
+                    if (result != writeme.length) errnoEnforce(0);
+                    return;
+                }
+            }
+
+            // put each character in turn
+            foreach (dchar c; writeme)
+            {
+                put(c);
+            }
+        }
+
+        // @@@BUG@@@ 2340
+        //void front(C)(C c) if (is(C : dchar)) {
+        /// ditto
+        void put(C)(C c) @safe if (is(C : const(dchar)))
+        {
+            import std.traits : Parameters;
+            static auto trustedFPUTC(int ch, _iobuf* h) @trusted
+            {
+                return FPUTC(ch, h);
+            }
+            static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted
+            {
+                return FPUTWC(ch, h);
+            }
+
+            static if (c.sizeof == 1)
+            {
+                // simple char
+                if (orientation_ <= 0) trustedFPUTC(c, handle_);
+                else trustedFPUTWC(c, handle_);
+            }
+            else static if (c.sizeof == 2)
+            {
+                import std.utf : toUTF8;
+
+                if (orientation_ <= 0)
+                {
+                    if (c <= 0x7F)
+                    {
+                        trustedFPUTC(c, handle_);
+                    }
+                    else
+                    {
+                        char[4] buf;
+                        auto b = toUTF8(buf, c);
+                        foreach (i ; 0 .. b.length)
+                            trustedFPUTC(b[i], handle_);
+                    }
+                }
+                else
+                {
+                    trustedFPUTWC(c, handle_);
+                }
+            }
+            else // 32-bit characters
+            {
+                import std.utf : toUTF8;
+
+                if (orientation_ <= 0)
+                {
+                    if (c <= 0x7F)
+                    {
+                        trustedFPUTC(c, handle_);
+                    }
+                    else
+                    {
+                        char[4] buf = void;
+                        auto b = toUTF8(buf, c);
+                        foreach (i ; 0 .. b.length)
+                            trustedFPUTC(b[i], handle_);
+                    }
+                }
+                else
+                {
+                    version (Windows)
+                    {
+                        import std.utf : isValidDchar;
+
+                        assert(isValidDchar(c));
+                        if (c <= 0xFFFF)
+                        {
+                            trustedFPUTWC(c, handle_);
+                        }
+                        else
+                        {
+                            trustedFPUTWC(cast(wchar)
+                                    ((((c - 0x10000) >> 10) & 0x3FF)
+                                            + 0xD800), handle_);
+                            trustedFPUTWC(cast(wchar)
+                                    (((c - 0x10000) & 0x3FF) + 0xDC00),
+                                    handle_);
+                        }
+                    }
+                    else version (Posix)
+                    {
+                        trustedFPUTWC(c, handle_);
+                    }
+                    else
+                    {
+                        static assert(0);
+                    }
+                }
+            }
+        }
+    }
+
+/** Returns an output range that locks the file and allows fast writing to it.
+
+See $(LREF byChunk) for an example.
+*/
+    auto lockingTextWriter() @safe
+    {
+        return LockingTextWriter(this);
+    }
+
+/// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs.
+    @property ulong size() @safe
+    {
+        import std.exception : collectException;
+
+        ulong pos = void;
+        if (collectException(pos = tell)) return ulong.max;
+        scope(exit) seek(pos);
+        seek(0, SEEK_END);
+        return tell;
+    }
+}
+
+unittest
+{
+    @system struct SystemToString
+    {
+        string toString()
+        {
+            return "system";
+        }
+    }
+
+    @trusted struct TrustedToString
+    {
+        string toString()
+        {
+            return "trusted";
+        }
+    }
+
+    @safe struct SafeToString
+    {
+        string toString()
+        {
+            return "safe";
+        }
+    }
+
+    @system void systemTests()
+    {
+        //system code can write to files/stdout with anything!
+        if(false)
+        {
+            auto f = File();
+
+            f.write("just a string");
+            f.write("string with arg: ", 47);
+            f.write(SystemToString());
+            f.write(TrustedToString());
+            f.write(SafeToString());
+
+            write("just a string");
+            write("string with arg: ", 47);
+            write(SystemToString());
+            write(TrustedToString());
+            write(SafeToString());
+
+            f.writeln("just a string");
+            f.writeln("string with arg: ", 47);
+            f.writeln(SystemToString());
+            f.writeln(TrustedToString());
+            f.writeln(SafeToString());
+
+            writeln("just a string");
+            writeln("string with arg: ", 47);
+            writeln(SystemToString());
+            writeln(TrustedToString());
+            writeln(SafeToString());
+
+            f.writef("string with arg: %s", 47);
+            f.writef("%s", SystemToString());
+            f.writef("%s", TrustedToString());
+            f.writef("%s", SafeToString());
+
+            writef("string with arg: %s", 47);
+            writef("%s", SystemToString());
+            writef("%s", TrustedToString());
+            writef("%s", SafeToString());
+
+            f.writefln("string with arg: %s", 47);
+            f.writefln("%s", SystemToString());
+            f.writefln("%s", TrustedToString());
+            f.writefln("%s", SafeToString());
+
+            writefln("string with arg: %s", 47);
+            writefln("%s", SystemToString());
+            writefln("%s", TrustedToString());
+            writefln("%s", SafeToString());
+        }
+    }
+
+    @safe void safeTests()
+    {
+        auto f = File();
+
+        //safe code can write to files only with @safe and @trusted code...
+        if(false)
+        {
+            f.write("just a string");
+            f.write("string with arg: ", 47);
+            f.write(TrustedToString());
+            f.write(SafeToString());
+
+            write("just a string");
+            write("string with arg: ", 47);
+            write(TrustedToString());
+            write(SafeToString());
+
+            f.writeln("just a string");
+            f.writeln("string with arg: ", 47);
+            f.writeln(TrustedToString());
+            f.writeln(SafeToString());
+
+            writeln("just a string");
+            writeln("string with arg: ", 47);
+            writeln(TrustedToString());
+            writeln(SafeToString());
+
+            f.writef("string with arg: %s", 47);
+            f.writef("%s", TrustedToString());
+            f.writef("%s", SafeToString());
+
+            writef("string with arg: %s", 47);
+            writef("%s", TrustedToString());
+            writef("%s", SafeToString());
+
+            f.writefln("string with arg: %s", 47);
+            f.writefln("%s", TrustedToString());
+            f.writefln("%s", SafeToString());
+
+            writefln("string with arg: %s", 47);
+            writefln("%s", TrustedToString());
+            writefln("%s", SafeToString());
+        }
+
+        static assert(!__traits(compiles, f.write(SystemToString().toString())));
+        static assert(!__traits(compiles, f.writeln(SystemToString())));
+        static assert(!__traits(compiles, f.writef("%s", SystemToString())));
+        static assert(!__traits(compiles, f.writefln("%s", SystemToString())));
+
+        static assert(!__traits(compiles, write(SystemToString().toString())));
+        static assert(!__traits(compiles, writeln(SystemToString())));
+        static assert(!__traits(compiles, writef("%s", SystemToString())));
+        static assert(!__traits(compiles, writefln("%s", SystemToString())));
+    }
+
+    systemTests();
+    safeTests();
+}
+
+unittest
+{
+    static import std.file;
+    import std.exception : collectException;
+
+    auto deleteme = testFilename();
+    scope(exit) collectException(std.file.remove(deleteme));
+    std.file.write(deleteme, "1 2 3");
+    auto f = File(deleteme);
+    assert(f.size == 5);
+    assert(f.tell == 0);
+}
+
+unittest
+{
+    static import std.file;
+    import std.range;
+
+    auto deleteme = testFilename();
+    scope(exit) std.file.remove(deleteme);
+
+    {
+        File f = File(deleteme, "w");
+        auto writer = f.lockingTextWriter();
+        static assert(isOutputRange!(typeof(writer), dchar));
+        writer.put("日本語");
+        writer.put("日本語"w);
+        writer.put("日本語"d);
+        writer.put('日');
+        writer.put(chain(only('本'), only('語')));
+        writer.put(repeat('#', 12)); // BUG 11945
+    }
+    assert(File(deleteme).readln() == "日本語日本語日本語日本語############");
+}
+
+@safe unittest
+{
+    import std.exception: collectException;
+    auto e = collectException({ File f; f.writeln("Hello!"); }());
+    assert(e && e.msg == "Attempting to write to closed File");
+}
+
+/// Used to specify the lock type for $(D File.lock) and $(D File.tryLock).
+enum LockType
+{
+    /// Specifies a _read (shared) lock. A _read lock denies all processes
+    /// write access to the specified region of the file, including the
+    /// process that first locks the region. All processes can _read the
+    /// locked region. Multiple simultaneous _read locks are allowed, as
+    /// long as there are no exclusive locks.
+    read,
+    /// Specifies a read/write (exclusive) lock. A read/write lock denies all
+    /// other processes both read and write access to the locked file region.
+    /// If a segment has an exclusive lock, it may not have any shared locks
+    /// or other exclusive locks.
+    readWrite
+}
+
+struct LockingTextReader
+{
+    private File _f;
+    private char _front;
+    private bool _hasChar;
+
+    this(File f)
+    {
+        import std.exception : enforce;
+        enforce(f.isOpen, "LockingTextReader: File must be open");
+        _f = f;
+        FLOCK(_f._p.handle);
+    }
+
+    this(this)
+    {
+        FLOCK(_f._p.handle);
+    }
+
+    ~this()
+    {
+        if (_hasChar)
+            ungetc(_front, cast(FILE*)_f._p.handle);
+
+        // File locking has its own reference count
+        if (_f.isOpen) FUNLOCK(_f._p.handle);
+    }
+
+    void opAssign(LockingTextReader r)
+    {
+        import std.algorithm : swap;
+        swap(this, r);
+    }
+
+    @property bool empty()
+    {
+        if (!_hasChar)
+        {
+            if (!_f.isOpen || _f.eof)
+                return true;
+            immutable int c = FGETC(cast(_iobuf*) _f._p.handle);
+            if (c == EOF)
+            {
+                .destroy(_f);
+                return true;
+            }
+            _front = cast(char)c;
+            _hasChar = true;
+        }
+        return false;
+    }
+
+    @property char front()
+    {
+        if (!_hasChar)
+        {
+            version(assert)
+            {
+                import core.exception : RangeError;
+                if (empty)
+                    throw new RangeError();
+            }
+            else
+            {
+                empty;
+            }
+        }
+        return _front;
+    }
+
+    void popFront()
+    {
+        if (!_hasChar)
+            empty;
+        _hasChar = false;
+    }
+}
+
+unittest
+{
+    static import std.file;
+    import std.range.primitives : isInputRange;
+
+    static assert(isInputRange!LockingTextReader);
+    auto deleteme = testFilename();
+    std.file.write(deleteme, "1 2 3");
+    scope(exit) std.file.remove(deleteme);
+    int x, y;
+    auto f = File(deleteme);
+    f.readf("%s ", &x);
+    assert(x == 1);
+    f.readf("%d ", &x);
+    assert(x == 2);
+    f.readf("%d ", &x);
+    assert(x == 3);
+    //pragma(msg, "--- todo: readf ---");
+}
+
+unittest // bugzilla 13686
+{
+    static import std.file;
+    import std.algorithm : equal;
+
+    auto deleteme = testFilename();
+    std.file.write(deleteme, "Тест");
+    scope(exit) std.file.remove(deleteme);
+
+    string s;
+    File(deleteme).readf("%s", &s);
+    assert(s == "Тест");
+
+    import std.utf;
+    auto ltr = LockingTextReader(File(deleteme)).byDchar;
+    assert(equal(ltr, "Тест".byDchar));
+}
+
+unittest // bugzilla 12320
+{
+    static import std.file;
+    auto deleteme = testFilename();
+    std.file.write(deleteme, "ab");
+    scope(exit) std.file.remove(deleteme);
+    auto ltr = LockingTextReader(File(deleteme));
+    assert(ltr.front == 'a');
+    ltr.popFront();
+    assert(ltr.front == 'b');
+    ltr.popFront();
+    assert(ltr.empty);
+}
+
+unittest // bugzilla 14861
+{
+    static import std.file;
+    auto deleteme = testFilename();
+    File fw = File(deleteme, "w");
+    for(int i; i != 5000; i++)
+        fw.writeln(i, ";", "Иванов;Пётр;Петрович");
+    fw.close();
+    scope(exit) std.file.remove(deleteme);
+    // Test read
+    File fr = File(deleteme, "r");
+    scope (exit) fr.close();
+    int nom; string fam, nam, ot;
+    // Error format read
+    while(!fr.eof)
+        fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot);
+}
+
+/**
+ * Indicates whether $(D T) is a file handle of some kind.
+ */
+template isFileHandle(T)
+{
+    enum isFileHandle = is(T : FILE*) ||
+        is(T : File);
+}
+
+unittest
+{
+    static assert(isFileHandle!(FILE*));
+    static assert(isFileHandle!(File));
+}
+
+/**
+ * Property used by writeln/etc. so it can infer @safe since stdout is __gshared
+ */
+private @property File trustedStdout() @trusted { return stdout; }
+
+/***********************************
+For each argument $(D arg) in $(D args), format the argument (as per
+$(LINK2 std_conv.html, to!(string)(arg))) and write the resulting
+string to $(D args[0]). A call without any arguments will fail to
+compile.
+
+Throws: In case of an I/O error, throws an $(D StdioException).
+ */
+void write(T...)(T args) if (!is(T[0] : File))
+{
+    trustedStdout.write(args);
+}
+
+unittest
+{
+    static import std.file;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+    void[] buf;
+    if (false) write(buf);
+    // test write
+    auto deleteme = testFilename();
+    auto f = File(deleteme, "w");
+    f.write("Hello, ",  "world number ", 42, "!");
+    f.close();
+    scope(exit) { std.file.remove(deleteme); }
+    assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!");
+    // // test write on stdout
+    //auto saveStdout = stdout;
+    //scope(exit) stdout = saveStdout;
+    //stdout.open(file, "w");
+    Object obj;
+    //write("Hello, ",  "world number ", 42, "! ", obj);
+    //stdout.close();
+    // auto result = cast(char[]) std.file.read(file);
+    // assert(result == "Hello, world number 42! null", result);
+}
+
+/***********************************
+ * Equivalent to $(D write(args, '\n')).  Calling $(D writeln) without
+ * arguments is valid and just prints a newline to the standard
+ * output.
+ */
+void writeln(T...)(T args)
+{
+    import std.traits : isAggregateType;
+    static if (T.length == 0)
+    {
+        import std.exception : enforce;
+
+        enforce(fputc('\n', .trustedStdout._p.handle) == '\n', "fputc failed");
+    }
+    else static if (T.length == 1 &&
+                    is(typeof(args[0]) : const(char)[]) &&
+                    !is(typeof(args[0]) == enum) &&
+                    !is(Unqual!(typeof(args[0])) == typeof(null)) &&
+                    !isAggregateType!(typeof(args[0])))
+    {
+        import std.exception : enforce;
+
+        // Specialization for strings - a very frequent case
+        auto w = .trustedStdout.lockingTextWriter();
+
+        static if (isStaticArray!(typeof(args[0])))
+        {
+            w.put(args[0][]);
+        }
+        else
+        {
+            w.put(args[0]);
+        }
+        w.put('\n');
+    }
+    else
+    {
+        // Most general instance
+        trustedStdout.write(args, '\n');
+    }
+}
+
+@safe unittest
+{
+    // Just make sure the call compiles
+    if (false) writeln();
+
+    if (false) writeln("wyda");
+
+    // bug 8040
+    if (false) writeln(null);
+    if (false) writeln(">", null, "<");
+
+    // Bugzilla 14041
+    if (false)
+    {
+        char[8] a;
+        writeln(a);
+    }
+}
+
+unittest
+{
+    static import std.file;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+    // test writeln
+    auto deleteme = testFilename();
+    auto f = File(deleteme, "w");
+    scope(exit) { std.file.remove(deleteme); }
+    f.writeln("Hello, ",  "world number ", 42, "!");
+    f.close();
+    version (Windows)
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, world number 42!\r\n");
+    else
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, world number 42!\n");
+
+    // test writeln on stdout
+    auto saveStdout = stdout;
+    scope(exit) stdout = saveStdout;
+    stdout.open(deleteme, "w");
+    writeln("Hello, ",  "world number ", 42, "!");
+    stdout.close();
+    version (Windows)
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, world number 42!\r\n");
+    else
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, world number 42!\n");
+
+    stdout.open(deleteme, "w");
+    writeln("Hello!"c);
+    writeln("Hello!"w);    // bug 8386
+    writeln("Hello!"d);    // bug 8386
+    writeln("embedded\0null"c); // bug 8730
+    stdout.close();
+    version (Windows)
+        assert(cast(char[]) std.file.read(deleteme) ==
+            "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n");
+    else
+        assert(cast(char[]) std.file.read(deleteme) ==
+            "Hello!\nHello!\nHello!\nembedded\0null\n");
+}
+
+unittest
+{
+    static import std.file;
+
+    auto deleteme = testFilename();
+    auto f = File(deleteme, "w");
+    scope(exit) { std.file.remove(deleteme); }
+
+    enum EI : int    { A, B }
+    enum ED : double { A, B }
+    enum EC : char   { A, B }
+    enum ES : string { A = "aaa", B = "bbb" }
+
+    f.writeln(EI.A);  // false, but A on 2.058
+    f.writeln(EI.B);  // true, but B on 2.058
+
+    f.writeln(ED.A);  // A
+    f.writeln(ED.B);  // B
+
+    f.writeln(EC.A);  // A
+    f.writeln(EC.B);  // B
+
+    f.writeln(ES.A);  // A
+    f.writeln(ES.B);  // B
+
+    f.close();
+    version (Windows)
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n");
+    else
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "A\nB\nA\nB\nA\nB\nA\nB\n");
+}
+
+unittest
+{
+    static auto useInit(T)(T ltw)
+    {
+        T val;
+        val = ltw;
+        val = T.init;
+        return val;
+    }
+    useInit(stdout.lockingTextWriter());
+}
+
+
+/***********************************
+Writes formatted data to standard output (without a trailing newline).
+
+Params:
+args = The first argument $(D args[0]) should be the format string, specifying
+how to format the rest of the arguments. For a full description of the syntax
+of the format string and how it controls the formatting of the rest of the
+arguments, please refer to the documentation for $(XREF format,
+formattedWrite).
+
+Note: In older versions of Phobos, it used to be possible to write:
+
+------
+writef(stderr, "%s", "message");
+------
+
+to print a message to $(D stderr). This syntax is no longer supported, and has
+been superceded by:
+
+------
+stderr.writef("%s", "message");
+------
+
+*/
+
+void writef(T...)(T args)
+{
+    trustedStdout.writef(args);
+}
+
+unittest
+{
+    static import std.file;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+    // test writef
+    auto deleteme = testFilename();
+    auto f = File(deleteme, "w");
+    scope(exit) { std.file.remove(deleteme); }
+    f.writef("Hello, %s world number %s!", "nice", 42);
+    f.close();
+    assert(cast(char[]) std.file.read(deleteme) ==  "Hello, nice world number 42!");
+    // test write on stdout
+    auto saveStdout = stdout;
+    scope(exit) stdout = saveStdout;
+    stdout.open(deleteme, "w");
+    writef("Hello, %s world number %s!", "nice", 42);
+    stdout.close();
+    assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!");
+}
+
+/***********************************
+ * Equivalent to $(D writef(args, '\n')).
+ */
+void writefln(T...)(T args)
+{
+    trustedStdout.writefln(args);
+}
+
+unittest
+{
+    static import std.file;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+    // test writefln
+    auto deleteme = testFilename();
+    auto f = File(deleteme, "w");
+    scope(exit) { std.file.remove(deleteme); }
+    f.writefln("Hello, %s world number %s!", "nice", 42);
+    f.close();
+    version (Windows)
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, nice world number 42!\r\n");
+    else
+        assert(cast(char[]) std.file.read(deleteme) ==
+                "Hello, nice world number 42!\n",
+                cast(char[]) std.file.read(deleteme));
+    // test write on stdout
+    // auto saveStdout = stdout;
+    // scope(exit) stdout = saveStdout;
+    // stdout.open(file, "w");
+    // assert(stdout.isOpen);
+    // writefln("Hello, %s world number %s!", "nice", 42);
+    // foreach (F ; AliasSeq!(ifloat, idouble, ireal))
+    // {
+    //     F a = 5i;
+    //     F b = a % 2;
+    //     writeln(b);
+    // }
+    // stdout.close();
+    // auto read = cast(char[]) std.file.read(file);
+    // version (Windows)
+    //     assert(read == "Hello, nice world number 42!\r\n1\r\n1\r\n1\r\n", read);
+    // else
+    //     assert(read == "Hello, nice world number 42!\n1\n1\n1\n", "["~read~"]");
+}
+
+/**
+ * Read data from $(D stdin) according to the specified
+ * $(LINK2 std_format.html#format-string, format specifier) using
+ * $(XREF format,formattedRead).
+ */
+uint readf(A...)(in char[] format, A args)
+{
+    return stdin.readf(format, args);
+}
+
+unittest
+{
+    float f;
+    if (false) uint x = readf("%s", &f);
+
+    char a;
+    wchar b;
+    dchar c;
+    if (false) readf("%s %s %s", &a,&b,&c);
+}
+
+/**********************************
+ * Read line from $(D stdin).
+ *
+ * This version manages its own read buffer, which means one memory allocation per call. If you are not
+ * retaining a reference to the read data, consider the $(D readln(buf)) version, which may offer
+ * better performance as it can reuse its read buffer.
+ *
+ * Returns:
+ *        The line that was read, including the line terminator character.
+ * Params:
+ *        S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string).
+ *        terminator = Line terminator (by default, $(D '\n')).
+ * Note:
+ *        String terminators are not supported due to ambiguity with readln(buf) below.
+ * Throws:
+ *        $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error.
+ * Example:
+ *        Reads $(D stdin) and writes it to $(D stdout).
+---
+import std.stdio;
+
+void main()
+{
+    string line;
+    while ((line = readln()) !is null)
+        write(line);
+}
+---
+*/
+S readln(S = string)(dchar terminator = '\n')
+if (isSomeString!S)
+{
+    return stdin.readln!S(terminator);
+}
+
+/**********************************
+ * Read line from $(D stdin) and write it to buf[], including terminating character.
+ *
+ * This can be faster than $(D line = readln()) because you can reuse
+ * the buffer for each call. Note that reusing the buffer means that you
+ * must copy the previous contents if you wish to retain them.
+ *
+ * Returns:
+ *        $(D size_t) 0 for end of file, otherwise number of characters read
+ * Params:
+ *        buf = Buffer used to store the resulting line data. buf is resized as necessary.
+ *        terminator = Line terminator (by default, $(D '\n')). Use $(XREF ascii, newline)
+ *        for portability (unless the file was opened in text mode).
+ * Throws:
+ *        $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error.
+ * Example:
+ *        Reads $(D stdin) and writes it to $(D stdout).
+---
+import std.stdio;
+
+void main()
+{
+    char[] buf;
+    while (readln(buf))
+        write(buf);
+}
+---
+*/
+size_t readln(C)(ref C[] buf, dchar terminator = '\n')
+if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum))
+{
+    return stdin.readln(buf, terminator);
+}
+
+/** ditto */
+size_t readln(C, R)(ref C[] buf, R terminator)
+if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
+    isBidirectionalRange!R && is(typeof(terminator.front == dchar.init)))
+{
+    return stdin.readln(buf, terminator);
+}
+
+unittest
+{
+    import std.meta : AliasSeq;
+
+    //we can't actually test readln, so at the very least,
+    //we test compilability
+    void foo()
+    {
+        readln();
+        readln('\t');
+        foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
+        {
+            readln!String();
+            readln!String('\t');
+        }
+        foreach (String; AliasSeq!(char[], wchar[], dchar[]))
+        {
+            String buf;
+            readln(buf);
+            readln(buf, '\t');
+            readln(buf, "<br />");
+        }
+    }
+}
+
+/*
+ * Convenience function that forwards to $(D core.sys.posix.stdio.fopen)
+ * (to $(D _wfopen) on Windows)
+ * with appropriately-constructed C-style strings.
+ */
+private FILE* fopen(R1, R2)(R1 name, R2 mode = "r")
+    if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) &&
+        (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2))
+{
+    import std.internal.cstring : tempCString;
+
+    auto namez = name.tempCString!FSChar();
+    auto modez = mode.tempCString!FSChar();
+
+    static fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc
+    {
+        version(Windows)
+        {
+            return _wfopen(namez, modez);
+        }
+        else version(Posix)
+        {
+            /*
+             * The new opengroup large file support API is transparently
+             * included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0
+             * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and
+             * the normal functions work fine. If not, then large file support
+             * probably isn't available. Do not use the old transitional API
+             * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0)
+             */
+            import core.sys.posix.stdio : fopen;
+            return fopen(namez, modez);
+        }
+        else
+        {
+            return .fopen(namez, modez);
+        }
+    }
+    return fopenImpl(namez, modez);
+}
+
+version (Posix)
+{
+    /***********************************
+     * Convenience function that forwards to $(D core.sys.posix.stdio.popen)
+     * with appropriately-constructed C-style strings.
+     */
+    FILE* popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc
+        if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) &&
+            (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2))
+    {
+        import std.internal.cstring : tempCString;
+
+        auto namez = name.tempCString!FSChar();
+        auto modez = mode.tempCString!FSChar();
+
+        static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc
+        {
+            import core.sys.posix.stdio : popen;
+            return popen(namez, modez);
+        }
+        return popenImpl(namez, modez);
+    }
+}
+
+/*
+ * Convenience function that forwards to $(D core.stdc.stdio.fwrite)
+ * and throws an exception upon error
+ */
+private void binaryWrite(T)(FILE* f, T obj)
+{
+    immutable result = fwrite(obj.ptr, obj[0].sizeof, obj.length, f);
+    if (result != obj.length) StdioException();
+}
+
+/**
+ * Iterates through the lines of a file by using $(D foreach).
+ *
+ * Example:
+ *
+---------
+void main()
+{
+  foreach (string line; lines(stdin))
+  {
+    ... use line ...
+  }
+}
+---------
+The line terminator ($(D '\n') by default) is part of the string read (it
+could be missing in the last line of the file). Several types are
+supported for $(D line), and the behavior of $(D lines)
+changes accordingly:
+
+$(OL $(LI If $(D line) has type $(D string), $(D
+wstring), or $(D dstring), a new string of the respective type
+is allocated every read.) $(LI If $(D line) has type $(D
+char[]), $(D wchar[]), $(D dchar[]), the line's content
+will be reused (overwritten) across reads.) $(LI If $(D line)
+has type $(D immutable(ubyte)[]), the behavior is similar to
+case (1), except that no UTF checking is attempted upon input.) $(LI
+If $(D line) has type $(D ubyte[]), the behavior is
+similar to case (2), except that no UTF checking is attempted upon
+input.))
+
+In all cases, a two-symbols versions is also accepted, in which case
+the first symbol (of integral type, e.g. $(D ulong) or $(D
+uint)) tracks the zero-based number of the current line.
+
+Example:
+----
+  foreach (ulong i, string line; lines(stdin))
+  {
+    ... use line ...
+  }
+----
+
+ In case of an I/O error, an $(D StdioException) is thrown.
+
+See_Also:
+$(LREF byLine)
+ */
+
+struct lines
+{
+    private File f;
+    private dchar terminator = '\n';
+    // private string fileName;  // Curretly, no use
+
+    /**
+    Constructor.
+    Params:
+    f = File to read lines from.
+    terminator = Line separator ($(D '\n') by default).
+    */
+    this(File f, dchar terminator = '\n')
+    {
+        this.f = f;
+        this.terminator = terminator;
+    }
+
+    // Keep these commented lines for later, when Walter fixes the
+    // exception model.
+
+//     static lines opCall(string fName, dchar terminator = '\n')
+//     {
+//         auto f = enforce(fopen(fName),
+//             new StdioException("Cannot open file `"~fName~"' for reading"));
+//         auto result = lines(f, terminator);
+//         result.fileName = fName;
+//         return result;
+//     }
+
+    int opApply(D)(scope D dg)
+    {
+//         scope(exit) {
+//             if (fileName.length && fclose(f))
+//                 StdioException("Could not close file `"~fileName~"'");
+//         }
+        import std.traits : Parameters;
+        alias Parms = Parameters!(dg);
+        static if (isSomeString!(Parms[$ - 1]))
+        {
+            enum bool duplicate = is(Parms[$ - 1] == string)
+                || is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring);
+            int result = 0;
+            static if (is(Parms[$ - 1] : const(char)[]))
+                alias C = char;
+            else static if (is(Parms[$ - 1] : const(wchar)[]))
+                alias C = wchar;
+            else static if (is(Parms[$ - 1] : const(dchar)[]))
+                alias C = dchar;
+            C[] line;
+            static if (Parms.length == 2)
+                Parms[0] i = 0;
+            for (;;)
+            {
+                import std.conv : to;
+
+                if (!f.readln(line, terminator)) break;
+                auto copy = to!(Parms[$ - 1])(line);
+                static if (Parms.length == 2)
+                {
+                    result = dg(i, copy);
+                    ++i;
+                }
+                else
+                {
+                    result = dg(copy);
+                }
+                if (result != 0) break;
+            }
+            return result;
+        }
+        else
+        {
+            // raw read
+            return opApplyRaw(dg);
+        }
+    }
+    // no UTF checking
+    int opApplyRaw(D)(scope D dg)
+    {
+        import std.exception : assumeUnique;
+        import std.conv : to;
+        import std.traits : Parameters;
+
+        alias Parms = Parameters!(dg);
+        enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]);
+        int result = 1;
+        int c = void;
+        FLOCK(f._p.handle);
+        scope(exit) FUNLOCK(f._p.handle);
+        ubyte[] buffer;
+        static if (Parms.length == 2)
+            Parms[0] line = 0;
+        while ((c = FGETC(cast(_iobuf*)f._p.handle)) != -1)
+        {
+            buffer ~= to!(ubyte)(c);
+            if (c == terminator)
+            {
+                static if (duplicate)
+                    auto arg = assumeUnique(buffer);
+                else
+                    alias arg = buffer;
+                // unlock the file while calling the delegate
+                FUNLOCK(f._p.handle);
+                scope(exit) FLOCK(f._p.handle);
+                static if (Parms.length == 1)
+                {
+                    result = dg(arg);
+                }
+                else
+                {
+                    result = dg(line, arg);
+                    ++line;
+                }
+                if (result) break;
+                static if (!duplicate)
+                    buffer.length = 0;
+            }
+        }
+        // can only reach when FGETC returned -1
+        if (!f.eof) throw new StdioException("Error in reading file"); // error occured
+        return result;
+    }
+}
+
+unittest
+{
+    static import std.file;
+    import std.meta : AliasSeq;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+    auto deleteme = testFilename();
+    scope(exit) { std.file.remove(deleteme); }
+
+    alias TestedWith =
+          AliasSeq!(string, wstring, dstring,
+                    char[], wchar[], dchar[]);
+    foreach (T; TestedWith) {
+        // test looping with an empty file
+        std.file.write(deleteme, "");
+        auto f = File(deleteme, "r");
+        foreach (T line; lines(f))
+        {
+            assert(false);
+        }
+        f.close();
+
+        // test looping with a file with three lines
+        std.file.write(deleteme, "Line one\nline two\nline three\n");
+        f.open(deleteme, "r");
+        uint i = 0;
+        foreach (T line; lines(f))
+        {
+            if (i == 0) assert(line == "Line one\n");
+            else if (i == 1) assert(line == "line two\n");
+            else if (i == 2) assert(line == "line three\n");
+            else assert(false);
+            ++i;
+        }
+        f.close();
+
+        // test looping with a file with three lines, last without a newline
+        std.file.write(deleteme, "Line one\nline two\nline three");
+        f.open(deleteme, "r");
+        i = 0;
+        foreach (T line; lines(f))
+        {
+            if (i == 0) assert(line == "Line one\n");
+            else if (i == 1) assert(line == "line two\n");
+            else if (i == 2) assert(line == "line three");
+            else assert(false);
+            ++i;
+        }
+        f.close();
+    }
+
+    // test with ubyte[] inputs
+    alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]);
+    foreach (T; TestedWith2) {
+        // test looping with an empty file
+        std.file.write(deleteme, "");
+        auto f = File(deleteme, "r");
+        foreach (T line; lines(f))
+        {
+            assert(false);
+        }
+        f.close();
+
+        // test looping with a file with three lines
+        std.file.write(deleteme, "Line one\nline two\nline three\n");
+        f.open(deleteme, "r");
+        uint i = 0;
+        foreach (T line; lines(f))
+        {
+            if (i == 0) assert(cast(char[]) line == "Line one\n");
+            else if (i == 1) assert(cast(char[]) line == "line two\n",
+                T.stringof ~ " " ~ cast(char[]) line);
+            else if (i == 2) assert(cast(char[]) line == "line three\n");
+            else assert(false);
+            ++i;
+        }
+        f.close();
+
+        // test looping with a file with three lines, last without a newline
+        std.file.write(deleteme, "Line one\nline two\nline three");
+        f.open(deleteme, "r");
+        i = 0;
+        foreach (T line; lines(f))
+        {
+            if (i == 0) assert(cast(char[]) line == "Line one\n");
+            else if (i == 1) assert(cast(char[]) line == "line two\n");
+            else if (i == 2) assert(cast(char[]) line == "line three");
+            else assert(false);
+            ++i;
+        }
+        f.close();
+
+    }
+
+    foreach (T; AliasSeq!(ubyte[]))
+    {
+        // test looping with a file with three lines, last without a newline
+        // using a counter too this time
+        std.file.write(deleteme, "Line one\nline two\nline three");
+        auto f = File(deleteme, "r");
+        uint i = 0;
+        foreach (ulong j, T line; lines(f))
+        {
+            if (i == 0) assert(cast(char[]) line == "Line one\n");
+            else if (i == 1) assert(cast(char[]) line == "line two\n");
+            else if (i == 2) assert(cast(char[]) line == "line three");
+            else assert(false);
+            ++i;
+        }
+        f.close();
+    }
+}
+
+/**
+Iterates through a file a chunk at a time by using $(D foreach).
+
+Example:
+
+---------
+void main()
+{
+    foreach (ubyte[] buffer; chunks(stdin, 4096))
+    {
+        ... use buffer ...
+    }
+}
+---------
+
+The content of $(D buffer) is reused across calls. In the
+ example above, $(D buffer.length) is 4096 for all iterations,
+ except for the last one, in which case $(D buffer.length) may
+ be less than 4096 (but always greater than zero).
+
+ In case of an I/O error, an $(D StdioException) is thrown.
+*/
+auto chunks(File f, size_t size)
+{
+    return ChunksImpl(f, size);
+}
+private struct ChunksImpl
+{
+    private File f;
+    private size_t size;
+    // private string fileName; // Currently, no use
+
+    this(File f, size_t size)
+    in
+    {
+        assert(size, "size must be larger than 0");
+    }
+    body
+    {
+        this.f = f;
+        this.size = size;
+    }
+
+//     static chunks opCall(string fName, size_t size)
+//     {
+//         auto f = enforce(fopen(fName),
+//             new StdioException("Cannot open file `"~fName~"' for reading"));
+//         auto result = chunks(f, size);
+//         result.fileName  = fName;
+//         return result;
+//     }
+
+    int opApply(D)(scope D dg)
+    {
+        import core.stdc.stdlib : alloca;
+        enum maxStackSize = 1024 * 16;
+        ubyte[] buffer = void;
+        if (size < maxStackSize)
+            buffer = (cast(ubyte*) alloca(size))[0 .. size];
+        else
+            buffer = new ubyte[size];
+        size_t r = void;
+        int result = 1;
+        uint tally = 0;
+        while ((r = fread(buffer.ptr, buffer[0].sizeof, size, f._p.handle)) > 0)
+        {
+            assert(r <= size);
+            if (r != size)
+            {
+                // error occured
+                if (!f.eof) throw new StdioException(null);
+                buffer.length = r;
+            }
+            static if (is(typeof(dg(tally, buffer)))) {
+                if ((result = dg(tally, buffer)) != 0) break;
+            } else {
+                if ((result = dg(buffer)) != 0) break;
+            }
+            ++tally;
+        }
+        return result;
+    }
+}
+
+unittest
+{
+    static import std.file;
+
+    //printf("Entering test at line %d\n", __LINE__);
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+
+    auto deleteme = testFilename();
+    scope(exit) { std.file.remove(deleteme); }
+
+    // test looping with an empty file
+    std.file.write(deleteme, "");
+    auto f = File(deleteme, "r");
+    foreach (ubyte[] line; chunks(f, 4))
+    {
+        assert(false);
+    }
+    f.close();
+
+    // test looping with a file with three lines
+    std.file.write(deleteme, "Line one\nline two\nline three\n");
+    f = File(deleteme, "r");
+    uint i = 0;
+    foreach (ubyte[] line; chunks(f, 3))
+    {
+        if (i == 0) assert(cast(char[]) line == "Lin");
+        else if (i == 1) assert(cast(char[]) line == "e o");
+        else if (i == 2) assert(cast(char[]) line == "ne\n");
+        else break;
+        ++i;
+    }
+    f.close();
+}
+
+/*********************
+ * Thrown if I/O errors happen.
+ */
+class StdioException : Exception
+{
+    static import core.stdc.errno;
+    /// Operating system error code.
+    uint errno;
+
+/**
+Initialize with a message and an error code.
+*/
+    this(string message, uint e = core.stdc.errno.errno)
+    {
+        import std.conv : to;
+
+        errno = e;
+        version (Posix)
+        {
+            import core.stdc.string : strerror_r;
+
+            char[256] buf = void;
+            version (CRuntime_Glibc)
+            {
+                auto s = strerror_r(errno, buf.ptr, buf.length);
+            }
+            else
+            {
+                strerror_r(errno, buf.ptr, buf.length);
+                auto s = buf.ptr;
+            }
+        }
+        else
+        {
+            import core.stdc.string : strerror;
+
+            auto s = core.stdc.string.strerror(errno);
+        }
+        auto sysmsg = to!string(s);
+        // If e is 0, we don't use the system error message.  (The message
+        // is "Success", which is rather pointless for an exception.)
+        super(e == 0 ? message
+                     : (message.ptr ? message ~ " (" ~ sysmsg ~ ")" : sysmsg));
+    }
+
+/** Convenience functions that throw an $(D StdioException). */
+    static void opCall(string msg)
+    {
+        throw new StdioException(msg);
+    }
+
+/// ditto
+    static void opCall()
+    {
+        throw new StdioException(null, core.stdc.errno.errno);
+    }
+}
+
+extern(C) void std_stdio_static_this()
+{
+    static import core.stdc.stdio;
+    //Bind stdin, stdout, stderr
+    __gshared File.Impl stdinImpl;
+    stdinImpl.handle = core.stdc.stdio.stdin;
+    .stdin._p = &stdinImpl;
+    // stdout
+    __gshared File.Impl stdoutImpl;
+    stdoutImpl.handle = core.stdc.stdio.stdout;
+    .stdout._p = &stdoutImpl;
+    // stderr
+    __gshared File.Impl stderrImpl;
+    stderrImpl.handle = core.stdc.stdio.stderr;
+    .stderr._p = &stderrImpl;
+}
+
+//---------
+__gshared
+{
+    /** The standard input stream.
+        Bugs:
+            Due to $(WEB https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768),
+            it is thread un-safe to reassign `stdin` to a different `File` instance
+            than the default.
+     */
+    File stdin;
+    ///
+    unittest
+    {
+        // Read stdin, sort lines, write to stdout
+        import std.stdio, std.array, std.algorithm : sort, copy;
+
+        void main() {
+            stdin                       // read from stdin
+            .byLineCopy(KeepTerminator.yes) // copying each line
+            .array()                    // convert to array of lines
+            .sort()                     // sort the lines
+            .copy(                      // copy output of .sort to an OutputRange
+                stdout.lockingTextWriter()); // the OutputRange
+        }
+    }
+
+    /**
+        The standard output stream.
+        Bugs:
+            Due to $(WEB https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768),
+            it is thread un-safe to reassign `stdout` to a different `File` instance
+            than the default.
+    */
+    File stdout;
+    /**
+        The standard error stream.
+        Bugs:
+            Due to $(WEB https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768),
+            it is thread un-safe to reassign `stderr` to a different `File` instance
+            than the default.
+    */
+    File stderr;
+}
+
+unittest
+{
+    static import std.file;
+    import std.typecons : tuple;
+
+    scope(failure) printf("Failed test at line %d\n", __LINE__);
+    auto deleteme = testFilename();
+
+    std.file.write(deleteme, "1 2\n4 1\n5 100");
+    scope(exit) std.file.remove(deleteme);
+    {
+        File f = File(deleteme);
+        scope(exit) f.close();
+        auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ];
+        uint i;
+        foreach (e; f.byRecord!(int, int)("%s %s"))
+        {
+            //writeln(e);
+            assert(e == t[i++]);
+        }
+        assert(i == 3);
+    }
+}
+
+// roll our own appender, but with "safe" arrays
+private struct ReadlnAppender
+{
+    import core.stdc.string;
+
+    char[] buf;
+    size_t pos;
+    bool safeAppend = false;
+
+    void initialize(char[] b)
+    {
+        buf = b;
+        pos = 0;
+    }
+    @property char[] data()
+    {
+        if (safeAppend)
+            assumeSafeAppend(buf.ptr[0..pos]);
+        return buf.ptr[0..pos];
+    }
+
+    bool reserveWithoutAllocating(size_t n)
+    {
+        if (buf.length >= pos + n) // buf is already large enough
+            return true;
+
+        immutable curCap = buf.capacity;
+        if (curCap >= pos + n)
+        {
+            buf.length = curCap;
+            /* Any extra capacity we end up not using can safely be claimed
+            by someone else. */
+            safeAppend = true;
+            return true;
+        }
+
+        return false;
+    }
+    void reserve(size_t n)
+    {
+        if (!reserveWithoutAllocating(n))
+        {
+            size_t ncap = buf.length * 2 + 128 + n;
+            char[] nbuf = new char[ncap];
+            memcpy(nbuf.ptr, buf.ptr, pos);
+            buf = nbuf;
+            // Allocated a new buffer. No one else knows about it.
+            safeAppend = true;
+        }
+    }
+    void putchar(char c)
+    {
+        reserve(1);
+        buf.ptr[pos++] = c;
+    }
+    void putdchar(dchar dc)
+    {
+        import std.utf : toUTF8;
+
+        char[4] ubuf;
+        char[] u = toUTF8(ubuf, dc);
+        reserve(u.length);
+        foreach(c; u)
+            buf.ptr[pos++] = c;
+    }
+    void putonly(char[] b)
+    {
+        assert(pos == 0);   // assume this is the only put call
+        if (reserveWithoutAllocating(b.length))
+            memcpy(buf.ptr + pos, b.ptr, b.length);
+        else
+            buf = b.dup;
+        pos = b.length;
+    }
+}
+
+// Private implementation of readln
+version (DIGITAL_MARS_STDIO)
+private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/)
+{
+    FLOCK(fps);
+    scope(exit) FUNLOCK(fps);
+
+    /* Since fps is now locked, we can create an "unshared" version
+     * of fp.
+     */
+    auto fp = cast(_iobuf*)fps;
+
+    ReadlnAppender app;
+    app.initialize(buf);
+
+    if (__fhnd_info[fp._file] & FHND_WCHAR)
+    {   /* Stream is in wide characters.
+         * Read them and convert to chars.
+         */
+        static assert(wchar_t.sizeof == 2);
+        for (int c = void; (c = FGETWC(fp)) != -1; )
+        {
+            if ((c & ~0x7F) == 0)
+            {
+                app.putchar(cast(char) c);
+                if (c == terminator)
+                    break;
+            }
+            else
+            {
+                if (c >= 0xD800 && c <= 0xDBFF)
+                {
+                    int c2 = void;
+                    if ((c2 = FGETWC(fp)) != -1 ||
+                            c2 < 0xDC00 && c2 > 0xDFFF)
+                    {
+                        StdioException("unpaired UTF-16 surrogate");
+                    }
+                    c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
+                }
+                app.putdchar(cast(dchar)c);
+            }
+        }
+        if (ferror(fps))
+            StdioException();
+    }
+
+    else if (fp._flag & _IONBF)
+    {
+        /* Use this for unbuffered I/O, when running
+         * across buffer boundaries, or for any but the common
+         * cases.
+         */
+      L1:
+        int c;
+        while((c = FGETC(fp)) != -1) {
+            app.putchar(cast(char) c);
+            if(c == terminator) {
+                buf = app.data;
+                return buf.length;
+            }
+
+        }
+
+        if (ferror(fps))
+            StdioException();
+    }
+    else
+    {
+        int u = fp._cnt;
+        char* p = fp._ptr;
+        int i;
+        if (fp._flag & _IOTRAN)
+        {   /* Translated mode ignores \r and treats ^Z as end-of-file
+             */
+            char c;
+            while (1)
+            {
+                if (i == u)                // if end of buffer
+                    goto L1;        // give up
+                c = p[i];
+                i++;
+                if (c != '\r')
+                {
+                    if (c == terminator)
+                        break;
+                    if (c != 0x1A)
+                        continue;
+                    goto L1;
+                }
+                else
+                {   if (i != u && p[i] == terminator)
+                        break;
+                    goto L1;
+                }
+            }
+            app.putonly(p[0..i]);
+            app.buf.ptr[i - 1] = cast(char)terminator;
+            if (terminator == '\n' && c == '\r')
+                i++;
+        }
+        else
+        {
+            while (1)
+            {
+                if (i == u)                // if end of buffer
+                    goto L1;        // give up
+                auto c = p[i];
+                i++;
+                if (c == terminator)
+                    break;
+            }
+            app.putonly(p[0..i]);
+        }
+        fp._cnt -= i;
+        fp._ptr += i;
+    }
+
+    buf = app.data;
+    return buf.length;
+}
+
+version (MICROSOFT_STDIO)
+private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/)
+{
+    import core.memory;
+
+    FLOCK(fps);
+    scope(exit) FUNLOCK(fps);
+
+    /* Since fps is now locked, we can create an "unshared" version
+     * of fp.
+     */
+    auto fp = cast(_iobuf*)fps;
+
+    ReadlnAppender app;
+    app.initialize(buf);
+
+    int c;
+    while((c = FGETC(fp)) != -1) {
+        app.putchar(cast(char) c);
+        if(c == terminator) {
+            buf = app.data;
+            return buf.length;
+        }
+
+    }
+
+    if (ferror(fps))
+        StdioException();
+    buf = app.data;
+    return buf.length;
+}
+
+version (HAS_GETDELIM)
+private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation)
+{
+    import core.memory;
+    import core.stdc.stdlib : free;
+    import core.stdc.wchar_ : fwide;
+
+    if (orientation == File.Orientation.wide)
+    {
+        /* Stream is in wide characters.
+         * Read them and convert to chars.
+         */
+        FLOCK(fps);
+        scope(exit) FUNLOCK(fps);
+        auto fp = cast(_iobuf*)fps;
+        version (Windows)
+        {
+            buf.length = 0;
+            for (int c = void; (c = FGETWC(fp)) != -1; )
+            {
+                if ((c & ~0x7F) == 0)
+                {   buf ~= c;
+                    if (c == terminator)
+                        break;
+                }
+                else
+                {
+                    if (c >= 0xD800 && c <= 0xDBFF)
+                    {
+                        int c2 = void;
+                        if ((c2 = FGETWC(fp)) != -1 ||
+                                c2 < 0xDC00 && c2 > 0xDFFF)
+                        {
+                            StdioException("unpaired UTF-16 surrogate");
+                        }
+                        c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
+                    }
+                    import std.utf : encode;
+                    encode(buf, c);
+                }
+            }
+            if (ferror(fp))
+                StdioException();
+            return buf.length;
+        }
+        else version (Posix)
+        {
+            buf.length = 0;
+            for (int c; (c = FGETWC(fp)) != -1; )
+            {
+                import std.utf : encode;
+
+                if ((c & ~0x7F) == 0)
+                    buf ~= cast(char)c;
+                else
+                    encode(buf, cast(dchar)c);
+                if (c == terminator)
+                    break;
+            }
+            if (ferror(fps))
+                StdioException();
+            return buf.length;
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+
+    static char *lineptr = null;
+    static size_t n = 0;
+    scope(exit)
+    {
+        if (n > 128 * 1024)
+        {
+            // Bound memory used by readln
+            free(lineptr);
+            lineptr = null;
+            n = 0;
+        }
+    }
+
+    auto s = getdelim(&lineptr, &n, terminator, fps);
+    if (s < 0)
+    {
+        if (ferror(fps))
+            StdioException();
+        buf.length = 0;                // end of file
+        return 0;
+    }
+
+    if (s <= buf.length)
+    {
+        buf = buf.ptr[0 .. s];
+        buf[] = lineptr[0 .. s];
+    }
+    else
+    {
+        buf = lineptr[0 .. s].dup;
+    }
+    return s;
+}
+
+version (NO_GETDELIM)
+private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation)
+{
+    import core.stdc.wchar_ : fwide;
+
+    FLOCK(fps);
+    scope(exit) FUNLOCK(fps);
+    auto fp = cast(_iobuf*)fps;
+    if (orientation == File.Orientation.wide)
+    {
+        /* Stream is in wide characters.
+         * Read them and convert to chars.
+         */
+        version (Windows)
+        {
+            buf.length = 0;
+            for (int c; (c = FGETWC(fp)) != -1; )
+            {
+                if ((c & ~0x7F) == 0)
+                {   buf ~= c;
+                    if (c == terminator)
+                        break;
+                }
+                else
+                {
+                    if (c >= 0xD800 && c <= 0xDBFF)
+                    {
+                        int c2 = void;
+                        if ((c2 = FGETWC(fp)) != -1 ||
+                                c2 < 0xDC00 && c2 > 0xDFFF)
+                        {
+                            StdioException("unpaired UTF-16 surrogate");
+                        }
+                        c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
+                    }
+                    import std.utf : encode;
+                    encode(buf, c);
+                }
+            }
+            if (ferror(fp))
+                StdioException();
+            return buf.length;
+        }
+        else version (Posix)
+        {
+            buf.length = 0;
+            for (int c; (c = FGETWC(fp)) != -1; )
+            {
+                if ((c & ~0x7F) == 0)
+                    buf ~= cast(char)c;
+                else
+                    std.utf.encode(buf, cast(dchar)c);
+                if (c == terminator)
+                    break;
+            }
+            if (ferror(fps))
+                StdioException();
+            return buf.length;
+        }
+        else
+        {
+            static assert(0);
+        }
+    }
+
+    // Narrow stream
+    // First, fill the existing buffer
+    for (size_t bufPos = 0; bufPos < buf.length; )
+    {
+        immutable c = FGETC(fp);
+        if (c == -1)
+        {
+            buf.length = bufPos;
+            goto endGame;
+        }
+        buf.ptr[bufPos++] = cast(char) c;
+        if (c == terminator)
+        {
+            // No need to test for errors in file
+            buf.length = bufPos;
+            return bufPos;
+        }
+    }
+    // Then, append to it
+    for (int c; (c = FGETC(fp)) != -1; )
+    {
+        buf ~= cast(char)c;
+        if (c == terminator)
+        {
+            // No need to test for errors in file
+            return buf.length;
+        }
+    }
+
+  endGame:
+    if (ferror(fps))
+        StdioException();
+    return buf.length;
+}
+
+unittest
+{
+    static import std.file;
+    auto deleteme = testFilename();
+    scope(exit) std.file.remove(deleteme);
+
+    std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n");
+    File f = File(deleteme, "rb");
+
+    char[] ln = new char[2];
+    //assert(ln.capacity > 5);
+    char* lnptr = ln.ptr;
+    f.readln(ln);
+
+    assert(ln == "abcd\n");
+    //assert(lnptr == ln.ptr); // should not reallocate
+    char[] t = ln[0..2];
+    t ~= 't';
+    assert(t == "abt");
+    assert(ln == "abcd\n");  // bug 13856: ln stomped to "abtd"
+
+    // it can also stomp the array length
+    ln = new char[4];
+    //assert(ln.capacity < 16);
+    lnptr = ln.ptr;
+    f.readln(ln);
+    assert(ln == "0123456789abcde\n");
+    //assert(ln.ptr != lnptr);  // used to write to ln, overwriting allocation length byte
+
+    char[100] buf;
+    ln = buf[];
+    f.readln(ln);
+    assert(ln == "1234\n");
+    assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough
+}
+
+/** Experimental network access via the File interface
+
+        Opens a TCP connection to the given host and port, then returns
+        a File struct with read and write access through the same interface
+        as any other file (meaning writef and the byLine ranges work!).
+
+        Authors:
+                Adam D. Ruppe
+
+        Bugs:
+                Only works on Linux
+*/
+version(linux)
+{
+    File openNetwork(string host, ushort port)
+    {
+        static import sock = core.sys.posix.sys.socket;
+        static import core.sys.posix.unistd;
+        import core.stdc.string : memcpy;
+        import core.sys.posix.arpa.inet : htons;
+        import core.sys.posix.netdb : gethostbyname;
+        import core.sys.posix.netinet.in_ : sockaddr_in;
+        import std.conv : to;
+        import std.exception : enforce;
+        import std.internal.cstring : tempCString;
+
+        auto h = enforce( gethostbyname(host.tempCString()),
+            new StdioException("gethostbyname"));
+
+        int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0);
+        enforce(s != -1, new StdioException("socket"));
+
+        scope(failure)
+        {
+            // want to make sure it doesn't dangle if something throws. Upon
+            // normal exit, the File struct's reference counting takes care of
+            // closing, so we don't need to worry about success
+            core.sys.posix.unistd.close(s);
+        }
+
+        sockaddr_in addr;
+
+        addr.sin_family = sock.AF_INET;
+        addr.sin_port = htons(port);
+        memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length);
+
+        enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1,
+            new StdioException("Connect failed"));
+
+        File f;
+        f.fdopen(s, "w+", host ~ ":" ~ to!string(port));
+        return f;
+    }
+}
+
+version(unittest) string testFilename(string file = __FILE__, size_t line = __LINE__) @safe
+{
+    import std.conv : text;
+    import std.file : deleteme;
+    import std.path : baseName;
+
+    // filename intentionally contains non-ASCII (Russian) characters for test Issue 7648
+    return text("deleteme-детка.", baseName(file), ".", line);
+}
diff --git a/libphobos/src/std/stdiobase.d b/libphobos/src/std/stdiobase.d
new file mode 100644
index 00000000000..f12e409a531
--- /dev/null
+++ b/libphobos/src/std/stdiobase.d
@@ -0,0 +1,24 @@
+// Written in the D programming language.
+
+/**
+ * The only purpose of this module is to do the static construction for
+ * std.stdio, to eliminate cyclic construction errors.
+ *
+ * Copyright: Copyright Andrei Alexandrescu 2008 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB erdani.org, Andrei Alexandrescu)
+ * Source:    $(PHOBOSSRC std/_stdiobase.d)
+ */
+/*          Copyright Andrei Alexandrescu 2008 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.stdiobase;
+
+extern(C) void std_stdio_static_this();
+
+shared static this()
+{
+    std_stdio_static_this();
+}
diff --git a/libphobos/src/std/stream.d b/libphobos/src/std/stream.d
new file mode 100644
index 00000000000..f6c667b4695
--- /dev/null
+++ b/libphobos/src/std/stream.d
@@ -0,0 +1,3067 @@
+// Written in the D programming language
+
+/**
+ * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
+ *       current standards. It will be remove in October 2016.)
+ *
+ * Source:    $(PHOBOSSRC std/_stream.d)
+ * Macros:
+ *      WIKI = Phobos/StdStream
+ */
+
+/*
+ * Copyright (c) 2001-2005
+ * Pavel "EvilOne" Minayev
+ *  with buffering and endian support added by Ben Hinkle
+ *  with buffered readLine performance improvements by Dave Fladebo
+ *  with opApply inspired by (and mostly copied from) Regan Heath
+ *  with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington
+ *
+ * Permission to use, copy, modify, distribute and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  Author makes no representations about
+ * the suitability of this software for any purpose. It is provided
+ * "as is" without express or implied warranty.
+ */
+deprecated("It will be removed from Phobos in October 2016. If you still need it, go to https://github.com/DigitalMars/undeaD") module std.stream;
+// @@@DEPRECATED_2016-10@@@
+
+
+import std.internal.cstring;
+
+/* Class structure:
+ *  InputStream       interface for reading
+ *  OutputStream      interface for writing
+ *  Stream            abstract base of stream implementations
+ *    File            an OS file stream
+ *    FilterStream    a base-class for wrappers around another stream
+ *      BufferedStream  a buffered stream wrapping another stream
+ *        BufferedFile  a buffered File
+ *      EndianStream    a wrapper stream for swapping byte order and BOMs
+ *      SliceStream     a portion of another stream
+ *    MemoryStream    a stream entirely stored in main memory
+ *    TArrayStream    a stream wrapping an array-like buffer
+ */
+
+/// A base class for stream exceptions.
+class StreamException: Exception {
+  /// Construct a StreamException with given error message.
+  this(string msg) { super(msg); }
+}
+
+/// Thrown when unable to read data from Stream.
+class ReadException: StreamException {
+  /// Construct a ReadException with given error message.
+  this(string msg) { super(msg); }
+}
+
+/// Thrown when unable to write data to Stream.
+class WriteException: StreamException {
+  /// Construct a WriteException with given error message.
+  this(string msg) { super(msg); }
+}
+
+/// Thrown when unable to move Stream pointer.
+class SeekException: StreamException {
+  /// Construct a SeekException with given error message.
+  this(string msg) { super(msg); }
+}
+
+// seek whence...
+enum SeekPos {
+  Set,
+  Current,
+  End
+}
+
+private {
+  import std.conv;
+  import std.algorithm;
+  import std.ascii;
+  import std.format;
+  import std.system;    // for Endian enumeration
+  import std.utf;
+  import core.bitop; // for bswap
+  import core.vararg;
+  import std.file;
+}
+
+/// InputStream is the interface for readable streams.
+
+interface InputStream {
+
+  /***
+   * Read exactly size bytes into the buffer.
+   *
+   * Throws a ReadException if it is not correct.
+   */
+  void readExact(void* buffer, size_t size);
+
+  /***
+   * Read a block of data big enough to fill the given array buffer.
+   *
+   * Returns: the actual number of bytes read. Unfilled bytes are not modified.
+   */
+  size_t read(ubyte[] buffer);
+
+  /***
+   * Read a basic type or counted string.
+   *
+   * Throw a ReadException if it could not be read.
+   * Outside of byte, ubyte, and char, the format is
+   * implementation-specific and should not be used except as opposite actions
+   * to write.
+   */
+  void read(out byte x);
+  void read(out ubyte x);       /// ditto
+  void read(out short x);       /// ditto
+  void read(out ushort x);      /// ditto
+  void read(out int x);         /// ditto
+  void read(out uint x);        /// ditto
+  void read(out long x);        /// ditto
+  void read(out ulong x);       /// ditto
+  void read(out float x);       /// ditto
+  void read(out double x);      /// ditto
+  void read(out real x);        /// ditto
+  void read(out ifloat x);      /// ditto
+  void read(out idouble x);     /// ditto
+  void read(out ireal x);       /// ditto
+  void read(out cfloat x);      /// ditto
+  void read(out cdouble x);     /// ditto
+  void read(out creal x);       /// ditto
+  void read(out char x);        /// ditto
+  void read(out wchar x);       /// ditto
+  void read(out dchar x);       /// ditto
+
+  // reads a string, written earlier by write()
+  void read(out char[] s);      /// ditto
+
+  // reads a Unicode string, written earlier by write()
+  void read(out wchar[] s);     /// ditto
+
+  /***
+   * Read a line that is terminated with some combination of carriage return and
+   * line feed or end-of-file.
+   *
+   * The terminators are not included. The wchar version
+   * is identical. The optional buffer parameter is filled (reallocating
+   * it if necessary) and a slice of the result is returned.
+   */
+  char[] readLine();
+  char[] readLine(char[] result);       /// ditto
+  wchar[] readLineW();                  /// ditto
+  wchar[] readLineW(wchar[] result);    /// ditto
+
+  /***
+   * Overload foreach statements to read the stream line by line and call the
+   * supplied delegate with each line or with each line with line number.
+   *
+   * The string passed in line may be reused between calls to the delegate.
+   * Line numbering starts at 1.
+   * Breaking out of the foreach will leave the stream
+   * position at the beginning of the next line to be read.
+   * For example, to echo a file line-by-line with line numbers run:
+   * ------------------------------------
+   * Stream file = new BufferedFile("sample.txt");
+   * foreach(ulong n, char[] line; file)
+   * {
+   *     writefln("line %d: %s", n, line);
+   * }
+   * file.close();
+   * ------------------------------------
+   */
+
+  // iterate through the stream line-by-line
+  int opApply(scope int delegate(ref char[] line) dg);
+  int opApply(scope int delegate(ref ulong n, ref char[] line) dg);  /// ditto
+  int opApply(scope int delegate(ref wchar[] line) dg);            /// ditto
+  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto
+
+  /// Read a string of the given length,
+  /// throwing ReadException if there was a problem.
+  char[] readString(size_t length);
+
+  /***
+   * Read a string of the given length, throwing ReadException if there was a
+   * problem.
+   *
+   * The file format is implementation-specific and should not be used
+   * except as opposite actions to <b>write</b>.
+   */
+
+  wchar[] readStringW(size_t length);
+
+
+  /***
+   * Read and return the next character in the stream.
+   *
+   * This is the only method that will handle ungetc properly.
+   * getcw's format is implementation-specific.
+   * If EOF is reached then getc returns char.init and getcw returns wchar.init.
+   */
+
+  char getc();
+  wchar getcw(); /// ditto
+
+  /***
+   * Push a character back onto the stream.
+   *
+   * They will be returned in first-in last-out order from getc/getcw.
+   * Only has effect on further calls to getc() and getcw().
+   */
+  char ungetc(char c);
+  wchar ungetcw(wchar c); /// ditto
+
+  /***
+   * Scan a string from the input using a similar form to C's scanf
+   * and <a href="std_format.html">std.format</a>.
+   *
+   * An argument of type string is interpreted as a format string.
+   * All other arguments must be pointer types.
+   * If a format string is not present a default will be supplied computed from
+   * the base type of the pointer type. An argument of type string* is filled
+   * (possibly with appending characters) and a slice of the result is assigned
+   * back into the argument. For example the following readf statements
+   * are equivalent:
+   * --------------------------
+   * int x;
+   * double y;
+   * string s;
+   * file.readf(&x, " hello ", &y, &s);
+   * file.readf("%d hello %f %s", &x, &y, &s);
+   * file.readf("%d hello %f", &x, &y, "%s", &s);
+   * --------------------------
+   */
+  int vreadf(TypeInfo[] arguments, va_list args);
+  int readf(...); /// ditto
+
+  /// Retrieve the number of bytes available for immediate reading.
+  @property size_t available();
+
+  /***
+   * Return whether the current file position is the same as the end of the
+   * file.
+   *
+   * This does not require actually reading past the end, as with stdio. For
+   * non-seekable streams this might only return true after attempting to read
+   * past the end.
+   */
+
+  @property bool eof();
+
+  @property bool isOpen();        /// Return true if the stream is currently open.
+}
+
+/// Interface for writable streams.
+interface OutputStream {
+
+  /***
+   * Write exactly size bytes from buffer, or throw a WriteException if that
+   * could not be done.
+   */
+  void writeExact(const void* buffer, size_t size);
+
+  /***
+   * Write as much of the buffer as possible,
+   * returning the number of bytes written.
+   */
+  size_t write(const(ubyte)[] buffer);
+
+  /***
+   * Write a basic type.
+   *
+   * Outside of byte, ubyte, and char, the format is implementation-specific
+   * and should only be used in conjunction with read.
+   * Throw WriteException on error.
+   */
+  void write(byte x);
+  void write(ubyte x);          /// ditto
+  void write(short x);          /// ditto
+  void write(ushort x);         /// ditto
+  void write(int x);            /// ditto
+  void write(uint x);           /// ditto
+  void write(long x);           /// ditto
+  void write(ulong x);          /// ditto
+  void write(float x);          /// ditto
+  void write(double x);         /// ditto
+  void write(real x);           /// ditto
+  void write(ifloat x);         /// ditto
+  void write(idouble x);        /// ditto
+  void write(ireal x);          /// ditto
+  void write(cfloat x);         /// ditto
+  void write(cdouble x);        /// ditto
+  void write(creal x);          /// ditto
+  void write(char x);           /// ditto
+  void write(wchar x);          /// ditto
+  void write(dchar x);          /// ditto
+
+  /***
+   * Writes a string, together with its length.
+   *
+   * The format is implementation-specific
+   * and should only be used in conjunction with read.
+   * Throw WriteException on error.
+   */
+    void write(const(char)[] s);
+    void write(const(wchar)[] s); /// ditto
+
+  /***
+   * Write a line of text,
+   * appending the line with an operating-system-specific line ending.
+   *
+   * Throws WriteException on error.
+   */
+  void writeLine(const(char)[] s);
+
+  /***
+   * Write a line of text,
+   * appending the line with an operating-system-specific line ending.
+   *
+   * The format is implementation-specific.
+   * Throws WriteException on error.
+   */
+    void writeLineW(const(wchar)[] s);
+
+  /***
+   * Write a string of text.
+   *
+   * Throws WriteException if it could not be fully written.
+   */
+    void writeString(const(char)[] s);
+
+  /***
+   * Write a string of text.
+   *
+   * The format is implementation-specific.
+   * Throws WriteException if it could not be fully written.
+   */
+  void writeStringW(const(wchar)[] s);
+
+  /***
+   * Print a formatted string into the stream using printf-style syntax,
+   * returning the number of bytes written.
+   */
+  size_t vprintf(const(char)[] format, va_list args);
+  size_t printf(const(char)[] format, ...);    /// ditto
+
+  /***
+   * Print a formatted string into the stream using writef-style syntax.
+   * References: <a href="std_format.html">std.format</a>.
+   * Returns: self to chain with other stream commands like flush.
+   */
+  OutputStream writef(...);
+  OutputStream writefln(...); /// ditto
+  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false);  /// ditto
+
+  void flush(); /// Flush pending output if appropriate.
+  void close(); /// Close the stream, flushing output if appropriate.
+  @property bool isOpen(); /// Return true if the stream is currently open.
+}
+
+
+/***
+ * Stream is the base abstract class from which the other stream classes derive.
+ *
+ * Stream's byte order is the format native to the computer.
+ *
+ * Reading:
+ * These methods require that the readable flag be set.
+ * Problems with reading result in a ReadException being thrown.
+ * Stream implements the InputStream interface in addition to the
+ * readBlock method.
+ *
+ * Writing:
+ * These methods require that the writeable flag be set. Problems with writing
+ * result in a WriteException being thrown. Stream implements the OutputStream
+ * interface in addition to the following methods:
+ * writeBlock
+ * copyFrom
+ * copyFrom
+ *
+ * Seeking:
+ * These methods require that the seekable flag be set.
+ * Problems with seeking result in a SeekException being thrown.
+ * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash
+ */
+
+// not really abstract, but its instances will do nothing useful
+class Stream : InputStream, OutputStream {
+  private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio;
+
+  // stream abilities
+  bool readable = false;        /// Indicates whether this stream can be read from.
+  bool writeable = false;       /// Indicates whether this stream can be written to.
+  bool seekable = false;        /// Indicates whether this stream can be seeked within.
+  protected bool isopen = true; /// Indicates whether this stream is open.
+
+  protected bool readEOF = false; /** Indicates whether this stream is at eof
+                                   * after the last read attempt.
+                                   */
+
+  protected bool prevCr = false; /** For a non-seekable stream indicates that
+                                  * the last readLine or readLineW ended on a
+                                  * '\r' character.
+                                  */
+
+  this() {}
+
+  /***
+   * Read up to size bytes into the buffer and return the number of bytes
+   * actually read. A return value of 0 indicates end-of-file.
+   */
+  abstract size_t readBlock(void* buffer, size_t size);
+
+  // reads block of data of specified size,
+  // throws ReadException on error
+  void readExact(void* buffer, size_t size) {
+    for(;;) {
+      if (!size) return;
+      size_t readsize = readBlock(buffer, size); // return 0 on eof
+      if (readsize == 0) break;
+      buffer += readsize;
+      size -= readsize;
+    }
+    if (size != 0)
+      throw new ReadException("not enough data in stream");
+  }
+
+  // reads block of data big enough to fill the given
+  // array, returns actual number of bytes read
+  size_t read(ubyte[] buffer) {
+    return readBlock(buffer.ptr, buffer.length);
+  }
+
+  // read a single value of desired type,
+  // throw ReadException on error
+  void read(out byte x) { readExact(&x, x.sizeof); }
+  void read(out ubyte x) { readExact(&x, x.sizeof); }
+  void read(out short x) { readExact(&x, x.sizeof); }
+  void read(out ushort x) { readExact(&x, x.sizeof); }
+  void read(out int x) { readExact(&x, x.sizeof); }
+  void read(out uint x) { readExact(&x, x.sizeof); }
+  void read(out long x) { readExact(&x, x.sizeof); }
+  void read(out ulong x) { readExact(&x, x.sizeof); }
+  void read(out float x) { readExact(&x, x.sizeof); }
+  void read(out double x) { readExact(&x, x.sizeof); }
+  void read(out real x) { readExact(&x, x.sizeof); }
+  void read(out ifloat x) { readExact(&x, x.sizeof); }
+  void read(out idouble x) { readExact(&x, x.sizeof); }
+  void read(out ireal x) { readExact(&x, x.sizeof); }
+  void read(out cfloat x) { readExact(&x, x.sizeof); }
+  void read(out cdouble x) { readExact(&x, x.sizeof); }
+  void read(out creal x) { readExact(&x, x.sizeof); }
+  void read(out char x) { readExact(&x, x.sizeof); }
+  void read(out wchar x) { readExact(&x, x.sizeof); }
+  void read(out dchar x) { readExact(&x, x.sizeof); }
+
+  // reads a string, written earlier by write()
+  void read(out char[] s) {
+    size_t len;
+    read(len);
+    s = readString(len);
+  }
+
+  // reads a Unicode string, written earlier by write()
+  void read(out wchar[] s) {
+    size_t len;
+    read(len);
+    s = readStringW(len);
+  }
+
+  // reads a line, terminated by either CR, LF, CR/LF, or EOF
+  char[] readLine() {
+    return readLine(null);
+  }
+
+  // reads a line, terminated by either CR, LF, CR/LF, or EOF
+  // reusing the memory in buffer if result will fit and otherwise
+  // allocates a new string
+  char[] readLine(char[] result) {
+    size_t strlen = 0;
+    char ch = getc();
+    while (readable) {
+      switch (ch) {
+      case '\r':
+        if (seekable) {
+          ch = getc();
+          if (ch != '\n')
+            ungetc(ch);
+        } else {
+          prevCr = true;
+        }
+        goto case;
+      case '\n':
+      case char.init:
+        result.length = strlen;
+        return result;
+
+      default:
+        if (strlen < result.length) {
+          result[strlen] = ch;
+        } else {
+          result ~= ch;
+        }
+        strlen++;
+      }
+      ch = getc();
+    }
+    result.length = strlen;
+    return result;
+  }
+
+  // reads a Unicode line, terminated by either CR, LF, CR/LF,
+  // or EOF; pretty much the same as the above, working with
+  // wchars rather than chars
+  wchar[] readLineW() {
+    return readLineW(null);
+  }
+
+  // reads a Unicode line, terminated by either CR, LF, CR/LF,
+  // or EOF;
+  // fills supplied buffer if line fits and otherwise allocates a new string.
+  wchar[] readLineW(wchar[] result) {
+    size_t strlen = 0;
+    wchar c = getcw();
+    while (readable) {
+      switch (c) {
+      case '\r':
+        if (seekable) {
+          c = getcw();
+          if (c != '\n')
+            ungetcw(c);
+        } else {
+          prevCr = true;
+        }
+        goto case;
+      case '\n':
+      case wchar.init:
+        result.length = strlen;
+        return result;
+
+      default:
+        if (strlen < result.length) {
+          result[strlen] = c;
+        } else {
+          result ~= c;
+        }
+        strlen++;
+      }
+      c = getcw();
+    }
+    result.length = strlen;
+    return result;
+  }
+
+  // iterate through the stream line-by-line - due to Regan Heath
+  int opApply(scope int delegate(ref char[] line) dg) {
+    int res = 0;
+    char[128] buf;
+    while (!eof) {
+      char[] line = readLine(buf);
+      res = dg(line);
+      if (res) break;
+    }
+    return res;
+  }
+
+  // iterate through the stream line-by-line with line count and string
+  int opApply(scope int delegate(ref ulong n, ref char[] line) dg) {
+    int res = 0;
+    ulong n = 1;
+    char[128] buf;
+    while (!eof) {
+      auto line = readLine(buf);
+      res = dg(n,line);
+      if (res) break;
+      n++;
+    }
+    return res;
+  }
+
+  // iterate through the stream line-by-line with wchar[]
+  int opApply(scope int delegate(ref wchar[] line) dg) {
+    int res = 0;
+    wchar[128] buf;
+    while (!eof) {
+      auto line = readLineW(buf);
+      res = dg(line);
+      if (res) break;
+    }
+    return res;
+  }
+
+  // iterate through the stream line-by-line with line count and wchar[]
+  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) {
+    int res = 0;
+    ulong n = 1;
+    wchar[128] buf;
+    while (!eof) {
+      auto line = readLineW(buf);
+      res = dg(n,line);
+      if (res) break;
+      n++;
+    }
+    return res;
+  }
+
+  // reads a string of given length, throws
+  // ReadException on error
+  char[] readString(size_t length) {
+    char[] result = new char[length];
+    readExact(result.ptr, length);
+    return result;
+  }
+
+  // reads a Unicode string of given length, throws
+  // ReadException on error
+  wchar[] readStringW(size_t length) {
+    auto result = new wchar[length];
+    readExact(result.ptr, result.length * wchar.sizeof);
+    return result;
+  }
+
+  // unget buffer
+  private wchar[] unget;
+  final bool ungetAvailable() { return unget.length > 1; }
+
+  // reads and returns next character from the stream,
+  // handles characters pushed back by ungetc()
+  // returns char.init on eof.
+  char getc() {
+    char c;
+    if (prevCr) {
+      prevCr = false;
+      c = getc();
+      if (c != '\n')
+        return c;
+    }
+    if (unget.length > 1) {
+      c = cast(char)unget[unget.length - 1];
+      unget.length = unget.length - 1;
+    } else {
+      readBlock(&c,1);
+    }
+    return c;
+  }
+
+  // reads and returns next Unicode character from the
+  // stream, handles characters pushed back by ungetc()
+  // returns wchar.init on eof.
+  wchar getcw() {
+    wchar c;
+    if (prevCr) {
+      prevCr = false;
+      c = getcw();
+      if (c != '\n')
+        return c;
+    }
+    if (unget.length > 1) {
+      c = unget[unget.length - 1];
+      unget.length = unget.length - 1;
+    } else {
+      void* buf = &c;
+      size_t n = readBlock(buf,2);
+      if (n == 1 && readBlock(buf+1,1) == 0)
+          throw new ReadException("not enough data in stream");
+    }
+    return c;
+  }
+
+  // pushes back character c into the stream; only has
+  // effect on further calls to getc() and getcw()
+  char ungetc(char c) {
+    if (c == c.init) return c;
+    // first byte is a dummy so that we never set length to 0
+    if (unget.length == 0)
+      unget.length = 1;
+    unget ~= c;
+    return c;
+  }
+
+  // pushes back Unicode character c into the stream; only
+  // has effect on further calls to getc() and getcw()
+  wchar ungetcw(wchar c) {
+    if (c == c.init) return c;
+    // first byte is a dummy so that we never set length to 0
+    if (unget.length == 0)
+      unget.length = 1;
+    unget ~= c;
+    return c;
+  }
+
+  int vreadf(TypeInfo[] arguments, va_list args) {
+    string fmt;
+    int j = 0;
+    int count = 0, i = 0;
+    char c;
+    bool firstCharacter = true;
+    while ((j < arguments.length || i < fmt.length) && !eof) {
+      if(firstCharacter) {
+        c = getc();
+        firstCharacter = false;
+      }
+      if (fmt.length == 0 || i == fmt.length) {
+        i = 0;
+        if (arguments[j] is typeid(string) || arguments[j] is typeid(char[])
+            || arguments[j] is typeid(const(char)[])) {
+          fmt = va_arg!(string)(args);
+          j++;
+          continue;
+        } else if (arguments[j] is typeid(int*) ||
+                   arguments[j] is typeid(byte*) ||
+                   arguments[j] is typeid(short*) ||
+                   arguments[j] is typeid(long*)) {
+          fmt = "%d";
+        } else if (arguments[j] is typeid(uint*) ||
+                   arguments[j] is typeid(ubyte*) ||
+                   arguments[j] is typeid(ushort*) ||
+                   arguments[j] is typeid(ulong*)) {
+          fmt = "%d";
+        } else if (arguments[j] is typeid(float*) ||
+                   arguments[j] is typeid(double*) ||
+                   arguments[j] is typeid(real*)) {
+          fmt = "%f";
+        } else if (arguments[j] is typeid(char[]*) ||
+                   arguments[j] is typeid(wchar[]*) ||
+                   arguments[j] is typeid(dchar[]*)) {
+          fmt = "%s";
+        } else if (arguments[j] is typeid(char*)) {
+          fmt = "%c";
+        }
+      }
+      if (fmt[i] == '%') {      // a field
+        i++;
+        bool suppress = false;
+        if (fmt[i] == '*') {    // suppress assignment
+          suppress = true;
+          i++;
+        }
+        // read field width
+        int width = 0;
+        while (isDigit(fmt[i])) {
+          width = width * 10 + (fmt[i] - '0');
+          i++;
+        }
+        if (width == 0)
+          width = -1;
+        // skip any modifier if present
+        if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L')
+          i++;
+        // check the typechar and act accordingly
+        switch (fmt[i]) {
+        case 'd':       // decimal/hexadecimal/octal integer
+        case 'D':
+        case 'u':
+        case 'U':
+        case 'o':
+        case 'O':
+        case 'x':
+        case 'X':
+        case 'i':
+        case 'I':
+          {
+            while (isWhite(c)) {
+              c = getc();
+              count++;
+            }
+            bool neg = false;
+            if (c == '-') {
+              neg = true;
+              c = getc();
+              count++;
+            } else if (c == '+') {
+              c = getc();
+              count++;
+            }
+            char ifmt = cast(char)(fmt[i] | 0x20);
+            if (ifmt == 'i')    { // undetermined base
+              if (c == '0')     { // octal or hex
+                c = getc();
+                count++;
+                if (c == 'x' || c == 'X')       { // hex
+                  ifmt = 'x';
+                  c = getc();
+                  count++;
+                } else {        // octal
+                  ifmt = 'o';
+                }
+              }
+              else      // decimal
+                ifmt = 'd';
+            }
+            long n = 0;
+            switch (ifmt)
+            {
+                case 'd':       // decimal
+                case 'u': {
+                  while (isDigit(c) && width) {
+                    n = n * 10 + (c - '0');
+                    width--;
+                    c = getc();
+                    count++;
+                  }
+                } break;
+
+                case 'o': {     // octal
+                  while (isOctalDigit(c) && width) {
+                    n = n * 8 + (c - '0');
+                    width--;
+                    c = getc();
+                    count++;
+                  }
+                } break;
+
+                case 'x': {     // hexadecimal
+                  while (isHexDigit(c) && width) {
+                    n *= 0x10;
+                    if (isDigit(c))
+                      n += c - '0';
+                    else
+                      n += 0xA + (c | 0x20) - 'a';
+                    width--;
+                    c = getc();
+                    count++;
+                  }
+                } break;
+
+                default:
+                    assert(0);
+            }
+            if (neg)
+              n = -n;
+            if (arguments[j] is typeid(int*)) {
+              int* p = va_arg!(int*)(args);
+              *p = cast(int)n;
+            } else if (arguments[j] is typeid(short*)) {
+              short* p = va_arg!(short*)(args);
+              *p = cast(short)n;
+            } else if (arguments[j] is typeid(byte*)) {
+              byte* p = va_arg!(byte*)(args);
+              *p = cast(byte)n;
+            } else if (arguments[j] is typeid(long*)) {
+              long* p = va_arg!(long*)(args);
+              *p = n;
+            } else if (arguments[j] is typeid(uint*)) {
+              uint* p = va_arg!(uint*)(args);
+              *p = cast(uint)n;
+            } else if (arguments[j] is typeid(ushort*)) {
+              ushort* p = va_arg!(ushort*)(args);
+              *p = cast(ushort)n;
+            } else if (arguments[j] is typeid(ubyte*)) {
+              ubyte* p = va_arg!(ubyte*)(args);
+              *p = cast(ubyte)n;
+            } else if (arguments[j] is typeid(ulong*)) {
+              ulong* p = va_arg!(ulong*)(args);
+              *p = cast(ulong)n;
+            }
+            j++;
+            i++;
+          } break;
+
+        case 'f':       // float
+        case 'F':
+        case 'e':
+        case 'E':
+        case 'g':
+        case 'G':
+          {
+            while (isWhite(c)) {
+              c = getc();
+              count++;
+            }
+            bool neg = false;
+            if (c == '-') {
+              neg = true;
+              c = getc();
+              count++;
+            } else if (c == '+') {
+              c = getc();
+              count++;
+            }
+            real r = 0;
+            while (isDigit(c) && width) {
+              r = r * 10 + (c - '0');
+              width--;
+              c = getc();
+              count++;
+            }
+            if (width && c == '.') {
+              width--;
+              c = getc();
+              count++;
+              double frac = 1;
+              while (isDigit(c) && width) {
+                r = r * 10 + (c - '0');
+                frac *= 10;
+                width--;
+                c = getc();
+                count++;
+              }
+              r /= frac;
+            }
+            if (width && (c == 'e' || c == 'E')) {
+              width--;
+              c = getc();
+              count++;
+              if (width) {
+                bool expneg = false;
+                if (c == '-') {
+                  expneg = true;
+                  width--;
+                  c = getc();
+                  count++;
+                } else if (c == '+') {
+                  width--;
+                  c = getc();
+                  count++;
+                }
+                real exp = 0;
+                while (isDigit(c) && width) {
+                  exp = exp * 10 + (c - '0');
+                  width--;
+                  c = getc();
+                  count++;
+                }
+                if (expneg) {
+                  while (exp--)
+                    r /= 10;
+                } else {
+                  while (exp--)
+                    r *= 10;
+                }
+              }
+            }
+            if(width && (c == 'n' || c == 'N')) {
+              width--;
+              c = getc();
+              count++;
+              if(width && (c == 'a' || c == 'A')) {
+                width--;
+                c = getc();
+                count++;
+                if(width && (c == 'n' || c == 'N')) {
+                  width--;
+                  c = getc();
+                  count++;
+                  r = real.nan;
+                }
+              }
+            }
+            if(width && (c == 'i' || c == 'I')) {
+              width--;
+              c = getc();
+              count++;
+              if(width && (c == 'n' || c == 'N')) {
+                width--;
+                c = getc();
+                count++;
+                if(width && (c == 'f' || c == 'F')) {
+                  width--;
+                  c = getc();
+                  count++;
+                  r = real.infinity;
+                }
+              }
+            }
+            if (neg)
+              r = -r;
+            if (arguments[j] is typeid(float*)) {
+              float* p = va_arg!(float*)(args);
+              *p = r;
+            } else if (arguments[j] is typeid(double*)) {
+              double* p = va_arg!(double*)(args);
+              *p = r;
+            } else if (arguments[j] is typeid(real*)) {
+              real* p = va_arg!(real*)(args);
+              *p = r;
+            }
+            j++;
+            i++;
+          } break;
+
+        case 's': {     // string
+          while (isWhite(c)) {
+            c = getc();
+            count++;
+          }
+          char[] s;
+          char[]* p;
+          size_t strlen;
+          if (arguments[j] is typeid(char[]*)) {
+            p = va_arg!(char[]*)(args);
+            s = *p;
+          }
+          while (!isWhite(c) && c != char.init) {
+            if (strlen < s.length) {
+              s[strlen] = c;
+            } else {
+              s ~= c;
+            }
+            strlen++;
+            c = getc();
+            count++;
+          }
+          s = s[0 .. strlen];
+          if (arguments[j] is typeid(char[]*)) {
+            *p = s;
+          } else if (arguments[j] is typeid(char*)) {
+            s ~= 0;
+            auto q = va_arg!(char*)(args);
+            q[0 .. s.length] = s[];
+          } else if (arguments[j] is typeid(wchar[]*)) {
+            auto q = va_arg!(const(wchar)[]*)(args);
+            *q = toUTF16(s);
+          } else if (arguments[j] is typeid(dchar[]*)) {
+            auto q = va_arg!(const(dchar)[]*)(args);
+            *q = toUTF32(s);
+          }
+          j++;
+          i++;
+        } break;
+
+        case 'c': {     // character(s)
+          char* s = va_arg!(char*)(args);
+          if (width < 0)
+            width = 1;
+          else
+            while (isWhite(c)) {
+            c = getc();
+            count++;
+          }
+          while (width-- && !eof) {
+            *(s++) = c;
+            c = getc();
+            count++;
+          }
+          j++;
+          i++;
+        } break;
+
+        case 'n': {     // number of chars read so far
+          int* p = va_arg!(int*)(args);
+          *p = count;
+          j++;
+          i++;
+        } break;
+
+        default:        // read character as is
+          goto nws;
+        }
+      } else if (isWhite(fmt[i])) {     // skip whitespace
+        while (isWhite(c))
+          c = getc();
+        i++;
+      } else {  // read character as is
+      nws:
+        if (fmt[i] != c)
+          break;
+        c = getc();
+        i++;
+      }
+    }
+    ungetc(c);
+    return count;
+  }
+
+  int readf(...) {
+    return vreadf(_arguments, _argptr);
+  }
+
+  // returns estimated number of bytes available for immediate reading
+  @property size_t available() { return 0; }
+
+  /***
+   * Write up to size bytes from buffer in the stream, returning the actual
+   * number of bytes that were written.
+   */
+  abstract size_t writeBlock(const void* buffer, size_t size);
+
+  // writes block of data of specified size,
+  // throws WriteException on error
+  void writeExact(const void* buffer, size_t size) {
+    const(void)* p = buffer;
+    for(;;) {
+      if (!size) return;
+      size_t writesize = writeBlock(p, size);
+      if (writesize == 0) break;
+      p += writesize;
+      size -= writesize;
+    }
+    if (size != 0)
+      throw new WriteException("unable to write to stream");
+  }
+
+  // writes the given array of bytes, returns
+  // actual number of bytes written
+  size_t write(const(ubyte)[] buffer) {
+    return writeBlock(buffer.ptr, buffer.length);
+  }
+
+  // write a single value of desired type,
+  // throw WriteException on error
+  void write(byte x) { writeExact(&x, x.sizeof); }
+  void write(ubyte x) { writeExact(&x, x.sizeof); }
+  void write(short x) { writeExact(&x, x.sizeof); }
+  void write(ushort x) { writeExact(&x, x.sizeof); }
+  void write(int x) { writeExact(&x, x.sizeof); }
+  void write(uint x) { writeExact(&x, x.sizeof); }
+  void write(long x) { writeExact(&x, x.sizeof); }
+  void write(ulong x) { writeExact(&x, x.sizeof); }
+  void write(float x) { writeExact(&x, x.sizeof); }
+  void write(double x) { writeExact(&x, x.sizeof); }
+  void write(real x) { writeExact(&x, x.sizeof); }
+  void write(ifloat x) { writeExact(&x, x.sizeof); }
+  void write(idouble x) { writeExact(&x, x.sizeof); }
+  void write(ireal x) { writeExact(&x, x.sizeof); }
+  void write(cfloat x) { writeExact(&x, x.sizeof); }
+  void write(cdouble x) { writeExact(&x, x.sizeof); }
+  void write(creal x) { writeExact(&x, x.sizeof); }
+  void write(char x) { writeExact(&x, x.sizeof); }
+  void write(wchar x) { writeExact(&x, x.sizeof); }
+  void write(dchar x) { writeExact(&x, x.sizeof); }
+
+  // writes a string, together with its length
+  void write(const(char)[] s) {
+    write(s.length);
+    writeString(s);
+  }
+
+  // writes a Unicode string, together with its length
+  void write(const(wchar)[] s) {
+    write(s.length);
+    writeStringW(s);
+  }
+
+  // writes a line, throws WriteException on error
+  void writeLine(const(char)[] s) {
+    writeString(s);
+    version (Windows)
+      writeString("\r\n");
+    else version (Mac)
+      writeString("\r");
+    else
+      writeString("\n");
+  }
+
+  // writes a Unicode line, throws WriteException on error
+  void writeLineW(const(wchar)[] s) {
+    writeStringW(s);
+    version (Windows)
+      writeStringW("\r\n");
+    else version (Mac)
+      writeStringW("\r");
+    else
+      writeStringW("\n");
+  }
+
+  // writes a string, throws WriteException on error
+  void writeString(const(char)[] s) {
+    writeExact(s.ptr, s.length);
+  }
+
+  // writes a Unicode string, throws WriteException on error
+  void writeStringW(const(wchar)[] s) {
+    writeExact(s.ptr, s.length * wchar.sizeof);
+  }
+
+  // writes data to stream using vprintf() syntax,
+  // returns number of bytes written
+  size_t vprintf(const(char)[] format, va_list args) {
+    // shamelessly stolen from OutBuffer,
+    // by Walter's permission
+    char[1024] buffer;
+    char* p = buffer.ptr;
+    // Can't use `tempCString()` here as it will result in compilation error:
+    // "cannot mix core.std.stdlib.alloca() and exception handling".
+    auto f = toStringz(format);
+    size_t psize = buffer.length;
+    size_t count;
+    while (true) {
+      version (Windows) {
+        count = vsnprintf(p, psize, f, args);
+        if (count != -1)
+          break;
+        psize *= 2;
+        p = cast(char*) alloca(psize);
+      } else version (Posix) {
+        count = vsnprintf(p, psize, f, args);
+        if (count == -1)
+          psize *= 2;
+        else if (count >= psize)
+          psize = count + 1;
+        else
+          break;
+        p = cast(char*) alloca(psize);
+      } else
+          throw new Exception("unsupported platform");
+    }
+    writeString(p[0 .. count]);
+    return count;
+  }
+
+  // writes data to stream using printf() syntax,
+  // returns number of bytes written
+  size_t printf(const(char)[] format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    auto result = vprintf(format, ap);
+    va_end(ap);
+    return result;
+  }
+
+  private void doFormatCallback(dchar c) {
+    char[4] buf;
+    auto b = std.utf.toUTF8(buf, c);
+    writeString(b);
+  }
+
+  // writes data to stream using writef() syntax,
+  OutputStream writef(...) {
+    return writefx(_arguments,_argptr,0);
+  }
+
+  // writes data with trailing newline
+  OutputStream writefln(...) {
+    return writefx(_arguments,_argptr,1);
+  }
+
+  // writes data with optional trailing newline
+  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) {
+    doFormat(&doFormatCallback,arguments,argptr);
+    if (newline)
+      writeLine("");
+    return this;
+  }
+
+  /***
+   * Copies all data from s into this stream.
+   * This may throw ReadException or WriteException on failure.
+   * This restores the file position of s so that it is unchanged.
+   */
+  void copyFrom(Stream s) {
+    if (seekable) {
+      ulong pos = s.position;
+      s.position = 0;
+      copyFrom(s, s.size);
+      s.position = pos;
+    } else {
+      ubyte[128] buf;
+      while (!s.eof) {
+        size_t m = s.readBlock(buf.ptr, buf.length);
+        writeExact(buf.ptr, m);
+      }
+    }
+  }
+
+  /***
+   * Copy a specified number of bytes from the given stream into this one.
+   * This may throw ReadException or WriteException on failure.
+   * Unlike the previous form, this doesn't restore the file position of s.
+   */
+  void copyFrom(Stream s, ulong count) {
+    ubyte[128] buf;
+    while (count > 0) {
+      size_t n = cast(size_t)(count<buf.length ? count : buf.length);
+      s.readExact(buf.ptr, n);
+      writeExact(buf.ptr, n);
+      count -= n;
+    }
+  }
+
+  /***
+   * Change the current position of the stream. whence is either SeekPos.Set, in
+   which case the offset is an absolute index from the beginning of the stream,
+   SeekPos.Current, in which case the offset is a delta from the current
+   position, or SeekPos.End, in which case the offset is a delta from the end of
+   the stream (negative or zero offsets only make sense in that case). This
+   returns the new file position.
+   */
+  abstract ulong seek(long offset, SeekPos whence);
+
+  /***
+   * Aliases for their normal seek counterparts.
+   */
+  ulong seekSet(long offset) { return seek (offset, SeekPos.Set); }
+  ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto
+  ulong seekEnd(long offset) { return seek (offset, SeekPos.End); }     /// ditto
+
+  /***
+   * Sets file position. Equivalent to calling seek(pos, SeekPos.Set).
+   */
+  @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); }
+
+  /***
+   * Returns current file position. Equivalent to seek(0, SeekPos.Current).
+   */
+  @property ulong position() { return seek(0, SeekPos.Current); }
+
+  /***
+   * Retrieve the size of the stream in bytes.
+   * The stream must be seekable or a SeekException is thrown.
+   */
+  @property ulong size() {
+    assertSeekable();
+    ulong pos = position, result = seek(0, SeekPos.End);
+    position = pos;
+    return result;
+  }
+
+  // returns true if end of stream is reached, false otherwise
+  @property bool eof() {
+    // for unseekable streams we only know the end when we read it
+    if (readEOF && !ungetAvailable())
+      return true;
+    else if (seekable)
+      return position == size;
+    else
+      return false;
+  }
+
+  // returns true if the stream is open
+  @property bool isOpen() { return isopen; }
+
+  // flush the buffer if writeable
+  void flush() {
+    if (unget.length > 1)
+      unget.length = 1; // keep at least 1 so that data ptr stays
+  }
+
+  // close the stream somehow; the default just flushes the buffer
+  void close() {
+    if (isopen)
+      flush();
+    readEOF = prevCr = isopen = readable = writeable = seekable = false;
+  }
+
+  /***
+   * Read the entire stream and return it as a string.
+   * If the stream is not seekable the contents from the current position to eof
+   * is read and returned.
+   */
+  override string toString() {
+    if (!readable)
+      return super.toString();
+    try
+    {
+        size_t pos;
+        size_t rdlen;
+        size_t blockSize;
+        char[] result;
+        if (seekable) {
+          ulong orig_pos = position;
+          scope(exit) position = orig_pos;
+          position = 0;
+          blockSize = cast(size_t)size;
+          result = new char[blockSize];
+          while (blockSize > 0) {
+            rdlen = readBlock(&result[pos], blockSize);
+            pos += rdlen;
+            blockSize -= rdlen;
+          }
+        } else {
+          blockSize = 4096;
+          result = new char[blockSize];
+          while ((rdlen = readBlock(&result[pos], blockSize)) > 0) {
+            pos += rdlen;
+            blockSize += rdlen;
+            result.length = result.length + blockSize;
+          }
+        }
+        return cast(string) result[0 .. pos];
+    }
+    catch (Throwable)
+    {
+        return super.toString();
+    }
+  }
+
+  /***
+   * Get a hash of the stream by reading each byte and using it in a CRC-32
+   * checksum.
+   */
+  override size_t toHash() @trusted {
+    if (!readable || !seekable)
+      return super.toHash();
+    try
+    {
+        ulong pos = position;
+        scope(exit) position = pos;
+        CRC32 crc;
+        crc.start();
+        position = 0;
+        ulong len = size;
+        for (ulong i = 0; i < len; i++)
+        {
+          ubyte c;
+          read(c);
+          crc.put(c);
+        }
+
+        union resUnion
+        {
+            size_t hash;
+            ubyte[4] crcVal;
+        }
+        resUnion res;
+        res.crcVal = crc.finish();
+        return res.hash;
+    }
+    catch (Throwable)
+    {
+        return super.toHash();
+    }
+  }
+
+  // helper for checking that the stream is readable
+  final protected void assertReadable() {
+    if (!readable)
+      throw new ReadException("Stream is not readable");
+  }
+  // helper for checking that the stream is writeable
+  final protected void assertWriteable() {
+    if (!writeable)
+      throw new WriteException("Stream is not writeable");
+  }
+  // helper for checking that the stream is seekable
+  final protected void assertSeekable() {
+    if (!seekable)
+      throw new SeekException("Stream is not seekable");
+  }
+
+  unittest { // unit test for Issue 3363
+    import std.stdio;
+    immutable fileName = std.file.deleteme ~ "-issue3363.txt";
+    auto w = std.stdio.File(fileName, "w");
+    scope (exit) remove(fileName.ptr);
+    w.write("one two three");
+    w.close();
+    auto r = std.stdio.File(fileName, "r");
+    const(char)[] constChar;
+    string str;
+    char[] chars;
+    r.readf("%s %s %s", &constChar, &str, &chars);
+    assert (constChar == "one", constChar);
+    assert (str == "two", str);
+    assert (chars == "three", chars);
+  }
+
+  unittest { //unit tests for Issue 1668
+    void tryFloatRoundtrip(float x, string fmt = "", string pad = "") {
+      auto s = new MemoryStream();
+      s.writef(fmt, x, pad);
+      s.position = 0;
+
+      float f;
+      assert(s.readf(&f));
+      assert(x == f || (x != x && f != f)); //either equal or both NaN
+    }
+
+    tryFloatRoundtrip(1.0);
+    tryFloatRoundtrip(1.0, "%f");
+    tryFloatRoundtrip(1.0, "", " ");
+    tryFloatRoundtrip(1.0, "%f", " ");
+
+    tryFloatRoundtrip(3.14);
+    tryFloatRoundtrip(3.14, "%f");
+    tryFloatRoundtrip(3.14, "", " ");
+    tryFloatRoundtrip(3.14, "%f", " ");
+
+    float nan = float.nan;
+    tryFloatRoundtrip(nan);
+    tryFloatRoundtrip(nan, "%f");
+    tryFloatRoundtrip(nan, "", " ");
+    tryFloatRoundtrip(nan, "%f", " ");
+
+    float inf = 1.0/0.0;
+    tryFloatRoundtrip(inf);
+    tryFloatRoundtrip(inf, "%f");
+    tryFloatRoundtrip(inf, "", " ");
+    tryFloatRoundtrip(inf, "%f", " ");
+
+    tryFloatRoundtrip(-inf);
+    tryFloatRoundtrip(-inf,"%f");
+    tryFloatRoundtrip(-inf, "", " ");
+    tryFloatRoundtrip(-inf, "%f", " ");
+  }
+}
+
+/***
+ * A base class for streams that wrap a source stream with additional
+ * functionality.
+ *
+ * The method implementations forward read/write/seek calls to the
+ * source stream. A FilterStream can change the position of the source stream
+ * arbitrarily and may not keep the source stream state in sync with the
+ * FilterStream, even upon flushing and closing the FilterStream. It is
+ * recommended to not make any assumptions about the state of the source position
+ * and read/write state after a FilterStream has acted upon it. Specifc subclasses
+ * of FilterStream should document how they modify the source stream and if any
+ * invariants hold true between the source and filter.
+ */
+class FilterStream : Stream {
+  private Stream s;              // source stream
+
+  /// Property indicating when this stream closes to close the source stream as
+  /// well.
+  /// Defaults to true.
+  bool nestClose = true;
+
+  /// Construct a FilterStream for the given source.
+  this(Stream source) {
+    s = source;
+    resetSource();
+  }
+
+  // source getter/setter
+
+  /***
+   * Get the current source stream.
+   */
+  final Stream source(){return s;}
+
+  /***
+   * Set the current source stream.
+   *
+   * Setting the source stream closes this stream before attaching the new
+   * source. Attaching an open stream reopens this stream and resets the stream
+   * state.
+   */
+  void source(Stream s) {
+    close();
+    this.s = s;
+    resetSource();
+  }
+
+  /***
+   * Indicates the source stream changed state and that this stream should reset
+   * any readable, writeable, seekable, isopen and buffering flags.
+   */
+  void resetSource() {
+    if (s !is null) {
+      readable = s.readable;
+      writeable = s.writeable;
+      seekable = s.seekable;
+      isopen = s.isOpen;
+    } else {
+      readable = writeable = seekable = false;
+      isopen = false;
+    }
+    readEOF = prevCr = false;
+  }
+
+  // read from source
+  override size_t readBlock(void* buffer, size_t size) {
+    size_t res = s.readBlock(buffer,size);
+    readEOF = res == 0;
+    return res;
+  }
+
+  // write to source
+  override size_t writeBlock(const void* buffer, size_t size) {
+    return s.writeBlock(buffer,size);
+  }
+
+  // close stream
+  override void close() {
+    if (isopen) {
+      super.close();
+      if (nestClose)
+        s.close();
+    }
+  }
+
+  // seek on source
+  override ulong seek(long offset, SeekPos whence) {
+    readEOF = false;
+    return s.seek(offset,whence);
+  }
+
+  override @property size_t available() { return s.available; }
+  override void flush() { super.flush(); s.flush(); }
+}
+
+/***
+ * This subclass is for buffering a source stream.
+ *
+ * A buffered stream must be
+ * closed explicitly to ensure the final buffer content is written to the source
+ * stream. The source stream position is changed according to the block size so
+ * reading or writing to the BufferedStream may not change the source stream
+ * position by the same amount.
+ */
+class BufferedStream : FilterStream {
+  ubyte[] buffer;       // buffer, if any
+  size_t bufferCurPos;    // current position in buffer
+  size_t bufferLen;       // amount of data in buffer
+  bool bufferDirty = false;
+  size_t bufferSourcePos; // position in buffer of source stream position
+  ulong streamPos;      // absolute position in source stream
+
+  /* Example of relationship between fields:
+   *
+   *  s             ...01234567890123456789012EOF
+   *  buffer                |--                     --|
+   *  bufferCurPos                       |
+   *  bufferLen             |--            --|
+   *  bufferSourcePos                        |
+   *
+   */
+
+  invariant() {
+    assert(bufferSourcePos <= bufferLen);
+    assert(bufferCurPos <= bufferLen);
+    assert(bufferLen <= buffer.length);
+  }
+
+  enum size_t DefaultBufferSize = 8192;
+
+  /***
+   * Create a buffered stream for the stream source with the buffer size
+   * bufferSize.
+   */
+  this(Stream source, size_t bufferSize = DefaultBufferSize) {
+    super(source);
+    if (bufferSize)
+      buffer = new ubyte[bufferSize];
+  }
+
+  override protected void resetSource() {
+    super.resetSource();
+    streamPos = 0;
+    bufferLen = bufferSourcePos = bufferCurPos = 0;
+    bufferDirty = false;
+  }
+
+  // reads block of data of specified size using any buffered data
+  // returns actual number of bytes read
+  override size_t readBlock(void* result, size_t len) {
+    if (len == 0) return 0;
+
+    assertReadable();
+
+    ubyte* outbuf = cast(ubyte*)result;
+    size_t readsize = 0;
+
+    if (bufferCurPos + len < bufferLen) {
+      // buffer has all the data so copy it
+      outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len];
+      bufferCurPos += len;
+      readsize = len;
+      goto ExitRead;
+    }
+
+    readsize = bufferLen - bufferCurPos;
+    if (readsize > 0) {
+      // buffer has some data so copy what is left
+      outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen];
+      outbuf += readsize;
+      bufferCurPos += readsize;
+      len -= readsize;
+    }
+
+    flush();
+
+    if (len >= buffer.length) {
+      // buffer can't hold the data so fill output buffer directly
+      size_t siz = super.readBlock(outbuf, len);
+      readsize += siz;
+      streamPos += siz;
+    } else {
+      // read a new block into buffer
+        bufferLen = super.readBlock(buffer.ptr, buffer.length);
+        if (bufferLen < len) len = bufferLen;
+        outbuf[0 .. len] = buffer[0 .. len];
+        bufferSourcePos = bufferLen;
+        streamPos += bufferLen;
+        bufferCurPos = len;
+        readsize += len;
+    }
+
+  ExitRead:
+    return readsize;
+  }
+
+  // write block of data of specified size
+  // returns actual number of bytes written
+  override size_t writeBlock(const void* result, size_t len) {
+    assertWriteable();
+
+    ubyte* buf = cast(ubyte*)result;
+    size_t writesize = 0;
+
+    if (bufferLen == 0) {
+      // buffer is empty so fill it if possible
+      if ((len < buffer.length) && (readable)) {
+        // read in data if the buffer is currently empty
+        bufferLen = s.readBlock(buffer.ptr, buffer.length);
+        bufferSourcePos = bufferLen;
+        streamPos += bufferLen;
+
+      } else if (len >= buffer.length) {
+        // buffer can't hold the data so write it directly and exit
+        writesize = s.writeBlock(buf,len);
+        streamPos += writesize;
+        goto ExitWrite;
+      }
+    }
+
+    if (bufferCurPos + len <= buffer.length) {
+      // buffer has space for all the data so copy it and exit
+      buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len];
+      bufferCurPos += len;
+      bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen;
+      writesize = len;
+      bufferDirty = true;
+      goto ExitWrite;
+    }
+
+    writesize = buffer.length - bufferCurPos;
+    if (writesize > 0) {
+      // buffer can take some data
+      buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize];
+      bufferCurPos = bufferLen = buffer.length;
+      buf += writesize;
+      len -= writesize;
+      bufferDirty = true;
+    }
+
+    assert(bufferCurPos == buffer.length);
+    assert(bufferLen == buffer.length);
+
+    flush();
+
+    writesize += writeBlock(buf,len);
+
+  ExitWrite:
+    return writesize;
+  }
+
+  override ulong seek(long offset, SeekPos whence) {
+    assertSeekable();
+
+    if ((whence != SeekPos.Current) ||
+        (offset + bufferCurPos < 0) ||
+        (offset + bufferCurPos >= bufferLen)) {
+      flush();
+      streamPos = s.seek(offset,whence);
+    } else {
+      bufferCurPos += offset;
+    }
+    readEOF = false;
+    return streamPos-bufferSourcePos+bufferCurPos;
+  }
+
+  // Buffered readLine - Dave Fladebo
+  // reads a line, terminated by either CR, LF, CR/LF, or EOF
+  // reusing the memory in buffer if result will fit, otherwise
+  // will reallocate (using concatenation)
+  template TreadLine(T) {
+      T[] readLine(T[] inBuffer)
+      {
+          size_t    lineSize = 0;
+          bool    haveCR = false;
+          T       c = '\0';
+          size_t    idx = 0;
+          ubyte*  pc = cast(ubyte*)&c;
+
+        L0:
+          for(;;) {
+              size_t start = bufferCurPos;
+            L1:
+              foreach(ubyte b; buffer[start .. bufferLen]) {
+                  bufferCurPos++;
+                  pc[idx] = b;
+                  if(idx < T.sizeof - 1) {
+                      idx++;
+                      continue L1;
+                  } else {
+                      idx = 0;
+                  }
+                  if(c == '\n' || haveCR) {
+                      if(haveCR && c != '\n') bufferCurPos--;
+                      break L0;
+                  } else {
+                      if(c == '\r') {
+                          haveCR = true;
+                      } else {
+                          if(lineSize < inBuffer.length) {
+                              inBuffer[lineSize] = c;
+                          } else {
+                              inBuffer ~= c;
+                          }
+                          lineSize++;
+                      }
+                  }
+              }
+              flush();
+              size_t res = super.readBlock(buffer.ptr, buffer.length);
+              if(!res) break L0; // EOF
+              bufferSourcePos = bufferLen = res;
+              streamPos += res;
+          }
+          return inBuffer[0 .. lineSize];
+      }
+  } // template TreadLine(T)
+
+  override char[] readLine(char[] inBuffer) {
+    if (ungetAvailable())
+      return super.readLine(inBuffer);
+    else
+      return TreadLine!(char).readLine(inBuffer);
+  }
+  alias readLine = Stream.readLine;
+
+  override wchar[] readLineW(wchar[] inBuffer) {
+    if (ungetAvailable())
+      return super.readLineW(inBuffer);
+    else
+      return TreadLine!(wchar).readLine(inBuffer);
+  }
+  alias readLineW = Stream.readLineW;
+
+  override void flush()
+  out {
+    assert(bufferCurPos == 0);
+    assert(bufferSourcePos == 0);
+    assert(bufferLen == 0);
+  }
+  body {
+    if (writeable && bufferDirty) {
+      if (bufferSourcePos != 0 && seekable) {
+        // move actual file pointer to front of buffer
+        streamPos = s.seek(-bufferSourcePos, SeekPos.Current);
+      }
+      // write buffer out
+      bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen);
+      if (bufferSourcePos != bufferLen) {
+        throw new WriteException("Unable to write to stream");
+      }
+    }
+    super.flush();
+    long diff = cast(long)bufferCurPos-bufferSourcePos;
+    if (diff != 0 && seekable) {
+      // move actual file pointer to current position
+      streamPos = s.seek(diff, SeekPos.Current);
+    }
+    // reset buffer data to be empty
+    bufferSourcePos = bufferCurPos = bufferLen = 0;
+    bufferDirty = false;
+  }
+
+  // returns true if end of stream is reached, false otherwise
+  override @property bool eof() {
+    if ((buffer.length == 0) || !readable) {
+      return super.eof;
+    }
+    // some simple tests to avoid flushing
+    if (ungetAvailable() || bufferCurPos != bufferLen)
+      return false;
+    if (bufferLen == buffer.length)
+      flush();
+    size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen);
+    bufferSourcePos +=  res;
+    bufferLen += res;
+    streamPos += res;
+    return readEOF;
+  }
+
+  // returns size of stream
+  override @property ulong size() {
+    if (bufferDirty) flush();
+    return s.size;
+  }
+
+  // returns estimated number of bytes available for immediate reading
+  override @property size_t available() {
+    return bufferLen - bufferCurPos;
+  }
+}
+
+/// An exception for File errors.
+class StreamFileException: StreamException {
+  /// Construct a StreamFileException with given error message.
+  this(string msg) { super(msg); }
+}
+
+/// An exception for errors during File.open.
+class OpenException: StreamFileException {
+  /// Construct an OpenFileException with given error message.
+  this(string msg) { super(msg); }
+}
+
+/// Specifies the $(LREF File) access mode used when opening the file.
+enum FileMode {
+  In = 1,     /// Opens the file for reading.
+  Out = 2,    /// Opens the file for writing.
+  OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist.
+  Append = 10 /// Opens the file for writing, appending new data to the end of the file.
+}
+
+version (Windows) {
+  private import core.sys.windows.windows;
+  extern (Windows) {
+    void FlushFileBuffers(HANDLE hFile);
+    DWORD  GetFileType(HANDLE hFile);
+  }
+}
+version (Posix) {
+  private import core.sys.posix.fcntl;
+  private import core.sys.posix.unistd;
+  alias HANDLE = int;
+}
+
+/// This subclass is for unbuffered file system streams.
+class File: Stream {
+
+  version (Windows) {
+    private HANDLE hFile;
+  }
+  version (Posix) {
+    private HANDLE hFile = -1;
+  }
+
+  this() {
+    super();
+    version (Windows) {
+      hFile = null;
+    }
+    version (Posix) {
+      hFile = -1;
+    }
+    isopen = false;
+  }
+
+  // opens existing handle; use with care!
+  this(HANDLE hFile, FileMode mode) {
+    super();
+    this.hFile = hFile;
+    readable = cast(bool)(mode & FileMode.In);
+    writeable = cast(bool)(mode & FileMode.Out);
+    version(Windows) {
+      seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK
+    } else {
+      auto result = lseek(hFile, 0, 0);
+      seekable = (result != ~0);
+    }
+  }
+
+  /***
+   * Create the stream with no open file, an open file in read mode, or an open
+   * file with explicit file mode.
+   * mode, if given, is a combination of FileMode.In
+   * (indicating a file that can be read) and FileMode.Out (indicating a file
+   * that can be written).
+   * Opening a file for reading that doesn't exist will error.
+   * Opening a file for writing that doesn't exist will create the file.
+   * The FileMode.OutNew mode will open the file for writing and reset the
+   * length to zero.
+   * The FileMode.Append mode will open the file for writing and move the
+   * file position to the end of the file.
+   */
+  this(string filename, FileMode mode = FileMode.In)
+  {
+      this();
+      open(filename, mode);
+  }
+
+
+  /***
+   * Open a file for the stream, in an identical manner to the constructors.
+   * If an error occurs an OpenException is thrown.
+   */
+  void open(string filename, FileMode mode = FileMode.In) {
+    close();
+    int access, share, createMode;
+    parseMode(mode, access, share, createMode);
+    seekable = true;
+    readable = cast(bool)(mode & FileMode.In);
+    writeable = cast(bool)(mode & FileMode.Out);
+    version (Windows) {
+      hFile = CreateFileW(filename.tempCStringW(), access, share,
+                          null, createMode, 0, null);
+      isopen = hFile != INVALID_HANDLE_VALUE;
+    }
+    version (Posix) {
+      hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share);
+      isopen = hFile != -1;
+    }
+    if (!isopen)
+      throw new OpenException(cast(string) ("Cannot open or create file '"
+                                            ~ filename ~ "'"));
+    else if ((mode & FileMode.Append) == FileMode.Append)
+      seekEnd(0);
+  }
+
+  private void parseMode(int mode,
+                         out int access,
+                         out int share,
+                         out int createMode) {
+    version (Windows) {
+      share |= FILE_SHARE_READ | FILE_SHARE_WRITE;
+      if (mode & FileMode.In) {
+        access |= GENERIC_READ;
+        createMode = OPEN_EXISTING;
+      }
+      if (mode & FileMode.Out) {
+        access |= GENERIC_WRITE;
+        createMode = OPEN_ALWAYS; // will create if not present
+      }
+      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
+        createMode = CREATE_ALWAYS; // resets file
+      }
+    }
+    version (Posix) {
+      share = octal!666;
+      if (mode & FileMode.In) {
+        access = O_RDONLY;
+      }
+      if (mode & FileMode.Out) {
+        createMode = O_CREAT; // will create if not present
+        access = O_WRONLY;
+      }
+      if (access == (O_WRONLY | O_RDONLY)) {
+        access = O_RDWR;
+      }
+      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
+        access |= O_TRUNC; // resets file
+      }
+    }
+  }
+
+  /// Create a file for writing.
+  void create(string filename) {
+    create(filename, FileMode.OutNew);
+  }
+
+  /// ditto
+  void create(string filename, FileMode mode) {
+    close();
+    open(filename, mode | FileMode.OutNew);
+  }
+
+  /// Close the current file if it is open; otherwise it does nothing.
+  override void close() {
+    if (isopen) {
+      super.close();
+      if (hFile) {
+        version (Windows) {
+          CloseHandle(hFile);
+          hFile = null;
+        } else version (Posix) {
+          core.sys.posix.unistd.close(hFile);
+          hFile = -1;
+        }
+      }
+    }
+  }
+
+  // destructor, closes file if still opened
+  ~this() { close(); }
+
+  version (Windows) {
+    // returns size of stream
+    override @property ulong size() {
+      assertSeekable();
+      uint sizehi;
+      uint sizelow = GetFileSize(hFile,&sizehi);
+      return (cast(ulong)sizehi << 32) + sizelow;
+    }
+  }
+
+  override size_t readBlock(void* buffer, size_t size) {
+    assertReadable();
+    version (Windows) {
+      auto dwSize = to!DWORD(size);
+      ReadFile(hFile, buffer, dwSize, &dwSize, null);
+      size = dwSize;
+    } else version (Posix) {
+      size = core.sys.posix.unistd.read(hFile, buffer, size);
+      if (size == -1)
+        size = 0;
+    }
+    readEOF = (size == 0);
+    return size;
+  }
+
+  override size_t writeBlock(const void* buffer, size_t size) {
+    assertWriteable();
+    version (Windows) {
+      auto dwSize = to!DWORD(size);
+      WriteFile(hFile, buffer, dwSize, &dwSize, null);
+      size = dwSize;
+    } else version (Posix) {
+      size = core.sys.posix.unistd.write(hFile, buffer, size);
+      if (size == -1)
+        size = 0;
+    }
+    return size;
+  }
+
+  override ulong seek(long offset, SeekPos rel) {
+    assertSeekable();
+    version (Windows) {
+      int hi = cast(int)(offset>>32);
+      uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel);
+      if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0))
+        throw new SeekException("unable to move file pointer");
+      ulong result = (cast(ulong)hi << 32) + low;
+    } else version (Posix) {
+      auto result = lseek(hFile, cast(off_t)offset, rel);
+      if (result == cast(typeof(result))-1)
+        throw new SeekException("unable to move file pointer");
+    }
+    readEOF = false;
+    return cast(ulong)result;
+  }
+
+  /***
+   * For a seekable file returns the difference of the size and position and
+   * otherwise returns 0.
+   */
+
+  override @property size_t available() {
+    if (seekable) {
+      ulong lavail = size - position;
+      if (lavail > size_t.max) lavail = size_t.max;
+      return cast(size_t)lavail;
+    }
+    return 0;
+  }
+
+  // OS-specific property, just in case somebody wants
+  // to mess with underlying API
+  HANDLE handle() { return hFile; }
+
+  // run a few tests
+  unittest {
+    File file = new File;
+    int i = 666;
+    auto stream_file = std.file.deleteme ~ "-stream.$$$";
+    file.create(stream_file);
+    // should be ok to write
+    assert(file.writeable);
+    file.writeLine("Testing stream.d:");
+    file.writeString("Hello, world!");
+    file.write(i);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+      assert(file.position == 19 + 13 + 4);
+    version (Posix)
+      assert(file.position == 18 + 13 + 4);
+    // we must be at the end of file
+    assert(file.eof);
+    file.close();
+    // no operations are allowed when file is closed
+    assert(!file.readable && !file.writeable && !file.seekable);
+    file.open(stream_file);
+    // should be ok to read
+    assert(file.readable);
+    assert(file.available == file.size);
+    char[] line = file.readLine();
+    char[] exp = "Testing stream.d:".dup;
+    assert(line[0] == 'T');
+    assert(line.length == exp.length);
+    assert(!std.algorithm.cmp(line, "Testing stream.d:"));
+    // jump over "Hello, "
+    file.seek(7, SeekPos.Current);
+    version (Windows)
+      assert(file.position == 19 + 7);
+    version (Posix)
+      assert(file.position == 18 + 7);
+    assert(!std.algorithm.cmp(file.readString(6), "world!"));
+    i = 0; file.read(i);
+    assert(i == 666);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+      assert(file.position == 19 + 13 + 4);
+    version (Posix)
+      assert(file.position == 18 + 13 + 4);
+    // we must be at the end of file
+    assert(file.eof);
+    file.close();
+    file.open(stream_file,FileMode.OutNew | FileMode.In);
+    file.writeLine("Testing stream.d:");
+    file.writeLine("Another line");
+    file.writeLine("");
+    file.writeLine("That was blank");
+    file.position = 0;
+    char[][] lines;
+    foreach(char[] line; file) {
+      lines ~= line.dup;
+    }
+    assert( lines.length == 4 );
+    assert( lines[0] == "Testing stream.d:");
+    assert( lines[1] == "Another line");
+    assert( lines[2] == "");
+    assert( lines[3] == "That was blank");
+    file.position = 0;
+    lines = new char[][4];
+    foreach(ulong n, char[] line; file) {
+      lines[cast(size_t)(n-1)] = line.dup;
+    }
+    assert( lines[0] == "Testing stream.d:");
+    assert( lines[1] == "Another line");
+    assert( lines[2] == "");
+    assert( lines[3] == "That was blank");
+    file.close();
+    std.file.remove(stream_file);
+  }
+}
+
+/***
+ * This subclass is for buffered file system streams.
+ *
+ * It is a convenience class for wrapping a File in a BufferedStream.
+ * A buffered stream must be closed explicitly to ensure the final buffer
+ * content is written to the file.
+ */
+class BufferedFile: BufferedStream {
+
+  /// opens file for reading
+  this() { super(new File()); }
+
+  /// opens file in requested mode and buffer size
+  this(string filename, FileMode mode = FileMode.In,
+       size_t bufferSize = DefaultBufferSize) {
+    super(new File(filename,mode),bufferSize);
+  }
+
+  /// opens file for reading with requested buffer size
+  this(File file, size_t bufferSize = DefaultBufferSize) {
+    super(file,bufferSize);
+  }
+
+  /// opens existing handle; use with care!
+  this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) {
+    super(new File(hFile,mode),buffersize);
+  }
+
+  /// opens file in requested mode
+  void open(string filename, FileMode mode = FileMode.In) {
+    File sf = cast(File)s;
+    sf.open(filename,mode);
+    resetSource();
+  }
+
+  /// creates file in requested mode
+  void create(string filename, FileMode mode = FileMode.OutNew) {
+    File sf = cast(File)s;
+    sf.create(filename,mode);
+    resetSource();
+  }
+
+  // run a few tests same as File
+  unittest {
+    BufferedFile file = new BufferedFile;
+    int i = 666;
+    auto stream_file = std.file.deleteme ~ "-stream.$$$";
+    file.create(stream_file);
+    // should be ok to write
+    assert(file.writeable);
+    file.writeLine("Testing stream.d:");
+    file.writeString("Hello, world!");
+    file.write(i);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+      assert(file.position == 19 + 13 + 4);
+    version (Posix)
+      assert(file.position == 18 + 13 + 4);
+    // we must be at the end of file
+    assert(file.eof);
+    long oldsize = cast(long)file.size;
+    file.close();
+    // no operations are allowed when file is closed
+    assert(!file.readable && !file.writeable && !file.seekable);
+    file.open(stream_file);
+    // should be ok to read
+    assert(file.readable);
+    // test getc/ungetc and size
+    char c1 = file.getc();
+    file.ungetc(c1);
+    assert( file.size == oldsize );
+    assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:"));
+    // jump over "Hello, "
+    file.seek(7, SeekPos.Current);
+    version (Windows)
+      assert(file.position == 19 + 7);
+    version (Posix)
+      assert(file.position == 18 + 7);
+    assert(!std.algorithm.cmp(file.readString(6), "world!"));
+    i = 0; file.read(i);
+    assert(i == 666);
+    // string#1 + string#2 + int should give exacly that
+    version (Windows)
+      assert(file.position == 19 + 13 + 4);
+    version (Posix)
+      assert(file.position == 18 + 13 + 4);
+    // we must be at the end of file
+    assert(file.eof);
+    file.close();
+    std.file.remove(stream_file);
+  }
+
+}
+
+/// UTF byte-order-mark signatures
+enum BOM {
+        UTF8,           /// UTF-8
+        UTF16LE,        /// UTF-16 Little Endian
+        UTF16BE,        /// UTF-16 Big Endian
+        UTF32LE,        /// UTF-32 Little Endian
+        UTF32BE,        /// UTF-32 Big Endian
+}
+
+private enum int NBOMS = 5;
+immutable Endian[NBOMS] BOMEndian =
+[ std.system.endian,
+  Endian.littleEndian, Endian.bigEndian,
+  Endian.littleEndian, Endian.bigEndian
+  ];
+
+immutable ubyte[][NBOMS] ByteOrderMarks =
+[ [0xEF, 0xBB, 0xBF],
+  [0xFF, 0xFE],
+  [0xFE, 0xFF],
+  [0xFF, 0xFE, 0x00, 0x00],
+  [0x00, 0x00, 0xFE, 0xFF]
+  ];
+
+
+/***
+ * This subclass wraps a stream with big-endian or little-endian byte order
+ * swapping.
+ *
+ * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or
+ * written.
+ * Note that an EndianStream should not be used as the source of another
+ * FilterStream since a FilterStream call the source with byte-oriented
+ * read/write requests and the EndianStream will not perform any byte swapping.
+ * The EndianStream reads and writes binary data (non-getc functions) in a
+ * one-to-one
+ * manner with the source stream so the source stream's position and state will be
+ * kept in sync with the EndianStream if only non-getc functions are called.
+ */
+class EndianStream : FilterStream {
+
+  Endian endian;        /// Endianness property of the source stream.
+
+  /***
+   * Create the endian stream for the source stream source with endianness end.
+   * The default endianness is the native byte order.
+   * The Endian type is defined
+   * in the std.system module.
+   */
+  this(Stream source, Endian end = std.system.endian) {
+    super(source);
+    endian = end;
+  }
+
+  /***
+   * Return -1 if no BOM and otherwise read the BOM and return it.
+   *
+   * If there is no BOM or if bytes beyond the BOM are read then the bytes read
+   * are pushed back onto the ungetc buffer or ungetcw buffer.
+   * Pass ungetCharSize == 2 to use
+   * ungetcw instead of ungetc when no BOM is present.
+   */
+  int readBOM(int ungetCharSize = 1) {
+    ubyte[4] BOM_buffer;
+    int n = 0;       // the number of read bytes
+    int result = -1; // the last match or -1
+    for (int i=0; i < NBOMS; ++i) {
+      int j;
+      immutable ubyte[] bom = ByteOrderMarks[i];
+      for (j=0; j < bom.length; ++j) {
+        if (n <= j) { // have to read more
+          if (eof)
+            break;
+          readExact(&BOM_buffer[n++],1);
+        }
+        if (BOM_buffer[j] != bom[j])
+          break;
+      }
+      if (j == bom.length) // found a match
+        result = i;
+    }
+    ptrdiff_t m = 0;
+    if (result != -1) {
+      endian = BOMEndian[result]; // set stream endianness
+      m = ByteOrderMarks[result].length;
+    }
+    if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) {
+      while (n-- > m)
+        ungetc(BOM_buffer[n]);
+    } else { // should eventually support unget for dchar as well
+      if (n & 1) // make sure we have an even number of bytes
+        readExact(&BOM_buffer[n++],1);
+      while (n > m) {
+        n -= 2;
+        wchar cw = *(cast(wchar*)&BOM_buffer[n]);
+        fixBO(&cw,2);
+        ungetcw(cw);
+      }
+    }
+    return result;
+  }
+
+  /***
+   * Correct the byte order of buffer to match native endianness.
+   * size must be even.
+   */
+  final void fixBO(const(void)* buffer, size_t size) {
+    if (endian != std.system.endian) {
+      ubyte* startb = cast(ubyte*)buffer;
+      uint* start = cast(uint*)buffer;
+      switch (size) {
+      case 0: break;
+      case 2: {
+        ubyte x = *startb;
+        *startb = *(startb+1);
+        *(startb+1) = x;
+        break;
+      }
+      case 4: {
+        *start = bswap(*start);
+        break;
+      }
+      default: {
+        uint* end = cast(uint*)(buffer + size - uint.sizeof);
+        while (start < end) {
+          uint x = bswap(*start);
+          *start = bswap(*end);
+          *end = x;
+          ++start;
+          --end;
+        }
+        startb = cast(ubyte*)start;
+        ubyte* endb = cast(ubyte*)end;
+        auto len = uint.sizeof - (startb - endb);
+        if (len > 0)
+          fixBO(startb,len);
+      }
+      }
+    }
+  }
+
+  /***
+   * Correct the byte order of the given buffer in blocks of the given size and
+   * repeated the given number of times.
+   * size must be even.
+   */
+  final void fixBlockBO(void* buffer, uint size, size_t repeat) {
+    while (repeat--) {
+      fixBO(buffer,size);
+      buffer += size;
+    }
+  }
+
+  override void read(out byte x) { readExact(&x, x.sizeof); }
+  override void read(out ubyte x) { readExact(&x, x.sizeof); }
+  override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); }
+  override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); }
+  override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); }
+  override void read(out char x) { readExact(&x, x.sizeof); }
+  override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+  override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
+
+  override wchar getcw() {
+    wchar c;
+    if (prevCr) {
+      prevCr = false;
+      c = getcw();
+      if (c != '\n')
+        return c;
+    }
+    if (unget.length > 1) {
+      c = unget[unget.length - 1];
+      unget.length = unget.length - 1;
+    } else {
+      void* buf = &c;
+      size_t n = readBlock(buf,2);
+      if (n == 1 && readBlock(buf+1,1) == 0)
+          throw new ReadException("not enough data in stream");
+      fixBO(&c,c.sizeof);
+    }
+    return c;
+  }
+
+  override wchar[] readStringW(size_t length) {
+    wchar[] result = new wchar[length];
+    readExact(result.ptr, length * wchar.sizeof);
+    fixBlockBO(result.ptr, wchar.sizeof, length);
+    return result;
+  }
+
+  /// Write the specified BOM b to the source stream.
+  void writeBOM(BOM b) {
+    immutable ubyte[] bom = ByteOrderMarks[b];
+    writeBlock(bom.ptr, bom.length);
+  }
+
+  override void write(byte x) { writeExact(&x, x.sizeof); }
+  override void write(ubyte x) { writeExact(&x, x.sizeof); }
+  override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); }
+  override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); }
+  override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof);  }
+  override void write(char x) { writeExact(&x, x.sizeof); }
+  override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+  override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
+
+  override void writeStringW(const(wchar)[] str) {
+    foreach(wchar cw;str) {
+      fixBO(&cw,2);
+      s.writeExact(&cw, 2);
+    }
+  }
+
+  override @property bool eof() { return s.eof && !ungetAvailable();  }
+  override @property ulong size() { return s.size;  }
+
+  unittest {
+    MemoryStream m;
+    m = new MemoryStream ();
+    EndianStream em = new EndianStream(m,Endian.bigEndian);
+    uint x = 0x11223344;
+    em.write(x);
+    assert( m.data[0] == 0x11 );
+    assert( m.data[1] == 0x22 );
+    assert( m.data[2] == 0x33 );
+    assert( m.data[3] == 0x44 );
+    em.position = 0;
+    ushort x2 = 0x5566;
+    em.write(x2);
+    assert( m.data[0] == 0x55 );
+    assert( m.data[1] == 0x66 );
+    em.position = 0;
+    static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12];
+    em.fixBO(x3.ptr,12);
+    if (std.system.endian == Endian.littleEndian) {
+      assert( x3[0] == 12 );
+      assert( x3[1] == 11 );
+      assert( x3[2] == 10 );
+      assert( x3[4] == 8 );
+      assert( x3[5] == 7 );
+      assert( x3[6] == 6 );
+      assert( x3[8] == 4 );
+      assert( x3[9] == 3 );
+      assert( x3[10] == 2 );
+      assert( x3[11] == 1 );
+    }
+    em.endian = Endian.littleEndian;
+    em.write(x);
+    assert( m.data[0] == 0x44 );
+    assert( m.data[1] == 0x33 );
+    assert( m.data[2] == 0x22 );
+    assert( m.data[3] == 0x11 );
+    em.position = 0;
+    em.write(x2);
+    assert( m.data[0] == 0x66 );
+    assert( m.data[1] == 0x55 );
+    em.position = 0;
+    em.fixBO(x3.ptr,12);
+    if (std.system.endian == Endian.bigEndian) {
+      assert( x3[0] == 12 );
+      assert( x3[1] == 11 );
+      assert( x3[2] == 10 );
+      assert( x3[4] == 8 );
+      assert( x3[5] == 7 );
+      assert( x3[6] == 6 );
+      assert( x3[8] == 4 );
+      assert( x3[9] == 3 );
+      assert( x3[10] == 2 );
+      assert( x3[11] == 1 );
+    }
+    em.writeBOM(BOM.UTF8);
+    assert( m.position == 3 );
+    assert( m.data[0] == 0xEF );
+    assert( m.data[1] == 0xBB );
+    assert( m.data[2] == 0xBF );
+    em.writeString ("Hello, world");
+    em.position = 0;
+    assert( m.position == 0 );
+    assert( em.readBOM() == BOM.UTF8 );
+    assert( m.position == 3 );
+    assert( em.getc() == 'H' );
+    em.position = 0;
+    em.writeBOM(BOM.UTF16BE);
+    assert( m.data[0] == 0xFE );
+    assert( m.data[1] == 0xFF );
+    em.position = 0;
+    em.writeBOM(BOM.UTF16LE);
+    assert( m.data[0] == 0xFF );
+    assert( m.data[1] == 0xFE );
+    em.position = 0;
+    em.writeString ("Hello, world");
+    em.position = 0;
+    assert( em.readBOM() == -1 );
+    assert( em.getc() == 'H' );
+    assert( em.getc() == 'e' );
+    assert( em.getc() == 'l' );
+    assert( em.getc() == 'l' );
+    em.position = 0;
+  }
+}
+
+/***
+ * Parameterized subclass that wraps an array-like buffer with a stream
+ * interface.
+ *
+ * The type Buffer must support the length property, opIndex and opSlice.
+ * Compile in release mode when directly instantiating a TArrayStream to avoid
+ * link errors.
+ */
+class TArrayStream(Buffer): Stream {
+  Buffer buf; // current data
+  ulong len;  // current data length
+  ulong cur;  // current file position
+
+  /// Create the stream for the the buffer buf. Non-copying.
+  this(Buffer buf) {
+    super ();
+    this.buf = buf;
+    this.len = buf.length;
+    readable = writeable = seekable = true;
+  }
+
+  // ensure subclasses don't violate this
+  invariant() {
+    assert(len <= buf.length);
+    assert(cur <= len);
+  }
+
+  override size_t readBlock(void* buffer, size_t size) {
+    assertReadable();
+    ubyte* cbuf = cast(ubyte*) buffer;
+    if (len - cur < size)
+      size = cast(size_t)(len - cur);
+    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
+    cbuf[0 .. size] = ubuf[];
+    cur += size;
+    return size;
+  }
+
+  override size_t writeBlock(const void* buffer, size_t size) {
+    assertWriteable();
+    ubyte* cbuf = cast(ubyte*) buffer;
+    ulong blen = buf.length;
+    if (cur + size > blen)
+      size = cast(size_t)(blen - cur);
+    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
+    ubuf[] = cbuf[0 .. size];
+    cur += size;
+    if (cur > len)
+      len = cur;
+    return size;
+  }
+
+  override ulong seek(long offset, SeekPos rel) {
+    assertSeekable();
+    long scur; // signed to saturate to 0 properly
+
+    switch (rel) {
+    case SeekPos.Set: scur = offset; break;
+    case SeekPos.Current: scur = cast(long)(cur + offset); break;
+    case SeekPos.End: scur = cast(long)(len + offset); break;
+    default:
+        assert(0);
+    }
+
+    if (scur < 0)
+      cur = 0;
+    else if (scur > len)
+      cur = len;
+    else
+      cur = cast(ulong)scur;
+
+    return cur;
+  }
+
+  override @property size_t available () { return cast(size_t)(len - cur); }
+
+  /// Get the current memory data in total.
+  @property ubyte[] data() {
+    if (len > size_t.max)
+      throw new StreamException("Stream too big");
+    const(void)[] res = buf[0 .. cast(size_t)len];
+    return cast(ubyte[])res;
+  }
+
+  override string toString() {
+      // assume data is UTF8
+      return to!(string)(cast(char[])data);
+  }
+}
+
+/* Test the TArrayStream */
+unittest {
+  char[100] buf;
+  TArrayStream!(char[]) m;
+
+  m = new TArrayStream!(char[]) (buf);
+  assert (m.isOpen);
+  m.writeString ("Hello, world");
+  assert (m.position == 12);
+  assert (m.available == 88);
+  assert (m.seekSet (0) == 0);
+  assert (m.available == 100);
+  assert (m.seekCur (4) == 4);
+  assert (m.available == 96);
+  assert (m.seekEnd (-8) == 92);
+  assert (m.available == 8);
+  assert (m.size == 100);
+  assert (m.seekSet (4) == 4);
+  assert (m.readString (4) == "o, w");
+  m.writeString ("ie");
+  assert (buf[0..12] == "Hello, wield");
+  assert (m.position == 10);
+  assert (m.available == 90);
+  assert (m.size == 100);
+  m.seekSet (0);
+  assert (m.printf ("Answer is %d", 42) == 12);
+  assert (buf[0..12] == "Answer is 42");
+}
+
+/// This subclass reads and constructs an array of bytes in memory.
+class MemoryStream: TArrayStream!(ubyte[]) {
+
+  /// Create the output buffer and setup for reading, writing, and seeking.
+  // clear to an empty buffer.
+  this() { this(cast(ubyte[]) null); }
+
+  /***
+   * Create the output buffer and setup for reading, writing, and seeking.
+   * Load it with specific input data.
+   */
+  this(ubyte[] buf) { super (buf); }
+  this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto
+  this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto
+
+  /// Ensure the stream can write count extra bytes from cursor position without an allocation.
+  void reserve(size_t count) {
+    if (cur + count > buf.length)
+      buf.length = cast(uint)((cur + count) * 2);
+  }
+
+  override size_t writeBlock(const void* buffer, size_t size) {
+    reserve(size);
+    return super.writeBlock(buffer,size);
+  }
+
+  unittest {
+    MemoryStream m;
+
+    m = new MemoryStream ();
+    assert (m.isOpen);
+    m.writeString ("Hello, world");
+    assert (m.position == 12);
+    assert (m.seekSet (0) == 0);
+    assert (m.available == 12);
+    assert (m.seekCur (4) == 4);
+    assert (m.available == 8);
+    assert (m.seekEnd (-8) == 4);
+    assert (m.available == 8);
+    assert (m.size == 12);
+    assert (m.readString (4) == "o, w");
+    m.writeString ("ie");
+    assert (cast(char[]) m.data == "Hello, wield");
+    m.seekEnd (0);
+    m.writeString ("Foo");
+    assert (m.position == 15);
+    assert (m.available == 0);
+    m.writeString ("Foo foo foo foo foo foo foo");
+    assert (m.position == 42);
+    m.position = 0;
+    assert (m.available == 42);
+    m.writef("%d %d %s",100,345,"hello");
+    auto str = m.toString();
+    assert (str[0..13] == "100 345 hello", str[0 .. 13]);
+    assert (m.available == 29);
+    assert (m.position == 13);
+
+    MemoryStream m2;
+    m.position = 3;
+    m2 = new MemoryStream ();
+    m2.writeString("before");
+    m2.copyFrom(m,10);
+    str = m2.toString();
+    assert (str[0..16] == "before 345 hello");
+    m2.position = 3;
+    m2.copyFrom(m);
+    auto str2 = m.toString();
+    str = m2.toString();
+    assert (str == ("bef" ~ str2));
+  }
+}
+
+import std.mmfile;
+
+/***
+ * This subclass wraps a memory-mapped file with the stream API.
+ * See std.mmfile module.
+ */
+class MmFileStream : TArrayStream!(MmFile) {
+
+  /// Create stream wrapper for file.
+  this(MmFile file) {
+    super (file);
+    MmFile.Mode mode = file.mode();
+    writeable = mode > MmFile.Mode.read;
+  }
+
+  override void flush() {
+    if (isopen) {
+      super.flush();
+      buf.flush();
+    }
+  }
+
+  override void close() {
+    if (isopen) {
+      super.close();
+      delete buf;
+      buf = null;
+    }
+  }
+}
+
+unittest {
+  auto test_file = std.file.deleteme ~ "-testing.txt";
+  MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null);
+  MmFileStream m;
+  m = new MmFileStream (mf);
+  m.writeString ("Hello, world");
+  assert (m.position == 12);
+  assert (m.seekSet (0) == 0);
+  assert (m.seekCur (4) == 4);
+  assert (m.seekEnd (-8) == 92);
+  assert (m.size == 100);
+  assert (m.seekSet (4));
+  assert (m.readString (4) == "o, w");
+  m.writeString ("ie");
+  ubyte[] dd = m.data;
+  assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield");
+  m.position = 12;
+  m.writeString ("Foo");
+  assert (m.position == 15);
+  m.writeString ("Foo foo foo foo foo foo foo");
+  assert (m.position == 42);
+  m.close();
+  mf = new MmFile(test_file);
+  m = new MmFileStream (mf);
+  assert (!m.writeable);
+  char[] str = m.readString(12);
+  assert (str == "Hello, wield");
+  m.close();
+  std.file.remove(test_file);
+}
+
+
+/***
+ * This subclass slices off a portion of another stream, making seeking relative
+ * to the boundaries of the slice.
+ *
+ * It could be used to section a large file into a
+ * set of smaller files, such as with tar archives. Reading and writing a
+ * SliceStream does not modify the position of the source stream if it is
+ * seekable.
+ */
+class SliceStream : FilterStream {
+  private {
+    ulong pos;  // our position relative to low
+    ulong low; // low stream offset.
+    ulong high; // high stream offset.
+    bool bounded; // upper-bounded by high.
+  }
+
+  /***
+   * Indicate both the source stream to use for reading from and the low part of
+   * the slice.
+   *
+   * The high part of the slice is dependent upon the end of the source
+   * stream, so that if you write beyond the end it resizes the stream normally.
+   */
+  this (Stream s, ulong low)
+  in {
+    assert (low <= s.size);
+  }
+  body {
+    super(s);
+    this.low = low;
+    this.high = 0;
+    this.bounded = false;
+  }
+
+  /***
+   * Indicate the high index as well.
+   *
+   * Attempting to read or write past the high
+   * index results in the end being clipped off.
+   */
+  this (Stream s, ulong low, ulong high)
+  in {
+    assert (low <= high);
+    assert (high <= s.size);
+  }
+  body {
+    super(s);
+    this.low = low;
+    this.high = high;
+    this.bounded = true;
+  }
+
+  invariant() {
+    if (bounded)
+      assert (pos <= high - low);
+    else
+      // size() does not appear to be const, though it should be
+      assert (pos <= (cast()s).size - low);
+  }
+
+  override size_t readBlock (void *buffer, size_t size) {
+    assertReadable();
+    if (bounded && size > high - low - pos)
+        size = cast(size_t)(high - low - pos);
+    ulong bp = s.position;
+    if (seekable)
+      s.position = low + pos;
+    size_t ret = super.readBlock(buffer, size);
+    if (seekable) {
+      pos = s.position - low;
+      s.position = bp;
+    }
+    return ret;
+  }
+
+  override size_t writeBlock (const void *buffer, size_t size) {
+    assertWriteable();
+    if (bounded && size > high - low - pos)
+        size = cast(size_t)(high - low - pos);
+    ulong bp = s.position;
+    if (seekable)
+      s.position = low + pos;
+    size_t ret = s.writeBlock(buffer, size);
+    if (seekable) {
+      pos = s.position - low;
+      s.position = bp;
+    }
+    return ret;
+  }
+
+  override ulong seek(long offset, SeekPos rel) {
+    assertSeekable();
+    long spos;
+
+    switch (rel) {
+      case SeekPos.Set:
+        spos = offset;
+        break;
+      case SeekPos.Current:
+        spos = cast(long)(pos + offset);
+        break;
+      case SeekPos.End:
+        if (bounded)
+          spos = cast(long)(high - low + offset);
+        else
+          spos = cast(long)(s.size - low + offset);
+        break;
+      default:
+        assert(0);
+    }
+
+    if (spos < 0)
+      pos = 0;
+    else if (bounded && spos > high - low)
+      pos = high - low;
+    else if (!bounded && spos > s.size - low)
+      pos = s.size - low;
+    else
+      pos = cast(ulong)spos;
+
+    readEOF = false;
+    return pos;
+  }
+
+  override @property size_t available() {
+    size_t res = s.available;
+    ulong bp = s.position;
+    if (bp <= pos+low && pos+low <= bp+res) {
+      if (!bounded || bp+res <= high)
+        return cast(size_t)(bp + res - pos - low);
+      else if (high <= bp+res)
+        return cast(size_t)(high - pos - low);
+    }
+    return 0;
+  }
+
+  unittest {
+    MemoryStream m;
+    SliceStream s;
+
+    m = new MemoryStream ((cast(char[])"Hello, world").dup);
+    s = new SliceStream (m, 4, 8);
+    assert (s.size == 4);
+    assert (m.position == 0);
+    assert (s.position == 0);
+    assert (m.available == 12);
+    assert (s.available == 4);
+
+    assert (s.writeBlock (cast(char *) "Vroom", 5) == 4);
+    assert (m.position == 0);
+    assert (s.position == 4);
+    assert (m.available == 12);
+    assert (s.available == 0);
+    assert (s.seekEnd (-2) == 2);
+    assert (s.available == 2);
+    assert (s.seekEnd (2) == 4);
+    assert (s.available == 0);
+    assert (m.position == 0);
+    assert (m.available == 12);
+
+    m.seekEnd(0);
+    m.writeString("\nBlaho");
+    assert (m.position == 18);
+    assert (m.available == 0);
+    assert (s.position == 4);
+    assert (s.available == 0);
+
+    s = new SliceStream (m, 4);
+    assert (s.size == 14);
+    assert (s.toString () == "Vrooorld\nBlaho");
+    s.seekEnd (0);
+    assert (s.available == 0);
+
+    s.writeString (", etcetera.");
+    assert (s.position == 25);
+    assert (s.seekSet (0) == 0);
+    assert (s.size == 25);
+    assert (m.position == 18);
+    assert (m.size == 29);
+    assert (m.toString() == "HellVrooorld\nBlaho, etcetera.");
+  }
+}
diff --git a/libphobos/src/std/string.d b/libphobos/src/std/string.d
new file mode 100644
index 00000000000..6ca34281b08
--- /dev/null
+++ b/libphobos/src/std/string.d
@@ -0,0 +1,6856 @@
+// Written in the D programming language.
+
+/**
+String handling functions.
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions) )
+$(TR $(TDNW Searching)
+    $(TD
+         $(MYREF column)
+         $(MYREF inPattern)
+         $(MYREF indexOf)
+         $(MYREF indexOfAny)
+         $(MYREF indexOfNeither)
+         $(MYREF lastIndexOf)
+         $(MYREF lastIndexOfAny)
+         $(MYREF lastIndexOfNeither)
+    )
+)
+$(TR $(TDNW Comparison)
+    $(TD
+         $(MYREF countchars)
+         $(MYREF isNumeric)
+    )
+)
+$(TR $(TDNW Mutation)
+    $(TD
+         $(MYREF capitalize)
+         $(MYREF munch)
+         $(MYREF removechars)
+         $(MYREF squeeze)
+    )
+)
+$(TR $(TDNW Pruning and Filling)
+    $(TD
+         $(MYREF center)
+         $(MYREF chomp)
+         $(MYREF chompPrefix)
+         $(MYREF chop)
+         $(MYREF detabber)
+         $(MYREF detab)
+         $(MYREF entab)
+         $(MYREF entabber)
+         $(MYREF leftJustify)
+         $(MYREF outdent)
+         $(MYREF rightJustify)
+         $(MYREF strip)
+         $(MYREF stripLeft)
+         $(MYREF stripRight)
+         $(MYREF wrap)
+    )
+)
+$(TR $(TDNW Substitution)
+    $(TD
+         $(MYREF abbrev)
+         $(MYREF soundex)
+         $(MYREF soundexer)
+         $(MYREF succ)
+         $(MYREF tr)
+         $(MYREF translate)
+    )
+)
+$(TR $(TDNW Miscellaneous)
+    $(TD
+         $(MYREF assumeUTF)
+         $(MYREF fromStringz)
+         $(MYREF lineSplitter)
+         $(MYREF representation)
+         $(MYREF splitLines)
+         $(MYREF toStringz)
+    )
+)))
+
+Objects of types $(D _string), $(D wstring), and $(D dstring) are value types
+and cannot be mutated element-by-element. For using mutation during building
+strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring)
+types are preferable because they don't exhibit undesired aliasing, thus
+making code more robust.
+
+The following functions are publicly imported:
+
+$(BOOKTABLE ,
+$(TR $(TH Module) $(TH Functions) )
+$(LEADINGROW Publicly imported functions)
+    $(TR $(TD std.algorithm)
+        $(TD
+         $(SHORTXREF_PACK algorithm,comparison,cmp)
+         $(SHORTXREF_PACK algorithm,searching,count)
+         $(SHORTXREF_PACK algorithm,searching,endsWith)
+         $(SHORTXREF_PACK algorithm,searching,startsWith)
+    ))
+    $(TR $(TD std.array)
+        $(TD
+         $(SHORTXREF array, join)
+         $(SHORTXREF array, replace)
+         $(SHORTXREF array, replaceInPlace)
+         $(SHORTXREF array, split)
+         $(SHORTXREF array, empty)
+    ))
+    $(TR $(TD std.format)
+        $(TD
+         $(SHORTXREF format, format)
+         $(SHORTXREF format, sformat)
+    ))
+    $(TR $(TD std.uni)
+        $(TD
+         $(SHORTXREF uni, icmp)
+         $(SHORTXREF uni, toLower)
+         $(SHORTXREF uni, toLowerInPlace)
+         $(SHORTXREF uni, toUpper)
+         $(SHORTXREF uni, toUpperInPlace)
+    ))
+)
+
+There is a rich set of functions for _string handling defined in other modules.
+Functions related to Unicode and ASCII are found in $(LINK2 std_uni.html, std.uni)
+and $(LINK2 std_ascii.html, std.ascii), respectively. Other functions that have a
+wider generality than just strings can be found in $(LINK2 std_algorithm.html,
+std.algorithm) and $(LINK2 std_range.html, std.range).
+
+See_Also:
+    $(LIST
+    $(LINK2 std_algorithm.html, std.algorithm) and
+    $(LINK2 std_range.html, std.range)
+    for generic range algorithms
+    ,
+    $(LINK2 std_ascii.html, std.ascii)
+    for functions that work with ASCII strings
+    ,
+    $(LINK2 std_uni.html, std.uni)
+    for functions that work with unicode strings
+    )
+
+Macros: WIKI = Phobos/StdString
+        SHORTXREF=$(XREF2 $1, $2, $(TT $2))
+        SHORTXREF_PACK=$(XREF_PACK_NAMED $1,$2,$3, $(TT $3))
+
+Copyright: Copyright Digital Mars 2007-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB digitalmars.com, Walter Bright),
+         $(WEB erdani.org, Andrei Alexandrescu),
+        Jonathan M Davis,
+        and David L. 'SpottedTiger' Davis
+
+Source:    $(PHOBOSSRC std/_string.d)
+
+*/
+module std.string;
+
+//debug=string;                 // uncomment to turn on debugging trustedPrintf's
+
+debug(string) private
+void trustedPrintf(in char* str) @trusted nothrow @nogc
+{
+    import core.stdc.stdio : printf;
+    printf("%s", str);
+}
+
+version (unittest)
+{
+private:
+    struct TestAliasedString
+    {
+        string get() @safe @nogc pure nothrow { return _s; }
+        alias get this;
+        @disable this(this);
+        string _s;
+    }
+
+    bool testAliasedString(alias func, Args...)(string s, Args args)
+    {
+        import std.algorithm.comparison : equal;
+        auto a = func(TestAliasedString(s), args);
+        auto b = func(s, args);
+        static if (is(typeof(equal(a, b))))
+        {
+            // For ranges, compare contents instead of object identity.
+            return equal(a, b);
+        }
+        else
+        {
+            return a == b;
+        }
+    }
+}
+
+public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace;
+public import std.format : format, sformat;
+import std.typecons : Flag;
+
+import std.meta;
+import std.range.primitives;
+import std.traits;
+
+//public imports for backward compatibility
+public import std.algorithm : startsWith, endsWith, cmp, count;
+public import std.array : join, replace, replaceInPlace, split, empty;
+
+/* ************* Exceptions *************** */
+
+/++
+    Exception thrown on errors in std.string functions.
+  +/
+class StringException : Exception
+{
+    /++
+        Params:
+            msg  = The message for the exception.
+            file = The file where the exception occurred.
+            line = The line number where the exception occurred.
+            next = The previous exception in the chain of exceptions, if any.
+      +/
+    this(string msg,
+         string file = __FILE__,
+         size_t line = __LINE__,
+         Throwable next = null) @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+}
+
+
+/++
+    Params:
+        cString = A null-terminated c-style string.
+
+    Returns: A D-style array of $(D char) referencing the same string.  The
+    returned array will retain the same type qualifiers as the input.
+
+    $(RED Important Note:) The returned array is a slice of the original buffer.
+    The original data is not changed and not copied.
++/
+
+inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow {
+    import core.stdc.string : strlen;
+    return cString ? cString[0 .. strlen(cString)] : null;
+}
+
+///
+@system pure unittest
+{
+    assert(fromStringz(null) == null);
+    assert(fromStringz("foo") == "foo");
+}
+
+/++
+    Params:
+        s = A D-style string.
+
+    Returns: A C-style null-terminated string equivalent to $(D s). $(D s)
+    must not contain embedded $(D '\0')'s as any C function will treat the
+    first $(D '\0') that it sees as the end of the string. If $(D s.empty) is
+    $(D true), then a string containing only $(D '\0') is returned.
+
+    $(RED Important Note:) When passing a $(D char*) to a C function, and the C
+    function keeps it around for any reason, make sure that you keep a
+    reference to it in your D code. Otherwise, it may become invalid during a
+    garbage collection cycle and cause a nasty bug when the C code tries to use
+    it.
+  +/
+immutable(char)* toStringz(const(char)[] s) @trusted pure nothrow
+in
+{
+    // The assert below contradicts the unittests!
+    //assert(memchr(s.ptr, 0, s.length) == null,
+    //text(s.length, ": `", s, "'"));
+}
+out (result)
+{
+    import core.stdc.string : strlen, memcmp;
+    if (result)
+    {
+        auto slen = s.length;
+        while (slen > 0 && s[slen-1] == 0) --slen;
+        assert(strlen(result) == slen);
+        assert(memcmp(result, s.ptr, slen) == 0);
+    }
+}
+body
+{
+    import std.exception : assumeUnique;
+    /+ Unfortunately, this isn't reliable.
+     We could make this work if string literals are put
+     in read-only memory and we test if s[] is pointing into
+     that.
+
+     /* Peek past end of s[], if it's 0, no conversion necessary.
+     * Note that the compiler will put a 0 past the end of static
+     * strings, and the storage allocator will put a 0 past the end
+     * of newly allocated char[]'s.
+     */
+     char* p = &s[0] + s.length;
+     if (*p == 0)
+     return s;
+     +/
+
+    // Need to make a copy
+    auto copy = new char[s.length + 1];
+    copy[0..s.length] = s[];
+    copy[s.length] = 0;
+
+    return assumeUnique(copy).ptr;
+}
+
+/++ Ditto +/
+immutable(char)* toStringz(in string s) @trusted pure nothrow
+{
+    if (s.empty) return "".ptr;
+    /* Peek past end of s[], if it's 0, no conversion necessary.
+     * Note that the compiler will put a 0 past the end of static
+     * strings, and the storage allocator will put a 0 past the end
+     * of newly allocated char[]'s.
+     */
+    immutable p = s.ptr + s.length;
+    // Is p dereferenceable? A simple test: if the p points to an
+    // address multiple of 4, then conservatively assume the pointer
+    // might be pointing to a new block of memory, which might be
+    // unreadable. Otherwise, it's definitely pointing to valid
+    // memory.
+    if ((cast(size_t) p & 3) && *p == 0)
+        return s.ptr;
+    return toStringz(cast(const char[]) s);
+}
+
+pure nothrow unittest
+{
+    import core.stdc.string : strlen;
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.toStringz.unittest\n");
+
+    // TODO: CTFEable toStringz is really necessary?
+    //assertCTFEable!(
+    //{
+    auto p = toStringz("foo");
+    assert(strlen(p) == 3);
+    const(char)[] foo = "abbzxyzzy";
+    p = toStringz(foo[3..5]);
+    assert(strlen(p) == 2);
+
+    string test = "";
+    p = toStringz(test);
+    assert(*p == 0);
+
+    test = "\0";
+    p = toStringz(test);
+    assert(*p == 0);
+
+    test = "foo\0";
+    p = toStringz(test);
+    assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
+
+    const string test2 = "";
+    p = toStringz(test2);
+    assert(*p == 0);
+    //});
+}
+
+
+/**
+   Flag indicating whether a search is case-sensitive.
+*/
+alias CaseSensitive = Flag!"caseSensitive";
+
+/++
+    Searches for character in range.
+
+    Params:
+        s = string or InputRange of characters to search in correct UTF format
+        c = character to search for
+        startIdx = starting index to a well-formed code point
+        cs = CaseSensitive.yes or CaseSensitive.no
+
+    Returns:
+        the index of the first occurrence of $(D c) in $(D s) with
+        respect to the start index $(D startIdx). If $(D c)
+        is not found, then $(D -1) is returned.
+        If $(D c) is found the value of the returned index is at least
+        $(D startIdx).
+        If the parameters are not valid UTF, the result will still
+        be in the range [-1 .. s.length], but will not be reliable otherwise.
+
+    Throws:
+        If the sequence starting at $(D startIdx) does not represent a well
+        formed codepoint, then a $(XREF utf,UTFException) may be thrown.
+
+  +/
+ptrdiff_t indexOf(Range)(Range s, in dchar c,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static import std.ascii;
+    static import std.uni;
+    import std.utf : byDchar, byCodeUnit, UTFException, codeLength;
+    alias Char = Unqual!(ElementEncodingType!Range);
+
+    if (cs == CaseSensitive.yes)
+    {
+        static if (Char.sizeof == 1 && isSomeString!Range)
+        {
+            import core.stdc.string : memchr;
+            if (std.ascii.isASCII(c) && !__ctfe)
+            {                                               // Plain old ASCII
+                auto trustedmemchr() @trusted { return cast(Char*)memchr(s.ptr, c, s.length); }
+                const p = trustedmemchr();
+                if (p)
+                    return p - s.ptr;
+                else
+                    return -1;
+            }
+        }
+
+        static if (Char.sizeof == 1)
+        {
+            if (c <= 0x7F)
+            {
+                ptrdiff_t i;
+                foreach (const c2; s)
+                {
+                    if (c == c2)
+                        return i;
+                    ++i;
+                }
+            }
+            else
+            {
+                ptrdiff_t i;
+                foreach (const c2; s.byDchar())
+                {
+                    if (c == c2)
+                        return i;
+                    i += codeLength!Char(c2);
+                }
+            }
+        }
+        else static if (Char.sizeof == 2)
+        {
+            if (c <= 0xFFFF)
+            {
+                ptrdiff_t i;
+                foreach (const c2; s)
+                {
+                    if (c == c2)
+                        return i;
+                    ++i;
+                }
+            }
+            else if (c <= 0x10FFFF)
+            {
+                // Encode UTF-16 surrogate pair
+                const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
+                const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
+                ptrdiff_t i;
+                for (auto r = s.byCodeUnit(); !r.empty; r.popFront())
+                {
+                    if (c1 == r.front)
+                    {
+                        r.popFront();
+                        if (r.empty)    // invalid UTF - missing second of pair
+                            break;
+                        if (c2 == r.front)
+                            return i;
+                        ++i;
+                    }
+                    ++i;
+                }
+            }
+        }
+        else static if (Char.sizeof == 4)
+        {
+            ptrdiff_t i;
+            foreach (const c2; s)
+            {
+                if (c == c2)
+                    return i;
+                ++i;
+            }
+        }
+        else
+            static assert(0);
+        return -1;
+    }
+    else
+    {
+        if (std.ascii.isASCII(c))
+        {                                                   // Plain old ASCII
+            auto c1 = cast(char) std.ascii.toLower(c);
+
+            ptrdiff_t i;
+            foreach (const c2; s.byCodeUnit())
+            {
+                if (c1 == std.ascii.toLower(c2))
+                    return i;
+                ++i;
+            }
+        }
+        else
+        {                                                   // c is a universal character
+            auto c1 = std.uni.toLower(c);
+
+            ptrdiff_t i;
+            foreach (const c2; s.byDchar())
+            {
+                if (c1 == std.uni.toLower(c2))
+                    return i;
+                i += codeLength!Char(c2);
+            }
+        }
+    }
+    return -1;
+}
+
+/// Ditto
+ptrdiff_t indexOf(Range)(Range s, in dchar c, in size_t startIdx,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static if (isSomeString!(typeof(s)) ||
+                (hasSlicing!(typeof(s)) && hasLength!(typeof(s))))
+    {
+        if (startIdx < s.length)
+        {
+            ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs);
+            if (foundIdx != -1)
+            {
+                return foundIdx + cast(ptrdiff_t)startIdx;
+            }
+        }
+    }
+    else
+    {
+        foreach (i; 0 .. startIdx)
+        {
+            if (s.empty)
+                return -1;
+            s.popFront();
+        }
+        ptrdiff_t foundIdx = indexOf(s, c, cs);
+        if (foundIdx != -1)
+        {
+            return foundIdx + cast(ptrdiff_t)startIdx;
+        }
+    }
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(indexOf(s, 'W') == 6);
+    assert(indexOf(s, 'Z') == -1);
+    assert(indexOf(s, 'w', CaseSensitive.no) == 6);
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(indexOf(s, 'W', 4) == 6);
+    assert(indexOf(s, 'Z', 100) == -1);
+    assert(indexOf(s, 'w', 3, CaseSensitive.no) == 6);
+}
+
+ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (isConvertibleToString!Range)
+{
+    return indexOf!(StringTypeOf!Range)(s, c, cs);
+}
+
+ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, in size_t startIdx,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (isConvertibleToString!Range)
+{
+    return indexOf!(StringTypeOf!Range)(s, c, startIdx, cs);
+}
+
+unittest
+{
+    assert(testAliasedString!indexOf("std/string.d", '/'));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    debug(string) trustedPrintf("string.indexOf.unittest\n");
+
+    import std.exception;
+    import std.utf : byChar, byWchar, byDchar;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        assert(indexOf(cast(S)null, cast(dchar)'a') == -1);
+        assert(indexOf(to!S("def"), cast(dchar)'a') == -1);
+        assert(indexOf(to!S("abba"), cast(dchar)'a') == 0);
+        assert(indexOf(to!S("def"), cast(dchar)'f') == 2);
+
+        assert(indexOf(to!S("def"), cast(dchar)'a', CaseSensitive.no) == -1);
+        assert(indexOf(to!S("def"), cast(dchar)'a', CaseSensitive.no) == -1);
+        assert(indexOf(to!S("Abba"), cast(dchar)'a', CaseSensitive.no) == 0);
+        assert(indexOf(to!S("def"), cast(dchar)'F', CaseSensitive.no) == 2);
+        assert(indexOf(to!S("ödef"), 'ö', CaseSensitive.no) == 0);
+
+        S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+        assert(indexOf("def", cast(char)'f', CaseSensitive.no) == 2);
+        assert(indexOf(sPlts, cast(char)'P', CaseSensitive.no) == 23);
+        assert(indexOf(sPlts, cast(char)'R', CaseSensitive.no) == 2);
+    }
+
+    foreach (cs; EnumMembers!CaseSensitive)
+    {
+        assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9);
+        assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7);
+        assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6);
+
+        assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9);
+        assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7);
+        assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6);
+
+        assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l',      cs) == 2);
+        assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7);
+        assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8);
+
+        assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5);
+        assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1);
+    }
+
+    char[10] fixedSizeArray = "0123456789";
+    assert(indexOf(fixedSizeArray, '2') == 2);
+    });
+}
+
+unittest
+{
+    assert(testAliasedString!indexOf("std/string.d", '/', 3));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    debug(string) trustedPrintf("string.indexOf(startIdx).unittest\n");
+
+    import std.utf : byCodeUnit, byChar, byWchar;
+    assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2);
+    assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2);
+    assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1);
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        assert(indexOf(cast(S)null, cast(dchar)'a', 1) == -1);
+        assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1);
+        assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3);
+        assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2);
+
+        assert((to!S("def")).indexOf(cast(dchar)'a', 1,
+                CaseSensitive.no) == -1);
+        assert(indexOf(to!S("def"), cast(dchar)'a', 1,
+                CaseSensitive.no) == -1);
+        assert(indexOf(to!S("def"), cast(dchar)'a', 12,
+                CaseSensitive.no) == -1);
+        assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2,
+                CaseSensitive.no) == 3);
+        assert(indexOf(to!S("def"), cast(dchar)'F', 2, CaseSensitive.no) == 2);
+
+        S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+        assert(indexOf("def", cast(char)'f', cast(uint)2,
+            CaseSensitive.no) == 2);
+        assert(indexOf(sPlts, cast(char)'P', 12, CaseSensitive.no) == 23);
+        assert(indexOf(sPlts, cast(char)'R', cast(ulong)1,
+            CaseSensitive.no) == 2);
+    }
+
+    foreach (cs; EnumMembers!CaseSensitive)
+    {
+        assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs)
+            == 9);
+        assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs)
+            == 7);
+        assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs)
+            == 6);
+    }
+}
+
+/++
+    Searches for substring in $(D s).
+
+    Params:
+        s = string or ForwardRange of characters to search in correct UTF format
+        sub = substring to search for
+        startIdx = the index into s to start searching from
+        cs = CaseSensitive.yes or CaseSensitive.no
+
+    Returns:
+        the index of the first occurrence of $(D sub) in $(D s) with
+        respect to the start index $(D startIdx). If $(D sub) is not found,
+        then $(D -1) is returned.
+        If the arguments are not valid UTF, the result will still
+        be in the range [-1 .. s.length], but will not be reliable otherwise.
+        If $(D sub) is found the value of the returned index is at least
+        $(D startIdx).
+
+    Throws:
+        If the sequence starting at $(D startIdx) does not represent a well
+        formed codepoint, then a $(XREF utf,UTFException) may be thrown.
+
+    Bugs:
+        Does not work with case insensitive strings where the mapping of
+        tolower and toupper is not 1:1.
+  +/
+ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        isSomeChar!Char)
+{
+    import std.uni : toLower;
+    alias Char1 = Unqual!(ElementEncodingType!Range);
+
+    static if (isSomeString!Range)
+    {
+        import std.algorithm : find;
+
+        const(Char1)[] balance;
+        if (cs == CaseSensitive.yes)
+        {
+            balance = find(s, sub);
+        }
+        else
+        {
+            balance = find!
+                ((a, b) => toLower(a) == toLower(b))
+                (s, sub);
+        }
+        return balance.empty ? -1 : balance.ptr - s.ptr;
+    }
+    else
+    {
+        if (s.empty)
+            return -1;
+        if (sub.empty)
+            return 0;                   // degenerate case
+
+        import std.utf : byDchar, codeLength;
+        auto subr = sub.byDchar;        // decode sub[] by dchar's
+        dchar sub0 = subr.front;        // cache first character of sub[]
+        subr.popFront();
+
+        // Special case for single character search
+        if (subr.empty)
+            return indexOf(s, sub0, cs);
+
+        if (cs == CaseSensitive.no)
+            sub0 = toLower(sub0);
+
+        /* Classic double nested loop search algorithm
+         */
+        ptrdiff_t index = 0;            // count code unit index into s
+        for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront())
+        {
+            dchar c2 = sbydchar.front;
+            if (cs == CaseSensitive.no)
+                c2 = toLower(c2);
+            if (c2 == sub0)
+            {
+                auto s2 = sbydchar.save;        // why s must be a forward range
+                foreach (c; subr.save)
+                {
+                    s2.popFront();
+                    if (s2.empty)
+                        return -1;
+                    if (cs == CaseSensitive.yes ? c != s2.front
+                                                : toLower(c) != toLower(s2.front)
+                       )
+                        goto Lnext;
+                }
+                return index;
+            }
+          Lnext:
+            index += codeLength!Char1(c2);
+        }
+        return -1;
+    }
+}
+
+/// Ditto
+ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
+        in size_t startIdx, in CaseSensitive cs = CaseSensitive.yes)
+    @safe if (isSomeChar!Char1 && isSomeChar!Char2)
+{
+    if (startIdx < s.length)
+    {
+        ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs);
+        if (foundIdx != -1)
+        {
+            return foundIdx + cast(ptrdiff_t)startIdx;
+        }
+    }
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(indexOf(s, "Wo", 4) == 6);
+    assert(indexOf(s, "Zo", 100) == -1);
+    assert(indexOf(s, "wo", 3, CaseSensitive.no) == 6);
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(indexOf(s, "Wo") == 6);
+    assert(indexOf(s, "Zo") == -1);
+    assert(indexOf(s, "wO", CaseSensitive.no) == 6);
+}
+
+ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub,
+        in CaseSensitive cs = CaseSensitive.yes)
+    if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+          isSomeChar!Char) &&
+        is(StringTypeOf!Range))
+{
+    return indexOf!(StringTypeOf!Range)(s, sub, cs);
+}
+
+unittest
+{
+    assert(testAliasedString!indexOf("std/string.d", "string"));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    debug(string) trustedPrintf("string.indexOf.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOf(cast(S)null, to!T("a")) == -1);
+            assert(indexOf(to!S("def"), to!T("a")) == -1);
+            assert(indexOf(to!S("abba"), to!T("a")) == 0);
+            assert(indexOf(to!S("def"), to!T("f")) == 2);
+            assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3);
+            assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6);
+
+            assert(indexOf(to!S("dfeffgfff"), to!T("a"), CaseSensitive.no) == -1);
+            assert(indexOf(to!S("def"), to!T("a"), CaseSensitive.no) == -1);
+            assert(indexOf(to!S("abba"), to!T("a"), CaseSensitive.no) == 0);
+            assert(indexOf(to!S("def"), to!T("f"), CaseSensitive.no) == 2);
+            assert(indexOf(to!S("dfefffg"), to!T("fff"), CaseSensitive.no) == 3);
+            assert(indexOf(to!S("dfeffgfff"), to!T("fff"), CaseSensitive.no) == 6);
+
+            S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+            S sMars = "Who\'s \'My Favorite Maritian?\'";
+
+            assert(indexOf(sMars, to!T("MY fAVe"), CaseSensitive.no) == -1);
+            assert(indexOf(sMars, to!T("mY fAVOriTe"), CaseSensitive.no) == 7);
+            assert(indexOf(sPlts, to!T("mArS:"), CaseSensitive.no) == 0);
+            assert(indexOf(sPlts, to!T("rOcK"), CaseSensitive.no) == 17);
+            assert(indexOf(sPlts, to!T("Un."), CaseSensitive.no) == 41);
+            assert(indexOf(sPlts, to!T(sPlts), CaseSensitive.no) == 0);
+
+            assert(indexOf("\u0100", to!T("\u0100"), CaseSensitive.no) == 0);
+
+            // Thanks to Carlos Santander B. and zwang
+            assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
+                           to!T("page-break-before"), CaseSensitive.no) == -1);
+        }();
+
+        foreach (cs; EnumMembers!CaseSensitive)
+        {
+            assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9);
+            assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7);
+            assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6);
+        }
+    }
+    });
+}
+
+@safe pure @nogc nothrow
+unittest
+{
+    import std.utf : byWchar;
+
+    foreach (cs; EnumMembers!CaseSensitive)
+    {
+        assert(indexOf("".byWchar, "", cs) == -1);
+        assert(indexOf("hello".byWchar, "", cs) == 0);
+        assert(indexOf("hello".byWchar, "l", cs) == 2);
+        assert(indexOf("heLLo".byWchar, "LL", cs) == 2);
+        assert(indexOf("hello".byWchar, "lox", cs) == -1);
+        assert(indexOf("hello".byWchar, "betty", cs) == -1);
+        assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7);
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    debug(string) trustedPrintf("string.indexOf(startIdx).unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOf(cast(S)null, to!T("a"), 1337) == -1);
+            assert(indexOf(to!S("def"), to!T("a"), 0) == -1);
+            assert(indexOf(to!S("abba"), to!T("a"), 2) == 3);
+            assert(indexOf(to!S("def"), to!T("f"), 1) == 2);
+            assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3);
+            assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6);
+
+            assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, CaseSensitive.no) == -1);
+            assert(indexOf(to!S("def"), to!T("a"), 2, CaseSensitive.no) == -1);
+            assert(indexOf(to!S("abba"), to!T("a"), 3, CaseSensitive.no) == 3);
+            assert(indexOf(to!S("def"), to!T("f"), 1, CaseSensitive.no) == 2);
+            assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, CaseSensitive.no) == 3);
+            assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, CaseSensitive.no) == 6);
+            assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, CaseSensitive.no) == 9,
+                to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, CaseSensitive.no))
+                ~ " " ~ S.stringof ~ " " ~ T.stringof);
+
+            S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+            S sMars = "Who\'s \'My Favorite Maritian?\'";
+
+            assert(indexOf(sMars, to!T("MY fAVe"), 10,
+                CaseSensitive.no) == -1);
+            assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, CaseSensitive.no) == 7);
+            assert(indexOf(sPlts, to!T("mArS:"), 0, CaseSensitive.no) == 0);
+            assert(indexOf(sPlts, to!T("rOcK"), 12, CaseSensitive.no) == 17);
+            assert(indexOf(sPlts, to!T("Un."), 32, CaseSensitive.no) == 41);
+            assert(indexOf(sPlts, to!T(sPlts), 0, CaseSensitive.no) == 0);
+
+            assert(indexOf("\u0100", to!T("\u0100"), 0, CaseSensitive.no) == 0);
+
+            // Thanks to Carlos Santander B. and zwang
+            assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
+                           to!T("page-break-before"), 10, CaseSensitive.no) == -1);
+
+            // In order for indexOf with and without index to be consistent
+            assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0));
+        }();
+
+        foreach (cs; EnumMembers!CaseSensitive)
+        {
+            assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"),
+                3, cs) == 9);
+            assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"),
+                3, cs) == 7);
+            assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"),
+                3, cs) == 6);
+        }
+    }
+}
+
+/++
+    Params:
+        s = string to search
+        c = character to search for
+        startIdx = the index into s to start searching from
+        cs = CaseSensitive.yes or CaseSensitive.no
+
+    Returns:
+        The index of the last occurrence of $(D c) in $(D s). If $(D c) is not
+        found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in
+        the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
+        codeunit index in $(D s).
+
+    Throws:
+        If the sequence ending at $(D startIdx) does not represent a well
+        formed codepoint, then a $(XREF utf,UTFException) may be thrown.
+
+    $(D cs) indicates whether the comparisons are case sensitive.
+  +/
+ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char)
+{
+    static import std.ascii, std.uni;
+    import std.utf : canSearchInCodeUnits;
+    if (cs == CaseSensitive.yes)
+    {
+        if (canSearchInCodeUnits!Char(c))
+        {
+            foreach_reverse (i, it; s)
+            {
+                if (it == c)
+                {
+                    return i;
+                }
+            }
+        }
+        else
+        {
+            foreach_reverse (i, dchar it; s)
+            {
+                if (it == c)
+                {
+                    return i;
+                }
+            }
+        }
+    }
+    else
+    {
+        if (std.ascii.isASCII(c))
+        {
+            immutable c1 = std.ascii.toLower(c);
+
+            foreach_reverse (i, it; s)
+            {
+                immutable c2 = std.ascii.toLower(it);
+                if (c1 == c2)
+                {
+                    return i;
+                }
+            }
+        }
+        else
+        {
+            immutable c1 = std.uni.toLower(c);
+
+            foreach_reverse (i, dchar it; s)
+            {
+                immutable c2 = std.uni.toLower(it);
+                if (c1 == c2)
+                {
+                    return i;
+                }
+            }
+        }
+    }
+
+    return -1;
+}
+
+/// Ditto
+ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char)
+{
+    if (startIdx <= s.length)
+    {
+        return lastIndexOf(s[0u .. startIdx], c, cs);
+    }
+
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(lastIndexOf(s, 'l') == 9);
+    assert(lastIndexOf(s, 'Z') == -1);
+    assert(lastIndexOf(s, 'L', CaseSensitive.no) == 9);
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(lastIndexOf(s, 'l', 4) == 3);
+    assert(lastIndexOf(s, 'Z', 1337) == -1);
+    assert(lastIndexOf(s, 'L', 7, CaseSensitive.no) == 3);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    debug(string) trustedPrintf("string.lastIndexOf.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        assert(lastIndexOf(cast(S) null, 'a') == -1);
+        assert(lastIndexOf(to!S("def"), 'a') == -1);
+        assert(lastIndexOf(to!S("abba"), 'a') == 3);
+        assert(lastIndexOf(to!S("def"), 'f') == 2);
+        assert(lastIndexOf(to!S("ödef"), 'ö') == 0);
+
+        assert(lastIndexOf(cast(S) null, 'a', CaseSensitive.no) == -1);
+        assert(lastIndexOf(to!S("def"), 'a', CaseSensitive.no) == -1);
+        assert(lastIndexOf(to!S("AbbA"), 'a', CaseSensitive.no) == 3);
+        assert(lastIndexOf(to!S("def"), 'F', CaseSensitive.no) == 2);
+        assert(lastIndexOf(to!S("ödef"), 'ö', CaseSensitive.no) == 0);
+        assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"),
+            CaseSensitive.no) == 1);
+
+        S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+
+        assert(lastIndexOf(to!S("def"), 'f', CaseSensitive.no) == 2);
+        assert(lastIndexOf(sPlts, 'M', CaseSensitive.no) == 34);
+        assert(lastIndexOf(sPlts, 'S', CaseSensitive.no) == 40);
+    }
+
+    foreach (cs; EnumMembers!CaseSensitive)
+    {
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
+    }
+    });
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOf.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        assert(lastIndexOf(cast(S) null, 'a') == -1);
+        assert(lastIndexOf(to!S("def"), 'a') == -1);
+        assert(lastIndexOf(to!S("abba"), 'a', 3) == 0);
+        assert(lastIndexOf(to!S("deff"), 'f', 3) == 2);
+
+        assert(lastIndexOf(cast(S) null, 'a', CaseSensitive.no) == -1);
+        assert(lastIndexOf(to!S("def"), 'a', CaseSensitive.no) == -1);
+        assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), CaseSensitive.no) == 3,
+                to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, CaseSensitive.no)));
+        assert(lastIndexOf(to!S("def"), 'F', 3, CaseSensitive.no) == 2);
+
+        S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+
+        assert(lastIndexOf(to!S("def"), 'f', 4, CaseSensitive.no) == -1);
+        assert(lastIndexOf(sPlts, 'M', sPlts.length -2, CaseSensitive.no) == 34);
+        assert(lastIndexOf(sPlts, 'S', sPlts.length -2, CaseSensitive.no) == 40);
+    }
+
+    foreach (cs; EnumMembers!CaseSensitive)
+    {
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
+        assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
+    }
+}
+
+/++
+    Params:
+        s = string to search
+        sub = substring to search for
+        startIdx = the index into s to start searching from
+        cs = CaseSensitive.yes or CaseSensitive.no
+
+    Returns:
+        the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is
+        not found, then $(D -1) is returned. The $(D startIdx) slices $(D s)
+        in the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
+        codeunit index in $(D s).
+
+    Throws:
+        If the sequence ending at $(D startIdx) does not represent a well
+        formed codepoint, then a $(XREF utf,UTFException) may be thrown.
+
+    $(D cs) indicates whether the comparisons are case sensitive.
+  +/
+ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char1 && isSomeChar!Char2)
+{
+    import std.utf : strideBack;
+    import std.conv : to;
+    import std.algorithm : endsWith;
+    if (sub.empty)
+        return -1;
+
+    if (walkLength(sub) == 1)
+        return lastIndexOf(s, sub.front, cs);
+
+    if (cs == CaseSensitive.yes)
+    {
+        static if (is(Unqual!Char1 == Unqual!Char2))
+        {
+            import core.stdc.string : memcmp;
+
+            immutable c = sub[0];
+
+            for (ptrdiff_t i = s.length - sub.length; i >= 0; --i)
+            {
+                if (s[i] == c)
+                {
+                    if (__ctfe)
+                    {
+                        foreach (j; 1 .. sub.length)
+                        {
+                            if (s[i + j] != sub[j])
+                                continue;
+                        }
+                        return i;
+                    }
+                    else
+                    {
+                        auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted
+                        {
+                            return memcmp(s1, s2, n);
+                        }
+                        if (trustedMemcmp(&s[i + 1], &sub[1],
+                                (sub.length - 1) * Char1.sizeof) == 0)
+                            return i;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (size_t i = s.length; !s.empty;)
+            {
+                if (s.endsWith(sub))
+                    return cast(ptrdiff_t)i - to!(const(Char1)[])(sub).length;
+
+                i -= strideBack(s, i);
+                s = s[0 .. i];
+            }
+        }
+    }
+    else
+    {
+        for (size_t i = s.length; !s.empty;)
+        {
+            if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))
+                         (s, sub))
+            {
+                return cast(ptrdiff_t)i - to!(const(Char1)[])(sub).length;
+            }
+
+            i -= strideBack(s, i);
+            s = s[0 .. i];
+        }
+    }
+
+    return -1;
+}
+
+/// Ditto
+ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
+        in size_t startIdx, in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char1 && isSomeChar!Char2)
+{
+    if (startIdx <= s.length)
+    {
+        return lastIndexOf(s[0u .. startIdx], sub, cs);
+    }
+
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(lastIndexOf(s, "ll") == 2);
+    assert(lastIndexOf(s, "Zo") == -1);
+    assert(lastIndexOf(s, "lL", CaseSensitive.no) == 2);
+}
+
+///
+@safe pure unittest
+{
+    string s = "Hello World";
+    assert(lastIndexOf(s, "ll", 4) == 2);
+    assert(lastIndexOf(s, "Zo", 128) == -1);
+    assert(lastIndexOf(s, "lL", 3, CaseSensitive.no) == -1);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        auto r = to!S("").lastIndexOf("hello");
+        assert(r == -1, to!string(r));
+
+        r = to!S("hello").lastIndexOf("");
+        assert(r == -1, to!string(r));
+
+        r = to!S("").lastIndexOf("");
+        assert(r == -1, to!string(r));
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOf.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            enum typeStr = S.stringof ~ " " ~ T.stringof;
+
+            assert(lastIndexOf(cast(S)null, to!T("a")) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr);
+            assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr);
+
+            assert(lastIndexOf(cast(S)null, to!T("a"), CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), CaseSensitive.no) == 0, typeStr);
+
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), CaseSensitive.no) == 7, typeStr);
+
+            assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), CaseSensitive.yes) == 0);
+
+            S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+            S sMars = "Who\'s \'My Favorite Maritian?\'";
+
+            assert(lastIndexOf(sMars, to!T("RiTE maR"), CaseSensitive.no) == 14, typeStr);
+            assert(lastIndexOf(sPlts, to!T("FOuRTh"), CaseSensitive.no) == 10, typeStr);
+            assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), CaseSensitive.no) == 0, typeStr);
+            assert(lastIndexOf(sMars, to!T(sMars), CaseSensitive.no) == 0, typeStr);
+        }();
+
+        foreach (cs; EnumMembers!CaseSensitive)
+        {
+            enum csString = to!string(cs);
+
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString);
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString);
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString);
+        }
+    }
+    });
+}
+
+@safe pure unittest // issue13529
+{
+    import std.conv : to;
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        {
+            enum typeStr = S.stringof ~ " " ~ T.stringof;
+            auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö"));
+            assert(idx != -1, to!string(idx) ~ " " ~ typeStr);
+
+            idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd"));
+            assert(idx == -1, to!string(idx) ~ " " ~ typeStr);
+        }
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOf.unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            enum typeStr = S.stringof ~ " " ~ T.stringof;
+
+            assert(lastIndexOf(cast(S)null, to!T("a")) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~
+                format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6)));
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr);
+            assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~
+                    to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472
+
+            assert(lastIndexOf(cast(S)null, to!T("a"), 1, CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, CaseSensitive.no) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, CaseSensitive.no) == 2, typeStr ~
+                " " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, CaseSensitive.no)));
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, CaseSensitive.no) == -1, typeStr);
+
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, CaseSensitive.no) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, CaseSensitive.no) == 2, typeStr);
+            assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, CaseSensitive.no) == 3, typeStr);
+            assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr);
+        }();
+
+        foreach (cs; EnumMembers!CaseSensitive)
+        {
+            enum csString = to!string(cs);
+
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString);
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString);
+            assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString);
+        }
+    }
+}
+
+private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)(
+        const(Char)[] haystack, const(Char2)[] needles,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    import std.algorithm : canFind;
+    if (cs == CaseSensitive.yes)
+    {
+        static if (forward)
+        {
+            static if (any)
+            {
+                import std.algorithm : findAmong;
+                size_t n = haystack.findAmong(needles).length;
+                return n ? haystack.length - n : -1;
+            }
+            else
+            {
+                foreach (idx, dchar hay; haystack)
+                {
+                    if (!canFind(needles, hay))
+                    {
+                        return idx;
+                    }
+                }
+            }
+        }
+        else
+        {
+            static if (any)
+            {
+                import std.utf : strideBack;
+                import std.algorithm : findAmong;
+                import std.range : retro;
+                size_t n = haystack.retro.findAmong(needles).source.length;
+                if (n)
+                {
+                    return n - haystack.strideBack(n);
+                }
+            }
+            else
+            {
+                foreach_reverse (idx, dchar hay; haystack)
+                {
+                    if(!canFind(needles, hay))
+                    {
+                        return idx;
+                    }
+                }
+            }
+        }
+    }
+    else
+    {
+        import std.uni: toLower;
+        if (needles.length <= 16 && needles.walkLength(17))
+        {
+            size_t si = 0;
+            dchar[16] scratch = void;
+            foreach ( dchar c; needles)
+            {
+                scratch[si++] = toLower(c);
+            }
+
+            static if (forward)
+            {
+                foreach (i, dchar c; haystack)
+                {
+                    if (canFind(scratch[0 .. si], toLower(c)) == any)
+                    {
+                        return i;
+                    }
+                }
+            }
+            else
+            {
+                foreach_reverse (i, dchar c; haystack)
+                {
+                    if (canFind(scratch[0 .. si], toLower(c)) == any)
+                    {
+                        return i;
+                    }
+                }
+            }
+        }
+        else
+        {
+            static bool f(dchar a, dchar b)
+            {
+                return toLower(a) == b;
+            }
+
+            static if (forward)
+            {
+                foreach (i, dchar c; haystack)
+                {
+                    if (canFind!f(needles, toLower(c)) == any)
+                    {
+                        return i;
+                    }
+                }
+            }
+            else
+            {
+                foreach_reverse (i, dchar c; haystack)
+                {
+                    if (canFind!f(needles, toLower(c)) == any)
+                    {
+                        return i;
+                    }
+                }
+            }
+        }
+    }
+
+    return -1;
+}
+
+/**
+    Returns the index of the first occurence of any of the elements in $(D
+    needles) in $(D haystack). If no element of $(D needles) is found,
+    then $(D -1) is returned. The $(D startIdx) slices $(D haystack) in the
+    following way $(D haystack[startIdx .. $]). $(D startIdx) represents a
+    codeunit index in $(D haystack). If the sequence ending at $(D startIdx)
+    does not represent a well formed codepoint, then a $(XREF utf,UTFException)
+    may be thrown.
+
+    Params:
+        haystack = String to search for needles in.
+        needles = Strings to search for in haystack.
+        startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
+            the startIdx is greater equal the length of haystack the functions
+            returns $(D -1).
+        cs = Indicates whether the comparisons are case sensitive.
+*/
+ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs);
+}
+
+/// Ditto
+ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
+        in size_t startIdx, in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    if (startIdx < haystack.length)
+    {
+        ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs);
+        if (foundIdx != -1)
+        {
+            return foundIdx + cast(ptrdiff_t)startIdx;
+        }
+    }
+
+    return -1;
+}
+
+///
+@safe pure unittest {
+    import std.conv : to;
+
+    ptrdiff_t i = "helloWorld".indexOfAny("Wr");
+    assert(i == 5);
+    i = "öällo world".indexOfAny("lo ");
+    assert(i == 4, to!string(i));
+}
+
+///
+@safe pure unittest
+{
+    import std.conv : to;
+
+    ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4);
+    assert(i == 5);
+
+    i = "Foo öällo world".indexOfAny("lh", 3);
+    assert(i == 8, to!string(i));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        auto r = to!S("").indexOfAny("hello");
+        assert(r == -1, to!string(r));
+
+        r = to!S("hello").indexOfAny("");
+        assert(r == -1, to!string(r));
+
+        r = to!S("").indexOfAny("");
+        assert(r == -1, to!string(r));
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.indexOfAny.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOfAny(cast(S)null, to!T("a")) == -1);
+            assert(indexOfAny(to!S("def"), to!T("rsa")) == -1);
+            assert(indexOfAny(to!S("abba"), to!T("a")) == 0);
+            assert(indexOfAny(to!S("def"), to!T("f")) == 2);
+            assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1);
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1);
+
+            assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
+                CaseSensitive.no) == -1);
+            assert(indexOfAny(to!S("def"), to!T("MI6"),
+                CaseSensitive.no) == -1);
+            assert(indexOfAny(to!S("abba"), to!T("DEA"),
+                CaseSensitive.no) == 0);
+            assert(indexOfAny(to!S("def"), to!T("FBI"), CaseSensitive.no) == 2);
+            assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), CaseSensitive.no)
+                == -1);
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"),
+                CaseSensitive.no) == 0);
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
+                CaseSensitive.no) == 0);
+
+            assert(indexOfAny("\u0100", to!T("\u0100"), CaseSensitive.no) == 0);
+        }();
+    }
+    }
+    );
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.indexOfAny(startIdx).unittest\n");
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOfAny(cast(S)null, to!T("a"), 1337) == -1);
+            assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1);
+            assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3);
+            assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2);
+            assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3);
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6);
+
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1,
+                CaseSensitive.no) == -1);
+            assert(indexOfAny(to!S("def"), to!T("DRS"), 2,
+                CaseSensitive.no) == -1);
+            assert(indexOfAny(to!S("abba"), to!T("SI"), 3,
+                CaseSensitive.no) == -1);
+            assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1,
+                CaseSensitive.no) == 2);
+            assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2,
+                CaseSensitive.no) == 3);
+            assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4,
+                CaseSensitive.no) == 4);
+            assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9,
+                CaseSensitive.no) == 9);
+
+            assert(indexOfAny("\u0100", to!T("\u0100"), 0,
+                CaseSensitive.no) == 0);
+        }();
+
+        foreach (cs; EnumMembers!CaseSensitive)
+        {
+            assert(indexOfAny("hello\U00010143\u0100\U00010143",
+                to!S("e\u0100"), 3, cs) == 9);
+            assert(indexOfAny("hello\U00010143\u0100\U00010143"w,
+                to!S("h\u0100"), 3, cs) == 7);
+            assert(indexOfAny("hello\U00010143\u0100\U00010143"d,
+                to!S("l\u0100"), 5, cs) == 6);
+        }
+    }
+}
+
+/**
+    Returns the index of the last occurence of any of the elements in $(D
+    needles) in $(D haystack). If no element of $(D needles) is found,
+    then $(D -1) is returned. The $(D stopIdx) slices $(D haystack) in the
+    following way $(D s[0 .. stopIdx]). $(D stopIdx) represents a codeunit
+    index in $(D haystack). If the sequence ending at $(D startIdx) does not
+    represent a well formed codepoint, then a $(XREF utf,UTFException) may be
+    thrown.
+
+    Params:
+        haystack = String to search for needles in.
+        needles = Strings to search for in haystack.
+        stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If
+            the stopIdx is greater equal the length of haystack the functions
+            returns $(D -1).
+        cs = Indicates whether the comparisons are case sensitive.
+*/
+ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in CaseSensitive cs = CaseSensitive.yes)
+        @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs);
+}
+
+/// Ditto
+ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in size_t stopIdx,
+        in CaseSensitive cs = CaseSensitive.yes) @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    if (stopIdx <= haystack.length)
+    {
+        return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs);
+    }
+
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo");
+    assert(i == 8);
+
+    i = "Foo öäöllo world".lastIndexOfAny("öF");
+    assert(i == 8);
+}
+
+///
+@safe pure unittest
+{
+    import std.conv : to;
+
+    ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4);
+    assert(i == 3);
+
+    i = "Foo öäöllo world".lastIndexOfAny("öF", 3);
+    assert(i == 0);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        auto r = to!S("").lastIndexOfAny("hello");
+        assert(r == -1, to!string(r));
+
+        r = to!S("hello").lastIndexOfAny("");
+        assert(r == -1, to!string(r));
+
+        r = to!S("").lastIndexOfAny("");
+        assert(r == -1, to!string(r));
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOfAny.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(lastIndexOfAny(cast(S)null, to!T("a")) == -1);
+            assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1);
+            assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3);
+            assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2);
+            assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6);
+
+            ptrdiff_t oeIdx = 9;
+               if (is(S == wstring) || is(S == dstring))
+            {
+                oeIdx = 8;
+            }
+
+            auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg"));
+            assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
+
+            assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
+                CaseSensitive.no) == -1);
+            assert(lastIndexOfAny(to!S("def"), to!T("MI6"),
+                CaseSensitive.no) == -1);
+            assert(lastIndexOfAny(to!S("abba"), to!T("DEA"),
+                CaseSensitive.no) == 3);
+            assert(lastIndexOfAny(to!S("def"), to!T("FBI"),
+                CaseSensitive.no) == 2);
+            assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"),
+                CaseSensitive.no) == -1);
+
+            oeIdx = 2;
+               if (is(S == wstring) || is(S == dstring))
+            {
+                oeIdx = 1;
+            }
+            assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"),
+                CaseSensitive.no) == oeIdx);
+
+            assert(lastIndexOfAny("\u0100", to!T("\u0100"),
+                CaseSensitive.no) == 0);
+        }();
+    }
+    }
+    );
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOfAny(index).unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            enum typeStr = S.stringof ~ " " ~ T.stringof;
+
+            assert(lastIndexOfAny(cast(S)null, to!T("a"), 1337) == -1,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1,
+                typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1,
+                typeStr);
+            assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0,
+                typeStr);
+
+            assert(lastIndexOfAny(cast(S)null, to!T("a"), 1337,
+                CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7,
+                CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5,
+                CaseSensitive.no) == 3, typeStr);
+            assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6,
+                CaseSensitive.no) == 5, typeStr);
+            assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8,
+                CaseSensitive.no) == 6, typeStr);
+            assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7,
+                CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4,
+                CaseSensitive.no) == -1, typeStr);
+            assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2,
+                CaseSensitive.no) == 0, typeStr);
+        }();
+    }
+    }
+    );
+}
+
+/**
+    Returns the index of the first occurence of any character not an elements
+    in $(D needles) in $(D haystack). If all element of $(D haystack) are
+    element of $(D needles) $(D -1) is returned.
+
+    Params:
+        haystack = String to search for needles in.
+        needles = Strings to search for in haystack.
+        startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
+            the startIdx is greater equal the length of haystack the functions
+            returns $(D -1).
+        cs = Indicates whether the comparisons are case sensitive.
+*/
+ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in CaseSensitive cs = CaseSensitive.yes)
+        @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs);
+}
+
+/// Ditto
+ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in size_t startIdx,
+        in CaseSensitive cs = CaseSensitive.yes)
+        @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    if (startIdx < haystack.length)
+    {
+        ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)(
+            haystack[startIdx .. $], needles, cs);
+        if (foundIdx != -1)
+        {
+            return foundIdx + cast(ptrdiff_t)startIdx;
+        }
+    }
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    assert(indexOfNeither("abba", "a", 2) == 2);
+    assert(indexOfNeither("def", "de", 1) == 2);
+    assert(indexOfNeither("dfefffg", "dfe", 4) == 6);
+}
+
+///
+@safe pure unittest
+{
+    assert(indexOfNeither("def", "a") == 0);
+    assert(indexOfNeither("def", "de") == 2);
+    assert(indexOfNeither("dfefffg", "dfe") == 6);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        auto r = to!S("").indexOfNeither("hello");
+        assert(r == -1, to!string(r));
+
+        r = to!S("hello").indexOfNeither("");
+        assert(r == 0, to!string(r));
+
+        r = to!S("").indexOfNeither("");
+        assert(r == -1, to!string(r));
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.indexOf.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOfNeither(cast(S)null, to!T("a")) == -1);
+            assert(indexOfNeither("abba", "a") == 1);
+
+            assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"),
+                CaseSensitive.no) == 0);
+            assert(indexOfNeither(to!S("def"), to!T("D"),
+                CaseSensitive.no) == 1);
+            assert(indexOfNeither(to!S("ABca"), to!T("a"),
+                CaseSensitive.no) == 1);
+            assert(indexOfNeither(to!S("def"), to!T("f"),
+                CaseSensitive.no) == 0);
+            assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"),
+                CaseSensitive.no) == 6);
+            if (is(S == string))
+            {
+                assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
+                    CaseSensitive.no) == 8,
+                    to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
+                    CaseSensitive.no)));
+            }
+            else
+            {
+                assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
+                    CaseSensitive.no) == 7,
+                    to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
+                    CaseSensitive.no)));
+            }
+        }();
+    }
+    }
+    );
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.indexOfNeither(index).unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(indexOfNeither(cast(S)null, to!T("a"), 1) == -1);
+            assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1,
+                to!string(indexOfNeither(to!S("def"), to!T("a"), 1)));
+
+            assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4,
+                CaseSensitive.no) == 4);
+            assert(indexOfNeither(to!S("def"), to!T("D"), 2,
+                CaseSensitive.no) == 2);
+            assert(indexOfNeither(to!S("ABca"), to!T("a"), 3,
+                CaseSensitive.no) == -1);
+            assert(indexOfNeither(to!S("def"), to!T("tzf"), 2,
+                CaseSensitive.no) == -1);
+            assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5,
+                CaseSensitive.no) == 6);
+            if (is(S == string))
+            {
+                assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
+                    CaseSensitive.no) == 3, to!string(indexOfNeither(
+                    to!S("öDfEfffg"), to!T("äDi"), 2, CaseSensitive.no)));
+            }
+            else
+            {
+                assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
+                    CaseSensitive.no) == 2, to!string(indexOfNeither(
+                    to!S("öDfEfffg"), to!T("äDi"), 2, CaseSensitive.no)));
+            }
+        }();
+    }
+    }
+    );
+}
+
+/**
+    Returns the last index of the first occurence of any character that is not
+    an elements in $(D needles) in $(D haystack). If all element of
+    $(D haystack) are element of $(D needles) $(D -1) is returned.
+
+    Params:
+        haystack = String to search for needles in.
+        needles = Strings to search for in haystack.
+        stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If
+        the stopIdx is greater equal the length of haystack the functions
+        returns $(D -1).
+        cs = Indicates whether the comparisons are case sensitive.
+*/
+ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in CaseSensitive cs = CaseSensitive.yes)
+        @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs);
+}
+
+/// Ditto
+ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
+        const(Char2)[] needles, in size_t stopIdx,
+        in CaseSensitive cs = CaseSensitive.yes)
+        @safe pure
+    if (isSomeChar!Char && isSomeChar!Char2)
+{
+    if (stopIdx < haystack.length)
+    {
+        return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx],
+            needles, cs);
+    }
+    return -1;
+}
+
+///
+@safe pure unittest
+{
+    assert(lastIndexOfNeither("abba", "a") == 2);
+    assert(lastIndexOfNeither("def", "f") == 1);
+}
+
+///
+@safe pure unittest
+{
+    assert(lastIndexOfNeither("def", "rsa", 3) == -1);
+    assert(lastIndexOfNeither("abba", "a", 2) == 1);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        auto r = to!S("").lastIndexOfNeither("hello");
+        assert(r == -1, to!string(r));
+
+        r = to!S("hello").lastIndexOfNeither("");
+        assert(r == 4, to!string(r));
+
+        r = to!S("").lastIndexOfNeither("");
+        assert(r == -1, to!string(r));
+    }
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOfNeither.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(lastIndexOfNeither(cast(S)null, to!T("a")) == -1);
+            assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2);
+            assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
+
+            ptrdiff_t oeIdx = 8;
+               if (is(S == string))
+            {
+                oeIdx = 9;
+            }
+
+            auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"));
+            assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
+
+            assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"),
+                CaseSensitive.no) == 5);
+            assert(lastIndexOfNeither(to!S("def"), to!T("MI6"),
+                CaseSensitive.no) == 2, to!string(lastIndexOfNeither(to!S("def"),
+                to!T("MI6"), CaseSensitive.no)));
+            assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"),
+                CaseSensitive.no) == 6, to!string(lastIndexOfNeither(
+                to!S("abbadeafsb"), to!T("fSb"), CaseSensitive.no)));
+            assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"),
+                CaseSensitive.no) == 1);
+            assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"),
+                CaseSensitive.no) == 6);
+            assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
+                CaseSensitive.no) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"),
+                to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), CaseSensitive.no)));
+        }();
+    }
+    }
+    );
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.lastIndexOfNeither(index).unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(lastIndexOfNeither(cast(S)null, to!T("a"), 1337) == -1);
+            assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1);
+            assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
+
+            ptrdiff_t oeIdx = 4;
+               if (is(S == string))
+            {
+                oeIdx = 5;
+            }
+
+            auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"),
+                7);
+            assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
+
+            assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6,
+                CaseSensitive.no) == 5);
+            assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2,
+                CaseSensitive.no) == 1, to!string(lastIndexOfNeither(to!S("def"),
+                to!T("MI6"), 2, CaseSensitive.no)));
+            assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6,
+                CaseSensitive.no) == 5, to!string(lastIndexOfNeither(
+                to!S("abbadeafsb"), to!T("fSb"), 6, CaseSensitive.no)));
+            assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3,
+                CaseSensitive.no) == 1);
+            assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2,
+                CaseSensitive.no) == 1, to!string(lastIndexOfNeither(
+                    to!S("dfefffg"), to!T("NSA"), 2, CaseSensitive.no)));
+        }();
+    }
+    }
+    );
+}
+
+/**
+ * Returns the _representation of a string, which has the same type
+ * as the string except the character type is replaced by $(D ubyte),
+ * $(D ushort), or $(D uint) depending on the character width.
+ *
+ * Params:
+ *     s = The string to return the _representation of.
+ *
+ * Returns:
+ *     The _representation of the passed string.
+ */
+auto representation(Char)(Char[] s) @safe pure nothrow @nogc
+    if (isSomeChar!Char)
+{
+    alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2];
+    return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s;
+}
+
+///
+@safe pure unittest
+{
+    string s = "hello";
+    static assert(is(typeof(representation(s)) == immutable(ubyte)[]));
+    assert(representation(s) is cast(immutable(ubyte)[]) s);
+    assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]);
+}
+
+@trusted pure unittest
+{
+    import std.exception;
+    import std.typecons;
+
+    assertCTFEable!(
+    {
+    void test(Char, T)(Char[] str)
+    {
+        static assert(is(typeof(representation(str)) == T[]));
+        assert(representation(str) is cast(T[]) str);
+    }
+
+    foreach (Type; AliasSeq!(Tuple!(char , ubyte ),
+                             Tuple!(wchar, ushort),
+                             Tuple!(dchar, uint  )))
+    {
+        alias Char = Fields!Type[0];
+        alias Int  = Fields!Type[1];
+        enum immutable(Char)[] hello = "hello";
+
+        test!(   immutable Char,    immutable Int)(hello);
+        test!(       const Char,        const Int)(hello);
+        test!(             Char,              Int)(hello.dup);
+        test!(      shared Char,       shared Int)(cast(shared) hello.dup);
+        test!(const shared Char, const shared Int)(hello);
+    }
+    });
+}
+
+
+/**
+ * Capitalize the first character of $(D s) and convert the rest of $(D s) to
+ * lowercase.
+ *
+ * Params:
+ *     input = The string to _capitalize.
+ *
+ * Returns:
+ *     The capitalized string.
+ *
+ * See_Also:
+ *      $(XREF uni, asCapitalized) for a lazy range version that doesn't allocate memory
+ */
+S capitalize(S)(S input) @trusted pure
+    if (isSomeString!S)
+{
+    import std.uni : asCapitalized;
+    import std.conv: to;
+    import std.array;
+
+    return input.asCapitalized.array.to!S;
+}
+
+///
+pure @safe unittest
+{
+    assert(capitalize("hello") == "Hello");
+    assert(capitalize("World") == "World");
+}
+
+auto capitalize(S)(auto ref S s)
+    if (!isSomeString!S && is(StringTypeOf!S))
+{
+    return capitalize!(StringTypeOf!S)(s);
+}
+
+@safe pure unittest
+{
+    assert(testAliasedString!capitalize("hello"));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    import std.algorithm : cmp;
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
+    {
+        S s1 = to!S("FoL");
+        S s2;
+
+        s2 = capitalize(s1);
+        assert(cmp(s2, "Fol") == 0);
+        assert(s2 !is s1);
+
+        s2 = capitalize(s1[0 .. 2]);
+        assert(cmp(s2, "Fo") == 0);
+
+        s1 = to!S("fOl");
+        s2 = capitalize(s1);
+        assert(cmp(s2, "Fol") == 0);
+        assert(s2 !is s1);
+        s1 = to!S("\u0131 \u0130");
+        s2 = capitalize(s1);
+        assert(cmp(s2, "\u0049 i\u0307") == 0);
+        assert(s2 !is s1);
+
+        s1 = to!S("\u017F \u0049");
+        s2 = capitalize(s1);
+        assert(cmp(s2, "\u0053 \u0069") == 0);
+        assert(s2 !is s1);
+    }
+    });
+}
+
+/++
+    Split $(D s) into an array of lines according to the unicode standard using
+    $(D '\r'), $(D '\n'), $(D "\r\n"), $(XREF uni, lineSep),
+    $(XREF uni, paraSep), $(D U+0085) (NEL), $(D '\v')  and $(D '\f')
+    as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the
+    delimiter is included in the strings returned.
+
+    Does not throw on invalid UTF; such is simply passed unchanged
+    to the output.
+
+    Allocates memory; use $(LREF lineSplitter) for an alternative that
+    does not.
+
+    Adheres to $(WEB http://www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
+
+  Params:
+    s = a string of $(D chars), $(D wchars), or $(D dchars), or any custom
+        type that casts to a $(D string) type
+    keepTerm = whether delimiter is included or not in the results
+  Returns:
+    array of strings, each element is a line that is a slice of $(D s)
+  See_Also:
+    $(LREF lineSplitter)
+    $(XREF algorithm, splitter)
+    $(XREF regex, splitter)
+ +/
+alias KeepTerminator = Flag!"keepTerminator";
+
+/// ditto
+S[] splitLines(S)(S s, in KeepTerminator keepTerm = KeepTerminator.no) @safe pure
+    if (isSomeString!S)
+{
+    import std.uni : lineSep, paraSep;
+    import std.array : appender;
+
+    size_t iStart = 0;
+    auto retval = appender!(S[])();
+
+    for (size_t i; i < s.length; ++i)
+    {
+        switch (s[i])
+        {
+            case '\v', '\f', '\n':
+                retval.put(s[iStart .. i + (keepTerm == KeepTerminator.yes)]);
+                iStart = i + 1;
+                break;
+
+            case '\r':
+                if (i + 1 < s.length && s[i + 1] == '\n')
+                {
+                    retval.put(s[iStart .. i + (keepTerm == KeepTerminator.yes) * 2]);
+                    iStart = i + 2;
+                    ++i;
+                }
+                else
+                {
+                    goto case '\n';
+                }
+                break;
+
+            static if (s[i].sizeof == 1)
+            {
+                /* Manually decode:
+                 *  lineSep is E2 80 A8
+                 *  paraSep is E2 80 A9
+                 */
+                case 0xE2:
+                    if (i + 2 < s.length &&
+                        s[i + 1] == 0x80 &&
+                        (s[i + 2] == 0xA8 || s[i + 2] == 0xA9)
+                       )
+                    {
+                        retval.put(s[iStart .. i + (keepTerm == KeepTerminator.yes) * 3]);
+                        iStart = i + 3;
+                        i += 2;
+                    }
+                    else
+                        goto default;
+                    break;
+                /* Manually decode:
+                 *  NEL is C2 85
+                 */
+                case 0xC2:
+                    if(i + 1 < s.length && s[i + 1] == 0x85)
+                    {
+                        retval.put(s[iStart .. i + (keepTerm == KeepTerminator.yes) * 2]);
+                        iStart = i + 2;
+                        i += 1;
+                    }
+                    else
+                        goto default;
+                    break;
+            }
+            else
+            {
+                case lineSep:
+                case paraSep:
+                case '\u0085':
+                    goto case '\n';
+            }
+
+            default:
+                break;
+        }
+    }
+
+    if (iStart != s.length)
+        retval.put(s[iStart .. $]);
+
+    return retval.data;
+}
+
+///
+unittest
+{
+    string s = "Hello\nmy\rname\nis";
+    assert(splitLines(s) == ["Hello", "my", "name", "is"]);
+}
+
+auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = KeepTerminator.no)
+    if (!isSomeString!S && is(StringTypeOf!S))
+{
+    return splitLines!(StringTypeOf!S)(s, keepTerm);
+}
+
+unittest
+{
+    assert(testAliasedString!splitLines("hello\nworld"));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.splitLines.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        auto s = to!S(
+            "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~
+            "mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
+        );
+        auto lines = splitLines(s);
+        assert(lines.length == 14);
+        assert(lines[0] == "");
+        assert(lines[1] == "peter");
+        assert(lines[2] == "");
+        assert(lines[3] == "paul");
+        assert(lines[4] == "jerry");
+        assert(lines[5] == "ice");
+        assert(lines[6] == "cream");
+        assert(lines[7] == "");
+        assert(lines[8] == "sunday");
+        assert(lines[9] == "mon\u2030day");
+        assert(lines[10] == "schadenfreude");
+        assert(lines[11] == "kindergarten");
+        assert(lines[12] == "");
+        assert(lines[13] == "cookies");
+
+
+        ubyte[] u = ['a', 0xFF, 0x12, 'b'];     // invalid UTF
+        auto ulines = splitLines(cast(char[])u);
+        assert(cast(ubyte[])(ulines[0]) == u);
+
+        lines = splitLines(s, KeepTerminator.yes);
+        assert(lines.length == 14);
+        assert(lines[0] == "\r");
+        assert(lines[1] == "peter\n");
+        assert(lines[2] == "\r");
+        assert(lines[3] == "paul\r\n");
+        assert(lines[4] == "jerry\u2028");
+        assert(lines[5] == "ice\u2029");
+        assert(lines[6] == "cream\n");
+        assert(lines[7] == "\n");
+        assert(lines[8] == "sunday\n");
+        assert(lines[9] == "mon\u2030day\n");
+        assert(lines[10] == "schadenfreude\v");
+        assert(lines[11] == "kindergarten\f");
+        assert(lines[12] == "\v");
+        assert(lines[13] == "cookies\u0085");
+
+        s.popBack(); // Lop-off trailing \n
+        lines = splitLines(s);
+        assert(lines.length == 14);
+        assert(lines[9] == "mon\u2030day");
+
+        lines = splitLines(s, KeepTerminator.yes);
+        assert(lines.length == 14);
+        assert(lines[13] == "cookies");
+    }
+    });
+}
+
+private struct LineSplitter(KeepTerminator keepTerm = KeepTerminator.no, Range)
+{
+    import std.uni : lineSep, paraSep;
+    import std.conv : unsigned;
+private:
+    Range _input;
+
+    alias IndexType = typeof(unsigned(_input.length));
+    enum IndexType _unComputed = IndexType.max;
+    IndexType iStart = _unComputed;
+    IndexType iEnd = 0;
+    IndexType iNext = 0;
+
+public:
+    this(Range input)
+    {
+        _input = input;
+    }
+
+    static if (isInfinite!Range)
+    {
+        enum bool empty = false;
+    }
+    else
+    {
+        @property bool empty()
+        {
+            return iStart == _unComputed && iNext == _input.length;
+        }
+    }
+
+    @property typeof(_input) front()
+    {
+        if (iStart == _unComputed)
+        {
+            iStart = iNext;
+        Loop:
+            for (IndexType i = iNext; ; ++i)
+            {
+                if (i == _input.length)
+                {
+                    iEnd = i;
+                    iNext = i;
+                    break Loop;
+                }
+                switch (_input[i])
+                {
+                case '\v', '\f', '\n':
+                    iEnd = i + (keepTerm == KeepTerminator.yes);
+                    iNext = i + 1;
+                    break Loop;
+
+                case '\r':
+                    if (i + 1 < _input.length && _input[i + 1] == '\n')
+                    {
+                        iEnd = i + (keepTerm == KeepTerminator.yes) * 2;
+                        iNext = i + 2;
+                        break Loop;
+                    }
+                    else
+                    {
+                        goto case '\n';
+                    }
+
+                    static if (_input[i].sizeof == 1)
+                    {
+                        /* Manually decode:
+                         *  lineSep is E2 80 A8
+                         *  paraSep is E2 80 A9
+                         */
+                    case 0xE2:
+                        if (i + 2 < _input.length &&
+                            _input[i + 1] == 0x80 &&
+                            (_input[i + 2] == 0xA8 || _input[i + 2] == 0xA9)
+                        )
+                        {
+                            iEnd = i + (keepTerm == KeepTerminator.yes) * 3;
+                            iNext = i + 3;
+                            break Loop;
+                        }
+                        else
+                            goto default;
+                        /* Manually decode:
+                         *  NEL is C2 85
+                         */
+                    case 0xC2:
+                        if(i + 1 < _input.length && _input[i + 1] == 0x85)
+                        {
+                            iEnd = i + (keepTerm == KeepTerminator.yes) * 2;
+                            iNext = i + 2;
+                            break Loop;
+                        }
+                        else
+                            goto default;
+                    }
+                    else
+                    {
+                    case '\u0085':
+                    case lineSep:
+                    case paraSep:
+                        goto case '\n';
+                    }
+
+                default:
+                    break;
+                }
+            }
+        }
+        return _input[iStart .. iEnd];
+    }
+
+    void popFront()
+    {
+        if (iStart == _unComputed)
+        {
+            assert(!empty);
+            front;
+        }
+        iStart = _unComputed;
+    }
+
+    static if (isForwardRange!Range)
+    {
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            return ret;
+        }
+    }
+}
+
+/***********************************
+ *  Split an array or slicable range of characters into a range of lines
+    using $(D '\r'), $(D '\n'), $(D '\v'), $(D '\f'), $(D "\r\n"),
+    $(XREF uni, lineSep), $(XREF uni, paraSep) and $(D '\u0085') (NEL)
+    as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the
+    delimiter is included in the slices returned.
+
+    Does not throw on invalid UTF; such is simply passed unchanged
+    to the output.
+
+    Adheres to $(WEB http://www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
+
+    Does not allocate memory.
+
+  Params:
+    r = array of $(D chars), $(D wchars), or $(D dchars) or a slicable range
+    keepTerm = whether delimiter is included or not in the results
+  Returns:
+    range of slices of the input range $(D r)
+
+  See_Also:
+    $(LREF splitLines)
+    $(XREF algorithm, splitter)
+    $(XREF regex, splitter)
+ */
+auto lineSplitter(KeepTerminator keepTerm = KeepTerminator.no, Range)(Range r)
+    if ((hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) ||
+         isSomeString!Range) &&
+        !isConvertibleToString!Range)
+{
+    return LineSplitter!(keepTerm, Range)(r);
+}
+
+///
+@safe pure unittest
+{
+    import std.array : array;
+
+    string s = "Hello\nmy\rname\nis";
+
+    /* notice the call to $(D array) to turn the lazy range created by
+    lineSplitter comparable to the $(D string[]) created by splitLines.
+    */
+    assert(lineSplitter(s).array == splitLines(s));
+}
+
+auto lineSplitter(KeepTerminator keepTerm = KeepTerminator.no, Range)(auto ref Range r)
+    if (isConvertibleToString!Range)
+{
+    return LineSplitter!(keepTerm, StringTypeOf!Range)(r);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    import std.array : array;
+
+    debug(string) trustedPrintf("string.lineSplitter.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        auto s = to!S(
+            "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\n" ~
+            "sunday\nmon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
+        );
+
+        auto lines = lineSplitter(s).array;
+        assert(lines.length == 14);
+        assert(lines[0] == "");
+        assert(lines[1] == "peter");
+        assert(lines[2] == "");
+        assert(lines[3] == "paul");
+        assert(lines[4] == "jerry");
+        assert(lines[5] == "ice");
+        assert(lines[6] == "cream");
+        assert(lines[7] == "");
+        assert(lines[8] == "sunday");
+        assert(lines[9] == "mon\u2030day");
+        assert(lines[10] == "schadenfreude");
+        assert(lines[11] == "kindergarten");
+        assert(lines[12] == "");
+        assert(lines[13] == "cookies");
+
+
+        ubyte[] u = ['a', 0xFF, 0x12, 'b'];     // invalid UTF
+        auto ulines = lineSplitter(cast(char[])u).array;
+        assert(cast(ubyte[])(ulines[0]) == u);
+
+        lines = lineSplitter!(KeepTerminator.yes)(s).array;
+        assert(lines.length == 14);
+        assert(lines[0] == "\r");
+        assert(lines[1] == "peter\n");
+        assert(lines[2] == "\r");
+        assert(lines[3] == "paul\r\n");
+        assert(lines[4] == "jerry\u2028");
+        assert(lines[5] == "ice\u2029");
+        assert(lines[6] == "cream\n");
+        assert(lines[7] == "\n");
+        assert(lines[8] == "sunday\n");
+        assert(lines[9] == "mon\u2030day\n");
+        assert(lines[10] == "schadenfreude\v");
+        assert(lines[11] == "kindergarten\f");
+        assert(lines[12] == "\v");
+        assert(lines[13] == "cookies\u0085");
+
+        s.popBack(); // Lop-off trailing \n
+        lines = lineSplitter(s).array;
+        assert(lines.length == 14);
+        assert(lines[9] == "mon\u2030day");
+
+        lines = lineSplitter!(KeepTerminator.yes)(s).array;
+        assert(lines.length == 14);
+        assert(lines[13] == "cookies");
+    }
+    });
+}
+
+///
+@nogc @safe pure unittest
+{
+    auto s = "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\nmon\u2030day\n";
+    auto lines = s.lineSplitter();
+    static immutable witness = ["", "peter", "", "paul", "jerry", "ice", "cream", "", "sunday", "mon\u2030day"];
+    uint i;
+    foreach (line; lines)
+    {
+        assert(line == witness[i++]);
+    }
+    assert(i == witness.length);
+}
+
+unittest
+{
+    import std.algorithm.comparison : equal;
+    auto s = "std/string.d";
+    auto as = TestAliasedString(s);
+    assert(equal(s.lineSplitter(), as.lineSplitter()));
+}
+
+/++
+    Strips leading whitespace (as defined by $(XREF uni, isWhite)).
+
+    Params:
+        input = string or ForwardRange of characters
+
+    Returns: $(D input) stripped of leading whitespace.
+
+    Postconditions: $(D input) and the returned value
+    will share the same tail (see $(XREF array, sameTail)).
+
+    See_Also:
+        Generic stripping on ranges: $(REF _stripLeft, std, algorithm, mutation)
+  +/
+auto stripLeft(Range)(Range input)
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static import std.ascii;
+    static import std.uni;
+    import std.utf : decodeFront;
+
+    while (!input.empty)
+    {
+        auto c = input.front;
+        if (std.ascii.isASCII(c))
+        {
+            if (!std.ascii.isWhite(c))
+                break;
+            input.popFront();
+        }
+        else
+        {
+            auto save = input.save;
+            auto dc = decodeFront(input);
+            if (!std.uni.isWhite(dc))
+                return save;
+        }
+    }
+    return input;
+}
+
+///
+@safe pure unittest
+{
+    import std.uni : lineSep, paraSep;
+    assert(stripLeft("     hello world     ") ==
+           "hello world     ");
+    assert(stripLeft("\n\t\v\rhello world\n\t\v\r") ==
+           "hello world\n\t\v\r");
+    assert(stripLeft("hello world") ==
+           "hello world");
+    assert(stripLeft([lineSep] ~ "hello world" ~ lineSep) ==
+           "hello world" ~ [lineSep]);
+    assert(stripLeft([paraSep] ~ "hello world" ~ paraSep) ==
+           "hello world" ~ [paraSep]);
+
+    import std.utf : byChar;
+    import std.array;
+    assert(stripLeft("     hello world     "w.byChar).array ==
+           "hello world     ");
+}
+
+auto stripLeft(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return stripLeft!(StringTypeOf!Range)(str);
+}
+
+unittest
+{
+    assert(testAliasedString!stripLeft("  hello"));
+}
+
+/++
+    Strips trailing whitespace (as defined by $(XREF uni, isWhite)).
+
+    Params:
+        str = string or random access range of characters
+
+    Returns:
+        slice of $(D str) stripped of trailing whitespace.
+
+    See_Also:
+        Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation)
+  +/
+auto stripRight(Range)(Range str)
+    if (isSomeString!Range ||
+        isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
+        !isConvertibleToString!Range &&
+        isSomeChar!(ElementEncodingType!Range))
+{
+    import std.uni : isWhite;
+    alias C = Unqual!(ElementEncodingType!(typeof(str)));
+
+    static if (isSomeString!(typeof(str)))
+    {
+        import std.utf : codeLength;
+
+        foreach_reverse (i, dchar c; str)
+        {
+            if (!isWhite(c))
+                return str[0 .. i + codeLength!C(c)];
+        }
+
+        return str[0 .. 0];
+    }
+    else
+    {
+        size_t i = str.length;
+        while (i--)
+        {
+            static if (C.sizeof == 4)
+            {
+                if (isWhite(str[i]))
+                    continue;
+                break;
+            }
+            else static if (C.sizeof == 2)
+            {
+                auto c2 = str[i];
+                if (c2 < 0xD800 || c2 >= 0xE000)
+                {
+                    if (isWhite(c2))
+                        continue;
+                }
+                else if (c2 >= 0xDC00)
+                {
+                    if (i)
+                    {
+                        auto c1 = str[i - 1];
+                        if (c1 >= 0xD800 && c1 < 0xDC00)
+                        {
+                            dchar c = ((c1 - 0xD7C0) << 10) + (c2 - 0xDC00);
+                            if (isWhite(c))
+                            {
+                                --i;
+                                continue;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+            else static if (C.sizeof == 1)
+            {
+                import std.utf : byDchar;
+
+                char cx = str[i];
+                if (cx <= 0x7F)
+                {
+                    if (isWhite(cx))
+                        continue;
+                    break;
+                }
+                else
+                {
+                    size_t stride = 0;
+
+                    while (1)
+                    {
+                        ++stride;
+                        if (!i || (cx & 0xC0) == 0xC0 || stride == 4)
+                            break;
+                        cx = str[i - 1];
+                        if (!(cx & 0x80))
+                            break;
+                        --i;
+                    }
+
+                    if (!str[i .. i + stride].byDchar.front.isWhite)
+                        return str[0 .. i + stride];
+                }
+            }
+            else
+                static assert(0);
+        }
+
+        return str[0 .. i + 1];
+    }
+}
+
+///
+@safe pure
+unittest
+{
+    import std.uni : lineSep, paraSep;
+    assert(stripRight("     hello world     ") ==
+           "     hello world");
+    assert(stripRight("\n\t\v\rhello world\n\t\v\r") ==
+           "\n\t\v\rhello world");
+    assert(stripRight("hello world") ==
+           "hello world");
+    assert(stripRight([lineSep] ~ "hello world" ~ lineSep) ==
+           [lineSep] ~ "hello world");
+    assert(stripRight([paraSep] ~ "hello world" ~ paraSep) ==
+           [paraSep] ~ "hello world");
+}
+
+auto stripRight(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return stripRight!(StringTypeOf!Range)(str);
+}
+
+unittest
+{
+    assert(testAliasedString!stripRight("hello   "));
+}
+
+unittest
+{
+    import std.utf;
+    import std.array;
+    import std.uni : lineSep, paraSep;
+    assert(stripRight("     hello world     ".byChar).array == "     hello world");
+    assert(stripRight("\n\t\v\rhello world\n\t\v\r"w.byWchar).array == "\n\t\v\rhello world"w);
+    assert(stripRight("hello world"d.byDchar).array == "hello world"d);
+    assert(stripRight("\u2028hello world\u2020\u2028".byChar).array == "\u2028hello world\u2020");
+    assert(stripRight("hello world\U00010001"w.byWchar).array == "hello world\U00010001"w);
+
+    foreach (C; AliasSeq!(char, wchar, dchar))
+    {
+        foreach (s; invalidUTFstrings!C())
+        {
+            cast(void)stripRight(s.byUTF!C).array;
+        }
+    }
+
+    cast(void)stripRight("a\x80".byUTF!char).array;
+    wstring ws = ['a', cast(wchar)0xDC00];
+    cast(void)stripRight(ws.byUTF!wchar).array;
+}
+
+
+/++
+    Strips both leading and trailing whitespace (as defined by
+    $(XREF uni, isWhite)).
+
+    Params:
+        str = string or random access range of characters
+
+    Returns:
+        slice of $(D str) stripped of leading and trailing whitespace.
+
+    See_Also:
+        Generic stripping on ranges: $(REF _strip, std, algorithm, mutation)
+  +/
+auto strip(Range)(Range str)
+    if (isSomeString!Range ||
+        isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
+        !isConvertibleToString!Range &&
+        isSomeChar!(ElementEncodingType!Range))
+{
+    return stripRight(stripLeft(str));
+}
+
+///
+@safe pure unittest
+{
+    import std.uni : lineSep, paraSep;
+    assert(strip("     hello world     ") ==
+           "hello world");
+    assert(strip("\n\t\v\rhello world\n\t\v\r") ==
+           "hello world");
+    assert(strip("hello world") ==
+           "hello world");
+    assert(strip([lineSep] ~ "hello world" ~ [lineSep]) ==
+           "hello world");
+    assert(strip([paraSep] ~ "hello world" ~ [paraSep]) ==
+           "hello world");
+}
+
+auto strip(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return strip!(StringTypeOf!Range)(str);
+}
+
+@safe pure unittest
+{
+    assert(testAliasedString!strip("     hello world     "));
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    import std.algorithm : equal;
+
+    debug(string) trustedPrintf("string.strip.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!( char[], const  char[],  string,
+                          wchar[], const wchar[], wstring,
+                          dchar[], const dchar[], dstring))
+    {
+        assert(equal(stripLeft(to!S("  foo\t ")), "foo\t "));
+        assert(equal(stripLeft(to!S("\u2008  foo\t \u2007")), "foo\t \u2007"));
+        assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB \r"));
+        assert(equal(stripLeft(to!S("1")), "1"));
+        assert(equal(stripLeft(to!S("\U0010FFFE")), "\U0010FFFE"));
+        assert(equal(stripLeft(to!S("")), ""));
+
+        assert(equal(stripRight(to!S("  foo\t ")), "  foo"));
+        assert(equal(stripRight(to!S("\u2008  foo\t \u2007")), "\u2008  foo"));
+        assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r")), "\u0085 μ \u0085 \u00BB"));
+        assert(equal(stripRight(to!S("1")), "1"));
+        assert(equal(stripRight(to!S("\U0010FFFE")), "\U0010FFFE"));
+        assert(equal(stripRight(to!S("")), ""));
+
+        assert(equal(strip(to!S("  foo\t ")), "foo"));
+        assert(equal(strip(to!S("\u2008  foo\t \u2007")), "foo"));
+        assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB"));
+        assert(equal(strip(to!S("\U0010FFFE")), "\U0010FFFE"));
+        assert(equal(strip(to!S("")), ""));
+    }
+    });
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    import std.range;
+    assertCTFEable!(
+    {
+    wstring s = " ";
+    assert(s.sameTail(s.stripLeft()));
+    assert(s.sameHead(s.stripRight()));
+    });
+}
+
+
+/++
+    If $(D str) ends with $(D delimiter), then $(D str) is returned without
+    $(D delimiter) on its end. If it $(D str) does $(I not) end with
+    $(D delimiter), then it is returned unchanged.
+
+    If no $(D delimiter) is given, then one trailing  $(D '\r'), $(D '\n'),
+    $(D "\r\n"), $(D '\f'), $(D '\v'), $(XREF uni, lineSep), $(XREF uni, paraSep), or $(XREF uni, nelSep)
+    is removed from the end of $(D str). If $(D str) does not end with any of those characters,
+    then it is returned unchanged.
+
+    Params:
+        str = string or indexable range of characters
+        delimiter = string of characters to be sliced off end of str[]
+
+    Returns:
+        slice of str
+  +/
+Range chomp(Range)(Range str)
+    if ((isRandomAccessRange!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range)
+{
+    import std.uni : lineSep, paraSep, nelSep;
+    if (str.empty)
+        return str;
+
+    alias C = ElementEncodingType!Range;
+
+    switch (str[$ - 1])
+    {
+        case '\n':
+        {
+            if (str.length > 1 && str[$ - 2] == '\r')
+                return str[0 .. $ - 2];
+            goto case;
+        }
+        case '\r', '\v', '\f':
+            return str[0 .. $ - 1];
+
+        // Pop off the last character if lineSep, paraSep, or nelSep
+        static if (is(C : const char))
+        {
+            /* Manually decode:
+             *  lineSep is E2 80 A8
+             *  paraSep is E2 80 A9
+             */
+            case 0xA8: // Last byte of lineSep
+            case 0xA9: // Last byte of paraSep
+                if (str.length > 2 && str[$ - 2] == 0x80 && str[$ - 3] == 0xE2)
+                    return str [0 .. $ - 3];
+                goto default;
+
+            /* Manually decode:
+             *  NEL is C2 85
+             */
+            case 0x85:
+                if (str.length > 1 && str[$ - 2] == 0xC2)
+                    return str [0 .. $ - 2];
+                goto default;
+        }
+        else
+        {
+            case lineSep:
+            case paraSep:
+            case nelSep:
+                return str[0 .. $ - 1];
+        }
+        default:
+            return str;
+    }
+}
+
+/// Ditto
+Range chomp(Range, C2)(Range str, const(C2)[] delimiter)
+    if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range &&
+        isSomeChar!C2)
+{
+    if (delimiter.empty)
+        return chomp(str);
+
+    alias C1 = ElementEncodingType!Range;
+
+    static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
+    {
+        import std.algorithm : endsWith;
+        if (str.endsWith(delimiter))
+            return str[0 .. $ - delimiter.length];
+        return str;
+    }
+    else
+    {
+        auto orig = str.save;
+
+        static if (isSomeString!Range)
+            alias C = dchar;    // because strings auto-decode
+        else
+            alias C = C1;       // and ranges do not
+
+        foreach_reverse (C c; delimiter)
+        {
+            if (str.empty || str.back != c)
+                return orig;
+
+            str.popBack();
+        }
+
+        return str;
+    }
+}
+
+///
+@safe pure
+unittest
+{
+    import std.utf : decode;
+    import std.uni : lineSep, paraSep, nelSep;
+    assert(chomp(" hello world  \n\r") == " hello world  \n");
+    assert(chomp(" hello world  \r\n") == " hello world  ");
+    assert(chomp(" hello world  \f") == " hello world  ");
+    assert(chomp(" hello world  \v") == " hello world  ");
+    assert(chomp(" hello world  \n\n") == " hello world  \n");
+    assert(chomp(" hello world  \n\n ") == " hello world  \n\n ");
+    assert(chomp(" hello world  \n\n" ~ [lineSep]) == " hello world  \n\n");
+    assert(chomp(" hello world  \n\n" ~ [paraSep]) == " hello world  \n\n");
+    assert(chomp(" hello world  \n\n" ~ [ nelSep]) == " hello world  \n\n");
+    assert(chomp(" hello world") == " hello world");
+    assert(chomp("") == "");
+
+    assert(chomp(" hello world", "orld") == " hello w");
+    assert(chomp(" hello world", " he") == " hello world");
+    assert(chomp("", "hello") == "");
+
+    // Don't decode pointlessly
+    assert(chomp("hello\xFE", "\r") == "hello\xFE");
+}
+
+StringTypeOf!Range chomp(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return chomp!(StringTypeOf!Range)(str);
+}
+
+StringTypeOf!Range chomp(Range, C2)(auto ref Range str, const(C2)[] delimiter)
+    if (isConvertibleToString!Range)
+{
+    return chomp!(StringTypeOf!Range, C2)(str, delimiter);
+}
+
+unittest
+{
+    assert(testAliasedString!chomp(" hello world  \n\r"));
+    assert(testAliasedString!chomp(" hello world", "orld"));
+}
+
+unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.chomp.unittest\n");
+    string s;
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        // @@@ BUG IN COMPILER, MUST INSERT CAST
+        assert(chomp(cast(S)null) is null);
+        assert(chomp(to!S("hello")) == "hello");
+        assert(chomp(to!S("hello\n")) == "hello");
+        assert(chomp(to!S("hello\r")) == "hello");
+        assert(chomp(to!S("hello\r\n")) == "hello");
+        assert(chomp(to!S("hello\n\r")) == "hello\n");
+        assert(chomp(to!S("hello\n\n")) == "hello\n");
+        assert(chomp(to!S("hello\r\r")) == "hello\r");
+        assert(chomp(to!S("hello\nxxx\n")) == "hello\nxxx");
+        assert(chomp(to!S("hello\u2028")) == "hello");
+        assert(chomp(to!S("hello\u2029")) == "hello");
+        assert(chomp(to!S("hello\u0085")) == "hello");
+        assert(chomp(to!S("hello\u2028\u2028")) == "hello\u2028");
+        assert(chomp(to!S("hello\u2029\u2029")) == "hello\u2029");
+        assert(chomp(to!S("hello\u2029\u2129")) == "hello\u2029\u2129");
+        assert(chomp(to!S("hello\u2029\u0185")) == "hello\u2029\u0185");
+
+        foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            // @@@ BUG IN COMPILER, MUST INSERT CAST
+            assert(chomp(cast(S)null, cast(T)null) is null);
+            assert(chomp(to!S("hello\n"), cast(T)null) == "hello");
+            assert(chomp(to!S("hello"), to!T("o")) == "hell");
+            assert(chomp(to!S("hello"), to!T("p")) == "hello");
+            // @@@ BUG IN COMPILER, MUST INSERT CAST
+            assert(chomp(to!S("hello"), cast(T) null) == "hello");
+            assert(chomp(to!S("hello"), to!T("llo")) == "he");
+            assert(chomp(to!S("\uFF28ello"), to!T("llo")) == "\uFF28e");
+            assert(chomp(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")) == "\uFF28e");
+        }();
+    }
+    });
+
+    // Ranges
+    import std.utf : byChar, byWchar, byDchar;
+    import std.array;
+    assert(chomp("hello world\r\n" .byChar ).array == "hello world");
+    assert(chomp("hello world\r\n"w.byWchar).array == "hello world"w);
+    assert(chomp("hello world\r\n"d.byDchar).array == "hello world"d);
+
+    assert(chomp("hello world"d.byDchar, "ld").array == "hello wor"d);
+
+    assert(chomp("hello\u2020" .byChar , "\u2020").array == "hello");
+    assert(chomp("hello\u2020"d.byDchar, "\u2020"d).array == "hello"d);
+}
+
+
+/++
+    If $(D str) starts with $(D delimiter), then the part of $(D str) following
+    $(D delimiter) is returned. If $(D str) does $(I not) start with
+
+    $(D delimiter), then it is returned unchanged.
+
+    Params:
+        str = string or forward range of characters
+        delimiter = string of characters to be sliced off front of str[]
+
+    Returns:
+        slice of str
+ +/
+Range chompPrefix(Range, C2)(Range str, const(C2)[] delimiter)
+    if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range &&
+        isSomeChar!C2)
+{
+    alias C1 = ElementEncodingType!Range;
+
+    static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
+    {
+        import std.algorithm : startsWith;
+        if (str.startsWith(delimiter))
+            return str[delimiter.length .. $];
+        return str;
+    }
+    else
+    {
+        auto orig = str.save;
+
+        static if (isSomeString!Range)
+            alias C = dchar;    // because strings auto-decode
+        else
+            alias C = C1;       // and ranges do not
+
+        foreach (C c; delimiter)
+        {
+            if (str.empty || str.front != c)
+                return orig;
+
+            str.popFront();
+        }
+
+        return str;
+    }
+}
+
+///
+@safe pure unittest
+{
+    assert(chompPrefix("hello world", "he") == "llo world");
+    assert(chompPrefix("hello world", "hello w") == "orld");
+    assert(chompPrefix("hello world", " world") == "hello world");
+    assert(chompPrefix("", "hello") == "");
+}
+
+StringTypeOf!Range chompPrefix(Range, C2)(auto ref Range str, const(C2)[] delimiter)
+    if (isConvertibleToString!Range)
+{
+    return chompPrefix!(StringTypeOf!Range, C2)(str, delimiter);
+}
+
+@safe pure
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : equal;
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(equal(chompPrefix(to!S("abcdefgh"), to!T("abcde")), "fgh"));
+            assert(equal(chompPrefix(to!S("abcde"), to!T("abcdefgh")), "abcde"));
+            assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el\uFF4co")), ""));
+            assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")), "\uFF4co"));
+            assert(equal(chompPrefix(to!S("\uFF28el"), to!T("\uFF28el\uFF4co")), "\uFF28el"));
+        }();
+    }
+    });
+
+    // Ranges
+    import std.utf : byChar, byWchar, byDchar;
+    import std.array;
+    assert(chompPrefix("hello world" .byChar , "hello"d).array == " world");
+    assert(chompPrefix("hello world"w.byWchar, "hello" ).array == " world"w);
+    assert(chompPrefix("hello world"d.byDchar, "hello"w).array == " world"d);
+    assert(chompPrefix("hello world"c.byDchar, "hello"w).array == " world"d);
+
+    assert(chompPrefix("hello world"d.byDchar, "lx").array == "hello world"d);
+    assert(chompPrefix("hello world"d.byDchar, "hello world xx").array == "hello world"d);
+
+    assert(chompPrefix("\u2020world" .byChar , "\u2020").array == "world");
+    assert(chompPrefix("\u2020world"d.byDchar, "\u2020"d).array == "world"d);
+}
+
+unittest
+{
+    assert(testAliasedString!chompPrefix("hello world", "hello"));
+}
+
+/++
+    Returns $(D str) without its last character, if there is one. If $(D str)
+    ends with $(D "\r\n"), then both are removed. If $(D str) is empty, then
+    then it is returned unchanged.
+
+    Params:
+        str = string (must be valid UTF)
+    Returns:
+        slice of str
+ +/
+
+Range chop(Range)(Range str)
+    if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range)
+{
+    if (str.empty)
+        return str;
+
+    static if (isSomeString!Range)
+    {
+        if (str.length >= 2 && str[$ - 1] == '\n' && str[$ - 2] == '\r')
+            return str[0 .. $ - 2];
+        str.popBack();
+        return str;
+    }
+    else
+    {
+        alias C = Unqual!(ElementEncodingType!Range);
+        C c = str.back;
+        str.popBack();
+        if (c == '\n')
+        {
+            if (!str.empty && str.back == '\r')
+                str.popBack();
+            return str;
+        }
+        // Pop back a dchar, not just a code unit
+        static if (C.sizeof == 1)
+        {
+            int cnt = 1;
+            while ((c & 0xC0) == 0x80)
+            {
+                if (str.empty)
+                    break;
+                c = str.back;
+                str.popBack();
+                if (++cnt > 4)
+                    break;
+            }
+        }
+        else static if (C.sizeof == 2)
+        {
+            if (c >= 0xD800 && c <= 0xDBFF)
+            {
+                if (!str.empty)
+                    str.popBack();
+            }
+        }
+        else static if (C.sizeof == 4)
+        {
+        }
+        else
+            static assert(0);
+        return str;
+    }
+}
+
+///
+@safe pure unittest
+{
+    assert(chop("hello world") == "hello worl");
+    assert(chop("hello world\n") == "hello world");
+    assert(chop("hello world\r") == "hello world");
+    assert(chop("hello world\n\r") == "hello world\n");
+    assert(chop("hello world\r\n") == "hello world");
+    assert(chop("Walter Bright") == "Walter Brigh");
+    assert(chop("") == "");
+}
+
+StringTypeOf!Range chop(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return chop!(StringTypeOf!Range)(str);
+}
+
+unittest
+{
+    assert(testAliasedString!chop("hello world"));
+}
+
+@safe pure unittest
+{
+    import std.utf : byChar, byWchar, byDchar, byCodeUnit, invalidUTFstrings;
+    import std.array;
+
+    assert(chop("hello world".byChar).array == "hello worl");
+    assert(chop("hello world\n"w.byWchar).array == "hello world"w);
+    assert(chop("hello world\r"d.byDchar).array == "hello world"d);
+    assert(chop("hello world\n\r".byChar).array == "hello world\n");
+    assert(chop("hello world\r\n"w.byWchar).array == "hello world"w);
+    assert(chop("Walter Bright"d.byDchar).array == "Walter Brigh"d);
+    assert(chop("".byChar).array == "");
+
+    assert(chop(`ミツバチと科学者` .byCodeUnit).array == "ミツバチと科学");
+    assert(chop(`ミツバチと科学者`w.byCodeUnit).array == "ミツバチと科学"w);
+    assert(chop(`ミツバチと科学者`d.byCodeUnit).array == "ミツバチと科学"d);
+
+    auto ca = invalidUTFstrings!char();
+    foreach (s; ca)
+    {
+        foreach (c; chop(s.byCodeUnit))
+        {
+        }
+    }
+
+    auto wa = invalidUTFstrings!wchar();
+    foreach (s; wa)
+    {
+        foreach (c; chop(s.byCodeUnit))
+        {
+        }
+    }
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.algorithm : equal;
+
+    debug(string) trustedPrintf("string.chop.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        assert(chop(cast(S) null) is null);
+        assert(equal(chop(to!S("hello")), "hell"));
+        assert(equal(chop(to!S("hello\r\n")), "hello"));
+        assert(equal(chop(to!S("hello\n\r")), "hello\n"));
+        assert(equal(chop(to!S("Verité")), "Verit"));
+        assert(equal(chop(to!S(`さいごの果実`)), "さいごの果"));
+        assert(equal(chop(to!S(`ミツバチと科学者`)), "ミツバチと科学"));
+    }
+    });
+}
+
+
+/++
+    Left justify $(D s) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D s) doesn't fill.
+
+    Params:
+        s = string
+        width = minimum field width
+        fillChar = used to pad end up to $(D width) characters
+
+    Returns:
+        GC allocated string
+
+    See_Also:
+        $(LREF leftJustifier), which does not allocate
+  +/
+S leftJustify(S)(S s, size_t width, dchar fillChar = ' ')
+    if (isSomeString!S)
+{
+    import std.array;
+    return leftJustifier(s, width, fillChar).array;
+}
+
+///
+@safe pure unittest
+{
+    assert(leftJustify("hello", 7, 'X') == "helloXX");
+    assert(leftJustify("hello", 2, 'X') == "hello");
+    assert(leftJustify("hello", 9, 'X') == "helloXXXX");
+}
+
+/++
+    Left justify $(D s) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D s) doesn't fill.
+
+    Params:
+        r = string or range of characters
+        width = minimum field width
+        fillChar = used to pad end up to $(D width) characters
+
+    Returns:
+        a lazy range of the left justified result
+
+    See_Also:
+        $(LREF rightJustifier)
+  +/
+
+auto leftJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    static if (C.sizeof == 1)
+    {
+        import std.utf : byDchar, byChar;
+        return leftJustifier(r.byDchar, width, fillChar).byChar;
+    }
+    else static if (C.sizeof == 2)
+    {
+        import std.utf : byDchar, byWchar;
+        return leftJustifier(r.byDchar, width, fillChar).byWchar;
+    }
+    else static if (C.sizeof == 4)
+    {
+        static struct Result
+        {
+          private:
+            Range _input;
+            size_t _width;
+            dchar _fillChar;
+            size_t len;
+
+          public:
+            this(Range input, size_t width, dchar fillChar)
+            {
+                _input = input;
+                _width = width;
+                _fillChar = fillChar;
+            }
+
+            @property bool empty()
+            {
+                return len >= _width && _input.empty;
+            }
+
+            @property C front()
+            {
+                return _input.empty ? _fillChar : _input.front;
+            }
+
+            void popFront()
+            {
+                ++len;
+                if (!_input.empty)
+                    _input.popFront();
+            }
+
+            static if (isForwardRange!Range)
+            {
+                @property typeof(this) save()
+                {
+                    auto ret = this;
+                    ret._input = _input.save;
+                    return ret;
+                }
+            }
+        }
+
+        return Result(r, width, fillChar);
+    }
+    else
+        static assert(0);
+}
+
+///
+@safe pure @nogc nothrow
+unittest
+{
+    import std.algorithm : equal;
+    import std.utf : byChar;
+    assert(leftJustifier("hello", 2).equal("hello".byChar));
+    assert(leftJustifier("hello", 7).equal("hello  ".byChar));
+    assert(leftJustifier("hello", 7, 'x').equal("helloxx".byChar));
+}
+
+auto leftJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
+    if (isConvertibleToString!Range)
+{
+    return leftJustifier!(StringTypeOf!Range)(r, width, fillChar);
+}
+
+unittest
+{
+    auto r = "hello".leftJustifier(8);
+    r.popFront();
+    auto save = r.save;
+    r.popFront();
+    assert(r.front == 'l');
+    assert(save.front == 'e');
+}
+
+unittest
+{
+    assert(testAliasedString!leftJustifier("hello", 2));
+}
+
+/++
+    Right justify $(D s) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D s) doesn't fill.
+
+    Params:
+        s = string
+        width = minimum field width
+        fillChar = used to pad end up to $(D width) characters
+
+    Returns:
+        GC allocated string
+
+    See_Also:
+        $(LREF rightJustifier), which does not allocate
+  +/
+S rightJustify(S)(S s, size_t width, dchar fillChar = ' ')
+    if (isSomeString!S)
+{
+    import std.array;
+    return rightJustifier(s, width, fillChar).array;
+}
+
+///
+@safe pure unittest
+{
+    assert(rightJustify("hello", 7, 'X') == "XXhello");
+    assert(rightJustify("hello", 2, 'X') == "hello");
+    assert(rightJustify("hello", 9, 'X') == "XXXXhello");
+}
+
+/++
+    Right justify $(D s) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D s) doesn't fill.
+
+    Params:
+        r = string or forward range of characters
+        width = minimum field width
+        fillChar = used to pad end up to $(D width) characters
+
+    Returns:
+        a lazy range of the right justified result
+
+    See_Also:
+        $(LREF leftJustifier)
+  +/
+
+auto rightJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    static if (C.sizeof == 1)
+    {
+        import std.utf : byDchar, byChar;
+        return rightJustifier(r.byDchar, width, fillChar).byChar;
+    }
+    else static if (C.sizeof == 2)
+    {
+        import std.utf : byDchar, byWchar;
+        return rightJustifier(r.byDchar, width, fillChar).byWchar;
+    }
+    else static if (C.sizeof == 4)
+    {
+        static struct Result
+        {
+          private:
+            Range _input;
+            size_t _width;
+            alias nfill = _width;       // number of fill characters to prepend
+            dchar _fillChar;
+            bool inited;
+
+            // Lazy initialization so constructor is trivial and cannot fail
+            void initialize()
+            {
+                // Replace _width with nfill
+                // (use alias instead of union because CTFE cannot deal with unions)
+                assert(_width);
+                static if (hasLength!Range)
+                {
+                    auto len = _input.length;
+                    nfill = (_width > len) ? _width - len : 0;
+                }
+                else
+                {
+                    // Lookahead to see now many fill characters are needed
+                    import std.range : walkLength, take;
+                    nfill = _width - walkLength(_input.save.take(_width), _width);
+                }
+                inited = true;
+            }
+
+          public:
+            this(Range input, size_t width, dchar fillChar) pure nothrow
+            {
+                _input = input;
+                _fillChar = fillChar;
+                _width = width;
+            }
+
+            @property bool empty()
+            {
+                return !nfill && _input.empty;
+            }
+
+            @property C front()
+            {
+                if (!nfill)
+                    return _input.front;   // fast path
+                if (!inited)
+                    initialize();
+                return nfill ? _fillChar : _input.front;
+            }
+
+            void popFront()
+            {
+                if (!nfill)
+                    _input.popFront();  // fast path
+                else
+                {
+                    if (!inited)
+                        initialize();
+                    if (nfill)
+                        --nfill;
+                    else
+                        _input.popFront();
+                }
+            }
+
+            @property typeof(this) save()
+            {
+                auto ret = this;
+                ret._input = _input.save;
+                return ret;
+            }
+        }
+
+        return Result(r, width, fillChar);
+    }
+    else
+        static assert(0);
+}
+
+///
+@safe pure @nogc nothrow
+unittest
+{
+    import std.algorithm : equal;
+    import std.utf : byChar;
+    assert(rightJustifier("hello", 2).equal("hello".byChar));
+    assert(rightJustifier("hello", 7).equal("  hello".byChar));
+    assert(rightJustifier("hello", 7, 'x').equal("xxhello".byChar));
+}
+
+auto rightJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
+    if (isConvertibleToString!Range)
+{
+    return rightJustifier!(StringTypeOf!Range)(r, width, fillChar);
+}
+
+unittest
+{
+    assert(testAliasedString!rightJustifier("hello", 2));
+}
+
+unittest
+{
+    auto r = "hello"d.rightJustifier(6);
+    r.popFront();
+    auto save = r.save;
+    r.popFront();
+    assert(r.front == 'e');
+    assert(save.front == 'h');
+
+    auto t = "hello".rightJustifier(7);
+    t.popFront();
+    assert(t.front == ' ');
+    t.popFront();
+    assert(t.front == 'h');
+
+    auto u = "hello"d.rightJustifier(5);
+    u.popFront();
+    u.popFront();
+    u.popFront();
+}
+
+/++
+    Center $(D s) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D s) doesn't fill.
+
+    Params:
+        s = The string to center
+        width = Width of the field to center `s` in
+        fillChar = The character to use for filling excess space in the field
+
+    Returns:
+        The resulting _center-justified string. The returned string is
+        GC-allocated. To avoid GC allocation, use $(LREF centerJustifier)
+        instead.
+  +/
+S center(S)(S s, size_t width, dchar fillChar = ' ')
+    if (isSomeString!S)
+{
+    import std.array;
+    return centerJustifier(s, width, fillChar).array;
+}
+
+///
+@safe pure unittest
+{
+    assert(center("hello", 7, 'X') == "XhelloX");
+    assert(center("hello", 2, 'X') == "hello");
+    assert(center("hello", 9, 'X') == "XXhelloXX");
+}
+
+@safe pure
+unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.justify.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        S s = to!S("hello");
+
+        assert(leftJustify(s, 2) == "hello");
+        assert(rightJustify(s, 2) == "hello");
+        assert(center(s, 2) == "hello");
+
+        assert(leftJustify(s, 7) == "hello  ");
+        assert(rightJustify(s, 7) == "  hello");
+        assert(center(s, 7) == " hello ");
+
+        assert(leftJustify(s, 8) == "hello   ");
+        assert(rightJustify(s, 8) == "   hello");
+        assert(center(s, 8) == " hello  ");
+
+        assert(leftJustify(s, 8, '\u0100') == "hello\u0100\u0100\u0100");
+        assert(rightJustify(s, 8, '\u0100') == "\u0100\u0100\u0100hello");
+        assert(center(s, 8, '\u0100') == "\u0100hello\u0100\u0100");
+
+        assert(leftJustify(s, 8, 'ö') == "helloööö");
+        assert(rightJustify(s, 8, 'ö') == "öööhello");
+        assert(center(s, 8, 'ö') == "öhelloöö");
+    }
+    });
+}
+
+/++
+    Center justify $(D r) in a field $(D width) characters wide. $(D fillChar)
+    is the character that will be used to fill up the space in the field that
+    $(D r) doesn't fill.
+
+    Params:
+        r = string or forward range of characters
+        width = minimum field width
+        fillChar = used to pad end up to $(D width) characters
+
+    Returns:
+        a lazy range of the center justified result
+
+    See_Also:
+        $(LREF leftJustifier)
+        $(LREF rightJustifier)
+  +/
+
+auto centerJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    static if (C.sizeof == 1)
+    {
+        import std.utf : byDchar, byChar;
+        return centerJustifier(r.byDchar, width, fillChar).byChar;
+    }
+    else static if (C.sizeof == 2)
+    {
+        import std.utf : byDchar, byWchar;
+        return centerJustifier(r.byDchar, width, fillChar).byWchar;
+    }
+    else static if (C.sizeof == 4)
+    {
+        import std.range : chain, repeat, walkLength;
+
+        auto len = walkLength(r.save, width);
+        if (len > width)
+            len = width;
+        const nleft = (width - len) / 2;
+        const nright = width - len - nleft;
+        return chain(repeat(fillChar, nleft), r, repeat(fillChar, nright));
+    }
+    else
+        static assert(0);
+}
+
+///
+@safe pure @nogc nothrow
+unittest
+{
+    import std.algorithm : equal;
+    import std.utf : byChar;
+    assert(centerJustifier("hello", 2).equal("hello".byChar));
+    assert(centerJustifier("hello", 8).equal(" hello  ".byChar));
+    assert(centerJustifier("hello", 7, 'x').equal("xhellox".byChar));
+}
+
+auto centerJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
+    if (isConvertibleToString!Range)
+{
+    return centerJustifier!(StringTypeOf!Range)(r, width, fillChar);
+}
+
+unittest
+{
+    assert(testAliasedString!centerJustifier("hello", 8));
+}
+
+unittest
+{
+    static auto byFwdRange(dstring s)
+    {
+        static struct FRange
+        {
+            dstring str;
+            this(dstring s) { str = s; }
+            @property bool empty() { return str.length == 0; }
+            @property dchar front() { return str[0]; }
+            void popFront() { str = str[1 .. $]; }
+            @property FRange save() { return this; }
+        }
+        return FRange(s);
+    }
+
+    auto r = centerJustifier(byFwdRange("hello"d), 6);
+    r.popFront();
+    auto save = r.save;
+    r.popFront();
+    assert(r.front == 'l');
+    assert(save.front == 'e');
+
+    auto t = "hello".centerJustifier(7);
+    t.popFront();
+    assert(t.front == 'h');
+    t.popFront();
+    assert(t.front == 'e');
+
+    auto u = byFwdRange("hello"d).centerJustifier(6);
+    u.popFront();
+    u.popFront();
+    u.popFront();
+    u.popFront();
+    u.popFront();
+    u.popFront();
+}
+
+
+/++
+    Replace each tab character in $(D s) with the number of spaces necessary
+    to align the following character at the next tab stop.
+
+    Params:
+        s = string
+        tabSize = distance between tab stops
+
+    Returns:
+        GC allocated string with tabs replaced with spaces
+  +/
+auto detab(Range)(auto ref Range s, size_t tabSize = 8) pure
+    if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
+        || __traits(compiles, StringTypeOf!Range))
+{
+    import std.array;
+    return detabber(s, tabSize).array;
+}
+
+///
+@trusted pure unittest
+{
+    import std.array;
+
+    assert(detab(" \n\tx", 9) == " \n         x");
+}
+
+unittest
+{
+    static struct TestStruct
+    {
+        string s;
+        alias s this;
+    }
+
+    static struct TestStruct2
+    {
+        string s;
+        alias s this;
+        @disable this(this);
+    }
+
+    string s = " \n\tx";
+    string cmp = " \n         x";
+    auto t = TestStruct(s);
+    assert(detab(t, 9) == cmp);
+    assert(detab(TestStruct(s), 9) == cmp);
+    assert(detab(TestStruct(s), 9) == detab(TestStruct(s), 9));
+    assert(detab(TestStruct2(s), 9) == detab(TestStruct2(s), 9));
+    assert(detab(TestStruct2(s), 9) == cmp);
+}
+
+/++
+    Replace each tab character in $(D r) with the number of spaces
+    necessary to align the following character at the next tab stop.
+
+    Params:
+        r = string or forward range
+        tabSize = distance between tab stops
+
+    Returns:
+        lazy forward range with tabs replaced with spaces
+  +/
+auto detabber(Range)(Range r, size_t tabSize = 8)
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    import std.uni : lineSep, paraSep, nelSep;
+    import std.utf : codeUnitLimit, decodeFront;
+
+    assert(tabSize > 0);
+
+    alias C = Unqual!(ElementEncodingType!(Range));
+
+    static struct Result
+    {
+    private:
+        Range _input;
+        size_t _tabSize;
+        size_t nspaces;
+        int column;
+        size_t index;
+
+    public:
+
+        this(Range input, size_t tabSize)
+        {
+            _input = input;
+            _tabSize = tabSize;
+        }
+
+        static if (isInfinite!(Range))
+        {
+            enum bool empty = false;
+        }
+        else
+        {
+            @property bool empty()
+            {
+                return _input.empty && nspaces == 0;
+            }
+        }
+
+        @property C front()
+        {
+            if (nspaces)
+                return ' ';
+            static if (isSomeString!(Range))
+                C c = _input[0];
+            else
+                C c = _input.front;
+            if (index)
+                return c;
+            dchar dc;
+            if (c < codeUnitLimit!(immutable(C)[]))
+            {
+                dc = c;
+                index = 1;
+            }
+            else
+            {
+                auto r = _input.save;
+                dc = decodeFront(r, index);     // lookahead to decode
+            }
+            switch (dc)
+            {
+                case '\r':
+                case '\n':
+                case paraSep:
+                case lineSep:
+                case nelSep:
+                    column = 0;
+                    break;
+
+                case '\t':
+                    nspaces = _tabSize - (column % _tabSize);
+                    column += nspaces;
+                    c = ' ';
+                    break;
+
+                default:
+                    ++column;
+                    break;
+            }
+            return c;
+        }
+
+        void popFront()
+        {
+            if (!index)
+                front;
+            if (nspaces)
+                --nspaces;
+            if (!nspaces)
+            {
+                static if (isSomeString!(Range))
+                   _input = _input[1 .. $];
+                else
+                    _input.popFront();
+                --index;
+            }
+        }
+
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            return ret;
+        }
+    }
+
+    return Result(r, tabSize);
+}
+
+///
+@trusted pure unittest
+{
+    import std.array;
+
+    assert(detabber(" \n\tx", 9).array == " \n         x");
+}
+
+auto detabber(Range)(auto ref Range r, size_t tabSize = 8)
+    if (isConvertibleToString!Range)
+{
+    return detabber!(StringTypeOf!Range)(r, tabSize);
+}
+
+unittest
+{
+    assert(testAliasedString!detabber(  "  ab\t asdf ", 8));
+}
+
+@trusted pure unittest
+{
+    import std.conv : to;
+    import std.algorithm : cmp;
+
+    debug(string) trustedPrintf("string.detab.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        S s = to!S("This \tis\t a fofof\tof list");
+        assert(cmp(detab(s), "This    is       a fofof        of list") == 0);
+
+        assert(detab(cast(S)null) is null);
+        assert(detab("").empty);
+        assert(detab("a") == "a");
+        assert(detab("\t") == "        ");
+        assert(detab("\t", 3) == "   ");
+        assert(detab("\t", 9) == "         ");
+        assert(detab(  "  ab\t asdf ") == "  ab     asdf ");
+        assert(detab(  "  \U00010000b\tasdf ") == "  \U00010000b    asdf ");
+        assert(detab("\r\t", 9) == "\r         ");
+        assert(detab("\n\t", 9) == "\n         ");
+        assert(detab("\u0085\t", 9) == "\u0085         ");
+        assert(detab("\u2028\t", 9) == "\u2028         ");
+        assert(detab(" \u2029\t", 9) == " \u2029         ");
+    }
+    });
+}
+
+///
+@trusted pure unittest
+{
+    import std.utf;
+    import std.array;
+
+    assert(detabber(" \u2029\t".byChar, 9).array == " \u2029         ");
+    auto r = "hel\tx".byWchar.detabber();
+    assert(r.front == 'h' && r.front == 'h');
+    auto s = r.save;
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+    assert(s.front == 'h');
+}
+
+/++
+    Replaces spaces in $(D s) with the optimal number of tabs.
+    All spaces and tabs at the end of a line are removed.
+
+    Params:
+        s       = String to convert.
+        tabSize = Tab columns are $(D tabSize) spaces apart.
+
+    Returns:
+        GC allocated string with spaces replaced with tabs;
+        use $(LREF entabber) to not allocate.
+
+    See_Also:
+        $(LREF entabber)
+ +/
+auto entab(Range)(Range s, size_t tabSize = 8)
+    if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
+{
+    import std.array : array;
+    return entabber(s, tabSize).array;
+}
+
+///
+unittest
+{
+    assert(entab("        x \n") == "\tx\n");
+}
+
+auto entab(Range)(auto ref Range s, size_t tabSize = 8)
+    if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) &&
+        is(StringTypeOf!Range))
+{
+    return entab!(StringTypeOf!Range)(s, tabSize);
+}
+
+unittest
+{
+    assert(testAliasedString!entab("        x \n"));
+}
+
+/++
+    Replaces spaces in range $(D r) with the optimal number of tabs.
+    All spaces and tabs at the end of a line are removed.
+
+    Params:
+        r = string or forward range
+        tabSize = distance between tab stops
+
+    Returns:
+        lazy forward range with spaces replaced with tabs
+
+    See_Also:
+        $(LREF entab)
+  +/
+auto entabber(Range)(Range r, size_t tabSize = 8)
+    if (isForwardRange!Range && !isConvertibleToString!Range)
+{
+    import std.uni : lineSep, paraSep, nelSep;
+    import std.utf : codeUnitLimit, decodeFront;
+
+    assert(tabSize > 0);
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    static struct Result
+    {
+    private:
+        Range _input;
+        size_t _tabSize;
+        size_t nspaces;
+        size_t ntabs;
+        int column;
+        size_t index;
+
+        @property C getFront()
+        {
+            static if (isSomeString!Range)
+                return _input[0];       // avoid autodecode
+            else
+                return _input.front;
+        }
+
+    public:
+
+        this(Range input, size_t tabSize)
+        {
+            _input = input;
+            _tabSize = tabSize;
+        }
+
+        @property bool empty()
+        {
+            if (ntabs || nspaces)
+                return false;
+
+            /* Since trailing spaces are removed,
+             * look ahead for anything that is not a trailing space
+             */
+            static if (isSomeString!Range)
+            {
+                foreach (c; _input)
+                {
+                    if (c != ' ' && c != '\t')
+                        return false;
+                }
+                return true;
+            }
+            else
+            {
+                if (_input.empty)
+                    return true;
+                C c = _input.front;
+                if (c != ' ' && c != '\t')
+                    return false;
+                auto t = _input.save;
+                t.popFront();
+                foreach (c2; t)
+                {
+                    if (c2 != ' ' && c2 != '\t')
+                        return false;
+                }
+                return true;
+            }
+        }
+
+        @property C front()
+        {
+            //writefln("   front(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
+            if (ntabs)
+                return '\t';
+            if (nspaces)
+                return ' ';
+            C c = getFront;
+            if (index)
+                return c;
+            dchar dc;
+            if (c < codeUnitLimit!(immutable(C)[]))
+            {
+                index = 1;
+                dc = c;
+                if (c == ' ' || c == '\t')
+                {
+                    // Consume input until a non-blank is encountered
+                    size_t startcol = column;
+                    C cx;
+                    static if (isSomeString!Range)
+                    {
+                        while (1)
+                        {
+                            assert(_input.length);
+                            cx = _input[0];
+                            if (cx == ' ')
+                                ++column;
+                            else if (cx == '\t')
+                                column += _tabSize - (column % _tabSize);
+                            else
+                                break;
+                            _input = _input[1 .. $];
+                        }
+                    }
+                    else
+                    {
+                        while (1)
+                        {
+                            assert(!_input.empty);
+                            cx = _input.front;
+                            if (cx == ' ')
+                                ++column;
+                            else if (cx == '\t')
+                                column += _tabSize - (column % _tabSize);
+                            else
+                                break;
+                            _input.popFront();
+                        }
+                    }
+                    // Compute ntabs+nspaces to get from startcol to column
+                    auto n = column - startcol;
+                    if (n == 1)
+                    {
+                        nspaces = 1;
+                    }
+                    else
+                    {
+                        ntabs = column / _tabSize - startcol / _tabSize;
+                        if (ntabs == 0)
+                            nspaces = column - startcol;
+                        else
+                            nspaces = column % _tabSize;
+                    }
+                    //writefln("\tstartcol = %s, column = %s, _tabSize = %s", startcol, column, _tabSize);
+                    //writefln("\tntabs = %s, nspaces = %s", ntabs, nspaces);
+                    if (cx < codeUnitLimit!(immutable(C)[]))
+                    {
+                        dc = cx;
+                        index = 1;
+                    }
+                    else
+                    {
+                        auto r = _input.save;
+                        dc = decodeFront(r, index);     // lookahead to decode
+                    }
+                    switch (dc)
+                    {
+                        case '\r':
+                        case '\n':
+                        case paraSep:
+                        case lineSep:
+                        case nelSep:
+                            column = 0;
+                            // Spaces followed by newline are ignored
+                            ntabs = 0;
+                            nspaces = 0;
+                            return cx;
+
+                        default:
+                            ++column;
+                            break;
+                    }
+                    return ntabs ? '\t' : ' ';
+                }
+            }
+            else
+            {
+                auto r = _input.save;
+                dc = decodeFront(r, index);     // lookahead to decode
+            }
+            //writefln("dc = x%x", dc);
+            switch (dc)
+            {
+                case '\r':
+                case '\n':
+                case paraSep:
+                case lineSep:
+                case nelSep:
+                    column = 0;
+                    break;
+
+                default:
+                    ++column;
+                    break;
+            }
+            return c;
+        }
+
+        void popFront()
+        {
+            //writefln("popFront(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
+            if (!index)
+                front;
+            if (ntabs)
+                --ntabs;
+            else if (nspaces)
+                --nspaces;
+            else if (!ntabs && !nspaces)
+            {
+                static if (isSomeString!Range)
+                   _input = _input[1 .. $];
+                else
+                    _input.popFront();
+                --index;
+            }
+        }
+
+        @property typeof(this) save()
+        {
+            auto ret = this;
+            ret._input = _input.save;
+            return ret;
+        }
+    }
+
+    return Result(r, tabSize);
+}
+
+///
+unittest
+{
+    import std.array;
+    assert(entabber("        x \n").array == "\tx\n");
+}
+
+auto entabber(Range)(auto ref Range r, size_t tabSize = 8)
+    if (isConvertibleToString!Range)
+{
+    return entabber!(StringTypeOf!Range)(r, tabSize);
+}
+
+unittest
+{
+    assert(testAliasedString!entabber("  ab    asdf ", 8));
+}
+
+@safe pure
+unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.entab.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(entab(cast(string) null) is null);
+    assert(entab("").empty);
+    assert(entab("a") == "a");
+    assert(entab("        ") == "");
+    assert(entab("        x") == "\tx");
+    assert(entab("  ab    asdf ") == "  ab\tasdf");
+    assert(entab("  ab     asdf ") == "  ab\t asdf");
+    assert(entab("  ab \t   asdf ") == "  ab\t   asdf");
+    assert(entab("1234567 \ta") == "1234567\t\ta");
+    assert(entab("1234567  \ta") == "1234567\t\ta");
+    assert(entab("1234567   \ta") == "1234567\t\ta");
+    assert(entab("1234567    \ta") == "1234567\t\ta");
+    assert(entab("1234567     \ta") == "1234567\t\ta");
+    assert(entab("1234567      \ta") == "1234567\t\ta");
+    assert(entab("1234567       \ta") == "1234567\t\ta");
+    assert(entab("1234567        \ta") == "1234567\t\ta");
+    assert(entab("1234567         \ta") == "1234567\t\t\ta");
+
+    assert(entab("a               ") == "a");
+    assert(entab("a\v") == "a\v");
+    assert(entab("a\f") == "a\f");
+    assert(entab("a\n") == "a\n");
+    assert(entab("a\n\r") == "a\n\r");
+    assert(entab("a\r\n") == "a\r\n");
+    assert(entab("a\u2028") == "a\u2028");
+    assert(entab("a\u2029") == "a\u2029");
+    assert(entab("a\u0085") == "a\u0085");
+    assert(entab("a  ") == "a");
+    assert(entab("a\t") == "a");
+    assert(entab("\uFF28\uFF45\uFF4C\uFF4C567      \t\uFF4F \t") ==
+                 "\uFF28\uFF45\uFF4C\uFF4C567\t\t\uFF4F");
+    assert(entab(" \naa") == "\naa");
+    assert(entab(" \r aa") == "\r aa");
+    assert(entab(" \u2028 aa") == "\u2028 aa");
+    assert(entab(" \u2029 aa") == "\u2029 aa");
+    assert(entab(" \u0085 aa") == "\u0085 aa");
+    });
+}
+
+@safe pure
+unittest
+{
+    import std.utf : byChar;
+    import std.array;
+    assert(entabber(" \u0085 aa".byChar).array == "\u0085 aa");
+    assert(entabber(" \u2028\t aa \t".byChar).array == "\u2028\t aa");
+
+    auto r = entabber("1234", 4);
+    r.popFront();
+    auto rsave = r.save;
+    r.popFront();
+    assert(r.front == '3');
+    assert(rsave.front == '2');
+}
+
+
+/++
+    Replaces the characters in $(D str) which are keys in $(D transTable) with
+    their corresponding values in $(D transTable). $(D transTable) is an AA
+    where its keys are $(D dchar) and its values are either $(D dchar) or some
+    type of string. Also, if $(D toRemove) is given, the characters in it are
+    removed from $(D str) prior to translation. $(D str) itself is unaltered.
+    A copy with the changes is returned.
+
+    See_Also:
+        $(LREF tr)
+        $(XREF array, replace)
+
+    Params:
+        str        = The original string.
+        transTable = The AA indicating which characters to replace and what to
+                     replace them with.
+        toRemove   = The characters to remove from the string.
+  +/
+C1[] translate(C1, C2 = immutable char)(C1[] str,
+                                        in dchar[dchar] transTable,
+                                        const(C2)[] toRemove = null) @safe pure
+    if (isSomeChar!C1 && isSomeChar!C2)
+{
+    import std.array : appender;
+    auto buffer = appender!(C1[])();
+    translateImpl(str, transTable, toRemove, buffer);
+    return buffer.data;
+}
+
+///
+@safe pure unittest
+{
+    dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
+    assert(translate("hello world", transTable1) == "h5ll7 w7rld");
+
+    assert(translate("hello world", transTable1, "low") == "h5 rd");
+
+    string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
+    assert(translate("hello world", transTable2) == "h5llorange worangerld");
+}
+
+@safe pure unittest // issue 13018
+{
+    immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
+    assert(translate("hello world", transTable1) == "h5ll7 w7rld");
+
+    assert(translate("hello world", transTable1, "low") == "h5 rd");
+
+    immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
+    assert(translate("hello world", transTable2) == "h5llorange worangerld");
+}
+
+@trusted pure unittest
+{
+    import std.conv : to;
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
+                          wchar[], const(wchar)[], immutable(wchar)[],
+                          dchar[], const(dchar)[], immutable(dchar)[]))
+    {
+        assert(translate(to!S("hello world"), cast(dchar[dchar])['h' : 'q', 'l' : '5']) ==
+               to!S("qe55o wor5d"));
+        assert(translate(to!S("hello world"), cast(dchar[dchar])['o' : 'l', 'l' : '\U00010143']) ==
+               to!S("he\U00010143\U00010143l wlr\U00010143d"));
+        assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['h' : 'q', 'l': '5']) ==
+               to!S("qe55o \U00010143 wor5d"));
+        assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['o' : '0', '\U00010143' : 'o']) ==
+               to!S("hell0 o w0rld"));
+        assert(translate(to!S("hello world"), cast(dchar[dchar])null) == to!S("hello world"));
+
+        foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
+                              wchar[], const(wchar)[], immutable(wchar)[],
+                              dchar[], const(dchar)[], immutable(dchar)[]))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            foreach(R; AliasSeq!(dchar[dchar], const dchar[dchar],
+                        immutable dchar[dchar]))
+            {
+                R tt = ['h' : 'q', 'l' : '5'];
+                assert(translate(to!S("hello world"), tt, to!T("r"))
+                    == to!S("qe55o wo5d"));
+                assert(translate(to!S("hello world"), tt, to!T("helo"))
+                    == to!S(" wrd"));
+                assert(translate(to!S("hello world"), tt, to!T("q5"))
+                    == to!S("qe55o wor5d"));
+            }
+        }();
+
+        auto s = to!S("hello world");
+        dchar[dchar] transTable = ['h' : 'q', 'l' : '5'];
+        static assert(is(typeof(s) == typeof(translate(s, transTable))));
+    }
+    });
+}
+
+/++ Ditto +/
+C1[] translate(C1, S, C2 = immutable char)(C1[] str,
+                                           in S[dchar] transTable,
+                                           const(C2)[] toRemove = null) @safe pure
+    if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2)
+{
+    import std.array : appender;
+    auto buffer = appender!(C1[])();
+    translateImpl(str, transTable, toRemove, buffer);
+    return buffer.data;
+}
+
+@trusted pure unittest
+{
+    import std.conv : to;
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
+                          wchar[], const(wchar)[], immutable(wchar)[],
+                          dchar[], const(dchar)[], immutable(dchar)[]))
+    {
+        assert(translate(to!S("hello world"), ['h' : "yellow", 'l' : "42"]) ==
+               to!S("yellowe4242o wor42d"));
+        assert(translate(to!S("hello world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
+               to!S("he\U00010143\U00010143\U00010143\U00010143owl wowlr\U00010143\U00010143d"));
+        assert(translate(to!S("hello \U00010143 world"), ['h' : "yellow", 'l' : "42"]) ==
+               to!S("yellowe4242o \U00010143 wor42d"));
+        assert(translate(to!S("hello \U00010143 world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
+               to!S("he\U00010143\U00010143\U00010143\U00010143owl \U00010143 wowlr\U00010143\U00010143d"));
+        assert(translate(to!S("hello \U00010143 world"), ['h' : ""]) ==
+               to!S("ello \U00010143 world"));
+        assert(translate(to!S("hello \U00010143 world"), ['\U00010143' : ""]) ==
+               to!S("hello  world"));
+        assert(translate(to!S("hello world"), cast(string[dchar])null) == to!S("hello world"));
+
+        foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
+                              wchar[], const(wchar)[], immutable(wchar)[],
+                              dchar[], const(dchar)[], immutable(dchar)[]))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+
+            foreach(R; AliasSeq!(string[dchar], const string[dchar],
+                        immutable string[dchar]))
+            {
+                R tt = ['h' : "yellow", 'l' : "42"];
+                assert(translate(to!S("hello world"), tt, to!T("r")) ==
+                       to!S("yellowe4242o wo42d"));
+                assert(translate(to!S("hello world"), tt, to!T("helo")) ==
+                       to!S(" wrd"));
+                assert(translate(to!S("hello world"), tt, to!T("y42")) ==
+                       to!S("yellowe4242o wor42d"));
+                assert(translate(to!S("hello world"), tt, to!T("hello world")) ==
+                       to!S(""));
+                assert(translate(to!S("hello world"), tt, to!T("42")) ==
+                       to!S("yellowe4242o wor42d"));
+            }
+        }();
+
+        auto s = to!S("hello world");
+        string[dchar] transTable = ['h' : "silly", 'l' : "putty"];
+        static assert(is(typeof(s) == typeof(translate(s, transTable))));
+    }
+    });
+}
+
+/++
+    This is an overload of $(D translate) which takes an existing buffer to write the contents to.
+
+    Params:
+        str        = The original string.
+        transTable = The AA indicating which characters to replace and what to
+                     replace them with.
+        toRemove   = The characters to remove from the string.
+        buffer     = An output range to write the contents to.
+  +/
+void translate(C1, C2 = immutable char, Buffer)(C1[] str,
+                                        in dchar[dchar] transTable,
+                                        const(C2)[] toRemove,
+                                        Buffer buffer)
+    if (isSomeChar!C1 && isSomeChar!C2 && isOutputRange!(Buffer, C1))
+{
+    translateImpl(str, transTable, toRemove, buffer);
+}
+
+///
+@safe pure unittest
+{
+    import std.array : appender;
+    dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
+    auto buffer = appender!(dchar[])();
+    translate("hello world", transTable1, null, buffer);
+    assert(buffer.data == "h5ll7 w7rld");
+
+    buffer.clear();
+    translate("hello world", transTable1, "low", buffer);
+    assert(buffer.data == "h5 rd");
+
+    buffer.clear();
+    string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
+    translate("hello world", transTable2, null, buffer);
+    assert(buffer.data == "h5llorange worangerld");
+}
+
+@safe pure unittest // issue 13018
+{
+    import std.array : appender;
+    immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
+    auto buffer = appender!(dchar[])();
+    translate("hello world", transTable1, null, buffer);
+    assert(buffer.data == "h5ll7 w7rld");
+
+    buffer.clear();
+    translate("hello world", transTable1, "low", buffer);
+    assert(buffer.data == "h5 rd");
+
+    buffer.clear();
+    immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
+    translate("hello world", transTable2, null, buffer);
+    assert(buffer.data == "h5llorange worangerld");
+}
+
+/++ Ditto +/
+void translate(C1, S, C2 = immutable char, Buffer)(C1[] str,
+                                                   in S[dchar] transTable,
+                                                   const(C2)[] toRemove,
+                                                   Buffer buffer)
+    if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2 && isOutputRange!(Buffer, S))
+{
+    translateImpl(str, transTable, toRemove, buffer);
+}
+
+private void translateImpl(C1, T, C2, Buffer)(C1[] str,
+                                      T transTable,
+                                      const(C2)[] toRemove,
+                                      Buffer buffer)
+{
+    bool[dchar] removeTable;
+
+    foreach (dchar c; toRemove)
+        removeTable[c] = true;
+
+    foreach (dchar c; str)
+    {
+        if (c in removeTable)
+            continue;
+
+        auto newC = c in transTable;
+
+        if (newC)
+            put(buffer, *newC);
+        else
+            put(buffer, c);
+    }
+}
+
+/++
+    This is an $(I $(RED ASCII-only)) overload of $(LREF _translate). It
+    will $(I not) work with Unicode. It exists as an optimization for the
+    cases where Unicode processing is not necessary.
+
+    Unlike the other overloads of $(LREF _translate), this one does not take
+    an AA. Rather, it takes a $(D string) generated by $(LREF makeTransTable).
+
+    The array generated by $(D makeTransTable) is $(D 256) elements long such that
+    the index is equal to the ASCII character being replaced and the value is
+    equal to the character that it's being replaced with. Note that translate
+    does not decode any of the characters, so you can actually pass it Extended
+    ASCII characters if you want to (ASCII only actually uses $(D 128)
+    characters), but be warned that Extended ASCII characters are not valid
+    Unicode and therefore will result in a $(D UTFException) being thrown from
+    most other Phobos functions.
+
+    Also, because no decoding occurs, it is possible to use this overload to
+    translate ASCII characters within a proper UTF-8 string without altering the
+    other, non-ASCII characters. It's replacing any code unit greater than
+    $(D 127) with another code unit or replacing any code unit with another code
+    unit greater than $(D 127) which will cause UTF validation issues.
+
+    See_Also:
+        $(LREF tr)
+        $(XREF array, replace)
+
+    Params:
+        str        = The original string.
+        transTable = The string indicating which characters to replace and what
+                     to replace them with. It is generated by $(LREF makeTransTable).
+        toRemove   = The characters to remove from the string.
+  +/
+C[] translate(C = immutable char)(in char[] str, in char[] transTable, in char[] toRemove = null) @trusted pure nothrow
+    if (is(Unqual!C == char))
+in
+{
+    assert(transTable.length == 256);
+}
+body
+{
+    bool[256] remTable = false;
+
+    foreach (char c; toRemove)
+        remTable[c] = true;
+
+    size_t count = 0;
+    foreach (char c; str)
+    {
+        if (!remTable[c])
+            ++count;
+    }
+
+    auto buffer = new char[count];
+
+    size_t i = 0;
+    foreach (char c; str)
+    {
+        if (!remTable[c])
+            buffer[i++] = transTable[c];
+    }
+
+    return cast(C[])(buffer);
+}
+
+
+/**
+ * Do same thing as $(LREF makeTransTable) but allocate the translation table
+ * on the GC heap.
+ *
+ * Use $(LREF makeTransTable) instead.
+ */
+string makeTrans(in char[] from, in char[] to) @trusted pure nothrow
+{
+    return makeTransTable(from, to)[].idup;
+}
+
+///
+@safe pure nothrow unittest
+{
+    auto transTable1 = makeTrans("eo5", "57q");
+    assert(translate("hello world", transTable1) == "h5ll7 w7rld");
+
+    assert(translate("hello world", transTable1, "low") == "h5 rd");
+}
+
+/*******
+ * Construct 256 character translation table, where characters in from[] are replaced
+ * by corresponding characters in to[].
+ *
+ * Params:
+ *      from = array of chars, less than or equal to 256 in length
+ *      to = corresponding array of chars to translate to
+ * Returns:
+ *      translation array
+ */
+
+char[256] makeTransTable(in char[] from, in char[] to) @safe pure nothrow @nogc
+in
+{
+    import std.ascii : isASCII;
+    assert(from.length == to.length);
+    assert(from.length <= 256);
+    foreach (char c; from)
+        assert(isASCII(c));
+    foreach (char c; to)
+        assert(isASCII(c));
+}
+body
+{
+    char[256] result = void;
+
+    foreach (i; 0 .. result.length)
+        result[i] = cast(char)i;
+    foreach (i, c; from)
+        result[c] = to[i];
+    return result;
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (C; AliasSeq!(char, const char, immutable char))
+    {
+        assert(translate!C("hello world", makeTransTable("hl", "q5")) == to!(C[])("qe55o wor5d"));
+
+        auto s = to!(C[])("hello world");
+        auto transTable = makeTransTable("hl", "q5");
+        static assert(is(typeof(s) == typeof(translate!C(s, transTable))));
+    }
+
+    foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[]))
+    {
+        assert(translate(to!S("hello world"), makeTransTable("hl", "q5")) == to!S("qe55o wor5d"));
+        assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5")) ==
+               to!S("qe55o \U00010143 wor5d"));
+        assert(translate(to!S("hello world"), makeTransTable("ol", "1o")) == to!S("heoo1 w1rod"));
+        assert(translate(to!S("hello world"), makeTransTable("", "")) == to!S("hello world"));
+        assert(translate(to!S("hello world"), makeTransTable("12345", "67890")) == to!S("hello world"));
+        assert(translate(to!S("hello \U00010143 world"), makeTransTable("12345", "67890")) ==
+               to!S("hello \U00010143 world"));
+
+        foreach (T; AliasSeq!(char[], const(char)[], immutable(char)[]))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("r")) ==
+                   to!S("qe55o wo5d"));
+            assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5"), to!T("r")) ==
+                   to!S("qe55o \U00010143 wo5d"));
+            assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("helo")) ==
+                   to!S(" wrd"));
+            assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("q5")) ==
+                   to!S("qe55o wor5d"));
+        }();
+    }
+    });
+}
+
+/++
+    This is an $(I $(RED ASCII-only)) overload of $(D translate) which takes an existing buffer to write the contents to.
+
+    Params:
+        str        = The original string.
+        transTable = The string indicating which characters to replace and what
+                     to replace them with. It is generated by $(LREF makeTransTable).
+        toRemove   = The characters to remove from the string.
+        buffer     = An output range to write the contents to.
+  +/
+void translate(C = immutable char, Buffer)(in char[] str, in char[] transTable,
+        in char[] toRemove, Buffer buffer) @trusted pure
+    if (is(Unqual!C == char) && isOutputRange!(Buffer, char))
+in
+{
+    assert(transTable.length == 256);
+}
+body
+{
+    bool[256] remTable = false;
+
+    foreach (char c; toRemove)
+        remTable[c] = true;
+
+    foreach (char c; str)
+    {
+        if (!remTable[c])
+            put(buffer, transTable[c]);
+    }
+}
+
+///
+@safe pure unittest
+{
+    import std.array : appender;
+    auto buffer = appender!(char[])();
+    auto transTable1 = makeTransTable("eo5", "57q");
+    translate("hello world", transTable1, null, buffer);
+    assert(buffer.data == "h5ll7 w7rld");
+
+    buffer.clear();
+    translate("hello world", transTable1, "low", buffer);
+    assert(buffer.data == "h5 rd");
+}
+
+
+/***********************************************
+ * See if character c is in the pattern.
+ * Patterns:
+ *
+ *  A <i>pattern</i> is an array of characters much like a <i>character
+ *  class</i> in regular expressions. A sequence of characters
+ *  can be given, such as "abcde". The '-' can represent a range
+ *  of characters, as "a-e" represents the same pattern as "abcde".
+ *  "a-fA-F0-9" represents all the hex characters.
+ *  If the first character of a pattern is '^', then the pattern
+ *  is negated, i.e. "^0-9" means any character except a digit.
+ *  The functions inPattern, <b>countchars</b>, <b>removeschars</b>,
+ *  and <b>squeeze</b>
+ *  use patterns.
+ *
+ * Note: In the future, the pattern syntax may be improved
+ *  to be more like regular expression character classes.
+ */
+
+bool inPattern(S)(dchar c, in S pattern) @safe pure @nogc if (isSomeString!S)
+{
+    bool result = false;
+    int range = 0;
+    dchar lastc;
+
+    foreach (size_t i, dchar p; pattern)
+    {
+        if (p == '^' && i == 0)
+        {
+            result = true;
+            if (i + 1 == pattern.length)
+                return (c == p);    // or should this be an error?
+        }
+        else if (range)
+        {
+            range = 0;
+            if (lastc <= c && c <= p || c == p)
+                return !result;
+        }
+        else if (p == '-' && i > result && i + 1 < pattern.length)
+        {
+            range = 1;
+            continue;
+        }
+        else if (c == p)
+            return !result;
+        lastc = p;
+    }
+    return result;
+}
+
+
+@safe pure @nogc unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.inPattern.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(inPattern('x', "x") == 1);
+    assert(inPattern('x', "y") == 0);
+    assert(inPattern('x', string.init) == 0);
+    assert(inPattern('x', "^y") == 1);
+    assert(inPattern('x', "yxxy") == 1);
+    assert(inPattern('x', "^yxxy") == 0);
+    assert(inPattern('x', "^abcd") == 1);
+    assert(inPattern('^', "^^") == 0);
+    assert(inPattern('^', "^") == 1);
+    assert(inPattern('^', "a^") == 1);
+    assert(inPattern('x', "a-z") == 1);
+    assert(inPattern('x', "A-Z") == 0);
+    assert(inPattern('x', "^a-z") == 0);
+    assert(inPattern('x', "^A-Z") == 1);
+    assert(inPattern('-', "a-") == 1);
+    assert(inPattern('-', "^A-") == 0);
+    assert(inPattern('a', "z-a") == 1);
+    assert(inPattern('z', "z-a") == 1);
+    assert(inPattern('x', "z-a") == 0);
+    });
+}
+
+
+/***********************************************
+ * See if character c is in the intersection of the patterns.
+ */
+
+bool inPattern(S)(dchar c, S[] patterns) @safe pure @nogc if (isSomeString!S)
+{
+    foreach (string pattern; patterns)
+    {
+        if (!inPattern(c, pattern))
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+
+/********************************************
+ * Count characters in s that match pattern.
+ */
+
+size_t countchars(S, S1)(S s, in S1 pattern) @safe pure @nogc if (isSomeString!S && isSomeString!S1)
+{
+    size_t count;
+    foreach (dchar c; s)
+    {
+        count += inPattern(c, pattern);
+    }
+    return count;
+}
+
+@safe pure @nogc unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.count.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(countchars("abc", "a-c") == 3);
+    assert(countchars("hello world", "or") == 3);
+    });
+}
+
+
+/********************************************
+ * Return string that is s with all characters removed that match pattern.
+ */
+
+S removechars(S)(S s, in S pattern) @safe pure if (isSomeString!S)
+{
+    import std.utf : encode;
+
+    Unqual!(typeof(s[0]))[] r;
+    bool changed = false;
+
+    foreach (size_t i, dchar c; s)
+    {
+        if (inPattern(c, pattern))
+        {
+            if (!changed)
+            {
+                changed = true;
+                r = s[0 .. i].dup;
+            }
+            continue;
+        }
+        if (changed)
+        {
+            encode(r, c);
+        }
+    }
+    if (changed)
+        return r;
+    else
+        return s;
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.removechars.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(removechars("abc", "a-c").length == 0);
+    assert(removechars("hello world", "or") == "hell wld");
+    assert(removechars("hello world", "d") == "hello worl");
+    assert(removechars("hah", "h") == "a");
+    });
+}
+
+
+/***************************************************
+ * Return string where sequences of a character in s[] from pattern[]
+ * are replaced with a single instance of that character.
+ * If pattern is null, it defaults to all characters.
+ */
+
+S squeeze(S)(S s, in S pattern = null)
+{
+    import std.utf : encode, stride;
+
+    Unqual!(typeof(s[0]))[] r;
+    dchar lastc;
+    size_t lasti;
+    int run;
+    bool changed;
+
+    foreach (size_t i, dchar c; s)
+    {
+        if (run && lastc == c)
+        {
+            changed = true;
+        }
+        else if (pattern is null || inPattern(c, pattern))
+        {
+            run = 1;
+            if (changed)
+            {
+                if (r is null)
+                    r = s[0 .. lasti].dup;
+                encode(r, c);
+            }
+            else
+                lasti = i + stride(s, i);
+            lastc = c;
+        }
+        else
+        {
+            run = 0;
+            if (changed)
+            {
+                if (r is null)
+                    r = s[0 .. lasti].dup;
+                encode(r, c);
+            }
+        }
+    }
+    return changed ? ((r is null) ? s[0 .. lasti] : cast(S) r) : s;
+}
+
+@trusted pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.squeeze.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    string s;
+
+    assert(squeeze("hello") == "helo");
+
+    s = "abcd";
+    assert(squeeze(s) is s);
+    s = "xyzz";
+    assert(squeeze(s).ptr == s.ptr); // should just be a slice
+
+    assert(squeeze("hello goodbyee", "oe") == "hello godbye");
+    });
+}
+
+/***************************************************************
+ Finds the position $(D_PARAM pos) of the first character in $(D_PARAM
+ s) that does not match $(D_PARAM pattern) (in the terminology used by
+ $(LINK2 std_string.html,inPattern)). Updates $(D_PARAM s =
+ s[pos..$]). Returns the slice from the beginning of the original
+ (before update) string up to, and excluding, $(D_PARAM pos).
+
+The $(D_PARAM munch) function is mostly convenient for skipping
+certain category of characters (e.g. whitespace) when parsing
+strings. (In such cases, the return value is not used.)
+ */
+
+S1 munch(S1, S2)(ref S1 s, S2 pattern) @safe pure @nogc
+{
+    size_t j = s.length;
+    foreach (i, dchar c; s)
+    {
+        if (!inPattern(c, pattern))
+        {
+            j = i;
+            break;
+        }
+    }
+    scope(exit) s = s[j .. $];
+    return s[0 .. j];
+}
+
+///
+@safe pure @nogc unittest
+{
+    string s = "123abc";
+    string t = munch(s, "0123456789");
+    assert(t == "123" && s == "abc");
+    t = munch(s, "0123456789");
+    assert(t == "" && s == "abc");
+}
+
+@safe pure @nogc unittest
+{
+    string s = "123€abc";
+    string t = munch(s, "0123456789");
+    assert(t == "123" && s == "€abc");
+    t = munch(s, "0123456789");
+    assert(t == "" && s == "€abc");
+    t = munch(s, "£$€¥");
+    assert(t == "€" && s == "abc");
+}
+
+
+/**********************************************
+ * Return string that is the 'successor' to s[].
+ * If the rightmost character is a-zA-Z0-9, it is incremented within
+ * its case or digits. If it generates a carry, the process is
+ * repeated with the one to its immediate left.
+ */
+
+S succ(S)(S s) @safe pure if (isSomeString!S)
+{
+    import std.ascii : isAlphaNum;
+
+    if (s.length && isAlphaNum(s[$ - 1]))
+    {
+        auto r = s.dup;
+        size_t i = r.length - 1;
+
+        while (1)
+        {
+            dchar c = s[i];
+            dchar carry;
+
+            switch (c)
+            {
+            case '9':
+                c = '0';
+                carry = '1';
+                goto Lcarry;
+            case 'z':
+            case 'Z':
+                c -= 'Z' - 'A';
+                carry = c;
+            Lcarry:
+                r[i] = cast(char)c;
+                if (i == 0)
+                {
+                    auto t = new typeof(r[0])[r.length + 1];
+                    t[0] = cast(char) carry;
+                    t[1 .. $] = r[];
+                    return t;
+                }
+                i--;
+                break;
+
+            default:
+                if (isAlphaNum(c))
+                    r[i]++;
+                return r;
+            }
+        }
+    }
+    return s;
+}
+
+///
+@safe pure unittest
+{
+    assert(succ("1") == "2");
+    assert(succ("9") == "10");
+    assert(succ("999") == "1000");
+    assert(succ("zz99") == "aaa00");
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.succ.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(succ(string.init) is null);
+    assert(succ("!@#$%") == "!@#$%");
+    assert(succ("1") == "2");
+    assert(succ("9") == "10");
+    assert(succ("999") == "1000");
+    assert(succ("zz99") == "aaa00");
+    });
+}
+
+
+/++
+    Replaces the characters in $(D str) which are in $(D from) with the
+    the corresponding characters in $(D to) and returns the resulting string.
+
+    $(D tr) is based on
+    $(WEB pubs.opengroup.org/onlinepubs/9699919799/utilities/_tr.html, Posix's tr),
+    though it doesn't do everything that the Posix utility does.
+
+    Params:
+        str       = The original string.
+        from      = The characters to replace.
+        to        = The characters to replace with.
+        modifiers = String containing modifiers.
+
+    Modifiers:
+        $(BOOKTABLE,
+        $(TR $(TD Modifier) $(TD Description))
+        $(TR $(TD $(D 'c')) $(TD Complement the list of characters in $(D from)))
+        $(TR $(TD $(D 'd')) $(TD Removes matching characters with no corresponding
+                              replacement in $(D to)))
+        $(TR $(TD $(D 's')) $(TD Removes adjacent duplicates in the replaced
+                              characters))
+        )
+
+    If the modifier $(D 'd') is present, then the number of characters in
+    $(D to) may be only $(D 0) or $(D 1).
+
+    If the modifier $(D 'd') is $(I not) present, and $(D to) is empty, then
+    $(D to) is taken to be the same as $(D from).
+
+    If the modifier $(D 'd') is $(I not) present, and $(D to) is shorter than
+    $(D from), then $(D to) is extended by replicating the last character in
+    $(D to).
+
+    Both $(D from) and $(D to) may contain ranges using the $(D '-') character
+    (e.g. $(D "a-d") is synonymous with $(D "abcd").) Neither accept a leading
+    $(D '^') as meaning the complement of the string (use the $(D 'c') modifier
+    for that).
+  +/
+C1[] tr(C1, C2, C3, C4 = immutable char)
+       (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null)
+{
+    import std.conv : conv_to = to;
+    import std.utf : decode;
+    import std.array : appender;
+
+    bool mod_c;
+    bool mod_d;
+    bool mod_s;
+
+    foreach (char c; modifiers)
+    {
+        switch (c)
+        {
+        case 'c':   mod_c = 1; break;   // complement
+        case 'd':   mod_d = 1; break;   // delete unreplaced chars
+        case 's':   mod_s = 1; break;   // squeeze duplicated replaced chars
+        default:    assert(0);
+        }
+    }
+
+    if (to.empty && !mod_d)
+        to = conv_to!(typeof(to))(from);
+
+    auto result = appender!(C1[])();
+    bool modified;
+    dchar lastc;
+
+    foreach (dchar c; str)
+    {
+        dchar lastf;
+        dchar lastt;
+        dchar newc;
+        int n = 0;
+
+        for (size_t i = 0; i < from.length; )
+        {
+            dchar f = decode(from, i);
+            if (f == '-' && lastf != dchar.init && i < from.length)
+            {
+                dchar nextf = decode(from, i);
+                if (lastf <= c && c <= nextf)
+                {
+                    n += c - lastf - 1;
+                    if (mod_c)
+                        goto Lnotfound;
+                    goto Lfound;
+                }
+                n += nextf - lastf;
+                lastf = lastf.init;
+                continue;
+            }
+
+            if (c == f)
+            {   if (mod_c)
+                    goto Lnotfound;
+                goto Lfound;
+            }
+            lastf = f;
+            n++;
+        }
+        if (!mod_c)
+            goto Lnotfound;
+        n = 0;          // consider it 'found' at position 0
+
+      Lfound:
+
+        // Find the nth character in to[]
+        dchar nextt;
+        for (size_t i = 0; i < to.length; )
+        {   dchar t = decode(to, i);
+            if (t == '-' && lastt != dchar.init && i < to.length)
+            {
+                nextt = decode(to, i);
+                n -= nextt - lastt;
+                if (n < 0)
+                {
+                    newc = nextt + n + 1;
+                    goto Lnewc;
+                }
+                lastt = dchar.init;
+                continue;
+            }
+            if (n == 0)
+            {   newc = t;
+                goto Lnewc;
+            }
+            lastt = t;
+            nextt = t;
+            n--;
+        }
+        if (mod_d)
+            continue;
+        newc = nextt;
+
+      Lnewc:
+        if (mod_s && modified && newc == lastc)
+            continue;
+        result.put(newc);
+        assert(newc != dchar.init);
+        modified = true;
+        lastc = newc;
+        continue;
+
+      Lnotfound:
+        result.put(c);
+        lastc = c;
+        modified = false;
+    }
+
+    return result.data;
+}
+
+unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("std.string.tr.unittest\n");
+    import std.algorithm : equal;
+
+    // Complete list of test types; too slow to test'em all
+    // alias TestTypes = AliasSeq!(
+    //          char[], const( char)[], immutable( char)[],
+    //         wchar[], const(wchar)[], immutable(wchar)[],
+    //         dchar[], const(dchar)[], immutable(dchar)[]);
+
+    // Reduced list of test types
+    alias TestTypes = AliasSeq!(char[], const(wchar)[], immutable(dchar)[]);
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; TestTypes)
+    {
+        foreach (T; TestTypes)
+        {
+            foreach (U; TestTypes)
+            {
+                assert(equal(tr(to!S("abcdef"), to!T("cd"), to!U("CD")), "abCDef"));
+                assert(equal(tr(to!S("abcdef"), to!T("b-d"), to!U("B-D")), "aBCDef"));
+                assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-Dx")), "aBCDefgx"));
+                assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-CDx")), "aBCDefgx"));
+                assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-BCDx")), "aBCDefgx"));
+                assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U("*"), to!S("c")), "****ef"));
+                assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U(""), to!T("d")), "abcd"));
+                assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U(""), to!U("s")), "helo godbye"));
+                assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U("x"), "s"), "hex gxdbye"));
+                assert(equal(tr(to!S("14-Jul-87"), to!T("a-zA-Z"), to!U(" "), "cs"), " Jul "));
+                assert(equal(tr(to!S("Abc"), to!T("AAA"), to!U("XYZ")), "Xbc"));
+            }
+        }
+
+        auto s = to!S("hello world");
+        static assert(is(typeof(s) == typeof(tr(s, "he", "if"))));
+    }
+    });
+}
+
+/**
+ * Takes a string $(D s) and determines if it represents a number. This function
+ * also takes an optional parameter, $(D bAllowSep), which will accept the
+ * separator characters $(D ',') and $(D '__') within the string. But these
+ * characters should be stripped from the string before using any
+ * of the conversion functions like $(D to!int()), $(D to!float()), and etc
+ * else an error will occur.
+ *
+ * Also please note, that no spaces are allowed within the string
+ * anywhere whether it's a leading, trailing, or embedded space(s),
+ * thus they too must be stripped from the string before using this
+ * function, or any of the conversion functions.
+ *
+ * Params:
+ *     s = the string to check
+ *     bAllowSep = accept separator characters or not
+ *
+ * Returns:
+ *     $(D bool)
+ */
+
+bool isNumeric(const(char)[] s, in bool bAllowSep = false) @safe pure
+{
+    import std.algorithm : among;
+
+    immutable iLen = s.length;
+    if (iLen == 0)
+        return false;
+
+    // Check for NaN (Not a Number) and for Infinity
+    if (s.among!((a, b) => icmp(a, b) == 0)
+            ("nan", "nani", "nan+nani", "inf", "-inf"))
+        return true;
+
+    immutable j = s[0].among!('-', '+')() != 0;
+    bool bDecimalPoint, bExponent, bComplex, sawDigits;
+
+    for (size_t i = j; i < iLen; i++)
+    {
+        immutable c = s[i];
+
+        // Digits are good, continue checking
+        // with the popFront character... ;)
+        if (c >= '0' && c <= '9')
+        {
+            sawDigits = true;
+            continue;
+        }
+
+        // Check for the complex type, and if found
+        // reset the flags for checking the 2nd number.
+        if (c == '+')
+        {
+            if (!i)
+                return false;
+            bDecimalPoint = false;
+            bExponent = false;
+            bComplex = true;
+            sawDigits = false;
+            continue;
+        }
+
+        // Allow only one exponent per number
+        if (c.among!('e', 'E')())
+        {
+            // A 2nd exponent found, return not a number
+            if (bExponent || i + 1 >= iLen)
+                return false;
+            // Look forward for the sign, and if
+            // missing then this is not a number.
+            if (!s[i + 1].among!('-', '+')())
+                return false;
+            bExponent = true;
+            i++;
+            continue;
+        }
+        // Allow only one decimal point per number to be used
+        if (c == '.' )
+        {
+            // A 2nd decimal point found, return not a number
+            if (bDecimalPoint)
+                return false;
+            bDecimalPoint = true;
+            continue;
+        }
+        // Check for ending literal characters: "f,u,l,i,ul,fi,li",
+        // and whether they're being used with the correct datatype.
+        if (i == iLen - 2)
+        {
+            if (!sawDigits)
+                return false;
+            // Integer Whole Number
+            if (icmp(s[i..iLen], "ul") == 0 &&
+                    (!bDecimalPoint && !bExponent && !bComplex))
+                return true;
+            // Floating-Point Number
+            if (s[i..iLen].among!((a, b) => icmp(a, b) == 0)("fi", "li") &&
+                    (bDecimalPoint || bExponent || bComplex))
+                return true;
+            if (icmp(s[i..iLen], "ul") == 0 &&
+                    (bDecimalPoint || bExponent || bComplex))
+                return false;
+            // Could be a Integer or a Float, thus
+            // all these suffixes are valid for both
+            return s[i..iLen].among!((a, b) => icmp(a, b) == 0)
+                ("ul", "fi", "li") != 0;
+        }
+        if (i == iLen - 1)
+        {
+            if (!sawDigits)
+                return false;
+            // Integer Whole Number
+            if (c.among!('u', 'l', 'U', 'L')() &&
+                   (!bDecimalPoint && !bExponent && !bComplex))
+                return true;
+            // Check to see if the last character in the string
+            // is the required 'i' character
+            if (bComplex)
+                return c.among!('i', 'I')() != 0;
+            // Floating-Point Number
+            return c.among!('l', 'L', 'f', 'F', 'i', 'I')() != 0;
+        }
+        // Check if separators are allowed to be in the numeric string
+        if (!bAllowSep || !c.among!('_', ',')())
+            return false;
+    }
+
+    return sawDigits;
+}
+
+/**
+ * Integer Whole Number: (byte, ubyte, short, ushort, int, uint, long, and ulong)
+ * ['+'|'-']digit(s)[U|L|UL]
+ */
+@safe pure unittest
+{
+    assert(isNumeric("123"));
+    assert(isNumeric("123UL"));
+    assert(isNumeric("123L"));
+    assert(isNumeric("+123U"));
+    assert(isNumeric("-123L"));
+}
+
+/**
+ * Floating-Point Number: (float, double, real, ifloat, idouble, and ireal)
+ * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
+ *      or [nan|nani|inf|-inf]
+ */
+@safe pure unittest
+{
+    assert(isNumeric("+123"));
+    assert(isNumeric("-123.01"));
+    assert(isNumeric("123.3e-10f"));
+    assert(isNumeric("123.3e-10fi"));
+    assert(isNumeric("123.3e-10L"));
+
+    assert(isNumeric("nan"));
+    assert(isNumeric("nani"));
+    assert(isNumeric("-inf"));
+}
+
+/**
+ * Floating-Point Number: (cfloat, cdouble, and creal)
+ * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][+]
+ *         [digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
+ *      or [nan|nani|nan+nani|inf|-inf]
+ */
+@safe pure unittest
+{
+    assert(isNumeric("-123e-1+456.9e-10Li"));
+    assert(isNumeric("+123e+10+456i"));
+    assert(isNumeric("123+456"));
+}
+
+@safe pure unittest
+{
+    assert(!isNumeric("F"));
+    assert(!isNumeric("L"));
+    assert(!isNumeric("U"));
+    assert(!isNumeric("i"));
+    assert(!isNumeric("fi"));
+    assert(!isNumeric("ul"));
+    assert(!isNumeric("li"));
+    assert(!isNumeric("."));
+    assert(!isNumeric("-"));
+    assert(!isNumeric("+"));
+    assert(!isNumeric("e-"));
+    assert(!isNumeric("e+"));
+    assert(!isNumeric(".f"));
+    assert(!isNumeric("e+f"));
+}
+
+@trusted unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("isNumeric(in string, bool = false).unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    // Test the isNumeric(in string) function
+    assert(isNumeric("1") == true );
+    assert(isNumeric("1.0") == true );
+    assert(isNumeric("1e-1") == true );
+    assert(isNumeric("12345xxxx890") == false );
+    assert(isNumeric("567L") == true );
+    assert(isNumeric("23UL") == true );
+    assert(isNumeric("-123..56f") == false );
+    assert(isNumeric("12.3.5.6") == false );
+    assert(isNumeric(" 12.356") == false );
+    assert(isNumeric("123 5.6") == false );
+    assert(isNumeric("1233E-1+1.0e-1i") == true );
+
+    assert(isNumeric("123.00E-5+1234.45E-12Li") == true);
+    assert(isNumeric("123.00e-5+1234.45E-12iL") == false);
+    assert(isNumeric("123.00e-5+1234.45e-12uL") == false);
+    assert(isNumeric("123.00E-5+1234.45e-12lu") == false);
+
+    assert(isNumeric("123fi") == true);
+    assert(isNumeric("123li") == true);
+    assert(isNumeric("--123L") == false);
+    assert(isNumeric("+123.5UL") == false);
+    assert(isNumeric("123f") == true);
+    assert(isNumeric("123.u") == false);
+
+  // @@@BUG@@ to!string(float) is not CTFEable.
+  // Related: formatValue(T) if (is(FloatingPointTypeOf!T))
+  if (!__ctfe)
+  {
+    assert(isNumeric(to!string(real.nan)) == true);
+    assert(isNumeric(to!string(-real.infinity)) == true);
+    assert(isNumeric(to!string(123e+2+1234.78Li)) == true);
+  }
+
+    string s = "$250.99-";
+    assert(isNumeric(s[1..s.length - 2]) == true);
+    assert(isNumeric(s) == false);
+    assert(isNumeric(s[0..s.length - 1]) == false);
+    });
+
+    assert(!isNumeric("-"));
+    assert(!isNumeric("+"));
+}
+
+
+/*****************************
+ * Soundex algorithm.
+ *
+ * The Soundex algorithm converts a word into 4 characters
+ * based on how the word sounds phonetically. The idea is that
+ * two spellings that sound alike will have the same Soundex
+ * value, which means that Soundex can be used for fuzzy matching
+ * of names.
+ *
+ * Params:
+ *  str = String or InputRange to convert to Soundex representation.
+ *
+ * Returns:
+ *  The four character array with the Soundex result in it.
+ *  The array has zero's in it if there is no Soundex representation for the string.
+ *
+ * See_Also:
+ *  $(LINK2 http://en.wikipedia.org/wiki/Soundex, Wikipedia),
+ *  $(LUCKY The Soundex Indexing System)
+ *  $(LREF soundex)
+ *
+ * Bugs:
+ *  Only works well with English names.
+ *  There are other arguably better Soundex algorithms,
+ *  but this one is the standard one.
+ */
+char[4] soundexer(Range)(Range str)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    alias C = Unqual!(ElementEncodingType!Range);
+
+    static immutable dex =
+        // ABCDEFGHIJKLMNOPQRSTUVWXYZ
+          "01230120022455012623010202";
+
+    char[4] result = void;
+    size_t b = 0;
+    C lastc;
+    foreach (C c; str)
+    {
+        if (c >= 'a' && c <= 'z')
+            c -= 'a' - 'A';
+        else if (c >= 'A' && c <= 'Z')
+        {
+        }
+        else
+        {
+            lastc = lastc.init;
+            continue;
+        }
+        if (b == 0)
+        {
+            result[0] = cast(char)c;
+            b++;
+            lastc = dex[c - 'A'];
+        }
+        else
+        {
+            if (c == 'H' || c == 'W')
+                continue;
+            if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
+                lastc = lastc.init;
+            c = dex[c - 'A'];
+            if (c != '0' && c != lastc)
+            {
+                result[b] = cast(char)c;
+                b++;
+                lastc = c;
+            }
+            if (b == 4)
+                goto Lret;
+        }
+    }
+    if (b == 0)
+        result[] = 0;
+    else
+        result[b .. 4] = '0';
+  Lret:
+    return result;
+}
+
+char[4] soundexer(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return soundexer!(StringTypeOf!Range)(str);
+}
+
+/*****************************
+ * Like $(LREF soundexer), but with different parameters
+ * and return value.
+ *
+ * Params:
+ *  str = String to convert to Soundex representation.
+ *  buffer = Optional 4 char array to put the resulting Soundex
+ *      characters into. If null, the return value
+ *      buffer will be allocated on the heap.
+ * Returns:
+ *  The four character array with the Soundex result in it.
+ *  Returns null if there is no Soundex representation for the string.
+ * See_Also:
+ *  $(LREF soundexer)
+ */
+char[] soundex(const(char)[] str, char[] buffer = null)
+    @safe pure nothrow
+in
+{
+    assert(!buffer.ptr || buffer.length >= 4);
+}
+out (result)
+{
+    if (result.ptr)
+    {
+        assert(result.length == 4);
+        assert(result[0] >= 'A' && result[0] <= 'Z');
+        foreach (char c; result[1 .. 4])
+            assert(c >= '0' && c <= '6');
+    }
+}
+body
+{
+    char[4] result = soundexer(str);
+    if (result[0] == 0)
+        return null;
+    if (!buffer.ptr)
+        buffer = new char[4];
+    buffer[] = result[];
+    return buffer;
+}
+
+
+@safe pure nothrow unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    char[4] buffer;
+
+    assert(soundex(null) == null);
+    assert(soundex("") == null);
+    assert(soundex("0123^&^^**&^") == null);
+    assert(soundex("Euler") == "E460");
+    assert(soundex(" Ellery ") == "E460");
+    assert(soundex("Gauss") == "G200");
+    assert(soundex("Ghosh") == "G200");
+    assert(soundex("Hilbert") == "H416");
+    assert(soundex("Heilbronn") == "H416");
+    assert(soundex("Knuth") == "K530");
+    assert(soundex("Kant", buffer) == "K530");
+    assert(soundex("Lloyd") == "L300");
+    assert(soundex("Ladd") == "L300");
+    assert(soundex("Lukasiewicz", buffer) == "L222");
+    assert(soundex("Lissajous") == "L222");
+    assert(soundex("Robert") == "R163");
+    assert(soundex("Rupert") == "R163");
+    assert(soundex("Rubin") == "R150");
+    assert(soundex("Washington") == "W252");
+    assert(soundex("Lee") == "L000");
+    assert(soundex("Gutierrez") == "G362");
+    assert(soundex("Pfister") == "P236");
+    assert(soundex("Jackson") == "J250");
+    assert(soundex("Tymczak") == "T522");
+    assert(soundex("Ashcraft") == "A261");
+
+    assert(soundex("Woo") == "W000");
+    assert(soundex("Pilgrim") == "P426");
+    assert(soundex("Flingjingwaller") == "F452");
+    assert(soundex("PEARSE") == "P620");
+    assert(soundex("PIERCE") == "P620");
+    assert(soundex("Price") == "P620");
+    assert(soundex("CATHY") == "C300");
+    assert(soundex("KATHY") == "K300");
+    assert(soundex("Jones") == "J520");
+    assert(soundex("johnsons") == "J525");
+    assert(soundex("Hardin") == "H635");
+    assert(soundex("Martinez") == "M635");
+
+    import std.utf;
+    assert(soundexer("Martinez".byChar ) == "M635");
+    assert(soundexer("Martinez".byWchar) == "M635");
+    assert(soundexer("Martinez".byDchar) == "M635");
+    });
+}
+
+unittest
+{
+    assert(testAliasedString!soundexer("Martinez"));
+}
+
+
+/***************************************************
+ * Construct an associative array consisting of all
+ * abbreviations that uniquely map to the strings in values.
+ *
+ * This is useful in cases where the user is expected to type
+ * in one of a known set of strings, and the program will helpfully
+ * autocomplete the string once sufficient characters have been
+ * entered that uniquely identify it.
+ */
+
+string[string] abbrev(string[] values) @safe pure
+{
+    import std.algorithm : sort;
+
+    string[string] result;
+
+    // Make a copy when sorting so we follow COW principles.
+    values = values.dup;
+    sort(values);
+
+    size_t values_length = values.length;
+    size_t lasti = values_length;
+    size_t nexti;
+
+    string nv;
+    string lv;
+
+    for (size_t i = 0; i < values_length; i = nexti)
+    {
+        string value = values[i];
+
+        // Skip dups
+        for (nexti = i + 1; nexti < values_length; nexti++)
+        {
+            nv = values[nexti];
+            if (value != values[nexti])
+                break;
+        }
+
+        import std.utf : stride;
+
+        for (size_t j = 0; j < value.length; j += stride(value, j))
+        {
+            string v = value[0 .. j];
+
+            if ((nexti == values_length || j > nv.length || v != nv[0 .. j]) &&
+                (lasti == values_length || j > lv.length || v != lv[0 .. j]))
+            {
+                result[v] = value;
+            }
+        }
+        result[value] = value;
+        lasti = i;
+        lv = value;
+    }
+
+    return result;
+}
+
+///
+unittest
+{
+    import std.string;
+
+    static string[] list = [ "food", "foxy" ];
+    auto abbrevs = std.string.abbrev(list);
+    assert(abbrevs == ["fox": "foxy", "food": "food",
+                       "foxy": "foxy", "foo": "food"]);
+}
+
+
+@trusted pure unittest
+{
+    import std.conv : to;
+    import std.algorithm : sort;
+
+    debug(string) trustedPrintf("string.abbrev.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    string[] values;
+    values ~= "hello";
+    values ~= "hello";
+    values ~= "he";
+
+    string[string] r;
+
+    r = abbrev(values);
+    auto keys = r.keys.dup;
+    sort(keys);
+
+    assert(keys.length == 4);
+    assert(keys[0] == "he");
+    assert(keys[1] == "hel");
+    assert(keys[2] == "hell");
+    assert(keys[3] == "hello");
+
+    assert(r[keys[0]] == "he");
+    assert(r[keys[1]] == "hello");
+    assert(r[keys[2]] == "hello");
+    assert(r[keys[3]] == "hello");
+    });
+}
+
+
+/******************************************
+ * Compute _column number at the end of the printed form of the string,
+ * assuming the string starts in the leftmost _column, which is numbered
+ * starting from 0.
+ *
+ * Tab characters are expanded into enough spaces to bring the _column number
+ * to the next multiple of tabsize.
+ * If there are multiple lines in the string, the _column number of the last
+ * line is returned.
+ *
+ * Params:
+ *    str = string or InputRange to be analyzed
+ *    tabsize = number of columns a tab character represents
+ *
+ * Returns:
+ *    column number
+ */
+
+size_t column(Range)(Range str, in size_t tabsize = 8)
+    if ((isInputRange!Range && isSomeChar!(Unqual!(ElementEncodingType!Range)) ||
+         isNarrowString!Range) &&
+        !isConvertibleToString!Range)
+{
+    static if (is(Unqual!(ElementEncodingType!Range) == char))
+    {
+        // decoding needed for chars
+        import std.utf: byDchar;
+
+        return str.byDchar.column(tabsize);
+    }
+    else
+    {
+        // decoding not needed for wchars and dchars
+        import std.uni : lineSep, paraSep, nelSep;
+
+        size_t column;
+
+        foreach (const c; str)
+        {
+            switch (c)
+            {
+                case '\t':
+                    column = (column + tabsize) / tabsize * tabsize;
+                    break;
+
+                case '\r':
+                case '\n':
+                case paraSep:
+                case lineSep:
+                case nelSep:
+                    column = 0;
+                    break;
+
+                default:
+                    column++;
+                    break;
+            }
+        }
+        return column;
+    }
+}
+
+///
+unittest
+{
+    import std.utf : byChar, byWchar, byDchar;
+
+    assert(column("1234 ") == 5);
+    assert(column("1234 "w) == 5);
+    assert(column("1234 "d) == 5);
+
+    assert(column("1234 ".byChar()) == 5);
+    assert(column("1234 "w.byWchar()) == 5);
+    assert(column("1234 "d.byDchar()) == 5);
+
+    // Tab stops are set at 8 spaces by default; tab characters insert enough
+    // spaces to bring the column position to the next multiple of 8.
+    assert(column("\t") == 8);
+    assert(column("1\t") == 8);
+    assert(column("\t1") == 9);
+    assert(column("123\t") == 8);
+
+    // Other tab widths are possible by specifying it explicitly:
+    assert(column("\t", 4) == 4);
+    assert(column("1\t", 4) == 4);
+    assert(column("\t1", 4) == 5);
+    assert(column("123\t", 4) == 4);
+
+    // New lines reset the column number.
+    assert(column("abc\n") == 0);
+    assert(column("abc\n1") == 1);
+    assert(column("abcdefg\r1234") == 4);
+    assert(column("abc\u20281") == 1);
+    assert(column("abc\u20291") == 1);
+    assert(column("abc\u00851") == 1);
+    assert(column("abc\u00861") == 5);
+}
+
+size_t column(Range)(auto ref Range str, in size_t tabsize = 8)
+    if (isConvertibleToString!Range)
+{
+    return column!(StringTypeOf!Range)(str, tabsize);
+}
+
+unittest
+{
+    assert(testAliasedString!column("abc\u00861"));
+}
+
+@safe @nogc unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.column.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(column(string.init) == 0);
+    assert(column("") == 0);
+    assert(column("\t") == 8);
+    assert(column("abc\t") == 8);
+    assert(column("12345678\t") == 16);
+    });
+}
+
+/******************************************
+ * Wrap text into a paragraph.
+ *
+ * The input text string s is formed into a paragraph
+ * by breaking it up into a sequence of lines, delineated
+ * by \n, such that the number of columns is not exceeded
+ * on each line.
+ * The last line is terminated with a \n.
+ * Params:
+ *  s = text string to be wrapped
+ *  columns = maximum number of _columns in the paragraph
+ *  firstindent = string used to _indent first line of the paragraph
+ *  indent = string to use to _indent following lines of the paragraph
+ *  tabsize = column spacing of tabs in firstindent[] and indent[]
+ * Returns:
+ *  resulting paragraph as an allocated string
+ */
+
+S wrap(S)(S s, in size_t columns = 80, S firstindent = null,
+        S indent = null, in size_t tabsize = 8) if (isSomeString!S)
+{
+    import std.uni: isWhite;
+    typeof(s.dup) result;
+    bool inword;
+    bool first = true;
+    size_t wordstart;
+
+    const indentcol = column(indent, tabsize);
+
+    result.length = firstindent.length + s.length;
+    result.length = firstindent.length;
+    result[] = firstindent[];
+    auto col = column(firstindent, tabsize);
+    foreach (size_t i, dchar c; s)
+    {
+        if (isWhite(c))
+        {
+            if (inword)
+            {
+                if (first)
+                {
+                }
+                else if (col + 1 + (i - wordstart) > columns)
+                {
+                    result ~= '\n';
+                    result ~= indent;
+                    col = indentcol;
+                }
+                else
+                {
+                    result ~= ' ';
+                    col += 1;
+                }
+                result ~= s[wordstart .. i];
+                col += i - wordstart;
+                inword = false;
+                first = false;
+            }
+        }
+        else
+        {
+            if (!inword)
+            {
+                wordstart = i;
+                inword = true;
+            }
+        }
+    }
+
+    if (inword)
+    {
+        if (col + 1 + (s.length - wordstart) >= columns)
+        {
+            result ~= '\n';
+            result ~= indent;
+        }
+        else if (result.length != firstindent.length)
+            result ~= ' ';
+        result ~= s[wordstart .. s.length];
+    }
+    result ~= '\n';
+
+    return result;
+}
+
+///
+@safe pure unittest
+{
+    assert(wrap("a short string", 7) == "a short\nstring\n");
+
+    // wrap will not break inside of a word, but at the next space
+    assert(wrap("a short string", 4) == "a\nshort\nstring\n");
+
+    assert(wrap("a short string", 7, "\t") == "\ta\nshort\nstring\n");
+    assert(wrap("a short string", 7, "\t", "    ") == "\ta\n    short\n    string\n");
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.wrap.unittest\n");
+
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(wrap(string.init) == "\n");
+    assert(wrap(" a b   df ") == "a b df\n");
+    assert(wrap(" a b   df ", 3) == "a b\ndf\n");
+    assert(wrap(" a bc   df ", 3) == "a\nbc\ndf\n");
+    assert(wrap(" abcd   df ", 3) == "abcd\ndf\n");
+    assert(wrap("x") == "x\n");
+    assert(wrap("u u") == "u u\n");
+    assert(wrap("abcd", 3) == "\nabcd\n");
+    assert(wrap("a de", 10, "\t", "   ", 8) == "\ta\n   de\n");
+    });
+}
+
+/******************************************
+ * Removes one level of indentation from a multi-line string.
+ *
+ * This uniformly outdents the text as much as possible.
+ * Whitespace-only lines are always converted to blank lines.
+ *
+ * Does not allocate memory if it does not throw.
+ *
+ * Params:
+ *     str = multi-line string
+ *
+ * Returns:
+ *      outdented string
+ *
+ * Throws:
+ *     StringException if indentation is done with different sequences
+ *     of whitespace characters.
+ */
+S outdent(S)(S str) @safe pure if(isSomeString!S)
+{
+    return str.splitLines(KeepTerminator.yes).outdent().join();
+}
+
+///
+@safe pure unittest
+{
+    enum pretty = q{
+       import std.stdio;
+       void main() {
+           writeln("Hello");
+       }
+    }.outdent();
+
+    enum ugly = q{
+import std.stdio;
+void main() {
+    writeln("Hello");
+}
+};
+
+    assert(pretty == ugly);
+}
+
+
+/******************************************
+ * Removes one level of indentation from an array of single-line strings.
+ *
+ * This uniformly outdents the text as much as possible.
+ * Whitespace-only lines are always converted to blank lines.
+ *
+ * Params:
+ *     lines = array of single-line strings
+ *
+ * Returns:
+ *      lines[] is rewritten in place with outdented lines
+ *
+ * Throws:
+ *     StringException if indentation is done with different sequences
+ *     of whitespace characters.
+ */
+S[] outdent(S)(S[] lines) @safe pure if(isSomeString!S)
+{
+    import std.algorithm : startsWith;
+
+    if (lines.empty)
+    {
+        return null;
+    }
+
+    static S leadingWhiteOf(S str)
+    {
+        return str[ 0 .. $ - stripLeft(str).length ];
+    }
+
+    S shortestIndent;
+    foreach (ref line; lines)
+    {
+        auto stripped = line.stripLeft();
+
+        if (stripped.empty)
+        {
+            line = line[line.chomp().length .. $];
+        }
+        else
+        {
+            auto indent = leadingWhiteOf(line);
+
+            // Comparing number of code units instead of code points is OK here
+            // because this function throws upon inconsistent indentation.
+            if (shortestIndent is null || indent.length < shortestIndent.length)
+            {
+                if (indent.empty)
+                    return lines;
+                shortestIndent = indent;
+            }
+        }
+    }
+
+    foreach (ref line; lines)
+    {
+        auto stripped = line.stripLeft();
+
+        if (stripped.empty)
+        {
+            // Do nothing
+        }
+        else if (line.startsWith(shortestIndent))
+        {
+            line = line[shortestIndent.length .. $];
+        }
+        else
+        {
+            throw new StringException("outdent: Inconsistent indentation");
+        }
+    }
+
+    return lines;
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+
+    debug(string) trustedPrintf("string.outdent.unittest\n");
+
+    template outdent_testStr(S)
+    {
+        enum S outdent_testStr =
+"
+ \t\tX
+ \t\U00010143X
+ \t\t
+
+ \t\t\tX
+\t ";
+    }
+
+    template outdent_expected(S)
+    {
+        enum S outdent_expected =
+"
+\tX
+\U00010143X
+
+
+\t\tX
+";
+    }
+
+    import std.exception;
+    assertCTFEable!(
+    {
+
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        enum S blank = "";
+        assert(blank.outdent() == blank);
+        static assert(blank.outdent() == blank);
+
+        enum S testStr1  = " \n \t\n ";
+        enum S expected1 = "\n\n";
+        assert(testStr1.outdent() == expected1);
+        static assert(testStr1.outdent() == expected1);
+
+        assert(testStr1[0..$-1].outdent() == expected1);
+        static assert(testStr1[0..$-1].outdent() == expected1);
+
+        enum S testStr2  = "a\n \t\nb";
+        assert(testStr2.outdent() == testStr2);
+        static assert(testStr2.outdent() == testStr2);
+
+        enum S testStr3 =
+"
+ \t\tX
+ \t\U00010143X
+ \t\t
+
+ \t\t\tX
+\t ";
+
+        enum S expected3 =
+"
+\tX
+\U00010143X
+
+
+\t\tX
+";
+        assert(testStr3.outdent() == expected3);
+        static assert(testStr3.outdent() == expected3);
+
+        enum testStr4 = "  X\r  X\n  X\r\n  X\u2028  X\u2029  X";
+        enum expected4 = "X\rX\nX\r\nX\u2028X\u2029X";
+        assert(testStr4.outdent() == expected4);
+        static assert(testStr4.outdent() == expected4);
+
+        enum testStr5  = testStr4[0..$-1];
+        enum expected5 = expected4[0..$-1];
+        assert(testStr5.outdent() == expected5);
+        static assert(testStr5.outdent() == expected5);
+
+        enum testStr6 = "  \r  \n  \r\n  \u2028  \u2029";
+        enum expected6 = "\r\n\r\n\u2028\u2029";
+        assert(testStr6.outdent() == expected6);
+        static assert(testStr6.outdent() == expected6);
+
+        enum testStr7 = " a \n b ";
+        enum expected7 = "a \nb ";
+        assert(testStr7.outdent() == expected7);
+        static assert(testStr7.outdent() == expected7);
+    }
+    });
+}
+
+/** Assume the given array of integers $(D arr) is a well-formed UTF string and
+return it typed as a UTF string.
+
+$(D ubyte) becomes $(D char), $(D ushort) becomes $(D wchar) and $(D uint)
+becomes $(D dchar). Type qualifiers are preserved.
+
+Params:
+    arr = array of bytes, ubytes, shorts, ushorts, ints, or uints
+
+Returns:
+    arr retyped as an array of chars, wchars, or dchars
+
+See_Also: $(LREF representation)
+*/
+auto assumeUTF(T)(T[] arr) pure
+    if(staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1)
+{
+    import std.utf : validate;
+    alias ToUTFType(U) = AliasSeq!(char, wchar, dchar)[U.sizeof / 2];
+    auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[])arr;
+    debug validate(asUTF);
+    return asUTF;
+}
+
+///
+@safe pure unittest
+{
+    string a = "Hölo World";
+    immutable(ubyte)[] b = a.representation;
+    string c = b.assumeUTF;
+
+    assert(a == c);
+}
+
+pure unittest
+{
+    import std.algorithm : equal;
+    foreach(T; AliasSeq!(char[], wchar[], dchar[]))
+    {
+        immutable T jti = "Hello World";
+        T jt = jti.dup;
+
+        static if(is(T == char[]))
+        {
+            auto gt = cast(ubyte[])jt;
+            auto gtc = cast(const(ubyte)[])jt;
+            auto gti = cast(immutable(ubyte)[])jt;
+        }
+        else static if(is(T == wchar[]))
+        {
+            auto gt = cast(ushort[])jt;
+            auto gtc = cast(const(ushort)[])jt;
+            auto gti = cast(immutable(ushort)[])jt;
+        }
+        else static if(is(T == dchar[]))
+        {
+            auto gt = cast(uint[])jt;
+            auto gtc = cast(const(uint)[])jt;
+            auto gti = cast(immutable(uint)[])jt;
+        }
+
+        auto ht = assumeUTF(gt);
+        auto htc = assumeUTF(gtc);
+        auto hti = assumeUTF(gti);
+        assert(equal(jt, ht));
+        assert(equal(jt, htc));
+        assert(equal(jt, hti));
+    }
+}
+
diff --git a/libphobos/src/std/system.d b/libphobos/src/std/system.d
new file mode 100644
index 00000000000..5e33342d7f3
--- /dev/null
+++ b/libphobos/src/std/system.d
@@ -0,0 +1,77 @@
+// Written in the D programming language.
+
+/**
+ * Information about the target operating system, environment, and CPU.
+ *
+ * Macros:
+ *      WIKI = Phobos/StdSystem
+ *
+ *  Copyright: Copyright Digital Mars 2000 - 2011
+ *  License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ *  Authors:   $(WEB digitalmars.com, Walter Bright) and Jonathan M Davis
+ *  Source:    $(PHOBOSSRC std/_system.d)
+ */
+module std.system;
+
+immutable
+{
+    /++
+        Operating system.
+
+        Note:
+            This is for cases where you need a value representing the OS at
+            runtime. If you're doing something which should compile differently
+            on different OSes, then please use $(D version(Windows)),
+            $(D version(linux)), etc.
+
+        See_Also:
+            $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions)
+      +/
+    enum OS
+    {
+        win32 = 1, /// Microsoft 32 bit Windows systems
+        win64,     /// Microsoft 64 bit Windows systems
+        linux,     /// All Linux Systems
+        osx,       /// Mac OS X
+        freeBSD,   /// FreeBSD
+        netBSD,    /// NetBSD
+        solaris,   /// Solaris
+        android,   /// Android
+        otherPosix /// Other Posix Systems
+    }
+
+    /// The OS that the program was compiled for.
+    version(Win32)        OS os = OS.win32;
+    else version(Win64)   OS os = OS.win64;
+    else version(Android) OS os = OS.android;
+    else version(linux)   OS os = OS.linux;
+    else version(OSX)     OS os = OS.osx;
+    else version(FreeBSD) OS os = OS.freeBSD;
+    else version(NetBSD)  OS os = OS.netBSD;
+    else version(Posix)   OS os = OS.otherPosix;
+    else static assert(0, "Unknown OS.");
+
+    /++
+        Byte order endianness.
+
+        Note:
+            This is intended for cases where you need to deal with endianness at
+            runtime. If you're doing something which should compile differently
+            depending on whether you're compiling on a big endian or little
+            endian machine, then please use $(D version(BigEndian)) and
+            $(D version(LittleEndian)).
+
+        See_Also:
+            $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions)
+      +/
+    enum Endian
+    {
+        bigEndian,   /// Big endian byte order
+        littleEndian /// Little endian byte order
+    }
+
+    /// The endianness that the program was compiled for.
+    version(LittleEndian) Endian endian = Endian.littleEndian;
+    else                  Endian endian = Endian.bigEndian;
+}
+
diff --git a/libphobos/src/std/traits.d b/libphobos/src/std/traits.d
new file mode 100644
index 00000000000..1c7d4c6bbf1
--- /dev/null
+++ b/libphobos/src/std/traits.d
@@ -0,0 +1,6850 @@
+// Written in the D programming language.
+
+/**
+ * Templates which extract information about types and symbols at compile time.
+ *
+ * $(SCRIPT inhibitQuickIndex = 1;)
+ *
+ * $(DIVC quickindex,
+ * $(BOOKTABLE ,
+ * $(TR $(TH Category) $(TH Templates))
+ * $(TR $(TD Symbol Name _traits) $(TD
+ *           $(LREF fullyQualifiedName)
+ *           $(LREF moduleName)
+ *           $(LREF packageName)
+ * ))
+ * $(TR $(TD Function _traits) $(TD
+ *           $(LREF arity)
+ *           $(LREF functionAttributes)
+ *           $(LREF functionLinkage)
+ *           $(LREF FunctionTypeOf)
+ *           $(LREF isSafe)
+ *           $(LREF isUnsafe)
+ *           $(LREF ParameterDefaults)
+ *           $(LREF ParameterIdentifierTuple)
+ *           $(LREF ParameterStorageClassTuple)
+ *           $(LREF Parameters)
+ *           $(LREF ReturnType)
+ *           $(LREF SetFunctionAttributes)
+ *           $(LREF variadicFunctionStyle)
+ * ))
+ * $(TR $(TD Aggregate Type _traits) $(TD
+ *           $(LREF BaseClassesTuple)
+ *           $(LREF BaseTypeTuple)
+ *           $(LREF classInstanceAlignment)
+ *           $(LREF EnumMembers)
+ *           $(LREF FieldNameTuple)
+ *           $(LREF Fields)
+ *           $(LREF hasAliasing)
+ *           $(LREF hasElaborateAssign)
+ *           $(LREF hasElaborateCopyConstructor)
+ *           $(LREF hasElaborateDestructor)
+ *           $(LREF hasIndirections)
+ *           $(LREF hasMember)
+ *           $(LREF hasNested)
+ *           $(LREF hasUnsharedAliasing)
+ *           $(LREF InterfacesTuple)
+ *           $(LREF isNested)
+ *           $(LREF MemberFunctionsTuple)
+ *           $(LREF RepresentationTypeTuple)
+ *           $(LREF TemplateArgsOf)
+ *           $(LREF TemplateOf)
+ *           $(LREF TransitiveBaseTypeTuple)
+ * ))
+ * $(TR $(TD Type Conversion) $(TD
+ *           $(LREF CommonType)
+ *           $(LREF ImplicitConversionTargets)
+ *           $(LREF CopyTypeQualifiers)
+ *           $(LREF CopyConstness)
+ *           $(LREF isAssignable)
+ *           $(LREF isCovariantWith)
+ *           $(LREF isImplicitlyConvertible)
+ * ))
+ * <!--$(TR $(TD SomethingTypeOf) $(TD
+ *           $(LREF BooleanTypeOf)
+ *           $(LREF IntegralTypeOf)
+ *           $(LREF FloatingPointTypeOf)
+ *           $(LREF NumericTypeOf)
+ *           $(LREF UnsignedTypeOf)
+ *           $(LREF SignedTypeOf)
+ *           $(LREF CharTypeOf)
+ *           $(LREF StaticArrayTypeOf)
+ *           $(LREF DynamicArrayTypeOf)
+ *           $(LREF ArrayTypeOf)
+ *           $(LREF StringTypeOf)
+ *           $(LREF AssocArrayTypeOf)
+ *           $(LREF BuiltinTypeOf)
+ * ))-->
+ * $(TR $(TD Categories of types) $(TD
+ *           $(LREF isAggregateType)
+ *           $(LREF isArray)
+ *           $(LREF isAssociativeArray)
+ *           $(LREF isAutodecodableString)
+ *           $(LREF isBasicType)
+ *           $(LREF isBoolean)
+ *           $(LREF isBuiltinType)
+ *           $(LREF isDynamicArray)
+ *           $(LREF isFloatingPoint)
+ *           $(LREF isIntegral)
+ *           $(LREF isNarrowString)
+ *           $(LREF isNumeric)
+ *           $(LREF isPointer)
+ *           $(LREF isScalarType)
+ *           $(LREF isSigned)
+ *           $(LREF isSomeChar)
+ *           $(LREF isSomeString)
+ *           $(LREF isStaticArray)
+ *           $(LREF isUnsigned)
+ * ))
+ * $(TR $(TD Type behaviours) $(TD
+ *           $(LREF isAbstractClass)
+ *           $(LREF isAbstractFunction)
+ *           $(LREF isCallable)
+ *           $(LREF isDelegate)
+ *           $(LREF isExpressions)
+ *           $(LREF isFinalClass)
+ *           $(LREF isFinalFunction)
+ *           $(LREF isFunctionPointer)
+ *           $(LREF isInstanceOf)
+ *           $(LREF isIterable)
+ *           $(LREF isMutable)
+ *           $(LREF isSomeFunction)
+ *           $(LREF isTypeTuple)
+ * ))
+ * $(TR $(TD General Types) $(TD
+ *           $(LREF ForeachType)
+ *           $(LREF KeyType)
+ *           $(LREF Largest)
+ *           $(LREF mostNegative)
+ *           $(LREF OriginalType)
+ *           $(LREF PointerTarget)
+ *           $(LREF Signed)
+ *           $(LREF Unqual)
+ *           $(LREF Unsigned)
+ *           $(LREF ValueType)
+ * ))
+ * $(TR $(TD Misc) $(TD
+ *           $(LREF mangledName)
+ *           $(LREF Select)
+ *           $(LREF select)
+ * ))
+ * $(TR $(TD User-Defined Attributes) $(TD
+ *           $(LREF hasUDA)
+ *           $(LREF getUDAs)
+ *           $(LREF getSymbolsByUDA)
+ * ))
+ * )
+ * )
+ *
+ * Macros:
+ *  WIKI = Phobos/StdTraits
+ *
+ * Copyright: Copyright Digital Mars 2005 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright),
+ *            Tomasz Stachowiak ($(D isExpressions)),
+ *            $(WEB erdani.org, Andrei Alexandrescu),
+ *            Shin Fujishiro,
+ *            $(WEB octarineparrot.com, Robert Clipsham),
+ *            $(WEB klickverbot.at, David Nadlinger),
+ *            Kenji Hara,
+ *            Shoichi Kato
+ * Source:    $(PHOBOSSRC std/_traits.d)
+ */
+/*          Copyright Digital Mars 2005 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.traits;
+
+import std.typetuple;
+
+///////////////////////////////////////////////////////////////////////////////
+// Functions
+///////////////////////////////////////////////////////////////////////////////
+
+// Petit demangler
+// (this or similar thing will eventually go to std.demangle if necessary
+//  ctfe stuffs are available)
+private
+{
+    struct Demangle(T)
+    {
+        T       value;  // extracted information
+        string  rest;
+    }
+
+    /* Demangles mstr as the storage class part of Argument. */
+    Demangle!uint demangleParameterStorageClass(string mstr)
+    {
+        uint pstc = 0; // parameter storage class
+
+        // Argument --> Argument2 | M Argument2
+        if (mstr.length > 0 && mstr[0] == 'M')
+        {
+            pstc |= ParameterStorageClass.scope_;
+            mstr  = mstr[1 .. $];
+        }
+
+        // Argument2 --> Type | J Type | K Type | L Type
+        ParameterStorageClass stc2;
+
+        switch (mstr.length ? mstr[0] : char.init)
+        {
+            case 'J': stc2 = ParameterStorageClass.out_;  break;
+            case 'K': stc2 = ParameterStorageClass.ref_;  break;
+            case 'L': stc2 = ParameterStorageClass.lazy_; break;
+            case 'N': if (mstr.length >= 2 && mstr[1] == 'k')
+                        stc2 = ParameterStorageClass.return_;
+                      break;
+            default : break;
+        }
+        if (stc2 != ParameterStorageClass.init)
+        {
+            pstc |= stc2;
+            mstr  = mstr[1 .. $];
+            if (stc2 & ParameterStorageClass.return_)
+                mstr  = mstr[1 .. $];
+        }
+
+        return Demangle!uint(pstc, mstr);
+    }
+
+    /* Demangles mstr as FuncAttrs. */
+    Demangle!uint demangleFunctionAttributes(string mstr)
+    {
+        enum LOOKUP_ATTRIBUTE =
+        [
+            'a': FunctionAttribute.pure_,
+            'b': FunctionAttribute.nothrow_,
+            'c': FunctionAttribute.ref_,
+            'd': FunctionAttribute.property,
+            'e': FunctionAttribute.trusted,
+            'f': FunctionAttribute.safe,
+            'i': FunctionAttribute.nogc,
+            'j': FunctionAttribute.return_
+        ];
+        uint atts = 0;
+
+        // FuncAttrs --> FuncAttr | FuncAttr FuncAttrs
+        // FuncAttr  --> empty | Na | Nb | Nc | Nd | Ne | Nf | Ni | Nj
+        // except 'Ng' == inout, because it is a qualifier of function type
+        while (mstr.length >= 2 && mstr[0] == 'N' && mstr[1] != 'g' && mstr[1] != 'k')
+        {
+            if (FunctionAttribute att = LOOKUP_ATTRIBUTE[ mstr[1] ])
+            {
+                atts |= att;
+                mstr  = mstr[2 .. $];
+            }
+            else assert(0);
+        }
+        return Demangle!uint(atts, mstr);
+    }
+
+    static if (is(ucent))
+    {
+        alias CentTypeList         = TypeTuple!(cent, ucent);
+        alias SignedCentTypeList   = TypeTuple!(cent);
+        alias UnsignedCentTypeList = TypeTuple!(ucent);
+    }
+    else
+    {
+        alias CentTypeList         = TypeTuple!();
+        alias SignedCentTypeList   = TypeTuple!();
+        alias UnsignedCentTypeList = TypeTuple!();
+    }
+
+    alias IntegralTypeList      = TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList);
+    alias SignedIntTypeList     = TypeTuple!(byte, short, int, long, SignedCentTypeList);
+    alias UnsignedIntTypeList   = TypeTuple!(ubyte, ushort, uint, ulong, UnsignedCentTypeList);
+    alias FloatingPointTypeList = TypeTuple!(float, double, real);
+    alias ImaginaryTypeList     = TypeTuple!(ifloat, idouble, ireal);
+    alias ComplexTypeList       = TypeTuple!(cfloat, cdouble, creal);
+    alias NumericTypeList       = TypeTuple!(IntegralTypeList, FloatingPointTypeList);
+    alias CharTypeList          = TypeTuple!(char, wchar, dchar);
+}
+
+package
+{
+    /// Add specific qualifier to the given type T.
+    template MutableOf(T)     { alias MutableOf     =              T  ; }
+}
+
+/// Add specific qualifier to the given type T.
+template InoutOf(T)       { alias InoutOf       =        inout(T) ; }
+/// ditto.
+template ConstOf(T)       { alias ConstOf       =        const(T) ; }
+/// ditto.
+template SharedOf(T)      { alias SharedOf      =       shared(T) ; }
+/// ditto.
+template SharedInoutOf(T) { alias SharedInoutOf = shared(inout(T)); }
+/// ditto.
+template SharedConstOf(T) { alias SharedConstOf = shared(const(T)); }
+/// ditto.
+template ImmutableOf(T)   { alias ImmutableOf   =    immutable(T) ; }
+
+unittest
+{
+    static assert(is(    MutableOf!int ==              int));
+    static assert(is(      InoutOf!int ==        inout int));
+    static assert(is(      ConstOf!int ==        const int));
+    static assert(is(     SharedOf!int == shared       int));
+    static assert(is(SharedInoutOf!int == shared inout int));
+    static assert(is(SharedConstOf!int == shared const int));
+    static assert(is(  ImmutableOf!int ==    immutable int));
+}
+
+// Get qualifier template from the given type T
+template QualifierOf(T)
+{
+         static if (is(T == shared(const U), U)) alias QualifierOf = SharedConstOf;
+    else static if (is(T ==        const U , U)) alias QualifierOf = ConstOf;
+    else static if (is(T == shared(inout U), U)) alias QualifierOf = SharedInoutOf;
+    else static if (is(T ==        inout U , U)) alias QualifierOf = InoutOf;
+    else static if (is(T ==    immutable U , U)) alias QualifierOf = ImmutableOf;
+    else static if (is(T ==       shared U , U)) alias QualifierOf = SharedOf;
+    else                                         alias QualifierOf = MutableOf;
+}
+
+unittest
+{
+    alias Qual1 = QualifierOf!(             int);   static assert(is(Qual1!long ==              long));
+    alias Qual2 = QualifierOf!(       inout int);   static assert(is(Qual2!long ==        inout long));
+    alias Qual3 = QualifierOf!(       const int);   static assert(is(Qual3!long ==        const long));
+    alias Qual4 = QualifierOf!(shared       int);   static assert(is(Qual4!long == shared       long));
+    alias Qual5 = QualifierOf!(shared inout int);   static assert(is(Qual5!long == shared inout long));
+    alias Qual6 = QualifierOf!(shared const int);   static assert(is(Qual6!long == shared const long));
+    alias Qual7 = QualifierOf!(   immutable int);   static assert(is(Qual7!long ==    immutable long));
+}
+
+version(unittest)
+{
+    alias TypeQualifierList = TypeTuple!(MutableOf, ConstOf, SharedOf, SharedConstOf, ImmutableOf);
+
+    struct SubTypeOf(T)
+    {
+        T val;
+        alias val this;
+    }
+}
+
+private alias parentOf(alias sym) = Identity!(__traits(parent, sym));
+private alias parentOf(alias sym : T!Args, alias T, Args...) = Identity!(__traits(parent, T));
+
+/**
+ * Get the full package name for the given symbol.
+ */
+template packageName(alias T)
+{
+    import std.algorithm : startsWith;
+
+    static if (__traits(compiles, parentOf!T))
+        enum parent = packageName!(parentOf!T);
+    else
+        enum string parent = null;
+
+    static if (T.stringof.startsWith("package "))
+        enum packageName = (parent.length ? parent ~ '.' : "") ~ T.stringof[8 .. $];
+    else static if (parent)
+        enum packageName = parent;
+    else
+        static assert(false, T.stringof ~ " has no parent");
+}
+
+///
+unittest
+{
+    import std.traits;
+    static assert(packageName!packageName == "std");
+}
+
+unittest
+{
+    import std.array;
+
+    // Commented out because of dmd @@@BUG8922@@@
+    // static assert(packageName!std == "std");  // this package (currently: "std.std")
+    static assert(packageName!(std.traits) == "std");     // this module
+    static assert(packageName!packageName == "std");      // symbol in this module
+    static assert(packageName!(std.array) == "std");  // other module from same package
+
+    import core.sync.barrier;  // local import
+    static assert(packageName!core == "core");
+    static assert(packageName!(core.sync) == "core.sync");
+    static assert(packageName!Barrier == "core.sync");
+
+    struct X12287(T) { T i; }
+    static assert(packageName!(X12287!int.i) == "std");
+}
+
+version (none) version(unittest) //Please uncomment me when changing packageName to test global imports
+{
+    import core.sync.barrier;  // global import
+    static assert(packageName!core == "core");
+    static assert(packageName!(core.sync) == "core.sync");
+    static assert(packageName!Barrier == "core.sync");
+}
+
+/**
+ * Get the module name (including package) for the given symbol.
+ */
+template moduleName(alias T)
+{
+    import std.algorithm : startsWith;
+
+    static assert(!T.stringof.startsWith("package "), "cannot get the module name for a package");
+
+    static if (T.stringof.startsWith("module "))
+    {
+        static if (__traits(compiles, packageName!T))
+            enum packagePrefix = packageName!T ~ '.';
+        else
+            enum packagePrefix = "";
+
+        enum moduleName = packagePrefix ~ T.stringof[7..$];
+    }
+    else
+        alias moduleName = moduleName!(parentOf!T); // If you use enum, it will cause compiler ICE
+}
+
+///
+unittest
+{
+    import std.traits;
+    static assert(moduleName!moduleName == "std.traits");
+}
+
+unittest
+{
+    import std.array;
+
+    static assert(!__traits(compiles, moduleName!std));
+    static assert(moduleName!(std.traits) == "std.traits");            // this module
+    static assert(moduleName!moduleName == "std.traits");              // symbol in this module
+    static assert(moduleName!(std.array) == "std.array");      // other module
+    static assert(moduleName!(std.array.array) == "std.array");  // symbol in other module
+
+    import core.sync.barrier;  // local import
+    static assert(!__traits(compiles, moduleName!(core.sync)));
+    static assert(moduleName!(core.sync.barrier) == "core.sync.barrier");
+    static assert(moduleName!Barrier == "core.sync.barrier");
+
+    struct X12287(T) { T i; }
+    static assert(moduleName!(X12287!int.i) == "std.traits");
+}
+
+version (none) version(unittest) //Please uncomment me when changing moduleName to test global imports
+{
+    import core.sync.barrier;  // global import
+    static assert(!__traits(compiles, moduleName!(core.sync)));
+    static assert(moduleName!(core.sync.barrier) == "core.sync.barrier");
+    static assert(moduleName!Barrier == "core.sync.barrier");
+}
+
+/***
+ * Get the fully qualified name of a type or a symbol. Can act as an intelligent type/symbol to string  converter.
+
+Example:
+-----------------
+module myModule;
+struct MyStruct {}
+static assert(fullyQualifiedName!(const MyStruct[]) == "const(myModule.MyStruct[])");
+-----------------
+*/
+template fullyQualifiedName(T...)
+    if (T.length == 1)
+{
+
+    static if (is(T))
+        enum fullyQualifiedName = fqnType!(T[0], false, false, false, false);
+    else
+        enum fullyQualifiedName = fqnSym!(T[0]);
+}
+
+///
+unittest
+{
+    static assert(fullyQualifiedName!fullyQualifiedName == "std.traits.fullyQualifiedName");
+}
+
+version(unittest)
+{
+    // Used for both fqnType and fqnSym unittests
+    private struct QualifiedNameTests
+    {
+        struct Inner
+        {
+        }
+
+        ref const(Inner[string]) func( ref Inner var1, lazy scope string var2 );
+        ref const(Inner[string]) retfunc( return ref Inner var1 );
+        Inner inoutFunc(inout Inner) inout;
+        shared(const(Inner[string])[]) data;
+        const Inner delegate(double, string) @safe nothrow deleg;
+        inout(int) delegate(inout int) inout inoutDeleg;
+        Inner function(out double, string) funcPtr;
+        extern(C) Inner function(double, string) cFuncPtr;
+
+        extern(C) void cVarArg(int, ...);
+        void dVarArg(...);
+        void dVarArg2(int, ...);
+        void typesafeVarArg(int[] ...);
+
+        Inner[] array;
+        Inner[16] sarray;
+        Inner[Inner] aarray;
+        const(Inner[const(Inner)]) qualAarray;
+
+        shared(immutable(Inner) delegate(ref double, scope string) const shared @trusted nothrow) attrDeleg;
+
+        struct Data(T) { int x; }
+        void tfunc(T...)(T args) {}
+
+        template Inst(alias A) { int x; }
+
+        class Test12309(T, int x, string s) {}
+    }
+
+    private enum QualifiedEnum
+    {
+        a = 42
+    }
+}
+
+private template fqnSym(alias T : X!A, alias X, A...)
+{
+    template fqnTuple(T...)
+    {
+        static if (T.length == 0)
+            enum fqnTuple = "";
+        else static if (T.length == 1)
+        {
+            static if (isExpressionTuple!T)
+                enum fqnTuple = T[0].stringof;
+            else
+                enum fqnTuple = fullyQualifiedName!(T[0]);
+        }
+        else
+            enum fqnTuple = fqnTuple!(T[0]) ~ ", " ~ fqnTuple!(T[1 .. $]);
+    }
+
+    enum fqnSym =
+        fqnSym!(__traits(parent, X)) ~
+        '.' ~ __traits(identifier, X) ~ "!(" ~ fqnTuple!A ~ ")";
+}
+
+private template fqnSym(alias T)
+{
+    static if (__traits(compiles, __traits(parent, T)))
+        enum parentPrefix = fqnSym!(__traits(parent, T)) ~ '.';
+    else
+        enum parentPrefix = null;
+
+    static string adjustIdent(string s)
+    {
+        import std.algorithm : skipOver, findSplit;
+
+        if (s.skipOver("package ") || s.skipOver("module "))
+            return s;
+        return s.findSplit("(")[0];
+    }
+    enum fqnSym = parentPrefix ~ adjustIdent(__traits(identifier, T));
+}
+
+unittest
+{
+    alias fqn = fullyQualifiedName;
+
+    // Make sure those 2 are the same
+    static assert(fqnSym!fqn == fqn!fqn);
+
+    static assert(fqn!fqn == "std.traits.fullyQualifiedName");
+
+    alias qnTests = QualifiedNameTests;
+    enum prefix = "std.traits.QualifiedNameTests.";
+    static assert(fqn!(qnTests.Inner)           == prefix ~ "Inner");
+    static assert(fqn!(qnTests.func)            == prefix ~ "func");
+    static assert(fqn!(qnTests.Data!int)        == prefix ~ "Data!(int)");
+    static assert(fqn!(qnTests.Data!int.x)      == prefix ~ "Data!(int).x");
+    static assert(fqn!(qnTests.tfunc!(int[]))   == prefix ~ "tfunc!(int[])");
+    static assert(fqn!(qnTests.Inst!(Object))   == prefix ~ "Inst!(object.Object)");
+    static assert(fqn!(qnTests.Inst!(Object).x) == prefix ~ "Inst!(object.Object).x");
+
+    static assert(fqn!(qnTests.Test12309!(int, 10, "str"))
+                                                == prefix ~ "Test12309!(int, 10, \"str\")");
+
+    import core.sync.barrier;
+    static assert(fqn!Barrier == "core.sync.barrier.Barrier");
+}
+
+private template fqnType(T,
+    bool alreadyConst, bool alreadyImmutable, bool alreadyShared, bool alreadyInout)
+{
+    import std.format : format;
+
+    // Convenience tags
+    enum {
+        _const = 0,
+        _immutable = 1,
+        _shared = 2,
+        _inout = 3
+    }
+
+    alias qualifiers   = TypeTuple!(is(T == const), is(T == immutable), is(T == shared), is(T == inout));
+    alias noQualifiers = TypeTuple!(false, false, false, false);
+
+    string storageClassesString(uint psc)() @property
+    {
+        alias PSC = ParameterStorageClass;
+
+        return format("%s%s%s%s%s",
+            psc & PSC.scope_ ? "scope " : "",
+            psc & PSC.return_ ? "return " : "",
+            psc & PSC.out_ ? "out " : "",
+            psc & PSC.ref_ ? "ref " : "",
+            psc & PSC.lazy_ ? "lazy " : ""
+        );
+    }
+
+    string parametersTypeString(T)() @property
+    {
+        alias parameters   = Parameters!(T);
+        alias parameterStC = ParameterStorageClassTuple!(T);
+
+        enum variadic = variadicFunctionStyle!T;
+        static if (variadic == Variadic.no)
+            enum variadicStr = "";
+        else static if (variadic == Variadic.c)
+            enum variadicStr = ", ...";
+        else static if (variadic == Variadic.d)
+            enum variadicStr = parameters.length ? ", ..." : "...";
+        else static if (variadic == Variadic.typesafe)
+            enum variadicStr = " ...";
+        else
+            static assert(0, "New variadic style has been added, please update fullyQualifiedName implementation");
+
+        static if (parameters.length)
+        {
+            import std.algorithm : map;
+            import std.range : join, zip;
+
+            string result = join(
+                map!(a => format("%s%s", a[0], a[1]))(
+                    zip([staticMap!(storageClassesString, parameterStC)],
+                        [staticMap!(fullyQualifiedName, parameters)])
+                ),
+                ", "
+            );
+
+            return result ~= variadicStr;
+        }
+        else
+            return variadicStr;
+    }
+
+    string linkageString(T)() @property
+    {
+        enum linkage = functionLinkage!T;
+
+        if (linkage != "D")
+            return format("extern(%s) ", linkage);
+        else
+            return "";
+    }
+
+    string functionAttributeString(T)() @property
+    {
+        alias FA = FunctionAttribute;
+        enum attrs = functionAttributes!T;
+
+        static if (attrs == FA.none)
+            return "";
+        else
+            return format("%s%s%s%s%s%s%s%s",
+                 attrs & FA.pure_ ? " pure" : "",
+                 attrs & FA.nothrow_ ? " nothrow" : "",
+                 attrs & FA.ref_ ? " ref" : "",
+                 attrs & FA.property ? " @property" : "",
+                 attrs & FA.trusted ? " @trusted" : "",
+                 attrs & FA.safe ? " @safe" : "",
+                 attrs & FA.nogc ? " @nogc" : "",
+                 attrs & FA.return_ ? " return" : ""
+            );
+    }
+
+    string addQualifiers(string typeString,
+        bool addConst, bool addImmutable, bool addShared, bool addInout)
+    {
+        auto result = typeString;
+        if (addShared)
+        {
+            result = format("shared(%s)", result);
+        }
+        if (addConst || addImmutable || addInout)
+        {
+            result = format("%s(%s)",
+                addConst ? "const" :
+                    addImmutable ? "immutable" : "inout",
+                result
+            );
+        }
+        return result;
+    }
+
+    // Convenience template to avoid copy-paste
+    template chain(string current)
+    {
+        enum chain = addQualifiers(current,
+            qualifiers[_const]     && !alreadyConst,
+            qualifiers[_immutable] && !alreadyImmutable,
+            qualifiers[_shared]    && !alreadyShared,
+            qualifiers[_inout]     && !alreadyInout);
+    }
+
+    static if (is(T == string))
+    {
+        enum fqnType = "string";
+    }
+    else static if (is(T == wstring))
+    {
+        enum fqnType = "wstring";
+    }
+    else static if (is(T == dstring))
+    {
+        enum fqnType = "dstring";
+    }
+    else static if (isBasicType!T && !is(T == enum))
+    {
+        enum fqnType = chain!((Unqual!T).stringof);
+    }
+    else static if (isAggregateType!T || is(T == enum))
+    {
+        enum fqnType = chain!(fqnSym!T);
+    }
+    else static if (isStaticArray!T)
+    {
+        import std.conv;
+
+        enum fqnType = chain!(
+            format("%s[%s]", fqnType!(typeof(T.init[0]), qualifiers), T.length)
+        );
+    }
+    else static if (isArray!T)
+    {
+        enum fqnType = chain!(
+            format("%s[]", fqnType!(typeof(T.init[0]), qualifiers))
+        );
+    }
+    else static if (isAssociativeArray!T)
+    {
+        enum fqnType = chain!(
+            format("%s[%s]", fqnType!(ValueType!T, qualifiers), fqnType!(KeyType!T, noQualifiers))
+        );
+    }
+    else static if (isSomeFunction!T)
+    {
+        static if (is(T F == delegate))
+        {
+            enum qualifierString = format("%s%s",
+                is(F == shared) ? " shared" : "",
+                is(F == inout) ? " inout" :
+                is(F == immutable) ? " immutable" :
+                is(F == const) ? " const" : ""
+            );
+            enum formatStr = "%s%s delegate(%s)%s%s";
+            enum fqnType = chain!(
+                format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers),
+                    parametersTypeString!(T), functionAttributeString!T, qualifierString)
+            );
+        }
+        else
+        {
+            static if (isFunctionPointer!T)
+                enum formatStr = "%s%s function(%s)%s";
+            else
+                enum formatStr = "%s%s(%s)%s";
+
+            enum fqnType = chain!(
+                format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers),
+                    parametersTypeString!(T), functionAttributeString!T)
+            );
+        }
+    }
+    else static if (isPointer!T)
+    {
+        enum fqnType = chain!(
+            format("%s*", fqnType!(PointerTarget!T, qualifiers))
+        );
+    }
+    else static if (is(T : __vector(V[N]), V, size_t N))
+    {
+        enum fqnType = chain!(
+            format("__vector(%s[%s])", fqnType!(V, qualifiers), N)
+        );
+   }
+    else
+        // In case something is forgotten
+        static assert(0, "Unrecognized type " ~ T.stringof ~ ", can't convert to fully qualified string");
+}
+
+unittest
+{
+    import std.format : format;
+    alias fqn = fullyQualifiedName;
+
+    // Verify those 2 are the same for simple case
+    alias Ambiguous = const(QualifiedNameTests.Inner);
+    static assert(fqn!Ambiguous == fqnType!(Ambiguous, false, false, false, false));
+
+    // Main tests
+    enum inner_name = "std.traits.QualifiedNameTests.Inner";
+    with (QualifiedNameTests)
+    {
+        // Special cases
+        static assert(fqn!(string) == "string");
+        static assert(fqn!(wstring) == "wstring");
+        static assert(fqn!(dstring) == "dstring");
+
+        // Basic qualified name
+        static assert(fqn!(Inner) == inner_name);
+        static assert(fqn!(QualifiedEnum) == "std.traits.QualifiedEnum"); // type
+        static assert(fqn!(QualifiedEnum.a) == "std.traits.QualifiedEnum.a"); // symbol
+
+        // Array types
+        static assert(fqn!(typeof(array)) == format("%s[]", inner_name));
+        static assert(fqn!(typeof(sarray)) == format("%s[16]", inner_name));
+        static assert(fqn!(typeof(aarray)) == format("%s[%s]", inner_name, inner_name));
+
+        // qualified key for AA
+        static assert(fqn!(typeof(qualAarray)) == format("const(%s[const(%s)])", inner_name, inner_name));
+
+        // Qualified composed data types
+        static assert(fqn!(typeof(data)) == format("shared(const(%s[string])[])", inner_name));
+
+        // Function types + function attributes
+        static assert(fqn!(typeof(func)) == format("const(%s[string])(ref %s, scope lazy string) ref", inner_name, inner_name));
+        static assert(fqn!(typeof(retfunc)) == format("const(%s[string])(return %s) ref", inner_name, inner_name));
+        static assert(fqn!(typeof(inoutFunc)) == format("inout(%s(inout(%s)))", inner_name, inner_name));
+        static assert(fqn!(typeof(deleg)) == format("const(%s delegate(double, string) nothrow @safe)", inner_name));
+        static assert(fqn!(typeof(inoutDeleg)) == "inout(int) delegate(inout(int)) inout");
+        static assert(fqn!(typeof(funcPtr)) == format("%s function(out double, string)", inner_name));
+        static assert(fqn!(typeof(cFuncPtr)) == format("extern(C) %s function(double, string)", inner_name));
+
+        // Delegate type with qualified function type
+        static assert(fqn!(typeof(attrDeleg)) == format("shared(immutable(%s) "~
+            "delegate(ref double, scope string) nothrow @trusted shared const)", inner_name));
+
+        // Variable argument function types
+        static assert(fqn!(typeof(cVarArg)) == "extern(C) void(int, ...)");
+        static assert(fqn!(typeof(dVarArg)) == "void(...)");
+        static assert(fqn!(typeof(dVarArg2)) == "void(int, ...)");
+        static assert(fqn!(typeof(typesafeVarArg)) == "void(int[] ...)");
+
+        // SIMD vector
+        static if (is(__vector(float[4])))
+        {
+            static assert(fqn!(__vector(float[4])) == "__vector(float[4])");
+        }
+    }
+}
+
+/***
+ * Get the type of the return value from a function,
+ * a pointer to function, a delegate, a struct
+ * with an opCall, a pointer to a struct with an opCall,
+ * or a class with an $(D opCall). Please note that $(D_KEYWORD ref)
+ * is not part of a type, but the attribute of the function
+ * (see template $(LREF functionAttributes)).
+ */
+template ReturnType(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    static if (is(FunctionTypeOf!func R == return))
+        alias ReturnType = R;
+    else
+        static assert(0, "argument has no return type");
+}
+
+///
+unittest
+{
+    int foo();
+    ReturnType!foo x;   // x is declared as int
+}
+
+unittest
+{
+    struct G
+    {
+        int opCall (int i) { return 1;}
+    }
+
+    alias ShouldBeInt = ReturnType!G;
+    static assert(is(ShouldBeInt == int));
+
+    G g;
+    static assert(is(ReturnType!g == int));
+
+    G* p;
+    alias pg = ReturnType!p;
+    static assert(is(pg == int));
+
+    class C
+    {
+        int opCall (int i) { return 1;}
+    }
+
+    static assert(is(ReturnType!C == int));
+
+    C c;
+    static assert(is(ReturnType!c == int));
+
+    class Test
+    {
+        int prop() @property { return 0; }
+    }
+    alias R_Test_prop = ReturnType!(Test.prop);
+    static assert(is(R_Test_prop == int));
+
+    alias R_dglit = ReturnType!((int a) { return a; });
+    static assert(is(R_dglit == int));
+}
+
+/***
+Get, as a tuple, the types of the parameters to a function, a pointer
+to function, a delegate, a struct with an $(D opCall), a pointer to a
+struct with an $(D opCall), or a class with an $(D opCall).
+*/
+template Parameters(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    static if (is(FunctionTypeOf!func P == function))
+        alias Parameters = P;
+    else
+        static assert(0, "argument has no parameters");
+}
+
+///
+unittest
+{
+    int foo(int, long);
+    void bar(Parameters!foo);      // declares void bar(int, long);
+    void abc(Parameters!foo[1]);   // declares void abc(long);
+}
+
+/**
+ * Alternate name for $(LREF Parameters), kept for legacy compatibility.
+ */
+alias ParameterTypeTuple = Parameters;
+
+unittest
+{
+    int foo(int i, bool b) { return 0; }
+    static assert(is(ParameterTypeTuple!foo == TypeTuple!(int, bool)));
+    static assert(is(ParameterTypeTuple!(typeof(&foo)) == TypeTuple!(int, bool)));
+
+    struct S { real opCall(real r, int i) { return 0.0; } }
+    S s;
+    static assert(is(ParameterTypeTuple!S == TypeTuple!(real, int)));
+    static assert(is(ParameterTypeTuple!(S*) == TypeTuple!(real, int)));
+    static assert(is(ParameterTypeTuple!s == TypeTuple!(real, int)));
+
+    class Test
+    {
+        int prop() @property { return 0; }
+    }
+    alias P_Test_prop = ParameterTypeTuple!(Test.prop);
+    static assert(P_Test_prop.length == 0);
+
+    alias P_dglit = ParameterTypeTuple!((int a){});
+    static assert(P_dglit.length == 1);
+    static assert(is(P_dglit[0] == int));
+}
+
+/**
+Returns the number of arguments of function $(D func).
+arity is undefined for variadic functions.
+*/
+template arity(alias func)
+    if ( isCallable!func && variadicFunctionStyle!func == Variadic.no )
+{
+    enum size_t arity = Parameters!func.length;
+}
+
+///
+unittest {
+    void foo(){}
+    static assert(arity!foo==0);
+    void bar(uint){}
+    static assert(arity!bar==1);
+    void variadicFoo(uint...){}
+    static assert(!__traits(compiles, arity!variadicFoo));
+}
+
+/**
+Returns a tuple consisting of the storage classes of the parameters of a
+function $(D func).
+ */
+enum ParameterStorageClass : uint
+{
+    /**
+     * These flags can be bitwise OR-ed together to represent complex storage
+     * class.
+     */
+    none    = 0,
+    scope_  = 0b000_1,  /// ditto
+    out_    = 0b001_0,  /// ditto
+    ref_    = 0b010_0,  /// ditto
+    lazy_   = 0b100_0,  /// ditto
+    return_ = 0b1000_0, /// ditto
+}
+
+/// ditto
+template ParameterStorageClassTuple(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    alias Func = Unqual!(FunctionTypeOf!func);
+
+    /*
+     * TypeFuncion:
+     *     CallConvention FuncAttrs Arguments ArgClose Type
+     */
+    alias Params = Parameters!Func;
+
+    // chop off CallConvention and FuncAttrs
+    enum margs = demangleFunctionAttributes(mangledName!Func[1 .. $]).rest;
+
+    // demangle Arguments and store parameter storage classes in a tuple
+    template demangleNextParameter(string margs, size_t i = 0)
+    {
+        static if (i < Params.length)
+        {
+            enum demang = demangleParameterStorageClass(margs);
+            enum skip = mangledName!(Params[i]).length; // for bypassing Type
+            enum rest = demang.rest;
+
+            alias demangleNextParameter =
+                TypeTuple!(
+                    demang.value + 0, // workaround: "not evaluatable at ..."
+                    demangleNextParameter!(rest[skip .. $], i + 1)
+                );
+        }
+        else // went thru all the parameters
+        {
+            alias demangleNextParameter = TypeTuple!();
+        }
+    }
+
+    alias ParameterStorageClassTuple = demangleNextParameter!margs;
+}
+
+///
+unittest
+{
+    alias STC = ParameterStorageClass; // shorten the enum name
+
+    void func(ref int ctx, out real result, real param)
+    {
+    }
+    alias pstc = ParameterStorageClassTuple!func;
+    static assert(pstc.length == 3); // three parameters
+    static assert(pstc[0] == STC.ref_);
+    static assert(pstc[1] == STC.out_);
+    static assert(pstc[2] == STC.none);
+}
+
+unittest
+{
+    alias STC = ParameterStorageClass;
+
+    void noparam() {}
+    static assert(ParameterStorageClassTuple!noparam.length == 0);
+
+    void test(scope int*, ref int, out int, lazy int, int, return ref int) { }
+    alias test_pstc = ParameterStorageClassTuple!test;
+    static assert(test_pstc.length == 6);
+    static assert(test_pstc[0] == STC.scope_);
+    static assert(test_pstc[1] == STC.ref_);
+    static assert(test_pstc[2] == STC.out_);
+    static assert(test_pstc[3] == STC.lazy_);
+    static assert(test_pstc[4] == STC.none);
+    static assert(test_pstc[5] == STC.return_);
+
+    interface Test
+    {
+        void test_const(int) const;
+        void test_sharedconst(int) shared const;
+    }
+    Test testi;
+
+    alias test_const_pstc = ParameterStorageClassTuple!(Test.test_const);
+    static assert(test_const_pstc.length == 1);
+    static assert(test_const_pstc[0] == STC.none);
+
+    alias test_sharedconst_pstc = ParameterStorageClassTuple!(testi.test_sharedconst);
+    static assert(test_sharedconst_pstc.length == 1);
+    static assert(test_sharedconst_pstc[0] == STC.none);
+
+    alias dglit_pstc = ParameterStorageClassTuple!((ref int a) {});
+    static assert(dglit_pstc.length == 1);
+    static assert(dglit_pstc[0] == STC.ref_);
+
+    // Bugzilla 9317
+    static inout(int) func(inout int param) { return param; }
+    static assert(ParameterStorageClassTuple!(typeof(func))[0] == STC.none);
+}
+
+unittest
+{
+    // Bugzilla 14253
+    static struct Foo {
+        ref Foo opAssign(ref Foo rhs) return { return this; }
+    }
+
+    alias tup = ParameterStorageClassTuple!(__traits(getOverloads, Foo, "opAssign")[0]);
+}
+
+
+/**
+Get, as a tuple, the identifiers of the parameters to a function symbol.
+ */
+template ParameterIdentifierTuple(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    static if (is(FunctionTypeOf!func PT == __parameters))
+    {
+        template Get(size_t i)
+        {
+            static if (!isFunctionPointer!func && !isDelegate!func
+                       // Unnamed parameters yield CT error.
+                       && is(typeof(__traits(identifier, PT[i..i+1]))x))
+            {
+                enum Get = __traits(identifier, PT[i..i+1]);
+            }
+            else
+            {
+                enum Get = "";
+            }
+        }
+    }
+    else
+    {
+        static assert(0, func[0].stringof ~ "is not a function");
+
+        // Define dummy entities to avoid pointless errors
+        template Get(size_t i) { enum Get = ""; }
+        alias PT = TypeTuple!();
+    }
+
+    template Impl(size_t i = 0)
+    {
+        static if (i == PT.length)
+            alias Impl = TypeTuple!();
+        else
+            alias Impl = TypeTuple!(Get!i, Impl!(i+1));
+    }
+
+    alias ParameterIdentifierTuple = Impl!();
+}
+
+///
+unittest
+{
+    int foo(int num, string name, int);
+    static assert([ParameterIdentifierTuple!foo] == ["num", "name", ""]);
+}
+
+unittest
+{
+    alias PIT = ParameterIdentifierTuple;
+
+    void bar(int num, string name, int[] array){}
+    static assert([PIT!bar] == ["num", "name", "array"]);
+
+    // might be changed in the future?
+    void function(int num, string name) fp;
+    static assert([PIT!fp] == ["", ""]);
+
+    // might be changed in the future?
+    void delegate(int num, string name, int[long] aa) dg;
+    static assert([PIT!dg] == ["", "", ""]);
+
+    interface Test
+    {
+        @property string getter();
+        @property void setter(int a);
+        Test method(int a, long b, string c);
+    }
+    static assert([PIT!(Test.getter)] == []);
+    static assert([PIT!(Test.setter)] == ["a"]);
+    static assert([PIT!(Test.method)] == ["a", "b", "c"]);
+
+/+
+    // depends on internal
+    void baw(int, string, int[]){}
+    static assert([PIT!baw] == ["_param_0", "_param_1", "_param_2"]);
+
+    // depends on internal
+    void baz(TypeTuple!(int, string, int[]) args){}
+    static assert([PIT!baz] == ["_param_0", "_param_1", "_param_2"]);
++/
+}
+
+
+/**
+Get, as a tuple, the default value of the parameters to a function symbol.
+If a parameter doesn't have the default value, $(D void) is returned instead.
+ */
+template ParameterDefaults(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    static if (is(FunctionTypeOf!(func[0]) PT == __parameters))
+    {
+        template Get(size_t i)
+        {
+            enum ParamName = ParameterIdentifierTuple!(func[0])[i];
+            static if (ParamName.length)
+                enum get = (PT[i..i+1]) => mixin(ParamName);
+            else // Unnamed parameter
+                enum get = (PT[i..i+1] __args) => __args[0];
+            static if (is(typeof(get())))
+                enum Get = get();
+            else
+                alias Get = void;
+                // If default arg doesn't exist, returns void instead.
+        }
+    }
+    else static if (is(FunctionTypeOf!func PT == __parameters))
+    {
+        template Get(size_t i)
+        {
+            enum Get = "";
+        }
+    }
+    else
+    {
+        static assert(0, func[0].stringof ~ "is not a function");
+
+        // Define dummy entities to avoid pointless errors
+        template Get(size_t i) { enum Get = ""; }
+        alias PT = TypeTuple!();
+    }
+
+    template Impl(size_t i = 0)
+    {
+        static if (i == PT.length)
+            alias Impl = TypeTuple!();
+        else
+            alias Impl = TypeTuple!(Get!i, Impl!(i+1));
+    }
+
+    alias ParameterDefaults = Impl!();
+}
+
+///
+unittest
+{
+    int foo(int num, string name = "hello", int[] = [1,2,3]);
+    static assert(is(ParameterDefaults!foo[0] == void));
+    static assert(   ParameterDefaults!foo[1] == "hello");
+    static assert(   ParameterDefaults!foo[2] == [1,2,3]);
+}
+
+/**
+ * Alternate name for $(LREF ParameterDefaults), kept for legacy compatibility.
+ */
+alias ParameterDefaultValueTuple = ParameterDefaults;
+
+unittest
+{
+    alias PDVT = ParameterDefaultValueTuple;
+
+    void bar(int n = 1, string s = "hello"){}
+    static assert(PDVT!bar.length == 2);
+    static assert(PDVT!bar[0] == 1);
+    static assert(PDVT!bar[1] == "hello");
+    static assert(is(typeof(PDVT!bar) == typeof(TypeTuple!(1, "hello"))));
+
+    void baz(int x, int n = 1, string s = "hello"){}
+    static assert(PDVT!baz.length == 3);
+    static assert(is(PDVT!baz[0] == void));
+    static assert(   PDVT!baz[1] == 1);
+    static assert(   PDVT!baz[2] == "hello");
+    static assert(is(typeof(PDVT!baz) == typeof(TypeTuple!(void, 1, "hello"))));
+
+    // bug 10800 - property functions return empty string
+    @property void foo(int x = 3) { }
+    static assert(PDVT!foo.length == 1);
+    static assert(PDVT!foo[0] == 3);
+    static assert(is(typeof(PDVT!foo) == typeof(TypeTuple!(3))));
+
+    struct Colour
+    {
+        ubyte a,r,g,b;
+
+        static immutable Colour white = Colour(255,255,255,255);
+    }
+    void bug8106(Colour c = Colour.white){}
+    //pragma(msg, PDVT!bug8106);
+    static assert(PDVT!bug8106[0] == Colour.white);
+}
+
+
+/**
+Returns the attributes attached to a function $(D func).
+ */
+enum FunctionAttribute : uint
+{
+    /**
+     * These flags can be bitwise OR-ed together to represent a complex attribute.
+     */
+    none       = 0,
+    pure_      = 1 << 0,  /// ditto
+    nothrow_   = 1 << 1,  /// ditto
+    ref_       = 1 << 2,  /// ditto
+    property   = 1 << 3,  /// ditto
+    trusted    = 1 << 4,  /// ditto
+    safe       = 1 << 5,  /// ditto
+    nogc       = 1 << 6,  /// ditto
+    system     = 1 << 7,  /// ditto
+    const_     = 1 << 8,  /// ditto
+    immutable_ = 1 << 9,  /// ditto
+    inout_     = 1 << 10, /// ditto
+    shared_    = 1 << 11, /// ditto
+    return_    = 1 << 12, /// ditto
+}
+
+/// ditto
+template functionAttributes(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    // @bug: workaround for opCall
+    alias FuncSym = Select!(is(typeof(__traits(getFunctionAttributes, func))),
+                            func, Unqual!(FunctionTypeOf!func));
+
+    enum FunctionAttribute functionAttributes =
+        extractAttribFlags!(__traits(getFunctionAttributes, FuncSym))();
+}
+
+///
+unittest
+{
+    import std.traits : functionAttributes, FunctionAttribute;
+
+    alias FA = FunctionAttribute; // shorten the enum name
+
+    real func(real x) pure nothrow @safe
+    {
+        return x;
+    }
+    static assert(functionAttributes!func & FA.pure_);
+    static assert(functionAttributes!func & FA.safe);
+    static assert(!(functionAttributes!func & FA.trusted)); // not @trusted
+}
+
+unittest
+{
+    alias FA = FunctionAttribute;
+
+    struct S
+    {
+        int noF() { return 0; }
+        int constF() const { return 0; }
+        int immutableF() immutable { return 0; }
+        int inoutF() inout { return 0; }
+        int sharedF() shared { return 0; }
+
+        int x;
+        ref int refF() return { return x; }
+        int propertyF() @property { return 0; }
+        int nothrowF() nothrow { return 0; }
+        int nogcF() @nogc { return 0; }
+
+        int systemF() @system { return 0; }
+        int trustedF() @trusted { return 0; }
+        int safeF() @safe { return 0; }
+
+        int pureF() pure { return 0; }
+    }
+
+    static assert(functionAttributes!(S.noF) == FA.system);
+    static assert(functionAttributes!(typeof(S.noF)) == FA.system);
+
+    static assert(functionAttributes!(S.constF) == (FA.const_ | FA.system));
+    static assert(functionAttributes!(typeof(S.constF)) == (FA.const_ | FA.system));
+
+    static assert(functionAttributes!(S.immutableF) == (FA.immutable_ | FA.system));
+    static assert(functionAttributes!(typeof(S.immutableF)) == (FA.immutable_ | FA.system));
+
+    static assert(functionAttributes!(S.inoutF) == (FA.inout_ | FA.system));
+    static assert(functionAttributes!(typeof(S.inoutF)) == (FA.inout_ | FA.system));
+
+    static assert(functionAttributes!(S.sharedF) == (FA.shared_ | FA.system));
+    static assert(functionAttributes!(typeof(S.sharedF)) == (FA.shared_ | FA.system));
+
+    static assert(functionAttributes!(S.refF) == (FA.ref_ | FA.system | FA.return_));
+    static assert(functionAttributes!(typeof(S.refF)) == (FA.ref_ | FA.system | FA.return_));
+
+    static assert(functionAttributes!(S.propertyF) == (FA.property | FA.system));
+    static assert(functionAttributes!(typeof(&S.propertyF)) == (FA.property | FA.system));
+
+    static assert(functionAttributes!(S.nothrowF) == (FA.nothrow_ | FA.system));
+    static assert(functionAttributes!(typeof(S.nothrowF)) == (FA.nothrow_ | FA.system));
+
+    static assert(functionAttributes!(S.nogcF) == (FA.nogc | FA.system));
+    static assert(functionAttributes!(typeof(S.nogcF)) == (FA.nogc | FA.system));
+
+    static assert(functionAttributes!(S.systemF) == FA.system);
+    static assert(functionAttributes!(typeof(S.systemF)) == FA.system);
+
+    static assert(functionAttributes!(S.trustedF) == FA.trusted);
+    static assert(functionAttributes!(typeof(S.trustedF)) == FA.trusted);
+
+    static assert(functionAttributes!(S.safeF) == FA.safe);
+    static assert(functionAttributes!(typeof(S.safeF)) == FA.safe);
+
+    static assert(functionAttributes!(S.pureF) == (FA.pure_ | FA.system));
+    static assert(functionAttributes!(typeof(S.pureF)) == (FA.pure_ | FA.system));
+
+    int pure_nothrow() nothrow pure { return 0; }
+    void safe_nothrow() @safe nothrow { }
+    static ref int static_ref_property() @property { return *(new int); }
+    ref int ref_property() @property { return *(new int); }
+
+    static assert(functionAttributes!(pure_nothrow) == (FA.pure_ | FA.nothrow_ | FA.system));
+    static assert(functionAttributes!(typeof(pure_nothrow)) == (FA.pure_ | FA.nothrow_ | FA.system));
+
+    static assert(functionAttributes!(safe_nothrow) == (FA.safe | FA.nothrow_));
+    static assert(functionAttributes!(typeof(safe_nothrow)) == (FA.safe | FA.nothrow_));
+
+    static assert(functionAttributes!(static_ref_property) == (FA.property | FA.ref_ | FA.system));
+    static assert(functionAttributes!(typeof(&static_ref_property)) == (FA.property | FA.ref_ | FA.system));
+
+    static assert(functionAttributes!(ref_property) == (FA.property | FA.ref_ | FA.system));
+    static assert(functionAttributes!(typeof(&ref_property)) == (FA.property | FA.ref_ | FA.system));
+
+    struct S2
+    {
+        int pure_const() const pure { return 0; }
+        int pure_sharedconst() const shared pure { return 0; }
+    }
+
+    static assert(functionAttributes!(S2.pure_const) == (FA.const_ | FA.pure_ | FA.system));
+    static assert(functionAttributes!(typeof(S2.pure_const)) == (FA.const_ | FA.pure_ | FA.system));
+
+    static assert(functionAttributes!(S2.pure_sharedconst) == (FA.const_ | FA.shared_ | FA.pure_ | FA.system));
+    static assert(functionAttributes!(typeof(S2.pure_sharedconst)) == (FA.const_ | FA.shared_ | FA.pure_ | FA.system));
+
+    static assert(functionAttributes!((int a) { }) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe));
+    static assert(functionAttributes!(typeof((int a) { })) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe));
+
+    auto safeDel = delegate() @safe { };
+    static assert(functionAttributes!(safeDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe));
+    static assert(functionAttributes!(typeof(safeDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe));
+
+    auto trustedDel = delegate() @trusted { };
+    static assert(functionAttributes!(trustedDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.trusted));
+    static assert(functionAttributes!(typeof(trustedDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.trusted));
+
+    auto systemDel = delegate() @system { };
+    static assert(functionAttributes!(systemDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.system));
+    static assert(functionAttributes!(typeof(systemDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.system));
+}
+
+private FunctionAttribute extractAttribFlags(Attribs...)()
+{
+    auto res = FunctionAttribute.none;
+
+    foreach (attrib; Attribs)
+    {
+        switch (attrib) with (FunctionAttribute)
+        {
+            case "pure":      res |= pure_; break;
+            case "nothrow":   res |= nothrow_; break;
+            case "ref":       res |= ref_; break;
+            case "@property": res |= property; break;
+            case "@trusted":  res |= trusted; break;
+            case "@safe":     res |= safe; break;
+            case "@nogc":     res |= nogc; break;
+            case "@system":   res |= system; break;
+            case "const":     res |= const_; break;
+            case "immutable": res |= immutable_; break;
+            case "inout":     res |= inout_; break;
+            case "shared":    res |= shared_; break;
+            case "return":    res |= return_; break;
+            default: assert(0, attrib);
+        }
+    }
+
+    return res;
+}
+
+
+/**
+$(D true) if $(D func) is $(D @safe) or $(D @trusted).
+ */
+template isSafe(alias func)
+    if(isCallable!func)
+{
+    enum isSafe = (functionAttributes!func & FunctionAttribute.safe) != 0 ||
+                  (functionAttributes!func & FunctionAttribute.trusted) != 0;
+}
+
+///
+unittest
+{
+    @safe    int add(int a, int b) {return a+b;}
+    @trusted int sub(int a, int b) {return a-b;}
+    @system  int mul(int a, int b) {return a*b;}
+
+    static assert( isSafe!add);
+    static assert( isSafe!sub);
+    static assert(!isSafe!mul);
+}
+
+
+unittest
+{
+    //Member functions
+    interface Set
+    {
+        int systemF() @system;
+        int trustedF() @trusted;
+        int safeF() @safe;
+    }
+    static assert( isSafe!(Set.safeF));
+    static assert( isSafe!(Set.trustedF));
+    static assert(!isSafe!(Set.systemF));
+
+    //Functions
+    @safe static safeFunc() {}
+    @trusted static trustedFunc() {}
+    @system static systemFunc() {}
+
+    static assert( isSafe!safeFunc);
+    static assert( isSafe!trustedFunc);
+    static assert(!isSafe!systemFunc);
+
+    //Delegates
+    auto safeDel = delegate() @safe {};
+    auto trustedDel = delegate() @trusted {};
+    auto systemDel = delegate() @system {};
+
+    static assert( isSafe!safeDel);
+    static assert( isSafe!trustedDel);
+    static assert(!isSafe!systemDel);
+
+    //Lambdas
+    static assert( isSafe!({safeDel();}));
+    static assert( isSafe!({trustedDel();}));
+    static assert(!isSafe!({systemDel();}));
+
+    //Static opCall
+    struct SafeStatic { @safe static SafeStatic opCall() { return SafeStatic.init; } }
+    struct TrustedStatic { @trusted static TrustedStatic opCall() { return TrustedStatic.init; } }
+    struct SystemStatic { @system static SystemStatic opCall() { return SystemStatic.init; } }
+
+    static assert( isSafe!(SafeStatic()));
+    static assert( isSafe!(TrustedStatic()));
+    static assert(!isSafe!(SystemStatic()));
+
+    //Non-static opCall
+    struct Safe { @safe Safe opCall() { return Safe.init; } }
+    struct Trusted { @trusted Trusted opCall() { return Trusted.init; } }
+    struct System { @system System opCall() { return System.init; } }
+
+    static assert( isSafe!(Safe.init()));
+    static assert( isSafe!(Trusted.init()));
+    static assert(!isSafe!(System.init()));
+}
+
+
+/**
+$(D true) if $(D func) is $(D @system).
+*/
+template isUnsafe(alias func)
+{
+    enum isUnsafe = !isSafe!func;
+}
+
+///
+unittest
+{
+    @safe    int add(int a, int b) {return a+b;}
+    @trusted int sub(int a, int b) {return a-b;}
+    @system  int mul(int a, int b) {return a*b;}
+
+    static assert(!isUnsafe!add);
+    static assert(!isUnsafe!sub);
+    static assert( isUnsafe!mul);
+}
+
+unittest
+{
+    //Member functions
+    interface Set
+    {
+        int systemF() @system;
+        int trustedF() @trusted;
+        int safeF() @safe;
+    }
+    static assert(!isUnsafe!(Set.safeF));
+    static assert(!isUnsafe!(Set.trustedF));
+    static assert( isUnsafe!(Set.systemF));
+
+    //Functions
+    @safe static safeFunc() {}
+    @trusted static trustedFunc() {}
+    @system static systemFunc() {}
+
+    static assert(!isUnsafe!safeFunc);
+    static assert(!isUnsafe!trustedFunc);
+    static assert( isUnsafe!systemFunc);
+
+    //Delegates
+    auto safeDel = delegate() @safe {};
+    auto trustedDel = delegate() @trusted {};
+    auto systemDel = delegate() @system {};
+
+    static assert(!isUnsafe!safeDel);
+    static assert(!isUnsafe!trustedDel);
+    static assert( isUnsafe!systemDel);
+
+    //Lambdas
+    static assert(!isUnsafe!({safeDel();}));
+    static assert(!isUnsafe!({trustedDel();}));
+    static assert( isUnsafe!({systemDel();}));
+
+    //Static opCall
+    struct SafeStatic { @safe static SafeStatic opCall() { return SafeStatic.init; } }
+    struct TrustedStatic { @trusted static TrustedStatic opCall() { return TrustedStatic.init; } }
+    struct SystemStatic { @system static SystemStatic opCall() { return SystemStatic.init; } }
+
+    static assert(!isUnsafe!(SafeStatic()));
+    static assert(!isUnsafe!(TrustedStatic()));
+    static assert( isUnsafe!(SystemStatic()));
+
+    //Non-static opCall
+    struct Safe { @safe Safe opCall() { return Safe.init; } }
+    struct Trusted { @trusted Trusted opCall() { return Trusted.init; } }
+    struct System { @system System opCall() { return System.init; } }
+
+    static assert(!isUnsafe!(Safe.init()));
+    static assert(!isUnsafe!(Trusted.init()));
+    static assert( isUnsafe!(System.init()));
+}
+
+
+// Explicitly undocumented. It will be removed in June 2016. @@@DEPRECATED_2016-06@@@
+deprecated("Please use allSatisfy(isSafe, ...) instead.")
+template areAllSafe(funcs...)
+    if (funcs.length > 0)
+{
+    static if (funcs.length == 1)
+    {
+        enum areAllSafe = isSafe!(funcs[0]);
+    }
+    else static if (isSafe!(funcs[0]))
+    {
+        enum areAllSafe = areAllSafe!(funcs[1..$]);
+    }
+    else
+    {
+        enum areAllSafe = false;
+    }
+}
+
+// Verify Example
+deprecated unittest
+{
+    @safe    int add(int a, int b) {return a+b;}
+    @trusted int sub(int a, int b) {return a-b;}
+    @system  int mul(int a, int b) {return a*b;}
+
+    static assert( areAllSafe!(add, add));
+    static assert( areAllSafe!(add, sub));
+    static assert(!areAllSafe!(sub, mul));
+}
+
+deprecated unittest
+{
+    interface Set
+    {
+        int systemF() @system;
+        int trustedF() @trusted;
+        int safeF() @safe;
+    }
+    static assert( areAllSafe!((int a){}, Set.safeF));
+    static assert( areAllSafe!((int a){}, Set.safeF, Set.trustedF));
+    static assert(!areAllSafe!(Set.trustedF, Set.systemF));
+}
+
+
+/**
+Returns the calling convention of function as a string.
+*/
+template functionLinkage(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    alias Func = Unqual!(FunctionTypeOf!func);
+
+    enum string functionLinkage =
+        [
+            'F': "D",
+            'U': "C",
+            'W': "Windows",
+            'V': "Pascal",
+            'R': "C++"
+        ][ mangledName!Func[0] ];
+}
+
+///
+unittest
+{
+    extern(D) void Dfunc() {}
+    extern(C) void Cfunc() {}
+    static assert(functionLinkage!Dfunc == "D");
+    static assert(functionLinkage!Cfunc == "C");
+
+    string a = functionLinkage!Dfunc;
+    assert(a == "D");
+
+    auto fp = &Cfunc;
+    string b = functionLinkage!fp;
+    assert(b == "C");
+}
+
+unittest
+{
+    interface Test
+    {
+        void const_func() const;
+        void sharedconst_func() shared const;
+    }
+    static assert(functionLinkage!(Test.const_func) == "D");
+    static assert(functionLinkage!(Test.sharedconst_func) == "D");
+
+    static assert(functionLinkage!((int a){}) == "D");
+}
+
+
+/**
+Determines what kind of variadic parameters function has.
+ */
+enum Variadic
+{
+    no,       /// Function is not variadic.
+    c,        /// Function is a _C-style variadic function.
+              /// Function is a _D-style variadic function, which uses
+    d,        /// __argptr and __arguments.
+    typesafe, /// Function is a typesafe variadic function.
+}
+
+/// ditto
+template variadicFunctionStyle(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    alias Func = Unqual!(FunctionTypeOf!func);
+
+    // TypeFuncion --> CallConvention FuncAttrs Arguments ArgClose Type
+    enum callconv = functionLinkage!Func;
+    enum mfunc = mangledName!Func;
+    enum mtype = mangledName!(ReturnType!Func);
+    static assert(mfunc[$ - mtype.length .. $] == mtype, mfunc ~ "|" ~ mtype);
+
+    enum argclose = mfunc[$ - mtype.length - 1];
+    static assert(argclose >= 'X' && argclose <= 'Z');
+
+    enum Variadic variadicFunctionStyle =
+        argclose == 'X' ? Variadic.typesafe :
+        argclose == 'Y' ? (callconv == "C") ? Variadic.c : Variadic.d :
+        Variadic.no; // 'Z'
+}
+
+///
+unittest
+{
+    void func() {}
+    static assert(variadicFunctionStyle!func == Variadic.no);
+
+    extern(C) int printf(in char*, ...);
+    static assert(variadicFunctionStyle!printf == Variadic.c);
+}
+
+unittest
+{
+    import core.vararg;
+
+    extern(D) void novar() {}
+    extern(C) void cstyle(int, ...) {}
+    extern(D) void dstyle(...) {}
+    extern(D) void typesafe(int[]...) {}
+
+    static assert(variadicFunctionStyle!novar == Variadic.no);
+    static assert(variadicFunctionStyle!cstyle == Variadic.c);
+    static assert(variadicFunctionStyle!dstyle == Variadic.d);
+    static assert(variadicFunctionStyle!typesafe == Variadic.typesafe);
+
+    static assert(variadicFunctionStyle!((int[] a...) {}) == Variadic.typesafe);
+}
+
+
+/**
+Get the function type from a callable object $(D func).
+
+Using builtin $(D typeof) on a property function yields the types of the
+property value, not of the property function itself.  Still,
+$(D FunctionTypeOf) is able to obtain function types of properties.
+
+Note:
+Do not confuse function types with function pointer types; function types are
+usually used for compile-time reflection purposes.
+ */
+template FunctionTypeOf(func...)
+    if (func.length == 1 && isCallable!func)
+{
+    static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate))
+    {
+        alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol
+    }
+    else static if (is(typeof(& func[0].opCall) Fobj == delegate))
+    {
+        alias FunctionTypeOf = Fobj; // HIT: callable object
+    }
+    else static if (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function))
+    {
+        alias FunctionTypeOf = Ftyp; // HIT: callable type
+    }
+    else static if (is(func[0] T) || is(typeof(func[0]) T))
+    {
+        static if (is(T == function))
+            alias FunctionTypeOf = T;    // HIT: function
+        else static if (is(T Fptr : Fptr*) && is(Fptr == function))
+            alias FunctionTypeOf = Fptr; // HIT: function pointer
+        else static if (is(T Fdlg == delegate))
+            alias FunctionTypeOf = Fdlg; // HIT: delegate
+        else
+            static assert(0);
+    }
+    else
+        static assert(0);
+}
+
+///
+unittest
+{
+    class C
+    {
+        int value() @property { return 0; }
+    }
+    static assert(is( typeof(C.value) == int ));
+    static assert(is( FunctionTypeOf!(C.value) == function ));
+}
+
+unittest
+{
+    int test(int a) { return 0; }
+    int propGet() @property { return 0; }
+    int propSet(int a) @property { return 0; }
+    int function(int) test_fp;
+    int delegate(int) test_dg;
+    static assert(is( typeof(test) == FunctionTypeOf!(typeof(test)) ));
+    static assert(is( typeof(test) == FunctionTypeOf!test ));
+    static assert(is( typeof(test) == FunctionTypeOf!test_fp ));
+    static assert(is( typeof(test) == FunctionTypeOf!test_dg ));
+    alias int GetterType() @property;
+    alias int SetterType(int) @property;
+    static assert(is( FunctionTypeOf!propGet == GetterType ));
+    static assert(is( FunctionTypeOf!propSet == SetterType ));
+
+    interface Prop { int prop() @property; }
+    Prop prop;
+    static assert(is( FunctionTypeOf!(Prop.prop) == GetterType ));
+    static assert(is( FunctionTypeOf!(prop.prop) == GetterType ));
+
+    class Callable { int opCall(int) { return 0; } }
+    auto call = new Callable;
+    static assert(is( FunctionTypeOf!call == typeof(test) ));
+
+    struct StaticCallable { static int opCall(int) { return 0; } }
+    StaticCallable stcall_val;
+    StaticCallable* stcall_ptr;
+    static assert(is( FunctionTypeOf!stcall_val == typeof(test) ));
+    static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) ));
+
+    interface Overloads
+    {
+        void test(string);
+        real test(real);
+        int  test(int);
+        int  test() @property;
+    }
+    alias ov = TypeTuple!(__traits(getVirtualFunctions, Overloads, "test"));
+    alias F_ov0 = FunctionTypeOf!(ov[0]);
+    alias F_ov1 = FunctionTypeOf!(ov[1]);
+    alias F_ov2 = FunctionTypeOf!(ov[2]);
+    alias F_ov3 = FunctionTypeOf!(ov[3]);
+    static assert(is(F_ov0* == void function(string)));
+    static assert(is(F_ov1* == real function(real)));
+    static assert(is(F_ov2* == int function(int)));
+    static assert(is(F_ov3* == int function() @property));
+
+    alias F_dglit = FunctionTypeOf!((int a){ return a; });
+    static assert(is(F_dglit* : int function(int)));
+}
+
+/**
+ * Constructs a new function or delegate type with the same basic signature
+ * as the given one, but different attributes (including linkage).
+ *
+ * This is especially useful for adding/removing attributes to/from types in
+ * generic code, where the actual type name cannot be spelt out.
+ *
+ * Params:
+ *    T = The base type.
+ *    linkage = The desired linkage of the result type.
+ *    attrs = The desired $(LREF FunctionAttribute)s of the result type.
+ */
+template SetFunctionAttributes(T, string linkage, uint attrs)
+    if (isFunctionPointer!T || isDelegate!T)
+{
+    mixin({
+        import std.algorithm : canFind;
+
+        static assert(!(attrs & FunctionAttribute.trusted) ||
+            !(attrs & FunctionAttribute.safe),
+            "Cannot have a function/delegate that is both trusted and safe.");
+
+        enum linkages = ["D", "C", "Windows", "Pascal", "C++", "System"];
+        static assert(canFind(linkages, linkage), "Invalid linkage '" ~
+            linkage ~ "', must be one of " ~ linkages.stringof ~ ".");
+
+        string result = "alias ";
+
+        static if (linkage != "D")
+            result ~= "extern(" ~ linkage ~ ") ";
+
+        static if (attrs & FunctionAttribute.ref_)
+            result ~= "ref ";
+
+        result ~= "ReturnType!T";
+
+        static if (isDelegate!T)
+            result ~= " delegate";
+        else
+            result ~= " function";
+
+        result ~= "(";
+
+        static if (Parameters!T.length > 0)
+            result ~= "Parameters!T";
+
+        enum varStyle = variadicFunctionStyle!T;
+        static if (varStyle == Variadic.c)
+            result ~= ", ...";
+        else static if (varStyle == Variadic.d)
+            result ~= "...";
+        else static if (varStyle == Variadic.typesafe)
+            result ~= "...";
+
+        result ~= ")";
+
+        static if (attrs & FunctionAttribute.pure_)
+            result ~= " pure";
+        static if (attrs & FunctionAttribute.nothrow_)
+            result ~= " nothrow";
+        static if (attrs & FunctionAttribute.property)
+            result ~= " @property";
+        static if (attrs & FunctionAttribute.trusted)
+            result ~= " @trusted";
+        static if (attrs & FunctionAttribute.safe)
+            result ~= " @safe";
+        static if (attrs & FunctionAttribute.nogc)
+            result ~= " @nogc";
+        static if (attrs & FunctionAttribute.system)
+            result ~= " @system";
+        static if (attrs & FunctionAttribute.const_)
+            result ~= " const";
+        static if (attrs & FunctionAttribute.immutable_)
+            result ~= " immutable";
+        static if (attrs & FunctionAttribute.inout_)
+            result ~= " inout";
+        static if (attrs & FunctionAttribute.shared_)
+            result ~= " shared";
+        static if (attrs & FunctionAttribute.return_)
+            result ~= " return";
+
+        result ~= " SetFunctionAttributes;";
+        return result;
+    }());
+}
+
+/// Ditto
+template SetFunctionAttributes(T, string linkage, uint attrs)
+    if (is(T == function))
+{
+    // To avoid a lot of syntactic headaches, we just use the above version to
+    // operate on the corresponding function pointer type and then remove the
+    // indirection again.
+    alias SetFunctionAttributes = FunctionTypeOf!(SetFunctionAttributes!(T*, linkage, attrs));
+}
+
+///
+unittest
+{
+    alias ExternC(T) = SetFunctionAttributes!(T, "C", functionAttributes!T);
+
+    auto assumePure(T)(T t)
+        if (isFunctionPointer!T || isDelegate!T)
+    {
+        enum attrs = functionAttributes!T | FunctionAttribute.pure_;
+        return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
+    }
+}
+
+version (unittest)
+{
+    // Some function types to test.
+    int sc(scope int, ref int, out int, lazy int, int);
+    extern(System) int novar();
+    extern(C) int cstyle(int, ...);
+    extern(D) int dstyle(...);
+    extern(D) int typesafe(int[]...);
+}
+unittest
+{
+    import std.algorithm : reduce;
+
+    alias FA = FunctionAttribute;
+    foreach (BaseT; TypeTuple!(typeof(&sc), typeof(&novar), typeof(&cstyle),
+        typeof(&dstyle), typeof(&typesafe)))
+    {
+        foreach (T; TypeTuple!(BaseT, FunctionTypeOf!BaseT))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            enum linkage = functionLinkage!T;
+            enum attrs = functionAttributes!T;
+
+            static assert(is(SetFunctionAttributes!(T, linkage, attrs) == T),
+                "Identity check failed for: " ~ T.stringof);
+
+            // Check that all linkage types work (D-style variadics require D linkage).
+            static if (variadicFunctionStyle!T != Variadic.d)
+            {
+                foreach (newLinkage; TypeTuple!("D", "C", "Windows", "Pascal", "C++"))
+                {
+                    alias New = SetFunctionAttributes!(T, newLinkage, attrs);
+                    static assert(functionLinkage!New == newLinkage,
+                        "Linkage test failed for: " ~ T.stringof ~ ", " ~ newLinkage ~
+                        " (got " ~ New.stringof ~ ")");
+                }
+            }
+
+            // Add @safe.
+            alias T1 = SetFunctionAttributes!(T, functionLinkage!T, FA.safe);
+            static assert(functionAttributes!T1 == FA.safe);
+
+            // Add all known attributes, excluding conflicting ones.
+            enum allAttrs = reduce!"a | b"([EnumMembers!FA])
+                & ~FA.safe & ~FA.property & ~FA.const_ & ~FA.immutable_ & ~FA.inout_ & ~FA.shared_ & ~FA.system & ~FA.return_;
+
+            alias T2 = SetFunctionAttributes!(T1, functionLinkage!T, allAttrs);
+            static assert(functionAttributes!T2 == allAttrs);
+
+            // Strip all attributes again.
+            alias T3 = SetFunctionAttributes!(T2, functionLinkage!T, FA.none);
+            static assert(is(T3 == T));
+        }();
+    }
+}
+
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// Aggregate Types
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/**
+Determines whether $(D T) has its own context pointer.
+$(D T) must be either $(D class), $(D struct), or $(D union).
+*/
+template isNested(T)
+    if(is(T == class) || is(T == struct) || is(T == union))
+{
+    enum isNested = __traits(isNested, T);
+}
+
+///
+unittest
+{
+    static struct S { }
+    static assert(!isNested!S);
+
+    int i;
+    struct NestedStruct { void f() { ++i; } }
+    static assert(isNested!NestedStruct);
+}
+
+/**
+Determines whether $(D T) or any of its representation types
+have a context pointer.
+*/
+template hasNested(T)
+{
+    static if(isStaticArray!T && T.length)
+        enum hasNested = hasNested!(typeof(T.init[0]));
+    else static if(is(T == class) || is(T == struct) || is(T == union))
+        enum hasNested = isNested!T ||
+            anySatisfy!(.hasNested, Fields!T);
+    else
+        enum hasNested = false;
+}
+
+///
+unittest
+{
+    static struct S { }
+
+    int i;
+    struct NS { void f() { ++i; } }
+
+    static assert(!hasNested!(S[2]));
+    static assert(hasNested!(NS[2]));
+}
+
+unittest
+{
+    static assert(!__traits(compiles, isNested!int));
+    static assert(!hasNested!int);
+
+    static struct StaticStruct { }
+    static assert(!isNested!StaticStruct);
+    static assert(!hasNested!StaticStruct);
+
+    int i;
+    struct NestedStruct { void f() { ++i; } }
+    static assert( isNested!NestedStruct);
+    static assert( hasNested!NestedStruct);
+    static assert( isNested!(immutable NestedStruct));
+    static assert( hasNested!(immutable NestedStruct));
+
+    static assert(!__traits(compiles, isNested!(NestedStruct[1])));
+    static assert( hasNested!(NestedStruct[1]));
+    static assert(!hasNested!(NestedStruct[0]));
+
+    struct S1 { NestedStruct nested; }
+    static assert(!isNested!S1);
+    static assert( hasNested!S1);
+
+    static struct S2 { NestedStruct nested; }
+    static assert(!isNested!S2);
+    static assert( hasNested!S2);
+
+    static struct S3 { NestedStruct[0] nested; }
+    static assert(!isNested!S3);
+    static assert(!hasNested!S3);
+
+    static union U { NestedStruct nested; }
+    static assert(!isNested!U);
+    static assert( hasNested!U);
+
+    static class StaticClass { }
+    static assert(!isNested!StaticClass);
+    static assert(!hasNested!StaticClass);
+
+    class NestedClass { void f() { ++i; } }
+    static assert( isNested!NestedClass);
+    static assert( hasNested!NestedClass);
+    static assert( isNested!(immutable NestedClass));
+    static assert( hasNested!(immutable NestedClass));
+
+    static assert(!__traits(compiles, isNested!(NestedClass[1])));
+    static assert( hasNested!(NestedClass[1]));
+    static assert(!hasNested!(NestedClass[0]));
+}
+
+
+/***
+ * Get as a tuple the types of the fields of a struct, class, or union.
+ * This consists of the fields that take up memory space,
+ * excluding the hidden fields like the virtual function
+ * table pointer or a context pointer for nested types.
+ * If $(D T) isn't a struct, class, or union returns a tuple
+ * with one element $(D T).
+ */
+template Fields(T)
+{
+    static if (is(T == struct) || is(T == union))
+        alias Fields = typeof(T.tupleof[0 .. $ - isNested!T]);
+    else static if (is(T == class))
+        alias Fields = typeof(T.tupleof);
+    else
+        alias Fields = TypeTuple!T;
+}
+
+///
+unittest
+{
+    struct S { int x; float y; }
+    static assert(is(Fields!S == TypeTuple!(int, float)));
+}
+
+/**
+ * Alternate name for $(LREF Fields), kept for legacy compatibility.
+ */
+alias FieldTypeTuple = Fields;
+
+unittest
+{
+    static assert(is(FieldTypeTuple!int == TypeTuple!int));
+
+    static struct StaticStruct1 { }
+    static assert(is(FieldTypeTuple!StaticStruct1 == TypeTuple!()));
+
+    static struct StaticStruct2 { int a, b; }
+    static assert(is(FieldTypeTuple!StaticStruct2 == TypeTuple!(int, int)));
+
+    int i;
+
+    struct NestedStruct1 { void f() { ++i; } }
+    static assert(is(FieldTypeTuple!NestedStruct1 == TypeTuple!()));
+
+    struct NestedStruct2 { int a; void f() { ++i; } }
+    static assert(is(FieldTypeTuple!NestedStruct2 == TypeTuple!int));
+
+    class NestedClass { int a; void f() { ++i; } }
+    static assert(is(FieldTypeTuple!NestedClass == TypeTuple!int));
+}
+
+
+//Required for FieldNameTuple
+private enum NameOf(alias T) = T.stringof;
+
+/**
+ * Get as an expression tuple the names of the fields of a struct, class, or
+ * union. This consists of the fields that take up memory space, excluding the
+ * hidden fields like the virtual function table pointer or a context pointer
+ * for nested types. If $(D T) isn't a struct, class, or union returns an
+ * expression tuple with an empty string.
+ */
+template FieldNameTuple(T)
+{
+    static if (is(T == struct) || is(T == union))
+        alias FieldNameTuple = staticMap!(NameOf, T.tupleof[0 .. $ - isNested!T]);
+    else static if (is(T == class))
+        alias FieldNameTuple = staticMap!(NameOf, T.tupleof);
+    else
+        alias FieldNameTuple = TypeTuple!"";
+}
+
+///
+unittest
+{
+    struct S { int x; float y; }
+    static assert(FieldNameTuple!S == TypeTuple!("x", "y"));
+    static assert(FieldNameTuple!int == TypeTuple!"");
+}
+
+unittest
+{
+    static assert(FieldNameTuple!int == TypeTuple!"");
+
+    static struct StaticStruct1 { }
+    static assert(is(FieldNameTuple!StaticStruct1 == TypeTuple!()));
+
+    static struct StaticStruct2 { int a, b; }
+    static assert(FieldNameTuple!StaticStruct2 == TypeTuple!("a", "b"));
+
+    int i;
+
+    struct NestedStruct1 { void f() { ++i; } }
+    static assert(is(FieldNameTuple!NestedStruct1 == TypeTuple!()));
+
+    struct NestedStruct2 { int a; void f() { ++i; } }
+    static assert(FieldNameTuple!NestedStruct2 == TypeTuple!"a");
+
+    class NestedClass { int a; void f() { ++i; } }
+    static assert(FieldNameTuple!NestedClass == TypeTuple!"a");
+}
+
+
+/***
+Get the primitive types of the fields of a struct or class, in
+topological order.
+*/
+template RepresentationTypeTuple(T)
+{
+    template Impl(T...)
+    {
+        static if (T.length == 0)
+        {
+            alias Impl = TypeTuple!();
+        }
+        else
+        {
+            import std.typecons : Rebindable;
+
+            static if (is(T[0] R: Rebindable!R))
+            {
+                alias Impl = Impl!(Impl!R, T[1 .. $]);
+            }
+            else  static if (is(T[0] == struct) || is(T[0] == union))
+            {
+                // @@@BUG@@@ this should work
+                //alias .RepresentationTypes!(T[0].tupleof)
+                //    RepresentationTypes;
+                alias Impl = Impl!(FieldTypeTuple!(T[0]), T[1 .. $]);
+            }
+            else
+            {
+                alias Impl = TypeTuple!(T[0], Impl!(T[1 .. $]));
+            }
+        }
+    }
+
+    static if (is(T == struct) || is(T == union) || is(T == class))
+    {
+        alias RepresentationTypeTuple = Impl!(FieldTypeTuple!T);
+    }
+    else
+    {
+        alias RepresentationTypeTuple = Impl!T;
+    }
+}
+
+///
+unittest
+{
+    struct S1 { int a; float b; }
+    struct S2 { char[] a; union { S1 b; S1 * c; } }
+    alias R = RepresentationTypeTuple!S2;
+    assert(R.length == 4
+        && is(R[0] == char[]) && is(R[1] == int)
+        && is(R[2] == float) && is(R[3] == S1*));
+}
+
+unittest
+{
+    alias S1 = RepresentationTypeTuple!int;
+    static assert(is(S1 == TypeTuple!int));
+
+    struct S2 { int a; }
+    struct S3 { int a; char b; }
+    struct S4 { S1 a; int b; S3 c; }
+    static assert(is(RepresentationTypeTuple!S2 == TypeTuple!int));
+    static assert(is(RepresentationTypeTuple!S3 == TypeTuple!(int, char)));
+    static assert(is(RepresentationTypeTuple!S4 == TypeTuple!(int, int, int, char)));
+
+    struct S11 { int a; float b; }
+    struct S21 { char[] a; union { S11 b; S11 * c; } }
+    alias R = RepresentationTypeTuple!S21;
+    assert(R.length == 4
+           && is(R[0] == char[]) && is(R[1] == int)
+           && is(R[2] == float) && is(R[3] == S11*));
+
+    class C { int a; float b; }
+    alias R1 = RepresentationTypeTuple!C;
+    static assert(R1.length == 2 && is(R1[0] == int) && is(R1[1] == float));
+
+    /* Issue 6642 */
+    import std.typecons : Rebindable;
+
+    struct S5 { int a; Rebindable!(immutable Object) b; }
+    alias R2 = RepresentationTypeTuple!S5;
+    static assert(R2.length == 2 && is(R2[0] == int) && is(R2[1] == immutable(Object)));
+}
+
+/*
+Statically evaluates to $(D true) if and only if $(D T)'s
+representation contains at least one field of pointer or array type.
+Members of class types are not considered raw pointers. Pointers to
+immutable objects are not considered raw aliasing.
+*/
+private template hasRawAliasing(T...)
+{
+    template Impl(T...)
+    {
+        static if (T.length == 0)
+        {
+            enum Impl = false;
+        }
+        else
+        {
+            static if (is(T[0] foo : U*, U) && !isFunctionPointer!(T[0]))
+                enum has = !is(U == immutable);
+            else static if (is(T[0] foo : U[], U) && !isStaticArray!(T[0]))
+                enum has = !is(U == immutable);
+            else static if (isAssociativeArray!(T[0]))
+                enum has = !is(T[0] == immutable);
+            else
+                enum has = false;
+
+            enum Impl = has || Impl!(T[1 .. $]);
+        }
+    }
+
+    enum hasRawAliasing = Impl!(RepresentationTypeTuple!T);
+}
+
+///
+unittest
+{
+    // simple types
+    static assert(!hasRawAliasing!int);
+    static assert( hasRawAliasing!(char*));
+    // references aren't raw pointers
+    static assert(!hasRawAliasing!Object);
+    // built-in arrays do contain raw pointers
+    static assert( hasRawAliasing!(int[]));
+    // aggregate of simple types
+    struct S1 { int a; double b; }
+    static assert(!hasRawAliasing!S1);
+    // indirect aggregation
+    struct S2 { S1 a; double b; }
+    static assert(!hasRawAliasing!S2);
+}
+
+unittest
+{
+    // struct with a pointer member
+    struct S3 { int a; double * b; }
+    static assert( hasRawAliasing!S3);
+    // struct with an indirect pointer member
+    struct S4 { S3 a; double b; }
+    static assert( hasRawAliasing!S4);
+    struct S5 { int a; Object z; int c; }
+    static assert( hasRawAliasing!S3);
+    static assert( hasRawAliasing!S4);
+    static assert(!hasRawAliasing!S5);
+
+    union S6 { int a; int b; }
+    union S7 { int a; int * b; }
+    static assert(!hasRawAliasing!S6);
+    static assert( hasRawAliasing!S7);
+
+    static assert(!hasRawAliasing!(void delegate()));
+    static assert(!hasRawAliasing!(void delegate() const));
+    static assert(!hasRawAliasing!(void delegate() immutable));
+    static assert(!hasRawAliasing!(void delegate() shared));
+    static assert(!hasRawAliasing!(void delegate() shared const));
+    static assert(!hasRawAliasing!(const(void delegate())));
+    static assert(!hasRawAliasing!(immutable(void delegate())));
+
+    struct S8 { void delegate() a; int b; Object c; }
+    class S12 { typeof(S8.tupleof) a; }
+    class S13 { typeof(S8.tupleof) a; int* b; }
+    static assert(!hasRawAliasing!S8);
+    static assert(!hasRawAliasing!S12);
+    static assert( hasRawAliasing!S13);
+
+    enum S9 { a }
+    static assert(!hasRawAliasing!S9);
+
+    // indirect members
+    struct S10 { S7 a; int b; }
+    struct S11 { S6 a; int b; }
+    static assert( hasRawAliasing!S10);
+    static assert(!hasRawAliasing!S11);
+
+    static assert( hasRawAliasing!(int[string]));
+    static assert(!hasRawAliasing!(immutable(int[string])));
+}
+
+/*
+Statically evaluates to $(D true) if and only if $(D T)'s
+representation contains at least one non-shared field of pointer or
+array type.  Members of class types are not considered raw pointers.
+Pointers to immutable objects are not considered raw aliasing.
+*/
+private template hasRawUnsharedAliasing(T...)
+{
+    template Impl(T...)
+    {
+        static if (T.length == 0)
+        {
+            enum Impl = false;
+        }
+        else
+        {
+            static if (is(T[0] foo : U*, U) && !isFunctionPointer!(T[0]))
+                enum has = !is(U == immutable) && !is(U == shared);
+            else static if (is(T[0] foo : U[], U) && !isStaticArray!(T[0]))
+                enum has = !is(U == immutable) && !is(U == shared);
+            else static if (isAssociativeArray!(T[0]))
+                enum has = !is(T[0] == immutable) && !is(T[0] == shared);
+            else
+                enum has = false;
+
+            enum Impl = has || Impl!(T[1 .. $]);
+        }
+    }
+
+    enum hasRawUnsharedAliasing = Impl!(RepresentationTypeTuple!T);
+}
+
+///
+unittest
+{
+    // simple types
+    static assert(!hasRawUnsharedAliasing!int);
+    static assert( hasRawUnsharedAliasing!(char*));
+    static assert(!hasRawUnsharedAliasing!(shared char*));
+    // references aren't raw pointers
+    static assert(!hasRawUnsharedAliasing!Object);
+    // built-in arrays do contain raw pointers
+    static assert( hasRawUnsharedAliasing!(int[]));
+    static assert(!hasRawUnsharedAliasing!(shared int[]));
+    // aggregate of simple types
+    struct S1 { int a; double b; }
+    static assert(!hasRawUnsharedAliasing!S1);
+    // indirect aggregation
+    struct S2 { S1 a; double b; }
+    static assert(!hasRawUnsharedAliasing!S2);
+    // struct with a pointer member
+    struct S3 { int a; double * b; }
+    static assert( hasRawUnsharedAliasing!S3);
+    struct S4 { int a; shared double * b; }
+    static assert(!hasRawUnsharedAliasing!S4);
+}
+
+unittest
+{
+    // struct with a pointer member
+    struct S3 { int a; double * b; }
+    static assert( hasRawUnsharedAliasing!S3);
+    struct S4 { int a; shared double * b; }
+    static assert(!hasRawUnsharedAliasing!S4);
+    // struct with an indirect pointer member
+    struct S5 { S3 a; double b; }
+    static assert( hasRawUnsharedAliasing!S5);
+    struct S6 { S4 a; double b; }
+    static assert(!hasRawUnsharedAliasing!S6);
+    struct S7 { int a; Object z;      int c; }
+    static assert( hasRawUnsharedAliasing!S5);
+    static assert(!hasRawUnsharedAliasing!S6);
+    static assert(!hasRawUnsharedAliasing!S7);
+
+    union S8  { int a; int b; }
+    union S9  { int a; int* b; }
+    union S10 { int a; shared int* b; }
+    static assert(!hasRawUnsharedAliasing!S8);
+    static assert( hasRawUnsharedAliasing!S9);
+    static assert(!hasRawUnsharedAliasing!S10);
+
+    static assert(!hasRawUnsharedAliasing!(void delegate()));
+    static assert(!hasRawUnsharedAliasing!(void delegate() const));
+    static assert(!hasRawUnsharedAliasing!(void delegate() immutable));
+    static assert(!hasRawUnsharedAliasing!(void delegate() shared));
+    static assert(!hasRawUnsharedAliasing!(void delegate() shared const));
+    static assert(!hasRawUnsharedAliasing!(const(void delegate())));
+    static assert(!hasRawUnsharedAliasing!(const(void delegate() const)));
+    static assert(!hasRawUnsharedAliasing!(const(void delegate() immutable)));
+    static assert(!hasRawUnsharedAliasing!(const(void delegate() shared)));
+    static assert(!hasRawUnsharedAliasing!(const(void delegate() shared const)));
+    static assert(!hasRawUnsharedAliasing!(immutable(void delegate())));
+    static assert(!hasRawUnsharedAliasing!(immutable(void delegate() const)));
+    static assert(!hasRawUnsharedAliasing!(immutable(void delegate() immutable)));
+    static assert(!hasRawUnsharedAliasing!(immutable(void delegate() shared)));
+    static assert(!hasRawUnsharedAliasing!(immutable(void delegate() shared const)));
+    static assert(!hasRawUnsharedAliasing!(shared(void delegate())));
+    static assert(!hasRawUnsharedAliasing!(shared(void delegate() const)));
+    static assert(!hasRawUnsharedAliasing!(shared(void delegate() immutable)));
+    static assert(!hasRawUnsharedAliasing!(shared(void delegate() shared)));
+    static assert(!hasRawUnsharedAliasing!(shared(void delegate() shared const)));
+    static assert(!hasRawUnsharedAliasing!(shared(const(void delegate()))));
+    static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() const))));
+    static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() immutable))));
+    static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() shared))));
+    static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() shared const))));
+    static assert(!hasRawUnsharedAliasing!(void function()));
+
+    enum S13 { a }
+    static assert(!hasRawUnsharedAliasing!S13);
+
+    // indirect members
+    struct S14 { S9  a; int b; }
+    struct S15 { S10 a; int b; }
+    struct S16 { S6  a; int b; }
+    static assert( hasRawUnsharedAliasing!S14);
+    static assert(!hasRawUnsharedAliasing!S15);
+    static assert(!hasRawUnsharedAliasing!S16);
+
+    static assert( hasRawUnsharedAliasing!(int[string]));
+    static assert(!hasRawUnsharedAliasing!(shared(int[string])));
+    static assert(!hasRawUnsharedAliasing!(immutable(int[string])));
+
+    struct S17
+    {
+        void delegate() shared a;
+        void delegate() immutable b;
+        void delegate() shared const c;
+        shared(void delegate()) d;
+        shared(void delegate() shared) e;
+        shared(void delegate() immutable) f;
+        shared(void delegate() shared const) g;
+        immutable(void delegate()) h;
+        immutable(void delegate() shared) i;
+        immutable(void delegate() immutable) j;
+        immutable(void delegate() shared const) k;
+        shared(const(void delegate())) l;
+        shared(const(void delegate() shared)) m;
+        shared(const(void delegate() immutable)) n;
+        shared(const(void delegate() shared const)) o;
+    }
+    struct S18 { typeof(S17.tupleof) a; void delegate() p; }
+    struct S19 { typeof(S17.tupleof) a; Object p; }
+    struct S20 { typeof(S17.tupleof) a; int* p; }
+    class S21 { typeof(S17.tupleof) a; }
+    class S22 { typeof(S17.tupleof) a; void delegate() p; }
+    class S23 { typeof(S17.tupleof) a; Object p; }
+    class S24 { typeof(S17.tupleof) a; int* p; }
+    static assert(!hasRawUnsharedAliasing!S17);
+    static assert(!hasRawUnsharedAliasing!(immutable(S17)));
+    static assert(!hasRawUnsharedAliasing!(shared(S17)));
+    static assert(!hasRawUnsharedAliasing!S18);
+    static assert(!hasRawUnsharedAliasing!(immutable(S18)));
+    static assert(!hasRawUnsharedAliasing!(shared(S18)));
+    static assert(!hasRawUnsharedAliasing!S19);
+    static assert(!hasRawUnsharedAliasing!(immutable(S19)));
+    static assert(!hasRawUnsharedAliasing!(shared(S19)));
+    static assert( hasRawUnsharedAliasing!S20);
+    static assert(!hasRawUnsharedAliasing!(immutable(S20)));
+    static assert(!hasRawUnsharedAliasing!(shared(S20)));
+    static assert(!hasRawUnsharedAliasing!S21);
+    static assert(!hasRawUnsharedAliasing!(immutable(S21)));
+    static assert(!hasRawUnsharedAliasing!(shared(S21)));
+    static assert(!hasRawUnsharedAliasing!S22);
+    static assert(!hasRawUnsharedAliasing!(immutable(S22)));
+    static assert(!hasRawUnsharedAliasing!(shared(S22)));
+    static assert(!hasRawUnsharedAliasing!S23);
+    static assert(!hasRawUnsharedAliasing!(immutable(S23)));
+    static assert(!hasRawUnsharedAliasing!(shared(S23)));
+    static assert( hasRawUnsharedAliasing!S24);
+    static assert(!hasRawUnsharedAliasing!(immutable(S24)));
+    static assert(!hasRawUnsharedAliasing!(shared(S24)));
+    struct S25 {}
+    class S26 {}
+    interface S27 {}
+    union S28 {}
+    static assert(!hasRawUnsharedAliasing!S25);
+    static assert(!hasRawUnsharedAliasing!S26);
+    static assert(!hasRawUnsharedAliasing!S27);
+    static assert(!hasRawUnsharedAliasing!S28);
+}
+
+/*
+Statically evaluates to $(D true) if and only if $(D T)'s
+representation includes at least one non-immutable object reference.
+*/
+
+private template hasObjects(T...)
+{
+    static if (T.length == 0)
+    {
+        enum hasObjects = false;
+    }
+    else static if (is(T[0] == struct))
+    {
+        enum hasObjects = hasObjects!(
+            RepresentationTypeTuple!(T[0]), T[1 .. $]);
+    }
+    else
+    {
+        enum hasObjects = ((is(T[0] == class) || is(T[0] == interface))
+            && !is(T[0] == immutable)) || hasObjects!(T[1 .. $]);
+    }
+}
+
+/*
+Statically evaluates to $(D true) if and only if $(D T)'s
+representation includes at least one non-immutable non-shared object
+reference.
+*/
+private template hasUnsharedObjects(T...)
+{
+    static if (T.length == 0)
+    {
+        enum hasUnsharedObjects = false;
+    }
+    else static if (is(T[0] == struct))
+    {
+        enum hasUnsharedObjects = hasUnsharedObjects!(
+            RepresentationTypeTuple!(T[0]), T[1 .. $]);
+    }
+    else
+    {
+        enum hasUnsharedObjects = ((is(T[0] == class) || is(T[0] == interface)) &&
+                                !is(T[0] == immutable) && !is(T[0] == shared)) ||
+            hasUnsharedObjects!(T[1 .. $]);
+    }
+}
+
+/**
+Returns $(D true) if and only if $(D T)'s representation includes at
+least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U)
+is not immutable;) $(LI an array $(D U[]) and $(D U) is not
+immutable;) $(LI a reference to a class or interface type $(D C) and $(D C) is
+not immutable.) $(LI an associative array that is not immutable.)
+$(LI a delegate.))
+*/
+template hasAliasing(T...)
+{
+    import std.typecons : Rebindable;
+
+    static if (T.length && is(T[0] : Rebindable!R, R))
+    {
+        enum hasAliasing = hasAliasing!(R, T[1 .. $]);
+    }
+    else
+    {
+        template isAliasingDelegate(T)
+        {
+            enum isAliasingDelegate = isDelegate!T
+                                  && !is(T == immutable)
+                                  && !is(FunctionTypeOf!T == immutable);
+        }
+        enum hasAliasing = hasRawAliasing!T || hasObjects!T ||
+            anySatisfy!(isAliasingDelegate, T, RepresentationTypeTuple!T);
+    }
+}
+
+///
+unittest
+{
+    struct S1 { int a; Object b; }
+    struct S2 { string a; }
+    struct S3 { int a; immutable Object b; }
+    struct S4 { float[3] vals; }
+    static assert( hasAliasing!S1);
+    static assert(!hasAliasing!S2);
+    static assert(!hasAliasing!S3);
+    static assert(!hasAliasing!S4);
+}
+
+unittest
+{
+    static assert( hasAliasing!(uint[uint]));
+    static assert(!hasAliasing!(immutable(uint[uint])));
+    static assert( hasAliasing!(void delegate()));
+    static assert( hasAliasing!(void delegate() const));
+    static assert(!hasAliasing!(void delegate() immutable));
+    static assert( hasAliasing!(void delegate() shared));
+    static assert( hasAliasing!(void delegate() shared const));
+    static assert( hasAliasing!(const(void delegate())));
+    static assert( hasAliasing!(const(void delegate() const)));
+    static assert(!hasAliasing!(const(void delegate() immutable)));
+    static assert( hasAliasing!(const(void delegate() shared)));
+    static assert( hasAliasing!(const(void delegate() shared const)));
+    static assert(!hasAliasing!(immutable(void delegate())));
+    static assert(!hasAliasing!(immutable(void delegate() const)));
+    static assert(!hasAliasing!(immutable(void delegate() immutable)));
+    static assert(!hasAliasing!(immutable(void delegate() shared)));
+    static assert(!hasAliasing!(immutable(void delegate() shared const)));
+    static assert( hasAliasing!(shared(const(void delegate()))));
+    static assert( hasAliasing!(shared(const(void delegate() const))));
+    static assert(!hasAliasing!(shared(const(void delegate() immutable))));
+    static assert( hasAliasing!(shared(const(void delegate() shared))));
+    static assert( hasAliasing!(shared(const(void delegate() shared const))));
+    static assert(!hasAliasing!(void function()));
+
+    interface I;
+    static assert( hasAliasing!I);
+
+    import std.typecons : Rebindable;
+    static assert( hasAliasing!(Rebindable!(const Object)));
+    static assert(!hasAliasing!(Rebindable!(immutable Object)));
+    static assert( hasAliasing!(Rebindable!(shared Object)));
+    static assert( hasAliasing!(Rebindable!Object));
+
+    struct S5
+    {
+        void delegate() immutable b;
+        shared(void delegate() immutable) f;
+        immutable(void delegate() immutable) j;
+        shared(const(void delegate() immutable)) n;
+    }
+    struct S6 { typeof(S5.tupleof) a; void delegate() p; }
+    static assert(!hasAliasing!S5);
+    static assert( hasAliasing!S6);
+
+    struct S7 { void delegate() a; int b; Object c; }
+    class S8 { int a; int b; }
+    class S9 { typeof(S8.tupleof) a; }
+    class S10 { typeof(S8.tupleof) a; int* b; }
+    static assert( hasAliasing!S7);
+    static assert( hasAliasing!S8);
+    static assert( hasAliasing!S9);
+    static assert( hasAliasing!S10);
+    struct S11 {}
+    class S12 {}
+    interface S13 {}
+    union S14 {}
+    static assert(!hasAliasing!S11);
+    static assert( hasAliasing!S12);
+    static assert( hasAliasing!S13);
+    static assert(!hasAliasing!S14);
+}
+/**
+Returns $(D true) if and only if $(D T)'s representation includes at
+least one of the following: $(OL $(LI a raw pointer $(D U*);) $(LI an
+array $(D U[]);) $(LI a reference to a class type $(D C).)
+$(LI an associative array.) $(LI a delegate.))
+ */
+template hasIndirections(T)
+{
+    static if (is(T == struct) || is(T == union))
+        enum hasIndirections = anySatisfy!(.hasIndirections, FieldTypeTuple!T);
+    else static if (isStaticArray!T && is(T : E[N], E, size_t N))
+        enum hasIndirections = is(E == void) ? true : hasIndirections!E;
+    else static if (isFunctionPointer!T)
+        enum hasIndirections = false;
+    else
+        enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T ||
+            isAssociativeArray!T || is (T == class) || is(T == interface);
+}
+
+///
+unittest
+{
+    static assert( hasIndirections!(int[string]));
+    static assert( hasIndirections!(void delegate()));
+    static assert( hasIndirections!(void delegate() immutable));
+    static assert( hasIndirections!(immutable(void delegate())));
+    static assert( hasIndirections!(immutable(void delegate() immutable)));
+
+    static assert(!hasIndirections!(void function()));
+    static assert( hasIndirections!(void*[1]));
+    static assert(!hasIndirections!(byte[1]));
+}
+
+unittest
+{
+    // void static array hides actual type of bits, so "may have indirections".
+    static assert( hasIndirections!(void[1]));
+    interface I {}
+    struct S1 {}
+    struct S2 { int a; }
+    struct S3 { int a; int b; }
+    struct S4 { int a; int* b; }
+    struct S5 { int a; Object b; }
+    struct S6 { int a; string b; }
+    struct S7 { int a; immutable Object b; }
+    struct S8 { int a; immutable I b; }
+    struct S9 { int a; void delegate() b; }
+    struct S10 { int a; immutable(void delegate()) b; }
+    struct S11 { int a; void delegate() immutable b; }
+    struct S12 { int a; immutable(void delegate() immutable) b; }
+    class S13 {}
+    class S14 { int a; }
+    class S15 { int a; int b; }
+    class S16 { int a; Object b; }
+    class S17 { string a; }
+    class S18 { int a; immutable Object b; }
+    class S19 { int a; immutable(void delegate() immutable) b; }
+    union S20 {}
+    union S21 { int a; }
+    union S22 { int a; int b; }
+    union S23 { int a; Object b; }
+    union S24 { string a; }
+    union S25 { int a; immutable Object b; }
+    union S26 { int a; immutable(void delegate() immutable) b; }
+    static assert( hasIndirections!I);
+    static assert(!hasIndirections!S1);
+    static assert(!hasIndirections!S2);
+    static assert(!hasIndirections!S3);
+    static assert( hasIndirections!S4);
+    static assert( hasIndirections!S5);
+    static assert( hasIndirections!S6);
+    static assert( hasIndirections!S7);
+    static assert( hasIndirections!S8);
+    static assert( hasIndirections!S9);
+    static assert( hasIndirections!S10);
+    static assert( hasIndirections!S12);
+    static assert( hasIndirections!S13);
+    static assert( hasIndirections!S14);
+    static assert( hasIndirections!S15);
+    static assert( hasIndirections!S16);
+    static assert( hasIndirections!S17);
+    static assert( hasIndirections!S18);
+    static assert( hasIndirections!S19);
+    static assert(!hasIndirections!S20);
+    static assert(!hasIndirections!S21);
+    static assert(!hasIndirections!S22);
+    static assert( hasIndirections!S23);
+    static assert( hasIndirections!S24);
+    static assert( hasIndirections!S25);
+    static assert( hasIndirections!S26);
+}
+
+unittest //12000
+{
+    static struct S(T)
+    {
+        static assert(hasIndirections!T);
+    }
+
+    static class A(T)
+    {
+        S!A a;
+    }
+
+    A!int dummy;
+}
+
+/**
+Returns $(D true) if and only if $(D T)'s representation includes at
+least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U)
+is not immutable or shared;) $(LI an array $(D U[]) and $(D U) is not
+immutable or shared;) $(LI a reference to a class type $(D C) and
+$(D C) is not immutable or shared.) $(LI an associative array that is not
+immutable or shared.) $(LI a delegate that is not shared.))
+*/
+
+template hasUnsharedAliasing(T...)
+{
+    import std.typecons : Rebindable;
+
+    static if (!T.length)
+    {
+        enum hasUnsharedAliasing = false;
+    }
+    else static if (is(T[0] R: Rebindable!R))
+    {
+        enum hasUnsharedAliasing = hasUnsharedAliasing!R;
+    }
+    else
+    {
+        template unsharedDelegate(T)
+        {
+            enum bool unsharedDelegate = isDelegate!T
+                                     && !is(T == shared)
+                                     && !is(T == shared)
+                                     && !is(T == immutable)
+                                     && !is(FunctionTypeOf!T == shared)
+                                     && !is(FunctionTypeOf!T == immutable);
+        }
+
+        enum hasUnsharedAliasing =
+            hasRawUnsharedAliasing!(T[0]) ||
+            anySatisfy!(unsharedDelegate, RepresentationTypeTuple!(T[0])) ||
+            hasUnsharedObjects!(T[0]) ||
+            hasUnsharedAliasing!(T[1..$]);
+    }
+}
+
+///
+unittest
+{
+    struct S1 { int a; Object b; }
+    struct S2 { string a; }
+    struct S3 { int a; immutable Object b; }
+    static assert( hasUnsharedAliasing!S1);
+    static assert(!hasUnsharedAliasing!S2);
+    static assert(!hasUnsharedAliasing!S3);
+
+    struct S4 { int a; shared Object b; }
+    struct S5 { char[] a; }
+    struct S6 { shared char[] b; }
+    struct S7 { float[3] vals; }
+    static assert(!hasUnsharedAliasing!S4);
+    static assert( hasUnsharedAliasing!S5);
+    static assert(!hasUnsharedAliasing!S6);
+    static assert(!hasUnsharedAliasing!S7);
+}
+
+unittest
+{
+    /* Issue 6642 */
+    import std.typecons : Rebindable;
+    struct S8 { int a; Rebindable!(immutable Object) b; }
+    static assert(!hasUnsharedAliasing!S8);
+
+    static assert( hasUnsharedAliasing!(uint[uint]));
+
+    static assert( hasUnsharedAliasing!(void delegate()));
+    static assert( hasUnsharedAliasing!(void delegate() const));
+    static assert(!hasUnsharedAliasing!(void delegate() immutable));
+    static assert(!hasUnsharedAliasing!(void delegate() shared));
+    static assert(!hasUnsharedAliasing!(void delegate() shared const));
+}
+
+unittest
+{
+    import std.typecons : Rebindable;
+    static assert( hasUnsharedAliasing!(const(void delegate())));
+    static assert( hasUnsharedAliasing!(const(void delegate() const)));
+    static assert(!hasUnsharedAliasing!(const(void delegate() immutable)));
+    static assert(!hasUnsharedAliasing!(const(void delegate() shared)));
+    static assert(!hasUnsharedAliasing!(const(void delegate() shared const)));
+    static assert(!hasUnsharedAliasing!(immutable(void delegate())));
+    static assert(!hasUnsharedAliasing!(immutable(void delegate() const)));
+    static assert(!hasUnsharedAliasing!(immutable(void delegate() immutable)));
+    static assert(!hasUnsharedAliasing!(immutable(void delegate() shared)));
+    static assert(!hasUnsharedAliasing!(immutable(void delegate() shared const)));
+    static assert(!hasUnsharedAliasing!(shared(void delegate())));
+    static assert(!hasUnsharedAliasing!(shared(void delegate() const)));
+    static assert(!hasUnsharedAliasing!(shared(void delegate() immutable)));
+    static assert(!hasUnsharedAliasing!(shared(void delegate() shared)));
+    static assert(!hasUnsharedAliasing!(shared(void delegate() shared const)));
+    static assert(!hasUnsharedAliasing!(shared(const(void delegate()))));
+    static assert(!hasUnsharedAliasing!(shared(const(void delegate() const))));
+    static assert(!hasUnsharedAliasing!(shared(const(void delegate() immutable))));
+    static assert(!hasUnsharedAliasing!(shared(const(void delegate() shared))));
+    static assert(!hasUnsharedAliasing!(shared(const(void delegate() shared const))));
+    static assert(!hasUnsharedAliasing!(void function()));
+
+    interface I {}
+    static assert(hasUnsharedAliasing!I);
+
+    static assert( hasUnsharedAliasing!(Rebindable!(const Object)));
+    static assert(!hasUnsharedAliasing!(Rebindable!(immutable Object)));
+    static assert(!hasUnsharedAliasing!(Rebindable!(shared Object)));
+    static assert( hasUnsharedAliasing!(Rebindable!Object));
+
+    /* Issue 6979 */
+    static assert(!hasUnsharedAliasing!(int, shared(int)*));
+    static assert( hasUnsharedAliasing!(int, int*));
+    static assert( hasUnsharedAliasing!(int, const(int)[]));
+    static assert( hasUnsharedAliasing!(int, shared(int)*, Rebindable!Object));
+    static assert(!hasUnsharedAliasing!(shared(int)*, Rebindable!(shared Object)));
+    static assert(!hasUnsharedAliasing!());
+
+    struct S9
+    {
+        void delegate() shared a;
+        void delegate() immutable b;
+        void delegate() shared const c;
+        shared(void delegate()) d;
+        shared(void delegate() shared) e;
+        shared(void delegate() immutable) f;
+        shared(void delegate() shared const) g;
+        immutable(void delegate()) h;
+        immutable(void delegate() shared) i;
+        immutable(void delegate() immutable) j;
+        immutable(void delegate() shared const) k;
+        shared(const(void delegate())) l;
+        shared(const(void delegate() shared)) m;
+        shared(const(void delegate() immutable)) n;
+        shared(const(void delegate() shared const)) o;
+    }
+    struct S10 { typeof(S9.tupleof) a; void delegate() p; }
+    struct S11 { typeof(S9.tupleof) a; Object p; }
+    struct S12 { typeof(S9.tupleof) a; int* p; }
+    class S13 { typeof(S9.tupleof) a; }
+    class S14 { typeof(S9.tupleof) a; void delegate() p; }
+    class S15 { typeof(S9.tupleof) a; Object p; }
+    class S16 { typeof(S9.tupleof) a; int* p; }
+    static assert(!hasUnsharedAliasing!S9);
+    static assert(!hasUnsharedAliasing!(immutable(S9)));
+    static assert(!hasUnsharedAliasing!(shared(S9)));
+    static assert( hasUnsharedAliasing!S10);
+    static assert(!hasUnsharedAliasing!(immutable(S10)));
+    static assert(!hasUnsharedAliasing!(shared(S10)));
+    static assert( hasUnsharedAliasing!S11);
+    static assert(!hasUnsharedAliasing!(immutable(S11)));
+    static assert(!hasUnsharedAliasing!(shared(S11)));
+    static assert( hasUnsharedAliasing!S12);
+    static assert(!hasUnsharedAliasing!(immutable(S12)));
+    static assert(!hasUnsharedAliasing!(shared(S12)));
+    static assert( hasUnsharedAliasing!S13);
+    static assert(!hasUnsharedAliasing!(immutable(S13)));
+    static assert(!hasUnsharedAliasing!(shared(S13)));
+    static assert( hasUnsharedAliasing!S14);
+    static assert(!hasUnsharedAliasing!(immutable(S14)));
+    static assert(!hasUnsharedAliasing!(shared(S14)));
+    static assert( hasUnsharedAliasing!S15);
+    static assert(!hasUnsharedAliasing!(immutable(S15)));
+    static assert(!hasUnsharedAliasing!(shared(S15)));
+    static assert( hasUnsharedAliasing!S16);
+    static assert(!hasUnsharedAliasing!(immutable(S16)));
+    static assert(!hasUnsharedAliasing!(shared(S16)));
+    struct S17 {}
+    class S18 {}
+    interface S19 {}
+    union S20 {}
+    static assert(!hasUnsharedAliasing!S17);
+    static assert( hasUnsharedAliasing!S18);
+    static assert( hasUnsharedAliasing!S19);
+    static assert(!hasUnsharedAliasing!S20);
+}
+
+/**
+ True if $(D S) or any type embedded directly in the representation of $(D S)
+ defines an elaborate copy constructor. Elaborate copy constructors are
+ introduced by defining $(D this(this)) for a $(D struct).
+
+ Classes and unions never have elaborate copy constructors.
+ */
+template hasElaborateCopyConstructor(S)
+{
+    static if(isStaticArray!S && S.length)
+    {
+        enum bool hasElaborateCopyConstructor = hasElaborateCopyConstructor!(typeof(S.init[0]));
+    }
+    else static if(is(S == struct))
+    {
+        enum hasElaborateCopyConstructor = hasMember!(S, "__postblit")
+            || anySatisfy!(.hasElaborateCopyConstructor, FieldTypeTuple!S);
+    }
+    else
+    {
+        enum bool hasElaborateCopyConstructor = false;
+    }
+}
+
+///
+unittest
+{
+    static assert(!hasElaborateCopyConstructor!int);
+
+    static struct S1 { }
+    static struct S2 { this(this) {} }
+    static struct S3 { S2 field; }
+    static struct S4 { S3[1] field; }
+    static struct S5 { S3[] field; }
+    static struct S6 { S3[0] field; }
+    static struct S7 { @disable this(); S3 field; }
+    static assert(!hasElaborateCopyConstructor!S1);
+    static assert( hasElaborateCopyConstructor!S2);
+    static assert( hasElaborateCopyConstructor!(immutable S2));
+    static assert( hasElaborateCopyConstructor!S3);
+    static assert( hasElaborateCopyConstructor!(S3[1]));
+    static assert(!hasElaborateCopyConstructor!(S3[0]));
+    static assert( hasElaborateCopyConstructor!S4);
+    static assert(!hasElaborateCopyConstructor!S5);
+    static assert(!hasElaborateCopyConstructor!S6);
+    static assert( hasElaborateCopyConstructor!S7);
+}
+
+/**
+   True if $(D S) or any type directly embedded in the representation of $(D S)
+   defines an elaborate assignment. Elaborate assignments are introduced by
+   defining $(D opAssign(typeof(this))) or $(D opAssign(ref typeof(this)))
+   for a $(D struct) or when there is a compiler-generated $(D opAssign).
+
+   A type $(D S) gets compiler-generated $(D opAssign) in case it has
+   an elaborate copy constructor or elaborate destructor.
+
+   Classes and unions never have elaborate assignments.
+
+   Note: Structs with (possibly nested) postblit operator(s) will have a
+   hidden yet elaborate compiler generated assignment operator (unless
+   explicitly disabled).
+ */
+template hasElaborateAssign(S)
+{
+    static if(isStaticArray!S && S.length)
+    {
+        enum bool hasElaborateAssign = hasElaborateAssign!(typeof(S.init[0]));
+    }
+    else static if(is(S == struct))
+    {
+        enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) ||
+                                  is(typeof(S.init.opAssign(lvalueOf!S))) ||
+            anySatisfy!(.hasElaborateAssign, FieldTypeTuple!S);
+    }
+    else
+    {
+        enum bool hasElaborateAssign = false;
+    }
+}
+
+///
+unittest
+{
+    static assert(!hasElaborateAssign!int);
+
+    static struct S  { void opAssign(S) {} }
+    static assert( hasElaborateAssign!S);
+    static assert(!hasElaborateAssign!(const(S)));
+
+    static struct S1 { void opAssign(ref S1) {} }
+    static struct S2 { void opAssign(int) {} }
+    static struct S3 { S s; }
+    static assert( hasElaborateAssign!S1);
+    static assert(!hasElaborateAssign!S2);
+    static assert( hasElaborateAssign!S3);
+    static assert( hasElaborateAssign!(S3[1]));
+    static assert(!hasElaborateAssign!(S3[0]));
+}
+
+unittest
+{
+    static struct S  { void opAssign(S) {} }
+    static struct S4
+    {
+        void opAssign(U)(U u) {}
+        @disable void opAssign(U)(ref U u);
+    }
+    static assert( hasElaborateAssign!S4);
+
+    static struct S41
+    {
+        void opAssign(U)(ref U u) {}
+        @disable void opAssign(U)(U u);
+    }
+    static assert( hasElaborateAssign!S41);
+
+    static struct S5 { @disable this(); this(int n){ s = S(); } S s; }
+    static assert( hasElaborateAssign!S5);
+
+    static struct S6 { this(this) {} }
+    static struct S7 { this(this) {} @disable void opAssign(S7); }
+    static struct S8 { this(this) {} @disable void opAssign(S8); void opAssign(int) {} }
+    static struct S9 { this(this) {}                             void opAssign(int) {} }
+    static struct S10 { ~this() { } }
+    static assert( hasElaborateAssign!S6);
+    static assert(!hasElaborateAssign!S7);
+    static assert(!hasElaborateAssign!S8);
+    static assert( hasElaborateAssign!S9);
+    static assert( hasElaborateAssign!S10);
+    static struct SS6 { S6 s; }
+    static struct SS7 { S7 s; }
+    static struct SS8 { S8 s; }
+    static struct SS9 { S9 s; }
+    static assert( hasElaborateAssign!SS6);
+    static assert( hasElaborateAssign!SS7);
+    static assert( hasElaborateAssign!SS8);
+    static assert( hasElaborateAssign!SS9);
+}
+
+/**
+   True if $(D S) or any type directly embedded in the representation
+   of $(D S) defines an elaborate destructor. Elaborate destructors
+   are introduced by defining $(D ~this()) for a $(D
+   struct).
+
+   Classes and unions never have elaborate destructors, even
+   though classes may define $(D ~this()).
+ */
+template hasElaborateDestructor(S)
+{
+    static if(isStaticArray!S && S.length)
+    {
+        enum bool hasElaborateDestructor = hasElaborateDestructor!(typeof(S.init[0]));
+    }
+    else static if(is(S == struct))
+    {
+        enum hasElaborateDestructor = hasMember!(S, "__dtor")
+            || anySatisfy!(.hasElaborateDestructor, FieldTypeTuple!S);
+    }
+    else
+    {
+        enum bool hasElaborateDestructor = false;
+    }
+}
+
+///
+unittest
+{
+    static assert(!hasElaborateDestructor!int);
+
+    static struct S1 { }
+    static struct S2 { ~this() {} }
+    static struct S3 { S2 field; }
+    static struct S4 { S3[1] field; }
+    static struct S5 { S3[] field; }
+    static struct S6 { S3[0] field; }
+    static struct S7 { @disable this(); S3 field; }
+    static assert(!hasElaborateDestructor!S1);
+    static assert( hasElaborateDestructor!S2);
+    static assert( hasElaborateDestructor!(immutable S2));
+    static assert( hasElaborateDestructor!S3);
+    static assert( hasElaborateDestructor!(S3[1]));
+    static assert(!hasElaborateDestructor!(S3[0]));
+    static assert( hasElaborateDestructor!S4);
+    static assert(!hasElaborateDestructor!S5);
+    static assert(!hasElaborateDestructor!S6);
+    static assert( hasElaborateDestructor!S7);
+}
+
+alias Identity(alias A) = A;
+
+/**
+   Yields $(D true) if and only if $(D T) is an aggregate that defines
+   a symbol called $(D name).
+ */
+enum hasMember(T, string name) = __traits(hasMember, T, name);
+
+///
+unittest
+{
+    static assert(!hasMember!(int, "blah"));
+    struct S1 { int blah; }
+    struct S2 { int blah(){ return 0; } }
+    class C1 { int blah; }
+    class C2 { int blah(){ return 0; } }
+    static assert(hasMember!(S1, "blah"));
+    static assert(hasMember!(S2, "blah"));
+    static assert(hasMember!(C1, "blah"));
+    static assert(hasMember!(C2, "blah"));
+}
+
+unittest
+{
+    // 8321
+    struct S {
+        int x;
+        void f(){}
+        void t()(){}
+        template T(){}
+    }
+    struct R1(T) {
+        T t;
+        alias t this;
+    }
+    struct R2(T) {
+        T t;
+        @property ref inout(T) payload() inout { return t; }
+        alias t this;
+    }
+    static assert(hasMember!(S, "x"));
+    static assert(hasMember!(S, "f"));
+    static assert(hasMember!(S, "t"));
+    static assert(hasMember!(S, "T"));
+    static assert(hasMember!(R1!S, "x"));
+    static assert(hasMember!(R1!S, "f"));
+    static assert(hasMember!(R1!S, "t"));
+    static assert(hasMember!(R1!S, "T"));
+    static assert(hasMember!(R2!S, "x"));
+    static assert(hasMember!(R2!S, "f"));
+    static assert(hasMember!(R2!S, "t"));
+    static assert(hasMember!(R2!S, "T"));
+}
+
+unittest
+{
+    static struct S
+    {
+        void opDispatch(string n, A)(A dummy) {}
+    }
+    static assert(hasMember!(S, "foo"));
+}
+
+/**
+Retrieves the members of an enumerated type $(D enum E).
+
+Params:
+ E = An enumerated type. $(D E) may have duplicated values.
+
+Returns:
+ Static tuple composed of the members of the enumerated type $(D E).
+ The members are arranged in the same order as declared in $(D E).
+
+Note:
+ An enum can have multiple members which have the same value. If you want
+ to use EnumMembers to e.g. generate switch cases at compile-time,
+ you should use the $(XREF typetuple, NoDuplicates) template to avoid
+ generating duplicate switch cases.
+
+Note:
+ Returned values are strictly typed with $(D E). Thus, the following code
+ does not work without the explicit cast:
+--------------------
+enum E : int { a, b, c }
+int[] abc = cast(int[]) [ EnumMembers!E ];
+--------------------
+ Cast is not necessary if the type of the variable is inferred. See the
+ example below.
+
+Example:
+ Creating an array of enumerated values:
+--------------------
+enum Sqrts : real
+{
+    one   = 1,
+    two   = 1.41421,
+    three = 1.73205,
+}
+auto sqrts = [ EnumMembers!Sqrts ];
+assert(sqrts == [ Sqrts.one, Sqrts.two, Sqrts.three ]);
+--------------------
+
+ A generic function $(D rank(v)) in the following example uses this
+ template for finding a member $(D e) in an enumerated type $(D E).
+--------------------
+// Returns i if e is the i-th enumerator of E.
+size_t rank(E)(E e)
+    if (is(E == enum))
+{
+    foreach (i, member; EnumMembers!E)
+    {
+        if (e == member)
+            return i;
+    }
+    assert(0, "Not an enum member");
+}
+
+enum Mode
+{
+    read  = 1,
+    write = 2,
+    map   = 4,
+}
+assert(rank(Mode.read ) == 0);
+assert(rank(Mode.write) == 1);
+assert(rank(Mode.map  ) == 2);
+--------------------
+ */
+template EnumMembers(E)
+    if (is(E == enum))
+{
+    // Supply the specified identifier to an constant value.
+    template WithIdentifier(string ident)
+    {
+        static if (ident == "Symbolize")
+        {
+            template Symbolize(alias value)
+            {
+                enum Symbolize = value;
+            }
+        }
+        else
+        {
+            mixin("template Symbolize(alias "~ ident ~")"
+                 ~"{"
+                     ~"alias Symbolize = "~ ident ~";"
+                 ~"}");
+        }
+    }
+
+    template EnumSpecificMembers(names...)
+    {
+        static if (names.length > 0)
+        {
+            alias EnumSpecificMembers =
+                TypeTuple!(
+                    WithIdentifier!(names[0])
+                        .Symbolize!(__traits(getMember, E, names[0])),
+                    EnumSpecificMembers!(names[1 .. $])
+                );
+        }
+        else
+        {
+            alias EnumSpecificMembers = TypeTuple!();
+        }
+    }
+
+    alias EnumMembers = EnumSpecificMembers!(__traits(allMembers, E));
+}
+
+unittest
+{
+    enum A { a }
+    static assert([ EnumMembers!A ] == [ A.a ]);
+    enum B { a, b, c, d, e }
+    static assert([ EnumMembers!B ] == [ B.a, B.b, B.c, B.d, B.e ]);
+}
+
+unittest    // typed enums
+{
+    enum A : string { a = "alpha", b = "beta" }
+    static assert([ EnumMembers!A ] == [ A.a, A.b ]);
+
+    static struct S
+    {
+        int value;
+        int opCmp(S rhs) const nothrow { return value - rhs.value; }
+    }
+    enum B : S { a = S(1), b = S(2), c = S(3) }
+    static assert([ EnumMembers!B ] == [ B.a, B.b, B.c ]);
+}
+
+unittest    // duplicated values
+{
+    enum A
+    {
+        a = 0, b = 0,
+        c = 1, d = 1, e
+    }
+    static assert([ EnumMembers!A ] == [ A.a, A.b, A.c, A.d, A.e ]);
+}
+
+unittest
+{
+    enum E { member, a = 0, b = 0 }
+    static assert(__traits(identifier, EnumMembers!E[0]) == "member");
+    static assert(__traits(identifier, EnumMembers!E[1]) == "a");
+    static assert(__traits(identifier, EnumMembers!E[2]) == "b");
+}
+
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// Classes and Interfaces
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/***
+ * Get a $(D_PARAM TypeTuple) of the base class and base interfaces of
+ * this class or interface. $(D_PARAM BaseTypeTuple!Object) returns
+ * the empty type tuple.
+ */
+template BaseTypeTuple(A)
+{
+    static if (is(A P == super))
+        alias BaseTypeTuple = P;
+    else
+        static assert(0, "argument is not a class or interface");
+}
+
+///
+unittest
+{
+    interface I1 { }
+    interface I2 { }
+    interface I12 : I1, I2 { }
+    static assert(is(BaseTypeTuple!I12 == TypeTuple!(I1, I2)));
+
+    interface I3 : I1 { }
+    interface I123 : I1, I2, I3 { }
+    static assert(is(BaseTypeTuple!I123 == TypeTuple!(I1, I2, I3)));
+}
+
+unittest
+{
+    interface I1 { }
+    interface I2 { }
+    class A { }
+    class C : A, I1, I2 { }
+
+    alias TL = BaseTypeTuple!C;
+    assert(TL.length == 3);
+    assert(is (TL[0] == A));
+    assert(is (TL[1] == I1));
+    assert(is (TL[2] == I2));
+
+    assert(BaseTypeTuple!Object.length == 0);
+}
+
+/**
+ * Get a $(D_PARAM TypeTuple) of $(I all) base classes of this class,
+ * in decreasing order. Interfaces are not included. $(D_PARAM
+ * BaseClassesTuple!Object) yields the empty type tuple.
+ */
+template BaseClassesTuple(T)
+    if (is(T == class))
+{
+    static if (is(T == Object))
+    {
+        alias BaseClassesTuple = TypeTuple!();
+    }
+    else static if (is(BaseTypeTuple!T[0] == Object))
+    {
+        alias BaseClassesTuple = TypeTuple!Object;
+    }
+    else
+    {
+        alias BaseClassesTuple =
+            TypeTuple!(BaseTypeTuple!T[0],
+                       BaseClassesTuple!(BaseTypeTuple!T[0]));
+    }
+}
+
+///
+unittest
+{
+    class C1 { }
+    class C2 : C1 { }
+    class C3 : C2 { }
+    static assert(!BaseClassesTuple!Object.length);
+    static assert(is(BaseClassesTuple!C1 == TypeTuple!(Object)));
+    static assert(is(BaseClassesTuple!C2 == TypeTuple!(C1, Object)));
+    static assert(is(BaseClassesTuple!C3 == TypeTuple!(C2, C1, Object)));
+}
+
+unittest
+{
+    struct S { }
+    static assert(!__traits(compiles, BaseClassesTuple!S));
+    interface I { }
+    static assert(!__traits(compiles, BaseClassesTuple!I));
+    class C4 : I { }
+    class C5 : C4, I { }
+    static assert(is(BaseClassesTuple!C5 == TypeTuple!(C4, Object)));
+}
+
+/**
+ * Get a $(D_PARAM TypeTuple) of $(I all) interfaces directly or
+ * indirectly inherited by this class or interface. Interfaces do not
+ * repeat if multiply implemented. $(D_PARAM InterfacesTuple!Object)
+ * yields the empty type tuple.
+ */
+template InterfacesTuple(T)
+{
+    template Flatten(H, T...)
+    {
+        static if (T.length)
+        {
+            alias Flatten = TypeTuple!(Flatten!H, Flatten!T);
+        }
+        else
+        {
+            static if (is(H == interface))
+                alias Flatten = TypeTuple!(H, InterfacesTuple!H);
+            else
+                alias Flatten = InterfacesTuple!H;
+        }
+    }
+
+    static if (is(T S == super) && S.length)
+        alias InterfacesTuple = NoDuplicates!(Flatten!S);
+    else
+        alias InterfacesTuple = TypeTuple!();
+}
+
+unittest
+{
+    // doc example
+    interface I1 {}
+    interface I2 {}
+    class A : I1, I2 { }
+    class B : A, I1 { }
+    class C : B { }
+    alias TL = InterfacesTuple!C;
+    static assert(is(TL[0] == I1) && is(TL[1] == I2));
+}
+
+unittest
+{
+    interface Iaa {}
+    interface Iab {}
+    interface Iba {}
+    interface Ibb {}
+    interface Ia : Iaa, Iab {}
+    interface Ib : Iba, Ibb {}
+    interface I : Ia, Ib {}
+    interface J {}
+    class B2 : J {}
+    class C2 : B2, Ia, Ib {}
+    static assert(is(InterfacesTuple!I ==
+                    TypeTuple!(Ia, Iaa, Iab, Ib, Iba, Ibb)));
+    static assert(is(InterfacesTuple!C2 ==
+                    TypeTuple!(J, Ia, Iaa, Iab, Ib, Iba, Ibb)));
+
+}
+
+/**
+ * Get a $(D_PARAM TypeTuple) of $(I all) base classes of $(D_PARAM
+ * T), in decreasing order, followed by $(D_PARAM T)'s
+ * interfaces. $(D_PARAM TransitiveBaseTypeTuple!Object) yields the
+ * empty type tuple.
+ */
+template TransitiveBaseTypeTuple(T)
+{
+    static if (is(T == Object))
+        alias TransitiveBaseTypeTuple = TypeTuple!();
+    else
+        alias TransitiveBaseTypeTuple =
+            TypeTuple!(BaseClassesTuple!T, InterfacesTuple!T);
+}
+
+///
+unittest
+{
+    interface J1 {}
+    interface J2 {}
+    class B1 {}
+    class B2 : B1, J1, J2 {}
+    class B3 : B2, J1 {}
+    alias TL = TransitiveBaseTypeTuple!B3;
+    assert(TL.length == 5);
+    assert(is (TL[0] == B2));
+    assert(is (TL[1] == B1));
+    assert(is (TL[2] == Object));
+    assert(is (TL[3] == J1));
+    assert(is (TL[4] == J2));
+
+    assert(TransitiveBaseTypeTuple!Object.length == 0);
+}
+
+
+/**
+Returns a tuple of non-static functions with the name $(D name) declared in the
+class or interface $(D C).  Covariant duplicates are shrunk into the most
+derived one.
+ */
+template MemberFunctionsTuple(C, string name)
+    if (is(C == class) || is(C == interface))
+{
+    static if (__traits(hasMember, C, name))
+    {
+        /*
+         * First, collect all overloads in the class hierarchy.
+         */
+        template CollectOverloads(Node)
+        {
+            static if (__traits(hasMember, Node, name) && __traits(compiles, __traits(getMember, Node, name)))
+            {
+                // Get all overloads in sight (not hidden).
+                alias inSight = TypeTuple!(__traits(getVirtualFunctions, Node, name));
+
+                // And collect all overloads in ancestor classes to reveal hidden
+                // methods.  The result may contain duplicates.
+                template walkThru(Parents...)
+                {
+                    static if (Parents.length > 0)
+                        alias walkThru = TypeTuple!(
+                                    CollectOverloads!(Parents[0]),
+                                    walkThru!(Parents[1 .. $])
+                                );
+                    else
+                        alias walkThru = TypeTuple!();
+                }
+
+                static if (is(Node Parents == super))
+                    alias CollectOverloads = TypeTuple!(inSight, walkThru!Parents);
+                else
+                    alias CollectOverloads = TypeTuple!inSight;
+            }
+            else
+                alias CollectOverloads = TypeTuple!(); // no overloads in this hierarchy
+        }
+
+        // duplicates in this tuple will be removed by shrink()
+        alias overloads = CollectOverloads!C;
+
+        // shrinkOne!args[0]    = the most derived one in the covariant siblings of target
+        // shrinkOne!args[1..$] = non-covariant others
+        template shrinkOne(/+ alias target, rest... +/ args...)
+        {
+            alias target = args[0 .. 1]; // prevent property functions from being evaluated
+            alias rest = args[1 .. $];
+
+            static if (rest.length > 0)
+            {
+                alias Target = FunctionTypeOf!target;
+                alias Rest0 = FunctionTypeOf!(rest[0]);
+
+                static if (isCovariantWith!(Target, Rest0))
+                    // target overrides rest[0] -- erase rest[0].
+                    alias shrinkOne = shrinkOne!(target, rest[1 .. $]);
+                else static if (isCovariantWith!(Rest0, Target))
+                    // rest[0] overrides target -- erase target.
+                    alias shrinkOne = shrinkOne!(rest[0], rest[1 .. $]);
+                else
+                    // target and rest[0] are distinct.
+                    alias shrinkOne = TypeTuple!(
+                                shrinkOne!(target, rest[1 .. $]),
+                                rest[0] // keep
+                            );
+            }
+            else
+                alias shrinkOne = TypeTuple!target; // done
+        }
+
+        /*
+         * Now shrink covariant overloads into one.
+         */
+        template shrink(overloads...)
+        {
+            static if (overloads.length > 0)
+            {
+                alias temp = shrinkOne!overloads;
+                alias shrink = TypeTuple!(temp[0], shrink!(temp[1 .. $]));
+            }
+            else
+                alias shrink = TypeTuple!(); // done
+        }
+
+        // done.
+        alias MemberFunctionsTuple = shrink!overloads;
+    }
+    else
+        alias MemberFunctionsTuple = TypeTuple!();
+}
+
+///
+unittest
+{
+    interface I { I foo(); }
+    class B
+    {
+        real foo(real v) { return v; }
+    }
+    class C : B, I
+    {
+        override C foo() { return this; } // covariant overriding of I.foo()
+    }
+    alias foos = MemberFunctionsTuple!(C, "foo");
+    static assert(foos.length == 2);
+    static assert(__traits(isSame, foos[0], C.foo));
+    static assert(__traits(isSame, foos[1], B.foo));
+}
+
+unittest
+{
+    interface I     { I test(); }
+    interface J : I { J test(); }
+    interface K     { K test(int); }
+    class B : I, K
+    {
+        K test(int) { return this; }
+        B test() { return this; }
+        static void test(string) { }
+    }
+    class C : B, J
+    {
+        override C test() { return this; }
+    }
+    alias test =MemberFunctionsTuple!(C, "test");
+    static assert(test.length == 2);
+    static assert(is(FunctionTypeOf!(test[0]) == FunctionTypeOf!(C.test)));
+    static assert(is(FunctionTypeOf!(test[1]) == FunctionTypeOf!(K.test)));
+    alias noexist = MemberFunctionsTuple!(C, "noexist");
+    static assert(noexist.length == 0);
+
+    interface L { int prop() @property; }
+    alias prop = MemberFunctionsTuple!(L, "prop");
+    static assert(prop.length == 1);
+
+    interface Test_I
+    {
+        void foo();
+        void foo(int);
+        void foo(int, int);
+    }
+    interface Test : Test_I {}
+    alias Test_foo = MemberFunctionsTuple!(Test, "foo");
+    static assert(Test_foo.length == 3);
+    static assert(is(typeof(&Test_foo[0]) == void function()));
+    static assert(is(typeof(&Test_foo[2]) == void function(int)));
+    static assert(is(typeof(&Test_foo[1]) == void function(int, int)));
+}
+
+
+/**
+Returns an alias to the template that $(D T) is an instance of.
+ */
+template TemplateOf(alias T : Base!Args, alias Base, Args...)
+{
+    alias TemplateOf = Base;
+}
+
+/// ditto
+template TemplateOf(T : Base!Args, alias Base, Args...)
+{
+    alias TemplateOf = Base;
+}
+
+///
+unittest
+{
+    struct Foo(T, U) {}
+    static assert(__traits(isSame, TemplateOf!(Foo!(int, real)), Foo));
+}
+
+unittest
+{
+    template Foo1(A) {}
+    template Foo2(A, B) {}
+    template Foo3(alias A) {}
+    template Foo4(string A) {}
+    struct Foo5(A) {}
+    struct Foo6(A, B) {}
+    struct Foo7(alias A) {}
+    template Foo8(A) { template Foo9(B) {} }
+    template Foo10() {}
+
+    static assert(__traits(isSame, TemplateOf!(Foo1!(int)), Foo1));
+    static assert(__traits(isSame, TemplateOf!(Foo2!(int, int)), Foo2));
+    static assert(__traits(isSame, TemplateOf!(Foo3!(123)), Foo3));
+    static assert(__traits(isSame, TemplateOf!(Foo4!("123")), Foo4));
+    static assert(__traits(isSame, TemplateOf!(Foo5!(int)), Foo5));
+    static assert(__traits(isSame, TemplateOf!(Foo6!(int, int)), Foo6));
+    static assert(__traits(isSame, TemplateOf!(Foo7!(123)), Foo7));
+    static assert(__traits(isSame, TemplateOf!(Foo8!(int).Foo9!(real)), Foo8!(int).Foo9));
+    static assert(__traits(isSame, TemplateOf!(Foo10!()), Foo10));
+}
+
+
+/**
+Returns a $(D TypeTuple) of the template arguments used to instantiate $(D T).
+ */
+template TemplateArgsOf(alias T : Base!Args, alias Base, Args...)
+{
+    alias TemplateArgsOf = Args;
+}
+
+/// ditto
+template TemplateArgsOf(T : Base!Args, alias Base, Args...)
+{
+    alias TemplateArgsOf = Args;
+}
+
+///
+unittest
+{
+    struct Foo(T, U) {}
+    static assert(is(TemplateArgsOf!(Foo!(int, real)) == TypeTuple!(int, real)));
+}
+
+unittest
+{
+    template Foo1(A) {}
+    template Foo2(A, B) {}
+    template Foo3(alias A) {}
+    template Foo4(string A) {}
+    struct Foo5(A) {}
+    struct Foo6(A, B) {}
+    struct Foo7(alias A) {}
+    template Foo8(A) { template Foo9(B) {} }
+    template Foo10() {}
+
+    enum x = 123;
+    enum y = "123";
+    static assert(is(TemplateArgsOf!(Foo1!(int)) == TypeTuple!(int)));
+    static assert(is(TemplateArgsOf!(Foo2!(int, int)) == TypeTuple!(int, int)));
+    static assert(__traits(isSame, TemplateArgsOf!(Foo3!(x)), TypeTuple!(x)));
+    static assert(TemplateArgsOf!(Foo4!(y)) == TypeTuple!(y));
+    static assert(is(TemplateArgsOf!(Foo5!(int)) == TypeTuple!(int)));
+    static assert(is(TemplateArgsOf!(Foo6!(int, int)) == TypeTuple!(int, int)));
+    static assert(__traits(isSame, TemplateArgsOf!(Foo7!(x)), TypeTuple!(x)));
+    static assert(is(TemplateArgsOf!(Foo8!(int).Foo9!(real)) == TypeTuple!(real)));
+    static assert(is(TemplateArgsOf!(Foo10!()) == TypeTuple!()));
+}
+
+
+private template maxAlignment(U...) if (isTypeTuple!U)
+{
+    static if (U.length == 0)
+        static assert(0);
+    else static if (U.length == 1)
+        enum maxAlignment = U[0].alignof;
+    else
+    {
+        import std.algorithm : max;
+        enum maxAlignment = max(staticMap!(.maxAlignment, U));
+    }
+}
+
+
+/**
+Returns class instance alignment.
+ */
+template classInstanceAlignment(T) if(is(T == class))
+{
+    alias classInstanceAlignment = maxAlignment!(void*, typeof(T.tupleof));
+}
+
+///
+unittest
+{
+    class A { byte b; }
+    class B { long l; }
+
+    // As class instance always has a hidden pointer
+    static assert(classInstanceAlignment!A == (void*).alignof);
+    static assert(classInstanceAlignment!B == long.alignof);
+}
+
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// Type Conversion
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/**
+Get the type that all types can be implicitly converted to. Useful
+e.g. in figuring out an array type from a bunch of initializing
+values. Returns $(D_PARAM void) if passed an empty list, or if the
+types have no common type.
+ */
+template CommonType(T...)
+{
+    static if (!T.length)
+    {
+        alias CommonType = void;
+    }
+    else static if (T.length == 1)
+    {
+        static if(is(typeof(T[0])))
+        {
+            alias CommonType = typeof(T[0]);
+        }
+        else
+        {
+            alias CommonType = T[0];
+        }
+    }
+    else static if (is(typeof(true ? T[0].init : T[1].init) U))
+    {
+        alias CommonType = CommonType!(U, T[2 .. $]);
+    }
+    else
+        alias CommonType = void;
+}
+
+///
+unittest
+{
+    alias X = CommonType!(int, long, short);
+    assert(is(X == long));
+    alias Y = CommonType!(int, char[], short);
+    assert(is(Y == void));
+}
+unittest
+{
+    static assert(is(CommonType!(3) == int));
+    static assert(is(CommonType!(double, 4, float) == double));
+    static assert(is(CommonType!(string, char[]) == const(char)[]));
+    static assert(is(CommonType!(3, 3U) == uint));
+}
+
+
+/**
+ * Returns a tuple with all possible target types of an implicit
+ * conversion of a value of type $(D_PARAM T).
+ *
+ * Important note:
+ *
+ * The possible targets are computed more conservatively than the D
+ * 2.005 compiler does, eliminating all dangerous conversions. For
+ * example, $(D_PARAM ImplicitConversionTargets!double) does not
+ * include $(D_PARAM float).
+ */
+template ImplicitConversionTargets(T)
+{
+    static if (is(T == bool))
+        alias ImplicitConversionTargets =
+            TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList,
+                       float, double, real, char, wchar, dchar);
+    else static if (is(T == byte))
+        alias ImplicitConversionTargets =
+            TypeTuple!(short, ushort, int, uint, long, ulong, CentTypeList,
+                       float, double, real, char, wchar, dchar);
+    else static if (is(T == ubyte))
+        alias ImplicitConversionTargets =
+            TypeTuple!(short, ushort, int, uint, long, ulong, CentTypeList,
+                       float, double, real, char, wchar, dchar);
+    else static if (is(T == short))
+        alias ImplicitConversionTargets =
+            TypeTuple!(int, uint, long, ulong, CentTypeList, float, double, real);
+    else static if (is(T == ushort))
+        alias ImplicitConversionTargets =
+            TypeTuple!(int, uint, long, ulong, CentTypeList, float, double, real);
+    else static if (is(T == int))
+        alias ImplicitConversionTargets =
+            TypeTuple!(long, ulong, CentTypeList, float, double, real);
+    else static if (is(T == uint))
+        alias ImplicitConversionTargets =
+            TypeTuple!(long, ulong, CentTypeList, float, double, real);
+    else static if (is(T == long))
+        alias ImplicitConversionTargets = TypeTuple!(float, double, real);
+    else static if (is(T == ulong))
+        alias ImplicitConversionTargets = TypeTuple!(float, double, real);
+    else static if (is(cent) && is(T == cent))
+        alias ImplicitConversionTargets = TypeTuple!(float, double, real);
+    else static if (is(ucent) && is(T == ucent))
+        alias ImplicitConversionTargets = TypeTuple!(float, double, real);
+    else static if (is(T == float))
+        alias ImplicitConversionTargets = TypeTuple!(double, real);
+    else static if (is(T == double))
+        alias ImplicitConversionTargets = TypeTuple!real;
+    else static if (is(T == char))
+        alias ImplicitConversionTargets =
+            TypeTuple!(wchar, dchar, byte, ubyte, short, ushort,
+                       int, uint, long, ulong, CentTypeList, float, double, real);
+    else static if (is(T == wchar))
+        alias ImplicitConversionTargets =
+            TypeTuple!(dchar, short, ushort, int, uint, long, ulong, CentTypeList,
+                       float, double, real);
+    else static if (is(T == dchar))
+        alias ImplicitConversionTargets =
+            TypeTuple!(int, uint, long, ulong, CentTypeList, float, double, real);
+    else static if (is(T : typeof(null)))
+        alias ImplicitConversionTargets = TypeTuple!(typeof(null));
+    else static if(is(T : Object))
+        alias ImplicitConversionTargets = TransitiveBaseTypeTuple!(T);
+    else static if (isDynamicArray!T && !is(typeof(T.init[0]) == const))
+        alias ImplicitConversionTargets =
+            TypeTuple!(const(Unqual!(typeof(T.init[0])))[]);
+    else static if (is(T : void*))
+        alias ImplicitConversionTargets = TypeTuple!(void*);
+    else
+        alias ImplicitConversionTargets = TypeTuple!();
+}
+
+unittest
+{
+    static assert(is(ImplicitConversionTargets!(double)[0] == real));
+    static assert(is(ImplicitConversionTargets!(string)[0] == const(char)[]));
+}
+
+/**
+Is $(D From) implicitly convertible to $(D To)?
+ */
+template isImplicitlyConvertible(From, To)
+{
+    enum bool isImplicitlyConvertible = is(typeof({
+        void fun(ref From v)
+        {
+            void gun(To) {}
+            gun(v);
+        }
+    }));
+}
+
+unittest
+{
+    static assert( isImplicitlyConvertible!(immutable(char), char));
+    static assert( isImplicitlyConvertible!(const(char), char));
+    static assert( isImplicitlyConvertible!(char, wchar));
+
+    static assert(!isImplicitlyConvertible!(wchar, char));
+
+    // bug6197
+    static assert(!isImplicitlyConvertible!(const(ushort), ubyte));
+    static assert(!isImplicitlyConvertible!(const(uint), ubyte));
+    static assert(!isImplicitlyConvertible!(const(ulong), ubyte));
+
+    // from std.conv.implicitlyConverts
+    assert(!isImplicitlyConvertible!(const(char)[], string));
+    assert( isImplicitlyConvertible!(string, const(char)[]));
+}
+
+/**
+Returns $(D true) iff a value of type $(D Rhs) can be assigned to a variable of
+type $(D Lhs).
+
+$(D isAssignable) returns whether both an lvalue and rvalue can be assigned.
+
+If you omit $(D Rhs), $(D isAssignable) will check identity assignable of $(D Lhs).
+*/
+enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAssignable!(Lhs, Rhs);
+
+///
+unittest
+{
+    static assert( isAssignable!(long, int));
+    static assert(!isAssignable!(int, long));
+    static assert( isAssignable!(const(char)[], string));
+    static assert(!isAssignable!(string, char[]));
+
+    // int is assignable to int
+    static assert( isAssignable!int);
+
+    // immutable int is not assignable to immutable int
+    static assert(!isAssignable!(immutable int));
+}
+
+// ditto
+private enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs);
+
+// ditto
+private enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs);
+
+unittest
+{
+    static assert(!isAssignable!(immutable int, int));
+    static assert( isAssignable!(int, immutable int));
+
+    static assert(!isAssignable!(inout int, int));
+    static assert( isAssignable!(int, inout int));
+    static assert(!isAssignable!(inout int));
+
+    static assert( isAssignable!(shared int, int));
+    static assert( isAssignable!(int, shared int));
+    static assert( isAssignable!(shared int));
+
+    static assert( isAssignable!(void[1], void[1]));
+
+    struct S { @disable this(); this(int n){} }
+    static assert( isAssignable!(S, S));
+
+    struct S2 { this(int n){} }
+    static assert( isAssignable!(S2, S2));
+    static assert(!isAssignable!(S2, int));
+
+    struct S3 { @disable void opAssign(); }
+    static assert( isAssignable!(S3, S3));
+
+    struct S3X { @disable void opAssign(S3X); }
+    static assert(!isAssignable!(S3X, S3X));
+
+    struct S4 { void opAssign(int); }
+    static assert( isAssignable!(S4, S4));
+    static assert( isAssignable!(S4, int));
+    static assert( isAssignable!(S4, immutable int));
+
+    struct S5 { @disable this(); @disable this(this); }
+    struct S6 { void opAssign(in ref S5); }
+    static assert(!isAssignable!(S6, S5));
+    static assert(!isRvalueAssignable!(S6, S5));
+    static assert( isLvalueAssignable!(S6, S5));
+    static assert( isLvalueAssignable!(S6, immutable S5));
+}
+
+
+// Equivalent with TypeStruct::isAssignable in compiler code.
+package template isBlitAssignable(T)
+{
+    static if (is(OriginalType!T U) && !is(T == U))
+    {
+        enum isBlitAssignable = isBlitAssignable!U;
+    }
+    else static if (isStaticArray!T && is(T == E[n], E, size_t n))
+    // Workaround for issue 11499 : isStaticArray!T should not be necessary.
+    {
+        enum isBlitAssignable = isBlitAssignable!E;
+    }
+    else static if (is(T == struct) || is(T == union))
+    {
+        enum isBlitAssignable = isMutable!T &&
+        {
+            size_t offset = 0;
+            bool assignable = true;
+            foreach (i, F; FieldTypeTuple!T)
+            {
+                static if (i == 0)
+                {
+                }
+                else if (T.tupleof[i].offsetof == offset)
+                {
+                    if (assignable)
+                        continue;
+                }
+                else
+                {
+                    if (!assignable)
+                        return false;
+                }
+                assignable = isBlitAssignable!(typeof(T.tupleof[i]));
+                offset = T.tupleof[i].offsetof;
+            }
+            return assignable;
+        }();
+    }
+    else
+        enum isBlitAssignable = isMutable!T;
+}
+
+unittest
+{
+    static assert( isBlitAssignable!int);
+    static assert(!isBlitAssignable!(const int));
+
+    class C{ const int i; }
+    static assert( isBlitAssignable!C);
+
+    struct S1{ int i; }
+    struct S2{ const int i; }
+    static assert( isBlitAssignable!S1);
+    static assert(!isBlitAssignable!S2);
+
+    struct S3X { union {       int x;       int y; } }
+    struct S3Y { union {       int x; const int y; } }
+    struct S3Z { union { const int x; const int y; } }
+    static assert( isBlitAssignable!(S3X));
+    static assert( isBlitAssignable!(S3Y));
+    static assert(!isBlitAssignable!(S3Z));
+    static assert(!isBlitAssignable!(const S3X));
+    static assert(!isBlitAssignable!(inout S3Y));
+    static assert(!isBlitAssignable!(immutable S3Z));
+    static assert( isBlitAssignable!(S3X[3]));
+    static assert( isBlitAssignable!(S3Y[3]));
+    static assert(!isBlitAssignable!(S3Z[3]));
+    enum ES3X : S3X { a = S3X() }
+    enum ES3Y : S3Y { a = S3Y() }
+    enum ES3Z : S3Z { a = S3Z() }
+    static assert( isBlitAssignable!(ES3X));
+    static assert( isBlitAssignable!(ES3Y));
+    static assert(!isBlitAssignable!(ES3Z));
+    static assert(!isBlitAssignable!(const ES3X));
+    static assert(!isBlitAssignable!(inout ES3Y));
+    static assert(!isBlitAssignable!(immutable ES3Z));
+    static assert( isBlitAssignable!(ES3X[3]));
+    static assert( isBlitAssignable!(ES3Y[3]));
+    static assert(!isBlitAssignable!(ES3Z[3]));
+
+    union U1X {       int x;       int y; }
+    union U1Y {       int x; const int y; }
+    union U1Z { const int x; const int y; }
+    static assert( isBlitAssignable!(U1X));
+    static assert( isBlitAssignable!(U1Y));
+    static assert(!isBlitAssignable!(U1Z));
+    static assert(!isBlitAssignable!(const U1X));
+    static assert(!isBlitAssignable!(inout U1Y));
+    static assert(!isBlitAssignable!(immutable U1Z));
+    static assert( isBlitAssignable!(U1X[3]));
+    static assert( isBlitAssignable!(U1Y[3]));
+    static assert(!isBlitAssignable!(U1Z[3]));
+    enum EU1X : U1X { a = U1X() }
+    enum EU1Y : U1Y { a = U1Y() }
+    enum EU1Z : U1Z { a = U1Z() }
+    static assert( isBlitAssignable!(EU1X));
+    static assert( isBlitAssignable!(EU1Y));
+    static assert(!isBlitAssignable!(EU1Z));
+    static assert(!isBlitAssignable!(const EU1X));
+    static assert(!isBlitAssignable!(inout EU1Y));
+    static assert(!isBlitAssignable!(immutable EU1Z));
+    static assert( isBlitAssignable!(EU1X[3]));
+    static assert( isBlitAssignable!(EU1Y[3]));
+    static assert(!isBlitAssignable!(EU1Z[3]));
+
+    struct SA
+    {
+        @property int[3] foo() { return [1,2,3]; }
+        alias foo this;
+        const int x;    // SA is not blit assignable
+    }
+    static assert(!isStaticArray!SA);
+    static assert(!isBlitAssignable!(SA[3]));
+}
+
+
+/*
+Works like $(D isImplicitlyConvertible), except this cares only about storage
+classes of the arguments.
+ */
+private template isStorageClassImplicitlyConvertible(From, To)
+{
+    alias Pointify(T) = void*;
+
+    enum isStorageClassImplicitlyConvertible = isImplicitlyConvertible!(
+            ModifyTypePreservingTQ!(Pointify, From),
+            ModifyTypePreservingTQ!(Pointify,   To) );
+}
+
+unittest
+{
+    static assert( isStorageClassImplicitlyConvertible!(          int, const int));
+    static assert( isStorageClassImplicitlyConvertible!(immutable int, const int));
+
+    static assert(!isStorageClassImplicitlyConvertible!(const int,           int));
+    static assert(!isStorageClassImplicitlyConvertible!(const int, immutable int));
+    static assert(!isStorageClassImplicitlyConvertible!(int, shared int));
+    static assert(!isStorageClassImplicitlyConvertible!(shared int, int));
+}
+
+
+/**
+Determines whether the function type $(D F) is covariant with $(D G), i.e.,
+functions of the type $(D F) can override ones of the type $(D G).
+ */
+template isCovariantWith(F, G)
+    if (is(F == function) && is(G == function))
+{
+    static if (is(F : G))
+        enum isCovariantWith = true;
+    else
+    {
+        alias Upr = F;
+        alias Lwr = G;
+
+        /*
+         * Check for calling convention: require exact match.
+         */
+        template checkLinkage()
+        {
+            enum ok = functionLinkage!Upr == functionLinkage!Lwr;
+        }
+        /*
+         * Check for variadic parameter: require exact match.
+         */
+        template checkVariadicity()
+        {
+            enum ok = variadicFunctionStyle!Upr == variadicFunctionStyle!Lwr;
+        }
+        /*
+         * Check for function storage class:
+         *  - overrider can have narrower storage class than base
+         */
+        template checkSTC()
+        {
+            // Note the order of arguments.  The convertion order Lwr -> Upr is
+            // correct since Upr should be semantically 'narrower' than Lwr.
+            enum ok = isStorageClassImplicitlyConvertible!(Lwr, Upr);
+        }
+        /*
+         * Check for function attributes:
+         *  - require exact match for ref and @property
+         *  - overrider can add pure and nothrow, but can't remove them
+         *  - @safe and @trusted are covariant with each other, unremovable
+         */
+        template checkAttributes()
+        {
+            alias FA = FunctionAttribute;
+            enum uprAtts = functionAttributes!Upr;
+            enum lwrAtts = functionAttributes!Lwr;
+            //
+            enum wantExact = FA.ref_ | FA.property;
+            enum safety = FA.safe | FA.trusted;
+            enum ok =
+                (  (uprAtts & wantExact)   == (lwrAtts & wantExact)) &&
+                (  (uprAtts & FA.pure_   ) >= (lwrAtts & FA.pure_   )) &&
+                (  (uprAtts & FA.nothrow_) >= (lwrAtts & FA.nothrow_)) &&
+                (!!(uprAtts & safety    )  >= !!(lwrAtts & safety    )) ;
+        }
+        /*
+         * Check for return type: usual implicit convertion.
+         */
+        template checkReturnType()
+        {
+            enum ok = is(ReturnType!Upr : ReturnType!Lwr);
+        }
+        /*
+         * Check for parameters:
+         *  - require exact match for types (cf. bugzilla 3075)
+         *  - require exact match for in, out, ref and lazy
+         *  - overrider can add scope, but can't remove
+         */
+        template checkParameters()
+        {
+            alias STC = ParameterStorageClass;
+            alias UprParams = Parameters!Upr;
+            alias LwrParams = Parameters!Lwr;
+            alias UprPSTCs  = ParameterStorageClassTuple!Upr;
+            alias LwrPSTCs  = ParameterStorageClassTuple!Lwr;
+            //
+            template checkNext(size_t i)
+            {
+                static if (i < UprParams.length)
+                {
+                    enum uprStc = UprPSTCs[i];
+                    enum lwrStc = LwrPSTCs[i];
+                    //
+                    enum wantExact = STC.out_ | STC.ref_ | STC.lazy_ | STC.return_;
+                    enum ok =
+                        ((uprStc & wantExact )  == (lwrStc & wantExact )) &&
+                        ((uprStc & STC.scope_)  >= (lwrStc & STC.scope_)) &&
+                        checkNext!(i + 1).ok;
+                }
+                else
+                    enum ok = true; // done
+            }
+            static if (UprParams.length == LwrParams.length)
+                enum ok = is(UprParams == LwrParams) && checkNext!(0).ok;
+            else
+                enum ok = false;
+        }
+
+        /* run all the checks */
+        enum isCovariantWith =
+            checkLinkage    !().ok &&
+            checkVariadicity!().ok &&
+            checkSTC        !().ok &&
+            checkAttributes !().ok &&
+            checkReturnType !().ok &&
+            checkParameters !().ok ;
+    }
+}
+
+///
+unittest
+{
+    interface I { I clone(); }
+    interface J { J clone(); }
+    class C : I
+    {
+        override C clone()   // covariant overriding of I.clone()
+        {
+            return new C;
+        }
+    }
+
+    // C.clone() can override I.clone(), indeed.
+    static assert(isCovariantWith!(typeof(C.clone), typeof(I.clone)));
+
+    // C.clone() can't override J.clone(); the return type C is not implicitly
+    // convertible to J.
+    static assert(!isCovariantWith!(typeof(C.clone), typeof(J.clone)));
+}
+
+unittest
+{
+    enum bool isCovariantWith(alias f, alias g) = .isCovariantWith!(typeof(f), typeof(g));
+
+    // covariant return type
+    interface I     {}
+    interface J : I {}
+    interface BaseA            {          const(I) test(int); }
+    interface DerivA_1 : BaseA { override const(J) test(int); }
+    interface DerivA_2 : BaseA { override       J  test(int); }
+    static assert( isCovariantWith!(DerivA_1.test, BaseA.test));
+    static assert( isCovariantWith!(DerivA_2.test, BaseA.test));
+    static assert(!isCovariantWith!(BaseA.test, DerivA_1.test));
+    static assert(!isCovariantWith!(BaseA.test, DerivA_2.test));
+    static assert( isCovariantWith!(BaseA.test, BaseA.test));
+    static assert( isCovariantWith!(DerivA_1.test, DerivA_1.test));
+    static assert( isCovariantWith!(DerivA_2.test, DerivA_2.test));
+
+    // scope parameter
+    interface BaseB            {          void test(      int*,       int*); }
+    interface DerivB_1 : BaseB { override void test(scope int*,       int*); }
+    interface DerivB_2 : BaseB { override void test(      int*, scope int*); }
+    interface DerivB_3 : BaseB { override void test(scope int*, scope int*); }
+    static assert( isCovariantWith!(DerivB_1.test, BaseB.test));
+    static assert( isCovariantWith!(DerivB_2.test, BaseB.test));
+    static assert( isCovariantWith!(DerivB_3.test, BaseB.test));
+    static assert(!isCovariantWith!(BaseB.test, DerivB_1.test));
+    static assert(!isCovariantWith!(BaseB.test, DerivB_2.test));
+    static assert(!isCovariantWith!(BaseB.test, DerivB_3.test));
+
+    // function storage class
+    interface BaseC            {          void test()      ; }
+    interface DerivC_1 : BaseC { override void test() const; }
+    static assert( isCovariantWith!(DerivC_1.test, BaseC.test));
+    static assert(!isCovariantWith!(BaseC.test, DerivC_1.test));
+
+    // increasing safety
+    interface BaseE            {          void test()         ; }
+    interface DerivE_1 : BaseE { override void test() @safe   ; }
+    interface DerivE_2 : BaseE { override void test() @trusted; }
+    static assert( isCovariantWith!(DerivE_1.test, BaseE.test));
+    static assert( isCovariantWith!(DerivE_2.test, BaseE.test));
+    static assert(!isCovariantWith!(BaseE.test, DerivE_1.test));
+    static assert(!isCovariantWith!(BaseE.test, DerivE_2.test));
+
+    // @safe and @trusted
+    interface BaseF
+    {
+        void test1() @safe;
+        void test2() @trusted;
+    }
+    interface DerivF : BaseF
+    {
+        override void test1() @trusted;
+        override void test2() @safe;
+    }
+    static assert( isCovariantWith!(DerivF.test1, BaseF.test1));
+    static assert( isCovariantWith!(DerivF.test2, BaseF.test2));
+}
+
+
+// Needed for rvalueOf/lvalueOf because "inout on return means
+// inout must be on a parameter as well"
+private struct __InoutWorkaroundStruct{}
+
+/**
+Creates an lvalue or rvalue of type $(D T) for $(D typeof(...)) and
+$(D __traits(compiles, ...)) purposes. No actual value is returned.
+
+Note: Trying to use returned value will result in a
+"Symbol Undefined" error at link time.
+
+Example:
+---
+// Note that `f` doesn't have to be implemented
+// as is isn't called.
+int f(int);
+bool f(ref int);
+static assert(is(typeof(f(rvalueOf!int)) == int));
+static assert(is(typeof(f(lvalueOf!int)) == bool));
+
+int i = rvalueOf!int; // error, no actual value is returned
+---
+*/
+@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);
+
+/// ditto
+@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);
+
+// Note: unittest can't be used as an example here as function overloads
+// aren't allowed inside functions.
+
+unittest
+{
+    void needLvalue(T)(ref T);
+    static struct S { }
+    int i;
+    struct Nested { void f() { ++i; } }
+    foreach(T; TypeTuple!(int, immutable int, inout int, string, S, Nested, Object))
+    {
+        static assert(!__traits(compiles, needLvalue(rvalueOf!T)));
+        static assert( __traits(compiles, needLvalue(lvalueOf!T)));
+        static assert(is(typeof(rvalueOf!T) == T));
+        static assert(is(typeof(lvalueOf!T) == T));
+    }
+
+    static assert(!__traits(compiles, rvalueOf!int = 1));
+    static assert( __traits(compiles, lvalueOf!byte = 127));
+    static assert(!__traits(compiles, lvalueOf!byte = 128));
+}
+
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// SomethingTypeOf
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+private template AliasThisTypeOf(T) if (isAggregateType!T)
+{
+    alias members = TypeTuple!(__traits(getAliasThis, T));
+
+    static if (members.length == 1)
+    {
+        alias AliasThisTypeOf = typeof(__traits(getMember, T.init, members[0]));
+    }
+    else
+        static assert(0, T.stringof~" does not have alias this type");
+}
+
+/*
+ */
+template BooleanTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = BooleanTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (is(Unqual!X == bool))
+    {
+        alias BooleanTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not boolean type");
+}
+
+unittest
+{
+    // unexpected failure, maybe dmd type-merging bug
+    foreach (T; TypeTuple!bool)
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( is(Q!T == BooleanTypeOf!(            Q!T  )));
+            static assert( is(Q!T == BooleanTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(void, NumericTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(BooleanTypeOf!(            Q!T  )), Q!T.stringof);
+            static assert(!is(BooleanTypeOf!( SubTypeOf!(Q!T) )));
+        }
+}
+
+unittest
+{
+    struct B
+    {
+        bool val;
+        alias val this;
+    }
+    struct S
+    {
+        B b;
+        alias b this;
+    }
+    static assert(is(BooleanTypeOf!B == bool));
+    static assert(is(BooleanTypeOf!S == bool));
+}
+
+/*
+ */
+template IntegralTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = IntegralTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (staticIndexOf!(Unqual!X, IntegralTypeList) >= 0)
+    {
+        alias IntegralTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not an integral type");
+}
+
+unittest
+{
+    foreach (T; IntegralTypeList)
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( is(Q!T == IntegralTypeOf!(            Q!T  )));
+            static assert( is(Q!T == IntegralTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(void, bool, FloatingPointTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(IntegralTypeOf!(            Q!T  )));
+            static assert(!is(IntegralTypeOf!( SubTypeOf!(Q!T) )));
+        }
+}
+
+/*
+ */
+template FloatingPointTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = FloatingPointTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (staticIndexOf!(Unqual!X, FloatingPointTypeList) >= 0)
+    {
+        alias FloatingPointTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not a floating point type");
+}
+
+unittest
+{
+    foreach (T; FloatingPointTypeList)
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( is(Q!T == FloatingPointTypeOf!(            Q!T  )));
+            static assert( is(Q!T == FloatingPointTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(void, bool, IntegralTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(FloatingPointTypeOf!(            Q!T  )));
+            static assert(!is(FloatingPointTypeOf!( SubTypeOf!(Q!T) )));
+        }
+}
+
+/*
+ */
+template NumericTypeOf(T)
+{
+    static if (is(IntegralTypeOf!T X) || is(FloatingPointTypeOf!T X))
+    {
+        alias NumericTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not a numeric type");
+}
+
+unittest
+{
+    foreach (T; NumericTypeList)
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( is(Q!T == NumericTypeOf!(            Q!T  )));
+            static assert( is(Q!T == NumericTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(void, bool, CharTypeList, ImaginaryTypeList, ComplexTypeList))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(NumericTypeOf!(            Q!T  )));
+            static assert(!is(NumericTypeOf!( SubTypeOf!(Q!T) )));
+        }
+}
+
+/*
+ */
+template UnsignedTypeOf(T)
+{
+    static if (is(IntegralTypeOf!T X) &&
+               staticIndexOf!(Unqual!X, UnsignedIntTypeList) >= 0)
+        alias UnsignedTypeOf = X;
+    else
+        static assert(0, T.stringof~" is not an unsigned type.");
+}
+
+/*
+ */
+template SignedTypeOf(T)
+{
+    static if (is(IntegralTypeOf!T X) &&
+               staticIndexOf!(Unqual!X, SignedIntTypeList) >= 0)
+        alias SignedTypeOf = X;
+    else static if (is(FloatingPointTypeOf!T X))
+        alias SignedTypeOf = X;
+    else
+        static assert(0, T.stringof~" is not an signed type.");
+}
+
+/*
+ */
+template CharTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = CharTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (staticIndexOf!(Unqual!X, CharTypeList) >= 0)
+    {
+        alias CharTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not a character type");
+}
+
+unittest
+{
+    foreach (T; CharTypeList)
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( is(CharTypeOf!(            Q!T  )));
+            static assert( is(CharTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(void, bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(CharTypeOf!(            Q!T  )));
+            static assert(!is(CharTypeOf!( SubTypeOf!(Q!T) )));
+        }
+
+    foreach (T; TypeTuple!(string, wstring, dstring, char[4]))
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!is(CharTypeOf!(            Q!T  )));
+            static assert(!is(CharTypeOf!( SubTypeOf!(Q!T) )));
+        }
+}
+
+/*
+ */
+template StaticArrayTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = StaticArrayTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (is(X : E[n], E, size_t n))
+        alias StaticArrayTypeOf = X;
+    else
+        static assert(0, T.stringof~" is not a static array type");
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList))
+        foreach (Q; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+        {
+            static assert(is( Q!(   T[1] ) == StaticArrayTypeOf!( Q!(              T[1]  ) ) ));
+
+            foreach (P; TypeQualifierList)
+            { // SubTypeOf cannot have inout type
+                static assert(is( Q!(P!(T[1])) == StaticArrayTypeOf!( Q!(SubTypeOf!(P!(T[1]))) ) ));
+            }
+        }
+
+    foreach (T; TypeTuple!void)
+        foreach (Q; TypeTuple!TypeQualifierList)
+        {
+            static assert(is( StaticArrayTypeOf!( Q!(void[1]) ) == Q!(void[1]) ));
+        }
+}
+
+/*
+ */
+template DynamicArrayTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = DynamicArrayTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; })))
+    {
+        alias DynamicArrayTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not a dynamic array");
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(/*void, */bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList))
+        foreach (Q; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+        {
+            static assert(is( Q!T[]  == DynamicArrayTypeOf!( Q!T[] ) ));
+            static assert(is( Q!(T[])  == DynamicArrayTypeOf!( Q!(T[]) ) ));
+
+            foreach (P; TypeTuple!(MutableOf, ConstOf, ImmutableOf))
+            {
+                static assert(is( Q!(P!T[]) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!T[])) ) ));
+                static assert(is( Q!(P!(T[])) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!(T[]))) ) ));
+            }
+        }
+
+    static assert(!is(DynamicArrayTypeOf!(int[3])));
+    static assert(!is(DynamicArrayTypeOf!(void[3])));
+    static assert(!is(DynamicArrayTypeOf!(typeof(null))));
+}
+
+/*
+ */
+template ArrayTypeOf(T)
+{
+    static if (is(StaticArrayTypeOf!T X) || is(DynamicArrayTypeOf!T X))
+    {
+        alias ArrayTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not an array type");
+}
+
+/*
+Always returns the Dynamic Array version.
+ */
+template StringTypeOf(T)
+{
+    static if (is(T == typeof(null)))
+    {
+        // It is impossible to determine exact string type from typeof(null) -
+        // it means that StringTypeOf!(typeof(null)) is undefined.
+        // Then this behavior is convenient for template constraint.
+        static assert(0, T.stringof~" is not a string type");
+    }
+    else static if (is(T : const char[]) || is(T : const wchar[]) || is(T : const dchar[]))
+    {
+        static if (is(T : U[], U))
+            alias StringTypeOf = U[];
+        else
+            static assert(0);
+    }
+    else
+        static assert(0, T.stringof~" is not a string type");
+}
+
+unittest
+{
+    foreach (T; CharTypeList)
+        foreach (Q; TypeTuple!(MutableOf, ConstOf, ImmutableOf, InoutOf))
+        {
+            static assert(is(Q!T[] == StringTypeOf!( Q!T[] )));
+
+            static if (!__traits(isSame, Q, InoutOf))
+            {
+                static assert(is(Q!T[] == StringTypeOf!( SubTypeOf!(Q!T[]) )));
+
+                alias Str = Q!T[];
+                class C(S) { S val;  alias val this; }
+                static assert(is(StringTypeOf!(C!Str) == Str));
+            }
+        }
+
+    foreach (T; CharTypeList)
+        foreach (Q; TypeTuple!(SharedOf, SharedConstOf, SharedInoutOf))
+        {
+            static assert(!is(StringTypeOf!( Q!T[] )));
+        }
+}
+
+unittest
+{
+    static assert(is(StringTypeOf!(char[4]) == char[]));
+}
+
+/*
+ */
+template AssocArrayTypeOf(T)
+{
+    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
+        alias X = AssocArrayTypeOf!AT;
+    else
+        alias X = OriginalType!T;
+
+    static if (is(Unqual!X : V[K], K, V))
+    {
+        alias AssocArrayTypeOf = X;
+    }
+    else
+        static assert(0, T.stringof~" is not an associative array type");
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/))
+        foreach (P; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+            foreach (Q; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+                foreach (R; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+                {
+                    static assert(is( P!(Q!T[R!T]) == AssocArrayTypeOf!(            P!(Q!T[R!T])  ) ));
+                }
+
+    foreach (T; TypeTuple!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/))
+        foreach (O; TypeTuple!(TypeQualifierList, InoutOf, SharedInoutOf))
+            foreach (P; TypeTuple!TypeQualifierList)
+                foreach (Q; TypeTuple!TypeQualifierList)
+                    foreach (R; TypeTuple!TypeQualifierList)
+                    {
+                        static assert(is( O!(P!(Q!T[R!T])) == AssocArrayTypeOf!( O!(SubTypeOf!(P!(Q!T[R!T]))) ) ));
+                    }
+}
+
+/*
+ */
+template BuiltinTypeOf(T)
+{
+         static if (is(T : void))               alias BuiltinTypeOf = void;
+    else static if (is(BooleanTypeOf!T X))      alias BuiltinTypeOf = X;
+    else static if (is(IntegralTypeOf!T X))     alias BuiltinTypeOf = X;
+    else static if (is(FloatingPointTypeOf!T X))alias BuiltinTypeOf = X;
+    else static if (is(T : const(ireal)))       alias BuiltinTypeOf = ireal;  //TODO
+    else static if (is(T : const(creal)))       alias BuiltinTypeOf = creal;  //TODO
+    else static if (is(CharTypeOf!T X))         alias BuiltinTypeOf = X;
+    else static if (is(ArrayTypeOf!T X))        alias BuiltinTypeOf = X;
+    else static if (is(AssocArrayTypeOf!T X))   alias BuiltinTypeOf = X;
+    else                                        static assert(0);
+}
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// isSomething
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/**
+ * Detect whether $(D T) is a built-in boolean type.
+ */
+enum bool isBoolean(T) = is(BooleanTypeOf!T) && !isAggregateType!T;
+
+///
+unittest
+{
+    static assert( isBoolean!bool);
+    enum EB : bool { a = true }
+    static assert( isBoolean!EB);
+    static assert(!isBoolean!(SubTypeOf!bool));
+}
+
+/**
+ * Detect whether $(D T) is a built-in integral type. Types $(D bool),
+ * $(D char), $(D wchar), and $(D dchar) are not considered integral.
+ */
+enum bool isIntegral(T) = is(IntegralTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; IntegralTypeList)
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isIntegral!(Q!T));
+            static assert(!isIntegral!(SubTypeOf!(Q!T)));
+        }
+    }
+
+    static assert(!isIntegral!float);
+
+    enum EU : uint { a = 0, b = 1, c = 2 }  // base type is unsigned
+    enum EI : int { a = -1, b = 0, c = 1 }  // base type is signed (bug 7909)
+    static assert(isIntegral!EU &&  isUnsigned!EU && !isSigned!EU);
+    static assert(isIntegral!EI && !isUnsigned!EI &&  isSigned!EI);
+}
+
+/**
+ * Detect whether $(D T) is a built-in floating point type.
+ */
+enum bool isFloatingPoint(T) = is(FloatingPointTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    enum EF : real { a = 1.414, b = 1.732, c = 2.236 }
+
+    foreach (T; TypeTuple!(FloatingPointTypeList, EF))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isFloatingPoint!(Q!T));
+            static assert(!isFloatingPoint!(SubTypeOf!(Q!T)));
+        }
+    }
+    foreach (T; IntegralTypeList)
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!isFloatingPoint!(Q!T));
+        }
+    }
+}
+
+/**
+Detect whether $(D T) is a built-in numeric type (integral or floating
+point).
+ */
+enum bool isNumeric(T) = is(NumericTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(NumericTypeList))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isNumeric!(Q!T));
+            static assert(!isNumeric!(SubTypeOf!(Q!T)));
+        }
+    }
+}
+
+/**
+Detect whether $(D T) is a scalar type (a built-in numeric, character or boolean type).
+ */
+enum bool isScalarType(T) = isNumeric!T || isSomeChar!T || isBoolean!T;
+
+///
+unittest
+{
+    static assert(!isScalarType!void);
+    static assert( isScalarType!(immutable(int)));
+    static assert( isScalarType!(shared(float)));
+    static assert( isScalarType!(shared(const bool)));
+    static assert( isScalarType!(const(dchar)));
+}
+
+/**
+Detect whether $(D T) is a basic type (scalar type or void).
+ */
+enum bool isBasicType(T) = isScalarType!T || is(T == void);
+
+///
+unittest
+{
+    static assert(isBasicType!void);
+    static assert(isBasicType!(immutable(int)));
+    static assert(isBasicType!(shared(float)));
+    static assert(isBasicType!(shared(const bool)));
+    static assert(isBasicType!(const(dchar)));
+}
+
+/**
+Detect whether $(D T) is a built-in unsigned numeric type.
+ */
+enum bool isUnsigned(T) = is(UnsignedTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(UnsignedIntTypeList))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isUnsigned!(Q!T));
+            static assert(!isUnsigned!(SubTypeOf!(Q!T)));
+        }
+    }
+}
+
+/**
+Detect whether $(D T) is a built-in signed numeric type.
+ */
+enum bool isSigned(T) = is(SignedTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(SignedIntTypeList))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isSigned!(Q!T));
+            static assert(!isSigned!(SubTypeOf!(Q!T)));
+        }
+    }
+}
+
+/**
+Detect whether $(D T) is one of the built-in character types.
+ */
+enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T;
+
+///
+unittest
+{
+    static assert(!isSomeChar!int);
+    static assert(!isSomeChar!byte);
+    static assert(!isSomeChar!string);
+    static assert(!isSomeChar!wstring);
+    static assert(!isSomeChar!dstring);
+    static assert(!isSomeChar!(char[4]));
+}
+
+unittest
+{
+    enum EC : char { a = 'x', b = 'y' }
+
+    foreach (T; TypeTuple!(CharTypeList, EC))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isSomeChar!(            Q!T  ));
+            static assert(!isSomeChar!( SubTypeOf!(Q!T) ));
+        }
+    }
+}
+
+/**
+Detect whether $(D T) is one of the built-in string types.
+
+The built-in string types are $(D Char[]), where $(D Char) is any of $(D char),
+$(D wchar) or $(D dchar), with or without qualifiers.
+
+Static arrays of characters (like $(D char[80])) are not considered
+built-in string types.
+ */
+enum bool isSomeString(T) = is(StringTypeOf!T) && !isAggregateType!T && !isStaticArray!T;
+
+///
+unittest
+{
+    static assert(!isSomeString!int);
+    static assert(!isSomeString!(int[]));
+    static assert(!isSomeString!(byte[]));
+    static assert(!isSomeString!(typeof(null)));
+    static assert(!isSomeString!(char[4]));
+
+    enum ES : string { a = "aaa", b = "bbb" }
+    static assert( isSomeString!ES);
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(char[], dchar[], string, wstring, dstring))
+    {
+        static assert( isSomeString!(           T ));
+        static assert(!isSomeString!(SubTypeOf!(T)));
+    }
+}
+
+/**
+ * Detect whether type $(D T) is a narrow string.
+ *
+ * All arrays that use char, wchar, and their qualified versions are narrow
+ * strings. (Those include string and wstring).
+ */
+enum bool isNarrowString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isAggregateType!T && !isStaticArray!T;
+
+///
+unittest
+{
+    static assert(isNarrowString!string);
+    static assert(isNarrowString!wstring);
+    static assert(isNarrowString!(char[]));
+    static assert(isNarrowString!(wchar[]));
+
+    static assert(!isNarrowString!dstring);
+    static assert(!isNarrowString!(dchar[]));
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(char[], string, wstring))
+    {
+        foreach (Q; TypeTuple!(MutableOf, ConstOf, ImmutableOf)/*TypeQualifierList*/)
+        {
+            static assert( isNarrowString!(            Q!T  ));
+            static assert(!isNarrowString!( SubTypeOf!(Q!T) ));
+        }
+    }
+
+    foreach (T; TypeTuple!(int, int[], byte[], dchar[], dstring, char[4]))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert(!isNarrowString!(            Q!T  ));
+            static assert(!isNarrowString!( SubTypeOf!(Q!T) ));
+        }
+    }
+}
+
+
+/*
+Detect whether $(D T) is a struct or static array that is implicitly
+convertible to a string.
+ */
+template isConvertibleToString(T)
+{
+    enum isConvertibleToString = (isAggregateType!T || isStaticArray!T) && is(StringTypeOf!T);
+}
+
+unittest
+{
+    static struct AliasedString
+    {
+        string s;
+        alias s this;
+    }
+    assert(!isConvertibleToString!string);
+    assert(isConvertibleToString!AliasedString);
+    assert(isConvertibleToString!(char[25]));
+}
+
+package template convertToString(T)
+{
+    static if (isConvertibleToString!T)
+        alias convertToString = StringTypeOf!T;
+    else
+        alias convertToString = T;
+}
+
+/**
+ * Detect whether type $(D T) is a string that will be autodecoded.
+ *
+ * All arrays that use char, wchar, and their qualified versions are narrow
+ * strings. (Those include string and wstring).
+ * Aggregates that implicitly cast to narrow strings are included.
+ *
+ * Params:
+ *      T = type to be tested
+ *
+ * Returns:
+ *      true if T represents a string that is subject to autodecoding
+ *
+ * See Also:
+ *      $(LREF isNarrowString)
+ */
+enum bool isAutodecodableString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isStaticArray!T;
+
+///
+unittest
+{
+    static struct Stringish
+    {
+        string s;
+        alias s this;
+    }
+    assert(isAutodecodableString!wstring);
+    assert(isAutodecodableString!Stringish);
+    assert(!isAutodecodableString!dstring);
+}
+
+/**
+ * Detect whether type $(D T) is a static array.
+ */
+enum bool isStaticArray(T) = is(StaticArrayTypeOf!T) && !isAggregateType!T;
+
+///
+unittest
+{
+    static assert(!isStaticArray!(const(int)[]));
+    static assert(!isStaticArray!(immutable(int)[]));
+    static assert(!isStaticArray!(const(int)[4][]));
+    static assert(!isStaticArray!(int[]));
+    static assert(!isStaticArray!(int[char]));
+    static assert(!isStaticArray!(int[1][]));
+    static assert(!isStaticArray!(int[int]));
+    static assert(!isStaticArray!int);
+}
+
+unittest
+{
+    foreach (T; TypeTuple!(int[51], int[][2],
+                           char[][int][11], immutable char[13u],
+                           const(real)[1], const(real)[1][1], void[0]))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isStaticArray!(            Q!T  ));
+            static assert(!isStaticArray!( SubTypeOf!(Q!T) ));
+        }
+    }
+
+    //enum ESA : int[1] { a = [1], b = [2] }
+    //static assert( isStaticArray!ESA);
+}
+
+/**
+ * Detect whether type $(D T) is a dynamic array.
+ */
+enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(int[], char[], string, long[3][], double[string][]))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isDynamicArray!(            Q!T  ));
+            static assert(!isDynamicArray!( SubTypeOf!(Q!T) ));
+        }
+    }
+
+    static assert(!isDynamicArray!(int[5]));
+    static assert(!isDynamicArray!(typeof(null)));
+
+    //enum EDA : int[] { a = [1], b = [2] }
+    //static assert( isDynamicArray!EDA);
+}
+
+/**
+ * Detect whether type $(D T) is an array (static or dynamic; for associative
+ *  arrays see $(LREF isAssociativeArray)).
+ */
+enum bool isArray(T) = isStaticArray!T || isDynamicArray!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(int[], int[5], void[]))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isArray!(Q!T));
+            static assert(!isArray!(SubTypeOf!(Q!T)));
+        }
+    }
+
+    static assert(!isArray!uint);
+    static assert(!isArray!(uint[uint]));
+    static assert(!isArray!(typeof(null)));
+}
+
+/**
+ * Detect whether $(D T) is an associative array type
+ */
+enum bool isAssociativeArray(T) = is(AssocArrayTypeOf!T) && !isAggregateType!T;
+
+unittest
+{
+    struct Foo
+    {
+        @property uint[] keys()   { return null; }
+        @property uint[] values() { return null; }
+    }
+
+    foreach (T; TypeTuple!(int[int], int[string], immutable(char[5])[int]))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isAssociativeArray!(Q!T));
+            static assert(!isAssociativeArray!(SubTypeOf!(Q!T)));
+        }
+    }
+
+    static assert(!isAssociativeArray!Foo);
+    static assert(!isAssociativeArray!int);
+    static assert(!isAssociativeArray!(int[]));
+    static assert(!isAssociativeArray!(typeof(null)));
+
+    //enum EAA : int[int] { a = [1:1], b = [2:2] }
+    //static assert( isAssociativeArray!EAA);
+}
+
+/**
+ * Detect whether type $(D T) is a builtin type.
+ */
+enum bool isBuiltinType(T) = is(BuiltinTypeOf!T) && !isAggregateType!T;
+
+///
+unittest
+{
+    class C;
+    union U;
+    struct S;
+    interface I;
+
+    static assert( isBuiltinType!void);
+    static assert( isBuiltinType!string);
+    static assert( isBuiltinType!(int[]));
+    static assert( isBuiltinType!(C[string]));
+    static assert(!isBuiltinType!C);
+    static assert(!isBuiltinType!U);
+    static assert(!isBuiltinType!S);
+    static assert(!isBuiltinType!I);
+    static assert(!isBuiltinType!(void delegate(int)));
+}
+
+/**
+ * Detect whether type $(D T) is a SIMD vector type.
+ */
+enum bool isSIMDVector(T) = is(T : __vector(V[N]), V, size_t N);
+
+unittest
+{
+    static if (is(__vector(float[4])))
+    {
+        alias SimdVec = __vector(float[4]);
+        static assert(isSIMDVector!(__vector(float[4])));
+        static assert(isSIMDVector!SimdVec);
+    }
+    static assert(!isSIMDVector!uint);
+    static assert(!isSIMDVector!(float[4]));
+}
+
+/**
+ * Detect whether type $(D T) is a pointer.
+ */
+enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T;
+
+unittest
+{
+    foreach (T; TypeTuple!(int*, void*, char[]*))
+    {
+        foreach (Q; TypeQualifierList)
+        {
+            static assert( isPointer!(Q!T));
+            static assert(!isPointer!(SubTypeOf!(Q!T)));
+        }
+    }
+
+    static assert(!isPointer!uint);
+    static assert(!isPointer!(uint[uint]));
+    static assert(!isPointer!(char[]));
+    static assert(!isPointer!(typeof(null)));
+}
+
+/**
+Returns the target type of a pointer.
+*/
+alias PointerTarget(T : T*) = T;
+
+// Explicitly undocumented. It will be removed in June 2016. @@@DEPRECATED_2016-06@@@
+deprecated("Please use PointerTarget instead.")
+alias pointerTarget = PointerTarget;
+
+unittest
+{
+    static assert( is(PointerTarget!(int*) == int));
+    static assert( is(PointerTarget!(long*) == long));
+
+    static assert(!is(PointerTarget!int));
+}
+
+/**
+ * Detect whether type $(D T) is an aggregate type.
+ */
+enum bool isAggregateType(T) = is(T == struct) || is(T == union) ||
+                               is(T == class) || is(T == interface);
+
+///
+unittest
+{
+    class C;
+    union U;
+    struct S;
+    interface I;
+
+    static assert( isAggregateType!C);
+    static assert( isAggregateType!U);
+    static assert( isAggregateType!S);
+    static assert( isAggregateType!I);
+    static assert(!isAggregateType!void);
+    static assert(!isAggregateType!string);
+    static assert(!isAggregateType!(int[]));
+    static assert(!isAggregateType!(C[string]));
+    static assert(!isAggregateType!(void delegate(int)));
+}
+
+/**
+ * Returns $(D true) if T can be iterated over using a $(D foreach) loop with
+ * a single loop variable of automatically inferred type, regardless of how
+ * the $(D foreach) loop is implemented.  This includes ranges, structs/classes
+ * that define $(D opApply) with a single loop variable, and builtin dynamic,
+ * static and associative arrays.
+ */
+enum bool isIterable(T) = is(typeof({ foreach(elem; T.init) {} }));
+
+///
+unittest
+{
+    struct OpApply
+    {
+        int opApply(int delegate(ref uint) dg) { assert(0); }
+    }
+
+    struct Range
+    {
+        @property uint front() { assert(0); }
+        void popFront() { assert(0); }
+        enum bool empty = false;
+    }
+
+    static assert( isIterable!(uint[]));
+    static assert( isIterable!OpApply);
+    static assert( isIterable!(uint[string]));
+    static assert( isIterable!Range);
+
+    static assert(!isIterable!uint);
+}
+
+/**
+ * Returns true if T is not const or immutable.  Note that isMutable is true for
+ * string, or immutable(char)[], because the 'head' is mutable.
+ */
+enum bool isMutable(T) = !is(T == const) && !is(T == immutable) && !is(T == inout);
+
+///
+unittest
+{
+    static assert( isMutable!int);
+    static assert( isMutable!string);
+    static assert( isMutable!(shared int));
+    static assert( isMutable!(shared const(int)[]));
+
+    static assert(!isMutable!(const int));
+    static assert(!isMutable!(inout int));
+    static assert(!isMutable!(shared(const int)));
+    static assert(!isMutable!(shared(inout int)));
+    static assert(!isMutable!(immutable string));
+}
+
+/**
+ * Returns true if T is an instance of the template S.
+ */
+enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...);
+
+///
+unittest
+{
+    static struct Foo(T...) { }
+    static struct Bar(T...) { }
+    static struct Doo(T) { }
+    static struct ABC(int x) { }
+    static assert(isInstanceOf!(Foo, Foo!int));
+    static assert(!isInstanceOf!(Foo, Bar!int));
+    static assert(!isInstanceOf!(Foo, int));
+    static assert(isInstanceOf!(Doo, Doo!int));
+    static assert(isInstanceOf!(ABC, ABC!1));
+    static assert(!__traits(compiles, isInstanceOf!(Foo, Foo)));
+}
+
+/**
+ * Check whether the tuple T is an expression tuple.
+ * An expression tuple only contains expressions.
+ *
+ * See_Also: $(LREF isTypeTuple).
+ */
+template isExpressions(T ...)
+{
+    static if (T.length >= 2)
+        enum bool isExpressions =
+            isExpressions!(T[0 .. $/2]) &&
+            isExpressions!(T[$/2 .. $]);
+    else static if (T.length == 1)
+        enum bool isExpressions =
+            !is(T[0]) && __traits(compiles, { auto ex = T[0]; });
+    else
+        enum bool isExpressions = true; // default
+}
+
+///
+unittest
+{
+    static assert(isExpressions!(1, 2.0, "a"));
+    static assert(!isExpressions!(int, double, string));
+    static assert(!isExpressions!(int, 2.0, "a"));
+}
+
+/**
+ * Alternate name for $(LREF isExpressions), kept for legacy compatibility.
+ */
+
+alias isExpressionTuple = isExpressions;
+
+unittest
+{
+    void foo();
+    static int bar() { return 42; }
+    enum aa = [ 1: -1 ];
+    alias myint = int;
+
+    static assert( isExpressionTuple!(42));
+    static assert( isExpressionTuple!aa);
+    static assert( isExpressionTuple!("cattywampus", 2.7, aa));
+    static assert( isExpressionTuple!(bar()));
+
+    static assert(!isExpressionTuple!isExpressionTuple);
+    static assert(!isExpressionTuple!foo);
+    static assert(!isExpressionTuple!( (a) { } ));
+    static assert(!isExpressionTuple!int);
+    static assert(!isExpressionTuple!myint);
+}
+
+
+/**
+ * Check whether the tuple $(D T) is a type tuple.
+ * A type tuple only contains types.
+ *
+ * See_Also: $(LREF isExpressions).
+ */
+template isTypeTuple(T...)
+{
+    static if (T.length >= 2)
+        enum bool isTypeTuple = isTypeTuple!(T[0 .. $/2]) && isTypeTuple!(T[$/2 .. $]);
+    else static if (T.length == 1)
+        enum bool isTypeTuple = is(T[0]);
+    else
+        enum bool isTypeTuple = true; // default
+}
+
+///
+unittest
+{
+    static assert(isTypeTuple!(int, float, string));
+    static assert(!isTypeTuple!(1, 2.0, "a"));
+    static assert(!isTypeTuple!(1, double, string));
+}
+
+unittest
+{
+    class C {}
+    void func(int) {}
+    auto c = new C;
+    enum CONST = 42;
+
+    static assert( isTypeTuple!int);
+    static assert( isTypeTuple!string);
+    static assert( isTypeTuple!C);
+    static assert( isTypeTuple!(typeof(func)));
+    static assert( isTypeTuple!(int, char, double));
+
+    static assert(!isTypeTuple!c);
+    static assert(!isTypeTuple!isTypeTuple);
+    static assert(!isTypeTuple!CONST);
+}
+
+
+/**
+Detect whether symbol or type $(D T) is a function pointer.
+ */
+template isFunctionPointer(T...)
+    if (T.length == 1)
+{
+    static if (is(T[0] U) || is(typeof(T[0]) U))
+    {
+        static if (is(U F : F*) && is(F == function))
+            enum bool isFunctionPointer = true;
+        else
+            enum bool isFunctionPointer = false;
+    }
+    else
+        enum bool isFunctionPointer = false;
+}
+
+///
+unittest
+{
+    static void foo() {}
+    void bar() {}
+
+    auto fpfoo = &foo;
+    static assert( isFunctionPointer!fpfoo);
+    static assert( isFunctionPointer!(void function()));
+
+    auto dgbar = &bar;
+    static assert(!isFunctionPointer!dgbar);
+    static assert(!isFunctionPointer!(void delegate()));
+    static assert(!isFunctionPointer!foo);
+    static assert(!isFunctionPointer!bar);
+
+    static assert( isFunctionPointer!((int a) {}));
+}
+
+/**
+Detect whether symbol or type $(D T) is a delegate.
+*/
+template isDelegate(T...)
+    if (T.length == 1)
+{
+    static if (is(typeof(& T[0]) U : U*) && is(typeof(& T[0]) U == delegate))
+    {
+        // T is a (nested) function symbol.
+        enum bool isDelegate = true;
+    }
+    else static if (is(T[0] W) || is(typeof(T[0]) W))
+    {
+        // T is an expression or a type.  Take the type of it and examine.
+        enum bool isDelegate = is(W == delegate);
+    }
+    else
+        enum bool isDelegate = false;
+}
+
+///
+unittest
+{
+    static void sfunc() { }
+    int x;
+    void func() { x++; }
+
+    int delegate() dg;
+    assert(isDelegate!dg);
+    assert(isDelegate!(int delegate()));
+    assert(isDelegate!(typeof(&func)));
+
+    int function() fp;
+    assert(!isDelegate!fp);
+    assert(!isDelegate!(int function()));
+    assert(!isDelegate!(typeof(&sfunc)));
+}
+
+/**
+Detect whether symbol or type $(D T) is a function, a function pointer or a delegate.
+ */
+template isSomeFunction(T...)
+    if (T.length == 1)
+{
+    static if (is(typeof(& T[0]) U : U*) && is(U == function) || is(typeof(& T[0]) U == delegate))
+    {
+        // T is a (nested) function symbol.
+        enum bool isSomeFunction = true;
+    }
+    else static if (is(T[0] W) || is(typeof(T[0]) W))
+    {
+        // T is an expression or a type.  Take the type of it and examine.
+        static if (is(W F : F*) && is(F == function))
+            enum bool isSomeFunction = true; // function pointer
+        else
+            enum bool isSomeFunction = is(W == function) || is(W == delegate);
+    }
+    else
+        enum bool isSomeFunction = false;
+}
+
+unittest
+{
+    static real func(ref int) { return 0; }
+    static void prop() @property { }
+    void nestedFunc() { }
+    void nestedProp() @property { }
+    class C
+    {
+        real method(ref int) { return 0; }
+        real prop() @property { return 0; }
+    }
+    auto c = new C;
+    auto fp = &func;
+    auto dg = &c.method;
+    real val;
+
+    static assert( isSomeFunction!func);
+    static assert( isSomeFunction!prop);
+    static assert( isSomeFunction!nestedFunc);
+    static assert( isSomeFunction!nestedProp);
+    static assert( isSomeFunction!(C.method));
+    static assert( isSomeFunction!(C.prop));
+    static assert( isSomeFunction!(c.prop));
+    static assert( isSomeFunction!(c.prop));
+    static assert( isSomeFunction!fp);
+    static assert( isSomeFunction!dg);
+    static assert( isSomeFunction!(typeof(func)));
+    static assert( isSomeFunction!(real function(ref int)));
+    static assert( isSomeFunction!(real delegate(ref int)));
+    static assert( isSomeFunction!((int a) { return a; }));
+
+    static assert(!isSomeFunction!int);
+    static assert(!isSomeFunction!val);
+    static assert(!isSomeFunction!isSomeFunction);
+}
+
+
+/**
+Detect whether $(D T) is a callable object, which can be called with the
+function call operator $(D $(LPAREN)...$(RPAREN)).
+ */
+template isCallable(T...)
+    if (T.length == 1)
+{
+    static if (is(typeof(& T[0].opCall) == delegate))
+        // T is a object which has a member function opCall().
+        enum bool isCallable = true;
+    else static if (is(typeof(& T[0].opCall) V : V*) && is(V == function))
+        // T is a type which has a static member function opCall().
+        enum bool isCallable = true;
+    else
+        enum bool isCallable = isSomeFunction!T;
+}
+
+///
+unittest
+{
+    interface I { real value() @property; }
+    struct S { static int opCall(int) { return 0; } }
+    class C { int opCall(int) { return 0; } }
+    auto c = new C;
+
+    static assert( isCallable!c);
+    static assert( isCallable!S);
+    static assert( isCallable!(c.opCall));
+    static assert( isCallable!(I.value));
+    static assert( isCallable!((int a) { return a; }));
+
+    static assert(!isCallable!I);
+}
+
+
+/**
+ * Detect whether $(D T) is a an abstract function.
+ */
+template isAbstractFunction(T...)
+    if (T.length == 1)
+{
+    enum bool isAbstractFunction = __traits(isAbstractFunction, T[0]);
+}
+
+unittest
+{
+    struct S { void foo() { } }
+    class C { void foo() { } }
+    class AC { abstract void foo(); }
+    static assert(!isAbstractFunction!(S.foo));
+    static assert(!isAbstractFunction!(C.foo));
+    static assert( isAbstractFunction!(AC.foo));
+}
+
+/**
+ * Detect whether $(D T) is a a final function.
+ */
+template isFinalFunction(T...)
+    if (T.length == 1)
+{
+    enum bool isFinalFunction = __traits(isFinalFunction, T[0]);
+}
+
+///
+unittest
+{
+    struct S { void bar() { } }
+    final class FC { void foo(); }
+    class C
+    {
+        void bar() { }
+        final void foo();
+    }
+    static assert(!isFinalFunction!(S.bar));
+    static assert( isFinalFunction!(FC.foo));
+    static assert(!isFinalFunction!(C.bar));
+    static assert( isFinalFunction!(C.foo));
+}
+
+/**
+Determines whether function $(D f) requires a context pointer.
+*/
+template isNestedFunction(alias f)
+{
+    enum isNestedFunction = __traits(isNested, f);
+}
+
+unittest
+{
+    static void f() { }
+    void g() { }
+    static assert(!isNestedFunction!f);
+    static assert( isNestedFunction!g);
+}
+
+/**
+ * Detect whether $(D T) is a an abstract class.
+ */
+template isAbstractClass(T...)
+    if (T.length == 1)
+{
+    enum bool isAbstractClass = __traits(isAbstractClass, T[0]);
+}
+
+///
+unittest
+{
+    struct S { }
+    class C { }
+    abstract class AC { }
+    static assert(!isAbstractClass!S);
+    static assert(!isAbstractClass!C);
+    static assert( isAbstractClass!AC);
+}
+
+/**
+ * Detect whether $(D T) is a a final class.
+ */
+template isFinalClass(T...)
+    if (T.length == 1)
+{
+    enum bool isFinalClass = __traits(isFinalClass, T[0]);
+}
+
+///
+unittest
+{
+    class C { }
+    abstract class AC { }
+    final class FC1 : C { }
+    final class FC2 { }
+    static assert(!isFinalClass!C);
+    static assert(!isFinalClass!AC);
+    static assert( isFinalClass!FC1);
+    static assert( isFinalClass!FC2);
+}
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// General Types
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/**
+Removes all qualifiers, if any, from type $(D T).
+ */
+template Unqual(T)
+{
+    version (none) // Error: recursive alias declaration @@@BUG1308@@@
+    {
+             static if (is(T U ==     const U)) alias Unqual = Unqual!U;
+        else static if (is(T U == immutable U)) alias Unqual = Unqual!U;
+        else static if (is(T U ==     inout U)) alias Unqual = Unqual!U;
+        else static if (is(T U ==    shared U)) alias Unqual = Unqual!U;
+        else                                    alias Unqual =        T;
+    }
+    else // workaround
+    {
+             static if (is(T U ==          immutable U)) alias Unqual = U;
+        else static if (is(T U == shared inout const U)) alias Unqual = U;
+        else static if (is(T U == shared inout       U)) alias Unqual = U;
+        else static if (is(T U == shared       const U)) alias Unqual = U;
+        else static if (is(T U == shared             U)) alias Unqual = U;
+        else static if (is(T U ==        inout const U)) alias Unqual = U;
+        else static if (is(T U ==        inout       U)) alias Unqual = U;
+        else static if (is(T U ==              const U)) alias Unqual = U;
+        else                                             alias Unqual = T;
+    }
+}
+
+///
+unittest
+{
+    static assert(is(Unqual!int == int));
+    static assert(is(Unqual!(const int) == int));
+    static assert(is(Unqual!(immutable int) == int));
+    static assert(is(Unqual!(shared int) == int));
+    static assert(is(Unqual!(shared(const int)) == int));
+}
+
+unittest
+{
+    static assert(is(Unqual!(                   int) == int));
+    static assert(is(Unqual!(             const int) == int));
+    static assert(is(Unqual!(       inout       int) == int));
+    static assert(is(Unqual!(       inout const int) == int));
+    static assert(is(Unqual!(shared             int) == int));
+    static assert(is(Unqual!(shared       const int) == int));
+    static assert(is(Unqual!(shared inout       int) == int));
+    static assert(is(Unqual!(shared inout const int) == int));
+    static assert(is(Unqual!(         immutable int) == int));
+
+    alias ImmIntArr = immutable(int[]);
+    static assert(is(Unqual!ImmIntArr == immutable(int)[]));
+}
+
+// [For internal use]
+package template ModifyTypePreservingTQ(alias Modifier, T)
+{
+         static if (is(T U ==          immutable U)) alias ModifyTypePreservingTQ =          immutable Modifier!U;
+    else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U;
+    else static if (is(T U == shared inout       U)) alias ModifyTypePreservingTQ = shared inout       Modifier!U;
+    else static if (is(T U == shared       const U)) alias ModifyTypePreservingTQ = shared       const Modifier!U;
+    else static if (is(T U == shared             U)) alias ModifyTypePreservingTQ = shared             Modifier!U;
+    else static if (is(T U ==        inout const U)) alias ModifyTypePreservingTQ =        inout const Modifier!U;
+    else static if (is(T U ==        inout       U)) alias ModifyTypePreservingTQ =              inout Modifier!U;
+    else static if (is(T U ==              const U)) alias ModifyTypePreservingTQ =              const Modifier!U;
+    else                                             alias ModifyTypePreservingTQ =                    Modifier!T;
+}
+
+unittest
+{
+    alias Intify(T) = int;
+    static assert(is(ModifyTypePreservingTQ!(Intify,                    real) ==                    int));
+    static assert(is(ModifyTypePreservingTQ!(Intify,              const real) ==              const int));
+    static assert(is(ModifyTypePreservingTQ!(Intify,        inout       real) ==        inout       int));
+    static assert(is(ModifyTypePreservingTQ!(Intify,        inout const real) ==        inout const int));
+    static assert(is(ModifyTypePreservingTQ!(Intify, shared             real) == shared             int));
+    static assert(is(ModifyTypePreservingTQ!(Intify, shared       const real) == shared       const int));
+    static assert(is(ModifyTypePreservingTQ!(Intify, shared inout       real) == shared inout       int));
+    static assert(is(ModifyTypePreservingTQ!(Intify, shared inout const real) == shared inout const int));
+    static assert(is(ModifyTypePreservingTQ!(Intify,          immutable real) ==          immutable int));
+}
+
+/**
+ * Copies type qualifiers from $(D FromType) to $(D ToType).
+ *
+ * Supported type qualifiers:
+ * $(UL
+ *     $(LI $(D const))
+ *     $(LI $(D inout))
+ *     $(LI $(D immutable))
+ *     $(LI $(D shared))
+ * )
+ */
+template CopyTypeQualifiers(FromType, ToType)
+{
+    alias T(U) = ToType;
+    alias CopyTypeQualifiers = ModifyTypePreservingTQ!(T, FromType);
+}
+
+///
+unittest
+{
+    static assert(is(CopyTypeQualifiers!(inout const real, int) == inout const int));
+}
+
+unittest
+{
+    static assert(is(CopyTypeQualifiers!(                   real, int) ==                    int));
+    static assert(is(CopyTypeQualifiers!(             const real, int) ==              const int));
+    static assert(is(CopyTypeQualifiers!(       inout       real, int) ==        inout       int));
+    static assert(is(CopyTypeQualifiers!(       inout const real, int) ==        inout const int));
+    static assert(is(CopyTypeQualifiers!(shared             real, int) == shared             int));
+    static assert(is(CopyTypeQualifiers!(shared       const real, int) == shared       const int));
+    static assert(is(CopyTypeQualifiers!(shared inout       real, int) == shared inout       int));
+    static assert(is(CopyTypeQualifiers!(shared inout const real, int) == shared inout const int));
+    static assert(is(CopyTypeQualifiers!(         immutable real, int) ==          immutable int));
+}
+
+/**
+Returns the type of `Target` with the "constness" of `Source`. A type's $(BOLD constness)
+refers to whether it is `const`, `immutable`, or `inout`. If `source` has no constness, the
+returned type will be the same as `Target`.
+*/
+template CopyConstness(FromType, ToType)
+{
+    alias Unshared(T) = T;
+    alias Unshared(T: shared U, U) = U;
+
+    alias CopyConstness = Unshared!(CopyTypeQualifiers!(FromType, ToType));
+}
+
+///
+unittest
+{
+    const(int) i;
+    CopyConstness!(typeof(i), float) f;
+    assert( is(typeof(f) == const float));
+
+    CopyConstness!(char, uint) u;
+    assert( is(typeof(u) == uint));
+
+    //The 'shared' qualifier will not be copied
+    assert(!is(CopyConstness!(shared bool, int) == shared int));
+
+    //But the constness will be
+    assert( is(CopyConstness!(shared const real, double) == const double));
+
+    //Careful, const(int)[] is a mutable array of const(int)
+    alias MutT = CopyConstness!(const(int)[], int);
+    assert(!is(MutT == const(int)));
+
+    //Okay, const(int[]) applies to array and contained ints
+    alias CstT = CopyConstness!(const(int[]), int);
+    assert( is(CstT == const(int)));
+}
+
+unittest
+{
+    struct Test
+    {
+        void method1() {}
+        void method2() const {}
+        void method3() immutable {}
+    }
+
+    assert(is(CopyConstness!(typeof(Test.method1), real) == real));
+
+    assert(is(CopyConstness!(typeof(Test.method2), byte) == const(byte)));
+
+    assert(is(CopyConstness!(typeof(Test.method3), string) == immutable(string)));
+}
+
+unittest
+{
+    assert(is(CopyConstness!(inout(int)[], int[]) == int[]));
+    assert(is(CopyConstness!(inout(int[]), int[]) == inout(int[])));
+}
+
+unittest
+{
+    static assert(is(CopyConstness!(                   int, real) ==             real));
+    static assert(is(CopyConstness!(const              int, real) ==       const real));
+    static assert(is(CopyConstness!(inout              int, real) ==       inout real));
+    static assert(is(CopyConstness!(inout const        int, real) == inout const real));
+    static assert(is(CopyConstness!(shared             int, real) ==             real));
+    static assert(is(CopyConstness!(shared const       int, real) ==       const real));
+    static assert(is(CopyConstness!(shared inout       int, real) == inout       real));
+    static assert(is(CopyConstness!(shared inout const int, real) == inout const real));
+    static assert(is(CopyConstness!(immutable          int, real) ==   immutable real));
+}
+
+/**
+Returns the inferred type of the loop variable when a variable of type T
+is iterated over using a $(D foreach) loop with a single loop variable and
+automatically inferred return type.  Note that this may not be the same as
+$(D std.range.ElementType!Range) in the case of narrow strings, or if T
+has both opApply and a range interface.
+*/
+template ForeachType(T)
+{
+    alias ForeachType = ReturnType!(typeof(
+    (inout int x = 0)
+    {
+        foreach(elem; T.init)
+        {
+            return elem;
+        }
+        assert(0);
+    }));
+}
+
+///
+unittest
+{
+    static assert(is(ForeachType!(uint[]) == uint));
+    static assert(is(ForeachType!string == immutable(char)));
+    static assert(is(ForeachType!(string[string]) == string));
+    static assert(is(ForeachType!(inout(int)[]) == inout(int)));
+}
+
+
+/**
+ * Strips off all $(D enum)s from type $(D T).
+ */
+template OriginalType(T)
+{
+    template Impl(T)
+    {
+        static if (is(T U == enum)) alias Impl = OriginalType!U;
+        else                        alias Impl =              T;
+    }
+
+    alias OriginalType = ModifyTypePreservingTQ!(Impl, T);
+}
+
+///
+unittest
+{
+    enum E : real { a }
+    enum F : E    { a = E.a }
+    alias G = const(F);
+    static assert(is(OriginalType!E == real));
+    static assert(is(OriginalType!F == real));
+    static assert(is(OriginalType!G == const real));
+}
+
+/**
+ * Get the Key type of an Associative Array.
+ */
+alias KeyType(V : V[K], K) = K;
+
+///
+unittest
+{
+    import std.traits;
+    alias Hash = int[string];
+    static assert(is(KeyType!Hash == string));
+    static assert(is(ValueType!Hash == int));
+    KeyType!Hash str = "a"; // str is declared as string
+    ValueType!Hash num = 1; // num is declared as int
+}
+
+/**
+ * Get the Value type of an Associative Array.
+ */
+alias ValueType(V : V[K], K) = V;
+
+///
+unittest
+{
+    import std.traits;
+    alias Hash = int[string];
+    static assert(is(KeyType!Hash == string));
+    static assert(is(ValueType!Hash == int));
+    KeyType!Hash str = "a"; // str is declared as string
+    ValueType!Hash num = 1; // num is declared as int
+}
+
+/**
+ * Returns the corresponding unsigned type for T. T must be a numeric
+ * integral type, otherwise a compile-time error occurs.
+ */
+template Unsigned(T)
+{
+    template Impl(T)
+    {
+        static if (is(T : __vector(V[N]), V, size_t N))
+            alias Impl = __vector(Impl!V[N]);
+        else static if (isUnsigned!T)
+            alias Impl = T;
+        else static if (isSigned!T && !isFloatingPoint!T)
+        {
+            static if (is(T == byte )) alias Impl = ubyte;
+            static if (is(T == short)) alias Impl = ushort;
+            static if (is(T == int  )) alias Impl = uint;
+            static if (is(T == long )) alias Impl = ulong;
+            static if (is(ucent) && is(T == cent )) alias Impl = ucent;
+        }
+        else
+            static assert(false, "Type " ~ T.stringof ~
+                                 " does not have an Unsigned counterpart");
+    }
+
+    alias Unsigned = ModifyTypePreservingTQ!(Impl, OriginalType!T);
+}
+
+unittest
+{
+    alias U1 = Unsigned!int;
+    alias U2 = Unsigned!(const(int));
+    alias U3 = Unsigned!(immutable(int));
+    static assert(is(U1 == uint));
+    static assert(is(U2 == const(uint)));
+    static assert(is(U3 == immutable(uint)));
+    static if (is(__vector(int[4])) && is(__vector(uint[4])))
+    {
+        alias UV1 = Unsigned!(__vector(int[4]));
+        alias UV2 = Unsigned!(const(__vector(int[4])));
+        static assert(is(UV1 == __vector(uint[4])));
+        static assert(is(UV2 == const(__vector(uint[4]))));
+    }
+    //struct S {}
+    //alias U2 = Unsigned!S;
+    //alias U3 = Unsigned!double;
+    static if (is(ucent))
+    {
+        alias U4 = Unsigned!cent;
+        alias U5 = Unsigned!(const(cent));
+        alias U6 = Unsigned!(immutable(cent));
+        static assert(is(U4 == ucent));
+        static assert(is(U5 == const(ucent)));
+        static assert(is(U6 == immutable(ucent)));
+    }
+}
+
+/**
+Returns the largest type, i.e. T such that T.sizeof is the largest.  If more
+than one type is of the same size, the leftmost argument of these in will be
+returned.
+*/
+template Largest(T...) if(T.length >= 1)
+{
+    static if (T.length == 1)
+    {
+        alias Largest = T[0];
+    }
+    else static if (T.length == 2)
+    {
+        static if(T[0].sizeof >= T[1].sizeof)
+        {
+            alias Largest = T[0];
+        }
+        else
+        {
+            alias Largest = T[1];
+        }
+    }
+    else
+    {
+        alias Largest = Largest!(Largest!(T[0 .. $/2]), Largest!(T[$/2 .. $]));
+    }
+}
+
+///
+unittest
+{
+    static assert(is(Largest!(uint, ubyte, ushort, real) == real));
+    static assert(is(Largest!(ulong, double) == ulong));
+    static assert(is(Largest!(double, ulong) == double));
+    static assert(is(Largest!(uint, byte, double, short) == double));
+    static if (is(ucent))
+        static assert(is(Largest!(uint, ubyte, ucent, ushort) == ucent));
+}
+
+/**
+Returns the corresponding signed type for T. T must be a numeric integral type,
+otherwise a compile-time error occurs.
+ */
+template Signed(T)
+{
+    template Impl(T)
+    {
+        static if (is(T : __vector(V[N]), V, size_t N))
+            alias Impl = __vector(Impl!V[N]);
+        else static if (isSigned!T)
+            alias Impl = T;
+        else static if (isUnsigned!T)
+        {
+            static if (is(T == ubyte )) alias Impl = byte;
+            static if (is(T == ushort)) alias Impl = short;
+            static if (is(T == uint  )) alias Impl = int;
+            static if (is(T == ulong )) alias Impl = long;
+            static if (is(ucent) && is(T == ucent )) alias Impl = cent;
+        }
+        else
+            static assert(false, "Type " ~ T.stringof ~
+                                 " does not have an Signed counterpart");
+    }
+
+    alias Signed = ModifyTypePreservingTQ!(Impl, OriginalType!T);
+}
+
+///
+unittest
+{
+    alias S1 = Signed!uint;
+    static assert(is(S1 == int));
+    alias S2 = Signed!(const(uint));
+    static assert(is(S2 == const(int)));
+    alias S3 = Signed!(immutable(uint));
+    static assert(is(S3 == immutable(int)));
+    static if (is(ucent))
+    {
+        alias S4 = Signed!ucent;
+        static assert(is(S4 == cent));
+    }
+}
+
+unittest
+{
+    static assert(is(Signed!float == float));
+    static if (is(__vector(int[4])) && is(__vector(uint[4])))
+    {
+        alias SV1 = Signed!(__vector(uint[4]));
+        alias SV2 = Signed!(const(__vector(uint[4])));
+        static assert(is(SV1 == __vector(int[4])));
+        static assert(is(SV2 == const(__vector(int[4]))));
+    }
+}
+
+
+/**
+Returns the most negative value of the numeric type T.
+*/
+template mostNegative(T)
+    if(isNumeric!T || isSomeChar!T || isBoolean!T)
+{
+    static if (is(typeof(T.min_normal)))
+        enum mostNegative = -T.max;
+    else static if (T.min == 0)
+        enum byte mostNegative = 0;
+    else
+        enum mostNegative = T.min;
+}
+
+///
+unittest
+{
+    static assert(mostNegative!float == -float.max);
+    static assert(mostNegative!double == -double.max);
+    static assert(mostNegative!real == -real.max);
+    static assert(mostNegative!bool == false);
+}
+
+///
+unittest
+{
+    foreach(T; TypeTuple!(bool, byte, short, int, long))
+        static assert(mostNegative!T == T.min);
+
+    foreach(T; TypeTuple!(ubyte, ushort, uint, ulong, char, wchar, dchar))
+        static assert(mostNegative!T == 0);
+}
+
+
+//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+// Misc.
+//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+/**
+Returns the mangled name of symbol or type $(D sth).
+
+$(D mangledName) is the same as builtin $(D .mangleof) property, except that
+the correct names of property functions are obtained.
+--------------------
+module test;
+import std.traits : mangledName;
+
+class C
+{
+    int value() @property;
+}
+pragma(msg, C.value.mangleof);      // prints "i"
+pragma(msg, mangledName!(C.value)); // prints "_D4test1C5valueMFNdZi"
+--------------------
+ */
+template mangledName(sth...)
+    if (sth.length == 1)
+{
+    static if (is(typeof(sth[0]) X) && is(X == void))
+    {
+        // sth[0] is a template symbol
+        enum string mangledName = removeDummyEnvelope(Dummy!sth.Hook.mangleof);
+    }
+    else
+    {
+        enum string mangledName = sth[0].mangleof;
+    }
+}
+
+private template Dummy(T...) { struct Hook {} }
+
+private string removeDummyEnvelope(string s)
+{
+    // remove --> S3std6traits ... Z4Hook
+    s = s[12 .. $ - 6];
+
+    // remove --> DIGIT+ __T5Dummy
+    foreach (i, c; s)
+    {
+        if (c < '0' || '9' < c)
+        {
+            s = s[i .. $];
+            break;
+        }
+    }
+    s = s[9 .. $]; // __T5Dummy
+
+    // remove --> T | V | S
+    immutable kind = s[0];
+    s = s[1 .. $];
+
+    if (kind == 'S') // it's a symbol
+    {
+        /*
+         * The mangled symbol name is packed in LName --> Number Name.  Here
+         * we are chopping off the useless preceding Number, which is the
+         * length of Name in decimal notation.
+         *
+         * NOTE: n = m + Log(m) + 1;  n = LName.length, m = Name.length.
+         */
+        immutable n = s.length;
+        size_t m_upb = 10;
+
+        foreach (k; 1 .. 5) // k = Log(m_upb)
+        {
+            if (n < m_upb + k + 1)
+            {
+                // Now m_upb/10 <= m < m_upb; hence k = Log(m) + 1.
+                s = s[k .. $];
+                break;
+            }
+            m_upb *= 10;
+        }
+    }
+
+    return s;
+}
+
+unittest
+{
+    class C { int value() @property { return 0; } }
+    static assert(mangledName!int == int.mangleof);
+    static assert(mangledName!C == C.mangleof);
+    static assert(mangledName!(C.value)[$ - 12 .. $] == "5valueMFNdZi");
+    static assert(mangledName!mangledName == "3std6traits11mangledName");
+    static assert(mangledName!removeDummyEnvelope ==
+            "_D3std6traits19removeDummyEnvelopeFAyaZAya");
+    int x;
+  static if (is(typeof({ return x; }) : int delegate() pure))   // issue 9148
+    static assert(mangledName!((int a) { return a+x; }) == "DFNaNbNiNfiZi");  // pure nothrow @safe @nogc
+  else
+    static assert(mangledName!((int a) { return a+x; }) == "DFNbNiNfiZi");  // nothrow @safe @nnogc
+}
+
+unittest
+{
+    // Test for bug 5718
+    import std.demangle;
+    int foo;
+    auto foo_demangled = demangle(mangledName!foo);
+    assert(foo_demangled[0 .. 4] == "int " && foo_demangled[$-3 .. $] == "foo",
+        foo_demangled);
+
+    void bar(){}
+    auto bar_demangled = demangle(mangledName!bar);
+    assert(bar_demangled[0 .. 5] == "void " && bar_demangled[$-5 .. $] == "bar()");
+}
+
+
+
+// XXX Select & select should go to another module. (functional or algorithm?)
+
+/**
+Aliases itself to $(D T[0]) if the boolean $(D condition) is $(D true)
+and to $(D T[1]) otherwise.
+ */
+template Select(bool condition, T...) if (T.length == 2)
+{
+    import std.meta : Alias;
+    alias Select = Alias!(T[!condition]);
+}
+
+///
+unittest
+{
+    // can select types
+    static assert(is(Select!(true, int, long) == int));
+    static assert(is(Select!(false, int, long) == long));
+    static struct Foo {}
+    static assert(is(Select!(false, const(int), const(Foo)) == const(Foo)));
+
+    // can select symbols
+    int a = 1;
+    int b = 2;
+    alias selA = Select!(true, a, b);
+    alias selB = Select!(false, a, b);
+    assert(selA == 1);
+    assert(selB == 2);
+
+    // can select (compile-time) expressions
+    enum val = Select!(false, -4, 9 - 6);
+    static assert(val == 3);
+}
+
+/**
+If $(D cond) is $(D true), returns $(D a) without evaluating $(D
+b). Otherwise, returns $(D b) without evaluating $(D a).
+ */
+A select(bool cond : true, A, B)(A a, lazy B b) { return a; }
+/// Ditto
+B select(bool cond : false, A, B)(lazy A a, B b) { return b; }
+
+unittest
+{
+    real pleasecallme() { return 0; }
+    int dontcallme() { assert(0); }
+    auto a = select!true(pleasecallme(), dontcallme());
+    auto b = select!false(dontcallme(), pleasecallme());
+    static assert(is(typeof(a) == real));
+    static assert(is(typeof(b) == real));
+}
+
+/**
+ * Determine if a symbol has a given
+ * $(DDSUBLINK spec/attribute,uda, user-defined attribute).
+ */
+template hasUDA(alias symbol, alias attribute)
+{
+    import std.typetuple : staticIndexOf;
+
+    static if (is(attribute == struct) || is(attribute == class))
+    {
+        template GetTypeOrExp(alias S)
+        {
+            static if (is(typeof(S)))
+                alias GetTypeOrExp = typeof(S);
+            else
+                alias GetTypeOrExp = S;
+        }
+        enum bool hasUDA = staticIndexOf!(attribute, staticMap!(GetTypeOrExp,
+                __traits(getAttributes, symbol))) != -1;
+    }
+    else
+        enum bool hasUDA = staticIndexOf!(attribute, __traits(getAttributes, symbol)) != -1;
+}
+
+///
+unittest
+{
+    enum E;
+    struct S;
+    struct Named { string name; }
+
+    @("alpha") int a;
+    static assert(hasUDA!(a, "alpha"));
+    static assert(!hasUDA!(a, S));
+    static assert(!hasUDA!(a, E));
+
+    @(E) int b;
+    static assert(!hasUDA!(b, "alpha"));
+    static assert(!hasUDA!(b, S));
+    static assert(hasUDA!(b, E));
+
+    @E int c;
+    static assert(!hasUDA!(c, "alpha"));
+    static assert(!hasUDA!(c, S));
+    static assert(hasUDA!(c, E));
+
+    @(S, E) int d;
+    static assert(!hasUDA!(d, "alpha"));
+    static assert(hasUDA!(d, S));
+    static assert(hasUDA!(d, E));
+
+    @S int e;
+    static assert(!hasUDA!(e, "alpha"));
+    static assert(hasUDA!(e, S));
+    static assert(!hasUDA!(e, E));
+
+    @(S, E, "alpha") int f;
+    static assert(hasUDA!(f, "alpha"));
+    static assert(hasUDA!(f, S));
+    static assert(hasUDA!(f, E));
+
+    @(100) int g;
+    static assert(hasUDA!(g, 100));
+
+    @Named("abc") int h;
+    static assert(hasUDA!(h, Named));
+}
+
+/**
+ * Gets the $(DDSUBLINK spec/attribute,uda, user-defined attributes) of the
+ * given type from the given symbol.
+ */
+template getUDAs(alias symbol, alias attribute)
+{
+    import std.typetuple : Filter;
+
+    template isDesiredUDA(alias S) {
+        static if(__traits(compiles, is(typeof(S) == attribute))) {
+            enum isDesiredUDA = is(typeof(S) == attribute);
+        } else {
+            enum isDesiredUDA = isInstanceOf!(attribute, typeof(S));
+        }
+    }
+    alias getUDAs = Filter!(isDesiredUDA, __traits(getAttributes, symbol));
+}
+
+///
+unittest
+{
+    struct Attr
+    {
+        string name;
+        int value;
+    }
+
+    @Attr("Answer", 42) int a;
+    static assert(getUDAs!(a, Attr)[0].name == "Answer");
+    static assert(getUDAs!(a, Attr)[0].value == 42);
+
+    @(Attr("Answer", 42), "string", 9999) int b;
+    static assert(getUDAs!(b, Attr)[0].name == "Answer");
+    static assert(getUDAs!(b, Attr)[0].value == 42);
+
+    @Attr("Answer", 42) @Attr("Pi", 3) int c;
+    static assert(getUDAs!(c, Attr)[0].name == "Answer");
+    static assert(getUDAs!(c, Attr)[0].value == 42);
+    static assert(getUDAs!(c, Attr)[1].name == "Pi");
+    static assert(getUDAs!(c, Attr)[1].value == 3);
+
+    struct AttrT(T)
+    {
+        string name;
+        T value;
+    }
+
+    @AttrT!uint("Answer", 42) @AttrT!int("Pi", 3) @AttrT int d;
+    static assert(getUDAs!(d, AttrT)[0].name == "Answer");
+    static assert(getUDAs!(d, AttrT)[0].value == 42);
+    static assert(getUDAs!(d, AttrT)[1].name == "Pi");
+    static assert(getUDAs!(d, AttrT)[1].value == 3);
+    static assert(getUDAs!(d, AttrT!uint)[0].name == "Answer");
+    static assert(getUDAs!(d, AttrT!uint)[0].value == 42);
+    static assert(getUDAs!(d, AttrT!int)[0].name == "Pi");
+    static assert(getUDAs!(d, AttrT!int)[0].value == 3);
+}
+
+/**
+ * Gets all symbols within `symbol` that have the given user-defined attribute.
+ * This is not recursive; it will not search for symbols within symbols such as
+ * nested structs or unions.
+ */
+template getSymbolsByUDA(alias symbol, alias attribute) {
+    import std.string : format;
+    import std.meta : AliasSeq, Filter;
+
+    // translate a list of strings into symbols. mixing in the entire alias
+    // avoids trying to access the symbol, which could cause a privacy violation
+    template toSymbols(names...) {
+        static if (names.length == 1)
+            mixin("alias toSymbols = AliasSeq!(symbol.%s);".format(names[0]));
+        else
+            mixin("alias toSymbols = AliasSeq!(symbol.%s, toSymbols!(names[1..$]));"
+                  .format(names[0]));
+    }
+
+    enum hasSpecificUDA(string name) = mixin("hasUDA!(symbol.%s, attribute)".format(name));
+
+    alias membersWithUDA = toSymbols!(Filter!(hasSpecificUDA, __traits(allMembers, symbol)));
+
+    // if the symbol itself has the UDA, tack it on to the front of the list
+    static if (hasUDA!(symbol, attribute))
+        alias getSymbolsByUDA = AliasSeq!(symbol, membersWithUDA);
+    else
+        alias getSymbolsByUDA = membersWithUDA;
+}
+
+///
+unittest
+{
+    enum Attr;
+
+    static struct A
+    {
+        @Attr int a;
+        int b;
+        @Attr void doStuff() {}
+        void doOtherStuff() {}
+        static struct Inner
+        {
+            // Not found by getSymbolsByUDA
+            @Attr int c;
+        }
+    }
+
+    // Finds both variables and functions with the attribute, but
+    // doesn't include the variables and functions without it.
+    static assert(getSymbolsByUDA!(A, Attr).length == 2);
+    // Can access attributes on the symbols returned by getSymbolsByUDA.
+    static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[0], Attr));
+    static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[1], Attr));
+
+    static struct UDA { string name; }
+
+    static struct B
+    {
+        @UDA("X")
+        int x;
+        @UDA("Y")
+        int y;
+        @(100)
+        int z;
+    }
+
+    // Finds both UDA attributes.
+    static assert(getSymbolsByUDA!(B, UDA).length == 2);
+    // Finds one `100` attribute.
+    static assert(getSymbolsByUDA!(B, 100).length == 1);
+    // Can get the value of the UDA from the return value
+    static assert(getUDAs!(getSymbolsByUDA!(B, UDA)[0], UDA)[0].name == "X");
+
+    @UDA("A")
+    static struct C
+    {
+        @UDA("B")
+        int d;
+    }
+
+    // Also checks the symbol itself
+    static assert(getSymbolsByUDA!(C, UDA).length == 2);
+    static assert(getSymbolsByUDA!(C, UDA)[0].stringof == "C");
+    static assert(getSymbolsByUDA!(C, UDA)[1].stringof == "d");
+}
+
+// #15335: getSymbolsByUDA fails if type has private members
+unittest
+{
+    // HasPrivateMembers has, well, private members, one of which has a UDA.
+    import std.internal.test.uda;
+    static assert(getSymbolsByUDA!(HasPrivateMembers, Attr).length == 2);
+    static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[0], Attr));
+    static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[1], Attr));
+}
+
+/**
+   Returns: $(D true) iff all types $(D T) are the same.
+*/
+template allSameType(T...)
+{
+    static if (T.length <= 1)
+    {
+        enum bool allSameType = true;
+    }
+    else
+    {
+        enum bool allSameType = is(T[0] == T[1]) && allSameType!(T[1..$]);
+    }
+}
+
+///
+unittest
+{
+    static assert(allSameType!(int, int));
+    static assert(allSameType!(int, int, int));
+    static assert(allSameType!(float, float, float));
+    static assert(!allSameType!(int, double));
+    static assert(!allSameType!(int, float, double));
+    static assert(!allSameType!(int, float, double, real));
+    static assert(!allSameType!(short, int, float, double, real));
+}
+
+/**
+   Returns: $(D true) iff the type $(D T) can be tested in an $(D
+   if)-expression, that is if $(D if (pred(T.init)) {}) is compilable.
+*/
+enum ifTestable(T, alias pred = a => a) = __traits(compiles, { if (pred(T.init)) {} });
+
+unittest
+{
+    import std.meta : AliasSeq;
+    static assert(allSatisfy!(ifTestable, AliasSeq!(bool, int, float, double, string)));
+    struct BoolWrapper { bool value; }
+    static assert(!ifTestable!(bool, a => BoolWrapper(a)));
+}
diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d
new file mode 100644
index 00000000000..708182ef8ab
--- /dev/null
+++ b/libphobos/src/std/typecons.d
@@ -0,0 +1,7089 @@
+// Written in the D programming language.
+
+/**
+This module implements a variety of type constructors, i.e., templates
+that allow construction of new, useful general-purpose types.
+
+Source:    $(PHOBOSSRC std/_typecons.d)
+
+Macros:
+
+WIKI = Phobos/StdVariant
+
+Synopsis:
+
+----
+// value tuples
+alias Coord = Tuple!(float, "x", float, "y", float, "z");
+Coord c;
+c[1] = 1;       // access by index
+c.z = 1;        // access by given name
+alias DicEntry = Tuple!(string, string); // names can be omitted
+
+// Rebindable references to const and immutable objects
+void bar()
+{
+    const w1 = new Widget, w2 = new Widget;
+    w1.foo();
+    // w1 = w2 would not work; can't rebind const object
+    auto r = Rebindable!(const Widget)(w1);
+    // invoke method as if r were a Widget object
+    r.foo();
+    // rebind r to refer to another object
+    r = w2;
+}
+----
+
+Copyright: Copyright the respective authors, 2008-
+License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu),
+           $(WEB bartoszmilewski.wordpress.com, Bartosz Milewski),
+           Don Clugston,
+           Shin Fujishiro,
+           Kenji Hara
+ */
+module std.typecons;
+
+import std.meta; // : AliasSeq, allSatisfy;
+import std.traits;
+
+debug(Unique) import std.stdio;
+
+/**
+Encapsulates unique ownership of a resource.  Resource of type $(D T) is
+deleted at the end of the scope, unless it is transferred.  The
+transfer can be explicit, by calling $(D release), or implicit, when
+returning Unique from a function. The resource can be a polymorphic
+class object, in which case Unique behaves polymorphically too.
+*/
+struct Unique(T)
+{
+/** Represents a reference to $(D T). Resolves to $(D T*) if $(D T) is a value type. */
+static if (is(T:Object))
+    alias RefT = T;
+else
+    alias RefT = T*;
+
+public:
+    // Deferred in case we get some language support for checking uniqueness.
+    version(None)
+    /**
+    Allows safe construction of $(D Unique). It creates the resource and
+    guarantees unique ownership of it (unless $(D T) publishes aliases of
+    $(D this)).
+    Note: Nested structs/classes cannot be created.
+    Params:
+    args = Arguments to pass to $(D T)'s constructor.
+    ---
+    static class C {}
+    auto u = Unique!(C).create();
+    ---
+    */
+    static Unique!T create(A...)(auto ref A args)
+    if (__traits(compiles, new T(args)))
+    {
+        debug(Unique) writeln("Unique.create for ", T.stringof);
+        Unique!T u;
+        u._p = new T(args);
+        return u;
+    }
+
+    /**
+    Constructor that takes an rvalue.
+    It will ensure uniqueness, as long as the rvalue
+    isn't just a view on an lvalue (e.g., a cast).
+    Typical usage:
+    ----
+    Unique!Foo f = new Foo;
+    ----
+    */
+    this(RefT p)
+    {
+        debug(Unique) writeln("Unique constructor with rvalue");
+        _p = p;
+    }
+    /**
+    Constructor that takes an lvalue. It nulls its source.
+    The nulling will ensure uniqueness as long as there
+    are no previous aliases to the source.
+    */
+    this(ref RefT p)
+    {
+        _p = p;
+        debug(Unique) writeln("Unique constructor nulling source");
+        p = null;
+        assert(p is null);
+    }
+    /**
+    Constructor that takes a $(D Unique) of a type that is convertible to our type.
+
+    Typically used to transfer a $(D Unique) rvalue of derived type to
+    a $(D Unique) of base type.
+    Example:
+    ---
+    class C : Object {}
+
+    Unique!C uc = new C;
+    Unique!Object uo = uc.release;
+    ---
+    */
+    this(U)(Unique!U u)
+    if (is(u.RefT:RefT))
+    {
+        debug(Unique) writeln("Unique constructor converting from ", U.stringof);
+        _p = u._p;
+        u._p = null;
+    }
+
+    /// Transfer ownership from a $(D Unique) of a type that is convertible to our type.
+    void opAssign(U)(Unique!U u)
+    if (is(u.RefT:RefT))
+    {
+        debug(Unique) writeln("Unique opAssign converting from ", U.stringof);
+        // first delete any resource we own
+        destroy(this);
+        _p = u._p;
+        u._p = null;
+    }
+
+    ~this()
+    {
+        debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p);
+        if (_p !is null) delete _p;
+        _p = null;
+    }
+    /** Returns whether the resource exists. */
+    @property bool isEmpty() const
+    {
+        return _p is null;
+    }
+    /** Transfer ownership to a $(D Unique) rvalue. Nullifies the current contents. */
+    Unique release()
+    {
+        debug(Unique) writeln("Release");
+        auto u = Unique(_p);
+        assert(_p is null);
+        debug(Unique) writeln("return from Release");
+        return u;
+    }
+    /** Forwards member access to contents. */
+    RefT opDot() { return _p; }
+
+    /**
+    Postblit operator is undefined to prevent the cloning of $(D Unique) objects.
+    */
+    @disable this(this);
+
+private:
+    RefT _p;
+}
+
+///
+unittest
+{
+    static struct S
+    {
+        int i;
+        this(int i){this.i = i;}
+    }
+    Unique!S produce()
+    {
+        // Construct a unique instance of S on the heap
+        Unique!S ut = new S(5);
+        // Implicit transfer of ownership
+        return ut;
+    }
+    // Borrow a unique resource by ref
+    void increment(ref Unique!S ur)
+    {
+        ur.i++;
+    }
+    void consume(Unique!S u2)
+    {
+        assert(u2.i == 6);
+        // Resource automatically deleted here
+    }
+    Unique!S u1;
+    assert(u1.isEmpty);
+    u1 = produce();
+    increment(u1);
+    assert(u1.i == 6);
+    //consume(u1); // Error: u1 is not copyable
+    // Transfer ownership of the resource
+    consume(u1.release);
+    assert(u1.isEmpty);
+}
+
+unittest
+{
+    // test conversion to base ref
+    int deleted = 0;
+    class C
+    {
+        ~this(){deleted++;}
+    }
+    // constructor conversion
+    Unique!Object u = Unique!C(new C);
+    static assert(!__traits(compiles, {u = new C;}));
+    assert(!u.isEmpty);
+    destroy(u);
+    assert(deleted == 1);
+
+    Unique!C uc = new C;
+    static assert(!__traits(compiles, {Unique!Object uo = uc;}));
+    Unique!Object uo = new C;
+    // opAssign conversion, deleting uo resource first
+    uo = uc.release;
+    assert(uc.isEmpty);
+    assert(!uo.isEmpty);
+    assert(deleted == 2);
+}
+
+unittest
+{
+    debug(Unique) writeln("Unique class");
+    class Bar
+    {
+        ~this() { debug(Unique) writeln("    Bar destructor"); }
+        int val() const { return 4; }
+    }
+    alias UBar = Unique!(Bar);
+    UBar g(UBar u)
+    {
+        debug(Unique) writeln("inside g");
+        return u.release;
+    }
+    auto ub = UBar(new Bar);
+    assert(!ub.isEmpty);
+    assert(ub.val == 4);
+    static assert(!__traits(compiles, {auto ub3 = g(ub);}));
+    debug(Unique) writeln("Calling g");
+    auto ub2 = g(ub.release);
+    debug(Unique) writeln("Returned from g");
+    assert(ub.isEmpty);
+    assert(!ub2.isEmpty);
+}
+
+unittest
+{
+    debug(Unique) writeln("Unique struct");
+    struct Foo
+    {
+        ~this() { debug(Unique) writeln("    Foo destructor"); }
+        int val() const { return 3; }
+    }
+    alias UFoo = Unique!(Foo);
+
+    UFoo f(UFoo u)
+    {
+        debug(Unique) writeln("inside f");
+        return u.release;
+    }
+
+    auto uf = UFoo(new Foo);
+    assert(!uf.isEmpty);
+    assert(uf.val == 3);
+    static assert(!__traits(compiles, {auto uf3 = f(uf);}));
+    debug(Unique) writeln("Unique struct: calling f");
+    auto uf2 = f(uf.release);
+    debug(Unique) writeln("Unique struct: returned from f");
+    assert(uf.isEmpty);
+    assert(!uf2.isEmpty);
+}
+
+// Used in Tuple.toString
+private template sharedToString(alias field)
+    if(is(typeof(field) == shared))
+{
+    static immutable sharedToString = typeof(field).stringof;
+}
+
+private template sharedToString(alias field)
+    if(!is(typeof(field) == shared))
+{
+    alias sharedToString = field;
+}
+
+/**
+Tuple of values, for example $(D Tuple!(int, string)) is a record that
+stores an $(D int) and a $(D string). $(D Tuple) can be used to bundle
+values together, notably when returning multiple values from a
+function. If $(D obj) is a `Tuple`, the individual members are
+accessible with the syntax $(D obj[0]) for the first field, $(D obj[1])
+for the second, and so on.
+
+The choice of zero-based indexing instead of one-base indexing was
+motivated by the ability to use value `Tuple`s with various compile-time
+loop constructs (e.g. $(XREF meta, AliasSeq) iteration), all of which use
+zero-based indexing.
+
+Params:
+    Specs = A list of types (and optionally, member names) that the `Tuple` contains.
+*/
+template Tuple(Specs...)
+{
+    import std.meta : staticMap;
+
+    // Parse (type,name) pairs (FieldSpecs) out of the specified
+    // arguments. Some fields would have name, others not.
+    template parseSpecs(Specs...)
+    {
+        static if (Specs.length == 0)
+        {
+            alias parseSpecs = AliasSeq!();
+        }
+        else static if (is(Specs[0]))
+        {
+            static if (is(typeof(Specs[1]) : string))
+            {
+                alias parseSpecs =
+                    AliasSeq!(FieldSpec!(Specs[0 .. 2]),
+                              parseSpecs!(Specs[2 .. $]));
+            }
+            else
+            {
+                alias parseSpecs =
+                    AliasSeq!(FieldSpec!(Specs[0]),
+                              parseSpecs!(Specs[1 .. $]));
+            }
+        }
+        else
+        {
+            static assert(0, "Attempted to instantiate Tuple with an "
+                            ~"invalid argument: "~ Specs[0].stringof);
+        }
+    }
+
+    template FieldSpec(T, string s = "")
+    {
+        alias Type = T;
+        alias name = s;
+    }
+
+    alias fieldSpecs = parseSpecs!Specs;
+
+    // Used with staticMap.
+    alias extractType(alias spec) = spec.Type;
+    alias extractName(alias spec) = spec.name;
+
+    // Generates named fields as follows:
+    //    alias name_0 = Identity!(field[0]);
+    //    alias name_1 = Identity!(field[1]);
+    //      :
+    // NOTE: field[k] is an expression (which yields a symbol of a
+    //       variable) and can't be aliased directly.
+    string injectNamedFields()
+    {
+        string decl = "";
+        foreach (i, name; staticMap!(extractName, fieldSpecs))
+        {
+            import std.format : format;
+
+            decl ~= format("alias _%s = Identity!(field[%s]);", i, i);
+            if (name.length != 0)
+            {
+                decl ~= format("alias %s = _%s;", name, i);
+            }
+        }
+        return decl;
+    }
+
+    // Returns Specs for a subtuple this[from .. to] preserving field
+    // names if any.
+    alias sliceSpecs(size_t from, size_t to) =
+        staticMap!(expandSpec, fieldSpecs[from .. to]);
+
+    template expandSpec(alias spec)
+    {
+        static if (spec.name.length == 0)
+        {
+            alias expandSpec = AliasSeq!(spec.Type);
+        }
+        else
+        {
+            alias expandSpec = AliasSeq!(spec.Type, spec.name);
+        }
+    }
+
+    enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!Tup2 && is(typeof(
+    (ref Tup1 tup1, ref Tup2 tup2)
+    {
+        static assert(tup1.field.length == tup2.field.length);
+        foreach (i, _; Tup1.Types)
+        {
+            auto lhs = typeof(tup1.field[i]).init;
+            auto rhs = typeof(tup2.field[i]).init;
+            static if (op == "=")
+                lhs = rhs;
+            else
+                auto result = mixin("lhs "~op~" rhs");
+        }
+    }));
+
+    enum areBuildCompatibleTuples(Tup1, Tup2) = isTuple!Tup2 && is(typeof(
+    {
+        static assert(Tup1.Types.length == Tup2.Types.length);
+        foreach (i, _; Tup1.Types)
+            static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i]));
+    }));
+
+    /+ Returns $(D true) iff a $(D T) can be initialized from a $(D U). +/
+    enum isBuildable(T, U) =  is(typeof(
+    {
+        U u = U.init;
+        T t = u;
+    }));
+    /+ Helper for partial instanciation +/
+    template isBuildableFrom(U)
+    {
+        enum isBuildableFrom(T) = isBuildable!(T, U);
+    }
+
+    struct Tuple
+    {
+        /**
+         * The types of the `Tuple`'s components.
+         */
+        alias Types = staticMap!(extractType, fieldSpecs);
+
+        ///
+        unittest
+        {
+            alias Fields = Tuple!(int, "id", string, float);
+            static assert(is(Fields.Types == AliasSeq!(int, string, float)));
+        }
+
+        /**
+         * The names of the `Tuple`'s components. Unnamed fields have empty names.
+         */
+        alias fieldNames = staticMap!(extractName, fieldSpecs);
+
+        ///
+        unittest
+        {
+            alias Fields = Tuple!(int, "id", string, float);
+            static assert(Fields.fieldNames == AliasSeq!("id", "", ""));
+        }
+
+        /**
+         * Use $(D t.expand) for a `Tuple` $(D t) to expand it into its
+         * components. The result of $(D expand) acts as if the `Tuple`'s components
+         * were listed as a list of values. (Ordinarily, a $(D Tuple) acts as a
+         * single value.)
+         */
+        Types expand;
+        mixin(injectNamedFields());
+
+        ///
+        unittest
+        {
+            auto t1 = tuple(1, " hello ", 2.3);
+            assert(t1.toString() == `Tuple!(int, string, double)(1, " hello ", 2.3)`);
+
+            void takeSeveralTypes(int n, string s, bool b)
+            {
+                assert(n == 4 && s == "test" && b == false);
+            }
+
+            auto t2 = tuple(4, "test", false);
+            //t.expand acting as a list of values
+            takeSeveralTypes(t2.expand);
+        }
+
+        static if (is(Specs))
+        {
+            // This is mostly to make t[n] work.
+            alias expand this;
+        }
+        else
+        {
+            @property
+            ref inout(Tuple!Types) _Tuple_super() inout @trusted
+            {
+                foreach (i, _; Types)   // Rely on the field layout
+                {
+                    static assert(typeof(return).init.tupleof[i].offsetof ==
+                                                       expand[i].offsetof);
+                }
+                return *cast(typeof(return)*) &(field[0]);
+            }
+            // This is mostly to make t[n] work.
+            alias _Tuple_super this;
+        }
+
+        // backwards compatibility
+        alias field = expand;
+
+        /**
+         * Constructor taking one value for each field.
+         *
+         * Params:
+         *     values = A list of values that are either the same
+         *              types as those given by the `Types` field
+         *              of this `Tuple`, or can implicitly convert
+         *              to those types. They must be in the same
+         *              order as they appear in `Types`.
+         */
+        static if (Types.length > 0)
+        {
+            this(Types values)
+            {
+                field[] = values[];
+            }
+        }
+
+        ///
+        unittest
+        {
+            alias ISD = Tuple!(int, string, double);
+            auto tup = ISD(1, "test", 3.2);
+            assert(tup.toString() == `Tuple!(int, string, double)(1, "test", 3.2)`);
+        }
+
+        /**
+         * Constructor taking a compatible array.
+         *
+         * Params:
+         *     values = A compatible static array to build the `Tuple` from.
+         *              Array slices are not supported.
+         */
+        this(U, size_t n)(U[n] values)
+        if (n == Types.length && allSatisfy!(isBuildableFrom!U, Types))
+        {
+            foreach (i, _; Types)
+            {
+                field[i] = values[i];
+            }
+        }
+
+        ///
+        unittest
+        {
+            int[2] ints;
+            Tuple!(int, int) t = ints;
+        }
+
+        /**
+         * Constructor taking a compatible `Tuple`. Two `Tuple`s are compatible
+         * $(B iff) they are both of the same length, and, for each type `T` on the
+         * left-hand side, the corresponding type `U` on the right-hand side can
+         * implicitly convert to `T`.
+         *
+         * Params:
+         *     another = A compatible `Tuple` to build from. Its type must be
+         *               compatible with the target `Tuple`'s type.
+         */
+        this(U)(U another)
+        if (areBuildCompatibleTuples!(typeof(this), U))
+        {
+            field[] = another.field[];
+        }
+
+        ///
+        unittest
+        {
+            alias IntVec = Tuple!(int, int, int);
+            alias DubVec = Tuple!(double, double, double);
+
+            IntVec iv = tuple(1, 1, 1);
+
+            //Ok, int can implicitly convert to double
+            DubVec dv = iv;
+            //Error: double cannot implicitly convert to int
+            //IntVec iv2 = dv;
+        }
+
+        /**
+         * Comparison for equality. Two `Tuple`s are considered equal
+         * $(B iff) they fulfill the following criteria:
+         *
+         * $(UL
+         *   $(LI Each `Tuple` is the same length.)
+         *   $(LI For each type `T` on the left-hand side and each type
+         *        `U` on the right-hand side, values of type `T` can be
+         *        compared with values of type `U`.)
+         *   $(LI For each value `v1` on the left-hand side and each value
+         *        `v2` on the right-hand side, the expression `v1 == v2` is
+         *        true.))
+         *
+         * Params:
+         *     rhs = The `Tuple` to compare against. It must meeting the criteria
+         *           for comparison between `Tuple`s.
+         *
+         * Returns:
+         *     true if both `Tuple`s are equal, otherwise false.
+         */
+        bool opEquals(R)(R rhs)
+        if (areCompatibleTuples!(typeof(this), R, "=="))
+        {
+            return field[] == rhs.field[];
+        }
+
+        /// ditto
+        bool opEquals(R)(R rhs) const
+        if (areCompatibleTuples!(typeof(this), R, "=="))
+        {
+            return field[] == rhs.field[];
+        }
+
+        ///
+        unittest
+        {
+            Tuple!(int, string) t1 = tuple(1, "test");
+            Tuple!(double, string) t2 =  tuple(1.0, "test");
+            //Ok, int can be compared with double and
+            //both have a value of 1
+            assert(t1 == t2);
+        }
+
+        /**
+         * Comparison for ordering.
+         *
+         * Params:
+         *     rhs = The `Tuple` to compare against. It must meet the criteria
+         *           for comparison between `Tuple`s.
+         *
+         * Returns:
+         * For any values `v1` on the right-hand side and `v2` on the
+         * left-hand side:
+         *
+         * $(UL
+         *   $(LI A negative integer if the expression `v1 < v2` is true.)
+         *   $(LI A positive integer if the expression `v1 > v2` is true.)
+         *   $(LI 0 if the expression `v1 == v2` is true.))
+         */
+        int opCmp(R)(R rhs)
+        if (areCompatibleTuples!(typeof(this), R, "<"))
+        {
+            foreach (i, Unused; Types)
+            {
+                if (field[i] != rhs.field[i])
+                {
+                    return field[i] < rhs.field[i] ? -1 : 1;
+                }
+            }
+            return 0;
+        }
+
+        /// ditto
+        int opCmp(R)(R rhs) const
+        if (areCompatibleTuples!(typeof(this), R, "<"))
+        {
+            foreach (i, Unused; Types)
+            {
+                if (field[i] != rhs.field[i])
+                {
+                    return field[i] < rhs.field[i] ? -1 : 1;
+                }
+            }
+            return 0;
+        }
+
+        /**
+            The first `v1` for which `v1 > v2` is true determines
+            the result. This could lead to unexpected behaviour.
+         */
+        unittest
+        {
+            auto tup1 = tuple(1, 1, 1);
+            auto tup2 = tuple(1, 100, 100);
+            assert(tup1 < tup2);
+
+            //Only the first result matters for comparison
+            tup1[0] = 2;
+            assert(tup1 > tup2);
+        }
+
+        /**
+         * Assignment from another `Tuple`.
+         *
+         * Params:
+         *     rhs = The source `Tuple` to assign from. Each element of the
+         *           source `Tuple` must be implicitly assignable to each
+         *           respective element of the target `Tuple`.
+         */
+        void opAssign(R)(auto ref R rhs)
+        if (areCompatibleTuples!(typeof(this), R, "="))
+        {
+            import std.algorithm : swap;
+
+            static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
+            {
+                if (__ctfe)
+                {
+                    // Cannot use swap at compile time
+                    field[] = rhs.field[];
+                }
+                else
+                {
+                    // Use swap-and-destroy to optimize rvalue assignment
+                    swap!(Tuple!Types)(this, rhs);
+                }
+            }
+            else
+            {
+                // Do not swap; opAssign should be called on the fields.
+                field[] = rhs.field[];
+            }
+        }
+
+        /**
+         * Takes a slice of this `Tuple`.
+         *
+         * Params:
+         *     from = A `size_t` designating the starting position of the slice.
+         *     to = A `size_t` designating the ending position (exclusive) of the slice.
+         *
+         * Returns:
+         *     A new `Tuple` that is a slice from `[from, to$(RPAREN)` of the original.
+         *     It has the same types and values as the range `[from, to$(RPAREN)` in
+         *     the original.
+         */
+        @property
+        ref Tuple!(sliceSpecs!(from, to)) slice(size_t from, size_t to)() @trusted
+        if (from <= to && to <= Types.length)
+        {
+            return *cast(typeof(return)*) &(field[from]);
+        }
+
+        ///
+        unittest
+        {
+            Tuple!(int, string, float, double) a;
+            a[1] = "abc";
+            a[2] = 4.5;
+            auto s = a.slice!(1, 3);
+            static assert(is(typeof(s) == Tuple!(string, float)));
+            assert(s[0] == "abc" && s[1] == 4.5);
+        }
+
+        /**
+            Creates a hash of this `Tuple`.
+
+            Returns:
+                A `size_t` representing the hash of this `Tuple`.
+         */
+        size_t toHash() const nothrow @trusted
+        {
+            size_t h = 0;
+            foreach (i, T; Types)
+                h += typeid(T).getHash(cast(const void*)&field[i]);
+            return h;
+        }
+
+        ///
+        template toString()
+        {
+            /**
+             * Converts to string.
+             *
+             * Returns:
+             *     The string representation of this `Tuple`.
+             */
+            string toString()() const
+            {
+                import std.array : appender;
+                auto app = appender!string();
+                this.toString((const(char)[] chunk) => app ~= chunk);
+                return app.data;
+            }
+
+            import std.format : FormatSpec;
+
+            /**
+             * Formats `Tuple` with either `%s`, `%(inner%)` or `%(inner%|sep%)`.
+             *
+             * $(TABLE2 Formats supported by Tuple,
+             * $(THEAD Format, Description)
+             * $(TROW $(P `%s`), $(P Format like `Tuple!(types)(elements formatted with %s each)`.))
+             * $(TROW $(P `%(inner%)`), $(P The format `inner` is applied the expanded `Tuple`, so
+             *      it may contain as many formats as the `Tuple` has fields.))
+             * $(TROW $(P `%(inner%|sep%)`), $(P The format `inner` is one format, that is applied
+             *      on all fields of the `Tuple`. The inner format must be compatible to all
+             *      of them.)))
+             * ---
+             *  Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ];
+             *
+             *  // Default format
+             *  assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`);
+             *
+             *  // One Format for each individual component
+             *  assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10))         == `0x1 v 1.0000 w 0xa`);
+             *  assert(format(  "%#x v %.4f w %#x"  , tuple(1, 1.0, 10).expand)  == `0x1 v 1.0000 w 0xa`);
+             *
+             *  // One Format for all components
+             *  assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`);
+             *
+             *  // Array of Tuples
+             *  assert(format("%(%(f(%d) = %.1f%);  %)", tupList) == `f(1) = 1.0;  f(2) = 4.0;  f(3) = 9.0`);
+             *
+             *
+             *  // Error: %( %) missing.
+             *  assertThrown!FormatException(
+             *      format("%d, %f", tuple(1, 2.0)) == `1, 2.0`
+             *  );
+             *
+             *  // Error: %( %| %) missing.
+             *  assertThrown!FormatException(
+             *      format("%d", tuple(1, 2)) == `1, 2`
+             *  );
+             *
+             *  // Error: %d inadequate for double.
+             *  assertThrown!FormatException(
+             *      format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0`
+             *  );
+             * ---
+             */
+            void toString(DG)(scope DG sink) const
+            {
+                toString(sink, FormatSpec!char());
+            }
+
+            /// ditto
+            void toString(DG, Char)(scope DG sink, FormatSpec!Char fmt) const
+            {
+                import std.format : formatElement, formattedWrite, FormatException;
+                if (fmt.nested)
+                {
+                    if (fmt.sep)
+                    {
+                        foreach (i, Type; Types)
+                        {
+                            static if (i > 0)
+                            {
+                                sink(fmt.sep);
+                            }
+                            // TODO: Change this once formattedWrite() works for shared objects.
+                            static if (is(Type == class) && is(Type == shared))
+                            {
+                                sink(Type.stringof);
+                            }
+                            else
+                            {
+                                formattedWrite(sink, fmt.nested, this.field[i]);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand));
+                    }
+                }
+                else if (fmt.spec == 's')
+                {
+                    enum header = Unqual!(typeof(this)).stringof ~ "(",
+                         footer = ")",
+                         separator = ", ";
+                    sink(header);
+                    foreach (i, Type; Types)
+                    {
+                        static if (i > 0)
+                        {
+                            sink(separator);
+                        }
+                        // TODO: Change this once formatElement() works for shared objects.
+                        static if (is(Type == class) && is(Type == shared))
+                        {
+                            sink(Type.stringof);
+                        }
+                        else
+                        {
+                            FormatSpec!Char f;
+                            formatElement(sink, field[i], f);
+                        }
+                    }
+                    sink(footer);
+                }
+                else
+                {
+                    throw new FormatException(
+                        "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~
+                            Unqual!(typeof(this)).stringof ~ "', not '%" ~ fmt.spec ~ "'.");
+                }
+            }
+        }
+    }
+}
+
+///
+unittest
+{
+    Tuple!(int, int) point;
+    // assign coordinates
+    point[0] = 5;
+    point[1] = 6;
+    // read coordinates
+    auto x = point[0];
+    auto y = point[1];
+}
+
+/**
+    `Tuple` members can be named. It is legal to mix named and unnamed
+    members. The method above is still applicable to all fields.
+ */
+unittest
+{
+    alias Entry = Tuple!(int, "index", string, "value");
+    Entry e;
+    e.index = 4;
+    e.value = "Hello";
+    assert(e[1] == "Hello");
+    assert(e[0] == 4);
+}
+
+/**
+    A `Tuple` with named fields is a distinct type from a `Tuple` with unnamed
+    fields, i.e. each naming imparts a separate type for the `Tuple`. Two
+    `Tuple`s differing in naming only are still distinct, even though they
+    might have the same structure.
+ */
+unittest
+{
+    Tuple!(int, "x", int, "y") point1;
+    Tuple!(int, int) point2;
+    assert(!is(typeof(point1) == typeof(point2)));
+}
+
+/**
+    Create a copy of a `Tuple` with its fields in reverse order.
+
+    Params:
+        t = The `Tuple` to copy.
+
+    Returns:
+        A copy of `t` with its fields in reverse order.
+ */
+ReverseTupleType!T reverse(T)(T t)
+    if (isTuple!T)
+{
+    import std.meta : Reverse;
+    // @@@BUG@@@ Cannot be an internal function due to forward reference issues.
+
+    // @@@BUG@@@ 9929 Need 'this' when calling template with expanded tuple
+    // return tuple(Reverse!(t.expand));
+
+    typeof(return) result;
+    auto tup = t.expand;
+    result.expand = Reverse!tup;
+    return result;
+}
+
+///
+unittest
+{
+    auto tup = tuple(1, "2");
+    assert(tup.reverse == tuple("2", 1));
+}
+
+/* Get a Tuple type with the reverse specification of Tuple T. */
+private template ReverseTupleType(T)
+    if (isTuple!T)
+{
+    static if (is(T : Tuple!A, A...))
+        alias ReverseTupleType = Tuple!(ReverseTupleSpecs!A);
+}
+
+/* Reverse the Specs of a Tuple. */
+private template ReverseTupleSpecs(T...)
+{
+    static if (T.length > 1)
+    {
+        static if (is(typeof(T[$-1]) : string))
+        {
+            alias ReverseTupleSpecs = AliasSeq!(T[$-2], T[$-1], ReverseTupleSpecs!(T[0 .. $-2]));
+        }
+        else
+        {
+            alias ReverseTupleSpecs = AliasSeq!(T[$-1], ReverseTupleSpecs!(T[0 .. $-1]));
+        }
+    }
+    else
+    {
+        alias ReverseTupleSpecs = T;
+    }
+}
+
+unittest
+{
+    import std.conv;
+    {
+        Tuple!(int, "a", int, "b") nosh;
+        static assert(nosh.length == 2);
+        nosh.a = 5;
+        nosh.b = 6;
+        assert(nosh.a == 5);
+        assert(nosh.b == 6);
+    }
+    {
+        Tuple!(short, double) b;
+        static assert(b.length == 2);
+        b[1] = 5;
+        auto a = Tuple!(int, real)(b);
+        assert(a[0] == 0 && a[1] == 5);
+        a = Tuple!(int, real)(1, 2);
+        assert(a[0] == 1 && a[1] == 2);
+        auto c = Tuple!(int, "a", double, "b")(a);
+        assert(c[0] == 1 && c[1] == 2);
+    }
+    {
+        Tuple!(int, real) nosh;
+        nosh[0] = 5;
+        nosh[1] = 0;
+        assert(nosh[0] == 5 && nosh[1] == 0);
+        assert(nosh.to!string == "Tuple!(int, real)(5, 0)", nosh.to!string);
+        Tuple!(int, int) yessh;
+        nosh = yessh;
+    }
+    {
+        class A {}
+        Tuple!(int, shared A) nosh;
+        nosh[0] = 5;
+        assert(nosh[0] == 5 && nosh[1] is null);
+        assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))");
+    }
+    {
+        Tuple!(int, string) t;
+        t[0] = 10;
+        t[1] = "str";
+        assert(t[0] == 10 && t[1] == "str");
+        assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string);
+    }
+    {
+        Tuple!(int, "a", double, "b") x;
+        static assert(x.a.offsetof == x[0].offsetof);
+        static assert(x.b.offsetof == x[1].offsetof);
+        x.b = 4.5;
+        x.a = 5;
+        assert(x[0] == 5 && x[1] == 4.5);
+        assert(x.a == 5 && x.b == 4.5);
+    }
+    // indexing
+    {
+        Tuple!(int, real) t;
+        static assert(is(typeof(t[0]) == int));
+        static assert(is(typeof(t[1]) == real));
+        int* p0 = &t[0];
+        real* p1 = &t[1];
+        t[0] = 10;
+        t[1] = -200.0L;
+        assert(*p0 == t[0]);
+        assert(*p1 == t[1]);
+    }
+    // slicing
+    {
+        Tuple!(int, "x", real, "y", double, "z", string) t;
+        t[0] = 10;
+        t[1] = 11;
+        t[2] = 12;
+        t[3] = "abc";
+        auto a = t.slice!(0, 3);
+        assert(a.length == 3);
+        assert(a.x == t.x);
+        assert(a.y == t.y);
+        assert(a.z == t.z);
+        auto b = t.slice!(2, 4);
+        assert(b.length == 2);
+        assert(b.z == t.z);
+        assert(b[1] == t[3]);
+    }
+    // nesting
+    {
+        Tuple!(Tuple!(int, real), Tuple!(string, "s")) t;
+        static assert(is(typeof(t[0]) == Tuple!(int, real)));
+        static assert(is(typeof(t[1]) == Tuple!(string, "s")));
+        static assert(is(typeof(t[0][0]) == int));
+        static assert(is(typeof(t[0][1]) == real));
+        static assert(is(typeof(t[1].s) == string));
+        t[0] = tuple(10, 20.0L);
+        t[1].s = "abc";
+        assert(t[0][0] == 10);
+        assert(t[0][1] == 20.0L);
+        assert(t[1].s == "abc");
+    }
+    // non-POD
+    {
+        static struct S
+        {
+            int count;
+            this(this) { ++count; }
+            ~this() { --count; }
+            void opAssign(S rhs) { count = rhs.count; }
+        }
+        Tuple!(S, S) ss;
+        Tuple!(S, S) ssCopy = ss;
+        assert(ssCopy[0].count == 1);
+        assert(ssCopy[1].count == 1);
+        ssCopy[1] = ssCopy[0];
+        assert(ssCopy[1].count == 2);
+    }
+    // bug 2800
+    {
+        static struct R
+        {
+            Tuple!(int, int) _front;
+            @property ref Tuple!(int, int) front() return { return _front;  }
+            @property bool empty() { return _front[0] >= 10; }
+            void popFront() { ++_front[0]; }
+        }
+        foreach (a; R())
+        {
+            static assert(is(typeof(a) == Tuple!(int, int)));
+            assert(0 <= a[0] && a[0] < 10);
+            assert(a[1] == 0);
+        }
+    }
+    // Construction with compatible elements
+    {
+        auto t1 = Tuple!(int, double)(1, 1);
+
+        // 8702
+        auto t8702a = tuple(tuple(1));
+        auto t8702b = Tuple!(Tuple!(int))(Tuple!(int)(1));
+    }
+    // Construction with compatible tuple
+    {
+        Tuple!(int, int) x;
+        x[0] = 10;
+        x[1] = 20;
+        Tuple!(int, "a", double, "b") y = x;
+        assert(y.a == 10);
+        assert(y.b == 20);
+        // incompatible
+        static assert(!__traits(compiles, Tuple!(int, int)(y)));
+    }
+    // 6275
+    {
+        const int x = 1;
+        auto t1 = tuple(x);
+        alias T = Tuple!(const(int));
+        auto t2 = T(1);
+    }
+    // 9431
+    {
+        alias T = Tuple!(int[1][]);
+        auto t = T([[10]]);
+    }
+    // 7666
+    {
+        auto tup = tuple(1, "2");
+        assert(tup.reverse == tuple("2", 1));
+    }
+    {
+        Tuple!(int, "x", string, "y") tup = tuple(1, "2");
+        auto rev = tup.reverse;
+        assert(rev == tuple("2", 1));
+        assert(rev.x == 1 && rev.y == "2");
+    }
+    {
+        Tuple!(wchar, dchar, int, "x", string, "y", char, byte, float) tup;
+        tup = tuple('a', 'b', 3, "4", 'c', cast(byte)0x0D, 0.00);
+        auto rev = tup.reverse;
+        assert(rev == tuple(0.00, cast(byte)0x0D, 'c', "4", 3, 'b', 'a'));
+        assert(rev.x == 3 && rev.y == "4");
+    }
+}
+unittest
+{
+    // opEquals
+    {
+        struct Equ1 { bool opEquals(Equ1) { return true; } }
+        auto  tm1 = tuple(Equ1.init);
+        const tc1 = tuple(Equ1.init);
+        static assert( is(typeof(tm1 == tm1)));
+        static assert(!is(typeof(tm1 == tc1)));
+        static assert(!is(typeof(tc1 == tm1)));
+        static assert(!is(typeof(tc1 == tc1)));
+
+        struct Equ2 { bool opEquals(const Equ2) const { return true; } }
+        auto  tm2 = tuple(Equ2.init);
+        const tc2 = tuple(Equ2.init);
+        static assert( is(typeof(tm2 == tm2)));
+        static assert( is(typeof(tm2 == tc2)));
+        static assert( is(typeof(tc2 == tm2)));
+        static assert( is(typeof(tc2 == tc2)));
+
+        struct Equ3 { bool opEquals(T)(T) { return true; } }
+        auto  tm3 = tuple(Equ3.init);           // bugzilla 8686
+        const tc3 = tuple(Equ3.init);
+        static assert( is(typeof(tm3 == tm3)));
+        static assert( is(typeof(tm3 == tc3)));
+        static assert(!is(typeof(tc3 == tm3)));
+        static assert(!is(typeof(tc3 == tc3)));
+
+        struct Equ4 { bool opEquals(T)(T) const { return true; } }
+        auto  tm4 = tuple(Equ4.init);
+        const tc4 = tuple(Equ4.init);
+        static assert( is(typeof(tm4 == tm4)));
+        static assert( is(typeof(tm4 == tc4)));
+        static assert( is(typeof(tc4 == tm4)));
+        static assert( is(typeof(tc4 == tc4)));
+    }
+    // opCmp
+    {
+        struct Cmp1 { int opCmp(Cmp1) { return 0; } }
+        auto  tm1 = tuple(Cmp1.init);
+        const tc1 = tuple(Cmp1.init);
+        static assert( is(typeof(tm1 < tm1)));
+        static assert(!is(typeof(tm1 < tc1)));
+        static assert(!is(typeof(tc1 < tm1)));
+        static assert(!is(typeof(tc1 < tc1)));
+
+        struct Cmp2 { int opCmp(const Cmp2) const { return 0; } }
+        auto  tm2 = tuple(Cmp2.init);
+        const tc2 = tuple(Cmp2.init);
+        static assert( is(typeof(tm2 < tm2)));
+        static assert( is(typeof(tm2 < tc2)));
+        static assert( is(typeof(tc2 < tm2)));
+        static assert( is(typeof(tc2 < tc2)));
+
+        struct Cmp3 { int opCmp(T)(T) { return 0; } }
+        auto  tm3 = tuple(Cmp3.init);
+        const tc3 = tuple(Cmp3.init);
+        static assert( is(typeof(tm3 < tm3)));
+        static assert( is(typeof(tm3 < tc3)));
+        static assert(!is(typeof(tc3 < tm3)));
+        static assert(!is(typeof(tc3 < tc3)));
+
+        struct Cmp4 { int opCmp(T)(T) const { return 0; } }
+        auto  tm4 = tuple(Cmp4.init);
+        const tc4 = tuple(Cmp4.init);
+        static assert( is(typeof(tm4 < tm4)));
+        static assert( is(typeof(tm4 < tc4)));
+        static assert( is(typeof(tc4 < tm4)));
+        static assert( is(typeof(tc4 < tc4)));
+    }
+    // Bugzilla 14890
+    static void test14890(inout int[] dummy)
+    {
+        alias V = Tuple!(int, int);
+
+                    V mv;
+              const V cv;
+          immutable V iv;
+              inout V wv;   // OK <- NG
+        inout const V wcv;  // OK <- NG
+
+        foreach (v1; AliasSeq!(mv, cv, iv, wv, wcv))
+        foreach (v2; AliasSeq!(mv, cv, iv, wv, wcv))
+        {
+            assert(!(v1 < v2));
+        }
+    }
+    {
+        int[2] ints = [ 1, 2 ];
+        Tuple!(int, int) t = ints;
+        assert(t[0] == 1 && t[1] == 2);
+        Tuple!(long, uint) t2 = ints;
+        assert(t2[0] == 1 && t2[1] == 2);
+    }
+}
+@safe unittest
+{
+    auto t1 = Tuple!(int, "x", string, "y")(1, "a");
+    assert(t1.x == 1);
+    assert(t1.y == "a");
+    void foo(Tuple!(int, string) t2) {}
+    foo(t1);
+
+    Tuple!(int, int)[] arr;
+    arr ~= tuple(10, 20); // OK
+    arr ~= Tuple!(int, "x", int, "y")(10, 20); // NG -> OK
+
+    static assert(is(typeof(Tuple!(int, "x", string, "y").tupleof) ==
+                     typeof(Tuple!(int,      string     ).tupleof)));
+}
+unittest
+{
+    // Bugzilla 10686
+    immutable Tuple!(int) t1;
+    auto r1 = t1[0]; // OK
+    immutable Tuple!(int, "x") t2;
+    auto r2 = t2[0]; // error
+}
+unittest
+{
+    import std.exception : assertCTFEable;
+
+    // Bugzilla 10218
+    assertCTFEable!(
+    {
+        auto t = tuple(1);
+        t = tuple(2);   // assignment
+    });
+}
+unittest
+{
+    class Foo{}
+    Tuple!(immutable(Foo)[]) a;
+}
+
+unittest
+{
+    //Test non-assignable
+    static struct S
+    {
+        int* p;
+    }
+    alias IS = immutable S;
+    static assert(!isAssignable!IS);
+
+    auto s = IS.init;
+
+    alias TIS = Tuple!IS;
+    TIS a = tuple(s);
+    TIS b = a;
+
+    alias TISIS = Tuple!(IS, IS);
+    TISIS d = tuple(s, s);
+    IS[2] ss;
+    TISIS e = TISIS(ss);
+}
+
+// Bugzilla #9819
+unittest
+{
+    alias T = Tuple!(int, "x", double, "foo");
+    static assert(T.fieldNames[0] == "x");
+    static assert(T.fieldNames[1] == "foo");
+
+    alias Fields = Tuple!(int, "id", string, float);
+    static assert(Fields.fieldNames == AliasSeq!("id", "", ""));
+}
+
+// Bugzilla 13837
+unittest
+{
+    // New behaviour, named arguments.
+    static assert(is(
+        typeof(tuple!("x")(1)) == Tuple!(int, "x")));
+    static assert(is(
+        typeof(tuple!("x")(1.0)) == Tuple!(double, "x")));
+    static assert(is(
+        typeof(tuple!("x")("foo")) == Tuple!(string, "x")));
+    static assert(is(
+        typeof(tuple!("x", "y")(1, 2.0)) == Tuple!(int, "x", double, "y")));
+
+    auto a = tuple!("a", "b", "c")("1", 2, 3.0f);
+    static assert(is(typeof(a.a) == string));
+    static assert(is(typeof(a.b) == int));
+    static assert(is(typeof(a.c) == float));
+
+    // Old behaviour, but with explicit type parameters.
+    static assert(is(
+        typeof(tuple!(int, double)(1, 2.0)) == Tuple!(int, double)));
+    static assert(is(
+        typeof(tuple!(const int)(1)) == Tuple!(const int)));
+    static assert(is(
+        typeof(tuple()) == Tuple!()));
+
+    // Nonsensical behaviour
+    static assert(!__traits(compiles, tuple!(1)(2)));
+    static assert(!__traits(compiles, tuple!("x")(1, 2)));
+    static assert(!__traits(compiles, tuple!("x", "y")(1)));
+    static assert(!__traits(compiles, tuple!("x")()));
+    static assert(!__traits(compiles, tuple!("x", int)(2)));
+}
+
+unittest
+{
+    class C {}
+    Tuple!(Rebindable!(const C)) a;
+    Tuple!(const C) b;
+    a = b;
+}
+
+@nogc unittest
+{
+    alias T = Tuple!(string, "s");
+    T x;
+    x = T.init;
+}
+
+@safe unittest
+{
+    import std.format : format, FormatException;
+    import std.exception : assertThrown;
+
+    // enum tupStr = tuple(1, 1.0).toString; // toString is *impure*.
+    //static assert (tupStr == `Tuple!(int, double)(1, 1)`);
+
+    Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ];
+
+    // Default format
+    assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`);
+
+    // One Format for each individual component
+    assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10))         == `0x1 v 1.0000 w 0xa`);
+    assert(format(  "%#x v %.4f w %#x"  , tuple(1, 1.0, 10).expand)  == `0x1 v 1.0000 w 0xa`);
+
+    // One Format for all components
+    assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`);
+
+    // Array of Tuples
+    assert(format("%(%(f(%d) = %.1f%);  %)", tupList) == `f(1) = 1.0;  f(2) = 4.0;  f(3) = 9.0`);
+
+
+    // Error: %( %) missing.
+    assertThrown!FormatException(
+        format("%d, %f", tuple(1, 2.0)) == `1, 2.0`
+    );
+
+    // Error: %( %| %) missing.
+    assertThrown!FormatException(
+        format("%d", tuple(1, 2)) == `1, 2`
+    );
+
+    // Error: %d inadequate for double
+    assertThrown!FormatException(
+        format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0`
+    );
+}
+
+/**
+    Constructs a $(D Tuple) object instantiated and initialized according to
+    the given arguments.
+
+    Params:
+        Names = A list of strings naming each successive field of the `Tuple`.
+                Each name matches up with the corresponding field given by `Args`.
+                A name does not have to be provided for every field, but as
+                the names must proceed in order, it is not possible to skip
+                one field and name the next after it.
+
+        args = Values to initialize the `Tuple` with. The `Tuple`'s type will
+               be inferred from the types of the values given.
+
+    Returns:
+        A new `Tuple` with its type inferred from the arguments given.
+*/
+template tuple(Names...)
+{
+    auto tuple(Args...)(Args args)
+    {
+        static if (Names.length == 0)
+        {
+            // No specified names, just infer types from Args...
+            return Tuple!Args(args);
+        }
+        else static if (!is(typeof(Names[0]) : string))
+        {
+            // Names[0] isn't a string, must be explicit types.
+            return Tuple!Names(args);
+        }
+        else
+        {
+            // Names[0] is a string, so must be specifying names.
+            static assert(Names.length == Args.length,
+                "Insufficient number of names given.");
+
+            // Interleave(a, b).and(c, d) == (a, c, b, d)
+            // This is to get the interleaving of types and names for Tuple
+            // e.g. Tuple!(int, "x", string, "y")
+            template Interleave(A...)
+            {
+                template and(B...) if (B.length == 1)
+                {
+                    alias AliasSeq!(A[0], B[0]) and;
+                }
+
+                template and(B...) if (B.length != 1)
+                {
+                    alias AliasSeq!(A[0], B[0],
+                        Interleave!(A[1..$]).and!(B[1..$])) and;
+                }
+            }
+            return Tuple!(Interleave!(Args).and!(Names))(args);
+        }
+    }
+}
+
+///
+unittest
+{
+    auto value = tuple(5, 6.7, "hello");
+    assert(value[0] == 5);
+    assert(value[1] == 6.7);
+    assert(value[2] == "hello");
+
+    // Field names can be provided.
+    auto entry = tuple!("index", "value")(4, "Hello");
+    assert(entry.index == 4);
+    assert(entry.value == "Hello");
+}
+
+/**
+    Returns $(D true) if and only if $(D T) is an instance of $(D std.typecons.Tuple).
+
+    Params:
+        T = The type to check.
+
+    Returns:
+        true if `T` is a `Tuple` type, false otherwise.
+ */
+enum isTuple(T) = __traits(compiles,
+                           {
+                               void f(Specs...)(Tuple!Specs tup) {}
+                               f(T.init);
+                           } );
+
+///
+unittest
+{
+    static assert(isTuple!(Tuple!()));
+    static assert(isTuple!(Tuple!(int)));
+    static assert(isTuple!(Tuple!(int, real, string)));
+    static assert(isTuple!(Tuple!(int, "x", real, "y")));
+    static assert(isTuple!(Tuple!(int, Tuple!(real), string)));
+}
+
+unittest
+{
+    static assert(isTuple!(const Tuple!(int)));
+    static assert(isTuple!(immutable Tuple!(int)));
+
+    static assert(!isTuple!(int));
+    static assert(!isTuple!(const int));
+
+    struct S {}
+    static assert(!isTuple!(S));
+}
+
+// used by both Rebindable and UnqualRef
+private mixin template RebindableCommon(T, U, alias This)
+    if (is(T == class) || is(T == interface) || isAssociativeArray!T)
+{
+    private union
+    {
+        T original;
+        U stripped;
+    }
+
+    @trusted pure nothrow @nogc
+    {
+        void opAssign(T another)
+        {
+            stripped = cast(U) another;
+        }
+
+        void opAssign(typeof(this) another)
+        {
+            stripped = another.stripped;
+        }
+
+        static if (is(T == const U) && is(T == const shared U))
+        {
+            // safely assign immutable to const / const shared
+            void opAssign(This!(immutable U) another)
+            {
+                stripped = another.stripped;
+            }
+        }
+
+        this(T initializer)
+        {
+            opAssign(initializer);
+        }
+
+        @property ref inout(T) get() inout
+        {
+            return original;
+        }
+    }
+
+    alias get this;
+}
+
+/**
+$(D Rebindable!(T)) is a simple, efficient wrapper that behaves just
+like an object of type $(D T), except that you can reassign it to
+refer to another object. For completeness, $(D Rebindable!(T)) aliases
+itself away to $(D T) if $(D T) is a non-const object type.
+
+You may want to use $(D Rebindable) when you want to have mutable
+storage referring to $(D const) objects, for example an array of
+references that must be sorted in place. $(D Rebindable) does not
+break the soundness of D's type system and does not incur any of the
+risks usually associated with $(D cast).
+
+Params:
+    T = An object, interface, array slice type, or associative array type.
+ */
+template Rebindable(T)
+    if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T)
+{
+    static if (is(T == const U, U) || is(T == immutable U, U))
+    {
+        static if (isDynamicArray!T)
+        {
+            import std.range.primitives : ElementEncodingType;
+            alias Rebindable = const(ElementEncodingType!T)[];
+        }
+        else
+        {
+            struct Rebindable
+            {
+                mixin RebindableCommon!(T, U, Rebindable);
+            }
+        }
+    }
+    else
+    {
+        alias Rebindable = T;
+    }
+}
+
+///Regular $(D const) object references cannot be reassigned.
+unittest
+{
+    class Widget { int x; int y() const { return x; } }
+    const a = new Widget;
+    // Fine
+    a.y();
+    // error! can't modify const a
+    // a.x = 5;
+    // error! can't modify const a
+    // a = new Widget;
+}
+
+/**
+    However, $(D Rebindable!(Widget)) does allow reassignment,
+    while otherwise behaving exactly like a $(D const Widget).
+ */
+unittest
+{
+    class Widget { int x; int y() const { return x; } }
+    auto a = Rebindable!(const Widget)(new Widget);
+    // Fine
+    a.y();
+    // error! can't modify const a
+    // a.x = 5;
+    // Fine
+    a = new Widget;
+}
+
+/**
+Convenience function for creating a $(D Rebindable) using automatic type
+inference.
+
+Params:
+    obj = A reference to an object, interface, associative array, or an array slice
+          to initialize the `Rebindable` with.
+
+Returns:
+    A newly constructed `Rebindable` initialized with the given reference.
+*/
+Rebindable!T rebindable(T)(T obj)
+    if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T)
+{
+    typeof(return) ret;
+    ret = obj;
+    return ret;
+}
+
+/**
+This function simply returns the $(D Rebindable) object passed in.  It's useful
+in generic programming cases when a given object may be either a regular
+$(D class) or a $(D Rebindable).
+
+Params:
+    obj = An instance of Rebindable!T.
+
+Returns:
+    `obj` without any modification.
+*/
+Rebindable!T rebindable(T)(Rebindable!T obj)
+{
+    return obj;
+}
+
+unittest
+{
+    interface CI { int foo() const; }
+    class C : CI {
+      int foo() const { return 42; }
+      @property int bar() const { return 23; }
+    }
+    Rebindable!(C) obj0;
+    static assert(is(typeof(obj0) == C));
+
+    Rebindable!(const(C)) obj1;
+    static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof);
+    static assert(is(typeof(obj1.stripped) == C));
+    obj1 = new C;
+    assert(obj1.get !is null);
+    obj1 = new const(C);
+    assert(obj1.get !is null);
+
+    Rebindable!(immutable(C)) obj2;
+    static assert(is(typeof(obj2.get) == immutable(C)));
+    static assert(is(typeof(obj2.stripped) == C));
+    obj2 = new immutable(C);
+    assert(obj1.get !is null);
+
+    // test opDot
+    assert(obj2.foo() == 42);
+    assert(obj2.bar == 23);
+
+    interface I { final int foo() const { return 42; } }
+    Rebindable!(I) obj3;
+    static assert(is(typeof(obj3) == I));
+
+    Rebindable!(const I) obj4;
+    static assert(is(typeof(obj4.get) == const I));
+    static assert(is(typeof(obj4.stripped) == I));
+    static assert(is(typeof(obj4.foo()) == int));
+    obj4 = new class I {};
+
+    Rebindable!(immutable C) obj5i;
+    Rebindable!(const C) obj5c;
+    obj5c = obj5c;
+    obj5c = obj5i;
+    obj5i = obj5i;
+    static assert(!__traits(compiles, obj5i = obj5c));
+
+    // Test the convenience functions.
+    auto obj5convenience = rebindable(obj5i);
+    assert(obj5convenience is obj5i);
+
+    auto obj6 = rebindable(new immutable(C));
+    static assert(is(typeof(obj6) == Rebindable!(immutable C)));
+    assert(obj6.foo() == 42);
+
+    auto obj7 = rebindable(new C);
+    CI interface1 = obj7;
+    auto interfaceRebind1 = rebindable(interface1);
+    assert(interfaceRebind1.foo() == 42);
+
+    const interface2 = interface1;
+    auto interfaceRebind2 = rebindable(interface2);
+    assert(interfaceRebind2.foo() == 42);
+
+    auto arr = [1,2,3,4,5];
+    const arrConst = arr;
+    assert(rebindable(arr) == arr);
+    assert(rebindable(arrConst) == arr);
+
+    // Issue 7654
+    immutable(char[]) s7654;
+    Rebindable!(typeof(s7654)) r7654 = s7654;
+
+    foreach (T; AliasSeq!(char, wchar, char, int))
+    {
+        static assert(is(Rebindable!(immutable(T[])) == immutable(T)[]));
+        static assert(is(Rebindable!(const(T[])) == const(T)[]));
+        static assert(is(Rebindable!(T[]) == T[]));
+    }
+
+    // Issue 12046
+    static assert(!__traits(compiles, Rebindable!(int[1])));
+    static assert(!__traits(compiles, Rebindable!(const int[1])));
+
+    // Pull request 3341
+    Rebindable!(immutable int[int]) pr3341 = [123:345];
+    assert(pr3341[123] == 345);
+    immutable int[int] pr3341_aa = [321:543];
+    pr3341 = pr3341_aa;
+    assert(pr3341[321] == 543);
+    assert(rebindable(pr3341_aa)[321] == 543);
+}
+
+/**
+    Similar to $(D Rebindable!(T)) but strips all qualifiers from the reference as
+    opposed to just constness / immutability. Primary intended use case is with
+    shared (having thread-local reference to shared class data)
+
+    Params:
+        T = A class or interface type.
+ */
+template UnqualRef(T)
+    if (is(T == class) || is(T == interface))
+{
+    static if (is(T == const U, U)
+        || is(T == immutable U, U)
+        || is(T == shared U, U)
+        || is(T == const shared U, U))
+    {
+        struct UnqualRef
+        {
+            mixin RebindableCommon!(T, U, UnqualRef);
+        }
+    }
+    else
+    {
+        alias UnqualRef = T;
+    }
+}
+
+///
+unittest
+{
+    class Data {}
+
+    static shared(Data) a;
+    static UnqualRef!(shared Data) b;
+
+    import core.thread;
+
+    auto thread = new core.thread.Thread({
+        a = new shared Data();
+        b = new shared Data();
+    });
+
+    thread.start();
+    thread.join();
+
+    assert(a !is null);
+    assert(b is null);
+}
+
+unittest
+{
+    class C { }
+    alias T = UnqualRef!(const shared C);
+    static assert (is(typeof(T.stripped) == C));
+}
+
+
+
+/**
+  Order the provided members to minimize size while preserving alignment.
+  Alignment is not always optimal for 80-bit reals, nor for structs declared
+  as align(1).
+
+  Params:
+      E = A list of the types to be aligned, representing fields
+          of an aggregate such as a `struct` or `class`.
+
+      names = The names of the fields that are to be aligned.
+
+  Returns:
+      A string to be mixed in to an aggregate, such as a `struct` or `class`.
+*/
+string alignForSize(E...)(const char[][] names...)
+{
+    // Sort all of the members by .alignof.
+    // BUG: Alignment is not always optimal for align(1) structs
+    // or 80-bit reals or 64-bit primitives on x86.
+    // TRICK: Use the fact that .alignof is always a power of 2,
+    // and maximum 16 on extant systems. Thus, we can perform
+    // a very limited radix sort.
+    // Contains the members with .alignof = 64,32,16,8,4,2,1
+
+    assert(E.length == names.length,
+        "alignForSize: There should be as many member names as the types");
+
+    string[7] declaration = ["", "", "", "", "", "", ""];
+
+    foreach (i, T; E)
+    {
+        auto a = T.alignof;
+        auto k = a>=64? 0 : a>=32? 1 : a>=16? 2 : a>=8? 3 : a>=4? 4 : a>=2? 5 : 6;
+        declaration[k] ~= T.stringof ~ " " ~ names[i] ~ ";\n";
+    }
+
+    auto s = "";
+    foreach (decl; declaration)
+        s ~= decl;
+    return s;
+}
+
+///
+unittest
+{
+    struct Banner {
+        mixin(alignForSize!(byte[6], double)(["name", "height"]));
+    }
+}
+
+unittest
+{
+    enum x = alignForSize!(int[], char[3], short, double[5])("x", "y","z", "w");
+    struct Foo { int x; }
+    enum y = alignForSize!(ubyte, Foo, cdouble)("x", "y", "z");
+
+    enum passNormalX = x == "double[5] w;\nint[] x;\nshort z;\nchar[3] y;\n";
+    enum passNormalY = y == "cdouble z;\nFoo y;\nubyte x;\n";
+
+    enum passAbnormalX = x == "int[] x;\ndouble[5] w;\nshort z;\nchar[3] y;\n";
+    enum passAbnormalY = y == "Foo y;\ncdouble z;\nubyte x;\n";
+    // ^ blame http://d.puremagic.com/issues/show_bug.cgi?id=231
+
+    static assert(passNormalX || passAbnormalX && double.alignof <= (int[]).alignof);
+    static assert(passNormalY || passAbnormalY && double.alignof <= int.alignof);
+}
+
+// Issue 12914
+unittest
+{
+    immutable string[] fieldNames = ["x", "y"];
+    struct S
+    {
+        mixin(alignForSize!(byte, int)(fieldNames));
+    }
+}
+
+/**
+Defines a value paired with a distinctive "null" state that denotes
+the absence of a value. If default constructed, a $(D
+Nullable!T) object starts in the null state. Assigning it renders it
+non-null. Calling $(D nullify) can nullify it again.
+
+Practically $(D Nullable!T) stores a $(D T) and a $(D bool).
+ */
+struct Nullable(T)
+{
+    private T _value;
+    private bool _isNull = true;
+
+/**
+Constructor initializing $(D this) with $(D value).
+
+Params:
+    value = The value to initialize this `Nullable` with.
+ */
+    this(inout T value) inout
+    {
+        _value = value;
+        _isNull = false;
+    }
+
+    template toString()
+    {
+        import std.format : FormatSpec, formatValue;
+        // Needs to be a template because of DMD @@BUG@@ 13737.
+        void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
+        {
+            if (isNull)
+            {
+                sink.formatValue("Nullable.null", fmt);
+            }
+            else
+            {
+                sink.formatValue(_value, fmt);
+            }
+        }
+
+        // Issue 14940
+        void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt)
+        {
+            if (isNull)
+            {
+                sink.formatValue("Nullable.null", fmt);
+            }
+            else
+            {
+                sink.formatValue(_value, fmt);
+            }
+        }
+    }
+
+/**
+Check if `this` is in the null state.
+
+Returns:
+    true $(B iff) `this` is in the null state, otherwise false.
+ */
+    @property bool isNull() const @safe pure nothrow
+    {
+        return _isNull;
+    }
+
+///
+unittest
+{
+    Nullable!int ni;
+    assert(ni.isNull);
+
+    ni = 0;
+    assert(!ni.isNull);
+}
+
+// Issue 14940
+@safe unittest
+{
+    import std.array : appender;
+    import std.format : formattedWrite;
+
+    auto app = appender!string();
+    Nullable!int a = 1;
+    formattedWrite(app, "%s", a);
+    assert(app.data == "1");
+}
+
+/**
+Forces $(D this) to the null state.
+ */
+    void nullify()()
+    {
+        .destroy(_value);
+        _isNull = true;
+    }
+
+///
+unittest
+{
+    Nullable!int ni = 0;
+    assert(!ni.isNull);
+
+    ni.nullify();
+    assert(ni.isNull);
+}
+
+/**
+Assigns $(D value) to the internally-held state. If the assignment
+succeeds, $(D this) becomes non-null.
+
+Params:
+    value = A value of type `T` to assign to this `Nullable`.
+ */
+    void opAssign()(T value)
+    {
+        _value = value;
+        _isNull = false;
+    }
+
+/**
+    If this `Nullable` wraps a type that already has a null value
+    (such as a pointer), then assigning the null value to this
+    `Nullable` is no different than assigning any other value of
+    type `T`, and the resulting code will look very strange. It
+    is strongly recommended that this be avoided by instead using
+    the version of `Nullable` that takes an additional `nullValue`
+    template argument.
+ */
+unittest
+{
+    //Passes
+    Nullable!(int*) npi;
+    assert(npi.isNull);
+
+    //Passes?!
+    npi = null;
+    assert(!npi.isNull);
+}
+
+/**
+Gets the value. $(D this) must not be in the null state.
+This function is also called for the implicit conversion to $(D T).
+
+Returns:
+    The value held internally by this `Nullable`.
+ */
+    @property ref inout(T) get() inout @safe pure nothrow
+    {
+        enum message = "Called `get' on null Nullable!" ~ T.stringof ~ ".";
+        assert(!isNull, message);
+        return _value;
+    }
+
+///
+unittest
+{
+    import std.exception: assertThrown, assertNotThrown;
+
+    Nullable!int ni;
+    //`get` is implicitly called. Will throw
+    //an AssertError in non-release mode
+    assertThrown!Throwable(ni == 0);
+
+    ni = 0;
+    assertNotThrown!Throwable(ni == 0);
+}
+
+/**
+Implicitly converts to $(D T).
+$(D this) must not be in the null state.
+ */
+    alias get this;
+}
+
+///
+unittest
+{
+    struct CustomerRecord
+    {
+        string name;
+        string address;
+        int customerNum;
+    }
+
+    Nullable!CustomerRecord getByName(string name)
+    {
+        //A bunch of hairy stuff
+
+        return Nullable!CustomerRecord.init;
+    }
+
+    auto queryResult = getByName("Doe, John");
+    if (!queryResult.isNull)
+    {
+        //Process Mr. Doe's customer record
+        auto address = queryResult.address;
+        auto customerNum = queryResult.customerNum;
+
+        //Do some things with this customer's info
+    }
+    else
+    {
+        //Add the customer to the database
+    }
+}
+
+unittest
+{
+    import std.exception : assertThrown;
+
+    Nullable!int a;
+    assert(a.isNull);
+    assertThrown!Throwable(a.get);
+    a = 5;
+    assert(!a.isNull);
+    assert(a == 5);
+    assert(a != 3);
+    assert(a.get != 3);
+    a.nullify();
+    assert(a.isNull);
+    a = 3;
+    assert(a == 3);
+    a *= 6;
+    assert(a == 18);
+    a = a;
+    assert(a == 18);
+    a.nullify();
+    assertThrown!Throwable(a += 2);
+}
+unittest
+{
+    auto k = Nullable!int(74);
+    assert(k == 74);
+    k.nullify();
+    assert(k.isNull);
+}
+unittest
+{
+    static int f(in Nullable!int x) {
+        return x.isNull ? 42 : x.get;
+    }
+    Nullable!int a;
+    assert(f(a) == 42);
+    a = 8;
+    assert(f(a) == 8);
+    a.nullify();
+    assert(f(a) == 42);
+}
+unittest
+{
+    import std.exception : assertThrown;
+
+    static struct S { int x; }
+    Nullable!S s;
+    assert(s.isNull);
+    s = S(6);
+    assert(s == S(6));
+    assert(s != S(0));
+    assert(s.get != S(0));
+    s.x = 9190;
+    assert(s.x == 9190);
+    s.nullify();
+    assertThrown!Throwable(s.x = 9441);
+}
+unittest
+{
+    // Ensure Nullable can be used in pure/nothrow/@safe environment.
+    function() @safe pure nothrow
+    {
+        Nullable!int n;
+        assert(n.isNull);
+        n = 4;
+        assert(!n.isNull);
+        assert(n == 4);
+        n.nullify();
+        assert(n.isNull);
+    }();
+}
+unittest
+{
+    // Ensure Nullable can be used when the value is not pure/nothrow/@safe
+    static struct S
+    {
+        int x;
+        this(this) @system {}
+    }
+
+    Nullable!S s;
+    assert(s.isNull);
+    s = S(5);
+    assert(!s.isNull);
+    assert(s.x == 5);
+    s.nullify();
+    assert(s.isNull);
+}
+unittest
+{
+    // Bugzilla 9404
+    alias N = Nullable!int;
+
+    void foo(N a)
+    {
+        N b;
+        b = a; // `N b = a;` works fine
+    }
+    N n;
+    foo(n);
+}
+unittest
+{
+    //Check nullable immutable is constructable
+    {
+        auto a1 = Nullable!(immutable int)();
+        auto a2 = Nullable!(immutable int)(1);
+        auto i = a2.get;
+    }
+    //Check immutable nullable is constructable
+    {
+        auto a1 = immutable (Nullable!int)();
+        auto a2 = immutable (Nullable!int)(1);
+        auto i = a2.get;
+    }
+}
+unittest
+{
+    alias NInt   = Nullable!int;
+
+    //Construct tests
+    {
+        //from other Nullable null
+        NInt a1;
+        NInt b1 = a1;
+        assert(b1.isNull);
+
+        //from other Nullable non-null
+        NInt a2 = NInt(1);
+        NInt b2 = a2;
+        assert(b2 == 1);
+
+        //Construct from similar nullable
+        auto a3 = immutable(NInt)();
+        NInt b3 = a3;
+        assert(b3.isNull);
+    }
+
+    //Assign tests
+    {
+        //from other Nullable null
+        NInt a1;
+        NInt b1;
+        b1 = a1;
+        assert(b1.isNull);
+
+        //from other Nullable non-null
+        NInt a2 = NInt(1);
+        NInt b2;
+        b2 = a2;
+        assert(b2 == 1);
+
+        //Construct from similar nullable
+        auto a3 = immutable(NInt)();
+        NInt b3 = a3;
+        b3 = a3;
+        assert(b3.isNull);
+    }
+}
+unittest
+{
+    //Check nullable is nicelly embedable in a struct
+    static struct S1
+    {
+        Nullable!int ni;
+    }
+    static struct S2 //inspired from 9404
+    {
+        Nullable!int ni;
+        this(S2 other)
+        {
+            ni = other.ni;
+        }
+        void opAssign(S2 other)
+        {
+            ni = other.ni;
+        }
+    }
+    foreach (S; AliasSeq!(S1, S2))
+    {
+        S a;
+        S b = a;
+        S c;
+        c = a;
+    }
+}
+unittest
+{
+    // Bugzilla 10268
+    import std.json;
+    JSONValue value = null;
+    auto na = Nullable!JSONValue(value);
+
+    struct S1 { int val; }
+    struct S2 { int* val; }
+    struct S3 { immutable int* val; }
+
+    {
+        auto sm = S1(1);
+        immutable si = immutable S1(1);
+        auto x1 =           Nullable!S1(sm);
+        auto x2 = immutable Nullable!S1(sm);
+        auto x3 =           Nullable!S1(si);
+        auto x4 = immutable Nullable!S1(si);
+        assert(x1.val == 1);
+        assert(x2.val == 1);
+        assert(x3.val == 1);
+        assert(x4.val == 1);
+    }
+
+    auto nm = 10;
+    immutable ni = 10;
+
+    {
+        auto sm = S2(&nm);
+        immutable si = immutable S2(&ni);
+        auto x1 =           Nullable!S2(sm);
+        static assert(!__traits(compiles, { auto x2 = immutable Nullable!S2(sm); }));
+        static assert(!__traits(compiles, { auto x3 =           Nullable!S2(si); }));
+        auto x4 = immutable Nullable!S2(si);
+        assert(*x1.val == 10);
+        assert(*x4.val == 10);
+    }
+
+    {
+        auto sm = S3(&ni);
+        immutable si = immutable S3(&ni);
+        auto x1 =           Nullable!S3(sm);
+        auto x2 = immutable Nullable!S3(sm);
+        auto x3 =           Nullable!S3(si);
+        auto x4 = immutable Nullable!S3(si);
+        assert(*x1.val == 10);
+        assert(*x2.val == 10);
+        assert(*x3.val == 10);
+        assert(*x4.val == 10);
+    }
+}
+unittest
+{
+    // Bugzila 10357
+    import std.datetime;
+    Nullable!SysTime time = SysTime(0);
+}
+unittest
+{
+    import std.conv: to;
+    import std.array;
+
+    // Bugzilla 10915
+    Appender!string buffer;
+
+    Nullable!int ni;
+    assert(ni.to!string() == "Nullable.null");
+
+    struct Test { string s; }
+    alias NullableTest = Nullable!Test;
+
+    NullableTest nt = Test("test");
+    assert(nt.to!string() == `Test("test")`);
+
+    NullableTest ntn = Test("null");
+    assert(ntn.to!string() == `Test("null")`);
+
+    class TestToString
+    {
+        double d;
+
+        this (double d)
+        {
+            this.d = d;
+        }
+
+        override string toString()
+        {
+            return d.to!string();
+        }
+    }
+    Nullable!TestToString ntts = new TestToString(2.5);
+    assert(ntts.to!string() == "2.5");
+}
+
+/**
+Just like $(D Nullable!T), except that the null state is defined as a
+particular value. For example, $(D Nullable!(uint, uint.max)) is an
+$(D uint) that sets aside the value $(D uint.max) to denote a null
+state. $(D Nullable!(T, nullValue)) is more storage-efficient than $(D
+Nullable!T) because it does not need to store an extra $(D bool).
+
+Params:
+    T = The wrapped type for which Nullable provides a null value.
+
+    nullValue = The null value which denotes the null state of this
+                `Nullable`. Must be of type `T`.
+ */
+struct Nullable(T, T nullValue)
+{
+    private T _value = nullValue;
+
+/**
+Constructor initializing $(D this) with $(D value).
+
+Params:
+    value = The value to initialize this `Nullable` with.
+ */
+    this(T value)
+    {
+        _value = value;
+    }
+
+    template toString()
+    {
+        import std.format : FormatSpec, formatValue;
+        // Needs to be a template because of DMD @@BUG@@ 13737.
+        void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
+        {
+            if (isNull)
+            {
+                sink.formatValue("Nullable.null", fmt);
+            }
+            else
+            {
+                sink.formatValue(_value, fmt);
+            }
+        }
+    }
+
+/**
+Check if `this` is in the null state.
+
+Returns:
+    true $(B iff) `this` is in the null state, otherwise false.
+ */
+    @property bool isNull() const
+    {
+        //Need to use 'is' if T is a nullable type and
+        //nullValue is null, or it's a compiler error
+        static if (is(CommonType!(T, typeof(null)) == T) && nullValue is null)
+        {
+            return _value is nullValue;
+        }
+        else
+        {
+            return _value == nullValue;
+        }
+    }
+
+///
+unittest
+{
+    Nullable!(int, -1) ni;
+    //Initialized to "null" state
+    assert(ni.isNull);
+
+    ni = 0;
+    assert(!ni.isNull);
+}
+
+/**
+Forces $(D this) to the null state.
+ */
+    void nullify()()
+    {
+        _value = nullValue;
+    }
+
+///
+unittest
+{
+    Nullable!(int, -1) ni = 0;
+    assert(!ni.isNull);
+
+    ni = -1;
+    assert(ni.isNull);
+}
+
+/**
+Assigns $(D value) to the internally-held state. If the assignment
+succeeds, $(D this) becomes non-null. No null checks are made. Note
+that the assignment may leave $(D this) in the null state.
+
+Params:
+    value = A value of type `T` to assign to this `Nullable`.
+            If it is `nullvalue`, then the internal state of
+            this `Nullable` will be set to null.
+ */
+    void opAssign()(T value)
+    {
+        _value = value;
+    }
+
+/**
+    If this `Nullable` wraps a type that already has a null value
+    (such as a pointer), and that null value is not given for
+    `nullValue`, then assigning the null value to this `Nullable`
+    is no different than assigning any other value of type `T`,
+    and the resulting code will look very strange. It is strongly
+    recommended that this be avoided by using `T`'s "built in"
+    null value for `nullValue`.
+ */
+unittest
+{
+    //Passes
+    enum nullVal = cast(int*)0xCAFEBABE;
+    Nullable!(int*, nullVal) npi;
+    assert(npi.isNull);
+
+    //Passes?!
+    npi = null;
+    assert(!npi.isNull);
+}
+
+/**
+Gets the value. $(D this) must not be in the null state.
+This function is also called for the implicit conversion to $(D T).
+
+Returns:
+    The value held internally by this `Nullable`.
+ */
+    @property ref inout(T) get() inout
+    {
+        //@@@6169@@@: We avoid any call that might evaluate nullValue's %s,
+        //Because it might messup get's purity and safety inference.
+        enum message = "Called `get' on null Nullable!(" ~ T.stringof ~ ",nullValue).";
+        assert(!isNull, message);
+        return _value;
+    }
+
+///
+unittest
+{
+    import std.exception: assertThrown, assertNotThrown;
+
+    Nullable!(int, -1) ni;
+    //`get` is implicitly called. Will throw
+    //an error in non-release mode
+    assertThrown!Throwable(ni == 0);
+
+    ni = 0;
+    assertNotThrown!Throwable(ni == 0);
+}
+
+/**
+Implicitly converts to $(D T).
+$(D this) must not be in the null state.
+ */
+    alias get this;
+}
+
+///
+unittest
+{
+    Nullable!(size_t, size_t.max) indexOf(string[] haystack, string needle)
+    {
+        //Find the needle, returning -1 if not found
+
+        return Nullable!(size_t, size_t.max).init;
+    }
+
+    void sendLunchInvite(string name)
+    {
+    }
+
+    //It's safer than C...
+    auto coworkers = ["Jane", "Jim", "Marry", "Fred"];
+    auto pos = indexOf(coworkers, "Bob");
+    if (!pos.isNull)
+    {
+        //Send Bob an invitation to lunch
+        sendLunchInvite(coworkers[pos]);
+    }
+    else
+    {
+        //Bob not found; report the error
+    }
+
+    //And there's no overhead
+    static assert(Nullable!(size_t, size_t.max).sizeof == size_t.sizeof);
+}
+unittest
+{
+    import std.exception : assertThrown;
+
+    Nullable!(int, int.min) a;
+    assert(a.isNull);
+    assertThrown!Throwable(a.get);
+    a = 5;
+    assert(!a.isNull);
+    assert(a == 5);
+    static assert(a.sizeof == int.sizeof);
+}
+unittest
+{
+    auto a = Nullable!(int, int.min)(8);
+    assert(a == 8);
+    a.nullify();
+    assert(a.isNull);
+}
+unittest
+{
+    static int f(in Nullable!(int, int.min) x) {
+        return x.isNull ? 42 : x.get;
+    }
+    Nullable!(int, int.min) a;
+    assert(f(a) == 42);
+    a = 8;
+    assert(f(a) == 8);
+    a.nullify();
+    assert(f(a) == 42);
+}
+unittest
+{
+    // Ensure Nullable can be used in pure/nothrow/@safe environment.
+    function() @safe pure nothrow
+    {
+        Nullable!(int, int.min) n;
+        assert(n.isNull);
+        n = 4;
+        assert(!n.isNull);
+        assert(n == 4);
+        n.nullify();
+        assert(n.isNull);
+    }();
+}
+unittest
+{
+    // Ensure Nullable can be used when the value is not pure/nothrow/@safe
+    static struct S
+    {
+        int x;
+        bool opEquals(const S s) const @system { return s.x == x; }
+    }
+
+    Nullable!(S, S(711)) s;
+    assert(s.isNull);
+    s = S(5);
+    assert(!s.isNull);
+    assert(s.x == 5);
+    s.nullify();
+    assert(s.isNull);
+}
+unittest
+{
+    //Check nullable is nicelly embedable in a struct
+    static struct S1
+    {
+        Nullable!(int, 0) ni;
+    }
+    static struct S2 //inspired from 9404
+    {
+        Nullable!(int, 0) ni;
+        this(S2 other)
+        {
+            ni = other.ni;
+        }
+        void opAssign(S2 other)
+        {
+            ni = other.ni;
+        }
+    }
+    foreach (S; AliasSeq!(S1, S2))
+    {
+        S a;
+        S b = a;
+        S c;
+        c = a;
+    }
+}
+unittest
+{
+    import std.conv: to;
+
+    // Bugzilla 10915
+    Nullable!(int, 1) ni = 1;
+    assert(ni.to!string() == "Nullable.null");
+
+    struct Test { string s; }
+    alias NullableTest = Nullable!(Test, Test("null"));
+
+    NullableTest nt = Test("test");
+    assert(nt.to!string() == `Test("test")`);
+
+    NullableTest ntn = Test("null");
+    assert(ntn.to!string() == "Nullable.null");
+
+    class TestToString
+    {
+        double d;
+
+        this(double d)
+        {
+            this.d = d;
+        }
+
+        override string toString()
+        {
+            return d.to!string();
+        }
+    }
+    alias NullableTestToString = Nullable!(TestToString, null);
+
+    NullableTestToString ntts = new TestToString(2.5);
+    assert(ntts.to!string() == "2.5");
+}
+
+
+/**
+Just like $(D Nullable!T), except that the object refers to a value
+sitting elsewhere in memory. This makes assignments overwrite the
+initially assigned value. Internally $(D NullableRef!T) only stores a
+pointer to $(D T) (i.e., $(D Nullable!T.sizeof == (T*).sizeof)).
+ */
+struct NullableRef(T)
+{
+    private T* _value;
+
+/**
+Constructor binding $(D this) to $(D value).
+
+Params:
+    value = The value to bind to.
+ */
+    this(T* value) @safe pure nothrow
+    {
+        _value = value;
+    }
+
+    template toString()
+    {
+        import std.format : FormatSpec, formatValue;
+        // Needs to be a template because of DMD @@BUG@@ 13737.
+        void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
+        {
+            if (isNull)
+            {
+                sink.formatValue("Nullable.null", fmt);
+            }
+            else
+            {
+                sink.formatValue(*_value, fmt);
+            }
+        }
+    }
+
+/**
+Binds the internal state to $(D value).
+
+Params:
+    value = A pointer to a value of type `T` to bind this `NullableRef` to.
+ */
+    void bind(T* value) @safe pure nothrow
+    {
+        _value = value;
+    }
+
+    ///
+    unittest
+    {
+        NullableRef!int nr = new int(42);
+        assert(nr == 42);
+
+        int* n = new int(1);
+        nr.bind(n);
+        assert(nr == 1);
+    }
+
+/**
+Returns $(D true) if and only if $(D this) is in the null state.
+
+Returns:
+    true if `this` is in the null state, otherwise false.
+ */
+    @property bool isNull() const @safe pure nothrow
+    {
+        return _value is null;
+    }
+
+    ///
+    unittest
+    {
+        NullableRef!int nr;
+        assert(nr.isNull);
+
+        int* n = new int(42);
+        nr.bind(n);
+        assert(!nr.isNull && nr == 42);
+    }
+
+/**
+Forces $(D this) to the null state.
+ */
+    void nullify() @safe pure nothrow
+    {
+        _value = null;
+    }
+
+    ///
+    unittest
+    {
+        NullableRef!int nr = new int(42);
+        assert(!nr.isNull);
+
+        nr.nullify();
+        assert(nr.isNull);
+    }
+
+/**
+Assigns $(D value) to the internally-held state.
+
+Params:
+    value = A value of type `T` to assign to this `NullableRef`.
+            If the internal state of this `NullableRef` has not
+            been initialized, an error will be thrown in
+            non-release mode.
+ */
+    void opAssign()(T value)
+        if (isAssignable!T) //@@@9416@@@
+    {
+        enum message = "Called `opAssign' on null NullableRef!" ~ T.stringof ~ ".";
+        assert(!isNull, message);
+        *_value = value;
+    }
+
+    ///
+    unittest
+    {
+        import std.exception: assertThrown, assertNotThrown;
+
+        NullableRef!int nr;
+        assert(nr.isNull);
+        assertThrown!Throwable(nr = 42);
+
+        nr.bind(new int(0));
+        assert(!nr.isNull);
+        assertNotThrown!Throwable(nr = 42);
+        assert(nr == 42);
+    }
+
+/**
+Gets the value. $(D this) must not be in the null state.
+This function is also called for the implicit conversion to $(D T).
+ */
+    @property ref inout(T) get() inout @safe pure nothrow
+    {
+        enum message = "Called `get' on null NullableRef!" ~ T.stringof ~ ".";
+        assert(!isNull, message);
+        return *_value;
+    }
+
+    ///
+    unittest
+    {
+        import std.exception: assertThrown, assertNotThrown;
+
+        NullableRef!int nr;
+        //`get` is implicitly called. Will throw
+        //an error in non-release mode
+        assertThrown!Throwable(nr == 0);
+
+        nr.bind(new int(0));
+        assertNotThrown!Throwable(nr == 0);
+    }
+
+/**
+Implicitly converts to $(D T).
+$(D this) must not be in the null state.
+ */
+    alias get this;
+}
+
+unittest
+{
+    import std.exception : assertThrown;
+
+    int x = 5, y = 7;
+    auto a = NullableRef!(int)(&x);
+    assert(!a.isNull);
+    assert(a == 5);
+    assert(x == 5);
+    a = 42;
+    assert(x == 42);
+    assert(!a.isNull);
+    assert(a == 42);
+    a.nullify();
+    assert(x == 42);
+    assert(a.isNull);
+    assertThrown!Throwable(a.get);
+    assertThrown!Throwable(a = 71);
+    a.bind(&y);
+    assert(a == 7);
+    y = 135;
+    assert(a == 135);
+}
+unittest
+{
+    static int f(in NullableRef!int x) {
+        return x.isNull ? 42 : x.get;
+    }
+    int x = 5;
+    auto a = NullableRef!int(&x);
+    assert(f(a) == 5);
+    a.nullify();
+    assert(f(a) == 42);
+}
+unittest
+{
+    // Ensure NullableRef can be used in pure/nothrow/@safe environment.
+    function() @safe pure nothrow
+    {
+        auto storage = new int;
+        *storage = 19902;
+        NullableRef!int n;
+        assert(n.isNull);
+        n.bind(storage);
+        assert(!n.isNull);
+        assert(n == 19902);
+        n = 2294;
+        assert(n == 2294);
+        assert(*storage == 2294);
+        n.nullify();
+        assert(n.isNull);
+    }();
+}
+unittest
+{
+    // Ensure NullableRef can be used when the value is not pure/nothrow/@safe
+    static struct S
+    {
+        int x;
+        this(this) @system {}
+        bool opEquals(const S s) const @system { return s.x == x; }
+    }
+
+    auto storage = S(5);
+
+    NullableRef!S s;
+    assert(s.isNull);
+    s.bind(&storage);
+    assert(!s.isNull);
+    assert(s.x == 5);
+    s.nullify();
+    assert(s.isNull);
+}
+unittest
+{
+    //Check nullable is nicelly embedable in a struct
+    static struct S1
+    {
+        NullableRef!int ni;
+    }
+    static struct S2 //inspired from 9404
+    {
+        NullableRef!int ni;
+        this(S2 other)
+        {
+            ni = other.ni;
+        }
+        void opAssign(S2 other)
+        {
+            ni = other.ni;
+        }
+    }
+    foreach (S; AliasSeq!(S1, S2))
+    {
+        S a;
+        S b = a;
+        S c;
+        c = a;
+    }
+}
+unittest
+{
+    import std.conv: to;
+
+    // Bugzilla 10915
+    NullableRef!int nri;
+    assert(nri.to!string() == "Nullable.null");
+
+    struct Test
+    {
+        string s;
+    }
+    NullableRef!Test nt = new Test("test");
+    assert(nt.to!string() == `Test("test")`);
+
+    class TestToString
+    {
+        double d;
+
+        this(double d)
+        {
+            this.d = d;
+        }
+
+        override string toString()
+        {
+            return d.to!string();
+        }
+    }
+    TestToString tts = new TestToString(2.5);
+    NullableRef!TestToString ntts = &tts;
+    assert(ntts.to!string() == "2.5");
+}
+
+
+/**
+$(D BlackHole!Base) is a subclass of $(D Base) which automatically implements
+all abstract member functions in $(D Base) as do-nothing functions.  Each
+auto-implemented function just returns the default value of the return type
+without doing anything.
+
+The name came from
+$(WEB search.cpan.org/~sburke/Class-_BlackHole-0.04/lib/Class/_BlackHole.pm, Class::_BlackHole)
+Perl module by Sean M. Burke.
+
+Params:
+    Base = A non-final class for `BlackHole` to inherit from.
+
+See_Also:
+  $(LREF AutoImplement), $(LREF generateEmptyFunction)
+ */
+alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFunction);
+
+///
+unittest
+{
+    import std.math: isNaN;
+
+    static abstract class C
+    {
+        int m_value;
+        this(int v) { m_value = v; }
+        int value() @property { return m_value; }
+
+        abstract real realValue() @property;
+        abstract void doSomething();
+    }
+
+    auto c = new BlackHole!C(42);
+    assert(c.value == 42);
+
+    // Returns real.init which is NaN
+    assert(c.realValue.isNaN);
+    // Abstract functions are implemented as do-nothing
+    c.doSomething();
+}
+
+unittest
+{
+    import std.math : isNaN;
+
+    // return default
+    {
+        interface I_1 { real test(); }
+        auto o = new BlackHole!I_1;
+        assert(o.test().isNaN()); // NaN
+    }
+    // doc example
+    {
+        static class C
+        {
+            int m_value;
+            this(int v) { m_value = v; }
+            int value() @property { return m_value; }
+
+            abstract real realValue() @property;
+            abstract void doSomething();
+        }
+
+        auto c = new BlackHole!C(42);
+        assert(c.value == 42);
+
+        assert(c.realValue.isNaN); // NaN
+        c.doSomething();
+    }
+
+    // Bugzilla 12058
+    interface Foo
+    {
+        inout(Object) foo() inout;
+    }
+    BlackHole!Foo o;
+}
+
+
+/**
+$(D WhiteHole!Base) is a subclass of $(D Base) which automatically implements
+all abstract member functions as functions that always fail. These functions
+simply throw an $(D Error) and never return. `Whitehole` is useful for
+trapping the use of class member functions that haven't been implemented.
+
+The name came from
+$(WEB search.cpan.org/~mschwern/Class-_WhiteHole-0.04/lib/Class/_WhiteHole.pm, Class::_WhiteHole)
+Perl module by Michael G Schwern.
+
+Params:
+    Base = A non-final class for `WhiteHole` to inherit from.
+
+See_Also:
+  $(LREF AutoImplement), $(LREF generateAssertTrap)
+ */
+alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap, isAbstractFunction);
+
+///
+unittest
+{
+    import std.exception: assertThrown;
+
+    static class C
+    {
+        abstract void notYetImplemented();
+    }
+
+    auto c = new WhiteHole!C;
+    assertThrown!NotImplementedError(c.notYetImplemented()); // throws an Error
+}
+
+// / ditto
+class NotImplementedError : Error
+{
+    this(string method)
+    {
+        super(method ~ " is not implemented");
+    }
+}
+
+unittest
+{
+    import std.exception : assertThrown;
+    // nothrow
+    {
+        interface I_1
+        {
+            void foo();
+            void bar() nothrow;
+        }
+        auto o = new WhiteHole!I_1;
+        assertThrown!NotImplementedError(o.foo());
+        assertThrown!NotImplementedError(o.bar());
+    }
+    // doc example
+    {
+        static class C
+        {
+            abstract void notYetImplemented();
+        }
+
+        auto c = new WhiteHole!C;
+        try
+        {
+            c.notYetImplemented();
+            assert(0);
+        }
+        catch (Error e) {}
+    }
+}
+
+
+/**
+$(D AutoImplement) automatically implements (by default) all abstract member
+functions in the class or interface $(D Base) in specified way.
+
+Params:
+  how  = template which specifies _how functions will be implemented/overridden.
+
+         Two arguments are passed to $(D how): the type $(D Base) and an alias
+         to an implemented function.  Then $(D how) must return an implemented
+         function body as a string.
+
+         The generated function body can use these keywords:
+         $(UL
+            $(LI $(D a0), $(D a1), &hellip;: arguments passed to the function;)
+            $(LI $(D args): a tuple of the arguments;)
+            $(LI $(D self): an alias to the function itself;)
+            $(LI $(D parent): an alias to the overridden function (if any).)
+         )
+
+        You may want to use templated property functions (instead of Implicit
+        Template Properties) to generate complex functions:
+--------------------
+// Prints log messages for each call to overridden functions.
+string generateLogger(C, alias fun)() @property
+{
+    import std.traits;
+    enum qname = C.stringof ~ "." ~ __traits(identifier, fun);
+    string stmt;
+
+    stmt ~= q{ struct Importer { import std.stdio; } };
+    stmt ~= `Importer.writeln("Log: ` ~ qname ~ `(", args, ")");`;
+    static if (!__traits(isAbstractFunction, fun))
+    {
+        static if (is(ReturnType!fun == void))
+            stmt ~= q{ parent(args); };
+        else
+            stmt ~= q{
+                auto r = parent(args);
+                Importer.writeln("--> ", r);
+                return r;
+            };
+    }
+    return stmt;
+}
+--------------------
+
+  what = template which determines _what functions should be
+         implemented/overridden.
+
+         An argument is passed to $(D what): an alias to a non-final member
+         function in $(D Base).  Then $(D what) must return a boolean value.
+         Return $(D true) to indicate that the passed function should be
+         implemented/overridden.
+
+--------------------
+// Sees if fun returns something.
+enum bool hasValue(alias fun) = !is(ReturnType!(fun) == void);
+--------------------
+
+
+Note:
+
+Generated code is inserted in the scope of $(D std.typecons) module.  Thus,
+any useful functions outside $(D std.typecons) cannot be used in the generated
+code.  To workaround this problem, you may $(D import) necessary things in a
+local struct, as done in the $(D generateLogger()) template in the above
+example.
+
+
+BUGS:
+
+$(UL
+ $(LI Variadic arguments to constructors are not forwarded to super.)
+ $(LI Deep interface inheritance causes compile error with messages like
+      "Error: function std.typecons._AutoImplement!(Foo)._AutoImplement.bar
+      does not override any function".  [$(BUGZILLA 2525), $(BUGZILLA 3525)] )
+ $(LI The $(D parent) keyword is actually a delegate to the super class'
+      corresponding member function.  [$(BUGZILLA 2540)] )
+ $(LI Using alias template parameter in $(D how) and/or $(D what) may cause
+     strange compile error.  Use template tuple parameter instead to workaround
+     this problem.  [$(BUGZILLA 4217)] )
+)
+ */
+class AutoImplement(Base, alias how, alias what = isAbstractFunction) : Base
+{
+    private alias autoImplement_helper_ =
+        AutoImplement_Helper!("autoImplement_helper_", "Base", Base, how, what);
+    mixin(autoImplement_helper_.code);
+}
+
+/*
+ * Code-generating stuffs are encupsulated in this helper template so that
+ * namespace pollution, which can cause name confliction with Base's public
+ * members, should be minimized.
+ */
+private template AutoImplement_Helper(string myName, string baseName,
+        Base, alias generateMethodBody, alias cherrypickMethod)
+{
+private static:
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Internal stuffs
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    // Returns function overload sets in the class C, filtered with pred.
+    template enumerateOverloads(C, alias pred)
+    {
+        template Impl(names...)
+        {
+            import std.meta : Filter;
+            static if (names.length > 0)
+            {
+                alias methods = Filter!(pred, MemberFunctionsTuple!(C, names[0]));
+                alias next = Impl!(names[1 .. $]);
+
+                static if (methods.length > 0)
+                    alias Impl = AliasSeq!(OverloadSet!(names[0], methods), next);
+                else
+                    alias Impl = next;
+            }
+            else
+                alias Impl = AliasSeq!();
+        }
+
+        alias enumerateOverloads = Impl!(__traits(allMembers, C));
+    }
+
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Target functions
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    // Add a non-final check to the cherrypickMethod.
+    enum bool canonicalPicker(fun.../+[BUG 4217]+/) =
+        !__traits(isFinalFunction, fun[0]) && cherrypickMethod!(fun);
+
+    /*
+     * A tuple of overload sets, each item of which consists of functions to be
+     * implemented by the generated code.
+     */
+    alias targetOverloadSets = enumerateOverloads!(Base, canonicalPicker);
+
+    /*
+     * A tuple of the super class' constructors.  Used for forwarding
+     * constructor calls.
+     */
+    static if (__traits(hasMember, Base, "__ctor"))
+        alias ctorOverloadSet = OverloadSet!("__ctor", __traits(getOverloads, Base, "__ctor"));
+    else
+        alias ctorOverloadSet = OverloadSet!("__ctor"); // empty
+
+
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Type information
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    /*
+     * The generated code will be mixed into AutoImplement, which will be
+     * instantiated in this module's scope.  Thus, any user-defined types are
+     * out of scope and cannot be used directly (i.e. by their names).
+     *
+     * We will use FuncInfo instances for accessing return types and parameter
+     * types of the implemented functions.  The instances will be populated to
+     * the AutoImplement's scope in a certain way; see the populate() below.
+     */
+
+    // Returns the preferred identifier for the FuncInfo instance for the i-th
+    // overloaded function with the name.
+    template INTERNAL_FUNCINFO_ID(string name, size_t i)
+    {
+        import std.format : format;
+
+        enum string INTERNAL_FUNCINFO_ID = format("F_%s_%s", name, i);
+    }
+
+    /*
+     * Insert FuncInfo instances about all the target functions here.  This
+     * enables the generated code to access type information via, for example,
+     * "autoImplement_helper_.F_foo_1".
+     */
+    template populate(overloads...)
+    {
+        static if (overloads.length > 0)
+        {
+            mixin populate!(overloads[0].name, overloads[0].contents);
+            mixin populate!(overloads[1 .. $]);
+        }
+    }
+    template populate(string name, methods...)
+    {
+        static if (methods.length > 0)
+        {
+            mixin populate!(name, methods[0 .. $ - 1]);
+            //
+            alias target = methods[$ - 1];
+            enum ith = methods.length - 1;
+            mixin("alias " ~ INTERNAL_FUNCINFO_ID!(name, ith) ~ " = FuncInfo!target;");
+        }
+    }
+
+    public mixin populate!(targetOverloadSets);
+    public mixin populate!(  ctorOverloadSet );
+
+
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Code-generating policies
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    /* Common policy configurations for generating constructors and methods. */
+    template CommonGeneratingPolicy()
+    {
+        // base class identifier which generated code should use
+        enum string BASE_CLASS_ID = baseName;
+
+        // FuncInfo instance identifier which generated code should use
+        template FUNCINFO_ID(string name, size_t i)
+        {
+            enum string FUNCINFO_ID =
+                myName ~ "." ~ INTERNAL_FUNCINFO_ID!(name, i);
+        }
+    }
+
+    /* Policy configurations for generating constructors. */
+    template ConstructorGeneratingPolicy()
+    {
+        mixin CommonGeneratingPolicy;
+
+        /* Generates constructor body.  Just forward to the base class' one. */
+        string generateFunctionBody(ctor.../+[BUG 4217]+/)() @property
+        {
+            enum varstyle = variadicFunctionStyle!(typeof(&ctor[0]));
+
+            static if (varstyle & (Variadic.c | Variadic.d))
+            {
+                // the argptr-forwarding problem
+                //pragma(msg, "Warning: AutoImplement!(", Base, ") ",
+                //        "ignored variadic arguments to the constructor ",
+                //        FunctionTypeOf!(typeof(&ctor[0])) );
+            }
+            return "super(args);";
+        }
+    }
+
+    /* Policy configurations for genearting target methods. */
+    template MethodGeneratingPolicy()
+    {
+        mixin CommonGeneratingPolicy;
+
+        /* Geneartes method body. */
+        string generateFunctionBody(func.../+[BUG 4217]+/)() @property
+        {
+            return generateMethodBody!(Base, func); // given
+        }
+    }
+
+
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Generated code
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    alias ConstructorGenerator = MemberFunctionGenerator!(ConstructorGeneratingPolicy!());
+    alias MethodGenerator      = MemberFunctionGenerator!(MethodGeneratingPolicy!());
+
+    public enum string code =
+        ConstructorGenerator.generateCode!(  ctorOverloadSet ) ~ "\n" ~
+             MethodGenerator.generateCode!(targetOverloadSets);
+
+    debug (SHOW_GENERATED_CODE)
+    {
+        pragma(msg, "-------------------- < ", Base, " >");
+        pragma(msg, code);
+        pragma(msg, "--------------------");
+    }
+}
+
+//debug = SHOW_GENERATED_CODE;
+unittest
+{
+    import core.vararg;
+    // no function to implement
+    {
+        interface I_1 {}
+        auto o = new BlackHole!I_1;
+    }
+    // parameters
+    {
+        interface I_3 { void test(int, in int, out int, ref int, lazy int); }
+        auto o = new BlackHole!I_3;
+    }
+    // use of user-defined type
+    {
+        struct S {}
+        interface I_4 { S test(); }
+        auto o = new BlackHole!I_4;
+    }
+    // overloads
+    {
+        interface I_5
+        {
+            void test(string);
+            real test(real);
+            int  test();
+        }
+        auto o = new BlackHole!I_5;
+    }
+    // constructor forwarding
+    {
+        static class C_6
+        {
+            this(int n) { assert(n == 42); }
+            this(string s) { assert(s == "Deeee"); }
+            this(...) {}
+        }
+        auto o1 = new BlackHole!C_6(42);
+        auto o2 = new BlackHole!C_6("Deeee");
+        auto o3 = new BlackHole!C_6(1, 2, 3, 4);
+    }
+    // attributes
+    {
+        interface I_7
+        {
+            ref int test_ref();
+            int test_pure() pure;
+            int test_nothrow() nothrow;
+            int test_property() @property;
+            int test_safe() @safe;
+            int test_trusted() @trusted;
+            int test_system() @system;
+            int test_pure_nothrow() pure nothrow;
+        }
+        auto o = new BlackHole!I_7;
+    }
+    // storage classes
+    {
+        interface I_8
+        {
+            void test_const() const;
+            void test_immutable() immutable;
+            void test_shared() shared;
+            void test_shared_const() shared const;
+        }
+        auto o = new BlackHole!I_8;
+    }
+    /+ // deep inheritance
+    {
+    // XXX [BUG 2525,3525]
+    // NOTE: [r494] func.c(504-571) FuncDeclaration::semantic()
+        interface I { void foo(); }
+        interface J : I {}
+        interface K : J {}
+        static abstract class C_9 : K {}
+        auto o = new BlackHole!C_9;
+    }+/
+}
+
+version(unittest)
+{
+    // Issue 10647
+    // Add prefix "issue10647_" as a workaround for issue 1238
+    private string issue10647_generateDoNothing(C, alias fun)() @property
+    {
+        string stmt;
+
+        static if (is(ReturnType!fun == void))
+            stmt ~= "";
+        else
+        {
+            string returnType = ReturnType!fun.stringof;
+            stmt ~= "return "~returnType~".init;";
+        }
+        return stmt;
+    }
+
+    private template issue10647_isAlwaysTrue(alias fun)
+    {
+        enum issue10647_isAlwaysTrue = true;
+    }
+
+    // Do nothing template
+    private template issue10647_DoNothing(Base)
+    {
+        alias issue10647_DoNothing = AutoImplement!(Base, issue10647_generateDoNothing, issue10647_isAlwaysTrue);
+    }
+
+    // A class to be overridden
+    private class issue10647_Foo{
+        void bar(int a) { }
+    }
+}
+unittest
+{
+    auto foo = new issue10647_DoNothing!issue10647_Foo();
+    foo.bar(13);
+}
+
+/*
+Used by MemberFunctionGenerator.
+ */
+package template OverloadSet(string nam, T...)
+{
+    enum string name = nam;
+    alias contents = T;
+}
+
+/*
+Used by MemberFunctionGenerator.
+ */
+package template FuncInfo(alias func, /+[BUG 4217 ?]+/ T = typeof(&func))
+{
+    alias RT = ReturnType!T;
+    alias PT = Parameters!T;
+}
+package template FuncInfo(Func)
+{
+    alias RT = ReturnType!Func;
+    alias PT = Parameters!Func;
+}
+
+/*
+General-purpose member function generator.
+--------------------
+template GeneratingPolicy()
+{
+    // [optional] the name of the class where functions are derived
+    enum string BASE_CLASS_ID;
+
+    // [optional] define this if you have only function types
+    enum bool WITHOUT_SYMBOL;
+
+    // [optional] Returns preferred identifier for i-th parameter.
+    template PARAMETER_VARIABLE_ID(size_t i);
+
+    // Returns the identifier of the FuncInfo instance for the i-th overload
+    // of the specified name.  The identifier must be accessible in the scope
+    // where generated code is mixed.
+    template FUNCINFO_ID(string name, size_t i);
+
+    // Returns implemented function body as a string.  When WITHOUT_SYMBOL is
+    // defined, the latter is used.
+    template generateFunctionBody(alias func);
+    template generateFunctionBody(string name, FuncType);
+}
+--------------------
+ */
+package template MemberFunctionGenerator(alias Policy)
+{
+private static:
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Internal stuffs
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    import std.format;
+
+    enum CONSTRUCTOR_NAME = "__ctor";
+
+    // true if functions are derived from a base class
+    enum WITH_BASE_CLASS = __traits(hasMember, Policy, "BASE_CLASS_ID");
+
+    // true if functions are specified as types, not symbols
+    enum WITHOUT_SYMBOL = __traits(hasMember, Policy, "WITHOUT_SYMBOL");
+
+    // preferred identifier for i-th parameter variable
+    static if (__traits(hasMember, Policy, "PARAMETER_VARIABLE_ID"))
+    {
+        alias PARAMETER_VARIABLE_ID = Policy.PARAMETER_VARIABLE_ID;
+    }
+    else
+    {
+        enum string PARAMETER_VARIABLE_ID(size_t i) = format("a%s", i);
+            // default: a0, a1, ...
+    }
+
+    // Returns a tuple consisting of 0,1,2,...,n-1.  For static foreach.
+    template CountUp(size_t n)
+    {
+        static if (n > 0)
+            alias CountUp = AliasSeq!(CountUp!(n - 1), n - 1);
+        else
+            alias CountUp = AliasSeq!();
+    }
+
+
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+    // Code generator
+    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::://
+
+    /*
+     * Runs through all the target overload sets and generates D code which
+     * implements all the functions in the overload sets.
+     */
+    public string generateCode(overloads...)() @property
+    {
+        string code = "";
+
+        // run through all the overload sets
+        foreach (i_; CountUp!(0 + overloads.length)) // workaround
+        {
+            enum i = 0 + i_; // workaround
+            alias oset = overloads[i];
+
+            code ~= generateCodeForOverloadSet!(oset);
+
+            static if (WITH_BASE_CLASS && oset.name != CONSTRUCTOR_NAME)
+            {
+                // The generated function declarations may hide existing ones
+                // in the base class (cf. HiddenFuncError), so we put an alias
+                // declaration here to reveal possible hidden functions.
+                code ~= format("alias %s = %s.%s;\n",
+                            oset.name,
+                            Policy.BASE_CLASS_ID, // [BUG 2540] super.
+                            oset.name);
+            }
+        }
+        return code;
+    }
+
+    // handle each overload set
+    private string generateCodeForOverloadSet(alias oset)() @property
+    {
+        string code = "";
+
+        foreach (i_; CountUp!(0 + oset.contents.length)) // workaround
+        {
+            enum i = 0 + i_; // workaround
+            code ~= generateFunction!(
+                    Policy.FUNCINFO_ID!(oset.name, i), oset.name,
+                    oset.contents[i]) ~ "\n";
+        }
+        return code;
+    }
+
+    /*
+     * Returns D code which implements the function func.  This function
+     * actually generates only the declarator part; the function body part is
+     * generated by the functionGenerator() policy.
+     */
+    public string generateFunction(
+            string myFuncInfo, string name, func... )() @property
+    {
+        import std.format : format;
+
+        enum isCtor = (name == CONSTRUCTOR_NAME);
+
+        string code; // the result
+
+        auto paramsRes = generateParameters!(myFuncInfo, func)();
+        code ~= paramsRes.imports;
+
+        /*** Function Declarator ***/
+        {
+            alias Func = FunctionTypeOf!(func);
+            alias FA = FunctionAttribute;
+            enum atts     = functionAttributes!(func);
+            enum realName = isCtor ? "this" : name;
+
+            // FIXME?? Make it so that these aren't CTFE funcs any more, since
+            // Format is deprecated, and format works at compile time?
+            /* Made them CTFE funcs just for the sake of Format!(...) */
+
+            // return type with optional "ref"
+            static string make_returnType()
+            {
+                string rtype = "";
+
+                if (!isCtor)
+                {
+                    if (atts & FA.ref_) rtype ~= "ref ";
+                    rtype ~= myFuncInfo ~ ".RT";
+                }
+                return rtype;
+            }
+            enum returnType = make_returnType();
+
+            // function attributes attached after declaration
+            static string make_postAtts()
+            {
+                string poatts = "";
+                if (atts & FA.pure_   ) poatts ~= " pure";
+                if (atts & FA.nothrow_) poatts ~= " nothrow";
+                if (atts & FA.property) poatts ~= " @property";
+                if (atts & FA.safe    ) poatts ~= " @safe";
+                if (atts & FA.trusted ) poatts ~= " @trusted";
+                return poatts;
+            }
+            enum postAtts = make_postAtts();
+
+            // function storage class
+            static string make_storageClass()
+            {
+                string postc = "";
+                if (is(Func ==    shared)) postc ~= " shared";
+                if (is(Func ==     const)) postc ~= " const";
+                if (is(Func ==     inout)) postc ~= " inout";
+                if (is(Func == immutable)) postc ~= " immutable";
+                return postc;
+            }
+            enum storageClass = make_storageClass();
+
+            //
+            if (__traits(isVirtualMethod, func))
+                code ~= "override ";
+            code ~= format("extern(%s) %s %s(%s) %s %s\n",
+                    functionLinkage!(func),
+                    returnType,
+                    realName,
+                    paramsRes.params,
+                    postAtts, storageClass );
+        }
+
+        /*** Function Body ***/
+        code ~= "{\n";
+        {
+            enum nparams = Parameters!(func).length;
+
+            /* Declare keywords: args, self and parent. */
+            string preamble;
+
+            preamble ~= "alias args = AliasSeq!(" ~ enumerateParameters!(nparams) ~ ");\n";
+            if (!isCtor)
+            {
+                preamble ~= "alias self = " ~ name ~ ";\n";
+                if (WITH_BASE_CLASS && !__traits(isAbstractFunction, func))
+                    //preamble ~= "alias super." ~ name ~ " parent;\n"; // [BUG 2540]
+                    preamble ~= "auto parent = &super." ~ name ~ ";\n";
+            }
+
+            // Function body
+            static if (WITHOUT_SYMBOL)
+                enum fbody = Policy.generateFunctionBody!(name, func);
+            else
+                enum fbody = Policy.generateFunctionBody!(func);
+
+            code ~= preamble;
+            code ~= fbody;
+        }
+        code ~= "}";
+
+        return code;
+    }
+
+    /*
+     * Returns D code which declares function parameters,
+     * and optionally any imports (e.g. core.vararg)
+     * "ref int a0, real a1, ..."
+     */
+    static struct GenParams { string imports, params; }
+    private GenParams generateParameters(string myFuncInfo, func...)()
+    {
+        alias STC = ParameterStorageClass;
+        alias stcs = ParameterStorageClassTuple!(func);
+        enum nparams = stcs.length;
+
+        string imports = ""; // any imports required
+        string params = ""; // parameters
+
+        foreach (i, stc; stcs)
+        {
+            if (i > 0) params ~= ", ";
+
+            // Parameter storage classes.
+            if (stc & STC.scope_) params ~= "scope ";
+            if (stc & STC.out_  ) params ~= "out ";
+            if (stc & STC.ref_  ) params ~= "ref ";
+            if (stc & STC.lazy_ ) params ~= "lazy ";
+
+            // Take parameter type from the FuncInfo.
+            params ~= format("%s.PT[%s]", myFuncInfo, i);
+
+            // Declare a parameter variable.
+            params ~= " " ~ PARAMETER_VARIABLE_ID!(i);
+        }
+
+        // Add some ellipsis part if needed.
+        auto style = variadicFunctionStyle!(func);
+        final switch (style)
+        {
+            case Variadic.no:
+                break;
+
+            case Variadic.c, Variadic.d:
+                imports ~= "import core.vararg;\n";
+                // (...) or (a, b, ...)
+                params ~= (nparams == 0) ? "..." : ", ...";
+                break;
+
+            case Variadic.typesafe:
+                params ~= " ...";
+                break;
+        }
+
+        return typeof(return)(imports, params);
+    }
+
+    // Returns D code which enumerates n parameter variables using comma as the
+    // separator.  "a0, a1, a2, a3"
+    private string enumerateParameters(size_t n)() @property
+    {
+        string params = "";
+
+        foreach (i_; CountUp!(n))
+        {
+            enum i = 0 + i_; // workaround
+            if (i > 0) params ~= ", ";
+            params ~= PARAMETER_VARIABLE_ID!(i);
+        }
+        return params;
+    }
+}
+
+
+/**
+Predefined how-policies for $(D AutoImplement).  These templates are also used by
+$(D BlackHole) and $(D WhiteHole), respectively.
+ */
+template generateEmptyFunction(C, func.../+[BUG 4217]+/)
+{
+    static if (is(ReturnType!(func) == void))
+        enum string generateEmptyFunction = q{
+        };
+    else static if (functionAttributes!(func) & FunctionAttribute.ref_)
+        enum string generateEmptyFunction = q{
+            static typeof(return) dummy;
+            return dummy;
+        };
+    else
+        enum string generateEmptyFunction = q{
+            return typeof(return).init;
+        };
+}
+
+/// ditto
+template generateAssertTrap(C, func...)
+{
+    enum string generateAssertTrap =
+        `throw new NotImplementedError("` ~ C.stringof ~ "."
+                ~ __traits(identifier, func) ~ `");`;
+}
+
+private
+{
+    pragma(mangle, "_d_toObject")
+    extern(C) pure nothrow Object typecons_d_toObject(void* p);
+}
+
+/*
+ * Avoids opCast operator overloading.
+ */
+private template dynamicCast(T)
+if (is(T == class) || is(T == interface))
+{
+    @trusted
+    T dynamicCast(S)(inout S source)
+    if (is(S == class) || is(S == interface))
+    {
+        static if (is(Unqual!S : Unqual!T))
+        {
+            import std.traits : QualifierOf;
+            alias Qual = QualifierOf!S; // SharedOf or MutableOf
+            alias TmpT = Qual!(Unqual!T);
+            inout(TmpT) tmp = source;   // bypass opCast by implicit conversion
+            return *cast(T*)(&tmp);     // + variable pointer cast + dereference
+        }
+        else
+        {
+            return cast(T)typecons_d_toObject(*cast(void**)(&source));
+        }
+    }
+}
+
+unittest
+{
+    class C { @disable opCast(T)() {} }
+    auto c = new C;
+    static assert(!__traits(compiles, cast(Object)c));
+    auto o = dynamicCast!Object(c);
+    assert(c is o);
+
+    interface I { @disable opCast(T)() {} Object instance(); }
+    interface J { @disable opCast(T)() {} Object instance(); }
+    class D : I, J { Object instance() { return this; } }
+    I i = new D();
+    static assert(!__traits(compiles, cast(J)i));
+    J j = dynamicCast!J(i);
+    assert(i.instance() is j.instance());
+}
+
+/**
+ * Supports structural based typesafe conversion.
+ *
+ * If $(D Source) has structural conformance with the $(D interface) $(D Targets),
+ * wrap creates internal wrapper class which inherits $(D Targets) and
+ * wrap $(D src) object, then return it.
+ */
+template wrap(Targets...)
+if (Targets.length >= 1 && allSatisfy!(isMutable, Targets))
+{
+    import std.meta : staticMap;
+
+    // strict upcast
+    auto wrap(Source)(inout Source src) @trusted pure nothrow
+    if (Targets.length == 1 && is(Source : Targets[0]))
+    {
+        alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]);
+        return dynamicCast!(inout T)(src);
+    }
+    // structural upcast
+    template wrap(Source)
+    if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets))
+    {
+        auto wrap(inout Source src)
+        {
+            static assert(hasRequireMethods!(),
+                          "Source "~Source.stringof~
+                          " does not have structural conformance to "~
+                          Targets.stringof);
+
+            alias T = Select!(is(Source == shared), shared Impl, Impl);
+            return new inout T(src);
+        }
+
+        template FuncInfo(string s, F)
+        {
+            enum name = s;
+            alias type = F;
+        }
+
+        // Concat all Targets function members into one tuple
+        template Concat(size_t i = 0)
+        {
+            static if (i >= Targets.length)
+                alias Concat = AliasSeq!();
+            else
+            {
+                alias Concat = AliasSeq!(GetOverloadedMethods!(Targets[i]), Concat!(i + 1));
+            }
+        }
+        // Remove duplicated functions based on the identifier name and function type covariance
+        template Uniq(members...)
+        {
+            static if (members.length == 0)
+                alias Uniq = AliasSeq!();
+            else
+            {
+                alias func = members[0];
+                enum  name = __traits(identifier, func);
+                alias type = FunctionTypeOf!func;
+                template check(size_t i, mem...)
+                {
+                    static if (i >= mem.length)
+                        enum ptrdiff_t check = -1;
+                    else
+                    {
+                        enum ptrdiff_t check =
+                            __traits(identifier, func) == __traits(identifier, mem[i]) &&
+                            !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void)
+                          ? i : check!(i + 1, mem);
+                    }
+                }
+                enum ptrdiff_t x = 1 + check!(0, members[1 .. $]);
+                static if (x >= 1)
+                {
+                    alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x]));
+                    alias remain = Uniq!(members[1 .. x], members[x + 1 .. $]);
+
+                    static if (remain.length >= 1 && remain[0].name == name &&
+                               !is(DerivedFunctionType!(typex, remain[0].type) == void))
+                    {
+                        alias F = DerivedFunctionType!(typex, remain[0].type);
+                        alias Uniq = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]);
+                    }
+                    else
+                        alias Uniq = AliasSeq!(FuncInfo!(name, typex), remain);
+                }
+                else
+                {
+                    alias Uniq = AliasSeq!(FuncInfo!(name, type), Uniq!(members[1 .. $]));
+                }
+            }
+        }
+        alias TargetMembers = Uniq!(Concat!());             // list of FuncInfo
+        alias SourceMembers = GetOverloadedMethods!Source;  // list of function symbols
+
+        // Check whether all of SourceMembers satisfy covariance target in TargetMembers
+        template hasRequireMethods(size_t i = 0)
+        {
+            static if (i >= TargetMembers.length)
+                enum hasRequireMethods = true;
+            else
+            {
+                enum hasRequireMethods =
+                    findCovariantFunction!(TargetMembers[i], Source, SourceMembers) != -1 &&
+                    hasRequireMethods!(i + 1);
+            }
+        }
+
+        // Internal wrapper class
+        final class Impl : Structural, Targets
+        {
+        private:
+            Source _wrap_source;
+
+            this(       inout Source s)        inout @safe pure nothrow { _wrap_source = s; }
+            this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; }
+
+            // BUG: making private should work with NVI.
+            protected final inout(Object) _wrap_getSource() inout @trusted
+            {
+                return dynamicCast!(inout Object)(_wrap_source);
+            }
+
+            import std.conv : to;
+            import std.functional : forward;
+            template generateFun(size_t i)
+            {
+                enum name = TargetMembers[i].name;
+                enum fa = functionAttributes!(TargetMembers[i].type);
+                static @property stc()
+                {
+                    string r;
+                    if (fa & FunctionAttribute.property)    r ~= "@property ";
+                    if (fa & FunctionAttribute.ref_)        r ~= "ref ";
+                    if (fa & FunctionAttribute.pure_)       r ~= "pure ";
+                    if (fa & FunctionAttribute.nothrow_)    r ~= "nothrow ";
+                    if (fa & FunctionAttribute.trusted)     r ~= "@trusted ";
+                    if (fa & FunctionAttribute.safe)        r ~= "@safe ";
+                    return r;
+                }
+                static @property mod()
+                {
+                    alias type = AliasSeq!(TargetMembers[i].type)[0];
+                    string r;
+                    static if (is(type == immutable))       r ~= " immutable";
+                    else
+                    {
+                        static if (is(type == shared))      r ~= " shared";
+                        static if (is(type == const))       r ~= " const";
+                        else static if (is(type == inout))  r ~= " inout";
+                        //else  --> mutable
+                    }
+                    return r;
+                }
+                enum n = to!string(i);
+                static if (fa & FunctionAttribute.property)
+                {
+                    static if (Parameters!(TargetMembers[i].type).length == 0)
+                        enum fbody = "_wrap_source."~name;
+                    else
+                        enum fbody = "_wrap_source."~name~" = forward!args";
+                }
+                else
+                {
+                        enum fbody = "_wrap_source."~name~"(forward!args)";
+                }
+                enum generateFun =
+                    "override "~stc~"ReturnType!(TargetMembers["~n~"].type) "
+                    ~ name~"(Parameters!(TargetMembers["~n~"].type) args) "~mod~
+                    "{ return "~fbody~"; }";
+            }
+
+        public:
+            mixin mixinAll!(
+                staticMap!(generateFun, staticIota!(0, TargetMembers.length)));
+        }
+    }
+}
+/// ditto
+template wrap(Targets...)
+if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets))
+{
+    import std.meta : staticMap;
+
+    alias wrap = .wrap!(staticMap!(Unqual, Targets));
+}
+
+// Internal class to support dynamic cross-casting
+private interface Structural
+{
+    inout(Object) _wrap_getSource() inout @safe pure nothrow;
+}
+
+/**
+ * Extract object which wrapped by $(D wrap).
+ */
+template unwrap(Target)
+if (isMutable!Target)
+{
+    // strict downcast
+    auto unwrap(Source)(inout Source src) @trusted pure nothrow
+    if (is(Target : Source))
+    {
+        alias T = Select!(is(Source == shared), shared Target, Target);
+        return dynamicCast!(inout T)(src);
+    }
+    // structural downcast
+    auto unwrap(Source)(inout Source src) @trusted pure nothrow
+    if (!is(Target : Source))
+    {
+        alias T = Select!(is(Source == shared), shared Target, Target);
+        Object o = dynamicCast!(Object)(src);   // remove qualifier
+        do
+        {
+            if (auto a = dynamicCast!(Structural)(o))
+            {
+                if (auto d = dynamicCast!(inout T)(o = a._wrap_getSource()))
+                    return d;
+            }
+            else if (auto d = dynamicCast!(inout T)(o))
+                return d;
+            else
+                break;
+        } while (o);
+        return null;
+    }
+}
+/// ditto
+template unwrap(Target)
+if (!isMutable!Target)
+{
+    alias unwrap = .unwrap!(Unqual!Target);
+}
+
+///
+unittest
+{
+    interface Quack
+    {
+        int quack();
+        @property int height();
+    }
+    interface Flyer
+    {
+        @property int height();
+    }
+    class Duck : Quack
+    {
+        int quack() { return 1; }
+        @property int height() { return 10; }
+    }
+    class Human
+    {
+        int quack() { return 2; }
+        @property int height() { return 20; }
+    }
+
+    Duck d1 = new Duck();
+    Human h1 = new Human();
+
+    interface Refleshable
+    {
+        int reflesh();
+    }
+    // does not have structural conformance
+    static assert(!__traits(compiles, d1.wrap!Refleshable));
+    static assert(!__traits(compiles, h1.wrap!Refleshable));
+
+    // strict upcast
+    Quack qd = d1.wrap!Quack;
+    assert(qd is d1);
+    assert(qd.quack() == 1);    // calls Duck.quack
+    // strict downcast
+    Duck d2 = qd.unwrap!Duck;
+    assert(d2 is d1);
+
+    // structural upcast
+    Quack qh = h1.wrap!Quack;
+    assert(qh.quack() == 2);    // calls Human.quack
+    // structural downcast
+    Human h2 = qh.unwrap!Human;
+    assert(h2 is h1);
+
+    // structural upcast (two steps)
+    Quack qx = h1.wrap!Quack;   // Human -> Quack
+    Flyer fx = qx.wrap!Flyer;   // Quack -> Flyer
+    assert(fx.height == 20);    // calls Human.height
+    // strucural downcast (two steps)
+    Quack qy = fx.unwrap!Quack; // Flyer -> Quack
+    Human hy = qy.unwrap!Human; // Quack -> Human
+    assert(hy is h1);
+    // strucural downcast (one step)
+    Human hz = fx.unwrap!Human; // Flyer -> Human
+    assert(hz is h1);
+}
+///
+unittest
+{
+    interface A { int run(); }
+    interface B { int stop(); @property int status(); }
+    class X
+    {
+        int run() { return 1; }
+        int stop() { return 2; }
+        @property int status() { return 3; }
+    }
+
+    auto x = new X();
+    auto ab = x.wrap!(A, B);
+    A a = ab;
+    B b = ab;
+    assert(a.run() == 1);
+    assert(b.stop() == 2);
+    assert(b.status == 3);
+    static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property);
+}
+unittest
+{
+    class A
+    {
+        int draw()              { return 1; }
+        int draw(int v)         { return v; }
+
+        int draw() const        { return 2; }
+        int draw() shared       { return 3; }
+        int draw() shared const { return 4; }
+        int draw() immutable    { return 5; }
+    }
+    interface Drawable
+    {
+        int draw();
+        int draw() const;
+        int draw() shared;
+        int draw() shared const;
+        int draw() immutable;
+    }
+    interface Drawable2
+    {
+        int draw(int v);
+    }
+
+    auto ma = new A();
+    auto sa = new shared A();
+    auto ia = new immutable A();
+    {
+                     Drawable  md = ma.wrap!Drawable;
+               const Drawable  cd = ma.wrap!Drawable;
+              shared Drawable  sd = sa.wrap!Drawable;
+        shared const Drawable scd = sa.wrap!Drawable;
+           immutable Drawable  id = ia.wrap!Drawable;
+        assert( md.draw() == 1);
+        assert( cd.draw() == 2);
+        assert( sd.draw() == 3);
+        assert(scd.draw() == 4);
+        assert( id.draw() == 5);
+    }
+    {
+        Drawable2 d = ma.wrap!Drawable2;
+        static assert(!__traits(compiles, d.draw()));
+        assert(d.draw(10) == 10);
+    }
+}
+unittest
+{
+    // Bugzilla 10377
+    import std.range, std.algorithm;
+
+    interface MyInputRange(T)
+    {
+        @property T front();
+        void popFront();
+        @property bool empty();
+    }
+
+    //auto o = iota(0,10,1).inputRangeObject();
+    //pragma(msg, __traits(allMembers, typeof(o)));
+    auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)();
+    assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
+}
+unittest
+{
+    // Bugzilla 10536
+    interface Interface
+    {
+        int foo();
+    }
+    class Pluggable
+    {
+        int foo() { return 1; }
+        @disable void opCast(T, this X)();  // !
+    }
+
+    Interface i = new Pluggable().wrap!Interface;
+    assert(i.foo() == 1);
+}
+unittest
+{
+    // Enhancement 10538
+    interface Interface
+    {
+        int foo();
+        int bar(int);
+    }
+    class Pluggable
+    {
+        int opDispatch(string name, A...)(A args) { return 100; }
+    }
+
+    Interface i = wrap!Interface(new Pluggable());
+    assert(i.foo() == 100);
+    assert(i.bar(10) == 100);
+}
+
+// Make a tuple of non-static function symbols
+private template GetOverloadedMethods(T)
+{
+    import std.meta : Filter;
+
+    alias allMembers = AliasSeq!(__traits(allMembers, T));
+    template follows(size_t i = 0)
+    {
+        static if (i >= allMembers.length)
+        {
+            alias follows = AliasSeq!();
+        }
+        else static if (!__traits(compiles, mixin("T."~allMembers[i])))
+        {
+            alias follows = follows!(i + 1);
+        }
+        else
+        {
+            enum name = allMembers[i];
+
+            template isMethod(alias f)
+            {
+                static if (is(typeof(&f) F == F*) && is(F == function))
+                    enum isMethod = !__traits(isStaticFunction, f);
+                else
+                    enum isMethod = false;
+            }
+            alias follows = AliasSeq!(
+                std.meta.Filter!(isMethod, __traits(getOverloads, T, name)),
+                follows!(i + 1));
+        }
+    }
+    alias GetOverloadedMethods = follows!();
+}
+// find a function from Fs that has same identifier and covariant type with f
+private template findCovariantFunction(alias finfo, Source, Fs...)
+{
+    template check(size_t i = 0)
+    {
+        static if (i >= Fs.length)
+            enum ptrdiff_t check = -1;
+        else
+        {
+            enum ptrdiff_t check =
+                (finfo.name == __traits(identifier, Fs[i])) &&
+                isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type)
+              ? i : check!(i + 1);
+        }
+    }
+    enum x = check!();
+    static if (x == -1 && is(typeof(Source.opDispatch)))
+    {
+        alias Params = Parameters!(finfo.type);
+        enum ptrdiff_t findCovariantFunction =
+            is(typeof((             Source).init.opDispatch!(finfo.name)(Params.init))) ||
+            is(typeof((       const Source).init.opDispatch!(finfo.name)(Params.init))) ||
+            is(typeof((   immutable Source).init.opDispatch!(finfo.name)(Params.init))) ||
+            is(typeof((      shared Source).init.opDispatch!(finfo.name)(Params.init))) ||
+            is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init)))
+          ? ptrdiff_t.max : -1;
+    }
+    else
+        enum ptrdiff_t findCovariantFunction = x;
+}
+
+private enum TypeModifier
+{
+    mutable     = 0,    // type is mutable
+    const_      = 1,    // type is const
+    immutable_  = 2,    // type is immutable
+    shared_     = 4,    // type is shared
+    inout_      = 8,    // type is wild
+}
+private template TypeMod(T)
+{
+    static if (is(T == immutable))
+    {
+        enum mod1 = TypeModifier.immutable_;
+        enum mod2 = 0;
+    }
+    else
+    {
+        enum mod1 = is(T == shared) ? TypeModifier.shared_ : 0;
+        static if (is(T == const))
+            enum mod2 = TypeModifier.const_;
+        else static if (is(T == inout))
+            enum mod2 = TypeModifier.inout_;
+        else
+            enum mod2 = TypeModifier.mutable;
+    }
+    enum TypeMod = cast(TypeModifier)(mod1 | mod2);
+}
+
+version(unittest)
+{
+    private template UnittestFuncInfo(alias f)
+    {
+        enum name = __traits(identifier, f);
+        alias type = FunctionTypeOf!f;
+    }
+}
+unittest
+{
+    class A
+    {
+        int draw() { return 1; }
+        @property int value() { return 2; }
+        final int run() { return 3; }
+    }
+    alias methods = GetOverloadedMethods!A;
+
+    alias int F1();
+    alias @property int F2();
+    alias string F3();
+    alias nothrow @trusted uint F4();
+    alias int F5(Object);
+    alias bool F6(Object);
+    static assert(methods.length == 3 + 4);
+    static assert(__traits(identifier, methods[0]) == "draw"     && is(typeof(&methods[0]) == F1*));
+    static assert(__traits(identifier, methods[1]) == "value"    && is(typeof(&methods[1]) == F2*));
+    static assert(__traits(identifier, methods[2]) == "run"      && is(typeof(&methods[2]) == F1*));
+
+    int draw() { return 0; }
+    @property int value() { return 0; }
+    void opEquals() {}
+    int nomatch() { return 0; }
+    static assert(findCovariantFunction!(UnittestFuncInfo!draw,     A, methods) == 0);
+    static assert(findCovariantFunction!(UnittestFuncInfo!value,    A, methods) == 1);
+    static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, A, methods) == -1);
+    static assert(findCovariantFunction!(UnittestFuncInfo!nomatch,  A, methods) == -1);
+
+    // considering opDispatch
+    class B
+    {
+        void opDispatch(string name, A...)(A) {}
+    }
+    alias methodsB = GetOverloadedMethods!B;
+    static assert(findCovariantFunction!(UnittestFuncInfo!draw,     B, methodsB) == ptrdiff_t.max);
+    static assert(findCovariantFunction!(UnittestFuncInfo!value,    B, methodsB) == ptrdiff_t.max);
+    static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, B, methodsB) == ptrdiff_t.max);
+    static assert(findCovariantFunction!(UnittestFuncInfo!nomatch,  B, methodsB) == ptrdiff_t.max);
+}
+
+private template DerivedFunctionType(T...)
+{
+    static if (!T.length)
+    {
+        alias DerivedFunctionType = void;
+    }
+    else static if (T.length == 1)
+    {
+        static if (is(T[0] == function))
+        {
+            alias DerivedFunctionType = T[0];
+        }
+        else
+        {
+            alias DerivedFunctionType = void;
+        }
+    }
+    else static if (is(T[0] P0 == function) && is(T[1] P1 == function))
+    {
+        alias FA = FunctionAttribute;
+
+        alias F0 = T[0], R0 = ReturnType!F0, PSTC0 = ParameterStorageClassTuple!F0;
+        alias F1 = T[1], R1 = ReturnType!F1, PSTC1 = ParameterStorageClassTuple!F1;
+        enum FA0 = functionAttributes!F0;
+        enum FA1 = functionAttributes!F1;
+
+        template CheckParams(size_t i = 0)
+        {
+            static if (i >= P0.length)
+                enum CheckParams = true;
+            else
+            {
+                enum CheckParams = (is(P0[i] == P1[i]) && PSTC0[i] == PSTC1[i]) &&
+                                   CheckParams!(i + 1);
+            }
+        }
+        static if (R0.sizeof == R1.sizeof && !is(CommonType!(R0, R1) == void) &&
+                   P0.length == P1.length && CheckParams!() && TypeMod!F0 == TypeMod!F1 &&
+                   variadicFunctionStyle!F0 == variadicFunctionStyle!F1 &&
+                   functionLinkage!F0 == functionLinkage!F1 &&
+                   ((FA0 ^ FA1) & (FA.ref_ | FA.property)) == 0)
+        {
+            alias R = Select!(is(R0 : R1), R0, R1);
+            alias FX = FunctionTypeOf!(R function(P0));
+            // @system is default
+            alias FY = SetFunctionAttributes!(FX, functionLinkage!F0, (FA0 | FA1) & ~FA.system);
+            alias DerivedFunctionType = DerivedFunctionType!(FY, T[2 .. $]);
+        }
+        else
+            alias DerivedFunctionType = void;
+    }
+    else
+        alias DerivedFunctionType = void;
+}
+unittest
+{
+    // attribute covariance
+    alias int F1();
+    static assert(is(DerivedFunctionType!(F1, F1) == F1));
+    alias int F2() pure nothrow;
+    static assert(is(DerivedFunctionType!(F1, F2) == F2));
+    alias int F3() @safe;
+    alias int F23() @safe pure nothrow;
+    static assert(is(DerivedFunctionType!(F2, F3) == F23));
+
+    // return type covariance
+    alias long F4();
+    static assert(is(DerivedFunctionType!(F1, F4) == void));
+    class C {}
+    class D : C {}
+    alias C F5();
+    alias D F6();
+    static assert(is(DerivedFunctionType!(F5, F6) == F6));
+    alias typeof(null) F7();
+    alias int[] F8();
+    alias int* F9();
+    static assert(is(DerivedFunctionType!(F5, F7) == F7));
+    static assert(is(DerivedFunctionType!(F7, F8) == void));
+    static assert(is(DerivedFunctionType!(F7, F9) == F7));
+
+    // variadic type equality
+    alias int F10(int);
+    alias int F11(int...);
+    alias int F12(int, ...);
+    static assert(is(DerivedFunctionType!(F10, F11) == void));
+    static assert(is(DerivedFunctionType!(F10, F12) == void));
+    static assert(is(DerivedFunctionType!(F11, F12) == void));
+
+    // linkage equality
+    alias extern(C) int F13(int);
+    alias extern(D) int F14(int);
+    alias extern(Windows) int F15(int);
+    static assert(is(DerivedFunctionType!(F13, F14) == void));
+    static assert(is(DerivedFunctionType!(F13, F15) == void));
+    static assert(is(DerivedFunctionType!(F14, F15) == void));
+
+    // ref & @property equality
+    alias int F16(int);
+    alias ref int F17(int);
+    alias @property int F18(int);
+    static assert(is(DerivedFunctionType!(F16, F17) == void));
+    static assert(is(DerivedFunctionType!(F16, F18) == void));
+    static assert(is(DerivedFunctionType!(F17, F18) == void));
+}
+
+package template staticIota(int beg, int end)
+{
+    static if (beg + 1 >= end)
+    {
+        static if (beg >= end)
+        {
+            alias staticIota = AliasSeq!();
+        }
+        else
+        {
+            alias staticIota = AliasSeq!(+beg);
+        }
+    }
+    else
+    {
+        enum mid = beg + (end - beg) / 2;
+        alias staticIota = AliasSeq!(staticIota!(beg, mid), staticIota!(mid, end));
+    }
+}
+
+private template mixinAll(mixins...)
+{
+    static if (mixins.length == 1)
+    {
+        static if (is(typeof(mixins[0]) == string))
+        {
+            mixin(mixins[0]);
+        }
+        else
+        {
+            alias it = mixins[0];
+            mixin it;
+        }
+    }
+    else static if (mixins.length >= 2)
+    {
+        mixin mixinAll!(mixins[ 0 .. $/2]);
+        mixin mixinAll!(mixins[$/2 .. $ ]);
+    }
+}
+
+private template Bind(alias Template, args1...)
+{
+    alias Bind(args2...) = Template!(args1, args2);
+}
+
+
+/**
+Options regarding auto-initialization of a $(D RefCounted) object (see
+the definition of $(D RefCounted) below).
+ */
+enum RefCountedAutoInitialize
+{
+    /// Do not auto-initialize the object
+    no,
+    /// Auto-initialize the object
+    yes,
+}
+
+/**
+Defines a reference-counted object containing a $(D T) value as
+payload. $(D RefCounted) keeps track of all references of an object,
+and when the reference count goes down to zero, frees the underlying
+store. $(D RefCounted) uses $(D malloc) and $(D free) for operation.
+
+$(D RefCounted) is unsafe and should be used with care. No references
+to the payload should be escaped outside the $(D RefCounted) object.
+
+The $(D autoInit) option makes the object ensure the store is
+automatically initialized. Leaving $(D autoInit ==
+RefCountedAutoInitialize.yes) (the default option) is convenient but
+has the cost of a test whenever the payload is accessed. If $(D
+autoInit == RefCountedAutoInitialize.no), user code must call either
+$(D refCountedStore.isInitialized) or $(D refCountedStore.ensureInitialized)
+before attempting to access the payload. Not doing so results in null
+pointer dereference.
+ */
+struct RefCounted(T, RefCountedAutoInitialize autoInit =
+        RefCountedAutoInitialize.yes)
+if (!is(T == class) && !(is(T == interface)))
+{
+    /// $(D RefCounted) storage implementation.
+    struct RefCountedStore
+    {
+        private struct Impl
+        {
+            T _payload;
+            size_t _count;
+        }
+
+        private Impl* _store;
+
+        private void initialize(A...)(auto ref A args)
+        {
+            import core.exception : onOutOfMemoryError;
+            import core.memory : GC;
+            import core.stdc.stdlib : malloc;
+            import std.conv : emplace;
+
+            _store = cast(Impl*)malloc(Impl.sizeof);
+            if (_store is null)
+                onOutOfMemoryError();
+            static if (hasIndirections!T)
+                GC.addRange(&_store._payload, T.sizeof);
+            emplace(&_store._payload, args);
+            _store._count = 1;
+        }
+
+        private void move(ref T source)
+        {
+            import core.exception : onOutOfMemoryError;
+            import core.memory : GC;
+            import core.stdc.stdlib : malloc;
+            import core.stdc.string : memcpy, memset;
+
+            _store = cast(Impl*)malloc(Impl.sizeof);
+            if (_store is null)
+                onOutOfMemoryError();
+            static if (hasIndirections!T)
+                GC.addRange(&_store._payload, T.sizeof);
+
+            // Can't use std.algorithm.move(source, _store._payload)
+            // here because it requires the target to be initialized.
+            // Might be worth to add this as `moveEmplace`
+
+            // Can avoid destructing result.
+            static if (hasElaborateAssign!T || !isAssignable!T)
+                memcpy(&_store._payload, &source, T.sizeof);
+            else
+                _store._payload = source;
+
+            // If the source defines a destructor or a postblit hook, we must obliterate the
+            // object in order to avoid double freeing and undue aliasing
+            static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T)
+            {
+                // If T is nested struct, keep original context pointer
+                static if (__traits(isNested, T))
+                    enum sz = T.sizeof - (void*).sizeof;
+                else
+                    enum sz = T.sizeof;
+
+                auto init = typeid(T).initializer();
+                if (init.ptr is null) // null ptr means initialize to 0s
+                    memset(&source, 0, sz);
+                else
+                    memcpy(&source, init.ptr, sz);
+            }
+
+            _store._count = 1;
+        }
+
+        /**
+           Returns $(D true) if and only if the underlying store has been
+           allocated and initialized.
+        */
+        @property nothrow @safe pure @nogc
+        bool isInitialized() const
+        {
+            return _store !is null;
+        }
+
+        /**
+           Returns underlying reference count if it is allocated and initialized
+           (a positive integer), and $(D 0) otherwise.
+        */
+        @property nothrow @safe pure @nogc
+        size_t refCount() const
+        {
+            return isInitialized ? _store._count : 0;
+        }
+
+        /**
+           Makes sure the payload was properly initialized. Such a
+           call is typically inserted before using the payload.
+        */
+        void ensureInitialized()
+        {
+            if (!isInitialized) initialize();
+        }
+
+    }
+    RefCountedStore _refCounted;
+
+    /// Returns storage implementation struct.
+    @property nothrow @safe
+    ref inout(RefCountedStore) refCountedStore() inout
+    {
+        return _refCounted;
+    }
+
+/**
+Constructor that initializes the payload.
+
+Postcondition: $(D refCountedStore.isInitialized)
+ */
+    this(A...)(auto ref A args) if (A.length > 0)
+    {
+        _refCounted.initialize(args);
+    }
+
+    /// Ditto
+    this(T val)
+    {
+        _refCounted.move(val);
+    }
+
+/**
+Constructor that tracks the reference count appropriately. If $(D
+!refCountedStore.isInitialized), does nothing.
+ */
+    this(this) @safe pure nothrow @nogc
+    {
+        if (!_refCounted.isInitialized) return;
+        ++_refCounted._store._count;
+    }
+
+/**
+Destructor that tracks the reference count appropriately. If $(D
+!refCountedStore.isInitialized), does nothing. When the reference count goes
+down to zero, calls $(D destroy) agaist the payload and calls $(D free)
+to deallocate the corresponding resource.
+ */
+    ~this()
+    {
+        if (!_refCounted.isInitialized) return;
+        assert(_refCounted._store._count > 0);
+        if (--_refCounted._store._count)
+            return;
+        // Done, deallocate
+        .destroy(_refCounted._store._payload);
+        static if (hasIndirections!T)
+        {
+            import core.memory : GC;
+            GC.removeRange(&_refCounted._store._payload);
+        }
+        import core.stdc.stdlib : free;
+        free(_refCounted._store);
+        _refCounted._store = null;
+    }
+
+/**
+Assignment operators
+ */
+    void opAssign(typeof(this) rhs)
+    {
+        import std.algorithm : swap;
+
+        swap(_refCounted._store, rhs._refCounted._store);
+    }
+
+/// Ditto
+    void opAssign(T rhs)
+    {
+        import std.algorithm : move;
+
+        static if (autoInit == RefCountedAutoInitialize.yes)
+        {
+            _refCounted.ensureInitialized();
+        }
+        else
+        {
+            assert(_refCounted.isInitialized);
+        }
+        move(rhs, _refCounted._store._payload);
+    }
+
+    //version to have a single properly ddoc'ed function (w/ correct sig)
+    version(StdDdoc)
+    {
+        /**
+        Returns a reference to the payload. If (autoInit ==
+        RefCountedAutoInitialize.yes), calls $(D
+        refCountedStore.ensureInitialized). Otherwise, just issues $(D
+        assert(refCountedStore.isInitialized)). Used with $(D alias
+        refCountedPayload this;), so callers can just use the $(D RefCounted)
+        object as a $(D T).
+
+        $(BLUE The first overload exists only if $(D autoInit == RefCountedAutoInitialize.yes).)
+        So if $(D autoInit == RefCountedAutoInitialize.no)
+        or called for a constant or immutable object, then
+        $(D refCountedPayload) will also be qualified as safe and nothrow
+        (but will still assert if not initialized).
+         */
+        @property
+        ref T refCountedPayload() return;
+
+        /// ditto
+        @property nothrow @safe pure @nogc
+        ref inout(T) refCountedPayload() inout return;
+    }
+    else
+    {
+        static if (autoInit == RefCountedAutoInitialize.yes)
+        {
+            //Can't use inout here because of potential mutation
+            @property
+            ref T refCountedPayload() return
+            {
+                _refCounted.ensureInitialized();
+                return _refCounted._store._payload;
+            }
+        }
+
+        @property nothrow @safe pure @nogc
+        ref inout(T) refCountedPayload() inout return
+        {
+            assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload.");
+            return _refCounted._store._payload;
+        }
+    }
+
+/**
+Returns a reference to the payload. If (autoInit ==
+RefCountedAutoInitialize.yes), calls $(D
+refCountedStore.ensureInitialized). Otherwise, just issues $(D
+assert(refCountedStore.isInitialized)).
+ */
+    alias refCountedPayload this;
+}
+
+///
+unittest
+{
+    // A pair of an $(D int) and a $(D size_t) - the latter being the
+    // reference count - will be dynamically allocated
+    auto rc1 = RefCounted!int(5);
+    assert(rc1 == 5);
+    // No more allocation, add just one extra reference count
+    auto rc2 = rc1;
+    // Reference semantics
+    rc2 = 42;
+    assert(rc1 == 42);
+    // the pair will be freed when rc1 and rc2 go out of scope
+}
+
+unittest
+{
+    RefCounted!int* p;
+    {
+        auto rc1 = RefCounted!int(5);
+        p = &rc1;
+        assert(rc1 == 5);
+        assert(rc1._refCounted._store._count == 1);
+        auto rc2 = rc1;
+        assert(rc1._refCounted._store._count == 2);
+        // Reference semantics
+        rc2 = 42;
+        assert(rc1 == 42);
+        rc2 = rc2;
+        assert(rc2._refCounted._store._count == 2);
+        rc1 = rc2;
+        assert(rc1._refCounted._store._count == 2);
+    }
+    assert(p._refCounted._store == null);
+
+    // RefCounted as a member
+    struct A
+    {
+        RefCounted!int x;
+        this(int y)
+        {
+            x._refCounted.initialize(y);
+        }
+        A copy()
+        {
+            auto another = this;
+            return another;
+        }
+    }
+    auto a = A(4);
+    auto b = a.copy();
+    assert(a.x._refCounted._store._count == 2, "BUG 4356 still unfixed");
+}
+
+unittest
+{
+    import std.algorithm : swap;
+
+    RefCounted!int p1, p2;
+    swap(p1, p2);
+}
+
+// 6606
+unittest
+{
+    union U {
+       size_t i;
+       void* p;
+    }
+
+    struct S {
+       U u;
+    }
+
+    alias SRC = RefCounted!S;
+}
+
+// 6436
+unittest
+{
+    struct S { this(ref int val) { assert(val == 3); ++val; } }
+
+    int val = 3;
+    auto s = RefCounted!S(val);
+    assert(val == 4);
+}
+
+unittest
+{
+    RefCounted!int a;
+    a = 5; //This should not assert
+    assert(a == 5);
+
+    RefCounted!int b;
+    b = a; //This should not assert either
+    assert(b == 5);
+}
+
+/**
+ * Initializes a `RefCounted` with `val`. The template parameter
+ * `T` of `RefCounted` is inferred from `val`.
+ * This function can be used to move non-copyable values to the heap.
+ * It also disables the `autoInit` option of `RefCounted`.
+ *
+ * Params:
+ *   val = The value to be reference counted
+ * Returns:
+ *   An initialized $(D RefCounted) containing $(D val).
+ * See_Also:
+ *   $(WEB http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared)
+ */
+RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val)
+{
+    typeof(return) res;
+    res._refCounted.move(val);
+    return res;
+}
+
+///
+unittest
+{
+    static struct File
+    {
+        string name;
+        @disable this(this); // not copyable
+        ~this() { name = null; }
+    }
+
+    auto file = File("name");
+    assert(file.name == "name");
+    // file cannot be copied and has unique ownership
+    static assert(!__traits(compiles, {auto file2 = file;}));
+
+    // make the file refcounted to share ownership
+    import std.algorithm.mutation : move;
+    auto rcFile = refCounted(move(file));
+    assert(rcFile.name == "name");
+    assert(file.name == null);
+    auto rcFile2 = rcFile;
+    assert(rcFile.refCountedStore.refCount == 2);
+    // file gets properly closed when last reference is dropped
+}
+
+/**
+    Creates a proxy for the value `a` that will forward all operations
+    while disabling implicit conversions. The aliased item `a` must be
+    an $(B lvalue). This is useful for creating a new type from the
+    "base" type (though this is $(B not) a subtype-supertype
+    relationship; the new type is not related to the old type in any way,
+    by design).
+
+    The new type supports all operations that the underlying type does,
+    including all operators such as `+`, `--`, `<`, `[]`, etc.
+
+    Params:
+        a = The value to act as a proxy for all operations. It must
+            be an lvalue.
+ */
+mixin template Proxy(alias a)
+{
+    private alias ValueType = typeof({ return a; }());
+    private enum bool accessibleFrom(T) =
+        is(typeof((ref T self){ cast(void)mixin("self."~__traits(identifier, a)); }));
+
+    static if (is(typeof(this) == class))
+    {
+        override bool opEquals(Object o)
+        {
+            if (auto b = cast(typeof(this))o)
+            {
+                return a == mixin("b."~__traits(identifier, a));
+            }
+            return false;
+        }
+
+        bool opEquals(T)(T b)
+            if (is(ValueType : T) || is(typeof(a.opEquals(b))) || is(typeof(b.opEquals(a))))
+        {
+            static if (is(typeof(a.opEquals(b))))
+                return a.opEquals(b);
+            else static if (is(typeof(b.opEquals(a))))
+                return b.opEquals(a);
+            else
+                return a == b;
+        }
+
+        override int opCmp(Object o)
+        {
+            if (auto b = cast(typeof(this))o)
+            {
+                return a < mixin("b."~__traits(identifier, a)) ? -1
+                     : a > mixin("b."~__traits(identifier, a)) ? +1 : 0;
+            }
+            static if (is(ValueType == class))
+                return a.opCmp(o);
+            else
+                throw new Exception("Attempt to compare a "~typeid(this).toString~" and a "~typeid(o).toString);
+        }
+
+        int opCmp(T)(auto ref const T b)
+            if (is(ValueType : T) || is(typeof(a.opCmp(b))) || is(typeof(b.opCmp(a))))
+        {
+            static if (is(typeof(a.opCmp(b))))
+                return a.opCmp(b);
+            else static if (is(typeof(b.opCmp(a))))
+                return -b.opCmp(b);
+            else
+                return a < b ? -1 : a > b ? +1 : 0;
+        }
+
+        static if (accessibleFrom!(const typeof(this)))
+        {
+            override hash_t toHash() const nothrow @trusted
+            {
+                static if (is(typeof(&a) == ValueType*))
+                    alias v = a;
+                else
+                    auto v = a;     // if a is (property) function
+                return typeid(ValueType).getHash(cast(const void*)&v);
+            }
+        }
+    }
+    else
+    {
+        auto ref opEquals(this X, B)(auto ref B b)
+        {
+            static if (is(immutable B == immutable typeof(this)))
+            {
+                return a == mixin("b."~__traits(identifier, a));
+            }
+            else
+                return a == b;
+        }
+
+        auto ref opCmp(this X, B)(auto ref B b)
+          if (!is(typeof(a.opCmp(b))) || !is(typeof(b.opCmp(a))))
+        {
+            static if (is(typeof(a.opCmp(b))))
+                return a.opCmp(b);
+            else static if (is(typeof(b.opCmp(a))))
+                return -b.opCmp(a);
+            else static if (isFloatingPoint!ValueType || isFloatingPoint!B)
+                return a < b ? -1 : a > b ? +1 : a == b ? 0 : float.nan;
+            else
+                return a < b ? -1 : (a > b);
+        }
+
+        static if (accessibleFrom!(const typeof(this)))
+        {
+            hash_t toHash() const nothrow @trusted
+            {
+                static if (is(typeof(&a) == ValueType*))
+                    alias v = a;
+                else
+                    auto v = a;     // if a is (property) function
+                return typeid(ValueType).getHash(cast(const void*)&v);
+            }
+        }
+    }
+
+    auto ref opCall(this X, Args...)(auto ref Args args) { return a(args); }
+
+    auto ref opCast(T, this X)() { return cast(T)a; }
+
+    auto ref opIndex(this X, D...)(auto ref D i)               { return a[i]; }
+    auto ref opSlice(this X      )()                           { return a[]; }
+    auto ref opSlice(this X, B, E)(auto ref B b, auto ref E e) { return a[b..e]; }
+
+    auto ref opUnary     (string op, this X      )()                           { return mixin(op~"a"); }
+    auto ref opIndexUnary(string op, this X, D...)(auto ref D i)               { return mixin(op~"a[i]"); }
+    auto ref opSliceUnary(string op, this X      )()                           { return mixin(op~"a[]"); }
+    auto ref opSliceUnary(string op, this X, B, E)(auto ref B b, auto ref E e) { return mixin(op~"a[b..e]"); }
+
+    auto ref opBinary(string op, this X, B)(auto ref B b)
+    if (op == "in" && is(typeof(a in b)) || op != "in")
+    {
+        return mixin("a "~op~" b");
+    }
+    auto ref opBinaryRight(string op, this X, B)(auto ref B b) { return mixin("b "~op~" a"); }
+
+    static if (!is(typeof(this) == class))
+    {
+        private import std.traits;
+        static if (isAssignable!ValueType)
+        {
+            auto ref opAssign(this X)(auto ref typeof(this) v)
+            {
+                a = mixin("v."~__traits(identifier, a));
+                return this;
+            }
+        }
+        else
+        {
+            @disable void opAssign(this X)(auto ref typeof(this) v);
+        }
+    }
+
+    auto ref opAssign     (this X, V      )(auto ref V v) if (!is(V == typeof(this))) { return a       = v; }
+    auto ref opIndexAssign(this X, V, D...)(auto ref V v, auto ref D i)               { return a[i]    = v; }
+    auto ref opSliceAssign(this X, V      )(auto ref V v)                             { return a[]     = v; }
+    auto ref opSliceAssign(this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) { return a[b..e] = v; }
+
+    auto ref opOpAssign     (string op, this X, V      )(auto ref V v)                             { return mixin("a "      ~op~"= v"); }
+    auto ref opIndexOpAssign(string op, this X, V, D...)(auto ref V v, auto ref D i)               { return mixin("a[i] "   ~op~"= v"); }
+    auto ref opSliceOpAssign(string op, this X, V      )(auto ref V v)                             { return mixin("a[] "    ~op~"= v"); }
+    auto ref opSliceOpAssign(string op, this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) { return mixin("a[b..e] "~op~"= v"); }
+
+    template opDispatch(string name)
+    {
+        static if (is(typeof(__traits(getMember, a, name)) == function))
+        {
+            // non template function
+            auto ref opDispatch(this X, Args...)(auto ref Args args) { return mixin("a."~name~"(args)"); }
+        }
+        else static if (is(typeof({ enum x = mixin("a."~name); })))
+        {
+            // built-in type field, manifest constant, and static non-mutable field
+            enum opDispatch = mixin("a."~name);
+        }
+        else static if (is(typeof(mixin("a."~name))) || __traits(getOverloads, a, name).length != 0)
+        {
+            // field or property function
+            @property auto ref opDispatch(this X)()                { return mixin("a."~name);        }
+            @property auto ref opDispatch(this X, V)(auto ref V v) { return mixin("a."~name~" = v"); }
+        }
+        else
+        {
+            // member template
+            template opDispatch(T...)
+            {
+                enum targs = T.length ? "!T" : "";
+                auto ref opDispatch(this X, Args...)(auto ref Args args){ return mixin("a."~name~targs~"(args)"); }
+            }
+        }
+    }
+
+    import std.traits : isArray;
+
+    static if (isArray!ValueType)
+    {
+        auto opDollar() const { return a.length; }
+    }
+    else static if (is(typeof(a.opDollar!0)))
+    {
+        auto ref opDollar(size_t pos)() { return a.opDollar!pos(); }
+    }
+    else static if (is(typeof(a.opDollar) == function))
+    {
+        auto ref opDollar() { return a.opDollar(); }
+    }
+    else static if (is(typeof(a.opDollar)))
+    {
+        alias opDollar = a.opDollar;
+    }
+}
+
+///
+unittest
+{
+    struct MyInt
+    {
+        private int value;
+        mixin Proxy!value;
+
+        this(int n){ value = n; }
+    }
+
+    MyInt n = 10;
+
+    // Enable operations that original type has.
+    ++n;
+    assert(n == 11);
+    assert(n * 2 == 22);
+
+    void func(int n) { }
+
+    // Disable implicit conversions to original type.
+    //int x = n;
+    //func(n);
+}
+
+///The proxied value must be an $(B lvalue).
+unittest
+{
+    struct NewIntType
+    {
+        //Won't work; the literal '1' is
+        //is an rvalue, not an lvalue
+        //mixin Proxy!1;
+
+        //Okay, n is an lvalue
+        int n;
+        mixin Proxy!n;
+
+        this(int n) { this.n = n; }
+    }
+
+    NewIntType nit = 0;
+    nit++;
+    assert(nit == 1);
+
+
+    struct NewObjectType
+    {
+        Object obj;
+        //Ok, obj is an lvalue
+        mixin Proxy!obj;
+
+        this (Object o) { obj = o; }
+    }
+
+    NewObjectType not = new Object();
+    assert(__traits(compiles, not.toHash()));
+}
+
+/**
+    There is one exception to the fact that the new type is not related to the
+    old type. $(DDSUBLINK spec/function,pseudo-member, Pseudo-member)
+    functions are usable with the new type; they will be forwarded on to the
+    proxied value.
+ */
+unittest
+{
+    import std.math;
+
+    float f = 1.0;
+    assert(!f.isInfinity);
+
+    struct NewFloat
+    {
+        float _;
+        mixin Proxy!_;
+
+        this(float f) { _ = f; }
+    }
+
+    NewFloat nf = 1.0f;
+    assert(!nf.isInfinity);
+}
+
+unittest
+{
+    static struct MyInt
+    {
+        private int value;
+        mixin Proxy!value;
+        this(int n) inout { value = n; }
+
+        enum str = "str";
+        static immutable arr = [1,2,3];
+    }
+
+    foreach (T; AliasSeq!(MyInt, const MyInt, immutable MyInt))
+    {
+        T m = 10;
+        static assert(!__traits(compiles, { int x = m; }));
+        static assert(!__traits(compiles, { void func(int n){} func(m); }));
+        assert(m == 10);
+        assert(m != 20);
+        assert(m < 20);
+        assert(+m == 10);
+        assert(-m == -10);
+        assert(cast(double)m == 10.0);
+        assert(m + 10 == 20);
+        assert(m - 5 == 5);
+        assert(m * 20 == 200);
+        assert(m / 2 == 5);
+        assert(10 + m == 20);
+        assert(15 - m == 5);
+        assert(20 * m == 200);
+        assert(50 / m == 5);
+        static if (is(T == MyInt))  // mutable
+        {
+            assert(++m == 11);
+            assert(m++ == 11); assert(m == 12);
+            assert(--m == 11);
+            assert(m-- == 11); assert(m == 10);
+            m = m;
+            m = 20; assert(m == 20);
+        }
+        static assert(T.max == int.max);
+        static assert(T.min == int.min);
+        static assert(T.init == int.init);
+        static assert(T.str == "str");
+        static assert(T.arr == [1,2,3]);
+    }
+}
+unittest
+{
+    static struct MyArray
+    {
+        private int[] value;
+        mixin Proxy!value;
+        this(int[] arr) { value = arr; }
+        this(immutable int[] arr) immutable { value = arr; }
+    }
+
+    foreach (T; AliasSeq!(MyArray, const MyArray, immutable MyArray))
+    {
+      static if (is(T == immutable) && !is(typeof({ T a = [1,2,3,4]; })))
+        T a = [1,2,3,4].idup;   // workaround until qualified ctor is properly supported
+      else
+        T a = [1,2,3,4];
+        assert(a == [1,2,3,4]);
+        assert(a != [5,6,7,8]);
+        assert(+a[0]    == 1);
+        version (LittleEndian)
+            assert(cast(ulong[])a == [0x0000_0002_0000_0001, 0x0000_0004_0000_0003]);
+        else
+            assert(cast(ulong[])a == [0x0000_0001_0000_0002, 0x0000_0003_0000_0004]);
+        assert(a ~ [10,11] == [1,2,3,4,10,11]);
+        assert(a[0]    == 1);
+        assert(a[]     == [1,2,3,4]);
+        assert(a[2..4] == [3,4]);
+        static if (is(T == MyArray))    // mutable
+        {
+            a = a;
+            a = [5,6,7,8];  assert(a == [5,6,7,8]);
+            a[0]     = 0;   assert(a == [0,6,7,8]);
+            a[]      = 1;   assert(a == [1,1,1,1]);
+            a[0..3]  = 2;   assert(a == [2,2,2,1]);
+            a[0]    += 2;   assert(a == [4,2,2,1]);
+            a[]     *= 2;   assert(a == [8,4,4,2]);
+            a[0..2] /= 2;   assert(a == [4,2,4,2]);
+        }
+    }
+}
+unittest
+{
+    class Foo
+    {
+        int field;
+
+        @property int val1() const { return field; }
+        @property void val1(int n) { field = n; }
+
+        @property ref int val2() { return field; }
+
+        int func(int x, int y) const { return x; }
+        void func1(ref int a) { a = 9; }
+
+        T ifti1(T)(T t) { return t; }
+        void ifti2(Args...)(Args args) { }
+        void ifti3(T, Args...)(Args args) { }
+
+        T opCast(T)(){ return T.init; }
+
+        T tempfunc(T)() { return T.init; }
+    }
+    class Hoge
+    {
+        Foo foo;
+        mixin Proxy!foo;
+        this(Foo f) { foo = f; }
+    }
+
+    auto h = new Hoge(new Foo());
+    int n;
+
+    static assert(!__traits(compiles, { Foo f = h; }));
+
+    // field
+    h.field = 1;            // lhs of assign
+    n = h.field;            // rhs of assign
+    assert(h.field == 1);   // lhs of BinExp
+    assert(1 == h.field);   // rhs of BinExp
+    assert(n == 1);
+
+    // getter/setter property function
+    h.val1 = 4;
+    n = h.val1;
+    assert(h.val1 == 4);
+    assert(4 == h.val1);
+    assert(n == 4);
+
+    // ref getter property function
+    h.val2 = 8;
+    n = h.val2;
+    assert(h.val2 == 8);
+    assert(8 == h.val2);
+    assert(n == 8);
+
+    // member function
+    assert(h.func(2,4) == 2);
+    h.func1(n);
+    assert(n == 9);
+
+    // IFTI
+    assert(h.ifti1(4) == 4);
+    h.ifti2(4);
+    h.ifti3!int(4, 3);
+
+    // bug5896 test
+    assert(h.opCast!int() == 0);
+    assert(cast(int)h == 0);
+    const ih = new const Hoge(new Foo());
+    static assert(!__traits(compiles, ih.opCast!int()));
+    static assert(!__traits(compiles, cast(int)ih));
+
+    // template member function
+    assert(h.tempfunc!int() == 0);
+}
+
+unittest // about Proxy inside a class
+{
+    class MyClass
+    {
+        int payload;
+        mixin Proxy!payload;
+        this(int i){ payload = i; }
+        string opCall(string msg){ return msg; }
+        int pow(int i){ return payload ^^ i; }
+    }
+
+    class MyClass2
+    {
+        MyClass payload;
+        mixin Proxy!payload;
+        this(int i){ payload = new MyClass(i); }
+    }
+
+    class MyClass3
+    {
+        int payload;
+        mixin Proxy!payload;
+        this(int i){ payload = i; }
+    }
+
+    // opEquals
+    Object a = new MyClass(5);
+    Object b = new MyClass(5);
+    Object c = new MyClass2(5);
+    Object d = new MyClass3(5);
+    assert(a == b);
+    assert((cast(MyClass)a) == 5);
+    assert(5 == (cast(MyClass)b));
+    assert(5 == cast(MyClass2)c);
+    assert(a != d);
+
+    assert(c != a);
+    // oops! above line is unexpected, isn't it?
+    // the reason is below.
+    // MyClass2.opEquals knows MyClass but,
+    // MyClass.opEquals doesn't know MyClass2.
+    // so, c.opEquals(a) is true, but a.opEquals(c) is false.
+    // furthermore, opEquals(T) couldn't be invoked.
+    assert((cast(MyClass2)c) != (cast(MyClass)a));
+
+    // opCmp
+    Object e = new MyClass2(7);
+    assert(a < cast(MyClass2)e); // OK. and
+    assert(e > a); // OK, but...
+    // assert(a < e); // RUNTIME ERROR!
+    // assert((cast(MyClass)a) < e); // RUNTIME ERROR!
+    assert(3 < cast(MyClass)a);
+    assert((cast(MyClass2)e) < 11);
+
+    // opCall
+    assert((cast(MyClass2)e)("hello") == "hello");
+
+    // opCast
+    assert((cast(MyClass)(cast(MyClass2)c)) == a);
+    assert((cast(int)(cast(MyClass2)c)) == 5);
+
+    // opIndex
+    class MyClass4
+    {
+        string payload;
+        mixin Proxy!payload;
+        this(string s){ payload = s; }
+    }
+    class MyClass5
+    {
+        MyClass4 payload;
+        mixin Proxy!payload;
+        this(string s){ payload = new MyClass4(s); }
+    }
+    auto f = new MyClass4("hello");
+    assert(f[1] == 'e');
+    auto g = new MyClass5("hello");
+    assert(f[1] == 'e');
+
+    // opSlice
+    assert(f[2..4] == "ll");
+
+    // opUnary
+    assert(-(cast(MyClass2)c) == -5);
+
+    // opBinary
+    assert((cast(MyClass)a) + (cast(MyClass2)c) == 10);
+    assert(5 + cast(MyClass)a == 10);
+
+    // opAssign
+    (cast(MyClass2)c) = 11;
+    assert((cast(MyClass2)c) == 11);
+    (cast(MyClass2)c) = new MyClass(13);
+    assert((cast(MyClass2)c) == 13);
+
+    // opOpAssign
+    assert((cast(MyClass2)c) += 4);
+    assert((cast(MyClass2)c) == 17);
+
+    // opDispatch
+    assert((cast(MyClass2)c).pow(2) == 289);
+
+    // opDollar
+    assert(f[2..$-1] == "ll");
+
+    // toHash
+    int[Object] hash;
+    hash[a] = 19;
+    hash[c] = 21;
+    assert(hash[b] == 19);
+    assert(hash[c] == 21);
+}
+
+unittest
+{
+    struct MyInt
+    {
+        int payload;
+
+        mixin Proxy!payload;
+    }
+
+    MyInt v;
+    v = v;
+
+    struct Foo
+    {
+        @disable void opAssign(typeof(this));
+    }
+    struct MyFoo
+    {
+        Foo payload;
+
+        mixin Proxy!payload;
+    }
+    MyFoo f;
+    static assert(!__traits(compiles, f = f));
+
+    struct MyFoo2
+    {
+        Foo payload;
+
+        mixin Proxy!payload;
+
+        // override default Proxy behavior
+        void opAssign(typeof(this) rhs){}
+    }
+    MyFoo2 f2;
+    f2 = f2;
+}
+unittest
+{
+    // bug8613
+    static struct Name
+    {
+        mixin Proxy!val;
+        private string val;
+        this(string s) { val = s; }
+    }
+
+    bool[Name] names;
+    names[Name("a")] = true;
+    bool* b = Name("a") in names;
+}
+
+unittest
+{
+    // bug14213, using function for the payload
+    static struct S
+    {
+        int foo() { return 12; }
+        mixin Proxy!foo;
+    }
+    static class C
+    {
+        int foo() { return 12; }
+        mixin Proxy!foo;
+    }
+    S s;
+    assert(s + 1 == 13);
+    C c = new C();
+    assert(s * 2 == 24);
+}
+
+// Check all floating point comparisons for both Proxy and Typedef,
+// also against int and a Typedef!int, to be as regression-proof
+// as possible. bug 15561
+unittest
+{
+    static struct MyFloatImpl
+    {
+        float value;
+        mixin Proxy!value;
+    }
+    static void allFail(T0, T1)(T0 a, T1 b)
+    {
+        assert(!(a==b));
+        assert(!(a<b));
+        assert(!(a<=b));
+        assert(!(a>b));
+        assert(!(a>=b));
+    }
+    foreach (T1; AliasSeq!(MyFloatImpl, Typedef!float, Typedef!double,
+        float, real, Typedef!int, int))
+    {
+        foreach (T2; AliasSeq!(MyFloatImpl, Typedef!float))
+        {
+            T1 a;
+            T2 b;
+
+            static if (isFloatingPoint!T1 || isFloatingPoint!(TypedefType!T1))
+                allFail(a, b);
+            a = 3;
+            allFail(a, b);
+
+            b = 4;
+            assert(a!=b);
+            assert(a<b);
+            assert(a<=b);
+            assert(!(a>b));
+            assert(!(a>=b));
+
+            a = 4;
+            assert(a==b);
+            assert(!(a<b));
+            assert(a<=b);
+            assert(!(a>b));
+            assert(a>=b);
+        }
+    }
+}
+
+/**
+$(B Typedef) allows the creation of a unique type which is
+based on an existing type. Unlike the $(D alias) feature,
+$(B Typedef) ensures the two types are not considered as equals.
+
+Example:
+----
+alias MyInt = Typedef!int;
+static void takeInt(int) { }
+static void takeMyInt(MyInt) { }
+
+int i;
+takeInt(i);    // ok
+takeMyInt(i);  // fails
+
+MyInt myInt;
+takeInt(myInt);    // fails
+takeMyInt(myInt);  // ok
+----
+
+Params:
+
+init = Optional initial value for the new type. For example:
+
+----
+alias MyInt = Typedef!(int, 10);
+MyInt myInt;
+assert(myInt == 10);  // default-initialized to 10
+----
+
+cookie = Optional, used to create multiple unique types which are
+based on the same origin type $(D T). For example:
+
+----
+alias TypeInt1 = Typedef!int;
+alias TypeInt2 = Typedef!int;
+
+// The two Typedefs are the same type.
+static assert(is(TypeInt1 == TypeInt2));
+
+alias MoneyEuros = Typedef!(float, float.init, "euros");
+alias MoneyDollars = Typedef!(float, float.init, "dollars");
+
+// The two Typedefs are _not_ the same type.
+static assert(!is(MoneyEuros == MoneyDollars));
+----
+
+Note: If a library routine cannot handle the Typedef type,
+you can use the $(D TypedefType) template to extract the
+type which the Typedef wraps.
+ */
+struct Typedef(T, T init = T.init, string cookie=null)
+{
+    private T Typedef_payload = init;
+
+    this(T init)
+    {
+        Typedef_payload = init;
+    }
+
+    this(Typedef tdef)
+    {
+        this(tdef.Typedef_payload);
+    }
+
+    // We need to add special overload for cast(Typedef!X)exp,
+    // thus we can't simply inherit Proxy!Typedef_payload
+    T2 opCast(T2 : Typedef!(T, Unused), this X, T, Unused...)()
+    {
+        return T2(cast(T)Typedef_payload);
+    }
+
+    auto ref opCast(T2, this X)()
+    {
+        return cast(T2)Typedef_payload;
+    }
+
+    mixin Proxy!Typedef_payload;
+}
+
+/**
+Get the underlying type which a $(D Typedef) wraps.
+If $(D T) is not a $(D Typedef) it will alias itself to $(D T).
+*/
+template TypedefType(T)
+{
+    static if (is(T : Typedef!Arg, Arg))
+        alias TypedefType = Arg;
+    else
+        alias TypedefType = T;
+}
+
+///
+unittest
+{
+    import std.typecons: Typedef, TypedefType;
+    import std.conv: to;
+
+    alias MyInt = Typedef!int;
+    static assert(is(TypedefType!MyInt == int));
+
+    /// Instantiating with a non-Typedef will return that type
+    static assert(is(TypedefType!int == int));
+
+    string num = "5";
+
+    // extract the needed type
+    MyInt myInt = MyInt( num.to!(TypedefType!MyInt) );
+    assert(myInt == 5);
+
+    // cast to the underlying type to get the value that's being wrapped
+    int x = cast(TypedefType!MyInt)myInt;
+
+    alias MyIntInit = Typedef!(int, 42);
+    static assert(is(TypedefType!MyIntInit == int));
+    static assert(MyIntInit() == 42);
+}
+
+unittest
+{
+    Typedef!int x = 10;
+    static assert(!__traits(compiles, { int y = x; }));
+    static assert(!__traits(compiles, { long z = x; }));
+
+    Typedef!int y = 10;
+    assert(x == y);
+
+    static assert(Typedef!int.init == int.init);
+
+    Typedef!(float, 1.0) z; // specifies the init
+    assert(z == 1.0);
+
+    static assert(typeof(z).init == 1.0);
+
+    alias Dollar = Typedef!(int, 0, "dollar");
+    alias Yen    = Typedef!(int, 0, "yen");
+    static assert(!is(Dollar == Yen));
+
+    Typedef!(int[3]) sa;
+    static assert(sa.length == 3);
+    static assert(typeof(sa).length == 3);
+
+    Typedef!(int[3]) dollar1;
+    assert(dollar1[0..$] is dollar1[0..3]);
+
+    Typedef!(int[]) dollar2;
+    dollar2.length = 3;
+    assert(dollar2[0..$] is dollar2[0..3]);
+
+    static struct Dollar1
+    {
+        static struct DollarToken {}
+        enum opDollar = DollarToken.init;
+        auto opSlice(size_t, DollarToken) { return 1; }
+        auto opSlice(size_t, size_t) { return 2; }
+    }
+
+    Typedef!Dollar1 drange1;
+    assert(drange1[0..$] == 1);
+    assert(drange1[0..1] == 2);
+
+    static struct Dollar2
+    {
+        size_t opDollar(size_t pos)() { return pos == 0 ? 1 : 100; }
+        size_t opIndex(size_t i, size_t j) { return i + j; }
+    }
+
+    Typedef!Dollar2 drange2;
+    assert(drange2[$, $] == 101);
+
+    static struct Dollar3
+    {
+        size_t opDollar() { return 123; }
+        size_t opIndex(size_t i) { return i; }
+    }
+
+    Typedef!Dollar3 drange3;
+    assert(drange3[$] == 123);
+}
+
+unittest
+{
+    // bug8655
+    import std.typecons;
+    import std.bitmanip;
+    static import core.stdc.config;
+
+    alias c_ulong = Typedef!(core.stdc.config.c_ulong);
+
+    static struct Foo
+    {
+        mixin(bitfields!(
+            c_ulong, "NameOffset", 31,
+            c_ulong, "NameIsString", 1
+        ));
+    }
+}
+
+unittest // Issue 12596
+{
+    import std.typecons;
+    alias TD = Typedef!int;
+    TD x = TD(1);
+    TD y = TD(x);
+    assert(x == y);
+}
+
+unittest // about toHash
+{
+    import std.typecons;
+    {
+        alias TD = Typedef!int;
+        int[TD] td;
+        td[TD(1)] = 1;
+        assert(td[TD(1)] == 1);
+    }
+
+    {
+        alias TD = Typedef!(int[]);
+        int[TD] td;
+        td[TD([1,2,3,4])] = 2;
+        assert(td[TD([1,2,3,4])] == 2);
+    }
+
+    {
+        alias TD = Typedef!(int[][]);
+        int[TD] td;
+        td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] = 3;
+        assert(td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] == 3);
+    }
+
+    {
+        struct MyStruct{ int x; }
+        alias TD = Typedef!MyStruct;
+        int[TD] td;
+        td[TD(MyStruct(10))] = 4;
+        assert(TD(MyStruct(20)) !in td);
+        assert(td[TD(MyStruct(10))] == 4);
+    }
+
+    {
+        static struct MyStruct2
+        {
+            int x;
+            size_t toHash() const nothrow @safe { return x; }
+            bool opEquals(ref const MyStruct2 r) const { return r.x == x; }
+        }
+
+        alias TD = Typedef!MyStruct2;
+        int[TD] td;
+        td[TD(MyStruct2(50))] = 5;
+        assert(td[TD(MyStruct2(50))] == 5);
+    }
+
+    {
+        class MyClass{}
+        alias TD = Typedef!MyClass;
+        int[TD] td;
+        auto c = new MyClass;
+        td[TD(c)] = 6;
+        assert(TD(new MyClass) !in td);
+        assert(td[TD(c)] == 6);
+    }
+}
+
+unittest
+{
+    alias String = Typedef!(char[]);
+    alias CString = Typedef!(const(char)[]);
+    CString cs = "fubar";
+    String s = cast(String)cs;
+    assert(cs == s);
+    char[] s2 = cast(char[])cs;
+    const(char)[] cs2 = cast(const(char)[])s;
+    assert(s2 == cs2);
+}
+
+/**
+Allocates a $(D class) object right inside the current scope,
+therefore avoiding the overhead of $(D new). This facility is unsafe;
+it is the responsibility of the user to not escape a reference to the
+object outside the scope.
+
+Note: it's illegal to move a class reference even if you are sure there
+are no pointers to it. As such, it is illegal to move a scoped object.
+ */
+template scoped(T)
+    if (is(T == class))
+{
+    // _d_newclass now use default GC alignment (looks like (void*).sizeof * 2 for
+    // small objects). We will just use the maximum of filed alignments.
+    alias alignment = classInstanceAlignment!T;
+    alias aligned = _alignUp!alignment;
+
+    static struct Scoped
+    {
+        // Addition of `alignment` is required as `Scoped_store` can be misaligned in memory.
+        private void[aligned(__traits(classInstanceSize, T) + size_t.sizeof) + alignment] Scoped_store = void;
+
+        @property inout(T) Scoped_payload() inout
+        {
+            void* alignedStore = cast(void*) aligned(cast(size_t) Scoped_store.ptr);
+            // As `Scoped` can be unaligned moved in memory class instance should be moved accordingly.
+            immutable size_t d = alignedStore - Scoped_store.ptr;
+            size_t* currD = cast(size_t*) &Scoped_store[$ - size_t.sizeof];
+            if(d != *currD)
+            {
+                import core.stdc.string;
+                memmove(alignedStore, Scoped_store.ptr + *currD, __traits(classInstanceSize, T));
+                *currD = d;
+            }
+            return cast(inout(T)) alignedStore;
+        }
+        alias Scoped_payload this;
+
+        @disable this();
+        @disable this(this);
+
+        ~this()
+        {
+            // `destroy` will also write .init but we have no functions in druntime
+            // for deterministic finalization and memory releasing for now.
+            .destroy(Scoped_payload);
+        }
+    }
+
+    /// Returns the scoped object
+    @system auto scoped(Args...)(auto ref Args args)
+    {
+        import std.conv : emplace;
+
+        Scoped result = void;
+        void* alignedStore = cast(void*) aligned(cast(size_t) result.Scoped_store.ptr);
+        immutable size_t d = alignedStore - result.Scoped_store.ptr;
+        *cast(size_t*) &result.Scoped_store[$ - size_t.sizeof] = d;
+        emplace!(Unqual!T)(result.Scoped_store[d .. $ - size_t.sizeof], args);
+        return result;
+    }
+}
+///
+unittest
+{
+    class A
+    {
+        int x;
+        this()     {x = 0;}
+        this(int i){x = i;}
+    }
+
+    // Standard usage
+    auto a1 = scoped!A();
+    auto a2 = scoped!A(1);
+    a1.x = 42;
+    assert(a1.x == 42);
+    assert(a2.x ==  1);
+
+    // Restrictions
+    static assert(!is(typeof({
+        auto e1 = a1; // illegal, scoped objects can't be copied
+        assert([a2][0].x == 42); // ditto
+        alias ScopedObject = typeof(a1);
+        auto e2 = ScopedObject();  //Illegal, must be built via scoped!A
+        auto e3 = ScopedObject(1); //Illegal, must be built via scoped!A
+    })));
+
+    // Use as member variable
+    struct B
+    {
+        typeof(scoped!A()) a; // note the trailing parentheses
+    }
+
+    // Use with alias
+    alias makeScopedA = scoped!A;
+    auto a6 = makeScopedA();
+    auto a7 = makeScopedA();
+}
+
+private size_t _alignUp(size_t alignment)(size_t n)
+    if(alignment > 0 && !((alignment - 1) & alignment))
+{
+    enum badEnd = alignment - 1; // 0b11, 0b111, ...
+    return (n + badEnd) & ~badEnd;
+}
+
+unittest // Issue 6580 testcase
+{
+    enum alignment = (void*).alignof;
+
+    static class C0 { }
+    static class C1 { byte b; }
+    static class C2 { byte[2] b; }
+    static class C3 { byte[3] b; }
+    static class C7 { byte[7] b; }
+    static assert(scoped!C0().sizeof % alignment == 0);
+    static assert(scoped!C1().sizeof % alignment == 0);
+    static assert(scoped!C2().sizeof % alignment == 0);
+    static assert(scoped!C3().sizeof % alignment == 0);
+    static assert(scoped!C7().sizeof % alignment == 0);
+
+    enum longAlignment = long.alignof;
+    static class C1long
+    {
+        long long_; byte byte_ = 4;
+        this() { }
+        this(long _long, ref int i) { long_ = _long; ++i; }
+    }
+    static class C2long { byte[2] byte_ = [5, 6]; long long_ = 7; }
+    static assert(scoped!C1long().sizeof % longAlignment == 0);
+    static assert(scoped!C2long().sizeof % longAlignment == 0);
+
+    void alignmentTest()
+    {
+        int var = 5;
+        auto c1long = scoped!C1long(3, var);
+        assert(var == 6);
+        auto c2long = scoped!C2long();
+        assert(cast(size_t)&c1long.long_ % longAlignment == 0);
+        assert(cast(size_t)&c2long.long_ % longAlignment == 0);
+        assert(c1long.long_ == 3 && c1long.byte_ == 4);
+        assert(c2long.byte_ == [5, 6] && c2long.long_ == 7);
+    }
+
+    alignmentTest();
+
+    version(DigitalMars)
+    {
+        void test(size_t size)
+        {
+            import core.stdc.stdlib;
+            alloca(size);
+            alignmentTest();
+        }
+        foreach(i; 0 .. 10)
+            test(i);
+    }
+    else
+    {
+        void test(size_t size)()
+        {
+            byte[size] arr;
+            alignmentTest();
+        }
+        foreach(i; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
+            test!i();
+    }
+}
+
+unittest // Original Issue 6580 testcase
+{
+    class C { int i; byte b; }
+
+    auto sa = [scoped!C(), scoped!C()];
+    assert(cast(size_t)&sa[0].i % int.alignof == 0);
+    assert(cast(size_t)&sa[1].i % int.alignof == 0); // fails
+}
+
+unittest
+{
+    class A { int x = 1; }
+    auto a1 = scoped!A();
+    assert(a1.x == 1);
+    auto a2 = scoped!A();
+    a1.x = 42;
+    a2.x = 53;
+    assert(a1.x == 42);
+}
+
+unittest
+{
+    class A { int x = 1; this() { x = 2; } }
+    auto a1 = scoped!A();
+    assert(a1.x == 2);
+    auto a2 = scoped!A();
+    a1.x = 42;
+    a2.x = 53;
+    assert(a1.x == 42);
+}
+
+unittest
+{
+    class A { int x = 1; this(int y) { x = y; } ~this() {} }
+    auto a1 = scoped!A(5);
+    assert(a1.x == 5);
+    auto a2 = scoped!A(42);
+    a1.x = 42;
+    a2.x = 53;
+    assert(a1.x == 42);
+}
+
+unittest
+{
+    class A { static bool dead; ~this() { dead = true; } }
+    class B : A { static bool dead; ~this() { dead = true; } }
+    {
+        auto b = scoped!B();
+    }
+    assert(B.dead, "asdasd");
+    assert(A.dead, "asdasd");
+}
+
+unittest // Issue 8039 testcase
+{
+    static int dels;
+    static struct S { ~this(){ ++dels; } }
+
+    static class A { S s; }
+    dels = 0; { scoped!A(); }
+    assert(dels == 1);
+
+    static class B { S[2] s; }
+    dels = 0; { scoped!B(); }
+    assert(dels == 2);
+
+    static struct S2 { S[3] s; }
+    static class C { S2[2] s; }
+    dels = 0; { scoped!C(); }
+    assert(dels == 6);
+
+    static class D: A { S2[2] s; }
+    dels = 0; { scoped!D(); }
+    assert(dels == 1+6);
+}
+
+unittest
+{
+    // bug4500
+    class A
+    {
+        this() { a = this; }
+        this(int i) { a = this; }
+        A a;
+        bool check() { return this is a; }
+    }
+
+    auto a1 = scoped!A();
+    assert(a1.check());
+
+    auto a2 = scoped!A(1);
+    assert(a2.check());
+
+    a1.a = a1;
+    assert(a1.check());
+}
+
+unittest
+{
+    static class A
+    {
+        static int sdtor;
+
+        this() { ++sdtor; assert(sdtor == 1); }
+        ~this() { assert(sdtor == 1); --sdtor; }
+    }
+
+    interface Bob {}
+
+    static class ABob : A, Bob
+    {
+        this() { ++sdtor; assert(sdtor == 2); }
+        ~this() { assert(sdtor == 2); --sdtor; }
+    }
+
+    A.sdtor = 0;
+    scope(exit) assert(A.sdtor == 0);
+    auto abob = scoped!ABob();
+}
+
+unittest
+{
+    static class A { this(int) {} }
+    static assert(!__traits(compiles, scoped!A()));
+}
+
+unittest
+{
+    static class A { @property inout(int) foo() inout { return 1; } }
+
+    auto a1 = scoped!A();
+    assert(a1.foo == 1);
+    static assert(is(typeof(a1.foo) == int));
+
+    auto a2 = scoped!(const(A))();
+    assert(a2.foo == 1);
+    static assert(is(typeof(a2.foo) == const(int)));
+
+    auto a3 = scoped!(immutable(A))();
+    assert(a3.foo == 1);
+    static assert(is(typeof(a3.foo) == immutable(int)));
+
+    const c1 = scoped!A();
+    assert(c1.foo == 1);
+    static assert(is(typeof(c1.foo) == const(int)));
+
+    const c2 = scoped!(const(A))();
+    assert(c2.foo == 1);
+    static assert(is(typeof(c2.foo) == const(int)));
+
+    const c3 = scoped!(immutable(A))();
+    assert(c3.foo == 1);
+    static assert(is(typeof(c3.foo) == immutable(int)));
+}
+
+unittest
+{
+    class C { this(ref int val) { assert(val == 3); ++val; } }
+
+    int val = 3;
+    auto s = scoped!C(val);
+    assert(val == 4);
+}
+
+unittest
+{
+    class C
+    {
+        this(){}
+        this(int){}
+        this(int, int){}
+    }
+    alias makeScopedC = scoped!C;
+
+    auto a = makeScopedC();
+    auto b = makeScopedC(1);
+    auto c = makeScopedC(1, 1);
+
+    static assert(is(typeof(a) == typeof(b)));
+    static assert(is(typeof(b) == typeof(c)));
+}
+
+/**
+Defines a simple, self-documenting yes/no flag. This makes it easy for
+APIs to define functions accepting flags without resorting to $(D
+bool), which is opaque in calls, and without needing to define an
+enumerated type separately. Using $(D Flag!"Name") instead of $(D
+bool) makes the flag's meaning visible in calls. Each yes/no flag has
+its own type, which makes confusions and mix-ups impossible.
+
+Example:
+
+Code calling $(D getLine) (usually far away from its definition) can't be
+understood without looking at the documentation, even by users familiar with
+the API:
+----
+string getLine(bool keepTerminator)
+{
+    ...
+    if (keepTerminator) ...
+    ...
+}
+...
+auto line = getLine(false);
+----
+
+Assuming the reverse meaning (i.e. "ignoreTerminator") and inserting the wrong
+code compiles and runs with erroneous results.
+
+After replacing the boolean parameter with an instantiation of $(D Flag), code
+calling $(D getLine) can be easily read and understood even by people not
+fluent with the API:
+
+----
+string getLine(Flag!"keepTerminator" keepTerminator)
+{
+    ...
+    if (keepTerminator) ...
+    ...
+}
+...
+auto line = getLine(Yes.keepTerminator);
+----
+
+The structs $(D Yes) and $(D No) are provided as shorthand for
+$(D Flag!"Name".yes) and $(D Flag!"Name".no) and are preferred for brevity and
+readability. These convenience structs mean it is usually unnecessary and
+counterproductive to create an alias of a $(D Flag) as a way of avoiding typing
+out the full type while specifying the affirmative or negative options.
+
+Passing categorical data by means of unstructured $(D bool)
+parameters is classified under "simple-data coupling" by Steve
+McConnell in the $(LUCKY Code Complete) book, along with three other
+kinds of coupling. The author argues citing several studies that
+coupling has a negative effect on code quality. $(D Flag) offers a
+simple structuring method for passing yes/no flags to APIs.
+ */
+template Flag(string name) {
+    ///
+    enum Flag : bool
+    {
+        /**
+         When creating a value of type $(D Flag!"Name"), use $(D
+         Flag!"Name".no) for the negative option. When using a value
+         of type $(D Flag!"Name"), compare it against $(D
+         Flag!"Name".no) or just $(D false) or $(D 0).  */
+        no = false,
+
+        /** When creating a value of type $(D Flag!"Name"), use $(D
+         Flag!"Name".yes) for the affirmative option. When using a
+         value of type $(D Flag!"Name"), compare it against $(D
+         Flag!"Name".yes).
+        */
+        yes = true
+    }
+}
+
+/**
+Convenience names that allow using e.g. $(D Yes.encryption) instead of
+$(D Flag!"encryption".yes) and $(D No.encryption) instead of $(D
+Flag!"encryption".no).
+*/
+struct Yes
+{
+    template opDispatch(string name)
+    {
+        enum opDispatch = Flag!name.yes;
+    }
+}
+//template yes(string name) { enum Flag!name yes = Flag!name.yes; }
+
+/// Ditto
+struct No
+{
+    template opDispatch(string name)
+    {
+        enum opDispatch = Flag!name.no;
+    }
+}
+
+///
+unittest
+{
+    Flag!"abc" flag1;
+    assert(flag1 == Flag!"abc".no);
+    assert(flag1 == No.abc);
+    assert(!flag1);
+    if (flag1) assert(false);
+    flag1 = Yes.abc;
+    assert(flag1);
+    if (!flag1) assert(false);
+    if (flag1) {} else assert(false);
+    assert(flag1 == Yes.abc);
+}
+
+/**
+Detect whether an enum is of integral type and has only "flag" values
+(i.e. values with a bit count of exactly 1).
+Additionally, a zero value is allowed for compatibility with enums including
+a "None" value.
+*/
+template isBitFlagEnum(E)
+{
+    static if (is(E Base == enum) && isIntegral!Base)
+    {
+        enum isBitFlagEnum = (E.min >= 0) &&
+        {
+            foreach (immutable flag; EnumMembers!E)
+            {
+                Base value = flag;
+                value &= value - 1;
+                if (value != 0) return false;
+            }
+            return true;
+        }();
+    }
+    else
+    {
+        enum isBitFlagEnum = false;
+    }
+}
+
+///
+@safe pure nothrow unittest
+{
+    enum A
+    {
+        None,
+        A = 1<<0,
+        B = 1<<1,
+        C = 1<<2,
+        D = 1<<3,
+    }
+
+    static assert(isBitFlagEnum!A);
+
+    enum B
+    {
+        A,
+        B,
+        C,
+        D // D == 3
+    }
+
+    static assert(!isBitFlagEnum!B);
+
+    enum C: double
+    {
+        A = 1<<0,
+        B = 1<<1
+    }
+
+    static assert(!isBitFlagEnum!C);
+}
+
+/**
+A typesafe structure for storing combinations of enum values.
+
+This template defines a simple struct to represent bitwise OR combinations of
+enum values. It can be used if all the enum values are integral constants with
+a bit count of at most 1, or if the $(D unsafe) parameter is explicitly set to
+Yes.
+This is much safer than using the enum itself to store
+the OR combination, which can produce surprising effects like this:
+----
+enum E
+{
+    A = 1<<0,
+    B = 1<<1
+}
+E e = E.A | E.B;
+// will throw SwitchError
+final switch(e)
+{
+    case E.A:
+        return;
+    case E.B:
+        return;
+}
+----
+*/
+struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) if (unsafe || isBitFlagEnum!(E))
+{
+@safe @nogc pure nothrow:
+private:
+    enum isBaseEnumType(T) = is(E == T);
+    alias Base = OriginalType!E;
+    Base mValue;
+    static struct Negation
+    {
+    @safe @nogc pure nothrow:
+    private:
+        Base mValue;
+
+        // Prevent non-copy construction outside the module.
+        @disable this();
+        this(Base value)
+        {
+            mValue = value;
+        }
+    }
+
+public:
+    this(E flag)
+    {
+        this = flag;
+    }
+
+    this(T...)(T flags)
+        if (allSatisfy!(isBaseEnumType, T))
+    {
+        this = flags;
+    }
+
+    bool opCast(B: bool)() const
+    {
+        return mValue != 0;
+    }
+
+    Base opCast(B)() const
+        if (isImplicitlyConvertible!(Base, B))
+    {
+        return mValue;
+    }
+
+    Negation opUnary(string op)() const
+        if (op == "~")
+    {
+        return Negation(~mValue);
+    }
+
+    auto ref opAssign(T...)(T flags)
+        if (allSatisfy!(isBaseEnumType, T))
+    {
+        mValue = 0;
+        foreach (E flag; flags)
+        {
+            mValue |= flag;
+        }
+        return this;
+    }
+
+    auto ref opAssign(E flag)
+    {
+        mValue = flag;
+        return this;
+    }
+
+    auto ref opOpAssign(string op: "|")(BitFlags flags)
+    {
+        mValue |= flags.mValue;
+        return this;
+    }
+
+    auto ref opOpAssign(string op: "&")(BitFlags  flags)
+    {
+        mValue &= flags.mValue;
+        return this;
+    }
+
+    auto ref opOpAssign(string op: "|")(E flag)
+    {
+        mValue |= flag;
+        return this;
+    }
+
+    auto ref opOpAssign(string op: "&")(E flag)
+    {
+        mValue &= flag;
+        return this;
+    }
+
+    auto ref opOpAssign(string op: "&")(Negation negatedFlags)
+    {
+        mValue &= negatedFlags.mValue;
+        return this;
+    }
+
+    auto opBinary(string op)(BitFlags flags) const
+        if (op == "|" || op == "&")
+    {
+        BitFlags result = this;
+        result.opOpAssign!op(flags);
+        return result;
+    }
+
+    auto opBinary(string op)(E flag) const
+        if (op == "|" || op == "&")
+    {
+        BitFlags result = this;
+        result.opOpAssign!op(flag);
+        return result;
+    }
+
+    auto opBinary(string op: "&")(Negation negatedFlags) const
+    {
+        BitFlags result = this;
+        result.opOpAssign!op(negatedFlags);
+        return result;
+    }
+
+    auto opBinaryRight(string op)(E flag) const
+        if (op == "|" || op == "&")
+    {
+        return opBinary!op(flag);
+    }
+}
+
+/// BitFlags can be manipulated with the usual operators
+@safe @nogc pure nothrow unittest
+{
+    // You can use such an enum with BitFlags straight away
+    enum Enum
+    {
+        None,
+        A = 1<<0,
+        B = 1<<1,
+        C = 1<<2
+    }
+    BitFlags!Enum flags1;
+    assert(!(flags1 & (Enum.A | Enum.B | Enum.C)));
+
+    // You need to specify the $(D unsafe) parameter for enum with custom values
+    enum UnsafeEnum
+    {
+        A,
+        B,
+        C,
+        D = B|C
+    }
+    static assert(!__traits(compiles, { BitFlags!UnsafeEnum flags2; }));
+    BitFlags!(UnsafeEnum, Yes.unsafe) flags3;
+
+    immutable BitFlags!Enum flags_empty;
+    // A default constructed BitFlags has no value set
+    assert(!(flags_empty & Enum.A) && !(flags_empty & Enum.B) && !(flags_empty & Enum.C));
+
+    // Value can be set with the | operator
+    immutable BitFlags!Enum flags_A = flags_empty | Enum.A;
+
+    // And tested with the & operator
+    assert(flags_A & Enum.A);
+
+    // Which commutes
+    assert(Enum.A & flags_A);
+
+    // BitFlags can be variadically initialized
+    immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B);
+    assert((flags_AB & Enum.A) && (flags_AB & Enum.B) && !(flags_AB & Enum.C));
+
+    // Use the ~ operator for subtracting flags
+    immutable BitFlags!Enum flags_B = flags_AB & ~BitFlags!Enum(Enum.A);
+    assert(!(flags_B & Enum.A) && (flags_B & Enum.B) && !(flags_B & Enum.C));
+
+    // You can use the EnumMembers template to set all flags
+    immutable BitFlags!Enum flags_all = EnumMembers!Enum;
+
+    // use & between BitFlags for intersection
+    immutable BitFlags!Enum flags_BC = BitFlags!Enum(Enum.B, Enum.C);
+    assert (flags_B == (flags_BC & flags_AB));
+
+    // All the binary operators work in their assignment version
+    BitFlags!Enum temp = flags_empty;
+    temp |= flags_AB;
+    assert(temp == (flags_empty | flags_AB));
+    temp = flags_empty;
+    temp |= Enum.B;
+    assert(temp == (flags_empty | Enum.B));
+    temp = flags_empty;
+    temp &= flags_AB;
+    assert(temp == (flags_empty & flags_AB));
+    temp = flags_empty;
+    temp &= Enum.A;
+    assert(temp == (flags_empty & Enum.A));
+
+    // BitFlags with no value set evaluate to false
+    assert(!flags_empty);
+
+    // BitFlags with at least one value set evaluate to true
+    assert(flags_A);
+
+    // This can be useful to check intersection between BitFlags
+    assert(flags_A & flags_AB);
+    assert(flags_AB & Enum.A);
+
+    // Finally, you can of course get you raw value out of flags
+    auto value = cast(int)flags_A;
+    assert(value == Enum.A);
+}
+
+// ReplaceType
+/**
+Replaces all occurrences of `From` into `To`, in one or more types `T`. For
+example, $(D ReplaceType!(int, uint, Tuple!(int, float)[string])) yields
+$(D Tuple!(uint, float)[string]). The types in which replacement is performed
+may be arbitrarily complex, including qualifiers, built-in type constructors
+(pointers, arrays, associative arrays, functions, and delegates), and template
+instantiations; replacement proceeds transitively through the type definition.
+However, member types in `struct`s or `class`es are not replaced because there
+are no ways to express the types resulting after replacement.
+
+This is an advanced type manipulation necessary e.g. for replacing the
+placeholder type `This` in $(XREF variant, Algebraic).
+
+Returns: `ReplaceType` aliases itself to the type(s) that result after
+replacement.
+*/
+template ReplaceType(From, To, T...)
+{
+    static if (T.length == 1)
+    {
+        static if (is(T[0] == From))
+            alias ReplaceType = To;
+        else static if (is(T[0] == const(U), U))
+            alias ReplaceType = const(ReplaceType!(From, To, U));
+        else static if (is(T[0] == immutable(U), U))
+            alias ReplaceType = immutable(ReplaceType!(From, To, U));
+        else static if (is(T[0] == shared(U), U))
+            alias ReplaceType = shared(ReplaceType!(From, To, U));
+        else static if (is(T[0] == U*, U))
+        {
+            static if (is(U == function))
+                alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]);
+            else
+                alias ReplaceType = ReplaceType!(From, To, U)*;
+        }
+        else static if (is(T[0] == delegate))
+        {
+            alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]);
+        }
+        else static if (is(T[0] == function))
+        {
+            static assert(0, "Function types not supported,"
+                " use a function pointer type instead of "~T[0].stringof);
+        }
+        else static if (is(T[0] : U!V, alias U, V...))
+        {
+            template replaceTemplateArgs(T...)
+            {
+                static if (is(typeof(T[0])))    // template argument is value or symbol
+                    enum replaceTemplateArgs = T[0];
+                else
+                    alias replaceTemplateArgs = ReplaceType!(From, To, T[0]);
+            }
+            alias ReplaceType = U!(staticMap!(replaceTemplateArgs, V));
+        }
+        else static if (is(T[0] == struct))
+            // don't match with alias this struct below (Issue 15168)
+            alias ReplaceType = T[0];
+        else static if (is(T[0] == U[], U))
+            alias ReplaceType = ReplaceType!(From, To, U)[];
+        else static if (is(T[0] == U[n], U, size_t n))
+            alias ReplaceType = ReplaceType!(From, To, U)[n];
+        else static if (is(T[0] == U[V], U, V))
+            alias ReplaceType =
+                ReplaceType!(From, To, U)[ReplaceType!(From, To, V)];
+        else
+            alias ReplaceType = T[0];
+    }
+    else static if (T.length > 1)
+    {
+        alias ReplaceType = AliasSeq!(ReplaceType!(From, To, T[0]),
+            ReplaceType!(From, To, T[1 .. $]));
+    }
+    else
+    {
+        alias ReplaceType = AliasSeq!();
+    }
+}
+
+///
+unittest
+{
+    static assert(
+        is(ReplaceType!(int, string, int[]) == string[]) &&
+        is(ReplaceType!(int, string, int[int]) == string[string]) &&
+        is(ReplaceType!(int, string, const(int)[]) == const(string)[]) &&
+        is(ReplaceType!(int, string, Tuple!(int[], float))
+            == Tuple!(string[], float))
+    );
+}
+
+private template replaceTypeInFunctionType(From, To, fun)
+{
+    alias RX = ReplaceType!(From, To, ReturnType!fun);
+    alias PX = AliasSeq!(ReplaceType!(From, To, Parameters!fun));
+    // Wrapping with AliasSeq is neccesary because ReplaceType doesn't return
+    // tuple if Parameters!fun.length == 1
+
+    string gen()
+    {
+        enum  linkage = functionLinkage!fun;
+        alias attributes = functionAttributes!fun;
+        enum  variadicStyle = variadicFunctionStyle!fun;
+        alias storageClasses = ParameterStorageClassTuple!fun;
+
+        string result;
+
+        result ~= "extern(" ~ linkage ~ ") ";
+        static if (attributes & FunctionAttribute.ref_)
+        {
+            result ~= "ref ";
+        }
+
+        result ~= "RX";
+        static if (is(fun == delegate))
+            result ~= " delegate";
+        else
+            result ~= " function";
+
+        result ~= "(";
+        foreach (i, _; PX)
+        {
+            if (i)
+                result ~= ", ";
+            if (storageClasses[i] & ParameterStorageClass.scope_)
+                result ~= "scope ";
+            if (storageClasses[i] & ParameterStorageClass.out_)
+                result ~= "out ";
+            if (storageClasses[i] & ParameterStorageClass.ref_)
+                result ~= "ref ";
+            if (storageClasses[i] & ParameterStorageClass.lazy_)
+                result ~= "lazy ";
+            if (storageClasses[i] & ParameterStorageClass.return_)
+                result ~= "return ";
+
+            result ~= "PX[" ~ i.stringof ~ "]";
+        }
+        static if (variadicStyle == Variadic.typesafe)
+            result ~= " ...";
+        else static if (variadicStyle != Variadic.no)
+            result ~= ", ...";
+        result ~= ")";
+
+        static if (attributes & FunctionAttribute.pure_)
+            result ~= " pure";
+        static if (attributes & FunctionAttribute.nothrow_)
+            result ~= " nothrow";
+        static if (attributes & FunctionAttribute.property)
+            result ~= " @property";
+        static if (attributes & FunctionAttribute.trusted)
+            result ~= " @trusted";
+        static if (attributes & FunctionAttribute.safe)
+            result ~= " @safe";
+        static if (attributes & FunctionAttribute.nogc)
+            result ~= " @nogc";
+        static if (attributes & FunctionAttribute.system)
+            result ~= " @system";
+        static if (attributes & FunctionAttribute.const_)
+            result ~= " @const";
+        static if (attributes & FunctionAttribute.immutable_)
+            result ~= " immutable";
+        static if (attributes & FunctionAttribute.inout_)
+            result ~= " inout";
+        static if (attributes & FunctionAttribute.shared_)
+            result ~= " shared";
+        static if (attributes & FunctionAttribute.return_)
+            result ~= " return";
+
+        return result;
+    }
+    //pragma(msg, "gen ==> ", gen());
+
+    mixin("alias replaceTypeInFunctionType = " ~ gen() ~ ";");
+}
+
+unittest
+{
+    template Test(Ts...)
+    {
+        static if (Ts.length)
+        {
+            //pragma(msg, "Testing: ReplaceType!("~Ts[0].stringof~", "
+            //    ~Ts[1].stringof~", "~Ts[2].stringof~")");
+            static assert(is(ReplaceType!(Ts[0], Ts[1], Ts[2]) == Ts[3]),
+                "ReplaceType!("~Ts[0].stringof~", "~Ts[1].stringof~", "
+                    ~Ts[2].stringof~") == "
+                    ~ReplaceType!(Ts[0], Ts[1], Ts[2]).stringof);
+            alias Test = Test!(Ts[4 .. $]);
+        }
+        else alias Test = void;
+    }
+
+    //import core.stdc.stdio;
+    alias RefFun1 = ref int function(float, long);
+    alias RefFun2 = ref float function(float, long);
+    extern(C) int printf(const char*, ...) nothrow @nogc @system;
+    extern(C) float floatPrintf(const char*, ...) nothrow @nogc @system;
+    int func(float);
+
+    int x;
+    struct S1 { void foo() { x = 1; } }
+    struct S2 { void bar() { x = 2; } }
+
+    alias Pass = Test!(
+        int, float, typeof(&func), float delegate(float),
+        int, float, typeof(&printf), typeof(&floatPrintf),
+        int, float, int function(out long, ...),
+            float function(out long, ...),
+        int, float, int function(ref float, long),
+            float function(ref float, long),
+        int, float, int function(ref int, long),
+            float function(ref float, long),
+        int, float, int function(out int, long),
+            float function(out float, long),
+        int, float, int function(lazy int, long),
+            float function(lazy float, long),
+        int, float, int function(out long, ref const int),
+            float function(out long, ref const float),
+        int, int, int, int,
+        int, float, int, float,
+        int, float, const int, const float,
+        int, float, immutable int, immutable float,
+        int, float, shared int, shared float,
+        int, float, int*, float*,
+        int, float, const(int)*, const(float)*,
+        int, float, const(int*), const(float*),
+        const(int)*, float, const(int*), const(float),
+        int*, float, const(int)*, const(int)*,
+        int, float, int[], float[],
+        int, float, int[42], float[42],
+        int, float, const(int)[42], const(float)[42],
+        int, float, const(int[42]), const(float[42]),
+        int, float, int[int], float[float],
+        int, float, int[double], float[double],
+        int, float, double[int], double[float],
+        int, float, int function(float, long), float function(float, long),
+        int, float, int function(float), float function(float),
+        int, float, int function(float, int), float function(float, float),
+        int, float, int delegate(float, long), float delegate(float, long),
+        int, float, int delegate(float), float delegate(float),
+        int, float, int delegate(float, int), float delegate(float, float),
+        int, float, Unique!int, Unique!float,
+        int, float, Tuple!(float, int), Tuple!(float, float),
+        int, float, RefFun1, RefFun2,
+        S1, S2,
+            S1[1][][S1]* function(),
+            S2[1][][S2]* function(),
+        int, string,
+               int[3] function(   int[] arr,    int[2] ...) pure @trusted,
+            string[3] function(string[] arr, string[2] ...) pure @trusted,
+    );
+
+    // Bugzilla 15168
+    static struct T1 { string s; alias s this; }
+    static struct T2 { char[10] s; alias s this; }
+    static struct T3 { string[string] s; alias s this; }
+    alias Pass2 = Test!(
+        ubyte, ubyte, T1, T1,
+        ubyte, ubyte, T2, T2,
+        ubyte, ubyte, T3, T3,
+    );
+}
diff --git a/libphobos/src/std/typetuple.d b/libphobos/src/std/typetuple.d
new file mode 100644
index 00000000000..c4cc27955a6
--- /dev/null
+++ b/libphobos/src/std/typetuple.d
@@ -0,0 +1,38 @@
+/**
+ * This module was renamed to disambiguate the term tuple, use
+ * $(LINK2 std_meta.html, std.meta) instead.
+ *
+ * Copyright: Copyright Digital Mars 2005 - 2015.
+ * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:
+ * Source:    $(PHOBOSSRC std/_typetuple.d)
+ */
+module std.typetuple;
+
+public import std.meta;
+
+/**
+ * Alternate name for $(XREF meta,AliasSeq) for legacy compatibility.
+ */
+alias TypeTuple = AliasSeq;
+
+///
+unittest
+{
+    import std.typetuple;
+    alias TL = TypeTuple!(int, double);
+
+    int foo(TL td)  // same as int foo(int, double);
+    {
+        return td[0] + cast(int)td[1];
+    }
+}
+
+///
+unittest
+{
+    alias TL = TypeTuple!(int, double);
+
+    alias Types = TypeTuple!(TL, char);
+    static assert(is(Types == TypeTuple!(int, double, char)));
+}
diff --git a/libphobos/src/std/uni.d b/libphobos/src/std/uni.d
new file mode 100644
index 00000000000..4e9a98910a4
--- /dev/null
+++ b/libphobos/src/std/uni.d
@@ -0,0 +1,9446 @@
+// Written in the D programming language.
+
+/++
+    $(P The $(D std.uni) module provides an implementation
+    of fundamental Unicode algorithms and data structures.
+    This doesn't include UTF encoding and decoding primitives,
+    see $(XREF _utf, decode) and $(XREF _utf, encode) in std.utf
+    for this functionality. )
+
+    $(P All primitives listed operate on Unicode characters and
+    sets of characters. For functions which operate on ASCII characters
+    and ignore Unicode $(CHARACTERS), see $(LINK2 std_ascii.html, std.ascii).
+    For definitions of Unicode $(CHARACTER), $(CODEPOINT) and other terms
+    used throughout this module see the $(S_LINK Terminology, terminology) section
+    below.
+    )
+
+    $(P The focus of this module is the core needs of developing Unicode-aware
+    applications. To that effect it provides the following optimized primitives:
+    )
+    $(UL
+    $(LI Character classification by category and common properties:
+        $(LREF isAlpha), $(LREF isWhite) and others.
+    )
+    $(LI
+        Case-insensitive string comparison ($(LREF sicmp), $(LREF icmp)).
+    )
+    $(LI
+        Converting text to any of the four normalization forms via $(LREF normalize).
+    )
+    $(LI
+        Decoding ($(LREF decodeGrapheme))  and iteration ($(LREF byGrapheme), $(LREF graphemeStride))
+        by user-perceived characters, that is by $(LREF Grapheme) clusters.
+    )
+    $(LI
+        Decomposing and composing of individual character(s) according to canonical
+        or compatibility rules, see $(LREF compose) and $(LREF decompose),
+        including the specific version for Hangul syllables $(LREF composeJamo)
+        and $(LREF decomposeHangul).
+    )
+    )
+    $(P It's recognized that an application may need further enhancements
+    and extensions, such as less commonly known algorithms,
+    or tailoring existing ones for region specific needs. To help users
+    with building any extra functionality beyond the core primitives,
+    the module provides:
+    )
+    $(UL
+    $(LI
+        $(LREF CodepointSet), a type for easy manipulation of sets of characters.
+        Besides the typical set algebra it provides an unusual feature:
+        a D source code generator for detection of $(CODEPOINTS) in this set.
+        This is a boon for meta-programming parser frameworks,
+        and is used internally to power classification in small
+        sets like $(LREF isWhite).
+    )
+    $(LI
+        A way to construct optimal packed multi-stage tables also known as a
+        special case of $(LUCKY Trie).
+        The functions $(LREF codepointTrie), $(LREF codepointSetTrie)
+        construct custom tries that map dchar to value.
+        The end result is a fast and predictable $(BIGOH 1) lookup that powers
+        functions like $(LREF isAlpha) and $(LREF combiningClass),
+        but for user-defined data sets.
+    )
+    $(LI
+        A useful technique for Unicode-aware parsers that perform
+        character classification of encoded $(CODEPOINTS)
+        is to avoid unnecassary decoding at all costs.
+        $(LREF utfMatcher) provides an improvement over the usual workflow
+        of decode-classify-process, combining the decoding and classification
+        steps. By extracting necessary bits directly from encoded
+        $(S_LINK Code unit, code units) matchers achieve
+        significant performance improvements. See $(LREF MatcherConcept) for
+        the common interface of UTF matchers.
+    )
+    $(LI
+        Generally useful building blocks for customized normalization:
+        $(LREF combiningClass) for querying combining class
+        and $(LREF allowedIn) for testing the Quick_Check
+        property of a given normalization form.
+    )
+    $(LI
+        Access to a large selection of commonly used sets of $(CODEPOINTS).
+        $(S_LINK Unicode properties, Supported sets) include Script,
+        Block and General Category. The exact contents of a set can be
+        observed in the CLDR utility, on the
+        $(WEB www.unicode.org/cldr/utility/properties.jsp, property index) page
+        of the Unicode website.
+        See $(LREF unicode) for easy and (optionally) compile-time checked set
+        queries.
+    )
+    )
+    $(SECTION Synopsis)
+    ---
+    import std.uni;
+    void main()
+    {
+        // initialize code point sets using script/block or property name
+        // now 'set' contains code points from both scripts.
+        auto set = unicode("Cyrillic") | unicode("Armenian");
+        // same thing but simpler and checked at compile-time
+        auto ascii = unicode.ASCII;
+        auto currency = unicode.Currency_Symbol;
+
+        // easy set ops
+        auto a = set & ascii;
+        assert(a.empty); // as it has no intersection with ascii
+        a = set | ascii;
+        auto b = currency - a; // subtract all ASCII, Cyrillic and Armenian
+
+        // some properties of code point sets
+        assert(b.length > 45); // 46 items in Unicode 6.1, even more in 6.2
+        // testing presence of a code point in a set
+        // is just fine, it is O(logN)
+        assert(!b['$']);
+        assert(!b['\u058F']); // Armenian dram sign
+        assert(b['¥']);
+
+        // building fast lookup tables, these guarantee O(1) complexity
+        // 1-level Trie lookup table essentially a huge bit-set ~262Kb
+        auto oneTrie = toTrie!1(b);
+        // 2-level far more compact but typically slightly slower
+        auto twoTrie = toTrie!2(b);
+        // 3-level even smaller, and a bit slower yet
+        auto threeTrie = toTrie!3(b);
+        assert(oneTrie['£']);
+        assert(twoTrie['£']);
+        assert(threeTrie['£']);
+
+        // build the trie with the most sensible trie level
+        // and bind it as a functor
+        auto cyrillicOrArmenian = toDelegate(set);
+        auto balance = find!(cyrillicOrArmenian)("Hello ընկեր!");
+        assert(balance == "ընկեր!");
+        // compatible with bool delegate(dchar)
+        bool delegate(dchar) bindIt = cyrillicOrArmenian;
+
+        // Normalization
+        string s = "Plain ascii (and not only), is always normalized!";
+        assert(s is normalize(s));// is the same string
+
+        string nonS = "A\u0308ffin"; // A ligature
+        auto nS = normalize(nonS); // to NFC, the W3C endorsed standard
+        assert(nS == "Äffin");
+        assert(nS != nonS);
+        string composed = "Äffin";
+
+        assert(normalize!NFD(composed) == "A\u0308ffin");
+        // to NFKD, compatibility decomposition useful for fuzzy matching/searching
+        assert(normalize!NFKD("2¹⁰") == "210");
+    }
+    ---
+    $(SECTION Terminology)
+    $(P The following is a list of important Unicode notions
+    and definitions. Any conventions used specifically in this
+    module alone are marked as such. The descriptions are based on the formal
+    definition as found in $(WEB www.unicode.org/versions/Unicode6.2.0/ch03.pdf,
+    chapter three of The Unicode Standard Core Specification.)
+    )
+
+    $(P $(DEF Abstract character) A unit of information used for the organization,
+    control, or representation of textual data.
+    Note that:
+        $(UL
+        $(LI When representing data, the nature of that data
+        is generally symbolic as opposed to some other
+        kind of data (for example, visual).)
+
+        $(LI An abstract character has no concrete form
+        and should not be confused with a $(S_LINK Glyph, glyph).)
+
+        $(LI An abstract character does not necessarily
+        correspond to what a user thinks of as a “character”
+         and should not be confused with a $(LREF Grapheme).)
+
+        $(LI The abstract characters encoded (see Encoded character)
+        are known as Unicode abstract characters.)
+
+        $(LI Abstract characters not directly
+        encoded by the Unicode Standard can often be
+        represented by the use of combining character sequences.)
+        )
+    )
+
+    $(P $(DEF Canonical decomposition)
+    The decomposition of a character or character sequence
+    that results from recursively applying the canonical
+    mappings found in the Unicode Character Database
+    and these described in Conjoining Jamo Behavior
+    (section 12 of
+    $(WEB www.unicode.org/uni2book/ch03.pdf, Unicode Conformance)).
+    )
+
+    $(P $(DEF Canonical composition)
+    The precise definition of the Canonical composition
+    is the algorithm as specified in $(WEB www.unicode.org/uni2book/ch03.pdf,
+    Unicode Conformance) section 11.
+    Informally it's the process that does the reverse of the canonical
+    decomposition with the addition of certain rules
+    that e.g. prevent legacy characters from appearing in the composed result.
+    )
+
+    $(P $(DEF Canonical equivalent)
+    Two character sequences are said to be canonical equivalents if
+    their full canonical decompositions are identical.
+    )
+
+    $(P $(DEF Character) Typically differs by context.
+    For the purpose of this documentation the term $(I character)
+    implies $(I encoded character), that is, a code point having
+    an assigned abstract character (a symbolic meaning).
+    )
+
+    $(P $(DEF Code point) Any value in the Unicode codespace;
+    that is, the range of integers from 0 to 10FFFF (hex).
+    Not all code points are assigned to encoded characters.
+    )
+
+    $(P $(DEF Code unit) The minimal bit combination that can represent
+    a unit of encoded text for processing or interchange.
+    Depending on the encoding this could be:
+    8-bit code units in the UTF-8 ($(D char)),
+    16-bit code units in the UTF-16 ($(D wchar)),
+    and 32-bit code units in the UTF-32 ($(D dchar)).
+    $(I Note that in UTF-32, a code unit is a code point
+    and is represented by the D $(D dchar) type.)
+    )
+
+    $(P $(DEF Combining character) A character with the General Category
+     of Combining Mark(M).
+        $(UL
+            $(LI All characters with non-zero canonical combining class
+            are combining characters, but the reverse is not the case:
+            there are combining characters with a zero combining class.
+            )
+            $(LI These characters are not normally used in isolation
+            unless they are being described. They include such characters
+            as accents, diacritics, Hebrew points, Arabic vowel signs,
+            and Indic matras.
+            )
+        )
+    )
+
+    $(P $(DEF Combining class)
+        A numerical value used by the Unicode Canonical Ordering Algorithm
+        to determine which sequences of combining marks are to be
+        considered canonically equivalent and  which are not.
+    )
+
+    $(P $(DEF Compatibility decomposition)
+    The decomposition of a character or character sequence that results
+    from recursively applying both the compatibility mappings and
+    the canonical mappings found in the Unicode Character Database, and those
+    described in Conjoining Jamo Behavior no characters
+    can be further decomposed.
+    )
+
+    $(P $(DEF Compatibility equivalent)
+    Two character sequences are said to be compatibility
+    equivalents if their full compatibility decompositions are identical.
+    )
+
+    $(P $(DEF Encoded character) An association (or mapping)
+    between an abstract character and a code point.
+    )
+
+    $(P $(DEF Glyph) The actual, concrete image of a glyph representation
+    having been rasterized or otherwise imaged onto some display surface.
+    )
+
+    $(P $(DEF Grapheme base) A character with the property
+     Grapheme_Base, or any standard Korean syllable block.
+    )
+
+    $(P $(DEF Grapheme cluster) Defined as the text between
+        grapheme boundaries  as specified by Unicode Standard Annex #29,
+        $(WEB www.unicode.org/reports/tr29/, Unicode text segmentation).
+        Important general properties of a grapheme:
+        $(UL
+            $(LI The grapheme cluster represents a horizontally segmentable
+            unit of text, consisting of some grapheme base (which may
+            consist of a Korean syllable) together with any number of
+            nonspacing marks applied to it.
+            )
+            $(LI  A grapheme cluster typically starts with a grapheme base
+            and then extends across any subsequent sequence of nonspacing marks.
+            A grapheme cluster is most directly relevant to text rendering and
+            processes such as cursor placement and text selection in editing,
+            but may also be relevant to comparison and searching.
+            )
+            $(LI For many processes, a grapheme cluster behaves as if it was a
+            single character with the same properties as its grapheme base.
+            Effectively, nonspacing marks apply $(I graphically) to the base,
+            but do not change its properties.
+            )
+        )
+        $(P This module defines a number of primitives that work with graphemes:
+        $(LREF Grapheme), $(LREF decodeGrapheme) and $(LREF graphemeStride).
+        All of them are using $(I extended grapheme) boundaries
+        as defined in the aforementioned standard annex.
+        )
+    )
+
+
+    $(P $(DEF Nonspacing mark) A combining character with the
+        General Category of Nonspacing Mark (Mn) or Enclosing Mark (Me).
+    )
+
+    $(P $(DEF Spacing mark) A combining character that is not a nonspacing mark.)
+
+
+    $(SECTION Normalization)
+
+    $(P The concepts of $(S_LINK Canonical equivalent, canonical equivalent)
+     or $(S_LINK Compatibility equivalent, compatibility equivalent)
+    characters in the Unicode Standard make it necessary to have a full, formal
+    definition of equivalence for Unicode strings.
+    String equivalence is determined by a process called normalization,
+    whereby strings are converted into forms which are compared
+    directly for identity. This is the primary goal of the normalization process,
+    see the function $(LREF normalize) to convert into any of
+    the four defined forms.
+    )
+
+    $(P A very important attribute of the Unicode Normalization Forms
+    is that they must remain stable between versions of the Unicode Standard.
+    A Unicode string normalized to a particular Unicode Normalization Form
+    in one version of the standard is guaranteed to remain in that Normalization
+    Form for implementations of future versions of the standard.
+    )
+
+    $(P The Unicode Standard specifies four normalization forms.
+    Informally, two of these forms are defined by maximal decomposition
+    of equivalent sequences, and two of these forms are defined
+    by maximal $(I composition) of equivalent sequences.
+        $(UL
+        $(LI Normalization Form D (NFD): The $(S_LINK Canonical decomposition,
+            canonical decomposition) of a character sequence.)
+        $(LI Normalization Form KD (NFKD): The $(S_LINK Compatibility decomposition,
+            compatibility decomposition) of a character sequence.)
+        $(LI Normalization Form C (NFC): The canonical composition of the
+            $(S_LINK Canonical decomposition, canonical decomposition)
+            of a coded character sequence.)
+        $(LI Normalization Form KC (NFKC): The canonical composition
+        of the $(S_LINK Compatibility decomposition,
+            compatibility decomposition) of a character sequence)
+        )
+    )
+
+    $(P The choice of the normalization form depends on the particular use case.
+    NFC is the best form for general text, since it's more compatible with
+    strings converted from legacy encodings. NFKC is the preferred form for
+    identifiers, especially where there are security concerns. NFD and NFKD
+    are the most useful for internal processing.
+    )
+
+    $(SECTION Construction of lookup tables)
+
+    $(P The Unicode standard describes a set of algorithms that
+    depend on having the ability to quickly look up various properties
+    of a code point. Given the the codespace of about 1 million $(CODEPOINTS),
+    it is not a trivial task to provide a space-efficient solution for
+    the multitude of properties.)
+
+    $(P Common approaches such as hash-tables or binary search over
+     sorted code point intervals (as in $(LREF InversionList)) are insufficient.
+     Hash-tables have enormous memory footprint and binary search
+     over intervals is not fast enough for some heavy-duty algorithms.
+     )
+
+    $(P The recommended solution (see Unicode Implementation Guidelines)
+    is using multi-stage tables that are an implementation of the
+    $(WEB en.wikipedia.org/wiki/Trie, Trie) data structure with integer
+    keys and a fixed number of stages. For the remainder of the section
+    this will be called a fixed trie. The following describes a particular
+    implementation that is aimed for the speed of access at the expense
+    of ideal size savings.
+    )
+
+    $(P Taking a 2-level Trie as an example the principle of operation is as follows.
+        Split the number of bits in a key (code point, 21 bits) into 2 components
+        (e.g. 15 and 8).  The first is the number of bits in the index of the trie
+         and the other is number of bits in each page of the trie.
+        The layout of the trie is then an array of size 2^^bits-of-index followed
+        an array of memory chunks of size 2^^bits-of-page/bits-per-element.
+    )
+
+    $(P The number of pages is variable (but not less then 1)
+        unlike the number of entries in the index. The slots of the index
+        all have to contain a number of a page that is present. The lookup is then
+        just a couple of operations - slice the upper bits,
+        lookup an index for these, take a page at this index and use
+        the lower bits as an offset within this page.
+
+        Assuming that pages are laid out consequently
+        in one array at $(D pages), the pseudo-code is:
+    )
+    ---
+    auto elemsPerPage = (2 ^^ bits_per_page) / Value.sizeOfInBits;
+    pages[index[n >> bits_per_page]][n & (elemsPerPage - 1)];
+    ---
+    $(P Where if $(D elemsPerPage) is a power of 2 the whole process is
+    a handful of simple instructions and 2 array reads. Subsequent levels
+    of the trie are introduced by recursing on this notion - the index array
+    is treated as values. The number of bits in index is then again
+    split into 2 parts, with pages over 'current-index' and the new 'upper-index'.
+    )
+
+    $(P For completeness a level 1 trie is simply an array.
+    The current implementation takes advantage of bit-packing values
+    when the range is known to be limited in advance (such as $(D bool)).
+    See also $(LREF BitPacked) for enforcing it manually.
+    The major size advantage however comes from the fact
+    that multiple $(B identical pages on every level are merged) by construction.
+    )
+
+    $(P The process of constructing a trie is more involved and is hidden from
+    the user in a form of the convenience functions $(LREF codepointTrie),
+    $(LREF codepointSetTrie) and the even more convenient $(LREF toTrie).
+    In general a set or built-in AA with $(D dchar) type
+    can be turned into a trie. The trie object in this module
+    is read-only (immutable); it's effectively frozen after construction.
+    )
+
+    $(SECTION Unicode properties)
+
+    $(P This is a full list of Unicode properties accessible through $(LREF unicode)
+    with specific helpers per category nested within. Consult the
+    $(WEB www.unicode.org/cldr/utility/properties.jsp, CLDR utility)
+    when in doubt about the contents of a particular set.)
+
+    $(P General category sets listed below are only accessible with the
+    $(LREF unicode) shorthand accessor.)
+    $(BOOKTABLE $(B General category ),
+         $(TR $(TH Abb.) $(TH Long form)
+            $(TH Abb.) $(TH Long form)$(TH Abb.) $(TH Long form))
+        $(TR $(TD L) $(TD Letter)
+            $(TD Cn) $(TD Unassigned)  $(TD Po) $(TD Other_Punctuation))
+        $(TR $(TD Ll) $(TD Lowercase_Letter)
+            $(TD Co) $(TD Private_Use) $(TD Ps) $(TD Open_Punctuation))
+        $(TR $(TD Lm) $(TD Modifier_Letter)
+            $(TD Cs) $(TD Surrogate)   $(TD S) $(TD Symbol))
+        $(TR $(TD Lo) $(TD Other_Letter)
+            $(TD N) $(TD Number)  $(TD Sc) $(TD Currency_Symbol))
+        $(TR $(TD Lt) $(TD Titlecase_Letter)
+          $(TD Nd) $(TD Decimal_Number)  $(TD Sk) $(TD Modifier_Symbol))
+        $(TR $(TD Lu) $(TD Uppercase_Letter)
+          $(TD Nl) $(TD Letter_Number)   $(TD Sm) $(TD Math_Symbol))
+        $(TR $(TD M) $(TD Mark)
+          $(TD No) $(TD Other_Number)    $(TD So) $(TD Other_Symbol))
+        $(TR $(TD Mc) $(TD Spacing_Mark)
+          $(TD P) $(TD Punctuation) $(TD Z) $(TD Separator))
+        $(TR $(TD Me) $(TD Enclosing_Mark)
+          $(TD Pc) $(TD Connector_Punctuation)   $(TD Zl) $(TD Line_Separator))
+        $(TR $(TD Mn) $(TD Nonspacing_Mark)
+          $(TD Pd) $(TD Dash_Punctuation)    $(TD Zp) $(TD Paragraph_Separator))
+        $(TR $(TD C) $(TD Other)
+          $(TD Pe) $(TD Close_Punctuation) $(TD Zs) $(TD Space_Separator))
+        $(TR $(TD Cc) $(TD Control) $(TD Pf)
+          $(TD Final_Punctuation)   $(TD -) $(TD Any))
+        $(TR $(TD Cf) $(TD Format)
+          $(TD Pi) $(TD Initial_Punctuation) $(TD -) $(TD ASCII))
+    )
+    $(P Sets for other commonly useful properties that are
+    accessible with $(LREF unicode):)
+    $(BOOKTABLE $(B Common binary properties),
+        $(TR $(TH Name) $(TH Name) $(TH Name))
+        $(TR $(TD Alphabetic)  $(TD Ideographic) $(TD Other_Uppercase))
+        $(TR $(TD ASCII_Hex_Digit) $(TD IDS_Binary_Operator) $(TD Pattern_Syntax))
+        $(TR $(TD Bidi_Control)    $(TD ID_Start)    $(TD Pattern_White_Space))
+        $(TR $(TD Cased)   $(TD IDS_Trinary_Operator)    $(TD Quotation_Mark))
+        $(TR $(TD Case_Ignorable)  $(TD Join_Control)    $(TD Radical))
+        $(TR $(TD Dash)    $(TD Logical_Order_Exception) $(TD Soft_Dotted))
+        $(TR $(TD Default_Ignorable_Code_Point)    $(TD Lowercase)   $(TD STerm))
+        $(TR $(TD Deprecated)  $(TD Math)    $(TD Terminal_Punctuation))
+        $(TR $(TD Diacritic)   $(TD Noncharacter_Code_Point) $(TD Unified_Ideograph))
+        $(TR $(TD Extender)    $(TD Other_Alphabetic)    $(TD Uppercase))
+        $(TR $(TD Grapheme_Base)   $(TD Other_Default_Ignorable_Code_Point)  $(TD Variation_Selector))
+        $(TR $(TD Grapheme_Extend) $(TD Other_Grapheme_Extend)   $(TD White_Space))
+        $(TR $(TD Grapheme_Link)   $(TD Other_ID_Continue)   $(TD XID_Continue))
+        $(TR $(TD Hex_Digit)   $(TD Other_ID_Start)  $(TD XID_Start))
+        $(TR $(TD Hyphen)  $(TD Other_Lowercase) )
+        $(TR $(TD ID_Continue) $(TD Other_Math)  )
+    )
+    $(P Bellow is the table with block names accepted by $(LREF unicode.block).
+    Note that the shorthand version $(LREF unicode) requires "In"
+    to be prepended to the names of blocks so as to disambiguate
+    scripts and blocks.)
+
+    $(BOOKTABLE $(B Blocks),
+        $(TR $(TD Aegean Numbers)    $(TD Ethiopic Extended) $(TD Mongolian))
+        $(TR $(TD Alchemical Symbols)    $(TD Ethiopic Extended-A)   $(TD Musical Symbols))
+        $(TR $(TD Alphabetic Presentation Forms) $(TD Ethiopic Supplement)   $(TD Myanmar))
+        $(TR $(TD Ancient Greek Musical Notation)    $(TD General Punctuation)   $(TD Myanmar Extended-A))
+        $(TR $(TD Ancient Greek Numbers) $(TD Geometric Shapes)  $(TD New Tai Lue))
+        $(TR $(TD Ancient Symbols)   $(TD Georgian)  $(TD NKo))
+        $(TR $(TD Arabic)    $(TD Georgian Supplement)   $(TD Number Forms))
+        $(TR $(TD Arabic Extended-A) $(TD Glagolitic)    $(TD Ogham))
+        $(TR $(TD Arabic Mathematical Alphabetic Symbols)    $(TD Gothic)    $(TD Ol Chiki))
+        $(TR $(TD Arabic Presentation Forms-A)   $(TD Greek and Coptic)  $(TD Old Italic))
+        $(TR $(TD Arabic Presentation Forms-B)   $(TD Greek Extended)    $(TD Old Persian))
+        $(TR $(TD Arabic Supplement) $(TD Gujarati)  $(TD Old South Arabian))
+        $(TR $(TD Armenian)  $(TD Gurmukhi)  $(TD Old Turkic))
+        $(TR $(TD Arrows)    $(TD Halfwidth and Fullwidth Forms) $(TD Optical Character Recognition))
+        $(TR $(TD Avestan)   $(TD Hangul Compatibility Jamo) $(TD Oriya))
+        $(TR $(TD Balinese)  $(TD Hangul Jamo)   $(TD Osmanya))
+        $(TR $(TD Bamum) $(TD Hangul Jamo Extended-A)    $(TD Phags-pa))
+        $(TR $(TD Bamum Supplement)  $(TD Hangul Jamo Extended-B)    $(TD Phaistos Disc))
+        $(TR $(TD Basic Latin)   $(TD Hangul Syllables)  $(TD Phoenician))
+        $(TR $(TD Batak) $(TD Hanunoo)   $(TD Phonetic Extensions))
+        $(TR $(TD Bengali)   $(TD Hebrew)    $(TD Phonetic Extensions Supplement))
+        $(TR $(TD Block Elements)    $(TD High Private Use Surrogates)   $(TD Playing Cards))
+        $(TR $(TD Bopomofo)  $(TD High Surrogates)   $(TD Private Use Area))
+        $(TR $(TD Bopomofo Extended) $(TD Hiragana)  $(TD Rejang))
+        $(TR $(TD Box Drawing)   $(TD Ideographic Description Characters)    $(TD Rumi Numeral Symbols))
+        $(TR $(TD Brahmi)    $(TD Imperial Aramaic)  $(TD Runic))
+        $(TR $(TD Braille Patterns)  $(TD Inscriptional Pahlavi) $(TD Samaritan))
+        $(TR $(TD Buginese)  $(TD Inscriptional Parthian)    $(TD Saurashtra))
+        $(TR $(TD Buhid) $(TD IPA Extensions)    $(TD Sharada))
+        $(TR $(TD Byzantine Musical Symbols) $(TD Javanese)  $(TD Shavian))
+        $(TR $(TD Carian)    $(TD Kaithi)    $(TD Sinhala))
+        $(TR $(TD Chakma)    $(TD Kana Supplement)   $(TD Small Form Variants))
+        $(TR $(TD Cham)  $(TD Kanbun)    $(TD Sora Sompeng))
+        $(TR $(TD Cherokee)  $(TD Kangxi Radicals)   $(TD Spacing Modifier Letters))
+        $(TR $(TD CJK Compatibility) $(TD Kannada)   $(TD Specials))
+        $(TR $(TD CJK Compatibility Forms)   $(TD Katakana)  $(TD Sundanese))
+        $(TR $(TD CJK Compatibility Ideographs)  $(TD Katakana Phonetic Extensions)  $(TD Sundanese Supplement))
+        $(TR $(TD CJK Compatibility Ideographs Supplement)   $(TD Kayah Li)  $(TD Superscripts and Subscripts))
+        $(TR $(TD CJK Radicals Supplement)   $(TD Kharoshthi)    $(TD Supplemental Arrows-A))
+        $(TR $(TD CJK Strokes)   $(TD Khmer) $(TD Supplemental Arrows-B))
+        $(TR $(TD CJK Symbols and Punctuation)   $(TD Khmer Symbols) $(TD Supplemental Mathematical Operators))
+        $(TR $(TD CJK Unified Ideographs)    $(TD Lao)   $(TD Supplemental Punctuation))
+        $(TR $(TD CJK Unified Ideographs Extension A)    $(TD Latin-1 Supplement)    $(TD Supplementary Private Use Area-A))
+        $(TR $(TD CJK Unified Ideographs Extension B)    $(TD Latin Extended-A)  $(TD Supplementary Private Use Area-B))
+        $(TR $(TD CJK Unified Ideographs Extension C)    $(TD Latin Extended Additional) $(TD Syloti Nagri))
+        $(TR $(TD CJK Unified Ideographs Extension D)    $(TD Latin Extended-B)  $(TD Syriac))
+        $(TR $(TD Combining Diacritical Marks)   $(TD Latin Extended-C)  $(TD Tagalog))
+        $(TR $(TD Combining Diacritical Marks for Symbols)   $(TD Latin Extended-D)  $(TD Tagbanwa))
+        $(TR $(TD Combining Diacritical Marks Supplement)    $(TD Lepcha)    $(TD Tags))
+        $(TR $(TD Combining Half Marks)  $(TD Letterlike Symbols)    $(TD Tai Le))
+        $(TR $(TD Common Indic Number Forms) $(TD Limbu) $(TD Tai Tham))
+        $(TR $(TD Control Pictures)  $(TD Linear B Ideograms)    $(TD Tai Viet))
+        $(TR $(TD Coptic)    $(TD Linear B Syllabary)    $(TD Tai Xuan Jing Symbols))
+        $(TR $(TD Counting Rod Numerals) $(TD Lisu)  $(TD Takri))
+        $(TR $(TD Cuneiform) $(TD Low Surrogates)    $(TD Tamil))
+        $(TR $(TD Cuneiform Numbers and Punctuation) $(TD Lycian)    $(TD Telugu))
+        $(TR $(TD Currency Symbols)  $(TD Lydian)    $(TD Thaana))
+        $(TR $(TD Cypriot Syllabary) $(TD Mahjong Tiles) $(TD Thai))
+        $(TR $(TD Cyrillic)  $(TD Malayalam) $(TD Tibetan))
+        $(TR $(TD Cyrillic Extended-A)   $(TD Mandaic)   $(TD Tifinagh))
+        $(TR $(TD Cyrillic Extended-B)   $(TD Mathematical Alphanumeric Symbols) $(TD Transport And Map Symbols))
+        $(TR $(TD Cyrillic Supplement)   $(TD Mathematical Operators)    $(TD Ugaritic))
+        $(TR $(TD Deseret)   $(TD Meetei Mayek)  $(TD Unified Canadian Aboriginal Syllabics))
+        $(TR $(TD Devanagari)    $(TD Meetei Mayek Extensions)   $(TD Unified Canadian Aboriginal Syllabics Extended))
+        $(TR $(TD Devanagari Extended)   $(TD Meroitic Cursive)  $(TD Vai))
+        $(TR $(TD Dingbats)  $(TD Meroitic Hieroglyphs)  $(TD Variation Selectors))
+        $(TR $(TD Domino Tiles)  $(TD Miao)  $(TD Variation Selectors Supplement))
+        $(TR $(TD Egyptian Hieroglyphs)  $(TD Miscellaneous Mathematical Symbols-A)  $(TD Vedic Extensions))
+        $(TR $(TD Emoticons) $(TD Miscellaneous Mathematical Symbols-B)  $(TD Vertical Forms))
+        $(TR $(TD Enclosed Alphanumerics)    $(TD Miscellaneous Symbols) $(TD Yijing Hexagram Symbols))
+        $(TR $(TD Enclosed Alphanumeric Supplement)  $(TD Miscellaneous Symbols and Arrows)  $(TD Yi Radicals))
+        $(TR $(TD Enclosed CJK Letters and Months)   $(TD Miscellaneous Symbols And Pictographs) $(TD Yi Syllables))
+        $(TR $(TD Enclosed Ideographic Supplement)   $(TD Miscellaneous Technical)   )
+        $(TR $(TD Ethiopic)  $(TD Modifier Tone Letters) )
+    )
+
+    $(P Bellow is the table with script names accepted by $(LREF unicode.script)
+    and by the shorthand version $(LREF unicode):)
+    $(BOOKTABLE $(B Scripts),
+        $(TR $(TD Arabic)  $(TD Hanunoo) $(TD Old_Italic))
+        $(TR $(TD Armenian)    $(TD Hebrew)  $(TD Old_Persian))
+        $(TR $(TD Avestan) $(TD Hiragana)    $(TD Old_South_Arabian))
+        $(TR $(TD Balinese)    $(TD Imperial_Aramaic)    $(TD Old_Turkic))
+        $(TR $(TD Bamum)   $(TD Inherited)   $(TD Oriya))
+        $(TR $(TD Batak)   $(TD Inscriptional_Pahlavi)   $(TD Osmanya))
+        $(TR $(TD Bengali) $(TD Inscriptional_Parthian)  $(TD Phags_Pa))
+        $(TR $(TD Bopomofo)    $(TD Javanese)    $(TD Phoenician))
+        $(TR $(TD Brahmi)  $(TD Kaithi)  $(TD Rejang))
+        $(TR $(TD Braille) $(TD Kannada) $(TD Runic))
+        $(TR $(TD Buginese)    $(TD Katakana)    $(TD Samaritan))
+        $(TR $(TD Buhid)   $(TD Kayah_Li)    $(TD Saurashtra))
+        $(TR $(TD Canadian_Aboriginal) $(TD Kharoshthi)  $(TD Sharada))
+        $(TR $(TD Carian)  $(TD Khmer)   $(TD Shavian))
+        $(TR $(TD Chakma)  $(TD Lao) $(TD Sinhala))
+        $(TR $(TD Cham)    $(TD Latin)   $(TD Sora_Sompeng))
+        $(TR $(TD Cherokee)    $(TD Lepcha)  $(TD Sundanese))
+        $(TR $(TD Common)  $(TD Limbu)   $(TD Syloti_Nagri))
+        $(TR $(TD Coptic)  $(TD Linear_B)    $(TD Syriac))
+        $(TR $(TD Cuneiform)   $(TD Lisu)    $(TD Tagalog))
+        $(TR $(TD Cypriot) $(TD Lycian)  $(TD Tagbanwa))
+        $(TR $(TD Cyrillic)    $(TD Lydian)  $(TD Tai_Le))
+        $(TR $(TD Deseret) $(TD Malayalam)   $(TD Tai_Tham))
+        $(TR $(TD Devanagari)  $(TD Mandaic) $(TD Tai_Viet))
+        $(TR $(TD Egyptian_Hieroglyphs)    $(TD Meetei_Mayek)    $(TD Takri))
+        $(TR $(TD Ethiopic)    $(TD Meroitic_Cursive)    $(TD Tamil))
+        $(TR $(TD Georgian)    $(TD Meroitic_Hieroglyphs)    $(TD Telugu))
+        $(TR $(TD Glagolitic)  $(TD Miao)    $(TD Thaana))
+        $(TR $(TD Gothic)  $(TD Mongolian)   $(TD Thai))
+        $(TR $(TD Greek)   $(TD Myanmar) $(TD Tibetan))
+        $(TR $(TD Gujarati)    $(TD New_Tai_Lue) $(TD Tifinagh))
+        $(TR $(TD Gurmukhi)    $(TD Nko) $(TD Ugaritic))
+        $(TR $(TD Han) $(TD Ogham)   $(TD Vai))
+        $(TR $(TD Hangul)  $(TD Ol_Chiki)    $(TD Yi))
+    )
+
+    $(P Bellow is the table of names accepted by $(LREF unicode.hangulSyllableType).)
+    $(BOOKTABLE $(B Hangul syllable type),
+        $(TR $(TH Abb.) $(TH Long form))
+        $(TR $(TD L)   $(TD Leading_Jamo))
+        $(TR $(TD LV)  $(TD LV_Syllable))
+        $(TR $(TD LVT) $(TD LVT_Syllable) )
+        $(TR $(TD T)   $(TD Trailing_Jamo))
+        $(TR $(TD V)   $(TD Vowel_Jamo))
+    )
+    References:
+        $(WEB www.digitalmars.com/d/ascii-table.html, ASCII Table),
+        $(WEB en.wikipedia.org/wiki/Unicode, Wikipedia),
+        $(WEB www.unicode.org, The Unicode Consortium),
+        $(WEB www.unicode.org/reports/tr15/, Unicode normalization forms),
+        $(WEB www.unicode.org/reports/tr29/, Unicode text segmentation)
+        $(WEB www.unicode.org/uni2book/ch05.pdf,
+            Unicode Implementation Guidelines)
+        $(WEB www.unicode.org/uni2book/ch03.pdf,
+            Unicode Conformance)
+    Trademarks:
+        Unicode(tm) is a trademark of Unicode, Inc.
+
+    Macros:
+        WIKI=Phobos/StdUni
+
+    Copyright: Copyright 2013 -
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+    Authors:   Dmitry Olshansky
+    Source:    $(PHOBOSSRC std/_uni.d)
+    Standards: $(WEB www.unicode.org/versions/Unicode6.2.0/, Unicode v6.2)
+
+Macros:
+
+SECTION = <h3><a id="$1">$0</a></h3>
+DEF = <div><a id="$1"><i>$0</i></a></div>
+S_LINK = <a href="#$1">$+</a>
+CODEPOINT = $(S_LINK Code point, code point)
+CODEPOINTS = $(S_LINK Code point, code points)
+CHARACTER = $(S_LINK Character, character)
+CHARACTERS = $(S_LINK Character, characters)
+CLUSTER = $(S_LINK Grapheme cluster, grapheme cluster)
++/
+module std.uni;
+
+import core.stdc.stdlib;
+import std.meta, std.traits;
+import std.range.primitives;
+
+
+// debug = std_uni;
+
+debug(std_uni) import std.stdio;
+
+private:
+
+version (unittest)
+{
+private:
+    struct TestAliasedString
+    {
+        string get() @safe @nogc pure nothrow { return _s; }
+        alias get this;
+        @disable this(this);
+        string _s;
+    }
+
+    bool testAliasedString(alias func, Args...)(string s, Args args)
+    {
+        import std.algorithm.comparison : equal;
+        auto a = func(TestAliasedString(s), args);
+        auto b = func(s, args);
+        static if (is(typeof(equal(a, b))))
+        {
+            // For ranges, compare contents instead of object identity.
+            return equal(a, b);
+        }
+        else
+        {
+            return a == b;
+        }
+    }
+}
+
+version(std_uni_bootstrap){}
+else
+{
+    import std.internal.unicode_tables; // generated file
+}
+
+void copyBackwards(T,U)(T[] src, U[] dest)
+{
+    assert(src.length == dest.length);
+    for(size_t i=src.length; i-- > 0; )
+        dest[i] = src[i];
+}
+
+void copyForward(T,U)(T[] src, U[] dest)
+{
+    assert(src.length == dest.length);
+    for(size_t i=0; i<src.length; i++)
+        dest[i] = src[i];
+}
+
+// TODO: update to reflect all major CPUs supporting unaligned reads
+version(X86)
+    enum hasUnalignedReads = true;
+else version(X86_64)
+    enum hasUnalignedReads = true;
+else
+    enum hasUnalignedReads = false; // better be safe then sorry
+
+public enum dchar lineSep = '\u2028'; /// Constant $(CODEPOINT) (0x2028) - line separator.
+public enum dchar paraSep = '\u2029'; /// Constant $(CODEPOINT) (0x2029) - paragraph separator.
+public enum dchar nelSep  = '\u0085'; /// Constant $(CODEPOINT) (0x0085) - next line.
+
+// test the intro example
+@safe unittest
+{
+    import std.algorithm : find;
+    // initialize code point sets using script/block or property name
+    // set contains code points from both scripts.
+    auto set = unicode("Cyrillic") | unicode("Armenian");
+    // or simpler and statically-checked look
+    auto ascii = unicode.ASCII;
+    auto currency = unicode.Currency_Symbol;
+
+    // easy set ops
+    auto a = set & ascii;
+    assert(a.empty); // as it has no intersection with ascii
+    a = set | ascii;
+    auto b = currency - a; // subtract all ASCII, Cyrillic and Armenian
+
+    // some properties of code point sets
+    assert(b.length > 45); // 46 items in Unicode 6.1, even more in 6.2
+    // testing presence of a code point in a set
+    // is just fine, it is O(logN)
+    assert(!b['$']);
+    assert(!b['\u058F']); // Armenian dram sign
+    assert(b['¥']);
+
+    // building fast lookup tables, these guarantee O(1) complexity
+    // 1-level Trie lookup table essentially a huge bit-set ~262Kb
+    auto oneTrie = toTrie!1(b);
+    // 2-level far more compact but typically slightly slower
+    auto twoTrie = toTrie!2(b);
+    // 3-level even smaller, and a bit slower yet
+    auto threeTrie = toTrie!3(b);
+    assert(oneTrie['£']);
+    assert(twoTrie['£']);
+    assert(threeTrie['£']);
+
+    // build the trie with the most sensible trie level
+    // and bind it as a functor
+    auto cyrillicOrArmenian = toDelegate(set);
+    auto balance = find!(cyrillicOrArmenian)("Hello ընկեր!");
+    assert(balance == "ընկեր!");
+    // compatible with bool delegate(dchar)
+    bool delegate(dchar) bindIt = cyrillicOrArmenian;
+
+    // Normalization
+    string s = "Plain ascii (and not only), is always normalized!";
+    assert(s is normalize(s));// is the same string
+
+    string nonS = "A\u0308ffin"; // A ligature
+    auto nS = normalize(nonS); // to NFC, the W3C endorsed standard
+    assert(nS == "Äffin");
+    assert(nS != nonS);
+    string composed = "Äffin";
+
+    assert(normalize!NFD(composed) == "A\u0308ffin");
+    // to NFKD, compatibility decomposition useful for fuzzy matching/searching
+    assert(normalize!NFKD("2¹⁰") == "210");
+}
+
+enum lastDchar = 0x10FFFF;
+
+auto force(T, F)(F from)
+    if(isIntegral!T && !is(T == F))
+{
+    assert(from <= T.max && from >= T.min);
+    return cast(T)from;
+}
+
+auto force(T, F)(F from)
+    if(isBitPacked!T && !is(T == F))
+{
+    assert(from <= 2^^bitSizeOf!T-1);
+    return T(cast(TypeOfBitPacked!T)from);
+}
+
+auto force(T, F)(F from)
+    if(is(T == F))
+{
+    return from;
+}
+
+// repeat X times the bit-pattern in val assuming it's length is 'bits'
+size_t replicateBits(size_t times, size_t bits)(size_t val) @safe pure nothrow @nogc
+{
+    static if(times == 1)
+        return val;
+    else static if(bits == 1)
+    {
+        static if(times == size_t.sizeof*8)
+            return val ? size_t.max : 0;
+        else
+            return val ? (1<<times)-1 : 0;
+    }
+    else static if(times % 2)
+        return (replicateBits!(times-1, bits)(val)<<bits) | val;
+    else
+        return replicateBits!(times/2, bits*2)((val<<bits) | val);
+}
+
+@safe pure nothrow @nogc unittest // for replicate
+{
+    import std.algorithm : sum, map;
+    import std.range : iota;
+    size_t m = 0b111;
+    size_t m2 = 0b01;
+    foreach(i; AliasSeq!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
+    {
+        assert(replicateBits!(i, 3)(m)+1 == (1<<(3*i)));
+        assert(replicateBits!(i, 2)(m2) == iota(0, i).map!"2^^(2*a)"().sum());
+    }
+}
+
+// multiple arrays squashed into one memory block
+struct MultiArray(Types...)
+{
+    this(size_t[] sizes...) @safe pure nothrow
+    {
+        assert(dim == sizes.length);
+        size_t full_size;
+        foreach(i, v; Types)
+        {
+            full_size += spaceFor!(bitSizeOf!v)(sizes[i]);
+            sz[i] = sizes[i];
+            static if(i >= 1)
+                offsets[i] = offsets[i-1] +
+                    spaceFor!(bitSizeOf!(Types[i-1]))(sizes[i-1]);
+        }
+
+        storage = new size_t[full_size];
+    }
+
+    this(const(size_t)[] raw_offsets,
+        const(size_t)[] raw_sizes, const(size_t)[] data)const @safe pure nothrow @nogc
+    {
+        offsets[] = raw_offsets[];
+        sz[] = raw_sizes[];
+        storage = data;
+    }
+
+    @property auto slice(size_t n)()inout pure nothrow @nogc
+    {
+        auto ptr = raw_ptr!n;
+        return packedArrayView!(Types[n])(ptr, sz[n]);
+    }
+
+    @property auto ptr(size_t n)()inout pure nothrow @nogc
+    {
+        auto ptr = raw_ptr!n;
+        return inout(PackedPtr!(Types[n]))(ptr);
+    }
+
+    template length(size_t n)
+    {
+        @property size_t length()const @safe pure nothrow @nogc{ return sz[n]; }
+
+        @property void length(size_t new_size)
+        {
+            if(new_size > sz[n])
+            {// extend
+                size_t delta = (new_size - sz[n]);
+                sz[n] += delta;
+                delta = spaceFor!(bitSizeOf!(Types[n]))(delta);
+                storage.length +=  delta;// extend space at end
+                // raw_slice!x must follow resize as it could be moved!
+                // next stmts move all data past this array, last-one-goes-first
+                static if(n != dim-1)
+                {
+                    auto start = raw_ptr!(n+1);
+                    // len includes delta
+                    size_t len = (storage.ptr+storage.length-start);
+
+                    copyBackwards(start[0..len-delta], start[delta..len]);
+
+                    start[0..delta] = 0;
+                    // offsets are used for raw_slice, ptr etc.
+                    foreach(i; n+1..dim)
+                        offsets[i] += delta;
+                }
+            }
+            else if(new_size < sz[n])
+            {// shrink
+                size_t delta = (sz[n] - new_size);
+                sz[n] -= delta;
+                delta = spaceFor!(bitSizeOf!(Types[n]))(delta);
+                // move all data past this array, forward direction
+                static if(n != dim-1)
+                {
+                    auto start = raw_ptr!(n+1);
+                    size_t len = (storage.ptr+storage.length-start);
+                    copyForward(start[0..len-delta], start[delta..len]);
+
+                    // adjust offsets last, they affect raw_slice
+                    foreach(i; n+1..dim)
+                        offsets[i] -= delta;
+                }
+                storage.length -= delta;
+            }
+            // else - NOP
+        }
+    }
+
+    @property size_t bytes(size_t n=size_t.max)() const
+    {
+        static if(n == size_t.max)
+            return storage.length*size_t.sizeof;
+        else static if(n != Types.length-1)
+            return (raw_ptr!(n+1)-raw_ptr!n)*size_t.sizeof;
+        else
+            return (storage.ptr+storage.length - raw_ptr!n)*size_t.sizeof;
+    }
+
+    void store(OutRange)(scope OutRange sink) const
+        if(isOutputRange!(OutRange, char))
+    {
+        import std.format;
+        formattedWrite(sink, "[%( 0x%x, %)]", offsets[]);
+        formattedWrite(sink, ", [%( 0x%x, %)]", sz[]);
+        formattedWrite(sink, ", [%( 0x%x, %)]", storage);
+    }
+
+private:
+    @property auto raw_ptr(size_t n)()inout pure nothrow @nogc
+    {
+        static if(n == 0)
+            return storage.ptr;
+        else
+        {
+            return storage.ptr+offsets[n];
+        }
+    }
+    enum dim = Types.length;
+    size_t[dim] offsets;// offset for level x
+    size_t[dim] sz;// size of level x
+    alias bitWidth = staticMap!(bitSizeOf, Types);
+    size_t[] storage;
+}
+
+unittest
+{
+    import std.conv;
+    enum dg = (){
+        // sizes are:
+        // lvl0: 3, lvl1 : 2, lvl2: 1
+        auto m = MultiArray!(int, ubyte, int)(3,2,1);
+
+        static void check(size_t k, T)(ref T m, int n)
+        {
+            foreach(i; 0..n)
+                assert(m.slice!(k)[i] == i+1, text("level:",i," : ",m.slice!(k)[0..n]));
+        }
+
+        static void checkB(size_t k, T)(ref T m, int n)
+        {
+            foreach(i; 0..n)
+                assert(m.slice!(k)[i] == n-i, text("level:",i," : ",m.slice!(k)[0..n]));
+        }
+
+        static void fill(size_t k, T)(ref T m, int n)
+        {
+            foreach(i; 0..n)
+                m.slice!(k)[i] = force!ubyte(i+1);
+        }
+
+        static void fillB(size_t k, T)(ref T m, int n)
+        {
+            foreach(i; 0..n)
+                m.slice!(k)[i] = force!ubyte(n-i);
+        }
+
+        m.length!1 = 100;
+        fill!1(m, 100);
+        check!1(m, 100);
+
+        m.length!0 = 220;
+        fill!0(m, 220);
+        check!1(m, 100);
+        check!0(m, 220);
+
+        m.length!2 = 17;
+        fillB!2(m, 17);
+        checkB!2(m, 17);
+        check!0(m, 220);
+        check!1(m, 100);
+
+        m.length!2 = 33;
+        checkB!2(m, 17);
+        fillB!2(m, 33);
+        checkB!2(m, 33);
+        check!0(m, 220);
+        check!1(m, 100);
+
+        m.length!1 = 195;
+        fillB!1(m, 195);
+        checkB!1(m, 195);
+        checkB!2(m, 33);
+        check!0(m, 220);
+
+        auto marr = MultiArray!(BitPacked!(uint, 4), BitPacked!(uint, 6))(20, 10);
+        marr.length!0 = 15;
+        marr.length!1 = 30;
+        fill!1(marr, 30);
+        fill!0(marr, 15);
+        check!1(marr, 30);
+        check!0(marr, 15);
+        return 0;
+    };
+    enum ct = dg();
+    auto rt = dg();
+}
+
+unittest
+{// more bitpacking tests
+    import std.conv;
+
+    alias Bitty =
+      MultiArray!(BitPacked!(size_t, 3)
+                , BitPacked!(size_t, 4)
+                , BitPacked!(size_t, 3)
+                , BitPacked!(size_t, 6)
+                , bool);
+    alias fn1 = sliceBits!(13, 16);
+    alias fn2 = sliceBits!( 9, 13);
+    alias fn3 = sliceBits!( 6,  9);
+    alias fn4 = sliceBits!( 0,  6);
+    static void check(size_t lvl, MA)(ref MA arr){
+        for(size_t i = 0; i< arr.length!lvl; i++)
+            assert(arr.slice!(lvl)[i] == i, text("Mismatch on lvl ", lvl, " idx ", i, " value: ", arr.slice!(lvl)[i]));
+    }
+
+    static void fillIdx(size_t lvl, MA)(ref MA arr){
+        for(size_t i = 0; i< arr.length!lvl; i++)
+            arr.slice!(lvl)[i] = i;
+    }
+    Bitty m1;
+
+    m1.length!4 = 10;
+    m1.length!3 = 2^^6;
+    m1.length!2 = 2^^3;
+    m1.length!1 = 2^^4;
+    m1.length!0 = 2^^3;
+
+    m1.length!4 = 2^^16;
+
+    for(size_t i = 0; i< m1.length!4; i++)
+        m1.slice!(4)[i] = i % 2;
+
+    fillIdx!1(m1);
+    check!1(m1);
+    fillIdx!2(m1);
+    check!2(m1);
+    fillIdx!3(m1);
+    check!3(m1);
+    fillIdx!0(m1);
+    check!0(m1);
+    check!3(m1);
+    check!2(m1);
+    check!1(m1);
+    for(size_t i=0; i < 2^^16; i++)
+    {
+        m1.slice!(4)[i] = i % 2;
+        m1.slice!(0)[fn1(i)] = fn1(i);
+        m1.slice!(1)[fn2(i)] = fn2(i);
+        m1.slice!(2)[fn3(i)] = fn3(i);
+        m1.slice!(3)[fn4(i)] = fn4(i);
+    }
+    for(size_t i=0; i < 2^^16; i++)
+    {
+        assert(m1.slice!(4)[i] == i % 2);
+        assert(m1.slice!(0)[fn1(i)] == fn1(i));
+        assert(m1.slice!(1)[fn2(i)] == fn2(i));
+        assert(m1.slice!(2)[fn3(i)] == fn3(i));
+        assert(m1.slice!(3)[fn4(i)] == fn4(i));
+    }
+}
+
+size_t spaceFor(size_t _bits)(size_t new_len) @safe pure nothrow @nogc
+{
+    enum bits = _bits == 1 ? 1 : ceilPowerOf2(_bits);// see PackedArrayView
+    static if(bits > 8*size_t.sizeof)
+    {
+        static assert(bits % (size_t.sizeof*8) == 0);
+        return new_len * bits/(8*size_t.sizeof);
+    }
+    else
+    {
+        enum factor = size_t.sizeof*8/bits;
+        return (new_len+factor-1)/factor; // rounded up
+    }
+}
+
+template isBitPackableType(T)
+{
+    enum isBitPackableType = isBitPacked!T
+        || isIntegral!T || is(T == bool) || isSomeChar!T;
+}
+
+//============================================================================
+template PackedArrayView(T)
+    if((is(T dummy == BitPacked!(U, sz), U, size_t sz)
+        && isBitPackableType!U) || isBitPackableType!T)
+{
+    private enum bits = bitSizeOf!T;
+    alias PackedArrayView = PackedArrayViewImpl!(T, bits > 1 ? ceilPowerOf2(bits) : 1);
+}
+
+//unsafe and fast access to a chunk of RAM as if it contains packed values
+template PackedPtr(T)
+    if((is(T dummy == BitPacked!(U, sz), U, size_t sz)
+        && isBitPackableType!U) || isBitPackableType!T)
+{
+    private enum bits = bitSizeOf!T;
+    alias PackedPtr = PackedPtrImpl!(T, bits > 1 ? ceilPowerOf2(bits) : 1);
+}
+
+@trusted struct PackedPtrImpl(T, size_t bits)
+{
+pure nothrow:
+    static assert(isPowerOf2(bits));
+
+    this(inout(size_t)* ptr)inout @safe @nogc
+    {
+        origin = ptr;
+    }
+
+    private T simpleIndex(size_t n) inout
+    {
+        auto q = n / factor;
+        auto r = n % factor;
+        return cast(T)((origin[q] >> bits*r) & mask);
+    }
+
+    private void simpleWrite(TypeOfBitPacked!T val, size_t n)
+    in
+    {
+        static if(isIntegral!T)
+            assert(val <= mask);
+    }
+    body
+    {
+        auto q = n / factor;
+        auto r = n % factor;
+        size_t tgt_shift = bits*r;
+        size_t word = origin[q];
+        origin[q] = (word & ~(mask<<tgt_shift))
+            | (cast(size_t)val << tgt_shift);
+    }
+
+    static if(factor == bytesPerWord// can safely pack by byte
+         || factor == 1 // a whole word at a time
+         || ((factor == bytesPerWord/2 || factor == bytesPerWord/4)
+                && hasUnalignedReads)) // this needs unaligned reads
+    {
+        static if(factor == bytesPerWord)
+            alias U = ubyte;
+        else static if(factor == bytesPerWord/2)
+            alias U = ushort;
+        else static if(factor == bytesPerWord/4)
+            alias U = uint;
+        else static if(size_t.sizeof == 8 && factor == bytesPerWord/8)
+            alias U = ulong;
+
+        T opIndex(size_t idx) inout
+        {
+            return __ctfe ? simpleIndex(idx) :
+                cast(inout(T))(cast(U*)origin)[idx];
+        }
+
+        static if(isBitPacked!T) // lack of user-defined implicit conversion
+        {
+            void opIndexAssign(T val, size_t idx)
+            {
+                return opIndexAssign(cast(TypeOfBitPacked!T)val, idx);
+            }
+        }
+
+        void opIndexAssign(TypeOfBitPacked!T val, size_t idx)
+        {
+            if(__ctfe)
+                simpleWrite(val, idx);
+            else
+                (cast(U*)origin)[idx] = cast(U)val;
+        }
+    }
+    else
+    {
+        T opIndex(size_t n) inout
+        {
+            return simpleIndex(n);
+        }
+
+        static if(isBitPacked!T) // lack of user-defined implicit conversion
+        {
+            void opIndexAssign(T val, size_t idx)
+            {
+                return opIndexAssign(cast(TypeOfBitPacked!T)val, idx);
+            }
+        }
+
+        void opIndexAssign(TypeOfBitPacked!T val, size_t n)
+        {
+            return simpleWrite(val, n);
+        }
+    }
+
+private:
+    // factor - number of elements in one machine word
+    enum factor = size_t.sizeof*8/bits, mask = 2^^bits-1;
+    enum bytesPerWord =  size_t.sizeof;
+    size_t* origin;
+}
+
+// data is packed only by power of two sized packs per word,
+// thus avoiding mul/div overhead at the cost of ultimate packing
+// this construct doesn't own memory, only provides access, see MultiArray for usage
+@trusted struct PackedArrayViewImpl(T, size_t bits)
+{
+pure nothrow:
+
+    this(inout(size_t)* origin, size_t offset, size_t items) inout
+    {
+        ptr = inout(PackedPtr!(T))(origin);
+        ofs = offset;
+        limit = items;
+    }
+
+    bool zeros(size_t s, size_t e)
+    in
+    {
+        assert(s <= e);
+    }
+    body
+    {
+        s += ofs;
+        e += ofs;
+        size_t pad_s = roundUp(s);
+        if ( s >= e)
+        {
+            foreach (i; s..e)
+                if(ptr[i])
+                    return false;
+            return true;
+        }
+        size_t pad_e = roundDown(e);
+        size_t i;
+        for(i=s; i<pad_s; i++)
+            if(ptr[i])
+                return false;
+        // all in between is x*factor elements
+        for(size_t j=i/factor; i<pad_e; i+=factor, j++)
+            if(ptr.origin[j])
+                return false;
+        for(; i<e; i++)
+            if(ptr[i])
+                return false;
+        return true;
+    }
+
+    T opIndex(size_t idx) inout
+    in
+    {
+        assert(idx < limit);
+    }
+    body
+    {
+        return ptr[ofs + idx];
+    }
+
+    static if(isBitPacked!T) // lack of user-defined implicit conversion
+    {
+        void opIndexAssign(T val, size_t idx)
+        {
+            return opIndexAssign(cast(TypeOfBitPacked!T)val, idx);
+        }
+    }
+
+    void opIndexAssign(TypeOfBitPacked!T val, size_t idx)
+    in
+    {
+        assert(idx < limit);
+    }
+    body
+    {
+        ptr[ofs + idx] = val;
+    }
+
+    static if(isBitPacked!T) // lack of user-defined implicit conversions
+    {
+        void opSliceAssign(T val, size_t start, size_t end)
+        {
+            opSliceAssign(cast(TypeOfBitPacked!T)val, start, end);
+        }
+    }
+
+    void opSliceAssign(TypeOfBitPacked!T val, size_t start, size_t end)
+    in
+    {
+        assert(start <= end);
+        assert(end <= limit);
+    }
+    body
+    {
+        // account for ofsetted view
+        start += ofs;
+        end += ofs;
+        // rounded to factor granularity
+        size_t pad_start = roundUp(start);// rounded up
+        if(pad_start >= end) //rounded up >= then end of slice
+        {
+            //nothing to gain, use per element assignment
+            foreach(i; start..end)
+                ptr[i] = val;
+            return;
+        }
+        size_t pad_end = roundDown(end); // rounded down
+        size_t i;
+        for(i=start; i<pad_start; i++)
+            ptr[i] = val;
+        // all in between is x*factor elements
+        if(pad_start != pad_end)
+        {
+            size_t repval = replicateBits!(factor, bits)(val);
+            for(size_t j=i/factor; i<pad_end; i+=factor, j++)
+                ptr.origin[j] = repval;// so speed it up by factor
+        }
+        for(; i<end; i++)
+            ptr[i] = val;
+    }
+
+    auto opSlice(size_t from, size_t to)inout
+    in
+    {
+        assert(from <= to);
+        assert(ofs + to <= limit);
+    }
+    body
+    {
+        return typeof(this)(ptr.origin, ofs + from, to - from);
+    }
+
+    auto opSlice(){ return opSlice(0, length); }
+
+    bool opEquals(T)(auto ref T arr) const
+    {
+        if(limit != arr.limit)
+           return false;
+        size_t s1 = ofs, s2 = arr.ofs;
+        size_t e1 = s1 + limit, e2 = s2 + limit;
+        if(s1 % factor == 0 && s2 % factor == 0 && length % factor == 0)
+        {
+            return ptr.origin[s1/factor .. e1/factor]
+                == arr.ptr.origin[s2/factor .. e2/factor];
+        }
+        for(size_t i=0;i<limit; i++)
+            if(this[i] != arr[i])
+                return false;
+        return true;
+    }
+
+    @property size_t length()const{ return limit; }
+
+private:
+    auto roundUp()(size_t val){ return (val+factor-1)/factor*factor; }
+    auto roundDown()(size_t val){ return val/factor*factor; }
+    // factor - number of elements in one machine word
+    enum factor = size_t.sizeof*8/bits;
+    PackedPtr!(T) ptr;
+    size_t ofs, limit;
+}
+
+
+private struct SliceOverIndexed(T)
+{
+    enum assignableIndex = is(typeof((){ T.init[0] = Item.init; }));
+    enum assignableSlice = is(typeof((){ T.init[0..0] = Item.init; }));
+    auto opIndex(size_t idx)const
+    in
+    {
+        assert(idx < to - from);
+    }
+    body
+    {
+        return (*arr)[from+idx];
+    }
+
+    static if(assignableIndex)
+    void opIndexAssign(Item val, size_t idx)
+    in
+    {
+        assert(idx < to - from);
+    }
+    body
+    {
+       (*arr)[from+idx] = val;
+    }
+
+    auto opSlice(size_t a, size_t b)
+    {
+        return typeof(this)(from+a, from+b, arr);
+    }
+
+    // static if(assignableSlice)
+    void opSliceAssign(T)(T val, size_t start, size_t end)
+    {
+        (*arr)[start+from .. end+from] = val;
+    }
+
+    auto opSlice()
+    {
+        return typeof(this)(from, to, arr);
+    }
+
+    @property size_t length()const { return to-from;}
+
+    auto opDollar()const { return length; }
+
+    @property bool empty()const { return from == to; }
+
+    @property auto front()const { return (*arr)[from]; }
+
+    static if(assignableIndex)
+    @property void front(Item val) { (*arr)[from] = val; }
+
+    @property auto back()const { return (*arr)[to-1]; }
+
+    static if(assignableIndex)
+    @property void back(Item val) { (*arr)[to-1] = val; }
+
+    @property auto save() inout { return this; }
+
+    void popFront() {   from++; }
+
+    void popBack() {    to--; }
+
+    bool opEquals(T)(auto ref T arr) const
+    {
+        if(arr.length != length)
+            return false;
+        for(size_t i=0; i <length; i++)
+            if(this[i] != arr[i])
+                return false;
+        return true;
+    }
+private:
+    alias Item = typeof(T.init[0]);
+    size_t from, to;
+    T* arr;
+}
+
+static assert(isRandomAccessRange!(SliceOverIndexed!(int[])));
+
+// BUG? forward reference to return type of sliceOverIndexed!Grapheme
+SliceOverIndexed!(const(T)) sliceOverIndexed(T)(size_t a, size_t b, const(T)* x)
+    if(is(Unqual!T == T))
+{
+    return SliceOverIndexed!(const(T))(a, b, x);
+}
+
+// BUG? inout is out of reach
+//...SliceOverIndexed.arr only parameters or stack based variables can be inout
+SliceOverIndexed!T sliceOverIndexed(T)(size_t a, size_t b, T* x)
+    if(is(Unqual!T == T))
+{
+    return SliceOverIndexed!T(a, b, x);
+}
+
+unittest
+{
+    int[] idxArray = [2, 3, 5, 8, 13];
+    auto sliced = sliceOverIndexed(0, idxArray.length, &idxArray);
+
+    assert(!sliced.empty);
+    assert(sliced.front == 2);
+    sliced.front = 1;
+    assert(sliced.front == 1);
+    assert(sliced.back == 13);
+    sliced.popFront();
+    assert(sliced.front == 3);
+    assert(sliced.back == 13);
+    sliced.back = 11;
+    assert(sliced.back == 11);
+    sliced.popBack();
+
+    assert(sliced.front == 3);
+    assert(sliced[$-1] == 8);
+    sliced = sliced[];
+    assert(sliced[0] == 3);
+    assert(sliced.back == 8);
+    sliced = sliced[1..$];
+    assert(sliced.front == 5);
+    sliced = sliced[0..$-1];
+    assert(sliced[$-1] == 5);
+
+    int[] other = [2, 5];
+    assert(sliced[] == sliceOverIndexed(1, 2, &other));
+    sliceOverIndexed(0, 2, &idxArray)[0..2] = -1;
+    assert(idxArray[0..2] == [-1, -1]);
+    uint[] nullArr = null;
+    auto nullSlice = sliceOverIndexed(0, 0, &idxArray);
+    assert(nullSlice.empty);
+}
+
+private auto packedArrayView(T)(inout(size_t)* ptr, size_t items) @trusted pure nothrow
+{
+    return inout(PackedArrayView!T)(ptr, 0, items);
+}
+
+
+//============================================================================
+// Partially unrolled binary search using Shar's method
+//============================================================================
+
+string genUnrolledSwitchSearch(size_t size)
+{
+    import std.conv : to;
+    import core.bitop : bsr;
+    import std.array : replace;
+    assert(isPowerOf2(size));
+    string code = `
+    import core.bitop : bsr;
+    auto power = bsr(m)+1;
+    switch(power){`;
+    size_t i = bsr(size);
+    foreach_reverse(val; 0..bsr(size))
+    {
+        auto v = 2^^val;
+        code ~= `
+        case pow:
+            if(pred(range[idx+m], needle))
+                idx +=  m;
+            goto case;
+        `.replace("m", to!string(v))
+        .replace("pow", to!string(i));
+        i--;
+    }
+    code ~= `
+        case 0:
+            if(pred(range[idx], needle))
+                idx += 1;
+            goto default;
+        `;
+    code ~= `
+        default:
+    }`;
+    return code;
+}
+
+bool isPowerOf2(size_t sz) @safe pure nothrow @nogc
+{
+    return (sz & (sz-1)) == 0;
+}
+
+size_t uniformLowerBound(alias pred, Range, T)(Range range, T needle)
+    if(is(T : ElementType!Range))
+{
+    assert(isPowerOf2(range.length));
+    size_t idx = 0, m = range.length/2;
+    while(m != 0)
+    {
+        if(pred(range[idx+m], needle))
+            idx += m;
+        m /= 2;
+    }
+    if(pred(range[idx], needle))
+        idx += 1;
+    return idx;
+}
+
+size_t switchUniformLowerBound(alias pred, Range, T)(Range range, T needle)
+    if(is(T : ElementType!Range))
+{
+    assert(isPowerOf2(range.length));
+    size_t idx = 0, m = range.length/2;
+    enum max = 1<<10;
+    while(m >= max)
+    {
+        if(pred(range[idx+m], needle))
+            idx += m;
+        m /= 2;
+    }
+    mixin(genUnrolledSwitchSearch(max));
+    return idx;
+}
+
+//
+size_t floorPowerOf2(size_t arg) @safe pure nothrow @nogc
+{
+    import core.bitop : bsr;
+    assert(arg > 1); // else bsr is undefined
+    return 1<<bsr(arg-1);
+}
+
+size_t ceilPowerOf2(size_t arg) @safe pure nothrow @nogc
+{
+    import core.bitop : bsr;
+    assert(arg > 1); // else bsr is undefined
+    return 1<<bsr(arg-1)+1;
+}
+
+template sharMethod(alias uniLowerBound)
+{
+    size_t sharMethod(alias _pred="a<b", Range, T)(Range range, T needle)
+        if(is(T : ElementType!Range))
+    {
+        import std.functional;
+        alias pred = binaryFun!_pred;
+        if(range.length == 0)
+            return 0;
+        if(isPowerOf2(range.length))
+            return uniLowerBound!pred(range, needle);
+        size_t n = floorPowerOf2(range.length);
+        if(pred(range[n-1], needle))
+        {// search in another 2^^k area that fully covers the tail of range
+            size_t k = ceilPowerOf2(range.length - n + 1);
+            return range.length - k + uniLowerBound!pred(range[$-k..$], needle);
+        }
+        else
+            return uniLowerBound!pred(range[0..n], needle);
+    }
+}
+
+alias sharLowerBound = sharMethod!uniformLowerBound;
+alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound;
+
+unittest
+{
+    import std.range;
+    auto stdLowerBound(T)(T[] range, T needle)
+    {
+        return assumeSorted(range).lowerBound(needle).length;
+    }
+    immutable MAX = 5*1173;
+    auto arr = array(iota(5, MAX, 5));
+    assert(arr.length == MAX/5-1);
+    foreach(i; 0..MAX+5)
+    {
+        auto st = stdLowerBound(arr, i);
+        assert(st == sharLowerBound(arr, i));
+        assert(st == sharSwitchLowerBound(arr, i));
+    }
+    arr = [];
+    auto st = stdLowerBound(arr, 33);
+    assert(st == sharLowerBound(arr, 33));
+    assert(st == sharSwitchLowerBound(arr, 33));
+}
+//============================================================================
+
+@safe
+{
+// hope to see simillar stuff in public interface... once Allocators are out
+//@@@BUG moveFront and friends? dunno, for now it's POD-only
+
+@trusted size_t genericReplace(Policy=void, T, Range)
+    (ref T dest, size_t from, size_t to, Range stuff)
+{
+    import std.algorithm : copy;
+    size_t delta = to - from;
+    size_t stuff_end = from+stuff.length;
+    if(stuff.length > delta)
+    {// replace increases length
+        delta = stuff.length - delta;// now, new is > old  by delta
+        static if(is(Policy == void))
+            dest.length = dest.length+delta;//@@@BUG lame @property
+        else
+            dest = Policy.realloc(dest, dest.length+delta);
+        copyBackwards(dest[to..dest.length-delta],
+            dest[to+delta..dest.length]);
+        copyForward(stuff, dest[from..stuff_end]);
+    }
+    else if(stuff.length == delta)
+    {
+        copy(stuff, dest[from..to]);
+    }
+    else
+    {// replace decreases length by delta
+        delta = delta - stuff.length;
+        copy(stuff, dest[from..stuff_end]);
+        copyForward(dest[to..dest.length],
+            dest[stuff_end..dest.length-delta]);
+        static if(is(Policy == void))
+            dest.length = dest.length - delta;//@@@BUG lame @property
+        else
+            dest = Policy.realloc(dest, dest.length-delta);
+    }
+    return stuff_end;
+}
+
+
+// Simple storage manipulation policy
+@trusted public struct GcPolicy
+{
+    static T[] dup(T)(const T[] arr)
+    {
+        return arr.dup;
+    }
+
+    static T[] alloc(T)(size_t size)
+    {
+        return new T[size];
+    }
+
+    static T[] realloc(T)(T[] arr, size_t sz)
+    {
+        arr.length = sz;
+        return arr;
+    }
+
+    static void replaceImpl(T, Range)(ref T[] dest, size_t from, size_t to, Range stuff)
+    {
+        replaceInPlace(dest, from, to, stuff);
+    }
+
+    static void append(T, V)(ref T[] arr, V value)
+        if(!isInputRange!V)
+    {
+        arr ~= force!T(value);
+    }
+
+    static void append(T, V)(ref T[] arr, V value)
+        if(isInputRange!V)
+    {
+        insertInPlace(arr, arr.length, value);
+    }
+
+    static void destroy(T)(ref T arr)
+        if(isDynamicArray!T && is(Unqual!T == T))
+    {
+        debug
+        {
+            arr[] = cast(typeof(T.init[0]))(0xdead_beef);
+        }
+        arr = null;
+    }
+
+    static void destroy(T)(ref T arr)
+        if(isDynamicArray!T && !is(Unqual!T == T))
+    {
+        arr = null;
+    }
+}
+
+// ditto
+@trusted struct ReallocPolicy
+{
+    static T[] dup(T)(const T[] arr)
+    {
+        auto result = alloc!T(arr.length);
+        result[] = arr[];
+        return result;
+    }
+
+    static T[] alloc(T)(size_t size)
+    {
+        import std.exception : enforce;
+        auto ptr = cast(T*)enforce(malloc(T.sizeof*size), "out of memory on C heap");
+        return ptr[0..size];
+    }
+
+    static T[] realloc(T)(T[] arr, size_t size)
+    {
+        import std.exception : enforce;
+        if(!size)
+        {
+            destroy(arr);
+            return null;
+        }
+        auto ptr = cast(T*)enforce(core.stdc.stdlib.realloc(
+                             arr.ptr, T.sizeof*size), "out of memory on C heap");
+        return ptr[0..size];
+    }
+
+    static void replaceImpl(T, Range)(ref T[] dest, size_t from, size_t to, Range stuff)
+    {
+        genericReplace!(ReallocPolicy)(dest, from, to, stuff);
+    }
+
+    static void append(T, V)(ref T[] arr, V value)
+        if(!isInputRange!V)
+    {
+        arr = realloc(arr, arr.length+1);
+        arr[$-1] = force!T(value);
+    }
+
+    static void append(T, V)(ref T[] arr, V value)
+        if(isInputRange!V && hasLength!V)
+    {
+        arr = realloc(arr, arr.length+value.length);
+        copy(value, arr[$-value.length..$]);
+    }
+
+    static void destroy(T)(ref T[] arr)
+    {
+        if(arr.ptr)
+            free(arr.ptr);
+        arr = null;
+    }
+}
+
+//build hack
+alias _RealArray = CowArray!ReallocPolicy;
+
+unittest
+{
+    with(ReallocPolicy)
+    {
+        bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result,
+                   string file = __FILE__, size_t line = __LINE__)
+        {
+            {
+                replaceImpl(orig, from, to, toReplace);
+                scope(exit) destroy(orig);
+                if(!equalS(orig, result))
+                    return false;
+            }
+            return true;
+        }
+        static T[] arr(T)(T[] args... )
+        {
+            return dup(args);
+        }
+
+        assert(test(arr([1, 2, 3, 4]), 0, 0, [5, 6, 7], [5, 6, 7, 1, 2, 3, 4]));
+        assert(test(arr([1, 2, 3, 4]), 0, 2, cast(int[])[], [3, 4]));
+        assert(test(arr([1, 2, 3, 4]), 0, 4, [5, 6, 7], [5, 6, 7]));
+        assert(test(arr([1, 2, 3, 4]), 0, 2, [5, 6, 7], [5, 6, 7, 3, 4]));
+        assert(test(arr([1, 2, 3, 4]), 2, 3, [5, 6, 7], [1, 2, 5, 6, 7, 4]));
+    }
+}
+
+/**
+    Tests if T is some kind a set of code points. Intended for template constraints.
+*/
+public template isCodepointSet(T)
+{
+    static if(is(T dummy == InversionList!(Args), Args...))
+        enum isCodepointSet = true;
+    else
+        enum isCodepointSet = false;
+}
+
+/**
+    Tests if $(D T) is a pair of integers that implicitly convert to $(D V).
+    The following code must compile for any pair $(D T):
+    ---
+    (T x){ V a = x[0]; V b = x[1];}
+    ---
+    The following must not compile:
+     ---
+    (T x){ V c = x[2];}
+    ---
+*/
+public template isIntegralPair(T, V=uint)
+{
+    enum isIntegralPair = is(typeof((T x){ V a = x[0]; V b = x[1];}))
+        && !is(typeof((T x){ V c = x[2]; }));
+}
+
+
+/**
+    The recommended default type for set of $(CODEPOINTS).
+    For details, see the current implementation: $(LREF InversionList).
+*/
+public alias CodepointSet = InversionList!GcPolicy;
+
+
+//@@@BUG: std.typecons tuples depend on std.format to produce fields mixin
+// which relies on std.uni.isGraphical and this chain blows up with Forward reference error
+// hence below doesn't seem to work
+// public alias CodepointInterval = Tuple!(uint, "a", uint, "b");
+
+/**
+    The recommended type of $(XREF _typecons, Tuple)
+    to represent [a, b$(RPAREN) intervals of $(CODEPOINTS). As used in $(LREF InversionList).
+    Any interval type should pass $(LREF isIntegralPair) trait.
+*/
+public struct CodepointInterval
+{
+pure:
+    uint[2] _tuple;
+    alias _tuple this;
+
+@safe pure nothrow @nogc:
+
+    this(uint low, uint high)
+    {
+        _tuple[0] = low;
+        _tuple[1] = high;
+    }
+    bool opEquals(T)(T val) const
+    {
+        return this[0] == val[0] && this[1] == val[1];
+    }
+    @property ref inout(uint) a() inout { return _tuple[0]; }
+    @property ref inout(uint) b() inout { return _tuple[1]; }
+}
+
+//@@@BUG another forward reference workaround
+@trusted bool equalS(R1, R2)(R1 lhs, R2 rhs)
+{
+    for(;;){
+        if(lhs.empty)
+            return rhs.empty;
+        if(rhs.empty)
+            return false;
+        if(lhs.front != rhs.front)
+            return false;
+        lhs.popFront();
+        rhs.popFront();
+    }
+}
+
+
+
+/**
+    $(P
+    $(D InversionList) is a set of $(CODEPOINTS)
+    represented as an array of open-right [a, b$(RPAREN)
+    intervals (see $(LREF CodepointInterval) above).
+    The name comes from the way the representation reads left to right.
+    For instance a set of all values [10, 50$(RPAREN), [80, 90$(RPAREN),
+    plus a singular value 60 looks like this:
+    )
+    ---
+    10, 50, 60, 61, 80, 90
+    ---
+    $(P
+    The way to read this is: start with negative meaning that all numbers
+    smaller then the next one are not present in this set (and positive
+    - the contrary). Then switch positive/negative after each
+    number passed from left to right.
+    )
+    $(P This way negative spans until 10, then positive until 50,
+    then negative until 60, then positive until 61, and so on.
+    As seen this provides a space-efficient storage of highly redundant data
+    that comes in long runs. A description which Unicode $(CHARACTER)
+    properties fit nicely. The technique itself could be seen as a variation
+    on $(LUCKY RLE encoding).
+    )
+
+    $(P Sets are value types (just like $(D int) is) thus they
+        are never aliased.
+    )
+        Example:
+        ---
+        auto a = CodepointSet('a', 'z'+1);
+        auto b = CodepointSet('A', 'Z'+1);
+        auto c = a;
+        a = a | b;
+        assert(a == CodepointSet('A', 'Z'+1, 'a', 'z'+1));
+        assert(a != c);
+        ---
+    $(P See also $(LREF unicode) for simpler construction of sets
+        from predefined ones.
+    )
+
+    $(P Memory usage is 8 bytes per each contiguous interval in a set.
+    The value semantics are achieved by using the
+    $(WEB en.wikipedia.org/wiki/Copy-on-write, COW) technique
+    and thus it's $(RED not) safe to cast this type to $(D_KEYWORD shared).
+    )
+
+    Note:
+    $(P It's not recommended to rely on the template parameters
+    or the exact type of a current $(CODEPOINT) set in $(D std.uni).
+    The type and parameters may change when the standard
+    allocators design is finalized.
+    Use $(LREF isCodepointSet) with templates or just stick with the default
+    alias $(LREF CodepointSet) throughout the whole code base.
+    )
+*/
+@trusted public struct InversionList(SP=GcPolicy)
+{
+    import std.range : assumeSorted;
+
+public:
+
+    /**
+        Construct from another code point set of any type.
+    */
+    this(Set)(Set set) pure
+        if(isCodepointSet!Set)
+    {
+        uint[] arr;
+        foreach(v; set.byInterval)
+        {
+            arr ~= v.a;
+            arr ~= v.b;
+        }
+        data = CowArray!(SP).reuse(arr);
+    }
+
+    /**
+        Construct a set from a forward range of code point intervals.
+    */
+    this(Range)(Range intervals) pure
+        if(isForwardRange!Range && isIntegralPair!(ElementType!Range))
+    {
+        uint[] arr;
+        foreach(v; intervals)
+        {
+            SP.append(arr, v.a);
+            SP.append(arr, v.b);
+        }
+        data = CowArray!(SP).reuse(arr);
+        sanitize(); //enforce invariant: sort intervals etc.
+    }
+
+    //helper function that avoids sanity check to be CTFE-friendly
+    private static fromIntervals(Range)(Range intervals) pure
+    {
+        import std.algorithm : map;
+        import std.range : roundRobin;
+        auto flattened = roundRobin(intervals.save.map!"a[0]"(),
+            intervals.save.map!"a[1]"());
+        InversionList set;
+        set.data = CowArray!(SP)(flattened);
+        return set;
+    }
+    //ditto untill sort is CTFE-able
+    private static fromIntervals()(uint[] intervals...) pure
+    in
+    {
+        import std.conv : text;
+        assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!");
+        for (uint i = 0; i < intervals.length; i += 2)
+        {
+            auto a = intervals[i], b = intervals[i+1];
+            assert(a < b, text("illegal interval [a, b): ", a, " > ", b));
+        }
+    }
+    body
+    {
+        InversionList set;
+        set.data = CowArray!(SP)(intervals);
+        return set;
+    }
+
+    /**
+        Construct a set from plain values of code point intervals.
+    */
+    this()(uint[] intervals...)
+    in
+    {
+        import std.conv : text;
+        assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!");
+        for (uint i = 0; i < intervals.length; i += 2)
+        {
+            auto a = intervals[i], b = intervals[i+1];
+            assert(a < b, text("illegal interval [a, b): ", a, " > ", b));
+        }
+    }
+    body
+    {
+        data = CowArray!(SP)(intervals);
+        sanitize(); //enforce invariant: sort intervals etc.
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm.comparison : equal;
+
+        auto set = CodepointSet('a', 'z'+1, 'а', 'я'+1);
+        foreach(v; 'a'..'z'+1)
+            assert(set[v]);
+        // Cyrillic lowercase interval
+        foreach(v; 'а'..'я'+1)
+            assert(set[v]);
+        //specific order is not required, intervals may interesect
+        auto set2 = CodepointSet('а', 'я'+1, 'a', 'd', 'b', 'z'+1);
+        //the same end result
+        assert(set2.byInterval.equal(set.byInterval));
+    }
+
+    /**
+        Get range that spans all of the $(CODEPOINT) intervals in this $(LREF InversionList).
+
+        Example:
+        -----------
+        import std.algorithm.comparison : equal;
+        import std.typecons : tuple;
+
+        auto set = CodepointSet('A', 'D'+1, 'a', 'd'+1);
+
+        assert(set.byInterval.equal([tuple('A','E'), tuple('a','e')]));
+        -----------
+    */
+    @property auto byInterval()
+    {
+        return Intervals!(typeof(data))(data);
+    }
+
+    /**
+        Tests the presence of code point $(D val) in this set.
+    */
+    bool opIndex(uint val) const
+    {
+        // the <= ensures that searching in  interval of [a, b) for 'a' you get .length == 1
+        // return assumeSorted!((a,b) => a<=b)(data[]).lowerBound(val).length & 1;
+        return sharSwitchLowerBound!"a<=b"(data[], val) & 1;
+    }
+
+    ///
+    unittest
+    {
+        auto gothic = unicode.Gothic;
+        // Gothic letter ahsa
+        assert(gothic['\U00010330']);
+        // no ascii in Gothic obviously
+        assert(!gothic['$']);
+    }
+
+    // Linear scan for $(D ch). Useful only for small sets.
+    // TODO:
+    // used internally in std.regex
+    // should be properly exposed in a public API ?
+    package auto scanFor()(dchar ch) const
+    {
+        immutable len = data.length;
+        for(size_t i = 0; i < len; i++)
+            if(ch < data[i])
+                return i & 1;
+        return 0;
+    }
+
+    /// Number of $(CODEPOINTS) in this set
+    @property size_t length()
+    {
+        size_t sum = 0;
+        foreach(iv; byInterval)
+        {
+            sum += iv.b - iv.a;
+        }
+        return sum;
+    }
+
+// bootstrap full set operations from 4 primitives (suitable as a template mixin):
+// addInterval, skipUpTo, dropUpTo & byInterval iteration
+//============================================================================
+public:
+    /**
+        $(P Sets support natural syntax for set algebra, namely: )
+        $(BOOKTABLE ,
+            $(TR $(TH Operator) $(TH Math notation) $(TH Description) )
+            $(TR $(TD &) $(TD a ∩ b) $(TD intersection) )
+            $(TR $(TD |) $(TD a ∪ b) $(TD union) )
+            $(TR $(TD -) $(TD a ∖ b) $(TD subtraction) )
+            $(TR $(TD ~) $(TD a ~ b) $(TD symmetric set difference i.e. (a ∪ b) \ (a ∩ b)) )
+        )
+    */
+    This opBinary(string op, U)(U rhs)
+        if(isCodepointSet!U || is(U:dchar))
+    {
+        static if(op == "&" || op == "|" || op == "~")
+        {// symmetric ops thus can swap arguments to reuse r-value
+            static if(is(U:dchar))
+            {
+                auto tmp = this;
+                mixin("tmp "~op~"= rhs; ");
+                return tmp;
+            }
+            else
+            {
+                static if(is(Unqual!U == U))
+                {
+                    // try hard to reuse r-value
+                    mixin("rhs "~op~"= this;");
+                    return rhs;
+                }
+                else
+                {
+                    auto tmp = this;
+                    mixin("tmp "~op~"= rhs;");
+                    return tmp;
+                }
+            }
+        }
+        else static if(op == "-") // anti-symmetric
+        {
+            auto tmp = this;
+            tmp -= rhs;
+            return tmp;
+        }
+        else
+            static assert(0, "no operator "~op~" defined for Set");
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm.comparison : equal;
+        import std.range : iota;
+
+        auto lower = unicode.LowerCase;
+        auto upper = unicode.UpperCase;
+        auto ascii = unicode.ASCII;
+
+        assert((lower & upper).empty); // no intersection
+        auto lowerASCII = lower & ascii;
+        assert(lowerASCII.byCodepoint.equal(iota('a', 'z'+1)));
+        // throw away all of the lowercase ASCII
+        assert((ascii - lower).length == 128 - 26);
+
+        auto onlyOneOf = lower ~ ascii;
+        assert(!onlyOneOf['Δ']); // not ASCII and not lowercase
+        assert(onlyOneOf['$']); // ASCII and not lowercase
+        assert(!onlyOneOf['a']); // ASCII and lowercase
+        assert(onlyOneOf['я']); // not ASCII but lowercase
+
+        // throw away all cased letters from ASCII
+        auto noLetters = ascii - (lower | upper);
+        assert(noLetters.length == 128 - 26*2);
+    }
+
+    /// The 'op=' versions of the above overloaded operators.
+    ref This opOpAssign(string op, U)(U rhs)
+        if(isCodepointSet!U || is(U:dchar))
+    {
+        static if(op == "|")    // union
+        {
+            static if(is(U:dchar))
+            {
+                this.addInterval(rhs, rhs+1);
+                return this;
+            }
+            else
+                return this.add(rhs);
+        }
+        else static if(op == "&")   // intersection
+                return this.intersect(rhs);// overloaded
+        else static if(op == "-")   // set difference
+                return this.sub(rhs);// overloaded
+        else static if(op == "~")   // symmetric set difference
+        {
+            auto copy = this & rhs;
+            this |= rhs;
+            this -= copy;
+            return this;
+        }
+        else
+            static assert(0, "no operator "~op~" defined for Set");
+    }
+
+    /**
+        Tests the presence of codepoint $(D ch) in this set,
+        the same as $(LREF opIndex).
+    */
+    bool opBinaryRight(string op: "in", U)(U ch) const
+        if(is(U : dchar))
+    {
+        return this[ch];
+    }
+
+    ///
+    unittest
+    {
+        assert('я' in unicode.Cyrillic);
+        assert(!('z' in unicode.Cyrillic));
+    }
+
+
+
+    /**
+     * Obtains a set that is the inversion of this set.
+     *
+     * See_Also: $(LREF inverted)
+     */
+    auto opUnary(string op: "!")()
+    {
+        return this.inverted;
+    }
+
+    /**
+        A range that spans each $(CODEPOINT) in this set.
+    */
+    @property auto byCodepoint()
+    {
+        @trusted static struct CodepointRange
+        {
+            this(This set)
+            {
+                r = set.byInterval;
+                if(!r.empty)
+                    cur = r.front.a;
+            }
+
+            @property dchar front() const
+            {
+                return cast(dchar)cur;
+            }
+
+            @property bool empty() const
+            {
+                return r.empty;
+            }
+
+            void popFront()
+            {
+                cur++;
+                while(cur >= r.front.b)
+                {
+                    r.popFront();
+                    if(r.empty)
+                        break;
+                    cur = r.front.a;
+                }
+            }
+        private:
+            uint cur;
+            typeof(This.init.byInterval) r;
+        }
+
+        return CodepointRange(this);
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm.comparison : equal;
+        import std.range : iota;
+
+        auto set = unicode.ASCII;
+        set.byCodepoint.equal(iota(0, 0x80));
+    }
+
+    /**
+        $(P Obtain textual representation of this set in from of
+        open-right intervals and feed it to $(D sink).
+        )
+        $(P Used by various standard formatting facilities such as
+         $(XREF _format, formattedWrite), $(XREF _stdio, write),
+         $(XREF _stdio, writef), $(XREF _conv, to) and others.
+        )
+        Example:
+        ---
+        import std.conv;
+        assert(unicode.ASCII.to!string == "[0..128$(RPAREN)");
+        ---
+    */
+
+    private import std.format : FormatException, FormatSpec;
+
+    /***************************************
+     * Obtain a textual representation of this InversionList
+     * in form of open-right intervals.
+     *
+     * The formatting flag is applied individually to each value, for example:
+     * $(LI $(B %s) and $(B %d) format the intervals as a [low..high$(RPAREN) range of integrals)
+     * $(LI $(B %x) formats the intervals as a [low..high$(RPAREN) range of lowercase hex characters)
+     * $(LI $(B %X) formats the intervals as a [low..high$(RPAREN) range of uppercase hex characters)
+     */
+    void toString(scope void delegate(const(char)[]) sink,
+                  FormatSpec!char fmt) /* const */
+    {
+        import std.format;
+        auto range = byInterval;
+        if(range.empty)
+            return;
+
+        while (1)
+        {
+            auto i = range.front;
+            range.popFront();
+
+            put(sink, "[");
+            formatValue(sink, i.a, fmt);
+            put(sink, "..");
+            formatValue(sink, i.b, fmt);
+            put(sink, ")");
+            if (range.empty) return;
+            put(sink, " ");
+        }
+    }
+
+    ///
+    unittest
+    {
+        import std.conv : to;
+        import std.format : format;
+        import std.uni : unicode;
+
+        assert(unicode.Cyrillic.to!string ==
+            "[1024..1157) [1159..1320) [7467..7468) [7544..7545) [11744..11776) [42560..42648) [42655..42656)");
+
+        // The specs '%s' and '%d' are equivalent to the to!string call above.
+        assert(format("%d", unicode.Cyrillic) == unicode.Cyrillic.to!string);
+
+        assert(format("%#x", unicode.Cyrillic) ==
+            "[0x400..0x485) [0x487..0x528) [0x1d2b..0x1d2c) [0x1d78..0x1d79) [0x2de0..0x2e00) [0xa640..0xa698) [0xa69f..0xa6a0)");
+
+        assert(format("%#X", unicode.Cyrillic) ==
+            "[0X400..0X485) [0X487..0X528) [0X1D2B..0X1D2C) [0X1D78..0X1D79) [0X2DE0..0X2E00) [0XA640..0XA698) [0XA69F..0XA6A0)");
+    }
+
+    unittest
+    {
+        import std.exception : assertThrown;
+        import std.format : format;
+        assertThrown!FormatException(format("%a", unicode.ASCII));
+    }
+
+
+    /**
+        Add an interval [a, b$(RPAREN) to this set.
+    */
+    ref add()(uint a, uint b)
+    {
+        addInterval(a, b);
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        CodepointSet someSet;
+        someSet.add('0', '5').add('A','Z'+1);
+        someSet.add('5', '9'+1);
+        assert(someSet['0']);
+        assert(someSet['5']);
+        assert(someSet['9']);
+        assert(someSet['Z']);
+    }
+
+private:
+
+  package(std)  // used from: std.regex.internal.parser
+    ref intersect(U)(U rhs)
+        if(isCodepointSet!U)
+    {
+        Marker mark;
+        foreach( i; rhs.byInterval)
+        {
+            mark = this.dropUpTo(i.a, mark);
+            mark = this.skipUpTo(i.b, mark);
+        }
+        this.dropUpTo(uint.max, mark);
+        return this;
+    }
+
+    ref intersect()(dchar ch)
+    {
+        foreach(i; byInterval)
+            if(i.a <= ch && ch < i.b)
+                return this = This.init.add(ch, ch+1);
+        this = This.init;
+        return this;
+    }
+
+    ///
+    unittest
+    {
+        assert(unicode.Cyrillic.intersect('-').byInterval.empty);
+    }
+
+    ref sub()(dchar ch)
+    {
+        return subChar(ch);
+    }
+
+    // same as the above except that skip & drop parts are swapped
+  package(std)  // used from: std.regex.internal.parser
+    ref sub(U)(U rhs)
+        if(isCodepointSet!U)
+    {
+        uint top;
+        Marker mark;
+        foreach(i; rhs.byInterval)
+        {
+            mark = this.skipUpTo(i.a, mark);
+            mark = this.dropUpTo(i.b, mark);
+        }
+        return this;
+    }
+
+  package(std)  // used from: std.regex.internal.parse
+    ref add(U)(U rhs)
+        if(isCodepointSet!U)
+    {
+        Marker start;
+        foreach(i; rhs.byInterval)
+        {
+            start = addInterval(i.a, i.b, start);
+        }
+        return this;
+    }
+
+// end of mixin-able part
+//============================================================================
+public:
+    /**
+        Obtains a set that is the inversion of this set.
+
+        See the '!' $(LREF opUnary) for the same but using operators.
+    */
+    @property auto inverted()
+    {
+        InversionList inversion = this;
+        if(inversion.data.length == 0)
+        {
+            inversion.addInterval(0, lastDchar+1);
+            return inversion;
+        }
+        if(inversion.data[0] != 0)
+            genericReplace(inversion.data, 0, 0, [0]);
+        else
+            genericReplace(inversion.data, 0, 1, cast(uint[])null);
+        if(data[data.length-1] != lastDchar+1)
+            genericReplace(inversion.data,
+                inversion.data.length, inversion.data.length, [lastDchar+1]);
+        else
+            genericReplace(inversion.data,
+                inversion.data.length-1, inversion.data.length, cast(uint[])null);
+
+        return inversion;
+    }
+
+    ///
+    unittest
+    {
+        auto set = unicode.ASCII;
+        // union with the inverse gets all of the code points in the Unicode
+        assert((set | set.inverted).length == 0x110000);
+        // no intersection with the inverse
+        assert((set & set.inverted).empty);
+    }
+
+    /**
+        Generates string with D source code of unary function with name of
+        $(D funcName) taking a single $(D dchar) argument. If $(D funcName) is empty
+        the code is adjusted to be a lambda function.
+
+        The function generated tests if the $(CODEPOINT) passed
+        belongs to this set or not. The result is to be used with string mixin.
+        The intended usage area is aggressive optimization via meta programming
+        in parser generators and the like.
+
+        Note: Use with care for relatively small or regular sets. It
+        could end up being slower then just using multi-staged tables.
+
+        Example:
+        ---
+        import std.stdio;
+
+        // construct set directly from [a, b$RPAREN intervals
+        auto set = CodepointSet(10, 12, 45, 65, 100, 200);
+        writeln(set);
+        writeln(set.toSourceCode("func"));
+        ---
+
+        The above outputs something along the lines of:
+        ---
+        bool func(dchar ch)  @safe pure nothrow @nogc
+        {
+            if(ch < 45)
+            {
+                if(ch == 10 || ch == 11) return true;
+                return false;
+            }
+            else if (ch < 65) return true;
+            else
+            {
+                if(ch < 100) return false;
+                if(ch < 200) return true;
+                return false;
+            }
+        }
+        ---
+    */
+    string toSourceCode(string funcName="")
+    {
+        import std.array : array;
+        import std.format : format;
+        import std.algorithm : countUntil;
+        enum maxBinary = 3;
+        static string linearScope(R)(R ivals, string indent)
+        {
+            string result = indent~"{\n";
+            string deeper = indent~"    ";
+            foreach(ival; ivals)
+            {
+                auto span = ival[1] - ival[0];
+                assert(span != 0);
+                if(span == 1)
+                {
+                    result ~= format("%sif(ch == %s) return true;\n", deeper, ival[0]);
+                }
+                else if(span == 2)
+                {
+                    result ~= format("%sif(ch == %s || ch == %s) return true;\n",
+                        deeper, ival[0], ival[0]+1);
+                }
+                else
+                {
+                    if(ival[0] != 0) // dchar is unsigned and  < 0 is useless
+                        result ~= format("%sif(ch < %s) return false;\n", deeper, ival[0]);
+                    result ~= format("%sif(ch < %s) return true;\n", deeper, ival[1]);
+                }
+            }
+            result ~= format("%sreturn false;\n%s}\n", deeper, indent); // including empty range of intervals
+            return result;
+        }
+
+        static string binaryScope(R)(R ivals, string indent)
+        {
+            // time to do unrolled comparisons?
+            if(ivals.length < maxBinary)
+                return linearScope(ivals, indent);
+            else
+                return bisect(ivals, ivals.length/2, indent);
+        }
+
+        // not used yet if/elsebinary search is far better with DMD  as of 2.061
+        // and GDC is doing fine job either way
+        static string switchScope(R)(R ivals, string indent)
+        {
+            string result = indent~"switch(ch){\n";
+            string deeper = indent~"    ";
+            foreach(ival; ivals)
+            {
+                if(ival[0]+1 == ival[1])
+                {
+                    result ~= format("%scase %s: return true;\n",
+                        deeper, ival[0]);
+                }
+                else
+                {
+                    result ~= format("%scase %s: .. case %s: return true;\n",
+                         deeper, ival[0], ival[1]-1);
+                }
+            }
+            result ~= deeper~"default: return false;\n"~indent~"}\n";
+            return result;
+        }
+
+        static string bisect(R)(R range, size_t idx, string indent)
+        {
+            string deeper = indent ~ "    ";
+            // bisect on one [a, b) interval at idx
+            string result = indent~"{\n";
+            // less branch, < a
+            result ~= format("%sif(ch < %s)\n%s",
+                deeper, range[idx][0], binaryScope(range[0..idx], deeper));
+            // middle point,  >= a && < b
+            result ~= format("%selse if (ch < %s) return true;\n",
+                deeper, range[idx][1]);
+            // greater or equal branch,  >= b
+            result ~= format("%selse\n%s",
+                deeper, binaryScope(range[idx+1..$], deeper));
+            return result~indent~"}\n";
+        }
+
+        string code = format("bool %s(dchar ch) @safe pure nothrow @nogc\n",
+            funcName.empty ? "function" : funcName);
+        auto range = byInterval.array();
+        // special case first bisection to be on ASCII vs beyond
+        auto tillAscii = countUntil!"a[0] > 0x80"(range);
+        if(tillAscii <= 0) // everything is ASCII or nothing is ascii (-1 & 0)
+            code ~= binaryScope(range, "");
+        else
+            code ~= bisect(range, tillAscii, "");
+        return code;
+    }
+
+    /**
+        True if this set doesn't contain any $(CODEPOINTS).
+    */
+    @property bool empty() const
+    {
+        return data.length == 0;
+    }
+
+    ///
+    unittest
+    {
+        CodepointSet emptySet;
+        assert(emptySet.length == 0);
+        assert(emptySet.empty);
+    }
+
+private:
+    alias This = typeof(this);
+    alias Marker = size_t;
+
+    // a random-access range of integral pairs
+    static struct Intervals(Range)
+    {
+        this(Range sp)
+        {
+            slice = sp;
+            start = 0;
+            end = sp.length;
+        }
+
+        this(Range sp, size_t s, size_t e)
+        {
+            slice = sp;
+            start = s;
+            end = e;
+        }
+
+        @property auto front()const
+        {
+            uint a = slice[start];
+            uint b = slice[start+1];
+            return CodepointInterval(a, b);
+        }
+
+        //may break sorted property - but we need std.sort to access it
+        //hence package protection attribute
+        package @property auto front(CodepointInterval val)
+        {
+            slice[start] = val.a;
+            slice[start+1] = val.b;
+        }
+
+        @property auto back()const
+        {
+            uint a = slice[end-2];
+            uint b = slice[end-1];
+            return CodepointInterval(a, b);
+        }
+
+        //ditto about package
+        package @property auto back(CodepointInterval val)
+        {
+            slice[end-2] = val.a;
+            slice[end-1] = val.b;
+        }
+
+        void popFront()
+        {
+            start += 2;
+        }
+
+        void popBack()
+        {
+            end -= 2;
+        }
+
+        auto opIndex(size_t idx) const
+        {
+            uint a = slice[start+idx*2];
+            uint b = slice[start+idx*2+1];
+            return CodepointInterval(a, b);
+        }
+
+        //ditto about package
+        package auto opIndexAssign(CodepointInterval val, size_t idx)
+        {
+            slice[start+idx*2] = val.a;
+            slice[start+idx*2+1] = val.b;
+        }
+
+        auto opSlice(size_t s, size_t e)
+        {
+            return Intervals(slice, s*2+start, e*2+start);
+        }
+
+        @property size_t length()const {  return slice.length/2; }
+
+        @property bool empty()const { return start == end; }
+
+        @property auto save(){ return this; }
+    private:
+        size_t start, end;
+        Range slice;
+    }
+
+    // called after construction from intervals
+    // to make sure invariants hold
+    void sanitize()
+    {
+        import std.algorithm : sort, SwapStrategy, max;
+        if (data.length == 0)
+            return;
+        alias Ival = CodepointInterval;
+        //intervals wrapper for a _range_ over packed array
+        auto ivals = Intervals!(typeof(data[]))(data[]);
+        //@@@BUG@@@ can't use "a.a < b.a" see issue 12265
+        sort!((a,b) => a.a < b.a, SwapStrategy.stable)(ivals);
+        // what follows is a variation on stable remove
+        // differences:
+        // - predicate is binary, and is tested against
+        //   the last kept element (at 'i').
+        // - predicate mutates lhs (merges rhs into lhs)
+        size_t len = ivals.length;
+        size_t i = 0;
+        size_t j = 1;
+        while (j < len)
+        {
+            if (ivals[i].b >= ivals[j].a)
+            {
+                ivals[i] = Ival(ivals[i].a, max(ivals[i].b, ivals[j].b));
+                j++;
+            }
+            else //unmergable
+            {
+                // check if there is a hole after merges
+                // (in the best case we do 0 writes to ivals)
+                if (j != i+1)
+                    ivals[i+1] = ivals[j]; //copy over
+                i++;
+                j++;
+            }
+        }
+        len = i + 1;
+        for (size_t k=0; k + 1 < len; k++)
+        {
+            assert(ivals[k].a < ivals[k].b);
+            assert(ivals[k].b < ivals[k+1].a);
+        }
+        data.length = len * 2;
+    }
+
+    // special case for normal InversionList
+    ref subChar(dchar ch)
+    {
+        auto mark = skipUpTo(ch);
+        if(mark != data.length
+            && data[mark] == ch && data[mark-1] == ch)
+        {
+            // it has split, meaning that ch happens to be in one of intervals
+            data[mark] = data[mark]+1;
+        }
+        return this;
+    }
+
+    //
+    Marker addInterval(int a, int b, Marker hint=Marker.init)
+    in
+    {
+        assert(a <= b);
+    }
+    body
+    {
+        import std.range : assumeSorted, SearchPolicy;
+        auto range = assumeSorted(data[]);
+        size_t pos;
+        size_t a_idx = hint + range[hint..$].lowerBound!(SearchPolicy.gallop)(a).length;
+        if(a_idx == range.length)
+        {
+            //  [---+++----++++----++++++]
+            //  [                         a  b]
+            data.append(a, b);
+            return data.length-1;
+        }
+        size_t b_idx = range[a_idx..range.length].lowerBound!(SearchPolicy.gallop)(b).length+a_idx;
+        uint[3] buf = void;
+        uint to_insert;
+        debug(std_uni)
+        {
+            writefln("a_idx=%d; b_idx=%d;", a_idx, b_idx);
+        }
+        if(b_idx == range.length)
+        {
+            //  [-------++++++++----++++++-]
+            //  [      s     a                 b]
+            if(a_idx & 1)// a in positive
+            {
+                buf[0] = b;
+                to_insert = 1;
+            }
+            else// a in negative
+            {
+                buf[0] = a;
+                buf[1] = b;
+                to_insert = 2;
+            }
+            pos = genericReplace(data, a_idx, b_idx, buf[0..to_insert]);
+            return pos - 1;
+        }
+
+        uint top = data[b_idx];
+
+        debug(std_uni)
+        {
+            writefln("a_idx=%d; b_idx=%d;", a_idx, b_idx);
+            writefln("a=%s; b=%s; top=%s;", a, b, top);
+        }
+        if(a_idx & 1)
+        {// a in positive
+            if(b_idx & 1)// b in positive
+            {
+                //  [-------++++++++----++++++-]
+                //  [       s    a        b    ]
+                buf[0] = top;
+                to_insert = 1;
+            }
+            else // b in negative
+            {
+                //  [-------++++++++----++++++-]
+                //  [       s    a   b         ]
+                if(top == b)
+                {
+                    assert(b_idx+1 < data.length);
+                    buf[0] = data[b_idx+1];
+                    pos = genericReplace(data, a_idx, b_idx+2, buf[0..1]);
+                    return pos - 1;
+                }
+                buf[0] = b;
+                buf[1] = top;
+                to_insert = 2;
+            }
+        }
+        else
+        { // a in negative
+            if(b_idx & 1) // b in positive
+            {
+                //  [----------+++++----++++++-]
+                //  [     a     b              ]
+                buf[0] = a;
+                buf[1] = top;
+                to_insert = 2;
+            }
+            else// b in negative
+            {
+                //  [----------+++++----++++++-]
+                //  [  a       s      b        ]
+                if(top == b)
+                {
+                    assert(b_idx+1 < data.length);
+                    buf[0] = a;
+                    buf[1] = data[b_idx+1];
+                    pos = genericReplace(data, a_idx, b_idx+2, buf[0..2]);
+                    return pos - 1;
+                }
+                buf[0] = a;
+                buf[1] = b;
+                buf[2] = top;
+                to_insert = 3;
+            }
+        }
+        pos = genericReplace(data, a_idx, b_idx+1, buf[0..to_insert]);
+        debug(std_uni)
+        {
+            writefln("marker idx: %d; length=%d", pos, data[pos], data.length);
+            writeln("inserting ", buf[0..to_insert]);
+        }
+        return pos - 1;
+    }
+
+    //
+    Marker dropUpTo(uint a, Marker pos=Marker.init)
+    in
+    {
+        assert(pos % 2 == 0); // at start of interval
+    }
+    body
+    {
+        auto range = assumeSorted!"a<=b"(data[pos..data.length]);
+        if(range.empty)
+            return pos;
+        size_t idx = pos;
+        idx += range.lowerBound(a).length;
+
+        debug(std_uni)
+        {
+            writeln("dropUpTo full length=", data.length);
+            writeln(pos,"~~~", idx);
+        }
+        if(idx == data.length)
+            return genericReplace(data, pos, idx, cast(uint[])[]);
+        if(idx & 1)
+        {   // a in positive
+            //[--+++----++++++----+++++++------...]
+            //      |<---si       s  a  t
+            genericReplace(data, pos, idx, [a]);
+        }
+        else
+        {   // a in negative
+            //[--+++----++++++----+++++++-------+++...]
+            //      |<---si              s  a  t
+            genericReplace(data, pos, idx, cast(uint[])[]);
+        }
+        return pos;
+    }
+
+    //
+    Marker skipUpTo(uint a, Marker pos=Marker.init)
+    out(result)
+    {
+        assert(result % 2 == 0);// always start of interval
+        //(may be  0-width after-split)
+    }
+    body
+    {
+        assert(data.length % 2 == 0);
+        auto range = assumeSorted!"a<=b"(data[pos..data.length]);
+        size_t idx = pos+range.lowerBound(a).length;
+
+        if(idx >= data.length) // could have Marker point to recently removed stuff
+            return data.length;
+
+        if(idx & 1)// inside of interval, check for split
+        {
+
+            uint top = data[idx];
+            if(top == a)// no need to split, it's end
+                return idx+1;
+            uint start = data[idx-1];
+            if(a == start)
+                return idx-1;
+            // split it up
+            genericReplace(data, idx, idx+1, [a, a, top]);
+            return idx+1;        // avoid odd index
+        }
+        return idx;
+    }
+
+    CowArray!SP data;
+}
+
+@system unittest
+{
+    import std.conv;
+    assert(unicode.ASCII.to!string() == "[0..128)");
+}
+
+// pedantic version for ctfe, and aligned-access only architectures
+@trusted uint safeRead24(const ubyte* ptr, size_t idx) pure nothrow @nogc
+{
+    idx *= 3;
+    version(LittleEndian)
+        return ptr[idx] + (cast(uint)ptr[idx+1]<<8)
+             + (cast(uint)ptr[idx+2]<<16);
+    else
+        return (cast(uint)ptr[idx]<<16) + (cast(uint)ptr[idx+1]<<8)
+             + ptr[idx+2];
+}
+
+// ditto
+@trusted void safeWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow @nogc
+{
+    idx *= 3;
+    version(LittleEndian)
+    {
+        ptr[idx] = val & 0xFF;
+        ptr[idx+1] = (val>>8) & 0xFF;
+        ptr[idx+2] = (val>>16) & 0xFF;
+    }
+    else
+    {
+        ptr[idx] = (val>>16) & 0xFF;
+        ptr[idx+1] = (val>>8) & 0xFF;
+        ptr[idx+2] = val & 0xFF;
+    }
+}
+
+// unaligned x86-like read/write functions
+@trusted uint unalignedRead24(const ubyte* ptr, size_t idx) pure nothrow @nogc
+{
+    uint* src = cast(uint*)(ptr+3*idx);
+    version(LittleEndian)
+        return *src & 0xFF_FFFF;
+    else
+        return *src >> 8;
+}
+
+// ditto
+@trusted void unalignedWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow @nogc
+{
+    uint* dest = cast(uint*)(cast(ubyte*)ptr + 3*idx);
+    version(LittleEndian)
+        *dest = val | (*dest & 0xFF00_0000);
+    else
+        *dest = (val<<8) | (*dest & 0xFF);
+}
+
+uint read24(const ubyte* ptr, size_t idx) @safe pure nothrow @nogc
+{
+    static if(hasUnalignedReads)
+        return __ctfe ? safeRead24(ptr, idx) : unalignedRead24(ptr, idx);
+    else
+        return safeRead24(ptr, idx);
+}
+
+void write24(ubyte* ptr, uint val, size_t idx) @safe pure nothrow @nogc
+{
+    static if(hasUnalignedReads)
+        return __ctfe ? safeWrite24(ptr, val, idx) : unalignedWrite24(ptr, val, idx);
+    else
+        return safeWrite24(ptr, val, idx);
+}
+@trusted struct CowArray(SP=GcPolicy)
+{
+    static auto reuse(uint[] arr)
+    {
+        CowArray cow;
+        cow.data = arr;
+        SP.append(cow.data, 1);
+        assert(cow.refCount == 1);
+        assert(cow.length == arr.length);
+        return cow;
+    }
+
+    this(Range)(Range range)
+        if(isInputRange!Range && hasLength!Range)
+    {
+        import std.algorithm : copy;
+        length = range.length;
+        copy(range, data[0..$-1]);
+    }
+
+    this(Range)(Range range)
+        if(isForwardRange!Range && !hasLength!Range)
+    {
+        import std.algorithm : copy;
+        auto len = walkLength(range.save);
+        length = len;
+        copy(range, data[0..$-1]);
+    }
+
+    this(this)
+    {
+        if(!empty)
+        {
+            refCount = refCount + 1;
+        }
+    }
+
+    ~this()
+    {
+        if(!empty)
+        {
+            auto cnt = refCount;
+            if(cnt == 1)
+                SP.destroy(data);
+            else
+                refCount = cnt - 1;
+        }
+    }
+
+    // no ref-count for empty U24 array
+    @property bool empty() const { return data.length == 0; }
+
+    // report one less then actual size
+    @property size_t length() const
+    {
+        return data.length ? data.length - 1 : 0;
+    }
+
+    //+ an extra slot for ref-count
+    @property void length(size_t len)
+    {
+        import std.algorithm : min, copy;
+        if(len == 0)
+        {
+            if(!empty)
+                freeThisReference();
+            return;
+        }
+        immutable total = len + 1; // including ref-count
+        if(empty)
+        {
+            data = SP.alloc!uint(total);
+            refCount = 1;
+            return;
+        }
+        auto cur_cnt = refCount;
+        if(cur_cnt != 1) // have more references to this memory
+        {
+            refCount = cur_cnt - 1;
+            auto new_data = SP.alloc!uint(total);
+            // take shrinking into account
+            auto to_copy = min(total, data.length) - 1;
+            copy(data[0..to_copy], new_data[0..to_copy]);
+            data = new_data; // before setting refCount!
+            refCount = 1;
+        }
+        else // 'this' is the only reference
+        {
+            // use the realloc (hopefully in-place operation)
+            data = SP.realloc(data, total);
+            refCount = 1; // setup a ref-count in the new end of the array
+        }
+    }
+
+    alias opDollar = length;
+
+    uint opIndex()(size_t idx)const
+    {
+        return data[idx];
+    }
+
+    void opIndexAssign(uint val, size_t idx)
+    {
+        auto cnt = refCount;
+        if(cnt != 1)
+            dupThisReference(cnt);
+        data[idx] = val;
+    }
+
+    //
+    auto opSlice(size_t from, size_t to)
+    {
+        if(!empty)
+        {
+            auto cnt = refCount;
+            if(cnt != 1)
+                dupThisReference(cnt);
+        }
+        return data[from .. to];
+
+    }
+
+    //
+    auto opSlice(size_t from, size_t to) const
+    {
+        return data[from .. to];
+    }
+
+    // length slices before the ref count
+    auto opSlice()
+    {
+        return opSlice(0, length);
+    }
+
+    // ditto
+    auto opSlice() const
+    {
+        return opSlice(0, length);
+    }
+
+    void append(Range)(Range range)
+        if(isInputRange!Range && hasLength!Range && is(ElementType!Range : uint))
+    {
+        size_t nl = length + range.length;
+        length = nl;
+        copy(range, this[nl-range.length..nl]);
+    }
+
+    void append()(uint[] val...)
+    {
+        length = length + val.length;
+        data[$-val.length-1 .. $-1] = val[];
+    }
+
+    bool opEquals()(auto const ref CowArray rhs)const
+    {
+        if(empty ^ rhs.empty)
+            return false; // one is empty and the other isn't
+        return empty || data[0..$-1] == rhs.data[0..$-1];
+    }
+
+private:
+    // ref-count is right after the data
+    @property uint refCount() const
+    {
+        return data[$-1];
+    }
+
+    @property void refCount(uint cnt)
+    {
+        data[$-1] = cnt;
+    }
+
+    void freeThisReference()
+    {
+        auto count = refCount;
+        if(count != 1) // have more references to this memory
+        {
+            // dec shared ref-count
+            refCount = count - 1;
+            data = [];
+        }
+        else
+            SP.destroy(data);
+        assert(!data.ptr);
+    }
+
+    void dupThisReference(uint count)
+    in
+    {
+        assert(!empty && count != 1 && count == refCount);
+    }
+    body
+    {
+        import std.algorithm : copy;
+        // dec shared ref-count
+        refCount = count - 1;
+        // copy to the new chunk of RAM
+        auto new_data = SP.alloc!uint(data.length);
+        // bit-blit old stuff except the counter
+        copy(data[0..$-1], new_data[0..$-1]);
+        data = new_data; // before setting refCount!
+        refCount = 1; // so that this updates the right one
+    }
+
+    uint[] data;
+}
+
+@safe unittest// Uint24 tests
+{
+    import std.algorithm;
+    import std.conv;
+    import std.range;
+    void funcRef(T)(ref T u24)
+    {
+        u24.length = 2;
+        u24[1] = 1024;
+        T u24_c = u24;
+        assert(u24[1] == 1024);
+        u24.length = 0;
+        assert(u24.empty);
+        u24.append([1, 2]);
+        assert(equalS(u24[], [1, 2]));
+        u24.append(111);
+        assert(equalS(u24[], [1, 2, 111]));
+        assert(!u24_c.empty && u24_c[1] == 1024);
+        u24.length = 3;
+        copy(iota(0, 3), u24[]);
+        assert(equalS(u24[], iota(0, 3)));
+        assert(u24_c[1] == 1024);
+    }
+
+    void func2(T)(T u24)
+    {
+        T u24_2 = u24;
+        T u24_3;
+        u24_3 = u24_2;
+        assert(u24_2 == u24_3);
+        assert(equalS(u24[], u24_2[]));
+        assert(equalS(u24_2[], u24_3[]));
+        funcRef(u24_3);
+
+        assert(equalS(u24_3[], iota(0, 3)));
+        assert(!equalS(u24_2[], u24_3[]));
+        assert(equalS(u24_2[], u24[]));
+        u24_2 = u24_3;
+        assert(equalS(u24_2[], iota(0, 3)));
+        // to test that passed arg is intact outside
+        // plus try out opEquals
+        u24 = u24_3;
+        u24 = T.init;
+        u24_3 = T.init;
+        assert(u24.empty);
+        assert(u24 == u24_3);
+        assert(u24 != u24_2);
+    }
+
+    foreach(Policy; AliasSeq!(GcPolicy, ReallocPolicy))
+    {
+        alias Range = typeof(CowArray!Policy.init[]);
+        alias U24A = CowArray!Policy;
+        static assert(isForwardRange!Range);
+        static assert(isBidirectionalRange!Range);
+        static assert(isOutputRange!(Range, uint));
+        static assert(isRandomAccessRange!(Range));
+
+        auto arr = U24A([42u, 36, 100]);
+        assert(arr[0] == 42);
+        assert(arr[1] == 36);
+        arr[0] = 72;
+        arr[1] = 0xFE_FEFE;
+        assert(arr[0] == 72);
+        assert(arr[1] == 0xFE_FEFE);
+        assert(arr[2] == 100);
+        U24A arr2 = arr;
+        assert(arr2[0] == 72);
+        arr2[0] = 11;
+        // test COW-ness
+        assert(arr[0] == 72);
+        assert(arr2[0] == 11);
+        // set this to about 100M to stress-test COW memory management
+        foreach(v; 0..10_000)
+            func2(arr);
+        assert(equalS(arr[], [72, 0xFE_FEFE, 100]));
+
+        auto r2 = U24A(iota(0, 100));
+        assert(equalS(r2[], iota(0, 100)), text(r2[]));
+        copy(iota(10, 170, 2), r2[10..90]);
+        assert(equalS(r2[], chain(iota(0, 10), iota(10, 170, 2), iota(90, 100)))
+               , text(r2[]));
+    }
+}
+
+version(unittest)
+{
+    private alias AllSets = AliasSeq!(InversionList!GcPolicy, InversionList!ReallocPolicy);
+}
+
+@safe unittest// core set primitives test
+{
+    import std.conv;
+    foreach(CodeList; AllSets)
+    {
+        CodeList a;
+        //"plug a hole" test
+        a.add(10, 20).add(25, 30).add(15, 27);
+        assert(a == CodeList(10, 30), text(a));
+
+        auto x = CodeList.init;
+        x.add(10, 20).add(30, 40).add(50, 60);
+
+        a = x;
+        a.add(20, 49);//[10, 49) [50, 60)
+        assert(a == CodeList(10, 49, 50 ,60));
+
+        a = x;
+        a.add(20, 50);
+        assert(a == CodeList(10, 60), text(a));
+
+        // simple unions, mostly edge effects
+        x = CodeList.init;
+        x.add(10, 20).add(40, 60);
+
+        a = x;
+        a.add(10, 25); //[10, 25) [40, 60)
+        assert(a == CodeList(10, 25, 40, 60));
+
+        a = x;
+        a.add(5, 15); //[5, 20) [40, 60)
+        assert(a == CodeList(5, 20, 40, 60));
+
+        a = x;
+        a.add(0, 10); // [0, 20) [40, 60)
+        assert(a == CodeList(0, 20, 40, 60));
+
+        a = x;
+        a.add(0, 5); // prepand
+        assert(a == CodeList(0, 5, 10, 20, 40, 60), text(a));
+
+        a = x;
+        a.add(5, 20);
+        assert(a == CodeList(5, 20, 40, 60));
+
+        a = x;
+        a.add(3, 37);
+        assert(a == CodeList(3, 37, 40, 60));
+
+        a = x;
+        a.add(37, 65);
+        assert(a == CodeList(10, 20, 37, 65));
+
+        // some tests on helpers for set intersection
+        x = CodeList.init.add(10, 20).add(40, 60).add(100, 120);
+        a = x;
+
+        auto m = a.skipUpTo(60);
+        a.dropUpTo(110, m);
+        assert(a == CodeList(10, 20, 40, 60, 110, 120), text(a.data[]));
+
+        a = x;
+        a.dropUpTo(100);
+        assert(a == CodeList(100, 120), text(a.data[]));
+
+        a = x;
+        m = a.skipUpTo(50);
+        a.dropUpTo(140, m);
+        assert(a == CodeList(10, 20, 40, 50), text(a.data[]));
+        a = x;
+        a.dropUpTo(60);
+        assert(a == CodeList(100, 120), text(a.data[]));
+    }
+}
+
+
+//test constructor to work with any order of intervals
+@safe unittest
+{
+    import std.conv, std.range, std.algorithm;
+    import std.typecons;
+    //ensure constructor handles bad ordering and overlap
+    auto c1 = CodepointSet('а', 'я'+1, 'А','Я'+1);
+    foreach(ch; chain(iota('а', 'я'+1), iota('А','Я'+1)))
+        assert(ch in c1, to!string(ch));
+
+    //contiguos
+    assert(CodepointSet(1000, 1006, 1006, 1009)
+        .byInterval.equal([tuple(1000, 1009)]));
+    //contains
+    assert(CodepointSet(900, 1200, 1000, 1100)
+        .byInterval.equal([tuple(900, 1200)]));
+    //intersect left
+    assert(CodepointSet(900, 1100, 1000, 1200)
+        .byInterval.equal([tuple(900, 1200)]));
+    //intersect right
+    assert(CodepointSet(1000, 1200, 900, 1100)
+        .byInterval.equal([tuple(900, 1200)]));
+
+    //ditto with extra items at end
+    assert(CodepointSet(1000, 1200, 900, 1100, 800, 850)
+        .byInterval.equal([tuple(800, 850), tuple(900, 1200)]));
+    assert(CodepointSet(900, 1100, 1000, 1200, 800, 850)
+        .byInterval.equal([tuple(800, 850), tuple(900, 1200)]));
+
+    //"plug a hole" test
+    auto c2 = CodepointSet(20, 40,
+        60, 80, 100, 140, 150, 200,
+        40, 60, 80, 100, 140, 150
+    );
+    assert(c2.byInterval.equal([tuple(20, 200)]));
+
+    auto c3 = CodepointSet(
+        20, 40, 60, 80, 100, 140, 150, 200,
+        0, 10, 15, 100, 10, 20, 200, 220);
+    assert(c3.byInterval.equal([tuple(0, 140), tuple(150, 220)]));
+}
+
+
+@safe unittest
+{   // full set operations
+    import std.conv;
+    foreach(CodeList; AllSets)
+    {
+        CodeList a, b, c, d;
+
+        //"plug a hole"
+        a.add(20, 40).add(60, 80).add(100, 140).add(150, 200);
+        b.add(40, 60).add(80, 100).add(140, 150);
+        c = a | b;
+        d = b | a;
+        assert(c == CodeList(20, 200), text(CodeList.stringof," ", c));
+        assert(c == d, text(c," vs ", d));
+
+        b = CodeList.init.add(25, 45).add(65, 85).add(95,110).add(150, 210);
+        c = a | b; //[20,45) [60, 85) [95, 140) [150, 210)
+        d = b | a;
+        assert(c == CodeList(20, 45, 60, 85, 95, 140, 150, 210), text(c));
+        assert(c == d, text(c," vs ", d));
+
+        b = CodeList.init.add(10, 20).add(30,100).add(145,200);
+        c = a | b;//[10, 140) [145, 200)
+        d = b | a;
+        assert(c == CodeList(10, 140, 145, 200));
+        assert(c == d, text(c," vs ", d));
+
+        b = CodeList.init.add(0, 10).add(15, 100).add(10, 20).add(200, 220);
+        c = a | b;//[0, 140) [150, 220)
+        d = b | a;
+        assert(c == CodeList(0, 140, 150, 220));
+        assert(c == d, text(c," vs ", d));
+
+
+        a = CodeList.init.add(20, 40).add(60, 80);
+        b = CodeList.init.add(25, 35).add(65, 75);
+        c = a & b;
+        d = b & a;
+        assert(c == CodeList(25, 35, 65, 75), text(c));
+        assert(c == d, text(c," vs ", d));
+
+        a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200);
+        b = CodeList.init.add(25, 35).add(65, 75).add(110, 130).add(160, 180);
+        c = a & b;
+        d = b & a;
+        assert(c == CodeList(25, 35, 65, 75, 110, 130, 160, 180), text(c));
+        assert(c == d, text(c," vs ", d));
+
+        a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200);
+        b = CodeList.init.add(10, 30).add(60, 120).add(135, 160);
+        c = a & b;//[20, 30)[60, 80) [100, 120) [135, 140) [150, 160)
+        d = b & a;
+
+        assert(c == CodeList(20, 30, 60, 80, 100, 120, 135, 140, 150, 160),text(c));
+        assert(c == d, text(c, " vs ",d));
+        assert((c & a) == c);
+        assert((d & b) == d);
+        assert((c & d) == d);
+
+        b = CodeList.init.add(40, 60).add(80, 100).add(140, 200);
+        c = a & b;
+        d = b & a;
+        assert(c == CodeList(150, 200), text(c));
+        assert(c == d, text(c, " vs ",d));
+        assert((c & a) == c);
+        assert((d & b) == d);
+        assert((c & d) == d);
+
+        assert((a & a) == a);
+        assert((b & b) == b);
+
+        a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200);
+        b = CodeList.init.add(30, 60).add(75, 120).add(190, 300);
+        c = a - b;// [30, 40) [60, 75) [120, 140) [150, 190)
+        d = b - a;// [40, 60) [80, 100) [200, 300)
+        assert(c == CodeList(20, 30, 60, 75, 120, 140, 150, 190), text(c));
+        assert(d == CodeList(40, 60, 80, 100, 200, 300), text(d));
+        assert(c - d == c, text(c-d, " vs ", c));
+        assert(d - c == d, text(d-c, " vs ", d));
+        assert(c - c == CodeList.init);
+        assert(d - d == CodeList.init);
+
+        a = CodeList.init.add(20, 40).add( 60, 80).add(100, 140).add(150,            200);
+        b = CodeList.init.add(10,  50).add(60,                           160).add(190, 300);
+        c = a - b;// [160, 190)
+        d = b - a;// [10, 20) [40, 50) [80, 100) [140, 150) [200, 300)
+        assert(c == CodeList(160, 190), text(c));
+        assert(d == CodeList(10, 20, 40, 50, 80, 100, 140, 150, 200, 300), text(d));
+        assert(c - d == c, text(c-d, " vs ", c));
+        assert(d - c == d, text(d-c, " vs ", d));
+        assert(c - c == CodeList.init);
+        assert(d - d == CodeList.init);
+
+        a = CodeList.init.add(20,    40).add(60, 80).add(100,      140).add(150,  200);
+        b = CodeList.init.add(10, 30).add(45,         100).add(130,             190);
+        c = a ~ b; // [10, 20) [30, 40) [45, 60) [80, 130) [140, 150) [190, 200)
+        d = b ~ a;
+        assert(c == CodeList(10, 20, 30, 40, 45, 60, 80, 130, 140, 150, 190, 200),
+               text(c));
+        assert(c == d, text(c, " vs ", d));
+    }
+}
+
+}
+
+@safe unittest// vs single dchar
+{
+    import std.conv;
+    CodepointSet a = CodepointSet(10, 100, 120, 200);
+    assert(a - 'A' == CodepointSet(10, 65, 66, 100, 120, 200), text(a - 'A'));
+    assert((a & 'B') == CodepointSet(66, 67));
+}
+
+@safe unittest// iteration & opIndex
+{
+    import std.conv;
+    import std.typecons;
+    foreach(CodeList; AliasSeq!(InversionList!(ReallocPolicy)))
+    {
+        auto arr = "ABCDEFGHIJKLMabcdefghijklm"d;
+        auto a = CodeList('A','N','a', 'n');
+        assert(equalS(a.byInterval,
+                [tuple(cast(uint)'A', cast(uint)'N'), tuple(cast(uint)'a', cast(uint)'n')]
+            ), text(a.byInterval));
+
+        // same @@@BUG as in issue 8949 ?
+        version(bug8949)
+        {
+            assert(equalS(retro(a.byInterval),
+                [tuple(cast(uint)'a', cast(uint)'n'), tuple(cast(uint)'A', cast(uint)'N')]
+            ), text(retro(a.byInterval)));
+        }
+        auto achr = a.byCodepoint;
+        assert(equalS(achr, arr), text(a.byCodepoint));
+        foreach(ch; a.byCodepoint)
+            assert(a[ch]);
+        auto x = CodeList(100, 500, 600, 900, 1200, 1500);
+        assert(equalS(x.byInterval, [ tuple(100, 500), tuple(600, 900), tuple(1200, 1500)]), text(x.byInterval));
+        foreach(ch; x.byCodepoint)
+            assert(x[ch]);
+        static if(is(CodeList == CodepointSet))
+        {
+            auto y = CodeList(x.byInterval);
+            assert(equalS(x.byInterval, y.byInterval));
+        }
+        assert(equalS(CodepointSet.init.byInterval, cast(Tuple!(uint, uint)[])[]));
+        assert(equalS(CodepointSet.init.byCodepoint, cast(dchar[])[]));
+    }
+}
+
+//============================================================================
+// Generic Trie template and various ways to build it
+//============================================================================
+
+// debug helper to get a shortened array dump
+auto arrayRepr(T)(T x)
+{
+    import std.conv : text;
+    if(x.length > 32)
+    {
+        return text(x[0..16],"~...~", x[x.length-16..x.length]);
+    }
+    else
+        return text(x);
+}
+
+/**
+    Maps $(D Key) to a suitable integer index within the range of $(D size_t).
+    The mapping is constructed by applying predicates from $(D Prefix) left to right
+    and concatenating the resulting bits.
+
+    The first (leftmost) predicate defines the most significant bits of
+    the resulting index.
+ */
+template mapTrieIndex(Prefix...)
+{
+    size_t mapTrieIndex(Key)(Key key)
+        if(isValidPrefixForTrie!(Key, Prefix))
+    {
+        alias p = Prefix;
+        size_t idx;
+        foreach(i, v; p[0..$-1])
+        {
+            idx |= p[i](key);
+            idx <<= p[i+1].bitSize;
+        }
+        idx |= p[$-1](key);
+        return idx;
+    }
+}
+
+/*
+    $(D TrieBuilder) is a type used for incremental construction
+    of $(LREF Trie)s.
+
+    See $(LREF buildTrie) for generic helpers built on top of it.
+*/
+@trusted struct TrieBuilder(Value, Key, Args...)
+    if(isBitPackableType!Value && isValidArgsForTrie!(Key, Args))
+{
+    import std.exception : enforce;
+
+private:
+    // last index is not stored in table, it is used as an offset to values in a block.
+    static if(is(Value == bool))// always pack bool
+        alias V = BitPacked!(Value, 1);
+    else
+        alias V = Value;
+    static auto deduceMaxIndex(Preds...)()
+    {
+        size_t idx = 1;
+        foreach(v; Preds)
+            idx *= 2^^v.bitSize;
+        return idx;
+    }
+
+    static if(is(typeof(Args[0]) : Key)) // Args start with upper bound on Key
+    {
+        alias Prefix = Args[1..$];
+        enum lastPageSize = 2^^Prefix[$-1].bitSize;
+        enum translatedMaxIndex = mapTrieIndex!(Prefix)(Args[0]);
+        enum roughedMaxIndex =
+            (translatedMaxIndex + lastPageSize-1)/lastPageSize*lastPageSize;
+        // check warp around - if wrapped, use the default deduction rule
+        enum maxIndex = roughedMaxIndex < translatedMaxIndex ?
+            deduceMaxIndex!(Prefix)() : roughedMaxIndex;
+    }
+    else
+    {
+        alias Prefix = Args;
+        enum maxIndex = deduceMaxIndex!(Prefix)();
+    }
+
+    alias getIndex = mapTrieIndex!(Prefix);
+
+    enum lastLevel = Prefix.length-1;
+    struct ConstructState
+    {
+        size_t idx_zeros, idx_ones;
+    }
+    // iteration over levels of Trie, each indexes its own level and thus a shortened domain
+    size_t[Prefix.length] indices;
+    // default filler value to use
+    Value defValue;
+    // this is a full-width index of next item
+    size_t curIndex;
+    // all-zeros page index, all-ones page index (+ indicator if there is such a page)
+    ConstructState[Prefix.length] state;
+    // the table being constructed
+    MultiArray!(idxTypes!(Key, fullBitSize!(Prefix), Prefix[0..$]), V) table;
+
+    @disable this();
+
+    //shortcut for index variable at level 'level'
+    @property ref idx(size_t level)(){ return indices[level]; }
+
+    // this function assumes no holes in the input so
+    // indices are going one by one
+    void addValue(size_t level, T)(T val, size_t numVals)
+    {
+        alias j = idx!level;
+        enum pageSize = 1<<Prefix[level].bitSize;
+        if(numVals == 0)
+            return;
+        auto ptr = table.slice!(level);
+        if(numVals == 1)
+        {
+            static if(level == Prefix.length-1)
+                ptr[j] = val;
+            else
+            {// can incur narrowing conversion
+                assert(j < ptr.length);
+                ptr[j] = force!(typeof(ptr[j]))(val);
+            }
+            j++;
+            if(j % pageSize == 0)
+                spillToNextPage!level(ptr);
+            return;
+        }
+        // longer row of values
+        // get to the next page boundary
+        size_t nextPB = (j + pageSize) & ~(pageSize-1);
+        size_t n =  nextPB - j;// can fill right in this page
+        if(numVals < n) //fits in current page
+        {
+            ptr[j..j+numVals]  = val;
+            j += numVals;
+            return;
+        }
+        static if(level != 0)//on the first level it always fits
+        {
+            numVals -= n;
+            //write till the end of current page
+            ptr[j..j+n]  = val;
+            j += n;
+            //spill to the next page
+            spillToNextPage!level(ptr);
+            // page at once loop
+            if(state[level].idx_zeros != size_t.max && val == T.init)
+            {
+                alias NextIdx = typeof(table.slice!(level-1)[0]);
+                addValue!(level-1)(force!NextIdx(state[level].idx_zeros),
+                    numVals/pageSize);
+                ptr = table.slice!level; //table structure might have changed
+                numVals %= pageSize;
+            }
+            else
+            {
+                while(numVals >= pageSize)
+                {
+                    numVals -= pageSize;
+                    ptr[j..j+pageSize]  = val;
+                    j += pageSize;
+                    spillToNextPage!level(ptr);
+                }
+            }
+            if(numVals)
+            {
+                // the leftovers, an incomplete page
+                ptr[j..j+numVals]  = val;
+                j += numVals;
+            }
+        }
+    }
+
+    void spillToNextPage(size_t level, Slice)(ref Slice ptr)
+    {
+        // last level (i.e. topmost) has 1 "page"
+        // thus it need not to add a new page on upper level
+        static if(level != 0)
+            spillToNextPageImpl!(level)(ptr);
+    }
+
+    // this can re-use the current page if duplicate or allocate a new one
+    // it also makes sure that previous levels point to the correct page in this level
+    void spillToNextPageImpl(size_t level, Slice)(ref Slice ptr)
+    {
+        alias NextIdx = typeof(table.slice!(level-1)[0]);
+        NextIdx next_lvl_index;
+        enum pageSize = 1<<Prefix[level].bitSize;
+        assert(idx!level % pageSize == 0);
+        auto last = idx!level-pageSize;
+        auto slice = ptr[idx!level - pageSize..idx!level];
+        size_t j;
+        for(j=0; j<last; j+=pageSize)
+        {
+            if(ptr[j..j+pageSize] == slice)
+            {
+                // get index to it, reuse ptr space for the next block
+                next_lvl_index = force!NextIdx(j/pageSize);
+                version(none)
+                {
+                writefln("LEVEL(%s) page maped idx: %s: 0..%s  ---> [%s..%s]"
+                        ,level
+                        ,indices[level-1], pageSize, j, j+pageSize);
+                writeln("LEVEL(", level
+                        , ") mapped page is: ", slice, ": ", arrayRepr(ptr[j..j+pageSize]));
+                writeln("LEVEL(", level
+                        , ") src page is :", ptr, ": ", arrayRepr(slice[0..pageSize]));
+                }
+                idx!level -= pageSize; // reuse this page, it is duplicate
+                break;
+            }
+        }
+        if(j == last)
+        {
+    L_allocate_page:
+            next_lvl_index = force!NextIdx(idx!level/pageSize - 1);
+            if(state[level].idx_zeros == size_t.max && ptr.zeros(j, j+pageSize))
+            {
+                state[level].idx_zeros = next_lvl_index;
+            }
+            // allocate next page
+            version(none)
+            {
+            writefln("LEVEL(%s) page allocated: %s"
+                     , level, arrayRepr(slice[0..pageSize]));
+            writefln("LEVEL(%s) index: %s ; page at this index %s"
+                     , level
+                     , next_lvl_index
+                     , arrayRepr(
+                         table.slice!(level)
+                          [pageSize*next_lvl_index..(next_lvl_index+1)*pageSize]
+                        ));
+            }
+            table.length!level = table.length!level + pageSize;
+        }
+    L_know_index:
+        // for the previous level, values are indices to the pages in the current level
+        addValue!(level-1)(next_lvl_index, 1);
+        ptr = table.slice!level; //re-load the slice after moves
+    }
+
+    // idx - full-width index to fill with v (full-width index != key)
+    // fills everything in the range of [curIndex, idx) with filler
+    void putAt(size_t idx, Value v)
+    {
+        assert(idx >= curIndex);
+        size_t numFillers = idx - curIndex;
+        addValue!lastLevel(defValue, numFillers);
+        addValue!lastLevel(v, 1);
+        curIndex = idx + 1;
+    }
+
+    // ditto, but sets the range of [idxA, idxB) to v
+    void putRangeAt(size_t idxA, size_t idxB, Value v)
+    {
+        assert(idxA >= curIndex);
+        assert(idxB >= idxA);
+        size_t numFillers = idxA - curIndex;
+        addValue!lastLevel(defValue, numFillers);
+        addValue!lastLevel(v, idxB - idxA);
+        curIndex = idxB; // open-right
+    }
+
+    enum errMsg = "non-monotonic prefix function(s), an unsorted range or "~
+        "duplicate key->value mapping";
+
+public:
+    /**
+        Construct a builder, where $(D filler) is a value
+        to indicate empty slots (or "not found" condition).
+    */
+    this(Value filler)
+    {
+        curIndex = 0;
+        defValue = filler;
+        // zeros-page index, ones-page index
+        foreach(ref v; state)
+            v = ConstructState(size_t.max, size_t.max);
+        table = typeof(table)(indices);
+        // one page per level is a bootstrap minimum
+        foreach(i, Pred; Prefix)
+            table.length!i = (1<<Pred.bitSize);
+    }
+
+    /**
+        Put a value $(D v) into interval as
+        mapped by keys from $(D a) to $(D b).
+        All slots prior to $(D a) are filled with
+        the default filler.
+    */
+    void putRange(Key a, Key b, Value v)
+    {
+        auto idxA = getIndex(a), idxB = getIndex(b);
+        // indexes of key should always grow
+        enforce(idxB >= idxA && idxA >= curIndex, errMsg);
+        putRangeAt(idxA, idxB, v);
+    }
+
+    /**
+        Put a value $(D v) into slot mapped by $(D key).
+        All slots prior to $(D key) are filled with the
+        default filler.
+    */
+    void putValue(Key key, Value v)
+    {
+        import std.conv : text;
+        auto idx = getIndex(key);
+        enforce(idx >= curIndex, text(errMsg, " ", idx));
+        putAt(idx, v);
+    }
+
+    /// Finishes construction of Trie, yielding an immutable Trie instance.
+    auto build()
+    {
+        static if(maxIndex != 0) // doesn't cover full range of size_t
+        {
+            assert(curIndex <= maxIndex);
+            addValue!lastLevel(defValue, maxIndex - curIndex);
+        }
+        else
+        {
+            if(curIndex != 0 // couldn't wrap around
+                || (Prefix.length != 1 && indices[lastLevel] == 0)) // can be just empty
+            {
+                addValue!lastLevel(defValue, size_t.max - curIndex);
+                addValue!lastLevel(defValue, 1);
+            }
+            // else curIndex already completed the full range of size_t by wrapping around
+        }
+        return Trie!(V, Key, maxIndex, Prefix)(table);
+    }
+}
+
+/*
+    $(P A generic Trie data-structure for a fixed number of stages.
+    The design goal is optimal speed with smallest footprint size.
+    )
+    $(P It's intentionally read-only and doesn't provide constructors.
+     To construct one use a special builder,
+     see $(LREF TrieBuilder) and $(LREF buildTrie).
+    )
+
+*/
+@trusted public struct Trie(Value, Key, Args...)
+    if(isValidPrefixForTrie!(Key, Args)
+        || (isValidPrefixForTrie!(Key, Args[1..$])
+            && is(typeof(Args[0]) : size_t)))
+{
+    static if(is(typeof(Args[0]) : size_t))
+    {
+        enum maxIndex = Args[0];
+        enum hasBoundsCheck = true;
+        alias Prefix = Args[1..$];
+    }
+    else
+    {
+        enum hasBoundsCheck = false;
+        alias Prefix = Args;
+    }
+
+    private this()(typeof(_table) table)
+    {
+        _table = table;
+    }
+
+    // only for constant Tries constructed from precompiled tables
+    private this()(const(size_t)[] offsets, const(size_t)[] sizes,
+        const(size_t)[] data) const
+    {
+        _table = typeof(_table)(offsets, sizes, data);
+    }
+
+    /*
+        $(P Lookup the $(D key) in this $(D Trie). )
+
+        $(P The lookup always succeeds if key fits the domain
+        provided during construction. The whole domain defined
+        is covered so instead of not found condition
+        the sentinel (filler) value could be used. )
+
+        $(P See $(LREF buildTrie), $(LREF TrieBuilder) for how to
+        define a domain of $(D Trie) keys and the sentinel value. )
+
+        Note:
+        Domain range-checking is only enabled in debug builds
+        and results in assertion failure.
+    */
+    // templated to auto-detect pure, @safe and nothrow
+    TypeOfBitPacked!Value opIndex()(Key key) const
+    {
+        static if(hasBoundsCheck)
+            assert(mapTrieIndex!Prefix(key) < maxIndex);
+        size_t idx;
+        alias p = Prefix;
+        idx = cast(size_t)p[0](key);
+        foreach(i, v; p[0..$-1])
+            idx = cast(size_t)((_table.ptr!i[idx]<<p[i+1].bitSize) + p[i+1](key));
+        return _table.ptr!(p.length-1)[idx];
+    }
+
+    @property size_t bytes(size_t n=size_t.max)() const
+    {
+        return _table.bytes!n;
+    }
+
+    @property size_t pages(size_t n)() const
+    {
+        return (bytes!n+2^^(Prefix[n].bitSize-1))
+                /2^^Prefix[n].bitSize;
+    }
+
+    void store(OutRange)(scope OutRange sink) const
+        if(isOutputRange!(OutRange, char))
+    {
+        _table.store(sink);
+    }
+
+private:
+    MultiArray!(idxTypes!(Key, fullBitSize!(Prefix), Prefix[0..$]), Value) _table;
+}
+
+// create a tuple of 'sliceBits' that slice the 'top' of bits into pieces of sizes 'sizes'
+// left-to-right, the most significant bits first
+template GetBitSlicing(size_t top, sizes...)
+{
+    static if(sizes.length > 0)
+        alias GetBitSlicing =
+            AliasSeq!(sliceBits!(top - sizes[0], top),
+                      GetBitSlicing!(top - sizes[0], sizes[1..$]));
+    else
+        alias GetBitSlicing = AliasSeq!();
+}
+
+template callableWith(T)
+{
+    template callableWith(alias Pred)
+    {
+        static if(!is(typeof(Pred(T.init))))
+            enum callableWith = false;
+        else
+        {
+            alias Result = typeof(Pred(T.init));
+            enum callableWith = isBitPackableType!(TypeOfBitPacked!(Result));
+        }
+    }
+}
+
+/*
+    Check if $(D Prefix) is a valid set of predicates
+    for $(D Trie) template having $(D Key) as the type of keys.
+    This requires all predicates to be callable, take
+    single argument of type $(D Key) and return unsigned value.
+*/
+template isValidPrefixForTrie(Key, Prefix...)
+{
+    enum isValidPrefixForTrie = allSatisfy!(callableWith!Key, Prefix); // TODO: tighten the screws
+}
+
+/*
+    Check if $(D Args) is a set of maximum key value followed by valid predicates
+    for $(D Trie) template having $(D Key) as the type of keys.
+*/
+template isValidArgsForTrie(Key, Args...)
+{
+    static if(Args.length > 1)
+    {
+        enum isValidArgsForTrie = isValidPrefixForTrie!(Key, Args)
+            || (isValidPrefixForTrie!(Key, Args[1..$]) && is(typeof(Args[0]) : Key));
+    }
+    else
+        enum isValidArgsForTrie = isValidPrefixForTrie!Args;
+}
+
+@property size_t sumOfIntegerTuple(ints...)()
+{
+    size_t count=0;
+    foreach(v; ints)
+        count += v;
+    return count;
+}
+
+/**
+    A shorthand for creating a custom multi-level fixed Trie
+    from a $(D CodepointSet). $(D sizes) are numbers of bits per level,
+    with the most significant bits used first.
+
+    Note: The sum of $(D sizes) must be equal 21.
+
+    See_Also: $(LREF toTrie), which is even simpler.
+
+    Example:
+    ---
+    {
+        import std.stdio;
+        auto set = unicode("Number");
+        auto trie = codepointSetTrie!(8, 5, 8)(set);
+        writeln("Input code points to test:");
+        foreach(line; stdin.byLine)
+        {
+            int count=0;
+            foreach(dchar ch; line)
+                if(trie[ch])// is number
+                    count++;
+            writefln("Contains %d number code points.", count);
+        }
+    }
+    ---
+*/
+public template codepointSetTrie(sizes...)
+    if(sumOfIntegerTuple!sizes == 21)
+{
+    auto codepointSetTrie(Set)(Set set)
+        if(isCodepointSet!Set)
+    {
+        auto builder = TrieBuilder!(bool, dchar, lastDchar+1, GetBitSlicing!(21, sizes))(false);
+        foreach(ival; set.byInterval)
+            builder.putRange(ival[0], ival[1], true);
+        return builder.build();
+    }
+}
+
+/// Type of Trie generated by codepointSetTrie function.
+public template CodepointSetTrie(sizes...)
+    if(sumOfIntegerTuple!sizes == 21)
+{
+    alias Prefix = GetBitSlicing!(21, sizes);
+    alias CodepointSetTrie = typeof(TrieBuilder!(bool, dchar, lastDchar+1, Prefix)(false).build());
+}
+
+/**
+    A slightly more general tool for building fixed $(D Trie)
+    for the Unicode data.
+
+    Specifically unlike $(D codepointSetTrie) it's allows creating mappings
+    of $(D dchar) to an arbitrary type $(D T).
+
+    Note: Overload taking $(D CodepointSet)s will naturally convert
+    only to bool mapping $(D Trie)s.
+*/
+public template codepointTrie(T, sizes...)
+    if(sumOfIntegerTuple!sizes == 21)
+{
+    alias Prefix = GetBitSlicing!(21, sizes);
+
+    static if(is(TypeOfBitPacked!T == bool))
+    {
+        auto codepointTrie(Set)(in Set set)
+            if(isCodepointSet!Set)
+        {
+            return codepointSetTrie(set);
+        }
+    }
+
+    auto codepointTrie()(T[dchar] map, T defValue=T.init)
+    {
+        return buildTrie!(T, dchar, Prefix)(map, defValue);
+    }
+
+    // unsorted range of pairs
+    auto codepointTrie(R)(R range, T defValue=T.init)
+        if(isInputRange!R
+            && is(typeof(ElementType!R.init[0]) : T)
+            && is(typeof(ElementType!R.init[1]) : dchar))
+    {
+        // build from unsorted array of pairs
+        // TODO: expose index sorting functions for Trie
+        return buildTrie!(T, dchar, Prefix)(range, defValue, true);
+    }
+}
+
+///
+pure unittest
+{
+    import std.algorithm.comparison : max;
+    import std.algorithm.searching : count;
+
+    // pick characters from the Greek script
+    auto set = unicode.Greek;
+
+    // a user-defined property (or an expensive function)
+    // that we want to look up
+    static uint luckFactor(dchar ch)
+    {
+        // here we consider a character lucky
+        // if its code point has a lot of identical hex-digits
+        // e.g. arabic letter DDAL (\u0688) has a "luck factor" of 2
+        ubyte[6] nibbles; // 6 4-bit chunks of code point
+        uint value = ch;
+        foreach(i; 0..6)
+        {
+            nibbles[i] = value & 0xF;
+            value >>= 4;
+        }
+        uint luck;
+        foreach(n; nibbles)
+            luck = cast(uint)max(luck, count(nibbles[], n));
+        return luck;
+    }
+
+    // only unsigned built-ins are supported at the moment
+    alias LuckFactor = BitPacked!(uint, 3);
+
+    // create a temporary associative array (AA)
+    LuckFactor[dchar] map;
+    foreach(ch; set.byCodepoint)
+        map[ch] = LuckFactor(luckFactor(ch));
+
+    // bits per stage are chosen randomly, fell free to optimize
+    auto trie = codepointTrie!(LuckFactor, 8, 5, 8)(map);
+
+    // from now on the AA is not needed
+    foreach(ch; set.byCodepoint)
+        assert(trie[ch] == luckFactor(ch)); // verify
+    // CJK is not Greek, thus it has the default value
+    assert(trie['\u4444'] == 0);
+    // and here is a couple of quite lucky Greek characters:
+    // Greek small letter epsilon with dasia
+    assert(trie['\u1F11'] == 3);
+    // Ancient Greek metretes sign
+    assert(trie['\U00010181'] == 3);
+
+}
+
+/// Type of Trie as generated by codepointTrie function.
+public template CodepointTrie(T, sizes...)
+    if(sumOfIntegerTuple!sizes == 21)
+{
+    alias Prefix = GetBitSlicing!(21, sizes);
+    alias CodepointTrie = typeof(TrieBuilder!(T, dchar, lastDchar+1, Prefix)(T.init).build());
+}
+
+// @@@BUG multiSort can's access private symbols from uni
+public template cmpK0(alias Pred)
+{
+    import std.typecons;
+    static bool cmpK0(Value, Key)
+        (Tuple!(Value, Key) a, Tuple!(Value, Key) b)
+    {
+        return Pred(a[1]) < Pred(b[1]);
+    }
+}
+
+/*
+    The most general utility for construction of $(D Trie)s
+    short of using $(D TrieBuilder) directly.
+
+    Provides a number of convenience overloads.
+    $(D Args) is tuple of maximum key value followed by
+    predicates to construct index from key.
+
+    Alternatively if the first argument is not a value convertible to $(D Key)
+    then the whole tuple of $(D Args) is treated as predicates
+    and the maximum Key is deduced from predicates.
+*/
+public template buildTrie(Value, Key, Args...)
+    if(isValidArgsForTrie!(Key, Args))
+{
+    static if(is(typeof(Args[0]) : Key)) // prefix starts with upper bound on Key
+    {
+        alias Prefix = Args[1..$];
+    }
+    else
+        alias Prefix = Args;
+
+    alias getIndex = mapTrieIndex!(Prefix);
+
+    // for multi-sort
+    template GetComparators(size_t n)
+    {
+        static if(n > 0)
+            alias GetComparators =
+                AliasSeq!(GetComparators!(n-1), cmpK0!(Prefix[n-1]));
+        else
+            alias GetComparators = AliasSeq!();
+    }
+
+    /*
+        Build $(D Trie) from a range of a Key-Value pairs,
+        assuming it is sorted by Key as defined by the following lambda:
+        ------
+        (a, b) => mapTrieIndex!(Prefix)(a) < mapTrieIndex!(Prefix)(b)
+        ------
+        Exception is thrown if it's detected that the above order doesn't hold.
+
+        In other words $(LREF mapTrieIndex) should be a
+        monotonically increasing function that maps $(D Key) to an integer.
+
+        See_Also: $(XREF _algorithm, sort),
+        $(XREF _range, SortedRange),
+        $(XREF _algorithm, setUnion).
+    */
+    auto buildTrie(Range)(Range range, Value filler=Value.init)
+        if(isInputRange!Range && is(typeof(Range.init.front[0]) : Value)
+            && is(typeof(Range.init.front[1]) : Key))
+    {
+        auto builder = TrieBuilder!(Value, Key, Prefix)(filler);
+        foreach(v; range)
+            builder.putValue(v[1], v[0]);
+        return builder.build();
+    }
+
+    /*
+        If $(D Value) is bool (or BitPacked!(bool, x)) then it's possible
+        to build $(D Trie) from a range of open-right intervals of $(D Key)s.
+        The requirement  on the ordering of keys (and the behavior on the
+        violation of it) is the same as for Key-Value range overload.
+
+        Intervals denote ranges of !$(D filler) i.e. the opposite of filler.
+        If no filler provided keys inside of the intervals map to true,
+        and $(D filler) is false.
+    */
+    auto buildTrie(Range)(Range range, Value filler=Value.init)
+        if(is(TypeOfBitPacked!Value ==  bool)
+            && isInputRange!Range && is(typeof(Range.init.front[0]) : Key)
+            && is(typeof(Range.init.front[1]) : Key))
+    {
+        auto builder = TrieBuilder!(Value, Key, Prefix)(filler);
+        foreach(ival; range)
+            builder.putRange(ival[0], ival[1], !filler);
+        return builder.build();
+    }
+
+    auto buildTrie(Range)(Range range, Value filler, bool unsorted)
+        if(isInputRange!Range
+            && is(typeof(Range.init.front[0]) : Value)
+            && is(typeof(Range.init.front[1]) : Key))
+    {
+        import std.algorithm : multiSort;
+        alias Comps = GetComparators!(Prefix.length);
+        if(unsorted)
+            multiSort!(Comps)(range);
+        return buildTrie(range, filler);
+    }
+
+    /*
+        If $(D Value) is bool (or BitPacked!(bool, x)) then it's possible
+        to build $(D Trie) simply from an input range of $(D Key)s.
+        The requirement  on the ordering of keys (and the behavior on the
+        violation of it) is the same as for Key-Value range overload.
+
+        Keys found in range denote !$(D filler) i.e. the opposite of filler.
+        If no filler provided keys map to true, and $(D filler) is false.
+    */
+    auto buildTrie(Range)(Range range, Value filler=Value.init)
+        if(is(TypeOfBitPacked!Value ==  bool)
+            && isInputRange!Range && is(typeof(Range.init.front) : Key))
+    {
+        auto builder = TrieBuilder!(Value, Key, Prefix)(filler);
+        foreach(v; range)
+            builder.putValue(v, !filler);
+        return builder.build();
+    }
+
+    /*
+        If $(D Key) is unsigned integer $(D Trie) could be constructed from array
+        of values where array index serves as key.
+    */
+    auto buildTrie()(Value[] array, Value filler=Value.init)
+        if(isUnsigned!Key)
+    {
+        auto builder = TrieBuilder!(Value, Key, Prefix)(filler);
+        foreach(idx, v; array)
+            builder.putValue(idx, v);
+        return builder.build();
+    }
+
+    /*
+        Builds $(D Trie) from associative array.
+    */
+    auto buildTrie(Key, Value)(Value[Key] map, Value filler=Value.init)
+    {
+        import std.range : zip, array;
+        auto range = array(zip(map.values, map.keys));
+        return buildTrie(range, filler, true); // sort it
+    }
+}
+
+// helper in place of assumeSize to
+//reduce mangled name & help DMD inline Trie functors
+struct clamp(size_t bits)
+{
+    static size_t opCall(T)(T arg){ return arg; }
+    enum bitSize = bits;
+}
+
+struct clampIdx(size_t idx, size_t bits)
+{
+    static size_t opCall(T)(T arg){ return arg[idx]; }
+    enum bitSize = bits;
+}
+
+/**
+    Conceptual type that outlines the common properties of all UTF Matchers.
+
+    Note: For illustration purposes only, every method
+    call results in assertion failure.
+    Use $(LREF utfMatcher) to obtain a concrete matcher
+    for UTF-8 or UTF-16 encodings.
+*/
+public struct MatcherConcept
+{
+    /**
+        $(P Perform a semantic equivalent 2 operations:
+        decoding a $(CODEPOINT) at front of $(D inp) and testing if
+        it belongs to the set of $(CODEPOINTS) of this matcher. )
+
+        $(P The effect on $(D inp) depends on the kind of function called:)
+
+        $(P Match. If the codepoint is found in the set then range $(D inp)
+        is advanced by its size in $(S_LINK Code unit, code units),
+        otherwise the range is not modifed.)
+
+        $(P Skip. The range is always advanced by the size
+        of the tested $(CODEPOINT) regardless of the result of test.)
+
+        $(P Test. The range is left unaffected regardless
+        of the result of test.)
+    */
+    public bool match(Range)(ref Range inp)
+        if(isRandomAccessRange!Range && is(ElementType!Range : char))
+    {
+       assert(false);
+    }
+
+    ///ditto
+    public bool skip(Range)(ref Range inp)
+        if(isRandomAccessRange!Range && is(ElementType!Range : char))
+    {
+        assert(false);
+    }
+
+    ///ditto
+    public bool test(Range)(ref Range inp)
+        if(isRandomAccessRange!Range && is(ElementType!Range : char))
+    {
+        assert(false);
+    }
+    ///
+    @safe unittest
+    {
+        string truth = "2² = 4";
+        auto m = utfMatcher!char(unicode.Number);
+        assert(m.match(truth)); // '2' is a number all right
+        assert(truth == "² = 4"); // skips on match
+        assert(m.match(truth)); // so is the superscript '2'
+        assert(!m.match(truth)); // space is not a number
+        assert(truth == " = 4"); // unaffected on no match
+        assert(!m.skip(truth)); // same test ...
+        assert(truth == "= 4"); // but skips a codepoint regardless
+        assert(!m.test(truth)); // '=' is not a number
+        assert(truth == "= 4"); // test never affects argument
+    }
+
+    /*
+        Advanced feature - provide direct access to a subset of matcher based a
+        set of known encoding lengths. Lengths are provided in
+        $(S_LINK Code unit, code units). The sub-matcher then may do less
+        operations per any $(D test)/$(D match).
+
+        Use with care as the sub-matcher won't match
+        any $(CODEPOINTS) that have encoded length that doesn't belong
+        to the selected set of lengths. Also the sub-matcher object references
+        the parent matcher and must not be used past the liftetime
+        of the latter.
+
+        Another caveat of using sub-matcher is that skip is not available
+        preciesly because sub-matcher doesn't detect all lengths.
+    */
+    @property auto subMatcher(Lengths...)()
+    {
+        assert(0);
+        return this;
+    }
+
+    ///
+    @safe unittest
+    {
+        auto m = utfMatcher!char(unicode.Number);
+        string square = "2²";
+        // about sub-matchers
+        assert(!m.subMatcher!(2,3,4).test(square)); // ASCII no covered
+        assert(m.subMatcher!1.match(square)); // ASCII-only, works
+        assert(!m.subMatcher!1.test(square)); // unicode '²'
+        assert(m.subMatcher!(2,3,4).match(square));  //
+        assert(square == "");
+        wstring wsquare = "2²";
+        auto m16 = utfMatcher!wchar(unicode.Number);
+        // may keep ref, but the orignal (m16) must be kept alive
+        auto bmp = m16.subMatcher!1;
+        assert(bmp.match(wsquare)); // Okay, in basic multilingual plan
+        assert(bmp.match(wsquare)); // And '²' too
+    }
+}
+
+/**
+    Test if $(D M) is an UTF Matcher for ranges of $(D Char).
+*/
+public enum isUtfMatcher(M, C) = __traits(compiles, (){
+    C[] s;
+    auto d = s.decoder;
+    M m;
+    assert(is(typeof(m.match(d)) == bool));
+    assert(is(typeof(m.test(d)) == bool));
+    static if(is(typeof(m.skip(d))))
+    {
+        assert(is(typeof(m.skip(d)) == bool));
+        assert(is(typeof(m.skip(s)) == bool));
+    }
+    assert(is(typeof(m.match(s)) == bool));
+    assert(is(typeof(m.test(s)) == bool));
+});
+
+unittest
+{
+    alias CharMatcher = typeof(utfMatcher!char(CodepointSet.init));
+    alias WcharMatcher = typeof(utfMatcher!wchar(CodepointSet.init));
+    static assert(isUtfMatcher!(CharMatcher, char));
+    static assert(isUtfMatcher!(CharMatcher, immutable(char)));
+    static assert(isUtfMatcher!(WcharMatcher, wchar));
+    static assert(isUtfMatcher!(WcharMatcher, immutable(wchar)));
+}
+
+enum Mode {
+    alwaysSkip,
+    neverSkip,
+    skipOnMatch
+}
+
+mixin template ForwardStrings()
+{
+    private bool fwdStr(string fn, C)(ref C[] str) const pure
+    {
+        alias type = typeof(units(str));
+        return mixin(fn~"(*cast(type*)&str)");
+    }
+}
+
+template Utf8Matcher()
+{
+    enum validSize(int sz) = sz >= 1 && sz <=4;
+
+    void badEncoding() pure @safe
+    {
+        import std.utf;
+        throw new UTFException("Invalid UTF-8 sequence");
+    }
+
+    //for 1-stage ASCII
+    alias AsciiSpec = AliasSeq!(bool, char, clamp!7);
+    //for 2-stage lookup of 2 byte UTF-8 sequences
+    alias Utf8Spec2 = AliasSeq!(bool, char[2],
+        clampIdx!(0, 5), clampIdx!(1, 6));
+    //ditto for 3 byte
+    alias Utf8Spec3 = AliasSeq!(bool, char[3],
+        clampIdx!(0, 4),
+        clampIdx!(1, 6),
+        clampIdx!(2, 6)
+    );
+    //ditto for 4 byte
+    alias Utf8Spec4 = AliasSeq!(bool, char[4],
+        clampIdx!(0, 3), clampIdx!(1, 6),
+        clampIdx!(2, 6), clampIdx!(3, 6)
+    );
+    alias Tables = AliasSeq!(
+        typeof(TrieBuilder!(AsciiSpec)(false).build()),
+        typeof(TrieBuilder!(Utf8Spec2)(false).build()),
+        typeof(TrieBuilder!(Utf8Spec3)(false).build()),
+        typeof(TrieBuilder!(Utf8Spec4)(false).build())
+    );
+    alias Table(int size) = Tables[size-1];
+
+    enum leadMask(size_t size) = (cast(size_t)1<<(7 - size))-1;
+    enum encMask(size_t size) = ((1<<size)-1)<<(8-size);
+
+    char truncate()(char ch) pure @safe
+    {
+        ch -= 0x80;
+        if (ch < 0x40)
+        {
+            return ch;
+        }
+        else
+        {
+            badEncoding();
+            return cast(char)0;
+        }
+    }
+
+    static auto encode(size_t sz)(dchar ch)
+        if(sz > 1)
+    {
+        import std.utf : encode;
+        char[4] buf;
+        std.utf.encode(buf, ch);
+        char[sz] ret;
+        buf[0] &= leadMask!sz;
+        foreach(n; 1..sz)
+            buf[n] = buf[n] & 0x3f; //keep 6 lower bits
+        ret[] = buf[0..sz];
+        return ret;
+    }
+
+    auto build(Set)(Set set)
+    {
+        import std.algorithm : map;
+        auto ascii = set & unicode.ASCII;
+        auto utf8_2 = set & CodepointSet(0x80, 0x800);
+        auto utf8_3 = set & CodepointSet(0x800, 0x1_0000);
+        auto utf8_4 = set & CodepointSet(0x1_0000, lastDchar+1);
+        auto asciiT = ascii.byCodepoint.map!(x=>cast(char)x).buildTrie!(AsciiSpec);
+        auto utf8_2T = utf8_2.byCodepoint.map!(x=>encode!2(x)).buildTrie!(Utf8Spec2);
+        auto utf8_3T = utf8_3.byCodepoint.map!(x=>encode!3(x)).buildTrie!(Utf8Spec3);
+        auto utf8_4T = utf8_4.byCodepoint.map!(x=>encode!4(x)).buildTrie!(Utf8Spec4);
+        alias Ret = Impl!(1,2,3,4);
+        return Ret(asciiT, utf8_2T, utf8_3T, utf8_4T);
+    }
+
+    // Bootstrap UTF-8 static matcher interface
+    // from 3 primitives: tab!(size), lookup and Sizes
+    mixin template DefMatcher()
+    {
+        import std.format : format;
+        enum hasASCII = staticIndexOf!(1, Sizes) >= 0;
+        alias UniSizes = Erase!(1, Sizes);
+
+        //generate dispatch code sequence for unicode parts
+        static auto genDispatch()
+        {
+            string code;
+            foreach(size; UniSizes)
+                code ~= format(q{
+                    if ((ch & ~leadMask!%d) == encMask!(%d))
+                        return lookup!(%d, mode)(inp);
+                    else
+                }, size, size, size);
+            static if (Sizes.length == 4) //covers all code unit cases
+                code ~= "{ badEncoding(); return false; }";
+            else
+                code ~= "return false;"; //may be just fine but not covered
+            return code;
+        }
+        enum dispatch = genDispatch();
+
+        public bool match(Range)(ref Range inp) const pure @trusted
+            if(isRandomAccessRange!Range && is(ElementType!Range : char))
+        {
+            enum mode = Mode.skipOnMatch;
+            assert(!inp.empty);
+            auto ch = inp[0];
+            static if(hasASCII)
+            {
+                if (ch < 0x80)
+                {
+                    bool r = tab!1[ch];
+                    if(r)
+                        inp.popFront();
+                    return r;
+                }
+                else
+                    mixin(dispatch);
+            }
+            else
+                mixin(dispatch);
+        }
+
+        static if(Sizes.length == 4) // can skip iff can detect all encodings
+        {
+            public bool skip(Range)(ref Range inp) const pure @trusted
+                if(isRandomAccessRange!Range && is(ElementType!Range : char))
+            {
+                enum mode = Mode.alwaysSkip;
+                assert(!inp.empty);
+                auto ch = inp[0];
+                static if(hasASCII)
+                {
+                    if (ch < 0x80)
+                    {
+                        inp.popFront();
+                        return tab!1[ch];
+                    }
+                    else
+                        mixin(dispatch);
+                }
+                else
+                    mixin(dispatch);
+            }
+        }
+
+        public bool test(Range)(ref Range inp) const pure @trusted
+            if(isRandomAccessRange!Range && is(ElementType!Range : char))
+        {
+            enum mode = Mode.neverSkip;
+            assert(!inp.empty);
+            auto ch = inp[0];
+            static if(hasASCII)
+            {
+                if (ch < 0x80)
+                    return tab!1[ch];
+                else
+                    mixin(dispatch);
+            }
+            else
+                mixin(dispatch);
+        }
+
+        bool match(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"match"(str);
+        }
+
+        bool skip(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"skip"(str);
+        }
+
+        bool test(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"test"(str);
+        }
+
+        mixin ForwardStrings;
+    }
+
+    struct Impl(Sizes...)
+    {
+        static assert(allSatisfy!(validSize, Sizes),
+            "Only lengths of 1, 2, 3 and 4 code unit are possible for UTF-8");
+    private:
+        //pick tables for chosen sizes
+        alias OurTabs = staticMap!(Table, Sizes);
+        OurTabs tables;
+        mixin DefMatcher;
+        //static disptach helper UTF size ==> table
+        alias tab(int i) = tables[i - 1];
+
+        package @property auto subMatcher(SizesToPick...)() @trusted
+        {
+            return CherryPick!(Impl, SizesToPick)(&this);
+        }
+
+        bool lookup(int size, Mode mode, Range)(ref Range inp) const pure @trusted
+        {
+            import std.typecons;
+            if(inp.length < size)
+            {
+                badEncoding();
+                return false;
+            }
+            char[size] needle = void;
+            needle[0] = leadMask!size & inp[0];
+            foreach(i; staticIota!(1, size))
+            {
+                needle[i] = truncate(inp[i]);
+            }
+            //overlong encoding checks
+            static if(size == 2)
+            {
+                //0x80-0x7FF
+                //got 6 bits in needle[1], must use at least 8 bits
+                //must use at least 2 bits in needle[1]
+                if(needle[0] < 2) badEncoding();
+            }
+            else static if(size == 3)
+            {
+                //0x800-0xFFFF
+                //got 6 bits in needle[2], must use at least 12bits
+                //must use 6 bits in needle[1] or anything in needle[0]
+                if(needle[0] == 0 && needle[1] < 0x20) badEncoding();
+            }
+            else static if(size == 4)
+            {
+                //0x800-0xFFFF
+                //got 2x6=12 bits in needle[2..3] must use at least 17bits
+                //must use 5 bits (or above) in needle[1] or anything in needle[0]
+                if(needle[0] == 0 && needle[1] < 0x10) badEncoding();
+            }
+            static if(mode == Mode.alwaysSkip)
+            {
+                inp.popFrontN(size);
+                return tab!size[needle];
+            }
+            else static if(mode == Mode.neverSkip)
+            {
+                return tab!size[needle];
+            }
+            else
+            {
+                static assert(mode == Mode.skipOnMatch);
+                if (tab!size[needle])
+                {
+                    inp.popFrontN(size);
+                    return true;
+                }
+                else
+                    return false;
+            }
+        }
+    }
+
+    struct CherryPick(I, Sizes...)
+    {
+        static assert(allSatisfy!(validSize, Sizes),
+            "Only lengths of 1, 2, 3 and 4 code unit are possible for UTF-8");
+    private:
+        I* m;
+        @property ref tab(int i)() const pure { return m.tables[i - 1]; }
+        bool lookup(int size, Mode mode, Range)(ref Range inp) const pure
+        {
+            return m.lookup!(size, mode)(inp);
+        }
+        mixin DefMatcher;
+    }
+}
+
+template Utf16Matcher()
+{
+    enum validSize(int sz) = sz >= 1 && sz <=2;
+
+    void badEncoding() pure
+    {
+        import std.utf;
+        throw new UTFException("Invalid UTF-16 sequence");
+    }
+
+    // 1-stage ASCII
+    alias AsciiSpec = AliasSeq!(bool, wchar, clamp!7);
+    //2-stage BMP
+    alias BmpSpec = AliasSeq!(bool, wchar, sliceBits!(7, 16), sliceBits!(0, 7));
+    //4-stage - full Unicode
+    //assume that 0xD800 & 0xDC00 bits are cleared
+    //thus leaving 10 bit per wchar to worry about
+    alias UniSpec = AliasSeq!(bool, wchar[2],
+        assumeSize!(x=>x[0]>>4, 6), assumeSize!(x=>x[0]&0xf, 4),
+        assumeSize!(x=>x[1]>>6, 4), assumeSize!(x=>x[1]&0x3f, 6),
+    );
+    alias Ascii = typeof(TrieBuilder!(AsciiSpec)(false).build());
+    alias Bmp = typeof(TrieBuilder!(BmpSpec)(false).build());
+    alias Uni = typeof(TrieBuilder!(UniSpec)(false).build());
+
+    auto encode2(dchar ch)
+    {
+        ch -= 0x1_0000;
+        assert(ch <= 0xF_FFFF);
+        wchar[2] ret;
+        //do not put surrogate bits, they are sliced off
+        ret[0] = cast(wchar)(ch>>10);
+        ret[1] = (ch & 0xFFF);
+        return ret;
+    }
+
+    auto build(Set)(Set set)
+    {
+        import std.algorithm : map;
+        auto ascii = set & unicode.ASCII;
+        auto bmp = (set & CodepointSet.fromIntervals(0x80, 0xFFFF+1))
+            - CodepointSet.fromIntervals(0xD800, 0xDFFF+1);
+        auto other = set - (bmp | ascii);
+        auto asciiT = ascii.byCodepoint.map!(x=>cast(char)x).buildTrie!(AsciiSpec);
+        auto bmpT = bmp.byCodepoint.map!(x=>cast(wchar)x).buildTrie!(BmpSpec);
+        auto otherT = other.byCodepoint.map!(x=>encode2(x)).buildTrie!(UniSpec);
+        alias Ret = Impl!(1,2);
+        return Ret(asciiT, bmpT, otherT);
+    }
+
+    //bootstrap full UTF-16 matcher interace from
+    //sizeFlags, lookupUni and ascii
+    mixin template DefMatcher()
+    {
+        public bool match(Range)(ref Range inp) const pure @trusted
+            if(isRandomAccessRange!Range && is(ElementType!Range : wchar))
+        {
+            enum mode = Mode.skipOnMatch;
+            assert(!inp.empty);
+            auto ch = inp[0];
+            static if(sizeFlags & 1)
+            {
+                if (ch < 0x80)
+                {
+                  if (ascii[ch])
+                  {
+                      inp.popFront();
+                      return true;
+                  }
+                  else
+                      return false;
+                }
+                return lookupUni!mode(inp);
+            }
+            else
+                return lookupUni!mode(inp);
+        }
+
+        static if(Sizes.length == 2)
+        {
+            public bool skip(Range)(ref Range inp) const pure @trusted
+                if(isRandomAccessRange!Range && is(ElementType!Range : wchar))
+            {
+                enum mode = Mode.alwaysSkip;
+                assert(!inp.empty);
+                auto ch = inp[0];
+                static if(sizeFlags & 1)
+                {
+                    if (ch < 0x80)
+                    {
+                        inp.popFront();
+                        return ascii[ch];
+                    }
+                    else
+                        return lookupUni!mode(inp);
+                }
+                else
+                    return lookupUni!mode(inp);
+            }
+        }
+
+        public bool test(Range)(ref Range inp) const pure @trusted
+            if(isRandomAccessRange!Range && is(ElementType!Range : wchar))
+        {
+            enum mode = Mode.neverSkip;
+            assert(!inp.empty);
+            auto ch = inp[0];
+            static if(sizeFlags & 1)
+                return ch < 0x80 ? ascii[ch] : lookupUni!mode(inp);
+            else
+                return lookupUni!mode(inp);
+        }
+
+        bool match(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"match"(str);
+        }
+
+        bool skip(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"skip"(str);
+        }
+
+        bool test(C)(ref C[] str) const pure @trusted
+            if(isSomeChar!C)
+        {
+            return fwdStr!"test"(str);
+        }
+
+        mixin ForwardStrings; //dispatch strings to range versions
+    }
+
+    struct Impl(Sizes...)
+        if(Sizes.length >= 1 && Sizes.length <= 2)
+    {
+    private:
+        static assert(allSatisfy!(validSize, Sizes),
+            "Only lengths of 1 and 2 code units are possible in UTF-16");
+        static if(Sizes.length > 1)
+            enum sizeFlags = Sizes[0] | Sizes[1];
+        else
+            enum sizeFlags = Sizes[0];
+
+        static if(sizeFlags & 1)
+        {
+            Ascii ascii;
+            Bmp bmp;
+        }
+        static if(sizeFlags & 2)
+        {
+            Uni uni;
+        }
+        mixin DefMatcher;
+
+        package @property auto subMatcher(SizesToPick...)() @trusted
+        {
+            return CherryPick!(Impl, SizesToPick)(&this);
+        }
+
+        bool lookupUni(Mode mode, Range)(ref Range inp) const pure
+        {
+            wchar x = cast(wchar)(inp[0] - 0xD800);
+            //not a high surrogate
+            if(x > 0x3FF)
+            {
+                //low surrogate
+                if(x <= 0x7FF) badEncoding();
+                static if(sizeFlags & 1)
+                {
+                    auto ch = inp[0];
+                    static if(mode == Mode.alwaysSkip)
+                        inp.popFront();
+                    static if(mode == Mode.skipOnMatch)
+                    {
+                        if (bmp[ch])
+                        {
+                            inp.popFront();
+                            return true;
+                        }
+                        else
+                            return false;
+                    }
+                    else
+                        return bmp[ch];
+                }
+                else //skip is not available for sub-matchers, so just false
+                    return false;
+            }
+            else
+            {
+                static if(sizeFlags & 2)
+                {
+                    if(inp.length < 2)
+                        badEncoding();
+                    wchar y = cast(wchar)(inp[1] - 0xDC00);
+                    //not a low surrogate
+                    if(y > 0x3FF)
+                        badEncoding();
+                    wchar[2] needle = [inp[0] & 0x3ff, inp[1] & 0x3ff];
+                    static if(mode == Mode.alwaysSkip)
+                        inp.popFrontN(2);
+                    static if(mode == Mode.skipOnMatch)
+                    {
+                        if (uni[needle])
+                        {
+                            inp.popFrontN(2);
+                            return true;
+                        }
+                        else
+                            return false;
+                    }
+                    else
+                        return uni[needle];
+                }
+                else //ditto
+                    return false;
+            }
+        }
+    }
+
+    struct CherryPick(I, Sizes...)
+        if(Sizes.length >= 1 && Sizes.length <= 2)
+    {
+    private:
+        I* m;
+        enum sizeFlags = I.sizeFlags;
+
+        static if(sizeFlags & 1)
+        {
+            @property ref ascii()() const pure{ return m.ascii; }
+        }
+
+        bool lookupUni(Mode mode, Range)(ref Range inp) const pure
+        {
+            return m.lookupUni!mode(inp);
+        }
+        mixin DefMatcher;
+        static assert(allSatisfy!(validSize, Sizes),
+            "Only lengths of 1 and 2 code units are possible in UTF-16");
+    }
+}
+
+private auto utf8Matcher(Set)(Set set) @trusted
+{
+    return Utf8Matcher!().build(set);
+}
+
+private auto utf16Matcher(Set)(Set set) @trusted
+{
+    return Utf16Matcher!().build(set);
+}
+
+/**
+    Constructs a matcher object
+    to classify $(CODEPOINTS) from the $(D set) for encoding
+    that has $(D Char) as code unit.
+
+    See $(LREF MatcherConcept) for API outline.
+*/
+public auto utfMatcher(Char, Set)(Set set) @trusted
+    if(isCodepointSet!Set)
+{
+    static if(is(Char : char))
+        return utf8Matcher(set);
+    else static if(is(Char : wchar))
+        return utf16Matcher(set);
+    else static if(is(Char : dchar))
+        static assert(false, "UTF-32 needs no decoding,
+            and thus not supported by utfMatcher");
+    else
+        static assert(false, "Only character types 'char' and 'wchar' are allowed");
+}
+
+
+//a range of code units, packed with index to speed up forward iteration
+package auto decoder(C)(C[] s, size_t offset=0) @safe pure nothrow @nogc
+    if(is(C : wchar) || is(C : char))
+{
+    static struct Decoder
+    {
+    pure nothrow:
+        C[] str;
+        size_t idx;
+        @property C front(){ return str[idx]; }
+        @property C back(){ return str[$-1]; }
+        void popFront(){ idx++; }
+        void popBack(){ str = str[0..$-1]; }
+        void popFrontN(size_t n){ idx += n; }
+        @property bool empty(){ return idx == str.length; }
+        @property auto save(){ return this; }
+        auto opIndex(size_t i){ return str[idx+i]; }
+        @property size_t length(){ return str.length - idx; }
+        alias opDollar = length;
+        auto opSlice(size_t a, size_t b){ return Decoder(str[0..idx+b], idx+a); }
+    }
+    static assert(isRandomAccessRange!Decoder);
+    static assert(is(ElementType!Decoder : C));
+    return Decoder(s, offset);
+}
+
+/*
+    Expose UTF string $(D s) as a random-access
+    range of $(S_LINK Code unit, code units).
+*/
+package auto units(C)(C[] s) @safe pure nothrow @nogc
+    if(is(C : wchar) || is(C : char))
+{
+    static struct Units
+    {
+    pure nothrow:
+        C[] str;
+        @property C front(){ return str[0]; }
+        @property C back(){ return str[$-1]; }
+        void popFront(){ str = str[1..$]; }
+        void popBack(){ str = str[0..$-1]; }
+        void popFrontN(size_t n){ str = str[n..$]; }
+        @property bool empty(){ return 0 == str.length; }
+        @property auto save(){ return this; }
+        auto opIndex(size_t i){ return str[i]; }
+        @property size_t length(){ return str.length; }
+        alias opDollar = length;
+        auto opSlice(size_t a, size_t b){ return Units(str[a..b]); }
+    }
+    static assert(isRandomAccessRange!Units);
+    static assert(is(ElementType!Units : C));
+    return Units(s);
+}
+
+@safe unittest
+{
+    import std.range;
+    string rs = "hi! ﾈемног砀 текста";
+    auto codec = rs.decoder;
+    auto utf8 =  utf8Matcher(unicode.Letter);
+    auto asc = utf8.subMatcher!(1);
+    auto uni = utf8.subMatcher!(2,3,4);
+    assert(asc.test(codec));
+    assert(!uni.match(codec));
+    assert(utf8.skip(codec));
+    assert(codec.idx == 1);
+
+    assert(!uni.match(codec));
+    assert(asc.test(codec));
+    assert(utf8.skip(codec));
+    assert(codec.idx == 2);
+    assert(!asc.match(codec));
+
+    assert(!utf8.test(codec));
+    assert(!utf8.skip(codec));
+
+    assert(!asc.test(codec));
+    assert(!utf8.test(codec));
+    assert(!utf8.skip(codec));
+    assert(utf8.test(codec));
+    foreach(i; 0..7)
+    {
+        assert(!asc.test(codec));
+        assert(uni.test(codec));
+        assert(utf8.skip(codec));
+    }
+    assert(!utf8.test(codec));
+    assert(!utf8.skip(codec));
+    //the same with match where applicable
+    codec = rs.decoder;
+    assert(utf8.match(codec));
+    assert(codec.idx == 1);
+    assert(utf8.match(codec));
+    assert(codec.idx == 2);
+    assert(!utf8.match(codec));
+    assert(codec.idx == 2);
+    assert(!utf8.skip(codec));
+    assert(!utf8.skip(codec));
+
+    foreach(i; 0..7)
+    {
+        assert(!asc.test(codec));
+        assert(utf8.test(codec));
+        assert(utf8.match(codec));
+    }
+    auto i = codec.idx;
+    assert(!utf8.match(codec));
+    assert(codec.idx == i);
+}
+
+@safe unittest
+{
+    import std.range;
+    static bool testAll(Matcher, Range)(ref Matcher m, ref Range r)
+    {
+        bool t = m.test(r);
+        auto save = r.idx;
+        assert(t == m.match(r));
+        assert(r.idx == save || t); //ether no change or was match
+        r.idx = save;
+        static if(is(typeof(m.skip(r))))
+        {
+            assert(t == m.skip(r));
+            assert(r.idx != save); //always changed
+            r.idx = save;
+        }
+        return t;
+    }
+    auto utf16 = utfMatcher!wchar(unicode.L);
+    auto bmp = utf16.subMatcher!1;
+    auto nonBmp = utf16.subMatcher!1;
+    auto utf8 = utfMatcher!char(unicode.L);
+    auto ascii = utf8.subMatcher!1;
+    auto uni2 = utf8.subMatcher!2;
+    auto uni3 = utf8.subMatcher!3;
+    auto uni24 = utf8.subMatcher!(2,4);
+    foreach(ch; unicode.L.byCodepoint.stride(3))
+    {
+        import std.utf : encode;
+        char[4] buf;
+        wchar[2] buf16;
+        auto len = encode(buf, ch);
+        auto len16 = encode(buf16, ch);
+        auto c8 = buf[0..len].decoder;
+        auto c16 = buf16[0..len16].decoder;
+        assert(testAll(utf16, c16));
+        assert(testAll(bmp, c16) || len16 != 1);
+        assert(testAll(nonBmp, c16) || len16 != 2);
+
+        assert(testAll(utf8, c8));
+
+        //submatchers return false on out of their domain
+        assert(testAll(ascii, c8) || len != 1);
+        assert(testAll(uni2, c8) || len != 2);
+        assert(testAll(uni3, c8) || len != 3);
+        assert(testAll(uni24, c8) || (len != 2 && len !=4));
+    }
+}
+
+// cover decode fail cases of Matcher
+unittest
+{
+    import std.exception : collectException;
+    import std.format : format;
+    import std.algorithm;
+    auto utf16 = utfMatcher!wchar(unicode.L);
+    auto utf8 = utfMatcher!char(unicode.L);
+    //decode failure cases UTF-8
+    alias fails8 = AliasSeq!("\xC1", "\x80\x00","\xC0\x00", "\xCF\x79",
+        "\xFF\x00\0x00\0x00\x00", "\xC0\0x80\0x80\x80", "\x80\0x00\0x00\x00",
+        "\xCF\x00\0x00\0x00\x00");
+    foreach(msg; fails8){
+        assert(collectException((){
+            auto s = msg;
+            import std.utf;
+            size_t idx = 0;
+            //decode(s, idx);
+            utf8.test(s);
+        }()), format("%( %2x %)", cast(ubyte[])msg));
+    }
+    //decode failure cases UTF-16
+    alias fails16 = AliasSeq!([0xD811], [0xDC02]);
+    foreach(msg; fails16){
+        assert(collectException((){
+            auto s = msg.map!(x => cast(wchar)x);
+            utf16.test(s);
+        }()));
+    }
+}
+
+/++
+    Convenience function to construct optimal configurations for
+    packed Trie from any $(D set) of $(CODEPOINTS).
+
+    The parameter $(D level) indicates the number of trie levels to use,
+    allowed values are: 1, 2, 3 or 4. Levels represent different trade-offs
+    speed-size wise.
+
+    $(P Level 1 is fastest and the most memory hungry (a bit array). )
+    $(P Level 4 is the slowest and has the smallest footprint. )
+
+    See the $(S_LINK Synopsis, Synopsis) section for example.
+
+    Note:
+    Level 4 stays very practical (being faster and more predictable)
+    compared to using direct lookup on the $(D set) itself.
+
+
++/
+public auto toTrie(size_t level, Set)(Set set)
+    if(isCodepointSet!Set)
+{
+    static if(level == 1)
+        return codepointSetTrie!(21)(set);
+    else static if(level == 2)
+        return codepointSetTrie!(10, 11)(set);
+    else static if(level == 3)
+        return codepointSetTrie!(8, 5, 8)(set);
+    else static if(level == 4)
+         return codepointSetTrie!(6, 4, 4, 7)(set);
+    else
+        static assert(false,
+            "Sorry, toTrie doesn't support levels > 4, use codepointSetTrie directly");
+}
+
+/**
+    $(P Builds a $(D Trie) with typically optimal speed-size trade-off
+    and wraps it into a delegate of the following type:
+    $(D bool delegate(dchar ch)). )
+
+    $(P Effectively this creates a 'tester' lambda suitable
+    for algorithms like std.algorithm.find that take unary predicates. )
+
+    See the $(S_LINK Synopsis, Synopsis) section for example.
+*/
+public auto toDelegate(Set)(Set set)
+    if(isCodepointSet!Set)
+{
+    // 3 is very small and is almost as fast as 2-level (due to CPU caches?)
+    auto t = toTrie!3(set);
+    return (dchar ch) => t[ch];
+}
+
+/**
+    $(P Opaque wrapper around unsigned built-in integers and
+    code unit (char/wchar/dchar) types.
+    Parameter $(D sz) indicates that the value is confined
+    to the range of [0, 2^^sz$(RPAREN). With this knowledge it can be
+    packed more tightly when stored in certain
+    data-structures like trie. )
+
+    Note:
+    $(P The $(D BitPacked!(T, sz)) is implicitly convertible to $(D T)
+    but not vise-versa. Users have to ensure the value fits in
+    the range required and use the $(D cast)
+    operator to perform the conversion.)
+*/
+struct BitPacked(T, size_t sz)
+    if(isIntegral!T || is(T:dchar))
+{
+    enum bitSize = sz;
+    T _value;
+    alias _value this;
+}
+
+/*
+    Depending on the form of the passed argument $(D bitSizeOf) returns
+    the amount of bits required to represent a given type
+    or a return type of a given functor.
+*/
+template bitSizeOf(Args...)
+    if(Args.length == 1)
+{
+    alias T = Args[0];
+    static if(__traits(compiles, { size_t val = T.bitSize; })) //(is(typeof(T.bitSize) : size_t))
+    {
+        enum bitSizeOf = T.bitSize;
+    }
+    else static if(is(ReturnType!T dummy == BitPacked!(U, bits), U, size_t bits))
+    {
+        enum bitSizeOf = bitSizeOf!(ReturnType!T);
+    }
+    else
+    {
+        enum bitSizeOf = T.sizeof*8;
+    }
+}
+
+/**
+    Tests if $(D T) is some instantiation of $(LREF BitPacked)!(U, x)
+    and thus suitable for packing.
+*/
+template isBitPacked(T)
+{
+    static if(is(T dummy == BitPacked!(U, bits), U, size_t bits))
+        enum isBitPacked = true;
+    else
+        enum isBitPacked = false;
+}
+
+/**
+    Gives the type $(D U) from $(LREF BitPacked)!(U, x)
+    or $(D T) itself for every other type.
+*/
+template TypeOfBitPacked(T)
+{
+    static if(is(T dummy == BitPacked!(U, bits), U, size_t bits))
+        alias TypeOfBitPacked = U;
+    else
+        alias TypeOfBitPacked = T;
+}
+
+/*
+    Wrapper, used in definition of custom data structures from $(D Trie) template.
+    Applying it to a unary lambda function indicates that the returned value always
+    fits within $(D bits) of bits.
+*/
+struct assumeSize(alias Fn, size_t bits)
+{
+    enum bitSize = bits;
+    static auto ref opCall(T)(auto ref T arg)
+    {
+        return Fn(arg);
+    }
+}
+
+/*
+    A helper for defining lambda function that yields a slice
+    of certain bits from an unsigned integral value.
+    The resulting lambda is wrapped in assumeSize and can be used directly
+    with $(D Trie) template.
+*/
+struct sliceBits(size_t from, size_t to)
+{
+    //for now bypass assumeSize, DMD has trouble inlining it
+    enum bitSize = to-from;
+    static auto opCall(T)(T x)
+    out(result)
+    {
+        assert(result < (1<<to-from));
+    }
+    body
+    {
+        static assert(from < to);
+        static if(from == 0)
+            return x & ((1<<to)-1);
+        else
+        return (x >> from) & ((1<<(to-from))-1);
+    }
+}
+
+@safe pure nothrow @nogc uint low_8(uint x) { return x&0xFF; }
+@safe pure nothrow @nogc uint midlow_8(uint x){ return (x&0xFF00)>>8; }
+alias lo8 = assumeSize!(low_8, 8);
+alias mlo8 = assumeSize!(midlow_8, 8);
+
+static assert(bitSizeOf!lo8 == 8);
+static assert(bitSizeOf!(sliceBits!(4, 7)) == 3);
+static assert(bitSizeOf!(BitPacked!(uint, 2)) == 2);
+
+template Sequence(size_t start, size_t end)
+{
+    static if(start < end)
+        alias Sequence = AliasSeq!(start, Sequence!(start+1, end));
+    else
+        alias Sequence = AliasSeq!();
+}
+
+//---- TRIE TESTS ----
+unittest
+{
+    import std.conv;
+    import std.algorithm;
+    import std.range;
+    static trieStats(TRIE)(TRIE t)
+    {
+        version(std_uni_stats)
+        {
+            import std.stdio;
+            writeln("---TRIE FOOTPRINT STATS---");
+            foreach(i; staticIota!(0, t.table.dim) )
+            {
+                writefln("lvl%s = %s bytes;  %s pages"
+                         , i, t.bytes!i, t.pages!i);
+            }
+            writefln("TOTAL: %s bytes", t.bytes);
+            version(none)
+            {
+                writeln("INDEX (excluding value level):");
+                foreach(i; staticIota!(0, t.table.dim-1) )
+                    writeln(t.table.slice!(i)[0..t.table.length!i]);
+            }
+            writeln("---------------------------");
+        }
+    }
+    //@@@BUG link failure, lambdas not found by linker somehow (in case of trie2)
+    // alias lo8   = assumeSize!(8, function (uint x) { return x&0xFF; });
+    // alias next8 = assumeSize!(7, function (uint x) { return (x&0x7F00)>>8; });
+    alias Set = CodepointSet;
+    auto set = Set('A','Z','a','z');
+    auto trie = buildTrie!(bool, uint, 256, lo8)(set.byInterval);// simple bool array
+    for(int a='a'; a<'z';a++)
+        assert(trie[a]);
+    for(int a='A'; a<'Z';a++)
+        assert(trie[a]);
+    for(int a=0; a<'A'; a++)
+        assert(!trie[a]);
+    for(int a ='Z'; a<'a'; a++)
+        assert(!trie[a]);
+    trieStats(trie);
+
+    auto redundant2 = Set(
+        1, 18, 256+2, 256+111, 512+1, 512+18, 768+2, 768+111);
+    auto trie2 = buildTrie!(bool, uint, 1024, mlo8, lo8)(redundant2.byInterval);
+    trieStats(trie2);
+    foreach(e; redundant2.byCodepoint)
+        assert(trie2[e], text(cast(uint)e, " - ", trie2[e]));
+    foreach(i; 0..1024)
+    {
+        assert(trie2[i] == (i in redundant2));
+    }
+
+
+    auto redundant3 = Set(
+          2,    4,    6,    8,    16,
+       2+16, 4+16, 16+6, 16+8, 16+16,
+       2+32, 4+32, 32+6, 32+8,
+      );
+
+    enum max3 = 256;
+    // sliceBits
+    auto trie3 = buildTrie!(bool, uint, max3,
+            sliceBits!(6,8), sliceBits!(4,6), sliceBits!(0,4)
+        )(redundant3.byInterval);
+    trieStats(trie3);
+    foreach(i; 0..max3)
+        assert(trie3[i] == (i in redundant3), text(cast(uint)i));
+
+    auto redundant4 = Set(
+            10, 64, 64+10, 128, 128+10, 256, 256+10, 512,
+            1000, 2000, 3000, 4000, 5000, 6000
+        );
+    enum max4 = 2^^16;
+    auto trie4 = buildTrie!(bool, size_t, max4,
+            sliceBits!(13, 16), sliceBits!(9, 13), sliceBits!(6, 9) , sliceBits!(0, 6)
+        )(redundant4.byInterval);
+    foreach(i; 0..max4){
+        if(i in redundant4)
+            assert(trie4[i], text(cast(uint)i));
+    }
+    trieStats(trie4);
+
+        alias mapToS = mapTrieIndex!(useItemAt!(0, char));
+        string[] redundantS = ["tea", "start", "orange"];
+        redundantS.sort!((a,b) => mapToS(a) < mapToS(b))();
+        auto strie = buildTrie!(bool, string, useItemAt!(0, char))(redundantS);
+        // using first char only
+        assert(redundantS == ["orange", "start", "tea"]);
+        assert(strie["test"], text(strie["test"]));
+        assert(!strie["aea"]);
+        assert(strie["s"]);
+
+    // a bit size test
+    auto a = array(map!(x => to!ubyte(x))(iota(0, 256)));
+    auto bt = buildTrie!(bool, ubyte, sliceBits!(7, 8), sliceBits!(5, 7), sliceBits!(0, 5))(a);
+    trieStats(bt);
+    foreach(i; 0..256)
+        assert(bt[cast(ubyte)i]);
+}
+
+template useItemAt(size_t idx, T)
+    if(isIntegral!T || is(T: dchar))
+{
+    size_t impl(in T[] arr){ return arr[idx]; }
+    alias useItemAt = assumeSize!(impl, 8*T.sizeof);
+}
+
+template useLastItem(T)
+{
+    size_t impl(in T[] arr){ return arr[$-1]; }
+    alias useLastItem = assumeSize!(impl, 8*T.sizeof);
+}
+
+template fullBitSize(Prefix...)
+{
+    static if(Prefix.length > 0)
+        enum fullBitSize = bitSizeOf!(Prefix[0])+fullBitSize!(Prefix[1..$]);
+    else
+        enum fullBitSize = 0;
+}
+
+template idxTypes(Key, size_t fullBits, Prefix...)
+{
+    static if(Prefix.length == 1)
+    {// the last level is value level, so no index once reduced to 1-level
+        alias idxTypes = AliasSeq!();
+    }
+    else
+    {
+        // Important note on bit packing
+        // Each level has to hold enough of bits to address the next one
+        // The bottom level is known to hold full bit width
+        // thus it's size in pages is full_bit_width - size_of_last_prefix
+        // Recourse on this notion
+        alias idxTypes =
+            AliasSeq!(
+                idxTypes!(Key, fullBits - bitSizeOf!(Prefix[$-1]), Prefix[0..$-1]),
+                BitPacked!(typeof(Prefix[$-2](Key.init)), fullBits - bitSizeOf!(Prefix[$-1]))
+            );
+    }
+}
+
+//============================================================================
+
+@safe pure int comparePropertyName(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) if (is(Char1 : dchar) && is(Char2 : dchar))
+{
+    import std.ascii : toLower;
+    import std.algorithm : cmp, map, filter;
+    static bool pred(dchar c) {return !c.isWhite && c != '-' && c != '_';}
+    return cmp(
+        a.map!toLower.filter!pred,
+        b.map!toLower.filter!pred);
+}
+
+@safe pure unittest
+{
+    assert(!comparePropertyName("foo-bar", "fooBar"));
+}
+
+bool propertyNameLess(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) @safe pure if (is(Char1 : dchar) && is(Char2 : dchar))
+{
+    return comparePropertyName(a, b) < 0;
+}
+
+//============================================================================
+// Utilities for compression of Unicode code point sets
+//============================================================================
+
+@safe void compressTo(uint val, ref ubyte[] arr) pure nothrow
+{
+    // not optimized as usually done 1 time (and not public interface)
+    if(val < 128)
+        arr ~= cast(ubyte)val;
+    else if(val < (1<<13))
+    {
+        arr ~= (0b1_00<<5) | cast(ubyte)(val>>8);
+        arr ~= val & 0xFF;
+    }
+    else
+    {
+        assert(val < (1<<21));
+        arr ~= (0b1_01<<5) | cast(ubyte)(val>>16);
+        arr ~= (val >> 8) & 0xFF;
+        arr ~= val  & 0xFF;
+    }
+}
+
+@safe uint decompressFrom(const(ubyte)[] arr, ref size_t idx) pure
+{
+    import std.exception : enforce;
+    uint first = arr[idx++];
+    if(!(first & 0x80)) // no top bit -> [0..127]
+        return first;
+    uint extra = ((first>>5) & 1) + 1; // [1, 2]
+    uint val = (first & 0x1F);
+    enforce(idx + extra <= arr.length, "bad code point interval encoding");
+    foreach(j; 0..extra)
+        val = (val<<8) | arr[idx+j];
+    idx += extra;
+    return val;
+}
+
+
+package ubyte[] compressIntervals(Range)(Range intervals)
+    if(isInputRange!Range && isIntegralPair!(ElementType!Range))
+{
+    ubyte[] storage;
+    uint base = 0;
+    // RLE encode
+    foreach(val; intervals)
+    {
+        compressTo(val[0]-base, storage);
+        base = val[0];
+        if(val[1] != lastDchar+1) // till the end of the domain so don't store it
+        {
+            compressTo(val[1]-base, storage);
+            base = val[1];
+        }
+    }
+    return storage;
+}
+
+@safe pure unittest
+{
+    import std.typecons;
+    auto run = [tuple(80, 127), tuple(128, (1<<10)+128)];
+    ubyte[] enc = [cast(ubyte)80, 47, 1, (0b1_00<<5) | (1<<2), 0];
+    assert(compressIntervals(run) == enc);
+    auto run2 = [tuple(0, (1<<20)+512+1), tuple((1<<20)+512+4, lastDchar+1)];
+    ubyte[] enc2 = [cast(ubyte)0, (0b1_01<<5) | (1<<4), 2, 1, 3]; // odd length-ed
+    assert(compressIntervals(run2) == enc2);
+    size_t  idx = 0;
+    assert(decompressFrom(enc, idx) == 80);
+    assert(decompressFrom(enc, idx) == 47);
+    assert(decompressFrom(enc, idx) == 1);
+    assert(decompressFrom(enc, idx) == (1<<10));
+    idx = 0;
+    assert(decompressFrom(enc2, idx) == 0);
+    assert(decompressFrom(enc2, idx) == (1<<20)+512+1);
+    assert(equalS(decompressIntervals(compressIntervals(run)), run));
+    assert(equalS(decompressIntervals(compressIntervals(run2)), run2));
+}
+
+// Creates a range of $(D CodepointInterval) that lazily decodes compressed data.
+@safe package auto decompressIntervals(const(ubyte)[] data) pure
+{
+    return DecompressedIntervals(data);
+}
+
+@safe struct DecompressedIntervals
+{
+pure:
+    const(ubyte)[] _stream;
+    size_t _idx;
+    CodepointInterval _front;
+
+    this(const(ubyte)[] stream)
+    {
+        _stream = stream;
+        popFront();
+    }
+
+    @property CodepointInterval front()
+    {
+        assert(!empty);
+        return _front;
+    }
+
+    void popFront()
+    {
+        if(_idx == _stream.length)
+        {
+            _idx = size_t.max;
+            return;
+        }
+        uint base = _front[1];
+        _front[0] = base + decompressFrom(_stream, _idx);
+        if(_idx == _stream.length)// odd length ---> till the end
+            _front[1] = lastDchar+1;
+        else
+        {
+            base = _front[0];
+            _front[1] = base + decompressFrom(_stream, _idx);
+        }
+    }
+
+    @property bool empty() const
+    {
+        return _idx == size_t.max;
+    }
+
+    @property DecompressedIntervals save() { return this; }
+}
+
+static assert(isInputRange!DecompressedIntervals);
+static assert(isForwardRange!DecompressedIntervals);
+//============================================================================
+
+version(std_uni_bootstrap){}
+else
+{
+
+// helper for looking up code point sets
+@trusted ptrdiff_t findUnicodeSet(alias table, C)(in C[] name) pure
+{
+    import std.range : assumeSorted;
+    import std.algorithm : map;
+    auto range = assumeSorted!((a,b) => propertyNameLess(a,b))
+        (table.map!"a.name"());
+    size_t idx = range.lowerBound(name).length;
+    if(idx < range.length && comparePropertyName(range[idx], name) == 0)
+        return idx;
+    return -1;
+}
+
+// another one that loads it
+@trusted bool loadUnicodeSet(alias table, Set, C)(in C[] name, ref Set dest) pure
+{
+    auto idx = findUnicodeSet!table(name);
+    if(idx >= 0)
+    {
+        dest = Set(asSet(table[idx].compressed));
+        return true;
+    }
+    return false;
+}
+
+@trusted bool loadProperty(Set=CodepointSet, C)
+    (in C[] name, ref Set target) pure
+{
+    alias ucmp = comparePropertyName;
+    // conjure cumulative properties by hand
+    if(ucmp(name, "L") == 0 || ucmp(name, "Letter") == 0)
+    {
+        target = asSet(uniProps.Lu);
+        target |= asSet(uniProps.Ll);
+        target |= asSet(uniProps.Lt);
+        target |= asSet(uniProps.Lo);
+        target |= asSet(uniProps.Lm);
+    }
+    else if(ucmp(name,"LC") == 0 || ucmp(name,"Cased Letter")==0)
+    {
+        target = asSet(uniProps.Ll);
+        target |= asSet(uniProps.Lu);
+        target |= asSet(uniProps.Lt);// Title case
+    }
+    else if(ucmp(name, "M") == 0 || ucmp(name, "Mark") == 0)
+    {
+        target = asSet(uniProps.Mn);
+        target |= asSet(uniProps.Mc);
+        target |= asSet(uniProps.Me);
+    }
+    else if(ucmp(name, "N") == 0 || ucmp(name, "Number") == 0)
+    {
+        target = asSet(uniProps.Nd);
+        target |= asSet(uniProps.Nl);
+        target |= asSet(uniProps.No);
+    }
+    else if(ucmp(name, "P") == 0 || ucmp(name, "Punctuation") == 0)
+    {
+        target = asSet(uniProps.Pc);
+        target |= asSet(uniProps.Pd);
+        target |= asSet(uniProps.Ps);
+        target |= asSet(uniProps.Pe);
+        target |= asSet(uniProps.Pi);
+        target |= asSet(uniProps.Pf);
+        target |= asSet(uniProps.Po);
+    }
+    else if(ucmp(name, "S") == 0 || ucmp(name, "Symbol") == 0)
+    {
+        target = asSet(uniProps.Sm);
+        target |= asSet(uniProps.Sc);
+        target |= asSet(uniProps.Sk);
+        target |= asSet(uniProps.So);
+    }
+    else if(ucmp(name, "Z") == 0 || ucmp(name, "Separator") == 0)
+    {
+        target = asSet(uniProps.Zs);
+        target |= asSet(uniProps.Zl);
+        target |= asSet(uniProps.Zp);
+    }
+    else if(ucmp(name, "C") == 0 || ucmp(name, "Other") == 0)
+    {
+        target = asSet(uniProps.Co);
+        target |= asSet(uniProps.Lo);
+        target |= asSet(uniProps.No);
+        target |= asSet(uniProps.So);
+        target |= asSet(uniProps.Po);
+    }
+    else if(ucmp(name, "graphical") == 0){
+        target = asSet(uniProps.Alphabetic);
+
+        target |= asSet(uniProps.Mn);
+        target |= asSet(uniProps.Mc);
+        target |= asSet(uniProps.Me);
+
+        target |= asSet(uniProps.Nd);
+        target |= asSet(uniProps.Nl);
+        target |= asSet(uniProps.No);
+
+        target |= asSet(uniProps.Pc);
+        target |= asSet(uniProps.Pd);
+        target |= asSet(uniProps.Ps);
+        target |= asSet(uniProps.Pe);
+        target |= asSet(uniProps.Pi);
+        target |= asSet(uniProps.Pf);
+        target |= asSet(uniProps.Po);
+
+        target |= asSet(uniProps.Zs);
+
+        target |= asSet(uniProps.Sm);
+        target |= asSet(uniProps.Sc);
+        target |= asSet(uniProps.Sk);
+        target |= asSet(uniProps.So);
+    }
+    else if(ucmp(name, "any") == 0)
+        target = Set.fromIntervals(0, 0x110000);
+    else if(ucmp(name, "ascii") == 0)
+        target = Set.fromIntervals(0, 0x80);
+    else
+        return loadUnicodeSet!(uniProps.tab)(name, target);
+    return true;
+}
+
+// CTFE-only helper for checking property names at compile-time
+@safe bool isPrettyPropertyName(C)(in C[] name)
+{
+    import std.algorithm : find;
+    auto names = [
+        "L", "Letter",
+        "LC", "Cased Letter",
+        "M", "Mark",
+        "N", "Number",
+        "P", "Punctuation",
+        "S", "Symbol",
+        "Z", "Separator",
+        "Graphical",
+        "any",
+        "ascii"
+    ];
+    auto x = find!(x => comparePropertyName(x, name) == 0)(names);
+    return !x.empty;
+}
+
+// ditto, CTFE-only, not optimized
+@safe private static bool findSetName(alias table, C)(in C[] name)
+{
+    return findUnicodeSet!table(name) >= 0;
+}
+
+template SetSearcher(alias table, string kind)
+{
+    /// Run-time checked search.
+    static auto opCall(C)(in C[] name)
+        if(is(C : dchar))
+    {
+        import std.conv : to;
+        CodepointSet set;
+        if(loadUnicodeSet!table(name, set))
+            return set;
+        throw new Exception("No unicode set for "~kind~" by name "
+            ~name.to!string()~" was found.");
+    }
+    /// Compile-time checked search.
+    static @property auto opDispatch(string name)()
+    {
+        static if(findSetName!table(name))
+        {
+            CodepointSet set;
+            loadUnicodeSet!table(name, set);
+            return set;
+        }
+        else
+            static assert(false, "No unicode set for "~kind~" by name "
+                ~name~" was found.");
+    }
+}
+
+/**
+    A single entry point to lookup Unicode $(CODEPOINT) sets by name or alias of
+    a block, script or general category.
+
+    It uses well defined standard rules of property name lookup.
+    This includes fuzzy matching of names, so that
+    'White_Space', 'white-SpAce' and 'whitespace' are all considered equal
+    and yield the same set of white space $(CHARACTERS).
+*/
+@safe public struct unicode
+{
+    /**
+        Performs the lookup of set of $(CODEPOINTS)
+        with compile-time correctness checking.
+        This short-cut version combines 3 searches:
+        across blocks, scripts, and common binary properties.
+
+        Note that since scripts and blocks overlap the
+        usual trick to disambiguate is used - to get a block use
+        $(D unicode.InBlockName), to search a script
+        use $(D unicode.ScriptName).
+
+        See_Also: $(LREF block), $(LREF script)
+        and (not included in this search) $(LREF hangulSyllableType).
+    */
+
+    static @property auto opDispatch(string name)() pure
+    {
+        static if(findAny(name))
+            return loadAny(name);
+        else
+            static assert(false, "No unicode set by name "~name~" was found.");
+    }
+
+    ///
+    unittest
+    {
+        auto ascii = unicode.ASCII;
+        assert(ascii['A']);
+        assert(ascii['~']);
+        assert(!ascii['\u00e0']);
+        // matching is case-insensitive
+        assert(ascii == unicode.ascII);
+        assert(!ascii['à']);
+        // underscores, '-' and whitespace in names are ignored too
+        auto latin = unicode.in_latin1_Supplement;
+        assert(latin['à']);
+        assert(!latin['$']);
+        // BTW Latin 1 Supplement is a block, hence "In" prefix
+        assert(latin == unicode("In Latin 1 Supplement"));
+        import std.exception;
+        // run-time look up throws if no such set is found
+        assert(collectException(unicode("InCyrilliac")));
+    }
+
+    /**
+        The same lookup across blocks, scripts, or binary properties,
+        but performed at run-time.
+        This version is provided for cases where $(D name)
+        is not known beforehand; otherwise compile-time
+        checked $(LREF opDispatch) is typically a better choice.
+
+        See the $(S_LINK Unicode properties, table of properties) for available
+        sets.
+    */
+    static auto opCall(C)(in C[] name)
+        if(is(C : dchar))
+    {
+        return loadAny(name);
+    }
+
+    /**
+        Narrows down the search for sets of $(CODEPOINTS) to all Unicode blocks.
+
+        Note:
+        Here block names are unambiguous as no scripts are searched
+        and thus to search use simply $(D unicode.block.BlockName) notation.
+
+        See $(S_LINK Unicode properties, table of properties) for available sets.
+        See_Also: $(S_LINK Unicode properties, table of properties).
+    */
+    struct block
+    {
+        mixin SetSearcher!(blocks.tab, "block");
+    }
+
+    ///
+    unittest
+    {
+        // use .block for explicitness
+        assert(unicode.block.Greek_and_Coptic == unicode.InGreek_and_Coptic);
+    }
+
+    /**
+        Narrows down the search for sets of $(CODEPOINTS) to all Unicode scripts.
+
+        See the $(S_LINK Unicode properties, table of properties) for available
+        sets.
+    */
+    struct script
+    {
+        mixin SetSearcher!(scripts.tab, "script");
+    }
+
+    ///
+    unittest
+    {
+        auto arabicScript = unicode.script.arabic;
+        auto arabicBlock = unicode.block.arabic;
+        // there is an intersection between script and block
+        assert(arabicBlock['؁']);
+        assert(arabicScript['؁']);
+        // but they are different
+        assert(arabicBlock != arabicScript);
+        assert(arabicBlock == unicode.inArabic);
+        assert(arabicScript == unicode.arabic);
+    }
+
+    /**
+        Fetch a set of $(CODEPOINTS) that have the given hangul syllable type.
+
+        Other non-binary properties (once supported) follow the same
+        notation - $(D unicode.propertyName.propertyValue) for compile-time
+        checked access and $(D unicode.propertyName(propertyValue))
+        for run-time checked one.
+
+        See the $(S_LINK Unicode properties, table of properties) for available
+        sets.
+    */
+    struct hangulSyllableType
+    {
+        mixin SetSearcher!(hangul.tab, "hangul syllable type");
+    }
+
+    ///
+    unittest
+    {
+        // L here is syllable type not Letter as in unicode.L short-cut
+        auto leadingVowel = unicode.hangulSyllableType("L");
+        // check that some leading vowels are present
+        foreach(vowel; '\u1110'..'\u115F')
+            assert(leadingVowel[vowel]);
+        assert(leadingVowel == unicode.hangulSyllableType.L);
+    }
+
+private:
+    alias ucmp = comparePropertyName;
+
+    static bool findAny(string name)
+    {
+        return isPrettyPropertyName(name)
+            || findSetName!(uniProps.tab)(name) || findSetName!(scripts.tab)(name)
+            || (ucmp(name[0..2],"In") == 0 && findSetName!(blocks.tab)(name[2..$]));
+    }
+
+    static auto loadAny(Set=CodepointSet, C)(in C[] name) pure
+    {
+        import std.conv : to;
+        Set set;
+        bool loaded = loadProperty(name, set) || loadUnicodeSet!(scripts.tab)(name, set)
+            || (name.length > 2 && ucmp(name[0..2],"In") == 0
+                && loadUnicodeSet!(blocks.tab)(name[2..$], set));
+        if(loaded)
+            return set;
+        throw new Exception("No unicode set by name "~name.to!string()~" was found.");
+    }
+
+    // FIXME: re-disable once the compiler is fixed
+    // Disabled to prevent the mistake of creating instances of this pseudo-struct.
+    //@disable ~this();
+}
+
+unittest
+{
+    assert(unicode("InHebrew") == asSet(blocks.Hebrew));
+    assert(unicode("separator") == (asSet(uniProps.Zs) | asSet(uniProps.Zl) | asSet(uniProps.Zp)));
+    assert(unicode("In-Kharoshthi") == asSet(blocks.Kharoshthi));
+}
+
+enum EMPTY_CASE_TRIE = ushort.max;// from what gen_uni uses internally
+
+// control - '\r'
+enum controlSwitch = `
+    case '\u0000':..case '\u0008':case '\u000E':..case '\u001F':case '\u007F':..case '\u0084':case '\u0086':..case '\u009F': case '\u0009':..case '\u000C': case '\u0085':
+`;
+// TODO: redo the most of hangul stuff algorithmically in case of Graphemes too
+// kill unrolled switches
+
+private static bool isRegionalIndicator(dchar ch) @safe
+{
+    return ch >= '\U0001F1E6' && ch <= '\U0001F1FF';
+}
+
+template genericDecodeGrapheme(bool getValue)
+{
+    alias graphemeExtend = graphemeExtendTrie;
+    alias spacingMark = mcTrie;
+    static if(getValue)
+        alias Value = Grapheme;
+    else
+        alias Value = void;
+
+    Value genericDecodeGrapheme(Input)(ref Input range)
+    {
+        enum GraphemeState {
+            Start,
+            CR,
+            RI,
+            L,
+            V,
+            LVT
+        }
+        static if(getValue)
+            Grapheme grapheme;
+        auto state = GraphemeState.Start;
+        enum eat = q{
+            static if(getValue)
+                grapheme ~= ch;
+            range.popFront();
+        };
+
+        dchar ch;
+        assert(!range.empty, "Attempting to decode grapheme from an empty " ~ Input.stringof);
+        while(!range.empty)
+        {
+            ch = range.front;
+            final switch(state) with(GraphemeState)
+            {
+            case Start:
+                mixin(eat);
+                if(ch == '\r')
+                    state = CR;
+                else if(isRegionalIndicator(ch))
+                    state = RI;
+                else if(isHangL(ch))
+                    state = L;
+                else if(hangLV[ch] || isHangV(ch))
+                    state = V;
+                else if(hangLVT[ch])
+                    state = LVT;
+                else if(isHangT(ch))
+                    state = LVT;
+                else
+                {
+                    switch(ch)
+                    {
+                    mixin(controlSwitch);
+                        goto L_End;
+                    default:
+                        goto L_End_Extend;
+                    }
+                }
+            break;
+            case CR:
+                if(ch == '\n')
+                    mixin(eat);
+                goto L_End_Extend;
+            case RI:
+                if(isRegionalIndicator(ch))
+                    mixin(eat);
+                else
+                    goto L_End_Extend;
+            break;
+            case L:
+                if(isHangL(ch))
+                    mixin(eat);
+                else if(isHangV(ch) || hangLV[ch])
+                {
+                    state = V;
+                    mixin(eat);
+                }
+                else if(hangLVT[ch])
+                {
+                    state = LVT;
+                    mixin(eat);
+                }
+                else
+                    goto L_End_Extend;
+            break;
+            case V:
+                if(isHangV(ch))
+                    mixin(eat);
+                else if(isHangT(ch))
+                {
+                    state = LVT;
+                    mixin(eat);
+                }
+                else
+                    goto L_End_Extend;
+            break;
+            case LVT:
+                if(isHangT(ch))
+                {
+                    mixin(eat);
+                }
+                else
+                    goto L_End_Extend;
+            break;
+            }
+        }
+    L_End_Extend:
+        while(!range.empty)
+        {
+            ch = range.front;
+            // extend & spacing marks
+            if(!graphemeExtend[ch] && !spacingMark[ch])
+                break;
+            mixin(eat);
+        }
+    L_End:
+        static if(getValue)
+            return grapheme;
+    }
+
+}
+
+public: // Public API continues
+
+/++
+    Computes the length of grapheme cluster starting at $(D index).
+    Both the resulting length and the $(D index) are measured
+    in $(S_LINK Code unit, code units).
+
+    Params:
+        C = type that is implicitly convertible to $(D dchars)
+        input = array of grapheme clusters
+        index = starting index into $(D input[])
+
+    Returns:
+        length of grapheme cluster
++/
+size_t graphemeStride(C)(in C[] input, size_t index)
+    if(is(C : dchar))
+{
+    auto src = input[index..$];
+    auto n = src.length;
+    genericDecodeGrapheme!(false)(src);
+    return n - src.length;
+}
+
+///
+@safe unittest
+{
+    assert(graphemeStride("  ", 1) == 1);
+    // A + combing ring above
+    string city = "A\u030Arhus";
+    size_t first = graphemeStride(city, 0);
+    assert(first == 3); //\u030A has 2 UTF-8 code units
+    assert(city[0..first] == "A\u030A");
+    assert(city[first..$] == "rhus");
+}
+
+/++
+    Reads one full grapheme cluster from an input range of dchar $(D inp).
+
+    For examples see the $(LREF Grapheme) below.
+
+    Note:
+    This function modifies $(D inp) and thus $(D inp)
+    must be an L-value.
++/
+Grapheme decodeGrapheme(Input)(ref Input inp)
+    if(isInputRange!Input && is(Unqual!(ElementType!Input) == dchar))
+{
+    return genericDecodeGrapheme!true(inp);
+}
+
+unittest
+{
+    Grapheme gr;
+    string s = " \u0020\u0308 ";
+    gr = decodeGrapheme(s);
+    assert(gr.length == 1 && gr[0] == ' ');
+    gr = decodeGrapheme(s);
+    assert(gr.length == 2 && equalS(gr[0..2], " \u0308"));
+    s = "\u0300\u0308\u1100";
+    assert(equalS(decodeGrapheme(s)[], "\u0300\u0308"));
+    assert(equalS(decodeGrapheme(s)[], "\u1100"));
+    s = "\u11A8\u0308\uAC01";
+    assert(equalS(decodeGrapheme(s)[], "\u11A8\u0308"));
+    assert(equalS(decodeGrapheme(s)[], "\uAC01"));
+}
+
+/++
+    $(P Iterate a string by grapheme.)
+
+    $(P Useful for doing string manipulation that needs to be aware
+    of graphemes.)
+
+    See_Also:
+        $(LREF byCodePoint)
++/
+auto byGrapheme(Range)(Range range)
+    if(isInputRange!Range && is(Unqual!(ElementType!Range) == dchar))
+{
+    // TODO: Bidirectional access
+    static struct Result
+    {
+        private Range _range;
+        private Grapheme _front;
+
+        bool empty() @property
+        {
+            return _front.length == 0;
+        }
+
+        Grapheme front() @property
+        {
+            return _front;
+        }
+
+        void popFront()
+        {
+            _front = _range.empty ? Grapheme.init : _range.decodeGrapheme();
+        }
+
+        static if(isForwardRange!Range)
+        {
+            Result save() @property
+            {
+                return Result(_range.save, _front);
+            }
+        }
+    }
+
+    auto result = Result(range);
+    result.popFront();
+    return result;
+}
+
+///
+unittest
+{
+    import std.conv;
+    import std.range;
+    import std.algorithm;
+    auto text = "noe\u0308l"; // noël using e + combining diaeresis
+    assert(text.walkLength == 5); // 5 code points
+
+    auto gText = text.byGrapheme;
+    assert(gText.walkLength == 4); // 4 graphemes
+
+    assert(gText.take(3).equal("noe\u0308".byGrapheme));
+    assert(gText.drop(3).equal("l".byGrapheme));
+}
+
+// For testing non-forward-range input ranges
+version(unittest)
+private static struct InputRangeString
+{
+    private string s;
+
+    bool empty() @property { return s.empty; }
+    dchar front() @property { return s.front; }
+    void popFront() { s.popFront(); }
+}
+
+unittest
+{
+    import std.conv;
+    import std.range;
+    import std.algorithm;
+    assert("".byGrapheme.walkLength == 0);
+
+    auto reverse = "le\u0308on";
+    assert(reverse.walkLength == 5);
+
+    auto gReverse = reverse.byGrapheme;
+    assert(gReverse.walkLength == 4);
+
+    foreach(text; AliasSeq!("noe\u0308l"c, "noe\u0308l"w, "noe\u0308l"d))
+    {
+        assert(text.walkLength == 5);
+        static assert(isForwardRange!(typeof(text)));
+
+        auto gText = text.byGrapheme;
+        static assert(isForwardRange!(typeof(gText)));
+        assert(gText.walkLength == 4);
+        assert(gText.array.retro.equal(gReverse));
+    }
+
+    auto nonForwardRange = InputRangeString("noe\u0308l").byGrapheme;
+    static assert(!isForwardRange!(typeof(nonForwardRange)));
+    assert(nonForwardRange.walkLength == 4);
+}
+
+/++
+    $(P Lazily transform a range of $(LREF Grapheme)s to a range of code points.)
+
+    $(P Useful for converting the result to a string after doing operations
+    on graphemes.)
+
+    $(P Acts as the identity function when given a range of code points.)
++/
+auto byCodePoint(Range)(Range range)
+    if(isInputRange!Range && is(Unqual!(ElementType!Range) == Grapheme))
+{
+    // TODO: Propagate bidirectional access
+    static struct Result
+    {
+        private Range _range;
+        private size_t i = 0;
+
+        bool empty() @property
+        {
+            return _range.empty;
+        }
+
+        dchar front() @property
+        {
+            return _range.front[i];
+        }
+
+        void popFront()
+        {
+            ++i;
+
+            if(i >= _range.front.length)
+            {
+                _range.popFront();
+                i = 0;
+            }
+        }
+
+        static if(isForwardRange!Range)
+        {
+            Result save() @property
+            {
+                return Result(_range.save, i);
+            }
+        }
+    }
+
+    return Result(range);
+}
+
+/// Ditto
+Range byCodePoint(Range)(Range range)
+    if(isInputRange!Range && is(Unqual!(ElementType!Range) == dchar))
+{
+    return range;
+}
+
+///
+unittest
+{
+    import std.conv : text;
+    import std.range;
+
+    string s = "noe\u0308l"; // noël
+
+    // reverse it and convert the result to a string
+    string reverse = s.byGrapheme
+        .array
+        .retro
+        .byCodePoint
+        .text;
+
+    assert(reverse == "le\u0308on"); // lëon
+}
+
+unittest
+{
+    import std.conv;
+    import std.algorithm;
+    assert("".byGrapheme.byCodePoint.equal(""));
+
+    string text = "noe\u0308l";
+    static assert(is(typeof(text.byCodePoint) == string));
+
+    auto gText = InputRangeString(text).byGrapheme;
+    static assert(!isForwardRange!(typeof(gText)));
+
+    auto cpText = gText.byCodePoint;
+    static assert(!isForwardRange!(typeof(cpText)));
+
+    assert(cpText.walkLength == text.walkLength);
+}
+
+@trusted:
+
+/++
+    $(P A structure designed to effectively pack $(CHARACTERS)
+    of a $(CLUSTER).
+    )
+
+    $(P $(D Grapheme) has value semantics so 2 copies of a $(D Grapheme)
+    always refer to distinct objects. In most actual scenarios a $(D Grapheme)
+    fits on the stack and avoids memory allocation overhead for all but quite
+    long clusters.
+    )
+
+    See_Also: $(LREF decodeGrapheme), $(LREF graphemeStride)
++/
+@trusted struct Grapheme
+{
+    import std.exception : enforce;
+
+public:
+    this(C)(in C[] chars...)
+        if(is(C : dchar))
+    {
+        this ~= chars;
+    }
+
+    this(Input)(Input seq)
+        if(!isDynamicArray!Input
+            && isInputRange!Input && is(ElementType!Input : dchar))
+    {
+        this ~= seq;
+    }
+
+    /// Gets a $(CODEPOINT) at the given index in this cluster.
+    dchar opIndex(size_t index) const pure nothrow @nogc
+    {
+        assert(index < length);
+        return read24(isBig ? ptr_ : small_.ptr, index);
+    }
+
+    /++
+        Writes a $(CODEPOINT) $(D ch) at given index in this cluster.
+
+        Warning:
+        Use of this facility may invalidate grapheme cluster,
+        see also $(LREF Grapheme.valid).
+    +/
+    void opIndexAssign(dchar ch, size_t index) pure nothrow @nogc
+    {
+        assert(index < length);
+        write24(isBig ? ptr_ : small_.ptr, ch, index);
+    }
+
+    ///
+    unittest
+    {
+        auto g = Grapheme("A\u0302");
+        assert(g[0] == 'A');
+        assert(g.valid);
+        g[1] = '~'; // ASCII tilda is not a combining mark
+        assert(g[1] == '~');
+        assert(!g.valid);
+    }
+
+    /++
+        Random-access range over Grapheme's $(CHARACTERS).
+
+        Warning: Invalidates when this Grapheme leaves the scope,
+        attempts to use it then would lead to memory corruption.
+    +/
+    @system auto opSlice(size_t a, size_t b) pure nothrow @nogc
+    {
+        return sliceOverIndexed(a, b, &this);
+    }
+
+    /// ditto
+    @system auto opSlice() pure nothrow @nogc
+    {
+        return sliceOverIndexed(0, length, &this);
+    }
+
+    /// Grapheme cluster length in $(CODEPOINTS).
+    @property size_t length() const pure nothrow @nogc
+    {
+        return isBig ? len_ : slen_ & 0x7F;
+    }
+
+    /++
+        Append $(CHARACTER) $(D ch) to this grapheme.
+        Warning:
+        Use of this facility may invalidate grapheme cluster,
+        see also $(D valid).
+
+        See_Also: $(LREF Grapheme.valid)
+    +/
+    ref opOpAssign(string op)(dchar ch)
+    {
+        static if(op == "~")
+        {
+            if(!isBig)
+            {
+                if(slen_ + 1 > small_cap)
+                    convertToBig();// & fallthrough to "big" branch
+                else
+                {
+                    write24(small_.ptr, ch, smallLength);
+                    slen_++;
+                    return this;
+                }
+            }
+
+            assert(isBig);
+            if(len_ + 1 > cap_)
+            {
+                cap_ += grow;
+                ptr_ = cast(ubyte*)enforce(realloc(ptr_, 3*(cap_+1)),
+                    "realloc failed");
+            }
+            write24(ptr_, ch, len_++);
+            return this;
+        }
+        else
+            static assert(false, "No operation "~op~" defined for Grapheme");
+    }
+
+    ///
+    unittest
+    {
+        import std.algorithm.comparison : equal;
+        auto g = Grapheme("A");
+        assert(g.valid);
+        g ~= '\u0301';
+        assert(g[].equal("A\u0301"));
+        assert(g.valid);
+        g ~= "B";
+        // not a valid grapheme cluster anymore
+        assert(!g.valid);
+        // still could be useful though
+        assert(g[].equal("A\u0301B"));
+    }
+
+    /// Append all $(CHARACTERS) from the input range $(D inp) to this Grapheme.
+    ref opOpAssign(string op, Input)(Input inp)
+        if(isInputRange!Input && is(ElementType!Input : dchar))
+    {
+        static if(op == "~")
+        {
+            foreach(dchar ch; inp)
+                this ~= ch;
+            return this;
+        }
+        else
+            static assert(false, "No operation "~op~" defined for Grapheme");
+    }
+
+    /++
+        True if this object contains valid extended grapheme cluster.
+        Decoding primitives of this module always return a valid $(D Grapheme).
+
+        Appending to and direct manipulation of grapheme's $(CHARACTERS) may
+        render it no longer valid. Certain applications may chose to use
+        Grapheme as a "small string" of any $(CODEPOINTS) and ignore this property
+        entirely.
+    +/
+    @property bool valid()() /*const*/
+    {
+        auto r = this[];
+        genericDecodeGrapheme!false(r);
+        return r.length == 0;
+    }
+
+    this(this)
+    {
+        if(isBig)
+        {// dup it
+            auto raw_cap = 3*(cap_+1);
+            auto p = cast(ubyte*)enforce(malloc(raw_cap), "malloc failed");
+            p[0..raw_cap] = ptr_[0..raw_cap];
+            ptr_ = p;
+        }
+    }
+
+    ~this()
+    {
+        if(isBig)
+        {
+            free(ptr_);
+        }
+    }
+
+
+private:
+    enum small_bytes = ((ubyte*).sizeof+3*size_t.sizeof-1);
+    // "out of the blue" grow rate, needs testing
+    // (though graphemes are typically small < 9)
+    enum grow = 20;
+    enum small_cap = small_bytes/3;
+    enum small_flag = 0x80, small_mask = 0x7F;
+    // 16 bytes in 32bits, should be enough for the majority of cases
+    union
+    {
+        struct
+        {
+            ubyte* ptr_;
+            size_t cap_;
+            size_t len_;
+            size_t padding_;
+        }
+        struct
+        {
+            ubyte[small_bytes] small_;
+            ubyte slen_;
+        }
+    }
+
+    void convertToBig()
+    {
+        size_t k = smallLength;
+        ubyte* p = cast(ubyte*)enforce(malloc(3*(grow+1)), "malloc failed");
+        for(int i=0; i<k; i++)
+            write24(p, read24(small_.ptr, i), i);
+        // now we can overwrite small array data
+        ptr_ = p;
+        len_ = slen_;
+        assert(grow > len_);
+        cap_ = grow;
+        setBig();
+    }
+
+    void setBig() pure nothrow @nogc { slen_ |= small_flag; }
+
+    @property size_t smallLength() const pure nothrow @nogc
+    {
+        return slen_ & small_mask;
+    }
+    @property ubyte isBig() const pure nothrow @nogc
+    {
+        return slen_ & small_flag;
+    }
+}
+
+static assert(Grapheme.sizeof == size_t.sizeof*4);
+
+///
+unittest
+{
+    import std.algorithm : filter;
+    string bold = "ku\u0308hn";
+
+    // note that decodeGrapheme takes parameter by ref
+    auto first = decodeGrapheme(bold);
+
+    assert(first.length == 1);
+    assert(first[0] == 'k');
+
+    // the next grapheme is 2 characters long
+    auto wideOne = decodeGrapheme(bold);
+    // slicing a grapheme yields a random-access range of dchar
+    assert(wideOne[].equalS("u\u0308"));
+    assert(wideOne.length == 2);
+    static assert(isRandomAccessRange!(typeof(wideOne[])));
+
+    // all of the usual range manipulation is possible
+    assert(wideOne[].filter!isMark().equalS("\u0308"));
+
+    auto g = Grapheme("A");
+    assert(g.valid);
+    g ~= '\u0301';
+    assert(g[].equalS("A\u0301"));
+    assert(g.valid);
+    g ~= "B";
+    // not a valid grapheme cluster anymore
+    assert(!g.valid);
+    // still could be useful though
+    assert(g[].equalS("A\u0301B"));
+}
+
+unittest
+{
+    auto g = Grapheme("A\u0302");
+    assert(g[0] == 'A');
+    assert(g.valid);
+    g[1] = '~'; // ASCII tilda is not a combining mark
+    assert(g[1] == '~');
+    assert(!g.valid);
+}
+
+unittest
+{
+    import std.conv;
+    import std.algorithm;
+    import std.range;
+
+    // not valid clusters (but it just a test)
+    auto g  = Grapheme('a', 'b', 'c', 'd', 'e');
+    assert(g[0] == 'a');
+    assert(g[1] == 'b');
+    assert(g[2] == 'c');
+    assert(g[3] == 'd');
+    assert(g[4] == 'e');
+    g[3] = 'Й';
+    assert(g[2] == 'c');
+    assert(g[3] == 'Й', text(g[3], " vs ", 'Й'));
+    assert(g[4] == 'e');
+    assert(!g.valid);
+
+    g ~= 'ц';
+    g ~= '~';
+    assert(g[0] == 'a');
+    assert(g[1] == 'b');
+    assert(g[2] == 'c');
+    assert(g[3] == 'Й');
+    assert(g[4] == 'e');
+    assert(g[5] == 'ц');
+    assert(g[6] == '~');
+    assert(!g.valid);
+
+    Grapheme copy = g;
+    copy[0] = 'X';
+    copy[1] = '-';
+    assert(g[0] == 'a' && copy[0] == 'X');
+    assert(g[1] == 'b' && copy[1] == '-');
+    assert(equalS(g[2..g.length], copy[2..copy.length]));
+    copy = Grapheme("АБВГДЕЁЖЗИКЛМ");
+    assert(equalS(copy[0..8], "АБВГДЕЁЖ"), text(copy[0..8]));
+    copy ~= "xyz";
+    assert(equalS(copy[13..15], "xy"), text(copy[13..15]));
+    assert(!copy.valid);
+
+    Grapheme h;
+    foreach(dchar v; iota(cast(int)'A', cast(int)'Z'+1).map!"cast(dchar)a"())
+        h ~= v;
+    assert(equalS(h[], iota(cast(int)'A', cast(int)'Z'+1)));
+}
+
+/++
+    $(P Does basic case-insensitive comparison of strings $(D str1) and $(D str2).
+    This function uses simpler comparison rule thus achieving better performance
+    than $(LREF icmp). However keep in mind the warning below.)
+
+    Params:
+        str1 = a string or a $(D ForwardRange) of $(D dchar)s
+        str2 = a string or a $(D ForwardRange) of $(D dchar)s
+
+    Returns:
+        An $(D int) that is 0 if the strings match,
+        &lt;0 if $(D str1) is lexicographically "less" than $(D str2),
+        &gt;0 if $(D str1) is lexicographically "greater" than $(D str2)
+
+    Warning:
+    This function only handles 1:1 $(CODEPOINT) mapping
+    and thus is not sufficient for certain alphabets
+    like German, Greek and few others.
+
+    See_Also:
+        $(LREF icmp)
+        $(XREF_PACK algorithm,comparison,cmp)
++/
+int sicmp(S1, S2)(S1 str1, S2 str2)
+    if(isForwardRange!S1 && is(Unqual!(ElementType!S1) == dchar)
+    && isForwardRange!S2 && is(Unqual!(ElementType!S2) == dchar))
+{
+    alias sTable = simpleCaseTable;
+    size_t ridx=0;
+    foreach(dchar lhs; str1)
+    {
+        if(ridx == str2.length)
+            return 1;
+        import std.utf : decode;
+        dchar rhs = decode(str2, ridx);
+        int diff = lhs - rhs;
+        if(!diff)
+            continue;
+        size_t idx = simpleCaseTrie[lhs];
+        size_t idx2 = simpleCaseTrie[rhs];
+        // simpleCaseTrie is packed index table
+        if(idx != EMPTY_CASE_TRIE)
+        {
+            if(idx2 != EMPTY_CASE_TRIE)
+            {// both cased chars
+                // adjust idx --> start of bucket
+                idx = idx - sTable[idx].n;
+                idx2 = idx2 - sTable[idx2].n;
+                if(idx == idx2)// one bucket, equivalent chars
+                    continue;
+                else//  not the same bucket
+                    diff = sTable[idx].ch - sTable[idx2].ch;
+            }
+            else
+                diff = sTable[idx - sTable[idx].n].ch - rhs;
+        }
+        else if(idx2 != EMPTY_CASE_TRIE)
+        {
+            diff = lhs - sTable[idx2 - sTable[idx2].n].ch;
+        }
+        // one of chars is not cased at all
+        return diff;
+    }
+    return ridx == str2.length ? 0 : -1;
+}
+
+///
+unittest{
+    assert(sicmp("Август", "авгусТ") == 0);
+    // Greek also works as long as there is no 1:M mapping in sight
+    assert(sicmp("ΌΎ", "όύ") == 0);
+    // things like the following won't get matched as equal
+    // Greek small letter iota with dialytika and tonos
+    assert(sicmp("ΐ", "\u03B9\u0308\u0301") != 0);
+
+    // while icmp has no problem with that
+    assert(icmp("ΐ", "\u03B9\u0308\u0301") == 0);
+    assert(icmp("ΌΎ", "όύ") == 0);
+}
+
+// overloads for the most common cases to reduce compile time
+@safe pure /*TODO nothrow*/
+{
+    int sicmp(const(char)[] str1, const(char)[] str2)
+    { return sicmp!(const(char)[], const(char)[])(str1, str2); }
+    int sicmp(const(wchar)[] str1, const(wchar)[] str2)
+    { return sicmp!(const(wchar)[], const(wchar)[])(str1, str2); }
+    int sicmp(const(dchar)[] str1, const(dchar)[] str2)
+    { return sicmp!(const(dchar)[], const(dchar)[])(str1, str2); }
+}
+
+private int fullCasedCmp(Range)(dchar lhs, dchar rhs, ref Range rtail)
+    @trusted pure /*TODO nothrow*/
+{
+    import std.algorithm : skipOver;
+    alias fTable = fullCaseTable;
+    size_t idx = fullCaseTrie[lhs];
+    // fullCaseTrie is packed index table
+    if(idx == EMPTY_CASE_TRIE)
+        return lhs;
+    size_t start = idx - fTable[idx].n;
+    size_t end = fTable[idx].size + start;
+    assert(fTable[start].entry_len == 1);
+    for(idx=start; idx<end; idx++)
+    {
+        auto entryLen = fTable[idx].entry_len;
+        if(entryLen == 1)
+        {
+            if(fTable[idx].seq[0] == rhs)
+            {
+                return 0;
+            }
+        }
+        else
+        {// OK it's a long chunk, like 'ss' for German
+            dstring seq = fTable[idx].seq[0..entryLen];
+            if(rhs == seq[0]
+                && rtail.skipOver(seq[1..$]))
+            {
+                // note that this path modifies rtail
+                // iff we managed to get there
+                return 0;
+            }
+        }
+    }
+    return fTable[start].seq[0]; // new remapped character for accurate diffs
+}
+
+/++
+    $(P Does case insensitive comparison of $(D str1) and $(D str2).
+    Follows the rules of full case-folding mapping.
+    This includes matching as equal german ß with "ss" and
+    other 1:M $(CODEPOINT) mappings unlike $(LREF sicmp).
+    The cost of $(D icmp) being pedantically correct is
+    slightly worse performance.
+    )
++/
+int icmp(S1, S2)(S1 str1, S2 str2)
+    if(isForwardRange!S1 && is(Unqual!(ElementType!S1) == dchar)
+    && isForwardRange!S2 && is(Unqual!(ElementType!S2) == dchar))
+{
+    for(;;)
+    {
+        if(str1.empty)
+            return str2.empty ? 0 : -1;
+        dchar lhs = str1.front;
+        if(str2.empty)
+            return 1;
+        dchar rhs = str2.front;
+        str1.popFront();
+        str2.popFront();
+        int diff = lhs - rhs;
+        if(!diff)
+            continue;
+        // first try to match lhs to <rhs,right-tail> sequence
+        int cmpLR = fullCasedCmp(lhs, rhs, str2);
+        if(!cmpLR)
+            continue;
+        // then rhs to <lhs,left-tail> sequence
+        int cmpRL = fullCasedCmp(rhs, lhs, str1);
+        if(!cmpRL)
+            continue;
+        // cmpXX contain remapped codepoints
+        // to obtain stable ordering of icmp
+        diff = cmpLR - cmpRL;
+        return diff;
+    }
+}
+
+///
+unittest{
+    assert(icmp("Rußland", "Russland") == 0);
+    assert(icmp("ᾩ -> \u1F70\u03B9", "\u1F61\u03B9 -> ᾲ") == 0);
+}
+
+// overloads for the most common cases to reduce compile time
+@safe pure /*TODO nothrow*/
+{
+    int icmp(const(char)[] str1, const(char)[] str2)
+    { return icmp!(const(char)[], const(char)[])(str1, str2); }
+    int icmp(const(wchar)[] str1, const(wchar)[] str2)
+    { return icmp!(const(wchar)[], const(wchar)[])(str1, str2); }
+    int icmp(const(dchar)[] str1, const(dchar)[] str2)
+    { return icmp!(const(dchar)[], const(dchar)[])(str1, str2); }
+}
+
+unittest
+{
+    import std.conv;
+    import std.exception : assertCTFEable;
+    import std.algorithm;
+    assertCTFEable!(
+    {
+    foreach(cfunc; AliasSeq!(icmp, sicmp))
+    {
+        foreach(S1; AliasSeq!(string, wstring, dstring))
+        foreach(S2; AliasSeq!(string, wstring, dstring))
+        (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
+            assert(cfunc("".to!S1(), "".to!S2()) == 0);
+            assert(cfunc("A".to!S1(), "".to!S2()) > 0);
+            assert(cfunc("".to!S1(), "0".to!S2()) < 0);
+            assert(cfunc("abc".to!S1(), "abc".to!S2()) == 0);
+            assert(cfunc("abcd".to!S1(), "abc".to!S2()) > 0);
+            assert(cfunc("abc".to!S1(), "abcd".to!S2()) < 0);
+            assert(cfunc("Abc".to!S1(), "aBc".to!S2()) == 0);
+            assert(cfunc("авГуст".to!S1(), "АВгУСТ".to!S2()) == 0);
+            // Check example:
+            assert(cfunc("Август".to!S1(), "авгусТ".to!S2()) == 0);
+            assert(cfunc("ΌΎ".to!S1(), "όύ".to!S2()) == 0);
+        }();
+        // check that the order is properly agnostic to the case
+        auto strs = [ "Apple", "ORANGE",  "orAcle", "amp", "banana"];
+        sort!((a,b) => cfunc(a,b) < 0)(strs);
+        assert(strs == ["amp", "Apple",  "banana", "orAcle", "ORANGE"]);
+    }
+    assert(icmp("ßb", "ssa") > 0);
+    // Check example:
+    assert(icmp("Russland", "Rußland") == 0);
+    assert(icmp("ᾩ -> \u1F70\u03B9", "\u1F61\u03B9 -> ᾲ") == 0);
+    assert(icmp("ΐ"w, "\u03B9\u0308\u0301") == 0);
+    assert(sicmp("ΐ", "\u03B9\u0308\u0301") != 0);
+    //bugzilla 11057
+    assert( icmp("K", "L") < 0 );
+    });
+}
+
+// This is package for the moment to be used as a support tool for std.regex
+// It needs a better API
+/*
+    Return a range of all $(CODEPOINTS) that casefold to
+    and from this $(D ch).
+*/
+package auto simpleCaseFoldings(dchar ch)
+{
+    alias sTable = simpleCaseTable;
+    static struct Range
+    {
+    pure nothrow:
+        uint idx; //if == uint.max, then read c.
+        union
+        {
+            dchar c; // == 0 - empty range
+            uint len;
+        }
+        @property bool isSmall() const { return idx == uint.max; }
+
+        this(dchar ch)
+        {
+            idx = uint.max;
+            c = ch;
+        }
+
+        this(uint start, uint size)
+        {
+            idx = start;
+            len = size;
+        }
+
+        @property dchar front() const
+        {
+            assert(!empty);
+            if(isSmall)
+            {
+                return c;
+            }
+            auto ch = sTable[idx].ch;
+            return ch;
+        }
+
+        @property bool empty() const
+        {
+            if(isSmall)
+            {
+                return c == 0;
+            }
+            return len == 0;
+        }
+
+        @property uint length() const
+        {
+            if(isSmall)
+            {
+                return c == 0 ? 0 : 1;
+            }
+            return len;
+        }
+
+        void popFront()
+        {
+            if(isSmall)
+                c = 0;
+            else
+            {
+                idx++;
+                len--;
+            }
+        }
+    }
+    immutable idx = simpleCaseTrie[ch];
+    if (idx == EMPTY_CASE_TRIE)
+        return Range(ch);
+    auto entry = sTable[idx];
+    immutable start = idx - entry.n;
+    return Range(start, entry.size);
+}
+
+unittest
+{
+    import std.exception : assertCTFEable;
+    import std.algorithm : canFind;
+    import std.array;
+    assertCTFEable!((){
+        auto r = simpleCaseFoldings('Э').array;
+        assert(r.length == 2);
+        assert(r.canFind('э') && r.canFind('Э'));
+        auto sr = simpleCaseFoldings('~');
+        assert(sr.equalS("~"));
+        //A with ring above - casefolds to the same bucket as Angstrom sign
+        sr = simpleCaseFoldings('Å');
+        assert(sr.length == 3);
+        assert(sr.canFind('å') && sr.canFind('Å') && sr.canFind('\u212B'));
+    });
+}
+
+/++
+    $(P Returns the $(S_LINK Combining class, combining class) of $(D ch).)
++/
+ubyte combiningClass(dchar ch) @safe pure nothrow @nogc
+{
+    return combiningClassTrie[ch];
+}
+
+///
+unittest{
+    // shorten the code
+    alias CC = combiningClass;
+
+    // combining tilda
+    assert(CC('\u0303') == 230);
+    // combining ring below
+    assert(CC('\u0325') == 220);
+    // the simple consequence is that  "tilda" should be
+    // placed after a "ring below" in a sequence
+}
+
+@safe pure nothrow @nogc unittest
+{
+    foreach(ch; 0..0x80)
+        assert(combiningClass(ch) == 0);
+    assert(combiningClass('\u05BD') == 22);
+    assert(combiningClass('\u0300') == 230);
+    assert(combiningClass('\u0317') == 220);
+    assert(combiningClass('\u1939') == 222);
+}
+
+/// Unicode character decomposition type.
+enum UnicodeDecomposition {
+    /// Canonical decomposition. The result is canonically equivalent sequence.
+    Canonical,
+    /**
+         Compatibility decomposition. The result is compatibility equivalent sequence.
+         Note: Compatibility decomposition is a $(B lossy) conversion,
+         typically suitable only for fuzzy matching and internal processing.
+    */
+    Compatibility
+}
+
+/**
+    Shorthand aliases for character decomposition type, passed as a
+    template parameter to $(LREF decompose).
+*/
+enum {
+    Canonical = UnicodeDecomposition.Canonical,
+    Compatibility = UnicodeDecomposition.Compatibility
+};
+
+/++
+    Try to canonically compose 2 $(CHARACTERS).
+    Returns the composed $(CHARACTER) if they do compose and dchar.init otherwise.
+
+    The assumption is that $(D first) comes before $(D second) in the original text,
+    usually meaning that the first is a starter.
+
+    Note: Hangul syllables are not covered by this function.
+    See $(D composeJamo) below.
++/
+public dchar compose(dchar first, dchar second) pure nothrow
+{
+    import std.internal.unicode_comp;
+    import std.algorithm : map;
+    import std.range : assumeSorted;
+    size_t packed = compositionJumpTrie[first];
+    if(packed == ushort.max)
+        return dchar.init;
+    // unpack offset and length
+    size_t idx = packed & composeIdxMask, cnt = packed >> composeCntShift;
+    // TODO: optimize this micro binary search (no more then 4-5 steps)
+    auto r = compositionTable[idx..idx+cnt].map!"a.rhs"().assumeSorted();
+    auto target = r.lowerBound(second).length;
+    if(target == cnt)
+        return dchar.init;
+    auto entry = compositionTable[idx+target];
+    if(entry.rhs != second)
+        return dchar.init;
+    return entry.composed;
+}
+
+///
+unittest{
+    assert(compose('A','\u0308') == '\u00C4');
+    assert(compose('A', 'B') == dchar.init);
+    assert(compose('C', '\u0301') == '\u0106');
+    // note that the starter is the first one
+    // thus the following doesn't compose
+    assert(compose('\u0308', 'A') == dchar.init);
+}
+
+/++
+    Returns a full $(S_LINK Canonical decomposition, Canonical)
+    (by default) or $(S_LINK Compatibility decomposition, Compatibility)
+    decomposition of $(CHARACTER) $(D ch).
+    If no decomposition is available returns a $(LREF Grapheme)
+    with the $(D ch) itself.
+
+    Note:
+    This function also decomposes hangul syllables
+    as prescribed by the standard.
+
+    See_Also: $(LREF decomposeHangul) for a restricted version
+    that takes into account only hangul syllables  but
+    no other decompositions.
++/
+public Grapheme decompose(UnicodeDecomposition decompType=Canonical)(dchar ch)
+{
+    import std.internal.unicode_decomp;
+    import std.algorithm : until;
+    static if(decompType == Canonical)
+    {
+        alias table = decompCanonTable;
+        alias mapping = canonMappingTrie;
+    }
+    else static if(decompType == Compatibility)
+    {
+        alias table = decompCompatTable;
+        alias mapping = compatMappingTrie;
+    }
+    ushort idx = mapping[ch];
+    if(!idx) // not found, check hangul arithmetic decomposition
+        return decomposeHangul(ch);
+    auto decomp = table[idx..$].until(0);
+    return Grapheme(decomp);
+}
+
+///
+unittest
+{
+    assert(compose('A','\u0308') == '\u00C4');
+    assert(compose('A', 'B') == dchar.init);
+    assert(compose('C', '\u0301') == '\u0106');
+    // note that the starter is the first one
+    // thus the following doesn't compose
+    assert(compose('\u0308', 'A') == dchar.init);
+
+    assert(decompose('Ĉ')[].equalS("C\u0302"));
+    assert(decompose('D')[].equalS("D"));
+    assert(decompose('\uD4DC')[].equalS("\u1111\u1171\u11B7"));
+    assert(decompose!Compatibility('¹')[].equalS("1"));
+}
+
+//----------------------------------------------------------------------------
+// Hangul specific composition/decomposition
+enum jamoSBase = 0xAC00;
+enum jamoLBase = 0x1100;
+enum jamoVBase = 0x1161;
+enum jamoTBase = 0x11A7;
+enum jamoLCount = 19, jamoVCount = 21, jamoTCount = 28;
+enum jamoNCount = jamoVCount * jamoTCount;
+enum jamoSCount = jamoLCount * jamoNCount;
+
+// Tests if $(D ch) is a Hangul leading consonant jamo.
+bool isJamoL(dchar ch) pure nothrow @nogc
+{
+    // first cmp rejects ~ 1M code points above leading jamo range
+    return ch < jamoLBase+jamoLCount && ch >= jamoLBase;
+}
+
+// Tests if $(D ch) is a Hangul vowel jamo.
+bool isJamoT(dchar ch) pure nothrow @nogc
+{
+    // first cmp rejects ~ 1M code points above trailing jamo range
+    // Note: ch == jamoTBase doesn't indicate trailing jamo (TIndex must be > 0)
+    return ch < jamoTBase+jamoTCount && ch > jamoTBase;
+}
+
+// Tests if $(D ch) is a Hangul trailnig consonant jamo.
+bool isJamoV(dchar ch) pure nothrow @nogc
+{
+    // first cmp rejects ~ 1M code points above vowel range
+    return  ch < jamoVBase+jamoVCount && ch >= jamoVBase;
+}
+
+int hangulSyllableIndex(dchar ch) pure nothrow @nogc
+{
+    int idxS = cast(int)ch - jamoSBase;
+    return idxS >= 0 && idxS < jamoSCount ? idxS : -1;
+}
+
+// internal helper: compose hangul syllables leaving dchar.init in holes
+void hangulRecompose(dchar[] seq) pure nothrow @nogc
+{
+    for(size_t idx = 0; idx + 1 < seq.length; )
+    {
+        if(isJamoL(seq[idx]) && isJamoV(seq[idx+1]))
+        {
+            int indexL = seq[idx] - jamoLBase;
+            int indexV = seq[idx+1] - jamoVBase;
+            int indexLV = indexL * jamoNCount + indexV * jamoTCount;
+            if(idx + 2 < seq.length && isJamoT(seq[idx+2]))
+            {
+                seq[idx] = jamoSBase + indexLV + seq[idx+2] - jamoTBase;
+                seq[idx+1] = dchar.init;
+                seq[idx+2] = dchar.init;
+                idx += 3;
+            }
+            else
+            {
+                seq[idx] = jamoSBase + indexLV;
+                seq[idx+1] = dchar.init;
+                idx += 2;
+            }
+        }
+        else
+            idx++;
+    }
+}
+
+//----------------------------------------------------------------------------
+public:
+
+/**
+    Decomposes a Hangul syllable. If $(D ch) is not a composed syllable
+    then this function returns $(LREF Grapheme) containing only $(D ch) as is.
+*/
+Grapheme decomposeHangul(dchar ch)
+{
+    int idxS = cast(int)ch - jamoSBase;
+    if(idxS < 0 || idxS >= jamoSCount) return Grapheme(ch);
+    int idxL = idxS / jamoNCount;
+    int idxV = (idxS % jamoNCount) / jamoTCount;
+    int idxT = idxS % jamoTCount;
+
+    int partL = jamoLBase + idxL;
+    int partV = jamoVBase + idxV;
+    if(idxT > 0) // there is a trailling consonant (T); <L,V,T> decomposition
+        return Grapheme(partL, partV, jamoTBase + idxT);
+    else // <L, V> decomposition
+        return Grapheme(partL, partV);
+}
+
+///
+unittest
+{
+    import std.algorithm;
+    assert(decomposeHangul('\uD4DB')[].equal("\u1111\u1171\u11B6"));
+}
+
+/++
+    Try to compose hangul syllable out of a leading consonant ($(D lead)),
+    a $(D vowel) and optional $(D trailing) consonant jamos.
+
+    On success returns the composed LV or LVT hangul syllable.
+
+    If any of $(D lead) and $(D vowel) are not a valid hangul jamo
+    of the respective $(CHARACTER) class returns dchar.init.
++/
+dchar composeJamo(dchar lead, dchar vowel, dchar trailing=dchar.init) pure nothrow @nogc
+{
+    if(!isJamoL(lead))
+        return dchar.init;
+    int indexL = lead - jamoLBase;
+    if(!isJamoV(vowel))
+        return dchar.init;
+    int indexV = vowel - jamoVBase;
+    int indexLV = indexL * jamoNCount + indexV * jamoTCount;
+    dchar syllable = jamoSBase + indexLV;
+    return isJamoT(trailing) ? syllable + (trailing - jamoTBase) : syllable;
+}
+
+///
+unittest
+{
+    assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB');
+    // leaving out T-vowel, or passing any codepoint
+    // that is not trailing consonant composes an LV-syllable
+    assert(composeJamo('\u1111', '\u1171') == '\uD4CC');
+    assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC');
+    assert(composeJamo('\u1111', 'A') == dchar.init);
+    assert(composeJamo('A', '\u1171') == dchar.init);
+}
+
+unittest
+{
+    import std.conv;
+
+    static void testDecomp(UnicodeDecomposition T)(dchar ch, string r)
+    {
+        Grapheme g = decompose!T(ch);
+        assert(equalS(g[], r), text(g[], " vs ", r));
+    }
+    testDecomp!Canonical('\u1FF4', "\u03C9\u0301\u0345");
+    testDecomp!Canonical('\uF907', "\u9F9C");
+    testDecomp!Compatibility('\u33FF', "\u0067\u0061\u006C");
+    testDecomp!Compatibility('\uA7F9', "\u0153");
+
+    // check examples
+    assert(decomposeHangul('\uD4DB')[].equalS("\u1111\u1171\u11B6"));
+    assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB');
+    assert(composeJamo('\u1111', '\u1171') == '\uD4CC'); // leave out T-vowel
+    assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC');
+    assert(composeJamo('\u1111', 'A') == dchar.init);
+    assert(composeJamo('A', '\u1171') == dchar.init);
+}
+
+/**
+    Enumeration type for normalization forms,
+    passed as template parameter for functions like $(LREF normalize).
+*/
+enum NormalizationForm {
+    NFC,
+    NFD,
+    NFKC,
+    NFKD
+}
+
+
+enum {
+    /**
+        Shorthand aliases from values indicating normalization forms.
+    */
+    NFC = NormalizationForm.NFC,
+    ///ditto
+    NFD = NormalizationForm.NFD,
+    ///ditto
+    NFKC = NormalizationForm.NFKC,
+    ///ditto
+    NFKD = NormalizationForm.NFKD
+};
+
+/++
+    Returns $(D input) string normalized to the chosen form.
+    Form C is used by default.
+
+    For more information on normalization forms see
+    the $(S_LINK Normalization, normalization section).
+
+    Note:
+    In cases where the string in question is already normalized,
+    it is returned unmodified and no memory allocation happens.
++/
+inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input)
+{
+    import std.algorithm : sort, SwapStrategy;
+    import std.range : zip;
+    import std.array : appender;
+
+    auto anchors = splitNormalized!norm(input);
+    if(anchors[0] == input.length && anchors[1] == input.length)
+        return input;
+    dchar[] decomposed;
+    decomposed.reserve(31);
+    ubyte[] ccc;
+    ccc.reserve(31);
+    auto app = appender!(C[])();
+    do
+    {
+        app.put(input[0..anchors[0]]);
+        foreach(dchar ch; input[anchors[0]..anchors[1]])
+            static if(norm == NFD || norm == NFC)
+            {
+                foreach(dchar c; decompose!Canonical(ch)[])
+                    decomposed ~= c;
+            }
+            else // NFKD & NFKC
+            {
+                foreach(dchar c; decompose!Compatibility(ch)[])
+                    decomposed ~= c;
+            }
+        ccc.length = decomposed.length;
+        size_t firstNonStable = 0;
+        ubyte lastClazz = 0;
+
+        foreach(idx, dchar ch; decomposed)
+        {
+            auto clazz = combiningClass(ch);
+            ccc[idx] = clazz;
+            if(clazz == 0 && lastClazz != 0)
+            {
+                // found a stable code point after unstable ones
+                sort!("a[0] < b[0]", SwapStrategy.stable)
+                    (zip(ccc[firstNonStable..idx], decomposed[firstNonStable..idx]));
+                firstNonStable = decomposed.length;
+            }
+            else if(clazz != 0 && lastClazz == 0)
+            {
+                // found first unstable code point after stable ones
+                firstNonStable = idx;
+            }
+            lastClazz = clazz;
+        }
+        sort!("a[0] < b[0]", SwapStrategy.stable)
+            (zip(ccc[firstNonStable..$], decomposed[firstNonStable..$]));
+        static if(norm == NFC || norm == NFKC)
+        {
+            import std.algorithm : countUntil;
+            size_t idx = 0;
+            auto first = countUntil(ccc, 0);
+            if(first >= 0) // no starters?? no recomposition
+            {
+                for(;;)
+                {
+                    auto second = recompose(first, decomposed, ccc);
+                    if(second == decomposed.length)
+                        break;
+                    first = second;
+                }
+                // 2nd pass for hangul syllables
+                hangulRecompose(decomposed);
+            }
+        }
+        static if(norm == NFD || norm == NFKD)
+            app.put(decomposed);
+        else
+        {
+            import std.algorithm : remove;
+            auto clean = remove!("a == dchar.init", SwapStrategy.stable)(decomposed);
+            app.put(decomposed[0 .. clean.length]);
+        }
+        // reset variables
+        decomposed.length = 0;
+        decomposed.assumeSafeAppend();
+        ccc.length = 0;
+        ccc.assumeSafeAppend();
+        input = input[anchors[1]..$];
+        // and move on
+        anchors = splitNormalized!norm(input);
+    }while(anchors[0] != input.length);
+    app.put(input[0..anchors[0]]);
+    return cast(inout(C)[])app.data;
+}
+
+///
+unittest
+{
+    // any encoding works
+    wstring greet = "Hello world";
+    assert(normalize(greet) is greet); // the same exact slice
+
+    // An example of a character with all 4 forms being different:
+    // Greek upsilon with acute and hook symbol (code point 0x03D3)
+    assert(normalize!NFC("ϓ") == "\u03D3");
+    assert(normalize!NFD("ϓ") == "\u03D2\u0301");
+    assert(normalize!NFKC("ϓ") == "\u038E");
+    assert(normalize!NFKD("ϓ") == "\u03A5\u0301");
+}
+
+unittest
+{
+    import std.conv;
+
+    assert(normalize!NFD("abc\uF904def") == "abc\u6ED1def", text(normalize!NFD("abc\uF904def")));
+    assert(normalize!NFKD("2¹⁰") == "210", normalize!NFKD("2¹⁰"));
+    assert(normalize!NFD("Äffin") == "A\u0308ffin");
+
+    // check example
+
+    // any encoding works
+    wstring greet = "Hello world";
+    assert(normalize(greet) is greet); // the same exact slice
+
+    // An example of a character with all 4 forms being different:
+    // Greek upsilon with acute and hook symbol (code point 0x03D3)
+    assert(normalize!NFC("ϓ") == "\u03D3");
+    assert(normalize!NFD("ϓ") == "\u03D2\u0301");
+    assert(normalize!NFKC("ϓ") == "\u038E");
+    assert(normalize!NFKD("ϓ") == "\u03A5\u0301");
+}
+
+// canonically recompose given slice of code points, works in-place and mutates data
+private size_t recompose(size_t start, dchar[] input, ubyte[] ccc) pure nothrow
+{
+    assert(input.length == ccc.length);
+    int accumCC = -1;// so that it's out of 0..255 range
+    bool foundSolidStarter = false;
+    // writefln("recomposing %( %04x %)", input);
+    // first one is always a starter thus we start at i == 1
+    size_t i = start+1;
+    for(; ; )
+    {
+        if(i == input.length)
+            break;
+        int curCC = ccc[i];
+        // In any character sequence beginning with a starter S
+        // a character C is blocked from S if and only if there
+        // is some character B between S and C, and either B
+        // is a starter or it has the same or higher combining class as C.
+        //------------------------
+        // Applying to our case:
+        // S is input[0]
+        // accumCC is the maximum CCC of characters between C and S,
+        //     as ccc are sorted
+        // C is input[i]
+
+        if(curCC > accumCC)
+        {
+            dchar comp = compose(input[start], input[i]);
+            if(comp != dchar.init)
+            {
+                input[start] = comp;
+                input[i] = dchar.init;// put a sentinel
+                // current was merged so its CCC shouldn't affect
+                // composing with the next one
+            }
+            else {
+                // if it was a starter then accumCC is now 0, end of loop
+                accumCC = curCC;
+                if(accumCC == 0)
+                    break;
+            }
+        }
+        else{
+            // ditto here
+            accumCC = curCC;
+            if(accumCC == 0)
+                break;
+        }
+        i++;
+    }
+    return i;
+}
+
+// returns tuple of 2 indexes that delimit:
+// normalized text, piece that needs normalization and
+// the rest of input starting with stable code point
+private auto splitNormalized(NormalizationForm norm, C)(const(C)[] input)
+{
+    import std.typecons : tuple;
+    auto result = input;
+    ubyte lastCC = 0;
+
+    foreach(idx, dchar ch; input)
+    {
+        static if(norm == NFC)
+            if(ch < 0x0300)
+            {
+                lastCC = 0;
+                continue;
+            }
+        ubyte CC = combiningClass(ch);
+        if(lastCC > CC && CC != 0)
+        {
+            return seekStable!norm(idx, input);
+        }
+
+        if(notAllowedIn!norm(ch))
+        {
+           return seekStable!norm(idx, input);
+        }
+        lastCC = CC;
+    }
+    return tuple(input.length, input.length);
+}
+
+private auto seekStable(NormalizationForm norm, C)(size_t idx, in C[] input)
+{
+    import std.utf : codeLength;
+    import std.typecons : tuple;
+
+    auto br = input[0..idx];
+    size_t region_start = 0;// default
+    for(;;)
+    {
+        if(br.empty)// start is 0
+            break;
+        dchar ch = br.back;
+        if(combiningClass(ch) == 0 && allowedIn!norm(ch))
+        {
+            region_start = br.length - codeLength!C(ch);
+            break;
+        }
+        br.popFront();
+    }
+    ///@@@BUG@@@ can't use find: " find is a nested function and can't be used..."
+    size_t region_end=input.length;// end is $ by default
+    foreach(i, dchar ch; input[idx..$])
+    {
+        if(combiningClass(ch) == 0 && allowedIn!norm(ch))
+        {
+            region_end = i+idx;
+            break;
+        }
+    }
+    // writeln("Region to normalize: ", input[region_start..region_end]);
+    return tuple(region_start, region_end);
+}
+
+/**
+    Tests if dchar $(D ch) is always allowed (Quick_Check=YES) in normalization
+    form $(D norm).
+*/
+public bool allowedIn(NormalizationForm norm)(dchar ch)
+{
+    return !notAllowedIn!norm(ch);
+}
+
+///
+unittest
+{
+    // e.g. Cyrillic is always allowed, so is ASCII
+    assert(allowedIn!NFC('я'));
+    assert(allowedIn!NFD('я'));
+    assert(allowedIn!NFKC('я'));
+    assert(allowedIn!NFKD('я'));
+    assert(allowedIn!NFC('Z'));
+}
+
+// not user friendly name but more direct
+private bool notAllowedIn(NormalizationForm norm)(dchar ch)
+{
+    static if(norm == NFC)
+        alias qcTrie = nfcQCTrie;
+    else static if(norm == NFD)
+        alias qcTrie = nfdQCTrie;
+    else static if(norm == NFKC)
+        alias qcTrie = nfkcQCTrie;
+    else static if(norm == NFKD)
+        alias qcTrie = nfkdQCTrie;
+    else
+        static assert("Unknown normalization form "~norm);
+    return qcTrie[ch];
+}
+
+unittest
+{
+    assert(allowedIn!NFC('я'));
+    assert(allowedIn!NFD('я'));
+    assert(allowedIn!NFKC('я'));
+    assert(allowedIn!NFKD('я'));
+    assert(allowedIn!NFC('Z'));
+}
+
+}
+
+version(std_uni_bootstrap)
+{
+    // old version used for bootstrapping of gen_uni.d that generates
+    // up to date optimal versions of all of isXXX functions
+    @safe pure nothrow @nogc public bool isWhite(dchar c)
+    {
+        import std.ascii : isWhite;
+        return isWhite(c) ||
+               c == lineSep || c == paraSep ||
+               c == '\u0085' || c == '\u00A0' || c == '\u1680' || c == '\u180E' ||
+               (c >= '\u2000' && c <= '\u200A') ||
+               c == '\u202F' || c == '\u205F' || c == '\u3000';
+    }
+}
+else
+{
+
+// trusted -> avoid bounds check
+@trusted pure nothrow @nogc
+{
+    // hide template instances behind functions (Bugzilla 13232)
+    ushort toLowerIndex(dchar c) { return toLowerIndexTrie[c]; }
+    ushort toLowerSimpleIndex(dchar c) { return toLowerSimpleIndexTrie[c]; }
+    dchar toLowerTab(size_t idx) { return toLowerTable[idx]; }
+
+    ushort toTitleIndex(dchar c) { return toTitleIndexTrie[c]; }
+    ushort toTitleSimpleIndex(dchar c) { return toTitleSimpleIndexTrie[c]; }
+    dchar toTitleTab(size_t idx) { return toTitleTable[idx]; }
+
+    ushort toUpperIndex(dchar c) { return toUpperIndexTrie[c]; }
+    ushort toUpperSimpleIndex(dchar c) { return toUpperSimpleIndexTrie[c]; }
+    dchar toUpperTab(size_t idx) { return toUpperTable[idx]; }
+}
+
+public:
+
+/++
+    Whether or not $(D c) is a Unicode whitespace $(CHARACTER).
+    (general Unicode category: Part of C0(tab, vertical tab, form feed,
+    carriage return, and linefeed characters), Zs, Zl, Zp, and NEL(U+0085))
++/
+@safe pure nothrow @nogc
+public bool isWhite(dchar c)
+{
+    return isWhiteGen(c); // call pregenerated binary search
+}
+
+/++
+    Return whether $(D c) is a Unicode lowercase $(CHARACTER).
++/
+@safe pure nothrow @nogc
+bool isLower(dchar c)
+{
+    import std.ascii : isLower, isASCII;
+    if(isASCII(c))
+        return isLower(c);
+    return lowerCaseTrie[c];
+}
+
+@safe unittest
+{
+    import std.ascii : isLower;
+    foreach(v; 0..0x80)
+        assert(isLower(v) == .isLower(v));
+    assert(.isLower('я'));
+    assert(.isLower('й'));
+    assert(!.isLower('Ж'));
+    // Greek HETA
+    assert(!.isLower('\u0370'));
+    assert(.isLower('\u0371'));
+    assert(!.isLower('\u039C')); // capital MU
+    assert(.isLower('\u03B2')); // beta
+    // from extended Greek
+    assert(!.isLower('\u1F18'));
+    assert(.isLower('\u1F00'));
+    foreach(v; unicode.lowerCase.byCodepoint)
+        assert(.isLower(v) && !isUpper(v));
+}
+
+
+/++
+    Return whether $(D c) is a Unicode uppercase $(CHARACTER).
++/
+@safe pure nothrow @nogc
+bool isUpper(dchar c)
+{
+    import std.ascii : isUpper, isASCII;
+    if(isASCII(c))
+        return isUpper(c);
+    return upperCaseTrie[c];
+}
+
+@safe unittest
+{
+    import std.ascii : isLower;
+    foreach(v; 0..0x80)
+        assert(isLower(v) == .isLower(v));
+    assert(!isUpper('й'));
+    assert(isUpper('Ж'));
+    // Greek HETA
+    assert(isUpper('\u0370'));
+    assert(!isUpper('\u0371'));
+    assert(isUpper('\u039C')); // capital MU
+    assert(!isUpper('\u03B2')); // beta
+    // from extended Greek
+    assert(!isUpper('\u1F00'));
+    assert(isUpper('\u1F18'));
+    foreach(v; unicode.upperCase.byCodepoint)
+        assert(isUpper(v) && !.isLower(v));
+}
+
+
+//TODO: Hidden for now, needs better API.
+//Other transforms could use better API as well, but this one is a new primitive.
+@safe pure nothrow @nogc
+private dchar toTitlecase(dchar c)
+{
+    // optimize ASCII case
+    if(c < 0xAA)
+    {
+        if(c < 'a')
+            return c;
+        if(c <= 'z')
+            return c - 32;
+        return c;
+    }
+    size_t idx = toTitleSimpleIndex(c);
+    if(idx != ushort.max)
+    {
+        return toTitleTab(idx);
+    }
+    return c;
+}
+
+private alias UpperTriple = AliasSeq!(toUpperIndex, MAX_SIMPLE_UPPER, toUpperTab);
+private alias LowerTriple = AliasSeq!(toLowerIndex, MAX_SIMPLE_LOWER, toLowerTab);
+
+// generic toUpper/toLower on whole string, creates new or returns as is
+private S toCase(alias indexFn, uint maxIdx, alias tableFn, S)(S s) @trusted pure
+    if(isSomeString!S)
+{
+    import std.array : appender;
+
+    foreach(i, dchar cOuter; s)
+    {
+        ushort idx = indexFn(cOuter);
+        if(idx == ushort.max)
+            continue;
+        auto result = appender!S(s[0..i]);
+        result.reserve(s.length);
+        foreach(dchar c; s[i .. $])
+        {
+            idx = indexFn(c);
+            if(idx == ushort.max)
+                result.put(c);
+            else if(idx < maxIdx)
+            {
+                c = tableFn(idx);
+                result.put(c);
+            }
+            else
+            {
+                auto val = tableFn(idx);
+                // unpack length + codepoint
+                uint len = val>>24;
+                result.put(cast(dchar)(val & 0xFF_FFFF));
+                foreach(j; idx+1..idx+len)
+                    result.put(tableFn(j));
+            }
+        }
+        return result.data;
+    }
+    return s;
+}
+
+unittest //12428
+{
+    import std.array;
+    auto s = "abcdefghij".replicate(300);
+    s = s[0..10];
+
+    toUpper(s);
+
+    assert(s == "abcdefghij");
+}
+
+
+// generic toUpper/toLower on whole range, returns range
+private auto toCaser(alias indexFn, uint maxIdx, alias tableFn, Range)(Range str)
+    // Accept range of dchar's
+    if (isInputRange!Range &&
+        isSomeChar!(ElementEncodingType!Range) &&
+        ElementEncodingType!Range.sizeof == dchar.sizeof)
+{
+    static struct ToCaserImpl
+    {
+        @property bool empty()
+        {
+            return !nLeft && r.empty;
+        }
+
+        @property auto front()
+        {
+            if (!nLeft)
+            {
+                dchar c = r.front;
+                const idx = indexFn(c);
+                if (idx == ushort.max)
+                {
+                    buf[0] = c;
+                    nLeft = 1;
+                }
+                else if (idx < maxIdx)
+                {
+                    buf[0] = tableFn(idx);
+                    nLeft = 1;
+                }
+                else
+                {
+                    auto val = tableFn(idx);
+                    // unpack length + codepoint
+                    nLeft = val >> 24;
+                    if (nLeft == 0)
+                        nLeft = 1;
+                    assert(nLeft <= buf.length);
+                    buf[nLeft - 1] = cast(dchar)(val & 0xFF_FFFF);
+                    foreach (j; 1 .. nLeft)
+                        buf[nLeft - j - 1] = tableFn(idx + j);
+                }
+            }
+            return buf[nLeft - 1];
+        }
+
+        void popFront()
+        {
+            if (!nLeft)
+                front;
+            assert(nLeft);
+            --nLeft;
+            if (!nLeft)
+                r.popFront();
+        }
+
+        static if (isForwardRange!Range)
+        {
+            @property auto save()
+            {
+                auto ret = this;
+                ret.r = r.save;
+                return ret;
+            }
+        }
+
+      private:
+        Range r;
+        uint nLeft;
+        dchar[3] buf = void;
+    }
+
+    return ToCaserImpl(str);
+}
+
+/*********************
+ * Convert input range or string to upper or lower case.
+ *
+ * Does not allocate memory.
+ * Characters in UTF-8 or UTF-16 format that cannot be decoded
+ * are treated as $(XREF utf, replacementDchar).
+ *
+ * Params:
+ *      str = string or range of characters
+ *
+ * Returns:
+ *      an InputRange of dchars
+ *
+ * See_Also:
+ *      $(LREF toUpper), $(LREF toLower)
+ */
+
+auto asLowerCase(Range)(Range str)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static if (ElementEncodingType!Range.sizeof < dchar.sizeof)
+    {
+        import std.utf : byDchar;
+
+        // Decode first
+        return toCaser!LowerTriple(str.byDchar);
+    }
+    else
+    {
+        return toCaser!LowerTriple(str);
+    }
+}
+
+/// ditto
+auto asUpperCase(Range)(Range str)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static if (ElementEncodingType!Range.sizeof < dchar.sizeof)
+    {
+        import std.utf : byDchar;
+
+        // Decode first
+        return toCaser!UpperTriple(str.byDchar);
+    }
+    else
+    {
+        return toCaser!UpperTriple(str);
+    }
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm.comparison : equal;
+
+    assert("hEllo".asUpperCase.equal("HELLO"));
+}
+
+auto asLowerCase(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return asLowerCase!(StringTypeOf!Range)(str);
+}
+
+auto asUpperCase(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return asUpperCase!(StringTypeOf!Range)(str);
+}
+
+unittest
+{
+    assert(testAliasedString!asLowerCase("hEllo"));
+    assert(testAliasedString!asUpperCase("hEllo"));
+}
+
+unittest
+{
+    import std.array;
+
+    auto a = "HELLo".asLowerCase;
+    auto savea = a.save;
+    auto s = a.array;
+    assert(s == "hello");
+    s = savea.array;
+    assert(s == "hello");
+
+    string[] lower = ["123", "abcфеж", "\u0131\u023f\u03c9", "i\u0307\u1Fe2"];
+    string[] upper = ["123", "ABCФЕЖ", "I\u2c7e\u2126", "\u0130\u03A5\u0308\u0300"];
+
+    foreach (i, slwr; lower)
+    {
+        import std.utf : byChar;
+
+        auto sx = slwr.asUpperCase.byChar.array;
+        assert(sx == toUpper(slwr));
+        auto sy = upper[i].asLowerCase.byChar.array;
+        assert(sy == toLower(upper[i]));
+    }
+
+    // Not necessary to call r.front
+    for (auto r = lower[3].asUpperCase; !r.empty; r.popFront())
+    {
+    }
+
+    import std.algorithm.comparison  : equal;
+
+    "HELLo"w.asLowerCase.equal("hello"d);
+    "HELLo"w.asUpperCase.equal("HELLO"d);
+    "HELLo"d.asLowerCase.equal("hello"d);
+    "HELLo"d.asUpperCase.equal("HELLO"d);
+
+    import std.utf : byChar;
+    assert(toLower("\u1Fe2") == asLowerCase("\u1Fe2").byChar.array);
+}
+
+// generic capitalizer on whole range, returns range
+private auto toCapitalizer(alias indexFnUpper, uint maxIdxUpper, alias tableFnUpper,
+                           Range)(Range str)
+    // Accept range of dchar's
+    if (isInputRange!Range &&
+        isSomeChar!(ElementEncodingType!Range) &&
+        ElementEncodingType!Range.sizeof == dchar.sizeof)
+{
+    static struct ToCapitalizerImpl
+    {
+        @property bool empty()
+        {
+            return lower ? lwr.empty : !nLeft && r.empty;
+        }
+
+        @property auto front()
+        {
+            if (lower)
+                return lwr.front;
+
+            if (!nLeft)
+            {
+                dchar c = r.front;
+                const idx = indexFnUpper(c);
+                if (idx == ushort.max)
+                {
+                    buf[0] = c;
+                    nLeft = 1;
+                }
+                else if (idx < maxIdxUpper)
+                {
+                    buf[0] = tableFnUpper(idx);
+                    nLeft = 1;
+                }
+                else
+                {
+                    auto val = tableFnUpper(idx);
+                    // unpack length + codepoint
+                    nLeft = val >> 24;
+                    if (nLeft == 0)
+                        nLeft = 1;
+                    assert(nLeft <= buf.length);
+                    buf[nLeft - 1] = cast(dchar)(val & 0xFF_FFFF);
+                    foreach (j; 1 .. nLeft)
+                        buf[nLeft - j - 1] = tableFnUpper(idx + j);
+                }
+            }
+            return buf[nLeft - 1];
+        }
+
+        void popFront()
+        {
+            if (lower)
+                lwr.popFront();
+            else
+            {
+                if (!nLeft)
+                    front;
+                assert(nLeft);
+                --nLeft;
+                if (!nLeft)
+                {
+                    r.popFront();
+                    lwr = r.asLowerCase();
+                    lower = true;
+                }
+            }
+        }
+
+        static if (isForwardRange!Range)
+        {
+            @property auto save()
+            {
+                auto ret = this;
+                ret.r = r.save;
+                ret.lwr = lwr.save;
+                return ret;
+            }
+        }
+
+      private:
+        Range r;
+        typeof(r.asLowerCase) lwr; // range representing the lower case rest of string
+        bool lower = false;     // false for first character, true for rest of string
+        dchar[3] buf = void;
+        uint nLeft = 0;
+    }
+
+    return ToCapitalizerImpl(str);
+}
+
+/*********************
+ * Capitalize input range or string, meaning convert the first
+ * character to upper case and subsequent characters to lower case.
+ *
+ * Does not allocate memory.
+ * Characters in UTF-8 or UTF-16 format that cannot be decoded
+ * are treated as $(XREF utf, replacementDchar).
+ *
+ * Params:
+ *      str = string or range of characters
+ *
+ * Returns:
+ *      an InputRange of dchars
+ *
+ * See_Also:
+ *      $(LREF toUpper), $(LREF toLower)
+ *      $(LREF asUpperCase), $(LREF asLowerCase)
+ */
+
+auto asCapitalized(Range)(Range str)
+    if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
+        !isConvertibleToString!Range)
+{
+    static if (ElementEncodingType!Range.sizeof < dchar.sizeof)
+    {
+        import std.utf : byDchar;
+
+        // Decode first
+        return toCapitalizer!UpperTriple(str.byDchar);
+    }
+    else
+    {
+        return toCapitalizer!UpperTriple(str);
+    }
+}
+
+///
+@safe pure unittest
+{
+    import std.algorithm.comparison : equal;
+
+    assert("hEllo".asCapitalized.equal("Hello"));
+}
+
+auto asCapitalized(Range)(auto ref Range str)
+    if (isConvertibleToString!Range)
+{
+    return asCapitalized!(StringTypeOf!Range)(str);
+}
+
+unittest
+{
+    assert(testAliasedString!asCapitalized("hEllo"));
+}
+
+@safe pure nothrow @nogc unittest
+{
+    auto r = "hEllo".asCapitalized();
+    assert(r.front == 'H');
+}
+
+unittest
+{
+    import std.array;
+
+    auto a = "hELLo".asCapitalized;
+    auto savea = a.save;
+    auto s = a.array;
+    assert(s == "Hello");
+    s = savea.array;
+    assert(s == "Hello");
+
+    string[2][] cases =
+    [
+        ["", ""],
+        ["h", "H"],
+        ["H", "H"],
+        ["3", "3"],
+        ["123", "123"],
+        ["h123A", "H123a"],
+        ["феж", "Феж"],
+        ["\u1Fe2", "\u03a5\u0308\u0300"],
+    ];
+
+    foreach (i; 0 .. cases.length)
+    {
+        import std.utf : byChar;
+
+        auto r = cases[i][0].asCapitalized.byChar.array;
+        auto result = cases[i][1];
+        assert(r == result);
+    }
+
+    // Don't call r.front
+    for (auto r = "\u1Fe2".asCapitalized; !r.empty; r.popFront())
+    {
+    }
+
+    import std.algorithm.comparison  : equal;
+
+    "HELLo"w.asCapitalized.equal("Hello"d);
+    "hElLO"w.asCapitalized.equal("Hello"d);
+    "hello"d.asCapitalized.equal("Hello"d);
+    "HELLO"d.asCapitalized.equal("Hello"d);
+
+    import std.utf : byChar;
+    assert(asCapitalized("\u0130").byChar.array == asUpperCase("\u0130").byChar.array);
+}
+
+// TODO: helper, I wish std.utf was more flexible (and stright)
+private size_t encodeTo(char[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc
+{
+    if (c <= 0x7F)
+    {
+        buf[idx] = cast(char)c;
+        idx++;
+    }
+    else if (c <= 0x7FF)
+    {
+        buf[idx] = cast(char)(0xC0 | (c >> 6));
+        buf[idx+1] = cast(char)(0x80 | (c & 0x3F));
+        idx += 2;
+    }
+    else if (c <= 0xFFFF)
+    {
+        buf[idx] = cast(char)(0xE0 | (c >> 12));
+        buf[idx+1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[idx+2] = cast(char)(0x80 | (c & 0x3F));
+        idx += 3;
+    }
+    else if (c <= 0x10FFFF)
+    {
+        buf[idx] = cast(char)(0xF0 | (c >> 18));
+        buf[idx+1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
+        buf[idx+2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[idx+3] = cast(char)(0x80 | (c & 0x3F));
+        idx += 4;
+    }
+    else
+        assert(0);
+    return idx;
+}
+
+unittest
+{
+    char[] s = "abcd".dup;
+    size_t i = 0;
+    i = encodeTo(s, i, 'X');
+    assert(s == "Xbcd");
+
+    i = encodeTo(s, i, cast(dchar)'\u00A9');
+    assert(s == "X\xC2\xA9d");
+}
+
+// TODO: helper, I wish std.utf was more flexible (and stright)
+private size_t encodeTo(wchar[] buf, size_t idx, dchar c) @trusted pure
+{
+    import std.utf;
+    if (c <= 0xFFFF)
+    {
+        if (0xD800 <= c && c <= 0xDFFF)
+            throw (new UTFException("Encoding an isolated surrogate code point in UTF-16")).setSequence(c);
+        buf[idx] = cast(wchar)c;
+        idx++;
+    }
+    else if (c <= 0x10FFFF)
+    {
+        buf[idx] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
+        buf[idx+1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
+        idx += 2;
+    }
+    else
+        assert(0);
+    return idx;
+}
+
+private size_t encodeTo(dchar[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc
+{
+    buf[idx] = c;
+    idx++;
+    return idx;
+}
+
+private void toCaseInPlace(alias indexFn, uint maxIdx, alias tableFn, C)(ref C[] s) @trusted pure
+    if (is(C == char) || is(C == wchar)  || is(C == dchar))
+{
+    import std.utf;
+    size_t curIdx = 0;
+    size_t destIdx = 0;
+    alias slowToCase = toCaseInPlaceAlloc!(indexFn, maxIdx, tableFn);
+    size_t lastUnchanged = 0;
+    // in-buffer move of bytes to a new start index
+    // the trick is that it may not need to copy at all
+    static size_t moveTo(C[] str, size_t dest, size_t from, size_t to)
+    {
+        // Interestingly we may just bump pointer for a while
+        // then have to copy if a re-cased char was smaller the original
+        // later we may regain pace with char that got bigger
+        // In the end it sometimes flip-flops between the 2 cases below
+        if(dest == from)
+            return to;
+        // got to copy
+        foreach(C c; str[from..to])
+            str[dest++] = c;
+        return dest;
+    }
+    while(curIdx != s.length)
+    {
+        size_t startIdx = curIdx;
+        dchar ch = decode(s, curIdx);
+        // TODO: special case for ASCII
+        auto caseIndex = indexFn(ch);
+        if(caseIndex == ushort.max) // unchanged, skip over
+        {
+            continue;
+        }
+        else if(caseIndex < maxIdx)  // 1:1 codepoint mapping
+        {
+            // previous cased chars had the same length as uncased ones
+            // thus can just adjust pointer
+            destIdx = moveTo(s, destIdx, lastUnchanged, startIdx);
+            lastUnchanged = curIdx;
+            dchar cased = tableFn(caseIndex);
+            auto casedLen = codeLength!C(cased);
+            if(casedLen + destIdx > curIdx) // no place to fit cased char
+            {
+                // switch to slow codepath, where we allocate
+                return slowToCase(s, startIdx, destIdx);
+            }
+            else
+            {
+                destIdx = encodeTo(s, destIdx, cased);
+            }
+        }
+        else  // 1:m codepoint mapping, slow codepath
+        {
+            destIdx = moveTo(s, destIdx, lastUnchanged, startIdx);
+            lastUnchanged = curIdx;
+            return slowToCase(s, startIdx, destIdx);
+        }
+        assert(destIdx <= curIdx);
+    }
+    if(lastUnchanged != s.length)
+    {
+        destIdx = moveTo(s, destIdx, lastUnchanged, s.length);
+    }
+    s = s[0..destIdx];
+}
+
+// helper to precalculate size of case-converted string
+private template toCaseLength(alias indexFn, uint maxIdx, alias tableFn)
+{
+    size_t toCaseLength(C)(in C[] str)
+    {
+        import std.utf;
+        size_t codeLen = 0;
+        size_t lastNonTrivial = 0;
+        size_t curIdx = 0;
+        while(curIdx != str.length)
+        {
+            size_t startIdx = curIdx;
+            dchar ch = decode(str, curIdx);
+            ushort caseIndex = indexFn(ch);
+            if(caseIndex == ushort.max)
+                continue;
+            else if(caseIndex < maxIdx)
+            {
+                codeLen += startIdx - lastNonTrivial;
+                lastNonTrivial = curIdx;
+                dchar cased = tableFn(caseIndex);
+                codeLen += codeLength!C(cased);
+            }
+            else
+            {
+                codeLen += startIdx - lastNonTrivial;
+                lastNonTrivial = curIdx;
+                auto val = tableFn(caseIndex);
+                auto len = val>>24;
+                dchar cased = val & 0xFF_FFFF;
+                codeLen += codeLength!C(cased);
+                foreach(j; caseIndex+1..caseIndex+len)
+                    codeLen += codeLength!C(tableFn(j));
+            }
+        }
+        if(lastNonTrivial != str.length)
+            codeLen += str.length - lastNonTrivial;
+        return codeLen;
+    }
+}
+
+unittest
+{
+    import std.conv;
+    alias toLowerLength = toCaseLength!(LowerTriple);
+    assert(toLowerLength("abcd") == 4);
+    assert(toLowerLength("аБВгд456") == 10+3);
+}
+
+// slower code path that preallocates and then copies
+// case-converted stuf to the new string
+private template toCaseInPlaceAlloc(alias indexFn, uint maxIdx, alias tableFn)
+{
+    void toCaseInPlaceAlloc(C)(ref C[] s, size_t curIdx,
+        size_t destIdx) @trusted pure
+        if (is(C == char) || is(C == wchar) || is(C == dchar))
+    {
+        import std.utf : decode;
+        alias caseLength = toCaseLength!(indexFn, maxIdx, tableFn);
+        auto trueLength = destIdx + caseLength(s[curIdx..$]);
+        C[] ns = new C[trueLength];
+        ns[0..destIdx] = s[0..destIdx];
+        size_t lastUnchanged = curIdx;
+        while(curIdx != s.length)
+        {
+            size_t startIdx = curIdx; // start of current codepoint
+            dchar ch = decode(s, curIdx);
+            auto caseIndex = indexFn(ch);
+            if(caseIndex == ushort.max) // skip over
+            {
+                continue;
+            }
+            else if(caseIndex < maxIdx)  // 1:1 codepoint mapping
+            {
+                dchar cased = tableFn(caseIndex);
+                auto toCopy = startIdx - lastUnchanged;
+                ns[destIdx .. destIdx+toCopy] = s[lastUnchanged .. startIdx];
+                lastUnchanged = curIdx;
+                destIdx += toCopy;
+                destIdx = encodeTo(ns, destIdx, cased);
+            }
+            else  // 1:m codepoint mapping, slow codepath
+            {
+                auto toCopy = startIdx - lastUnchanged;
+                ns[destIdx .. destIdx+toCopy] = s[lastUnchanged .. startIdx];
+                lastUnchanged = curIdx;
+                destIdx += toCopy;
+                auto val = tableFn(caseIndex);
+                // unpack length + codepoint
+                uint len = val>>24;
+                destIdx = encodeTo(ns, destIdx, cast(dchar)(val & 0xFF_FFFF));
+                foreach(j; caseIndex+1..caseIndex+len)
+                    destIdx = encodeTo(ns, destIdx, tableFn(j));
+            }
+        }
+        if(lastUnchanged != s.length)
+        {
+            auto toCopy = s.length - lastUnchanged;
+            ns[destIdx..destIdx+toCopy] = s[lastUnchanged..$];
+            destIdx += toCopy;
+        }
+        assert(ns.length == destIdx);
+        s = ns;
+    }
+}
+
+/++
+    Converts $(D s) to lowercase (by performing Unicode lowercase mapping) in place.
+    For a few characters string length may increase after the transformation,
+    in such a case the function reallocates exactly once.
+    If $(D s) does not have any uppercase characters, then $(D s) is unaltered.
++/
+void toLowerInPlace(C)(ref C[] s) @trusted pure
+    if (is(C == char) || is(C == wchar) || is(C == dchar))
+{
+    toCaseInPlace!(LowerTriple)(s);
+}
+// overloads for the most common cases to reduce compile time
+@safe pure /*TODO nothrow*/
+{
+    void toLowerInPlace(ref char[] s)
+    { toLowerInPlace!char(s); }
+    void toLowerInPlace(ref wchar[] s)
+    { toLowerInPlace!wchar(s); }
+    void toLowerInPlace(ref dchar[] s)
+    { toLowerInPlace!dchar(s); }
+}
+
+/++
+    Converts $(D s) to uppercase  (by performing Unicode uppercase mapping) in place.
+    For a few characters string length may increase after the transformation,
+    in such a case the function reallocates exactly once.
+    If $(D s) does not have any lowercase characters, then $(D s) is unaltered.
++/
+void toUpperInPlace(C)(ref C[] s) @trusted pure
+    if (is(C == char) || is(C == wchar) || is(C == dchar))
+{
+    toCaseInPlace!(UpperTriple)(s);
+}
+// overloads for the most common cases to reduce compile time/code size
+@safe pure /*TODO nothrow*/
+{
+    void toUpperInPlace(ref char[] s)
+    { toUpperInPlace!char(s); }
+    void toUpperInPlace(ref wchar[] s)
+    { toUpperInPlace!wchar(s); }
+    void toUpperInPlace(ref dchar[] s)
+    { toUpperInPlace!dchar(s); }
+}
+
+/++
+    If $(D c) is a Unicode uppercase $(CHARACTER), then its lowercase equivalent
+    is returned. Otherwise $(D c) is returned.
+
+    Warning: certain alphabets like German and Greek have no 1:1
+    upper-lower mapping. Use overload of toLower which takes full string instead.
++/
+@safe pure nothrow @nogc
+dchar toLower(dchar c)
+{
+     // optimize ASCII case
+    if(c < 0xAA)
+    {
+        if(c < 'A')
+            return c;
+        if(c <= 'Z')
+            return c + 32;
+        return c;
+    }
+    size_t idx = toLowerSimpleIndex(c);
+    if(idx != ushort.max)
+    {
+        return toLowerTab(idx);
+    }
+    return c;
+}
+
+/++
+    Returns a string which is identical to $(D s) except that all of its
+    characters are converted to lowercase (by preforming Unicode lowercase mapping).
+    If none of $(D s) characters were affected, then $(D s) itself is returned.
++/
+S toLower(S)(S s) @trusted pure
+    if(isSomeString!S)
+{
+    return toCase!(LowerTriple)(s);
+}
+// overloads for the most common cases to reduce compile time
+@safe pure /*TODO nothrow*/
+{
+    string toLower(string s)
+    { return toLower!string(s); }
+    wstring toLower(wstring s)
+    { return toLower!wstring(s); }
+    dstring toLower(dstring s)
+    { return toLower!dstring(s); }
+}
+
+
+@trusted unittest //@@@BUG std.format is not @safe
+{
+    import std.format : format;
+    static import std.ascii;
+    foreach(ch; 0..0x80)
+        assert(std.ascii.toLower(ch) == toLower(ch));
+    assert(toLower('Я') == 'я');
+    assert(toLower('Δ') == 'δ');
+    foreach(ch; unicode.upperCase.byCodepoint)
+    {
+        dchar low = ch.toLower();
+        assert(low == ch || isLower(low), format("%s -> %s", ch, low));
+    }
+    assert(toLower("АЯ") == "ая");
+
+    assert("\u1E9E".toLower == "\u00df");
+    assert("\u00df".toUpper == "SS");
+}
+
+//bugzilla 9629
+unittest
+{
+    wchar[] test = "hello þ world"w.dup;
+    auto piece = test[6..7];
+    toUpperInPlace(piece);
+    assert(test == "hello Þ world");
+}
+
+
+unittest
+{
+    import std.algorithm : cmp;
+    string s1 = "FoL";
+    string s2 = toLower(s1);
+    assert(cmp(s2, "fol") == 0, s2);
+    assert(s2 != s1);
+
+    char[] s3 = s1.dup;
+    toLowerInPlace(s3);
+    assert(s3 == s2);
+
+    s1 = "A\u0100B\u0101d";
+    s2 = toLower(s1);
+    s3 = s1.dup;
+    assert(cmp(s2, "a\u0101b\u0101d") == 0);
+    assert(s2 !is s1);
+    toLowerInPlace(s3);
+    assert(s3 == s2);
+
+    s1 = "A\u0460B\u0461d";
+    s2 = toLower(s1);
+    s3 = s1.dup;
+    assert(cmp(s2, "a\u0461b\u0461d") == 0);
+    assert(s2 !is s1);
+    toLowerInPlace(s3);
+    assert(s3 == s2);
+
+    s1 = "\u0130";
+    s2 = toLower(s1);
+    s3 = s1.dup;
+    assert(s2 == "i\u0307");
+    assert(s2 !is s1);
+    toLowerInPlace(s3);
+    assert(s3 == s2);
+
+    // Test on wchar and dchar strings.
+    assert(toLower("Some String"w) == "some string"w);
+    assert(toLower("Some String"d) == "some string"d);
+
+    // bugzilla 12455
+    dchar c = 'İ'; // '\U0130' LATIN CAPITAL LETTER I WITH DOT ABOVE
+    assert(isUpper(c));
+    assert(toLower(c) == 'i');
+    // extend on 12455 reprot - check simple-case toUpper too
+    c = '\u1f87';
+    assert(isLower(c));
+    assert(toUpper(c) == '\u1F8F');
+}
+
+
+/++
+    If $(D c) is a Unicode lowercase $(CHARACTER), then its uppercase equivalent
+    is returned. Otherwise $(D c) is returned.
+
+    Warning:
+    Certain alphabets like German and Greek have no 1:1
+    upper-lower mapping. Use overload of toUpper which takes full string instead.
+
+    toUpper can be used as an argument to $(XREF_PACK algorithm,iteration,map)
+    to produce an algorithm that can convert a range of characters to upper case
+    without allocating memory.
+    A string can then be produced by using $(XREF_PACK algorithm,mutation,copy)
+    to send it to an $(XREF array, appender).
++/
+@safe pure nothrow @nogc
+dchar toUpper(dchar c)
+{
+    // optimize ASCII case
+    if(c < 0xAA)
+    {
+        if(c < 'a')
+            return c;
+        if(c <= 'z')
+            return c - 32;
+        return c;
+    }
+    size_t idx = toUpperSimpleIndex(c);
+    if(idx != ushort.max)
+    {
+        return toUpperTab(idx);
+    }
+    return c;
+}
+
+///
+unittest
+{
+    import std.algorithm;
+    import std.uni;
+    import std.array;
+
+    auto abuf = appender!(char[])();
+    "hello".map!toUpper.copy(&abuf);
+    assert(abuf.data == "HELLO");
+}
+
+@trusted unittest
+{
+    import std.format : format;
+    static import std.ascii;
+    foreach(ch; 0..0x80)
+        assert(std.ascii.toUpper(ch) == toUpper(ch));
+    assert(toUpper('я') == 'Я');
+    assert(toUpper('δ') == 'Δ');
+    auto title = unicode.Titlecase_Letter;
+    foreach(ch; unicode.lowerCase.byCodepoint)
+    {
+        dchar up = ch.toUpper();
+        assert(up == ch || isUpper(up) || title[up],
+            format("%x -> %x", ch, up));
+    }
+}
+
+/++
+    Returns a string which is identical to $(D s) except that all of its
+    characters are converted to uppercase (by preforming Unicode uppercase mapping).
+    If none of $(D s) characters were affected, then $(D s) itself is returned.
++/
+S toUpper(S)(S s) @trusted pure
+    if(isSomeString!S)
+{
+    return toCase!(UpperTriple)(s);
+}
+// overloads for the most common cases to reduce compile time
+@safe pure /*TODO nothrow*/
+{
+    string toUpper(string s)
+    { return toUpper!string(s); }
+    wstring toUpper(wstring s)
+    { return toUpper!wstring(s); }
+    dstring toUpper(dstring s)
+    { return toUpper!dstring(s); }
+}
+
+unittest
+{
+    import std.algorithm : cmp;
+
+    string s1 = "FoL";
+    string s2;
+    char[] s3;
+
+    s2 = toUpper(s1);
+    s3 = s1.dup; toUpperInPlace(s3);
+    assert(s3 == s2, s3);
+    assert(cmp(s2, "FOL") == 0);
+    assert(s2 !is s1);
+
+    s1 = "a\u0100B\u0101d";
+    s2 = toUpper(s1);
+    s3 = s1.dup; toUpperInPlace(s3);
+    assert(s3 == s2);
+    assert(cmp(s2, "A\u0100B\u0100D") == 0);
+    assert(s2 !is s1);
+
+    s1 = "a\u0460B\u0461d";
+    s2 = toUpper(s1);
+    s3 = s1.dup; toUpperInPlace(s3);
+    assert(s3 == s2);
+    assert(cmp(s2, "A\u0460B\u0460D") == 0);
+    assert(s2 !is s1);
+}
+
+unittest
+{
+    static void doTest(C)(const(C)[] s, const(C)[] trueUp, const(C)[] trueLow)
+    {
+        import std.format : format;
+        string diff = "src: %( %x %)\nres: %( %x %)\ntru: %( %x %)";
+        auto low = s.toLower() , up = s.toUpper();
+        auto lowInp = s.dup, upInp = s.dup;
+        lowInp.toLowerInPlace();
+        upInp.toUpperInPlace();
+        assert(low == trueLow, format(diff, low, trueLow));
+        assert(up == trueUp,  format(diff, up, trueUp));
+        assert(lowInp == trueLow,
+            format(diff, cast(ubyte[])s, cast(ubyte[])lowInp, cast(ubyte[])trueLow));
+        assert(upInp == trueUp,
+            format(diff, cast(ubyte[])s, cast(ubyte[])upInp, cast(ubyte[])trueUp));
+    }
+    foreach(S; AliasSeq!(dstring, wstring, string))
+    {
+
+        S easy = "123";
+        S good = "abCФеж";
+        S awful = "\u0131\u023f\u2126";
+        S wicked = "\u0130\u1FE2";
+        auto options = [easy, good, awful, wicked];
+        S[] lower = ["123", "abcфеж", "\u0131\u023f\u03c9", "i\u0307\u1Fe2"];
+        S[] upper = ["123", "ABCФЕЖ", "I\u2c7e\u2126", "\u0130\u03A5\u0308\u0300"];
+
+        foreach(val; AliasSeq!(easy, good))
+        {
+            auto e = val.dup;
+            auto g = e;
+            e.toUpperInPlace();
+            assert(e is g);
+            e.toLowerInPlace();
+            assert(e is g);
+        }
+        foreach(i, v; options)
+        {
+            doTest(v, upper[i], lower[i]);
+        }
+
+        // a few combinatorial runs
+        foreach(i; 0..options.length)
+        foreach(j; i..options.length)
+        foreach(k; j..options.length)
+        {
+            auto sample = options[i] ~ options[j] ~ options[k];
+            auto sample2 = options[k] ~ options[j] ~ options[i];
+            doTest(sample, upper[i] ~ upper[j] ~ upper[k],
+                lower[i] ~ lower[j] ~ lower[k]);
+            doTest(sample2, upper[k] ~ upper[j] ~ upper[i],
+                lower[k] ~ lower[j] ~ lower[i]);
+        }
+    }
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode alphabetic $(CHARACTER)
+    (general Unicode category: Alphabetic).
++/
+@safe pure nothrow @nogc
+bool isAlpha(dchar c)
+{
+    // optimization
+    if(c < 0xAA)
+    {
+        size_t x = c - 'A';
+        if(x <= 'Z' - 'A')
+            return true;
+        else
+        {
+            x = c - 'a';
+            if(x <= 'z'-'a')
+                return true;
+        }
+        return false;
+    }
+
+    return alphaTrie[c];
+}
+
+@safe unittest
+{
+    auto alpha = unicode("Alphabetic");
+    foreach(ch; alpha.byCodepoint)
+        assert(isAlpha(ch));
+    foreach(ch; 0..0x4000)
+        assert((ch in alpha) == isAlpha(ch));
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode mark
+    (general Unicode category: Mn, Me, Mc).
++/
+@safe pure nothrow @nogc
+bool isMark(dchar c)
+{
+    return markTrie[c];
+}
+
+@safe unittest
+{
+    auto mark = unicode("Mark");
+    foreach(ch; mark.byCodepoint)
+        assert(isMark(ch));
+    foreach(ch; 0..0x4000)
+        assert((ch in mark) == isMark(ch));
+}
+
+/++
+    Returns whether $(D c) is a Unicode numerical $(CHARACTER)
+    (general Unicode category: Nd, Nl, No).
++/
+@safe pure nothrow @nogc
+bool isNumber(dchar c)
+{
+    return numberTrie[c];
+}
+
+@safe unittest
+{
+    auto n = unicode("N");
+    foreach(ch; n.byCodepoint)
+        assert(isNumber(ch));
+    foreach(ch; 0..0x4000)
+        assert((ch in n) == isNumber(ch));
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode punctuation $(CHARACTER)
+    (general Unicode category: Pd, Ps, Pe, Pc, Po, Pi, Pf).
++/
+@safe pure nothrow @nogc
+bool isPunctuation(dchar c)
+{
+    return punctuationTrie[c];
+}
+
+unittest
+{
+    assert(isPunctuation('\u0021'));
+    assert(isPunctuation('\u0028'));
+    assert(isPunctuation('\u0029'));
+    assert(isPunctuation('\u002D'));
+    assert(isPunctuation('\u005F'));
+    assert(isPunctuation('\u00AB'));
+    assert(isPunctuation('\u00BB'));
+    foreach(ch; unicode("P").byCodepoint)
+        assert(isPunctuation(ch));
+}
+
+/++
+    Returns whether $(D c) is a Unicode symbol $(CHARACTER)
+    (general Unicode category: Sm, Sc, Sk, So).
++/
+@safe pure nothrow @nogc
+bool isSymbol(dchar c)
+{
+   return symbolTrie[c];
+}
+
+unittest
+{
+    import std.format : format;
+    assert(isSymbol('\u0024'));
+    assert(isSymbol('\u002B'));
+    assert(isSymbol('\u005E'));
+    assert(isSymbol('\u00A6'));
+    foreach(ch; unicode("S").byCodepoint)
+        assert(isSymbol(ch), format("%04x", ch));
+}
+
+/++
+    Returns whether $(D c) is a Unicode space $(CHARACTER)
+    (general Unicode category: Zs)
+    Note: This doesn't include '\n', '\r', \t' and other non-space $(CHARACTER).
+    For commonly used less strict semantics see $(LREF isWhite).
++/
+@safe pure nothrow @nogc
+bool isSpace(dchar c)
+{
+    return isSpaceGen(c);
+}
+
+unittest
+{
+    assert(isSpace('\u0020'));
+    auto space = unicode.Zs;
+    foreach(ch; space.byCodepoint)
+        assert(isSpace(ch));
+    foreach(ch; 0..0x1000)
+        assert(isSpace(ch) == space[ch]);
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode graphical $(CHARACTER)
+    (general Unicode category: L, M, N, P, S, Zs).
+
++/
+@safe pure nothrow @nogc
+bool isGraphical(dchar c)
+{
+    return graphicalTrie[c];
+}
+
+
+unittest
+{
+    auto set = unicode("Graphical");
+    import std.format : format;
+    foreach(ch; set.byCodepoint)
+        assert(isGraphical(ch), format("%4x", ch));
+    foreach(ch; 0..0x4000)
+        assert((ch in set) == isGraphical(ch));
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode control $(CHARACTER)
+    (general Unicode category: Cc).
++/
+@safe pure nothrow @nogc
+bool isControl(dchar c)
+{
+    return isControlGen(c);
+}
+
+unittest
+{
+    assert(isControl('\u0000'));
+    assert(isControl('\u0081'));
+    assert(!isControl('\u0100'));
+    auto cc = unicode.Cc;
+    foreach(ch; cc.byCodepoint)
+        assert(isControl(ch));
+    foreach(ch; 0..0x1000)
+        assert(isControl(ch) == cc[ch]);
+}
+
+
+/++
+    Returns whether $(D c) is a Unicode formatting $(CHARACTER)
+    (general Unicode category: Cf).
++/
+@safe pure nothrow @nogc
+bool isFormat(dchar c)
+{
+    return isFormatGen(c);
+}
+
+
+unittest
+{
+    assert(isFormat('\u00AD'));
+    foreach(ch; unicode("Format").byCodepoint)
+        assert(isFormat(ch));
+}
+
+// code points for private use, surrogates are not likely to change in near feature
+// if need be they can be generated from unicode data as well
+
+/++
+    Returns whether $(D c) is a Unicode Private Use $(CODEPOINT)
+    (general Unicode category: Co).
++/
+@safe pure nothrow @nogc
+bool isPrivateUse(dchar c)
+{
+    return (0x00_E000 <= c && c <= 0x00_F8FF)
+        || (0x0F_0000 <= c && c <= 0x0F_FFFD)
+        || (0x10_0000 <= c && c <= 0x10_FFFD);
+}
+
+/++
+    Returns whether $(D c) is a Unicode surrogate $(CODEPOINT)
+    (general Unicode category: Cs).
++/
+@safe pure nothrow @nogc
+bool isSurrogate(dchar c)
+{
+    return (0xD800 <= c && c <= 0xDFFF);
+}
+
+/++
+    Returns whether $(D c) is a Unicode high surrogate (lead surrogate).
++/
+@safe pure nothrow @nogc
+bool isSurrogateHi(dchar c)
+{
+    return (0xD800 <= c && c <= 0xDBFF);
+}
+
+/++
+    Returns whether $(D c) is a Unicode low surrogate (trail surrogate).
++/
+@safe pure nothrow @nogc
+bool isSurrogateLo(dchar c)
+{
+    return (0xDC00 <= c && c <= 0xDFFF);
+}
+
+/++
+    Returns whether $(D c) is a Unicode non-character i.e.
+    a $(CODEPOINT) with no assigned abstract character.
+    (general Unicode category: Cn)
++/
+@safe pure nothrow @nogc
+bool isNonCharacter(dchar c)
+{
+    return nonCharacterTrie[c];
+}
+
+unittest
+{
+    auto set = unicode("Cn");
+    foreach(ch; set.byCodepoint)
+        assert(isNonCharacter(ch));
+}
+
+private:
+// load static data from pre-generated tables into usable datastructures
+
+
+@safe auto asSet(const (ubyte)[] compressed) pure
+{
+    return CodepointSet.fromIntervals(decompressIntervals(compressed));
+}
+
+@safe pure nothrow auto asTrie(T...)(in TrieEntry!T e)
+{
+    return const(CodepointTrie!T)(e.offsets, e.sizes, e.data);
+}
+
+@safe pure nothrow @nogc @property
+{
+    // It's important to use auto return here, so that the compiler
+    // only runs semantic on the return type if the function gets
+    // used. Also these are functions rather than templates to not
+    // increase the object size of the caller.
+    auto lowerCaseTrie() { static immutable res = asTrie(lowerCaseTrieEntries); return res; }
+    auto upperCaseTrie() { static immutable res = asTrie(upperCaseTrieEntries); return res; }
+    auto simpleCaseTrie() { static immutable res = asTrie(simpleCaseTrieEntries); return res; }
+    auto fullCaseTrie() { static immutable res = asTrie(fullCaseTrieEntries); return res; }
+    auto alphaTrie() { static immutable res = asTrie(alphaTrieEntries); return res; }
+    auto markTrie() { static immutable res = asTrie(markTrieEntries); return res; }
+    auto numberTrie() { static immutable res = asTrie(numberTrieEntries); return res; }
+    auto punctuationTrie() { static immutable res = asTrie(punctuationTrieEntries); return res; }
+    auto symbolTrie() { static immutable res = asTrie(symbolTrieEntries); return res; }
+    auto graphicalTrie() { static immutable res = asTrie(graphicalTrieEntries); return res; }
+    auto nonCharacterTrie() { static immutable res = asTrie(nonCharacterTrieEntries); return res; }
+
+    //normalization quick-check tables
+    auto nfcQCTrie()
+    {
+        import std.internal.unicode_norm;
+        static immutable res = asTrie(nfcQCTrieEntries);
+        return res;
+    }
+
+    auto nfdQCTrie()
+    {
+        import std.internal.unicode_norm;
+        static immutable res = asTrie(nfdQCTrieEntries);
+        return res;
+    }
+
+    auto nfkcQCTrie()
+    {
+        import std.internal.unicode_norm;
+        static immutable res = asTrie(nfkcQCTrieEntries);
+        return res;
+    }
+
+    auto nfkdQCTrie()
+    {
+        import std.internal.unicode_norm;
+        static immutable res = asTrie(nfkdQCTrieEntries);
+        return res;
+    }
+
+    //grapheme breaking algorithm tables
+    auto mcTrie()
+    {
+        import std.internal.unicode_grapheme;
+        static immutable res = asTrie(mcTrieEntries);
+        return res;
+    }
+
+    auto graphemeExtendTrie()
+    {
+        import std.internal.unicode_grapheme;
+        static immutable res = asTrie(graphemeExtendTrieEntries);
+        return res;
+    }
+
+    auto hangLV()
+    {
+        import std.internal.unicode_grapheme;
+        static immutable res = asTrie(hangulLVTrieEntries);
+        return res;
+    }
+
+    auto hangLVT()
+    {
+        import std.internal.unicode_grapheme;
+        static immutable res = asTrie(hangulLVTTrieEntries);
+        return res;
+    }
+
+    // tables below are used for composition/decomposition
+    auto combiningClassTrie()
+    {
+        import std.internal.unicode_comp;
+        static immutable res = asTrie(combiningClassTrieEntries);
+        return res;
+    }
+
+    auto compatMappingTrie()
+    {
+        import std.internal.unicode_decomp;
+        static immutable res = asTrie(compatMappingTrieEntries);
+        return res;
+    }
+
+    auto canonMappingTrie()
+    {
+        import std.internal.unicode_decomp;
+        static immutable res = asTrie(canonMappingTrieEntries);
+        return res;
+    }
+
+    auto compositionJumpTrie()
+    {
+        import std.internal.unicode_comp;
+        static immutable res = asTrie(compositionJumpTrieEntries);
+        return res;
+    }
+
+    //case conversion tables
+    auto toUpperIndexTrie() { static immutable res = asTrie(toUpperIndexTrieEntries); return res; }
+    auto toLowerIndexTrie() { static immutable res = asTrie(toLowerIndexTrieEntries); return res; }
+    auto toTitleIndexTrie() { static immutable res = asTrie(toTitleIndexTrieEntries); return res; }
+    //simple case conversion tables
+    auto toUpperSimpleIndexTrie() { static immutable res = asTrie(toUpperSimpleIndexTrieEntries); return res; }
+    auto toLowerSimpleIndexTrie() { static immutable res = asTrie(toLowerSimpleIndexTrieEntries); return res; }
+    auto toTitleSimpleIndexTrie() { static immutable res = asTrie(toTitleSimpleIndexTrieEntries); return res; }
+
+}
+
+}// version(!std_uni_bootstrap)
diff --git a/libphobos/src/std/uri.d b/libphobos/src/std/uri.d
new file mode 100644
index 00000000000..658cc7e48f2
--- /dev/null
+++ b/libphobos/src/std/uri.d
@@ -0,0 +1,554 @@
+// Written in the D programming language.
+
+/**
+ * Encode and decode Uniform Resource Identifiers (URIs).
+ * URIs are used in internet transfer protocols.
+ * Valid URI characters consist of letters, digits,
+ * and the characters $(B ;/?:@&amp;=+$,-_.!~*'())
+ * Reserved URI characters are $(B ;/?:@&amp;=+$,)
+ * Escape sequences consist of $(B %) followed by two hex digits.
+ *
+ * See_Also:
+ *  $(LINK2 http://www.ietf.org/rfc/rfc3986.txt, RFC 3986)<br>
+ *  $(LINK2 http://en.wikipedia.org/wiki/Uniform_resource_identifier, Wikipedia)
+ * Macros:
+ *  WIKI = Phobos/StdUri
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_uri.d)
+ */
+/*          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.uri;
+
+//debug=uri;        // uncomment to turn on debugging writefln's
+debug(uri) private import std.stdio;
+
+/* ====================== URI Functions ================ */
+
+private import std.ascii;
+private import core.stdc.stdlib;
+private import std.utf;
+private import std.traits : isSomeChar;
+import core.exception : OutOfMemoryError;
+import std.exception;
+
+/** This Exception is thrown if something goes wrong when encoding or
+decoding a URI.
+*/
+class URIException : Exception
+{
+    mixin basicExceptionCtors;
+}
+
+private enum
+{
+    URI_Alpha = 1,
+    URI_Reserved = 2,
+    URI_Mark = 4,
+    URI_Digit = 8,
+    URI_Hash = 0x10,        // '#'
+}
+
+immutable char[16] hex2ascii = "0123456789ABCDEF";
+
+immutable ubyte[128] uri_flags =      // indexed by character
+    ({
+        ubyte[128] uflags;
+
+        // Compile time initialize
+        uflags['#'] |= URI_Hash;
+
+        foreach (c; 'A' .. 'Z' + 1)
+        {
+            uflags[c] |= URI_Alpha;
+            uflags[c + 0x20] |= URI_Alpha;   // lowercase letters
+        }
+        foreach (c; '0' .. '9' + 1) uflags[c] |= URI_Digit;
+        foreach (c; ";/?:@&=+$,")   uflags[c] |= URI_Reserved;
+        foreach (c; "-_.!~*'()")    uflags[c] |= URI_Mark;
+        return uflags;
+    })();
+
+private string URI_Encode(dstring string, uint unescapedSet)
+{
+    uint j;
+    uint k;
+    dchar V;
+    dchar C;
+
+    // result buffer
+    char[50] buffer = void;
+    char* R;
+    uint Rlen;
+    uint Rsize; // alloc'd size
+
+    auto len = string.length;
+
+    R = buffer.ptr;
+    Rsize = buffer.length;
+    Rlen = 0;
+
+    for (k = 0; k != len; k++)
+    {
+        C = string[k];
+        // if (C in unescapedSet)
+        if (C < uri_flags.length && uri_flags[C] & unescapedSet)
+        {
+            if (Rlen == Rsize)
+            {
+                char* R2;
+
+                Rsize *= 2;
+                if (Rsize > 1024) {
+                    R2 = (new char[Rsize]).ptr;
+                }
+                else
+                {
+                    R2 = cast(char *)alloca(Rsize * char.sizeof);
+                    if (!R2)
+                        throw new OutOfMemoryError("Alloca failure");
+                }
+                R2[0..Rlen] = R[0..Rlen];
+                R = R2;
+            }
+            R[Rlen] = cast(char)C;
+            Rlen++;
+        }
+        else
+        {
+            char[6] Octet;
+            uint L;
+
+            V = C;
+
+            // Transform V into octets
+            if (V <= 0x7F)
+            {
+                Octet[0] = cast(char) V;
+                L = 1;
+            }
+            else if (V <= 0x7FF)
+            {
+                Octet[0] = cast(char)(0xC0 | (V >> 6));
+                Octet[1] = cast(char)(0x80 | (V & 0x3F));
+                L = 2;
+            }
+            else if (V <= 0xFFFF)
+            {
+                Octet[0] = cast(char)(0xE0 | (V >> 12));
+                Octet[1] = cast(char)(0x80 | ((V >> 6) & 0x3F));
+                Octet[2] = cast(char)(0x80 | (V & 0x3F));
+                L = 3;
+            }
+            else if (V <= 0x1FFFFF)
+            {
+                Octet[0] = cast(char)(0xF0 | (V >> 18));
+                Octet[1] = cast(char)(0x80 | ((V >> 12) & 0x3F));
+                Octet[2] = cast(char)(0x80 | ((V >> 6) & 0x3F));
+                Octet[3] = cast(char)(0x80 | (V & 0x3F));
+                L = 4;
+            }
+            /+
+            else if (V <= 0x3FFFFFF)
+            {
+                Octet[0] = cast(char)(0xF8 | (V >> 24));
+                Octet[1] = cast(char)(0x80 | ((V >> 18) & 0x3F));
+                Octet[2] = cast(char)(0x80 | ((V >> 12) & 0x3F));
+                Octet[3] = cast(char)(0x80 | ((V >> 6) & 0x3F));
+                Octet[4] = cast(char)(0x80 | (V & 0x3F));
+                L = 5;
+            }
+            else if (V <= 0x7FFFFFFF)
+            {
+                Octet[0] = cast(char)(0xFC | (V >> 30));
+                Octet[1] = cast(char)(0x80 | ((V >> 24) & 0x3F));
+                Octet[2] = cast(char)(0x80 | ((V >> 18) & 0x3F));
+                Octet[3] = cast(char)(0x80 | ((V >> 12) & 0x3F));
+                Octet[4] = cast(char)(0x80 | ((V >> 6) & 0x3F));
+                Octet[5] = cast(char)(0x80 | (V & 0x3F));
+                L = 6;
+            }
+            +/
+            else
+            {
+                throw new URIException("Undefined UTF-32 code point");
+            }
+
+            if (Rlen + L * 3 > Rsize)
+            {
+                char *R2;
+
+                Rsize = 2 * (Rlen + L * 3);
+                if (Rsize > 1024) {
+                    R2 = (new char[Rsize]).ptr;
+                }
+                else
+                {
+                    R2 = cast(char *)alloca(Rsize * char.sizeof);
+                    if (!R2)
+                        throw new OutOfMemoryError("Alloca failure");
+                }
+                R2[0..Rlen] = R[0..Rlen];
+                R = R2;
+            }
+
+            for (j = 0; j < L; j++)
+            {
+                R[Rlen] = '%';
+                R[Rlen + 1] = hex2ascii[Octet[j] >> 4];
+                R[Rlen + 2] = hex2ascii[Octet[j] & 15];
+
+                Rlen += 3;
+            }
+        }
+    }
+
+    return R[0..Rlen].idup;
+}
+
+uint ascii2hex(dchar c)
+{
+    return (c <= '9') ? c - '0' :
+        (c <= 'F') ? c - 'A' + 10 :
+        c - 'a' + 10;
+}
+
+private dstring URI_Decode(Char)(in Char[] uri, uint reservedSet) if (isSomeChar!Char)
+{
+    uint j;
+    uint k;
+    uint V;
+    dchar C;
+
+    // Result array, allocated on stack
+    dchar* R;
+    uint Rlen;
+
+    auto len = uri.length;
+    auto s = uri.ptr;
+
+    // Preallocate result buffer R guaranteed to be large enough for result
+    auto Rsize = len;
+    if (Rsize > 1024 / dchar.sizeof) {
+        R = (new dchar[Rsize]).ptr;
+    }
+    else
+    {
+        R = cast(dchar *)alloca(Rsize * dchar.sizeof);
+        if (!R)
+            throw new OutOfMemoryError("Alloca failure");
+    }
+    Rlen = 0;
+
+    for (k = 0; k != len; k++)
+    {
+        char B;
+        uint start;
+
+        C = s[k];
+        if (C != '%')
+        {
+            R[Rlen] = C;
+            Rlen++;
+            continue;
+        }
+        start = k;
+        if (k + 2 >= len)
+            throw new URIException("Unexpected end of URI");
+        if (!isHexDigit(s[k + 1]) || !isHexDigit(s[k + 2]))
+            throw new URIException("Expected two hexadecimal digits after '%'");
+        B = cast(char)((ascii2hex(s[k + 1]) << 4) + ascii2hex(s[k + 2]));
+        k += 2;
+        if ((B & 0x80) == 0)
+        {
+            C = B;
+        }
+        else
+        {
+            uint n;
+
+            for (n = 1; ; n++)
+            {
+                if (n > 4)
+                    throw new URIException("UTF-32 code point size too large");
+                if (((B << n) & 0x80) == 0)
+                {
+                    if (n == 1)
+                        throw new URIException("UTF-32 code point size too small");
+                    break;
+                }
+            }
+
+            // Pick off (7 - n) significant bits of B from first byte of octet
+            V = B & ((1 << (7 - n)) - 1);   // (!!!)
+
+            if (k + (3 * (n - 1)) >= len)
+                throw new URIException("UTF-32 unaligned String");
+            for (j = 1; j != n; j++)
+            {
+                k++;
+                if (s[k] != '%')
+                    throw new URIException("Expected: '%'");
+                if (!isHexDigit(s[k + 1]) || !isHexDigit(s[k + 2]))
+                    throw new URIException("Expected two hexadecimal digits after '%'");
+                B = cast(char)((ascii2hex(s[k + 1]) << 4) + ascii2hex(s[k + 2]));
+                if ((B & 0xC0) != 0x80)
+                    throw new URIException("Incorrect UTF-32 multi-byte sequence");
+                k += 2;
+                V = (V << 6) | (B & 0x3F);
+            }
+            if (V > 0x10FFFF)
+                throw new URIException("Unknown UTF-32 code point");
+            C = V;
+        }
+        if (C < uri_flags.length && uri_flags[C] & reservedSet)
+        {
+            // R ~= s[start .. k + 1];
+            int width = (k + 1) - start;
+            for (int ii = 0; ii < width; ii++)
+                R[Rlen + ii] = s[start + ii];
+            Rlen += width;
+        }
+        else
+        {
+            R[Rlen] = C;
+            Rlen++;
+        }
+    }
+    assert(Rlen <= Rsize);  // enforce our preallocation size guarantee
+
+    // Copy array on stack to array in memory
+    return R[0..Rlen].idup;
+}
+
+/*************************************
+ * Decodes the URI string encodedURI into a UTF-8 string and returns it.
+ * Escape sequences that resolve to reserved URI characters are not replaced.
+ * Escape sequences that resolve to the '#' character are not replaced.
+ */
+
+string decode(Char)(in Char[] encodedURI) if (isSomeChar!Char)
+{
+    auto s = URI_Decode(encodedURI, URI_Reserved | URI_Hash);
+    return std.utf.toUTF8(s);
+}
+
+/*******************************
+ * Decodes the URI string encodedURI into a UTF-8 string and returns it. All
+ * escape sequences are decoded.
+ */
+
+string decodeComponent(Char)(in Char[] encodedURIComponent) if (isSomeChar!Char)
+{
+    auto s = URI_Decode(encodedURIComponent, 0);
+    return std.utf.toUTF8(s);
+}
+
+/*****************************
+ * Encodes the UTF-8 string uri into a URI and returns that URI. Any character
+ * not a valid URI character is escaped. The '#' character is not escaped.
+ */
+
+string encode(Char)(in Char[] uri) if (isSomeChar!Char)
+{
+    auto s = std.utf.toUTF32(uri);
+    return URI_Encode(s, URI_Reserved | URI_Hash | URI_Alpha | URI_Digit | URI_Mark);
+}
+
+/********************************
+ * Encodes the UTF-8 string uriComponent into a URI and returns that URI.
+ * Any character not a letter, digit, or one of -_.!~*'() is escaped.
+ */
+
+string encodeComponent(Char)(in Char[] uriComponent) if (isSomeChar!Char)
+{
+    auto s = std.utf.toUTF32(uriComponent);
+    return URI_Encode(s, URI_Alpha | URI_Digit | URI_Mark);
+}
+
+/***************************
+ * Does string s[] start with a URL?
+ * Returns:
+ *  -1   it does not
+ *  len  it does, and s[0..len] is the slice of s[] that is that URL
+ */
+
+ptrdiff_t uriLength(Char)(in Char[] s) if (isSomeChar!Char)
+{
+    /* Must start with one of:
+     *  http://
+     *  https://
+     *  www.
+     */
+    import std.uni : icmp;
+
+    ptrdiff_t i;
+
+    if (s.length <= 4)
+        return -1;
+
+    if (s.length > 7 && icmp(s[0 .. 7], "http://") == 0) {
+        i = 7;
+    }
+    else
+    {
+        if (s.length > 8 && icmp(s[0 .. 8], "https://") == 0)
+            i = 8;
+        else
+            return -1;
+    }
+    //    if (icmp(s[0 .. 4], "www.") == 0)
+    //  i = 4;
+
+    ptrdiff_t lastdot;
+    for (; i < s.length; i++)
+    {
+        auto c = s[i];
+        if (isAlphaNum(c))
+            continue;
+        if (c == '-' || c == '_' || c == '?' ||
+                c == '=' || c == '%' || c == '&' ||
+                c == '/' || c == '+' || c == '#' ||
+                c == '~' || c == '$')
+            continue;
+        if (c == '.')
+        {
+            lastdot = i;
+            continue;
+        }
+        break;
+    }
+    //if (!lastdot || (i - lastdot != 3 && i - lastdot != 4))
+    if (!lastdot)
+        return -1;
+
+    return i;
+}
+
+///
+unittest
+{
+    string s1 = "http://www.digitalmars.com/~fred/fredsRX.html#foo end!";
+    assert (uriLength(s1) == 49);
+    string s2 = "no uri here";
+    assert (uriLength(s2) == -1);
+    assert (uriLength("issue 14924") < 0);
+}
+
+
+/***************************
+ * Does string s[] start with an email address?
+ * Returns:
+ *  -1    it does not
+ *  len   it does, and s[0..i] is the slice of s[] that is that email address
+ * References:
+ *  RFC2822
+ */
+ptrdiff_t emailLength(Char)(in Char[] s) if (isSomeChar!Char)
+{
+    ptrdiff_t i;
+
+    if (!isAlpha(s[0]))
+        return -1;
+
+    for (i = 1; 1; i++)
+    {
+        if (i == s.length)
+            return -1;
+        auto c = s[i];
+        if (isAlphaNum(c))
+            continue;
+        if (c == '-' || c == '_' || c == '.')
+            continue;
+        if (c != '@')
+            return -1;
+        i++;
+        break;
+    }
+
+    /* Now do the part past the '@'
+     */
+    ptrdiff_t lastdot;
+    for (; i < s.length; i++)
+    {
+        auto c = s[i];
+        if (isAlphaNum(c))
+            continue;
+        if (c == '-' || c == '_')
+            continue;
+        if (c == '.')
+        {
+            lastdot = i;
+            continue;
+        }
+        break;
+    }
+    if (!lastdot || (i - lastdot != 3 && i - lastdot != 4))
+        return -1;
+
+    return i;
+}
+
+///
+unittest
+{
+    string s1 = "my.e-mail@www.example-domain.com with garbage added";
+    assert (emailLength(s1) == 32);
+    string s2 = "no email address here";
+    assert (emailLength(s2) == -1);
+    assert (emailLength("issue 14924") < 0);
+}
+
+
+unittest
+{
+    debug(uri) writeln("uri.encodeURI.unittest");
+
+    string source = "http://www.digitalmars.com/~fred/fred's RX.html#foo";
+    string target = "http://www.digitalmars.com/~fred/fred's%20RX.html#foo";
+
+    auto result = encode(source);
+    debug(uri) writefln("result = '%s'", result);
+    assert(result == target);
+    result = decode(target);
+    debug(uri) writefln("result = '%s'", result);
+    assert(result == source);
+
+    result = encode(decode("%E3%81%82%E3%81%82"));
+    assert(result == "%E3%81%82%E3%81%82");
+
+    result = encodeComponent("c++");
+    assert(result == "c%2B%2B");
+
+    auto str = new char[10_000_000];
+    str[] = 'A';
+    result = encodeComponent(str);
+    foreach (char c; result)
+        assert(c == 'A');
+
+    result = decode("%41%42%43");
+    debug(uri) writeln(result);
+
+    import std.meta : AliasSeq;
+    foreach (StringType; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
+    {
+        import std.conv : to;
+        StringType decoded1 = source.to!StringType;
+        string encoded1 = encode(decoded1);
+        assert(decoded1 == source.to!StringType); // check that `decoded1` wasn't changed
+        assert(encoded1 == target);
+        assert(decoded1 == decode(encoded1).to!StringType);
+
+        StringType encoded2 = target.to!StringType;
+        string decoded2 = decode(encoded2);
+        assert(encoded2 == target.to!StringType); // check that `encoded2` wasn't changed
+        assert(decoded2 == source);
+        assert(encoded2 == encode(decoded2).to!StringType);
+    }
+}
diff --git a/libphobos/src/std/utf.d b/libphobos/src/std/utf.d
new file mode 100644
index 00000000000..1f91447a399
--- /dev/null
+++ b/libphobos/src/std/utf.d
@@ -0,0 +1,3589 @@
+// Written in the D programming language.
+
+/++
+    Encode and decode UTF-8, UTF-16 and UTF-32 strings.
+
+    UTF character support is restricted to
+    $(D '\u0000' &lt;= character &lt;= '\U0010FFFF').
+
+    See_Also:
+        $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)<br>
+        $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)<br>
+        $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335)
+    Macros:
+        WIKI = Phobos/StdUtf
+
+    Copyright: Copyright Digital Mars 2000 - 2012.
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+    Authors:   $(WEB digitalmars.com, Walter Bright) and Jonathan M Davis
+    Source:    $(PHOBOSSRC std/_utf.d)
+   +/
+module std.utf;
+
+import std.meta;       // AliasSeq
+import std.range.primitives;
+import std.traits;     // isSomeChar, isSomeString
+import std.typecons;   // Flag
+import std.exception;  // basicExceptionCtors
+
+//debug=utf;           // uncomment to turn on debugging printf's
+
+debug (utf) import core.stdc.stdio : printf;
+
+
+/++
+    Exception thrown on errors in std.utf functions.
+  +/
+class UTFException : Exception
+{
+    import core.internal.string;
+
+    uint[4] sequence;
+    size_t  len;
+
+    @safe pure nothrow @nogc
+    UTFException setSequence(uint[] data...)
+    {
+        assert(data.length <= 4);
+
+        len = data.length < 4 ? data.length : 4;
+        sequence[0 .. len] = data[0 .. len];
+
+        return this;
+    }
+
+    // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed
+
+    this(string msg, string file = __FILE__, size_t line = __LINE__,
+         Throwable next = null) @nogc @safe pure nothrow
+    {
+        super(msg, file, line, next);
+    }
+
+    this(string msg, size_t index, string file = __FILE__,
+         size_t line = __LINE__, Throwable next = null) @safe pure nothrow
+    {
+        UnsignedStringBuf buf = void;
+        msg ~= " (at index " ~ unsignedToTempString(index, buf, 10) ~ ")";
+        super(msg, file, line, next);
+    }
+
+
+    override string toString()
+    {
+        if (len == 0)
+            return super.toString();
+
+        string result = "Invalid UTF sequence:";
+
+        foreach (i; sequence[0 .. len])
+        {
+            UnsignedStringBuf buf = void;
+            result ~= ' ';
+            auto h = unsignedToTempString(i, buf, 16);
+            if (h.length == 1)
+                result ~= '0';
+            result ~= h;
+            result ~= 'x';
+        }
+
+        if (super.msg.length > 0)
+        {
+            result ~= " - ";
+            result ~= super.msg;
+        }
+
+        return result;
+    }
+}
+
+/*
+   Provide array of invalidly encoded UTF strings. Useful for testing.
+
+   Params:
+        Char = char, wchar, or dchar
+
+   Returns:
+        an array of invalidly encoded UTF strings
+ */
+
+package auto invalidUTFstrings(Char)() @safe pure @nogc nothrow
+    if (isSomeChar!Char)
+{
+    static if (is(Char == char))
+    {
+        enum x = 0xDC00;         // invalid surrogate value
+        enum y = 0x110000;       // out of range
+
+        static immutable string[8] result =
+        [
+            "\x80",             // not a start byte
+            "\xC0",             // truncated
+            "\xC0\xC0",         // invalid continuation
+            "\xF0\x82\x82\xAC", // overlong
+            [
+              0xE0 | (x >> 12),
+              0x80 | ((x >> 6) & 0x3F),
+              0x80 | (x & 0x3F)
+            ],
+            [
+              cast(char)(0xF0 | (y >> 18)),
+              cast(char)(0x80 | ((y >> 12) & 0x3F)),
+              cast(char)(0x80 | ((y >> 6) & 0x3F)),
+              cast(char)(0x80 | (y & 0x3F))
+            ],
+            [
+              cast(char)(0xF8 | 3),     // 5 byte encoding
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+            ],
+            [
+              cast(char)(0xFC | 3),     // 6 byte encoding
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+              cast(char)(0x80 | 3),
+            ],
+        ];
+
+        return result[];
+    }
+    else static if (is(Char == wchar))
+    {
+        static immutable wstring[5] result =
+        [
+            [
+              cast(wchar)0xDC00,
+            ],
+            [
+              cast(wchar)0xDFFF,
+            ],
+            [
+              cast(wchar)0xDBFF,
+              cast(wchar)0xDBFF,
+            ],
+            [
+              cast(wchar)0xDBFF,
+              cast(wchar)0xE000,
+            ],
+            [
+              cast(wchar)0xD800,
+            ],
+        ];
+
+        return result[];
+    }
+    else static if (is(Char == dchar))
+    {
+        static immutable dstring[3] result =
+        [
+            [ cast(dchar)0x110000 ],
+            [ cast(dchar)0x00D800 ],
+            [ cast(dchar)0x00DFFF ],
+        ];
+
+        return result;
+    }
+    else
+        static assert(0);
+}
+
+/++
+    Check whether the given Unicode code point is valid.
+
+    Params:
+        c = code point to check
+
+    Returns:
+        $(D true) iff $(D c) is a valid Unicode code point
+
+    Note:
+    $(D '\uFFFE') and $(D '\uFFFF') are considered valid by $(D isValidDchar),
+    as they are permitted for internal use by an application, but they are
+    not allowed for interchange by the Unicode standard.
+  +/
+bool isValidDchar(dchar c) pure nothrow @safe @nogc
+{
+    /* Note: FFFE and FFFF are specifically permitted by the
+     * Unicode standard for application internal use, but are not
+     * allowed for interchange.
+     * (thanks to Arcane Jill)
+     */
+
+    return c < 0xD800 ||
+          (c > 0xDFFF && c <= 0x10FFFF /*&& c != 0xFFFE && c != 0xFFFF*/);
+}
+
+pure nothrow @safe @nogc unittest
+{
+    import std.exception;
+    debug(utf) printf("utf.isValidDchar.unittest\n");
+
+    assertCTFEable!(
+    {
+    assert( isValidDchar(cast(dchar)'a') == true);
+    assert( isValidDchar(cast(dchar)0x1FFFFF) == false);
+
+    assert(!isValidDchar(cast(dchar)0x00D800));
+    assert(!isValidDchar(cast(dchar)0x00DBFF));
+    assert(!isValidDchar(cast(dchar)0x00DC00));
+    assert(!isValidDchar(cast(dchar)0x00DFFF));
+    assert( isValidDchar(cast(dchar)0x00FFFE));
+    assert( isValidDchar(cast(dchar)0x00FFFF));
+    assert( isValidDchar(cast(dchar)0x01FFFF));
+    assert( isValidDchar(cast(dchar)0x10FFFF));
+    assert(!isValidDchar(cast(dchar)0x110000));
+    });
+}
+
+
+/++
+    Calculate the length of the UTF sequence starting at $(D index)
+    in $(D str).
+
+    Params:
+        str = input range of UTF code units. Must be random access if
+        $(D index) is passed
+        index = starting index of UTF sequence (default: $(D 0))
+
+    Returns:
+        The number of code units in the UTF sequence. For UTF-8, this is a
+        value between 1 and 4 (as per $(WEB tools.ietf.org/html/rfc3629#section-3, RFC 3629$(COMMA) section 3)).
+        For UTF-16, it is either 1 or 2. For UTF-32, it is always 1.
+
+    Throws:
+        May throw a $(D UTFException) if $(D str[index]) is not the start of a
+        valid UTF sequence.
+
+    Note:
+        $(D stride) will only analyze the first $(D str[index]) element. It
+        will not fully verify the validity of the UTF sequence, nor even verify
+        the presence of the sequence: it will not actually guarantee that
+        $(D index + stride(str, index) <= str.length).
+  +/
+uint stride(S)(auto ref S str, size_t index)
+    if (is(S : const char[]) ||
+        (isRandomAccessRange!S && is(Unqual!(ElementType!S) == char)))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index < str.length, "Past the end of the UTF-8 sequence");
+    immutable c = str[index];
+
+    if (c < 0x80)
+        return 1;
+    else
+        return strideImpl(c, index);
+}
+
+/// Ditto
+uint stride(S)(auto ref S str)
+    if (is(S : const char[]) ||
+        (isInputRange!S && is(Unqual!(ElementType!S) == char)))
+{
+    static if (is(S : const char[]))
+        immutable c = str[0];
+    else
+        immutable c = str.front;
+
+    if (c < 0x80)
+        return 1;
+    else
+        return strideImpl(c, 0);
+}
+
+private uint strideImpl(char c, size_t index) @trusted pure
+in { assert(c & 0x80); }
+body
+{
+    import core.bitop : bsr;
+    immutable msbs = 7 - bsr(~c);
+    if (!~c || msbs < 2 || msbs > 4)
+        throw new UTFException("Invalid UTF-8 sequence", index);
+    return msbs;
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(string s, dchar c, size_t i = 0, size_t line = __LINE__)
+    {
+        enforce(stride(s, i) == codeLength!char(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(stride(RandomCU!char(s), i) == codeLength!char(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!char(s);
+        immutable randLen = refRandom.length;
+        enforce(stride(refRandom, i) == codeLength!char(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == 0)
+        {
+            enforce(stride(s) == codeLength!char(c),
+                    new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line));
+
+            enforce(stride(InputCU!char(s)) == codeLength!char(c),
+                    new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!char(s);
+            immutable bidirLen = refBidir.length;
+            enforce(stride(refBidir) == codeLength!char(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'a');
+    test("hello\U00010143\u0100\U00010143", 'h', 0);
+    test("hello\U00010143\u0100\U00010143", 'e', 1);
+    test("hello\U00010143\u0100\U00010143", 'l', 2);
+    test("hello\U00010143\u0100\U00010143", 'l', 3);
+    test("hello\U00010143\u0100\U00010143", 'o', 4);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 5);
+    test("hello\U00010143\u0100\U00010143", '\u0100', 9);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 11);
+
+    foreach (S; AliasSeq!(char[], const char[], string))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!({ stride(str, 0); }));
+        static assert(isSafe!({ stride(str);    }));
+        static assert((functionAttributes!({ stride(str, 0); }) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!({ stride(str);    }) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+unittest // invalid start bytes
+{
+    import std.exception: assertThrown;
+    immutable char[] invalidStartBytes = [
+        0b1111_1000, // indicating a sequence length of 5
+        0b1111_1100, // 6
+        0b1111_1110, // 7
+        0b1111_1111, // 8
+        0b1000_0000, // continuation byte
+    ];
+    foreach(c; invalidStartBytes)
+        assertThrown!UTFException(stride([c]));
+}
+
+/// Ditto
+uint stride(S)(auto ref S str, size_t index)
+    if (is(S : const wchar[]) ||
+        (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar)))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index < str.length, "Past the end of the UTF-16 sequence");
+    immutable uint u = str[index];
+    return 1 + (u >= 0xD800 && u <= 0xDBFF);
+}
+
+/// Ditto
+uint stride(S)(auto ref S str) @safe pure
+    if (is(S : const wchar[]))
+{
+    return stride(str, 0);
+}
+
+/// Ditto
+uint stride(S)(auto ref S str)
+    if (isInputRange!S && is(Unqual!(ElementType!S) == wchar))
+{
+    assert(!str.empty, "UTF-16 sequence is empty");
+    immutable uint u = str.front;
+    return 1 + (u >= 0xD800 && u <= 0xDBFF);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(wstring s, dchar c, size_t i = 0, size_t line = __LINE__)
+    {
+        enforce(stride(s, i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(stride(RandomCU!wchar(s), i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!wchar(s);
+        immutable randLen = refRandom.length;
+        enforce(stride(refRandom, i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == 0)
+        {
+            enforce(stride(s) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line));
+
+            enforce(stride(InputCU!wchar(s)) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!wchar(s);
+            immutable bidirLen = refBidir.length;
+            enforce(stride(refBidir) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'a');
+    test("hello\U00010143\u0100\U00010143", 'h', 0);
+    test("hello\U00010143\u0100\U00010143", 'e', 1);
+    test("hello\U00010143\u0100\U00010143", 'l', 2);
+    test("hello\U00010143\u0100\U00010143", 'l', 3);
+    test("hello\U00010143\u0100\U00010143", 'o', 4);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 5);
+    test("hello\U00010143\u0100\U00010143", '\u0100', 7);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 8);
+
+    foreach (S; AliasSeq!(wchar[], const wchar[], wstring))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!(() => stride(str, 0)));
+        static assert(isSafe!(() => stride(str)   ));
+        static assert((functionAttributes!(() => stride(str, 0)) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!(() => stride(str)   ) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+/// Ditto
+uint stride(S)(auto ref S str, size_t index = 0)
+    if (is(S : const dchar[]) ||
+        (isInputRange!S && is(Unqual!(ElementEncodingType!S) == dchar)))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index < str.length, "Past the end of the UTF-32 sequence");
+    else
+        assert(!str.empty, "UTF-32 sequence is empty.");
+    return 1;
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(dstring s, dchar c, size_t i = 0, size_t line = __LINE__)
+    {
+        enforce(stride(s, i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(stride(RandomCU!dchar(s), i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!dchar(s);
+        immutable randLen = refRandom.length;
+        enforce(stride(refRandom, i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == 0)
+        {
+            enforce(stride(s) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line));
+
+            enforce(stride(InputCU!dchar(s)) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!dchar(s);
+            immutable bidirLen = refBidir.length;
+            enforce(stride(refBidir) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'a');
+    test("hello\U00010143\u0100\U00010143", 'h', 0);
+    test("hello\U00010143\u0100\U00010143", 'e', 1);
+    test("hello\U00010143\u0100\U00010143", 'l', 2);
+    test("hello\U00010143\u0100\U00010143", 'l', 3);
+    test("hello\U00010143\u0100\U00010143", 'o', 4);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 5);
+    test("hello\U00010143\u0100\U00010143", '\u0100', 6);
+    test("hello\U00010143\u0100\U00010143", '\U00010143', 7);
+
+    foreach (S; AliasSeq!(dchar[], const dchar[], dstring))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!(() => stride(str, 0)));
+        static assert(isSafe!(() => stride(str)   ));
+        static assert((functionAttributes!(() => stride(str, 0)) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!(() => stride(str)   ) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+/++
+    Calculate the length of the UTF sequence ending one code unit before
+    $(D index) in $(D str).
+
+    Params:
+        str = bidirectional range of UTF code units. Must be random access if
+        $(D index) is passed
+        index = index one past end of UTF sequence (default: $(D str.length))
+
+    Returns:
+        The number of code units in the UTF sequence. For UTF-8, this is a
+        value between 1 and 4 (as per $(WEB tools.ietf.org/html/rfc3629#section-3, RFC 3629$(COMMA) section 3)).
+        For UTF-16, it is either 1 or 2. For UTF-32, it is always 1.
+
+    Throws:
+        May throw a $(D UTFException) if $(D str[index]) is not one past the
+        end of a valid UTF sequence.
+
+    Note:
+        $(D strideBack) will only analyze the element at $(D str[index - 1])
+        element. It will not fully verify the validity of the UTF sequence, nor
+        even verify the presence of the sequence: it will not actually
+        guarantee that $(D strideBack(str, index) <= index).
+  +/
+uint strideBack(S)(auto ref S str, size_t index)
+    if (is(S : const char[]) ||
+        (isRandomAccessRange!S && is(Unqual!(ElementType!S) == char)))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index <= str.length, "Past the end of the UTF-8 sequence");
+    assert(index > 0, "Not the end of the UTF-8 sequence");
+
+    if ((str[index-1] & 0b1100_0000) != 0b1000_0000)
+        return 1;
+
+    if (index >= 4) //single verification for most common case
+    {
+        foreach (i; AliasSeq!(2, 3, 4))
+        {
+            if ((str[index-i] & 0b1100_0000) != 0b1000_0000)
+                return i;
+        }
+    }
+    else
+    {
+        foreach (i; AliasSeq!(2, 3))
+        {
+            if (index >= i && (str[index-i] & 0b1100_0000) != 0b1000_0000)
+                return i;
+        }
+    }
+    throw new UTFException("Not the end of the UTF sequence", index);
+}
+
+/// Ditto
+uint strideBack(S)(auto ref S str)
+    if (is(S : const char[]) ||
+        (isRandomAccessRange!S && hasLength!S && is(Unqual!(ElementType!S) == char)))
+{
+    return strideBack(str, str.length);
+}
+
+/// Ditto
+uint strideBack(S)(auto ref S str)
+    if (isBidirectionalRange!S && is(Unqual!(ElementType!S) == char) && !isRandomAccessRange!S)
+{
+    assert(!str.empty, "Past the end of the UTF-8 sequence");
+    auto temp = str.save;
+    foreach (i; AliasSeq!(1, 2, 3, 4))
+    {
+        if ((temp.back & 0b1100_0000) != 0b1000_0000)
+            return i;
+        temp.popBack();
+        if (temp.empty)
+            break;
+    }
+    throw new UTFException("The last code unit is not the end of the UTF-8 sequence");
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(string s, dchar c, size_t i = size_t.max, size_t line = __LINE__)
+    {
+        enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!char(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(strideBack(RandomCU!char(s), i == size_t.max ? s.length : i) == codeLength!char(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!char(s);
+        immutable randLen = refRandom.length;
+        enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!char(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == size_t.max)
+        {
+            enforce(strideBack(s) == codeLength!char(c),
+                    new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line));
+
+            enforce(strideBack(BidirCU!char(s)) == codeLength!char(c),
+                    new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!char(s);
+            immutable bidirLen = refBidir.length;
+            enforce(strideBack(refBidir) == codeLength!char(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'f');
+    test("\U00010143\u0100\U00010143hello", 'o', 15);
+    test("\U00010143\u0100\U00010143hello", 'l', 14);
+    test("\U00010143\u0100\U00010143hello", 'l', 13);
+    test("\U00010143\u0100\U00010143hello", 'e', 12);
+    test("\U00010143\u0100\U00010143hello", 'h', 11);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 10);
+    test("\U00010143\u0100\U00010143hello", '\u0100', 6);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 4);
+
+    foreach (S; AliasSeq!(char[], const char[], string))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!({ strideBack(str, 0); }));
+        static assert(isSafe!({ strideBack(str);    }));
+        static assert((functionAttributes!({ strideBack(str, 0); }) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!({ strideBack(str);    }) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+//UTF-16 is self synchronizing: The length of strideBack can be found from
+//the value of a single wchar
+/// Ditto
+uint strideBack(S)(auto ref S str, size_t index)
+    if (is(S : const wchar[]) ||
+        (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar)))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index <= str.length, "Past the end of the UTF-16 sequence");
+    assert(index > 0, "Not the end of a UTF-16 sequence");
+
+    immutable c2 = str[index-1];
+    return 1 + (0xDC00 <= c2 && c2 < 0xE000);
+}
+
+/// Ditto
+uint strideBack(S)(auto ref S str)
+    if (is(S : const wchar[]) ||
+        (isBidirectionalRange!S && is(Unqual!(ElementType!S) == wchar)))
+{
+    assert(!str.empty, "UTF-16 sequence is empty");
+
+    static if (is(S : const(wchar)[]))
+        immutable c2 = str[$ - 1];
+    else
+        immutable c2 = str.back;
+
+    return 1 + (0xDC00 <= c2 && c2 <= 0xE000);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(wstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__)
+    {
+        enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(strideBack(RandomCU!wchar(s), i == size_t.max ? s.length : i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!wchar(s);
+        immutable randLen = refRandom.length;
+        enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!wchar(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == size_t.max)
+        {
+            enforce(strideBack(s) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line));
+
+            enforce(strideBack(BidirCU!wchar(s)) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!wchar(s);
+            immutable bidirLen = refBidir.length;
+            enforce(strideBack(refBidir) == codeLength!wchar(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'f');
+    test("\U00010143\u0100\U00010143hello", 'o', 10);
+    test("\U00010143\u0100\U00010143hello", 'l', 9);
+    test("\U00010143\u0100\U00010143hello", 'l', 8);
+    test("\U00010143\u0100\U00010143hello", 'e', 7);
+    test("\U00010143\u0100\U00010143hello", 'h', 6);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 5);
+    test("\U00010143\u0100\U00010143hello", '\u0100', 3);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 2);
+
+    foreach (S; AliasSeq!(wchar[], const wchar[], wstring))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!(() => strideBack(str, 0)));
+        static assert(isSafe!(() => strideBack(str)   ));
+        static assert((functionAttributes!(() => strideBack(str, 0)) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!(() => strideBack(str)   ) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+/// Ditto
+uint strideBack(S)(auto ref S str, size_t index)
+    if (isRandomAccessRange!S && is(Unqual!(ElementEncodingType!S) == dchar))
+{
+    static if (is(typeof(str.length) : ulong))
+        assert(index <= str.length, "Past the end of the UTF-32 sequence");
+    assert(index > 0, "Not the end of the UTF-32 sequence");
+    return 1;
+}
+
+/// Ditto
+uint strideBack(S)(auto ref S str)
+    if (isBidirectionalRange!S && is(Unqual!(ElementEncodingType!S) == dchar))
+{
+    assert(!str.empty, "Empty UTF-32 sequence");
+    return 1;
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    static void test(dstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__)
+    {
+        enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure string: %s", s), __FILE__, line));
+
+        enforce(strideBack(RandomCU!dchar(s), i == size_t.max ? s.length : i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure range: %s", s), __FILE__, line));
+
+        auto refRandom = new RefRandomCU!dchar(s);
+        immutable randLen = refRandom.length;
+        enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!dchar(c),
+                new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line));
+        enforce(refRandom.length == randLen,
+                new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line));
+
+        if (i == size_t.max)
+        {
+            enforce(strideBack(s) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line));
+
+            enforce(strideBack(BidirCU!dchar(s)) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line));
+
+            auto refBidir = new RefBidirCU!dchar(s);
+            immutable bidirLen = refBidir.length;
+            enforce(strideBack(refBidir) == codeLength!dchar(c),
+                    new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line));
+            enforce(refBidir.length == bidirLen,
+                    new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line));
+        }
+    }
+
+    assertCTFEable!(
+    {
+    test("a", 'a');
+    test(" ", ' ');
+    test("\u2029", '\u2029'); //paraSep
+    test("\u0100", '\u0100');
+    test("\u0430", '\u0430');
+    test("\U00010143", '\U00010143');
+    test("abcdefcdef", 'f');
+    test("\U00010143\u0100\U00010143hello", 'o', 8);
+    test("\U00010143\u0100\U00010143hello", 'l', 7);
+    test("\U00010143\u0100\U00010143hello", 'l', 6);
+    test("\U00010143\u0100\U00010143hello", 'e', 5);
+    test("\U00010143\u0100\U00010143hello", 'h', 4);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 3);
+    test("\U00010143\u0100\U00010143hello", '\u0100', 2);
+    test("\U00010143\u0100\U00010143hello", '\U00010143', 1);
+
+    foreach (S; AliasSeq!(dchar[], const dchar[], dstring))
+    {
+        enum str = to!S("hello world");
+        static assert(isSafe!(() => strideBack(str, 0)));
+        static assert(isSafe!(() => strideBack(str)   ));
+        static assert((functionAttributes!(() => strideBack(str, 0)) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!(() => strideBack(str)   ) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+
+/++
+    Given $(D index) into $(D str) and assuming that $(D index) is at the start
+    of a UTF sequence, $(D toUCSindex) determines the number of UCS characters
+    up to $(D index). So, $(D index) is the index of a code unit at the
+    beginning of a code point, and the return value is how many code points into
+    the string that that code point is.
+  +/
+size_t toUCSindex(C)(const(C)[] str, size_t index) @safe pure
+    if (isSomeChar!C)
+{
+    static if (is(Unqual!C == dchar))
+        return index;
+    else
+    {
+        size_t n = 0;
+        size_t j = 0;
+
+        for (; j < index; ++n)
+            j += stride(str, j);
+
+        if (j > index)
+        {
+            static if (is(Unqual!C == char))
+                throw new UTFException("Invalid UTF-8 sequence", index);
+            else
+                throw new UTFException("Invalid UTF-16 sequence", index);
+        }
+
+        return n;
+    }
+}
+
+///
+unittest
+{
+    assert(toUCSindex(`hello world`, 7) == 7);
+    assert(toUCSindex(`hello world`w, 7) == 7);
+    assert(toUCSindex(`hello world`d, 7) == 7);
+
+    assert(toUCSindex(`Ma Chérie`, 7) == 6);
+    assert(toUCSindex(`Ma Chérie`w, 7) == 7);
+    assert(toUCSindex(`Ma Chérie`d, 7) == 7);
+
+    assert(toUCSindex(`さいごの果実 / ミツバチと科学者`, 9) == 3);
+    assert(toUCSindex(`さいごの果実 / ミツバチと科学者`w, 9) == 9);
+    assert(toUCSindex(`さいごの果実 / ミツバチと科学者`d, 9) == 9);
+}
+
+
+/++
+    Given a UCS index $(D n) into $(D str), returns the UTF index.
+    So, $(D n) is how many code points into the string the code point is, and
+    the array index of the code unit is returned.
+  +/
+size_t toUTFindex(C)(const(C)[] str, size_t n) @safe pure
+    if (isSomeChar!C)
+{
+    static if (is(Unqual!C == dchar))
+    {
+        return n;
+    }
+    else
+    {
+        size_t i;
+        while (n--)
+        {
+            i += stride(str, i);
+        }
+        return i;
+    }
+}
+
+///
+unittest
+{
+    assert(toUTFindex(`hello world`, 7) == 7);
+    assert(toUTFindex(`hello world`w, 7) == 7);
+    assert(toUTFindex(`hello world`d, 7) == 7);
+
+    assert(toUTFindex(`Ma Chérie`, 6) == 7);
+    assert(toUTFindex(`Ma Chérie`w, 7) == 7);
+    assert(toUTFindex(`Ma Chérie`d, 7) == 7);
+
+    assert(toUTFindex(`さいごの果実 / ミツバチと科学者`, 3) == 9);
+    assert(toUTFindex(`さいごの果実 / ミツバチと科学者`w, 9) == 9);
+    assert(toUTFindex(`さいごの果実 / ミツバチと科学者`d, 9) == 9);
+}
+
+
+/* =================== Decode ======================= */
+
+/// Whether or not to replace invalid UTF with $(LREF replacementDchar)
+alias UseReplacementDchar = Flag!"useReplacementDchar";
+
+/++
+    Decodes and returns the code point starting at $(D str[index]). $(D index)
+    is advanced to one past the decoded code point. If the code point is not
+    well-formed, then a $(D UTFException) is thrown and $(D index) remains
+    unchanged.
+
+    decode will only work with strings and random access ranges of code units
+    with length and slicing, whereas $(LREF decodeFront) will work with any
+    input range of code units.
+
+    Params:
+        useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing
+        str = input string or indexable Range
+        index = starting index into s[]; incremented by number of code units processed
+
+    Returns:
+        decoded character
+
+    Throws:
+        $(LREF UTFException) if $(D str[index]) is not the start of a valid UTF
+        sequence and useReplacementDchar is UseReplacementDchar.no
+  +/
+dchar decode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(auto ref S str, ref size_t index)
+    if (!isSomeString!S &&
+        isRandomAccessRange!S && hasSlicing!S && hasLength!S && isSomeChar!(ElementType!S))
+in
+{
+    assert(index < str.length, "Attempted to decode past the end of a string");
+}
+out (result)
+{
+    assert(isValidDchar(result));
+}
+body
+{
+    if (str[index] < codeUnitLimit!S)
+        return str[index++];
+    else
+        return decodeImpl!(true, useReplacementDchar)(str, index);
+}
+
+dchar decode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(auto ref S str, ref size_t index) @trusted pure
+    if (isSomeString!S)
+in
+{
+    assert(index < str.length, "Attempted to decode past the end of a string");
+}
+out (result)
+{
+    assert(isValidDchar(result));
+}
+body
+{
+    if (str[index] < codeUnitLimit!S)
+        return str[index++];
+    else
+        return decodeImpl!(true, useReplacementDchar)(str, index);
+}
+
+/++
+    $(D decodeFront) is a variant of $(LREF decode) which specifically decodes
+    the first code point. Unlike $(LREF decode), $(D decodeFront) accepts any
+    input range of code units (rather than just a string or random access
+    range). It also takes the range by $(D ref) and pops off the elements as it
+    decodes them. If $(D numCodeUnits) is passed in, it gets set to the number
+    of code units which were in the code point which was decoded.
+
+    Params:
+        useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing
+        str = input string or indexable Range
+        numCodeUnits = set to number of code units processed
+
+    Returns:
+        decoded character
+
+    Throws:
+        $(LREF UTFException) if $(D str.front) is not the start of a valid UTF
+        sequence. If an exception is thrown, then there is no guarantee as to
+        the number of code units which were popped off, as it depends on the
+        type of range being used and how many code units had to be popped off
+        before the code point was determined to be invalid.
+  +/
+dchar decodeFront(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(ref S str, out size_t numCodeUnits)
+    if (!isSomeString!S && isInputRange!S && isSomeChar!(ElementType!S))
+in
+{
+    assert(!str.empty);
+}
+out (result)
+{
+    assert(isValidDchar(result));
+}
+body
+{
+    immutable fst = str.front;
+
+    if (fst < codeUnitLimit!S)
+    {
+        str.popFront();
+        numCodeUnits = 1;
+        return fst;
+    }
+    else
+    {
+        //@@@BUG@@@ 14447 forces canIndex to be done outside of decodeImpl, which
+        //is undesirable, since not all overloads of decodeImpl need it. So, it
+        //should be moved back into decodeImpl once bug# 8521 has been fixed.
+        enum canIndex = isRandomAccessRange!S && hasSlicing!S && hasLength!S;
+        immutable retval = decodeImpl!(canIndex, useReplacementDchar)(str, numCodeUnits);
+
+        // The other range types were already popped by decodeImpl.
+        static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
+            str = str[numCodeUnits .. str.length];
+
+        return retval;
+    }
+}
+
+dchar decodeFront(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(ref S str, out size_t numCodeUnits) @trusted pure
+    if (isSomeString!S)
+in
+{
+    assert(!str.empty);
+}
+out (result)
+{
+    assert(isValidDchar(result));
+}
+body
+{
+    if (str[0] < codeUnitLimit!S)
+    {
+        numCodeUnits = 1;
+        immutable retval = str[0];
+        str = str[1 .. $];
+        return retval;
+    }
+    else
+    {
+        immutable retval = decodeImpl!(true, useReplacementDchar)(str, numCodeUnits);
+        str = str[numCodeUnits .. $];
+        return retval;
+    }
+}
+
+/++ Ditto +/
+dchar decodeFront(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(ref S str)
+    if (isInputRange!S && isSomeChar!(ElementType!S))
+{
+    size_t numCodeUnits;
+    return decodeFront!useReplacementDchar(str, numCodeUnits);
+}
+
+// Gives the maximum value that a code unit for the given range type can hold.
+package template codeUnitLimit(S)
+    if (isSomeChar!(ElementEncodingType!S))
+{
+    static if (is(Unqual!(ElementEncodingType!S) == char))
+        enum char codeUnitLimit = 0x80;
+    else static if (is(Unqual!(ElementEncodingType!S) == wchar))
+        enum wchar codeUnitLimit = 0xD800;
+    else
+        enum dchar codeUnitLimit = 0xD800;
+}
+
+/*
+ * For strings, this function does its own bounds checking to give a
+ * more useful error message when attempting to decode past the end of a string.
+ * Subsequently it uses a pointer instead of an array to avoid
+ * redundant bounds checking.
+ *
+ * The three overloads of this operate on chars, wchars, and dchars.
+ *
+ * Params:
+ *      canIndex = if S is indexable
+ *      useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing
+ *      str = input string or Range
+ *      index = starting index into s[]; incremented by number of code units processed
+ *
+ * Returns:
+ *      decoded character
+ */
+private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(auto ref S str, ref size_t index)
+    if (is(S : const char[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == char)))
+{
+    /* The following encodings are valid, except for the 5 and 6 byte
+     * combinations:
+     *  0xxxxxxx
+     *  110xxxxx 10xxxxxx
+     *  1110xxxx 10xxxxxx 10xxxxxx
+     *  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     *  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     *  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     */
+
+    /* Dchar bitmask for different numbers of UTF-8 code units.
+     */
+    alias bitMask = AliasSeq!((1 << 7) - 1, (1 << 11) - 1, (1 << 16) - 1, (1 << 21) - 1);
+
+    static if (is(S : const char[]))
+        auto pstr = str.ptr + index;
+    else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
+        auto pstr = str[index .. str.length];
+    else
+        alias pstr = str;
+
+    //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl
+    //enum canIndex = is(S : const char[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S);
+
+    static if (canIndex)
+    {
+        immutable length = str.length - index;
+        ubyte fst = pstr[0];
+    }
+    else
+    {
+        ubyte fst = pstr.front;
+        pstr.popFront();
+    }
+
+    static if (!useReplacementDchar)
+    {
+        static if (canIndex)
+        {
+            static UTFException exception(S)(S str, string msg)
+            {
+                uint[4] sequence = void;
+                size_t i;
+
+                do
+                {
+                    sequence[i] = str[i];
+                } while (++i < str.length && i < 4 && (str[i] & 0xC0) == 0x80);
+
+                return new UTFException(msg, i).setSequence(sequence[0 .. i]);
+            }
+        }
+
+        UTFException invalidUTF()
+        {
+            static if (canIndex)
+               return exception(pstr[0 .. length], "Invalid UTF-8 sequence");
+            else
+            {
+                //We can't include the invalid sequence with input strings without
+                //saving each of the code units along the way, and we can't do it with
+                //forward ranges without saving the entire range. Both would incur a
+                //cost for the decoding of every character just to provide a better
+                //error message for the (hopefully) rare case when an invalid UTF-8
+                //sequence is encountered, so we don't bother trying to include the
+                //invalid sequence here, unlike with strings and sliceable ranges.
+               return new UTFException("Invalid UTF-8 sequence");
+            }
+        }
+
+        UTFException outOfBounds()
+        {
+            static if (canIndex)
+               return exception(pstr[0 .. length], "Attempted to decode past the end of a string");
+            else
+               return new UTFException("Attempted to decode past the end of a string");
+        }
+    }
+
+    if ((fst & 0b1100_0000) != 0b1100_0000)
+    {
+        static if (useReplacementDchar)
+        {
+            ++index;            // always consume bad input to avoid infinite loops
+            return replacementDchar;
+        }
+        else
+            throw invalidUTF(); // starter must have at least 2 first bits set
+    }
+    ubyte tmp = void;
+    dchar d = fst; // upper control bits are masked out later
+    fst <<= 1;
+
+    foreach (i; AliasSeq!(1, 2, 3))
+    {
+
+        static if (canIndex)
+        {
+            if (i == length)
+            {
+                static if (useReplacementDchar)
+                {
+                    index += i;
+                    return replacementDchar;
+                }
+                else
+                    throw outOfBounds();
+            }
+        }
+        else
+        {
+            if (pstr.empty)
+            {
+                static if (useReplacementDchar)
+                {
+                    index += i;
+                    return replacementDchar;
+                }
+                else
+                    throw outOfBounds();
+            }
+        }
+
+        static if (canIndex)
+            tmp = pstr[i];
+        else
+        {
+            tmp = pstr.front;
+            pstr.popFront();
+        }
+
+        if ((tmp & 0xC0) != 0x80)
+        {
+            static if (useReplacementDchar)
+            {
+                index += i + 1;
+                return replacementDchar;
+            }
+            else
+                throw invalidUTF();
+        }
+
+        d = (d << 6) | (tmp & 0x3F);
+        fst <<= 1;
+
+        if (!(fst & 0x80)) // no more bytes
+        {
+            d &= bitMask[i]; // mask out control bits
+
+            // overlong, could have been encoded with i bytes
+            if ((d & ~bitMask[i - 1]) == 0)
+            {
+                static if (useReplacementDchar)
+                {
+                    index += i + 1;
+                    return replacementDchar;
+                }
+                else
+                    throw invalidUTF();
+            }
+
+            // check for surrogates only needed for 3 bytes
+            static if (i == 2)
+            {
+                if (!isValidDchar(d))
+                {
+                    static if (useReplacementDchar)
+                    {
+                        index += i + 1;
+                        return replacementDchar;
+                    }
+                    else
+                        throw invalidUTF();
+                }
+            }
+
+            index += i + 1;
+            static if (i == 3)
+            {
+                if (d > dchar.max)
+                {
+                    static if (useReplacementDchar)
+                        d = replacementDchar;
+                    else
+                        throw invalidUTF();
+                }
+            }
+            return d;
+        }
+    }
+
+    static if (useReplacementDchar)
+    {
+        index += 4;             // read 4 chars by now
+        return replacementDchar;
+    }
+    else
+        throw invalidUTF();
+}
+
+@safe pure @nogc nothrow
+unittest
+{
+    // Add tests for useReplacemendDchar==yes path
+
+    static struct R
+    {
+      @safe pure @nogc nothrow:
+        this(string s) { this.s = s; }
+        @property bool empty() { return idx == s.length; }
+        @property char front() { return s[idx]; }
+        void popFront() { ++idx; }
+        size_t idx;
+        string s;
+    }
+
+    foreach (s; invalidUTFstrings!char())
+    {
+        auto r = R(s);
+        size_t index;
+        dchar dc = decodeImpl!(false, Flag!"useReplacementDchar".yes)(r, index);
+        assert(dc == replacementDchar);
+        assert(1 <= index && index <= s.length);
+    }
+}
+
+private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(auto ref S str, ref size_t index)
+    if (is(S : const wchar[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == wchar)))
+{
+    static if (is(S : const wchar[]))
+        auto pstr = str.ptr + index;
+    else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
+        auto pstr = str[index .. str.length];
+    else
+        alias pstr = str;
+
+    //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl
+    //enum canIndex = is(S : const wchar[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S);
+
+    static if (canIndex)
+    {
+        immutable length = str.length - index;
+        uint u = pstr[0];
+    }
+    else
+    {
+        uint u = pstr.front;
+        pstr.popFront();
+    }
+
+    static if (!useReplacementDchar)
+    {
+        UTFException exception(string msg)
+        {
+            static if (canIndex)
+                return new UTFException(msg).setSequence(pstr[0]);
+            else
+                return new UTFException(msg);
+        }
+    }
+
+    string msg;
+
+    // The < case must be taken care of before decodeImpl is called.
+    assert(u >= 0xD800);
+
+    if (u <= 0xDBFF)
+    {
+        static if (canIndex)
+            immutable onlyOneCodeUnit = length == 1;
+        else
+            immutable onlyOneCodeUnit = pstr.empty;
+
+        if (onlyOneCodeUnit)
+        {
+            static if (useReplacementDchar)
+            {
+                ++index;
+                return replacementDchar;
+            }
+            else
+                throw exception("surrogate UTF-16 high value past end of string");
+        }
+
+        static if (canIndex)
+            immutable uint u2 = pstr[1];
+        else
+        {
+            immutable uint u2 = pstr.front;
+            pstr.popFront();
+        }
+
+        if (u2 < 0xDC00 || u2 > 0xDFFF)
+        {
+            static if (useReplacementDchar)
+                u = replacementDchar;
+            else
+                throw exception("surrogate UTF-16 low value out of range");
+        }
+        else
+            u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00);
+        ++index;
+    }
+    else if (u >= 0xDC00 && u <= 0xDFFF)
+    {
+        static if (useReplacementDchar)
+            u = replacementDchar;
+        else
+            throw exception("unpaired surrogate UTF-16 value");
+    }
+    ++index;
+
+    // Note: u+FFFE and u+FFFF are specifically permitted by the
+    // Unicode standard for application internal use (see isValidDchar)
+
+    return cast(dchar)u;
+}
+
+pure @nogc nothrow
+unittest
+{
+    // Add tests for useReplacemendDchar==true path
+
+    static struct R
+    {
+      @safe pure @nogc nothrow:
+        this(wstring s) { this.s = s; }
+        @property bool empty() { return idx == s.length; }
+        @property wchar front() { return s[idx]; }
+        void popFront() { ++idx; }
+        size_t idx;
+        wstring s;
+    }
+
+    foreach (s; invalidUTFstrings!wchar())
+    {
+        auto r = R(s);
+        size_t index;
+        dchar dc = decodeImpl!(false, Flag!"useReplacementDchar".yes)(r, index);
+        assert(dc == replacementDchar);
+        assert(1 <= index && index <= s.length);
+    }
+}
+
+private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = UseReplacementDchar.no, S)(auto ref S str, ref size_t index)
+    if (is(S : const dchar[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == dchar)))
+{
+    static if (is(S : const dchar[]))
+        auto pstr = str.ptr;
+    else
+        alias pstr = str;
+
+    static if (is(S : const dchar[]) || isRandomAccessRange!S)
+    {
+        dchar dc = pstr[index];
+        if (!isValidDchar(dc))
+        {
+            static if (useReplacementDchar)
+                dc = replacementDchar;
+            else
+                throw new UTFException("Invalid UTF-32 value").setSequence(dc);
+        }
+        ++index;
+        return dc;
+    }
+    else
+    {
+        dchar dc = pstr.front;
+        if (!isValidDchar(dc))
+        {
+            static if (useReplacementDchar)
+                dc = replacementDchar;
+            else
+                throw new UTFException("Invalid UTF-32 value").setSequence(dc);
+        }
+        ++index;
+        pstr.popFront();
+        return dc;
+    }
+}
+
+pure @nogc nothrow
+unittest
+{
+    // Add tests for useReplacemendDchar==true path
+
+    static struct R
+    {
+      @safe pure @nogc nothrow:
+        this(dstring s) { this.s = s; }
+        @property bool empty() { return idx == s.length; }
+        @property dchar front() { return s[idx]; }
+        void popFront() { ++idx; }
+        size_t idx;
+        dstring s;
+    }
+
+    foreach (s; invalidUTFstrings!dchar())
+    {
+        auto r = R(s);
+        size_t index;
+        dchar dc = decodeImpl!(false, Flag!"useReplacementDchar".yes)(r, index);
+        assert(dc == replacementDchar);
+        assert(1 <= index && index <= s.length);
+    }
+}
+
+
+version(unittest) private void testDecode(R)(R range,
+                                             size_t index,
+                                             dchar expectedChar,
+                                             size_t expectedIndex,
+                                             size_t line = __LINE__)
+{
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+
+    static if (hasLength!R)
+        immutable lenBefore = range.length;
+
+    static if (isRandomAccessRange!R)
+    {
+        {
+            immutable result = decode(range, index);
+            enforce(result == expectedChar,
+                    new AssertError(format("decode: Wrong character: %s", result), __FILE__, line));
+            enforce(index == expectedIndex,
+                    new AssertError(format("decode: Wrong index: %s", index), __FILE__, line));
+            static if (hasLength!R)
+            {
+                enforce(range.length == lenBefore,
+                        new AssertError(format("decode: length changed: %s", range.length), __FILE__, line));
+            }
+        }
+    }
+}
+
+version(unittest) private void testDecodeFront(R)(ref R range,
+                                                  dchar expectedChar,
+                                                  size_t expectedNumCodeUnits,
+                                                  size_t line = __LINE__)
+{
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+
+    static if (hasLength!R)
+        immutable lenBefore = range.length;
+
+    size_t numCodeUnits;
+    immutable result = decodeFront(range, numCodeUnits);
+    enforce(result == expectedChar,
+            new AssertError(format("decodeFront: Wrong character: %s", result), __FILE__, line));
+    enforce(numCodeUnits == expectedNumCodeUnits,
+            new AssertError(format("decodeFront: Wrong numCodeUnits: %s", numCodeUnits), __FILE__, line));
+
+    static if (hasLength!R)
+    {
+        enforce(range.length == lenBefore - numCodeUnits,
+                new AssertError(format("decodeFront: wrong length: %s", range.length), __FILE__, line));
+    }
+}
+
+version(unittest) private void testBothDecode(R)(R range,
+                                                 dchar expectedChar,
+                                                 size_t expectedIndex,
+                                                 size_t line = __LINE__)
+{
+    testDecode(range, 0, expectedChar, expectedIndex, line);
+    testDecodeFront(range, expectedChar, expectedIndex, line);
+}
+
+version(unittest) private void testBadDecode(R)(R range, size_t index, size_t line = __LINE__)
+{
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+
+    immutable initialIndex = index;
+
+    static if (hasLength!R)
+        immutable lenBefore = range.length;
+
+    static if (isRandomAccessRange!R)
+    {
+        assertThrown!UTFException(decode(range, index), null, __FILE__, line);
+        enforce(index == initialIndex,
+                new AssertError(format("decode: Wrong index: %s", index), __FILE__, line));
+        static if (hasLength!R)
+        {
+            enforce(range.length == lenBefore,
+                    new AssertError(format("decode: length changed:", range.length), __FILE__, line));
+        }
+    }
+
+    if (initialIndex == 0)
+        assertThrown!UTFException(decodeFront(range, index), null, __FILE__, line);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    debug(utf) printf("utf.decode.unittest\n");
+
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(to!string, InputCU!char, RandomCU!char,
+                          (string s) => new RefBidirCU!char(s),
+                          (string s) => new RefRandomCU!char(s)))
+    {
+        enum sHasLength = hasLength!(typeof(S("abcd")));
+
+        {
+            auto range = S("abcd");
+            testDecode(range, 0, 'a', 1);
+            testDecode(range, 1, 'b', 2);
+            testDecodeFront(range, 'a', 1);
+            testDecodeFront(range, 'b', 1);
+            assert(decodeFront(range) == 'c');
+            assert(decodeFront(range) == 'd');
+        }
+
+        {
+            auto range = S("ウェブサイト");
+            testDecode(range, 0, 'ウ', 3);
+            testDecode(range, 3, 'ェ', 6);
+            testDecodeFront(range, 'ウ', 3);
+            testDecodeFront(range, 'ェ', 3);
+            assert(decodeFront(range) == 'ブ');
+            assert(decodeFront(range) == 'サ');
+        }
+
+        testBothDecode(S("\xC2\xA9"), '\u00A9', 2);
+        testBothDecode(S("\xE2\x89\xA0"), '\u2260', 3);
+
+        foreach (str; ["\xE2\x89", // too short
+                       "\xC0\x8A",
+                       "\xE0\x80\x8A",
+                       "\xF0\x80\x80\x8A",
+                       "\xF8\x80\x80\x80\x8A",
+                       "\xFC\x80\x80\x80\x80\x8A"])
+        {
+            testBadDecode(S(str), 0);
+            testBadDecode(S(str), 1);
+        }
+
+        //Invalid UTF-8 sequence where the first code unit is valid.
+        testBothDecode(S("\xEF\xBF\xBE"), cast(dchar)0xFFFE, 3);
+        testBothDecode(S("\xEF\xBF\xBF"), cast(dchar)0xFFFF, 3);
+
+        //Invalid UTF-8 sequence where the first code unit isn't valid.
+        testBadDecode(S("\xED\xA0\x80"), 0);
+        testBadDecode(S("\xED\xAD\xBF"), 0);
+        testBadDecode(S("\xED\xAE\x80"), 0);
+        testBadDecode(S("\xED\xAF\xBF"), 0);
+        testBadDecode(S("\xED\xB0\x80"), 0);
+        testBadDecode(S("\xED\xBE\x80"), 0);
+        testBadDecode(S("\xED\xBF\xBF"), 0);
+    }
+    });
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(to!wstring, InputCU!wchar, RandomCU!wchar,
+                          (wstring s) => new RefBidirCU!wchar(s),
+                          (wstring s) => new RefRandomCU!wchar(s)))
+    {
+        testBothDecode(S([cast(wchar)0x1111]), cast(dchar)0x1111, 1);
+        testBothDecode(S([cast(wchar)0xD800, cast(wchar)0xDC00]), cast(dchar)0x10000, 2);
+        testBothDecode(S([cast(wchar)0xDBFF, cast(wchar)0xDFFF]), cast(dchar)0x10FFFF, 2);
+        testBothDecode(S([cast(wchar)0xFFFE]), cast(dchar)0xFFFE, 1);
+        testBothDecode(S([cast(wchar)0xFFFF]), cast(dchar)0xFFFF, 1);
+
+        testBadDecode(S([ cast(wchar)0xD801 ]), 0);
+        testBadDecode(S([ cast(wchar)0xD800, cast(wchar)0x1200 ]), 0);
+
+        {
+            auto range = S("ウェブサイト");
+            testDecode(range, 0, 'ウ', 1);
+            testDecode(range, 1, 'ェ', 2);
+            testDecodeFront(range, 'ウ', 1);
+            testDecodeFront(range, 'ェ', 1);
+            assert(decodeFront(range) == 'ブ');
+            assert(decodeFront(range) == 'サ');
+        }
+    }
+
+    foreach (S; AliasSeq!(to!wstring, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s)))
+    {
+        auto str = S([cast(wchar)0xD800, cast(wchar)0xDC00,
+                      cast(wchar)0x1400,
+                      cast(wchar)0xDAA7, cast(wchar)0xDDDE]);
+        testDecode(str, 0, cast(dchar)0x10000, 2);
+        testDecode(str, 2, cast(dchar)0x1400, 3);
+        testDecode(str, 3, cast(dchar)0xB9DDE, 5);
+    }
+    });
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, InputCU!dchar,
+                          (dstring s) => new RefBidirCU!dchar(s),
+                          (dstring s) => new RefRandomCU!dchar(s)))
+    {
+        testBothDecode(S([cast(dchar)0x1111]), cast(dchar)0x1111, 1);
+        testBothDecode(S([cast(dchar)0x10000]), cast(dchar)0x10000, 1);
+        testBothDecode(S([cast(dchar)0x10FFFF]), cast(dchar)0x10FFFF, 1);
+        testBothDecode(S([cast(dchar)0xFFFE]), cast(dchar)0xFFFE, 1);
+        testBothDecode(S([cast(dchar)0xFFFF]), cast(dchar)0xFFFF, 1);
+
+        testBadDecode(S([cast(dchar)0xD800]), 0);
+        testBadDecode(S([cast(dchar)0xDFFE]), 0);
+        testBadDecode(S([cast(dchar)0x110000]), 0);
+
+        {
+            auto range = S("ウェブサイト");
+            testDecode(range, 0, 'ウ', 1);
+            testDecode(range, 1, 'ェ', 2);
+            testDecodeFront(range, 'ウ', 1);
+            testDecodeFront(range, 'ェ', 1);
+            assert(decodeFront(range) == 'ブ');
+            assert(decodeFront(range) == 'サ');
+        }
+    }
+
+    foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, (dstring s) => new RefRandomCU!dchar(s)))
+    {
+        auto str = S([cast(dchar)0x10000, cast(dchar)0x1400, cast(dchar)0xB9DDE]);
+        testDecode(str, 0, 0x10000, 1);
+        testDecode(str, 1, 0x1400, 2);
+        testDecode(str, 2, 0xB9DDE, 3);
+    }
+    });
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!( char[], const( char)[],  string,
+                          wchar[], const(wchar)[], wstring,
+                          dchar[], const(dchar)[], dstring))
+    {
+        static assert(isSafe!({ S str; size_t i = 0; decode(str, i);      }));
+        static assert(isSafe!({ S str; size_t i = 0; decodeFront(str, i); }));
+        static assert(isSafe!({ S str; decodeFront(str); }));
+        static assert((functionAttributes!({ S str; size_t i = 0; decode(str, i);      }) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!({ S str; size_t i = 0; decodeFront(str, i); }) & FunctionAttribute.pure_) != 0);
+        static assert((functionAttributes!({ S str; decodeFront(str); }) & FunctionAttribute.pure_) != 0);
+    }
+    });
+}
+
+unittest
+{
+    import std.exception;
+    char[4] val;
+    val[0] = 0b1111_0111;
+    val[1] = 0b1011_1111;
+    val[2] = 0b1011_1111;
+    val[3] = 0b1011_1111;
+    size_t i = 0;
+    assertThrown!UTFException((){ dchar ch = decode(val[], i); }());
+}
+/* =================== Encode ======================= */
+
+private dchar _utfException(UseReplacementDchar useReplacementDchar)(string msg, dchar c)
+{
+    static if (useReplacementDchar)
+        return replacementDchar;
+    else
+        throw new UTFException(msg).setSequence(c);
+}
+
+/++
+    Encodes $(D c) into the static array, $(D buf), and returns the actual
+    length of the encoded character (a number between $(D 1) and $(D 4) for
+    $(D char[4]) buffers and a number between $(D 1) and $(D 2) for
+    $(D wchar[2]) buffers).
+
+    Throws:
+        $(D UTFException) if $(D c) is not a valid UTF code point.
+  +/
+size_t encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref char[4] buf, dchar c) @safe pure
+{
+    if (c <= 0x7F)
+    {
+        assert(isValidDchar(c));
+        buf[0] = cast(char)c;
+        return 1;
+    }
+    if (c <= 0x7FF)
+    {
+        assert(isValidDchar(c));
+        buf[0] = cast(char)(0xC0 | (c >> 6));
+        buf[1] = cast(char)(0x80 | (c & 0x3F));
+        return 2;
+    }
+    if (c <= 0xFFFF)
+    {
+        if (0xD800 <= c && c <= 0xDFFF)
+            c = _utfException!useReplacementDchar("Encoding a surrogate code point in UTF-8", c);
+
+        assert(isValidDchar(c));
+    L3:
+        buf[0] = cast(char)(0xE0 | (c >> 12));
+        buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[2] = cast(char)(0x80 | (c & 0x3F));
+        return 3;
+    }
+    if (c <= 0x10FFFF)
+    {
+        assert(isValidDchar(c));
+        buf[0] = cast(char)(0xF0 | (c >> 18));
+        buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
+        buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[3] = cast(char)(0x80 | (c & 0x3F));
+        return 4;
+    }
+
+    assert(!isValidDchar(c));
+    c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-8", c);
+    goto L3;
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    char[4] buf;
+
+    assert(encode(buf, '\u0000') == 1 && buf[0 .. 1] == "\u0000");
+    assert(encode(buf, '\u007F') == 1 && buf[0 .. 1] == "\u007F");
+    assert(encode(buf, '\u0080') == 2 && buf[0 .. 2] == "\u0080");
+    assert(encode(buf, '\u07FF') == 2 && buf[0 .. 2] == "\u07FF");
+    assert(encode(buf, '\u0800') == 3 && buf[0 .. 3] == "\u0800");
+    assert(encode(buf, '\uD7FF') == 3 && buf[0 .. 3] == "\uD7FF");
+    assert(encode(buf, '\uE000') == 3 && buf[0 .. 3] == "\uE000");
+    assert(encode(buf, 0xFFFE) == 3 && buf[0 .. 3] == "\xEF\xBF\xBE");
+    assert(encode(buf, 0xFFFF) == 3 && buf[0 .. 3] == "\xEF\xBF\xBF");
+    assert(encode(buf, '\U00010000') == 4 && buf[0 .. 4] == "\U00010000");
+    assert(encode(buf, '\U0010FFFF') == 4 && buf[0 .. 4] == "\U0010FFFF");
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000) == buf.stride);
+    assert(buf.front == replacementDchar);
+    });
+}
+
+
+/// Ditto
+size_t encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref wchar[2] buf, dchar c) @safe pure
+{
+    if (c <= 0xFFFF)
+    {
+        if (0xD800 <= c && c <= 0xDFFF)
+            c = _utfException!useReplacementDchar("Encoding an isolated surrogate code point in UTF-16", c);
+
+        assert(isValidDchar(c));
+    L1:
+        buf[0] = cast(wchar)c;
+        return 1;
+    }
+    if (c <= 0x10FFFF)
+    {
+        assert(isValidDchar(c));
+        buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
+        buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
+        return 2;
+    }
+
+    c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-16", c);
+    goto L1;
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    wchar[2] buf;
+
+    assert(encode(buf, '\u0000') == 1 && buf[0 .. 1] == "\u0000");
+    assert(encode(buf, '\uD7FF') == 1 && buf[0 .. 1] == "\uD7FF");
+    assert(encode(buf, '\uE000') == 1 && buf[0 .. 1] == "\uE000");
+    assert(encode(buf, 0xFFFE) == 1 && buf[0] == 0xFFFE);
+    assert(encode(buf, 0xFFFF) == 1 && buf[0] == 0xFFFF);
+    assert(encode(buf, '\U00010000') == 2 && buf[0 .. 2] == "\U00010000");
+    assert(encode(buf, '\U0010FFFF') == 2 && buf[0 .. 2] == "\U0010FFFF");
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000) == buf.stride);
+    assert(buf.front == replacementDchar);
+    });
+}
+
+
+/// Ditto
+size_t encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref dchar[1] buf, dchar c) @safe pure
+{
+    if ((0xD800 <= c && c <= 0xDFFF) || 0x10FFFF < c)
+        c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-32", c);
+    else
+        assert(isValidDchar(c));
+    buf[0] = c;
+    return 1;
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    dchar[1] buf;
+
+    encode(buf, '\u0000'); assert(buf[0] == '\u0000');
+    encode(buf, '\uD7FF'); assert(buf[0] == '\uD7FF');
+    encode(buf, '\uE000'); assert(buf[0] == '\uE000');
+    encode(buf, 0xFFFE ); assert(buf[0] == 0xFFFE);
+    encode(buf, 0xFFFF ); assert(buf[0] == 0xFFFF);
+    encode(buf, '\U0010FFFF'); assert(buf[0] == '\U0010FFFF');
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000) == buf.stride);
+    assert(buf.front == replacementDchar);
+    });
+}
+
+
+/++
+    Encodes $(D c) in $(D str)'s encoding and appends it to $(D str).
+
+    Throws:
+        $(D UTFException) if $(D c) is not a valid UTF code point.
+  +/
+void encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref char[] str, dchar c) @safe pure
+{
+    char[] r = str;
+
+    if (c <= 0x7F)
+    {
+        assert(isValidDchar(c));
+        r ~= cast(char)c;
+    }
+    else
+    {
+        char[4] buf;
+        uint L;
+
+        if (c <= 0x7FF)
+        {
+            assert(isValidDchar(c));
+            buf[0] = cast(char)(0xC0 | (c >> 6));
+            buf[1] = cast(char)(0x80 | (c & 0x3F));
+            L = 2;
+        }
+        else if (c <= 0xFFFF)
+        {
+            if (0xD800 <= c && c <= 0xDFFF)
+                c = _utfException!useReplacementDchar("Encoding a surrogate code point in UTF-8", c);
+
+            assert(isValidDchar(c));
+        L3:
+            buf[0] = cast(char)(0xE0 | (c >> 12));
+            buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+            buf[2] = cast(char)(0x80 | (c & 0x3F));
+            L = 3;
+        }
+        else if (c <= 0x10FFFF)
+        {
+            assert(isValidDchar(c));
+            buf[0] = cast(char)(0xF0 | (c >> 18));
+            buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
+            buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+            buf[3] = cast(char)(0x80 | (c & 0x3F));
+            L = 4;
+        }
+        else
+        {
+            assert(!isValidDchar(c));
+            c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-8", c);
+            goto L3;
+        }
+        r ~= buf[0 .. L];
+    }
+    str = r;
+}
+
+unittest
+{
+    import std.exception;
+    debug(utf) printf("utf.encode.unittest\n");
+
+    assertCTFEable!(
+    {
+    char[] s = "abcd".dup;
+    encode(s, cast(dchar)'a');
+    assert(s.length == 5);
+    assert(s == "abcda");
+
+    encode(s, cast(dchar)'\u00A9');
+    assert(s.length == 7);
+    assert(s == "abcda\xC2\xA9");
+    //assert(s == "abcda\u00A9");   // BUG: fix compiler
+
+    encode(s, cast(dchar)'\u2260');
+    assert(s.length == 10);
+    assert(s == "abcda\xC2\xA9\xE2\x89\xA0");
+    });
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    char[] buf;
+
+    encode(buf, '\u0000'); assert(buf[0 .. $] == "\u0000");
+    encode(buf, '\u007F'); assert(buf[1 .. $] == "\u007F");
+    encode(buf, '\u0080'); assert(buf[2 .. $] == "\u0080");
+    encode(buf, '\u07FF'); assert(buf[4 .. $] == "\u07FF");
+    encode(buf, '\u0800'); assert(buf[6 .. $] == "\u0800");
+    encode(buf, '\uD7FF'); assert(buf[9 .. $] == "\uD7FF");
+    encode(buf, '\uE000'); assert(buf[12 .. $] == "\uE000");
+    encode(buf, 0xFFFE); assert(buf[15 .. $] == "\xEF\xBF\xBE");
+    encode(buf, 0xFFFF); assert(buf[18 .. $] == "\xEF\xBF\xBF");
+    encode(buf, '\U00010000'); assert(buf[21 .. $] == "\U00010000");
+    encode(buf, '\U0010FFFF'); assert(buf[25 .. $] == "\U0010FFFF");
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(buf.back != replacementDchar);
+    encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000);
+    assert(buf.back == replacementDchar);
+    });
+}
+
+/// ditto
+void encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref wchar[] str, dchar c) @safe pure
+{
+    wchar[] r = str;
+
+    if (c <= 0xFFFF)
+    {
+        if (0xD800 <= c && c <= 0xDFFF)
+            c = _utfException!useReplacementDchar("Encoding an isolated surrogate code point in UTF-16", c);
+
+        assert(isValidDchar(c));
+    L1:
+        r ~= cast(wchar)c;
+    }
+    else if (c <= 0x10FFFF)
+    {
+        wchar[2] buf;
+
+        assert(isValidDchar(c));
+        buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
+        buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
+        r ~= buf;
+    }
+    else
+    {
+        assert(!isValidDchar(c));
+        c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-16", c);
+        goto L1;
+    }
+
+    str = r;
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    wchar[] buf;
+
+    encode(buf, '\u0000'); assert(buf[0] == '\u0000');
+    encode(buf, '\uD7FF'); assert(buf[1] == '\uD7FF');
+    encode(buf, '\uE000'); assert(buf[2] == '\uE000');
+    encode(buf, 0xFFFE); assert(buf[3] == 0xFFFE);
+    encode(buf, 0xFFFF); assert(buf[4] == 0xFFFF);
+    encode(buf, '\U00010000'); assert(buf[5 .. $] == "\U00010000");
+    encode(buf, '\U0010FFFF'); assert(buf[7 .. $] == "\U0010FFFF");
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(buf.back != replacementDchar);
+    encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000);
+    assert(buf.back == replacementDchar);
+    });
+}
+
+/// ditto
+void encode(UseReplacementDchar useReplacementDchar = UseReplacementDchar.no)(
+    ref dchar[] str, dchar c) @safe pure
+{
+    if ((0xD800 <= c && c <= 0xDFFF) || 0x10FFFF < c)
+        c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-32", c);
+    else
+        assert(isValidDchar(c));
+    str ~= c;
+}
+
+unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    dchar[] buf;
+
+    encode(buf, '\u0000'); assert(buf[0] == '\u0000');
+    encode(buf, '\uD7FF'); assert(buf[1] == '\uD7FF');
+    encode(buf, '\uE000'); assert(buf[2] == '\uE000');
+    encode(buf, 0xFFFE ); assert(buf[3] == 0xFFFE);
+    encode(buf, 0xFFFF ); assert(buf[4] == 0xFFFF);
+    encode(buf, '\U0010FFFF'); assert(buf[5] == '\U0010FFFF');
+
+    assertThrown!UTFException(encode(buf, cast(dchar)0xD800));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDBFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDC00));
+    assertThrown!UTFException(encode(buf, cast(dchar)0xDFFF));
+    assertThrown!UTFException(encode(buf, cast(dchar)0x110000));
+
+    assert(buf.back != replacementDchar);
+    encode!(UseReplacementDchar.yes)(buf, cast(dchar)0x110000);
+    assert(buf.back == replacementDchar);
+    });
+}
+
+
+/++
+    Returns the number of code units that are required to encode the code point
+    $(D c) when $(D C) is the character type used to encode it.
+  +/
+ubyte codeLength(C)(dchar c) @safe pure nothrow @nogc
+    if (isSomeChar!C)
+{
+    static if (C.sizeof == 1)
+    {
+        if (c <= 0x7F) return 1;
+        if (c <= 0x7FF) return 2;
+        if (c <= 0xFFFF) return 3;
+        if (c <= 0x10FFFF) return 4;
+        assert(false);
+    }
+    else static if (C.sizeof == 2)
+    {
+        return c <= 0xFFFF ? 1 : 2;
+    }
+    else
+    {
+        static assert(C.sizeof == 4);
+        return 1;
+    }
+}
+
+///
+pure nothrow @nogc unittest
+{
+    assert(codeLength!char('a') == 1);
+    assert(codeLength!wchar('a') == 1);
+    assert(codeLength!dchar('a') == 1);
+
+    assert(codeLength!char('\U0010FFFF') == 4);
+    assert(codeLength!wchar('\U0010FFFF') == 2);
+    assert(codeLength!dchar('\U0010FFFF') == 1);
+}
+
+
+/++
+    Returns the number of code units that are required to encode $(D str)
+    in a string whose character type is $(D C). This is particularly useful
+    when slicing one string with the length of another and the two string
+    types use different character types.
+  +/
+size_t codeLength(C, InputRange)(InputRange input)
+    if (isInputRange!InputRange && is(ElementType!InputRange : dchar))
+{
+    alias EncType = Unqual!(ElementEncodingType!InputRange);
+    static if (isSomeString!InputRange && is(EncType == C) && is(typeof(input.length)))
+        return input.length;
+    else
+    {
+        size_t total = 0;
+
+        foreach (dchar c; input)
+            total += codeLength!C(c);
+
+        return total;
+    }
+}
+
+///
+unittest
+{
+    import std.conv : to;
+    assert(codeLength!char("hello world") ==
+           to!string("hello world").length);
+    assert(codeLength!wchar("hello world") ==
+           to!wstring("hello world").length);
+    assert(codeLength!dchar("hello world") ==
+           to!dstring("hello world").length);
+
+    assert(codeLength!char(`プログラミング`) ==
+           to!string(`プログラミング`).length);
+    assert(codeLength!wchar(`プログラミング`) ==
+           to!wstring(`プログラミング`).length);
+    assert(codeLength!dchar(`プログラミング`) ==
+           to!dstring(`プログラミング`).length);
+
+    string haystack = `Être sans la verité, ça, ce ne serait pas bien.`;
+    wstring needle = `Être sans la verité`;
+    assert(haystack[codeLength!char(needle) .. $] ==
+           `, ça, ce ne serait pas bien.`);
+}
+
+unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.algorithm : filter;
+
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!( char[], const  char[],  string,
+                          wchar[], const wchar[], wstring,
+                          dchar[], const dchar[], dstring))
+    {
+        foreach (C; AliasSeq!(char, wchar, dchar))
+        {
+            assert(codeLength!C(to!S("Walter Bright")) == to!(C[])("Walter Bright").length);
+            assert(codeLength!C(to!S(`言語`)) == to!(C[])(`言語`).length);
+            assert(codeLength!C(to!S(`ウェブサイト@La_Verité.com`)) ==
+                   to!(C[])(`ウェブサイト@La_Verité.com`).length);
+            assert(codeLength!C(to!S(`ウェブサイト@La_Verité.com`).filter!(x => true)()) ==
+                   to!(C[])(`ウェブサイト@La_Verité.com`).length);
+        }
+    }
+    });
+}
+
+/+
+Internal helper function:
+
+Returns true if it is safe to search for the Codepoint $(D c) inside
+code units, without decoding.
+
+This is a runtime check that is used an optimization in various functions,
+particularly, in $(D std.string).
+  +/
+package bool canSearchInCodeUnits(C)(dchar c)
+if (isSomeChar!C)
+{
+    static if (C.sizeof == 1)
+         return c <= 0x7F;
+    else static if (C.sizeof == 2)
+        return c <= 0xD7FF || (0xE000 <= c && c <= 0xFFFF);
+    else static if (C.sizeof == 4)
+        return true;
+    else
+        static assert(0);
+}
+unittest
+{
+    assert( canSearchInCodeUnits! char('a'));
+    assert( canSearchInCodeUnits!wchar('a'));
+    assert( canSearchInCodeUnits!dchar('a'));
+    assert(!canSearchInCodeUnits! char('ö')); //Important test: ö <= 0xFF
+    assert(!canSearchInCodeUnits! char(cast(char)'ö')); //Important test: ö <= 0xFF
+    assert( canSearchInCodeUnits!wchar('ö'));
+    assert( canSearchInCodeUnits!dchar('ö'));
+    assert(!canSearchInCodeUnits! char('日'));
+    assert( canSearchInCodeUnits!wchar('日'));
+    assert( canSearchInCodeUnits!dchar('日'));
+    assert(!canSearchInCodeUnits!wchar(cast(wchar)0xDA00));
+    assert( canSearchInCodeUnits!dchar(cast(dchar)0xDA00));
+    assert(!canSearchInCodeUnits! char('\U00010001'));
+    assert(!canSearchInCodeUnits!wchar('\U00010001'));
+    assert( canSearchInCodeUnits!dchar('\U00010001'));
+}
+
+/* =================== Validation ======================= */
+
+/++
+    Checks to see if $(D str) is well-formed unicode or not.
+
+    Throws:
+        $(D UTFException) if $(D str) is not well-formed.
+  +/
+void validate(S)(in S str) @safe pure
+    if (isSomeString!S)
+{
+    immutable len = str.length;
+    for (size_t i = 0; i < len; )
+    {
+        decode(str, i);
+    }
+}
+
+
+unittest // bugzilla 12923
+{
+    import std.exception;
+    assertThrown((){
+        char[3]a=[167, 133, 175];
+        validate(a[]);
+    }());
+}
+
+/* =================== Conversion to UTF8 ======================= */
+
+pure
+{
+
+char[] toUTF8(return out char[4] buf, dchar c) nothrow @nogc @safe
+{
+    if (c <= 0x7F)
+    {
+        buf[0] = cast(char)c;
+        return buf[0 .. 1];
+    }
+    else if (c <= 0x7FF)
+    {
+        buf[0] = cast(char)(0xC0 | (c >> 6));
+        buf[1] = cast(char)(0x80 | (c & 0x3F));
+        return buf[0 .. 2];
+    }
+    else if (c <= 0xFFFF)
+    {
+        if (c >= 0xD800 && c <= 0xDFFF)
+            c = replacementDchar;
+
+    L3:
+        buf[0] = cast(char)(0xE0 | (c >> 12));
+        buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[2] = cast(char)(0x80 | (c & 0x3F));
+        return buf[0 .. 3];
+    }
+    else
+    {
+        if (c > 0x10FFFF)
+        {
+            c = replacementDchar;
+            goto L3;
+        }
+
+        buf[0] = cast(char)(0xF0 | (c >> 18));
+        buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
+        buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
+        buf[3] = cast(char)(0x80 | (c & 0x3F));
+        return buf[0 .. 4];
+    }
+}
+
+/*******************
+ * Encodes string $(D_PARAM s) into UTF-8 and returns the encoded string.
+ */
+string toUTF8(in char[] s) @safe
+{
+    validate(s);
+    return s.idup;
+}
+
+/// ditto
+string toUTF8(in wchar[] s) @safe
+{
+    char[] r;
+    size_t i;
+    size_t slen = s.length;
+
+    r.length = slen;
+    for (i = 0; i < slen; i++)
+    {
+        wchar c = s[i];
+
+        if (c <= 0x7F)
+            r[i] = cast(char)c;     // fast path for ascii
+        else
+        {
+            r.length = i;
+            while (i < slen)
+                encode(r, decode(s, i));
+            break;
+        }
+    }
+
+    return r;
+}
+
+/// ditto
+string toUTF8(in dchar[] s) @safe
+{
+    char[] r;
+    size_t i;
+    size_t slen = s.length;
+
+    r.length = slen;
+    for (i = 0; i < slen; i++)
+    {
+        dchar c = s[i];
+
+        if (c <= 0x7F)
+            r[i] = cast(char)c;     // fast path for ascii
+        else
+        {
+            r.length = i;
+            foreach (dchar d; s[i .. slen])
+            {
+                encode(r, d);
+            }
+            break;
+        }
+    }
+
+    return r;
+}
+
+
+/* =================== Conversion to UTF16 ======================= */
+
+wchar[] toUTF16(return ref wchar[2] buf, dchar c) nothrow @nogc @safe
+in
+{
+    assert(isValidDchar(c));
+}
+body
+{
+    if (c <= 0xFFFF)
+    {
+        buf[0] = cast(wchar)c;
+        return buf[0 .. 1];
+    }
+    else
+    {
+        buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
+        buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
+        return buf[0 .. 2];
+    }
+}
+
+/****************
+ * Encodes string $(D s) into UTF-16 and returns the encoded string.
+ */
+wstring toUTF16(in char[] s) @safe
+{
+    wchar[] r;
+    size_t slen = s.length;
+
+    r.length = slen;
+    r.length = 0;
+    for (size_t i = 0; i < slen; )
+    {
+        dchar c = s[i];
+        if (c <= 0x7F)
+        {
+            i++;
+            r ~= cast(wchar)c;
+        }
+        else
+        {
+            c = decode(s, i);
+            encode(r, c);
+        }
+    }
+
+    return r;
+}
+
+/// ditto
+wstring toUTF16(in wchar[] s) @safe
+{
+    validate(s);
+    return s.idup;
+}
+
+/// ditto
+wstring toUTF16(in dchar[] s) @safe
+{
+    wchar[] r;
+    size_t slen = s.length;
+
+    r.length = slen;
+    r.length = 0;
+    for (size_t i = 0; i < slen; i++)
+    {
+        encode(r, s[i]);
+    }
+
+    return r;
+}
+
+
+/* =================== Conversion to UTF32 ======================= */
+
+/*****
+ * Encodes string $(D_PARAM s) into UTF-32 and returns the encoded string.
+ */
+dstring toUTF32(in char[] s) @safe
+{
+    dchar[] r;
+    size_t slen = s.length;
+    size_t j = 0;
+
+    r.length = slen;        // r[] will never be longer than s[]
+    for (size_t i = 0; i < slen; )
+    {
+        dchar c = s[i];
+        if (c >= 0x80)
+            c = decode(s, i);
+        else
+            i++;        // c is ascii, no need for decode
+        r[j++] = c;
+    }
+
+    return r[0 .. j];
+}
+
+/// ditto
+dstring toUTF32(in wchar[] s) @safe
+{
+    dchar[] r;
+    size_t slen = s.length;
+    size_t j = 0;
+
+    r.length = slen;        // r[] will never be longer than s[]
+    for (size_t i = 0; i < slen; )
+    {
+        dchar c = s[i];
+        if (c >= 0x80)
+            c = decode(s, i);
+        else
+            i++;        // c is ascii, no need for decode
+        r[j++] = c;
+    }
+
+    return r[0 .. j];
+}
+
+/// ditto
+dstring toUTF32(in dchar[] s) @safe
+{
+    validate(s);
+    return s.idup;
+}
+
+} // Convert functions are @safe
+
+
+/* =================== toUTFz ======================= */
+
+/++
+    Returns a C-style zero-terminated string equivalent to $(D str). $(D str)
+    must not contain embedded $(D '\0')'s as any C function will treat the first
+    $(D '\0') that it sees as the end of the string. If $(D str.empty) is
+    $(D true), then a string containing only $(D '\0') is returned.
+
+    $(D toUTFz) accepts any type of string and is templated on the type of
+    character pointer that you wish to convert to. It will avoid allocating a
+    new string if it can, but there's a decent chance that it will end up having
+    to allocate a new string - particularly when dealing with character types
+    other than $(D char).
+
+    $(RED Warning 1:) If the result of $(D toUTFz) equals $(D str.ptr), then if
+    anything alters the character one past the end of $(D str) (which is the
+    $(D '\0') character terminating the string), then the string won't be
+    zero-terminated anymore. The most likely scenarios for that are if you
+    append to $(D str) and no reallocation takes place or when $(D str) is a
+    slice of a larger array, and you alter the character in the larger array
+    which is one character past the end of $(D str). Another case where it could
+    occur would be if you had a mutable character array immediately after
+    $(D str) in memory (for example, if they're member variables in a
+    user-defined type with one declared right after the other) and that
+    character array happened to start with $(D '\0'). Such scenarios will never
+    occur if you immediately use the zero-terminated string after calling
+    $(D toUTFz) and the C function using it doesn't keep a reference to it.
+    Also, they are unlikely to occur even if you save the zero-terminated string
+    (the cases above would be among the few examples of where it could happen).
+    However, if you save the zero-terminate string and want to be absolutely
+    certain that the string stays zero-terminated, then simply append a
+    $(D '\0') to the string and use its $(D ptr) property rather than calling
+    $(D toUTFz).
+
+    $(RED Warning 2:) When passing a character pointer to a C function, and the
+    C function keeps it around for any reason, make sure that you keep a
+    reference to it in your D code. Otherwise, it may go away during a garbage
+    collection cycle and cause a nasty bug when the C code tries to use it.
+  +/
+template toUTFz(P)
+{
+    P toUTFz(S)(S str) @safe pure
+    {
+        return toUTFzImpl!(P, S)(str);
+    }
+}
+
+///
+@safe pure unittest
+{
+    auto p1 = toUTFz!(char*)("hello world");
+    auto p2 = toUTFz!(const(char)*)("hello world");
+    auto p3 = toUTFz!(immutable(char)*)("hello world");
+    auto p4 = toUTFz!(char*)("hello world"d);
+    auto p5 = toUTFz!(const(wchar)*)("hello world");
+    auto p6 = toUTFz!(immutable(dchar)*)("hello world"w);
+}
+
+private P toUTFzImpl(P, S)(S str) @safe pure
+    if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) &&
+        is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S)) &&
+        is(immutable(Unqual!(ElementEncodingType!S)) == ElementEncodingType!S))
+//immutable(C)[] -> C*, const(C)*, or immutable(C)*
+{
+    if (str.empty)
+    {
+        typeof(*P.init)[] retval = ['\0'];
+
+        return retval.ptr;
+    }
+
+    alias C = Unqual!(ElementEncodingType!S);
+
+    //If the P is mutable, then we have to make a copy.
+    static if (is(Unqual!(typeof(*P.init)) == typeof(*P.init)))
+    {
+        return toUTFzImpl!(P, const(C)[])(cast(const(C)[])str);
+    }
+    else
+    {
+        if (!__ctfe)
+        {
+            auto trustedPtrAdd(S s) @trusted { return s.ptr + s.length; }
+            immutable p = trustedPtrAdd(str);
+
+            // Peek past end of str, if it's 0, no conversion necessary.
+            // Note that the compiler will put a 0 past the end of static
+            // strings, and the storage allocator will put a 0 past the end
+            // of newly allocated char[]'s.
+            // Is p dereferenceable? A simple test: if the p points to an
+            // address multiple of 4, then conservatively assume the pointer
+            // might be pointing to a new block of memory, which might be
+            // unreadable. Otherwise, it's definitely pointing to valid
+            // memory.
+            if ((cast(size_t)p & 3) && *p == '\0')
+                return str.ptr;
+        }
+
+        return toUTFzImpl!(P, const(C)[])(cast(const(C)[])str);
+    }
+}
+
+private P toUTFzImpl(P, S)(S str) @safe pure
+    if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) &&
+        is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S)) &&
+        !is(immutable(Unqual!(ElementEncodingType!S)) == ElementEncodingType!S))
+//C[] or const(C)[] -> C*, const(C)*, or immutable(C)*
+{
+    alias InChar  = ElementEncodingType!S;
+    alias OutChar = typeof(*P.init);
+
+    //const(C)[] -> const(C)* or
+    //C[] -> C* or const(C)*
+    static if (( is(const(Unqual!InChar) == InChar) &&  is(const(Unqual!OutChar) == OutChar)) ||
+               (!is(const(Unqual!InChar) == InChar) && !is(immutable(Unqual!OutChar) == OutChar)))
+    {
+        if (!__ctfe)
+        {
+            auto trustedPtrAdd(S s) @trusted { return s.ptr + s.length; }
+            auto p = trustedPtrAdd(str);
+
+            if ((cast(size_t)p & 3) && *p == '\0')
+                return str.ptr;
+        }
+
+        str ~= '\0';
+        return str.ptr;
+    }
+    //const(C)[] -> C* or immutable(C)* or
+    //C[] -> immutable(C)*
+    else
+    {
+        import std.array : uninitializedArray;
+        auto copy = uninitializedArray!(Unqual!OutChar[])(str.length + 1);
+        copy[0 .. $ - 1] = str[];
+        copy[$ - 1] = '\0';
+
+        auto trustedCast(typeof(copy) c) @trusted { return cast(P)c.ptr; }
+        return trustedCast(copy);
+    }
+}
+
+private P toUTFzImpl(P, S)(S str) @safe pure
+    if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) &&
+        !is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S)))
+//C1[], const(C1)[], or immutable(C1)[] -> C2*, const(C2)*, or immutable(C2)*
+{
+    import std.array : appender;
+    auto retval = appender!(typeof(*P.init)[])();
+
+    foreach (dchar c; str)
+        retval.put(c);
+    retval.put('\0');
+
+    return cast(P)retval.data.ptr;
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    import std.exception;
+    import std.string : format;
+    import core.exception : AssertError;
+    import std.algorithm;
+
+    assertCTFEable!(
+    {
+    foreach (S; AliasSeq!(string, wstring, dstring))
+    {
+        alias C = Unqual!(ElementEncodingType!S);
+
+        auto s1 = to!S("hello\U00010143\u0100\U00010143");
+        auto temp = new C[](s1.length + 1);
+        temp[0 .. $ - 1] = s1[0 .. $];
+        temp[$ - 1] = '\n';
+        --temp.length;
+        auto trustedAssumeUnique(T)(T t) @trusted { return assumeUnique(t); }
+        auto s2 = trustedAssumeUnique(temp);
+        assert(s1 == s2);
+
+        void trustedCStringAssert(P, S)(S s) @trusted
+        {
+            auto p = toUTFz!P(s);
+            assert(p[0 .. s.length] == s);
+            assert(p[s.length] == '\0');
+        }
+
+        foreach (P; AliasSeq!(C*, const(C)*, immutable(C)*))
+        {
+            trustedCStringAssert!P(s1);
+            trustedCStringAssert!P(s2);
+        }
+    }
+    });
+
+    static void test(P, S)(S s, size_t line = __LINE__) @trusted
+    {
+        static size_t zeroLen(C)(const(C)* ptr) @trusted
+        {
+            size_t len = 0;
+            while (*ptr != '\0') { ++ptr; ++len; }
+            return len;
+        }
+
+        auto p = toUTFz!P(s);
+        immutable len = zeroLen(p);
+        enforce(cmp(s, p[0 .. len]) == 0,
+                new AssertError(format("Unit test failed: %s %s", P.stringof, S.stringof),
+                                __FILE__, line));
+    }
+
+    assertCTFEable!(
+    {
+    foreach (P; AliasSeq!(wchar*, const(wchar)*, immutable(wchar)*,
+                          dchar*, const(dchar)*, immutable(dchar)*))
+    {
+        test!P("hello\U00010143\u0100\U00010143");
+    }
+    foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*,
+                          dchar*, const(dchar)*, immutable(dchar)*))
+    {
+        test!P("hello\U00010143\u0100\U00010143"w);
+    }
+    foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*,
+                          wchar*, const(wchar)*, immutable(wchar)*))
+    {
+        test!P("hello\U00010143\u0100\U00010143"d);
+    }
+    foreach (S; AliasSeq!( char[], const( char)[],
+                          wchar[], const(wchar)[],
+                          dchar[], const(dchar)[]))
+    {
+        auto s = to!S("hello\U00010143\u0100\U00010143");
+
+        foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*,
+                              wchar*, const(wchar)*, immutable(wchar)*,
+                              dchar*, const(dchar)*, immutable(dchar)*))
+        {
+            test!P(s);
+        }
+    }
+    });
+}
+
+
+/++
+    $(D toUTF16z) is a convenience function for $(D toUTFz!(const(wchar)*)).
+
+    Encodes string $(D s) into UTF-16 and returns the encoded string.
+    $(D toUTF16z) is suitable for calling the 'W' functions in the Win32 API
+    that take an $(D LPWSTR) or $(D LPCWSTR) argument.
+  +/
+const(wchar)* toUTF16z(C)(const(C)[] str) @safe pure
+    if (isSomeChar!C)
+{
+    return toUTFz!(const(wchar)*)(str);
+}
+
+@safe pure unittest
+{
+    import std.conv : to;
+    //toUTFz is already thoroughly tested, so this will just verify that
+    //toUTF16z compiles properly for the various string types.
+    foreach (S; AliasSeq!(string, wstring, dstring))
+        assert(toUTF16z(to!S("hello world")) !is null);
+}
+
+
+/* ================================ tests ================================== */
+
+pure unittest
+{
+    import std.exception;
+    debug(utf) printf("utf.toUTF.unittest\n");
+
+    assertCTFEable!(
+    {
+    assert(toUTF16("hello"c) == "hello");
+    assert(toUTF32("hello"c) == "hello");
+    assert(toUTF8 ("hello"w) == "hello");
+    assert(toUTF32("hello"w) == "hello");
+    assert(toUTF8 ("hello"d) == "hello");
+    assert(toUTF16("hello"d) == "hello");
+
+    assert(toUTF16("hel\u1234o"c) == "hel\u1234o");
+    assert(toUTF32("hel\u1234o"c) == "hel\u1234o");
+    assert(toUTF8 ("hel\u1234o"w) == "hel\u1234o");
+    assert(toUTF32("hel\u1234o"w) == "hel\u1234o");
+    assert(toUTF8 ("hel\u1234o"d) == "hel\u1234o");
+    assert(toUTF16("hel\u1234o"d) == "hel\u1234o");
+
+    assert(toUTF16("he\U0010AAAAllo"c) == "he\U0010AAAAllo");
+    assert(toUTF32("he\U0010AAAAllo"c) == "he\U0010AAAAllo");
+    assert(toUTF8 ("he\U0010AAAAllo"w) == "he\U0010AAAAllo");
+    assert(toUTF32("he\U0010AAAAllo"w) == "he\U0010AAAAllo");
+    assert(toUTF8 ("he\U0010AAAAllo"d) == "he\U0010AAAAllo");
+    assert(toUTF16("he\U0010AAAAllo"d) == "he\U0010AAAAllo");
+    });
+}
+
+
+/++
+    Returns the total number of code points encoded in $(D str).
+
+    Supercedes: This function supercedes $(LREF toUCSindex).
+
+    Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252
+
+    Throws:
+        $(D UTFException) if $(D str) is not well-formed.
+  +/
+size_t count(C)(const(C)[] str) @trusted pure nothrow @nogc
+    if (isSomeChar!C)
+{
+    return walkLength(str);
+}
+
+pure nothrow @nogc unittest
+{
+    import std.exception;
+    assertCTFEable!(
+    {
+    assert(count("") == 0);
+    assert(count("a") == 1);
+    assert(count("abc") == 3);
+    assert(count("\u20AC100") == 4);
+    });
+}
+
+
+// Ranges of code units for testing.
+version(unittest)
+{
+    struct InputCU(C)
+    {
+        import std.conv : to;
+        @property bool empty() { return _str.empty; }
+        @property C front() { return _str[0]; }
+        void popFront() { _str = _str[1 .. $]; }
+
+        this(inout(C)[] str)
+        {
+            _str = to!(C[])(str);
+        }
+
+        C[] _str;
+    }
+
+    struct BidirCU(C)
+    {
+        import std.conv : to;
+        @property bool empty() { return _str.empty; }
+        @property C front() { return _str[0]; }
+        void popFront() { _str = _str[1 .. $]; }
+        @property C back() { return _str[$ - 1]; }
+        void popBack() { _str = _str[0 .. $ - 1]; }
+        @property auto save() { return BidirCU(_str); }
+        @property size_t length() { return _str.length; }
+
+        this(inout(C)[] str)
+        {
+            _str = to!(C[])(str);
+        }
+
+        C[] _str;
+    }
+
+    struct RandomCU(C)
+    {
+        import std.conv : to;
+        @property bool empty() { return _str.empty; }
+        @property C front() { return _str[0]; }
+        void popFront() { _str = _str[1 .. $]; }
+        @property C back() { return _str[$ - 1]; }
+        void popBack() { _str = _str[0 .. $ - 1]; }
+        @property auto save() { return RandomCU(_str); }
+        @property size_t length() { return _str.length; }
+        C opIndex(size_t i) { return _str[i]; }
+        auto opSlice(size_t i, size_t j) { return RandomCU(_str[i .. j]); }
+
+        this(inout(C)[] str)
+        {
+            _str = to!(C[])(str);
+        }
+
+        C[] _str;
+    }
+
+    class RefBidirCU(C)
+    {
+        import std.conv : to;
+        @property bool empty() { return _str.empty; }
+        @property C front() { return _str[0]; }
+        void popFront() { _str = _str[1 .. $]; }
+        @property C back() { return _str[$ - 1]; }
+        void popBack() { _str = _str[0 .. $ - 1]; }
+        @property auto save() { return new RefBidirCU(_str); }
+        @property size_t length() { return _str.length; }
+
+        this(inout(C)[] str)
+        {
+            _str = to!(C[])(str);
+        }
+
+        C[] _str;
+    }
+
+    class RefRandomCU(C)
+    {
+        import std.conv : to;
+        @property bool empty() { return _str.empty; }
+        @property C front() { return _str[0]; }
+        void popFront() { _str = _str[1 .. $]; }
+        @property C back() { return _str[$ - 1]; }
+        void popBack() { _str = _str[0 .. $ - 1]; }
+        @property auto save() { return new RefRandomCU(_str); }
+        @property size_t length() { return _str.length; }
+        C opIndex(size_t i) { return _str[i]; }
+        auto opSlice(size_t i, size_t j) { return new RefRandomCU(_str[i .. j]); }
+
+        this(inout(C)[] str)
+        {
+            _str = to!(C[])(str);
+        }
+
+        C[] _str;
+    }
+}
+
+
+/**
+ * Inserted in place of invalid UTF sequences.
+ *
+ * References:
+ *      $(LINK http://en.wikipedia.org/wiki/Replacement_character#Replacement_character)
+ */
+enum dchar replacementDchar = '\uFFFD';
+
+/********************************************
+ * Iterate a range of char, wchar, or dchars by code unit.
+ *
+ * The purpose is to bypass the special case decoding that
+ * $(XREF array,front) does to character arrays.
+ * Params:
+ *      r = input range of characters, or array of characters
+ * Returns:
+ *      input range
+ */
+
+auto byCodeUnit(R)(R r) if (isAutodecodableString!R)
+{
+    /* Turn an array into an InputRange.
+     */
+    static struct ByCodeUnitImpl
+    {
+    pure nothrow @nogc:
+
+        @property bool empty() const         { return r.length == 0; }
+        @property auto ref front() inout     { return r[0]; }
+        void popFront()                      { r = r[1 .. $]; }
+        auto ref opIndex(size_t index) inout { return r[index]; }
+
+        @property auto ref back() inout
+        {
+            return r[$ - 1];
+        }
+
+        void popBack()
+        {
+            r = r[0 .. $-1];
+        }
+
+        static if (!isAggregateType!R)
+        {
+            auto opSlice(size_t lower, size_t upper)
+            {
+                return ByCodeUnitImpl(r[lower..upper]);
+            }
+        }
+
+        @property size_t length() const
+        {
+            return r.length;
+        }
+        alias opDollar = length;
+
+        static if (!isAggregateType!R)
+        {
+            @property auto save()
+            {
+                return ByCodeUnitImpl(r.save);
+            }
+        }
+
+      private:
+        R r;
+    }
+
+    static assert(isAggregateType!R || isRandomAccessRange!ByCodeUnitImpl);
+
+    return ByCodeUnitImpl(r);
+}
+
+/// Ditto
+auto ref byCodeUnit(R)(R r)
+    if (!isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R))
+{
+    // byCodeUnit for ranges and dchar[] is a no-op
+    return r;
+}
+
+pure nothrow @nogc unittest
+{
+    import std.range;
+    {
+        char[5] s;
+        int i;
+        foreach (c; "hello".byCodeUnit().byCodeUnit())
+        {
+            s[i++] = c;
+        }
+        assert(s == "hello");
+    }
+    {
+        wchar[5] s;
+        int i;
+        foreach (c; "hello"w.byCodeUnit().byCodeUnit())
+        {
+            s[i++] = c;
+        }
+        assert(s == "hello"w);
+    }
+    {
+        dchar[5] s;
+        int i;
+        foreach (c; "hello"d.byCodeUnit().byCodeUnit())
+        {
+            s[i++] = c;
+        }
+        assert(s == "hello"d);
+    }
+    {
+        auto r = "hello".byCodeUnit();
+        assert(r.length == 5);
+        assert(r[3] == 'l');
+        assert(r[2..4][1] == 'l');
+    }
+    {
+        char[5] buff = "hello";
+        auto s = buff[].byCodeUnit();
+        s.front = 'H';
+        assert(s.front == 'H');
+        s[1] = 'E';
+        assert(s[1] == 'E');
+    }
+    {
+        auto r = "hello".byCodeUnit().byCodeUnit();
+        static assert(isForwardRange!(typeof(r)));
+        auto s = r.save;
+        r.popFront();
+        assert(s.front == 'h');
+    }
+    {
+        auto r = "hello".byCodeUnit();
+        static assert(hasSlicing!(typeof(r)));
+        static assert(isBidirectionalRange!(typeof(r)));
+        auto ret = r.retro;
+        assert(ret.front == 'o');
+        ret.popFront();
+        assert(ret.front == 'l');
+    }
+    {
+        auto r = "κόσμε"w.byCodeUnit();
+        static assert(hasSlicing!(typeof(r)));
+        static assert(isBidirectionalRange!(typeof(r)));
+        auto ret = r.retro;
+        assert(ret.front == 'ε');
+        ret.popFront();
+        assert(ret.front == 'μ');
+    }
+    {
+        static struct Stringish
+        {
+            string s;
+            alias s this;
+        }
+
+        auto fn = Stringish("test.d");
+        auto x = fn.byCodeUnit();
+        assert(x.front == 't');
+    }
+}
+
+/****************************
+ * Iterate an input range of characters by char, wchar, or dchar.
+ * These aliases simply forward to $(LREF byUTF) with the
+ * corresponding C argument.
+ *
+ * Params:
+ *      r = input range of characters, or array of characters
+ */
+alias byChar = byUTF!char;
+
+/// Ditto
+alias byWchar = byUTF!wchar;
+
+/// Ditto
+alias byDchar = byUTF!dchar;
+
+pure nothrow @nogc unittest
+{
+  {
+    char[5] s;
+    int i;
+    foreach (c; "hello".byChar.byChar())
+    {
+        //writefln("[%d] '%c'", i, c);
+        s[i++] = c;
+    }
+    assert(s == "hello");
+  }
+  {
+    char[5+2+3+4+3+3] s;
+    int i;
+    dchar[10] a;
+    a[0..8] = "hello\u07FF\uD7FF\U0010FFFF"d;
+    a[8] = 0xD800;   // invalid
+    a[9] = cast(dchar)0x110000; // invalid
+    foreach (c; a[].byChar())
+    {
+        //writefln("[%d] '%c'", i, c);
+        s[i++] = c;
+    }
+    assert(s == "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD");
+  }
+  {
+    auto r = "hello"w.byChar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+  {
+    auto r = "hello"d.byChar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+  {
+    auto r = "hello"d.byChar();
+    assert(isForwardRange!(typeof(r)));
+    auto s = r.save;
+    r.popFront();
+    assert(s.front == 'h');
+  }
+}
+
+pure nothrow @nogc unittest
+{
+  {
+    wchar[11] s;
+    int i;
+    dchar[10] a;
+    a[0..8] = "hello\u07FF\uD7FF\U0010FFFF"d;
+    a[8] = 0xD800;   // invalid
+    a[9] = cast(dchar)0x110000; // invalid
+    foreach (c; a[].byWchar())
+    {
+        //writefln("[%d] '%c' x%x", i, c, c);
+        s[i++] = c;
+    }
+    foreach (j, wchar c; "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD"w)
+    {
+        //writefln("[%d] '%c' x%x", j, c, c);
+    }
+    assert(s == "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD"w);
+  }
+
+  {
+    auto r = "hello".byWchar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+  {
+    auto r = "hello"d.byWchar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+  {
+    auto r = "hello"d.byWchar();
+    assert(isForwardRange!(typeof(r)));
+    auto s = r.save;
+    r.popFront();
+    assert(s.front == 'h');
+  }
+}
+
+pure nothrow @nogc unittest
+{
+  {
+    dchar[9] s;
+    int i;
+    string a = "hello\u07FF\uD7FF\U00010000\U0010FFFF"; // 1,2,3,4 byte sequences
+    foreach (c; a.byDchar())
+    {
+        s[i++] = c;
+    }
+    assert(s == "hello\u07FF\uD7FF\U00010000\U0010FFFF"d);
+  }
+  {
+    foreach (s; invalidUTFstrings!char())
+    {
+        auto r = s.byDchar();
+        assert(!r.empty);
+        assert(r.front == r.front);
+        dchar c = r.front;
+        assert(c == replacementDchar);
+    }
+  }
+  {
+    auto r = "hello".byDchar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+
+  {
+    dchar[8] s;
+    int i;
+    wstring a = "hello\u07FF\uD7FF\U0010FFFF"w;
+    foreach (c; a.byDchar())
+    {
+        //writefln("[%d] '%c' x%x", i, c, c);
+        s[i++] = c;
+    }
+    assert(s == "hello\u07FF\uD7FF\U0010FFFF"d);
+  }
+  {
+    foreach (s; invalidUTFstrings!wchar())
+    {
+        auto r = s.byDchar();
+        assert(!r.empty);
+        assert(r.front == r.front);
+        dchar c = r.front;
+        assert(c == replacementDchar);
+    }
+  }
+  {
+    wchar[2] ws;
+    ws[0] = 0xD800;
+    ws[1] = 0xDD00;             // correct surrogate pair
+    auto r = ws[].byDchar();
+    assert(!r.empty);
+    assert(r.front == r.front);
+    dchar c = r.front;
+    assert(c == '\U00010100');
+  }
+  {
+    auto r = "hello"w.byDchar();
+    r.popFront();
+    r.popFront();
+    assert(r.front == 'l');
+  }
+
+  {
+    dchar[5] s;
+    int i;
+    dstring a = "hello"d;
+    foreach (c; a.byDchar.byDchar())
+    {
+        //writefln("[%d] '%c' x%x", i, c, c);
+        s[i++] = c;
+    }
+    assert(s == "hello"d);
+  }
+  {
+    auto r = "hello".byDchar();
+    assert(isForwardRange!(typeof(r)));
+    auto s = r.save;
+    r.popFront();
+    assert(s.front == 'h');
+  }
+  {
+    auto r = "hello"w.byDchar();
+    assert(isForwardRange!(typeof(r)));
+    auto s = r.save;
+    r.popFront();
+    assert(s.front == 'h');
+  }
+}
+
+// test pure, @safe, nothrow, @nogc correctness of byChar/byWchar/byDchar,
+// which needs to support ranges with and without those attributes
+
+pure @safe nothrow @nogc unittest
+{
+    dchar[5] s = "hello"d;
+    foreach (c; s[].byChar())  { }
+    foreach (c; s[].byWchar()) { }
+    foreach (c; s[].byDchar()) { }
+}
+
+version(unittest)
+int impureVariable;
+
+@system unittest
+{
+    static struct ImpureThrowingSystemRange(Char)
+    {
+        @property bool empty() const { return true; }
+        @property Char front() const { return Char.init; }
+        void popFront()
+        {
+            impureVariable++;
+            throw new Exception("only for testing nothrow");
+        }
+    }
+
+    foreach (Char; AliasSeq!(char, wchar, dchar))
+    {
+        ImpureThrowingSystemRange!Char range;
+        foreach (c; range.byChar())  { }
+        foreach (c; range.byWchar()) { }
+        foreach (c; range.byDchar()) { }
+    }
+}
+
+/****************************
+ * Iterate an input range of characters by char type C.
+ *
+ * UTF sequences that cannot be converted to UTF-8 are replaced by U+FFFD
+ * per "5.22 Best Practice for U+FFFD Substitution" of the Unicode Standard 6.2.
+ * Hence byUTF is not symmetric.
+ * This algorithm is lazy, and does not allocate memory.
+ * Purity, nothrow, and safety are inferred from the r parameter.
+ *
+ * Params:
+ *      C = char, wchar, or dchar
+ *      r = input range of characters, or array of characters
+ * Returns:
+ *      input range of type C
+ */
+template byUTF(C) if (isSomeChar!C)
+{
+    static if (!is(Unqual!C == C))
+        alias byUTF = byUTF!(Unqual!C);
+    else:
+
+    auto ref byUTF(R)(R r)
+        if (isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R))
+    {
+        return byUTF(r.byCodeUnit());
+    }
+
+    auto ref byUTF(R)(R r)
+        if (!isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R))
+    {
+        alias RC = Unqual!(ElementEncodingType!R);
+
+        static if (is(RC == C))
+        {
+            return r.byCodeUnit();
+        }
+        else
+        {
+            static struct Result
+            {
+                this(ref R r)
+                {
+                    this.r = r;
+                }
+
+                @property bool empty()
+                {
+                    return pos == fill && r.empty;
+                }
+
+                @property auto front()
+                {
+                    if (pos == fill)
+                    {
+                        pos = 0;
+                        fill = cast(ushort)encode!(UseReplacementDchar.yes)(
+                            buf, decodeFront!(UseReplacementDchar.yes)(r));
+                    }
+                    return buf[pos];
+                }
+
+                void popFront()
+                {
+                    if (pos == fill)
+                        front;
+                    ++pos;
+                }
+
+                static if (isForwardRange!R)
+                {
+                    @property auto save()
+                    {
+                        auto ret = this;
+                        ret.r = r.save;
+                        return ret;
+                    }
+                }
+
+            private:
+
+                R r;
+                C[4 / C.sizeof] buf = void;
+                ushort pos, fill;
+            }
+
+            return Result(r);
+        }
+    }
+}
+
+///
+@safe pure nothrow @nogc unittest
+{
+    foreach (c; "h".byUTF!char())
+        assert(c == 'h');
+    foreach (c; "h".byUTF!wchar())
+        assert(c == 'h');
+    foreach (c; "h".byUTF!dchar())
+        assert(c == 'h');
+}
diff --git a/libphobos/src/std/uuid.d b/libphobos/src/std/uuid.d
new file mode 100644
index 00000000000..97f48caaa6b
--- /dev/null
+++ b/libphobos/src/std/uuid.d
@@ -0,0 +1,1727 @@
+/**
+ * A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or
+ * $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier),
+ * is intended to uniquely identify information in a distributed environment
+ * without significant central coordination. It can be
+ * used to tag objects with very short lifetimes, or to reliably identify very
+ * persistent objects across a network.
+ *
+$(SCRIPT inhibitQuickIndex = 1;)
+
+$(DIVC quickindex,
+$(BOOKTABLE ,
+$(TR $(TH Category) $(TH Functions)
+)
+$(TR $(TDNW Parsing UUIDs)
+     $(TD $(MYREF parseUUID)
+          $(MYREF UUID)
+          $(MYREF UUIDParsingException)
+          $(MYREF uuidRegex)
+          )
+     )
+$(TR $(TDNW Generating UUIDs)
+     $(TD $(MYREF sha1UUID)
+          $(MYREF randomUUID)
+          $(MYREF md5UUID)
+          )
+     )
+$(TR $(TDNW Using UUIDs)
+     $(TD $(MYREF2 UUID.uuidVersion, uuidVersion)
+          $(MYREF2 UUID.variant, variant)
+          $(MYREF2 UUID.toString, toString)
+          $(MYREF2 UUID.data, data)
+          $(MYREF2 UUID.swap, swap)
+          $(MYREF2 UUID.opEquals, opEquals)
+          $(MYREF2 UUID.opCmp, opCmp)
+          $(MYREF2 UUID.toHash, toHash)
+          )
+     )
+$(TR $(TDNW UUID namespaces)
+     $(TD $(MYREF dnsNamespace)
+          $(MYREF urlNamespace)
+          $(MYREF oidNamespace)
+          $(MYREF x500Namespace)
+          )
+     )
+)
+)
+
+ * UUIDs have many applications. Some examples follow: Databases may use UUIDs to identify
+ * rows or records in order to ensure that they are unique across different
+ * databases, or for publication/subscription services. Network messages may be
+ * identified with a UUID to ensure that different parts of a message are put back together
+ * again. Distributed computing may use UUIDs to identify a remote procedure call.
+ * Transactions and classes involved in serialization may be identified by UUIDs.
+ * Microsoft's component object model (COM) uses UUIDs to distinguish different software
+ * component interfaces. UUIDs are inserted into documents from Microsoft Office programs.
+ * UUIDs identify audio or video streams in the Advanced Systems Format (ASF). UUIDs are
+ * also a basis for OIDs (object identifiers), and URNs (uniform resource name).
+ *
+ * An attractive feature of UUIDs when compared to alternatives is their relative small size,
+ * of 128 bits, or 16 bytes. Another is that the creation of UUIDs does not require
+ * a centralized authority.
+ *
+ * When UUIDs are generated by one of the defined mechanisms, they are either guaranteed
+ * to be unique, different from all other generated UUIDs (that is, it has never been
+ * generated before and it will never be generated again), or it is extremely likely
+ * to be unique (depending on the mechanism).
+ *
+ * For efficiency, UUID is implemented as a struct. UUIDs are therefore empty if not explicitly
+ * initialized. An UUID is empty if $(MYREF3 UUID.empty, empty) is true. Empty UUIDs are equal to
+ * $(D UUID.init), which is a UUID with all 16 bytes set to 0.
+ * Use UUID's constructors or the UUID generator functions to get an initialized UUID.
+ *
+ * This is a port of $(LINK2 http://www.boost.org/doc/libs/1_42_0/libs/uuid/uuid.html,
+ * boost._uuid) from the Boost project with some minor additions and API
+ * changes for a more D-like API.
+ *
+ * Standards:
+ * $(LINK2 http://www.ietf.org/rfc/rfc4122.txt, RFC 4122)
+ *
+ * See_Also:
+ * $(LINK http://en.wikipedia.org/wiki/Universally_unique_identifier)
+ *
+ * Copyright: Copyright Johannes Pfau 2011 - .
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   Johannes Pfau
+ * Source:    $(PHOBOSSRC std/_uuid.d)
+ *
+ * Macros:
+ * MYREF2 = <a href="#$2">$(TT $1)</a>&nbsp;
+ * MYREF3 = <a href="#$2">$(D $1)</a>
+ */
+/*          Copyright Johannes Pfau 2011 - 2012.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.uuid;
+
+///
+unittest
+{
+    import std.uuid;
+
+    UUID[] ids;
+    ids ~= randomUUID();
+    ids ~= md5UUID("test.name.123");
+    ids ~= sha1UUID("test.name.123");
+
+    foreach(entry; ids)
+    {
+        assert(entry.variant == UUID.Variant.rfc4122);
+    }
+    assert(ids[0].uuidVersion == UUID.Version.randomNumberBased);
+    assert(ids[1].toString() == "22390768-cced-325f-8f0f-cfeaa19d0ccd");
+    assert(ids[1].data == [34, 57, 7, 104, 204, 237, 50, 95, 143, 15, 207,
+        234, 161, 157, 12, 205]);
+    UUID id;
+    assert(id.empty);
+}
+
+import std.range.primitives;
+import std.traits;
+
+/**
+ *
+ */
+public struct UUID
+{
+    import std.meta : AliasSeq, allSatisfy;
+
+    private:
+        alias skipSeq = AliasSeq!(8, 13, 18, 23);
+        alias byteSeq = AliasSeq!(0,2,4,6,/++/9,11,/++/14,16,/++/19,21,/++/24,26,28,30,32,34);
+
+        @safe pure nothrow @nogc Char toChar(Char)(size_t i) const
+        {
+            if(i <= 9)
+                return cast(Char)('0' + i);
+            else
+                return cast(Char)('a' + (i-10));
+        }
+
+        @safe pure nothrow unittest
+        {
+            assert(UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45,
+                179, 189, 251, 70]).toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+        }
+
+        // Reinterpret the UUID as an array of some other primitive.
+        @trusted ref T[16 / T.sizeof] asArrayOf(T)() return
+        if (isIntegral!T)
+        {
+            return *cast(typeof(return)*)&data;
+        }
+
+    public:
+        /**
+         * RFC 4122 defines different internal data layouts for UUIDs. These are
+         * the UUID formats supported by this module. It's
+         * possible to read, compare and use all these Variants, but
+         * UUIDs generated by this module will always be in rfc4122 format.
+         *
+         * Note: Do not confuse this with $(XREF _variant, _Variant).
+         */
+        enum Variant
+        {
+            ncs, /// NCS backward compatibility
+            rfc4122, /// Defined in RFC 4122 document
+            microsoft, /// Microsoft Corporation backward compatibility
+            future ///Reserved for future use
+        }
+
+        /**
+         * RFC 4122 defines different UUID versions. The version shows
+         * how a UUID was generated, e.g. a version 4 UUID was generated
+         * from a random number, a version 3 UUID from an MD5 hash of a name.
+         *
+         * Note:
+         * All of these UUID versions can be read and processed by
+         * $(D std.uuid), but only version 3, 4 and 5 UUIDs can be generated.
+         */
+        enum Version
+        {
+            ///Unknown version
+            unknown = -1,
+            ///Version 1
+            timeBased = 1,
+            ///Version 2
+            dceSecurity = 2,
+            ///Version 3 (Name based + MD5)
+            nameBasedMD5 = 3,
+            ///Version 4 (Random)
+            randomNumberBased = 4,
+            ///Version 5 (Name based + SHA-1)
+            nameBasedSHA1 = 5
+        }
+
+        union
+        {
+            /**
+             * It is sometimes useful to get or set the 16 bytes of a UUID
+             * directly.
+             *
+             * Note:
+             * UUID uses a 16-ubyte representation for the UUID data.
+             * RFC 4122 defines a UUID as a special structure in big-endian
+             * format. These 16-ubytes always equal the big-endian structure
+             * defined in RFC 4122.
+             *
+             * Example:
+             * -----------------------------------------------
+             * auto rawData = uuid.data; //get data
+             * rawData[0] = 1; //modify
+             * uuid.data = rawData; //set data
+             * uuid.data[1] = 2; //modify directly
+             * -----------------------------------------------
+             */
+            ubyte[16] data;
+            private ulong[2] ulongs;
+            static if(size_t.sizeof == 4)
+                private uint[4] uints;
+        }
+
+        /*
+         * We could use a union here to also provide access to the
+         * fields specified in RFC 4122, but as we never have to access
+         * those (only necessary for version 1 (and maybe 2) UUIDs),
+         * that is not needed right now.
+         */
+
+        @safe pure unittest
+        {
+            UUID tmp;
+            tmp.data = cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,
+                13,14,15];
+            assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
+                12,13,14,15]);
+            tmp.data[2] = 3;
+            assert(tmp.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
+                12,13,14,15]);
+
+            auto tmp2 = cast(immutable UUID)tmp;
+            assert(tmp2.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
+                12,13,14,15]);
+        }
+
+        /**
+         * Construct a UUID struct from the 16 byte representation
+         * of a UUID.
+         */
+        @safe pure nothrow @nogc this(ref in ubyte[16] uuidData)
+        {
+            data = uuidData;
+        }
+        /// ditto
+        @safe pure nothrow @nogc this(in ubyte[16] uuidData)
+        {
+            data = uuidData;
+        }
+
+        ///
+        @safe pure unittest
+        {
+            enum ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
+            auto uuid = UUID(data);
+            enum ctfe = UUID(data);
+            assert(uuid.data == data);
+            assert(ctfe.data == data);
+        }
+
+        /**
+         * Construct a UUID struct from the 16 byte representation
+         * of a UUID. Variadic constructor to allow a simpler syntax, see examples.
+         * You need to pass exactly 16 ubytes.
+         */
+        @safe pure this(T...)(T uuidData)
+            if(uuidData.length == 16 && allSatisfy!(isIntegral, T))
+        {
+            import std.conv : to;
+
+            foreach(idx, it; uuidData)
+            {
+                this.data[idx] = to!ubyte(it);
+            }
+        }
+
+        ///
+        unittest
+        {
+            auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
+            assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
+                12,13,14,15]);
+        }
+
+        unittest
+        {
+            UUID tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
+            assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
+                12,13,14,15]);
+
+            enum UUID ctfeID = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
+            assert(ctfeID == tmp);
+
+            //Too few arguments
+            assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))));
+
+            //Too many arguments
+            assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1))));
+        }
+
+        /**
+         * <a name="UUID(string)"></a>
+         * Parse a UUID from its canonical string form. An UUID in its
+         * canonical form looks like this: 8ab3060e-2cba-4f23-b74c-b52db3bdfb46
+         *
+         * Throws:
+         * $(LREF UUIDParsingException) if the input is invalid
+         *
+         * CTFE:
+         * This function is supported in CTFE code. Note that error messages
+         * caused by a malformed UUID parsed at compile time can be cryptic,
+         * but errors are detected and reported at
+         * compile time.
+         *
+         * Note:
+         * This is a strict parser. It only accepts the pattern above.
+         * It doesn't support any leading or trailing characters. It only
+         * accepts characters used for hex numbers and the string must have
+         * hyphens exactly like above.
+         *
+         * For a less strict parser, see $(LREF parseUUID)
+         */
+        this(T)(in T[] uuid) if(isSomeChar!(Unqual!T))
+        {
+            import std.conv : to, parse;
+            if(uuid.length < 36)
+            {
+                throw new UUIDParsingException(to!string(uuid), 0,
+                    UUIDParsingException.Reason.tooLittle, "Insufficient Input");
+            }
+            if(uuid.length > 36)
+            {
+                throw new UUIDParsingException(to!string(uuid), 35, UUIDParsingException.Reason.tooMuch,
+                    "Input is too long, need exactly 36 characters");
+            }
+            enum skipInd = [skipSeq];
+            foreach(pos; skipInd)
+                if(uuid[pos] != '-')
+                    throw new UUIDParsingException(to!string(uuid), pos,
+                        UUIDParsingException.Reason.invalidChar, "Expected '-'");
+
+            ubyte[16] data2; //ctfe bug
+            uint pos = void;
+
+            foreach(i, p; byteSeq)
+            {
+                enum uint s = 'a'-10-'0';
+                uint h = uuid[p];
+                uint l = uuid[p+1];
+                pos = p;
+                if(h < '0') goto Lerr;
+                if(l < '0') goto Lerr;
+                if (h > '9')
+                {
+                    h |= 0x20; //poorman's tolower
+                    if (h < 'a') goto Lerr;
+                    if (h > 'f') goto Lerr;
+                    h -= s;
+                }
+                if (l > '9')
+                {
+                    l |= 0x20; //poorman's tolower
+                    if (l < 'a') goto Lerr;
+                    if (l > 'f') goto Lerr;
+                    l -= s;
+                }
+                h -= '0';
+                l -= '0';
+
+                data2[i] = cast(ubyte)((h << 4) ^ l);
+            }
+            this.data = data2;
+            return;
+
+        Lerr: throw new UUIDParsingException(to!string(uuid), pos,
+                UUIDParsingException.Reason.invalidChar, "Couldn't parse ubyte");
+        }
+
+        ///
+        @safe pure unittest
+        {
+            auto id = UUID("8AB3060E-2cba-4f23-b74c-b52db3bdfb46");
+            assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
+               181, 45, 179, 189, 251, 70]);
+            assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+
+            //Can also be used in CTFE, for example as UUID literals:
+            enum ctfeID = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+            //here parsing is done at compile time, no runtime overhead!
+        }
+
+        @safe pure unittest
+        {
+            import std.exception;
+            import std.meta;
+            import std.conv : to;
+
+            foreach(S; AliasSeq!(char[], const(char)[], immutable(char)[],
+                                  wchar[], const(wchar)[], immutable(wchar)[],
+                                  dchar[], const(dchar)[], immutable(dchar)[],
+                                  immutable(char[]), immutable(wchar[]), immutable(dchar[])))
+            {
+                //Test valid, working cases
+                assert(UUID(to!S("00000000-0000-0000-0000-000000000000")).empty);
+
+                auto id = UUID(to!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46"));
+                assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
+                    181, 45, 179, 189, 251, 70]);
+                assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+
+                enum UUID ctfe = UUID(to!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+                assert(ctfe == id);
+
+                assert(UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a")).data
+                    == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
+
+                //Test too short UUIDS
+                auto except = collectException!UUIDParsingException(
+                    UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886")));
+                assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
+
+                //Test too long UUIDS
+                except = collectException!UUIDParsingException(
+                    UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa")));
+                assert(except && except.reason == UUIDParsingException.Reason.tooMuch);
+
+                //Test dashes
+                except = collectException!UUIDParsingException(
+                    UUID(to!S("8ab3060e2cba-4f23-b74c-b52db3bdfb-46")));
+                assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+                //Test dashes 2
+                except = collectException!UUIDParsingException(
+                    UUID(to!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")));
+                assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+                //Test invalid characters
+                //make sure 36 characters in total or we'll get a 'tooMuch' reason
+                except = collectException!UUIDParsingException(
+                    UUID(to!S("{8ab3060e-2cba-4f23-b74c-b52db3bdf6}")));
+                assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+                //Boost test
+                assert(UUID(to!S("01234567-89ab-cdef-0123-456789ABCDEF"))
+                    == UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01,
+                    0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
+            }
+        }
+
+        /**
+         * Returns true if and only if the UUID is equal
+         * to {00000000-0000-0000-0000-000000000000}
+         */
+        @trusted pure nothrow @nogc @property bool empty() const
+        {
+            if(__ctfe)
+                return data == (ubyte[16]).init;
+
+            auto p = cast(const(size_t*))data.ptr;
+            static if(size_t.sizeof == 4)
+                return p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0;
+            else static if(size_t.sizeof == 8)
+                return p[0] == 0 && p[1] == 0;
+            else
+                static assert(false, "nonsense, it's not 32 or 64 bit");
+        }
+
+        ///
+        @safe pure unittest
+        {
+            UUID id;
+            assert(id.empty);
+            id = UUID("00000000-0000-0000-0000-000000000001");
+            assert(!id.empty);
+        }
+
+        @safe pure unittest
+        {
+            ubyte[16] getData(size_t i)
+            {
+                ubyte[16] data;
+                data[i] = 1;
+                return data;
+            }
+
+            for(size_t i = 0; i < 16; i++)
+            {
+                assert(!UUID(getData(i)).empty);
+            }
+
+            enum ctfeEmpty = UUID.init.empty;
+            assert(ctfeEmpty);
+
+            bool ctfeTest()
+            {
+                for(size_t i = 0; i < 16; i++)
+                {
+                    auto ctfeEmpty2 = UUID(getData(i)).empty;
+                    assert(!ctfeEmpty2);
+                }
+                return true;
+            }
+            enum res = ctfeTest();
+        }
+
+        /**
+         * RFC 4122 defines different internal data layouts for UUIDs.
+         * Returns the format used by this UUID.
+         *
+         * Note: Do not confuse this with $(XREF _variant, _Variant).
+         * The type of this property is $(MYREF3 std.uuid.UUID.Variant, _Variant).
+         *
+         * See_Also:
+         * $(MYREF3 UUID.Variant, Variant)
+         */
+        @safe pure nothrow @nogc @property Variant variant() const
+        {
+            //variant is stored in octet 7
+            //which is index 8, since indexes count backwards
+            auto octet7 = data[8]; //octet 7 is array index 8
+
+            if((octet7 & 0x80) == 0x00) //0b0xxxxxxx
+                return Variant.ncs;
+            else if((octet7 & 0xC0) == 0x80) //0b10xxxxxx
+                return Variant.rfc4122;
+            else if((octet7 & 0xE0) == 0xC0) //0b110xxxxx
+                return Variant.microsoft;
+            else
+            {
+                //assert((octet7 & 0xE0) == 0xE0, "Unknown UUID variant!") //0b111xxxx
+                return Variant.future;
+            }
+        }
+
+        ///
+        @safe pure unittest
+        {
+            assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant
+               == UUID.Variant.rfc4122);
+        }
+        pure unittest
+        {
+            Variant[ubyte] tests = cast(Variant[ubyte])[0x00 : Variant.ncs,
+                                    0x10 : Variant.ncs,
+                                    0x20 : Variant.ncs,
+                                    0x30 : Variant.ncs,
+                                    0x40 : Variant.ncs,
+                                    0x50 : Variant.ncs,
+                                    0x60 : Variant.ncs,
+                                    0x70 : Variant.ncs,
+                                    0x80 : Variant.rfc4122,
+                                    0x90 : Variant.rfc4122,
+                                    0xa0 : Variant.rfc4122,
+                                    0xb0 : Variant.rfc4122,
+                                    0xc0 : Variant.microsoft,
+                                    0xd0 : Variant.microsoft,
+                                    0xe0 : Variant.future,
+                                    0xf0 : Variant.future];
+            foreach(key, value; tests)
+            {
+                UUID u;
+                u.data[8] = key;
+                assert(u.variant == value);
+            }
+        }
+
+        /**
+         * RFC 4122 defines different UUID versions. The version shows
+         * how a UUID was generated, e.g. a version 4 UUID was generated
+         * from a random number, a version 3 UUID from an MD5 hash of a name.
+         * Returns the version used by this UUID.
+         *
+         * See_Also:
+         * $(MYREF3 UUID.Version, Version)
+         */
+        @safe pure nothrow @nogc @property Version uuidVersion() const
+        {
+            //version is stored in octet 9
+            //which is index 6, since indexes count backwards
+            auto octet9 = data[6];
+            if ((octet9 & 0xF0) == 0x10)
+                return Version.timeBased;
+            else if ((octet9 & 0xF0) == 0x20)
+                return Version.dceSecurity;
+            else if ((octet9 & 0xF0) == 0x30)
+                return Version.nameBasedMD5;
+            else if ((octet9 & 0xF0) == 0x40)
+                return Version.randomNumberBased;
+            else if ((octet9 & 0xF0) == 0x50)
+                return Version.nameBasedSHA1;
+            else
+                return Version.unknown;
+        }
+
+        ///
+        unittest
+        {
+            assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion
+                == UUID.Version.randomNumberBased);
+        }
+        unittest
+        {
+            Version[ubyte] tests = cast(Version[ubyte]) [
+                0x00 : UUID.Version.unknown,
+                0x10 : UUID.Version.timeBased,
+                0x20 : UUID.Version.dceSecurity,
+                0x30 : UUID.Version.nameBasedMD5,
+                0x40 : UUID.Version.randomNumberBased,
+                0x50 : UUID.Version.nameBasedSHA1,
+                0x60 : UUID.Version.unknown,
+                0x70 : UUID.Version.unknown,
+                0x80 : UUID.Version.unknown,
+                0x90 : UUID.Version.unknown,
+                0xa0 : UUID.Version.unknown,
+                0xb0 : UUID.Version.unknown,
+                0xc0 : UUID.Version.unknown,
+                0xd0 : UUID.Version.unknown,
+                0xe0 : UUID.Version.unknown,
+                0xf0 : UUID.Version.unknown];
+            foreach(key, value; tests)
+            {
+                UUID u;
+                u.data[6] = key;
+                assert(u.uuidVersion == value);
+            }
+        }
+
+        /**
+         * Swap the data of this UUID with the data of rhs.
+         */
+        @safe pure nothrow @nogc void swap(ref UUID rhs)
+        {
+            auto bck = data;
+            data = rhs.data;
+            rhs.data = bck;
+        }
+
+        ///
+        unittest
+        {
+            immutable ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
+            UUID u1;
+            UUID u2 = UUID(data);
+            u1.swap(u2);
+
+            assert(u1 == UUID(data));
+            assert(u2 == UUID.init);
+        }
+
+        /**
+         * All of the standard numeric operators are defined for
+         * the UUID struct.
+         */
+        @safe pure nothrow @nogc bool opEquals(in UUID s) const
+        {
+            return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
+        }
+
+        ///
+        pure unittest
+        {
+            //compare UUIDs
+            assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
+
+            //UUIDs in associative arrays:
+            int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
+                UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
+                UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
+
+            assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
+
+            //UUIDS can be sorted:
+            import std.algorithm;
+            UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
+                          UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
+                          UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
+            sort(ids);
+        }
+
+        /**
+         * ditto
+         */
+        @safe pure nothrow @nogc bool opEquals(ref in UUID s) const
+        {
+            return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
+        }
+
+        /**
+         * ditto
+         */
+        @safe pure nothrow @nogc int opCmp(in UUID s) const
+        {
+            import std.algorithm : cmp;
+            return cmp(this.data[], s.data[]);
+        }
+
+        /**
+         * ditto
+         */
+        @safe pure nothrow @nogc int opCmp(ref in UUID s) const
+        {
+            import std.algorithm : cmp;
+            return cmp(this.data[], s.data[]);
+        }
+
+        /**
+         * ditto
+         */
+       @safe pure nothrow @nogc UUID opAssign(in UUID s)
+        {
+            ulongs[0] = s.ulongs[0];
+            ulongs[1] = s.ulongs[1];
+            return this;
+        }
+
+        /**
+         * ditto
+         */
+        @safe pure nothrow @nogc UUID opAssign(ref in UUID s)
+        {
+            ulongs[0] = s.ulongs[0];
+            ulongs[1] = s.ulongs[1];
+            return this;
+        }
+
+        /**
+         * ditto
+         */
+        //MurmurHash2
+        @safe pure nothrow @nogc size_t toHash() const
+        {
+            static if(size_t.sizeof == 4)
+            {
+                enum uint m = 0x5bd1e995;
+                enum uint n = 16;
+                enum uint r = 24;
+
+                uint h = n;
+
+                uint k = uints[0];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+
+                k = uints[1];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+
+                k = uints[2];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+
+                k = uints[3];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+            }
+            else
+            {
+                enum ulong m = 0xc6a4a7935bd1e995UL;
+                enum ulong n = m * 16;
+                enum uint r = 47;
+
+                ulong h = n;
+
+                ulong k = ulongs[0];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+
+                k = ulongs[1];
+                k *= m;
+                k ^= k >> r;
+                k *= m;
+
+                h ^= k;
+                h *= m;
+            }
+            return h;
+        }
+        unittest
+        {
+            assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
+            int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
+                UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
+                UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
+
+            assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
+
+            import std.algorithm;
+            UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
+                          UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
+                          UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
+            sort(ids);
+            auto id2 = ids.dup;
+
+            ids = [UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
+                   UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
+                   UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
+            sort(ids);
+            assert(ids == id2);
+
+            //test comparsion
+            UUID u1;
+            UUID u2 = UUID(cast(ubyte[16])[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
+            UUID u3 = UUID(cast(ubyte[16])[255,255,255,255,255,255,255,255,255,
+                255,255,255,255,255,255,255]);
+
+            assert(u1 == u1);
+
+            assert(u1 != u2);
+
+            assert(u1 < u2);
+            assert(u2 < u3);
+
+            assert(u1 <= u1);
+            assert(u1 <= u2);
+            assert(u2 <= u3);
+
+            assert(u2 >= u2);
+            assert(u3 >= u2);
+
+            assert(u3 >= u3);
+            assert(u2 >= u1);
+            assert(u3 >= u1);
+
+            // test hash
+            assert(u1.toHash() != u2.toHash());
+            assert(u2.toHash() != u3.toHash());
+            assert(u3.toHash() != u1.toHash());
+        }
+
+        /**
+         * Write the UUID into `result` as an ASCII string in the canonical form.
+         */
+        @safe pure nothrow @nogc void toString(Range)(Range result) const
+            if (isRandomAccessRange!Range && hasAssignableElements!Range && hasLength!Range && (isSomeChar!(typeof(Range.init[0])) || isUnsigned!(typeof(Range.init[0])))
+                || isSomeString!Range && isMutable!(typeof(Range.init[0])))
+        in {
+            assert(result.length >= 36, "Result's length for UUID.toString must be greater or equal 36.");
+        }
+        body {
+            alias Char = typeof(Range.init[0]);
+            foreach(pos; skipSeq)
+                result[pos] = cast(Char)('-');
+            foreach(i, pos; byteSeq)
+            {
+                const uint entry = this.data[i];
+                const uint hi = entry >> 4;
+                result[pos  ] = toChar!Char(hi);
+                const uint lo = (entry) & 0x0F;
+                result[pos+1] = toChar!Char(lo);
+            }
+        }
+
+        /**
+         * Call a delegate with a string in the canonical form.
+         */
+        void toString(scope void delegate(const(char)[]) sink) const
+        {
+            char[36] result = void;
+            toString(result[]);
+            sink(result[]);
+        }
+
+        /**
+         * Return the UUID as a string in the canonical form.
+         */
+        @trusted pure nothrow string toString() const
+        {
+            import std.exception: assumeUnique;
+            auto result = new char[36];
+            toString(result);
+            return result.assumeUnique;
+        }
+
+        ///
+        @safe pure unittest
+        {
+            immutable str = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
+            auto id = UUID(str);
+            assert(id.toString() == str);
+        }
+
+        @safe pure nothrow @nogc unittest
+        {
+            import std.meta: AliasSeq;
+            foreach(Char; AliasSeq!(char, wchar, dchar))
+            {
+                alias String = immutable(Char)[];
+                //CTFE
+                enum String s = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
+                enum id = UUID(s);
+                static if(is(Char == char))
+                {
+                    enum p = id.toString();
+                    static assert(s == p);
+                }
+                //nogc
+                Char[36] str;
+                id.toString(str[]);
+                assert(str == s);
+            }
+        }
+
+        pure nothrow @nogc unittest
+        {
+            import std.encoding: Char = AsciiChar;
+            enum  utfstr = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
+            alias String = immutable(Char)[];
+            enum String s = cast(String)utfstr;
+            enum id = UUID(utfstr);
+            //nogc
+            Char[36] str;
+            id.toString(str[]);
+            assert(str == s);
+        }
+
+        unittest
+        {
+            auto u1 = UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79,
+                35, 183, 76, 181, 45, 179, 189, 251, 70]);
+            assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+            u1 = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+            assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+
+            char[] buf;
+            void sink(const(char)[] data)
+            {
+                buf ~= data;
+            }
+            u1.toString(&sink);
+            assert(buf == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+        }
+}
+
+
+/**
+ * This function generates a name based (Version 3) UUID from a namespace UUID and a name.
+ * If no namespace UUID was passed, the empty UUID $(D UUID.init) is used.
+ *
+ * Note:
+ * The default namespaces ($(LREF dnsNamespace), ...) defined by
+ * this module should be used when appropriate.
+ *
+ * RFC 4122 recommends to use Version 5 UUIDs (SHA-1) instead of Version 3
+ * UUIDs (MD5) for new applications.
+ *
+ * CTFE:
+ * CTFE is not supported.
+ *
+ * Note:
+ * RFC 4122 isn't very clear on how UUIDs should be generated from names.
+ * It is possible that different implementations return different UUIDs
+ * for the same input, so be warned. The implementation for UTF-8 strings
+ * and byte arrays used by $(D std.uuid) is compatible with Boost's implementation.
+ * $(D std.uuid) guarantees that the same input to this function will generate
+ * the same output at any time, on any system (this especially means endianness
+ * doesn't matter).
+ *
+ * Note:
+ * This function does not provide overloads for wstring and dstring, as
+ * there's no clear answer on how that should be implemented. It could be
+ * argued, that string, wstring and dstring input should have the same output,
+ * but that wouldn't be compatible with Boost, which generates different output
+ * for strings and wstrings. It's always possible to pass wstrings and dstrings
+ * by using the ubyte[] function overload (but be aware of endianness issues!).
+ */
+@safe pure nothrow @nogc UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init)
+{
+    return md5UUID(cast(const(ubyte[]))name, namespace);
+}
+
+/**
+ * ditto
+ */
+@safe pure nothrow @nogc UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init)
+{
+    import std.digest.md : MD5;
+
+    MD5 hash;
+    hash.start();
+
+    /*
+     * NOTE: RFC 4122 says namespace should be converted to big-endian.
+     * We always keep the UUID data in big-endian representation, so
+     * that's fine
+     */
+    hash.put(namespace.data[]);
+    hash.put(data[]);
+
+    UUID u;
+    u.data = hash.finish();
+
+    //set variant
+    //must be 0b10xxxxxx
+    u.data[8] &= 0b10111111;
+    u.data[8] |= 0b10000000;
+
+    //set version
+    //must be 0b0011xxxx
+    u.data[6] &= 0b00111111;
+    u.data[6] |= 0b00110000;
+
+    return u;
+}
+
+///
+unittest
+{
+    //Use default UUID.init namespace
+    auto simpleID = md5UUID("test.uuid.any.string");
+
+    //use a name-based id as namespace
+    auto namespace = md5UUID("my.app");
+    auto id = md5UUID("some-description", namespace);
+}
+
+@safe pure unittest
+{
+    auto simpleID = md5UUID("test.uuid.any.string");
+    assert(simpleID.data == cast(ubyte[16])[126, 206, 86, 72, 29, 233, 62, 213, 178, 139, 198, 136,
+        188, 135, 153, 123]);
+    auto namespace = md5UUID("my.app");
+    auto id = md5UUID("some-description", namespace);
+    assert(id.data == cast(ubyte[16])[166, 138, 167, 79, 48, 219, 55, 166, 170, 103, 39, 73, 216,
+        150, 144, 164]);
+
+    auto constTest = md5UUID(cast(const(char)[])"test");
+    constTest = md5UUID(cast(const(char[]))"test");
+
+    char[] mutable = "test".dup;
+    id = md5UUID(mutable, namespace);
+
+    const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
+    id = md5UUID(data);
+    assert(id.data == cast(ubyte[16])[16, 50, 29, 247, 243, 185, 61, 178, 157, 100, 253, 236, 73,
+        76, 51, 47]);
+
+    assert(id.variant == UUID.Variant.rfc4122);
+    assert(id.uuidVersion == UUID.Version.nameBasedMD5);
+
+    auto correct = UUID("3d813cbb-47fb-32ba-91df-831e1593ac29");
+
+    auto u = md5UUID("www.widgets.com", dnsNamespace);
+    //enum ctfeId = md5UUID("www.widgets.com", dnsNamespace);
+    //assert(ctfeId == u);
+    assert(u == correct);
+    assert(u.variant == UUID.Variant.rfc4122);
+    assert(u.uuidVersion == UUID.Version.nameBasedMD5);
+}
+
+ /**
+ * This function generates a name based (Version 5) UUID from a namespace
+ * UUID and a name.
+ * If no namespace UUID was passed, the empty UUID $(D UUID.init) is used.
+ *
+ * Note:
+ * The default namespaces ($(LREF dnsNamespace), ...) defined by
+ * this module should be used when appropriate.
+ *
+ * CTFE:
+ * CTFE is not supported.
+ *
+ * Note:
+ * RFC 4122 isn't very clear on how UUIDs should be generated from names.
+ * It is possible that different implementations return different UUIDs
+ * for the same input, so be warned. The implementation for UTF-8 strings
+ * and byte arrays used by $(D std.uuid) is compatible with Boost's implementation.
+ * $(D std.uuid) guarantees that the same input to this function will generate
+ * the same output at any time, on any system (this especially means endianness
+ * doesn't matter).
+ *
+ * Note:
+ * This function does not provide overloads for wstring and dstring, as
+ * there's no clear answer on how that should be implemented. It could be
+ * argued, that string, wstring and dstring input should have the same output,
+ * but that wouldn't be compatible with Boost, which generates different output
+ * for strings and wstrings. It's always possible to pass wstrings and dstrings
+ * by using the ubyte[] function overload (but be aware of endianness issues!).
+ */
+@safe pure nothrow @nogc UUID sha1UUID(in char[] name, const UUID namespace = UUID.init)
+{
+    return sha1UUID(cast(const(ubyte[]))name, namespace);
+}
+
+/**
+ * ditto
+ */
+@safe pure nothrow @nogc UUID sha1UUID(in ubyte[] data, const UUID namespace = UUID.init)
+{
+    import std.digest.sha : SHA1;
+
+    SHA1 sha;
+    sha.start();
+
+    /*
+     * NOTE: RFC 4122 says namespace should be converted to big-endian.
+     * We always keep the UUID data in big-endian representation, so
+     * that's fine
+     */
+    sha.put(namespace.data[]);
+    sha.put(data[]);
+
+    auto hash = sha.finish();
+    auto u = UUID();
+    u.data[] = hash[0 .. 16];
+
+    //set variant
+    //must be 0b10xxxxxx
+    u.data[8] &= 0b10111111;
+    u.data[8] |= 0b10000000;
+
+    //set version
+    //must be 0b0101xxxx
+    u.data[6] &= 0b01011111;
+    u.data[6] |= 0b01010000;
+
+    return u;
+}
+
+///
+unittest
+{
+    //Use default UUID.init namespace
+    auto simpleID = sha1UUID("test.uuid.any.string");
+
+    //use a name-based id as namespace
+    auto namespace = sha1UUID("my.app");
+    auto id = sha1UUID("some-description", namespace);
+}
+
+@safe pure unittest
+{
+    auto simpleID = sha1UUID("test.uuid.any.string");
+    assert(simpleID.data == cast(ubyte[16])[16, 209, 239, 61, 99, 12, 94, 70, 159, 79, 255, 250,
+        131, 79, 14, 147]);
+    auto namespace = sha1UUID("my.app");
+    auto id = sha1UUID("some-description", namespace);
+    assert(id.data == cast(ubyte[16])[225, 94, 195, 219, 126, 75, 83, 71, 157, 52, 247, 43, 238, 248,
+        148, 46]);
+
+    auto constTest = sha1UUID(cast(const(char)[])"test");
+    constTest = sha1UUID(cast(const(char[]))"test");
+
+    char[] mutable = "test".dup;
+    id = sha1UUID(mutable, namespace);
+
+    const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
+    id = sha1UUID(data);
+    assert(id.data == cast(ubyte[16])[60, 65, 92, 240, 96, 46, 95, 238, 149, 100, 12, 64, 199, 194,
+        243, 12]);
+
+    auto correct = UUID("21f7f8de-8051-5b89-8680-0195ef798b6a");
+
+    auto u = sha1UUID("www.widgets.com", dnsNamespace);
+    assert(u == correct);
+    assert(u.variant == UUID.Variant.rfc4122);
+    assert(u.uuidVersion == UUID.Version.nameBasedSHA1);
+}
+
+/**
+ * This function generates a random number based UUID from a random
+ * number generator.
+ *
+ * CTFE:
+ * This function is not supported at compile time.
+ *
+ */
+@safe UUID randomUUID()
+{
+    import std.random : rndGen;
+    return randomUUID(rndGen);
+}
+
+/**
+ * ditto
+ */
+/**
+ * Params:
+ *      randomGen = uniform RNG
+ * See_Also: $(XREF random, isUniformRNG)
+ */
+UUID randomUUID(RNG)(ref RNG randomGen)
+if (isInputRange!RNG && isIntegral!(ElementType!RNG))
+{
+    import std.random : isUniformRNG;
+    static assert (isUniformRNG!RNG, "randomGen must be a uniform RNG");
+
+    alias E = ElementEncodingType!RNG;
+    enum size_t elemSize = E.sizeof;
+    static assert (elemSize <= 16);
+    static assert (16 % elemSize == 0);
+
+    UUID u;
+    foreach (ref E e ; u.asArrayOf!E())
+    {
+        e = randomGen.front;
+        randomGen.popFront();
+    }
+
+    //set variant
+    //must be 0b10xxxxxx
+    u.data[8] &= 0b10111111;
+    u.data[8] |= 0b10000000;
+
+    //set version
+    //must be 0b0100xxxx
+    u.data[6] &= 0b01001111;
+    u.data[6] |= 0b01000000;
+
+    return u;
+}
+
+///
+@safe unittest
+{
+    import std.random : Xorshift192, unpredictableSeed;
+
+    //simple call
+    auto uuid = randomUUID();
+
+    //provide a custom RNG. Must be seeded manually.
+    Xorshift192 gen;
+
+    gen.seed(unpredictableSeed);
+    auto uuid3 = randomUUID(gen);
+}
+
+/*
+ * Original boost.uuid used Mt19937, we don't want
+ * to use anything worse than that. If Random is changed
+ * to something else, this assert and the randomUUID function
+ * have to be updated.
+ */
+@safe unittest
+{
+    import std.random : rndGen, Mt19937;
+    static assert(is(typeof(rndGen) == Mt19937));
+}
+
+@safe unittest
+{
+    import std.random : Xorshift192, unpredictableSeed;
+    //simple call
+    auto uuid = randomUUID();
+
+    //provide a custom RNG. Must be seeded manually.
+    Xorshift192 gen;
+    gen.seed(unpredictableSeed);
+    auto uuid3 = randomUUID(gen);
+
+    auto u1 = randomUUID();
+    auto u2 = randomUUID();
+    assert(u1 != u2);
+    assert(u1.variant == UUID.Variant.rfc4122);
+    assert(u1.uuidVersion == UUID.Version.randomNumberBased);
+}
+
+/**
+ * This is a less strict parser compared to the parser used in the
+ * UUID constructor. It enforces the following rules:
+ *
+ * $(UL
+ *   $(LI hex numbers are always two hexdigits([0-9a-fA-F]))
+ *   $(LI there must be exactly 16 such pairs in the input, not less, not more)
+ *   $(LI there can be exactly one dash between two hex-pairs, but not more)
+ *   $(LI there can be multiple characters enclosing the 16 hex pairs,
+ *     as long as these characters do not contain [0-9a-fA-F])
+ * )
+ *
+ * Note:
+ * Like most parsers, it consumes its argument. This means:
+ * -------------------------
+ * string s = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46";
+ * parseUUID(s);
+ * assert(s == "");
+ * -------------------------
+ *
+ * Throws:
+ * $(LREF UUIDParsingException) if the input is invalid
+ *
+ * CTFE:
+ * This function is supported in CTFE code. Note that error messages
+ * caused by a malformed UUID parsed at compile time can be cryptic,
+ * but errors are detected and reported at compile time.
+ */
+UUID parseUUID(T)(T uuidString) if(isSomeString!T)
+{
+    return parseUUID(uuidString);
+}
+
+///ditto
+UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range
+    && is(Unqual!(ElementType!Range) == dchar))
+{
+    import std.conv : ConvException, parse;
+    import std.ascii : isHexDigit;
+
+    static if(isForwardRange!Range)
+        auto errorCopy = uuidRange.save;
+
+    void parserError()(size_t pos, UUIDParsingException.Reason reason, string message, Throwable next = null,
+        string file = __FILE__, size_t line = __LINE__)
+    {
+        static if(isForwardRange!Range)
+        {
+            import std.conv : to;
+            static if(isInfinite!Range)
+            {
+                throw new UUIDParsingException(to!string(take(errorCopy, pos)), pos, reason, message,
+                    next, file, line);
+            }
+            else
+            {
+                throw new UUIDParsingException(to!string(errorCopy), pos, reason, message, next, file,
+                    line);
+            }
+        }
+        else
+        {
+            throw new UUIDParsingException("", pos, reason, message, next, file, line);
+        }
+    }
+
+    static if(hasLength!Range)
+    {
+        import std.conv : to;
+        if(uuidRange.length < 32)
+        {
+            throw new UUIDParsingException(to!string(uuidRange), 0, UUIDParsingException.Reason.tooLittle,
+                "Insufficient Input");
+        }
+    }
+
+    UUID result;
+    size_t consumed;
+    size_t element = 0;
+
+    //skip garbage
+    size_t skip()()
+    {
+        size_t skipped;
+        while(!uuidRange.empty && !isHexDigit(uuidRange.front))
+        {
+            skipped++;
+            uuidRange.popFront();
+        }
+        return skipped;
+    }
+
+    consumed += skip();
+
+    if(uuidRange.empty)
+        parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
+
+    bool dashAllowed = false;
+
+    parseLoop: while(!uuidRange.empty)
+    {
+        dchar character = uuidRange.front;
+
+        if(character == '-')
+        {
+            if(!dashAllowed)
+                parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected '-'");
+            else
+                dashAllowed = false;
+
+            consumed++;
+        }
+        else if(!isHexDigit(character))
+        {
+            parserError(consumed, UUIDParsingException.Reason.invalidChar,
+                "Unexpected character (wanted a hexDigit)");
+        }
+        else
+        {
+            try
+            {
+                consumed += 2;
+                static if(isSomeString!Range)
+                {
+                    if(uuidRange.length < 2)
+                    {
+                        parserError(consumed, UUIDParsingException.Reason.tooLittle,
+                            "Insufficient Input");
+                    }
+                    auto part = uuidRange[0 .. 2];
+                    result.data[element++] = parse!ubyte(part, 16);
+                    uuidRange.popFront();
+                }
+                else
+                {
+                    dchar[2] copyBuf;
+                    copyBuf[0] = character;
+                    uuidRange.popFront();
+                    if(uuidRange.empty)
+                    {
+                        parserError(consumed, UUIDParsingException.Reason.tooLittle,
+                            "Insufficient Input");
+                    }
+                    copyBuf[1] = uuidRange.front;
+                    auto part = copyBuf[];
+                    result.data[element++] = parse!ubyte(part, 16);
+                }
+
+                if(element == 16)
+                {
+                    uuidRange.popFront();
+                    break parseLoop;
+                }
+
+                dashAllowed = true;
+            }
+            catch(ConvException e)
+            {
+                parserError(consumed, UUIDParsingException.Reason.invalidChar,
+                    "Couldn't parse ubyte", e);
+            }
+        }
+        uuidRange.popFront();
+    }
+    assert(element <= 16);
+
+    if(element < 16)
+        parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
+
+    consumed += skip();
+    if(!uuidRange.empty)
+        parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected character");
+
+    return result;
+}
+
+///
+unittest
+{
+    auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
+    //no dashes
+    id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46");
+    //dashes at different positions
+    id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
+    //leading / trailing characters
+    id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
+    //unicode
+    id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
+    //multiple trailing/leading characters
+    id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||");
+
+    //Can also be used in CTFE, for example as UUID literals:
+    enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+    //here parsing is done at compile time, no runtime overhead!
+}
+
+@safe pure unittest
+{
+    import std.exception;
+    import std.meta;
+    import std.conv : to;
+
+    struct TestRange(bool forward)
+    {
+        dstring input;
+
+        @property dchar front()
+        {
+            return input.front;
+        }
+
+        void popFront()
+        {
+            input.popFront();
+        }
+
+        @property bool empty()
+        {
+            return input.empty;
+        }
+
+        static if(forward)
+        {
+            @property TestRange!true save()
+            {
+                return this;
+            }
+        }
+    }
+    alias TestInputRange = TestRange!false;
+    alias TestForwardRange = TestRange!true;
+
+    assert(isInputRange!TestInputRange);
+    assert(is(ElementType!TestInputRange == dchar));
+    assert(isInputRange!TestForwardRange);
+    assert(isForwardRange!TestForwardRange);
+    assert(is(ElementType!TestForwardRange == dchar));
+
+    //Helper function for unittests - Need to pass ranges by ref
+    UUID parseHelper(T)(string input)
+    {
+        static if(is(T == TestInputRange) || is(T == TestForwardRange))
+        {
+            T range = T(to!dstring(input));
+            return parseUUID(range);
+        }
+        else
+            return parseUUID(to!T(input));
+    }
+
+    foreach(S; AliasSeq!(char[], const(char)[], immutable(char)[],
+                          wchar[], const(wchar)[], immutable(wchar)[],
+                          dchar[], const(dchar)[], immutable(dchar)[],
+                          immutable(char[]), immutable(wchar[]), immutable(dchar[]),
+                          TestForwardRange, TestInputRange))
+    {
+        //Verify examples.
+        auto id = parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
+        //no dashes
+        id = parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46");
+        //dashes at different positions
+        id = parseHelper!S("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
+        //leading / trailing characters
+        id = parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
+        //unicode
+        id = parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
+        //multiple trailing/leading characters
+        id = parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||");
+        enum ctfeId = parseHelper!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
+        assert(parseHelper!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46") == ctfeId);
+
+        //Test valid, working cases
+        assert(parseHelper!S("00000000-0000-0000-0000-000000000000").empty);
+        assert(parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46").data
+            == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]);
+
+        assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
+            == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
+
+        //wstring / dstring
+        assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
+            == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
+        assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
+            == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
+
+        //Test too short UUIDS
+        auto except = collectException!UUIDParsingException(
+            parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886"));
+        assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
+
+        //Test too long UUIDS
+        except = collectException!UUIDParsingException(
+            parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa"));
+        assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+        //Test too long UUIDS 2
+        except = collectException!UUIDParsingException(
+            parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a-aa"));
+        assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+        //Test dashes
+        assert(parseHelper!S("8ab3060e2cba-4f23-b74c-b52db3bdfb46")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+        assert(parseHelper!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+        assert(parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+
+        except = collectException!UUIDParsingException(
+            parseHelper!S("8-ab3060e2cba-4f23-b74c-b52db3bdfb46"));
+        assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
+
+        //Test leading/trailing characters
+        assert(parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+        assert(parseHelper!S("{8ab3060e2cba4f23b74cb52db3bdfb46}")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+
+        //Boost test
+        auto u_increasing = UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
+            0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
+        assert(parseHelper!S("0123456789abcdef0123456789ABCDEF") == UUID(cast(ubyte[16])[0x01,
+            0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
+
+        //unicode
+        assert(parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+
+        //multiple trailing/leading characters
+        assert(parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||")
+            == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
+    }
+}
+
+/**
+ * Default namespace from RFC 4122
+ *
+ * Name string is a fully-qualified domain name
+ */
+enum dnsNamespace = UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+
+/**
+ * Default namespace from RFC 4122
+ *
+ * Name string is a URL
+ */
+enum urlNamespace = UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
+
+/**
+ * Default namespace from RFC 4122
+ *
+ * Name string is an ISO OID
+ */
+enum oidNamespace = UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
+
+/**
+ * Default namespace from RFC 4122
+ *
+ * Name string is an X.500 DN (in DER or a text output format)
+ */
+enum x500Namespace = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
+
+/**
+ * Regex string to extract UUIDs from text.
+ */
+enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
+    "-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}";
+
+///
+unittest
+{
+    import std.algorithm;
+    import std.regex;
+
+    string test = "Lorem ipsum dolor sit amet, consetetur "~
+    "6ba7b814-9dad-11d1-80b4-00c04fd430c8 sadipscing \n"~
+    "elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \r\n"~
+    "magna aliquyam erat, sed diam voluptua. "~
+    "8ab3060e-2cba-4f23-b74c-b52db3bdfb46 At vero eos et accusam et "~
+    "justo duo dolores et ea rebum.";
+
+    auto r = regex(uuidRegex, "g");
+    UUID[] found;
+    foreach(c; match(test, r))
+    {
+        found ~= UUID(c.hit);
+    }
+    assert(found == [
+        UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"),
+        UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"),
+    ]);
+}
+
+/**
+ * This exception is thrown if an error occurs when parsing a UUID
+ * from a string.
+ */
+public class UUIDParsingException : Exception
+{
+    /**
+     * The reason why parsing the UUID string failed (if known)
+     */
+    enum Reason
+    {
+        unknown, ///
+        tooLittle, ///The passed in input was correct, but more input was expected.
+        tooMuch, ///The input data is too long (There's no guarantee the first part of the data is valid)
+        invalidChar, ///Encountered an invalid character
+
+    }
+    ///ditto
+    Reason reason;
+    ///The original input string which should have been parsed.
+    string input;
+    ///The position in the input string where the error occurred.
+    size_t position;
+
+    private this(string input, size_t pos, Reason why = Reason.unknown, string msg = "",
+        Throwable next = null, string file = __FILE__, size_t line = __LINE__) pure @trusted
+    {
+        import std.array : replace;
+        import std.format : format;
+        this.input = input;
+        this.position = pos;
+        this.reason = why;
+        string message = format("An error occured in the UUID parser: %s\n" ~
+          " * Input:\t'%s'\n * Position:\t%s", msg, replace(replace(input,
+          "\r", "\\r"), "\n", "\\n"), pos);
+        super(message, file, line, next);
+    }
+}
+
+///
+unittest
+{
+    auto ex = new UUIDParsingException("foo", 10, UUIDParsingException.Reason.tooMuch);
+    assert(ex.input == "foo");
+    assert(ex.position == 10);
+    assert(ex.reason == UUIDParsingException.Reason.tooMuch);
+}
diff --git a/libphobos/src/std/variant.d b/libphobos/src/std/variant.d
new file mode 100644
index 00000000000..c41e2bcf4ae
--- /dev/null
+++ b/libphobos/src/std/variant.d
@@ -0,0 +1,2620 @@
+// Written in the D programming language.
+
+/**
+This module implements a
+$(WEB erdani.org/publications/cuj-04-2002.html,discriminated union)
+type (a.k.a.
+$(WEB en.wikipedia.org/wiki/Tagged_union,tagged union),
+$(WEB en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)).
+Such types are useful
+for type-uniform binary interfaces, interfacing with scripting
+languages, and comfortable exploratory programming.
+
+Macros:
+ WIKI = Phobos/StdVariant
+
+Synopsis:
+----
+Variant a; // Must assign before use, otherwise exception ensues
+// Initialize with an integer; make the type int
+Variant b = 42;
+assert(b.type == typeid(int));
+// Peek at the value
+assert(b.peek!(int) !is null && *b.peek!(int) == 42);
+// Automatically convert per language rules
+auto x = b.get!(real);
+// Assign any other type, including other variants
+a = b;
+a = 3.14;
+assert(a.type == typeid(double));
+// Implicit conversions work just as with built-in types
+assert(a < b);
+// Check for convertibility
+assert(!a.convertsTo!(int)); // double not convertible to int
+// Strings and all other arrays are supported
+a = "now I'm a string";
+assert(a == "now I'm a string");
+a = new int[42]; // can also assign arrays
+assert(a.length == 42);
+a[5] = 7;
+assert(a[5] == 7);
+// Can also assign class values
+class Foo {}
+auto foo = new Foo;
+a = foo;
+assert(*a.peek!(Foo) == foo); // and full type information is preserved
+----
+
+A $(LREF Variant) object can hold a value of any type, with very few
+restrictions (such as `shared` types and noncopyable types). Setting the value
+is as immediate as assigning to the `Variant` object. To read back the value of
+the appropriate type `T`, use the $(LREF get!T) call. To query whether a
+`Variant` currently holds a value of type `T`, use $(LREF peek!T). To fetch the
+exact type currently held, call $(LREF type), which returns the `TypeInfo` of
+the current value.
+
+In addition to $(LREF Variant), this module also defines the $(LREF Algebraic)
+type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of
+types, which are specified in the instantiation (e.g. $(D Algebraic!(int,
+string)) may only hold an `int` or a `string`).
+
+Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review
+prompting the following improvements: (1) better support for arrays; (2) support
+for associative arrays; (3) friendlier behavior towards the garbage collector.
+Copyright: Copyright Andrei Alexandrescu 2007 - 2015.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   $(WEB erdani.org, Andrei Alexandrescu)
+Source:    $(PHOBOSSRC std/_variant.d)
+*/
+module std.variant;
+
+import core.stdc.string, std.conv, std.exception, std.meta, std.traits,
+    std.typecons;
+
+/++
+    Gives the $(D sizeof) the largest type given.
+  +/
+template maxSize(T...)
+{
+    static if (T.length == 1)
+    {
+        enum size_t maxSize = T[0].sizeof;
+    }
+    else
+    {
+        import std.algorithm.comparison : max;
+        enum size_t maxSize = max(T[0].sizeof, maxSize!(T[1 .. $]));
+    }
+}
+
+struct This;
+
+private alias This2Variant(V, T...) = AliasSeq!(ReplaceType!(This, V, T));
+
+/**
+ * $(D VariantN) is a back-end type seldom used directly by user
+ * code. Two commonly-used types using $(D VariantN) as
+ * back-end are:
+ *
+ * $(OL $(LI $(B Algebraic): A closed discriminated union with a
+ * limited type universe (e.g., $(D Algebraic!(int, double,
+ * string)) only accepts these three types and rejects anything
+ * else).) $(LI $(B Variant): An open discriminated union allowing an
+ * unbounded set of types. If any of the types in the $(D Variant)
+ * are larger than the largest built-in type, they will automatically
+ * be boxed. This means that even large types will only be the size
+ * of a pointer within the $(D Variant), but this also implies some
+ * overhead. $(D Variant) can accommodate all primitive types and
+ * all user-defined types.))
+ *
+ * Both $(D Algebraic) and $(D Variant) share $(D
+ * VariantN)'s interface. (See their respective documentations below.)
+ *
+ * $(D VariantN) is a discriminated union type parameterized
+ * with the largest size of the types stored ($(D maxDataSize))
+ * and with the list of allowed types ($(D AllowedTypes)). If
+ * the list is empty, then any type up of size up to $(D
+ * maxDataSize) (rounded up for alignment) can be stored in a
+ * $(D VariantN) object without being boxed (types larger
+ * than this will be boxed).
+ *
+ */
+struct VariantN(size_t maxDataSize, AllowedTypesParam...)
+{
+    /**
+    The list of allowed types. If empty, any type is allowed.
+    */
+    alias AllowedTypes = This2Variant!(VariantN, AllowedTypesParam);
+
+private:
+    // Compute the largest practical size from maxDataSize
+    struct SizeChecker
+    {
+        int function() fptr;
+        ubyte[maxDataSize] data;
+    }
+    enum size = SizeChecker.sizeof - (int function()).sizeof;
+
+    /** Tells whether a type $(D T) is statically allowed for
+     * storage inside a $(D VariantN) object by looking
+     * $(D T) up in $(D AllowedTypes).
+     */
+    public template allowed(T)
+    {
+        enum bool allowed
+            = is(T == VariantN)
+            ||
+            //T.sizeof <= size &&
+            (AllowedTypes.length == 0 || staticIndexOf!(T, AllowedTypes) >= 0);
+    }
+
+    // Each internal operation is encoded with an identifier. See
+    // the "handler" function below.
+    enum OpID { getTypeInfo, get, compare, equals, testConversion, toString,
+            index, indexAssign, catAssign, copyOut, length,
+            apply, postblit, destruct }
+
+    // state
+    ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr
+        = &handler!(void);
+    union
+    {
+        ubyte[size] store;
+        // conservatively mark the region as pointers
+        static if (size >= (void*).sizeof)
+            void*[size / (void*).sizeof] p;
+    }
+
+    // internals
+    // Handler for an uninitialized value
+    static ptrdiff_t handler(A : void)(OpID selector, ubyte[size]*, void* parm)
+    {
+        switch (selector)
+        {
+        case OpID.getTypeInfo:
+            *cast(TypeInfo *) parm = typeid(A);
+            break;
+        case OpID.copyOut:
+            auto target = cast(VariantN *) parm;
+            target.fptr = &handler!(A);
+            // no need to copy the data (it's garbage)
+            break;
+        case OpID.compare:
+        case OpID.equals:
+            auto rhs = cast(const VariantN *) parm;
+            return rhs.peek!(A)
+                ? 0 // all uninitialized are equal
+                : ptrdiff_t.min; // uninitialized variant is not comparable otherwise
+        case OpID.toString:
+            string * target = cast(string*) parm;
+            *target = "<Uninitialized VariantN>";
+            break;
+        case OpID.postblit:
+        case OpID.destruct:
+            break;
+        case OpID.get:
+        case OpID.testConversion:
+        case OpID.index:
+        case OpID.indexAssign:
+        case OpID.catAssign:
+        case OpID.length:
+            throw new VariantException(
+                "Attempt to use an uninitialized VariantN");
+        default: assert(false, "Invalid OpID");
+        }
+        return 0;
+    }
+
+    // Handler for all of a type's operations
+    static ptrdiff_t handler(A)(OpID selector, ubyte[size]* pStore, void* parm)
+    {
+        static A* getPtr(void* untyped)
+        {
+            if (untyped)
+            {
+                static if (A.sizeof <= size)
+                    return cast(A*) untyped;
+                else
+                    return *cast(A**) untyped;
+            }
+            return null;
+        }
+
+        static ptrdiff_t compare(A* rhsPA, A* zis, OpID selector)
+        {
+            static if (is(typeof(*rhsPA == *zis)))
+            {
+                if (*rhsPA == *zis)
+                {
+                    return 0;
+                }
+                static if (is(typeof(*zis < *rhsPA)))
+                {
+                    // Many types (such as any using the default Object opCmp)
+                    // will throw on an invalid opCmp, so do it only
+                    // if the caller requests it.
+                    if (selector == OpID.compare)
+                        return *zis < *rhsPA ? -1 : 1;
+                    else
+                        return ptrdiff_t.min;
+                }
+                else
+                {
+                    // Not equal, and type does not support ordering
+                    // comparisons.
+                    return ptrdiff_t.min;
+                }
+            }
+            else
+            {
+                // Type does not support comparisons at all.
+                return ptrdiff_t.min;
+            }
+        }
+
+        auto zis = getPtr(pStore);
+        // Input: TypeInfo object
+        // Output: target points to a copy of *me, if me was not null
+        // Returns: true iff the A can be converted to the type represented
+        // by the incoming TypeInfo
+        static bool tryPutting(A* src, TypeInfo targetType, void* target)
+        {
+            alias UA = Unqual!A;
+            alias MutaTypes = AliasSeq!(UA, ImplicitConversionTargets!UA);
+            alias ConstTypes = staticMap!(ConstOf, MutaTypes);
+            alias SharedTypes = staticMap!(SharedOf, MutaTypes);
+            alias SharedConstTypes = staticMap!(SharedConstOf, MutaTypes);
+            alias ImmuTypes  = staticMap!(ImmutableOf, MutaTypes);
+
+            static if (is(A == immutable))
+                alias AllTypes = AliasSeq!(ImmuTypes, ConstTypes, SharedConstTypes);
+            else static if (is(A == shared))
+            {
+                static if (is(A == const))
+                    alias AllTypes = SharedConstTypes;
+                else
+                    alias AllTypes = AliasSeq!(SharedTypes, SharedConstTypes);
+            }
+            else
+            {
+                static if (is(A == const))
+                    alias AllTypes = ConstTypes;
+                else
+                    alias AllTypes = AliasSeq!(MutaTypes, ConstTypes);
+            }
+
+            foreach (T ; AllTypes)
+            {
+                if (targetType != typeid(T))
+                {
+                    continue;
+                }
+
+                static if (is(typeof(*cast(T*) target = *src)))
+                {
+                    auto zat = cast(T*) target;
+                    if (src)
+                    {
+                        static if (T.sizeof > 0)
+                            assert(target, "target must be non-null");
+                        *zat = *src;
+                    }
+                }
+                else static if (is(T == const(U), U) ||
+                                is(T == shared(U), U) ||
+                                is(T == shared const(U), U) ||
+                                is(T == immutable(U), U))
+                {
+                    auto zat = cast(U*) target;
+                    if (src)
+                    {
+                        static if (U.sizeof > 0)
+                            assert(target, "target must be non-null");
+                        *zat = *(cast(UA*) (src));
+                    }
+                }
+                else
+                {
+                    // type is not assignable
+                    if (src) assert(false, A.stringof);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        switch (selector)
+        {
+        case OpID.getTypeInfo:
+            *cast(TypeInfo *) parm = typeid(A);
+            break;
+        case OpID.copyOut:
+            auto target = cast(VariantN *) parm;
+            assert(target);
+
+            static if (target.size < A.sizeof)
+            {
+                if (target.type.tsize < A.sizeof)
+                    *cast(A**)&target.store = new A;
+            }
+            tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store))
+                || assert(false);
+            target.fptr = &handler!(A);
+            break;
+        case OpID.get:
+            auto t = * cast(Tuple!(TypeInfo, void*)*) parm;
+            return !tryPutting(zis, t[0], t[1]);
+        case OpID.testConversion:
+            return !tryPutting(null, *cast(TypeInfo*) parm, null);
+        case OpID.compare:
+        case OpID.equals:
+            auto rhsP = cast(VariantN *) parm;
+            auto rhsType = rhsP.type;
+            // Are we the same?
+            if (rhsType == typeid(A))
+            {
+                // cool! Same type!
+                auto rhsPA = getPtr(&rhsP.store);
+                return compare(rhsPA, zis, selector);
+            } else if (rhsType == typeid(void))
+            {
+                // No support for ordering comparisons with
+                // uninitialized vars
+                return ptrdiff_t.min;
+            }
+            VariantN temp;
+            // Do I convert to rhs?
+            if (tryPutting(zis, rhsType, &temp.store))
+            {
+                // cool, I do; temp's store contains my data in rhs's type!
+                // also fix up its fptr
+                temp.fptr = rhsP.fptr;
+                // now lhsWithRhsType is a full-blown VariantN of rhs's type
+                if (selector == OpID.compare)
+                    return temp.opCmp(*rhsP);
+                else
+                    return temp.opEquals(*rhsP) ? 0 : 1;
+            }
+            // Does rhs convert to zis?
+            auto t = tuple(typeid(A), &temp.store);
+            if (rhsP.fptr(OpID.get, &rhsP.store, &t) == 0)
+            {
+                // cool! Now temp has rhs in my type!
+                auto rhsPA = getPtr(&temp.store);
+                return compare(rhsPA, zis, selector);
+            }
+            return ptrdiff_t.min; // dunno
+        case OpID.toString:
+            auto target = cast(string*) parm;
+            static if (is(typeof(to!(string)(*zis))))
+            {
+                *target = to!(string)(*zis);
+                break;
+            }
+            // TODO: The following test evaluates to true for shared objects.
+            //       Use __traits for now until this is sorted out.
+            // else static if (is(typeof((*zis).toString)))
+            else static if (__traits(compiles, {(*zis).toString();}))
+            {
+                *target = (*zis).toString();
+                break;
+            }
+            else
+            {
+                throw new VariantException(typeid(A), typeid(string));
+            }
+
+        case OpID.index:
+            auto result = cast(Variant*) parm;
+            static if (isArray!(A) && !is(Unqual!(typeof(A.init[0])) == void))
+            {
+                // array type; input and output are the same VariantN
+                size_t index = result.convertsTo!(int)
+                    ? result.get!(int) : result.get!(size_t);
+                *result = (*zis)[index];
+                break;
+            }
+            else static if (isAssociativeArray!(A))
+            {
+                *result = (*zis)[result.get!(typeof(A.init.keys[0]))];
+                break;
+            }
+            else
+            {
+                throw new VariantException(typeid(A), result[0].type);
+            }
+
+        case OpID.indexAssign:
+            // array type; result comes first, index comes second
+            auto args = cast(Variant*) parm;
+            static if (isArray!(A) && is(typeof((*zis)[0] = (*zis)[0])))
+            {
+                size_t index = args[1].convertsTo!(int)
+                    ? args[1].get!(int) : args[1].get!(size_t);
+                (*zis)[index] = args[0].get!(typeof((*zis)[0]));
+                break;
+            }
+            else static if (isAssociativeArray!(A))
+            {
+                (*zis)[args[1].get!(typeof(A.init.keys[0]))]
+                    = args[0].get!(typeof(A.init.values[0]));
+                break;
+            }
+            else
+            {
+                throw new VariantException(typeid(A), args[0].type);
+            }
+
+        case OpID.catAssign:
+            static if (!is(Unqual!(typeof((*zis)[0])) == void) && is(typeof((*zis)[0])) && is(typeof((*zis) ~= *zis)))
+            {
+                // array type; parm is the element to append
+                auto arg = cast(Variant*) parm;
+                alias E = typeof((*zis)[0]);
+                if (arg[0].convertsTo!(E))
+                {
+                    // append one element to the array
+                    (*zis) ~= [ arg[0].get!(E) ];
+                }
+                else
+                {
+                    // append a whole array to the array
+                    (*zis) ~= arg[0].get!(A);
+                }
+                break;
+            }
+            else
+            {
+                throw new VariantException(typeid(A), typeid(void[]));
+            }
+
+        case OpID.length:
+            static if (isArray!(A) || isAssociativeArray!(A))
+            {
+                return zis.length;
+            }
+            else
+            {
+                throw new VariantException(typeid(A), typeid(void[]));
+            }
+
+        case OpID.apply:
+            static if (!isFunctionPointer!A && !isDelegate!A)
+            {
+                enforce(0, text("Cannot apply `()' to a value of type `",
+                                A.stringof, "'."));
+            }
+            else
+            {
+                alias ParamTypes = Parameters!A;
+                auto p = cast(Variant*) parm;
+                auto argCount = p.get!size_t;
+                // To assign the tuple we need to use the unqualified version,
+                // otherwise we run into issues such as with const values.
+                // We still get the actual type from the Variant though
+                // to ensure that we retain const correctness.
+                Tuple!(staticMap!(Unqual, ParamTypes)) t;
+                enforce(t.length == argCount,
+                        text("Argument count mismatch: ",
+                             A.stringof, " expects ", t.length,
+                             " argument(s), not ", argCount, "."));
+                auto variantArgs = p[1 .. argCount + 1];
+                foreach (i, T; ParamTypes)
+                {
+                    t[i] = cast()variantArgs[i].get!T;
+                }
+
+                auto args = cast(Tuple!(ParamTypes))t;
+                static if(is(ReturnType!A == void))
+                {
+                    (*zis)(args.expand);
+                    *p = Variant.init; // void returns uninitialized Variant.
+                }
+                else
+                {
+                    *p = (*zis)(args.expand);
+                }
+            }
+            break;
+
+        case OpID.postblit:
+            static if (hasElaborateCopyConstructor!A)
+            {
+                typeid(A).postblit(zis);
+            }
+            break;
+
+        case OpID.destruct:
+            static if (hasElaborateDestructor!A)
+            {
+                typeid(A).destroy(zis);
+            }
+            break;
+
+        default: assert(false);
+        }
+        return 0;
+    }
+
+public:
+    /** Constructs a $(D VariantN) value given an argument of a
+     * generic type. Statically rejects disallowed types.
+     */
+
+    this(T)(T value)
+    {
+        static assert(allowed!(T), "Cannot store a " ~ T.stringof
+            ~ " in a " ~ VariantN.stringof);
+        opAssign(value);
+    }
+
+    /// Allows assignment from a subset algebraic type
+    this(T : VariantN!(tsize, Types), size_t tsize, Types...)(T value)
+        if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
+    {
+        opAssign(value);
+    }
+
+    static if (!AllowedTypes.length || anySatisfy!(hasElaborateCopyConstructor, AllowedTypes))
+    {
+        this(this)
+        {
+            fptr(OpID.postblit, &store, null);
+        }
+    }
+
+    static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
+    {
+        ~this()
+        {
+            fptr(OpID.destruct, &store, null);
+        }
+    }
+
+    /** Assigns a $(D VariantN) from a generic
+     * argument. Statically rejects disallowed types. */
+
+    VariantN opAssign(T)(T rhs)
+    {
+        //writeln(typeid(rhs));
+        static assert(allowed!(T), "Cannot store a " ~ T.stringof
+            ~ " in a " ~ VariantN.stringof ~ ". Valid types are "
+                ~ AllowedTypes.stringof);
+
+        static if (is(T : VariantN))
+        {
+            rhs.fptr(OpID.copyOut, &rhs.store, &this);
+        }
+        else static if (is(T : const(VariantN)))
+        {
+            static assert(false,
+                    "Assigning Variant objects from const Variant"~
+                    " objects is currently not supported.");
+        }
+        else
+        {
+            static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
+            {
+                // Assignment should destruct previous value
+                fptr(OpID.destruct, &store, null);
+            }
+
+            static if (T.sizeof <= size)
+            {
+                // If T is a class we're only copying the reference, so it
+                // should be safe to cast away shared so the memcpy will work.
+                //
+                // TODO: If a shared class has an atomic reference then using
+                //       an atomic load may be more correct.  Just make sure
+                //       to use the fastest approach for the load op.
+                static if (is(T == class) && is(T == shared))
+                    memcpy(&store, cast(const(void*)) &rhs, rhs.sizeof);
+                else
+                    memcpy(&store, &rhs, rhs.sizeof);
+                static if (hasElaborateCopyConstructor!T)
+                {
+                    typeid(T).postblit(&store);
+                }
+            }
+            else
+            {
+                static if (__traits(compiles, {new T(rhs);}))
+                {
+                    auto p = new T(rhs);
+                }
+                else
+                {
+                    auto p = new T;
+                    *p = rhs;
+                }
+                memcpy(&store, &p, p.sizeof);
+            }
+            fptr = &handler!(T);
+        }
+        return this;
+    }
+
+    // Allow assignment from another variant which is a subset of this one
+    VariantN opAssign(T : VariantN!(tsize, Types), size_t tsize, Types...)(T rhs)
+        if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
+    {
+        // discover which type rhs is actually storing
+        foreach (V; T.AllowedTypes)
+            if (rhs.type == typeid(V))
+                return this = rhs.get!V;
+        assert(0, T.AllowedTypes.stringof);
+    }
+
+
+    Variant opCall(P...)(auto ref P params)
+    {
+        Variant[P.length + 1] pack;
+        pack[0] = P.length;
+        foreach (i, _; params)
+        {
+            pack[i + 1] = params[i];
+        }
+        fptr(OpID.apply, &store, &pack);
+        return pack[0];
+    }
+
+    /** Returns true if and only if the $(D VariantN) object
+     * holds a valid value (has been initialized with, or assigned
+     * from, a valid value).
+     */
+    @property bool hasValue() const pure nothrow
+    {
+        // @@@BUG@@@ in compiler, the cast shouldn't be needed
+        return cast(typeof(&handler!(void))) fptr != &handler!(void);
+    }
+
+    ///
+    unittest
+    {
+        Variant a;
+        assert(!a.hasValue);
+        Variant b;
+        a = b;
+        assert(!a.hasValue); // still no value
+        a = 5;
+        assert(a.hasValue);
+    }
+
+    /**
+     * If the $(D VariantN) object holds a value of the
+     * $(I exact) type $(D T), returns a pointer to that
+     * value. Otherwise, returns $(D null). In cases
+     * where $(D T) is statically disallowed, $(D
+     * peek) will not compile.
+     */
+    @property inout(T)* peek(T)() inout
+    {
+        static if (!is(T == void))
+            static assert(allowed!(T), "Cannot store a " ~ T.stringof
+                    ~ " in a " ~ VariantN.stringof);
+        if (type != typeid(T))
+            return null;
+        static if (T.sizeof <= size)
+            return cast(inout T*)&store;
+        else
+            return *cast(inout T**)&store;
+    }
+
+    ///
+    unittest
+    {
+        Variant a = 5;
+        auto b = a.peek!(int);
+        assert(b !is null);
+        *b = 6;
+        assert(a == 6);
+    }
+
+    /**
+     * Returns the $(D typeid) of the currently held value.
+     */
+
+    @property TypeInfo type() const nothrow @trusted
+    {
+        scope(failure) assert(0);
+
+        TypeInfo result;
+        fptr(OpID.getTypeInfo, null, &result);
+        return result;
+    }
+
+    /**
+     * Returns $(D true) if and only if the $(D VariantN)
+     * object holds an object implicitly convertible to type $(D
+     * U). Implicit convertibility is defined as per
+     * $(LINK2 std_traits.html#ImplicitConversionTargets,ImplicitConversionTargets).
+     */
+
+    @property bool convertsTo(T)() const
+    {
+        TypeInfo info = typeid(T);
+        return fptr(OpID.testConversion, null, &info) == 0;
+    }
+
+    /**
+    Returns the value stored in the `VariantN` object, either by specifying the
+    needed type or the index in the list of allowed types. The latter overload
+    only applies to bounded variants (e.g. $(LREF Algebraic)).
+
+    Params:
+    T = The requested type. The currently stored value must implicitly convert
+    to the requested type, in fact `DecayStaticToDynamicArray!T`. If an
+    implicit conversion is not possible, throws a `VariantException`.
+    index = The index of the type among `AllowedTypesParam`, zero-based.
+     */
+    @property inout(T) get(T)() inout
+    {
+        static if (is(T == shared))
+            shared Unqual!T result;
+        else
+            Unqual!T result;
+        auto buf = tuple(typeid(T), &result);
+
+        if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf))
+        {
+            throw new VariantException(type, typeid(T));
+        }
+        return * cast(inout T*) &result;
+    }
+
+    /// Ditto
+    @property auto get(uint index)() inout
+    if (index < AllowedTypes.length)
+    {
+        foreach (i, T; AllowedTypes)
+        {
+            static if (index == i) return get!T;
+        }
+        assert(0);
+    }
+
+    /**
+     * Returns the value stored in the $(D VariantN) object,
+     * explicitly converted (coerced) to the requested type $(D
+     * T). If $(D T) is a string type, the value is formatted as
+     * a string. If the $(D VariantN) object is a string, a
+     * parse of the string to type $(D T) is attempted. If a
+     * conversion is not possible, throws a $(D
+     * VariantException).
+     */
+
+    @property T coerce(T)()
+    {
+        static if (isNumeric!T || isBoolean!T)
+        {
+            if (convertsTo!real)
+            {
+                // maybe optimize this fella; handle ints separately
+                return to!T(get!real);
+            }
+            else if (convertsTo!(const(char)[]))
+            {
+                return to!T(get!(const(char)[]));
+            }
+            // I'm not sure why this doesn't convert to const(char),
+            // but apparently it doesn't (probably a deeper bug).
+            //
+            // Until that is fixed, this quick addition keeps a common
+            // function working. "10".coerce!int ought to work.
+            else if (convertsTo!(immutable(char)[]))
+            {
+                return to!T(get!(immutable(char)[]));
+            }
+            else
+            {
+                enforce(false, text("Type ", type, " does not convert to ",
+                                typeid(T)));
+                assert(0);
+            }
+        }
+        else static if (is(T : Object))
+        {
+            return to!(T)(get!(Object));
+        }
+        else static if (isSomeString!(T))
+        {
+            return to!(T)(toString());
+        }
+        else
+        {
+            // Fix for bug 1649
+            static assert(false, "unsupported type for coercion");
+        }
+    }
+
+    /**
+     * Formats the stored value as a string.
+     */
+
+    string toString()
+    {
+        string result;
+        fptr(OpID.toString, &store, &result) == 0 || assert(false);
+        return result;
+    }
+
+    /**
+     * Comparison for equality used by the "==" and "!="  operators.
+     */
+
+    // returns 1 if the two are equal
+    bool opEquals(T)(auto ref T rhs) const
+    {
+        static if (is(Unqual!T == VariantN))
+            alias temp = rhs;
+        else
+            auto temp = VariantN(rhs);
+        return !fptr(OpID.equals, cast(ubyte[size]*) &store,
+                     cast(void*) &temp);
+    }
+
+    // workaround for bug 10567 fix
+    int opCmp(ref const VariantN rhs) const
+    {
+        return (cast()this).opCmp!(VariantN)(cast()rhs);
+    }
+
+    /**
+     * Ordering comparison used by the "<", "<=", ">", and ">="
+     * operators. In case comparison is not sensible between the held
+     * value and $(D rhs), an exception is thrown.
+     */
+
+    int opCmp(T)(T rhs)
+    {
+        static if (is(T == VariantN))
+            alias temp = rhs;
+        else
+            auto temp = VariantN(rhs);
+        auto result = fptr(OpID.compare, &store, &temp);
+        if (result == ptrdiff_t.min)
+        {
+            throw new VariantException(type, temp.type);
+        }
+
+        assert(result >= -1 && result <= 1);  // Should be true for opCmp.
+        return cast(int) result;
+    }
+
+    /**
+     * Computes the hash of the held value.
+     */
+
+    size_t toHash() const nothrow @safe
+    {
+        return type.getHash(&store);
+    }
+
+    private VariantN opArithmetic(T, string op)(T other)
+    {
+        static if (isInstanceOf!(.VariantN, T))
+        {
+            string tryUseType(string tp)
+            {
+                import std.format : format;
+                return q{
+                    static if (allowed!%1$s && T.allowed!%1$s)
+                        if (convertsTo!%1$s && other.convertsTo!%1$s)
+                            return VariantN(get!%1$s %2$s other.get!%1$s);
+                }.format(tp, op);
+            }
+
+            mixin(tryUseType("uint"));
+            mixin(tryUseType("int"));
+            mixin(tryUseType("ulong"));
+            mixin(tryUseType("long"));
+            mixin(tryUseType("float"));
+            mixin(tryUseType("double"));
+            mixin(tryUseType("real"));
+        }
+        else
+        {
+            static if (allowed!T)
+                if (auto pv = peek!T) return VariantN(mixin("*pv " ~ op ~ " other"));
+            static if (allowed!uint && is(typeof(T.max) : uint) && isUnsigned!T)
+                if (convertsTo!uint) return VariantN(mixin("get!(uint) " ~ op ~ " other"));
+            static if (allowed!int && is(typeof(T.max) : int) && !isUnsigned!T)
+                if (convertsTo!int) return VariantN(mixin("get!(int) " ~ op ~ " other"));
+            static if (allowed!ulong && is(typeof(T.max) : ulong) && isUnsigned!T)
+                if (convertsTo!ulong) return VariantN(mixin("get!(ulong) " ~ op ~ " other"));
+            static if (allowed!long && is(typeof(T.max) : long) && !isUnsigned!T)
+                if (convertsTo!long) return VariantN(mixin("get!(long) " ~ op ~ " other"));
+            static if (allowed!float && is(T : float))
+                if (convertsTo!float) return VariantN(mixin("get!(float) " ~ op ~ " other"));
+            static if (allowed!double && is(T : double))
+                if (convertsTo!double) return VariantN(mixin("get!(double) " ~ op ~ " other"));
+            static if (allowed!real && is (T : real))
+                if (convertsTo!real) return VariantN(mixin("get!(real) " ~ op ~ " other"));
+        }
+
+        throw new VariantException("No possible match found for VariantN "~op~" "~T.stringof);
+    }
+
+    private VariantN opLogic(T, string op)(T other)
+    {
+        VariantN result;
+        static if (is(T == VariantN))
+        {
+            if (convertsTo!(uint) && other.convertsTo!(uint))
+                result = mixin("get!(uint) " ~ op ~ " other.get!(uint)");
+            else if (convertsTo!(int) && other.convertsTo!(int))
+                result = mixin("get!(int) " ~ op ~ " other.get!(int)");
+            else if (convertsTo!(ulong) && other.convertsTo!(ulong))
+                result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)");
+            else
+                result = mixin("get!(long) " ~ op ~ " other.get!(long)");
+        }
+        else
+        {
+            if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint))
+                result = mixin("get!(uint) " ~ op ~ " other");
+            else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int))
+                result = mixin("get!(int) " ~ op ~ " other");
+            else if (is(typeof(T.max) : ulong) && T.min == 0
+                     && convertsTo!(ulong))
+                result = mixin("get!(ulong) " ~ op ~ " other");
+            else
+                result = mixin("get!(long) " ~ op ~ " other");
+        }
+        return result;
+    }
+
+    /**
+     * Arithmetic between $(D VariantN) objects and numeric
+     * values. All arithmetic operations return a $(D VariantN)
+     * object typed depending on the types of both values
+     * involved. The conversion rules mimic D's built-in rules for
+     * arithmetic conversions.
+     */
+
+    // Adapted from http://www.prowiki.org/wiki4d/wiki.cgi?DanielKeep/Variant
+    // arithmetic
+    VariantN opAdd(T)(T rhs) { return opArithmetic!(T, "+")(rhs); }
+    ///ditto
+    VariantN opSub(T)(T rhs) { return opArithmetic!(T, "-")(rhs); }
+
+    // Commenteed all _r versions for now because of ambiguities
+    // arising when two Variants are used
+
+    // ///ditto
+    // VariantN opSub_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opArithmetic!(VariantN, "-")(this);
+    // }
+    ///ditto
+    VariantN opMul(T)(T rhs) { return opArithmetic!(T, "*")(rhs); }
+    ///ditto
+    VariantN opDiv(T)(T rhs) { return opArithmetic!(T, "/")(rhs); }
+    // ///ditto
+    // VariantN opDiv_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opArithmetic!(VariantN, "/")(this);
+    // }
+    ///ditto
+    VariantN opMod(T)(T rhs) { return opArithmetic!(T, "%")(rhs); }
+    // ///ditto
+    // VariantN opMod_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opArithmetic!(VariantN, "%")(this);
+    // }
+    ///ditto
+    VariantN opAnd(T)(T rhs) { return opLogic!(T, "&")(rhs); }
+    ///ditto
+    VariantN opOr(T)(T rhs) { return opLogic!(T, "|")(rhs); }
+    ///ditto
+    VariantN opXor(T)(T rhs) { return opLogic!(T, "^")(rhs); }
+    ///ditto
+    VariantN opShl(T)(T rhs) { return opLogic!(T, "<<")(rhs); }
+    // ///ditto
+    // VariantN opShl_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opLogic!(VariantN, "<<")(this);
+    // }
+    ///ditto
+    VariantN opShr(T)(T rhs) { return opLogic!(T, ">>")(rhs); }
+    // ///ditto
+    // VariantN opShr_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opLogic!(VariantN, ">>")(this);
+    // }
+    ///ditto
+    VariantN opUShr(T)(T rhs) { return opLogic!(T, ">>>")(rhs); }
+    // ///ditto
+    // VariantN opUShr_r(T)(T lhs)
+    // {
+    //     return VariantN(lhs).opLogic!(VariantN, ">>>")(this);
+    // }
+    ///ditto
+    VariantN opCat(T)(T rhs)
+    {
+        auto temp = this;
+        temp ~= rhs;
+        return temp;
+    }
+    // ///ditto
+    // VariantN opCat_r(T)(T rhs)
+    // {
+    //     VariantN temp = rhs;
+    //     temp ~= this;
+    //     return temp;
+    // }
+
+    ///ditto
+    VariantN opAddAssign(T)(T rhs)  { return this = this + rhs; }
+    ///ditto
+    VariantN opSubAssign(T)(T rhs)  { return this = this - rhs; }
+    ///ditto
+    VariantN opMulAssign(T)(T rhs)  { return this = this * rhs; }
+    ///ditto
+    VariantN opDivAssign(T)(T rhs)  { return this = this / rhs; }
+    ///ditto
+    VariantN opModAssign(T)(T rhs)  { return this = this % rhs; }
+    ///ditto
+    VariantN opAndAssign(T)(T rhs)  { return this = this & rhs; }
+    ///ditto
+    VariantN opOrAssign(T)(T rhs)   { return this = this | rhs; }
+    ///ditto
+    VariantN opXorAssign(T)(T rhs)  { return this = this ^ rhs; }
+    ///ditto
+    VariantN opShlAssign(T)(T rhs)  { return this = this << rhs; }
+    ///ditto
+    VariantN opShrAssign(T)(T rhs)  { return this = this >> rhs; }
+    ///ditto
+    VariantN opUShrAssign(T)(T rhs) { return this = this >>> rhs; }
+    ///ditto
+    VariantN opCatAssign(T)(T rhs)
+    {
+        auto toAppend = Variant(rhs);
+        fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false);
+        return this;
+    }
+
+    /**
+     * Array and associative array operations. If a $(D
+     * VariantN) contains an (associative) array, it can be indexed
+     * into. Otherwise, an exception is thrown.
+     */
+    Variant opIndex(K)(K i)
+    {
+        auto result = Variant(i);
+        fptr(OpID.index, &store, &result) == 0 || assert(false);
+        return result;
+    }
+
+    ///
+    unittest
+    {
+        auto a = Variant(new int[10]);
+        a[5] = 42;
+        assert(a[5] == 42);
+        int[int] hash = [ 42:24 ];
+        a = hash;
+        assert(a[42] == 24);
+    }
+
+    /** Caveat:
+    Due to limitations in current language, read-modify-write
+    operations $(D op=) will not work properly:
+    */
+    unittest
+    {
+        Variant a = new int[10];
+        a[5] = 42;
+        a[5] += 8;
+        //assert(a[5] == 50); // will fail, a[5] is still 42
+    }
+
+    unittest
+    {
+        int[int] hash = [ 42:24 ];
+        Variant v = hash;
+        assert(v[42] == 24);
+        v[42] = 5;
+        assert(v[42] == 5);
+    }
+
+    /// ditto
+    Variant opIndexAssign(T, N)(T value, N i)
+    {
+        Variant[2] args = [ Variant(value), Variant(i) ];
+        fptr(OpID.indexAssign, &store, &args) == 0 || assert(false);
+        return args[0];
+    }
+
+    /** If the $(D VariantN) contains an (associative) array,
+     * returns the length of that array. Otherwise, throws an
+     * exception.
+     */
+    @property size_t length()
+    {
+        return cast(size_t) fptr(OpID.length, &store, null);
+    }
+
+    /**
+       If the $(D VariantN) contains an array, applies $(D dg) to each
+       element of the array in turn. Otherwise, throws an exception.
+     */
+    int opApply(Delegate)(scope Delegate dg) if (is(Delegate == delegate))
+    {
+        alias A = Parameters!(Delegate)[0];
+        if (type == typeid(A[]))
+        {
+            auto arr = get!(A[]);
+            foreach (ref e; arr)
+            {
+                if (dg(e)) return 1;
+            }
+        }
+        else static if (is(A == VariantN))
+        {
+            foreach (i; 0 .. length)
+            {
+                // @@@TODO@@@: find a better way to not confuse
+                // clients who think they change values stored in the
+                // Variant when in fact they are only changing tmp.
+                auto tmp = this[i];
+                debug scope(exit) assert(tmp == this[i]);
+                if (dg(tmp)) return 1;
+            }
+        }
+        else
+        {
+            enforce(false, text("Variant type ", type,
+                            " not iterable with values of type ",
+                            A.stringof));
+        }
+        return 0;
+    }
+}
+
+unittest
+{
+    Variant v;
+    int foo() { return 42; }
+    v = &foo;
+    assert(v() == 42);
+
+    static int bar(string s) { return to!int(s); }
+    v = &bar;
+    assert(v("43") == 43);
+}
+
+// opIndex with static arrays, issue 12771
+unittest
+{
+    int[4] elements = [0, 1, 2, 3];
+    Variant v = elements;
+    assert(v == elements);
+    assert(v[2] == 2);
+    assert(v[3] == 3);
+    v[2] = 6;
+    assert(v[2] == 6);
+    assert(v != elements);
+}
+
+//Issue# 8195
+unittest
+{
+    struct S
+    {
+        int a;
+        long b;
+        string c;
+        real d = 0.0;
+        bool e;
+    }
+
+    static assert(S.sizeof >= Variant.sizeof);
+    alias Types = AliasSeq!(string, int, S);
+    alias MyVariant = VariantN!(maxSize!Types, Types);
+
+    auto v = MyVariant(S.init);
+    assert(v == S.init);
+}
+
+// Issue #10961
+unittest
+{
+    // Primarily test that we can assign a void[] to a Variant.
+    void[] elements = cast(void[])[1, 2, 3];
+    Variant v = elements;
+    void[] returned = v.get!(void[]);
+    assert(returned == elements);
+}
+
+// Issue #13352
+unittest
+{
+    alias TP = Algebraic!(long);
+    auto a = TP(1L);
+    auto b = TP(2L);
+    assert(!TP.allowed!ulong);
+    assert(a + b == 3L);
+    assert(a + 2 == 3L);
+    assert(1 + b == 3L);
+
+    alias TP2 = Algebraic!(long, string);
+    auto c = TP2(3L);
+    assert(a + c == 4L);
+}
+
+// Issue #13354
+unittest
+{
+    alias A = Algebraic!(string[]);
+    A a = ["a", "b"];
+    assert(a[0] == "a");
+    assert(a[1] == "b");
+    a[1] = "c";
+    assert(a[1] == "c");
+
+    alias AA = Algebraic!(int[string]);
+    AA aa = ["a": 1, "b": 2];
+    assert(aa["a"] == 1);
+    assert(aa["b"] == 2);
+    aa["b"] = 3;
+    assert(aa["b"] == 3);
+}
+
+// Issue #14198
+unittest
+{
+    Variant a = true;
+    assert(a.type == typeid(bool));
+}
+
+// Issue #14233
+unittest
+{
+    alias Atom = Algebraic!(string, This[]);
+
+    Atom[] values = [];
+    auto a = Atom(values);
+}
+
+pure nothrow @nogc
+unittest
+{
+    Algebraic!(int, double) a;
+    a = 100;
+    a = 1.0;
+}
+
+// Issue 14457
+unittest
+{
+    alias A = Algebraic!(int, float, double);
+    alias B = Algebraic!(int, float);
+
+    A a = 1;
+    B b = 6f;
+    a = b;
+
+    assert(a.type == typeid(float));
+    assert(a.get!float == 6f);
+}
+
+// Issue 14585
+unittest
+{
+    static struct S
+    {
+        int x = 42;
+        ~this() {assert(x == 42);}
+    }
+    Variant(S()).get!S;
+}
+
+// Issue 14586
+unittest
+{
+    const Variant v = new immutable Object;
+    v.get!(immutable Object);
+}
+
+unittest
+{
+    static struct S
+    {
+        T opCast(T)() {assert(false);}
+    }
+    Variant v = S();
+    v.get!S;
+}
+
+
+/**
+Algebraic data type restricted to a closed set of possible
+types. It's an alias for a $(LREF VariantN) with an
+appropriately-constructed maximum size. `Algebraic` is
+useful when it is desirable to restrict what a discriminated type
+could hold to the end of defining simpler and more efficient
+manipulation.
+
+*/
+template Algebraic(T...)
+{
+    alias Algebraic = VariantN!(maxSize!T, T);
+}
+
+///
+unittest
+{
+    auto v = Algebraic!(int, double, string)(5);
+    assert(v.peek!(int));
+    v = 3.14;
+    assert(v.peek!(double));
+    // auto x = v.peek!(long); // won't compile, type long not allowed
+    // v = '1'; // won't compile, type char not allowed
+}
+
+/**
+$(H4 Self-Referential Types)
+
+A useful and popular use of algebraic data structures is for defining $(LUCKY
+self-referential data structures), i.e. structures that embed references to
+values of their own type within.
+
+This is achieved with `Algebraic` by using `This` as a placeholder whenever a
+reference to the type being defined is needed. The `Algebraic` instantiation
+will perform $(LUCKY alpha renaming) on its constituent types, replacing `This`
+with the self-referenced type. The structure of the type involving `This` may
+be arbitrarily complex.
+*/
+unittest
+{
+    // A tree is either a leaf or a branch of two other trees
+    alias Tree(Leaf) = Algebraic!(Leaf, Tuple!(This*, This*));
+    Tree!int tree = tuple(new Tree!int(42), new Tree!int(43));
+    Tree!int* right = tree.get!1[1];
+    assert(*right == 43);
+
+    // An object is a double, a string, or a hash of objects
+    alias Obj = Algebraic!(double, string, This[string]);
+    Obj obj = "hello";
+    assert(obj.get!1 == "hello");
+    obj = 42.0;
+    assert(obj.get!0 == 42);
+    obj = ["customer": Obj("John"), "paid": Obj(23.95)];
+    assert(obj.get!2["customer"] == "John");
+}
+
+/**
+`Variant` is an alias for `VariantN` instantiated with the largest of `creal`,
+`char[]`, and `void delegate()`. This ensures that `Variant` is large enough
+to hold all of D's predefined types unboxed, including all numeric types,
+pointers, delegates, and class references.  You may want to use
+$(D VariantN) directly with a different maximum size either for
+storing larger types unboxed, or for saving memory.
+ */
+alias Variant = VariantN!(maxSize!(creal, char[], void delegate()));
+
+/**
+ * Returns an array of variants constructed from $(D args).
+ *
+ * This is by design. During construction the $(D Variant) needs
+ * static type information about the type being held, so as to store a
+ * pointer to function for fast retrieval.
+ */
+Variant[] variantArray(T...)(T args)
+{
+    Variant[] result;
+    foreach (arg; args)
+    {
+        result ~= Variant(arg);
+    }
+    return result;
+}
+
+///
+unittest
+{
+    auto a = variantArray(1, 3.14, "Hi!");
+    assert(a[1] == 3.14);
+    auto b = Variant(a); // variant array as variant
+    assert(b[1] == 3.14);
+}
+
+/** Code that needs functionality similar to the $(D boxArray)
+function in the $(D std.boxer) module can achieve it like this:
+*/
+unittest
+{
+    Variant[] fun(T...)(T args)
+    {
+        // ...
+        return variantArray(args);
+    }
+}
+
+/**
+
+/**
+ * Thrown in three cases:
+ *
+ * $(OL $(LI An uninitialized Variant is used in any way except
+ * assignment and $(D hasValue);) $(LI A $(D get) or
+ * $(D coerce) is attempted with an incompatible target type;)
+ * $(LI A comparison between $(D Variant) objects of
+ * incompatible types is attempted.))
+ *
+ */
+
+// @@@ BUG IN COMPILER. THE 'STATIC' BELOW SHOULD NOT COMPILE
+static class VariantException : Exception
+{
+    /// The source type in the conversion or comparison
+    TypeInfo source;
+    /// The target type in the conversion or comparison
+    TypeInfo target;
+    this(string s)
+    {
+        super(s);
+    }
+    this(TypeInfo source, TypeInfo target)
+    {
+        super("Variant: attempting to use incompatible types "
+                            ~ source.toString()
+                            ~ " and " ~ target.toString());
+        this.source = source;
+        this.target = target;
+    }
+}
+
+unittest
+{
+    alias W1 = This2Variant!(char, int, This[int]);
+    alias W2 = AliasSeq!(int, char[int]);
+    static assert(is(W1 == W2));
+
+    alias var_t = Algebraic!(void, string);
+    var_t foo = "quux";
+}
+
+unittest
+{
+     alias A = Algebraic!(real, This[], This[int], This[This]);
+     A v1, v2, v3;
+     v2 = 5.0L;
+     v3 = 42.0L;
+     //v1 = [ v2 ][];
+      auto v = v1.peek!(A[]);
+     //writeln(v[0]);
+     v1 = [ 9 : v3 ];
+     //writeln(v1);
+     v1 = [ v3 : v3 ];
+     //writeln(v1);
+}
+
+unittest
+{
+    // try it with an oddly small size
+    VariantN!(1) test;
+    assert(test.size > 1);
+
+    // variantArray tests
+    auto heterogeneous = variantArray(1, 4.5, "hi");
+    assert(heterogeneous.length == 3);
+    auto variantArrayAsVariant = Variant(heterogeneous);
+    assert(variantArrayAsVariant[0] == 1);
+    assert(variantArrayAsVariant.length == 3);
+
+    // array tests
+    auto arr = Variant([1.2].dup);
+    auto e = arr[0];
+    assert(e == 1.2);
+    arr[0] = 2.0;
+    assert(arr[0] == 2);
+    arr ~= 4.5;
+    assert(arr[1] == 4.5);
+
+    // general tests
+    Variant a;
+    auto b = Variant(5);
+    assert(!b.peek!(real) && b.peek!(int));
+    // assign
+    a = *b.peek!(int);
+    // comparison
+    assert(a == b, a.type.toString() ~ " " ~ b.type.toString());
+    auto c = Variant("this is a string");
+    assert(a != c);
+    // comparison via implicit conversions
+    a = 42; b = 42.0; assert(a == b);
+
+    // try failing conversions
+    bool failed = false;
+    try
+    {
+        auto d = c.get!(int);
+    }
+    catch (Exception e)
+    {
+        //writeln(stderr, e.toString);
+        failed = true;
+    }
+    assert(failed); // :o)
+
+    // toString tests
+    a = Variant(42); assert(a.toString() == "42");
+    a = Variant(42.22); assert(a.toString() == "42.22");
+
+    // coerce tests
+    a = Variant(42.22); assert(a.coerce!(int) == 42);
+    a = cast(short) 5; assert(a.coerce!(double) == 5);
+    a = Variant("10"); assert(a.coerce!int == 10);
+
+    a = Variant(1);
+    assert(a.coerce!bool);
+    a = Variant(0);
+    assert(!a.coerce!bool);
+
+    a = Variant(1.0);
+    assert(a.coerce!bool);
+    a = Variant(0.0);
+    assert(!a.coerce!bool);
+    a = Variant(float.init);
+    assertThrown!ConvException(a.coerce!bool);
+
+    a = Variant("true");
+    assert(a.coerce!bool);
+    a = Variant("false");
+    assert(!a.coerce!bool);
+    a = Variant("");
+    assertThrown!ConvException(a.coerce!bool);
+
+    // Object tests
+    class B1 {}
+    class B2 : B1 {}
+    a = new B2;
+    assert(a.coerce!(B1) !is null);
+    a = new B1;
+    assert(collectException(a.coerce!(B2) is null));
+    a = cast(Object) new B2; // lose static type info; should still work
+    assert(a.coerce!(B2) !is null);
+
+//     struct Big { int a[45]; }
+//     a = Big.init;
+
+    // hash
+    assert(a.toHash() != 0);
+}
+
+// tests adapted from
+// http://www.dsource.org/projects/tango/browser/trunk/tango/core/Variant.d?rev=2601
+unittest
+{
+    Variant v;
+
+    assert(!v.hasValue);
+    v = 42;
+    assert( v.peek!(int) );
+    assert( v.convertsTo!(long) );
+    assert( v.get!(int) == 42 );
+    assert( v.get!(long) == 42L );
+    assert( v.get!(ulong) == 42uL );
+
+    v = "Hello, World!";
+    assert( v.peek!(string) );
+
+    assert( v.get!(string) == "Hello, World!" );
+    assert(!is(char[] : wchar[]));
+    assert( !v.convertsTo!(wchar[]) );
+    assert( v.get!(string) == "Hello, World!" );
+
+    // Literal arrays are dynamically-typed
+    v = cast(int[4]) [1,2,3,4];
+    assert( v.peek!(int[4]) );
+    assert( v.get!(int[4]) == [1,2,3,4] );
+
+    {
+         v = [1,2,3,4,5];
+         assert( v.peek!(int[]) );
+         assert( v.get!(int[]) == [1,2,3,4,5] );
+    }
+
+    v = 3.1413;
+    assert( v.peek!(double) );
+    assert( v.convertsTo!(real) );
+    //@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT
+    assert( !v.convertsTo!(float) );
+    assert( *v.peek!(double) == 3.1413 );
+
+    auto u = Variant(v);
+    assert( u.peek!(double) );
+    assert( *u.peek!(double) == 3.1413 );
+
+    // operators
+    v = 38;
+    assert( v + 4 == 42 );
+    assert( 4 + v == 42 );
+    assert( v - 4 == 34 );
+    assert( Variant(4) - v == -34 );
+    assert( v * 2 == 76 );
+    assert( 2 * v == 76 );
+    assert( v / 2 == 19 );
+    assert( Variant(2) / v == 0 );
+    assert( v % 2 == 0 );
+    assert( Variant(2) % v == 2 );
+    assert( (v & 6) == 6 );
+    assert( (6 & v) == 6 );
+    assert( (v | 9) == 47 );
+    assert( (9 | v) == 47 );
+    assert( (v ^ 5) == 35 );
+    assert( (5 ^ v) == 35 );
+    assert( v << 1 == 76 );
+    assert( Variant(1) << Variant(2) == 4 );
+    assert( v >> 1 == 19 );
+    assert( Variant(4) >> Variant(2) == 1 );
+    assert( Variant("abc") ~ "def" == "abcdef" );
+    assert( Variant("abc") ~ Variant("def") == "abcdef" );
+
+    v = 38;
+    v += 4;
+    assert( v == 42 );
+    v = 38; v -= 4; assert( v == 34 );
+    v = 38; v *= 2; assert( v == 76 );
+    v = 38; v /= 2; assert( v == 19 );
+    v = 38; v %= 2; assert( v == 0 );
+    v = 38; v &= 6; assert( v == 6 );
+    v = 38; v |= 9; assert( v == 47 );
+    v = 38; v ^= 5; assert( v == 35 );
+    v = 38; v <<= 1; assert( v == 76 );
+    v = 38; v >>= 1; assert( v == 19 );
+    v = 38; v += 1;  assert( v < 40 );
+
+    v = "abc";
+    v ~= "def";
+    assert( v == "abcdef", *v.peek!(char[]) );
+    assert( Variant(0) < Variant(42) );
+    assert( Variant(42) > Variant(0) );
+    assert( Variant(42) > Variant(0.1) );
+    assert( Variant(42.1) > Variant(1) );
+    assert( Variant(21) == Variant(21) );
+    assert( Variant(0) != Variant(42) );
+    assert( Variant("bar") == Variant("bar") );
+    assert( Variant("foo") != Variant("bar") );
+
+    {
+        auto v1 = Variant(42);
+        auto v2 = Variant("foo");
+        auto v3 = Variant(1+2.0i);
+
+        int[Variant] hash;
+        hash[v1] = 0;
+        hash[v2] = 1;
+        hash[v3] = 2;
+
+        assert( hash[v1] == 0 );
+        assert( hash[v2] == 1 );
+        assert( hash[v3] == 2 );
+    }
+
+    {
+        int[char[]] hash;
+        hash["a"] = 1;
+        hash["b"] = 2;
+        hash["c"] = 3;
+        Variant vhash = hash;
+
+        assert( vhash.get!(int[char[]])["a"] == 1 );
+        assert( vhash.get!(int[char[]])["b"] == 2 );
+        assert( vhash.get!(int[char[]])["c"] == 3 );
+    }
+}
+
+unittest
+{
+    // bug 1558
+    Variant va=1;
+    Variant vb=-2;
+    assert((va+vb).get!(int) == -1);
+    assert((va-vb).get!(int) == 3);
+}
+
+unittest
+{
+    Variant a;
+    a=5;
+    Variant b;
+    b=a;
+    Variant[] c;
+    c = variantArray(1, 2, 3.0, "hello", 4);
+    assert(c[3] == "hello");
+}
+
+unittest
+{
+    Variant v = 5;
+    assert (!__traits(compiles, v.coerce!(bool delegate())));
+}
+
+
+unittest
+{
+    struct Huge {
+        real a, b, c, d, e, f, g;
+    }
+
+    Huge huge;
+    huge.e = 42;
+    Variant v;
+    v = huge;  // Compile time error.
+    assert(v.get!(Huge).e == 42);
+}
+
+unittest
+{
+    const x = Variant(42);
+    auto y1 = x.get!(const int);
+    // @@@BUG@@@
+    //auto y2 = x.get!(immutable int)();
+}
+
+// test iteration
+unittest
+{
+    auto v = Variant([ 1, 2, 3, 4 ][]);
+    auto j = 0;
+    foreach (int i; v)
+    {
+        assert(i == ++j);
+    }
+    assert(j == 4);
+}
+
+// test convertibility
+unittest
+{
+    auto v = Variant("abc".dup);
+    assert(v.convertsTo!(char[]));
+}
+
+// http://d.puremagic.com/issues/show_bug.cgi?id=5424
+unittest
+{
+    interface A {
+        void func1();
+    }
+    static class AC: A {
+        void func1() {
+        }
+    }
+
+    A a = new AC();
+    a.func1();
+    Variant b = Variant(a);
+}
+
+unittest
+{
+    // bug 7070
+    Variant v;
+    v = null;
+}
+
+// Class and interface opEquals, issue 12157
+unittest
+{
+    class Foo { }
+
+    class DerivedFoo : Foo { }
+
+    Foo f1 = new Foo();
+    Foo f2 = new DerivedFoo();
+
+    Variant v1 = f1, v2 = f2;
+    assert(v1 == f1);
+    assert(v1 != new Foo());
+    assert(v1 != f2);
+    assert(v2 != v1);
+    assert(v2 == f2);
+}
+
+// Const parameters with opCall, issue 11361.
+unittest
+{
+    static string t1(string c) {
+        return c ~ "a";
+    }
+
+    static const(char)[] t2(const(char)[] p) {
+        return p ~ "b";
+    }
+
+    static char[] t3(int p) {
+        return p.text.dup;
+    }
+
+    Variant v1 = &t1;
+    Variant v2 = &t2;
+    Variant v3 = &t3;
+
+    assert(v1("abc") == "abca");
+    assert(v1("abc").type == typeid(string));
+    assert(v2("abc") == "abcb");
+
+    assert(v2(cast(char[])("abc".dup)) == "abcb");
+    assert(v2("abc").type == typeid(const(char)[]));
+
+    assert(v3(4) == ['4']);
+    assert(v3(4).type == typeid(char[]));
+}
+
+// issue 12071
+unittest
+{
+    static struct Structure { int data; }
+    alias VariantTest = Algebraic!(Structure delegate());
+
+    bool called = false;
+    Structure example()
+    {
+        called = true;
+        return Structure.init;
+    }
+    auto m = VariantTest(&example);
+    m();
+    assert(called);
+}
+
+// Ordering comparisons of incompatible types, e.g. issue 7990.
+unittest
+{
+    assertThrown!VariantException(Variant(3) < "a");
+    assertThrown!VariantException("a" < Variant(3));
+    assertThrown!VariantException(Variant(3) < Variant("a"));
+
+    assertThrown!VariantException(Variant.init < Variant(3));
+    assertThrown!VariantException(Variant(3) < Variant.init);
+}
+
+// Handling of unordered types, e.g. issue 9043.
+unittest
+{
+    static struct A { int a; }
+
+    assert(Variant(A(3)) != A(4));
+
+    assertThrown!VariantException(Variant(A(3)) < A(4));
+    assertThrown!VariantException(A(3) < Variant(A(4)));
+    assertThrown!VariantException(Variant(A(3)) < Variant(A(4)));
+}
+
+// Handling of empty types and arrays, e.g. issue 10958
+unittest
+{
+    class EmptyClass { }
+    struct EmptyStruct { }
+    alias EmptyArray = void[0];
+    alias Alg = Algebraic!(EmptyClass, EmptyStruct, EmptyArray);
+
+    Variant testEmpty(T)()
+    {
+        T inst;
+        Variant v = inst;
+        assert(v.get!T == inst);
+        assert(v.peek!T !is null);
+        assert(*v.peek!T == inst);
+        Alg alg = inst;
+        assert(alg.get!T == inst);
+        return v;
+    }
+
+    testEmpty!EmptyClass();
+    testEmpty!EmptyStruct();
+    testEmpty!EmptyArray();
+
+    // EmptyClass/EmptyStruct sizeof is 1, so we have this to test just size 0.
+    EmptyArray arr = EmptyArray.init;
+    Algebraic!(EmptyArray) a = arr;
+    assert(a.length == 0);
+    assert(a.get!EmptyArray == arr);
+}
+
+// Handling of void function pointers / delegates, e.g. issue 11360
+unittest
+{
+    static void t1() { }
+    Variant v = &t1;
+    assert(v() == Variant.init);
+
+    static int t2() { return 3; }
+    Variant v2 = &t2;
+    assert(v2() == 3);
+}
+
+// Using peek for large structs, issue 8580
+unittest
+{
+    struct TestStruct(bool pad)
+    {
+        int val1;
+        static if (pad)
+            ubyte[Variant.size] padding;
+        int val2;
+    }
+
+    void testPeekWith(T)()
+    {
+        T inst;
+        inst.val1 = 3;
+        inst.val2 = 4;
+        Variant v = inst;
+        T* original = v.peek!T;
+        assert(original.val1 == 3);
+        assert(original.val2 == 4);
+        original.val1 = 6;
+        original.val2 = 8;
+        T modified = v.get!T;
+        assert(modified.val1 == 6);
+        assert(modified.val2 == 8);
+    }
+
+    testPeekWith!(TestStruct!false)();
+    testPeekWith!(TestStruct!true)();
+}
+
+/**
+ * Applies a delegate or function to the given Algebraic depending on the held type,
+ * ensuring that all types are handled by the visiting functions.
+ *
+ * The delegate or function having the currently held value as parameter is called
+ * with $(D variant)'s current value. Visiting handlers are passed
+ * in the template parameter list.
+ * It is statically ensured that all types of
+ * $(D variant) are handled across all handlers.
+ * $(D visit) allows delegates and static functions to be passed
+ * as parameters.
+ *
+ * If a function without parameters is specified, this function is called
+ * when variant doesn't hold a value. Exactly one parameter-less function
+ * is allowed.
+ *
+ * Duplicate overloads matching the same type in one of the visitors are disallowed.
+ *
+ * Returns: The return type of visit is deduced from the visiting functions and must be
+ * the same across all overloads.
+ * Throws: If no parameter-less, error function is specified:
+ * $(D VariantException) if $(D variant) doesn't hold a value.
+ */
+template visit(Handler ...)
+    if (Handler.length > 0)
+{
+    auto visit(VariantType)(VariantType variant)
+        if (isAlgebraic!VariantType)
+    {
+        return visitImpl!(true, VariantType, Handler)(variant);
+    }
+}
+
+///
+unittest
+{
+    Algebraic!(int, string) variant;
+
+    variant = 10;
+    assert(variant.visit!((string s) => cast(int)s.length,
+                          (int i)    => i)()
+                          == 10);
+    variant = "string";
+    assert(variant.visit!((int i) => i,
+                          (string s) => cast(int)s.length)()
+                          == 6);
+
+    // Error function usage
+    Algebraic!(int, string) emptyVar;
+    auto rslt = emptyVar.visit!((string s) => cast(int)s.length,
+                          (int i)    => i,
+                          () => -1)();
+    assert(rslt == -1);
+}
+
+unittest
+{
+    Algebraic!(size_t, string) variant;
+
+    // not all handled check
+    static assert(!__traits(compiles, variant.visit!((size_t i){ })() ));
+
+    variant = cast(size_t)10;
+    auto which = 0;
+    variant.visit!( (string s) => which = 1,
+                    (size_t i) => which = 0
+                    )();
+
+    // integer overload was called
+    assert(which == 0);
+
+    // mustn't compile as generic Variant not supported
+    Variant v;
+    static assert(!__traits(compiles, v.visit!((string s) => which = 1,
+                                               (size_t i) => which = 0
+                                                )()
+                                                ));
+
+    static size_t func(string s) {
+        return s.length;
+    }
+
+    variant = "test";
+    assert( 4 == variant.visit!(func,
+                                (size_t i) => i
+                                )());
+
+    Algebraic!(int, float, string) variant2 = 5.0f;
+    // Shouldn' t compile as float not handled by visitor.
+    static assert(!__traits(compiles, variant2.visit!(
+                        (int) {},
+                        (string) {})()));
+
+    Algebraic!(size_t, string, float) variant3;
+    variant3 = 10.0f;
+    auto floatVisited = false;
+
+    assert(variant3.visit!(
+                 (float f) { floatVisited = true; return cast(size_t)f; },
+                 func,
+                 (size_t i) { return i; }
+                 )() == 10);
+    assert(floatVisited == true);
+
+    Algebraic!(float, string) variant4;
+
+    assert(variant4.visit!(func, (float f) => cast(size_t)f, () => size_t.max)() == size_t.max);
+
+    // double error func check
+    static assert(!__traits(compiles,
+                            visit!(() => size_t.max, func, (float f) => cast(size_t)f, () => size_t.max)(variant4))
+                 );
+}
+
+/**
+ * Behaves as $(D visit) but doesn't enforce that all types are handled
+ * by the visiting functions.
+ *
+ * If a parameter-less function is specified it is called when
+ * either $(D variant) doesn't hold a value or holds a type
+ * which isn't handled by the visiting functions.
+ *
+ * Returns: The return type of tryVisit is deduced from the visiting functions and must be
+ * the same across all overloads.
+ * Throws: If no parameter-less, error function is specified: $(D VariantException) if
+ *         $(D variant) doesn't hold a value or
+ *         if $(D variant) holds a value which isn't handled by the visiting
+ *         functions.
+ */
+template tryVisit(Handler ...)
+    if (Handler.length > 0)
+{
+    auto tryVisit(VariantType)(VariantType variant)
+        if (isAlgebraic!VariantType)
+    {
+        return visitImpl!(false, VariantType, Handler)(variant);
+    }
+}
+
+///
+unittest
+{
+    Algebraic!(int, string) variant;
+
+    variant = 10;
+    auto which = -1;
+    variant.tryVisit!((int i) { which = 0; })();
+    assert(which == 0);
+
+    // Error function usage
+    variant = "test";
+    variant.tryVisit!((int i) { which = 0; },
+                      ()      { which = -100; })();
+    assert(which == -100);
+}
+
+unittest
+{
+    Algebraic!(int, string) variant;
+
+    variant = 10;
+    auto which = -1;
+    variant.tryVisit!((int i){ which = 0; })();
+
+    assert(which == 0);
+
+    variant = "test";
+
+    assertThrown!VariantException(variant.tryVisit!((int i) { which = 0; })());
+
+    void errorfunc()
+    {
+        which = -1;
+    }
+
+    variant.tryVisit!((int i) { which = 0; }, errorfunc)();
+
+    assert(which == -1);
+}
+
+private template isAlgebraic(Type)
+{
+    static if (is(Type _ == VariantN!T, T...))
+        enum isAlgebraic = T.length >= 2; // T[0] == maxDataSize, T[1..$] == AllowedTypesParam
+    else
+        enum isAlgebraic = false;
+}
+
+unittest
+{
+    static assert(!isAlgebraic!(Variant));
+    static assert( isAlgebraic!(Algebraic!(string)));
+    static assert( isAlgebraic!(Algebraic!(int, int[])));
+}
+
+private auto visitImpl(bool Strict, VariantType, Handler...)(VariantType variant)
+    if (isAlgebraic!VariantType && Handler.length > 0)
+{
+    alias AllowedTypes = VariantType.AllowedTypes;
+
+
+    /**
+     * Returns: Struct where $(D indices)  is an array which
+     * contains at the n-th position the index in Handler which takes the
+     * n-th type of AllowedTypes. If an Handler doesn't match an
+     * AllowedType, -1 is set. If a function in the delegates doesn't
+     * have parameters, the field $(D exceptionFuncIdx) is set;
+     * otherwise it's -1.
+     */
+    auto visitGetOverloadMap()
+    {
+        struct Result {
+            int[AllowedTypes.length] indices;
+            int exceptionFuncIdx = -1;
+        }
+
+        Result result;
+
+        foreach(tidx, T; AllowedTypes)
+        {
+            bool added = false;
+            foreach(dgidx, dg; Handler)
+            {
+                // Handle normal function objects
+                static if (isSomeFunction!dg)
+                {
+                    alias Params = Parameters!dg;
+                    static if (Params.length == 0)
+                    {
+                        // Just check exception functions in the first
+                        // inner iteration (over delegates)
+                        if (tidx > 0)
+                            continue;
+                        else
+                        {
+                            if (result.exceptionFuncIdx != -1)
+                                assert(false, "duplicate parameter-less (error-)function specified");
+                            result.exceptionFuncIdx = dgidx;
+                        }
+                    }
+                    else if (is(Unqual!(Params[0]) == T))
+                    {
+                        if (added)
+                            assert(false, "duplicate overload specified for type '" ~ T.stringof ~ "'");
+
+                        added = true;
+                        result.indices[tidx] = dgidx;
+                    }
+                }
+                // Handle composite visitors with opCall overloads
+                else
+                {
+                    static assert(false, dg.stringof ~ " is not a function or delegate");
+                }
+            }
+
+            if (!added)
+                result.indices[tidx] = -1;
+        }
+
+        return result;
+    }
+
+    enum HandlerOverloadMap = visitGetOverloadMap();
+
+    if (!variant.hasValue)
+    {
+        // Call the exception function. The HandlerOverloadMap
+        // will have its exceptionFuncIdx field set to value != -1 if an
+        // exception function has been specified; otherwise we just through an exception.
+        static if (HandlerOverloadMap.exceptionFuncIdx != -1)
+            return Handler[ HandlerOverloadMap.exceptionFuncIdx ]();
+        else
+            throw new VariantException("variant must hold a value before being visited.");
+    }
+
+    foreach(idx, T; AllowedTypes)
+    {
+        if (auto ptr = variant.peek!T)
+        {
+            enum dgIdx = HandlerOverloadMap.indices[idx];
+
+            static if (dgIdx == -1)
+            {
+                static if (Strict)
+                    static assert(false, "overload for type '" ~ T.stringof ~ "' hasn't been specified");
+                else
+                {
+                    static if (HandlerOverloadMap.exceptionFuncIdx != -1)
+                        return Handler[ HandlerOverloadMap.exceptionFuncIdx ]();
+                    else
+                        throw new VariantException("variant holds value of type '" ~ T.stringof ~ "' but no visitor has been provided");
+                }
+            }
+            else
+            {
+                return Handler[ dgIdx ](*ptr);
+            }
+        }
+    }
+
+    assert(false);
+}
+
+unittest
+{
+    // validate that visit can be called with a const type
+    struct Foo { int depth; }
+    struct Bar { int depth; }
+    alias FooBar = Algebraic!(Foo, Bar);
+
+    int depth(in FooBar fb) {
+        return fb.visit!((Foo foo) => foo.depth,
+                         (Bar bar) => bar.depth);
+    }
+
+    FooBar fb = Foo(3);
+    assert(depth(fb) == 3);
+}
+
+unittest
+{
+    // http://d.puremagic.com/issues/show_bug.cgi?id=5310
+    const Variant a;
+    assert(a == a);
+    Variant b;
+    assert(a == b);
+    assert(b == a);
+}
+
+unittest
+{
+    // http://d.puremagic.com/issues/show_bug.cgi?id=10017
+    static struct S
+    {
+        ubyte[Variant.size + 1] s;
+    }
+
+    Variant v1, v2;
+    v1 = S(); // the payload is allocated on the heap
+    v2 = v1;  // AssertError: target must be non-null
+    assert(v1 == v2);
+}
+unittest
+{
+    // http://d.puremagic.com/issues/show_bug.cgi?id=7069
+    Variant v;
+
+    int i = 10;
+    v = i;
+    foreach (qual; AliasSeq!(MutableOf, ConstOf))
+    {
+        assert(v.get!(qual!int) == 10);
+        assert(v.get!(qual!float) == 10.0f);
+    }
+    foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!int));
+    }
+
+    const(int) ci = 20;
+    v = ci;
+    foreach (qual; AliasSeq!(ConstOf))
+    {
+        assert(v.get!(qual!int) == 20);
+        assert(v.get!(qual!float) == 20.0f);
+    }
+    foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!int));
+        assertThrown!VariantException(v.get!(qual!float));
+    }
+
+    immutable(int) ii = ci;
+    v = ii;
+    foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
+    {
+        assert(v.get!(qual!int) == 20);
+        assert(v.get!(qual!float) == 20.0f);
+    }
+    foreach (qual; AliasSeq!(MutableOf, SharedOf))
+    {
+        assertThrown!VariantException(v.get!(qual!int));
+        assertThrown!VariantException(v.get!(qual!float));
+    }
+
+    int[] ai = [1,2,3];
+    v = ai;
+    foreach (qual; AliasSeq!(MutableOf, ConstOf))
+    {
+        assert(v.get!(qual!(int[])) == [1,2,3]);
+        assert(v.get!(qual!(int)[]) == [1,2,3]);
+    }
+    foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!(int[])));
+        assertThrown!VariantException(v.get!(qual!(int)[]));
+    }
+
+    const(int[]) cai = [4,5,6];
+    v = cai;
+    foreach (qual; AliasSeq!(ConstOf))
+    {
+        assert(v.get!(qual!(int[])) == [4,5,6]);
+        assert(v.get!(qual!(int)[]) == [4,5,6]);
+    }
+    foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!(int[])));
+        assertThrown!VariantException(v.get!(qual!(int)[]));
+    }
+
+    immutable(int[]) iai = [7,8,9];
+    v = iai;
+    //assert(v.get!(immutable(int[])) == [7,8,9]);   // Bug ??? runtime error
+    assert(v.get!(immutable(int)[]) == [7,8,9]);
+    assert(v.get!(const(int[])) == [7,8,9]);
+    assert(v.get!(const(int)[]) == [7,8,9]);
+    //assert(v.get!(shared(const(int[]))) == cast(shared const)[7,8,9]);    // Bug ??? runtime error
+    //assert(v.get!(shared(const(int))[]) == cast(shared const)[7,8,9]);    // Bug ??? runtime error
+    foreach (qual; AliasSeq!(MutableOf))
+    {
+        assertThrown!VariantException(v.get!(qual!(int[])));
+        assertThrown!VariantException(v.get!(qual!(int)[]));
+    }
+
+    class A {}
+    class B : A {}
+    B b = new B();
+    v = b;
+    foreach (qual; AliasSeq!(MutableOf, ConstOf))
+    {
+        assert(v.get!(qual!B) is b);
+        assert(v.get!(qual!A) is b);
+        assert(v.get!(qual!Object) is b);
+    }
+    foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!B));
+        assertThrown!VariantException(v.get!(qual!A));
+        assertThrown!VariantException(v.get!(qual!Object));
+    }
+
+    const(B) cb = new B();
+    v = cb;
+    foreach (qual; AliasSeq!(ConstOf))
+    {
+        assert(v.get!(qual!B) is cb);
+        assert(v.get!(qual!A) is cb);
+        assert(v.get!(qual!Object) is cb);
+    }
+    foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!B));
+        assertThrown!VariantException(v.get!(qual!A));
+        assertThrown!VariantException(v.get!(qual!Object));
+    }
+
+    immutable(B) ib = new immutable(B)();
+    v = ib;
+    foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
+    {
+        assert(v.get!(qual!B) is ib);
+        assert(v.get!(qual!A) is ib);
+        assert(v.get!(qual!Object) is ib);
+    }
+    foreach (qual; AliasSeq!(MutableOf, SharedOf))
+    {
+        assertThrown!VariantException(v.get!(qual!B));
+        assertThrown!VariantException(v.get!(qual!A));
+        assertThrown!VariantException(v.get!(qual!Object));
+    }
+
+    shared(B) sb = new shared B();
+    v = sb;
+    foreach (qual; AliasSeq!(SharedOf, SharedConstOf))
+    {
+        assert(v.get!(qual!B) is sb);
+        assert(v.get!(qual!A) is sb);
+        assert(v.get!(qual!Object) is sb);
+    }
+    foreach (qual; AliasSeq!(MutableOf, ImmutableOf, ConstOf))
+    {
+        assertThrown!VariantException(v.get!(qual!B));
+        assertThrown!VariantException(v.get!(qual!A));
+        assertThrown!VariantException(v.get!(qual!Object));
+    }
+
+    shared(const(B)) scb = new shared const B();
+    v = scb;
+    foreach (qual; AliasSeq!(SharedConstOf))
+    {
+        assert(v.get!(qual!B) is scb);
+        assert(v.get!(qual!A) is scb);
+        assert(v.get!(qual!Object) is scb);
+    }
+    foreach (qual; AliasSeq!(MutableOf, ConstOf, ImmutableOf, SharedOf))
+    {
+        assertThrown!VariantException(v.get!(qual!B));
+        assertThrown!VariantException(v.get!(qual!A));
+        assertThrown!VariantException(v.get!(qual!Object));
+    }
+}
+
+unittest
+{
+    static struct DummyScope
+    {
+        // https://d.puremagic.com/issues/show_bug.cgi?id=12540
+        alias Alias12540 = Algebraic!Class12540;
+
+        static class Class12540
+        {
+            Alias12540 entity;
+        }
+    }
+}
+
+unittest
+{
+    // https://issues.dlang.org/show_bug.cgi?id=10194
+    // Also test for elaborate copying
+    static struct S
+    {
+        @disable this();
+        this(int dummy)
+        {
+            ++cnt;
+        }
+
+        this(this)
+        {
+            ++cnt;
+        }
+
+        @disable S opAssign();
+
+        ~this()
+        {
+            --cnt;
+            assert(cnt >= 0);
+        }
+        static int cnt = 0;
+    }
+
+    {
+        Variant v;
+        {
+            v = S(0);
+            assert(S.cnt == 1);
+        }
+        assert(S.cnt == 1);
+
+        // assigning a new value should destroy the existing one
+        v = 0;
+        assert(S.cnt == 0);
+
+        // destroying the variant should destroy it's current value
+        v = S(0);
+        assert(S.cnt == 1);
+    }
+    assert(S.cnt == 0);
+}
+
+unittest
+{
+    // Bugzilla 13300
+    static struct S
+    {
+        this(this) {}
+        ~this() {}
+    }
+
+    static assert( hasElaborateCopyConstructor!(Variant));
+    static assert(!hasElaborateCopyConstructor!(Algebraic!bool));
+    static assert( hasElaborateCopyConstructor!(Algebraic!S));
+    static assert( hasElaborateCopyConstructor!(Algebraic!(bool, S)));
+
+    static assert( hasElaborateDestructor!(Variant));
+    static assert(!hasElaborateDestructor!(Algebraic!bool));
+    static assert( hasElaborateDestructor!(Algebraic!S));
+    static assert( hasElaborateDestructor!(Algebraic!(bool, S)));
+
+    import std.array;
+    alias Value = Algebraic!bool;
+
+    static struct T
+    {
+        Value value;
+        @disable this();
+    }
+    auto a = appender!(T[]);
+}
+
+unittest
+{
+    // Bugzilla 13871
+    alias A = Algebraic!(int, typeof(null));
+    static struct B { A value; }
+    alias C = std.variant.Algebraic!B;
+
+    C var;
+    var = C(B());
+}
+
+unittest
+{
+    // Make sure Variant can handle types with opDispatch but no length field.
+    struct SWithNoLength
+    {
+        void opDispatch(string s)() { }
+    }
+
+    struct SWithLength
+    {
+        @property int opDispatch(string s)()
+        {
+            // Assume that s == "length"
+            return 5; // Any value is OK for test.
+        }
+    }
+
+    SWithNoLength sWithNoLength;
+    Variant v = sWithNoLength;
+    assertThrown!VariantException(v.length);
+
+    SWithLength sWithLength;
+    v = sWithLength;
+    assertNotThrown!VariantException(v.get!SWithLength.length);
+    assertThrown!VariantException(v.length);
+}
+
+unittest
+{
+    // Bugzilla 13534
+    static assert(!__traits(compiles, () @safe {
+        auto foo() @system { return 3; }
+        auto v = Variant(&foo);
+        v(); // foo is called in safe code!?
+    }));
+}
+
+unittest
+{
+    // Bugzilla 15039
+    import std.variant;
+    import std.typecons;
+
+    alias IntTypedef = Typedef!int;
+    alias Obj = Algebraic!(int, IntTypedef, This[]);
+
+    Obj obj = 1;
+
+    obj.visit!(
+        (int x) => {},
+        (IntTypedef x) => {},
+        (Obj[] x) => {},
+    );
+}
diff --git a/libphobos/src/std/windows/charset.d b/libphobos/src/std/windows/charset.d
new file mode 100644
index 00000000000..9ee1d3db861
--- /dev/null
+++ b/libphobos/src/std/windows/charset.d
@@ -0,0 +1,126 @@
+// Written in the D programming language.
+
+/**
+ * Support UTF-8 on Windows 95, 98 and ME systems.
+ *
+ * Macros:
+ *      WIKI = Phobos/StdWindowsCharset
+ *
+ * Copyright: Copyright Digital Mars 2005 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ */
+/*          Copyright Digital Mars 2005 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.windows.charset;
+
+version (StdDdoc)
+{
+    /******************************************
+     * Converts the UTF-8 string s into a null-terminated string in a Windows
+     * 8-bit character set.
+     *
+     * Params:
+     * s = UTF-8 string to convert.
+     * codePage = is the number of the target codepage, or
+     *   0 - ANSI,
+     *   1 - OEM,
+     *   2 - Mac
+     *
+     * Authors:
+     *      yaneurao, Walter Bright, Stewart Gordon
+     */
+    const(char)* toMBSz(in char[] s, uint codePage = 0);
+
+    /**********************************************
+     * Converts the null-terminated string s from a Windows 8-bit character set
+     * into a UTF-8 char array.
+     *
+     * Params:
+     * s = UTF-8 string to convert.
+     * codePage = is the number of the source codepage, or
+     *   0 - ANSI,
+     *   1 - OEM,
+     *   2 - Mac
+     * Authors: Stewart Gordon, Walter Bright
+     */
+    string fromMBSz(immutable(char)* s, int codePage = 0);
+}
+else:
+
+version (Windows):
+
+private import std.conv;
+private import core.sys.windows.windows;
+private import std.windows.syserror;
+private import std.utf;
+private import std.string;
+
+import std.internal.cstring;
+
+const(char)* toMBSz(in char[] s, uint codePage = 0)
+{
+    // Only need to do this if any chars have the high bit set
+    foreach (char c; s)
+    {
+        if (c >= 0x80)
+        {
+            char[] result;
+            int readLen;
+            auto wsTmp = s.tempCStringW();
+            result.length = WideCharToMultiByte(codePage, 0, wsTmp, -1, null, 0,
+                    null, null);
+
+            if (result.length)
+            {
+                readLen = WideCharToMultiByte(codePage, 0, wsTmp, -1, result.ptr,
+                        to!int(result.length), null, null);
+            }
+
+            if (!readLen || readLen != result.length)
+            {
+                throw new Exception("Couldn't convert string: " ~
+                        sysErrorString(GetLastError()));
+            }
+
+            return result.ptr;
+        }
+    }
+    return std.string.toStringz(s);
+}
+
+string fromMBSz(immutable(char)* s, int codePage = 0)
+{
+    const(char)* c;
+
+    for (c = s; *c != 0; c++)
+    {
+        if (*c >= 0x80)
+        {
+            wchar[] result;
+            int readLen;
+
+            result.length = MultiByteToWideChar(codePage, 0, s, -1, null, 0);
+
+            if (result.length)
+            {
+                readLen = MultiByteToWideChar(codePage, 0, s, -1, result.ptr,
+                        to!int(result.length));
+            }
+
+            if (!readLen || readLen != result.length)
+            {
+                throw new Exception("Couldn't convert string: " ~
+                    sysErrorString(GetLastError()));
+            }
+
+            return std.utf.toUTF8(result[0 .. result.length-1]); // omit trailing null
+        }
+    }
+    return s[0 .. c-s];         // string is ASCII, no conversion necessary
+}
+
+
diff --git a/libphobos/src/std/windows/iunknown.d b/libphobos/src/std/windows/iunknown.d
new file mode 100644
index 00000000000..78099196a89
--- /dev/null
+++ b/libphobos/src/std/windows/iunknown.d
@@ -0,0 +1,13 @@
+// Written in the D programming language.
+
+// @@@DEPRECATED_2017-06@@@
+
+/++
+    $(RED Deprecated. Use $(D core.sys.windows.com) instead. This module
+          will be removed in June 2017.)
+  +/
+deprecated("Import core.sys.windows.com instead")
+module std.windows.iunknown;
+
+// Replaced by:
+public import core.sys.windows.com;
diff --git a/libphobos/src/std/windows/registry.d b/libphobos/src/std/windows/registry.d
new file mode 100644
index 00000000000..1520609bebc
--- /dev/null
+++ b/libphobos/src/std/windows/registry.d
@@ -0,0 +1,1840 @@
+/**
+    This library provides Win32 Registry facilities.
+
+    Copyright: Copyright 2003-2004 by Matthew Wilson and Synesis Software
+               Written by Matthew Wilson
+
+    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+    Author:    Matthew Wilson, Kenji Hara
+
+    History:
+        Created      15th March 2003,
+        Updated      25th April 2004,
+
+    Source:    $(PHOBOSSRC std/windows/_registry.d)
+*/
+/* /////////////////////////////////////////////////////////////////////////////
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, in both source and binary form, subject to the following
+ * restrictions:
+ *
+ * -  The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * -  Altered source versions must be plainly marked as such, and must not
+ *    be misrepresented as being the original software.
+ * -  This notice may not be removed or altered from any source
+ *    distribution.
+ *
+ * ////////////////////////////////////////////////////////////////////////// */
+module std.windows.registry;
+version (Windows):
+
+import std.array;
+import std.system : Endian, endian;
+import std.exception;
+import core.sys.windows.windows;
+import std.windows.syserror;
+import std.conv;
+import std.utf : toUTF8, toUTF16;
+private import std.internal.windows.advapi32;
+import std.internal.cstring;
+
+//debug = winreg;
+debug(winreg) import std.stdio;
+
+private
+{
+    import core.sys.windows.winbase : lstrlenW;
+
+    void enforceSucc(LONG res, lazy string message, string fn = __FILE__, size_t ln = __LINE__)
+    {
+        if (res != ERROR_SUCCESS)
+            throw new RegistryException(message, res, fn, ln);
+    }
+}
+
+/* ************* Exceptions *************** */
+
+// Do not use. Left for compatibility.
+class Win32Exception : WindowsException
+{
+    @safe
+    this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+    {
+        super(0, message, fn, ln);
+    }
+
+    @safe
+    this(string message, int errnum, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+    {
+        super(errnum, message, fn, ln);
+    }
+
+    @property int error() { return super.code; }
+}
+
+version(unittest) import std.string : startsWith, endsWith;
+
+unittest
+{
+    // Test that we can throw and catch one by its own type
+    string message = "Test W1";
+
+    auto e = collectException!Win32Exception(
+        enforce(false, new Win32Exception(message)));
+    assert(e.msg.startsWith(message));
+}
+
+unittest
+{
+    // ditto
+    string message = "Test W2";
+    int    code    = 5;
+
+    auto e = collectException!Win32Exception(
+        enforce(false, new Win32Exception(message, code)));
+    assert(e.error == code);
+    assert(e.msg.startsWith(message));
+}
+
+/**
+    Exception class thrown by the std.windows.registry classes.
+ */
+class RegistryException
+    : Win32Exception
+{
+public:
+    /**
+        Creates an instance of the exception.
+
+        Params:
+            message = The message associated with the exception.
+     */
+    @safe
+    this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+    {
+        super(message, fn, ln, next);
+    }
+
+    /**
+        Creates an instance of the exception, with the given.
+
+        Params:
+            message = The message associated with the exception.
+            error = The Win32 error number associated with the exception.
+     */
+    @safe
+    this(string message, int error, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+    {
+        super(message, error, fn, ln, next);
+    }
+}
+
+unittest
+{
+    // (i) Test that we can throw and catch one by its own type
+    string message = "Test 1";
+    int    code    = 3;
+
+    auto e = collectException!RegistryException(
+        enforce(false, new RegistryException(message, code)));
+    assert(e.error == code);
+    assert(e.msg.startsWith(message));
+}
+
+unittest
+{
+    // ditto
+    string message = "Test 2";
+
+    auto e = collectException!RegistryException(
+        enforce(false, new RegistryException(message)));
+    assert(e.msg.startsWith(message));
+}
+
+/* ************* public enumerations *************** */
+
+/**
+    Enumeration of the recognised registry access modes.
+ */
+enum REGSAM
+{
+    KEY_QUERY_VALUE         = 0x0001,   /// Permission to query subkey data
+    KEY_SET_VALUE           = 0x0002,   /// Permission to set subkey data
+    KEY_CREATE_SUB_KEY      = 0x0004,   /// Permission to create subkeys
+    KEY_ENUMERATE_SUB_KEYS  = 0x0008,   /// Permission to enumerate subkeys
+    KEY_NOTIFY              = 0x0010,   /// Permission for change notification
+    KEY_CREATE_LINK         = 0x0020,   /// Permission to create a symbolic link
+    KEY_WOW64_32KEY         = 0x0200,   /// Enables a 64- or 32-bit application to open a 32-bit key
+    KEY_WOW64_64KEY         = 0x0100,   /// Enables a 64- or 32-bit application to open a 64-bit key
+    KEY_WOW64_RES           = 0x0300,   ///
+    KEY_READ                = (STANDARD_RIGHTS_READ
+                               | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY)
+                              & ~(SYNCHRONIZE),
+                                        /// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE,
+                                        /// KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY access rights
+    KEY_WRITE               = (STANDARD_RIGHTS_WRITE
+                               | KEY_SET_VALUE | KEY_CREATE_SUB_KEY)
+                              & ~(SYNCHRONIZE),
+                                        /// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE,
+                                        /// and KEY_CREATE_SUB_KEY access rights
+    KEY_EXECUTE             = KEY_READ & ~(SYNCHRONIZE),
+                                        /// Permission for read access
+    KEY_ALL_ACCESS          = (STANDARD_RIGHTS_ALL
+                               | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY
+                               | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK)
+                              & ~(SYNCHRONIZE),
+                                        /// Combines the KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS,
+                                        /// KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, and
+                                        /// KEY_SET_VALUE access rights, plus all the standard
+                                        /// access rights except SYNCHRONIZE
+}
+
+/**
+    Enumeration of the recognised registry value types.
+ */
+enum REG_VALUE_TYPE : DWORD
+{
+    REG_UNKNOWN                     =  -1,  ///
+    REG_NONE                        =   0,  /// The null value type. (In practise this is treated as a zero-length binary array by the Win32 registry)
+    REG_SZ                          =   1,  /// A zero-terminated string
+    REG_EXPAND_SZ                   =   2,  /// A zero-terminated string containing expandable environment variable references
+    REG_BINARY                      =   3,  /// A binary blob
+    REG_DWORD                       =   4,  /// A 32-bit unsigned integer
+    REG_DWORD_LITTLE_ENDIAN         =   4,  /// A 32-bit unsigned integer, stored in little-endian byte order
+    REG_DWORD_BIG_ENDIAN            =   5,  /// A 32-bit unsigned integer, stored in big-endian byte order
+    REG_LINK                        =   6,  /// A registry link
+    REG_MULTI_SZ                    =   7,  /// A set of zero-terminated strings
+    REG_RESOURCE_LIST               =   8,  /// A hardware resource list
+    REG_FULL_RESOURCE_DESCRIPTOR    =   9,  /// A hardware resource descriptor
+    REG_RESOURCE_REQUIREMENTS_LIST  =  10,  /// A hardware resource requirements list
+    REG_QWORD                       =  11,  /// A 64-bit unsigned integer
+    REG_QWORD_LITTLE_ENDIAN         =  11,  /// A 64-bit unsigned integer, stored in little-endian byte order
+}
+
+
+/* ************* private *************** */
+
+private import core.sys.windows.winnt :
+    DELETE                  ,
+    READ_CONTROL            ,
+    WRITE_DAC               ,
+    WRITE_OWNER             ,
+    SYNCHRONIZE             ,
+
+    STANDARD_RIGHTS_REQUIRED,
+
+    STANDARD_RIGHTS_READ    ,
+    STANDARD_RIGHTS_WRITE   ,
+    STANDARD_RIGHTS_EXECUTE ,
+
+    STANDARD_RIGHTS_ALL     ,
+
+    SPECIFIC_RIGHTS_ALL     ;
+
+private import core.sys.windows.winreg :
+    REG_CREATED_NEW_KEY     ,
+    REG_OPENED_EXISTING_KEY ;
+
+// Returns samDesired but without WoW64 flags if not in WoW64 mode
+// for compatibility with Windows 2000
+private REGSAM compatibleRegsam(in REGSAM samDesired)
+{
+    return isWow64 ? samDesired : cast(REGSAM)(samDesired & ~REGSAM.KEY_WOW64_RES);
+}
+
+///Returns true, if we are in WoW64 mode and have WoW64 flags
+private bool haveWoW64Job(in REGSAM samDesired)
+{
+    return isWow64 && (samDesired & REGSAM.KEY_WOW64_RES);
+}
+
+private REG_VALUE_TYPE _RVT_from_Endian(Endian endian)
+{
+    final switch (endian)
+    {
+        case Endian.bigEndian:
+            return REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN;
+
+        case Endian.littleEndian:
+            return REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN;
+    }
+}
+
+private LONG regCloseKey(in HKEY hkey)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    /* No need to attempt to close any of the standard hive keys.
+     * Although it's documented that calling RegCloseKey() on any of
+     * these hive keys is ignored, we'd rather not trust the Win32
+     * API.
+     */
+    if (cast(uint)hkey & 0x80000000)
+    {
+        switch (cast(uint)hkey)
+        {
+            case HKEY_CLASSES_ROOT:
+            case HKEY_CURRENT_USER:
+            case HKEY_LOCAL_MACHINE:
+            case HKEY_USERS:
+            case HKEY_PERFORMANCE_DATA:
+            case HKEY_PERFORMANCE_TEXT:
+            case HKEY_PERFORMANCE_NLSTEXT:
+            case HKEY_CURRENT_CONFIG:
+            case HKEY_DYN_DATA:
+                return ERROR_SUCCESS;
+            default:
+                /* Do nothing */
+                break;
+        }
+    }
+
+    return RegCloseKey(hkey);
+}
+
+private void regFlushKey(in HKEY hkey)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    immutable res = RegFlushKey(hkey);
+    enforceSucc(res, "Key cannot be flushed");
+}
+
+private HKEY regCreateKey(in HKEY hkey, in string subKey, in DWORD dwOptions, in REGSAM samDesired,
+                          in LPSECURITY_ATTRIBUTES lpsa, out DWORD disposition)
+in
+{
+    assert(hkey !is null);
+    assert(subKey !is null);
+}
+body
+{
+    HKEY hkeyResult;
+    enforceSucc(RegCreateKeyExW(
+                        hkey, subKey.tempCStringW(), 0, null, dwOptions,
+                        compatibleRegsam(samDesired), cast(LPSECURITY_ATTRIBUTES) lpsa,
+                        &hkeyResult, &disposition),
+        "Failed to create requested key: \"" ~ subKey ~ "\"");
+
+    return hkeyResult;
+}
+
+private void regDeleteKey(in HKEY hkey, in string subKey, in REGSAM samDesired)
+in
+{
+    assert(hkey !is null);
+    assert(subKey !is null);
+}
+body
+{
+    LONG res;
+    if (haveWoW64Job(samDesired))
+    {
+        loadAdvapi32();
+        res = pRegDeleteKeyExW(hkey, subKey.tempCStringW(), samDesired, 0);
+    }
+    else
+    {
+        res = RegDeleteKeyW(hkey, subKey.tempCStringW());
+    }
+    enforceSucc(res, "Key cannot be deleted: \"" ~ subKey ~ "\"");
+}
+
+private void regDeleteValue(in HKEY hkey, in string valueName)
+in
+{
+    assert(hkey !is null);
+    assert(valueName !is null);
+}
+body
+{
+    enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()),
+        "Value cannot be deleted: \"" ~ valueName ~ "\"");
+}
+
+private HKEY regDup(HKEY hkey)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    /* Can't duplicate standard keys, but don't need to, so can just return */
+    if (cast(uint)hkey & 0x80000000)
+    {
+        switch (cast(uint)hkey)
+        {
+            case HKEY_CLASSES_ROOT:
+            case HKEY_CURRENT_USER:
+            case HKEY_LOCAL_MACHINE:
+            case HKEY_USERS:
+            case HKEY_PERFORMANCE_DATA:
+            case HKEY_PERFORMANCE_TEXT:
+            case HKEY_PERFORMANCE_NLSTEXT:
+            case HKEY_CURRENT_CONFIG:
+            case HKEY_DYN_DATA:
+                return hkey;
+            default:
+                /* Do nothing */
+                break;
+        }
+    }
+
+    HKEY hkeyDup;
+    immutable res = RegOpenKeyW(hkey, null, &hkeyDup);
+
+    debug(winreg)
+    {
+        if (res != ERROR_SUCCESS)
+        {
+            writefln("regDup() failed: 0x%08x 0x%08x %d", hkey, hkeyDup, res);
+        }
+
+        assert(res == ERROR_SUCCESS);
+    }
+
+    return (res == ERROR_SUCCESS) ? hkeyDup : null;
+}
+
+private LONG regEnumKeyName(in HKEY hkey, in DWORD index, ref wchar[] name, out DWORD cchName)
+in
+{
+    assert(hkey !is null);
+    assert(name !is null);
+    assert(name.length > 0);
+}
+out(res)
+{
+    assert(res != ERROR_MORE_DATA);
+}
+body
+{
+    // The Registry API lies about the lengths of a very few sub-key lengths
+    // so we have to test to see if it whinges about more data, and provide
+    // more if it does.
+    for (;;)
+    {
+        cchName = to!DWORD(name.length);
+        immutable res = RegEnumKeyExW(hkey, index, name.ptr, &cchName, null, null, null, null);
+        if (res != ERROR_MORE_DATA)
+            return res;
+
+        // Now need to increase the size of the buffer and try again
+        name.length *= 2;
+    }
+
+    assert(0);
+}
+
+
+private LONG regEnumValueName(in HKEY hkey, in DWORD dwIndex, ref wchar[] name, out DWORD cchName)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    for (;;)
+    {
+        cchName = to!DWORD(name.length);
+        immutable res = RegEnumValueW(hkey, dwIndex, name.ptr, &cchName, null, null, null, null);
+        if (res != ERROR_MORE_DATA)
+            return res;
+
+        name.length *= 2;
+    }
+
+    assert(0);
+}
+
+private LONG regGetNumSubKeys(in HKEY hkey, out DWORD cSubKeys, out DWORD cchSubKeyMaxLen)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    return RegQueryInfoKeyW(hkey, null, null, null, &cSubKeys,
+                            &cchSubKeyMaxLen, null, null, null, null, null, null);
+}
+
+private LONG regGetNumValues(in HKEY hkey, out DWORD cValues, out DWORD cchValueMaxLen)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    return RegQueryInfoKeyW(hkey, null, null, null, null, null, null,
+                            &cValues, &cchValueMaxLen, null, null, null);
+}
+
+private REG_VALUE_TYPE regGetValueType(in HKEY hkey, in string name)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    REG_VALUE_TYPE type;
+    enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, null, null),
+        "Value cannot be opened: \"" ~ name ~ "\"");
+
+    return type;
+}
+
+private HKEY regOpenKey(in HKEY hkey, in string subKey, in REGSAM samDesired)
+in
+{
+    assert(hkey !is null);
+    assert(subKey !is null);
+}
+body
+{
+    HKEY hkeyResult;
+    enforceSucc(RegOpenKeyExW(hkey, subKey.tempCStringW(), 0, compatibleRegsam(samDesired), &hkeyResult),
+        "Failed to open requested key: \"" ~ subKey ~ "\"");
+
+    return hkeyResult;
+}
+
+private void regQueryValue(in HKEY hkey, string name, out string value, REG_VALUE_TYPE reqType)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    import core.bitop : bswap;
+
+    REG_VALUE_TYPE type;
+
+    // See bugzilla 961 on this
+    union U
+    {
+        uint    dw;
+        ulong   qw;
+    }
+    U u;
+    void* data = &u.qw;
+    DWORD cbData = u.qw.sizeof;
+
+    auto keynameTmp = name.tempCStringW();
+    LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData);
+    if (res == ERROR_MORE_DATA)
+    {
+        data = (new ubyte[cbData]).ptr;
+        res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData);
+    }
+
+    enforceSucc(res,
+        "Cannot read the requested value");
+    enforce(type == reqType,
+            new RegistryException("Value type has been changed since the value was acquired"));
+
+    switch (type)
+    {
+        case REG_VALUE_TYPE.REG_SZ:
+        case REG_VALUE_TYPE.REG_EXPAND_SZ:
+            auto wstr = (cast(immutable(wchar)*)data)[0 .. cbData / wchar.sizeof];
+            assert(wstr.length > 0 && wstr[$-1] == '\0');
+            if (wstr.length && wstr[$-1] == '\0')
+                wstr.length = wstr.length - 1;
+            assert(wstr.length == 0 || wstr[$-1] != '\0');
+            value = toUTF8(wstr);
+            break;
+
+        case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN:
+            version(LittleEndian)
+                value = to!string(u.dw);
+            else
+                value = to!string(core.bitop.bswap(u.dw));
+            break;
+
+        case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN:
+            version(LittleEndian)
+                value = to!string(core.bitop.bswap(u.dw));
+            else
+                value = to!string(u.dw);
+            break;
+
+        case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN:
+            value = to!string(u.qw);
+            break;
+
+        case REG_VALUE_TYPE.REG_BINARY:
+        case REG_VALUE_TYPE.REG_MULTI_SZ:
+        default:
+            throw new RegistryException("Cannot read the given value as a string");
+    }
+}
+
+private void regQueryValue(in HKEY hkey, in string name, out string[] value, REG_VALUE_TYPE reqType)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    REG_VALUE_TYPE type;
+
+    auto keynameTmp = name.tempCStringW();
+    wchar[] data = new wchar[256];
+    DWORD cbData = to!DWORD(data.length * wchar.sizeof);
+    LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData);
+    if (res == ERROR_MORE_DATA)
+    {
+        data.length = cbData / wchar.sizeof;
+        res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData);
+    }
+    else if (res == ERROR_SUCCESS)
+    {
+        data.length = cbData / wchar.sizeof;
+    }
+    enforceSucc(res, "Cannot read the requested value");
+    enforce(type == REG_VALUE_TYPE.REG_MULTI_SZ,
+            new RegistryException("Cannot read the given value as a string"));
+    enforce(type == reqType,
+            new RegistryException("Value type has been changed since the value was acquired"));
+
+    // Remove last two (or one) null terminator
+    assert(data.length > 0 && data[$-1] == '\0');
+    data.length = data.length - 1;
+    if (data.length > 0 && data[$-1] == '\0')
+        data.length = data.length - 1;
+
+    auto list = std.array.split(data[], "\0");
+    value.length = list.length;
+    foreach (i, ref v; value)
+    {
+        v = toUTF8(list[i]);
+    }
+}
+
+private void regQueryValue(in HKEY hkey, in string name, out uint value, REG_VALUE_TYPE reqType)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    import core.bitop : bswap;
+
+    REG_VALUE_TYPE type;
+
+    DWORD cbData = value.sizeof;
+    enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData),
+        "Cannot read the requested value");
+    enforce(type == reqType,
+            new RegistryException("Value type has been changed since the value was acquired"));
+
+    switch (type)
+    {
+        case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN:
+            version(LittleEndian)
+                static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN);
+            else
+                value = core.bitop.bswap(value);
+            break;
+
+        case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN:
+            version(LittleEndian)
+                value = core.bitop.bswap(value);
+            else
+                static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN);
+            break;
+
+        default:
+            throw new RegistryException("Cannot read the given value as a 32-bit integer");
+    }
+}
+
+private void regQueryValue(in HKEY hkey, in string name, out ulong value, REG_VALUE_TYPE reqType)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    REG_VALUE_TYPE type;
+
+    DWORD cbData = value.sizeof;
+    enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData),
+        "Cannot read the requested value");
+    enforce(type == reqType,
+            new RegistryException("Value type has been changed since the value was acquired"));
+
+    switch (type)
+    {
+        case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN:
+            break;
+
+        default:
+            throw new RegistryException("Cannot read the given value as a 64-bit integer");
+    }
+}
+
+private void regQueryValue(in HKEY hkey, in string name, out byte[] value, REG_VALUE_TYPE reqType)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    REG_VALUE_TYPE type;
+
+    byte[] data = new byte[100];
+    DWORD cbData = to!DWORD(data.length);
+    LONG res;
+    auto keynameTmp = name.tempCStringW();
+    res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData);
+    if (res == ERROR_MORE_DATA)
+    {
+        data.length = cbData;
+        res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData);
+    }
+    enforceSucc(res, "Cannot read the requested value");
+    enforce(type == reqType,
+            new RegistryException("Value type has been changed since the value was acquired"));
+
+    switch (type)
+    {
+        case REG_VALUE_TYPE.REG_BINARY:
+            data.length = cbData;
+            value = data;
+            break;
+
+        default:
+            throw new RegistryException("Cannot read the given value as a string");
+    }
+}
+
+private void regSetValue(in HKEY hkey, in string subKey, in REG_VALUE_TYPE type, in LPCVOID lpData, in DWORD cbData)
+in
+{
+    assert(hkey !is null);
+}
+body
+{
+    enforceSucc(RegSetValueExW(hkey, subKey.tempCStringW(), 0, type, cast(BYTE*) lpData, cbData),
+        "Value cannot be set: \"" ~ subKey ~ "\"");
+}
+
+private void regProcessNthKey(HKEY hkey, scope void delegate(scope LONG delegate(DWORD, out string)) dg)
+{
+    DWORD cSubKeys;
+    DWORD cchSubKeyMaxLen;
+
+    immutable res = regGetNumSubKeys(hkey, cSubKeys, cchSubKeyMaxLen);
+    assert(res == ERROR_SUCCESS);
+
+    wchar[] sName = new wchar[cchSubKeyMaxLen + 1];
+
+    dg((DWORD index, out string name)
+    {
+        DWORD cchName;
+        immutable res = regEnumKeyName(hkey, index, sName, cchName);
+        if (res == ERROR_SUCCESS)
+        {
+            name = toUTF8(sName[0 .. cchName]);
+        }
+        return res;
+    });
+}
+
+private void regProcessNthValue(HKEY hkey, scope void delegate(scope LONG delegate(DWORD, out string)) dg)
+{
+    DWORD cValues;
+    DWORD cchValueMaxLen;
+
+    immutable res = regGetNumValues(hkey, cValues, cchValueMaxLen);
+    assert(res == ERROR_SUCCESS);
+
+    wchar[] sName = new wchar[cchValueMaxLen + 1];
+
+    dg((DWORD index, out string name)
+    {
+        DWORD cchName;
+        immutable res = regEnumValueName(hkey, index, sName, cchName);
+        if (res == ERROR_SUCCESS)
+        {
+            name = toUTF8(sName[0 .. cchName]);
+        }
+        return res;
+    });
+}
+
+/* ************* public classes *************** */
+
+/**
+    This class represents a registry key.
+ */
+class Key
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_hkey !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(HKEY hkey, string name, bool created)
+    in
+    {
+        assert(hkey !is null);
+    }
+    body
+    {
+        m_hkey = hkey;
+        m_name = name;
+    }
+
+    ~this()
+    {
+        regCloseKey(m_hkey);
+
+        // Even though this is horried waste-of-cycles programming
+        // we're doing it here so that the
+        m_hkey = null;
+    }
+
+public:
+    /// The name of the key
+    @property string name() @safe pure nothrow const
+    {
+        return m_name;
+    }
+
+    /**
+        The number of sub keys.
+     */
+    @property size_t keyCount() const
+    {
+        uint cSubKeys;
+        uint cchSubKeyMaxLen;
+        enforceSucc(regGetNumSubKeys(m_hkey, cSubKeys, cchSubKeyMaxLen),
+            "Number of sub-keys cannot be determined");
+
+        return cSubKeys;
+    }
+
+    /**
+        An enumerable sequence of all the sub-keys of this key.
+     */
+    @property KeySequence keys() @safe pure
+    {
+        return new KeySequence(this);
+    }
+
+    /**
+        An enumerable sequence of the names of all the sub-keys of this key.
+     */
+    @property KeyNameSequence keyNames() @safe pure
+    {
+        return new KeyNameSequence(this);
+    }
+
+    /**
+        The number of values.
+     */
+    @property size_t valueCount() const
+    {
+        uint cValues;
+        uint cchValueMaxLen;
+        enforceSucc(regGetNumValues(m_hkey, cValues, cchValueMaxLen),
+            "Number of values cannot be determined");
+
+        return cValues;
+    }
+
+    /**
+        An enumerable sequence of all the values of this key.
+     */
+    @property ValueSequence values() @safe pure
+    {
+        return new ValueSequence(this);
+    }
+
+    /**
+        An enumerable sequence of the names of all the values of this key.
+     */
+    @property ValueNameSequence valueNames() @safe pure
+    {
+        return new ValueNameSequence(this);
+    }
+
+public:
+    /**
+        Returns the named sub-key of this key.
+
+        Params:
+            name = The name of the subkey to create. May not be $(D null).
+        Returns:
+            The created key.
+        Throws:
+            $(D RegistryException) is thrown if the key cannot be created.
+     */
+    Key createKey(string name, REGSAM access = REGSAM.KEY_ALL_ACCESS)
+    {
+        enforce(!name.empty, new RegistryException("Key name is invalid"));
+
+        DWORD disposition;
+        HKEY hkey = regCreateKey(m_hkey, name, 0, access, null, disposition);
+        assert(hkey !is null);
+
+        // Potential resource leak here!!
+        //
+        // If the allocation of the memory for Key fails, the HKEY could be
+        // lost. Hence, we catch such a failure by the finally, and release
+        // the HKEY there. If the creation of
+        try
+        {
+            Key key = new Key(hkey, name, disposition == REG_CREATED_NEW_KEY);
+            hkey = null;
+            return key;
+        }
+        finally
+        {
+            if (hkey !is null)
+            {
+                regCloseKey(hkey);
+            }
+        }
+    }
+
+    /**
+        Returns the named sub-key of this key.
+
+        Params:
+            name = The name of the subkey to aquire. If name is the empty
+                   string, then the called key is duplicated.
+            access = The desired access; one of the $(D REGSAM) enumeration.
+        Returns:
+            The aquired key.
+        Throws:
+            This function never returns $(D null). If a key corresponding to
+            the requested name is not found, $(D RegistryException) is thrown.
+     */
+    Key getKey(string name, REGSAM access = REGSAM.KEY_READ)
+    {
+        if (name.empty)
+            return new Key(regDup(m_hkey), m_name, false);
+
+        HKEY hkey = regOpenKey(m_hkey, name, access);
+        assert(hkey !is null);
+
+        // Potential resource leak here!!
+        //
+        // If the allocation of the memory for Key fails, the HKEY could be
+        // lost. Hence, we catch such a failure by the finally, and release
+        // the HKEY there. If the creation of
+        try
+        {
+            Key key = new Key(hkey, name, false);
+            hkey = null;
+            return key;
+        }
+        finally
+        {
+            if (hkey != null)
+            {
+                regCloseKey(hkey);
+            }
+        }
+    }
+
+    /**
+        Deletes the named key.
+
+        Params:
+            name = The name of the key to delete. May not be $(D null).
+     */
+    void deleteKey(string name, REGSAM access = cast(REGSAM)0)
+    {
+        enforce(!name.empty, new RegistryException("Key name is invalid"));
+
+        regDeleteKey(m_hkey, name, access);
+    }
+
+    /**
+        Returns the named value.
+        If $(D name) is the empty string, then the default value is returned.
+
+        Returns:
+            This function never returns $(D null). If a value corresponding
+            to the requested name is not found, $(D RegistryException) is thrown.
+     */
+    Value getValue(string name)
+    {
+        return new Value(this, name, regGetValueType(m_hkey, name));
+    }
+
+    /**
+        Sets the named value with the given 32-bit unsigned integer value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The 32-bit unsigned value to set.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, uint value)
+    {
+        setValue(name, value, endian);
+    }
+
+    /**
+        Sets the named value with the given 32-bit unsigned integer value,
+        according to the desired byte-ordering.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The 32-bit unsigned value to set.
+            endian = Can be $(D Endian.BigEndian) or $(D Endian.LittleEndian).
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, uint value, Endian endian)
+    {
+        REG_VALUE_TYPE  type = _RVT_from_Endian(endian);
+
+        assert(type == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN ||
+               type == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN);
+
+        regSetValue(m_hkey, name, type, &value, value.sizeof);
+    }
+
+    /**
+        Sets the named value with the given 64-bit unsigned integer value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The 64-bit unsigned value to set.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, ulong value)
+    {
+        regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_QWORD, &value, value.sizeof);
+    }
+
+    /**
+        Sets the named value with the given string value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The string value to set.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, string value)
+    {
+        setValue(name, value, false);
+    }
+
+    /**
+        Sets the named value with the given string value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The string value to set.
+            asEXPAND_SZ = If $(D true), the value will be stored as an
+                          expandable environment string, otherwise as a normal string.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, string value, bool asEXPAND_SZ)
+    {
+        auto pszTmp = value.tempCStringW();
+        const(void)* data = pszTmp;
+        DWORD len = to!DWORD(lstrlenW(pszTmp) * wchar.sizeof);
+
+        regSetValue(m_hkey, name,
+                    asEXPAND_SZ ? REG_VALUE_TYPE.REG_EXPAND_SZ
+                                : REG_VALUE_TYPE.REG_SZ,
+                    data, len);
+    }
+
+    /**
+        Sets the named value with the given multiple-strings value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The multiple-strings value to set.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, string[] value)
+    {
+        wstring[] data = new wstring[value.length+1];
+        foreach (i, ref s; data[0..$-1])
+        {
+            s = toUTF16(value[i]);
+        }
+        data[$-1] = "\0";
+        auto ws = std.array.join(data, "\0"w);
+
+        regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_MULTI_SZ, ws.ptr, to!uint(ws.length * wchar.sizeof));
+    }
+
+    /**
+        Sets the named value with the given binary value.
+
+        Params:
+            name = The name of the value to set. If it is the empty string,
+                   sets the default value.
+            value = The binary value to set.
+        Throws:
+            If a value corresponding to the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void setValue(string name, byte[] value)
+    {
+        regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_BINARY, value.ptr, to!DWORD(value.length));
+    }
+
+    /**
+        Deletes the named value.
+
+        Params:
+            name = The name of the value to delete. May not be $(D null).
+        Throws:
+            If a value of the requested name is not found,
+            $(D RegistryException) is thrown.
+     */
+    void deleteValue(string name)
+    {
+        regDeleteValue(m_hkey, name);
+    }
+
+    /**
+        Flushes any changes to the key to disk.
+     */
+    void flush()
+    {
+        regFlushKey(m_hkey);
+    }
+
+private:
+    HKEY   m_hkey;
+    string m_name;
+}
+
+/**
+    This class represents a value of a registry key.
+ */
+class Value
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_key !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(Key key, string name, REG_VALUE_TYPE type)
+    in
+    {
+        assert(null !is key);
+    }
+    body
+    {
+        m_key = key;
+        m_type = type;
+        m_name = name;
+    }
+
+public:
+    /**
+        The name of the value.
+        If the value represents a default value of a key, which has no name,
+        the returned string will be of zero length.
+     */
+    @property string name() @safe pure nothrow const
+    {
+        return m_name;
+    }
+
+    /**
+        The type of value.
+     */
+    @property REG_VALUE_TYPE type() @safe pure nothrow const
+    {
+        return m_type;
+    }
+
+    /**
+        Obtains the current value of the value as a string.
+        If the value's type is REG_EXPAND_SZ the returned value is <b>not</b>
+        expanded; $(D value_EXPAND_SZ) should be called
+
+        Returns:
+            The contents of the value.
+        Throws:
+            $(D RegistryException) if the type of the value is not REG_SZ,
+            REG_EXPAND_SZ, REG_DWORD, or REG_QWORD.
+     */
+    @property string value_SZ() const
+    {
+        string value;
+
+        regQueryValue(m_key.m_hkey, m_name, value, m_type);
+
+        return value;
+    }
+
+    /**
+        Obtains the current value as a string, within which any environment
+        variables have undergone expansion.
+        This function works with the same value-types as $(D value_SZ).
+
+        Returns:
+            The contents of the value.
+     */
+    @property string value_EXPAND_SZ() const
+    {
+        string  value   =   value_SZ;
+
+        // ExpandEnvironemntStrings():
+        //      http://msdn2.microsoft.com/en-us/library/ms724265.aspx
+        const srcTmp        =   value.tempCStringW();
+        DWORD   cchRequired =   ExpandEnvironmentStringsW(srcTmp, null, 0);
+        wchar[]  newValue   =   new wchar[cchRequired];
+
+        immutable DWORD count = enforce!Win32Exception(
+            ExpandEnvironmentStringsW(srcTmp, newValue.ptr, to!DWORD(newValue.length)),
+            "Failed to expand environment variables");
+
+        return toUTF8(newValue[0 .. count-1]); // remove trailing 0
+    }
+
+    /**
+        Obtains the current value as an array of strings.
+
+        Returns:
+            The contents of the value.
+        Throws:
+            $(D RegistryException) if the type of the value is not REG_MULTI_SZ.
+     */
+    @property string[] value_MULTI_SZ() const
+    {
+        string[] value;
+
+        regQueryValue(m_key.m_hkey, m_name, value, m_type);
+
+        return value;
+    }
+
+    /**
+        Obtains the current value as a 32-bit unsigned integer, ordered
+        correctly according to the current architecture.
+
+        Returns:
+            The contents of the value.
+        Throws:
+            $(D RegistryException) is thrown for all types other than
+            REG_DWORD, REG_DWORD_LITTLE_ENDIAN and REG_DWORD_BIG_ENDIAN.
+     */
+    @property uint value_DWORD() const
+    {
+        uint value;
+
+        regQueryValue(m_key.m_hkey, m_name, value, m_type);
+
+        return value;
+    }
+
+    /**
+        Obtains the value as a 64-bit unsigned integer, ordered correctly
+        according to the current architecture.
+
+        Returns:
+            The contents of the value.
+        Throws:
+            $(D RegistryException) if the type of the value is not REG_QWORD.
+     */
+    @property ulong value_QWORD() const
+    {
+        ulong value;
+
+        regQueryValue(m_key.m_hkey, m_name, value, m_type);
+
+        return value;
+    }
+
+    /**
+        Obtains the value as a binary blob.
+
+        Returns:
+            The contents of the value.
+        Throws:
+            $(D RegistryException) if the type of the value is not REG_BINARY.
+     */
+    @property byte[] value_BINARY() const
+    {
+        byte[] value;
+
+        regQueryValue(m_key.m_hkey, m_name, value, m_type);
+
+        return value;
+    }
+
+private:
+    Key             m_key;
+    REG_VALUE_TYPE  m_type;
+    string          m_name;
+}
+
+/**
+    Represents the local system registry.
+ */
+final class Registry
+{
+private:
+    @disable this() { }
+
+public:
+    /// Returns the root key for the HKEY_CLASSES_ROOT hive
+    static @property Key classesRoot()     { return new Key(HKEY_CLASSES_ROOT,     "HKEY_CLASSES_ROOT",     false); }
+    /// Returns the root key for the HKEY_CURRENT_USER hive
+    static @property Key currentUser()     { return new Key(HKEY_CURRENT_USER,     "HKEY_CURRENT_USER",     false); }
+    /// Returns the root key for the HKEY_LOCAL_MACHINE hive
+    static @property Key localMachine()    { return new Key(HKEY_LOCAL_MACHINE,    "HKEY_LOCAL_MACHINE",    false); }
+    /// Returns the root key for the HKEY_USERS hive
+    static @property Key users()           { return new Key(HKEY_USERS,            "HKEY_USERS",            false); }
+    /// Returns the root key for the HKEY_PERFORMANCE_DATA hive
+    static @property Key performanceData() { return new Key(HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA", false); }
+    /// Returns the root key for the HKEY_CURRENT_CONFIG hive
+    static @property Key currentConfig()   { return new Key(HKEY_CURRENT_CONFIG,   "HKEY_CURRENT_CONFIG",   false); }
+    /// Returns the root key for the HKEY_DYN_DATA hive
+    static @property Key dynData()         { return new Key(HKEY_DYN_DATA,         "HKEY_DYN_DATA",         false); }
+}
+
+/**
+    An enumerable sequence representing the names of the sub-keys of a registry Key.
+
+Example:
+----
+Key key = ...
+foreach (string subkeyName; key.keyNames)
+{
+    // using subkeyName
+}
+----
+ */
+class KeyNameSequence
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_key !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(Key key)
+    {
+        m_key = key;
+    }
+
+public:
+    /**
+        The number of keys.
+     */
+    @property size_t count() const
+    {
+        return m_key.keyCount;
+    }
+
+    /**
+        The name of the key at the given index.
+
+        Params:
+            index = The 0-based index of the key to retrieve.
+        Returns:
+            The name of the key corresponding to the given index.
+        Throws:
+            RegistryException if no corresponding key is retrieved.
+     */
+    string getKeyName(size_t index)
+    {
+        string name;
+        regProcessNthKey(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            enforceSucc(getName(to!DWORD(index), name), "Invalid key");
+        });
+        return name;
+    }
+
+    /**
+        The name of the key at the given index.
+
+        Params:
+            index = The 0-based index of the key to retrieve.
+        Returns:
+            The name of the key corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding key is retrieved.
+     */
+    string opIndex(size_t index)
+    {
+        return getKeyName(index);
+    }
+
+public:
+    ///
+    int opApply(scope int delegate(ref string name) dg)
+    {
+        int result;
+        regProcessNthKey(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            for (DWORD index = 0; !result; ++index)
+            {
+                string name;
+                immutable res = getName(index, name);
+                if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete
+                    break;
+                enforceSucc(res, "Key name enumeration incomplete");
+
+                result = dg(name);
+            }
+        });
+        return result;
+    }
+
+private:
+    Key m_key;
+}
+
+
+/**
+    An enumerable sequence representing the sub-keys of a registry Key.
+
+Example:
+----
+Key key = ...
+foreach (Key subkey; key.keys)
+{
+    // using subkey
+}
+----
+ */
+class KeySequence
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_key !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(Key key)
+    {
+        m_key = key;
+    }
+
+public:
+    /**
+        The number of keys.
+     */
+    @property size_t count() const
+    {
+        return m_key.keyCount;
+    }
+
+    /**
+        The key at the given index.
+
+        Params:
+            index = The 0-based index of the key to retrieve.
+        Returns:
+            The key corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding key is retrieved.
+     */
+    Key getKey(size_t index)
+    {
+        string name;
+        regProcessNthKey(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            enforceSucc(getName(to!DWORD(index), name), "Invalid key");
+        });
+        return m_key.getKey(name);
+    }
+
+    /**
+        The key at the given index.
+
+        Params:
+            index = The 0-based index of the key to retrieve.
+        Returns:
+            The key corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding key is retrieved.
+     */
+    Key opIndex(size_t index)
+    {
+        return getKey(index);
+    }
+
+public:
+    ///
+    int opApply(scope int delegate(ref Key key) dg)
+    {
+        int result = 0;
+        regProcessNthKey(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            for (DWORD index = 0; !result; ++index)
+            {
+                string name;
+                immutable res = getName(index, name);
+                if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete
+                    break;
+                enforceSucc(res, "Key enumeration incomplete");
+
+                try
+                {
+                    Key key = m_key.getKey(name);
+                    result = dg(key);
+                }
+                catch (RegistryException e)
+                {
+                    // Skip inaccessible keys; they are
+                    // accessible via the KeyNameSequence
+                    if (e.error == ERROR_ACCESS_DENIED)
+                        continue;
+
+                    throw e;
+                }
+            }
+        });
+        return result;
+    }
+
+private:
+    Key m_key;
+}
+
+/**
+    An enumerable sequence representing the names of the values of a registry Key.
+
+Example:
+----
+Key key = ...
+foreach (string valueName; key.valueNames)
+{
+    // using valueName
+}
+----
+ */
+class ValueNameSequence
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_key !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(Key key)
+    {
+        m_key = key;
+    }
+
+public:
+    /**
+        The number of values.
+     */
+    @property size_t count() const
+    {
+        return m_key.valueCount;
+    }
+
+    /**
+        The name of the value at the given index.
+
+        Params:
+            index = The 0-based index of the value to retrieve.
+        Returns:
+            The name of the value corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding value is retrieved.
+     */
+    string getValueName(size_t index)
+    {
+        string name;
+        regProcessNthValue(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            enforceSucc(getName(to!DWORD(index), name), "Invalid value");
+        });
+        return name;
+    }
+
+    /**
+        The name of the value at the given index.
+
+        Params:
+            index = The 0-based index of the value to retrieve.
+        Returns:
+            The name of the value corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding value is retrieved.
+     */
+    string opIndex(size_t index)
+    {
+        return getValueName(index);
+    }
+
+public:
+    ///
+    int opApply(scope int delegate(ref string name) dg)
+    {
+        int result = 0;
+        regProcessNthValue(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            for (DWORD index = 0; !result; ++index)
+            {
+                string name;
+                immutable res = getName(index, name);
+                if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete
+                    break;
+                enforceSucc(res, "Value name enumeration incomplete");
+
+                result = dg(name);
+            }
+        });
+        return result;
+    }
+
+private:
+    Key m_key;
+}
+
+/**
+    An enumerable sequence representing the values of a registry Key.
+
+Example:
+----
+Key key = ...
+foreach (Value value; key.values)
+{
+    // using value
+}
+----
+ */
+class ValueSequence
+{
+    @safe pure nothrow
+    invariant()
+    {
+        assert(m_key !is null);
+    }
+
+private:
+    @safe pure nothrow
+    this(Key key)
+    {
+        m_key = key;
+    }
+
+public:
+    /// The number of values
+    @property size_t count() const
+    {
+        return m_key.valueCount;
+    }
+
+    /**
+        The value at the given $(D index).
+
+        Params:
+            index = The 0-based index of the value to retrieve
+        Returns:
+            The value corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding value is retrieved
+     */
+    Value getValue(size_t index)
+    {
+        string name;
+        regProcessNthValue(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            enforceSucc(getName(to!DWORD(index), name), "Invalid value");
+        });
+        return m_key.getValue(name);
+    }
+
+    /**
+        The value at the given $(D index).
+
+        Params:
+            index = The 0-based index of the value to retrieve.
+        Returns:
+            The value corresponding to the given index.
+        Throws:
+            $(D RegistryException) if no corresponding value is retrieved.
+     */
+    Value opIndex(size_t index)
+    {
+        return getValue(index);
+    }
+
+public:
+    ///
+    int opApply(scope int delegate(ref Value value) dg)
+    {
+        int result = 0;
+        regProcessNthValue(m_key.m_hkey, (scope LONG delegate(DWORD, out string) getName)
+        {
+            for (DWORD index = 0; !result; ++index)
+            {
+                string name;
+                immutable res = getName(index, name);
+                if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete
+                    break;
+                enforceSucc(res, "Value enumeration incomplete");
+
+                Value value = m_key.getValue(name);
+                result = dg(value);
+            }
+        });
+        return result;
+    }
+
+private:
+    Key m_key;
+}
+
+
+unittest
+{
+    debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded.");
+    debug(winreg) writefln("std.windows.registry.unittest read");
+
+/+
+    // Mask for test speed up
+
+    Key HKCR  = Registry.classesRoot;
+    Key CLSID = HKCR.getKey("CLSID");
+
+    foreach (Key key; CLSID.keys)
+    {
+        foreach (Value val; key.values)
+        {
+        }
+    }
++/
+    Key HKCU = Registry.currentUser;
+    assert(HKCU);
+
+    // Enumerate all subkeys of key Software
+    Key softwareKey = HKCU.getKey("Software");
+    assert(softwareKey);
+    foreach (Key key; softwareKey.keys)
+    {
+        //writefln("Key %s", key.name);
+        foreach (Value val; key.values)
+        {
+        }
+    }
+}
+
+unittest
+{
+    debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded.");
+    debug(winreg) writefln("std.windows.registry.unittest write");
+
+    // Warning: This unit test writes to the registry.
+    // The test can fail if you don't have sufficient rights
+
+    Key HKCU = Registry.currentUser;
+    assert(HKCU);
+
+    // Create a new key
+    string unittestKeyName = "Temporary key for a D UnitTest which can be deleted afterwards";
+    Key unittestKey = HKCU.createKey(unittestKeyName);
+    assert(unittestKey);
+    Key cityKey = unittestKey.createKey("CityCollection using foreign names with umlauts and accents: \u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e0\u00e1\u00e2\u00df");
+    cityKey.setValue("K\u00f6ln", "Germany"); // Cologne
+    cityKey.setValue("\u041c\u0438\u043d\u0441\u043a", "Belarus"); // Minsk
+    cityKey.setValue("\u5317\u4eac", "China"); // Bejing
+    bool foundCologne, foundMinsk, foundBeijing;
+    foreach (Value v; cityKey.values)
+    {
+        auto vname = v.name;
+        auto vvalue_SZ = v.value_SZ;
+        if (v.name == "K\u00f6ln")
+        {
+            foundCologne = true;
+            assert(v.value_SZ == "Germany");
+        }
+        if (v.name == "\u041c\u0438\u043d\u0441\u043a")
+        {
+            foundMinsk = true;
+            assert(v.value_SZ == "Belarus");
+        }
+        if (v.name == "\u5317\u4eac")
+        {
+            foundBeijing = true;
+            assert(v.value_SZ == "China");
+        }
+    }
+    assert(foundCologne);
+    assert(foundMinsk);
+    assert(foundBeijing);
+
+    Key stateKey = unittestKey.createKey("StateCollection");
+    stateKey.setValue("Germany", ["D\u00fcsseldorf", "K\u00f6ln", "Hamburg"]);
+    Value v = stateKey.getValue("Germany");
+    string[] actual = v.value_MULTI_SZ;
+    assert(actual.length == 3);
+    assert(actual[0] == "D\u00fcsseldorf");
+    assert(actual[1] == "K\u00f6ln");
+    assert(actual[2] == "Hamburg");
+
+    Key numberKey = unittestKey.createKey("Number");
+    numberKey.setValue("One", 1);
+    Value one = numberKey.getValue("One");
+    assert(one.value_SZ == "1");
+    assert(one.value_DWORD == 1);
+
+    unittestKey.deleteKey(numberKey.name);
+    unittestKey.deleteKey(stateKey.name);
+    unittestKey.deleteKey(cityKey.name);
+    HKCU.deleteKey(unittestKeyName);
+
+    auto e = collectException!RegistryException(HKCU.getKey("cDhmxsX9K23a8Uf869uB"));
+    assert(e.msg.endsWith(" (error 2)"));
+}
diff --git a/libphobos/src/std/windows/syserror.d b/libphobos/src/std/windows/syserror.d
new file mode 100644
index 00000000000..2289db3fb28
--- /dev/null
+++ b/libphobos/src/std/windows/syserror.d
@@ -0,0 +1,200 @@
+// Written in the D programming language.
+
+/**
+ * Convert Win32 error code to string.
+ *
+ * Copyright: Copyright Digital Mars 2006 - 2013.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Credits:   Based on code written by Regan Heath
+ */
+/*          Copyright Digital Mars 2006 - 2013.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.windows.syserror;
+import std.traits : isSomeString;
+
+version (StdDdoc)
+{
+    private
+    {
+        alias DWORD = uint;
+        enum LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1;
+    }
+
+    /** Query the text for a Windows error code, as returned by
+        $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
+        $(D GetLastError)), as a D string.
+     */
+    string sysErrorString(
+        DWORD errCode,
+        // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
+        int langId = LANG_NEUTRAL,
+        int subLangId = SUBLANG_DEFAULT) @trusted;
+
+    /*********************
+       Thrown if errors that set
+       $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
+       $(D GetLastError)) occur.
+     */
+    class WindowsException : Exception
+    {
+        private alias DWORD = int;
+        final @property DWORD code(); /// $(D GetLastError)'s return value.
+        this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted;
+    }
+
+    /++
+        If $(D !!value) is true, $(D value) is returned. Otherwise,
+        $(D new WindowsException(GetLastError(), msg)) is thrown.
+        $(D WindowsException) assumes that the last operation set
+        $(D GetLastError()) appropriately.
+
+        Example:
+        --------------------
+        wenforce(DeleteFileA("junk.tmp"), "DeleteFile failed");
+        --------------------
+     +/
+    T wenforce(T, S)(T value, lazy S msg = null,
+        string file = __FILE__, size_t line = __LINE__) @safe
+        if (isSomeString!S);
+}
+else:
+
+version (Windows):
+
+import std.windows.charset;
+import std.array : appender;
+import std.conv : to;
+import std.format : formattedWrite;
+import core.sys.windows.windows;
+
+string sysErrorString(
+    DWORD errCode,
+    // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
+    int langId = LANG_NEUTRAL,
+    int subLangId = SUBLANG_DEFAULT) @trusted
+{
+    auto buf = appender!string();
+
+    if (!putSysError(errCode, buf, MAKELANGID(langId, subLangId)))
+    {
+        throw new Exception(
+            "failed getting error string for WinAPI error code: " ~
+            sysErrorString(GetLastError()));
+    }
+
+    return buf.data;
+}
+
+bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0)
+{
+    wchar *lpMsgBuf = null;
+    auto res = FormatMessageW(
+        FORMAT_MESSAGE_ALLOCATE_BUFFER |
+        FORMAT_MESSAGE_FROM_SYSTEM |
+        FORMAT_MESSAGE_IGNORE_INSERTS,
+        null,
+        code,
+        langId,
+        cast(LPWSTR)&lpMsgBuf,
+        0,
+        null);
+    scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf);
+
+    if (lpMsgBuf)
+    {
+        import std.string : strip;
+        w.put(lpMsgBuf[0..res].strip());
+        return true;
+    }
+    else
+        return false;
+}
+
+
+class WindowsException : Exception
+{
+    import core.sys.windows.windows;
+
+    final @property DWORD code() { return _code; } /// $(D GetLastError)'s return value.
+    private DWORD _code;
+
+    this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted
+    {
+        _code = code;
+
+        auto buf = appender!string();
+
+        if (str != null)
+        {
+            buf.put(str);
+            if (code)
+                buf.put(": ");
+        }
+
+        if (code)
+        {
+            auto success = putSysError(code, buf);
+            formattedWrite(buf, success ? " (error %d)" : "Error %d", code);
+        }
+
+        super(buf.data, file, line);
+    }
+}
+
+
+T wenforce(T, S)(T value, lazy S msg = null,
+    string file = __FILE__, size_t line = __LINE__) if (isSomeString!S)
+{
+    if (!value)
+        throw new WindowsException(GetLastError(), to!string(msg), file, line);
+    return value;
+}
+
+T wenforce(T)(T condition, const(char)[] name, const(wchar)* namez, string file = __FILE__, size_t line = __LINE__)
+{
+    if (condition)
+        return condition;
+    string names;
+    if (!name)
+    {
+        static string trustedToString(const(wchar)* stringz) @trusted
+        {
+            import std.conv : to;
+            import core.stdc.wchar_ : wcslen;
+            auto len = wcslen(stringz);
+            return to!string(stringz[0 .. len]);
+        }
+
+        names = trustedToString(namez);
+    }
+    else
+        names = to!string(name);
+    throw new WindowsException(GetLastError(), names, file, line);
+}
+
+version(Windows)
+unittest
+{
+    import std.exception;
+    import std.string;
+    import std.algorithm : startsWith, endsWith;
+
+    auto e = collectException!WindowsException(
+        DeleteFileA("unexisting.txt").wenforce("DeleteFile")
+    );
+    assert(e.code == ERROR_FILE_NOT_FOUND);
+    assert(e.msg.startsWith("DeleteFile: "));
+    // can't test the entire message, as it depends on Windows locale
+    assert(e.msg.endsWith(" (error 2)"));
+
+    // Test code zero
+    e = new WindowsException(0);
+    assert(e.msg == "");
+
+    e = new WindowsException(0, "Test");
+    assert(e.msg == "Test");
+}
diff --git a/libphobos/src/std/xml.d b/libphobos/src/std/xml.d
new file mode 100644
index 00000000000..3b3626eef18
--- /dev/null
+++ b/libphobos/src/std/xml.d
@@ -0,0 +1,2972 @@
+// Written in the D programming language.
+
+/**
+$(RED Warning: This module is considered out-dated and not up to Phobos'
+      current standards. It will remain until we have a suitable replacement,
+      but be aware that it will not remain long term.)
+
+Classes and functions for creating and parsing XML
+
+The basic architecture of this module is that there are standalone functions,
+classes for constructing an XML document from scratch (Tag, Element and
+Document), and also classes for parsing a pre-existing XML file (ElementParser
+and DocumentParser). The parsing classes <i>may</i> be used to build a
+Document, but that is not their primary purpose. The handling capabilities of
+DocumentParser and ElementParser are sufficiently customizable that you can
+make them do pretty much whatever you want.
+
+Example: This example creates a DOM (Document Object Model) tree
+    from an XML file.
+------------------------------------------------------------------------------
+import std.xml;
+import std.stdio;
+import std.string;
+import std.file;
+
+// books.xml is used in various samples throughout the Microsoft XML Core
+// Services (MSXML) SDK.
+//
+// See http://msdn2.microsoft.com/en-us/library/ms762271(VS.85).aspx
+
+void main()
+{
+    string s = cast(string)std.file.read("books.xml");
+
+    // Check for well-formedness
+    check(s);
+
+    // Make a DOM tree
+    auto doc = new Document(s);
+
+    // Plain-print it
+    writeln(doc);
+}
+------------------------------------------------------------------------------
+
+Example: This example does much the same thing, except that the file is
+    deconstructed and reconstructed by hand. This is more work, but the
+    techniques involved offer vastly more power.
+------------------------------------------------------------------------------
+import std.xml;
+import std.stdio;
+import std.string;
+
+struct Book
+{
+    string id;
+    string author;
+    string title;
+    string genre;
+    string price;
+    string pubDate;
+    string description;
+}
+
+void main()
+{
+    string s = cast(string)std.file.read("books.xml");
+
+    // Check for well-formedness
+    check(s);
+
+    // Take it apart
+    Book[] books;
+
+    auto xml = new DocumentParser(s);
+    xml.onStartTag["book"] = (ElementParser xml)
+    {
+        Book book;
+        book.id = xml.tag.attr["id"];
+
+        xml.onEndTag["author"]       = (in Element e) { book.author      = e.text(); };
+        xml.onEndTag["title"]        = (in Element e) { book.title       = e.text(); };
+        xml.onEndTag["genre"]        = (in Element e) { book.genre       = e.text(); };
+        xml.onEndTag["price"]        = (in Element e) { book.price       = e.text(); };
+        xml.onEndTag["publish-date"] = (in Element e) { book.pubDate     = e.text(); };
+        xml.onEndTag["description"]  = (in Element e) { book.description = e.text(); };
+
+        xml.parse();
+
+        books ~= book;
+    };
+    xml.parse();
+
+    // Put it back together again;
+    auto doc = new Document(new Tag("catalog"));
+    foreach(book;books)
+    {
+        auto element = new Element("book");
+        element.tag.attr["id"] = book.id;
+
+        element ~= new Element("author",      book.author);
+        element ~= new Element("title",       book.title);
+        element ~= new Element("genre",       book.genre);
+        element ~= new Element("price",       book.price);
+        element ~= new Element("publish-date",book.pubDate);
+        element ~= new Element("description", book.description);
+
+        doc ~= element;
+    }
+
+    // Pretty-print it
+    writefln(join(doc.pretty(3),"\n"));
+}
+-------------------------------------------------------------------------------
+Macros:
+    WIKI=Phobos/StdXml
+
+Copyright: Copyright Janice Caron 2008 - 2009.
+License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors:   Janice Caron
+Source:    $(PHOBOSSRC std/_xml.d)
+*/
+/*
+         Copyright Janice Caron 2008 - 2009.
+Distributed under the Boost Software License, Version 1.0.
+   (See accompanying file LICENSE_1_0.txt or copy at
+         http://www.boost.org/LICENSE_1_0.txt)
+*/
+module std.xml;
+
+import std.algorithm : count, startsWith;
+import std.array;
+import std.ascii;
+import std.string;
+import std.encoding;
+
+enum cdata = "<![CDATA[";
+
+/**
+ * Returns true if the character is a character according to the XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isChar(dchar c) // rule 2
+{
+    if (c <= 0xD7FF)
+    {
+        if (c >= 0x20)
+            return true;
+        switch(c)
+        {
+        case 0xA:
+        case 0x9:
+        case 0xD:
+            return true;
+        default:
+            return false;
+        }
+    }
+    else if (0xE000 <= c && c <= 0x10FFFF)
+    {
+        if ((c & 0x1FFFFE) != 0xFFFE) // U+FFFE and U+FFFF
+            return true;
+    }
+    return false;
+}
+
+unittest
+{
+//  const CharTable=[0x9,0x9,0xA,0xA,0xD,0xD,0x20,0xD7FF,0xE000,0xFFFD,
+//        0x10000,0x10FFFF];
+    assert(!isChar(cast(dchar)0x8));
+    assert( isChar(cast(dchar)0x9));
+    assert( isChar(cast(dchar)0xA));
+    assert(!isChar(cast(dchar)0xB));
+    assert(!isChar(cast(dchar)0xC));
+    assert( isChar(cast(dchar)0xD));
+    assert(!isChar(cast(dchar)0xE));
+    assert(!isChar(cast(dchar)0x1F));
+    assert( isChar(cast(dchar)0x20));
+    assert( isChar('J'));
+    assert( isChar(cast(dchar)0xD7FF));
+    assert(!isChar(cast(dchar)0xD800));
+    assert(!isChar(cast(dchar)0xDFFF));
+    assert( isChar(cast(dchar)0xE000));
+    assert( isChar(cast(dchar)0xFFFD));
+    assert(!isChar(cast(dchar)0xFFFE));
+    assert(!isChar(cast(dchar)0xFFFF));
+    assert( isChar(cast(dchar)0x10000));
+    assert( isChar(cast(dchar)0x10FFFF));
+    assert(!isChar(cast(dchar)0x110000));
+
+    debug (stdxml_TestHardcodedChecks)
+    {
+        foreach (c; 0 .. dchar.max + 1)
+            assert(isChar(c) == lookup(CharTable, c));
+    }
+}
+
+/**
+ * Returns true if the character is whitespace according to the XML standard
+ *
+ * Only the following characters are considered whitespace in XML - space, tab,
+ * carriage return and linefeed
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isSpace(dchar c)
+{
+    return c == '\u0020' || c == '\u0009' || c == '\u000A' || c == '\u000D';
+}
+
+/**
+ * Returns true if the character is a digit according to the XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isDigit(dchar c)
+{
+    if (c <= 0x0039 && c >= 0x0030)
+        return true;
+    else
+        return lookup(DigitTable,c);
+}
+
+unittest
+{
+    debug (stdxml_TestHardcodedChecks)
+    {
+        foreach (c; 0 .. dchar.max + 1)
+            assert(isDigit(c) == lookup(DigitTable, c));
+    }
+}
+
+/**
+ * Returns true if the character is a letter according to the XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isLetter(dchar c) // rule 84
+{
+    return isIdeographic(c) || isBaseChar(c);
+}
+
+/**
+ * Returns true if the character is an ideographic character according to the
+ * XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isIdeographic(dchar c)
+{
+    if (c == 0x3007)
+        return true;
+    if (c <= 0x3029 && c >= 0x3021 )
+        return true;
+    if (c <= 0x9FA5 && c >= 0x4E00)
+        return true;
+    return false;
+}
+
+unittest
+{
+    assert(isIdeographic('\u4E00'));
+    assert(isIdeographic('\u9FA5'));
+    assert(isIdeographic('\u3007'));
+    assert(isIdeographic('\u3021'));
+    assert(isIdeographic('\u3029'));
+
+    debug (stdxml_TestHardcodedChecks)
+    {
+        foreach (c; 0 .. dchar.max + 1)
+            assert(isIdeographic(c) == lookup(IdeographicTable, c));
+    }
+}
+
+/**
+ * Returns true if the character is a base character according to the XML
+ * standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isBaseChar(dchar c)
+{
+    return lookup(BaseCharTable,c);
+}
+
+/**
+ * Returns true if the character is a combining character according to the
+ * XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isCombiningChar(dchar c)
+{
+    return lookup(CombiningCharTable,c);
+}
+
+/**
+ * Returns true if the character is an extender according to the XML standard
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *    c = the character to be tested
+ */
+bool isExtender(dchar c)
+{
+    return lookup(ExtenderTable,c);
+}
+
+/**
+ * Encodes a string by replacing all characters which need to be escaped with
+ * appropriate predefined XML entities.
+ *
+ * encode() escapes certain characters (ampersand, quote, apostrophe, less-than
+ * and greater-than), and similarly, decode() unescapes them. These functions
+ * are provided for convenience only. You do not need to use them when using
+ * the std.xml classes, because then all the encoding and decoding will be done
+ * for you automatically.
+ *
+ * If the string is not modified, the original will be returned.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *      s = The string to be encoded
+ *
+ * Returns: The encoded string
+ *
+ * Example:
+ * --------------
+ * writefln(encode("a > b")); // writes "a &gt; b"
+ * --------------
+ */
+S encode(S)(S s)
+{
+    string r;
+    size_t lastI;
+    auto result = appender!S();
+
+    foreach (i, c; s)
+    {
+        switch (c)
+        {
+        case '&':  r = "&amp;"; break;
+        case '"':  r = "&quot;"; break;
+        case '\'': r = "&apos;"; break;
+        case '<':  r = "&lt;"; break;
+        case '>':  r = "&gt;"; break;
+        default: continue;
+        }
+        // Replace with r
+        result.put(s[lastI .. i]);
+        result.put(r);
+        lastI = i + 1;
+    }
+
+    if (!result.data.ptr) return s;
+    result.put(s[lastI .. $]);
+    return result.data;
+}
+
+unittest
+{
+    auto s = "hello";
+    assert(encode(s) is s);
+    assert(encode("a > b") == "a &gt; b", encode("a > b"));
+    assert(encode("a < b") == "a &lt; b");
+    assert(encode("don't") == "don&apos;t");
+    assert(encode("\"hi\"") == "&quot;hi&quot;", encode("\"hi\""));
+    assert(encode("cat & dog") == "cat &amp; dog");
+}
+
+/**
+ * Mode to use for decoding.
+ *
+ * $(DDOC_ENUM_MEMBERS NONE) Do not decode
+ * $(DDOC_ENUM_MEMBERS LOOSE) Decode, but ignore errors
+ * $(DDOC_ENUM_MEMBERS STRICT) Decode, and throw exception on error
+ */
+enum DecodeMode
+{
+    NONE, LOOSE, STRICT
+}
+
+/**
+ * Decodes a string by unescaping all predefined XML entities.
+ *
+ * encode() escapes certain characters (ampersand, quote, apostrophe, less-than
+ * and greater-than), and similarly, decode() unescapes them. These functions
+ * are provided for convenience only. You do not need to use them when using
+ * the std.xml classes, because then all the encoding and decoding will be done
+ * for you automatically.
+ *
+ * This function decodes the entities &amp;amp;, &amp;quot;, &amp;apos;,
+ * &amp;lt; and &amp;gt,
+ * as well as decimal and hexadecimal entities such as &amp;#x20AC;
+ *
+ * If the string does not contain an ampersand, the original will be returned.
+ *
+ * Note that the "mode" parameter can be one of DecodeMode.NONE (do not
+ * decode), DecodeMode.LOOSE (decode, but ignore errors), or DecodeMode.STRICT
+ * (decode, and throw a DecodeException in the event of an error).
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Params:
+ *      s = The string to be decoded
+ *      mode = (optional) Mode to use for decoding. (Defaults to LOOSE).
+ *
+ * Throws: DecodeException if mode == DecodeMode.STRICT and decode fails
+ *
+ * Returns: The decoded string
+ *
+ * Example:
+ * --------------
+ * writefln(decode("a &gt; b")); // writes "a > b"
+ * --------------
+ */
+string decode(string s, DecodeMode mode=DecodeMode.LOOSE)
+{
+    if (mode == DecodeMode.NONE) return s;
+
+    char[] buffer;
+    foreach (ref i; 0 .. s.length)
+    {
+        char c = s[i];
+        if (c != '&')
+        {
+            if (buffer.length != 0) buffer ~= c;
+        }
+        else
+        {
+            if (buffer.length == 0)
+            {
+                buffer = s[0 .. i].dup;
+            }
+            if (startsWith(s[i..$],"&#"))
+            {
+                try
+                {
+                    dchar d;
+                    string t = s[i..$];
+                    checkCharRef(t, d);
+                    char[4] temp;
+                    import std.utf : encode;
+                    buffer ~= temp[0 .. encode(temp, d)];
+                    i = s.length - t.length - 1;
+                }
+                catch(Err e)
+                {
+                    if (mode == DecodeMode.STRICT)
+                        throw new DecodeException("Unescaped &");
+                    buffer ~= '&';
+                }
+            }
+            else if (startsWith(s[i..$],"&amp;" )) { buffer ~= '&';  i += 4; }
+            else if (startsWith(s[i..$],"&quot;")) { buffer ~= '"';  i += 5; }
+            else if (startsWith(s[i..$],"&apos;")) { buffer ~= '\''; i += 5; }
+            else if (startsWith(s[i..$],"&lt;"  )) { buffer ~= '<';  i += 3; }
+            else if (startsWith(s[i..$],"&gt;"  )) { buffer ~= '>';  i += 3; }
+            else
+            {
+                if (mode == DecodeMode.STRICT)
+                    throw new DecodeException("Unescaped &");
+                buffer ~= '&';
+            }
+        }
+    }
+    return (buffer.length == 0) ? s : cast(string)buffer;
+}
+
+unittest
+{
+    void assertNot(string s)
+    {
+        bool b = false;
+        try { decode(s,DecodeMode.STRICT); }
+        catch (DecodeException e) { b = true; }
+        assert(b,s);
+    }
+
+    // Assert that things that should work, do
+    auto s = "hello";
+    assert(decode(s,                DecodeMode.STRICT) is s);
+    assert(decode("a &gt; b",       DecodeMode.STRICT) == "a > b");
+    assert(decode("a &lt; b",       DecodeMode.STRICT) == "a < b");
+    assert(decode("don&apos;t",     DecodeMode.STRICT) == "don't");
+    assert(decode("&quot;hi&quot;", DecodeMode.STRICT) == "\"hi\"");
+    assert(decode("cat &amp; dog",  DecodeMode.STRICT) == "cat & dog");
+    assert(decode("&#42;",          DecodeMode.STRICT) == "*");
+    assert(decode("&#x2A;",         DecodeMode.STRICT) == "*");
+    assert(decode("cat & dog",      DecodeMode.LOOSE) == "cat & dog");
+    assert(decode("a &gt b",        DecodeMode.LOOSE) == "a &gt b");
+    assert(decode("&#;",            DecodeMode.LOOSE) == "&#;");
+    assert(decode("&#x;",           DecodeMode.LOOSE) == "&#x;");
+    assert(decode("&#2G;",          DecodeMode.LOOSE) == "&#2G;");
+    assert(decode("&#x2G;",         DecodeMode.LOOSE) == "&#x2G;");
+
+    // Assert that things that shouldn't work, don't
+    assertNot("cat & dog");
+    assertNot("a &gt b");
+    assertNot("&#;");
+    assertNot("&#x;");
+    assertNot("&#2G;");
+    assertNot("&#x2G;");
+}
+
+/**
+ * Class representing an XML document.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ */
+class Document : Element
+{
+    /**
+     * Contains all text which occurs before the root element.
+     * Defaults to &lt;?xml version="1.0"?&gt;
+     */
+    string prolog = "<?xml version=\"1.0\"?>";
+    /**
+     * Contains all text which occurs after the root element.
+     * Defaults to the empty string
+     */
+    string epilog;
+
+    /**
+     * Constructs a Document by parsing XML text.
+     *
+     * This function creates a complete DOM (Document Object Model) tree.
+     *
+     * The input to this function MUST be valid XML.
+     * This is enforced by DocumentParser's in contract.
+     *
+     * Params:
+     *      s = the complete XML text.
+     */
+    this(string s)
+    in
+    {
+        assert(s.length != 0);
+    }
+    body
+    {
+        auto xml = new DocumentParser(s);
+        string tagString = xml.tag.tagString;
+
+        this(xml.tag);
+        prolog = s[0 .. tagString.ptr - s.ptr];
+        parse(xml);
+        epilog = *xml.s;
+    }
+
+    /**
+     * Constructs a Document from a Tag.
+     *
+     * Params:
+     *      tag = the start tag of the document.
+     */
+    this(const(Tag) tag)
+    {
+        super(tag);
+    }
+
+    const
+    {
+        /**
+         * Compares two Documents for equality
+         *
+         * Example:
+         * --------------
+         * Document d1,d2;
+         * if (d1 == d2) { }
+         * --------------
+         */
+        override bool opEquals(Object o)
+        {
+            const doc = toType!(const Document)(o);
+            return
+                (prolog != doc.prolog            ) ? false : (
+                (super  != cast(const Element)doc) ? false : (
+                (epilog != doc.epilog            ) ? false : (
+            true )));
+        }
+
+        /**
+         * Compares two Documents
+         *
+         * You should rarely need to call this function. It exists so that
+         * Documents can be used as associative array keys.
+         *
+         * Example:
+         * --------------
+         * Document d1,d2;
+         * if (d1 < d2) { }
+         * --------------
+         */
+        override int opCmp(Object o)
+        {
+            const doc = toType!(const Document)(o);
+            return
+                ((prolog != doc.prolog            )
+                    ? ( prolog < doc.prolog             ? -1 : 1 ) :
+                ((super  != cast(const Element)doc)
+                    ? ( cast()super  < cast()cast(const Element)doc ? -1 : 1 ) :
+                ((epilog != doc.epilog            )
+                    ? ( epilog < doc.epilog             ? -1 : 1 ) :
+            0 )));
+        }
+
+        /**
+         * Returns the hash of a Document
+         *
+         * You should rarely need to call this function. It exists so that
+         * Documents can be used as associative array keys.
+         */
+        override size_t toHash() @trusted
+        {
+            return hash(prolog, hash(epilog, (cast()super).toHash()));
+        }
+
+        /**
+         * Returns the string representation of a Document. (That is, the
+         * complete XML of a document).
+         */
+        override string toString()
+        {
+            return prolog ~ super.toString() ~ epilog;
+        }
+    }
+}
+
+/**
+ * Class representing an XML element.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ */
+class Element : Item
+{
+    Tag tag; /// The start tag of the element
+    Item[] items; /// The element's items
+    Text[] texts; /// The element's text items
+    CData[] cdatas; /// The element's CData items
+    Comment[] comments; /// The element's comments
+    ProcessingInstruction[] pis; /// The element's processing instructions
+    Element[] elements; /// The element's child elements
+
+    /**
+     * Constructs an Element given a name and a string to be used as a Text
+     * interior.
+     *
+     * Params:
+     *      name = the name of the element.
+     *      interior = (optional) the string interior.
+     *
+     * Example:
+     * -------------------------------------------------------
+     * auto element = new Element("title","Serenity")
+     *     // constructs the element <title>Serenity</title>
+     * -------------------------------------------------------
+     */
+    this(string name, string interior=null)
+    {
+        this(new Tag(name));
+        if (interior.length != 0) opCatAssign(new Text(interior));
+    }
+
+    /**
+     * Constructs an Element from a Tag.
+     *
+     * Params:
+     *      tag_ = the start or empty tag of the element.
+     */
+    this(const(Tag) tag_)
+    {
+        this.tag = new Tag(tag_.name);
+        tag.type = TagType.EMPTY;
+        foreach(k,v;tag_.attr) tag.attr[k] = v;
+        tag.tagString = tag_.tagString;
+    }
+
+    /**
+     * Append a text item to the interior of this element
+     *
+     * Params:
+     *      item = the item you wish to append.
+     *
+     * Example:
+     * --------------
+     * Element element;
+     * element ~= new Text("hello");
+     * --------------
+     */
+    void opCatAssign(Text item)
+    {
+        texts ~= item;
+        appendItem(item);
+    }
+
+    /**
+     * Append a CData item to the interior of this element
+     *
+     * Params:
+     *      item = the item you wish to append.
+     *
+     * Example:
+     * --------------
+     * Element element;
+     * element ~= new CData("hello");
+     * --------------
+     */
+    void opCatAssign(CData item)
+    {
+        cdatas ~= item;
+        appendItem(item);
+    }
+
+    /**
+     * Append a comment to the interior of this element
+     *
+     * Params:
+     *      item = the item you wish to append.
+     *
+     * Example:
+     * --------------
+     * Element element;
+     * element ~= new Comment("hello");
+     * --------------
+     */
+    void opCatAssign(Comment item)
+    {
+        comments ~= item;
+        appendItem(item);
+    }
+
+    /**
+     * Append a processing instruction to the interior of this element
+     *
+     * Params:
+     *      item = the item you wish to append.
+     *
+     * Example:
+     * --------------
+     * Element element;
+     * element ~= new ProcessingInstruction("hello");
+     * --------------
+     */
+    void opCatAssign(ProcessingInstruction item)
+    {
+        pis ~= item;
+        appendItem(item);
+    }
+
+    /**
+     * Append a complete element to the interior of this element
+     *
+     * Params:
+     *      item = the item you wish to append.
+     *
+     * Example:
+     * --------------
+     * Element element;
+     * Element other = new Element("br");
+     * element ~= other;
+     *    // appends element representing <br />
+     * --------------
+     */
+    void opCatAssign(Element item)
+    {
+        elements ~= item;
+        appendItem(item);
+    }
+
+    private void appendItem(Item item)
+    {
+        items ~= item;
+        if (tag.type == TagType.EMPTY && !item.isEmptyXML)
+            tag.type = TagType.START;
+    }
+
+    private void parse(ElementParser xml)
+    {
+        xml.onText = (string s) { opCatAssign(new Text(s)); };
+        xml.onCData = (string s) { opCatAssign(new CData(s)); };
+        xml.onComment = (string s) { opCatAssign(new Comment(s)); };
+        xml.onPI = (string s) { opCatAssign(new ProcessingInstruction(s)); };
+
+        xml.onStartTag[null] = (ElementParser xml)
+        {
+            auto e = new Element(xml.tag);
+            e.parse(xml);
+            opCatAssign(e);
+        };
+
+        xml.parse();
+    }
+
+    /**
+     * Compares two Elements for equality
+     *
+     * Example:
+     * --------------
+     * Element e1,e2;
+     * if (e1 == e2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const element = toType!(const Element)(o);
+        auto len = items.length;
+        if (len != element.items.length) return false;
+        foreach (i; 0 .. len)
+        {
+            if (!items[i].opEquals(cast()element.items[i])) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Compares two Elements
+     *
+     * You should rarely need to call this function. It exists so that Elements
+     * can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * Element e1,e2;
+     * if (e1 < e2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const element = toType!(const Element)(o);
+        for (uint i=0; ; ++i)
+        {
+            if (i == items.length && i == element.items.length) return 0;
+            if (i == items.length) return -1;
+            if (i == element.items.length) return 1;
+            if (items[i] != element.items[i])
+                return items[i].opCmp(cast()element.items[i]);
+        }
+    }
+
+    /**
+     * Returns the hash of an Element
+     *
+     * You should rarely need to call this function. It exists so that Elements
+     * can be used as associative array keys.
+     */
+    override size_t toHash() const
+    {
+        size_t hash = tag.toHash();
+        foreach(item;items) hash += item.toHash();
+        return hash;
+    }
+
+    const
+    {
+        /**
+         * Returns the decoded interior of an element.
+         *
+         * The element is assumed to contain text <i>only</i>. So, for
+         * example, given XML such as "&lt;title&gt;Good &amp;amp;
+         * Bad&lt;/title&gt;", will return "Good &amp; Bad".
+         *
+         * Params:
+         *      mode = (optional) Mode to use for decoding. (Defaults to LOOSE).
+         *
+         * Throws: DecodeException if decode fails
+         */
+        string text(DecodeMode mode=DecodeMode.LOOSE)
+        {
+            string buffer;
+            foreach(item;items)
+            {
+                Text t = cast(Text)item;
+                if (t is null) throw new DecodeException(item.toString());
+                buffer ~= decode(t.toString(),mode);
+            }
+            return buffer;
+        }
+
+        /**
+         * Returns an indented string representation of this item
+         *
+         * Params:
+         *      indent = (optional) number of spaces by which to indent this
+         *          element. Defaults to 2.
+         */
+        override string[] pretty(uint indent=2)
+        {
+
+            if (isEmptyXML) return [ tag.toEmptyString() ];
+
+            if (items.length == 1)
+            {
+                Text t = cast(Text)(items[0]);
+                if (t !is null)
+                {
+                    return [tag.toStartString() ~ t.toString() ~ tag.toEndString()];
+                }
+            }
+
+            string[] a = [ tag.toStartString() ];
+            foreach(item;items)
+            {
+                string[] b = item.pretty(indent);
+                foreach(s;b)
+                {
+                    a ~= rightJustify(s,count(s) + indent);
+                }
+            }
+            a ~= tag.toEndString();
+            return a;
+        }
+
+        /**
+         * Returns the string representation of an Element
+         *
+         * Example:
+         * --------------
+         * auto element = new Element("br");
+         * writefln(element.toString()); // writes "<br />"
+         * --------------
+         */
+        override string toString()
+        {
+            if (isEmptyXML) return tag.toEmptyString();
+
+            string buffer = tag.toStartString();
+            foreach (item;items) { buffer ~= item.toString(); }
+            buffer ~= tag.toEndString();
+            return buffer;
+        }
+
+        override @property bool isEmptyXML() { return items.length == 0; }
+    }
+}
+
+/**
+ * Tag types.
+ *
+ * $(DDOC_ENUM_MEMBERS START) Used for start tags
+ * $(DDOC_ENUM_MEMBERS END) Used for end tags
+ * $(DDOC_ENUM_MEMBERS EMPTY) Used for empty tags
+ *
+ */
+enum TagType { START, END, EMPTY }
+
+/**
+ * Class representing an XML tag.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * The class invariant guarantees
+ * <ul>
+ * <li> that $(B type) is a valid enum TagType value</li>
+ * <li> that $(B name) consists of valid characters</li>
+ * <li> that each attribute name consists of valid characters</li>
+ * </ul>
+ */
+class Tag
+{
+    TagType type = TagType.START;   /// Type of tag
+    string name;                    /// Tag name
+    string[string] attr;            /// Associative array of attributes
+    private string tagString;
+
+    invariant()
+    {
+        string s;
+        string t;
+
+        assert(type == TagType.START
+            || type == TagType.END
+            || type == TagType.EMPTY);
+
+        s = name;
+        try { checkName(s,t); }
+        catch(Err e) { assert(false,"Invalid tag name:" ~ e.toString()); }
+
+        foreach(k,v;attr)
+        {
+            s = k;
+            try { checkName(s,t); }
+            catch(Err e)
+                { assert(false,"Invalid atrribute name:" ~ e.toString()); }
+        }
+    }
+
+    /**
+     * Constructs an instance of Tag with a specified name and type
+     *
+     * The constructor does not initialize the attributes. To initialize the
+     * attributes, you access the $(B attr) member variable.
+     *
+     * Params:
+     *      name = the Tag's name
+     *      type = (optional) the Tag's type. If omitted, defaults to
+     *          TagType.START.
+     *
+     * Example:
+     * --------------
+     * auto tag = new Tag("img",Tag.EMPTY);
+     * tag.attr["src"] = "http://example.com/example.jpg";
+     * --------------
+     */
+    this(string name, TagType type=TagType.START)
+    {
+        this.name = name;
+        this.type = type;
+    }
+
+    /* Private constructor (so don't ddoc this!)
+     *
+     * Constructs a Tag by parsing the string representation, e.g. "<html>".
+     *
+     * The string is passed by reference, and is advanced over all characters
+     * consumed.
+     *
+     * The second parameter is a dummy parameter only, required solely to
+     * distinguish this constructor from the public one.
+     */
+    private this(ref string s, bool dummy)
+    {
+        tagString = s;
+        try
+        {
+            reqc(s,'<');
+            if (optc(s,'/')) type = TagType.END;
+            name = munch(s,"^/>"~whitespace);
+            munch(s,whitespace);
+            while(s.length > 0 && s[0] != '>' && s[0] != '/')
+            {
+                string key = munch(s,"^="~whitespace);
+                munch(s,whitespace);
+                reqc(s,'=');
+                munch(s,whitespace);
+                reqc(s,'"');
+                string val = decode(munch(s,"^\""), DecodeMode.LOOSE);
+                reqc(s,'"');
+                munch(s,whitespace);
+                attr[key] = val;
+            }
+            if (optc(s,'/'))
+            {
+                if (type == TagType.END) throw new TagException("");
+                type = TagType.EMPTY;
+            }
+            reqc(s,'>');
+            tagString.length = (s.ptr - tagString.ptr);
+        }
+        catch(XMLException e)
+        {
+            tagString.length = (s.ptr - tagString.ptr);
+            throw new TagException(tagString);
+        }
+    }
+
+    const
+    {
+        /**
+         * Compares two Tags for equality
+         *
+         * You should rarely need to call this function. It exists so that Tags
+         * can be used as associative array keys.
+         *
+         * Example:
+         * --------------
+         * Tag tag1,tag2
+         * if (tag1 == tag2) { }
+         * --------------
+         */
+        override bool opEquals(Object o)
+        {
+            const tag = toType!(const Tag)(o);
+            return
+                (name != tag.name) ? false : (
+                (attr != tag.attr) ? false : (
+                (type != tag.type) ? false : (
+            true )));
+        }
+
+        /**
+         * Compares two Tags
+         *
+         * Example:
+         * --------------
+         * Tag tag1,tag2
+         * if (tag1 < tag2) { }
+         * --------------
+         */
+        override int opCmp(Object o)
+        {
+            const tag = toType!(const Tag)(o);
+            // Note that attr is an AA, so the comparison is nonsensical (bug 10381)
+            return
+                ((name != tag.name) ? ( name < tag.name ? -1 : 1 ) :
+                ((attr != tag.attr) ? ( cast(void *)attr < cast(void*)tag.attr ? -1 : 1 ) :
+                ((type != tag.type) ? ( type < tag.type ? -1 : 1 ) :
+            0 )));
+        }
+
+        /**
+         * Returns the hash of a Tag
+         *
+         * You should rarely need to call this function. It exists so that Tags
+         * can be used as associative array keys.
+         */
+        override size_t toHash()
+        {
+            return typeid(name).getHash(&name);
+        }
+
+        /**
+         * Returns the string representation of a Tag
+         *
+         * Example:
+         * --------------
+         * auto tag = new Tag("book",TagType.START);
+         * writefln(tag.toString()); // writes "<book>"
+         * --------------
+         */
+        override string toString()
+        {
+            if (isEmpty) return toEmptyString();
+            return (isEnd) ? toEndString() : toStartString();
+        }
+
+        private
+        {
+            string toNonEndString()
+            {
+                string s = "<" ~ name;
+                foreach(key,val;attr)
+                    s ~= format(" %s=\"%s\"",key,encode(val));
+                return s;
+            }
+
+            string toStartString() { return toNonEndString() ~ ">"; }
+
+            string toEndString() { return "</" ~ name ~ ">"; }
+
+            string toEmptyString() { return toNonEndString() ~ " />"; }
+        }
+
+        /**
+         * Returns true if the Tag is a start tag
+         *
+         * Example:
+         * --------------
+         * if (tag.isStart) { }
+         * --------------
+         */
+        @property bool isStart() { return type == TagType.START; }
+
+        /**
+         * Returns true if the Tag is an end tag
+         *
+         * Example:
+         * --------------
+         * if (tag.isEnd) { }
+         * --------------
+         */
+        @property bool isEnd()   { return type == TagType.END;   }
+
+        /**
+         * Returns true if the Tag is an empty tag
+         *
+         * Example:
+         * --------------
+         * if (tag.isEmpty) { }
+         * --------------
+         */
+        @property bool isEmpty() { return type == TagType.EMPTY; }
+    }
+}
+
+/**
+ * Class representing a comment
+ */
+class Comment : Item
+{
+    private string content;
+
+    /**
+     * Construct a comment
+     *
+     * Params:
+     *      content = the body of the comment
+     *
+     * Throws: CommentException if the comment body is illegal (contains "--"
+     * or exactly equals "-")
+     *
+     * Example:
+     * --------------
+     * auto item = new Comment("This is a comment");
+     *    // constructs <!--This is a comment-->
+     * --------------
+     */
+    this(string content)
+    {
+        if (content == "-" || content.indexOf("==") != -1)
+            throw new CommentException(content);
+        this.content = content;
+    }
+
+    /**
+     * Compares two comments for equality
+     *
+     * Example:
+     * --------------
+     * Comment item1,item2;
+     * if (item1 == item2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(Comment)item;
+        return t !is null && content == t.content;
+    }
+
+    /**
+     * Compares two comments
+     *
+     * You should rarely need to call this function. It exists so that Comments
+     * can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * Comment item1,item2;
+     * if (item1 < item2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(Comment)item;
+        return t !is null && (content != t.content
+            ? (content < t.content ? -1 : 1 ) : 0 );
+    }
+
+    /**
+     * Returns the hash of a Comment
+     *
+     * You should rarely need to call this function. It exists so that Comments
+     * can be used as associative array keys.
+     */
+    override size_t toHash() const { return hash(content); }
+
+    /**
+     * Returns a string representation of this comment
+     */
+    override string toString() const { return "<!--" ~ content ~ "-->"; }
+
+    override @property bool isEmptyXML() const { return false; } /// Returns false always
+}
+
+/**
+ * Class representing a Character Data section
+ */
+class CData : Item
+{
+    private string content;
+
+    /**
+     * Construct a character data section
+     *
+     * Params:
+     *      content = the body of the character data segment
+     *
+     * Throws: CDataException if the segment body is illegal (contains "]]>")
+     *
+     * Example:
+     * --------------
+     * auto item = new CData("<b>hello</b>");
+     *    // constructs <![CDATA[<b>hello</b>]]>
+     * --------------
+     */
+    this(string content)
+    {
+        if (content.indexOf("]]>") != -1) throw new CDataException(content);
+        this.content = content;
+    }
+
+    /**
+     * Compares two CDatas for equality
+     *
+     * Example:
+     * --------------
+     * CData item1,item2;
+     * if (item1 == item2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(CData)item;
+        return t !is null && content == t.content;
+    }
+
+    /**
+     * Compares two CDatas
+     *
+     * You should rarely need to call this function. It exists so that CDatas
+     * can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * CData item1,item2;
+     * if (item1 < item2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(CData)item;
+        return t !is null && (content != t.content
+            ? (content < t.content ? -1 : 1 ) : 0 );
+    }
+
+    /**
+     * Returns the hash of a CData
+     *
+     * You should rarely need to call this function. It exists so that CDatas
+     * can be used as associative array keys.
+     */
+    override size_t toHash() const { return hash(content); }
+
+    /**
+     * Returns a string representation of this CData section
+     */
+    override string toString() const { return cdata ~ content ~ "]]>"; }
+
+    override @property bool isEmptyXML() const { return false; } /// Returns false always
+}
+
+/**
+ * Class representing a text (aka Parsed Character Data) section
+ */
+class Text : Item
+{
+    private string content;
+
+    /**
+     * Construct a text (aka PCData) section
+     *
+     * Params:
+     *      content = the text. This function encodes the text before
+     *      insertion, so it is safe to insert any text
+     *
+     * Example:
+     * --------------
+     * auto Text = new CData("a < b");
+     *    // constructs a &lt; b
+     * --------------
+     */
+    this(string content)
+    {
+        this.content = encode(content);
+    }
+
+    /**
+     * Compares two text sections for equality
+     *
+     * Example:
+     * --------------
+     * Text item1,item2;
+     * if (item1 == item2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(Text)item;
+        return t !is null && content == t.content;
+    }
+
+    /**
+     * Compares two text sections
+     *
+     * You should rarely need to call this function. It exists so that Texts
+     * can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * Text item1,item2;
+     * if (item1 < item2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(Text)item;
+        return t !is null
+            && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 );
+    }
+
+    /**
+     * Returns the hash of a text section
+     *
+     * You should rarely need to call this function. It exists so that Texts
+     * can be used as associative array keys.
+     */
+    override size_t toHash() const { return hash(content); }
+
+    /**
+     * Returns a string representation of this Text section
+     */
+    override string toString() const { return content; }
+
+    /**
+     * Returns true if the content is the empty string
+     */
+    override @property bool isEmptyXML() const { return content.length == 0; }
+}
+
+/**
+ * Class representing an XML Instruction section
+ */
+class XMLInstruction : Item
+{
+    private string content;
+
+    /**
+     * Construct an XML Instruction section
+     *
+     * Params:
+     *      content = the body of the instruction segment
+     *
+     * Throws: XIException if the segment body is illegal (contains ">")
+     *
+     * Example:
+     * --------------
+     * auto item = new XMLInstruction("ATTLIST");
+     *    // constructs <!ATTLIST>
+     * --------------
+     */
+    this(string content)
+    {
+        if (content.indexOf(">") != -1) throw new XIException(content);
+        this.content = content;
+    }
+
+    /**
+     * Compares two XML instructions for equality
+     *
+     * Example:
+     * --------------
+     * XMLInstruction item1,item2;
+     * if (item1 == item2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(XMLInstruction)item;
+        return t !is null && content == t.content;
+    }
+
+    /**
+     * Compares two XML instructions
+     *
+     * You should rarely need to call this function. It exists so that
+     * XmlInstructions can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * XMLInstruction item1,item2;
+     * if (item1 < item2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(XMLInstruction)item;
+        return t !is null
+            && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 );
+    }
+
+    /**
+     * Returns the hash of an XMLInstruction
+     *
+     * You should rarely need to call this function. It exists so that
+     * XmlInstructions can be used as associative array keys.
+     */
+    override size_t toHash() const { return hash(content); }
+
+    /**
+     * Returns a string representation of this XmlInstruction
+     */
+    override string toString() const { return "<!" ~ content ~ ">"; }
+
+    override @property bool isEmptyXML() const { return false; } /// Returns false always
+}
+
+/**
+ * Class representing a Processing Instruction section
+ */
+class ProcessingInstruction : Item
+{
+    private string content;
+
+    /**
+     * Construct a Processing Instruction section
+     *
+     * Params:
+     *      content = the body of the instruction segment
+     *
+     * Throws: PIException if the segment body is illegal (contains "?>")
+     *
+     * Example:
+     * --------------
+     * auto item = new ProcessingInstruction("php");
+     *    // constructs <?php?>
+     * --------------
+     */
+    this(string content)
+    {
+        if (content.indexOf("?>") != -1) throw new PIException(content);
+        this.content = content;
+    }
+
+    /**
+     * Compares two processing instructions for equality
+     *
+     * Example:
+     * --------------
+     * ProcessingInstruction item1,item2;
+     * if (item1 == item2) { }
+     * --------------
+     */
+    override bool opEquals(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(ProcessingInstruction)item;
+        return t !is null && content == t.content;
+    }
+
+    /**
+     * Compares two processing instructions
+     *
+     * You should rarely need to call this function. It exists so that
+     * ProcessingInstructions can be used as associative array keys.
+     *
+     * Example:
+     * --------------
+     * ProcessingInstruction item1,item2;
+     * if (item1 < item2) { }
+     * --------------
+     */
+    override int opCmp(Object o)
+    {
+        const item = toType!(const Item)(o);
+        const t = cast(ProcessingInstruction)item;
+        return t !is null
+            && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 );
+    }
+
+    /**
+     * Returns the hash of a ProcessingInstruction
+     *
+     * You should rarely need to call this function. It exists so that
+     * ProcessingInstructions can be used as associative array keys.
+     */
+    override size_t toHash() const { return hash(content); }
+
+    /**
+     * Returns a string representation of this ProcessingInstruction
+     */
+    override string toString() const { return "<?" ~ content ~ "?>"; }
+
+    override @property bool isEmptyXML() const { return false; } /// Returns false always
+}
+
+/**
+ * Abstract base class for XML items
+ */
+abstract class Item
+{
+    /// Compares with another Item of same type for equality
+    abstract override bool opEquals(Object o);
+
+    /// Compares with another Item of same type
+    abstract override int opCmp(Object o);
+
+    /// Returns the hash of this item
+    abstract override size_t toHash() const;
+
+    /// Returns a string representation of this item
+    abstract override string toString() const;
+
+    /**
+     * Returns an indented string representation of this item
+     *
+     * Params:
+     *      indent = number of spaces by which to indent child elements
+     */
+    string[] pretty(uint indent) const
+    {
+        string s = strip(toString());
+        return s.length == 0 ? [] : [ s ];
+    }
+
+    /// Returns true if the item represents empty XML text
+    abstract @property bool isEmptyXML() const;
+}
+
+/**
+ * Class for parsing an XML Document.
+ *
+ * This is a subclass of ElementParser. Most of the useful functions are
+ * documented there.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Bugs:
+ *      Currently only supports UTF documents.
+ *
+ *      If there is an encoding attribute in the prolog, it is ignored.
+ *
+ */
+class DocumentParser : ElementParser
+{
+    string xmlText;
+
+    /**
+     * Constructs a DocumentParser.
+     *
+     * The input to this function MUST be valid XML.
+     * This is enforced by the function's in contract.
+     *
+     * Params:
+     *      xmlText_ = the entire XML document as text
+     *
+     */
+    this(string xmlText_)
+    in
+    {
+        assert(xmlText_.length != 0);
+        try
+        {
+            // Confirm that the input is valid XML
+            check(xmlText_);
+        }
+        catch (CheckException e)
+        {
+            // And if it's not, tell the user why not
+            assert(false, "\n" ~ e.toString());
+        }
+    }
+    body
+    {
+        xmlText = xmlText_;
+        s = &xmlText;
+        super();    // Initialize everything
+        parse();    // Parse through the root tag (but not beyond)
+    }
+}
+
+/**
+ * Class for parsing an XML element.
+ *
+ * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
+ *
+ * Note that you cannot construct instances of this class directly. You can
+ * construct a DocumentParser (which is a subclass of ElementParser), but
+ * otherwise, Instances of ElementParser will be created for you by the
+ * library, and passed your way via onStartTag handlers.
+ *
+ */
+class ElementParser
+{
+    alias Handler = void delegate(string);
+    alias ElementHandler = void delegate(in Element element);
+    alias ParserHandler = void delegate(ElementParser parser);
+
+    private
+    {
+        Tag tag_;
+        string elementStart;
+        string* s;
+
+        Handler commentHandler = null;
+        Handler cdataHandler = null;
+        Handler xiHandler = null;
+        Handler piHandler = null;
+        Handler rawTextHandler = null;
+        Handler textHandler = null;
+
+        // Private constructor for start tags
+        this(ElementParser parent)
+        {
+            s = parent.s;
+            this();
+            tag_ = parent.tag_;
+        }
+
+        // Private constructor for empty tags
+        this(Tag tag, string* t)
+        {
+            s = t;
+            this();
+            tag_ = tag;
+        }
+    }
+
+    /**
+     * The Tag at the start of the element being parsed. You can read this to
+     * determine the tag's name and attributes.
+     */
+    @property const(Tag) tag() const { return tag_; }
+
+    /**
+     * Register a handler which will be called whenever a start tag is
+     * encountered which matches the specified name. You can also pass null as
+     * the name, in which case the handler will be called for any unmatched
+     * start tag.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever a <podcast> start tag is encountered
+     * onStartTag["podcast"] = (ElementParser xml)
+     * {
+     *     // Your code here
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     *
+     * // call myEpisodeStartHandler (defined elsewhere) whenever an <episode>
+     * // start tag is encountered
+     * onStartTag["episode"] = &myEpisodeStartHandler;
+     *
+     * // call delegate dg for all other start tags
+     * onStartTag[null] = dg;
+     * --------------
+     *
+     * This library will supply your function with a new instance of
+     * ElementHandler, which may be used to parse inside the element whose
+     * start tag was just found, or to identify the tag attributes of the
+     * element, etc.
+     *
+     * Note that your function will be called for both start tags and empty
+     * tags. That is, we make no distinction between &lt;br&gt;&lt;/br&gt;
+     * and &lt;br/&gt;.
+     */
+    ParserHandler[string] onStartTag;
+
+    /**
+     * Register a handler which will be called whenever an end tag is
+     * encountered which matches the specified name. You can also pass null as
+     * the name, in which case the handler will be called for any unmatched
+     * end tag.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever a </podcast> end tag is encountered
+     * onEndTag["podcast"] = (in Element e)
+     * {
+     *     // Your code here
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     *
+     * // call myEpisodeEndHandler (defined elsewhere) whenever an </episode>
+     * // end tag is encountered
+     * onEndTag["episode"] = &myEpisodeEndHandler;
+     *
+     * // call delegate dg for all other end tags
+     * onEndTag[null] = dg;
+     * --------------
+     *
+     * Note that your function will be called for both start tags and empty
+     * tags. That is, we make no distinction between &lt;br&gt;&lt;/br&gt;
+     * and &lt;br/&gt;.
+     */
+    ElementHandler[string] onEndTag;
+
+    protected this()
+    {
+        elementStart = *s;
+    }
+
+    /**
+     * Register a handler which will be called whenever text is encountered.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever text is encountered
+     * onText = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s will have been decoded by the time you see
+     *     // it, and so may contain any character.
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    @property void onText(Handler handler) { textHandler = handler; }
+
+    /**
+     * Register an alternative handler which will be called whenever text
+     * is encountered. This differs from onText in that onText will decode
+     * the text, whereas onTextRaw will not. This allows you to make design
+     * choices, since onText will be more accurate, but slower, while
+     * onTextRaw will be faster, but less accurate. Of course, you can
+     * still call decode() within your handler, if you want, but you'd
+     * probably want to use onTextRaw only in circumstances where you
+     * know that decoding is unnecessary.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever text is encountered
+     * onText = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s will NOT have been decoded.
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    void onTextRaw(Handler handler) { rawTextHandler = handler; }
+
+    /**
+     * Register a handler which will be called whenever a character data
+     * segment is encountered.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever a CData section is encountered
+     * onCData = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s does not include the opening <![CDATA[
+     *     // nor closing ]]>
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    @property void onCData(Handler handler) { cdataHandler = handler; }
+
+    /**
+     * Register a handler which will be called whenever a comment is
+     * encountered.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever a comment is encountered
+     * onComment = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s does not include the opening <!-- nor
+     *     // closing -->
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    @property void onComment(Handler handler) { commentHandler = handler; }
+
+    /**
+     * Register a handler which will be called whenever a processing
+     * instruction is encountered.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever a processing instruction is encountered
+     * onPI = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s does not include the opening <? nor
+     *     // closing ?>
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    @property void onPI(Handler handler) { piHandler = handler; }
+
+    /**
+     * Register a handler which will be called whenever an XML instruction is
+     * encountered.
+     *
+     * Example:
+     * --------------
+     * // Call this function whenever an XML instruction is encountered
+     * // (Note: XML instructions may only occur preceding the root tag of a
+     * // document).
+     * onPI = (string s)
+     * {
+     *     // Your code here
+     *
+     *     // The passed parameter s does not include the opening <! nor
+     *     // closing >
+     *     //
+     *     // This is a a closure, so code here may reference
+     *     // variables which are outside of this scope
+     * };
+     * --------------
+     */
+    @property void onXI(Handler handler) { xiHandler = handler; }
+
+    /**
+     * Parse an XML element.
+     *
+     * Parsing will continue until the end of the current element. Any items
+     * encountered for which a handler has been registered will invoke that
+     * handler.
+     *
+     * Throws: various kinds of XMLException
+     */
+    void parse()
+    {
+        string t;
+        Tag root = tag_;
+        Tag[string] startTags;
+        if (tag_ !is null) startTags[tag_.name] = tag_;
+
+        while(s.length != 0)
+        {
+            if (startsWith(*s,"<!--"))
+            {
+                chop(*s,4);
+                t = chop(*s,indexOf(*s,"-->"));
+                if (commentHandler.funcptr !is null) commentHandler(t);
+                chop(*s,3);
+            }
+            else if (startsWith(*s,"<![CDATA["))
+            {
+                chop(*s,9);
+                t = chop(*s,indexOf(*s,"]]>"));
+                if (cdataHandler.funcptr !is null) cdataHandler(t);
+                chop(*s,3);
+            }
+            else if (startsWith(*s,"<!"))
+            {
+                chop(*s,2);
+                t = chop(*s,indexOf(*s,">"));
+                if (xiHandler.funcptr !is null) xiHandler(t);
+                chop(*s,1);
+            }
+            else if (startsWith(*s,"<?"))
+            {
+                chop(*s,2);
+                t = chop(*s,indexOf(*s,"?>"));
+                if (piHandler.funcptr !is null) piHandler(t);
+                chop(*s,2);
+            }
+            else if (startsWith(*s,"<"))
+            {
+                tag_ = new Tag(*s,true);
+                if (root is null)
+                    return; // Return to constructor of derived class
+
+                if (tag_.isStart)
+                {
+                    startTags[tag_.name] = tag_;
+
+                    auto parser = new ElementParser(this);
+
+                    auto handler = tag_.name in onStartTag;
+                    if (handler !is null) (*handler)(parser);
+                    else
+                    {
+                        handler = null in onStartTag;
+                        if (handler !is null) (*handler)(parser);
+                    }
+                }
+                else if (tag_.isEnd)
+                {
+                    auto startTag = startTags[tag_.name];
+                    string text;
+
+                    immutable(char)* p = startTag.tagString.ptr
+                        + startTag.tagString.length;
+                    immutable(char)* q = tag_.tagString.ptr;
+                    text = decode(p[0..(q-p)], DecodeMode.LOOSE);
+
+                    auto element = new Element(startTag);
+                    if (text.length != 0) element ~= new Text(text);
+
+                    auto handler = tag_.name in onEndTag;
+                    if (handler !is null) (*handler)(element);
+                    else
+                    {
+                        handler = null in onEndTag;
+                        if (handler !is null) (*handler)(element);
+                    }
+
+                    if (tag_.name == root.name) return;
+                }
+                else if (tag_.isEmpty)
+                {
+                    Tag startTag = new Tag(tag_.name);
+
+                    // FIX by hed010gy, for bug 2979
+                    // http://d.puremagic.com/issues/show_bug.cgi?id=2979
+                    if (tag_.attr.length > 0)
+                          foreach(tn,tv; tag_.attr) startTag.attr[tn]=tv;
+                    // END FIX
+
+                    // Handle the pretend start tag
+                    string s2;
+                    auto parser = new ElementParser(startTag,&s2);
+                    auto handler1 = startTag.name in onStartTag;
+                    if (handler1 !is null) (*handler1)(parser);
+                    else
+                    {
+                        handler1 = null in onStartTag;
+                        if (handler1 !is null) (*handler1)(parser);
+                    }
+
+                    // Handle the pretend end tag
+                    auto element = new Element(startTag);
+                    auto handler2 = tag_.name in onEndTag;
+                    if (handler2 !is null) (*handler2)(element);
+                    else
+                    {
+                        handler2 = null in onEndTag;
+                        if (handler2 !is null) (*handler2)(element);
+                    }
+                }
+            }
+            else
+            {
+                t = chop(*s,indexOf(*s,"<"));
+                if (rawTextHandler.funcptr !is null)
+                    rawTextHandler(t);
+                else if (textHandler.funcptr !is null)
+                    textHandler(decode(t,DecodeMode.LOOSE));
+            }
+        }
+    }
+
+    /**
+     * Returns that part of the element which has already been parsed
+     */
+    override string toString() const
+    {
+        assert(elementStart.length >= s.length);
+        return elementStart[0 .. elementStart.length - s.length];
+    }
+
+}
+
+private
+{
+    template Check(string msg)
+    {
+        string old = s;
+
+        void fail()
+        {
+            s = old;
+            throw new Err(s,msg);
+        }
+
+        void fail(Err e)
+        {
+            s = old;
+            throw new Err(s,msg,e);
+        }
+
+        void fail(string msg2)
+        {
+            fail(new Err(s,msg2));
+        }
+    }
+
+    void checkMisc(ref string s) // rule 27
+    {
+        mixin Check!("Misc");
+
+        try
+        {
+                 if (s.startsWith("<!--")) { checkComment(s); }
+            else if (s.startsWith("<?"))   { checkPI(s); }
+            else                           { checkSpace(s); }
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkDocument(ref string s) // rule 1
+    {
+        mixin Check!("Document");
+        try
+        {
+            checkProlog(s);
+            checkElement(s);
+            star!(checkMisc)(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkChars(ref string s) // rule 2
+    {
+        // TO DO - Fix std.utf stride and decode functions, then use those
+        // instead
+
+        mixin Check!("Chars");
+
+        dchar c;
+        int n = -1;
+        foreach(int i,dchar d; s)
+        {
+            if (!isChar(d))
+            {
+                c = d;
+                n = i;
+                break;
+            }
+        }
+        if (n != -1)
+        {
+            s = s[n..$];
+            fail(format("invalid character: U+%04X",c));
+        }
+    }
+
+    void checkSpace(ref string s) // rule 3
+    {
+        mixin Check!("Whitespace");
+        munch(s,"\u0020\u0009\u000A\u000D");
+        if (s is old) fail();
+    }
+
+    void checkName(ref string s, out string name) // rule 5
+    {
+        mixin Check!("Name");
+
+        if (s.length == 0) fail();
+        int n;
+        foreach(int i,dchar c;s)
+        {
+            if (c == '_' || c == ':' || isLetter(c)) continue;
+            if (i == 0) fail();
+            if (c == '-' || c == '.' || isDigit(c)
+                || isCombiningChar(c) || isExtender(c)) continue;
+            n = i;
+            break;
+        }
+        name = s[0..n];
+        s = s[n..$];
+    }
+
+    void checkAttValue(ref string s) // rule 10
+    {
+        mixin Check!("AttValue");
+
+        if (s.length == 0) fail();
+        char c = s[0];
+        if (c != '\u0022' && c != '\u0027')
+            fail("attribute value requires quotes");
+        s = s[1..$];
+        for(;;)
+        {
+            munch(s,"^<&"~c);
+            if (s.length == 0) fail("unterminated attribute value");
+            if (s[0] == '<') fail("< found in attribute value");
+            if (s[0] == c) break;
+            try { checkReference(s); } catch(Err e) { fail(e); }
+        }
+        s = s[1..$];
+    }
+
+    void checkCharData(ref string s) // rule 14
+    {
+        mixin Check!("CharData");
+
+        while (s.length != 0)
+        {
+            if (s.startsWith("&")) break;
+            if (s.startsWith("<")) break;
+            if (s.startsWith("]]>")) fail("]]> found within char data");
+            s = s[1..$];
+        }
+    }
+
+    void checkComment(ref string s) // rule 15
+    {
+        mixin Check!("Comment");
+
+        try { checkLiteral("<!--",s); } catch(Err e) { fail(e); }
+        ptrdiff_t n = s.indexOf("--");
+        if (n == -1) fail("unterminated comment");
+        s = s[n..$];
+        try { checkLiteral("-->",s); } catch(Err e) { fail(e); }
+    }
+
+    void checkPI(ref string s) // rule 16
+    {
+        mixin Check!("PI");
+
+        try
+        {
+            checkLiteral("<?",s);
+            checkEnd("?>",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkCDSect(ref string s) // rule 18
+    {
+        mixin Check!("CDSect");
+
+        try
+        {
+            checkLiteral(cdata,s);
+            checkEnd("]]>",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkProlog(ref string s) // rule 22
+    {
+        mixin Check!("Prolog");
+
+        try
+        {
+            /* The XML declaration is optional
+             * http://www.w3.org/TR/2008/REC-xml-20081126/#NT-prolog
+             */
+            opt!(checkXMLDecl)(s);
+
+            star!(checkMisc)(s);
+            opt!(seq!(checkDocTypeDecl,star!(checkMisc)))(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkXMLDecl(ref string s) // rule 23
+    {
+        mixin Check!("XMLDecl");
+
+        try
+        {
+            checkLiteral("<?xml",s);
+            checkVersionInfo(s);
+            opt!(checkEncodingDecl)(s);
+            opt!(checkSDDecl)(s);
+            opt!(checkSpace)(s);
+            checkLiteral("?>",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkVersionInfo(ref string s) // rule 24
+    {
+        mixin Check!("VersionInfo");
+
+        try
+        {
+            checkSpace(s);
+            checkLiteral("version",s);
+            checkEq(s);
+            quoted!(checkVersionNum)(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkEq(ref string s) // rule 25
+    {
+        mixin Check!("Eq");
+
+        try
+        {
+            opt!(checkSpace)(s);
+            checkLiteral("=",s);
+            opt!(checkSpace)(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkVersionNum(ref string s) // rule 26
+    {
+        mixin Check!("VersionNum");
+
+        munch(s,"a-zA-Z0-9_.:-");
+        if (s is old) fail();
+    }
+
+    void checkDocTypeDecl(ref string s) // rule 28
+    {
+        mixin Check!("DocTypeDecl");
+
+        try
+        {
+            checkLiteral("<!DOCTYPE",s);
+            //
+            // TO DO -- ensure DOCTYPE is well formed
+            // (But not yet. That's one of our "future directions")
+            //
+            checkEnd(">",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkSDDecl(ref string s) // rule 32
+    {
+        mixin Check!("SDDecl");
+
+        try
+        {
+            checkSpace(s);
+            checkLiteral("standalone",s);
+            checkEq(s);
+        }
+        catch(Err e) { fail(e); }
+
+        int n = 0;
+             if (s.startsWith("'yes'") || s.startsWith("\"yes\"")) n = 5;
+        else if (s.startsWith("'no'" ) || s.startsWith("\"no\"" )) n = 4;
+        else fail("standalone attribute value must be 'yes', \"yes\","~
+            " 'no' or \"no\"");
+        s = s[n..$];
+    }
+
+    void checkElement(ref string s) // rule 39
+    {
+        mixin Check!("Element");
+
+        string sname,ename,t;
+        try { checkTag(s,t,sname); } catch(Err e) { fail(e); }
+
+        if (t == "STag")
+        {
+            try
+            {
+                checkContent(s);
+                t = s;
+                checkETag(s,ename);
+            }
+            catch(Err e) { fail(e); }
+
+            if (sname != ename)
+            {
+                s = t;
+                fail("end tag name \"" ~ ename
+                    ~ "\" differs from start tag name \""~sname~"\"");
+            }
+        }
+    }
+
+    // rules 40 and 44
+    void checkTag(ref string s, out string type, out string name)
+    {
+        mixin Check!("Tag");
+
+        try
+        {
+            type = "STag";
+            checkLiteral("<",s);
+            checkName(s,name);
+            star!(seq!(checkSpace,checkAttribute))(s);
+            opt!(checkSpace)(s);
+            if (s.length != 0 && s[0] == '/')
+            {
+                s = s[1..$];
+                type = "ETag";
+            }
+            checkLiteral(">",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkAttribute(ref string s) // rule 41
+    {
+        mixin Check!("Attribute");
+
+        try
+        {
+            string name;
+            checkName(s,name);
+            checkEq(s);
+            checkAttValue(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkETag(ref string s, out string name) // rule 42
+    {
+        mixin Check!("ETag");
+
+        try
+        {
+            checkLiteral("</",s);
+            checkName(s,name);
+            opt!(checkSpace)(s);
+            checkLiteral(">",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkContent(ref string s) // rule 43
+    {
+        mixin Check!("Content");
+
+        try
+        {
+            while (s.length != 0)
+            {
+                old = s;
+                     if (s.startsWith("&"))        { checkReference(s); }
+                else if (s.startsWith("<!--"))     { checkComment(s); }
+                else if (s.startsWith("<?"))       { checkPI(s); }
+                else if (s.startsWith(cdata)) { checkCDSect(s); }
+                else if (s.startsWith("</"))       { break; }
+                else if (s.startsWith("<"))        { checkElement(s); }
+                else                               { checkCharData(s); }
+            }
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkCharRef(ref string s, out dchar c) // rule 66
+    {
+        mixin Check!("CharRef");
+
+        c = 0;
+        try { checkLiteral("&#",s); } catch(Err e) { fail(e); }
+        int radix = 10;
+        if (s.length != 0 && s[0] == 'x')
+        {
+            s = s[1..$];
+            radix = 16;
+        }
+        if (s.length == 0) fail("unterminated character reference");
+        if (s[0] == ';')
+            fail("character reference must have at least one digit");
+        while (s.length != 0)
+        {
+            char d = s[0];
+            int n = 0;
+            switch(d)
+            {
+                case 'F','f': ++n;      goto case;
+                case 'E','e': ++n;      goto case;
+                case 'D','d': ++n;      goto case;
+                case 'C','c': ++n;      goto case;
+                case 'B','b': ++n;      goto case;
+                case 'A','a': ++n;      goto case;
+                case '9':     ++n;      goto case;
+                case '8':     ++n;      goto case;
+                case '7':     ++n;      goto case;
+                case '6':     ++n;      goto case;
+                case '5':     ++n;      goto case;
+                case '4':     ++n;      goto case;
+                case '3':     ++n;      goto case;
+                case '2':     ++n;      goto case;
+                case '1':     ++n;      goto case;
+                case '0':     break;
+                default: n = 100; break;
+            }
+            if (n >= radix) break;
+            c *= radix;
+            c += n;
+            s = s[1..$];
+        }
+        if (!isChar(c)) fail(format("U+%04X is not a legal character",c));
+        if (s.length == 0 || s[0] != ';') fail("expected ;");
+        else s = s[1..$];
+    }
+
+    void checkReference(ref string s) // rule 67
+    {
+        mixin Check!("Reference");
+
+        try
+        {
+            dchar c;
+            if (s.startsWith("&#")) checkCharRef(s,c);
+            else checkEntityRef(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkEntityRef(ref string s) // rule 68
+    {
+        mixin Check!("EntityRef");
+
+        try
+        {
+            string name;
+            checkLiteral("&",s);
+            checkName(s,name);
+            checkLiteral(";",s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    void checkEncName(ref string s) // rule 81
+    {
+        mixin Check!("EncName");
+
+        munch(s,"a-zA-Z");
+        if (s is old) fail();
+        munch(s,"a-zA-Z0-9_.-");
+    }
+
+    void checkEncodingDecl(ref string s) // rule 80
+    {
+        mixin Check!("EncodingDecl");
+
+        try
+        {
+            checkSpace(s);
+            checkLiteral("encoding",s);
+            checkEq(s);
+            quoted!(checkEncName)(s);
+        }
+        catch(Err e) { fail(e); }
+    }
+
+    // Helper functions
+
+    void checkLiteral(string literal,ref string s)
+    {
+        mixin Check!("Literal");
+
+        if (!s.startsWith(literal)) fail("Expected literal \""~literal~"\"");
+        s = s[literal.length..$];
+    }
+
+    void checkEnd(string end,ref string s)
+    {
+        // Deliberately no mixin Check here.
+
+        auto n = s.indexOf(end);
+        if (n == -1) throw new Err(s,"Unable to find terminating \""~end~"\"");
+        s = s[n..$];
+        checkLiteral(end,s);
+    }
+
+    // Metafunctions -- none of these use mixin Check
+
+    void opt(alias f)(ref string s)
+    {
+        try { f(s); } catch(Err e) {}
+    }
+
+    void plus(alias f)(ref string s)
+    {
+        f(s);
+        star!(f)(s);
+    }
+
+    void star(alias f)(ref string s)
+    {
+        while (s.length != 0)
+        {
+            try { f(s); }
+            catch(Err e) { return; }
+        }
+    }
+
+    void quoted(alias f)(ref string s)
+    {
+        if (s.startsWith("'"))
+        {
+            checkLiteral("'",s);
+            f(s);
+            checkLiteral("'",s);
+        }
+        else
+        {
+            checkLiteral("\"",s);
+            f(s);
+            checkLiteral("\"",s);
+        }
+    }
+
+    void seq(alias f,alias g)(ref string s)
+    {
+        f(s);
+        g(s);
+    }
+}
+
+/**
+ * Check an entire XML document for well-formedness
+ *
+ * Params:
+ *      s = the document to be checked, passed as a string
+ *
+ * Throws: CheckException if the document is not well formed
+ *
+ * CheckException's toString() method will yield the complete hierarchy of
+ * parse failure (the XML equivalent of a stack trace), giving the line and
+ * column number of every failure at every level.
+ */
+void check(string s)
+{
+    try
+    {
+        checkChars(s);
+        checkDocument(s);
+        if (s.length != 0) throw new Err(s,"Junk found after document");
+    }
+    catch(Err e)
+    {
+        e.complete(s);
+        throw e;
+    }
+}
+
+unittest
+{
+  version (none) // WHY ARE WE NOT RUNNING THIS UNIT TEST?
+  {
+    try
+    {
+        check(q"[<?xml version="1.0"?>
+        <catalog>
+           <book id="bk101">
+              <author>Gambardella, Matthew</author>
+              <title>XML Developer's Guide</title>
+              <genre>Computer</genre>
+              <price>44.95</price>
+              <publish_date>2000-10-01</publish_date>
+              <description>An in-depth look at creating applications
+              with XML.</description>
+           </book>
+           <book id="bk102">
+              <author>Ralls, Kim</author>
+              <title>Midnight Rain</title>
+              <genre>Fantasy</genres>
+              <price>5.95</price>
+              <publish_date>2000-12-16</publish_date>
+              <description>A former architect battles corporate zombies,
+              an evil sorceress, and her own childhood to become queen
+              of the world.</description>
+           </book>
+           <book id="bk103">
+              <author>Corets, Eva</author>
+              <title>Maeve Ascendant</title>
+              <genre>Fantasy</genre>
+              <price>5.95</price>
+              <publish_date>2000-11-17</publish_date>
+              <description>After the collapse of a nanotechnology
+              society in England, the young survivors lay the
+              foundation for a new society.</description>
+           </book>
+        </catalog>
+        ]");
+    assert(false);
+    }
+    catch(CheckException e)
+    {
+        int n = e.toString().indexOf("end tag name \"genres\" differs"~
+            " from start tag name \"genre\"");
+        assert(n != -1);
+    }
+  }
+}
+
+unittest
+{
+    string s = q"EOS
+<?xml version="1.0"?>
+<set>
+    <one>A</one>
+    <!-- comment -->
+    <two>B</two>
+</set>
+EOS";
+    try
+    {
+        check(s);
+    }
+    catch (CheckException e)
+    {
+        assert(0, e.toString());
+    }
+}
+
+unittest
+{
+    string s = q"EOS
+<?xml version="1.0" encoding="utf-8"?> <Tests>
+    <Test thing="What &amp; Up">What &amp; Up Second</Test>
+</Tests>
+EOS";
+    auto xml = new DocumentParser(s);
+
+    xml.onStartTag["Test"] = (ElementParser xml) {
+        assert(xml.tag.attr["thing"] == "What & Up");
+    };
+
+    xml.onEndTag["Test"] = (in Element e) {
+        assert(e.text() == "What & Up Second");
+    };
+    xml.parse();
+}
+
+unittest
+{
+    string s = `<tag attr="&quot;value&gt;" />`;
+    auto doc = new Document(s);
+    assert(doc.toString() == s);
+}
+
+/** The base class for exceptions thrown by this module */
+class XMLException : Exception { this(string msg) { super(msg); } }
+
+// Other exceptions
+
+/// Thrown during Comment constructor
+class CommentException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown during CData constructor
+class CDataException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown during XMLInstruction constructor
+class XIException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown during ProcessingInstruction constructor
+class PIException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown during Text constructor
+class TextException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown during decode()
+class DecodeException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown if comparing with wrong type
+class InvalidTypeException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/// Thrown when parsing for Tags
+class TagException : XMLException
+{ private this(string msg) { super(msg); } }
+
+/**
+ * Thrown during check()
+ */
+class CheckException : XMLException
+{
+    CheckException err; /// Parent in hierarchy
+    private string tail;
+    /**
+     * Name of production rule which failed to parse,
+     * or specific error message
+     */
+    string msg;
+    size_t line = 0; /// Line number at which parse failure occurred
+    size_t column = 0; /// Column number at which parse failure occurred
+
+    private this(string tail,string msg,Err err=null)
+    {
+        super(null);
+        this.tail = tail;
+        this.msg = msg;
+        this.err = err;
+    }
+
+    private void complete(string entire)
+    {
+        string head = entire[0..$-tail.length];
+        ptrdiff_t n = head.lastIndexOf('\n') + 1;
+        line = head.count("\n") + 1;
+        dstring t;
+        transcode(head[n..$],t);
+        column = t.length + 1;
+        if (err !is null) err.complete(entire);
+    }
+
+    override string toString() const
+    {
+        string s;
+        if (line != 0) s = format("Line %d, column %d: ",line,column);
+        s ~= msg;
+        s ~= '\n';
+        if (err !is null) s = err.toString() ~ s;
+        return s;
+    }
+}
+
+private alias Err = CheckException;
+
+// Private helper functions
+
+private
+{
+    T toType(T)(Object o)
+    {
+        T t = cast(T)(o);
+        if (t is null)
+        {
+            throw new InvalidTypeException("Attempt to compare a "
+                ~ T.stringof ~ " with an instance of another type");
+        }
+        return t;
+    }
+
+    string chop(ref string s, size_t n)
+    {
+        if (n == -1) n = s.length;
+        string t = s[0..n];
+        s = s[n..$];
+        return t;
+    }
+
+    bool optc(ref string s, char c)
+    {
+        bool b = s.length != 0 && s[0] == c;
+        if (b) s = s[1..$];
+        return b;
+    }
+
+    void reqc(ref string s, char c)
+    {
+        if (s.length == 0 || s[0] != c) throw new TagException("");
+        s = s[1..$];
+    }
+
+    size_t hash(string s,size_t h=0) @trusted nothrow
+    {
+        return typeid(s).getHash(&s) + h;
+    }
+
+    // Definitions from the XML specification
+    immutable CharTable=[0x9,0x9,0xA,0xA,0xD,0xD,0x20,0xD7FF,0xE000,0xFFFD,
+        0x10000,0x10FFFF];
+    immutable BaseCharTable=[0x0041,0x005A,0x0061,0x007A,0x00C0,0x00D6,0x00D8,
+        0x00F6,0x00F8,0x00FF,0x0100,0x0131,0x0134,0x013E,0x0141,0x0148,0x014A,
+        0x017E,0x0180,0x01C3,0x01CD,0x01F0,0x01F4,0x01F5,0x01FA,0x0217,0x0250,
+        0x02A8,0x02BB,0x02C1,0x0386,0x0386,0x0388,0x038A,0x038C,0x038C,0x038E,
+        0x03A1,0x03A3,0x03CE,0x03D0,0x03D6,0x03DA,0x03DA,0x03DC,0x03DC,0x03DE,
+        0x03DE,0x03E0,0x03E0,0x03E2,0x03F3,0x0401,0x040C,0x040E,0x044F,0x0451,
+        0x045C,0x045E,0x0481,0x0490,0x04C4,0x04C7,0x04C8,0x04CB,0x04CC,0x04D0,
+        0x04EB,0x04EE,0x04F5,0x04F8,0x04F9,0x0531,0x0556,0x0559,0x0559,0x0561,
+        0x0586,0x05D0,0x05EA,0x05F0,0x05F2,0x0621,0x063A,0x0641,0x064A,0x0671,
+        0x06B7,0x06BA,0x06BE,0x06C0,0x06CE,0x06D0,0x06D3,0x06D5,0x06D5,0x06E5,
+        0x06E6,0x0905,0x0939,0x093D,0x093D,0x0958,0x0961,0x0985,0x098C,0x098F,
+        0x0990,0x0993,0x09A8,0x09AA,0x09B0,0x09B2,0x09B2,0x09B6,0x09B9,0x09DC,
+        0x09DD,0x09DF,0x09E1,0x09F0,0x09F1,0x0A05,0x0A0A,0x0A0F,0x0A10,0x0A13,
+        0x0A28,0x0A2A,0x0A30,0x0A32,0x0A33,0x0A35,0x0A36,0x0A38,0x0A39,0x0A59,
+        0x0A5C,0x0A5E,0x0A5E,0x0A72,0x0A74,0x0A85,0x0A8B,0x0A8D,0x0A8D,0x0A8F,
+        0x0A91,0x0A93,0x0AA8,0x0AAA,0x0AB0,0x0AB2,0x0AB3,0x0AB5,0x0AB9,0x0ABD,
+        0x0ABD,0x0AE0,0x0AE0,0x0B05,0x0B0C,0x0B0F,0x0B10,0x0B13,0x0B28,0x0B2A,
+        0x0B30,0x0B32,0x0B33,0x0B36,0x0B39,0x0B3D,0x0B3D,0x0B5C,0x0B5D,0x0B5F,
+        0x0B61,0x0B85,0x0B8A,0x0B8E,0x0B90,0x0B92,0x0B95,0x0B99,0x0B9A,0x0B9C,
+        0x0B9C,0x0B9E,0x0B9F,0x0BA3,0x0BA4,0x0BA8,0x0BAA,0x0BAE,0x0BB5,0x0BB7,
+        0x0BB9,0x0C05,0x0C0C,0x0C0E,0x0C10,0x0C12,0x0C28,0x0C2A,0x0C33,0x0C35,
+        0x0C39,0x0C60,0x0C61,0x0C85,0x0C8C,0x0C8E,0x0C90,0x0C92,0x0CA8,0x0CAA,
+        0x0CB3,0x0CB5,0x0CB9,0x0CDE,0x0CDE,0x0CE0,0x0CE1,0x0D05,0x0D0C,0x0D0E,
+        0x0D10,0x0D12,0x0D28,0x0D2A,0x0D39,0x0D60,0x0D61,0x0E01,0x0E2E,0x0E30,
+        0x0E30,0x0E32,0x0E33,0x0E40,0x0E45,0x0E81,0x0E82,0x0E84,0x0E84,0x0E87,
+        0x0E88,0x0E8A,0x0E8A,0x0E8D,0x0E8D,0x0E94,0x0E97,0x0E99,0x0E9F,0x0EA1,
+        0x0EA3,0x0EA5,0x0EA5,0x0EA7,0x0EA7,0x0EAA,0x0EAB,0x0EAD,0x0EAE,0x0EB0,
+        0x0EB0,0x0EB2,0x0EB3,0x0EBD,0x0EBD,0x0EC0,0x0EC4,0x0F40,0x0F47,0x0F49,
+        0x0F69,0x10A0,0x10C5,0x10D0,0x10F6,0x1100,0x1100,0x1102,0x1103,0x1105,
+        0x1107,0x1109,0x1109,0x110B,0x110C,0x110E,0x1112,0x113C,0x113C,0x113E,
+        0x113E,0x1140,0x1140,0x114C,0x114C,0x114E,0x114E,0x1150,0x1150,0x1154,
+        0x1155,0x1159,0x1159,0x115F,0x1161,0x1163,0x1163,0x1165,0x1165,0x1167,
+        0x1167,0x1169,0x1169,0x116D,0x116E,0x1172,0x1173,0x1175,0x1175,0x119E,
+        0x119E,0x11A8,0x11A8,0x11AB,0x11AB,0x11AE,0x11AF,0x11B7,0x11B8,0x11BA,
+        0x11BA,0x11BC,0x11C2,0x11EB,0x11EB,0x11F0,0x11F0,0x11F9,0x11F9,0x1E00,
+        0x1E9B,0x1EA0,0x1EF9,0x1F00,0x1F15,0x1F18,0x1F1D,0x1F20,0x1F45,0x1F48,
+        0x1F4D,0x1F50,0x1F57,0x1F59,0x1F59,0x1F5B,0x1F5B,0x1F5D,0x1F5D,0x1F5F,
+        0x1F7D,0x1F80,0x1FB4,0x1FB6,0x1FBC,0x1FBE,0x1FBE,0x1FC2,0x1FC4,0x1FC6,
+        0x1FCC,0x1FD0,0x1FD3,0x1FD6,0x1FDB,0x1FE0,0x1FEC,0x1FF2,0x1FF4,0x1FF6,
+        0x1FFC,0x2126,0x2126,0x212A,0x212B,0x212E,0x212E,0x2180,0x2182,0x3041,
+        0x3094,0x30A1,0x30FA,0x3105,0x312C,0xAC00,0xD7A3];
+    immutable IdeographicTable=[0x3007,0x3007,0x3021,0x3029,0x4E00,0x9FA5];
+    immutable CombiningCharTable=[0x0300,0x0345,0x0360,0x0361,0x0483,0x0486,
+        0x0591,0x05A1,0x05A3,0x05B9,0x05BB,0x05BD,0x05BF,0x05BF,0x05C1,0x05C2,
+        0x05C4,0x05C4,0x064B,0x0652,0x0670,0x0670,0x06D6,0x06DC,0x06DD,0x06DF,
+        0x06E0,0x06E4,0x06E7,0x06E8,0x06EA,0x06ED,0x0901,0x0903,0x093C,0x093C,
+        0x093E,0x094C,0x094D,0x094D,0x0951,0x0954,0x0962,0x0963,0x0981,0x0983,
+        0x09BC,0x09BC,0x09BE,0x09BE,0x09BF,0x09BF,0x09C0,0x09C4,0x09C7,0x09C8,
+        0x09CB,0x09CD,0x09D7,0x09D7,0x09E2,0x09E3,0x0A02,0x0A02,0x0A3C,0x0A3C,
+        0x0A3E,0x0A3E,0x0A3F,0x0A3F,0x0A40,0x0A42,0x0A47,0x0A48,0x0A4B,0x0A4D,
+        0x0A70,0x0A71,0x0A81,0x0A83,0x0ABC,0x0ABC,0x0ABE,0x0AC5,0x0AC7,0x0AC9,
+        0x0ACB,0x0ACD,0x0B01,0x0B03,0x0B3C,0x0B3C,0x0B3E,0x0B43,0x0B47,0x0B48,
+        0x0B4B,0x0B4D,0x0B56,0x0B57,0x0B82,0x0B83,0x0BBE,0x0BC2,0x0BC6,0x0BC8,
+        0x0BCA,0x0BCD,0x0BD7,0x0BD7,0x0C01,0x0C03,0x0C3E,0x0C44,0x0C46,0x0C48,
+        0x0C4A,0x0C4D,0x0C55,0x0C56,0x0C82,0x0C83,0x0CBE,0x0CC4,0x0CC6,0x0CC8,
+        0x0CCA,0x0CCD,0x0CD5,0x0CD6,0x0D02,0x0D03,0x0D3E,0x0D43,0x0D46,0x0D48,
+        0x0D4A,0x0D4D,0x0D57,0x0D57,0x0E31,0x0E31,0x0E34,0x0E3A,0x0E47,0x0E4E,
+        0x0EB1,0x0EB1,0x0EB4,0x0EB9,0x0EBB,0x0EBC,0x0EC8,0x0ECD,0x0F18,0x0F19,
+        0x0F35,0x0F35,0x0F37,0x0F37,0x0F39,0x0F39,0x0F3E,0x0F3E,0x0F3F,0x0F3F,
+        0x0F71,0x0F84,0x0F86,0x0F8B,0x0F90,0x0F95,0x0F97,0x0F97,0x0F99,0x0FAD,
+        0x0FB1,0x0FB7,0x0FB9,0x0FB9,0x20D0,0x20DC,0x20E1,0x20E1,0x302A,0x302F,
+        0x3099,0x3099,0x309A,0x309A];
+    immutable DigitTable=[0x0030,0x0039,0x0660,0x0669,0x06F0,0x06F9,0x0966,
+        0x096F,0x09E6,0x09EF,0x0A66,0x0A6F,0x0AE6,0x0AEF,0x0B66,0x0B6F,0x0BE7,
+        0x0BEF,0x0C66,0x0C6F,0x0CE6,0x0CEF,0x0D66,0x0D6F,0x0E50,0x0E59,0x0ED0,
+        0x0ED9,0x0F20,0x0F29];
+    immutable ExtenderTable=[0x00B7,0x00B7,0x02D0,0x02D0,0x02D1,0x02D1,0x0387,
+        0x0387,0x0640,0x0640,0x0E46,0x0E46,0x0EC6,0x0EC6,0x3005,0x3005,0x3031,
+        0x3035,0x309D,0x309E,0x30FC,0x30FE];
+
+    bool lookup(const(int)[] table, int c)
+    {
+        while (table.length != 0)
+        {
+            auto m = (table.length >> 1) & ~1;
+            if (c < table[m])
+            {
+                table = table[0..m];
+            }
+            else if (c > table[m+1])
+            {
+                table = table[m+2..$];
+            }
+            else return true;
+        }
+        return false;
+    }
+
+    string startOf(string s)
+    {
+        string r;
+        foreach(char c;s)
+        {
+            r ~= (c < 0x20 || c > 0x7F) ? '.' : c;
+            if (r.length >= 40) { r ~= "___"; break; }
+        }
+        return r;
+    }
+
+    void exit(string s=null)
+    {
+        throw new XMLException(s);
+    }
+}
diff --git a/libphobos/src/std/zip.d b/libphobos/src/std/zip.d
new file mode 100644
index 00000000000..b875a66cd4f
--- /dev/null
+++ b/libphobos/src/std/zip.d
@@ -0,0 +1,937 @@
+// Written in the D programming language.
+
+/**
+ * Read/write data in the $(LINK2 http://www.info-_zip.org, zip archive) format.
+ * Makes use of the etc.c.zlib compression library.
+ *
+ * Bugs:
+ *      $(UL
+ *      $(LI Multi-disk zips not supported.)
+ *      $(LI Only Zip version 20 formats are supported.)
+ *      $(LI Only supports compression modes 0 (no compression) and 8 (deflate).)
+ *      $(LI Does not support encryption.)
+ *      $(LI $(BUGZILLA 592))
+ *      $(LI $(BUGZILLA 2137))
+ *      )
+ *
+ * Macros:
+ *      WIKI = Phobos/StdZip
+ *
+ * Example:
+ * ---
+// Read existing zip file.
+import std.digest.crc, std.file, std.stdio, std.zip;
+
+void main(string[] args)
+{
+    // read a zip file into memory
+    auto zip = new ZipArchive(read(args[1]));
+    writeln("Archive: ", args[1]);
+    writefln("%-10s  %-8s  Name", "Length", "CRC-32");
+    // iterate over all zip members
+    foreach (name, am; zip.directory)
+    {
+        // print some data about each member
+        writefln("%10s  %08x  %s", am.expandedSize, am.crc32, name);
+        assert(am.expandedData.length == 0);
+        // decompress the archive member
+        zip.expand(am);
+        assert(am.expandedData.length == am.expandedSize);
+    }
+}
+
+// Create and write new zip file.
+import std.file: write;
+import std.string: representation;
+
+void main()
+{
+    char[] data = "Test data.\n".dup;
+    // Create an ArchiveMember for the test file.
+    ArchiveMember am = new ArchiveMember();
+    am.name = "test.txt";
+    am.expandedData(data.representation);
+    // Create an archive and add the member.
+    ZipArchive zip = new ZipArchive();
+    zip.addMember(am);
+    // Build the archive
+    void[] compressed_data = zip.build();
+    // Write to a file
+    write("test.zip", compressed_data);
+}
+ * ---
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_zip.d)
+ */
+
+/*          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.zip;
+
+//debug=print;
+
+/** Thrown on error.
+ */
+class ZipException : Exception
+{
+    this(string msg)
+    {
+        super("ZipException: " ~ msg);
+    }
+}
+
+/**
+ * Compression method used by ArchiveMember
+ */
+enum CompressionMethod : ushort
+{
+    none = 0,   /// No compression, just archiving
+    deflate = 8 /// Deflate algorithm. Use zlib library to compress
+}
+
+/**
+ * A member of the ZipArchive.
+ */
+final class ArchiveMember
+{
+    import std.conv : to, octal;
+    import std.datetime : DosFileTime, SysTime, SysTimeToDosFileTime;
+
+    /**
+     * Read/Write: Usually the file name of the archive member; it is used to
+     * index the archive directory for the member. Each member must have a unique
+     * name[]. Do not change without removing member from the directory first.
+     */
+    string name;
+
+    ubyte[] extra;              /// Read/Write: extra data for this member.
+    string comment;             /// Read/Write: comment associated with this member.
+
+    private ubyte[] _compressedData;
+    private ubyte[] _expandedData;
+    private uint offset;
+    private uint _crc32;
+    private uint _compressedSize;
+    private uint _expandedSize;
+    private CompressionMethod _compressionMethod;
+    private ushort _madeVersion = 20;
+    private ushort _extractVersion = 20;
+    private ushort _diskNumber;
+    private uint _externalAttributes;
+    private DosFileTime _time;
+
+    ushort flags;                  /// Read/Write: normally set to 0
+    ushort internalAttributes;     /// Read/Write
+
+    @property ushort extractVersion()     { return _extractVersion; }    /// Read Only
+    @property uint crc32()         { return _crc32; }    /// Read Only: cyclic redundancy check (CRC) value
+
+    /// Read Only: size of data of member in compressed form.
+    @property uint compressedSize()     { return _compressedSize; }
+
+    /// Read Only: size of data of member in expanded form.
+    @property uint expandedSize()     { return _expandedSize; }
+    @property ushort diskNumber()     { return _diskNumber; }        /// Read Only: should be 0.
+
+    /// Read Only: data of member in compressed form.
+    @property ubyte[] compressedData()     { return _compressedData; }
+
+    /// Read data of member in uncompressed form.
+    @property ubyte[] expandedData()     { return _expandedData; }
+
+    /// Write data of member in uncompressed form.
+    @property void expandedData(ubyte[] ed)
+    {
+        _expandedData = ed;
+        _expandedSize  = to!uint(_expandedData.length);
+
+        // Clean old compressed data, if any
+        _compressedData.length = 0;
+        _compressedSize = 0;
+    }
+
+    /**
+     * Set the OS specific file attributes, as obtained by
+     * $(XREF file,getAttributes) or $(XREF file,DirEntry.attributes), for this archive member.
+     */
+    @property void fileAttributes(uint attr)
+    {
+        version (Posix)
+        {
+            _externalAttributes = (attr & 0xFFFF) << 16;
+            _madeVersion &= 0x00FF;
+            _madeVersion |= 0x0300; // attributes are in UNIX format
+        }
+        else version (Windows)
+        {
+            _externalAttributes = attr;
+            _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format
+        }
+        else
+        {
+            static assert(0, "Unimplemented platform");
+        }
+    }
+
+    version (Posix) unittest
+    {
+        auto am = new ArchiveMember();
+        am.fileAttributes = octal!100644;
+        assert(am._externalAttributes == octal!100644 << 16);
+        assert((am._madeVersion & 0xFF00) == 0x0300);
+    }
+
+    /**
+     * Get the OS specific file attributes for the archive member.
+     *
+     * Returns: The file attributes or 0 if the file attributes were
+     * encoded for an incompatible OS (Windows vs. Posix).
+     *
+     */
+    @property uint fileAttributes() const
+    {
+        version (Posix)
+        {
+            if ((_madeVersion & 0xFF00) == 0x0300)
+                return _externalAttributes >> 16;
+            return 0;
+        }
+        else version (Windows)
+        {
+            if ((_madeVersion & 0xFF00) == 0x0000)
+                return _externalAttributes;
+            return 0;
+        }
+        else
+        {
+            static assert(0, "Unimplemented platform");
+        }
+    }
+
+    /// Set the last modification time for this member.
+    @property void time(SysTime time)
+    {
+        _time = SysTimeToDosFileTime(time);
+    }
+
+    /// ditto
+    @property void time(DosFileTime time)
+    {
+        _time = time;
+    }
+
+    /// Get the last modification time for this member.
+    @property DosFileTime time() const
+    {
+        return _time;
+    }
+
+    /**
+     * Read compression method used for this member
+     * See_Also:
+     *     CompressionMethod
+     **/
+    @property CompressionMethod compressionMethod() { return _compressionMethod; }
+
+    /**
+     * Write compression method used for this member
+     * See_Also:
+     *     CompressionMethod
+     **/
+    @property void compressionMethod(CompressionMethod cm)
+    {
+        if (cm == _compressionMethod) return;
+
+        if (_compressedSize > 0)
+            throw new ZipException("Can't change compression method for a compressed element");
+
+        _compressionMethod = cm;
+    }
+
+    debug(print)
+    {
+    void print()
+    {
+        printf("name = '%.*s'\n", name.length, name.ptr);
+        printf("\tcomment = '%.*s'\n", comment.length, comment.ptr);
+        printf("\tmadeVersion = x%04x\n", _madeVersion);
+        printf("\textractVersion = x%04x\n", extractVersion);
+        printf("\tflags = x%04x\n", flags);
+        printf("\tcompressionMethod = %d\n", compressionMethod);
+        printf("\ttime = %d\n", time);
+        printf("\tcrc32 = x%08x\n", crc32);
+        printf("\texpandedSize = %d\n", expandedSize);
+        printf("\tcompressedSize = %d\n", compressedSize);
+        printf("\tinternalAttributes = x%04x\n", internalAttributes);
+        printf("\texternalAttributes = x%08x\n", externalAttributes);
+    }
+    }
+}
+
+/**
+ * Object representing the entire archive.
+ * ZipArchives are collections of ArchiveMembers.
+ */
+final class ZipArchive
+{
+    import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
+    import std.algorithm : max;
+    import std.conv : to;
+    import std.datetime : DosFileTime;
+
+    string comment;     /// Read/Write: the archive comment. Must be less than 65536 bytes in length.
+
+    private ubyte[] _data;
+    private uint endrecOffset;
+
+    private uint _diskNumber;
+    private uint _diskStartDir;
+    private uint _numEntries;
+    private uint _totalEntries;
+    private bool _isZip64;
+    static const ushort zip64ExtractVersion = 45;
+    static const int digiSignLength = 6;
+    static const int eocd64LocLength = 20;
+    static const int eocd64Length = 56;
+
+    /// Read Only: array representing the entire contents of the archive.
+    @property ubyte[] data()       { return _data; }
+
+    /// Read Only: 0 since multi-disk zip archives are not supported.
+    @property uint diskNumber()    { return _diskNumber; }
+
+    /// Read Only: 0 since multi-disk zip archives are not supported
+    @property uint diskStartDir()  { return _diskStartDir; }
+
+    /// Read Only: number of ArchiveMembers in the directory.
+    @property uint numEntries()    { return _numEntries; }
+    @property uint totalEntries()  { return _totalEntries; }    /// ditto
+
+    /// True when the archive is in Zip64 format.
+    @property bool isZip64()  { return _isZip64; }
+
+    /// Set this to true to force building a Zip64 archive.
+    @property void isZip64(bool value) { _isZip64 = value; }
+    /**
+     * Read Only: array indexed by the name of each member of the archive.
+     *  All the members of the archive can be accessed with a foreach loop:
+     * Example:
+     * --------------------
+     * ZipArchive archive = new ZipArchive(data);
+     * foreach (ArchiveMember am; archive.directory)
+     * {
+     *     writefln("member name is '%s'", am.name);
+     * }
+     * --------------------
+     */
+    @property ArchiveMember[string] directory() { return _directory; }
+
+    private ArchiveMember[string] _directory;
+
+    debug (print)
+    {
+    void print()
+    {
+        printf("\tdiskNumber = %u\n", diskNumber);
+        printf("\tdiskStartDir = %u\n", diskStartDir);
+        printf("\tnumEntries = %u\n", numEntries);
+        printf("\ttotalEntries = %u\n", totalEntries);
+        printf("\tcomment = '%.*s'\n", comment.length, comment.ptr);
+    }
+    }
+
+    /* ============ Creating a new archive =================== */
+
+    /** Constructor to use when creating a new archive.
+     */
+    this()
+    {
+    }
+
+    /** Add de to the archive.
+     */
+    void addMember(ArchiveMember de)
+    {
+        _directory[de.name] = de;
+    }
+
+    /** Delete de from the archive.
+     */
+    void deleteMember(ArchiveMember de)
+    {
+        _directory.remove(de.name);
+    }
+
+    /**
+     * Construct an archive out of the current members of the archive.
+     *
+     * Fills in the properties data[], diskNumber, diskStartDir, numEntries,
+     * totalEntries, and directory[].
+     * For each ArchiveMember, fills in properties crc32, compressedSize,
+     * compressedData[].
+     *
+     * Returns: array representing the entire archive.
+     */
+    void[] build()
+    {   uint i;
+        uint directoryOffset;
+
+        if (comment.length > 0xFFFF)
+            throw new ZipException("archive comment longer than 65535");
+
+        // Compress each member; compute size
+        uint archiveSize = 0;
+        uint directorySize = 0;
+        foreach (ArchiveMember de; _directory)
+        {
+            if (!de._compressedData.length)
+            {
+                switch (de.compressionMethod)
+                {
+                    case CompressionMethod.none:
+                        de._compressedData = de._expandedData;
+                        break;
+
+                    case CompressionMethod.deflate:
+                        import std.zlib : compress;
+                        de._compressedData = cast(ubyte[])compress(cast(void[])de._expandedData);
+                        de._compressedData = de._compressedData[2 .. de._compressedData.length - 4];
+                        break;
+
+                    default:
+                        throw new ZipException("unsupported compression method");
+                }
+
+                de._compressedSize = to!uint(de._compressedData.length);
+                import std.zlib : crc32;
+                de._crc32 = crc32(0, cast(void[])de._expandedData);
+            }
+            assert(de._compressedData.length == de._compressedSize);
+
+            if (to!ulong(archiveSize) + 30 + de.name.length + de.extra.length + de.compressedSize
+                    + directorySize + 46 + de.name.length + de.extra.length + de.comment.length
+                    + 22 + comment.length + eocd64LocLength + eocd64Length > uint.max)
+                throw new ZipException("zip files bigger than 4 GB are unsupported");
+
+            archiveSize += 30 + de.name.length +
+                                de.extra.length +
+                                de.compressedSize;
+            directorySize += 46 + de.name.length +
+                                de.extra.length +
+                                de.comment.length;
+        }
+
+        if (!isZip64 && _directory.length > ushort.max)
+            _isZip64 = true;
+        uint dataSize = archiveSize + directorySize + 22 + cast(uint)comment.length;
+        if (isZip64)
+            dataSize += eocd64LocLength + eocd64Length;
+
+        _data = new ubyte[dataSize];
+
+        // Populate the data[]
+
+        // Store each archive member
+        i = 0;
+        foreach (ArchiveMember de; _directory)
+        {
+            de.offset = i;
+            _data[i .. i + 4] = cast(ubyte[])"PK\x03\x04";
+            putUshort(i + 4,  de.extractVersion);
+            putUshort(i + 6,  de.flags);
+            putUshort(i + 8,  de._compressionMethod);
+            putUint  (i + 10, cast(uint)de.time);
+            putUint  (i + 14, de.crc32);
+            putUint  (i + 18, de.compressedSize);
+            putUint  (i + 22, to!uint(de.expandedSize));
+            putUshort(i + 26, cast(ushort)de.name.length);
+            putUshort(i + 28, cast(ushort)de.extra.length);
+            i += 30;
+
+            _data[i .. i + de.name.length] = (cast(ubyte[])de.name)[];
+            i += de.name.length;
+            _data[i .. i + de.extra.length] = (cast(ubyte[])de.extra)[];
+            i += de.extra.length;
+            _data[i .. i + de.compressedSize] = de.compressedData[];
+            i += de.compressedSize;
+        }
+
+        // Write directory
+        directoryOffset = i;
+        _numEntries = 0;
+        foreach (ArchiveMember de; _directory)
+        {
+            _data[i .. i + 4] = cast(ubyte[])"PK\x01\x02";
+            putUshort(i + 4,  de._madeVersion);
+            putUshort(i + 6,  de.extractVersion);
+            putUshort(i + 8,  de.flags);
+            putUshort(i + 10, de._compressionMethod);
+            putUint  (i + 12, cast(uint)de.time);
+            putUint  (i + 16, de.crc32);
+            putUint  (i + 20, de.compressedSize);
+            putUint  (i + 24, de.expandedSize);
+            putUshort(i + 28, cast(ushort)de.name.length);
+            putUshort(i + 30, cast(ushort)de.extra.length);
+            putUshort(i + 32, cast(ushort)de.comment.length);
+            putUshort(i + 34, de.diskNumber);
+            putUshort(i + 36, de.internalAttributes);
+            putUint  (i + 38, de._externalAttributes);
+            putUint  (i + 42, de.offset);
+            i += 46;
+
+            _data[i .. i + de.name.length] = (cast(ubyte[])de.name)[];
+            i += de.name.length;
+            _data[i .. i + de.extra.length] = (cast(ubyte[])de.extra)[];
+            i += de.extra.length;
+            _data[i .. i + de.comment.length] = (cast(ubyte[])de.comment)[];
+            i += de.comment.length;
+            _numEntries++;
+        }
+        _totalEntries = numEntries;
+
+        if (isZip64)
+        {
+            // Write zip64 end of central directory record
+            uint eocd64Offset = i;
+            _data[i .. i + 4] = cast(ubyte[])"PK\x06\x06";
+            putUlong (i + 4,  eocd64Length - 12);
+            putUshort(i + 12, zip64ExtractVersion);
+            putUshort(i + 14, zip64ExtractVersion);
+            putUint  (i + 16, diskNumber);
+            putUint  (i + 20, diskStartDir);
+            putUlong (i + 24, numEntries);
+            putUlong (i + 32, totalEntries);
+            putUlong (i + 40, directorySize);
+            putUlong (i + 48, directoryOffset);
+            i += eocd64Length;
+
+            // Write zip64 end of central directory record locator
+            _data[i .. i + 4] = cast(ubyte[])"PK\x06\x07";
+            putUint  (i + 4,  diskNumber);
+            putUlong (i + 8,  eocd64Offset);
+            putUint  (i + 16, 1);
+            i += eocd64LocLength;
+        }
+
+        // Write end record
+        endrecOffset = i;
+        _data[i .. i + 4] = cast(ubyte[])"PK\x05\x06";
+        putUshort(i + 4,  cast(ushort)diskNumber);
+        putUshort(i + 6,  cast(ushort)diskStartDir);
+        putUshort(i + 8,  (numEntries > ushort.max ? ushort.max : cast(ushort)numEntries));
+        putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort)totalEntries));
+        putUint  (i + 12, directorySize);
+        putUint  (i + 16, directoryOffset);
+        putUshort(i + 20, cast(ushort)comment.length);
+        i += 22;
+
+        // Write archive comment
+        assert(i + comment.length == data.length);
+        _data[i .. data.length] = (cast(ubyte[])comment)[];
+
+        return cast(void[])data;
+    }
+
+    /* ============ Reading an existing archive =================== */
+
+    /**
+     * Constructor to use when reading an existing archive.
+     *
+     * Fills in the properties data[], diskNumber, diskStartDir, numEntries,
+     * totalEntries, comment[], and directory[].
+     * For each ArchiveMember, fills in
+     * properties madeVersion, extractVersion, flags, compressionMethod, time,
+     * crc32, compressedSize, expandedSize, compressedData[], diskNumber,
+     * internalAttributes, externalAttributes, name[], extra[], comment[].
+     * Use expand() to get the expanded data for each ArchiveMember.
+     *
+     * Params:
+     *  buffer = the entire contents of the archive.
+     */
+
+    this(void[] buffer)
+    {   uint iend;
+        uint i;
+        int endcommentlength;
+        uint directorySize;
+        uint directoryOffset;
+
+        this._data = cast(ubyte[]) buffer;
+
+        if (data.length > uint.max - 2)
+            throw new ZipException("zip files bigger than 4 GB are unsupported");
+
+        // Find 'end record index' by searching backwards for signature
+        iend = (data.length > 66000 ? to!uint(data.length - 66000) : 0);
+        for (i = to!uint(data.length) - 22; 1; i--)
+        {
+            if (i < iend || i >= data.length)
+                throw new ZipException("no end record");
+
+            if (_data[i .. i + 4] == cast(ubyte[])"PK\x05\x06")
+            {
+                endcommentlength = getUshort(i + 20);
+                if (i + 22 + endcommentlength > data.length
+                        || i + 22 + endcommentlength < i)
+                    continue;
+                comment = cast(string)(_data[i + 22 .. i + 22 + endcommentlength]);
+                endrecOffset = i;
+
+                uint k = i - eocd64LocLength;
+                if (k < i && _data[k .. k + 4] == cast(ubyte[])"PK\x06\x07")
+                {
+                    _isZip64 = true;
+                    i = k;
+                }
+
+                break;
+            }
+        }
+
+        if (isZip64)
+        {
+            // Read Zip64 record data
+            uint eocd64LocStart = i;
+            ulong eocdOffset = getUlong(i + 8);
+            if (eocdOffset + eocd64Length > _data.length)
+                throw new ZipException("corrupted directory");
+
+            i = to!uint(eocdOffset);
+            if (_data[i .. i + 4] != cast(ubyte[])"PK\x06\x06")
+                throw new ZipException("invalid Zip EOCD64 signature");
+
+            ulong eocd64Size = getUlong(i + 4);
+            if (eocd64Size + i - 12 > data.length)
+                throw new ZipException("invalid Zip EOCD64 size");
+
+            _diskNumber = getUint(i + 16);
+            _diskStartDir = getUint(i + 20);
+
+            ulong numEntriesUlong = getUlong(i + 24);
+            ulong totalEntriesUlong = getUlong(i + 32);
+            ulong directorySizeUlong = getUlong(i + 40);
+            ulong directoryOffsetUlong = getUlong(i + 48);
+
+            if (numEntriesUlong > uint.max)
+                throw new ZipException("supposedly more than 4294967296 files in archive");
+
+            if (numEntriesUlong != totalEntriesUlong)
+                throw new ZipException("multiple disk zips not supported");
+
+            if (directorySizeUlong > i || directoryOffsetUlong > i
+                    || directorySizeUlong + directoryOffsetUlong > i)
+                throw new ZipException("corrupted directory");
+
+            _numEntries = to!uint(numEntriesUlong);
+            _totalEntries = to!uint(totalEntriesUlong);
+            directorySize = to!uint(directorySizeUlong);
+            directoryOffset = to!uint(directoryOffsetUlong);
+        }
+        else
+        {
+        // Read end record data
+        _diskNumber = getUshort(i + 4);
+        _diskStartDir = getUshort(i + 6);
+
+        _numEntries = getUshort(i + 8);
+        _totalEntries = getUshort(i + 10);
+
+        if (numEntries != totalEntries)
+            throw new ZipException("multiple disk zips not supported");
+
+        directorySize = getUint(i + 12);
+        directoryOffset = getUint(i + 16);
+
+        if (directoryOffset + directorySize > i)
+            throw new ZipException("corrupted directory");
+        }
+
+        i = directoryOffset;
+        for (int n = 0; n < numEntries; n++)
+        {
+            /* The format of an entry is:
+             *  'PK' 1, 2
+             *  directory info
+             *  path
+             *  extra data
+             *  comment
+             */
+
+            uint offset;
+            uint namelen;
+            uint extralen;
+            uint commentlen;
+
+            if (_data[i .. i + 4] != cast(ubyte[])"PK\x01\x02")
+                throw new ZipException("invalid directory entry 1");
+            ArchiveMember de = new ArchiveMember();
+            de._madeVersion = getUshort(i + 4);
+            de._extractVersion = getUshort(i + 6);
+            de.flags = getUshort(i + 8);
+            de._compressionMethod = cast(CompressionMethod)getUshort(i + 10);
+            de.time = cast(DosFileTime)getUint(i + 12);
+            de._crc32 = getUint(i + 16);
+            de._compressedSize = getUint(i + 20);
+            de._expandedSize = getUint(i + 24);
+            namelen = getUshort(i + 28);
+            extralen = getUshort(i + 30);
+            commentlen = getUshort(i + 32);
+            de._diskNumber = getUshort(i + 34);
+            de.internalAttributes = getUshort(i + 36);
+            de._externalAttributes = getUint(i + 38);
+            de.offset = getUint(i + 42);
+            i += 46;
+
+            if (i + namelen + extralen + commentlen > directoryOffset + directorySize)
+                throw new ZipException("invalid directory entry 2");
+
+            de.name = cast(string)(_data[i .. i + namelen]);
+            i += namelen;
+            de.extra = _data[i .. i + extralen];
+            i += extralen;
+            de.comment = cast(string)(_data[i .. i + commentlen]);
+            i += commentlen;
+
+            immutable uint dataOffset = de.offset + 30 + namelen + extralen;
+            if (dataOffset + de.compressedSize > endrecOffset)
+                throw new ZipException("Invalid directory entry offset or size.");
+            de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize];
+
+            _directory[de.name] = de;
+
+        }
+        if (i != directoryOffset + directorySize)
+            throw new ZipException("invalid directory entry 3");
+    }
+
+    /*****
+     * Decompress the contents of archive member de and return the expanded
+     * data.
+     *
+     * Fills in properties extractVersion, flags, compressionMethod, time,
+     * crc32, compressedSize, expandedSize, expandedData[], name[], extra[].
+     */
+    ubyte[] expand(ArchiveMember de)
+    {   uint namelen;
+        uint extralen;
+
+        if (_data[de.offset .. de.offset + 4] != cast(ubyte[])"PK\x03\x04")
+            throw new ZipException("invalid directory entry 4");
+
+        // These values should match what is in the main zip archive directory
+        de._extractVersion = getUshort(de.offset + 4);
+        de.flags = getUshort(de.offset + 6);
+        de._compressionMethod = cast(CompressionMethod)getUshort(de.offset + 8);
+        de.time = cast(DosFileTime)getUint(de.offset + 10);
+        de._crc32 = getUint(de.offset + 14);
+        de._compressedSize = max(getUint(de.offset + 18), de.compressedSize);
+        de._expandedSize = max(getUint(de.offset + 22), de.expandedSize);
+        namelen = getUshort(de.offset + 26);
+        extralen = getUshort(de.offset + 28);
+
+        debug(print)
+        {
+            printf("\t\texpandedSize = %d\n", de.expandedSize);
+            printf("\t\tcompressedSize = %d\n", de.compressedSize);
+            printf("\t\tnamelen = %d\n", namelen);
+            printf("\t\textralen = %d\n", extralen);
+        }
+
+        if (de.flags & 1)
+            throw new ZipException("encryption not supported");
+
+        int i;
+        i = de.offset + 30 + namelen + extralen;
+        if (i + de.compressedSize > endrecOffset)
+            throw new ZipException("invalid directory entry 5");
+
+        de._compressedData = _data[i .. i + de.compressedSize];
+        debug(print) arrayPrint(de.compressedData);
+
+        switch (de.compressionMethod)
+        {
+            case CompressionMethod.none:
+                de._expandedData = de.compressedData;
+                return de.expandedData;
+
+            case CompressionMethod.deflate:
+                // -15 is a magic value used to decompress zip files.
+                // It has the effect of not requiring the 2 byte header
+                // and 4 byte trailer.
+                import std.zlib : uncompress;
+                de._expandedData = cast(ubyte[])uncompress(cast(void[])de.compressedData, de.expandedSize, -15);
+                return de.expandedData;
+
+            default:
+                throw new ZipException("unsupported compression method");
+        }
+    }
+
+    /* ============ Utility =================== */
+
+    ushort getUshort(int i)
+    {
+        ubyte[2] result = data[i .. i + 2];
+        return littleEndianToNative!ushort(result);
+    }
+
+    uint getUint(int i)
+    {
+        ubyte[4] result = data[i .. i + 4];
+        return littleEndianToNative!uint(result);
+    }
+
+    ulong getUlong(int i)
+    {
+        ubyte[8] result = data[i .. i + 8];
+        return littleEndianToNative!ulong(result);
+    }
+
+    void putUshort(int i, ushort us)
+    {
+        data[i .. i + 2] = nativeToLittleEndian(us);
+    }
+
+    void putUint(int i, uint ui)
+    {
+        data[i .. i + 4] = nativeToLittleEndian(ui);
+    }
+
+    void putUlong(int i, ulong ul)
+    {
+        data[i .. i + 8] = nativeToLittleEndian(ul);
+    }
+}
+
+debug(print)
+{
+    void arrayPrint(ubyte[] array)
+    {
+        printf("array %p,%d\n", cast(void*)array, array.length);
+        for (int i = 0; i < array.length; i++)
+        {
+            printf("%02x ", array[i]);
+            if (((i + 1) & 15) == 0)
+                printf("\n");
+        }
+        printf("\n");
+    }
+}
+
+unittest
+{
+    auto zip1 = new ZipArchive();
+    auto zip2 = new ZipArchive();
+    auto am1 = new ArchiveMember();
+    am1.name = "foo";
+    am1.expandedData = new ubyte[](1024);
+    zip1.addMember(am1);
+    auto data1 = zip1.build();
+    zip2.addMember(zip1.directory["foo"]);
+    zip2.build();
+    auto am2 = zip2.directory["foo"];
+    zip2.expand(am2);
+    assert(am1.expandedData == am2.expandedData);
+    auto zip3 = new ZipArchive(data1);
+    zip3.build();
+    assert(zip3.directory["foo"].compressedSize == am1.compressedSize);
+
+    // Test if packing and unpacking produces the original data
+    import std.random : uniform, MinstdRand0;
+    import std.stdio, std.conv;
+    MinstdRand0 gen;
+    const uint itemCount = 20, minSize = 10, maxSize = 500;
+    foreach (variant; 0..2)
+    {
+        bool useZip64 = !!variant;
+        zip1 = new ZipArchive();
+        zip1.isZip64 = useZip64;
+        ArchiveMember[itemCount] ams;
+        foreach (i; 0..itemCount)
+        {
+            ams[i] = new ArchiveMember();
+            ams[i].name = to!string(i);
+            ams[i].expandedData = new ubyte[](uniform(minSize, maxSize));
+            foreach (ref ubyte c; ams[i].expandedData)
+                c = cast(ubyte)(uniform(0, 256));
+            ams[i].compressionMethod = CompressionMethod.deflate;
+            zip1.addMember(ams[i]);
+        }
+        auto zippedData = zip1.build();
+        zip2 = new ZipArchive(zippedData);
+        assert(zip2.isZip64 == useZip64);
+        foreach (am; ams)
+        {
+            am2 = zip2.directory[am.name];
+            zip2.expand(am2);
+            assert(am.crc32 == am2.crc32);
+            assert(am.expandedData == am2.expandedData);
+        }
+    }
+}
+
+unittest
+{
+    import std.zlib;
+
+    ubyte[] src = cast(ubyte[])
+"the quick brown fox jumps over the lazy dog\r
+the quick brown fox jumps over the lazy dog\r
+";
+    auto dst = cast(ubyte[])compress(cast(void[])src);
+    auto after = cast(ubyte[])uncompress(cast(void[])dst);
+    assert(src == after);
+}
+
+unittest
+{
+    import std.datetime;
+    ubyte[] buf = [1, 2, 3, 4, 5, 0, 7, 8, 9];
+
+    auto ar = new ZipArchive;
+    auto am = new ArchiveMember;  // 10
+    am.name = "buf";
+    am.expandedData = buf;
+    am.compressionMethod = CompressionMethod.deflate;
+    am.time = SysTimeToDosFileTime(Clock.currTime());
+    ar.addMember(am);            // 15
+
+    auto zip1 = ar.build();
+    auto arAfter = new ZipArchive(zip1);
+    assert(arAfter.directory.length == 1);
+    auto amAfter = arAfter.directory["buf"];
+    arAfter.expand(amAfter);
+    assert(amAfter.name == am.name);
+    assert(amAfter.expandedData == am.expandedData);
+    assert(amAfter.time == am.time);
+}
+
+// Posix-only, because we can't rely on the unzip command being available on Windows
+version(Posix) unittest
+{
+    import std.datetime, std.file, std.format, std.path, std.process, std.stdio;
+
+    auto zr = new ZipArchive();
+    auto am = new ArchiveMember();
+    am.compressionMethod = CompressionMethod.deflate;
+    am.name = "foo.bar";
+    am.time = SysTimeToDosFileTime(Clock.currTime());
+    am.expandedData = cast(ubyte[])"We all live in a yellow submarine, a yellow submarine";
+    zr.addMember(am);
+    auto data2 = zr.build();
+
+    mkdirRecurse(deleteme);
+    scope(exit) rmdirRecurse(deleteme);
+    string zipFile = buildPath(deleteme, "foo.zip");
+    std.file.write(zipFile, cast(byte[])data2);
+
+    auto result = executeShell(format("unzip -l %s", zipFile));
+    scope(failure) writeln(result.output);
+    assert(result.status == 0);
+}
diff --git a/libphobos/src/std/zlib.d b/libphobos/src/std/zlib.d
new file mode 100644
index 00000000000..65af8ff5665
--- /dev/null
+++ b/libphobos/src/std/zlib.d
@@ -0,0 +1,741 @@
+// Written in the D programming language.
+
+/**
+ * Compress/decompress data using the $(WEB www._zlib.net, _zlib library).
+ *
+ * Examples:
+ *
+ * If you have a small buffer you can use $(LREF compress) and
+ * $(LREF uncompress) directly.
+ *
+ * -------
+ * import std.zlib;
+ *
+ * auto src =
+ * "the quick brown fox jumps over the lazy dog\r
+ *  the quick brown fox jumps over the lazy dog\r";
+ *
+ * ubyte[] dst;
+ * ubyte[] result;
+ *
+ * dst = compress(src);
+ * result = cast(ubyte[])uncompress(dst);
+ * assert(result == src);
+ * -------
+ *
+ * When the data to be compressed doesn't fit in one buffer, use
+ * $(LREF Compress) and $(LREF UnCompress).
+ *
+ * -------
+ * import std.zlib;
+ * import std.stdio;
+ * import std.conv: to;
+ * import std.algorithm: map;
+ *
+ * UnCompress decmp = new UnCompress;
+ * foreach (chunk; stdin.byChunk(4096).map!(x => decmp.uncompress(x)))
+ * {
+ *     chunk.to!string.write;
+ * }
+
+ * -------
+ *
+ * References:
+ *  $(WEB en.wikipedia.org/wiki/Zlib, Wikipedia)
+ *
+ * Macros:
+ *  WIKI = Phobos/StdZlib
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2011.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ * Source:    $(PHOBOSSRC std/_zlib.d)
+ */
+/*          Copyright Digital Mars 2000 - 2011.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+module std.zlib;
+
+//debug=zlib;       // uncomment to turn on debugging printf's
+
+import etc.c.zlib;
+
+// Values for 'mode'
+
+enum
+{
+    Z_NO_FLUSH      = 0,
+    Z_SYNC_FLUSH    = 2,
+    Z_FULL_FLUSH    = 3,
+    Z_FINISH        = 4,
+}
+
+/*************************************
+ * Errors throw a ZlibException.
+ */
+
+class ZlibException : Exception
+{
+    this(int errnum)
+    {   string msg;
+
+        switch (errnum)
+        {
+            case Z_STREAM_END:      msg = "stream end"; break;
+            case Z_NEED_DICT:       msg = "need dict"; break;
+            case Z_ERRNO:           msg = "errno"; break;
+            case Z_STREAM_ERROR:    msg = "stream error"; break;
+            case Z_DATA_ERROR:      msg = "data error"; break;
+            case Z_MEM_ERROR:       msg = "mem error"; break;
+            case Z_BUF_ERROR:       msg = "buf error"; break;
+            case Z_VERSION_ERROR:   msg = "version error"; break;
+            default:                msg = "unknown error";  break;
+        }
+        super(msg);
+    }
+}
+
+/**
+ * $(P Compute the Adler-32 checksum of a buffer's worth of data.)
+ *
+ * Params:
+ *     adler = the starting checksum for the computation. Use 1
+ *             for a new checksum. Use the output of this function
+ *             for a cumulative checksum.
+ *     buf = buffer containing input data
+ *
+ * Returns:
+ *     A $(D uint) checksum for the provided input data and starting checksum
+ *
+ * See_Also:
+ *     $(LINK http://en.wikipedia.org/wiki/Adler-32)
+ */
+
+uint adler32(uint adler, const(void)[] buf)
+{
+    import std.range : chunks;
+    foreach(chunk; (cast(ubyte[])buf).chunks(0xFFFF0000))
+    {
+        adler = etc.c.zlib.adler32(adler, chunk.ptr, cast(uint)chunk.length);
+    }
+    return adler;
+}
+
+///
+unittest
+{
+    static ubyte[] data = [1,2,3,4,5,6,7,8,9,10];
+
+    uint adler = adler32(0u, data);
+    assert(adler == 0xdc0037);
+}
+
+unittest
+{
+    static string data = "test";
+
+    uint adler = adler32(1, data);
+    assert(adler == 0x045d01c1);
+}
+
+/**
+ * $(P Compute the CRC32 checksum of a buffer's worth of data.)
+ *
+ * Params:
+ *     crc = the starting checksum for the computation. Use 0
+ *             for a new checksum. Use the output of this function
+ *             for a cumulative checksum.
+ *     buf = buffer containing input data
+ *
+ * Returns:
+ *     A $(D uint) checksum for the provided input data and starting checksum
+ *
+ * See_Also:
+ *     $(LINK http://en.wikipedia.org/wiki/Cyclic_redundancy_check)
+ */
+
+uint crc32(uint crc, const(void)[] buf)
+{
+    import std.range : chunks;
+    foreach(chunk; (cast(ubyte[])buf).chunks(0xFFFF0000))
+    {
+        crc = etc.c.zlib.crc32(crc, chunk.ptr, cast(uint)chunk.length);
+    }
+    return crc;
+}
+
+unittest
+{
+    static ubyte[] data = [1,2,3,4,5,6,7,8,9,10];
+
+    uint crc;
+
+    debug(zlib) printf("D.zlib.crc32.unittest\n");
+    crc = crc32(0u, cast(void[])data);
+    debug(zlib) printf("crc = %x\n", crc);
+    assert(crc == 0x2520577b);
+}
+
+/**
+ * $(P Compress data)
+ *
+ * Params:
+ *     srcbuf = buffer containing the data to compress
+ *     level = compression level. Legal values are -1..9, with -1 indicating
+ *             the default level (6), 0 indicating no compression, 1 being the
+ *             least compression and 9 being the most.
+ *
+ * Returns:
+ *     the compressed data
+ */
+
+ubyte[] compress(const(void)[] srcbuf, int level)
+in
+{
+    assert(-1 <= level && level <= 9);
+}
+body
+{
+    auto destlen = srcbuf.length + ((srcbuf.length + 1023) / 1024) + 12;
+    auto destbuf = new ubyte[destlen];
+    auto err = etc.c.zlib.compress2(destbuf.ptr, &destlen, cast(ubyte *)srcbuf.ptr, srcbuf.length, level);
+    if (err)
+    {   delete destbuf;
+        throw new ZlibException(err);
+    }
+
+    destbuf.length = destlen;
+    return destbuf;
+}
+
+/*********************************************
+ * ditto
+ */
+
+ubyte[] compress(const(void)[] srcbuf)
+{
+    return compress(srcbuf, Z_DEFAULT_COMPRESSION);
+}
+
+/*********************************************
+ * Decompresses the data in srcbuf[].
+ * Params:
+ *  srcbuf  = buffer containing the compressed data.
+ *  destlen = size of the uncompressed data.
+ *            It need not be accurate, but the decompression will be faster
+ *            if the exact size is supplied.
+ *  winbits = the base two logarithm of the maximum window size.
+ * Returns: the decompressed data.
+ */
+
+void[] uncompress(const(void)[] srcbuf, size_t destlen = 0u, int winbits = 15)
+{
+    import std.conv : to;
+    int err;
+    ubyte[] destbuf;
+
+    if (!destlen)
+        destlen = srcbuf.length * 2 + 1;
+
+    etc.c.zlib.z_stream zs;
+    zs.next_in = cast(typeof(zs.next_in)) srcbuf.ptr;
+    zs.avail_in = to!uint(srcbuf.length);
+    err = etc.c.zlib.inflateInit2(&zs, winbits);
+    if (err)
+    {
+        throw new ZlibException(err);
+    }
+
+    size_t olddestlen = 0u;
+
+    loop:
+    while (true)
+    {
+        destbuf.length = destlen;
+        zs.next_out = cast(typeof(zs.next_out)) &destbuf[olddestlen];
+        zs.avail_out = to!uint(destlen - olddestlen);
+        olddestlen = destlen;
+
+        err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH);
+        switch (err)
+        {
+            case Z_OK:
+                destlen = destbuf.length * 2;
+                continue loop;
+
+            case Z_STREAM_END:
+                destbuf.length = zs.total_out;
+                err = etc.c.zlib.inflateEnd(&zs);
+                if (err != Z_OK)
+                    throw new ZlibException(err);
+                return destbuf;
+
+            default:
+                etc.c.zlib.inflateEnd(&zs);
+                throw new ZlibException(err);
+        }
+    }
+    assert(0);
+}
+
+unittest
+{
+    auto src =
+"the quick brown fox jumps over the lazy dog\r
+the quick brown fox jumps over the lazy dog\r
+";
+    ubyte[] dst;
+    ubyte[] result;
+
+    //arrayPrint(src);
+    dst = compress(src);
+    //arrayPrint(dst);
+    result = cast(ubyte[])uncompress(dst);
+    //arrayPrint(result);
+    assert(result == src);
+}
+
+unittest
+{
+    ubyte[] src = new ubyte[1000000];
+    ubyte[] dst;
+    ubyte[] result;
+
+    src[] = 0x80;
+    dst = compress(src);
+    assert(dst.length*2 + 1 < src.length);
+    result = cast(ubyte[]) uncompress(dst);
+    assert(result == src);
+}
+
+/+
+void arrayPrint(ubyte[] array)
+{
+    //printf("array %p,%d\n", cast(void*)array, array.length);
+    for (size_t i = 0; i < array.length; i++)
+    {
+        printf("%02x ", array[i]);
+        if (((i + 1) & 15) == 0)
+            printf("\n");
+    }
+    printf("\n\n");
+}
++/
+
+/// the header format the compressed stream is wrapped in
+enum HeaderFormat {
+    deflate, /// a standard zlib header
+    gzip, /// a gzip file format header
+    determineFromData /// used when decompressing. Try to automatically detect the stream format by looking at the data
+}
+
+/*********************************************
+ * Used when the data to be compressed is not all in one buffer.
+ */
+
+class Compress
+{
+    import std.conv: to;
+
+  private:
+    z_stream zs;
+    int level = Z_DEFAULT_COMPRESSION;
+    int inited;
+    immutable bool gzip;
+
+    void error(int err)
+    {
+        if (inited)
+        {   deflateEnd(&zs);
+            inited = 0;
+        }
+        throw new ZlibException(err);
+    }
+
+  public:
+
+    /**
+     * Constructor.
+     *
+     * Params:
+     *    level = compression level. Legal values are 1..9, with 1 being the least
+     *            compression and 9 being the most. The default value is 6.
+     *    header = sets the compression type to one of the options available
+     *             in $(LREF HeaderFormat). Defaults to HeaderFormat.deflate.
+     *
+     * See_Also:
+     *    $(LREF compress), $(LREF HeaderFormat)
+     */
+    this(int level, HeaderFormat header = HeaderFormat.deflate)
+    in
+    {
+        assert(1 <= level && level <= 9);
+    }
+    body
+    {
+        this.level = level;
+        this.gzip = header == HeaderFormat.gzip;
+    }
+
+    /// ditto
+    this(HeaderFormat header = HeaderFormat.deflate)
+    {
+        this.gzip = header == HeaderFormat.gzip;
+    }
+
+    ~this()
+    {   int err;
+
+        if (inited)
+        {
+            inited = 0;
+            deflateEnd(&zs);
+        }
+    }
+
+    /**
+     * Compress the data in buf and return the compressed data.
+     * Params:
+     *    buf = data to compress
+     *
+     * Returns:
+     *    the compressed data. The buffers returned from successive calls to this should be concatenated together.
+     *
+     */
+    const(void)[] compress(const(void)[] buf)
+    {   int err;
+        ubyte[] destbuf;
+
+        if (buf.length == 0)
+            return null;
+
+        if (!inited)
+        {
+            err = deflateInit2(&zs, level, Z_DEFLATED, 15 + (gzip ? 16 : 0), 8, Z_DEFAULT_STRATEGY);
+            if (err)
+                error(err);
+            inited = 1;
+        }
+
+        destbuf = new ubyte[zs.avail_in + buf.length];
+        zs.next_out = destbuf.ptr;
+        zs.avail_out = to!uint(destbuf.length);
+
+        if (zs.avail_in)
+            buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf;
+
+        zs.next_in = cast(typeof(zs.next_in)) buf.ptr;
+        zs.avail_in = to!uint(buf.length);
+
+        err = deflate(&zs, Z_NO_FLUSH);
+        if (err != Z_STREAM_END && err != Z_OK)
+        {   delete destbuf;
+            error(err);
+        }
+        destbuf.length = destbuf.length - zs.avail_out;
+        return destbuf;
+    }
+
+    /***
+     * Compress and return any remaining data.
+     * The returned data should be appended to that returned by compress().
+     * Params:
+     *  mode = one of the following:
+     *          $(DL
+                    $(DT Z_SYNC_FLUSH )
+                    $(DD Syncs up flushing to the next byte boundary.
+                        Used when more data is to be compressed later on.)
+                    $(DT Z_FULL_FLUSH )
+                    $(DD Syncs up flushing to the next byte boundary.
+                        Used when more data is to be compressed later on,
+                        and the decompressor needs to be restartable at this
+                        point.)
+                    $(DT Z_FINISH)
+                    $(DD (default) Used when finished compressing the data. )
+                )
+     */
+    void[] flush(int mode = Z_FINISH)
+    in
+    {
+        assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH);
+    }
+    body
+    {
+        ubyte[] destbuf;
+        ubyte[512] tmpbuf = void;
+        int err;
+
+        if (!inited)
+            return null;
+
+        /* may be  zs.avail_out+<some constant>
+         * zs.avail_out is set nonzero by deflate in previous compress()
+         */
+        //tmpbuf = new void[zs.avail_out];
+        zs.next_out = tmpbuf.ptr;
+        zs.avail_out = tmpbuf.length;
+
+        while( (err = deflate(&zs, mode)) != Z_STREAM_END)
+        {
+            if (err == Z_OK)
+            {
+                if (zs.avail_out != 0 && mode != Z_FINISH)
+                    break;
+                else if(zs.avail_out == 0)
+                {
+                    destbuf ~= tmpbuf;
+                    zs.next_out = tmpbuf.ptr;
+                    zs.avail_out = tmpbuf.length;
+                    continue;
+                }
+                err = Z_BUF_ERROR;
+            }
+            delete destbuf;
+            error(err);
+        }
+        destbuf ~= tmpbuf[0 .. (tmpbuf.length - zs.avail_out)];
+
+        if (mode == Z_FINISH)
+        {
+            err = deflateEnd(&zs);
+            inited = 0;
+            if (err)
+                error(err);
+        }
+        return destbuf;
+    }
+}
+
+/******
+ * Used when the data to be decompressed is not all in one buffer.
+ */
+
+class UnCompress
+{
+    import std.conv: to;
+
+  private:
+    z_stream zs;
+    int inited;
+    int done;
+    size_t destbufsize;
+
+    HeaderFormat format;
+
+    void error(int err)
+    {
+        if (inited)
+        {   inflateEnd(&zs);
+            inited = 0;
+        }
+        throw new ZlibException(err);
+    }
+
+  public:
+
+    /**
+     * Construct. destbufsize is the same as for D.zlib.uncompress().
+     */
+    this(uint destbufsize)
+    {
+        this.destbufsize = destbufsize;
+    }
+
+    /** ditto */
+    this(HeaderFormat format = HeaderFormat.determineFromData)
+    {
+        this.format = format;
+    }
+
+    ~this()
+    {   int err;
+
+        if (inited)
+        {
+            inited = 0;
+            inflateEnd(&zs);
+        }
+        done = 1;
+    }
+
+    /**
+     * Decompress the data in buf and return the decompressed data.
+     * The buffers returned from successive calls to this should be concatenated
+     * together.
+     */
+    const(void)[] uncompress(const(void)[] buf)
+    in
+    {
+        assert(!done);
+    }
+    body
+    {   int err;
+        ubyte[] destbuf;
+
+        if (buf.length == 0)
+            return null;
+
+        if (!inited)
+        {
+        int windowBits = 15;
+        if(format == HeaderFormat.gzip)
+            windowBits += 16;
+            else if(format == HeaderFormat.determineFromData)
+            windowBits += 32;
+
+            err = inflateInit2(&zs, windowBits);
+            if (err)
+                error(err);
+            inited = 1;
+        }
+
+        if (!destbufsize)
+            destbufsize = to!uint(buf.length) * 2;
+        destbuf = new ubyte[zs.avail_in * 2 + destbufsize];
+        zs.next_out = destbuf.ptr;
+        zs.avail_out = to!uint(destbuf.length);
+
+        if (zs.avail_in)
+            buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf;
+
+        zs.next_in = cast(ubyte*) buf.ptr;
+        zs.avail_in = to!uint(buf.length);
+
+        err = inflate(&zs, Z_NO_FLUSH);
+        if (err != Z_STREAM_END && err != Z_OK)
+        {   delete destbuf;
+            error(err);
+        }
+        destbuf.length = destbuf.length - zs.avail_out;
+        return destbuf;
+    }
+
+    /**
+     * Decompress and return any remaining data.
+     * The returned data should be appended to that returned by uncompress().
+     * The UnCompress object cannot be used further.
+     */
+    void[] flush()
+    in
+    {
+        assert(!done);
+    }
+    out
+    {
+        assert(done);
+    }
+    body
+    {
+        ubyte[] extra;
+        ubyte[] destbuf;
+        int err;
+
+        done = 1;
+        if (!inited)
+            return null;
+
+      L1:
+        destbuf = new ubyte[zs.avail_in * 2 + 100];
+        zs.next_out = destbuf.ptr;
+        zs.avail_out = to!uint(destbuf.length);
+
+        err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH);
+        if (err == Z_OK && zs.avail_out == 0)
+        {
+            extra ~= destbuf;
+            goto L1;
+        }
+        if (err != Z_STREAM_END)
+        {
+            delete destbuf;
+            if (err == Z_OK)
+                err = Z_BUF_ERROR;
+            error(err);
+        }
+        destbuf = destbuf.ptr[0 .. zs.next_out - destbuf.ptr];
+        err = etc.c.zlib.inflateEnd(&zs);
+        inited = 0;
+        if (err)
+            error(err);
+        if (extra.length)
+            destbuf = extra ~ destbuf;
+        return destbuf;
+    }
+}
+
+/* ========================== unittest ========================= */
+
+private import std.stdio;
+private import std.random;
+
+unittest // by Dave
+{
+    debug(zlib) writeln("std.zlib.unittest");
+
+    bool CompressThenUncompress (void[] src)
+    {
+        ubyte[] dst = std.zlib.compress(src);
+        double ratio = (dst.length / cast(double)src.length);
+        debug(zlib) writef("src.length: %1$d, dst: %2$d, Ratio = %3$f", src.length, dst.length, ratio);
+        ubyte[] uncompressedBuf;
+        uncompressedBuf = cast(ubyte[]) std.zlib.uncompress(dst);
+        assert(src.length == uncompressedBuf.length);
+        assert(src == uncompressedBuf);
+
+        return true;
+    }
+
+
+    // smallish buffers
+    for(int idx = 0; idx < 25; idx++) {
+        char[] buf = new char[uniform(0, 100)];
+
+        // Alternate between more & less compressible
+        foreach(ref char c; buf)
+            c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 2)));
+
+        if(CompressThenUncompress(buf)) {
+            debug(zlib) writeln("; Success.");
+        } else {
+            return;
+        }
+    }
+
+    // larger buffers
+    for(int idx = 0; idx < 25; idx++) {
+        char[] buf = new char[uniform(0, 1000/*0000*/)];
+
+        // Alternate between more & less compressible
+        foreach(ref char c; buf)
+            c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 10)));
+
+        if(CompressThenUncompress(buf)) {
+            debug(zlib) writefln("; Success.");
+        } else {
+            return;
+        }
+    }
+
+    debug(zlib) writefln("PASSED std.zlib.unittest");
+}
+
+
+unittest // by Artem Rebrov
+{
+    Compress cmp = new Compress;
+    UnCompress decmp = new UnCompress;
+
+    const(void)[] input;
+    input = "tesatdffadf";
+
+    const(void)[] buf = cmp.compress(input);
+    buf ~= cmp.flush();
+    const(void)[] output = decmp.uncompress(buf);
+
+    //writefln("input = '%s'", cast(char[])input);
+    //writefln("output = '%s'", cast(char[])output);
+    assert( output[] == input[] );
+}
+
diff --git a/libphobos/src/unittest.d b/libphobos/src/unittest.d
new file mode 100644
index 00000000000..7e85b592a4b
--- /dev/null
+++ b/libphobos/src/unittest.d
@@ -0,0 +1,129 @@
+// Written in the D programming language.
+
+/**
+ * This test program pulls in all the library modules in order to run the unit
+ * tests on them.  Then, it prints out the arguments passed to main().
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
+ * Authors:   $(WEB digitalmars.com, Walter Bright)
+ *
+ *          Copyright Digital Mars 2000 - 2009.
+ * Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+public import std.base64;
+public import std.compiler;
+public import std.concurrency;
+public import std.conv;
+public import std.container;
+public import std.cstream;
+public import std.datetime;
+public import std.demangle;
+public import std.file;
+public import std.format;
+public import std.getopt;
+public import std.math;
+public import std.mathspecial;
+public import std.mmfile;
+public import std.outbuffer;
+public import std.parallelism;
+public import std.path;
+public import std.process;
+public import std.random;
+public import std.regex;
+public import std.signals;
+//public import std.slist;
+public import std.socket;
+public import std.socketstream;
+public import std.stdint;
+public import std.stdio;
+public import std.stream;
+public import std.string;
+public import std.system;
+public import std.traits;
+public import std.typetuple;
+public import std.uni;
+public import std.uri;
+public import std.utf;
+public import std.uuid;
+public import std.variant;
+public import std.zip;
+public import std.zlib;
+public import std.net.isemail;
+//public import std.net.curl;
+public import std.digest.digest;
+public import std.digest.crc;
+public import std.digest.sha;
+public import std.digest.md;
+public import std.digest.hmac;
+
+int main(string[] args)
+{
+    // Bring in unit test for module by referencing function in it
+
+    cast(void)cmp("foo", "bar");                  // string
+    cast(void)filenameCharCmp('a', 'b');          // path
+    cast(void)isNaN(1.0);                         // math
+    std.conv.to!double("1.0");          // std.conv
+    OutBuffer b = new OutBuffer();      // outbuffer
+    auto r = regex("");                 // regex
+    uint ranseed = std.random.unpredictableSeed;
+    thisTid;
+    int[] a;
+    import std.algorithm : sort, reverse;
+    reverse(a);                         // adi
+    sort(a);                            // qsort
+    Clock.currTime();                   // datetime
+    Exception e = new ReadException(""); // stream
+    din.eof();                           // cstream
+    cast(void)isValidDchar(cast(dchar)0);          // utf
+    std.uri.ascii2hex(0);                // uri
+    std.zlib.adler32(0,null);            // D.zlib
+    auto t = task!cmp("foo", "bar");  // parallelism
+
+    creal c = 3.0 + 4.0i;
+    c = sqrt(c);
+    assert(c.re == 2);
+    assert(c.im == 1);
+
+    printf("args.length = %d\n", args.length);
+    for (int i = 0; i < args.length; i++)
+        printf("args[%d] = '%.*s'\n", i, args[i].length, args[i].ptr);
+
+    int[3] x;
+    x[0] = 3;
+    x[1] = 45;
+    x[2] = -1;
+    sort(x[]);
+    assert(x[0] == -1);
+    assert(x[1] == 3);
+    assert(x[2] == 45);
+
+    cast(void)std.math.sin(3.0);
+    cast(void)std.mathspecial.gamma(6.2);
+
+    std.demangle.demangle("hello");
+
+    cast(void)std.uni.isAlpha('A');
+
+    std.file.exists("foo");
+
+    foreach_reverse (dchar d; "hello"c) { }
+    foreach_reverse (k, dchar d; "hello"c) { }
+
+    std.signals.linkin();
+
+    bool isEmail = std.net.isemail.isEmail("abc");
+    //auto http = std.net.curl.HTTP("dlang.org");
+    auto uuid = randomUUID();
+
+    auto md5 = md5Of("hello");
+    auto sha1 = sha1Of("hello");
+    auto crc = crc32Of("hello");
+    auto string = toHexString(crc);
+    puts("Success!");
+    return 0;
+}
